From: Laszlo Boszormenyi (GCS) Date: Sun, 13 Sep 2026 06:42:13 +0000 (+0200) Subject: Import fuse3_3.18.3.orig.tar.gz X-Git-Tag: archive/raspbian/3.18.3-1+rpi1^2~2 X-Git-Url: https://dgit.raspbian.org/?a=commitdiff_plain;h=df1c5d7a101d4ae2229cdc221783784e1c1043fe;p=fuse3.git Import fuse3_3.18.3.orig.tar.gz [dgit import orig fuse3_3.18.3.orig.tar.gz] --- df1c5d7a101d4ae2229cdc221783784e1c1043fe diff --git a/.ackrc b/.ackrc new file mode 100644 index 0000000..c8f5d0a --- /dev/null +++ b/.ackrc @@ -0,0 +1,2 @@ +--ignore-dir=build +--ignore-dir=doc/html diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..3ebac7c --- /dev/null +++ b/.clang-format @@ -0,0 +1,119 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# clang-format configuration file. Intended for clang-format >= 11. +# +# For more information, see: +# +# Documentation/process/clang-format.rst +# https://clang.llvm.org/docs/ClangFormat.html +# https://clang.llvm.org/docs/ClangFormatStyleOptions.html +# +--- +AccessModifierOffset: -4 +AlignAfterOpenBracket: Align +AlignConsecutiveAssignments: false +AlignConsecutiveDeclarations: false +AlignEscapedNewlines: Left +AlignOperands: true +AlignTrailingComments: false +AllowAllParametersOfDeclarationOnNextLine: false +AllowShortBlocksOnASingleLine: false +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: None +AllowShortIfStatementsOnASingleLine: false +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: false +AlwaysBreakTemplateDeclarations: false +BinPackArguments: true +BinPackParameters: true +BraceWrapping: + AfterClass: false + AfterControlStatement: false + AfterEnum: false + AfterFunction: true + AfterNamespace: true + AfterObjCDeclaration: false + AfterStruct: false + AfterUnion: false + AfterExternBlock: false + BeforeCatch: false + BeforeElse: false + IndentBraces: false + SplitEmptyFunction: true + SplitEmptyRecord: true + SplitEmptyNamespace: true +BreakBeforeBinaryOperators: None +BreakBeforeBraces: Custom +BreakBeforeInheritanceComma: false +BreakBeforeTernaryOperators: false +BreakConstructorInitializersBeforeComma: false +BreakConstructorInitializers: BeforeComma +BreakAfterJavaFieldAnnotations: false +BreakStringLiterals: false +ColumnLimit: 80 +CommentPragmas: '^ IWYU pragma:' +CompactNamespaces: false +ConstructorInitializerAllOnOneLineOrOnePerLine: false +ConstructorInitializerIndentWidth: 8 +ContinuationIndentWidth: 8 +Cpp11BracedListStyle: false +DerivePointerAlignment: false +DisableFormat: false +ExperimentalAutoDetectBinPacking: false +FixNamespaceComments: false + +IncludeBlocks: Preserve +IncludeCategories: + - Regex: '.*' + Priority: 1 +IncludeIsMainRegex: '(Test)?$' +IndentCaseLabels: false +IndentGotoLabels: false +IndentPPDirectives: None +IndentWidth: 8 +IndentWrappedFunctionNames: false +JavaScriptQuotes: Leave +JavaScriptWrapImports: true +KeepEmptyLinesAtTheStartOfBlocks: false +MacroBlockBegin: '' +MacroBlockEnd: '' +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: None +ObjCBinPackProtocolList: Auto +ObjCBlockIndentWidth: 8 +ObjCSpaceAfterProperty: true +ObjCSpaceBeforeProtocolList: true + +# Taken from git's rules +PenaltyBreakAssignment: 10 +PenaltyBreakBeforeFirstCallParameter: 30 +PenaltyBreakComment: 10 +PenaltyBreakFirstLessLess: 0 +PenaltyBreakString: 10 +PenaltyExcessCharacter: 100 +PenaltyReturnTypeOnItsOwnLine: 60 + +PointerAlignment: Right +ReflowComments: false +SortIncludes: false +SortUsingDeclarations: false +SpaceAfterCStyleCast: false +SpaceAfterTemplateKeyword: true +SpaceBeforeAssignmentOperators: true +SpaceBeforeCtorInitializerColon: true +SpaceBeforeInheritanceColon: true +SpaceBeforeParens: ControlStatementsExceptForEachMacros +SpaceBeforeRangeBasedForLoopColon: true +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 1 +SpacesInAngles: false +SpacesInContainerLiterals: false +SpacesInCStyleCastParentheses: false +SpacesInParentheses: false +SpacesInSquareBrackets: false +Standard: Cpp03 +TabWidth: 8 +UseTab: Always +... diff --git a/.codespellrc b/.codespellrc new file mode 100644 index 0000000..cbd941f --- /dev/null +++ b/.codespellrc @@ -0,0 +1,13 @@ +[codespell] +skip = .git,*.pdf,*.svg,AUTHORS + +# The following strings shouldn't actually be accepted, but they're wrongly +# identified as words and there is currently no way to exclude them on +# a by-line basis (https://github.com/codespell-project/codespell/pull/2400). +# Therefore, pretend that they are correctly spelled words: +# - alse: used in regex +# - siz: wanted short +# - fiter: variable +# - re-used: intentional hyphenation +# - re-using: intentional hyphenation +ignore-words-list = alse,siz,fiter,re-used,re-using diff --git a/.dir-locals.el b/.dir-locals.el new file mode 100644 index 0000000..4616d40 --- /dev/null +++ b/.dir-locals.el @@ -0,0 +1,64 @@ +((python-mode . ((indent-tabs-mode . nil))) + (c++-mode . ((c-file-style . "linux-kernel") + (c-basic-offset . 8) + (c-label-minimum-indentation . 0) + (c-offsets-alist . ( + (arglist-close . c-lineup-arglist-tabs-only) + (arglist-cont-nonempty . + (c-lineup-gcc-asm-reg c-lineup-arglist-tabs-only)) + (arglist-intro . +) + (brace-list-intro . +) + (c . c-lineup-C-comments) + (case-label . 0) + (comment-intro . c-lineup-comment) + (cpp-define-intro . +) + (cpp-macro . -1000) + (cpp-macro-cont . +) + (defun-block-intro . +) + (else-clause . 0) + (func-decl-cont . +) + (inclass . +) + (inher-cont . c-lineup-multi-inher) + (knr-argdecl-intro . 0) + (label . -1000) + (statement . 0) + (statement-block-intro . +) + (statement-case-intro . +) + (statement-cont . +) + (substatement . +) + )) + (indent-tabs-mode . t) + (show-trailing-whitespace . t) + )) + (c-mode . ((c-file-style . "linux-kernel") + (c-basic-offset . 8) + (c-label-minimum-indentation . 0) + (c-offsets-alist . ( + (arglist-close . c-lineup-arglist-tabs-only) + (arglist-cont-nonempty . + (c-lineup-gcc-asm-reg c-lineup-arglist-tabs-only)) + (arglist-intro . +) + (brace-list-intro . +) + (c . c-lineup-C-comments) + (case-label . 0) + (comment-intro . c-lineup-comment) + (cpp-define-intro . +) + (cpp-macro . -1000) + (cpp-macro-cont . +) + (defun-block-intro . +) + (else-clause . 0) + (func-decl-cont . +) + (inclass . +) + (inher-cont . c-lineup-multi-inher) + (knr-argdecl-intro . 0) + (label . -1000) + (statement . 0) + (statement-block-intro . +) + (statement-case-intro . +) + (statement-cont . +) + (substatement . +) + )) + (indent-tabs-mode . t) + (show-trailing-whitespace . t) + )) +) diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 0000000..946beef --- /dev/null +++ b/AUTHORS @@ -0,0 +1,299 @@ +Current Maintainers +------------------ + +Bernd Schubert +Ashley Pittman +Antonio SJ Musumeci + + +Past Maintainers +---------------- + +Nikolaus Rath (until 02/2024) +Miklos Szeredi (until 12/2015) + + +Contributors +------------ + +CUSE has been written by Tejun Heo . Furthermore, the +following people have contributed patches (autogenerated list): + + +1c7718e7 +a1346054 <36859588+a1346054@users.noreply.github.com> +admorgan +Ahmed Masud +AKowshik +Alan Somers +Albert Chen <58009229+hselin-kalista-io@users.noreply.github.com> +Albert Chen +Aleksandr Mikhailov +Alexander +alex +Alex Richman +Amir Goldstein +amosonn +Anatol Pomozov +André Schröder +Andrew Gaul +Andrew Gaul +Angelo G. Del Regno +Anthony Rebello +Antonio SJ Musumeci +Arunav Sanyal +asafkahlon <35964924+asafkahlon@users.noreply.github.com> +Ashley Pittman +AsumFace +Banglang +Baptiste Daroussin +Benjamin Barenblat +Bernd Schubert +Bernd Schubert +Bill Zissimooulos +Bill Zissimopoulos +bobrofon +Brian Naylor +Carl Edquist +Carlos Maiolino +Chad Austin +Changli Gao +Christian Menges +Christopher Harrison +Ciaran +Consus +Craig Chi +Csaba Henk +Csaba Henk +cvs2git <> +Dalvik Khertel +Daniel Fullmer +Daniel Thau +David Galeano +David McNab +David Sheets +dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> +Dharmendra singh +Dharmendra Singh +divinity76 +DrDaveD <2129743+DrDaveD@users.noreply.github.com> +Dr. David Alan Gilbert +Emily Herbert +Emmanuel Dreyfus +Enke Chen +Eric Engestrom +Eric Wong +Etienne Dublé +Fabian Vogt +Fabrice Bauzac +Fabrice Fontaine +Fedor Korotkov +Feng Shuo +ferivoz <72023087+ferivoz@users.noreply.github.com> +Feverfew +Fina Wilke +Florian Weimer +Forty-Bot +Frank Dinoff +Giulio Benetti +Giuseppe Scrivano +Goswin von Brederlow +guraga +HazelFZ +Heiko Becker +Hendrik Brueckner +HereThereBeDragons +Hookey +human +ikbenlike +Ikey Doherty +itsdeepak +Jan Blumschein +Jann Horn +Jay Hankins +Jean-Pierre André +Jean-Yves VET +Jérémie Galarneau +Joachim Schiele +Joachim Schiele +Joerg Thalheim +John Baber-Lucero +John Muir +Joseph Dodge +Josh Soref +Junichi Uekawa +Junichi Uekawa +Junichi Uekawa +Kangjing "Chaser" Huang +Ken Schalk +Kevin Vigor +Kirill Smelkov +Kyle Lippincott +Laszlo Boszormenyi (GCS) +Laszlo Papp +Laurent Bigonville +Lilo Huang +Liu Bo +Li-Wen Hsu +lixiaokeng <63774002+lixiaokeng@users.noreply.github.com> +lixiaokeng +Luis Henriques +Madan Valluri +Manuel Jacob +Marcin Sulikowski +Mark Glines +Martin Blanchard +Martin Pärtel +Mateusz Urbańczyk +Matthias Goergens +Matthias Görgens +Mattias Nissler +maxice8 <30738253+maxice8@users.noreply.github.com> +Maximilian Heinzler +Max Krasnyansky +Michael Forney +Michael Grigoriev +Mihail Konev +Miklos Szeredi +Miklos Szeredi +Miklos Szeredi +Miklos Szeredi +Misono Tomohiro +mkmm@gmx-topmail.de +mrdvdrm +Natanael Copa +Niels de Vos +Nikola Petrov <73067824+Petrov22Nikola@users.noreply.github.com> +Nikolaus Rath +Nozomi Miyamori <99280467+nm004@users.noreply.github.com> +Oded Arbel +Olivier Blin +pablomh +Pedro Kaj Kjellerup Nacht +Pedro Nacht +Peri +Peter Lemenkov +philmd +Pierre Labastie +Przemyslaw Pawelczyk +Przemysław Pawełczyk +psykose +Ratna_Bolla@dell.com +Rethan <359062468@qq.con> +Reuben Hawkins +rfjakob +richardweinberger +Richard W.M. Jones +Riku Voipio +Robo Shimmer +Roland Bauerschmidt +Roman Bogorodskiy +Rosen Penev +Rostislav +Rostislav Skudnov +Rudi Heitbaum +Sam Huffman <40582525+samh-sifive@users.noreply.github.com> +Sam James +Sam Stuewe +Sangwoo Moon +Sarath Lakshman +Sargun Dhillon +scosu +Scott Worley +Sebastian Pipping +Sergey Fedoseev +Seunghoon Yeon +Sławek Rudnicki +Stefan Hajnoczi +Stefan Hajnoczi +Stephen Kitt +Tej Chajed +tenzap <46226844+tenzap@users.noreply.github.com> +therealneworld@gmail.com +Tobias Nießen +Tofik Sonono +Tomasz Kulasek <34129113+tkulasek@users.noreply.github.com> +Tom Callaway +Tom Callaway +Tomohiro Kusumi +userwithuid +Valentin Plugaru +Vivek Goyal +Waldir Pimenta +wdlkmpx +William Woodruff +Winfried Koehler +winndows +Xiubo Li +Yaroslav Halchenko +y +Yuri Per +Zhansong Gao +Zhiqiang Liu +zsugabubus + +# New authors since fuse-3.16.2 +farlongsignal <141166749+farlongsignal@users.noreply.github.com> +yangyun50 <149988609+yangyun50@users.noreply.github.com> +bigbrotherwei <1965867461@qq.com> +Caian Benedicto <2220062+Caian@users.noreply.github.com> +desertwitch <24509509+desertwitch@users.noreply.github.com> +SteveYang <40466358+SteveY4ng@users.noreply.github.com> +FredyVia <942513309@qq.com> +legezywzh <94814730+legezywzh@users.noreply.github.com> +CismonX +amitgeron +Bernd Schubert +Daniel Rosenberg +Horst Birthelmer +Joanne Koong +Josef Bacik +Matthew +gandalfs_cat +MJ Harvey +Nils +Norman Wilson +leipeng +Vladimir Serbinenko +George Hilliard +Tyler Hall +yangyun +Abhishek + +# New authors since fuse-3.17.1 +Luis Henriques +Zegang +swj <1186093704@qq.com> +Gleb Popov <6yearold@gmail.com> +WekaJosh <80121792+WekaJosh@users.noreply.github.com> +Alexander Monakov +Ben Dooks +Ben Linsay +Dave Vasilevsky +Darrick J. Wong +Georgi Valkov +Alik Aslanyan +jnr0006 +Jingbo Xu +Long Li +Maksim Harbachou +Meng Lu Wang +Vassili Tchersky +Vassili Tchersky +izxl007 +Zeno Sebastian Endemann + +# New authors since fuse-3.18.1 +Abhinav Agarwal + +# New authors since fuse-3.18.2 +Robert Doebbelin +Ashish Ahuja +Benjamin Peterson +wilkis +Jan Engelhardt +Alexander Mikhalitsyn +wangrzneu +nikola-sh +Mikhail Novosyolov +Keerthana KT diff --git a/ChangeLog.rst b/ChangeLog.rst new file mode 100644 index 0000000..dabe131 --- /dev/null +++ b/ChangeLog.rst @@ -0,0 +1,1064 @@ +libfuse 3.18.3 (2026-09-09) +=========================== + +Security Fixes +-------------- + +* ``fuse_session_custom_io()`` is disabled unless libfuse is built with + ``-Denable-custom-io=true``, and returns ``-ENOTSUP`` otherwise. The + ``hello_ll_uds`` example is built only with that option, and enabling it + warns at configure time. + Reason is a custom io peer might not be a kernel and can + forge requests that libfuse parses without bounds checks, crashing or + corrupting the filesystem process. See ``doc/README.custom-io``. + +* fusermount3: resolve the mountpoint once, through an ``O_PATH|O_NOFOLLOW`` + descriptor. A symlink swapped in between the type check and the second + lookup could redirect the mount. +* fusermount3: run the auto-unmount probe as the calling user. It opened the + caller-supplied mountpoint with elevated privileges before, so a symlink + could get an attacker-chosen path opened as root. +* mount_util: terminate the ``/bin/mount`` and ``/bin/umount`` argument + vectors with ``--``. ``fsname`` reaches them straight from ``-ofsname=`` in + setuid-root fusermount3, and the child raises the real uid to 0 before + ``execle()``, so an unprivileged caller controlled a positional operand of a + mount(8) that was not in restricted mode. +* mount_util: skip the mtab update entirely for option-like mount arguments. + BusyBox mount(8) does not honour ``--``. +* fusermount3: unmount through ``unmount_fuse()`` when passing the device + descriptor to the caller fails, so that path drops privileges and runs the + ``fusermount3 -u`` checks instead of calling ``umount2()`` as root on a + caller-supplied path. +* fusermount3 and lib: pass ``UMOUNT_NOFOLLOW`` on the kernel and non-setuid + unmount paths. +* fusermount3: check the ``fstat()`` return value when validating the + communication file descriptor. +* fusermount3: fix an out-of-bounds read at index -1 in ``get_mnt_opts()`` + when the option string is empty, which a read-only mount with no further + options reaches. +* util: avoid a pointer underflow when trimming ``fuse.conf`` lines. +* fusermount3: reject a negative ``mount_max`` other than the documented -1. + A typo such as -2 made the limit comparison always true and blocked every + non-root mount. +* lib: relay the KILLPRIV_V2 kill-suidgid flags to the filesystem in the new + ``fuse_file_info::kill_suidgid``. Only ``setattr`` saw them before, so a + filesystem that had taken over clearing suid/sgid never learned of it on + ``O_TRUNC`` open and on write, and the bits survived. + +Note: ``fuse_file_info::kill_suidgid`` is new in 3.18.3 and ``FUSE_VERSION`` +carries no patch level. A filesystem built against these headers but running +against an older 3.18 library finds the field permanently zero, so require +3.18.3 at run time as well. + +Important Fixes +--------------- + +* Fixed a hang on ``statx`` in builds without ``HAVE_STATX``: ``_do_statx()`` + never replied, so the kernel waited forever. +* io-uring: the CQE dispatch validated the opcode against ``fuse_ll_ops[]`` + but called through ``fuse_ll_ops2[]``, so an opcode with no handler there + was called as a null function pointer. +* io-uring: fixed the notify-retrieve reply buffer handling. The + ``fuse_notify_retrieve_in`` header sits at the start of the payload buffer, + not in the ring header. +* io-uring: fixed the ``req_header_sz`` calculation, which sized the header + buffer from the wrong struct. +* io-uring: create the rings with ``IORING_SETUP_SUBMIT_ALL``, so one failing + commit SQE no longer leaves the rest of the batch unsubmitted. +* io-uring: ``fuse_reply_none()`` commits the ring entry. A FORGET answered + that way leaked the entry and left the kernel-side request outstanding. +* fusermount3: treat ``ECONNABORTED`` like ``ENOTCONN`` when deciding whether + to auto-unmount, so a daemon that dies with io-uring registered no longer + leaves the mount behind. +* ``receive_fd()``: check ``CMSG_FIRSTHDR()`` for NULL before dereferencing it. +* ``fuse_session_loop_mt_312()`` no longer destroys ``se->mt_lock`` before + ``fuse_session_destroy()`` destroys it again, which was undefined behaviour + on every multi-threaded shutdown. +* ``fuse_loop_cfg_create()`` returning NULL is checked before the config is + dereferenced in ``fuse_session_loop_mt_312()`` and + ``fuse_session_loop_mt_31()``. +* iconv: the error check after opening the ``fromfs`` descriptor tested + ``tofs``, so a failed ``iconv_open()`` was ignored and left an invalid + descriptor behind. +* mount.fuse: a failure to clear ``FD_CLOEXEC`` went undetected, because the + result was compared against 1 rather than -1. +* ``grow_pipe_to_max()`` opens ``/proc/sys/fs/pipe-max-size`` with + ``O_CLOEXEC``; a concurrent fork+exec leaked the descriptor into the child. +* Fixed a build failure for ``FUSE_USE_VERSION`` 312 and newer without symbol + versioning, where ``fuse_loop_mt()`` expanded to an undeclared + ``fuse_loop_mt_312()``. +* Fixed a Clang 21 build failure in ``ST_MTIM_NSEC``. +* Fixed leaks: the pipe when its size cannot be grown, the mountpoint in + ``fuse_session_mount()`` and ``fuse_session_destroy()``, the pipe + descriptors when ``fork()`` or ``setsid()`` fail in ``fuse_daemonize()``, + the context when ``pthread_setspecific()`` fails, ``print_module_help()``, + and a ``fuse_pollhandle`` in ``fuse_lib_poll()``. +* Examples: ``update_fs()`` uses ``localtime_r()``. ``localtime()`` returns a + shared static ``struct tm``, so it raced with the session threads and + ``strftime()`` could format a half-overwritten time. +* Examples: ``cuse_client`` caps the transfer size at 16 MiB. ``do_rw()`` + passed the SIZE argument straight to ``calloc()``. +* Examples: memfs_ll locking, refcounting and bounds fixes, including a + use-after-free on rename overwrite and on a concurrent forget. +* Examples: passthrough_hp lock-order and lifetime fixes. The directory + stream is protected by a per-handle lock, ``fs.mutex`` is taken before + ``Inode::m`` and when ``link()`` raises nlookup, and no inode lock is held + across a syscall or a reply. + +Documentation +------------- + +* The fuse-devel mailing list moved to lists.linux.dev. +* Man page and README corrections. + + +libfuse 3.18.2 (2026-03-18) +=========================== +* Fix two io-uring issues that might be security critical + * fuse-io-uring: Fix UAF and NULL deref in startup error path + * fuse-io-uring: Fix NULL deref and memory leak in fuse_uring_init_queue + +libfuse 3.18.1 (2025-12-20) +=========================== +* Fix a critical ABI issue compared to libfuse-3.17.3+ +* Note: This breaks ABI compatibility to libfuse-3.18.0 + (given that 3.18.0 is out for 2 days only, probably the lesser evil) + +libfuse 3.18.0 (2025-12-18) +=========================== + +New Features +------------ + +* fuse-over-io-uring communication +* statx support +* Request timeouts: Prevent hung operations +* FUSE_NOTIFY_INC_EPOCH: New notification mechanism for epoch counters + +Important Fixes +---------------- + +* Fixed double unmount on FUSE_DESTROY +* Fixed junk readdirplus results when filesystem doesn't fill stat info +* Fixed memory deallocation in fuse_session_loop_remember +* Fixed COPY_FILE_RANGE interface + +Platform Support +---------------- + +* Improved FreeBSD support (mount error reporting, test runner, build fixes) +* Fixed 32-bit architecture builds +* Fixed build with musl libc and older kernels (< 5.9) + +Other Improvements +------------------ + +* Added PanFS to fusermount whitelist +* Thread naming support for easier debugging + + +libfuse 3.17.4 (2025-08-19) +=========================== +- Try to detect mount-utils by checking for /run/mount/utab + and don't try to update mtab if it does not exist +- Fix a build warning when HAVE_BACKTRACE is undefined +- fuse_loop_mt.c: fix close-on-exec flag on clone fd +- Remove struct size assertions from fuse_common.h + +libfuse 3.17.3 (2025-07-16) +=========================== +* more conn->want / conn->want_ext conversion fixes +* Fix feature detection for close_range +* Avoid double unmount on FUSE_DESTROY + +libfuse 3.17.2 (2025-04-23) +=========================== +* Fixed uninitized bufsize value (compilation warning and real + issue when HAVE_SPLICE was not defined) +* Fixed initialization races related to buffer realocation when + large buf sizes are used (/proc/sys/fs/fuse/max_pages_limit) +* Fix build with kernel < 5.9 +* Fix static_assert build failure with C++ version < 11 +* Compilation fix (remove second fuse_main_real_versioned declaration) +* Another conn.want flag conversion fix for high-level applications +* Check if pthread_setname_np() exists before use it +* fix example/memfs_ll rename deadlock error +* signal handlers: Store fuse_session unconditionally and restore + previous behavior that with multiple sessions the last session + was used for the signal exist handler + +libfuse 3.17.1 (2025-03-24) +=========================== +* fuse: Fix want conn.want flag conversion +* Prevent re-usage of stdio FDs for fusermount +* PanFS added to fusermount whitelist + +libfuse 3.17.1-rc1 (2025-02-18) +=============================== +* several BSD fixes +* x86 (32bit) build fixes +* nested declarations moved out of the inlined functions to avoid + build warnings +* signify public key added for future 3.18 + +libfuse 3.17.1-rc0 (2025-02.10) +=============================== + +* Fix libfuse build with FUSE_USE_VERSION 30 +* Fix build of memfs_ll without manual meson reconfigure +* Fix junk readdirplus results when filesystem not filling stat info +* Fix conn.want_ext truncation to 32bit +* Fix some build warnings with -Og +* Fix fuse_main_real symbols +* Several changes related to functions/symbols that added in + the libfuse version in 3.17 +* Add thread names to libfuse threads +* With auto-umounts the FUSE_COMMFD2 (parent process fd is + exported to be able to silence leak checkers + + +libfuse 3.17 (2025-01-01, not officially releaesed) +================================================== + +* 3.11 and 3.14.2 introduced ABI incompatibilities, the ABI is restored + to 3.10, .so version was increased since there were releases with + the incompatible ABI + +* The libfuse version a program was compiled against is now encoded into + that program, using inlined functions in fuse_lowlevel.h and fuse.h +* Allows to handle fatal signals and to print a backtrace. + New API function: fuse_set_fail_signal_handlers() + +* Allows fuse_log() messages to be send to syslog instead of stderr + New API functions: fuse_log_enable_syslog() and fuse_log_close_syslog() + +* Handle buffer misalignment for FUSE_WRITE + +* Added support for filesystem passthrough read/write of files when + FUSE_PASSTHROUGH capability is enabled + New API functions: fuse_passthrough_open() and fuse_passthrough_close(), + also see example/passthrough_hp.cc + +* Added fmask and dmask options to high-level API + - dmask: umask applied to directories + - fmask: umask applied to non-directories + +* Added FUSE_FILL_DIR_DEFAULTS enum to support C++ programs using + fuse_fill_dir_t function + +* Added support for FUSE_CAP_HANDLE_KILLPRIV_V2 + +Fixes: +* Fixed compilation failure on FreeBSD (mount_bsd.c now points to correct + header) + +libfuse 3.16.2 (2023-10-10) +=========================== + +* Various small fixes and improvements. + +libfuse 3.16.1 (2023-08-08) +=========================== + +* Readdir kernel cache can be enabled from high-level API. + +libfuse 3.15.1 (2023-07-05) +=========================== + +Future libfuse releases will be signed with `signify`_ rather than PGP (rationale_). This +release is the last to be signed with PGP and contains the signify public key for current +(3.15.X) and upcoming (3.16.X) minor release cycle. + +.. _signify: https://www.openbsd.org/papers/bsdcan-signify.html +.. _rationale: https://latacora.micro.blog/2019/07/16/the-pgp-problem.html + + +libfuse 3.15.0 (2023-06-09) +=========================== + +* Improved support for some less common systems (32 bit, alternative libcs) + +* Unsupported mount options are no longer silently accepted. + +* auto_unmount is now compatible with allow_other. + + +libfuse 3.14.1 (2023-03-26) +=========================== + +* The extended attribute name passed to the setxattr() handler is no longer + truncated at the beginning (bug introduced in 3.13.0). + +* As a result of the above, the additional setattr() flags introduced in 3.14 are no + longer available for now. They will hopefully be reintroduced in the next release. + +* Further improvements of configuration header handling. + + +libfuse 3.14.0 (2023-02-17) +=========================== + +* Properly fix the header installation issue. The fix in 3.13.1 resulted + in conflicts with other packages. + +* Introduce additional setattr() flags (FORCE, KILL_SUID, KILL_SGID, FILE, KILL_PRIV, + OPEN, TIMES_SET) + + +libfuse 3.13.1 (2023-02-03) +=========================== + +* Fixed an issue that resulted in errors when attempting to compile against + installed libfuse headers (because libc symbol versioning support was not + detected correctly in this case). + +libfuse 3.13.0 (2023-01-13) +=========================== + +* There is a new low-level API function `fuse_session_custom_io` that allows to implement + a daemon with a custom io. This can be used to create a daemon that can process incoming + FUSE requests to other destinations than `/dev/fuse`. + +* A segfault when loading custom FUSE modules has been fixed. + +* There is a new `fuse_notify_expire_entry` function. + +* A deadlock when resolving paths in the high-level API has been fixed. + +* libfuse can now be build explicitly for C libraries without symbol versioning support. + +libfuse 3.12.0 (2022-09-08) +=========================== + +* There is a new build parameter to specify where the SysV init script should be + installed. + +* The *max_idle_threads* parameter has been deprecated in favor of the new max_threads* + parameter (which avoids the excessive overhead of creating and destructing threads). + Using max_threads == 1 and calling fuse_session_loop_mt() will run single threaded + similar to fuse_session_loop(). + +The following changes apply when using the most recent API (-DFUSE_USE_VERSION=312, +see `example/passthrough_hp.cc` for an example for how to usse the new API): + +* `struct fuse_loop_config` is now private and has to be constructed using + *fuse_loop_cfg_create()* and destroyed with *fuse_loop_cfg_destroy()*. Parameters can be + changed using `fuse_loop_cfg_set_*()` functions. + +* *fuse_session_loop_mt()* now accepts `struct fuse_loop_config *` as NULL pointer. + +* *fuse_parse_cmdline()* now accepts a *max_threads* option. + + +libfuse 3.11.0 (2022-05-02) +=========================== + +* Add support for flag FOPEN_NOFLUSH for avoiding flush on close. +* Fixed returning an error condition to ioctl(2) + + +libfuse 3.10.5 (2021-09-06) +=========================== + +* Various improvements to make unit tests more robust. + + +libfuse 3.10.4 (2021-06-09) +=========================== + +* Building of unit tests is now optional. +* Fixed a test failure when running tests under XFS. +* Fixed memory leaks in examples. +* Minor documentation fixes. + +libfuse 3.10.3 (2021-04-12) +=========================== + +* Fix returning d_ino and d_type from readdir(3) in non-plus mode + +libfuse 3.10.2 (2021-02-05) +=========================== + +* Allow "nonempty" as a mount option, for backwards compatibility with fusermount 2. The + option has no effect since mounting over non-empty directories is allowed by default. +* Fix returning inode numbers from readdir() in offset==0 mode. +* FUSE filesystems can now be mounted underneath EXFAT mountpoints. +* Various minor bugfixes. + +libfuse 3.10.1 (2020-12-07) +=========================== + +* Various minor bugfixes. + +libfuse 3.10.0 (2020-10-09) +=========================== + +* Add FUSE_CAP_CACHE_SYMLINKS: allow caching symlinks in kernel page cache. +* Various minor bugfixes and improvements. + +libfuse 3.9.4 (2020-08-09) +========================== + +This was an "accidental" release, it is equivalent to 3.9.3. + +libfuse 3.9.3 (2020-08-09) +========================== + +* Fixed compilation under OS X and µClibc. +* Minor bugfixes and doc updates. + +libfuse 3.9.2 (2020-06-12) +========================== + +* Remove obsolete workarounds in examples. +* Do not require C++ compiler for building. +* Minor bugfixes. + +libfuse 3.9.1 (2020-03-19) +=========================== + +* Fixed memory leak in fuse_session_new(). +* Fixed an issue with the linker version script. +* Make ioctl prototype conditional on FUSE_USE_VERSION. Define FUSE_USE_VERSION < 35 to + get old ioctl prototype with int commands; define FUSE_USE_VERSION >= 35 to get new + ioctl prototype with unsigned int commands. +* Various small bugfixes. + +libfuse 3.9.0 (2019-12-14) +========================== + +* Added support for FUSE_EXPLICIT_INVAL_DATA to enable + only invalidate cached pages on explicit request. + +libfuse 3.8.0 (2019-11-03) +========================== + +* Added support for FUSE_LSEEK operation which can be used to report holes + in sparse files. + +libfuse 3.7.0 (2019-09-27) +========================== + +* Added UFSD to whitelist (so users can now mount FUSE filesystems + on mountpoints within UFSD filesystems). +* Added custom log message handler function support so that libfuse + applications can direct messages to syslog(3) or other logging systems. + stderr remains the default. See `fuse_log.h` for the new API. + +libfuse 3.6.2 (2019-07-09) +========================== + +* The init script is now installed to /etc/ rather than /usr/local/etc + by default. + +libfuse 3.6.1 (2019-06-13) +========================== + +* Fixed version number (release 3.6.0 was shipped with a declared + version of 3.0.0). + +libfuse 3.6.0 (2019-06-13) +========================== + +* Added a new example (passthrough_hp). The functionality is similar + to passthrough_ll, but the implementation focuses on performance and + correctness rather than simplicity. +* Added support for fuse kernel feature `max_pages` which allows to increase + the maximum number of pages that can be used per request. This feature was + introduced in kernel 4.20. `max_pages` is set based on the value in + `max_write`. By default `max_write` will be 1MiB now for kernels that support + `max_pages`. If you want smaller buffers or writes you have to set + `max_write` manually. + +libfuse 3.5.0 (2019-04-16) +========================== + +* Changed ioctl commands to "unsigned int" in order to support commands + which do not fit into a signed int. Commands issued by applications + are still truncated to 32 bits. +* Added SMB2 to whitelist (so users can now mount FUSE filesystems + on mountpoints within SMB 2.0 filesystems). +* Added a new `cache_readdir` flag to `fuse_file_info` to enable + caching of readdir results. Supported by kernels 4.20 and newer. +* Add support and documentation for FUSE_CAP_NO_OPENDIR_SUPPORT. + +libfuse 3.4.2 (2019-03-09) +========================== + +* Fixed a memory leak in `examples/passthrough_ll.c`. +* Added OpenAFS to whitelist (so users can now mount FUSE filesystems + on mountpoints within OpenAFS filesystems). +* Added HFS+ to whitelist (so users can now mount FUSE filesystems + on mountpoints within HFS+ filesystems). +* Documentation improvements. + +libfuse 3.4.1 (2018-12-22) +========================== + +* The `examples/passthrough_ll.c` example filesystem has been + significantly extended. +* Support for `copy_file_range` has been added. +* Build system updates for non-Linux systems. + +libfuse 3.4.0 +============= + +* Add `copy_file_range()` to support efficient copying of data from one file to + an other. + +libfuse 3.3.0 (2018-11-06) +========================== + +* The `auto_unmount` mode now works correctly in combination with + autofs. + +* The FUSE_CAP_READDIRPLUS_AUTO capability is no longer enabled by + default unless the file system defines both a readdir() and a + readdirplus() handler. + +* The description of the FUSE_CAP_READDIRPLUS_AUTO flag has been + improved. + +* Allow open `/dev/fuse` file descriptors to be passed via mountpoints of the + special format `/dev/fd/%u`. This allows mounting to be handled by the parent + so the FUSE filesystem process can run fully unprivileged. + +* Add a `drop_privileges` option to mount.fuse3 which causes it to open + `/dev/fuse` and mount the file system itself, then run the FUSE file + filesystem fully unprivileged and unable to re-acquire privilege via setuid, + fscaps, etc. + +* Documented under which conditions the `fuse_lowlevel_notify_*` + functions may block. + +libfuse 3.2.6 (2018-08-31) +========================== + +* The fuse_main() function now returns more fine-grained error codes. +* FUSE filesystems may now be mounted on mountpoint within + bcachefs, aufs and FAT filesystems. +* libfuse may now be used as a Meson subproject. +* Fix a few low-impact memory leaks. +* The `fuse.conf` file is no longer looked for in `/etc`, but in the + *sysconfdir* directory (which can be set with `meson configure`). By + default, the location is thus `/usr/local/etc/fuse.conf`. + +libfuse 3.2.5 (2018-07-24) +========================== + +* SECURITY UPDATE: In previous versions of libfuse it was possible to + for unprivileged users to specify the `allow_other` option even when + this was forbidden in `/etc/fuse.conf`. The vulnerability is + present only on systems where SELinux is active (including in + permissive mode). +* The fusermount binary has been hardened in several ways to reduce + potential attack surface. Most importantly, mountpoints and mount + options must now match a hard-coded whitelist. It is expected that + this whitelist covers all regular use-cases. +* Added a test of `seekdir` to test_syscalls. +* Fixed `readdir` bug when non-zero offsets are given to filler and the + filesystem client, after reading a whole directory, re-reads it from a + non-zero offset e. g. by calling `seekdir` followed by `readdir`. + +libfuse 3.2.4 (2018-07-11) +========================== + +* Fixed `rename` deadlock on FreeBSD. + +libfuse 3.2.3 (2018-05-11) +========================== + +* Fixed a number of compiler warnings. + +libfuse 3.2.2 (2018-03-31) +========================== + +* Added example fuse.conf file. +* Added "support" for -o nofail mount option (the option is accepted + and ignored). +* Various small bugfixes. + +libfuse 3.2.1 (2017-11-14) +========================== + +* Various small bugfixes. + +libfuse 3.2.0 (2017-09-12) +========================== + +* Support for building with autotools has been dropped. + +* Added new `fuse_invalidate_path()` routine for cache invalidation + from the high-level FUSE API, along with an example and tests. + +* There's a new `printcap` example that can be used to determine the + capabilities of the running kernel. + +* `fuse_loop_mt()` now returns the minus the actual errno if there was + an error (instead of just -1). + +* `fuse_loop()` no longer returns a positive value if the filesystem + loop was terminated without errors or signals. + +* Improved documentation of `fuse_lowlevel_notify_*` functions. + +* `fuse_lowlevel_notify_inval_inode()` and + `fuse_lowlevel_notify_inval_entry()` now return -ENOSYS instead of + an undefined error if the function is not supported by the kernel. + +* Documented the special meaning of the *zero* offset for the + fuse_fill_dir_t function. + +* The `passthrough_fh` example now works under FreeBSD. + +* libfuse can now be build without libiconv. + +* Fixed support for `FUSE_CAP_POSIX_ACL`: setting this capability + flag had no effect in the previous versions of libfuse 3.x; + now ACLs should actually work. + +* Fixed a number of compilation problems under FreeBSD. + +* Fixed installation directory for udev rules. + +* Fixed compilation with LTO. + +libfuse 3.1.1 (2017-08-06) +========================== + +* Documentation: clarified how filesystems are supposed to process + open() and create() flags (see include/fuse_lowlevel.h). + +* Fixed a compilation problem of the passthrough_ll example on + 32 bit systems (wrong check and wrong error message). + +* pkg-config is now used to determine the proper directory for + udev rules. + +* Fixed a symbol versioning problem that resulted in very strange + failures (segfaults, unexpected behavior) in different situations. + +* Fixed a test failure when /tmp is on btrfs. + +* The maximum number of idle worker threads used by `fuse_loop_mt()` + is now configurable. + +* `fuse_loop_mt()` and `fuse_session_loop_mt()` now take a + `struct fuse_loop_config` parameter that supersedes the *clone_fd* + parameter. + +* Incorporated several patches from the FreeBSD port. libfuse should + now compile under FreeBSD without the need for patches. + +* The passthrough_ll example now supports writeback caching. + +libfuse 3.1.0 (2017-07-08) +========================== + +* Added new `fuse_lib_help()` function. File-systems that previously + passed a ``--help`` option to `fuse_new()` must now process the + ``--help`` option internally and call `fuse_lib_help()` to print the + help for generic FUSE options. +* Fixed description of the `fuse_conn_info->time_gran`. The default + value of zero actually corresponds to full nanosecond resolution, + not one second resolution. +* The init script is now installed into the right location + (``$DESTDIR/etc/init.d`` rather than ``$prefix/$sysconfdir/init.d``) +* The `example/passthrough_ll` filesystem now supports creating + and writing to files. +* `fuse_main()` / `fuse_remove_signal_handlers()`: do not reset + `SIGPIPE` handler to `SIG_DFL` if it was not set by us. +* Documented the `RENAME_EXCHANGE` and `RENAME_NOREPLACE` flags that + may be passed to the `rename` handler of both the high- and + low-level API. Filesystem authors are strongly encouraged to check + that these flags are handled correctly. + +libfuse 3.0.2 (2017-05-24) +========================== + +* Option parsing for the high-level API now works correctly + (previously, default values would override specified values). +* Tests should now build (and run) under FreeBSD. +* Improved documentation of `struct fuse_context` +* Internal: calculate request buffer size from page size and kernel + page limit instead of using hardcoded 128 kB limit. + + +libfuse 3.0.1 (2017-04-10) +========================== + +* Re-introduced *examples/null.c*. +* Added experimental support for building with Meson. +* Document that `-o auto_unmount` implies `-o nodev,nosuid`. +* Document that the *use_ino* option of the high-level interface does + not affect the inode that libfuse and the kernel use internally. +* Fixed test cases for passthrough* examples (they weren't actually + testing the examples). +* Fixed several bugs in the passthrough* examples. + +libfuse 3.0.0 (2016-12-08) +========================== + +* NOTE TO PACKAGERS: + + libfuse 3 is designed to be co-installable with libfuse 2. However, + some files will be installed by both libfuse 2 and libfuse 3 + (e.g. /etc/fuse.conf, the udev and init scripts, and the + mount.fuse(8) manpage). These files should be taken from + libfuse 3. The format/content is guaranteed to remain backwards + compatible with libfuse 2. + + We recommend to ship libfuse2 and libfuse3 in three separate + packages: a libfuse-common package that contains files shared by + libfuse 2+3 (taken from the libfuse3 tarball), and libfuse2 and + libfuse3 packages that contain the shared library and helper + programs for the respective version. + +* Fixed test errors when running tests as root. + +* Made check for util-linux version more robust. + +* Added documentation for all fuse capability flags (`FUSE_CAP_*`) and + `struct fuse_conn_info` fields. + +* fuse_loop(), fuse_loop_mt(), fuse_session_loop() and + fuse_session_loop_mt() now return more detailed error codes instead + of just -1. See the documentation of fuse_session_loop() for details. + +* The FUSE main loop is now aborted if the file-system requests + capabilities that are not supported by the kernel. In this case, the + session loop is exited with a return code of ``-EPROTO``. + +* Most file-system capabilities that were opt-in in libfuse2 are now + enabled by default. Filesystem developers are encouraged to review + the documentation of the FUSE_CAP_* features to ensure that their + filesystem is compatible with the new semantics. As before, a + particular capability can still be disabled by unsetting the + corresponding bit of `fuse_conn_info.wants` in the init() handler. + +* Added FUSE_CAP_PARALLEL_DIROPS and FUSE_CAP_POSIX_ACL, + FUSE_HANDLE_KILLPRIV feature flags. + +* FUSE filesystems are now responsible for unsetting the setuid/setgid + flags when a file is written, truncated, or its owner + changed. Previously, this was handled by the kernel but subject to + race conditions. + +* The fusermount and mount.fuse binaries have been renamed to + fusermount3 and mount.fuse3 to allow co-installation of libfuse 2.x + and 3.x + +* Added a `max_read` field to `struct fuse_conn_info`. For the time + being, the maximum size of read requests has to be specified both + there *and* passed to fuse_session_new() using the ``-o + max_read=`` mount option. At some point in the future, specifying + the mount option will no longer be necessary. + +* Documentation: clarified that the fuse_argv structure that is passed + to `fuse_new()` and `fuse_lowlevel_new()` must always contain at + least one element. + +* The high-level init() handler now receives an additional struct + fuse_config pointer that can be used to adjust high-level API + specific configuration options. + +* The `nopath_flag` field of struct fuse_operations has been + removed. Instead, a new `nullpath_ok` flag can now be set + in struct fuse_config. + +* File systems that use the low-level API and support lookup requests + for '.' and '..' should continue make sure to set the + FUSE_CAP_EXPORT_SUPPORT bit in fuse_conn_info->want. + + (This has actually always been the case, but was not very obvious + from the documentation). + +* The help text generated by fuse_lowlevel_help(), fuse_new() (and + indirectly fuse_main()) no longer includes options that are unlikely + to be of interest to end-users. The full list of accepted options is + now included in the respective function's documentation (located in + the fuse.h/fuse_lowlevel.h and doc/html). + +* The ``-o nopath`` option has been dropped - it never actually did + anything (since it is unconditionally overwritten with the value of + the `nopath` flag in `struct fuse_operations`). + +* The ``-o large_read`` mount option has been dropped. Hopefully no + one uses a Linux 2.4 kernel anymore. + +* The `-o nonempty` mount point has been removed, mounting over + non-empty directories is now always allowed. This brings the + behavior of FUSE file systems in-line with the behavior of the + regular `mount` command. + + File systems that do not want to allow mounting to non-empty + directories should perform this check themselves before handing + control to libfuse. + +* The chmod, chown, truncate, utimens and getattr handlers of the + high-level API now all receive an additional struct fuse_file_info + pointer (which, however, may be NULL even if the file is currently + open). + + The fgetattr and ftruncate handlers have become obsolete and have + been removed. + +* The `fuse_session_new` function no longer accepts the ``-o + clone_fd`` option. Instead, this has become a parameter of the + `fuse_session_loop_mt` and `fuse_loop_mt` functions. + +* For low-level file systems that implement the `write_buf` handler, + the `splice_read` option is now enabled by default. As usual, this + can be changed in the file system's `init` handler. + +* The treatment of low-level options has been made more consistent: + + Options that can be set in the init() handler (via the + fuse_conn_info parameter) can now be set only here, + i.e. fuse_session_new() no longer accepts arguments that change the + fuse_conn_info object before or after the call do init(). As a side + effect, this removes the ambiguity where some options can be + overwritten by init(), while others overwrite the choices made by + init(). + + For file systems that wish to offer command line options for these + settings, the new fuse_parse_conn_info_opts() and + fuse_apply_conn_info_opts() functions are available. + + Consequently, the fuse_lowlevel_help() method has been dropped. + +* The `async_read` field in `struct fuse_conn_info` has been + removed. To determine if the kernel supports asynchronous reads, + file systems should check the `FUSE_CAP_ASYNC_READ` bit of the + `capable` field. To enable/disable asynchronous reads, file systems + should set the flag in the `wanted` field. + +* The `fuse_parse_cmdline` function no longer prints out help when the + ``--verbose`` or ``--help`` flags are given. This needs to be done + by the file system (e.g. using the `fuse_cmdline_help()` and + `fuse_lowlevel_help()` functions). + +* Added ``example/cuse_client.c`` to test ``example/cuse.c``. + +* Removed ``example/null.c``. This has not been working for a while + for unknown reasons -- maybe because it tries to treat the + mountpoint as a file rather than a directory? + +* There are several new examples that demonstrate the use of + the ``fuse_lowlevel_notify_*`` functions: + + - ``example/notify_store_retrieve.c`` + - ``example/notify_inval_inode.c`` + - ``example/notify_inval_entry.c`` + +* The ``-o big_writes`` mount option has been removed. It is now + always active. File systems that want to limit the size of write + requests should use the ``-o max_write=`` option instead. + +* The `fuse_lowlevel_new` function has been renamed to + `fuse_session_new` and no longer interprets the --version or --help + options. To print help or version information, use the new + `fuse_lowlevel_help` and `fuse_lowlevel_version` functions. + +* The ``allow_other`` and ``allow_root`` mount options (accepted by + `fuse_session_new()`) may now be specified together. In this case, + ``allow_root`` takes precedence. + +* There are new `fuse_session_unmount` and `fuse_session_mount` + functions that should be used in the low-level API. The `fuse_mount` + and `fuse_unmount` functions should be used with the high-level API + only. + +* Neither `fuse_mount` nor `fuse_session_mount` take struct fuse_opts + parameters anymore. Mount options are parsed by `fuse_new` (for the + high-level API) and `fuse_session_new` (for the low-level API) + instead. To print help or version information, use the new + `fuse_mount_help` and `fuse_mount_version` functions. + +* The ``fuse_lowlevel_notify_*`` functions now all take a `struct + fuse_session` parameter instead of a `struct fuse_chan`. + +* The channel interface (``fuse_chan_*`` functions) has been made + private. As a result, the typical initialization sequence of a + low-level file system has changed from :: + + ch = fuse_mount(mountpoint, &args); + se = fuse_lowlevel_new(&args, &lo_oper, sizeof(lo_oper), &lo); + fuse_set_signal_handlers(se); + fuse_session_add_chan(se, ch); + fuse_daemonize(fg); + if (mt) + fuse_session_loop_mt(se); + else + fuse_session_loop(se); + fuse_remove_signal_handlers(se); + fuse_session_remove_chan(ch); + fuse_session_destroy(se); + fuse_unmount(mountpoint, ch); + + to :: + + se = fuse_session_new(&args, &ll_ops, sizeof(ll_ops), NULL); + fuse_set_signal_handlers(se); + fuse_session_mount(se, mountpoint); + fuse_daemonize(fg); + if (mt) + fuse_session_loop_mt(se); + else + fuse_session_loop(se); + fuse_remove_signal_handlers(se); + fuse_session_unmount(se); + fuse_lowlevel_destroy(se); + + The typical high-level setup has changed from :: + + ch = fuse_mount(*mountpoint, &args); + fuse = fuse_new(ch, &args, op, op_size, user_data); + se = fuse_get_session(fuse); + fuse_set_signal_handlers(se); + fuse_daemonize(fg); + if (mt) + fuse_loop_mt(fuse); + else + fuse_loop(fuse); + fuse_remove_signal_handlers(se); + fuse_unmount(mountpoint, ch); + fuse_destroy(fuse); + + to :: + + fuse = fuse_new(&args, op, op_size, user_data); + se = fuse_get_session(fuse); + fuse_set_signal_handlers(se); + fuse_mount(fuse, mountpoint); + fuse_daemonize(fg); + if (mt) + fuse_loop_mt(fuse); + else + fuse_loop(fuse); + fuse_remove_signal_handlers(se); + fuse_unmount(fuse); + fuse_destroy(fuse); + + File systems that use `fuse_main` are not affected by this change. + + For integration with custom event loops, the new `fuse_session_fd` + function provides the file descriptor that's used for communication + with the kernel. + +* Added *clone_fd* option. This creates a separate device file + descriptor for each processing thread, which might improve + performance. + +* Added *writeback_cache* option. With kernel 3.14 and newer this + enables write-back caching which can significantly improve + performance. + +* Added *async_dio* option. With kernel 3.13 and newer, this allows + direct I/O to be done asynchronously. + +* The (high- and low-level) `rename` handlers now takes a *flags* + parameter (with values corresponding to the *renameat2* system call + introduced in Linux 3.15). + +* The "ulockmgr_server" has been dropped. + +* There is a new (low-level) `readdirplus` handler, with a + corresponding example in ``examples/fuse_lo-plus.c`` and a new + `fuse_add_direntry_plus` API function. + +* The (high-level) `readdir` handler now takes a *flags* argument. + +* The (high-level) `filler` function passed to `readdir` now takes an + additional *flags* argument. + +* The (high-level) `getdir` handler has been dropped. + +* The *flag_nullpath_ok* and *flag_utime_omit_ok* flags have been + dropped. + +* The (high-level) *utime* handler has been dropped. + +* The `fuse_invalidate` function has been removed. + +* The `fuse_is_lib_option` function has been removed. + +* The *fh_old* member of `struct fuse_file_info` has been dropped. + +* The type of the *writepage* member of `struct fuse_file_info` was + changed from *int* to *unsigned int*. + +* The `struct fuse_file_info` gained a new *poll_events* member. + +* There is a new `fuse_pkgversion` function. + +* The *fuse_off_t* and *fuse_ino_t* changed from *unsigned long* to + *uint64_t*, i.e. they are now 64 bits also on 32-bit systems. + +* The type of the *generation* member of `struct fuse_entry_param*` + changed from *unsigned* to *uint64_t*. + +* The (low-level) `setattr` handler gained a *FUSE_SET_ATTR_CTIME* bit + *for its *to_set* parameter. + +* The `struct fuse_session_ops` data structure has been dropped. + +* The documentation has been clarified and improved in many places. + + +FUSE 2.9.7 (2016-06-20) +======================= + +* Added SELinux support. +* Fixed race-condition when session is terminated right after starting + a FUSE file system. + +FUSE 2.9.6 (2016-04-23) +======================= + +* Tarball now includes documentation. +* Shared-object version has now been bumped correctly. + +FUSE 2.9.5 (2016-01-14) +======================= + +* New maintainer: Nikolaus Rath . Many thanks to + Miklos Szeredi for bringing FUSE to where it is + now! + +* fix warning in mount.c:receive_fd(). Reported by Albert Berger + +* fix possible memory leak. Reported by Jose R. Guzman + +FUSE 2.9.4 (2015-05-22) +======================= + +* fix exec environment for mount and umount. Found by Tavis Ormandy + (CVE-2015-3202). + +* fix fuse_remove_signal_handlers() to properly restore the default + signal handler. Reported by: Chris Johnson + +* highlevel API: fix directory file handle passed to ioctl() method. + Reported by Eric Biggers + +* libfuse: document deadlock avoidance for fuse_notify_inval_entry() + and fuse_notify_delete() + +* fusermount, libfuse: send value as unsigned in "user_id=" and + "group_id=" options. Uids/gids larger than 2147483647 would result + in EINVAL when mounting the filesystem. This also needs a fix in + the kernel. + +* Initialize stat buffer passed to ->getattr() and ->fgetattr() to + zero in all cases. Reported by Daniel Iwan + +* libfuse: Add missing includes. This allows compiling fuse with + musl. Patch by Daniel Thau + + +Older Versions (before 2013-01-01) +================================== + +Please see Git history, e.g. at +https://github.com/libfuse/libfuse/blob/fuse_2_9_3/ChangeLog. diff --git a/GPL2.txt b/GPL2.txt new file mode 100644 index 0000000..d159169 --- /dev/null +++ b/GPL2.txt @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/LGPL2.txt b/LGPL2.txt new file mode 100644 index 0000000..4362b49 --- /dev/null +++ b/LGPL2.txt @@ -0,0 +1,502 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations below. + + When we speak of free software, we are referring to freedom of use, +not price. Our General Public Licenses are designed to make sure that +you have the freedom to distribute copies of free software (and charge +for this service if you wish); that you receive source code or can get +it if you want it; that you can change the software and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it becomes +a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties with +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + + To apply these terms, attach the following notices to the library. It is +safest to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random Hacker. + + , 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..184a5b2 --- /dev/null +++ b/LICENSE @@ -0,0 +1,13 @@ +The following files may be used under the terms of the GNU Lesser +General Public License, version 2.1 ("LGPL"): + +- All files in the include/ directory. +- All files in the lib/ directory. +- meson.build + +The full terms of the LGPL can be found in the LGPL2.txt file. + + +All other files may be used only under the terms of the GNU General +Public License, version 2 ("GPL"). The full text of this license can +be found in the GPL2.txt file. diff --git a/README.md b/README.md new file mode 100644 index 0000000..156e496 --- /dev/null +++ b/README.md @@ -0,0 +1,165 @@ +libfuse +======= + +About +----- + +FUSE (Filesystem in Userspace) is an interface for userspace programs +to export a filesystem to the Linux kernel. The FUSE project consists +of two components: the *fuse* kernel module (maintained in the regular +kernel repositories) and the *libfuse* userspace library (maintained +in this repository). libfuse provides the reference implementation +for communicating with the FUSE kernel module. + +A FUSE file system is typically implemented as a standalone +application that links with libfuse. libfuse provides functions to +mount the file system, unmount it, read requests from the kernel, and +send responses back. libfuse offers two APIs: a "high-level", +synchronous API, and a "low-level" asynchronous API. In both cases, +incoming requests from the kernel are passed to the main program using +callbacks. When using the high-level API, the callbacks may work with +file names and paths instead of inodes, and processing of a request +finishes when the callback function returns. When using the low-level +API, the callbacks must work with inodes and responses must be sent +explicitly using a separate set of API functions. + + +Development Status +------------------ + +libfuse is shipped by all major Linux distributions and has been in +production use across a wide range of systems for many years. However, +at present libfuse does not have any active, regular contributors. The +current maintainer continues to apply pull requests and makes regular +releases, but unfortunately has no capacity to do any development +beyond addressing high-impact issues. When reporting bugs, please +understand that unless you are including a pull request or are +reporting a critical issue, you will probably not get a response. If +you are using libfuse, please consider contributing to the project. + + +Supported Platforms +------------------- + +* Linux (fully) +* BSD (mostly/best-effort) +* For OS-X, please use [OSXFUSE](https://osxfuse.github.io/) + + +Installation +------------ + +You can download libfuse from https://github.com/libfuse/libfuse/releases. To build and +install, you must use [Meson](http://mesonbuild.com/) and +[Ninja](https://ninja-build.org). After downloading the tarball and `.sig` file, verify +it using [signify](https://www.openbsd.org/papers/bsdcan-signify.html): + + signify -V -m fuse-X.Y.Z.tar.gz -p fuse-X.Y.pub + +The `fuse-X.Y.pub` file contains the signing key and needs to be obtained from a +trustworthy source. Each libfuse release contains the signing key for the release after it +in the `signify` directory, so you only need to manually acquire this file once when you +install libfuse for the first time. + +After you have validated the tarball, extract it, create a (temporary) build directory and +run Meson: + + $ tar xzf fuse-X.Y.Z.tar.gz; cd fuse-X.Y.Z + $ mkdir build; cd build + $ meson setup .. + +Normally, the default build options will work fine. If you +nevertheless want to adjust them, you can do so with the +*meson configure* command: + + $ meson configure # list options + $ meson configure -D disable-mtab=true # set an option + + $ # ensure all meson options are applied to the final build system + $ meson setup --reconfigure ../ + +To build, test, and install libfuse, you then use Ninja: + + $ ninja + $ sudo ../test/run-tests.py --build-dir . + $ sudo ninja install + +Running the tests requires bash, Python 3 and (to resolve core dumps) +gdb. Instead of running the tests as root, the majority of tests can +also be run as a regular user if *util/fusermount3* is made setuid root +first; the rest then skip themselves: + + $ ../test/run-tests.py --build-dir . --setuid-helpers + +Each test gets its own working and log directory, and the runner prints +what every one of them cost. See the README under *test/cases* for how +to add one and where to look when one fails. + +Security implications +--------------------- + +The *fusermount3* program is installed setuid root. This is done to +allow normal users to mount their own filesystem implementations. + +To limit the harm that malicious users can do this way, *fusermount3* +enforces the following limitations: + + - The user can only mount on a mountpoint for which they have write + permission + + - The mountpoint must not be a sticky directory which isn't owned by + the user (like /tmp usually is) + + - No other user (including root) can access the contents of the + mounted filesystem (though this can be relaxed by allowing the use + of the *allow_other* and *allow_root* mount options in + */etc/fuse.conf*) + + +If you intend to use the *allow_other* mount options, be aware that +FUSE has an unresolved [security +bug](https://github.com/libfuse/libfuse/issues/15): if the +*default_permissions* mount option is not used, the results of the +first permission check performed by the file system for a directory +entry will be re-used for subsequent accesses as long as the inode of +the accessed entry is present in the kernel cache - even if the +permissions have since changed, and even if the subsequent access is +made by a different user. This is of little concern if the filesystem +is accessible only to the mounting user (which has full access to the +filesystem anyway), but becomes a security issue when other users are +allowed to access the filesystem (since they can exploit this to +perform operations on the filesystem that they do not actually have +permissions for). + +This bug needs to be fixed in the Linux kernel and has been known +since 2006 but unfortunately no fix has been applied yet. If you +depend on correct permission handling for FUSE file systems, the only +workaround is to use `default_permissions` (which does not currently +support ACLs), or to completely disable caching of directory entry +attributes. + +Building your own filesystem +------------------------------ + +FUSE comes with several example file systems in the `example` +directory. For example, the *passthrough* examples mirror the contents +of the root directory under the mountpoint. Start from there and adapt +the code! + +The documentation of the API functions and necessary callbacks is +mostly contained in the files `include/fuse.h` (for the high-level +API) and `include/fuse_lowlevel.h` (for the low-level API). An +autogenerated html version of the API is available in the `doc/html` +directory and at http://libfuse.github.io/doxygen. + + +Getting Help +------------ + +If you need help, please ask on the +mailing list (subscribe by mailing +, archive at +https://lore.kernel.org/fuse-devel/). + +Please report any bugs on the GitHub issue tracker at +https://github.com/libfuse/libfuse/issues. diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..19e1366 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,20 @@ +# Security Policy + +If you have discovered a security vulnerability in this project, please report it +privately. **Do not disclose it as a public issue.** This gives me time to work with you +to fix the issue before public exposure, reducing the chance that the exploit will be +used before a patch is released. + +Please submit information on the vulnerability as a +[private report](https://github.com/libfuse/libfuse/security/advisories/new). + +Please provide the following information in your report: + +- A description of the vulnerability and its impact +- How to reproduce the issue + +This project is maintained by a single volunteer on a reasonable-effort basis. As such, +I ask that you give me 90 days to work on a fix before public exposure. + +Note we are aware of a long-standing security issue when using `allow_others` (see +[#15](https://github.com/libfuse/libfuse/issues/15)). \ No newline at end of file diff --git a/checkpatch.pl b/checkpatch.pl new file mode 100755 index 0000000..ae480df --- /dev/null +++ b/checkpatch.pl @@ -0,0 +1,7931 @@ +#!/usr/bin/env perl +# SPDX-License-Identifier: GPL-2.0 +# +# (c) 2001, Dave Jones. (the file handling bit) +# (c) 2005, Joel Schopp (the ugly bit) +# (c) 2007,2008, Andy Whitcroft (new conditions, test suite) +# (c) 2008-2010 Andy Whitcroft +# (c) 2010-2018 Joe Perches + +use strict; +use warnings; +use POSIX; +use File::Basename; +use Cwd 'abs_path'; +use Term::ANSIColor qw(:constants); +use Encode qw(decode encode); + +my $P = $0; +my $D = dirname(abs_path($P)); + +my $V = '0.32'; + +use Getopt::Long qw(:config no_auto_abbrev); + +my $quiet = 0; +my $verbose = 0; +my %verbose_messages = (); +my %verbose_emitted = (); +my $tree = 1; +my $chk_signoff = 1; +my $chk_fixes_tag = 1; +my $chk_patch = 1; +my $tst_only; +my $emacs = 0; +my $terse = 0; +my $showfile = 0; +my $file = 0; +my $git = 0; +my %git_commits = (); +my $check = 0; +my $check_orig = 0; +my $summary = 1; +my $mailback = 0; +my $summary_file = 0; +my $show_types = 0; +my $list_types = 0; +my $fix = 0; +my $fix_inplace = 0; +my $root; +my $gitroot = $ENV{'GIT_DIR'}; +$gitroot = ".git" if !defined($gitroot); +my %debug; +my %camelcase = (); +my %use_type = (); +my @use = (); +my %ignore_type = (); +my @ignore = (); +my $help = 0; +my $configuration_file = ".checkpatch.conf"; +my $max_line_length = 100; +my $ignore_perl_version = 0; +my $minimum_perl_version = 5.10.0; +my $min_conf_desc_length = 4; +my $spelling_file = "$D/spelling.txt"; +my $codespell = 0; +my $codespellfile = "/usr/share/codespell/dictionary.txt"; +my $user_codespellfile = ""; +my $conststructsfile = "$D/const_structs.checkpatch"; +my $docsfile = "$D/../Documentation/dev-tools/checkpatch.rst"; +my $typedefsfile; +my $color = "auto"; +my $allow_c99_comments = 1; # Can be overridden by --ignore C99_COMMENT_TOLERANCE +# git output parsing needs US English output, so first set backtick child process LANGUAGE +my $git_command ='export LANGUAGE=en_US.UTF-8; git'; +my $tabsize = 8; +my ${CONFIG_} = "CONFIG_"; + +my %maybe_linker_symbol; # for externs in c exceptions, when seen in *vmlinux.lds.h + +sub help { + my ($exitcode) = @_; + + print << "EOM"; +Usage: $P [OPTION]... [FILE]... +Version: $V + +Options: + -q, --quiet quiet + -v, --verbose verbose mode + --no-tree run without a kernel tree + --no-signoff do not check for 'Signed-off-by' line + --no-fixes-tag do not check for 'Fixes:' tag + --patch treat FILE as patchfile (default) + --emacs emacs compile window format + --terse one line per report + --showfile emit diffed file position, not input file position + -g, --git treat FILE as a single commit or git revision range + single git commit with: + + ^ + ~n + multiple git commits with: + .. + ... + - + git merges are ignored + -f, --file treat FILE as regular source file + --subjective, --strict enable more subjective tests + --list-types list the possible message types + --types TYPE(,TYPE2...) show only these comma separated message types + --ignore TYPE(,TYPE2...) ignore various comma separated message types + --show-types show the specific message type in the output + --max-line-length=n set the maximum line length, (default $max_line_length) + if exceeded, warn on patches + requires --strict for use with --file + --min-conf-desc-length=n set the minimum description length for config symbols + in lines, if shorter, warn (default $min_conf_desc_length) + --tab-size=n set the number of spaces for tab (default $tabsize) + --root=PATH PATH to the kernel tree root + --no-summary suppress the per-file summary + --mailback only produce a report in case of warnings/errors + --summary-file include the filename in summary + --debug KEY=[0|1] turn on/off debugging of KEY, where KEY is one of + 'values', 'possible', 'type', and 'attr' (default + is all off) + --test-only=WORD report only warnings/errors containing WORD + literally + --fix EXPERIMENTAL - may create horrible results + If correctable single-line errors exist, create + ".EXPERIMENTAL-checkpatch-fixes" + with potential errors corrected to the preferred + checkpatch style + --fix-inplace EXPERIMENTAL - may create horrible results + Is the same as --fix, but overwrites the input + file. It's your fault if there's no backup or git + --ignore-perl-version override checking of perl version. expect + runtime errors. + --codespell Use the codespell dictionary for spelling/typos + (default:$codespellfile) + --codespellfile Use this codespell dictionary + --typedefsfile Read additional types from this file + --color[=WHEN] Use colors 'always', 'never', or only when output + is a terminal ('auto'). Default is 'auto'. + --kconfig-prefix=WORD use WORD as a prefix for Kconfig symbols (default + ${CONFIG_}) + -h, --help, --version display this help and exit + +When FILE is - read standard input. +EOM + + exit($exitcode); +} + +my $DO_WHILE_0_ADVICE = q{ + do {} while (0) advice is over-stated in a few situations: + + The more obvious case is macros, like MODULE_PARM_DESC, invoked at + file-scope, where C disallows code (it must be in functions). See + $exceptions if you have one to add by name. + + More troublesome is declarative macros used at top of new scope, + like DECLARE_PER_CPU. These might just compile with a do-while-0 + wrapper, but would be incorrect. Most of these are handled by + detecting struct,union,etc declaration primitives in $exceptions. + + Theres also macros called inside an if (block), which "return" an + expression. These cannot do-while, and need a ({}) wrapper. + + Enjoy this qualification while we work to improve our heuristics. +}; + +sub uniq { + my %seen; + return grep { !$seen{$_}++ } @_; +} + +sub list_types { + my ($exitcode) = @_; + + my $count = 0; + + local $/ = undef; + + open(my $script, '<', abs_path($P)) or + die "$P: Can't read '$P' $!\n"; + + my $text = <$script>; + close($script); + + my %types = (); + # Also catch when type or level is passed through a variable + while ($text =~ /(?:(\bCHK|\bWARN|\bERROR|&\{\$msg_level})\s*\(|\$msg_type\s*=)\s*"([^"]+)"/g) { + if (defined($1)) { + if (exists($types{$2})) { + $types{$2} .= ",$1" if ($types{$2} ne $1); + } else { + $types{$2} = $1; + } + } else { + $types{$2} = "UNDETERMINED"; + } + } + + print("#\tMessage type\n\n"); + if ($color) { + print(" ( Color coding: "); + print(RED . "ERROR" . RESET); + print(" | "); + print(YELLOW . "WARNING" . RESET); + print(" | "); + print(GREEN . "CHECK" . RESET); + print(" | "); + print("Multiple levels / Undetermined"); + print(" )\n\n"); + } + + foreach my $type (sort keys %types) { + my $orig_type = $type; + if ($color) { + my $level = $types{$type}; + if ($level eq "ERROR") { + $type = RED . $type . RESET; + } elsif ($level eq "WARN") { + $type = YELLOW . $type . RESET; + } elsif ($level eq "CHK") { + $type = GREEN . $type . RESET; + } + } + print(++$count . "\t" . $type . "\n"); + if ($verbose && exists($verbose_messages{$orig_type})) { + my $message = $verbose_messages{$orig_type}; + $message =~ s/\n/\n\t/g; + print("\t" . $message . "\n\n"); + } + } + + exit($exitcode); +} + +my $conf = which_conf($configuration_file); +if (-f $conf) { + my @conf_args; + open(my $conffile, '<', "$conf") + or warn "$P: Can't find a readable $configuration_file file $!\n"; + + while (<$conffile>) { + my $line = $_; + + $line =~ s/\s*\n?$//g; + $line =~ s/^\s*//g; + $line =~ s/\s+/ /g; + + next if ($line =~ m/^\s*#/); + next if ($line =~ m/^\s*$/); + + my @words = split(" ", $line); + foreach my $word (@words) { + last if ($word =~ m/^#/); + push (@conf_args, $word); + } + } + close($conffile); + unshift(@ARGV, @conf_args) if @conf_args; +} + +sub load_docs { + open(my $docs, '<', "$docsfile") + or warn "$P: Can't read the documentation file $docsfile $!\n"; + + my $type = ''; + my $desc = ''; + my $in_desc = 0; + + while (<$docs>) { + chomp; + my $line = $_; + $line =~ s/\s+$//; + + if ($line =~ /^\s*\*\*(.+)\*\*$/) { + if ($desc ne '') { + $verbose_messages{$type} = trim($desc); + } + $type = $1; + $desc = ''; + $in_desc = 1; + } elsif ($in_desc) { + if ($line =~ /^(?:\s{4,}|$)/) { + $line =~ s/^\s{4}//; + $desc .= $line; + $desc .= "\n"; + } else { + $verbose_messages{$type} = trim($desc); + $type = ''; + $desc = ''; + $in_desc = 0; + } + } + } + + if ($desc ne '') { + $verbose_messages{$type} = trim($desc); + } + close($docs); +} + +# Perl's Getopt::Long allows options to take optional arguments after a space. +# Prevent --color by itself from consuming other arguments +foreach (@ARGV) { + if ($_ eq "--color" || $_ eq "-color") { + $_ = "--color=$color"; + } +} + +GetOptions( + 'q|quiet+' => \$quiet, + 'v|verbose!' => \$verbose, + 'tree!' => \$tree, + 'signoff!' => \$chk_signoff, + 'fixes-tag!' => \$chk_fixes_tag, + 'patch!' => \$chk_patch, + 'emacs!' => \$emacs, + 'terse!' => \$terse, + 'showfile!' => \$showfile, + 'f|file!' => \$file, + 'g|git!' => \$git, + 'subjective!' => \$check, + 'strict!' => \$check, + 'ignore=s' => \@ignore, + 'types=s' => \@use, + 'show-types!' => \$show_types, + 'list-types!' => \$list_types, + 'max-line-length=i' => \$max_line_length, + 'min-conf-desc-length=i' => \$min_conf_desc_length, + 'tab-size=i' => \$tabsize, + 'root=s' => \$root, + 'summary!' => \$summary, + 'mailback!' => \$mailback, + 'summary-file!' => \$summary_file, + 'fix!' => \$fix, + 'fix-inplace!' => \$fix_inplace, + 'ignore-perl-version!' => \$ignore_perl_version, + 'debug=s' => \%debug, + 'test-only=s' => \$tst_only, + 'codespell!' => \$codespell, + 'codespellfile=s' => \$user_codespellfile, + 'typedefsfile=s' => \$typedefsfile, + 'color=s' => \$color, + 'no-color' => \$color, #keep old behaviors of -nocolor + 'nocolor' => \$color, #keep old behaviors of -nocolor + 'kconfig-prefix=s' => \${CONFIG_}, + 'h|help' => \$help, + 'version' => \$help +) or $help = 2; + +if ($user_codespellfile) { + # Use the user provided codespell file unconditionally + $codespellfile = $user_codespellfile; +} elsif (!(-f $codespellfile)) { + # If /usr/share/codespell/dictionary.txt is not present, try to find it + # under codespell's install directory: /data/dictionary.txt + if (($codespell || $help) && which("python3") ne "") { + my $python_codespell_dict = << "EOF"; + +import os.path as op +import codespell_lib +codespell_dir = op.dirname(codespell_lib.__file__) +codespell_file = op.join(codespell_dir, 'data', 'dictionary.txt') +print(codespell_file, end='') +EOF + + my $codespell_dict = `python3 -c "$python_codespell_dict" 2> /dev/null`; + $codespellfile = $codespell_dict if (-f $codespell_dict); + } +} + +# $help is 1 if either -h, --help or --version is passed as option - exitcode: 0 +# $help is 2 if invalid option is passed - exitcode: 1 +help($help - 1) if ($help); + +die "$P: --git cannot be used with --file or --fix\n" if ($git && ($file || $fix)); +die "$P: --verbose cannot be used with --terse\n" if ($verbose && $terse); + +if ($color =~ /^[01]$/) { + $color = !$color; +} elsif ($color =~ /^always$/i) { + $color = 1; +} elsif ($color =~ /^never$/i) { + $color = 0; +} elsif ($color =~ /^auto$/i) { + $color = (-t STDOUT); +} else { + die "$P: Invalid color mode: $color\n"; +} + +load_docs() if ($verbose); +list_types(0) if ($list_types); + +$fix = 1 if ($fix_inplace); +$check_orig = $check; + +my $exit = 0; + +my $perl_version_ok = 1; +if ($^V && $^V lt $minimum_perl_version) { + $perl_version_ok = 0; + printf "$P: requires at least perl version %vd\n", $minimum_perl_version; + exit(1) if (!$ignore_perl_version); +} + +#if no filenames are given, push '-' to read patch from stdin +if ($#ARGV < 0) { + push(@ARGV, '-'); +} + +# skip TAB size 1 to avoid additional checks on $tabsize - 1 +die "$P: Invalid TAB size: $tabsize\n" if ($tabsize < 2); + +sub hash_save_array_words { + my ($hashRef, $arrayRef) = @_; + + my @array = split(/,/, join(',', @$arrayRef)); + foreach my $word (@array) { + $word =~ s/\s*\n?$//g; + $word =~ s/^\s*//g; + $word =~ s/\s+/ /g; + $word =~ tr/[a-z]/[A-Z]/; + + next if ($word =~ m/^\s*#/); + next if ($word =~ m/^\s*$/); + + $hashRef->{$word}++; + } +} + +sub hash_show_words { + my ($hashRef, $prefix) = @_; + + if (keys %$hashRef) { + print "\nNOTE: $prefix message types:"; + foreach my $word (sort keys %$hashRef) { + print " $word"; + } + print "\n"; + } +} + +hash_save_array_words(\%ignore_type, \@ignore); +hash_save_array_words(\%use_type, \@use); + +my $dbg_values = 0; +my $dbg_possible = 0; +my $dbg_type = 0; +my $dbg_attr = 0; +for my $key (keys %debug) { + ## no critic + eval "\${dbg_$key} = '$debug{$key}';"; + die "$@" if ($@); +} + +my $rpt_cleaners = 0; + +if ($terse) { + $emacs = 1; + $quiet++; +} + +if ($tree) { + if (defined $root) { + if (!top_of_kernel_tree($root)) { + die "$P: $root: --root does not point at a valid tree\n"; + } + } else { + if (top_of_kernel_tree('.')) { + $root = '.'; + } elsif ($0 =~ m@(.*)/scripts/[^/]*$@ && + top_of_kernel_tree($1)) { + $root = $1; + } + } + + if (!defined $root) { + print "Must be run from the top-level dir. of a kernel tree\n"; + exit(2); + } +} + +my $emitted_corrupt = 0; + +our $Ident = qr{ + [A-Za-z_][A-Za-z\d_]* + (?:\s*\#\#\s*[A-Za-z_][A-Za-z\d_]*)* + }x; +our $Storage = qr{extern|static|asmlinkage}; +our $Sparse = qr{ + __user| + __kernel| + __force| + __iomem| + __must_check| + __kprobes| + __ref| + __refconst| + __refdata| + __rcu| + __private + }x; +our $InitAttributePrefix = qr{__(?:mem|cpu|dev|net_|)}; +our $InitAttributeData = qr{$InitAttributePrefix(?:initdata\b)}; +our $InitAttributeConst = qr{$InitAttributePrefix(?:initconst\b)}; +our $InitAttributeInit = qr{$InitAttributePrefix(?:init\b)}; +our $InitAttribute = qr{$InitAttributeData|$InitAttributeConst|$InitAttributeInit}; + +# Notes to $Attribute: +# We need \b after 'init' otherwise 'initconst' will cause a false positive in a check +our $Attribute = qr{ + const| + volatile| + __percpu| + __nocast| + __safe| + __bitwise| + __packed__| + __packed2__| + __naked| + __maybe_unused| + __always_unused| + __noreturn| + __used| + __cold| + __pure| + __noclone| + __deprecated| + __read_mostly| + __ro_after_init| + __kprobes| + $InitAttribute| + __aligned\s*\(.*\)| + ____cacheline_aligned| + ____cacheline_aligned_in_smp| + ____cacheline_internodealigned_in_smp| + __weak| + __alloc_size\s*\(\s*\d+\s*(?:,\s*\d+\s*)?\)| + _Atomic + }x; +our $Modifier; +our $Inline = qr{inline|__always_inline|noinline|__inline|__inline__}; +our $Member = qr{->$Ident|\.$Ident|\[[^]]*\]}; +our $Lval = qr{$Ident(?:$Member)*}; + +our $Int_type = qr{(?i)llu|ull|ll|lu|ul|l|u}; +our $Binary = qr{(?i)0b[01]+$Int_type?}; +our $Hex = qr{(?i)0x[0-9a-f]+$Int_type?}; +our $Int = qr{[0-9]+$Int_type?}; +our $Octal = qr{0[0-7]+$Int_type?}; +our $String = qr{(?:\b[Lu])?"[X\t]*"}; +our $Float_hex = qr{(?i)0x[0-9a-f]+p-?[0-9]+[fl]?}; +our $Float_dec = qr{(?i)(?:[0-9]+\.[0-9]*|[0-9]*\.[0-9]+)(?:e-?[0-9]+)?[fl]?}; +our $Float_int = qr{(?i)[0-9]+e-?[0-9]+[fl]?}; +our $Float = qr{$Float_hex|$Float_dec|$Float_int}; +our $Constant = qr{$Float|$Binary|$Octal|$Hex|$Int}; +our $Assignment = qr{\*\=|/=|%=|\+=|-=|<<=|>>=|&=|\^=|\|=|=}; +our $Compare = qr{<=|>=|==|!=|<|(?}; +our $Arithmetic = qr{\+|-|\*|\/|%}; +our $Operators = qr{ + <=|>=|==|!=| + =>|->|<<|>>|<|>|!|~| + &&|\|\||,|\^|\+\+|--|&|\||$Arithmetic + }x; + +our $c90_Keywords = qr{do|for|while|if|else|return|goto|continue|switch|default|case|break}x; + +our $BasicType; +our $NonptrType; +our $NonptrTypeMisordered; +our $NonptrTypeWithAttr; +our $Type; +our $TypeMisordered; +our $Declare; +our $DeclareMisordered; + +our $NON_ASCII_UTF8 = qr{ + [\xC2-\xDF][\x80-\xBF] # non-overlong 2-byte + | \xE0[\xA0-\xBF][\x80-\xBF] # excluding overlongs + | [\xE1-\xEC\xEE\xEF][\x80-\xBF]{2} # straight 3-byte + | \xED[\x80-\x9F][\x80-\xBF] # excluding surrogates + | \xF0[\x90-\xBF][\x80-\xBF]{2} # planes 1-3 + | [\xF1-\xF3][\x80-\xBF]{3} # planes 4-15 + | \xF4[\x80-\x8F][\x80-\xBF]{2} # plane 16 +}x; + +our $UTF8 = qr{ + [\x09\x0A\x0D\x20-\x7E] # ASCII + | $NON_ASCII_UTF8 +}x; + +our $typeC99Typedefs = qr{(?:__)?(?:[us]_?)?int_?(?:8|16|32|64)_t}; +our $typeOtherOSTypedefs = qr{(?x: + u_(?:char|short|int|long) | # bsd + u(?:nchar|short|int|long) # sysv +)}; +our $typeKernelTypedefs = qr{(?x: + (?:__)?(?:u|s|be|le)(?:8|16|32|64)| + atomic_t +)}; +our $typeStdioTypedefs = qr{(?x: + FILE +)}; +our $typeTypedefs = qr{(?x: + $typeC99Typedefs\b| + $typeOtherOSTypedefs\b| + $typeKernelTypedefs\b| + $typeStdioTypedefs\b +)}; + +our $zero_initializer = qr{(?:(?:0[xX])?0+$Int_type?|NULL|false)\b}; + +our $logFunctions = qr{(?x: + printk(?:_ratelimited|_once|_deferred_once|_deferred|)| + (?:[a-z0-9]+_){1,2}(?:printk|emerg|alert|crit|err|warning|warn|notice|info|debug|dbg|vdbg|devel|cont|WARN)(?:_ratelimited|_once|)| + TP_printk| + WARN(?:_RATELIMIT|_ONCE|)| + panic| + MODULE_[A-Z_]+| + seq_vprintf|seq_printf|seq_puts +)}; + +our $allocFunctions = qr{(?x: + (?:(?:devm_)? + (?:kv|k|v)[czm]alloc(?:_array)?(?:_node)? | + kstrdup(?:_const)? | + kmemdup(?:_nul)?) | + (?:\w+)?alloc_skb(?:_ip_align)? | + # dev_alloc_skb/netdev_alloc_skb, et al + dma_alloc_coherent +)}; + +our $signature_tags = qr{(?xi: + Signed-off-by:| + Co-developed-by:| + Acked-by:| + Tested-by:| + Reviewed-by:| + Reported-by:| + Suggested-by:| + To:| + Cc: +)}; + +our @link_tags = qw(Link Closes); + +#Create a search and print patterns for all these strings to be used directly below +our $link_tags_search = ""; +our $link_tags_print = ""; +foreach my $entry (@link_tags) { + if ($link_tags_search ne "") { + $link_tags_search .= '|'; + $link_tags_print .= ' or '; + } + $entry .= ':'; + $link_tags_search .= $entry; + $link_tags_print .= "'$entry'"; +} +$link_tags_search = "(?:${link_tags_search})"; + +our $tracing_logging_tags = qr{(?xi: + [=-]*> | + <[=-]* | + \[ | + \] | + start | + called | + entered | + entry | + enter | + in | + inside | + here | + begin | + exit | + end | + done | + leave | + completed | + out | + return | + [\.\!:\s]* +)}; + +# Device ID types like found in include/linux/mod_devicetable.h. +our $dev_id_types = qr{\b[a-z]\w*_device_id\b}; + +sub edit_distance_min { + my (@arr) = @_; + my $len = scalar @arr; + if ((scalar @arr) < 1) { + # if underflow, return + return; + } + my $min = $arr[0]; + for my $i (0 .. ($len-1)) { + if ($arr[$i] < $min) { + $min = $arr[$i]; + } + } + return $min; +} + +sub get_edit_distance { + my ($str1, $str2) = @_; + $str1 = lc($str1); + $str2 = lc($str2); + $str1 =~ s/-//g; + $str2 =~ s/-//g; + my $len1 = length($str1); + my $len2 = length($str2); + # two dimensional array storing minimum edit distance + my @distance; + for my $i (0 .. $len1) { + for my $j (0 .. $len2) { + if ($i == 0) { + $distance[$i][$j] = $j; + } elsif ($j == 0) { + $distance[$i][$j] = $i; + } elsif (substr($str1, $i-1, 1) eq substr($str2, $j-1, 1)) { + $distance[$i][$j] = $distance[$i - 1][$j - 1]; + } else { + my $dist1 = $distance[$i][$j - 1]; #insert distance + my $dist2 = $distance[$i - 1][$j]; # remove + my $dist3 = $distance[$i - 1][$j - 1]; #replace + $distance[$i][$j] = 1 + edit_distance_min($dist1, $dist2, $dist3); + } + } + } + return $distance[$len1][$len2]; +} + +sub find_standard_signature { + my ($sign_off) = @_; + my @standard_signature_tags = ( + 'Signed-off-by:', 'Co-developed-by:', 'Acked-by:', 'Tested-by:', + 'Reviewed-by:', 'Reported-by:', 'Suggested-by:' + ); + foreach my $signature (@standard_signature_tags) { + return $signature if (get_edit_distance($sign_off, $signature) <= 2); + } + + return ""; +} + +our $obsolete_archives = qr{(?xi: + \Qfreedesktop.org/archives/dri-devel\E | + \Qlists.infradead.org\E | + \Qlkml.org\E | + \Qmail-archive.com\E | + \Qmailman.alsa-project.org/pipermail\E | + \Qmarc.info\E | + \Qozlabs.org/pipermail\E | + \Qspinics.net\E +)}; + +our @typeListMisordered = ( + qr{char\s+(?:un)?signed}, + qr{int\s+(?:(?:un)?signed\s+)?short\s}, + qr{int\s+short(?:\s+(?:un)?signed)}, + qr{short\s+int(?:\s+(?:un)?signed)}, + qr{(?:un)?signed\s+int\s+short}, + qr{short\s+(?:un)?signed}, + qr{long\s+int\s+(?:un)?signed}, + qr{int\s+long\s+(?:un)?signed}, + qr{long\s+(?:un)?signed\s+int}, + qr{int\s+(?:un)?signed\s+long}, + qr{int\s+(?:un)?signed}, + qr{int\s+long\s+long\s+(?:un)?signed}, + qr{long\s+long\s+int\s+(?:un)?signed}, + qr{long\s+long\s+(?:un)?signed\s+int}, + qr{long\s+long\s+(?:un)?signed}, + qr{long\s+(?:un)?signed}, +); + +our @typeList = ( + qr{void}, + qr{(?:(?:un)?signed\s+)?char}, + qr{(?:(?:un)?signed\s+)?short\s+int}, + qr{(?:(?:un)?signed\s+)?short}, + qr{(?:(?:un)?signed\s+)?int}, + qr{(?:(?:un)?signed\s+)?long\s+int}, + qr{(?:(?:un)?signed\s+)?long\s+long\s+int}, + qr{(?:(?:un)?signed\s+)?long\s+long}, + qr{(?:(?:un)?signed\s+)?long}, + qr{(?:un)?signed}, + qr{float}, + qr{double}, + qr{bool}, + qr{struct\s+$Ident}, + qr{union\s+$Ident}, + qr{enum\s+$Ident}, + qr{${Ident}_t}, + qr{${Ident}_handler}, + qr{${Ident}_handler_fn}, + @typeListMisordered, +); + +our $C90_int_types = qr{(?x: + long\s+long\s+int\s+(?:un)?signed| + long\s+long\s+(?:un)?signed\s+int| + long\s+long\s+(?:un)?signed| + (?:(?:un)?signed\s+)?long\s+long\s+int| + (?:(?:un)?signed\s+)?long\s+long| + int\s+long\s+long\s+(?:un)?signed| + int\s+(?:(?:un)?signed\s+)?long\s+long| + + long\s+int\s+(?:un)?signed| + long\s+(?:un)?signed\s+int| + long\s+(?:un)?signed| + (?:(?:un)?signed\s+)?long\s+int| + (?:(?:un)?signed\s+)?long| + int\s+long\s+(?:un)?signed| + int\s+(?:(?:un)?signed\s+)?long| + + int\s+(?:un)?signed| + (?:(?:un)?signed\s+)?int +)}; + +our @typeListFile = (); +our @typeListWithAttr = ( + @typeList, + qr{struct\s+$InitAttribute\s+$Ident}, + qr{union\s+$InitAttribute\s+$Ident}, +); + +our @modifierList = ( + qr{fastcall}, +); +our @modifierListFile = (); + +our @mode_permission_funcs = ( + ["module_param", 3], + ["module_param_(?:array|named|string)", 4], + ["module_param_array_named", 5], + ["debugfs_create_(?:file|u8|u16|u32|u64|x8|x16|x32|x64|size_t|atomic_t|bool|blob|regset32|u32_array)", 2], + ["proc_create(?:_data|)", 2], + ["(?:CLASS|DEVICE|SENSOR|SENSOR_DEVICE|IIO_DEVICE)_ATTR", 2], + ["IIO_DEV_ATTR_[A-Z_]+", 1], + ["SENSOR_(?:DEVICE_|)ATTR_2", 2], + ["SENSOR_TEMPLATE(?:_2|)", 3], + ["__ATTR", 2], +); + +my $word_pattern = '\b[A-Z]?[a-z]{2,}\b'; + +#Create a search pattern for all these functions to speed up a loop below +our $mode_perms_search = ""; +foreach my $entry (@mode_permission_funcs) { + $mode_perms_search .= '|' if ($mode_perms_search ne ""); + $mode_perms_search .= $entry->[0]; +} +$mode_perms_search = "(?:${mode_perms_search})"; + +our %deprecated_apis = ( + "kmap" => "kmap_local_page", + "kunmap" => "kunmap_local", + "kmap_atomic" => "kmap_local_page", + "kunmap_atomic" => "kunmap_local", + #These should be enough to drive away new IDR users + "DEFINE_IDR" => "DEFINE_XARRAY", + "idr_init" => "xa_init", + "idr_init_base" => "xa_init_flags", + "rcu_read_lock_trace" => "rcu_read_lock_tasks_trace", + "rcu_read_unlock_trace" => "rcu_read_unlock_tasks_trace", +); + +#Create a search pattern for all these strings to speed up a loop below +our $deprecated_apis_search = ""; +foreach my $entry (keys %deprecated_apis) { + $deprecated_apis_search .= '|' if ($deprecated_apis_search ne ""); + $deprecated_apis_search .= $entry; +} +$deprecated_apis_search = "(?:${deprecated_apis_search})"; + +our $mode_perms_world_writable = qr{ + S_IWUGO | + S_IWOTH | + S_IRWXUGO | + S_IALLUGO | + 0[0-7][0-7][2367] +}x; + +our %mode_permission_string_types = ( + "S_IRWXU" => 0700, + "S_IRUSR" => 0400, + "S_IWUSR" => 0200, + "S_IXUSR" => 0100, + "S_IRWXG" => 0070, + "S_IRGRP" => 0040, + "S_IWGRP" => 0020, + "S_IXGRP" => 0010, + "S_IRWXO" => 0007, + "S_IROTH" => 0004, + "S_IWOTH" => 0002, + "S_IXOTH" => 0001, + "S_IRWXUGO" => 0777, + "S_IRUGO" => 0444, + "S_IWUGO" => 0222, + "S_IXUGO" => 0111, +); + +#Create a search pattern for all these strings to speed up a loop below +our $mode_perms_string_search = ""; +foreach my $entry (keys %mode_permission_string_types) { + $mode_perms_string_search .= '|' if ($mode_perms_string_search ne ""); + $mode_perms_string_search .= $entry; +} +our $single_mode_perms_string_search = "(?:${mode_perms_string_search})"; +our $multi_mode_perms_string_search = qr{ + ${single_mode_perms_string_search} + (?:\s*\|\s*${single_mode_perms_string_search})* +}x; + +sub perms_to_octal { + my ($string) = @_; + + return trim($string) if ($string =~ /^\s*0[0-7]{3,3}\s*$/); + + my $val = ""; + my $oval = ""; + my $to = 0; + my $curpos = 0; + my $lastpos = 0; + while ($string =~ /\b(($single_mode_perms_string_search)\b(?:\s*\|\s*)?\s*)/g) { + $curpos = pos($string); + my $match = $2; + my $omatch = $1; + last if ($lastpos > 0 && ($curpos - length($omatch) != $lastpos)); + $lastpos = $curpos; + $to |= $mode_permission_string_types{$match}; + $val .= '\s*\|\s*' if ($val ne ""); + $val .= $match; + $oval .= $omatch; + } + $oval =~ s/^\s*\|\s*//; + $oval =~ s/\s*\|\s*$//; + return sprintf("%04o", $to); +} + +our $allowed_asm_includes = qr{(?x: + irq| + memory| + time| + reboot +)}; +# memory.h: ARM has a custom one + +# Load common spelling mistakes and build regular expression list. +my $misspellings; +my %spelling_fix; + +if (open(my $spelling, '<', $spelling_file)) { + while (<$spelling>) { + my $line = $_; + + $line =~ s/\s*\n?$//g; + $line =~ s/^\s*//g; + + next if ($line =~ m/^\s*#/); + next if ($line =~ m/^\s*$/); + + my ($suspect, $fix) = split(/\|\|/, $line); + + $spelling_fix{$suspect} = $fix; + } + close($spelling); +} else { + warn "No typos will be found - file '$spelling_file': $!\n"; +} + +if ($codespell) { + if (open(my $spelling, '<', $codespellfile)) { + while (<$spelling>) { + my $line = $_; + + $line =~ s/\s*\n?$//g; + $line =~ s/^\s*//g; + + next if ($line =~ m/^\s*#/); + next if ($line =~ m/^\s*$/); + next if ($line =~ m/, disabled/i); + + $line =~ s/,.*$//; + + my ($suspect, $fix) = split(/->/, $line); + + $spelling_fix{$suspect} = $fix; + } + close($spelling); + } else { + warn "No codespell typos will be found - file '$codespellfile': $!\n"; + } +} + +$misspellings = join("|", sort keys %spelling_fix) if keys %spelling_fix; + +sub read_words { + my ($wordsRef, $file) = @_; + + if (open(my $words, '<', $file)) { + while (<$words>) { + my $line = $_; + + $line =~ s/\s*\n?$//g; + $line =~ s/^\s*//g; + + next if ($line =~ m/^\s*#/); + next if ($line =~ m/^\s*$/); + if ($line =~ /\s/) { + print("$file: '$line' invalid - ignored\n"); + next; + } + + $$wordsRef .= '|' if (defined $$wordsRef); + $$wordsRef .= $line; + } + close($file); + return 1; + } + + return 0; +} + +my $const_structs; +if (show_type("CONST_STRUCT")) { + read_words(\$const_structs, $conststructsfile) + or warn "No structs that should be const will be found - file '$conststructsfile': $!\n"; +} + +if (defined($typedefsfile)) { + my $typeOtherTypedefs; + read_words(\$typeOtherTypedefs, $typedefsfile) + or warn "No additional types will be considered - file '$typedefsfile': $!\n"; + $typeTypedefs .= '|' . $typeOtherTypedefs if (defined $typeOtherTypedefs); +} + +sub build_types { + my $mods = "(?x: \n" . join("|\n ", (@modifierList, @modifierListFile)) . "\n)"; + my $all = "(?x: \n" . join("|\n ", (@typeList, @typeListFile)) . "\n)"; + my $Misordered = "(?x: \n" . join("|\n ", @typeListMisordered) . "\n)"; + my $allWithAttr = "(?x: \n" . join("|\n ", @typeListWithAttr) . "\n)"; + $Modifier = qr{(?:$Attribute|$Sparse|$mods)}; + $BasicType = qr{ + (?:$typeTypedefs\b)| + (?:${all}\b) + }x; + $NonptrType = qr{ + (?:$Modifier\s+|const\s+)* + (?: + (?:typeof|__typeof__)\s*\([^\)]*\)| + (?:$typeTypedefs\b)| + (?:${all}\b) + ) + (?:\s+$Modifier|\s+const)* + }x; + $NonptrTypeMisordered = qr{ + (?:$Modifier\s+|const\s+)* + (?: + (?:${Misordered}\b) + ) + (?:\s+$Modifier|\s+const)* + }x; + $NonptrTypeWithAttr = qr{ + (?:$Modifier\s+|const\s+)* + (?: + (?:typeof|__typeof__)\s*\([^\)]*\)| + (?:$typeTypedefs\b)| + (?:${allWithAttr}\b) + ) + (?:\s+$Modifier|\s+const)* + }x; + $Type = qr{ + $NonptrType + (?:(?:\s|\*|\[\])+\s*const|(?:\s|\*\s*(?:const\s*)?|\[\])+|(?:\s*\[\s*\])+){0,4} + (?:\s+$Inline|\s+$Modifier)* + }x; + $TypeMisordered = qr{ + $NonptrTypeMisordered + (?:(?:\s|\*|\[\])+\s*const|(?:\s|\*\s*(?:const\s*)?|\[\])+|(?:\s*\[\s*\])+){0,4} + (?:\s+$Inline|\s+$Modifier)* + }x; + $Declare = qr{(?:$Storage\s+(?:$Inline\s+)?)?$Type}; + $DeclareMisordered = qr{(?:$Storage\s+(?:$Inline\s+)?)?$TypeMisordered}; +} +build_types(); + +our $Typecast = qr{\s*(\(\s*$NonptrType\s*\)){0,1}\s*}; + +# Using $balanced_parens, $LvalOrFunc, or $FuncArg +# requires at least perl version v5.10.0 +# Any use must be runtime checked with $^V + +our $balanced_parens = qr/(\((?:[^\(\)]++|(?-1))*\))/; +our $LvalOrFunc = qr{((?:[\&\*]\s*)?$Lval)\s*($balanced_parens{0,1})\s*}; +our $FuncArg = qr{$Typecast{0,1}($LvalOrFunc|$Constant|$String)}; + +our $declaration_macros = qr{(?x: + (?:$Storage\s+)?(?:[A-Z_][A-Z0-9]*_){0,2}(?:DEFINE|DECLARE)(?:_[A-Z0-9]+){1,6}\s*\(| + (?:$Storage\s+)?[HLP]?LIST_HEAD\s*\(| + (?:SKCIPHER_REQUEST|SHASH_DESC|AHASH_REQUEST)_ON_STACK\s*\(| + (?:$Storage\s+)?(?:XA_STATE|XA_STATE_ORDER)\s*\(| + __cacheline_group_(?:begin|end)(?:_aligned)?\s*\(| + __dma_from_device_group_(?:begin|end)\s*\( +)}; + +our %allow_repeated_words = ( + add => '', + added => '', + bad => '', + be => '', +); + +sub deparenthesize { + my ($string) = @_; + return "" if (!defined($string)); + + while ($string =~ /^\s*\(.*\)\s*$/) { + $string =~ s@^\s*\(\s*@@; + $string =~ s@\s*\)\s*$@@; + } + + $string =~ s@\s+@ @g; + + return $string; +} + +sub seed_camelcase_file { + my ($file) = @_; + + return if (!(-f $file)); + + local $/; + + open(my $include_file, '<', "$file") + or warn "$P: Can't read '$file' $!\n"; + my $text = <$include_file>; + close($include_file); + + my @lines = split('\n', $text); + + foreach my $line (@lines) { + next if ($line !~ /(?:[A-Z][a-z]|[a-z][A-Z])/); + if ($line =~ /^[ \t]*(?:#[ \t]*define|typedef\s+$Type)\s+(\w*(?:[A-Z][a-z]|[a-z][A-Z])\w*)/) { + $camelcase{$1} = 1; + } elsif ($line =~ /^\s*$Declare\s+(\w*(?:[A-Z][a-z]|[a-z][A-Z])\w*)\s*[\(\[,;]/) { + $camelcase{$1} = 1; + } elsif ($line =~ /^\s*(?:union|struct|enum)\s+(\w*(?:[A-Z][a-z]|[a-z][A-Z])\w*)\s*[;\{]/) { + $camelcase{$1} = 1; + } + } +} + +our %maintained_status = (); + +sub is_maintained_obsolete { + my ($filename) = @_; + + return 0 if (!$tree || !(-e "$root/scripts/get_maintainer.pl")); + + if (!exists($maintained_status{$filename})) { + $maintained_status{$filename} = `perl $root/scripts/get_maintainer.pl --status --nom --nol --nogit --nogit-fallback -f $filename 2>&1`; + } + + return $maintained_status{$filename} =~ /obsolete/i; +} + +sub is_SPDX_License_valid { + my ($license) = @_; + + return 1 if (!$tree || which("python3") eq "" || !(-x "$root/scripts/spdxcheck.py") || !(-e "$gitroot")); + + my $root_path = abs_path($root); + my $status = `cd "$root_path"; echo "$license" | scripts/spdxcheck.py -`; + return 0 if ($status ne ""); + return 1; +} + +my $camelcase_seeded = 0; +sub seed_camelcase_includes { + return if ($camelcase_seeded); + + my $files; + my $camelcase_cache = ""; + my @include_files = (); + + $camelcase_seeded = 1; + + if (-e "$gitroot") { + my $git_last_include_commit = `${git_command} log --no-merges --pretty=format:"%h%n" -1 -- include`; + chomp $git_last_include_commit; + $camelcase_cache = ".checkpatch-camelcase.git.$git_last_include_commit"; + } else { + my $last_mod_date = 0; + $files = `find $root/include -name "*.h"`; + @include_files = split('\n', $files); + foreach my $file (@include_files) { + my $date = POSIX::strftime("%Y%m%d%H%M", + localtime((stat $file)[9])); + $last_mod_date = $date if ($last_mod_date < $date); + } + $camelcase_cache = ".checkpatch-camelcase.date.$last_mod_date"; + } + + if ($camelcase_cache ne "" && -f $camelcase_cache) { + open(my $camelcase_file, '<', "$camelcase_cache") + or warn "$P: Can't read '$camelcase_cache' $!\n"; + while (<$camelcase_file>) { + chomp; + $camelcase{$_} = 1; + } + close($camelcase_file); + + return; + } + + if (-e "$gitroot") { + $files = `${git_command} ls-files "include/*.h"`; + @include_files = split('\n', $files); + } + + foreach my $file (@include_files) { + seed_camelcase_file($file); + } + + if ($camelcase_cache ne "") { + unlink glob ".checkpatch-camelcase.*"; + open(my $camelcase_file, '>', "$camelcase_cache") + or warn "$P: Can't write '$camelcase_cache' $!\n"; + foreach (sort { lc($a) cmp lc($b) } keys(%camelcase)) { + print $camelcase_file ("$_\n"); + } + close($camelcase_file); + } +} + +sub git_is_single_file { + my ($filename) = @_; + + return 0 if ((which("git") eq "") || !(-e "$gitroot")); + + my $output = `${git_command} ls-files -- $filename 2>/dev/null`; + my $count = $output =~ tr/\n//; + return $count eq 1 && $output =~ m{^${filename}$}; +} + +sub git_commit_info { + my ($commit, $id, $desc) = @_; + + return ($id, $desc) if ((which("git") eq "") || !(-e "$gitroot")); + + my $output = `${git_command} log --no-color --format='%H %s' -1 $commit 2>&1`; + $output =~ s/^\s*//gm; + my @lines = split("\n", $output); + + return ($id, $desc) if ($#lines < 0); + + if ($lines[0] =~ /^error: short SHA1 $commit is ambiguous/) { +# Maybe one day convert this block of bash into something that returns +# all matching commit ids, but it's very slow... +# +# echo "checking commits $1..." +# git rev-list --remotes | grep -i "^$1" | +# while read line ; do +# git log --format='%H %s' -1 $line | +# echo "commit $(cut -c 1-12,41-)" +# done + } elsif ($lines[0] =~ /^fatal: ambiguous argument '$commit': unknown revision or path not in the working tree\./ || + $lines[0] =~ /^fatal: bad object $commit/) { + $id = undef; + } else { + $id = substr($lines[0], 0, 12); + $desc = substr($lines[0], 41); + } + + return ($id, $desc); +} + +$chk_signoff = 0 if ($file); +$chk_fixes_tag = 0 if ($file); + +my @rawlines = (); +my @lines = (); +my @fixed = (); +my @fixed_inserted = (); +my @fixed_deleted = (); +my $fixlinenr = -1; + +# If input is git commits, extract all commits from the commit expressions. +# For example, HEAD-3 means we need check 'HEAD, HEAD~1, HEAD~2'. +die "$P: No git repository found\n" if ($git && !-e "$gitroot"); + +if ($git) { + my @commits = (); + foreach my $commit_expr (@ARGV) { + my $git_range; + if ($commit_expr =~ m/^(.*)-(\d+)$/) { + $git_range = "-$2 $1"; + } elsif ($commit_expr =~ m/\.\./) { + $git_range = "$commit_expr"; + } else { + $git_range = "-1 $commit_expr"; + } + my $lines = `${git_command} log --no-color --no-merges --pretty=format:'%H %s' $git_range`; + foreach my $line (split(/\n/, $lines)) { + $line =~ /^([0-9a-fA-F]{40,40}) (.*)$/; + next if (!defined($1) || !defined($2)); + my $sha1 = $1; + my $subject = $2; + unshift(@commits, $sha1); + $git_commits{$sha1} = $subject; + } + } + die "$P: no git commits after extraction!\n" if (@commits == 0); + @ARGV = @commits; +} + +my $vname; +$allow_c99_comments = !defined $ignore_type{"C99_COMMENT_TOLERANCE"}; +for my $filename (@ARGV) { + my $FILE; + my $is_git_file = git_is_single_file($filename); + my $oldfile = $file; + $file = 1 if ($is_git_file); + if ($git) { + open($FILE, '-|', "git format-patch -M --stdout -1 $filename") || + die "$P: $filename: git format-patch failed - $!\n"; + } elsif ($file) { + open($FILE, '-|', "diff -u /dev/null $filename") || + die "$P: $filename: diff failed - $!\n"; + } elsif ($filename eq '-') { + open($FILE, '<&STDIN'); + } else { + open($FILE, '<', "$filename") || + die "$P: $filename: open failed - $!\n"; + } + if ($filename eq '-') { + $vname = 'Your patch'; + } elsif ($git) { + $vname = "Commit " . substr($filename, 0, 12) . ' ("' . $git_commits{$filename} . '")'; + } else { + $vname = $filename; + } + while (<$FILE>) { + chomp; + push(@rawlines, $_); + $vname = qq("$1") if ($filename eq '-' && $_ =~ m/^Subject:\s+(.+)/i); + } + close($FILE); + + if ($#ARGV > 0 && $quiet == 0) { + print '-' x length($vname) . "\n"; + print "$vname\n"; + print '-' x length($vname) . "\n"; + } + + if (!process($filename)) { + $exit = 1; + } + @rawlines = (); + @lines = (); + @fixed = (); + @fixed_inserted = (); + @fixed_deleted = (); + $fixlinenr = -1; + @modifierListFile = (); + @typeListFile = (); + build_types(); + $file = $oldfile if ($is_git_file); +} + +if (!$quiet) { + hash_show_words(\%use_type, "Used"); + hash_show_words(\%ignore_type, "Ignored"); + + if (!$perl_version_ok) { + print << "EOM" + +NOTE: perl $^V is not modern enough to detect all possible issues. + An upgrade to at least perl $minimum_perl_version is suggested. +EOM + } + if ($exit) { + print << "EOM" + +NOTE: If any of the errors are false positives, please report + them to the maintainer, see CHECKPATCH in MAINTAINERS. +EOM + } +} + +exit($exit); + +sub top_of_kernel_tree { + my ($root) = @_; + + my @tree_check = ( + "COPYING", "CREDITS", "Kbuild", "MAINTAINERS", "Makefile", + "README", "Documentation", "arch", "include", "drivers", + "fs", "init", "ipc", "kernel", "lib", "scripts", + ); + + foreach my $check (@tree_check) { + if (! -e $root . '/' . $check) { + return 0; + } + } + return 1; +} + +sub parse_email { + my ($formatted_email) = @_; + + my $name = ""; + my $quoted = ""; + my $name_comment = ""; + my $address = ""; + my $comment = ""; + + if ($formatted_email =~ /^(.*)<(\S+\@\S+)>(.*)$/) { + $name = $1; + $address = $2; + $comment = $3 if defined $3; + } elsif ($formatted_email =~ /^\s*<(\S+\@\S+)>(.*)$/) { + $address = $1; + $comment = $2 if defined $2; + } elsif ($formatted_email =~ /(\S+\@\S+)(.*)$/) { + $address = $1; + $comment = $2 if defined $2; + $formatted_email =~ s/\Q$address\E.*$//; + $name = $formatted_email; + $name = trim($name); + $name =~ s/^\"|\"$//g; + # If there's a name left after stripping spaces and + # leading quotes, and the address doesn't have both + # leading and trailing angle brackets, the address + # is invalid. ie: + # "joe smith joe@smith.com" bad + # "joe smith ]+>$/) { + $name = ""; + $address = ""; + $comment = ""; + } + } + + # Extract comments from names excluding quoted parts + # "John D. (Doe)" - Do not extract + if ($name =~ s/\"(.+)\"//) { + $quoted = $1; + } + while ($name =~ s/\s*($balanced_parens)\s*/ /) { + $name_comment .= trim($1); + } + $name =~ s/^[ \"]+|[ \"]+$//g; + $name = trim("$quoted $name"); + + $address = trim($address); + $address =~ s/^\<|\>$//g; + $comment = trim($comment); + + if ($name =~ /[^\w \-]/i) { ##has "must quote" chars + $name =~ s/(?"; + } + $formatted_email .= "$comment"; + return $formatted_email; +} + +sub reformat_email { + my ($email) = @_; + + my ($email_name, $name_comment, $email_address, $comment) = parse_email($email); + return format_email($email_name, $name_comment, $email_address, $comment); +} + +sub same_email_addresses { + my ($email1, $email2) = @_; + + my ($email1_name, $name1_comment, $email1_address, $comment1) = parse_email($email1); + my ($email2_name, $name2_comment, $email2_address, $comment2) = parse_email($email2); + + return $email1_name eq $email2_name && + $email1_address eq $email2_address && + $name1_comment eq $name2_comment && + $comment1 eq $comment2; +} + +sub which { + my ($bin) = @_; + + foreach my $path (split(/:/, $ENV{PATH})) { + if (-e "$path/$bin") { + return "$path/$bin"; + } + } + + return ""; +} + +sub which_conf { + my ($conf) = @_; + + foreach my $path (split(/:/, ".:$ENV{HOME}:.scripts")) { + if (-e "$path/$conf") { + return "$path/$conf"; + } + } + + return ""; +} + +sub expand_tabs { + my ($str) = @_; + + my $res = ''; + my $n = 0; + for my $c (split(//, $str)) { + if ($c eq "\t") { + $res .= ' '; + $n++; + for (; ($n % $tabsize) != 0; $n++) { + $res .= ' '; + } + next; + } + $res .= $c; + $n++; + } + + return $res; +} +sub copy_spacing { + (my $res = shift) =~ tr/\t/ /c; + return $res; +} + +sub line_stats { + my ($line) = @_; + + # Drop the diff line leader and expand tabs + $line =~ s/^.//; + $line = expand_tabs($line); + + # Pick the indent from the front of the line. + my ($white) = ($line =~ /^(\s*)/); + + return (length($line), length($white)); +} + +my $sanitise_quote = ''; + +sub sanitise_line_reset { + my ($in_comment) = @_; + + if ($in_comment) { + $sanitise_quote = '*/'; + } else { + $sanitise_quote = ''; + } +} +sub sanitise_line { + my ($line) = @_; + + my $res = ''; + my $l = ''; + + my $qlen = 0; + my $off = 0; + my $c; + + # Always copy over the diff marker. + $res = substr($line, 0, 1); + + for ($off = 1; $off < length($line); $off++) { + $c = substr($line, $off, 1); + + # Comments we are whacking completely including the begin + # and end, all to $;. + if ($sanitise_quote eq '' && substr($line, $off, 2) eq '/*') { + $sanitise_quote = '*/'; + + substr($res, $off, 2, "$;$;"); + $off++; + next; + } + if ($sanitise_quote eq '*/' && substr($line, $off, 2) eq '*/') { + $sanitise_quote = ''; + substr($res, $off, 2, "$;$;"); + $off++; + next; + } + if ($sanitise_quote eq '' && substr($line, $off, 2) eq '//') { + $sanitise_quote = '//'; + + substr($res, $off, 2, $sanitise_quote); + $off++; + next; + } + + # A \ in a string means ignore the next character. + if (($sanitise_quote eq "'" || $sanitise_quote eq '"') && + $c eq "\\") { + substr($res, $off, 2, 'XX'); + $off++; + next; + } + # Regular quotes. + if ($c eq "'" || $c eq '"') { + if ($sanitise_quote eq '') { + $sanitise_quote = $c; + + substr($res, $off, 1, $c); + next; + } elsif ($sanitise_quote eq $c) { + $sanitise_quote = ''; + } + } + + #print "c<$c> SQ<$sanitise_quote>\n"; + if ($off != 0 && $sanitise_quote eq '*/' && $c ne "\t") { + substr($res, $off, 1, $;); + } elsif ($off != 0 && $sanitise_quote eq '//' && $c ne "\t") { + substr($res, $off, 1, $;); + } elsif ($off != 0 && $sanitise_quote && $c ne "\t") { + substr($res, $off, 1, 'X'); + } else { + substr($res, $off, 1, $c); + } + } + + if ($sanitise_quote eq '//') { + $sanitise_quote = ''; + } + + # The pathname on a #include may be surrounded by '<' and '>'. + if ($res =~ /^.\s*\#\s*include\s+\<(.*)\>/) { + my $clean = 'X' x length($1); + $res =~ s@\<.*\>@<$clean>@; + + # The whole of a #error is a string. + } elsif ($res =~ /^.\s*\#\s*(?:error|warning)\s+(.*)\b/) { + my $clean = 'X' x length($1); + $res =~ s@(\#\s*(?:error|warning)\s+).*@$1$clean@; + } + + if ($allow_c99_comments && $res =~ m@(//.*$)@) { + my $match = $1; + $res =~ s/\Q$match\E/"$;" x length($match)/e; + } + + return $res; +} + +sub get_quoted_string { + my ($line, $rawline) = @_; + + return "" if (!defined($line) || !defined($rawline)); + return "" if ($line !~ m/($String)/g); + return substr($rawline, $-[0], $+[0] - $-[0]); +} + +sub ctx_statement_block { + my ($linenr, $remain, $off) = @_; + my $line = $linenr - 1; + my $blk = ''; + my $soff = $off; + my $coff = $off - 1; + my $coff_set = 0; + + my $loff = 0; + + my $type = ''; + my $level = 0; + my @stack = (); + my $p; + my $c; + my $len = 0; + + my $remainder; + while (1) { + @stack = (['', 0]) if ($#stack == -1); + + #warn "CSB: blk<$blk> remain<$remain>\n"; + # If we are about to drop off the end, pull in more + # context. + if ($off >= $len) { + for (; $remain > 0; $line++) { + last if (!defined $lines[$line]); + next if ($lines[$line] =~ /^-/); + $remain--; + $loff = $len; + $blk .= $lines[$line] . "\n"; + $len = length($blk); + $line++; + last; + } + # Bail if there is no further context. + #warn "CSB: blk<$blk> off<$off> len<$len>\n"; + if ($off >= $len) { + last; + } + if ($level == 0 && substr($blk, $off) =~ /^.\s*#\s*define/) { + $level++; + $type = '#'; + } + } + $p = $c; + $c = substr($blk, $off, 1); + $remainder = substr($blk, $off); + + #warn "CSB: c<$c> type<$type> level<$level> remainder<$remainder> coff_set<$coff_set>\n"; + + # Handle nested #if/#else. + if ($remainder =~ /^#\s*(?:ifndef|ifdef|if)\s/) { + push(@stack, [ $type, $level ]); + } elsif ($remainder =~ /^#\s*(?:else|elif)\b/) { + ($type, $level) = @{$stack[$#stack - 1]}; + } elsif ($remainder =~ /^#\s*endif\b/) { + ($type, $level) = @{pop(@stack)}; + } + + # Statement ends at the ';' or a close '}' at the + # outermost level. + if ($level == 0 && $c eq ';') { + last; + } + + # An else is really a conditional as long as its not else if + if ($level == 0 && $coff_set == 0 && + (!defined($p) || $p =~ /(?:\s|\}|\+)/) && + $remainder =~ /^(else)(?:\s|{)/ && + $remainder !~ /^else\s+if\b/) { + $coff = $off + length($1) - 1; + $coff_set = 1; + #warn "CSB: mark coff<$coff> soff<$soff> 1<$1>\n"; + #warn "[" . substr($blk, $soff, $coff - $soff + 1) . "]\n"; + } + + if (($type eq '' || $type eq '(') && $c eq '(') { + $level++; + $type = '('; + } + if ($type eq '(' && $c eq ')') { + $level--; + $type = ($level != 0)? '(' : ''; + + if ($level == 0 && $coff < $soff) { + $coff = $off; + $coff_set = 1; + #warn "CSB: mark coff<$coff>\n"; + } + } + if (($type eq '' || $type eq '{') && $c eq '{') { + $level++; + $type = '{'; + } + if ($type eq '{' && $c eq '}') { + $level--; + $type = ($level != 0)? '{' : ''; + + if ($level == 0) { + if (substr($blk, $off + 1, 1) eq ';') { + $off++; + } + last; + } + } + # Preprocessor commands end at the newline unless escaped. + if ($type eq '#' && $c eq "\n" && $p ne "\\") { + $level--; + $type = ''; + $off++; + last; + } + $off++; + } + # We are truly at the end, so shuffle to the next line. + if ($off == $len) { + $loff = $len + 1; + $line++; + $remain--; + } + + my $statement = substr($blk, $soff, $off - $soff + 1); + my $condition = substr($blk, $soff, $coff - $soff + 1); + + #warn "STATEMENT<$statement>\n"; + #warn "CONDITION<$condition>\n"; + + #print "coff<$coff> soff<$off> loff<$loff>\n"; + + return ($statement, $condition, + $line, $remain + 1, $off - $loff + 1, $level); +} + +sub statement_lines { + my ($stmt) = @_; + + # Strip the diff line prefixes and rip blank lines at start and end. + $stmt =~ s/(^|\n)./$1/g; + $stmt =~ s/^\s*//; + $stmt =~ s/\s*$//; + + my @stmt_lines = ($stmt =~ /\n/g); + + return $#stmt_lines + 2; +} + +sub statement_rawlines { + my ($stmt) = @_; + + my @stmt_lines = ($stmt =~ /\n/g); + + return $#stmt_lines + 2; +} + +sub statement_block_size { + my ($stmt) = @_; + + $stmt =~ s/(^|\n)./$1/g; + $stmt =~ s/^\s*{//; + $stmt =~ s/}\s*$//; + $stmt =~ s/^\s*//; + $stmt =~ s/\s*$//; + + my @stmt_lines = ($stmt =~ /\n/g); + my @stmt_statements = ($stmt =~ /;/g); + + my $stmt_lines = $#stmt_lines + 2; + my $stmt_statements = $#stmt_statements + 1; + + if ($stmt_lines > $stmt_statements) { + return $stmt_lines; + } else { + return $stmt_statements; + } +} + +sub ctx_statement_full { + my ($linenr, $remain, $off) = @_; + my ($statement, $condition, $level); + + my (@chunks); + + # Grab the first conditional/block pair. + ($statement, $condition, $linenr, $remain, $off, $level) = + ctx_statement_block($linenr, $remain, $off); + #print "F: c<$condition> s<$statement> remain<$remain>\n"; + push(@chunks, [ $condition, $statement ]); + if (!($remain > 0 && $condition =~ /^\s*(?:\n[+-])?\s*(?:if|else|do)\b/s)) { + return ($level, $linenr, @chunks); + } + + # Pull in the following conditional/block pairs and see if they + # could continue the statement. + for (;;) { + ($statement, $condition, $linenr, $remain, $off, $level) = + ctx_statement_block($linenr, $remain, $off); + #print "C: c<$condition> s<$statement> remain<$remain>\n"; + last if (!($remain > 0 && $condition =~ /^(?:\s*\n[+-])*\s*(?:else|do)\b/s)); + #print "C: push\n"; + push(@chunks, [ $condition, $statement ]); + } + + return ($level, $linenr, @chunks); +} + +sub ctx_block_get { + my ($linenr, $remain, $outer, $open, $close, $off) = @_; + my $line; + my $start = $linenr - 1; + my $blk = ''; + my @o; + my @c; + my @res = (); + + my $level = 0; + my @stack = ($level); + for ($line = $start; $remain > 0; $line++) { + next if ($rawlines[$line] =~ /^-/); + $remain--; + + $blk .= $rawlines[$line]; + + # Handle nested #if/#else. + if ($lines[$line] =~ /^.\s*#\s*(?:ifndef|ifdef|if)\s/) { + push(@stack, $level); + } elsif ($lines[$line] =~ /^.\s*#\s*(?:else|elif)\b/) { + $level = $stack[$#stack - 1]; + } elsif ($lines[$line] =~ /^.\s*#\s*endif\b/) { + $level = pop(@stack); + } + + foreach my $c (split(//, $lines[$line])) { + ##print "C<$c>L<$level><$open$close>O<$off>\n"; + if ($off > 0) { + $off--; + next; + } + + if ($c eq $close && $level > 0) { + $level--; + last if ($level == 0); + } elsif ($c eq $open) { + $level++; + } + } + + if (!$outer || $level <= 1) { + push(@res, $rawlines[$line]); + } + + last if ($level == 0); + } + + return ($level, @res); +} +sub ctx_block_outer { + my ($linenr, $remain) = @_; + + my ($level, @r) = ctx_block_get($linenr, $remain, 1, '{', '}', 0); + return @r; +} +sub ctx_block { + my ($linenr, $remain) = @_; + + my ($level, @r) = ctx_block_get($linenr, $remain, 0, '{', '}', 0); + return @r; +} +sub ctx_statement { + my ($linenr, $remain, $off) = @_; + + my ($level, @r) = ctx_block_get($linenr, $remain, 0, '(', ')', $off); + return @r; +} +sub ctx_block_level { + my ($linenr, $remain) = @_; + + return ctx_block_get($linenr, $remain, 0, '{', '}', 0); +} +sub ctx_statement_level { + my ($linenr, $remain, $off) = @_; + + return ctx_block_get($linenr, $remain, 0, '(', ')', $off); +} + +sub ctx_locate_comment { + my ($first_line, $end_line) = @_; + + # If c99 comment on the current line, or the line before or after + my ($current_comment) = ($rawlines[$end_line - 1] =~ m@^\+.*(//.*$)@); + return $current_comment if (defined $current_comment); + ($current_comment) = ($rawlines[$end_line - 2] =~ m@^[\+ ].*(//.*$)@); + return $current_comment if (defined $current_comment); + ($current_comment) = ($rawlines[$end_line] =~ m@^[\+ ].*(//.*$)@); + return $current_comment if (defined $current_comment); + + # Catch a comment on the end of the line itself. + ($current_comment) = ($rawlines[$end_line - 1] =~ m@.*(/\*.*\*/)\s*(?:\\\s*)?$@); + return $current_comment if (defined $current_comment); + + # Look through the context and try and figure out if there is a + # comment. + my $in_comment = 0; + $current_comment = ''; + for (my $linenr = $first_line; $linenr < $end_line; $linenr++) { + my $line = $rawlines[$linenr - 1]; + #warn " $line\n"; + if ($linenr == $first_line and $line =~ m@^.\s*\*@) { + $in_comment = 1; + } + if ($line =~ m@/\*@) { + $in_comment = 1; + } + if (!$in_comment && $current_comment ne '') { + $current_comment = ''; + } + $current_comment .= $line . "\n" if ($in_comment); + if ($line =~ m@\*/@) { + $in_comment = 0; + } + } + + chomp($current_comment); + return($current_comment); +} +sub ctx_has_comment { + my ($first_line, $end_line) = @_; + my $cmt = ctx_locate_comment($first_line, $end_line); + + ##print "LINE: $rawlines[$end_line - 1 ]\n"; + ##print "CMMT: $cmt\n"; + + return ($cmt ne ''); +} + +sub raw_line { + my ($linenr, $cnt) = @_; + + my $offset = $linenr - 1; + $cnt++; + + my $line; + while ($cnt) { + $line = $rawlines[$offset++]; + next if (defined($line) && $line =~ /^-/); + $cnt--; + } + + return $line; +} + +sub get_stat_real { + my ($linenr, $lc) = @_; + + my $stat_real = raw_line($linenr, 0); + for (my $count = $linenr + 1; $count <= $lc; $count++) { + $stat_real = $stat_real . "\n" . raw_line($count, 0); + } + + return $stat_real; +} + +sub get_stat_here { + my ($linenr, $cnt, $here) = @_; + + my $herectx = $here . "\n"; + for (my $n = 0; $n < $cnt; $n++) { + $herectx .= raw_line($linenr, $n) . "\n"; + } + + return $herectx; +} + +sub cat_vet { + my ($vet) = @_; + my ($res, $coded); + + $res = ''; + while ($vet =~ /([^[:cntrl:]]*)([[:cntrl:]]|$)/g) { + $res .= $1; + if ($2 ne '') { + $coded = sprintf("^%c", unpack('C', $2) + 64); + $res .= $coded; + } + } + $res =~ s/$/\$/; + + return $res; +} + +my $av_preprocessor = 0; +my $av_pending; +my @av_paren_type; +my $av_pend_colon; + +sub annotate_reset { + $av_preprocessor = 0; + $av_pending = '_'; + @av_paren_type = ('E'); + $av_pend_colon = 'O'; +} + +sub annotate_values { + my ($stream, $type) = @_; + + my $res; + my $var = '_' x length($stream); + my $cur = $stream; + + print "$stream\n" if ($dbg_values > 1); + + while (length($cur)) { + @av_paren_type = ('E') if ($#av_paren_type < 0); + print " <" . join('', @av_paren_type) . + "> <$type> <$av_pending>" if ($dbg_values > 1); + if ($cur =~ /^(\s+)/o) { + print "WS($1)\n" if ($dbg_values > 1); + if ($1 =~ /\n/ && $av_preprocessor) { + $type = pop(@av_paren_type); + $av_preprocessor = 0; + } + + } elsif ($cur =~ /^(\(\s*$Type\s*)\)/ && $av_pending eq '_') { + print "CAST($1)\n" if ($dbg_values > 1); + push(@av_paren_type, $type); + $type = 'c'; + + } elsif ($cur =~ /^($Type)\s*(?:$Ident|,|\)|\(|\s*$)/) { + print "DECLARE($1)\n" if ($dbg_values > 1); + $type = 'T'; + + } elsif ($cur =~ /^($Modifier)\s*/) { + print "MODIFIER($1)\n" if ($dbg_values > 1); + $type = 'T'; + + } elsif ($cur =~ /^(\#\s*define\s*$Ident)(\(?)/o) { + print "DEFINE($1,$2)\n" if ($dbg_values > 1); + $av_preprocessor = 1; + push(@av_paren_type, $type); + if ($2 ne '') { + $av_pending = 'N'; + } + $type = 'E'; + + } elsif ($cur =~ /^(\#\s*(?:undef\s*$Ident|include\b))/o) { + print "UNDEF($1)\n" if ($dbg_values > 1); + $av_preprocessor = 1; + push(@av_paren_type, $type); + + } elsif ($cur =~ /^(\#\s*(?:ifdef|ifndef|if))/o) { + print "PRE_START($1)\n" if ($dbg_values > 1); + $av_preprocessor = 1; + + push(@av_paren_type, $type); + push(@av_paren_type, $type); + $type = 'E'; + + } elsif ($cur =~ /^(\#\s*(?:else|elif))/o) { + print "PRE_RESTART($1)\n" if ($dbg_values > 1); + $av_preprocessor = 1; + + push(@av_paren_type, $av_paren_type[$#av_paren_type]); + + $type = 'E'; + + } elsif ($cur =~ /^(\#\s*(?:endif))/o) { + print "PRE_END($1)\n" if ($dbg_values > 1); + + $av_preprocessor = 1; + + # Assume all arms of the conditional end as this + # one does, and continue as if the #endif was not here. + pop(@av_paren_type); + push(@av_paren_type, $type); + $type = 'E'; + + } elsif ($cur =~ /^(\\\n)/o) { + print "PRECONT($1)\n" if ($dbg_values > 1); + + } elsif ($cur =~ /^(__attribute__)\s*\(?/o) { + print "ATTR($1)\n" if ($dbg_values > 1); + $av_pending = $type; + $type = 'N'; + + } elsif ($cur =~ /^(sizeof)\s*(\()?/o) { + print "SIZEOF($1)\n" if ($dbg_values > 1); + if (defined $2) { + $av_pending = 'V'; + } + $type = 'N'; + + } elsif ($cur =~ /^(if|while|for)\b/o) { + print "COND($1)\n" if ($dbg_values > 1); + $av_pending = 'E'; + $type = 'N'; + + } elsif ($cur =~/^(case)/o) { + print "CASE($1)\n" if ($dbg_values > 1); + $av_pend_colon = 'C'; + $type = 'N'; + + } elsif ($cur =~/^(return|else|goto|typeof|__typeof__)\b/o) { + print "KEYWORD($1)\n" if ($dbg_values > 1); + $type = 'N'; + + } elsif ($cur =~ /^(\()/o) { + print "PAREN('$1')\n" if ($dbg_values > 1); + push(@av_paren_type, $av_pending); + $av_pending = '_'; + $type = 'N'; + + } elsif ($cur =~ /^(\))/o) { + my $new_type = pop(@av_paren_type); + if ($new_type ne '_') { + $type = $new_type; + print "PAREN('$1') -> $type\n" + if ($dbg_values > 1); + } else { + print "PAREN('$1')\n" if ($dbg_values > 1); + } + + } elsif ($cur =~ /^($Ident)\s*\(/o) { + print "FUNC($1)\n" if ($dbg_values > 1); + $type = 'V'; + $av_pending = 'V'; + + } elsif ($cur =~ /^($Ident\s*):(?:\s*\d+\s*(,|=|;))?/) { + if (defined $2 && $type eq 'C' || $type eq 'T') { + $av_pend_colon = 'B'; + } elsif ($type eq 'E') { + $av_pend_colon = 'L'; + } + print "IDENT_COLON($1,$type>$av_pend_colon)\n" if ($dbg_values > 1); + $type = 'V'; + + } elsif ($cur =~ /^($Ident|$Constant)/o) { + print "IDENT($1)\n" if ($dbg_values > 1); + $type = 'V'; + + } elsif ($cur =~ /^($Assignment)/o) { + print "ASSIGN($1)\n" if ($dbg_values > 1); + $type = 'N'; + + } elsif ($cur =~/^(;|{|})/) { + print "END($1)\n" if ($dbg_values > 1); + $type = 'E'; + $av_pend_colon = 'O'; + + } elsif ($cur =~/^(,)/) { + print "COMMA($1)\n" if ($dbg_values > 1); + $type = 'C'; + + } elsif ($cur =~ /^(\?)/o) { + print "QUESTION($1)\n" if ($dbg_values > 1); + $type = 'N'; + + } elsif ($cur =~ /^(:)/o) { + print "COLON($1,$av_pend_colon)\n" if ($dbg_values > 1); + + substr($var, length($res), 1, $av_pend_colon); + if ($av_pend_colon eq 'C' || $av_pend_colon eq 'L') { + $type = 'E'; + } else { + $type = 'N'; + } + $av_pend_colon = 'O'; + + } elsif ($cur =~ /^(\[)/o) { + print "CLOSE($1)\n" if ($dbg_values > 1); + $type = 'N'; + + } elsif ($cur =~ /^(-(?![->])|\+(?!\+)|\*|\&\&|\&)/o) { + my $variant; + + print "OPV($1)\n" if ($dbg_values > 1); + if ($type eq 'V') { + $variant = 'B'; + } else { + $variant = 'U'; + } + + substr($var, length($res), 1, $variant); + $type = 'N'; + + } elsif ($cur =~ /^($Operators)/o) { + print "OP($1)\n" if ($dbg_values > 1); + if ($1 ne '++' && $1 ne '--') { + $type = 'N'; + } + + } elsif ($cur =~ /(^.)/o) { + print "C($1)\n" if ($dbg_values > 1); + } + if (defined $1) { + $cur = substr($cur, length($1)); + $res .= $type x length($1); + } + } + + return ($res, $var); +} + +sub possible { + my ($possible, $line) = @_; + my $notPermitted = qr{(?: + ^(?: + $Modifier| + $Storage| + $Type| + DEFINE_\S+ + )$| + ^(?: + goto| + return| + case| + else| + asm|__asm__| + do| + \#| + \#\#| + )(?:\s|$)| + ^(?:typedef|struct|enum)\b + )}x; + warn "CHECK<$possible> ($line)\n" if ($dbg_possible > 2); + if ($possible !~ $notPermitted) { + # Check for modifiers. + $possible =~ s/\s*$Storage\s*//g; + $possible =~ s/\s*$Sparse\s*//g; + if ($possible =~ /^\s*$/) { + + } elsif ($possible =~ /\s/) { + $possible =~ s/\s*$Type\s*//g; + for my $modifier (split(' ', $possible)) { + if ($modifier !~ $notPermitted) { + warn "MODIFIER: $modifier ($possible) ($line)\n" if ($dbg_possible); + push(@modifierListFile, $modifier); + } + } + + } else { + warn "POSSIBLE: $possible ($line)\n" if ($dbg_possible); + push(@typeListFile, $possible); + } + build_types(); + } else { + warn "NOTPOSS: $possible ($line)\n" if ($dbg_possible > 1); + } +} + +my $prefix = ''; + +sub show_type { + my ($type) = @_; + + $type =~ tr/[a-z]/[A-Z]/; + + return defined $use_type{$type} if (scalar keys %use_type > 0); + + return !defined $ignore_type{$type}; +} + +sub report { + my ($level, $type, $msg) = @_; + + if (!show_type($type) || + (defined $tst_only && $msg !~ /\Q$tst_only\E/)) { + return 0; + } + my $output = ''; + if ($color) { + if ($level eq 'ERROR') { + $output .= RED; + } elsif ($level eq 'WARNING') { + $output .= YELLOW; + } else { + $output .= GREEN; + } + } + $output .= $prefix . $level . ':'; + if ($show_types) { + $output .= BLUE if ($color); + $output .= "$type:"; + } + $output .= RESET if ($color); + $output .= ' ' . $msg . "\n"; + + if ($showfile) { + my @lines = split("\n", $output, -1); + splice(@lines, 1, 1); + $output = join("\n", @lines); + } + + if ($terse) { + $output = (split('\n', $output))[0] . "\n"; + } + + if ($verbose && exists($verbose_messages{$type}) && + !exists($verbose_emitted{$type})) { + $output .= $verbose_messages{$type} . "\n\n"; + $verbose_emitted{$type} = 1; + } + + push(our @report, $output); + + return 1; +} + +sub report_dump { + our @report; +} + +sub fixup_current_range { + my ($lineRef, $offset, $length) = @_; + + if ($$lineRef =~ /^\@\@ -\d+,\d+ \+(\d+),(\d+) \@\@/) { + my $o = $1; + my $l = $2; + my $no = $o + $offset; + my $nl = $l + $length; + $$lineRef =~ s/\+$o,$l \@\@/\+$no,$nl \@\@/; + } +} + +sub fix_inserted_deleted_lines { + my ($linesRef, $insertedRef, $deletedRef) = @_; + + my $range_last_linenr = 0; + my $delta_offset = 0; + + my $old_linenr = 0; + my $new_linenr = 0; + + my $next_insert = 0; + my $next_delete = 0; + + my @lines = (); + + my $inserted = @{$insertedRef}[$next_insert++]; + my $deleted = @{$deletedRef}[$next_delete++]; + + foreach my $old_line (@{$linesRef}) { + my $save_line = 1; + my $line = $old_line; #don't modify the array + if ($line =~ /^(?:\+\+\+|\-\-\-)\s+\S+/) { #new filename + $delta_offset = 0; + } elsif ($line =~ /^\@\@ -\d+,\d+ \+\d+,\d+ \@\@/) { #new hunk + $range_last_linenr = $new_linenr; + fixup_current_range(\$line, $delta_offset, 0); + } + + while (defined($deleted) && ${$deleted}{'LINENR'} == $old_linenr) { + $deleted = @{$deletedRef}[$next_delete++]; + $save_line = 0; + fixup_current_range(\$lines[$range_last_linenr], $delta_offset--, -1); + } + + while (defined($inserted) && ${$inserted}{'LINENR'} == $old_linenr) { + push(@lines, ${$inserted}{'LINE'}); + $inserted = @{$insertedRef}[$next_insert++]; + $new_linenr++; + fixup_current_range(\$lines[$range_last_linenr], $delta_offset++, 1); + } + + if ($save_line) { + push(@lines, $line); + $new_linenr++; + } + + $old_linenr++; + } + + return @lines; +} + +sub fix_insert_line { + my ($linenr, $line) = @_; + + my $inserted = { + LINENR => $linenr, + LINE => $line, + }; + push(@fixed_inserted, $inserted); +} + +sub fix_delete_line { + my ($linenr, $line) = @_; + + my $deleted = { + LINENR => $linenr, + LINE => $line, + }; + + push(@fixed_deleted, $deleted); +} + +sub ERROR { + my ($type, $msg) = @_; + + if (report("ERROR", $type, $msg)) { + our $clean = 0; + our $cnt_error++; + return 1; + } + return 0; +} +sub WARN { + my ($type, $msg) = @_; + + if (report("WARNING", $type, $msg)) { + our $clean = 0; + our $cnt_warn++; + return 1; + } + return 0; +} +sub CHK { + my ($type, $msg) = @_; + + if ($check && report("CHECK", $type, $msg)) { + our $clean = 0; + our $cnt_chk++; + return 1; + } + return 0; +} + +sub check_absolute_file { + my ($absolute, $herecurr) = @_; + my $file = $absolute; + + ##print "absolute<$absolute>\n"; + + # See if any suffix of this path is a path within the tree. + while ($file =~ s@^[^/]*/@@) { + if (-f "$root/$file") { + ##print "file<$file>\n"; + last; + } + } + if (! -f _) { + return 0; + } + + # It is, so see if the prefix is acceptable. + my $prefix = $absolute; + substr($prefix, -length($file)) = ''; + + ##print "prefix<$prefix>\n"; + if ($prefix ne ".../") { + WARN("USE_RELATIVE_PATH", + "use relative pathname instead of absolute in changelog text\n" . $herecurr); + } +} + +sub trim { + my ($string) = @_; + + $string =~ s/^\s+|\s+$//g; + + return $string; +} + +sub ltrim { + my ($string) = @_; + + $string =~ s/^\s+//; + + return $string; +} + +sub rtrim { + my ($string) = @_; + + $string =~ s/\s+$//; + + return $string; +} + +sub string_find_replace { + my ($string, $find, $replace) = @_; + + $string =~ s/$find/$replace/g; + + return $string; +} + +sub tabify { + my ($leading) = @_; + + my $source_indent = $tabsize; + my $max_spaces_before_tab = $source_indent - 1; + my $spaces_to_tab = " " x $source_indent; + + #convert leading spaces to tabs + 1 while $leading =~ s@^([\t]*)$spaces_to_tab@$1\t@g; + #Remove spaces before a tab + 1 while $leading =~ s@^([\t]*)( {1,$max_spaces_before_tab})\t@$1\t@g; + + return "$leading"; +} + +sub pos_last_openparen { + my ($line) = @_; + + my $pos = 0; + + my $opens = $line =~ tr/\(/\(/; + my $closes = $line =~ tr/\)/\)/; + + my $last_openparen = 0; + + if (($opens == 0) || ($closes >= $opens)) { + return -1; + } + + my $len = length($line); + + for ($pos = 0; $pos < $len; $pos++) { + my $string = substr($line, $pos); + if ($string =~ /^($FuncArg|$balanced_parens)/) { + $pos += length($1) - 1; + } elsif (substr($line, $pos, 1) eq '(') { + $last_openparen = $pos; + } elsif (index($string, '(') == -1) { + last; + } + } + + return length(expand_tabs(substr($line, 0, $last_openparen))) + 1; +} + +sub get_raw_comment { + my ($line, $rawline) = @_; + my $comment = ''; + + for my $i (0 .. (length($line) - 1)) { + if (substr($line, $i, 1) eq "$;") { + $comment .= substr($rawline, $i, 1); + } + } + + return $comment; +} + +sub exclude_global_initialisers { + my ($realfile) = @_; + + # Do not check for BPF programs (tools/testing/selftests/bpf/progs/*.c, samples/bpf/*_kern.c, *.bpf.c). + return $realfile =~ m@^tools/testing/selftests/bpf/progs/.*\.c$@ || + $realfile =~ m@^samples/bpf/.*_kern\.c$@ || + $realfile =~ m@/bpf/.*\.bpf\.c$@; +} + +sub is_userspace { + my ($realfile) = @_; + return ($realfile =~ m@^tools/@ || $realfile =~ m@^scripts/@); +} + +sub process { + my $filename = shift; + + my $linenr=0; + my $prevline=""; + my $prevrawline=""; + my $stashline=""; + my $stashrawline=""; + + my $length; + my $indent; + my $previndent=0; + my $stashindent=0; + + our $clean = 1; + my $signoff = 0; + my $fixes_tag = 0; + my $is_revert = 0; + my $needs_fixes_tag = ""; + my $author = ''; + my $authorsignoff = 0; + my $author_sob = ''; + my $is_patch = 0; + my $is_binding_patch = -1; + my $in_header_lines = $file ? 0 : 1; + my $in_commit_log = 0; #Scanning lines before patch + my $has_patch_separator = 0; #Found a --- line + my $has_commit_log = 0; #Encountered lines before patch + my $commit_log_lines = 0; #Number of commit log lines + my $commit_log_possible_stack_dump = 0; + my $commit_log_long_line = 0; + my $commit_log_has_diff = 0; + my $reported_maintainer_file = 0; + my $non_utf8_charset = 0; + + my $last_git_commit_id_linenr = -1; + + my $last_blank_line = 0; + my $last_coalesced_string_linenr = -1; + + our @report = (); + our $cnt_lines = 0; + our $cnt_error = 0; + our $cnt_warn = 0; + our $cnt_chk = 0; + + # Trace the real file/line as we go. + my $realfile = ''; + my $realline = 0; + my $realcnt = 0; + my $here = ''; + my $context_function; #undef'd unless there's a known function + my $in_comment = 0; + my $comment_edge = 0; + my $first_line = 0; + my $p1_prefix = ''; + + my $prev_values = 'E'; + + # suppression flags + my %suppress_ifbraces; + my %suppress_whiletrailers; + my %suppress_export; + my $suppress_statement = 0; + + my %signatures = (); + + # Pre-scan the patch sanitizing the lines. + # Pre-scan the patch looking for any __setup documentation. + # + my @setup_docs = (); + my $setup_docs = 0; + + my $camelcase_file_seeded = 0; + + my $checklicenseline = 1; + + sanitise_line_reset(); + my $line; + foreach my $rawline (@rawlines) { + $linenr++; + $line = $rawline; + + push(@fixed, $rawline) if ($fix); + + if ($rawline=~/^\+\+\+\s+(\S+)/) { + $setup_docs = 0; + if ($1 =~ m@Documentation/admin-guide/kernel-parameters.txt$@) { + $setup_docs = 1; + } + #next; + } + if ($rawline =~ /^\@\@ -\d+(?:,\d+)? \+(\d+)(,(\d+))? \@\@/) { + $realline=$1-1; + if (defined $2) { + $realcnt=$3+1; + } else { + $realcnt=1+1; + } + $in_comment = 0; + + # Guestimate if this is a continuing comment. Run + # the context looking for a comment "edge". If this + # edge is a close comment then we must be in a comment + # at context start. + my $edge; + my $cnt = $realcnt; + for (my $ln = $linenr + 1; $cnt > 0; $ln++) { + next if (defined $rawlines[$ln - 1] && + $rawlines[$ln - 1] =~ /^-/); + $cnt--; + #print "RAW<$rawlines[$ln - 1]>\n"; + last if (!defined $rawlines[$ln - 1]); + if ($rawlines[$ln - 1] =~ m@(/\*|\*/)@ && + $rawlines[$ln - 1] !~ m@"[^"]*(?:/\*|\*/)[^"]*"@) { + ($edge) = $1; + last; + } + } + if (defined $edge && $edge eq '*/') { + $in_comment = 1; + } + + # Guestimate if this is a continuing comment. If this + # is the start of a diff block and this line starts + # ' *' then it is very likely a comment. + if (!defined $edge && + $rawlines[$linenr] =~ m@^.\s*(?:\*\*+| \*)(?:\s|$)@) + { + $in_comment = 1; + } + + ##print "COMMENT:$in_comment edge<$edge> $rawline\n"; + sanitise_line_reset($in_comment); + + } elsif ($realcnt && $rawline =~ /^(?:\+| |$)/) { + # Standardise the strings and chars within the input to + # simplify matching -- only bother with positive lines. + $line = sanitise_line($rawline); + } + push(@lines, $line); + + if ($realcnt > 1) { + $realcnt-- if ($line =~ /^(?:\+| |$)/); + } else { + $realcnt = 0; + } + + #print "==>$rawline\n"; + #print "-->$line\n"; + + if ($setup_docs && $line =~ /^\+/) { + push(@setup_docs, $line); + } + } + + $prefix = ''; + + $realcnt = 0; + $linenr = 0; + $fixlinenr = -1; + foreach my $line (@lines) { + $linenr++; + $fixlinenr++; + my $sline = $line; #copy of $line + $sline =~ s/$;/ /g; #with comments as spaces + + my $rawline = $rawlines[$linenr - 1]; + my $raw_comment = get_raw_comment($line, $rawline); + +# check if it's a mode change, rename or start of a patch + if (!$in_commit_log && + ($line =~ /^ mode change [0-7]+ => [0-7]+ \S+\s*$/ || + ($line =~ /^rename (?:from|to) \S+\s*$/ || + $line =~ /^diff --git a\/[\w\/\.\_\-]+ b\/\S+\s*$/))) { + $is_patch = 1; + } + +#extract the line range in the file after the patch is applied + if (!$in_commit_log && + $line =~ /^\@\@ -\d+(?:,\d+)? \+(\d+)(,(\d+))? \@\@(.*)/) { + my $context = $4; + $is_patch = 1; + $first_line = $linenr + 1; + $realline=$1-1; + if (defined $2) { + $realcnt=$3+1; + } else { + $realcnt=1+1; + } + annotate_reset(); + $prev_values = 'E'; + + %suppress_ifbraces = (); + %suppress_whiletrailers = (); + %suppress_export = (); + $suppress_statement = 0; + if ($context =~ /\b(\w+)\s*\(/) { + $context_function = $1; + } else { + undef $context_function; + } + next; + +# track the line number as we move through the hunk, note that +# new versions of GNU diff omit the leading space on completely +# blank context lines so we need to count that too. + } elsif ($line =~ /^( |\+|$)/) { + $realline++; + $realcnt-- if ($realcnt != 0); + + # Measure the line length and indent. + ($length, $indent) = line_stats($rawline); + + # Track the previous line. + ($prevline, $stashline) = ($stashline, $line); + ($previndent, $stashindent) = ($stashindent, $indent); + ($prevrawline, $stashrawline) = ($stashrawline, $rawline); + + #warn "line<$line>\n"; + + } elsif ($realcnt == 1) { + $realcnt--; + } + + my $hunk_line = ($realcnt != 0); + + $here = "#$linenr: " if (!$file); + $here = "#$realline: " if ($file); + + my $found_file = 0; + # extract the filename as it passes + if ($line =~ /^diff --git.*?(\S+)$/) { + $realfile = $1; + $realfile =~ s@^([^/]*)/@@ if (!$file); + $in_commit_log = 0; + $found_file = 1; + } elsif ($line =~ /^\+\+\+\s+(\S+)/) { + $realfile = $1; + $realfile =~ s@^([^/]*)/@@ if (!$file); + $in_commit_log = 0; + + $p1_prefix = $1; + if (!$file && $tree && $p1_prefix ne '' && + -e "$root/$p1_prefix") { + WARN("PATCH_PREFIX", + "patch prefix '$p1_prefix' exists, appears to be a -p0 patch\n"); + } + + if ($realfile =~ m@^include/asm/@) { + ERROR("MODIFIED_INCLUDE_ASM", + "do not modify files in include/asm, change architecture specific files in arch//include/asm\n" . "$here$rawline\n"); + } + $found_file = 1; + } + +#make up the handle for any error we report on this line + if ($showfile) { + $prefix = "$realfile:$realline: " + } elsif ($emacs) { + if ($file) { + $prefix = "$filename:$realline: "; + } else { + $prefix = "$filename:$linenr: "; + } + } + + if ($found_file) { + if (is_maintained_obsolete($realfile)) { + WARN("OBSOLETE", + "$realfile is marked as 'obsolete' in the MAINTAINERS hierarchy. No unnecessary modifications please.\n"); + } + if ($realfile =~ m@^(?:drivers/net/|net/|drivers/staging/)@) { + $check = 1; + } else { + $check = $check_orig; + } + $checklicenseline = 1; + + if ($realfile !~ /^MAINTAINERS/) { + my $last_binding_patch = $is_binding_patch; + + $is_binding_patch = () = $realfile =~ m@^(?:Documentation/devicetree/|include/dt-bindings/)@; + + if (($last_binding_patch != -1) && + ($last_binding_patch ^ $is_binding_patch)) { + WARN("DT_SPLIT_BINDING_PATCH", + "DT binding docs and includes should be a separate patch. See: Documentation/devicetree/bindings/submitting-patches.rst\n"); + } + } + + next; + } + + $here .= "FILE: $realfile:$realline:" if ($realcnt != 0); + + my $hereline = "$here\n$rawline\n"; + my $herecurr = "$here\n$rawline\n"; + my $hereprev = "$here\n$prevrawline\n$rawline\n"; + + $cnt_lines++ if ($realcnt != 0); + +# Verify the existence of a commit log if appropriate +# 2 is used because a $signature is counted in $commit_log_lines + if ($in_commit_log) { + if ($line !~ /^\s*$/) { + $commit_log_lines++; #could be a $signature + } + } elsif ($has_commit_log && $commit_log_lines < 2) { + WARN("COMMIT_MESSAGE", + "Missing commit description - Add an appropriate one\n"); + $commit_log_lines = 2; #warn only once + } + +# Check if the commit log has what seems like a diff which can confuse patch + if ($in_commit_log && !$commit_log_has_diff && + (($line =~ m@^\s+diff\b.*a/([\w/]+)@ && + $line =~ m@^\s+diff\b.*a/[\w/]+\s+b/$1\b@) || + $line =~ m@^\s*(?:\-\-\-\s+a/|\+\+\+\s+b/)@ || + $line =~ m/^\s*\@\@ \-\d+,\d+ \+\d+,\d+ \@\@/)) { + ERROR("DIFF_IN_COMMIT_MSG", + "Avoid using diff content in the commit message - patch(1) might not work\n" . $herecurr); + $commit_log_has_diff = 1; + } + +# Check for incorrect file permissions + if ($line =~ /^new (file )?mode.*[7531]\d{0,2}$/) { + my $permhere = $here . "FILE: $realfile\n"; + if ($realfile !~ m@scripts/@ && + $realfile !~ /\.(py|pl|awk|sh)$/) { + ERROR("EXECUTE_PERMISSIONS", + "do not set execute permissions for source files\n" . $permhere); + } + } + +# Check the patch for a From: + if (decode("MIME-Header", $line) =~ /^From:\s*(.*)/) { + $author = $1; + my $curline = $linenr; + while(defined($rawlines[$curline]) && ($rawlines[$curline++] =~ /^[ \t]\s*(.*)/)) { + $author .= $1; + } + $author = encode("utf8", $author) if ($line =~ /=\?utf-8\?/i); + $author =~ s/"//g; + $author = reformat_email($author); + } + +# Check the patch for a signoff: + if ($line =~ /^\s*signed-off-by:\s*(.*)/i) { + $signoff++; + $in_commit_log = 0; + if ($author ne '' && $authorsignoff != 1) { + if (same_email_addresses($1, $author)) { + $authorsignoff = 1; + } else { + my $ctx = $1; + my ($email_name, $email_comment, $email_address, $comment1) = parse_email($ctx); + my ($author_name, $author_comment, $author_address, $comment2) = parse_email($author); + + if (lc $email_address eq lc $author_address && $email_name eq $author_name) { + $author_sob = $ctx; + $authorsignoff = 2; + } elsif (lc $email_address eq lc $author_address) { + $author_sob = $ctx; + $authorsignoff = 3; + } elsif ($email_name eq $author_name) { + $author_sob = $ctx; + $authorsignoff = 4; + + my $address1 = $email_address; + my $address2 = $author_address; + + if ($address1 =~ /(\S+)\+\S+(\@.*)/) { + $address1 = "$1$2"; + } + if ($address2 =~ /(\S+)\+\S+(\@.*)/) { + $address2 = "$1$2"; + } + if ($address1 eq $address2) { + $authorsignoff = 5; + } + } + } + } + } + +# Check for invalid patch separator + if ($in_commit_log && + $line =~ /^---.+/) { + if (ERROR("BAD_COMMIT_SEPARATOR", + "Invalid commit separator - some tools may have problems applying this\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/-/=/g; + } + } + +# Check for patch separator + if ($line =~ /^---$/) { + $has_patch_separator = 1; + $in_commit_log = 0; + } + +# Check if MAINTAINERS is being updated. If so, there's probably no need to +# emit the "does MAINTAINERS need updating?" message on file add/move/delete + if ($line =~ /^\s*MAINTAINERS\s*\|/) { + $reported_maintainer_file = 1; + } + +# Check signature styles + if (!$in_header_lines && + $line =~ /^(\s*)([a-z0-9_-]+by:|$signature_tags)(\s*)(.*)/i) { + my $space_before = $1; + my $sign_off = $2; + my $space_after = $3; + my $email = $4; + my $ucfirst_sign_off = ucfirst(lc($sign_off)); + + if ($sign_off !~ /$signature_tags/) { + my $suggested_signature = find_standard_signature($sign_off); + if ($suggested_signature eq "") { + WARN("BAD_SIGN_OFF", + "Non-standard signature: $sign_off\n" . $herecurr); + } else { + if (WARN("BAD_SIGN_OFF", + "Non-standard signature: '$sign_off' - perhaps '$suggested_signature'?\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/$sign_off/$suggested_signature/; + } + } + } + if (defined $space_before && $space_before ne "") { + if (WARN("BAD_SIGN_OFF", + "Do not use whitespace before $ucfirst_sign_off\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] = + "$ucfirst_sign_off $email"; + } + } + if ($sign_off =~ /-by:$/i && $sign_off ne $ucfirst_sign_off) { + if (WARN("BAD_SIGN_OFF", + "'$ucfirst_sign_off' is the preferred signature form\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] = + "$ucfirst_sign_off $email"; + } + + } + if (!defined $space_after || $space_after ne " ") { + if (WARN("BAD_SIGN_OFF", + "Use a single space after $ucfirst_sign_off\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] = + "$ucfirst_sign_off $email"; + } + } + + my ($email_name, $name_comment, $email_address, $comment) = parse_email($email); + my $suggested_email = format_email(($email_name, $name_comment, $email_address, $comment)); + if ($suggested_email eq "") { + ERROR("BAD_SIGN_OFF", + "Unrecognized email address: '$email'\n" . $herecurr); + } else { + my $dequoted = $suggested_email; + $dequoted =~ s/^"//; + $dequoted =~ s/" 1) { + WARN("BAD_SIGN_OFF", + "Use a single name comment in email: '$email'\n" . $herecurr); + } + + + # stable@vger.kernel.org or stable@kernel.org shouldn't + # have an email name. In addition comments should strictly + # begin with a # + if ($email =~ /^.*stable\@(?:vger\.)?kernel\.org/i) { + if (($comment ne "" && $comment !~ /^#.+/) || + ($email_name ne "")) { + my $cur_name = $email_name; + my $new_comment = $comment; + $cur_name =~ s/[a-zA-Z\s\-\"]+//g; + + # Remove brackets enclosing comment text + # and # from start of comments to get comment text + $new_comment =~ s/^\((.*)\)$/$1/; + $new_comment =~ s/^\[(.*)\]$/$1/; + $new_comment =~ s/^[\s\#]+|\s+$//g; + + $new_comment = trim("$new_comment $cur_name") if ($cur_name ne $new_comment); + $new_comment = " # $new_comment" if ($new_comment ne ""); + my $new_email = "$email_address$new_comment"; + + if (WARN("BAD_STABLE_ADDRESS_STYLE", + "Invalid email format for stable: '$email', prefer '$new_email'\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\Q$email\E/$new_email/; + } + } + } elsif ($comment ne "" && $comment !~ /^(?:#.+|\(.+\))$/) { + my $new_comment = $comment; + + # Extract comment text from within brackets or + # c89 style /*...*/ comments + $new_comment =~ s/^\[(.*)\]$/$1/; + $new_comment =~ s/^\/\*(.*)\*\/$/$1/; + + $new_comment = trim($new_comment); + $new_comment =~ s/^[^\w]$//; # Single lettered comment with non word character is usually a typo + $new_comment = "($new_comment)" if ($new_comment ne ""); + my $new_email = format_email($email_name, $name_comment, $email_address, $new_comment); + + if (WARN("BAD_SIGN_OFF", + "Unexpected content after email: '$email', should be: '$new_email'\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\Q$email\E/$new_email/; + } + } + } + +# Check for duplicate signatures + my $sig_nospace = $line; + $sig_nospace =~ s/\s//g; + $sig_nospace = lc($sig_nospace); + if (defined $signatures{$sig_nospace}) { + WARN("BAD_SIGN_OFF", + "Duplicate signature\n" . $herecurr); + } else { + $signatures{$sig_nospace} = 1; + } + +# Check Co-developed-by: immediately followed by Signed-off-by: with same name and email + if ($sign_off =~ /^co-developed-by:$/i) { + if ($email eq $author) { + WARN("BAD_SIGN_OFF", + "Co-developed-by: should not be used to attribute nominal patch author '$author'\n" . $herecurr); + } + if (!defined $lines[$linenr]) { + WARN("BAD_SIGN_OFF", + "Co-developed-by: must be immediately followed by Signed-off-by:\n" . $herecurr); + } elsif ($rawlines[$linenr] !~ /^signed-off-by:\s*(.*)/i) { + WARN("BAD_SIGN_OFF", + "Co-developed-by: must be immediately followed by Signed-off-by:\n" . $herecurr . $rawlines[$linenr] . "\n"); + } elsif ($1 ne $email) { + WARN("BAD_SIGN_OFF", + "Co-developed-by and Signed-off-by: name/email do not match\n" . $herecurr . $rawlines[$linenr] . "\n"); + } + } + +# check if Reported-by: is followed by a Closes: tag + if ($sign_off =~ /^reported(?:|-and-tested)-by:$/i) { + if (!defined $lines[$linenr]) { + WARN("BAD_REPORTED_BY_LINK", + "Reported-by: should be immediately followed by Closes: with a URL to the report\n" . $herecurr . "\n"); + } elsif ($rawlines[$linenr] !~ /^closes:\s*/i) { + WARN("BAD_REPORTED_BY_LINK", + "Reported-by: should be immediately followed by Closes: with a URL to the report\n" . $herecurr . $rawlines[$linenr] . "\n"); + } + } + } + +# These indicate a bug fix + if (!$in_header_lines && !$is_patch && + $line =~ /^This reverts commit/) { + $is_revert = 1; + } + + if (!$in_header_lines && !$is_patch && + $line =~ /((?:(?:BUG: K.|UB)SAN: |Call Trace:|stable\@|syzkaller))/) { + $needs_fixes_tag = $1; + } + +# Check Fixes: styles is correct + if (!$in_header_lines && + $line =~ /^\s*(fixes:?)\s*(?:commit\s*)?([0-9a-f]{5,40})(?:\s*($balanced_parens))?/i) { + my $tag = $1; + my $orig_commit = $2; + my $title; + my $title_has_quotes = 0; + $fixes_tag = 1; + if (defined $3) { + # Always strip leading/trailing parens then double quotes if existing + $title = substr($3, 1, -1); + if ($title =~ /^".*"$/) { + $title = substr($title, 1, -1); + $title_has_quotes = 1; + } + } else { + $title = "commit title" + } + + + my $tag_case = not ($tag eq "Fixes:"); + my $tag_space = not ($line =~ /^fixes:? [0-9a-f]{5,40} ($balanced_parens)/i); + + my $id_length = not ($orig_commit =~ /^[0-9a-f]{12,40}$/i); + my $id_case = not ($orig_commit !~ /[A-F]/); + + my $id = "0123456789ab"; + my ($cid, $ctitle) = git_commit_info($orig_commit, $id, + $title); + + if (defined($cid) && ($ctitle ne $title || $tag_case || $tag_space || $id_length || $id_case || !$title_has_quotes)) { + my $fixed = "Fixes: $cid (\"$ctitle\")"; + if (WARN("BAD_FIXES_TAG", + "Please use correct Fixes: style 'Fixes: <12+ chars of sha1> (\"\")' - ie: '$fixed'\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] = $fixed; + } + } + } + +# Check email subject for common tools that don't need to be mentioned + if ($in_header_lines && + $line =~ /^Subject:.*\b(?:checkpatch|sparse|smatch)\b[^:]/i) { + WARN("EMAIL_SUBJECT", + "A patch subject line should describe the change not the tool that found it\n" . $herecurr); + } + +# Check for Gerrit Change-Ids not in any patch context + if ($realfile eq '' && !$has_patch_separator && $line =~ /^\s*change-id:/i) { + if (ERROR("GERRIT_CHANGE_ID", + "Remove Gerrit Change-Id's before submitting upstream\n" . $herecurr) && + $fix) { + fix_delete_line($fixlinenr, $rawline); + } + } + +# Check if the commit log is in a possible stack dump + if ($in_commit_log && !$commit_log_possible_stack_dump && + ($line =~ /^\s*(?:WARNING:|BUG:)/ || + $line =~ /^\s*\[\s*\d+\.\d{6,6}\s*\]/ || + # timestamp + $line =~ /^\s*\[\<[0-9a-fA-F]{8,}\>\]/) || + $line =~ /^(?:\s+\w+:\s+[0-9a-fA-F]+){3,3}/ || + $line =~ /^\s*\#\d+\s*\[[0-9a-fA-F]+\]\s*\w+ at [0-9a-fA-F]+/) { + # stack dump address styles + $commit_log_possible_stack_dump = 1; + } + +# Check for line lengths > 75 in commit log, warn once + if ($in_commit_log && !$commit_log_long_line && + length($line) > 75 && + !($line =~ /^\s*[a-zA-Z0-9_\/\.]+\s+\|\s+\d+/ || + # file delta changes + $line =~ /^\s*(?:[\w\.\-\+]*\/)++[\w\.\-\+]+:/ || + # filename then : + $line =~ /^\s*(?:Fixes:|https?:|$link_tags_search|$signature_tags)/i || + # A Fixes:, link or signature tag line + $commit_log_possible_stack_dump)) { + WARN("COMMIT_LOG_LONG_LINE", + "Prefer a maximum 75 chars per line (possible unwrapped commit description?)\n" . $herecurr); + $commit_log_long_line = 1; + } + +# Reset possible stack dump if a blank line is found + if ($in_commit_log && $commit_log_possible_stack_dump && + $line =~ /^\s*$/) { + $commit_log_possible_stack_dump = 0; + } + +# Check for odd tags before a URI/URL + if ($in_commit_log && + $line =~ /^\s*(\w+:)\s*http/ && $1 !~ /^$link_tags_search$/) { + if ($1 =~ /^v(?:ersion)?\d+/i) { + WARN("COMMIT_LOG_VERSIONING", + "Patch version information should be after the --- line\n" . $herecurr); + } else { + WARN("COMMIT_LOG_USE_LINK", + "Unknown link reference '$1', use $link_tags_print instead\n" . $herecurr); + } + } + +# Check for misuse of the link tags + if ($in_commit_log && + $line =~ /^\s*(\w+:)\s*(\S+)/) { + my $tag = $1; + my $value = $2; + if ($tag =~ /^$link_tags_search$/ && $value !~ m{^https?://}) { + WARN("COMMIT_LOG_WRONG_LINK", + "'$tag' should be followed by a public http(s) link\n" . $herecurr); + } + } + +# Check for lines starting with a # + if ($in_commit_log && $line =~ /^#/) { + if (WARN("COMMIT_COMMENT_SYMBOL", + "Commit log lines starting with '#' are dropped by git as comments\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/^/ /; + } + } + +# Check for auto-generated unhandled placeholder text (mostly for cover letters) + if (($in_commit_log || $in_header_lines) && + $rawline =~ /(?:SUBJECT|BLURB) HERE/) { + ERROR("PLACEHOLDER_USE", + "Placeholder text detected\n" . $herecurr); + } + +# Check for git id commit length and improperly formed commit descriptions +# A correctly formed commit description is: +# commit <SHA-1 hash length 12+ chars> ("Complete commit subject") +# with the commit subject '("' prefix and '")' suffix +# This is a fairly compilicated block as it tests for what appears to be +# bare SHA-1 hash with minimum length of 5. It also avoids several types of +# possible SHA-1 matches. +# A commit match can span multiple lines so this block attempts to find a +# complete typical commit on a maximum of 3 lines + if ($perl_version_ok && + $in_commit_log && !$commit_log_possible_stack_dump && + $line !~ /^\s*(?:Link|Patchwork|http|https|BugLink|base-commit):/i && + $line !~ /^This reverts commit [0-9a-f]{7,40}/ && + (($line =~ /\bcommit\s+[0-9a-f]{5,}\b/i || + ($line =~ /\bcommit\s*$/i && defined($rawlines[$linenr]) && $rawlines[$linenr] =~ /^\s*[0-9a-f]{5,}\b/i)) || + ($line =~ /(?:\s|^)[0-9a-f]{12,40}(?:[\s"'\(\[]|$)/i && + $line !~ /[\<\[][0-9a-f]{12,40}[\>\]]/i && + $line !~ /\bfixes:\s*[0-9a-f]{12,40}/i))) { + my $init_char = "c"; + my $orig_commit = ""; + my $short = 1; + my $long = 0; + my $case = 1; + my $space = 1; + my $id = '0123456789ab'; + my $orig_desc = "commit description"; + my $description = ""; + my $herectx = $herecurr; + my $has_parens = 0; + my $has_quotes = 0; + + my $input = $line; + if ($line =~ /(?:\bcommit\s+[0-9a-f]{5,}|\bcommit\s*$)/i) { + for (my $n = 0; $n < 2; $n++) { + if ($input =~ /\bcommit\s+[0-9a-f]{5,}\s*($balanced_parens)/i) { + $orig_desc = $1; + $has_parens = 1; + # Always strip leading/trailing parens then double quotes if existing + $orig_desc = substr($orig_desc, 1, -1); + if ($orig_desc =~ /^".*"$/) { + $orig_desc = substr($orig_desc, 1, -1); + $has_quotes = 1; + } + last; + } + last if ($#lines < $linenr + $n); + $input .= " " . trim($rawlines[$linenr + $n]); + $herectx .= "$rawlines[$linenr + $n]\n"; + } + $herectx = $herecurr if (!$has_parens); + } + + if ($input =~ /\b(c)ommit\s+([0-9a-f]{5,})\b/i) { + $init_char = $1; + $orig_commit = lc($2); + $short = 0 if ($input =~ /\bcommit\s+[0-9a-f]{12,40}/i); + $long = 1 if ($input =~ /\bcommit\s+[0-9a-f]{41,}/i); + $space = 0 if ($input =~ /\bcommit [0-9a-f]/i); + $case = 0 if ($input =~ /\b[Cc]ommit\s+[0-9a-f]{5,40}[^A-F]/); + } elsif ($input =~ /\b([0-9a-f]{12,40})\b/i) { + $orig_commit = lc($1); + } + + ($id, $description) = git_commit_info($orig_commit, + $id, $orig_desc); + + if (defined($id) && + ($short || $long || $space || $case || ($orig_desc ne $description) || !$has_quotes) && + $last_git_commit_id_linenr != $linenr - 1) { + ERROR("GIT_COMMIT_ID", + "Please use git commit description style 'commit <12+ chars of sha1> (\"<title line>\")' - ie: '${init_char}ommit $id (\"$description\")'\n" . $herectx); + } + #don't report the next line if this line ends in commit and the sha1 hash is the next line + $last_git_commit_id_linenr = $linenr if ($line =~ /\bcommit\s*$/i); + } + +# Check for mailing list archives other than lore.kernel.org + if ($rawline =~ m{http.*\b$obsolete_archives}) { + WARN("PREFER_LORE_ARCHIVE", + "Use lore.kernel.org archive links when possible - see https://lore.kernel.org/lists.html\n" . $herecurr); + } + +# Check for added, moved or deleted files + if (!$reported_maintainer_file && !$in_commit_log && + ($line =~ /^(?:new|deleted) file mode\s*\d+\s*$/ || + $line =~ /^rename (?:from|to) [\w\/\.\-]+\s*$/ || + ($line =~ /\{\s*([\w\/\.\-]*)\s*\=\>\s*([\w\/\.\-]*)\s*\}/ && + (defined($1) || defined($2))))) { + $is_patch = 1; + $reported_maintainer_file = 1; + WARN("FILE_PATH_CHANGES", + "added, moved or deleted file(s), does MAINTAINERS need updating?\n" . $herecurr); + } + +# Check for adding new DT bindings not in schema format + if (!$in_commit_log && + ($line =~ /^new file mode\s*\d+\s*$/) && + ($realfile =~ m@^Documentation/devicetree/bindings/.*\.txt$@)) { + WARN("DT_SCHEMA_BINDING_PATCH", + "DT bindings should be in DT schema format. See: Documentation/devicetree/bindings/writing-schema.rst\n"); + } + +# Check for wrappage within a valid hunk of the file + if ($realcnt != 0 && $line !~ m{^(?:\+|-| |\\ No newline|$)}) { + ERROR("CORRUPTED_PATCH", + "patch seems to be corrupt (line wrapped?)\n" . + $herecurr) if (!$emitted_corrupt++); + } + +# UTF-8 regex found at http://www.w3.org/International/questions/qa-forms-utf-8.en.php + if (($realfile =~ /^$/ || $line =~ /^\+/) && + $rawline !~ m/^$UTF8*$/) { + my ($utf8_prefix) = ($rawline =~ /^($UTF8*)/); + + my $blank = copy_spacing($rawline); + my $ptr = substr($blank, 0, length($utf8_prefix)) . "^"; + my $hereptr = "$hereline$ptr\n"; + + CHK("INVALID_UTF8", + "Invalid UTF-8, patch and commit message should be encoded in UTF-8\n" . $hereptr); + } + +# Check if it's the start of a commit log +# (not a header line and we haven't seen the patch filename) + if ($in_header_lines && $realfile =~ /^$/ && + !($rawline =~ /^\s+(?:\S|$)/ || + $rawline =~ /^(?:commit\b|from\b|[\w-]+:)/i)) { + $in_header_lines = 0; + $in_commit_log = 1; + $has_commit_log = 1; + } + +# Check if there is UTF-8 in a commit log when a mail header has explicitly +# declined it, i.e defined some charset where it is missing. + if ($in_header_lines && + $rawline =~ /^Content-Type:.+charset="(.+)".*$/ && + $1 !~ /utf-8/i) { + $non_utf8_charset = 1; + } + + if ($in_commit_log && $non_utf8_charset && $realfile =~ /^$/ && + $rawline =~ /$NON_ASCII_UTF8/) { + WARN("UTF8_BEFORE_PATCH", + "8-bit UTF-8 used in possible commit log\n" . $herecurr); + } + +# Check for absolute kernel paths in commit message + if ($tree && $in_commit_log) { + while ($line =~ m{(?:^|\s)(/\S*)}g) { + my $file = $1; + + if ($file =~ m{^(.*?)(?::\d+)+:?$} && + check_absolute_file($1, $herecurr)) { + # + } else { + check_absolute_file($file, $herecurr); + } + } + } + +# Check for various typo / spelling mistakes + if (defined($misspellings) && + ($in_commit_log || $line =~ /^(?:\+|Subject:)/i)) { + my $rawline_utf8 = decode("utf8", $rawline); + while ($rawline_utf8 =~ /(?:^|[^\w\-'`])($misspellings)(?:[^\w\-'`]|$)/gi) { + my $typo = $1; + my $blank = copy_spacing($rawline_utf8); + my $ptr = substr($blank, 0, $-[1]) . "^" x length($typo); + my $hereptr = "$hereline$ptr\n"; + my $typo_fix = $spelling_fix{lc($typo)}; + $typo_fix = ucfirst($typo_fix) if ($typo =~ /^[A-Z]/); + $typo_fix = uc($typo_fix) if ($typo =~ /^[A-Z]+$/); + my $msg_level = \&WARN; + $msg_level = \&CHK if ($file); + if (&{$msg_level}("TYPO_SPELLING", + "'$typo' may be misspelled - perhaps '$typo_fix'?\n" . $hereptr) && + $fix) { + $fixed[$fixlinenr] =~ s/(^|[^A-Za-z@])($typo)($|[^A-Za-z@])/$1$typo_fix$3/; + } + } + } + +# check for invalid commit id + if ($in_commit_log && $line =~ /(^fixes:|\bcommit)\s+([0-9a-f]{6,40})\b/i) { + my $id; + my $description; + ($id, $description) = git_commit_info($2, undef, undef); + if (!defined($id)) { + WARN("UNKNOWN_COMMIT_ID", + "Unknown commit id '$2', maybe rebased or not pulled?\n" . $herecurr); + } + } + +# check for repeated words separated by a single space +# avoid false positive from list command eg, '-rw-r--r-- 1 root root' + if (($rawline =~ /^\+/ || $in_commit_log) && + $rawline !~ /[bcCdDlMnpPs\?-][rwxsStT-]{9}/) { + pos($rawline) = 1 if (!$in_commit_log); + while ($rawline =~ /\b($word_pattern) (?=($word_pattern))/g) { + + my $first = $1; + my $second = $2; + my $start_pos = $-[1]; + my $end_pos = $+[2]; + if ($first =~ /(?:struct|union|enum)/) { + pos($rawline) += length($first) + length($second) + 1; + next; + } + + next if (lc($first) ne lc($second)); + next if ($first eq 'long'); + + # check for character before and after the word matches + my $start_char = ''; + my $end_char = ''; + $start_char = substr($rawline, $start_pos - 1, 1) if ($start_pos > ($in_commit_log ? 0 : 1)); + $end_char = substr($rawline, $end_pos, 1) if ($end_pos < length($rawline)); + + next if ($start_char =~ /^\S$/); + next if (index(" \t.,;?!", $end_char) == -1); + + # avoid repeating hex occurrences like 'ff ff fe 09 ...' + if ($first =~ /\b[0-9a-f]{2,}\b/i) { + next if (!exists($allow_repeated_words{lc($first)})); + } + + if (WARN("REPEATED_WORD", + "Possible repeated word: '$first'\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\b$first $second\b/$first/; + } + } + + # if it's a repeated word on consecutive lines in a comment block + if ($prevline =~ /$;+\s*$/ && + $prevrawline =~ /($word_pattern)\s*$/) { + my $last_word = $1; + if ($rawline =~ /^\+\s*\*\s*$last_word /) { + if (WARN("REPEATED_WORD", + "Possible repeated word: '$last_word'\n" . $hereprev) && + $fix) { + $fixed[$fixlinenr] =~ s/(\+\s*\*\s*)$last_word /$1/; + } + } + } + } + +# ignore non-hunk lines and lines being removed + next if (!$hunk_line || $line =~ /^-/); + +#trailing whitespace + if ($line =~ /^\+.*\015/) { + my $herevet = "$here\n" . cat_vet($rawline) . "\n"; + if (ERROR("DOS_LINE_ENDINGS", + "DOS line endings\n" . $herevet) && + $fix) { + $fixed[$fixlinenr] =~ s/[\s\015]+$//; + } + } elsif ($rawline =~ /^\+.*\S\s+$/ || $rawline =~ /^\+\s+$/) { + my $herevet = "$here\n" . cat_vet($rawline) . "\n"; + if (ERROR("TRAILING_WHITESPACE", + "trailing whitespace\n" . $herevet) && + $fix) { + $fixed[$fixlinenr] =~ s/\s+$//; + } + + $rpt_cleaners = 1; + } + +# Check for FSF mailing addresses. + if ($rawline =~ /\bwrite to the Free/i || + $rawline =~ /\b675\s+Mass\s+Ave/i || + $rawline =~ /\b59\s+Temple\s+Pl/i || + $rawline =~ /\b51\s+Franklin\s+St/i) { + my $herevet = "$here\n" . cat_vet($rawline) . "\n"; + my $msg_level = \&ERROR; + $msg_level = \&CHK if ($file); + &{$msg_level}("FSF_MAILING_ADDRESS", + "Do not include the paragraph about writing to the Free Software Foundation's mailing address from the sample GPL notice. The FSF has changed addresses in the past, and may do so again. Linux already includes a copy of the GPL.\n" . $herevet) + } + +# check for Kconfig help text having a real description +# Only applies when adding the entry originally, after that we do not have +# sufficient context to determine whether it is indeed long enough. + if ($realfile =~ /Kconfig/ && + # 'choice' is usually the last thing on the line (though + # Kconfig supports named choices), so use a word boundary + # (\b) rather than a whitespace character (\s) + $line =~ /^\+\s*(?:config|menuconfig|choice)\b/) { + my $ln = $linenr; + my $needs_help = 0; + my $has_help = 0; + my $help_length = 0; + while (defined $lines[$ln]) { + my $f = $lines[$ln++]; + + next if ($f =~ /^-/); + last if ($f !~ /^[\+ ]/); # !patch context + + if ($f =~ /^\+\s*(?:bool|tristate|prompt)\s*["']/) { + $needs_help = 1; + next; + } + if ($f =~ /^\+\s*help\s*$/) { + $has_help = 1; + next; + } + + $f =~ s/^.//; # strip patch context [+ ] + $f =~ s/#.*//; # strip # directives + $f =~ s/^\s+//; # strip leading blanks + next if ($f =~ /^$/); # skip blank lines + + # At the end of this Kconfig block: + # This only checks context lines in the patch + # and so hopefully shouldn't trigger false + # positives, even though some of these are + # common words in help texts + if ($f =~ /^(?:config|menuconfig|choice|endchoice| + if|endif|menu|endmenu|source)\b/x) { + last; + } + $help_length++ if ($has_help); + } + if ($needs_help && + $help_length < $min_conf_desc_length) { + my $stat_real = get_stat_real($linenr, $ln - 1); + WARN("CONFIG_DESCRIPTION", + "please write a help paragraph that fully describes the config symbol with at least $min_conf_desc_length lines\n" . "$here\n$stat_real\n"); + } + } + +# check MAINTAINERS entries + if ($realfile =~ /^MAINTAINERS$/) { +# check MAINTAINERS entries for the right form + if ($rawline =~ /^\+[A-Z]:/ && + $rawline !~ /^\+[A-Z]:\t\S/) { + if (WARN("MAINTAINERS_STYLE", + "MAINTAINERS entries use one tab after TYPE:\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/^(\+[A-Z]):\s*/$1:\t/; + } + } +# check MAINTAINERS entries for the right ordering too + my $preferred_order = 'MRLSWQBCPTFXNK'; + if ($rawline =~ /^\+[A-Z]:/ && + $prevrawline =~ /^[\+ ][A-Z]:/) { + $rawline =~ /^\+([A-Z]):\s*(.*)/; + my $cur = $1; + my $curval = $2; + $prevrawline =~ /^[\+ ]([A-Z]):\s*(.*)/; + my $prev = $1; + my $prevval = $2; + my $curindex = index($preferred_order, $cur); + my $previndex = index($preferred_order, $prev); + if ($curindex < 0) { + WARN("MAINTAINERS_STYLE", + "Unknown MAINTAINERS entry type: '$cur'\n" . $herecurr); + } else { + if ($previndex >= 0 && $curindex < $previndex) { + WARN("MAINTAINERS_STYLE", + "Misordered MAINTAINERS entry - list '$cur:' before '$prev:'\n" . $hereprev); + } elsif ((($prev eq 'F' && $cur eq 'F') || + ($prev eq 'X' && $cur eq 'X')) && + ($prevval cmp $curval) > 0) { + WARN("MAINTAINERS_STYLE", + "Misordered MAINTAINERS entry - list file patterns in alphabetic order\n" . $hereprev); + } + } + } + } + +# check for DT compatible documentation + if (defined $root && + (($realfile =~ /\.dtsi?$/ && $line =~ /^\+\s*compatible\s*=\s*\"/) || + ($realfile =~ /\.[ch]$/ && $line =~ /^\+.*\.compatible\s*=\s*\"/))) { + + my @compats = $rawline =~ /\"([a-zA-Z0-9\-\,\.\+_]+)\"/g; + + my $dt_path = $root . "/Documentation/devicetree/bindings/"; + my $vp_file = $dt_path . "vendor-prefixes.yaml"; + + foreach my $compat (@compats) { + my $compat2 = $compat; + $compat2 =~ s/\,[a-zA-Z0-9]*\-/\,<\.\*>\-/; + my $compat3 = $compat; + $compat3 =~ s/\,([a-z]*)[0-9]*\-/\,$1<\.\*>\-/; + `grep -Erq "$compat|$compat2|$compat3" $dt_path`; + if ( $? >> 8 ) { + WARN("UNDOCUMENTED_DT_STRING", + "DT compatible string \"$compat\" appears un-documented -- check $dt_path\n" . $herecurr); + } + + next if $compat !~ /^([a-zA-Z0-9\-]+)\,/; + my $vendor = $1; + `grep -Eq "\\"\\^\Q$vendor\E,\\.\\*\\":" $vp_file`; + if ( $? >> 8 ) { + WARN("UNDOCUMENTED_DT_STRING", + "DT compatible string vendor \"$vendor\" appears un-documented -- check $vp_file\n" . $herecurr); + } + } + } + +# Check for RGMII phy-mode with delay on PCB + if ($realfile =~ /\.(dts|dtsi|dtso)$/ && + $line =~ /^\+\s*(phy-mode|phy-connection-type)\s*=\s*"/ && + !ctx_has_comment($first_line, $linenr)) { + my $prop = $1; + my $mode = get_quoted_string($line, $rawline); + if ($mode =~ /^"rgmii(?:|-rxid|-txid)"$/) { + WARN("UNCOMMENTED_RGMII_MODE", + "$prop $mode without comment -- delays on the PCB should be described, otherwise use \"rgmii-id\"\n" . $herecurr); + } + } + +# check for using SPDX license tag at beginning of files + if ($realline == $checklicenseline) { + if ($rawline =~ /^[ \+]\s*\#\!\s*\//) { + $checklicenseline = 2; + } elsif ($rawline =~ /^\+/) { + my $comment = ""; + if ($realfile =~ /\.(h|s|S)$/) { + $comment = '/*'; + } elsif ($realfile =~ /\.(c|rs|dts|dtsi)$/) { + $comment = '//'; + } elsif (($checklicenseline == 2) || $realfile =~ /\.(sh|pl|py|awk|tc|yaml)$/) { + $comment = '#'; + } elsif ($realfile =~ /\.rst$/) { + $comment = '..'; + } + +# check SPDX comment style for .[chsS] files + if ($realfile =~ /\.[chsS]$/ && + $rawline =~ /SPDX-License-Identifier:/ && + $rawline !~ m@^\+\s*\Q$comment\E\s*@) { + WARN("SPDX_LICENSE_TAG", + "Improper SPDX comment style for '$realfile', please use '$comment' instead\n" . $herecurr); + } + + if ($comment !~ /^$/ && + $rawline !~ m@^\+\Q$comment\E SPDX-License-Identifier: @) { + WARN("SPDX_LICENSE_TAG", + "Missing or malformed SPDX-License-Identifier tag in line $checklicenseline\n" . $herecurr); + } elsif ($rawline =~ /(SPDX-License-Identifier: .*)/) { + my $spdx_license = $1; + if (!is_SPDX_License_valid($spdx_license)) { + WARN("SPDX_LICENSE_TAG", + "'$spdx_license' is not supported in LICENSES/...\n" . $herecurr); + } + if ($realfile =~ m@^Documentation/devicetree/bindings/@ && + $spdx_license !~ /GPL-2\.0(?:-only)? OR BSD-2-Clause/) { + my $msg_level = \&WARN; + $msg_level = \&CHK if ($file); + if (&{$msg_level}("SPDX_LICENSE_TAG", + + "DT binding documents should be licensed (GPL-2.0-only OR BSD-2-Clause)\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/SPDX-License-Identifier: .*/SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)/; + } + } + if ($realfile =~ m@^include/dt-bindings/@ && + $spdx_license !~ /GPL-2\.0(?:-only)? OR \S+/) { + WARN("SPDX_LICENSE_TAG", + "DT binding headers should be licensed (GPL-2.0-only OR .*)\n" . $herecurr); + } + } + } + } + +# check for embedded filenames + if ($rawline =~ /^\+.*\b\Q$realfile\E\b/) { + WARN("EMBEDDED_FILENAME", + "It's generally not useful to have the filename in the file\n" . $herecurr); + } + +# check we are in a valid source file if not then ignore this hunk + next if ($realfile !~ /\.(h|c|rs|s|S|sh|dtsi|dts)$/); + +# check for using SPDX-License-Identifier on the wrong line number + if ($realline != $checklicenseline && + $rawline =~ /\bSPDX-License-Identifier:/ && + substr($line, @-, @+ - @-) eq "$;" x (@+ - @-)) { + WARN("SPDX_LICENSE_TAG", + "Misplaced SPDX-License-Identifier tag - use line $checklicenseline instead\n" . $herecurr); + } + +# line length limit (with some exclusions) +# +# There are a few types of lines that may extend beyond $max_line_length: +# logging functions like pr_info that end in a string +# lines with a single string +# #defines that are a single string +# lines with an RFC3986 like URL +# +# There are 3 different line length message types: +# LONG_LINE_COMMENT a comment starts before but extends beyond $max_line_length +# LONG_LINE_STRING a string starts before but extends beyond $max_line_length +# LONG_LINE all other lines longer than $max_line_length +# +# if LONG_LINE is ignored, the other 2 types are also ignored +# + + if ($line =~ /^\+/ && $length > $max_line_length) { + my $msg_type = "LONG_LINE"; + + # Check the allowed long line types first + + # logging functions that end in a string that starts + # before $max_line_length + if ($line =~ /^\+\s*$logFunctions\s*\(\s*(?:(?:KERN_\S+\s*|[^"]*))?($String\s*(?:|,|\)\s*;)\s*)$/ && + length(expand_tabs(substr($line, 1, length($line) - length($1) - 1))) <= $max_line_length) { + $msg_type = ""; + + # lines with only strings (w/ possible termination) + # #defines with only strings + } elsif ($line =~ /^\+\s*$String\s*(?:\s*|,|\)\s*;)\s*$/ || + $line =~ /^\+\s*#\s*define\s+\w+\s+$String$/) { + $msg_type = ""; + + # More special cases + } elsif ($line =~ /^\+.*\bEFI_GUID\s*\(/ || + $line =~ /^\+\s*(?:\w+)?\s*DEFINE_PER_CPU/) { + $msg_type = ""; + + # URL ($rawline is used in case the URL is in a comment) + } elsif ($rawline =~ /^\+.*\b[a-z][\w\.\+\-]*:\/\/\S+/i) { + $msg_type = ""; + + # Otherwise set the alternate message types + + # a comment starts before $max_line_length + } elsif ($line =~ /($;[\s$;]*)$/ && + length(expand_tabs(substr($line, 1, length($line) - length($1) - 1))) <= $max_line_length) { + $msg_type = "LONG_LINE_COMMENT" + + # a quoted string starts before $max_line_length + } elsif ($sline =~ /\s*($String(?:\s*(?:\\|,\s*|\)\s*;\s*))?)$/ && + length(expand_tabs(substr($line, 1, length($line) - length($1) - 1))) <= $max_line_length) { + $msg_type = "LONG_LINE_STRING" + } + + if ($msg_type ne "" && + show_type("LONG_LINE") && show_type($msg_type)) { + my $msg_level = \&WARN; + $msg_level = \&CHK if ($file); + &{$msg_level}($msg_type, + "line length of $length exceeds $max_line_length columns\n" . $herecurr); + } + } + +# check for adding lines without a newline. + if ($line =~ /^\+/ && defined $lines[$linenr] && $lines[$linenr] =~ /^\\ No newline at end of file/) { + if (WARN("MISSING_EOF_NEWLINE", + "adding a line without newline at end of file\n" . $herecurr) && + $fix) { + fix_delete_line($fixlinenr+1, "No newline at end of file"); + } + } + +# check for .L prefix local symbols in .S files + if ($realfile =~ /\.S$/ && + $line =~ /^\+\s*(?:[A-Z]+_)?SYM_[A-Z]+_(?:START|END)(?:_[A-Z_]+)?\s*\(\s*\.L/) { + WARN("AVOID_L_PREFIX", + "Avoid using '.L' prefixed local symbol names for denoting a range of code via 'SYM_*_START/END' annotations; see Documentation/core-api/asm-annotations.rst\n" . $herecurr); + } + +# check we are in a valid source file C or perl if not then ignore this hunk + next if ($realfile !~ /\.(h|c|pl|dtsi|dts)$/); + +# at the beginning of a line any tabs must come first and anything +# more than $tabsize must use tabs. + if ($rawline =~ /^\+\s* \t\s*\S/ || + $rawline =~ /^\+\s* \s*/) { + my $herevet = "$here\n" . cat_vet($rawline) . "\n"; + $rpt_cleaners = 1; + if (ERROR("CODE_INDENT", + "code indent should use tabs where possible\n" . $herevet) && + $fix) { + $fixed[$fixlinenr] =~ s/^\+([ \t]+)/"\+" . tabify($1)/e; + } + } + +# check for space before tabs. + if ($rawline =~ /^\+/ && $rawline =~ / \t/) { + my $herevet = "$here\n" . cat_vet($rawline) . "\n"; + if (WARN("SPACE_BEFORE_TAB", + "please, no space before tabs\n" . $herevet) && + $fix) { + while ($fixed[$fixlinenr] =~ + s/(^\+.*) {$tabsize,$tabsize}\t/$1\t\t/) {} + while ($fixed[$fixlinenr] =~ + s/(^\+.*) +\t/$1\t/) {} + } + } + +# check for assignments on the start of a line + if ($sline =~ /^\+\s+($Assignment)[^=]/) { + my $operator = $1; + if (CHK("ASSIGNMENT_CONTINUATIONS", + "Assignment operator '$1' should be on the previous line\n" . $hereprev) && + $fix && $prevrawline =~ /^\+/) { + # add assignment operator to the previous line, remove from current line + $fixed[$fixlinenr - 1] .= " $operator"; + $fixed[$fixlinenr] =~ s/\Q$operator\E\s*//; + } + } + +# check for && or || at the start of a line + if ($rawline =~ /^\+\s*(&&|\|\|)/) { + my $operator = $1; + if (CHK("LOGICAL_CONTINUATIONS", + "Logical continuations should be on the previous line\n" . $hereprev) && + $fix && $prevrawline =~ /^\+/) { + # insert logical operator at last non-comment, non-whitepsace char on previous line + $prevline =~ /[\s$;]*$/; + my $line_end = substr($prevrawline, $-[0]); + $fixed[$fixlinenr - 1] =~ s/\Q$line_end\E$/ $operator$line_end/; + $fixed[$fixlinenr] =~ s/\Q$operator\E\s*//; + } + } + +# check indentation starts on a tab stop + if ($perl_version_ok && + $sline =~ /^\+\t+( +)(?:$c90_Keywords\b|\{\s*$|\}\s*(?:else\b|while\b|\s*$)|$Declare\s*$Ident\s*[;=])/) { + my $indent = length($1); + if ($indent % $tabsize) { + if (WARN("TABSTOP", + "Statements should start on a tabstop\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s@(^\+\t+) +@$1 . "\t" x ($indent/$tabsize)@e; + } + } + } + +# check multi-line statement indentation matches previous line + if ($perl_version_ok && + $prevline =~ /^\+([ \t]*)((?:$c90_Keywords(?:\s+if)\s*)|(?:$Declare\s*)?(?:$Ident|\(\s*\*\s*$Ident\s*\))\s*|(?:\*\s*)*$Lval\s*=\s*$Ident\s*)\(.*(\&\&|\|\||,)\s*$/) { + $prevline =~ /^\+(\t*)(.*)$/; + my $oldindent = $1; + my $rest = $2; + + my $pos = pos_last_openparen($rest); + if ($pos >= 0) { + $line =~ /^(\+| )([ \t]*)/; + my $newindent = $2; + + my $goodtabindent = $oldindent . + "\t" x ($pos / $tabsize) . + " " x ($pos % $tabsize); + my $goodspaceindent = $oldindent . " " x $pos; + + if ($newindent ne $goodtabindent && + $newindent ne $goodspaceindent) { + + if (CHK("PARENTHESIS_ALIGNMENT", + "Alignment should match open parenthesis\n" . $hereprev) && + $fix && $line =~ /^\+/) { + $fixed[$fixlinenr] =~ + s/^\+[ \t]*/\+$goodtabindent/; + } + } + } + } + +# check for space after cast like "(int) foo" or "(struct foo) bar" +# avoid checking a few false positives: +# "sizeof(<type>)" or "__alignof__(<type>)" +# function pointer declarations like "(*foo)(int) = bar;" +# structure definitions like "(struct foo) { 0 };" +# multiline macros that define functions +# known attributes or the __attribute__ keyword + if ($line =~ /^\+(.*)\(\s*$Type\s*\)([ \t]++)((?![={]|\\$|$Attribute|__attribute__))/ && + (!defined($1) || $1 !~ /\b(?:sizeof|__alignof__)\s*$/)) { + if (CHK("SPACING", + "No space is necessary after a cast\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ + s/(\(\s*$Type\s*\))[ \t]+/$1/; + } + } + +# Block comments use * on subsequent lines + if ($prevline =~ /$;[ \t]*$/ && #ends in comment + $prevrawline =~ /^\+.*?\/\*/ && #starting /* + $prevrawline !~ /\*\/[ \t]*$/ && #no trailing */ + $rawline =~ /^\+/ && #line is new + $rawline !~ /^\+[ \t]*\*/) { #no leading * + WARN("BLOCK_COMMENT_STYLE", + "Block comments use * on subsequent lines\n" . $hereprev); + } + +# Block comments use */ on trailing lines + if ($rawline !~ m@^\+[ \t]*\*/[ \t]*$@ && #trailing */ + $rawline !~ m@^\+.*/\*.*\*/[ \t]*$@ && #inline /*...*/ + $rawline !~ m@^\+.*\*{2,}/[ \t]*$@ && #trailing **/ + $rawline =~ m@^\+[ \t]*.+\*\/[ \t]*$@) { #non blank */ + WARN("BLOCK_COMMENT_STYLE", + "Block comments use a trailing */ on a separate line\n" . $herecurr); + } + +# Block comment * alignment + if ($prevline =~ /$;[ \t]*$/ && #ends in comment + $line =~ /^\+[ \t]*$;/ && #leading comment + $rawline =~ /^\+[ \t]*\*/ && #leading * + (($prevrawline =~ /^\+.*?\/\*/ && #leading /* + $prevrawline !~ /\*\/[ \t]*$/) || #no trailing */ + $prevrawline =~ /^\+[ \t]*\*/)) { #leading * + my $oldindent; + $prevrawline =~ m@^\+([ \t]*/?)\*@; + if (defined($1)) { + $oldindent = expand_tabs($1); + } else { + $prevrawline =~ m@^\+(.*/?)\*@; + $oldindent = expand_tabs($1); + } + $rawline =~ m@^\+([ \t]*)\*@; + my $newindent = $1; + $newindent = expand_tabs($newindent); + if (length($oldindent) ne length($newindent)) { + WARN("BLOCK_COMMENT_STYLE", + "Block comments should align the * on each line\n" . $hereprev); + } + } + +# check for missing blank lines after struct/union declarations +# with exceptions for various attributes and macros + if ($prevline =~ /^[\+ ]};?\s*$/ && + $line =~ /^\+/ && + !($line =~ /^\+\s*$/ || + $line =~ /^\+\s*(?:EXPORT_SYMBOL|early_param|ALLOW_ERROR_INJECTION)/ || + $line =~ /^\+\s*MODULE_/i || + $line =~ /^\+\s*\#\s*(?:end|elif|else)/ || + $line =~ /^\+[a-z_]*init/ || + $line =~ /^\+\s*(?:static\s+)?[A-Z_]*ATTR/ || + $line =~ /^\+\s*DECLARE/ || + $line =~ /^\+\s*builtin_[\w_]*driver/ || + $line =~ /^\+\s*__setup/)) { + if (CHK("LINE_SPACING", + "Please use a blank line after function/struct/union/enum declarations\n" . $hereprev) && + $fix) { + fix_insert_line($fixlinenr, "\+"); + } + } + +# check for multiple consecutive blank lines + if ($prevline =~ /^[\+ ]\s*$/ && + $line =~ /^\+\s*$/ && + $last_blank_line != ($linenr - 1)) { + if (CHK("LINE_SPACING", + "Please don't use multiple blank lines\n" . $hereprev) && + $fix) { + fix_delete_line($fixlinenr, $rawline); + } + + $last_blank_line = $linenr; + } + +# check for missing blank lines after declarations +# (declarations must have the same indentation and not be at the start of line) + if (($prevline =~ /\+(\s+)\S/) && $sline =~ /^\+$1\S/) { + # use temporaries + my $sl = $sline; + my $pl = $prevline; + # remove $Attribute/$Sparse uses to simplify comparisons + $sl =~ s/\b(?:$Attribute|$Sparse)\b//g; + $pl =~ s/\b(?:$Attribute|$Sparse)\b//g; + if (($pl =~ /^\+\s+$Declare\s*$Ident\s*[=,;:\[]/ || + # function pointer declarations + $pl =~ /^\+\s+$Declare\s*\(\s*\*\s*$Ident\s*\)\s*[=,;:\[\(]/ || + # foo bar; where foo is some local typedef or #define + $pl =~ /^\+\s+$Ident(?:\s+|\s*\*\s*)$Ident\s*[=,;\[]/ || + # known declaration macros + $pl =~ /^\+\s+$declaration_macros/) && + # for "else if" which can look like "$Ident $Ident" + !($pl =~ /^\+\s+$c90_Keywords\b/ || + # other possible extensions of declaration lines + $pl =~ /(?:$Compare|$Assignment|$Operators)\s*$/ || + # not starting a section or a macro "\" extended line + $pl =~ /(?:\{\s*|\\)$/) && + # looks like a declaration + !($sl =~ /^\+\s+$Declare\s*$Ident\s*[=,;:\[]/ || + # function pointer declarations + $sl =~ /^\+\s+$Declare\s*\(\s*\*\s*$Ident\s*\)\s*[=,;:\[\(]/ || + # foo bar; where foo is some local typedef or #define + $sl =~ /^\+\s+$Ident(?:\s+|\s*\*\s*)$Ident\s*[=,;\[]/ || + # known declaration macros + $sl =~ /^\+\s+$declaration_macros/ || + # start of struct or union or enum + $sl =~ /^\+\s+(?:static\s+)?(?:const\s+)?(?:union|struct|enum|typedef)\b/ || + # start or end of block or continuation of declaration + $sl =~ /^\+\s+(?:$|[\{\}\.\#\"\?\:\(\[])/ || + # bitfield continuation + $sl =~ /^\+\s+$Ident\s*:\s*\d+\s*[,;]/ || + # other possible extensions of declaration lines + $sl =~ /^\+\s+\(?\s*(?:$Compare|$Assignment|$Operators)/)) { + if (WARN("LINE_SPACING", + "Missing a blank line after declarations\n" . $hereprev) && + $fix) { + fix_insert_line($fixlinenr, "\+"); + } + } + } + +# check for spaces at the beginning of a line. +# Exceptions: +# 1) within comments +# 2) indented preprocessor commands +# 3) hanging labels + if ($rawline =~ /^\+ / && $line !~ /^\+ *(?:$;|#|$Ident:)/) { + my $herevet = "$here\n" . cat_vet($rawline) . "\n"; + if (WARN("LEADING_SPACE", + "please, no spaces at the start of a line\n" . $herevet) && + $fix) { + $fixed[$fixlinenr] =~ s/^\+([ \t]+)/"\+" . tabify($1)/e; + } + } + +# check we are in a valid C source file if not then ignore this hunk + next if ($realfile !~ /\.(h|c)$/); + +# check for unusual line ending [ or ( + if ($line =~ /^\+.*([\[\(])\s*$/) { + CHK("OPEN_ENDED_LINE", + "Lines should not end with a '$1'\n" . $herecurr); + } + +# check if this appears to be the start function declaration, save the name + if ($sline =~ /^\+\{\s*$/ && + $prevline =~ /^\+(?:(?:(?:$Storage|$Inline)\s*)*\s*$Type\s*)?($Ident)\(/) { + $context_function = $1; + } + +# check if this appears to be the end of function declaration + if ($sline =~ /^\+\}\s*$/) { + undef $context_function; + } + +# check indentation of any line with a bare else +# (but not if it is a multiple line "if (foo) return bar; else return baz;") +# if the previous line is a break or return and is indented 1 tab more... + if ($sline =~ /^\+([\t]+)(?:}[ \t]*)?else(?:[ \t]*{)?\s*$/) { + my $tabs = length($1) + 1; + if ($prevline =~ /^\+\t{$tabs,$tabs}break\b/ || + ($prevline =~ /^\+\t{$tabs,$tabs}return\b/ && + defined $lines[$linenr] && + $lines[$linenr] !~ /^[ \+]\t{$tabs,$tabs}return/)) { + WARN("UNNECESSARY_ELSE", + "else is not generally useful after a break or return\n" . $hereprev); + } + } + +# check indentation of a line with a break; +# if the previous line is a goto, return or break +# and is indented the same # of tabs + if ($sline =~ /^\+([\t]+)break\s*;\s*$/) { + my $tabs = $1; + if ($prevline =~ /^\+$tabs(goto|return|break)\b/) { + if (WARN("UNNECESSARY_BREAK", + "break is not useful after a $1\n" . $hereprev) && + $fix) { + fix_delete_line($fixlinenr, $rawline); + } + } + } + +# check for RCS/CVS revision markers + if ($rawline =~ /^\+.*\$(Revision|Log|Id)(?:\$|)/) { + WARN("CVS_KEYWORD", + "CVS style keyword markers, these will _not_ be updated\n". $herecurr); + } + +# check for old HOTPLUG __dev<foo> section markings + if ($line =~ /\b(__dev(init|exit)(data|const|))\b/) { + WARN("HOTPLUG_SECTION", + "Using $1 is unnecessary\n" . $herecurr); + } + +# Check for potential 'bare' types + my ($stat, $cond, $line_nr_next, $remain_next, $off_next, + $realline_next); +#print "LINE<$line>\n"; + if ($linenr > $suppress_statement && + $realcnt && $sline =~ /.\s*\S/) { + ($stat, $cond, $line_nr_next, $remain_next, $off_next) = + ctx_statement_block($linenr, $realcnt, 0); + $stat =~ s/\n./\n /g; + $cond =~ s/\n./\n /g; + +#print "linenr<$linenr> <$stat>\n"; + # If this statement has no statement boundaries within + # it there is no point in retrying a statement scan + # until we hit end of it. + my $frag = $stat; $frag =~ s/;+\s*$//; + if ($frag !~ /(?:{|;)/) { +#print "skip<$line_nr_next>\n"; + $suppress_statement = $line_nr_next; + } + + # Find the real next line. + $realline_next = $line_nr_next; + if (defined $realline_next && + (!defined $lines[$realline_next - 1] || + substr($lines[$realline_next - 1], $off_next) =~ /^\s*$/)) { + $realline_next++; + } + + my $s = $stat; + $s =~ s/{.*$//s; + + # Ignore goto labels. + if ($s =~ /$Ident:\*$/s) { + + # Ignore functions being called + } elsif ($s =~ /^.\s*$Ident\s*\(/s) { + + } elsif ($s =~ /^.\s*else\b/s) { + + # declarations always start with types + } elsif ($prev_values eq 'E' && $s =~ /^.\s*(?:$Storage\s+)?(?:$Inline\s+)?(?:const\s+)?((?:\s*$Ident)+?)\b(?:\s+$Sparse)?\s*\**\s*(?:$Ident|\(\*[^\)]*\))(?:\s*$Modifier)?\s*(?:;|=|,|\()/s) { + my $type = $1; + $type =~ s/\s+/ /g; + possible($type, "A:" . $s); + + # definitions in global scope can only start with types + } elsif ($s =~ /^.(?:$Storage\s+)?(?:$Inline\s+)?(?:const\s+)?($Ident)\b\s*(?!:)/s) { + possible($1, "B:" . $s); + } + + # any (foo ... *) is a pointer cast, and foo is a type + while ($s =~ /\(($Ident)(?:\s+$Sparse)*[\s\*]+\s*\)/sg) { + possible($1, "C:" . $s); + } + + # Check for any sort of function declaration. + # int foo(something bar, other baz); + # void (*store_gdt)(x86_descr_ptr *); + if ($prev_values eq 'E' && $s =~ /^(.(?:typedef\s*)?(?:(?:$Storage|$Inline)\s*)*\s*$Type\s*(?:\b$Ident|\(\*\s*$Ident\))\s*)\(/s) { + my ($name_len) = length($1); + + my $ctx = $s; + substr($ctx, 0, $name_len + 1, ''); + $ctx =~ s/\)[^\)]*$//; + + for my $arg (split(/\s*,\s*/, $ctx)) { + if ($arg =~ /^(?:const\s+)?($Ident)(?:\s+$Sparse)*\s*\**\s*(:?\b$Ident)?$/s || $arg =~ /^($Ident)$/s) { + + possible($1, "D:" . $s); + } + } + } + + } + +# +# Checks which may be anchored in the context. +# + +# Check for switch () and associated case and default +# statements should be at the same indent. + if ($line=~/\bswitch\s*\(.*\)/) { + my $err = ''; + my $sep = ''; + my @ctx = ctx_block_outer($linenr, $realcnt); + shift(@ctx); + for my $ctx (@ctx) { + my ($clen, $cindent) = line_stats($ctx); + if ($ctx =~ /^\+\s*(case\s+|default:)/ && + $indent != $cindent) { + $err .= "$sep$ctx\n"; + $sep = ''; + } else { + $sep = "[...]\n"; + } + } + if ($err ne '') { + ERROR("SWITCH_CASE_INDENT_LEVEL", + "switch and case should be at the same indent\n$hereline$err"); + } + } + +# if/while/etc brace do not go on next line, unless defining a do while loop, +# or if that brace on the next line is for something else + if ($line =~ /(.*)\b((?:if|while|for|switch|(?:[a-z_]+|)for_each[a-z_]+)\s*\(|do\b|else\b)/ && $line !~ /^.\s*\#/) { + my $pre_ctx = "$1$2"; + + my ($level, @ctx) = ctx_statement_level($linenr, $realcnt, 0); + + if ($line =~ /^\+\t{6,}/) { + WARN("DEEP_INDENTATION", + "Too many leading tabs - consider code refactoring\n" . $herecurr); + } + + my $ctx_cnt = $realcnt - $#ctx - 1; + my $ctx = join("\n", @ctx); + + my $ctx_ln = $linenr; + my $ctx_skip = $realcnt; + + while ($ctx_skip > $ctx_cnt || ($ctx_skip == $ctx_cnt && + defined $lines[$ctx_ln - 1] && + $lines[$ctx_ln - 1] =~ /^-/)) { + ##print "SKIP<$ctx_skip> CNT<$ctx_cnt>\n"; + $ctx_skip-- if (!defined $lines[$ctx_ln - 1] || $lines[$ctx_ln - 1] !~ /^-/); + $ctx_ln++; + } + + #print "realcnt<$realcnt> ctx_cnt<$ctx_cnt>\n"; + #print "pre<$pre_ctx>\nline<$line>\nctx<$ctx>\nnext<$lines[$ctx_ln - 1]>\n"; + + if ($ctx !~ /{\s*/ && defined($lines[$ctx_ln - 1]) && $lines[$ctx_ln - 1] =~ /^\+\s*{/) { + ERROR("OPEN_BRACE", + "that open brace { should be on the previous line\n" . + "$here\n$ctx\n$rawlines[$ctx_ln - 1]\n"); + } + if ($level == 0 && $pre_ctx !~ /}\s*while\s*\($/ && + $ctx =~ /\)\s*\;\s*$/ && + defined $lines[$ctx_ln - 1]) + { + my ($nlength, $nindent) = line_stats($lines[$ctx_ln - 1]); + if ($nindent > $indent) { + WARN("TRAILING_SEMICOLON", + "trailing semicolon indicates no statements, indent implies otherwise\n" . + "$here\n$ctx\n$rawlines[$ctx_ln - 1]\n"); + } + } + } + +# Check relative indent for conditionals and blocks. + if ($line =~ /\b(?:(?:if|while|for|(?:[a-z_]+|)for_each[a-z_]+)\s*\(|(?:do|else)\b)/ && $line !~ /^.\s*#/ && $line !~ /\}\s*while\s*/) { + ($stat, $cond, $line_nr_next, $remain_next, $off_next) = + ctx_statement_block($linenr, $realcnt, 0) + if (!defined $stat); + my ($s, $c) = ($stat, $cond); + + substr($s, 0, length($c), ''); + + # remove inline comments + $s =~ s/$;/ /g; + $c =~ s/$;/ /g; + + # Find out how long the conditional actually is. + my @newlines = ($c =~ /\n/gs); + my $cond_lines = 1 + $#newlines; + + # Make sure we remove the line prefixes as we have + # none on the first line, and are going to readd them + # where necessary. + $s =~ s/\n./\n/gs; + while ($s =~ /\n\s+\\\n/) { + $cond_lines += $s =~ s/\n\s+\\\n/\n/g; + } + + # We want to check the first line inside the block + # starting at the end of the conditional, so remove: + # 1) any blank line termination + # 2) any opening brace { on end of the line + # 3) any do (...) { + my $continuation = 0; + my $check = 0; + $s =~ s/^.*\bdo\b//; + $s =~ s/^\s*{//; + if ($s =~ s/^\s*\\//) { + $continuation = 1; + } + if ($s =~ s/^\s*?\n//) { + $check = 1; + $cond_lines++; + } + + # Also ignore a loop construct at the end of a + # preprocessor statement. + if (($prevline =~ /^.\s*#\s*define\s/ || + $prevline =~ /\\\s*$/) && $continuation == 0) { + $check = 0; + } + + my $cond_ptr = -1; + $continuation = 0; + while ($cond_ptr != $cond_lines) { + $cond_ptr = $cond_lines; + + # If we see an #else/#elif then the code + # is not linear. + if ($s =~ /^\s*\#\s*(?:else|elif)/) { + $check = 0; + } + + # Ignore: + # 1) blank lines, they should be at 0, + # 2) preprocessor lines, and + # 3) labels. + if ($continuation || + $s =~ /^\s*?\n/ || + $s =~ /^\s*#\s*?/ || + $s =~ /^\s*$Ident\s*:/) { + $continuation = ($s =~ /^.*?\\\n/) ? 1 : 0; + if ($s =~ s/^.*?\n//) { + $cond_lines++; + } + } + } + + my (undef, $sindent) = line_stats("+" . $s); + my $stat_real = raw_line($linenr, $cond_lines); + + # Check if either of these lines are modified, else + # this is not this patch's fault. + if (!defined($stat_real) || + $stat !~ /^\+/ && $stat_real !~ /^\+/) { + $check = 0; + } + if (defined($stat_real) && $cond_lines > 1) { + $stat_real = "[...]\n$stat_real"; + } + + #print "line<$line> prevline<$prevline> indent<$indent> sindent<$sindent> check<$check> continuation<$continuation> s<$s> cond_lines<$cond_lines> stat_real<$stat_real> stat<$stat>\n"; + + if ($check && $s ne '' && + (($sindent % $tabsize) != 0 || + ($sindent < $indent) || + ($sindent == $indent && + ($s !~ /^\s*(?:\}|\{|else\b)/)) || + ($sindent > $indent + $tabsize))) { + WARN("SUSPECT_CODE_INDENT", + "suspect code indent for conditional statements ($indent, $sindent)\n" . $herecurr . "$stat_real\n"); + } + } + + # Track the 'values' across context and added lines. + my $opline = $line; $opline =~ s/^./ /; + my ($curr_values, $curr_vars) = + annotate_values($opline . "\n", $prev_values); + $curr_values = $prev_values . $curr_values; + if ($dbg_values) { + my $outline = $opline; $outline =~ s/\t/ /g; + print "$linenr > .$outline\n"; + print "$linenr > $curr_values\n"; + print "$linenr > $curr_vars\n"; + } + $prev_values = substr($curr_values, -1); + +#ignore lines not being added + next if ($line =~ /^[^\+]/); + +# check for self assignments used to avoid compiler warnings +# e.g.: int foo = foo, *bar = NULL; +# struct foo bar = *(&(bar)); + if ($line =~ /^\+\s*(?:$Declare)?([A-Za-z_][A-Za-z\d_]*)\s*=/) { + my $var = $1; + if ($line =~ /^\+\s*(?:$Declare)?$var\s*=\s*(?:$var|\*\s*\(?\s*&\s*\(?\s*$var\s*\)?\s*\)?)\s*[;,]/) { + WARN("SELF_ASSIGNMENT", + "Do not use self-assignments to avoid compiler warnings\n" . $herecurr); + } + } + +# check for dereferences that span multiple lines + if ($prevline =~ /^\+.*$Lval\s*(?:\.|->)\s*$/ && + $line =~ /^\+\s*(?!\#\s*(?!define\s+|if))\s*$Lval/) { + $prevline =~ /($Lval\s*(?:\.|->))\s*$/; + my $ref = $1; + $line =~ /^.\s*($Lval)/; + $ref .= $1; + $ref =~ s/\s//g; + WARN("MULTILINE_DEREFERENCE", + "Avoid multiple line dereference - prefer '$ref'\n" . $hereprev); + } + +# check for declarations of signed or unsigned without int + while ($line =~ m{\b($Declare)\s*(?!char\b|short\b|int\b|long\b)\s*($Ident)?\s*[=,;\[\)\(]}g) { + my $type = $1; + my $var = $2; + $var = "" if (!defined $var); + if ($type =~ /^(?:(?:$Storage|$Inline|$Attribute)\s+)*((?:un)?signed)((?:\s*\*)*)\s*$/) { + my $sign = $1; + my $pointer = $2; + + $pointer = "" if (!defined $pointer); + + if (WARN("UNSPECIFIED_INT", + "Prefer '" . trim($sign) . " int" . rtrim($pointer) . "' to bare use of '$sign" . rtrim($pointer) . "'\n" . $herecurr) && + $fix) { + my $decl = trim($sign) . " int "; + my $comp_pointer = $pointer; + $comp_pointer =~ s/\s//g; + $decl .= $comp_pointer; + $decl = rtrim($decl) if ($var eq ""); + $fixed[$fixlinenr] =~ s@\b$sign\s*\Q$pointer\E\s*$var\b@$decl$var@; + } + } + } + +# TEST: allow direct testing of the type matcher. + if ($dbg_type) { + if ($line =~ /^.\s*$Declare\s*$/) { + ERROR("TEST_TYPE", + "TEST: is type\n" . $herecurr); + } elsif ($dbg_type > 1 && $line =~ /^.+($Declare)/) { + ERROR("TEST_NOT_TYPE", + "TEST: is not type ($1 is)\n". $herecurr); + } + next; + } +# TEST: allow direct testing of the attribute matcher. + if ($dbg_attr) { + if ($line =~ /^.\s*$Modifier\s*$/) { + ERROR("TEST_ATTR", + "TEST: is attr\n" . $herecurr); + } elsif ($dbg_attr > 1 && $line =~ /^.+($Modifier)/) { + ERROR("TEST_NOT_ATTR", + "TEST: is not attr ($1 is)\n". $herecurr); + } + next; + } + +# check for initialisation to aggregates open brace on the next line + if ($line =~ /^.\s*{/ && + $prevline =~ /(?:^|[^=])=\s*$/) { + if (ERROR("OPEN_BRACE", + "that open brace { should be on the previous line\n" . $hereprev) && + $fix && $prevline =~ /^\+/ && $line =~ /^\+/) { + fix_delete_line($fixlinenr - 1, $prevrawline); + fix_delete_line($fixlinenr, $rawline); + my $fixedline = $prevrawline; + $fixedline =~ s/\s*=\s*$/ = {/; + fix_insert_line($fixlinenr, $fixedline); + $fixedline = $line; + $fixedline =~ s/^(.\s*)\{\s*/$1/; + fix_insert_line($fixlinenr, $fixedline); + } + } + +# +# Checks which are anchored on the added line. +# + +# check for malformed paths in #include statements (uses RAW line) + if ($rawline =~ m{^.\s*\#\s*include\s+[<"](.*)[">]}) { + my $path = $1; + if ($path =~ m{//}) { + ERROR("MALFORMED_INCLUDE", + "malformed #include filename\n" . $herecurr); + } + if ($path =~ "^uapi/" && $realfile =~ m@\binclude/uapi/@) { + ERROR("UAPI_INCLUDE", + "No #include in ...include/uapi/... should use a uapi/ path prefix\n" . $herecurr); + } + } + +# no C99 // comments + if ($line =~ m{//}) { + if (ERROR("C99_COMMENTS", + "do not use C99 // comments\n" . $herecurr) && + $fix) { + my $line = $fixed[$fixlinenr]; + if ($line =~ /\/\/(.*)$/) { + my $comment = trim($1); + $fixed[$fixlinenr] =~ s@\/\/(.*)$@/\* $comment \*/@; + } + } + } + # Remove C99 comments. + $line =~ s@//.*@@; + $opline =~ s@//.*@@; + +# EXPORT_SYMBOL should immediately follow the thing it is exporting, consider +# the whole statement. +#print "APW <$lines[$realline_next - 1]>\n"; + if (defined $realline_next && + exists $lines[$realline_next - 1] && + !defined $suppress_export{$realline_next} && + ($lines[$realline_next - 1] =~ /EXPORT_SYMBOL.*\((.*)\)/)) { + # Handle definitions which produce identifiers with + # a prefix: + # XXX(foo); + # EXPORT_SYMBOL(something_foo); + my $name = $1; + $name =~ s/^\s*($Ident).*/$1/; + if ($stat =~ /^(?:.\s*}\s*\n)?.([A-Z_]+)\s*\(\s*($Ident)/ && + $name =~ /^${Ident}_$2/) { +#print "FOO C name<$name>\n"; + $suppress_export{$realline_next} = 1; + + } elsif ($stat !~ /(?: + \n.}\s*$| + ^.DEFINE_$Ident\(\Q$name\E\)| + ^.DECLARE_$Ident\(\Q$name\E\)| + ^.LIST_HEAD\(\Q$name\E\)| + ^.(?:$Storage\s+)?$Type\s*\(\s*\*\s*\Q$name\E\s*\)\s*\(| + \b\Q$name\E(?:\s+$Attribute)*\s*(?:;|=|\[|\() + )/x) { +#print "FOO A<$lines[$realline_next - 1]> stat<$stat> name<$name>\n"; + $suppress_export{$realline_next} = 2; + } else { + $suppress_export{$realline_next} = 1; + } + } + if (!defined $suppress_export{$linenr} && + $prevline =~ /^.\s*$/ && + ($line =~ /EXPORT_SYMBOL.*\((.*)\)/)) { +#print "FOO B <$lines[$linenr - 1]>\n"; + $suppress_export{$linenr} = 2; + } + if (defined $suppress_export{$linenr} && + $suppress_export{$linenr} == 2) { + WARN("EXPORT_SYMBOL", + "EXPORT_SYMBOL(foo); should immediately follow its function/variable\n" . $herecurr); + } + +# check for global initialisers. + if ($line =~ /^\+$Type\s*$Ident(?:\s+$Modifier)*\s*=\s*($zero_initializer)\s*;/ && + !exclude_global_initialisers($realfile)) { + if (ERROR("GLOBAL_INITIALISERS", + "do not initialise globals to $1\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/(^.$Type\s*$Ident(?:\s+$Modifier)*)\s*=\s*$zero_initializer\s*;/$1;/; + } + } +# check for static initialisers. + if ($line =~ /^\+.*\bstatic\s.*=\s*($zero_initializer)\s*;/) { + if (ERROR("INITIALISED_STATIC", + "do not initialise statics to $1\n" . + $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/(\bstatic\s.*?)\s*=\s*$zero_initializer\s*;/$1;/; + } + } + +# check for misordered declarations of char/short/int/long with signed/unsigned + while ($sline =~ m{(\b$TypeMisordered\b)}g) { + my $tmp = trim($1); + WARN("MISORDERED_TYPE", + "type '$tmp' should be specified in [[un]signed] [short|int|long|long long] order\n" . $herecurr); + } + +# check for unnecessary <signed> int declarations of short/long/long long + while ($sline =~ m{\b($TypeMisordered(\s*\*)*|$C90_int_types)\b}g) { + my $type = trim($1); + next if ($type !~ /\bint\b/); + next if ($type !~ /\b(?:short|long\s+long|long)\b/); + my $new_type = $type; + $new_type =~ s/\b\s*int\s*\b/ /; + $new_type =~ s/\b\s*(?:un)?signed\b\s*/ /; + $new_type =~ s/^const\s+//; + $new_type = "unsigned $new_type" if ($type =~ /\bunsigned\b/); + $new_type = "const $new_type" if ($type =~ /^const\b/); + $new_type =~ s/\s+/ /g; + $new_type = trim($new_type); + if (WARN("UNNECESSARY_INT", + "Prefer '$new_type' over '$type' as the int is unnecessary\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\b\Q$type\E\b/$new_type/; + } + } + +# check for static const char * arrays. + if ($line =~ /\bstatic\s+const\s+char\s*\*\s*(\w+)\s*\[\s*\]\s*=\s*/) { + WARN("STATIC_CONST_CHAR_ARRAY", + "static const char * array should probably be static const char * const\n" . + $herecurr); + } + +# check for initialized const char arrays that should be static const + if ($line =~ /^\+\s*const\s+(char|unsigned\s+char|_*u8|(?:[us]_)?int8_t)\s+\w+\s*\[\s*(?:\w+\s*)?\]\s*=\s*"/) { + if (WARN("STATIC_CONST_CHAR_ARRAY", + "const array should probably be static const\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/(^.\s*)const\b/${1}static const/; + } + } + +# check for static char foo[] = "bar" declarations. + if ($line =~ /\bstatic\s+char\s+(\w+)\s*\[\s*\]\s*=\s*"/) { + WARN("STATIC_CONST_CHAR_ARRAY", + "static char array declaration should probably be static const char\n" . + $herecurr); + } + +# check for const <foo> const where <foo> is not a pointer or array type + if ($sline =~ /\bconst\s+($BasicType)\s+const\b/) { + my $found = $1; + if ($sline =~ /\bconst\s+\Q$found\E\s+const\b\s*\*/) { + WARN("CONST_CONST", + "'const $found const *' should probably be 'const $found * const'\n" . $herecurr); + } elsif ($sline !~ /\bconst\s+\Q$found\E\s+const\s+\w+\s*\[/) { + WARN("CONST_CONST", + "'const $found const' should probably be 'const $found'\n" . $herecurr); + } + } + +# check for const static or static <non ptr type> const declarations +# prefer 'static const <foo>' over 'const static <foo>' and 'static <foo> const' + if ($sline =~ /^\+\s*const\s+static\s+($Type)\b/ || + $sline =~ /^\+\s*static\s+($BasicType)\s+const\b/) { + if (WARN("STATIC_CONST", + "Move const after static - use 'static const $1'\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\bconst\s+static\b/static const/; + $fixed[$fixlinenr] =~ s/\bstatic\s+($BasicType)\s+const\b/static const $1/; + } + } + +# check for non-global char *foo[] = {"bar", ...} declarations. + if ($line =~ /^.\s+(?:static\s+|const\s+)?char\s+\*\s*\w+\s*\[\s*\]\s*=\s*\{/) { + WARN("STATIC_CONST_CHAR_ARRAY", + "char * array declaration might be better as static const\n" . + $herecurr); + } + +# check for sizeof(foo)/sizeof(foo[0]) that could be ARRAY_SIZE(foo) + if ($line =~ m@\bsizeof\s*\(\s*($Lval)\s*\)@) { + my $array = $1; + if ($line =~ m@\b(sizeof\s*\(\s*\Q$array\E\s*\)\s*/\s*sizeof\s*\(\s*\Q$array\E\s*\[\s*0\s*\]\s*\))@) { + my $array_div = $1; + if (WARN("ARRAY_SIZE", + "Prefer ARRAY_SIZE($array)\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\Q$array_div\E/ARRAY_SIZE($array)/; + } + } + } + +# check for function declarations without arguments like "int foo()" + if ($line =~ /(\b$Type\s*$Ident)\s*\(\s*\)/) { + if (ERROR("FUNCTION_WITHOUT_ARGS", + "Bad function definition - $1() should probably be $1(void)\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/(\b($Type)\s+($Ident))\s*\(\s*\)/$2 $3(void)/; + } + } + +# check for new typedefs, only function parameters and sparse annotations +# make sense. + if ($line =~ /\btypedef\s/ && + $line !~ /\btypedef\s+$Type\s*\(\s*\*?$Ident\s*\)\s*\(/ && + $line !~ /\btypedef\s+$Type\s+$Ident\s*\(/ && + $line !~ /\b$typeTypedefs\b/ && + $line !~ /\b__bitwise\b/) { + WARN("NEW_TYPEDEFS", + "do not add new typedefs\n" . $herecurr); + } + +# * goes on variable not on type + # (char*[ const]) + while ($line =~ m{(\($NonptrType(\s*(?:$Modifier\b\s*|\*\s*)+)\))}g) { + #print "AA<$1>\n"; + my ($ident, $from, $to) = ($1, $2, $2); + + # Should start with a space. + $to =~ s/^(\S)/ $1/; + # Should not end with a space. + $to =~ s/\s+$//; + # '*'s should not have spaces between. + while ($to =~ s/\*\s+\*/\*\*/) { + } + +## print "1: from<$from> to<$to> ident<$ident>\n"; + if ($from ne $to) { + if (ERROR("POINTER_LOCATION", + "\"(foo$from)\" should be \"(foo$to)\"\n" . $herecurr) && + $fix) { + my $sub_from = $ident; + my $sub_to = $ident; + $sub_to =~ s/\Q$from\E/$to/; + $fixed[$fixlinenr] =~ + s@\Q$sub_from\E@$sub_to@; + } + } + } + while ($line =~ m{(\b$NonptrType(\s*(?:$Modifier\b\s*|\*\s*)+)($Ident))}g) { + #print "BB<$1>\n"; + my ($match, $from, $to, $ident) = ($1, $2, $2, $3); + + # Should start with a space. + $to =~ s/^(\S)/ $1/; + # Should not end with a space. + $to =~ s/\s+$//; + # '*'s should not have spaces between. + while ($to =~ s/\*\s+\*/\*\*/) { + } + # Modifiers should have spaces. + $to =~ s/(\b$Modifier$)/$1 /; + +## print "2: from<$from> to<$to> ident<$ident>\n"; + if ($from ne $to && $ident !~ /^$Modifier$/) { + if (ERROR("POINTER_LOCATION", + "\"foo${from}bar\" should be \"foo${to}bar\"\n" . $herecurr) && + $fix) { + + my $sub_from = $match; + my $sub_to = $match; + $sub_to =~ s/\Q$from\E/$to/; + $fixed[$fixlinenr] =~ + s@\Q$sub_from\E@$sub_to@; + } + } + } + +# do not use BUG() or variants + if ($line =~ /\b(?!AA_|BUILD_|IDA_|KVM_|RWLOCK_|snd_|SPIN_)(?:[a-zA-Z_]*_)?BUG(?:_ON)?(?:_[A-Z_]+)?\s*\(/) { + my $msg_level = \&WARN; + $msg_level = \&CHK if ($file); + &{$msg_level}("AVOID_BUG", + "Do not crash the kernel unless it is absolutely unavoidable--use WARN_ON_ONCE() plus recovery code (if feasible) instead of BUG() or variants\n" . $herecurr); + } + +# avoid LINUX_VERSION_CODE + if ($line =~ /\bLINUX_VERSION_CODE\b/) { + WARN("LINUX_VERSION_CODE", + "LINUX_VERSION_CODE should be avoided, code should be for the version to which it is merged\n" . $herecurr); + } + +# check for uses of printk_ratelimit + if ($line =~ /\bprintk_ratelimit\s*\(/) { + WARN("PRINTK_RATELIMITED", + "Prefer printk_ratelimited or pr_<level>_ratelimited to printk_ratelimit\n" . $herecurr); + } + +# printk should use KERN_* levels + if ($line =~ /\bprintk\s*\(\s*(?!KERN_[A-Z]+\b)/) { + WARN("PRINTK_WITHOUT_KERN_LEVEL", + "printk() should include KERN_<LEVEL> facility level\n" . $herecurr); + } + +# prefer variants of (subsystem|netdev|dev|pr)_<level> to printk(KERN_<LEVEL> + if ($line =~ /\b(printk(_once|_ratelimited)?)\s*\(\s*KERN_([A-Z]+)/) { + my $printk = $1; + my $modifier = $2; + my $orig = $3; + $modifier = "" if (!defined($modifier)); + my $level = lc($orig); + $level = "warn" if ($level eq "warning"); + my $level2 = $level; + $level2 = "dbg" if ($level eq "debug"); + $level .= $modifier; + $level2 .= $modifier; + WARN("PREFER_PR_LEVEL", + "Prefer [subsystem eg: netdev]_$level2([subsystem]dev, ... then dev_$level2(dev, ... then pr_$level(... to $printk(KERN_$orig ...\n" . $herecurr); + } + +# prefer dev_<level> to dev_printk(KERN_<LEVEL> + if ($line =~ /\bdev_printk\s*\(\s*KERN_([A-Z]+)/) { + my $orig = $1; + my $level = lc($orig); + $level = "warn" if ($level eq "warning"); + $level = "dbg" if ($level eq "debug"); + WARN("PREFER_DEV_LEVEL", + "Prefer dev_$level(... to dev_printk(KERN_$orig, ...\n" . $herecurr); + } + +# trace_printk should not be used in production code. + if ($line =~ /\b(trace_printk|trace_puts|ftrace_vprintk)\s*\(/) { + WARN("TRACE_PRINTK", + "Do not use $1() in production code (this can be ignored if built only with a debug config option)\n" . $herecurr); + } + +# ENOSYS means "bad syscall nr" and nothing else. This will have a small +# number of false positives, but assembly files are not checked, so at +# least the arch entry code will not trigger this warning. + if ($line =~ /\bENOSYS\b/) { + WARN("ENOSYS", + "ENOSYS means 'invalid syscall nr' and nothing else\n" . $herecurr); + } + +# ENOTSUPP is not a standard error code and should be avoided in new patches. +# Folks usually mean EOPNOTSUPP (also called ENOTSUP), when they type ENOTSUPP. +# Similarly to ENOSYS warning a small number of false positives is expected. + if (!$file && $line =~ /\bENOTSUPP\b/) { + if (WARN("ENOTSUPP", + "ENOTSUPP is not a SUSV4 error code, prefer EOPNOTSUPP\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\bENOTSUPP\b/EOPNOTSUPP/; + } + } + +# function brace can't be on same line, except for #defines of do while, +# or if closed on same line + if ($perl_version_ok && + $sline =~ /$Type\s*$Ident\s*$balanced_parens\s*\{/ && + $sline !~ /\#\s*define\b.*do\s*\{/ && + $sline !~ /}/) { + if (ERROR("OPEN_BRACE", + "open brace '{' following function definitions go on the next line\n" . $herecurr) && + $fix) { + fix_delete_line($fixlinenr, $rawline); + my $fixed_line = $rawline; + $fixed_line =~ /(^..*$Type\s*$Ident\(.*\)\s*)\{(.*)$/; + my $line1 = $1; + my $line2 = $2; + fix_insert_line($fixlinenr, ltrim($line1)); + fix_insert_line($fixlinenr, "\+{"); + if ($line2 !~ /^\s*$/) { + fix_insert_line($fixlinenr, "\+\t" . trim($line2)); + } + } + } + +# open braces for enum, union and struct go on the same line. + if ($line =~ /^.\s*{/ && + $prevline =~ /^.\s*(?:typedef\s+)?(enum|union|struct)(?:\s+$Ident)?\s*$/) { + if (ERROR("OPEN_BRACE", + "open brace '{' following $1 go on the same line\n" . $hereprev) && + $fix && $prevline =~ /^\+/ && $line =~ /^\+/) { + fix_delete_line($fixlinenr - 1, $prevrawline); + fix_delete_line($fixlinenr, $rawline); + my $fixedline = rtrim($prevrawline) . " {"; + fix_insert_line($fixlinenr, $fixedline); + $fixedline = $rawline; + $fixedline =~ s/^(.\s*)\{\s*/$1\t/; + if ($fixedline !~ /^\+\s*$/) { + fix_insert_line($fixlinenr, $fixedline); + } + } + } + +# missing space after union, struct or enum definition + if ($line =~ /^.\s*(?:typedef\s+)?(enum|union|struct)(?:\s+$Ident){1,2}[=\{]/) { + if (WARN("SPACING", + "missing space after $1 definition\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ + s/^(.\s*(?:typedef\s+)?(?:enum|union|struct)(?:\s+$Ident){1,2})([=\{])/$1 $2/; + } + } + +# Function pointer declarations +# check spacing between type, funcptr, and args +# canonical declaration is "type (*funcptr)(args...)" + if ($line =~ /^.\s*($Declare)\((\s*)\*(\s*)($Ident)(\s*)\)(\s*)\(/) { + my $declare = $1; + my $pre_pointer_space = $2; + my $post_pointer_space = $3; + my $funcname = $4; + my $post_funcname_space = $5; + my $pre_args_space = $6; + +# the $Declare variable will capture all spaces after the type +# so check it for a missing trailing missing space but pointer return types +# don't need a space so don't warn for those. + my $post_declare_space = ""; + if ($declare =~ /(\s+)$/) { + $post_declare_space = $1; + $declare = rtrim($declare); + } + if ($declare !~ /\*$/ && $post_declare_space =~ /^$/) { + WARN("SPACING", + "missing space after return type\n" . $herecurr); + $post_declare_space = " "; + } + +# unnecessary space "type (*funcptr)(args...)" +# This test is not currently implemented because these declarations are +# equivalent to +# int foo(int bar, ...) +# and this is form shouldn't/doesn't generate a checkpatch warning. +# +# elsif ($declare =~ /\s{2,}$/) { +# WARN("SPACING", +# "Multiple spaces after return type\n" . $herecurr); +# } + +# unnecessary space "type ( *funcptr)(args...)" + if (defined $pre_pointer_space && + $pre_pointer_space =~ /^\s/) { + WARN("SPACING", + "Unnecessary space after function pointer open parenthesis\n" . $herecurr); + } + +# unnecessary space "type (* funcptr)(args...)" + if (defined $post_pointer_space && + $post_pointer_space =~ /^\s/) { + WARN("SPACING", + "Unnecessary space before function pointer name\n" . $herecurr); + } + +# unnecessary space "type (*funcptr )(args...)" + if (defined $post_funcname_space && + $post_funcname_space =~ /^\s/) { + WARN("SPACING", + "Unnecessary space after function pointer name\n" . $herecurr); + } + +# unnecessary space "type (*funcptr) (args...)" + if (defined $pre_args_space && + $pre_args_space =~ /^\s/) { + WARN("SPACING", + "Unnecessary space before function pointer arguments\n" . $herecurr); + } + + if (show_type("SPACING") && $fix) { + $fixed[$fixlinenr] =~ + s/^(.\s*)$Declare\s*\(\s*\*\s*$Ident\s*\)\s*\(/$1 . $declare . $post_declare_space . '(*' . $funcname . ')('/ex; + } + } + +# check for spacing round square brackets; allowed: +# 1. with a type on the left -- int [] a; +# 2. at the beginning of a line for slice initialisers -- [0...10] = 5, +# 3. inside a curly brace -- = { [0...10] = 5 } + while ($line =~ /(.*?\s)\[/g) { + my ($where, $prefix) = ($-[1], $1); + if ($prefix !~ /$Type\s+$/ && + ($where != 0 || $prefix !~ /^.\s+$/) && + $prefix !~ /[{,:]\s+$/) { + if (ERROR("BRACKET_SPACE", + "space prohibited before open square bracket '['\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ + s/^(\+.*?)\s+\[/$1\[/; + } + } + } + +# check for spaces between functions and their parentheses. + while ($line =~ /($Ident)\s+\(/g) { + my $name = $1; + my $ctx_before = substr($line, 0, $-[1]); + my $ctx = "$ctx_before$name"; + + # Ignore those directives where spaces _are_ permitted. + if ($name =~ /^(?: + if|for|while|switch|return|case| + volatile|__volatile__| + __attribute__|format|__extension__| + asm|__asm__|scoped_guard)$/x) + { + # cpp #define statements have non-optional spaces, ie + # if there is a space between the name and the open + # parenthesis it is simply not a parameter group. + } elsif ($ctx_before =~ /^.\s*\#\s*define\s*$/) { + + # cpp #elif statement condition may start with a ( + } elsif ($ctx =~ /^.\s*\#\s*elif\s*$/) { + + # If this whole things ends with a type its most + # likely a typedef for a function. + } elsif ($ctx =~ /$Type$/) { + + } else { + if (WARN("SPACING", + "space prohibited between function name and open parenthesis '('\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ + s/\b$name\s+\(/$name\(/; + } + } + } + +# Check operator spacing. + if (!($line=~/\#\s*include/)) { + my $fixed_line = ""; + my $line_fixed = 0; + + my $ops = qr{ + <<=|>>=|<=|>=|==|!=| + \+=|-=|\*=|\/=|%=|\^=|\|=|&=| + =>|->|<<|>>|<|>|=|!|~| + &&|\|\||,|\^|\+\+|--|&|\||\+|-|\*|\/|%| + \?:|\?|: + }x; + my @elements = split(/($ops|;)/, $opline); + +## print("element count: <" . $#elements . ">\n"); +## foreach my $el (@elements) { +## print("el: <$el>\n"); +## } + + my @fix_elements = (); + my $off = 0; + + foreach my $el (@elements) { + push(@fix_elements, substr($rawline, $off, length($el))); + $off += length($el); + } + + $off = 0; + + my $blank = copy_spacing($opline); + my $last_after = -1; + + for (my $n = 0; $n < $#elements; $n += 2) { + + my $good = $fix_elements[$n] . $fix_elements[$n + 1]; + +## print("n: <$n> good: <$good>\n"); + + $off += length($elements[$n]); + + # Pick up the preceding and succeeding characters. + my $ca = substr($opline, 0, $off); + my $cc = ''; + if (length($opline) >= ($off + length($elements[$n + 1]))) { + $cc = substr($opline, $off + length($elements[$n + 1])); + } + my $cb = "$ca$;$cc"; + + my $a = ''; + $a = 'V' if ($elements[$n] ne ''); + $a = 'W' if ($elements[$n] =~ /\s$/); + $a = 'C' if ($elements[$n] =~ /$;$/); + $a = 'B' if ($elements[$n] =~ /(\[|\()$/); + $a = 'O' if ($elements[$n] eq ''); + $a = 'E' if ($ca =~ /^\s*$/); + + my $op = $elements[$n + 1]; + + my $c = ''; + if (defined $elements[$n + 2]) { + $c = 'V' if ($elements[$n + 2] ne ''); + $c = 'W' if ($elements[$n + 2] =~ /^\s/); + $c = 'C' if ($elements[$n + 2] =~ /^$;/); + $c = 'B' if ($elements[$n + 2] =~ /^(\)|\]|;)/); + $c = 'O' if ($elements[$n + 2] eq ''); + $c = 'E' if ($elements[$n + 2] =~ /^\s*\\$/); + } else { + $c = 'E'; + } + + my $ctx = "${a}x${c}"; + + my $at = "(ctx:$ctx)"; + + my $ptr = substr($blank, 0, $off) . "^"; + my $hereptr = "$hereline$ptr\n"; + + # Pull out the value of this operator. + my $op_type = substr($curr_values, $off + 1, 1); + + # Get the full operator variant. + my $opv = $op . substr($curr_vars, $off, 1); + + # Ignore operators passed as parameters. + if ($op_type ne 'V' && + $ca =~ /\s$/ && $cc =~ /^\s*[,\)]/) { + +# # Ignore comments +# } elsif ($op =~ /^$;+$/) { + + # ; should have either the end of line or a space or \ after it + } elsif ($op eq ';') { + if ($ctx !~ /.x[WEBC]/ && + $cc !~ /^\\/ && $cc !~ /^;/) { + if (ERROR("SPACING", + "space required after that '$op' $at\n" . $hereptr)) { + $good = $fix_elements[$n] . trim($fix_elements[$n + 1]) . " "; + $line_fixed = 1; + } + } + + # // is a comment + } elsif ($op eq '//') { + + # : when part of a bitfield + } elsif ($opv eq ':B') { + # skip the bitfield test for now + + # No spaces for: + # -> + } elsif ($op eq '->') { + if ($ctx =~ /Wx.|.xW/) { + if (ERROR("SPACING", + "spaces prohibited around that '$op' $at\n" . $hereptr)) { + $good = rtrim($fix_elements[$n]) . trim($fix_elements[$n + 1]); + if (defined $fix_elements[$n + 2]) { + $fix_elements[$n + 2] =~ s/^\s+//; + } + $line_fixed = 1; + } + } + + # , must not have a space before and must have a space on the right. + } elsif ($op eq ',') { + my $rtrim_before = 0; + my $space_after = 0; + if ($ctx =~ /Wx./) { + if (ERROR("SPACING", + "space prohibited before that '$op' $at\n" . $hereptr)) { + $line_fixed = 1; + $rtrim_before = 1; + } + } + if ($ctx !~ /.x[WEC]/ && $cc !~ /^}/) { + if (ERROR("SPACING", + "space required after that '$op' $at\n" . $hereptr)) { + $line_fixed = 1; + $last_after = $n; + $space_after = 1; + } + } + if ($rtrim_before || $space_after) { + if ($rtrim_before) { + $good = rtrim($fix_elements[$n]) . trim($fix_elements[$n + 1]); + } else { + $good = $fix_elements[$n] . trim($fix_elements[$n + 1]); + } + if ($space_after) { + $good .= " "; + } + } + + # '*' as part of a type definition -- reported already. + } elsif ($opv eq '*_') { + #warn "'*' is part of type\n"; + + # unary operators should have a space before and + # none after. May be left adjacent to another + # unary operator, or a cast + } elsif ($op eq '!' || $op eq '~' || + $opv eq '*U' || $opv eq '-U' || + $opv eq '&U' || $opv eq '&&U') { + if ($ctx !~ /[WEBC]x./ && $ca !~ /(?:\)|!|~|\*|-|\&|\||\+\+|\-\-|\{)$/) { + if (ERROR("SPACING", + "space required before that '$op' $at\n" . $hereptr)) { + if ($n != $last_after + 2) { + $good = $fix_elements[$n] . " " . ltrim($fix_elements[$n + 1]); + $line_fixed = 1; + } + } + } + if ($op eq '*' && $cc =~/\s*$Modifier\b/) { + # A unary '*' may be const + + } elsif ($ctx =~ /.xW/) { + if (ERROR("SPACING", + "space prohibited after that '$op' $at\n" . $hereptr)) { + $good = $fix_elements[$n] . rtrim($fix_elements[$n + 1]); + if (defined $fix_elements[$n + 2]) { + $fix_elements[$n + 2] =~ s/^\s+//; + } + $line_fixed = 1; + } + } + + # unary ++ and unary -- are allowed no space on one side. + } elsif ($op eq '++' or $op eq '--') { + if ($ctx !~ /[WEOBC]x[^W]/ && $ctx !~ /[^W]x[WOBEC]/) { + if (ERROR("SPACING", + "space required one side of that '$op' $at\n" . $hereptr)) { + $good = $fix_elements[$n] . trim($fix_elements[$n + 1]) . " "; + $line_fixed = 1; + } + } + if ($ctx =~ /Wx[BE]/ || + ($ctx =~ /Wx./ && $cc =~ /^;/)) { + if (ERROR("SPACING", + "space prohibited before that '$op' $at\n" . $hereptr)) { + $good = rtrim($fix_elements[$n]) . trim($fix_elements[$n + 1]); + $line_fixed = 1; + } + } + if ($ctx =~ /ExW/) { + if (ERROR("SPACING", + "space prohibited after that '$op' $at\n" . $hereptr)) { + $good = $fix_elements[$n] . trim($fix_elements[$n + 1]); + if (defined $fix_elements[$n + 2]) { + $fix_elements[$n + 2] =~ s/^\s+//; + } + $line_fixed = 1; + } + } + + # << and >> may either have or not have spaces both sides + } elsif ($op eq '<<' or $op eq '>>' or + $op eq '&' or $op eq '^' or $op eq '|' or + $op eq '+' or $op eq '-' or + $op eq '*' or $op eq '/' or + $op eq '%') + { + if ($check) { + if (defined $fix_elements[$n + 2] && $ctx !~ /[EW]x[EW]/) { + if (CHK("SPACING", + "spaces preferred around that '$op' $at\n" . $hereptr)) { + $good = rtrim($fix_elements[$n]) . " " . trim($fix_elements[$n + 1]) . " "; + $fix_elements[$n + 2] =~ s/^\s+//; + $line_fixed = 1; + } + } elsif (!defined $fix_elements[$n + 2] && $ctx !~ /Wx[OE]/) { + if (CHK("SPACING", + "space preferred before that '$op' $at\n" . $hereptr)) { + $good = rtrim($fix_elements[$n]) . " " . trim($fix_elements[$n + 1]); + $line_fixed = 1; + } + } + } elsif ($ctx =~ /Wx[^WCE]|[^WCE]xW/) { + if (ERROR("SPACING", + "need consistent spacing around '$op' $at\n" . $hereptr)) { + $good = rtrim($fix_elements[$n]) . " " . trim($fix_elements[$n + 1]) . " "; + if (defined $fix_elements[$n + 2]) { + $fix_elements[$n + 2] =~ s/^\s+//; + } + $line_fixed = 1; + } + } + + # A colon needs no spaces before when it is + # terminating a case value or a label. + } elsif ($opv eq ':C' || $opv eq ':L') { + if ($ctx =~ /Wx./ and $realfile !~ m@.*\.lds\.h$@) { + if (ERROR("SPACING", + "space prohibited before that '$op' $at\n" . $hereptr)) { + $good = rtrim($fix_elements[$n]) . trim($fix_elements[$n + 1]); + $line_fixed = 1; + } + } + + # All the others need spaces both sides. + } elsif ($ctx !~ /[EWC]x[CWE]/) { + my $ok = 0; + + # Ignore email addresses <foo@bar> + if (($op eq '<' && + $cc =~ /^\S+\@\S+>/) || + ($op eq '>' && + $ca =~ /<\S+\@\S+$/)) + { + $ok = 1; + } + + # for asm volatile statements + # ignore a colon with another + # colon immediately before or after + if (($op eq ':') && + ($ca =~ /:$/ || $cc =~ /^:/)) { + $ok = 1; + } + + # messages are ERROR, but ?: are CHK + if ($ok == 0) { + my $msg_level = \&ERROR; + $msg_level = \&CHK if (($op eq '?:' || $op eq '?' || $op eq ':') && $ctx =~ /VxV/); + + if (&{$msg_level}("SPACING", + "spaces required around that '$op' $at\n" . $hereptr)) { + $good = rtrim($fix_elements[$n]) . " " . trim($fix_elements[$n + 1]) . " "; + if (defined $fix_elements[$n + 2]) { + $fix_elements[$n + 2] =~ s/^\s+//; + } + $line_fixed = 1; + } + } + } + $off += length($elements[$n + 1]); + +## print("n: <$n> GOOD: <$good>\n"); + + $fixed_line = $fixed_line . $good; + } + + if (($#elements % 2) == 0) { + $fixed_line = $fixed_line . $fix_elements[$#elements]; + } + + if ($fix && $line_fixed && $fixed_line ne $fixed[$fixlinenr]) { + $fixed[$fixlinenr] = $fixed_line; + } + + + } + +# check for whitespace before a non-naked semicolon + if ($line =~ /^\+.*\S\s+;\s*$/) { + if (WARN("SPACING", + "space prohibited before semicolon\n" . $herecurr) && + $fix) { + 1 while $fixed[$fixlinenr] =~ + s/^(\+.*\S)\s+;/$1;/; + } + } + +# check for multiple assignments + if ($line =~ /^.\s*$Lval\s*=\s*$Lval\s*=(?!=)/) { + CHK("MULTIPLE_ASSIGNMENTS", + "multiple assignments should be avoided\n" . $herecurr); + } + +## # check for multiple declarations, allowing for a function declaration +## # continuation. +## if ($line =~ /^.\s*$Type\s+$Ident(?:\s*=[^,{]*)?\s*,\s*$Ident.*/ && +## $line !~ /^.\s*$Type\s+$Ident(?:\s*=[^,{]*)?\s*,\s*$Type\s*$Ident.*/) { +## +## # Remove any bracketed sections to ensure we do not +## # falsely report the parameters of functions. +## my $ln = $line; +## while ($ln =~ s/\([^\(\)]*\)//g) { +## } +## if ($ln =~ /,/) { +## WARN("MULTIPLE_DECLARATION", +## "declaring multiple variables together should be avoided\n" . $herecurr); +## } +## } + +#need space before brace following if, while, etc + if (($line =~ /\(.*\)\{/ && $line !~ /\($Type\)\{/) || + $line =~ /\b(?:else|do)\{/) { + if (ERROR("SPACING", + "space required before the open brace '{'\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/^(\+.*(?:do|else|\)))\{/$1 {/; + } + } + +## # check for blank lines before declarations +## if ($line =~ /^.\t+$Type\s+$Ident(?:\s*=.*)?;/ && +## $prevrawline =~ /^.\s*$/) { +## WARN("SPACING", +## "No blank lines before declarations\n" . $hereprev); +## } +## + +# closing brace should have a space following it when it has anything +# on the line + if ($line =~ /}(?!(?:,|;|\)|\}))\S/) { + if (ERROR("SPACING", + "space required after that close brace '}'\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ + s/}((?!(?:,|;|\)))\S)/} $1/; + } + } + +# check spacing on square brackets + if ($line =~ /\[\s/ && $line !~ /\[\s*$/) { + if (ERROR("SPACING", + "space prohibited after that open square bracket '['\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ + s/\[\s+/\[/; + } + } + if ($line =~ /\s\]/) { + if (ERROR("SPACING", + "space prohibited before that close square bracket ']'\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ + s/\s+\]/\]/; + } + } + +# check spacing on parentheses + if ($line =~ /\(\s/ && $line !~ /\(\s*(?:\\)?$/ && + $line !~ /for\s*\(\s+;/) { + if (ERROR("SPACING", + "space prohibited after that open parenthesis '('\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ + s/\(\s+/\(/; + } + } + if ($line =~ /(\s+)\)/ && $line !~ /^.\s*\)/ && + $line !~ /for\s*\(.*;\s+\)/ && + $line !~ /:\s+\)/) { + if (ERROR("SPACING", + "space prohibited before that close parenthesis ')'\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ + s/\s+\)/\)/; + } + } + +# check unnecessary parentheses around addressof/dereference single $Lvals +# ie: &(foo->bar) should be &foo->bar and *(foo->bar) should be *foo->bar + + while ($line =~ /(?:[^&]&\s*|\*)\(\s*($Ident\s*(?:$Member\s*)+)\s*\)/g) { + my $var = $1; + if (CHK("UNNECESSARY_PARENTHESES", + "Unnecessary parentheses around $var\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\(\s*\Q$var\E\s*\)/$var/; + } + } + +# check for unnecessary parentheses around function pointer uses +# ie: (foo->bar)(); should be foo->bar(); +# but not "if (foo->bar) (" to avoid some false positives + if ($line =~ /(\bif\s*|)(\(\s*$Ident\s*(?:$Member\s*)+\))[ \t]*\(/ && $1 !~ /^if/) { + my $var = $2; + if (CHK("UNNECESSARY_PARENTHESES", + "Unnecessary parentheses around function pointer $var\n" . $herecurr) && + $fix) { + my $var2 = deparenthesize($var); + $var2 =~ s/\s//g; + $fixed[$fixlinenr] =~ s/\Q$var\E/$var2/; + } + } + +# check for unnecessary parentheses around comparisons +# except in drivers/staging + if (($realfile !~ m@^(?:drivers/staging/)@) && + $perl_version_ok && defined($stat) && + $stat =~ /(^.\s*if\s*($balanced_parens))/) { + my $if_stat = $1; + my $test = substr($2, 1, -1); + my $herectx; + while ($test =~ /(?:^|[^\w\&\!\~])+\s*\(\s*([\&\!\~]?\s*$Lval\s*(?:$Compare\s*$FuncArg)?)\s*\)/g) { + my $match = $1; + # avoid parentheses around potential macro args + next if ($match =~ /^\s*\w+\s*$/); + if (!defined($herectx)) { + $herectx = $here . "\n"; + my $cnt = statement_rawlines($if_stat); + for (my $n = 0; $n < $cnt; $n++) { + my $rl = raw_line($linenr, $n); + $herectx .= $rl . "\n"; + last if $rl =~ /^[ \+].*\{/; + } + } + CHK("UNNECESSARY_PARENTHESES", + "Unnecessary parentheses around '$match'\n" . $herectx); + } + } + +# check that goto labels aren't indented (allow a single space indentation) +# and ignore bitfield definitions like foo:1 +# Strictly, labels can have whitespace after the identifier and before the : +# but this is not allowed here as many ?: uses would appear to be labels + if ($sline =~ /^.\s+[A-Za-z_][A-Za-z\d_]*:(?!\s*\d+)/ && + $sline !~ /^. [A-Za-z\d_][A-Za-z\d_]*:/ && + $sline !~ /^.\s+default:/) { + if (WARN("INDENTED_LABEL", + "labels should not be indented\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ + s/^(.)\s+/$1/; + } + } + +# check if a statement with a comma should be two statements like: +# foo = bar(), /* comma should be semicolon */ +# bar = baz(); + if (defined($stat) && + $stat =~ /^\+\s*(?:$Lval\s*$Assignment\s*)?$FuncArg\s*,\s*(?:$Lval\s*$Assignment\s*)?$FuncArg\s*;\s*$/) { + my $cnt = statement_rawlines($stat); + my $herectx = get_stat_here($linenr, $cnt, $here); + WARN("SUSPECT_COMMA_SEMICOLON", + "Possible comma where semicolon could be used\n" . $herectx); + } + +# return is not a function + if (defined($stat) && $stat =~ /^.\s*return(\s*)\(/s) { + my $spacing = $1; + if ($perl_version_ok && + $stat =~ /^.\s*return\s*($balanced_parens)\s*;\s*$/) { + my $value = $1; + $value = deparenthesize($value); + if ($value =~ m/^\s*$FuncArg\s*(?:\?|$)/) { + ERROR("RETURN_PARENTHESES", + "return is not a function, parentheses are not required\n" . $herecurr); + } + } elsif ($spacing !~ /\s+/) { + ERROR("SPACING", + "space required before the open parenthesis '('\n" . $herecurr); + } + } + +# unnecessary return in a void function +# at end-of-function, with the previous line a single leading tab, then return; +# and the line before that not a goto label target like "out:" + if ($sline =~ /^[ \+]}\s*$/ && + $prevline =~ /^\+\treturn\s*;\s*$/ && + $linenr >= 3 && + $lines[$linenr - 3] =~ /^[ +]/ && + $lines[$linenr - 3] !~ /^[ +]\s*$Ident\s*:/) { + WARN("RETURN_VOID", + "void function return statements are not generally useful\n" . $hereprev); + } + +# if statements using unnecessary parentheses - ie: if ((foo == bar)) + if ($perl_version_ok && + $line =~ /\bif\s*((?:\(\s*){2,})/) { + my $openparens = $1; + my $count = $openparens =~ tr@\(@\(@; + my $msg = ""; + if ($line =~ /\bif\s*(?:\(\s*){$count,$count}$LvalOrFunc\s*($Compare)\s*$LvalOrFunc(?:\s*\)){$count,$count}/) { + my $comp = $4; #Not $1 because of $LvalOrFunc + $msg = " - maybe == should be = ?" if ($comp eq "=="); + WARN("UNNECESSARY_PARENTHESES", + "Unnecessary parentheses$msg\n" . $herecurr); + } + } + +# comparisons with a constant or upper case identifier on the left +# avoid cases like "foo + BAR < baz" +# only fix matches surrounded by parentheses to avoid incorrect +# conversions like "FOO < baz() + 5" being "misfixed" to "baz() > FOO + 5" + if ($perl_version_ok && + $line =~ /^\+(.*)\b($Constant|[A-Z_][A-Z0-9_]*)\s*($Compare)\s*($LvalOrFunc)/) { + my $lead = $1; + my $const = $2; + my $comp = $3; + my $to = $4; + my $newcomp = $comp; + if ($lead !~ /(?:$Operators|\.)\s*$/ && + $to !~ /^(?:Constant|[A-Z_][A-Z0-9_]*)$/ && + WARN("CONSTANT_COMPARISON", + "Comparisons should place the constant on the right side of the test\n" . $herecurr) && + $fix) { + if ($comp eq "<") { + $newcomp = ">"; + } elsif ($comp eq "<=") { + $newcomp = ">="; + } elsif ($comp eq ">") { + $newcomp = "<"; + } elsif ($comp eq ">=") { + $newcomp = "<="; + } + $fixed[$fixlinenr] =~ s/\(\s*\Q$const\E\s*$Compare\s*\Q$to\E\s*\)/($to $newcomp $const)/; + } + } + +# Return of what appears to be an errno should normally be negative + if ($sline =~ /\breturn(?:\s*\(+\s*|\s+)(E[A-Z]+)(?:\s*\)+\s*|\s*)[;:,]/) { + my $name = $1; + if ($name ne 'EOF' && $name ne 'ERROR' && $name !~ /^EPOLL/) { + WARN("USE_NEGATIVE_ERRNO", + "return of an errno should typically be negative (ie: return -$1)\n" . $herecurr); + } + } + +# Need a space before open parenthesis after if, while etc + if ($line =~ /\b(if|while|for|switch)\(/) { + if (ERROR("SPACING", + "space required before the open parenthesis '('\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ + s/\b(if|while|for|switch)\(/$1 \(/; + } + } + +# Check for illegal assignment in if conditional -- and check for trailing +# statements after the conditional. + if ($line =~ /do\s*(?!{)/) { + ($stat, $cond, $line_nr_next, $remain_next, $off_next) = + ctx_statement_block($linenr, $realcnt, 0) + if (!defined $stat); + my ($stat_next) = ctx_statement_block($line_nr_next, + $remain_next, $off_next); + $stat_next =~ s/\n./\n /g; + ##print "stat<$stat> stat_next<$stat_next>\n"; + + if ($stat_next =~ /^\s*while\b/) { + # If the statement carries leading newlines, + # then count those as offsets. + my ($whitespace) = + ($stat_next =~ /^((?:\s*\n[+-])*\s*)/s); + my $offset = + statement_rawlines($whitespace) - 1; + + $suppress_whiletrailers{$line_nr_next + + $offset} = 1; + } + } + if (!defined $suppress_whiletrailers{$linenr} && + defined($stat) && defined($cond) && + $line =~ /\b(?:if|while|for)\s*\(/ && $line !~ /^.\s*#/) { + my ($s, $c) = ($stat, $cond); + my $fixed_assign_in_if = 0; + + if ($c =~ /\bif\s*\(.*[^<>!=]=[^=].*/s) { + if (ERROR("ASSIGN_IN_IF", + "do not use assignment in if condition\n" . $herecurr) && + $fix && $perl_version_ok) { + if ($rawline =~ /^\+(\s+)if\s*\(\s*(\!)?\s*\(\s*(($Lval)\s*=\s*$LvalOrFunc)\s*\)\s*(?:($Compare)\s*($FuncArg))?\s*\)\s*(\{)?\s*$/) { + my $space = $1; + my $not = $2; + my $statement = $3; + my $assigned = $4; + my $test = $8; + my $against = $9; + my $brace = $15; + fix_delete_line($fixlinenr, $rawline); + fix_insert_line($fixlinenr, "$space$statement;"); + my $newline = "${space}if ("; + $newline .= '!' if defined($not); + $newline .= '(' if (defined $not && defined($test) && defined($against)); + $newline .= "$assigned"; + $newline .= " $test $against" if (defined($test) && defined($against)); + $newline .= ')' if (defined $not && defined($test) && defined($against)); + $newline .= ')'; + $newline .= " {" if (defined($brace)); + fix_insert_line($fixlinenr + 1, $newline); + $fixed_assign_in_if = 1; + } + } + } + + # Find out what is on the end of the line after the + # conditional. + substr($s, 0, length($c), ''); + $s =~ s/\n.*//g; + $s =~ s/$;//g; # Remove any comments + if (length($c) && $s !~ /^\s*{?\s*\\*\s*$/ && + $c !~ /}\s*while\s*/) + { + # Find out how long the conditional actually is. + my @newlines = ($c =~ /\n/gs); + my $cond_lines = 1 + $#newlines; + my $stat_real = ''; + + $stat_real = raw_line($linenr, $cond_lines) + . "\n" if ($cond_lines); + if (defined($stat_real) && $cond_lines > 1) { + $stat_real = "[...]\n$stat_real"; + } + + if (ERROR("TRAILING_STATEMENTS", + "trailing statements should be on next line\n" . $herecurr . $stat_real) && + !$fixed_assign_in_if && + $cond_lines == 0 && + $fix && $perl_version_ok && + $fixed[$fixlinenr] =~ /^\+(\s*)((?:if|while|for)\s*$balanced_parens)\s*(.*)$/) { + my $indent = $1; + my $test = $2; + my $rest = rtrim($4); + if ($rest =~ /;$/) { + $fixed[$fixlinenr] = "\+$indent$test"; + fix_insert_line($fixlinenr + 1, "$indent\t$rest"); + } + } + } + } + +# Check for bitwise tests written as boolean + if ($line =~ / + (?: + (?:\[|\(|\&\&|\|\|) + \s*0[xX][0-9]+\s* + (?:\&\&|\|\|) + | + (?:\&\&|\|\|) + \s*0[xX][0-9]+\s* + (?:\&\&|\|\||\)|\]) + )/x) + { + WARN("HEXADECIMAL_BOOLEAN_TEST", + "boolean test with hexadecimal, perhaps just 1 \& or \|?\n" . $herecurr); + } + +# if and else should not have general statements after it + if ($line =~ /^.\s*(?:}\s*)?else\b(.*)/) { + my $s = $1; + $s =~ s/$;//g; # Remove any comments + if ($s !~ /^\s*(?:\sif|(?:{|)\s*\\?\s*$)/) { + ERROR("TRAILING_STATEMENTS", + "trailing statements should be on next line\n" . $herecurr); + } + } +# if should not continue a brace + if ($line =~ /}\s*if\b/) { + ERROR("TRAILING_STATEMENTS", + "trailing statements should be on next line (or did you mean 'else if'?)\n" . + $herecurr); + } +# case and default should not have general statements after them + if ($line =~ /^.\s*(?:case\s*.*|default\s*):/g && + $line !~ /\G(?: + (?:\s*$;*)(?:\s*{)?(?:\s*$;*)(?:\s*\\)?\s*$| + \s*return\s+ + )/xg) + { + ERROR("TRAILING_STATEMENTS", + "trailing statements should be on next line\n" . $herecurr); + } + + # Check for }<nl>else {, these must be at the same + # indent level to be relevant to each other. + if ($prevline=~/}\s*$/ and $line=~/^.\s*else\s*/ && + $previndent == $indent) { + if (ERROR("ELSE_AFTER_BRACE", + "else should follow close brace '}'\n" . $hereprev) && + $fix && $prevline =~ /^\+/ && $line =~ /^\+/) { + fix_delete_line($fixlinenr - 1, $prevrawline); + fix_delete_line($fixlinenr, $rawline); + my $fixedline = $prevrawline; + $fixedline =~ s/}\s*$//; + if ($fixedline !~ /^\+\s*$/) { + fix_insert_line($fixlinenr, $fixedline); + } + $fixedline = $rawline; + $fixedline =~ s/^(.\s*)else/$1} else/; + fix_insert_line($fixlinenr, $fixedline); + } + } + + if ($prevline=~/}\s*$/ and $line=~/^.\s*while\s*/ && + $previndent == $indent) { + my ($s, $c) = ctx_statement_block($linenr, $realcnt, 0); + + # Find out what is on the end of the line after the + # conditional. + substr($s, 0, length($c), ''); + $s =~ s/\n.*//g; + + if ($s =~ /^\s*;/) { + if (ERROR("WHILE_AFTER_BRACE", + "while should follow close brace '}'\n" . $hereprev) && + $fix && $prevline =~ /^\+/ && $line =~ /^\+/) { + fix_delete_line($fixlinenr - 1, $prevrawline); + fix_delete_line($fixlinenr, $rawline); + my $fixedline = $prevrawline; + my $trailing = $rawline; + $trailing =~ s/^\+//; + $trailing = trim($trailing); + $fixedline =~ s/}\s*$/} $trailing/; + fix_insert_line($fixlinenr, $fixedline); + } + } + } + +#Specific variable tests + while ($line =~ m{($Constant|$Lval)}g) { + my $var = $1; + +#CamelCase + if ($var !~ /^$Constant$/ && + $var =~ /[A-Z][a-z]|[a-z][A-Z]/ && +#Ignore C keywords + $var !~ /^_Generic$/ && +#Ignore some autogenerated defines and enum values + $var !~ /^(?:[A-Z]+_){1,5}[A-Z]{1,3}[a-z]/ && +#Ignore Page<foo> variants + $var !~ /^(?:Clear|Set|TestClear|TestSet|)Page[A-Z]/ && +#Ignore ETHTOOL_LINK_MODE_<foo> variants + $var !~ /^ETHTOOL_LINK_MODE_/ && +#Ignore SI style variants like nS, mV and dB +#(ie: max_uV, regulator_min_uA_show, RANGE_mA_VALUE) + $var !~ /^(?:[a-z0-9_]*|[A-Z0-9_]*)?_?[a-z][A-Z](?:_[a-z0-9_]+|_[A-Z0-9_]+)?$/ && +#Ignore some three character SI units explicitly, like MiB and KHz + $var !~ /^(?:[a-z_]*?)_?(?:[KMGT]iB|[KMGT]?Hz)(?:_[a-z_]+)?$/) { + while ($var =~ m{\b($Ident)}g) { + my $word = $1; + next if ($word !~ /[A-Z][a-z]|[a-z][A-Z]/); + if ($check) { + seed_camelcase_includes(); + if (!$file && !$camelcase_file_seeded) { + seed_camelcase_file($realfile); + $camelcase_file_seeded = 1; + } + } + if (!defined $camelcase{$word}) { + $camelcase{$word} = 1; + CHK("CAMELCASE", + "Avoid CamelCase: <$word>\n" . $herecurr); + } + } + } + } + +#no spaces allowed after \ in define + if ($line =~ /\#\s*define.*\\\s+$/) { + if (WARN("WHITESPACE_AFTER_LINE_CONTINUATION", + "Whitespace after \\ makes next lines useless\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\s+$//; + } + } + +# warn if <asm/foo.h> is #included and <linux/foo.h> is available and includes +# itself <asm/foo.h> (uses RAW line) + if ($tree && $rawline =~ m{^.\s*\#\s*include\s*\<asm\/(.*)\.h\>}) { + my $file = "$1.h"; + my $checkfile = "include/linux/$file"; + if (-f "$root/$checkfile" && + $realfile ne $checkfile && + $1 !~ /$allowed_asm_includes/) + { + my $asminclude = `grep -Ec "#include\\s+<asm/$file>" $root/$checkfile`; + if ($asminclude > 0) { + if ($realfile =~ m{^arch/}) { + CHK("ARCH_INCLUDE_LINUX", + "Consider using #include <linux/$file> instead of <asm/$file>\n" . $herecurr); + } else { + WARN("INCLUDE_LINUX", + "Use #include <linux/$file> instead of <asm/$file>\n" . $herecurr); + } + } + } + } + +# Usually multi-statement macros should be enclosed in a do {} while +# (0) loop. Grab the first statement and ensure its the whole macro +# if its not enclosed in a known good container + if ($realfile !~ m@/vmlinux.lds.h$@ && + $line =~ /^.\s*\#\s*define\s*$Ident(\()?/) { + my $ln = $linenr; + my $cnt = $realcnt; + my ($off, $dstat, $dcond, $rest); + my $ctx = ''; + my $has_flow_statement = 0; + my $has_arg_concat = 0; + ($dstat, $dcond, $ln, $cnt, $off) = + ctx_statement_block($linenr, $realcnt, 0); + $ctx = $dstat; + #print "dstat<$dstat> dcond<$dcond> cnt<$cnt> off<$off>\n"; + #print "LINE<$lines[$ln-1]> len<" . length($lines[$ln-1]) . "\n"; + + $has_flow_statement = 1 if ($ctx =~ /\b(goto|return)\b/); + $has_arg_concat = 1 if ($ctx =~ /\#\#/ && $ctx !~ /\#\#\s*(?:__VA_ARGS__|args)\b/); + + $dstat =~ s/^.\s*\#\s*define\s+$Ident(\([^\)]*\))?\s*//; + my $define_args = $1; + my $define_stmt = $dstat; + my @def_args = (); + + if (defined $define_args && $define_args ne "") { + $define_args = substr($define_args, 1, length($define_args) - 2); + $define_args =~ s/\s*//g; + $define_args =~ s/\\\+?//g; + @def_args = split(",", $define_args); + } + + $dstat =~ s/$;//g; + $dstat =~ s/\\\n.//g; + $dstat =~ s/^\s*//s; + $dstat =~ s/\s*$//s; + + # Flatten any parentheses and braces + while ($dstat =~ s/\([^\(\)]*\)/1u/ || + $dstat =~ s/\{[^\{\}]*\}/1u/ || + $dstat =~ s/.\[[^\[\]]*\]/1u/) + { + } + + # Flatten any obvious string concatenation. + while ($dstat =~ s/($String)\s*$Ident/$1/ || + $dstat =~ s/$Ident\s*($String)/$1/) + { + } + + # Make asm volatile uses seem like a generic function + $dstat =~ s/\b_*asm_*\s+_*volatile_*\b/asm_volatile/g; + + my $exceptions = qr{ + $Declare| + # named exceptions + module_param_named| + MODULE_PARM_DESC| + DECLARE_PER_CPU| + DEFINE_PER_CPU| + static_assert| + # declaration primitives + __typeof__\(| + union| + struct| + \.$Ident\s*=\s*| + ^\"|\"$| + ^\[ + }x; + #print "REST<$rest> dstat<$dstat> ctx<$ctx>\n"; + + $ctx =~ s/\n*$//; + my $stmt_cnt = statement_rawlines($ctx); + my $herectx = get_stat_here($linenr, $stmt_cnt, $here); + + if ($dstat ne '' && + $dstat !~ /^(?:$Ident|-?$Constant),$/ && # 10, // foo(), + $dstat !~ /^(?:$Ident|-?$Constant);$/ && # foo(); + $dstat !~ /^[!~-]?(?:$Lval|$Constant)$/ && # 10 // foo() // !foo // ~foo // -foo // foo->bar // foo.bar->baz + $dstat !~ /^'X'$/ && $dstat !~ /^'XX'$/ && # character constants + $dstat !~ /$exceptions/ && + $dstat !~ /^\.$Ident\s*=/ && # .foo = + $dstat !~ /^(?:\#\s*$Ident|\#\s*$Constant)\s*$/ && # stringification #foo + $dstat !~ /^case\b/ && # case ... + $dstat !~ /^do\s*$Constant\s*while\s*$Constant;?$/ && # do {...} while (...); // do {...} while (...) + $dstat !~ /^while\s*$Constant\s*$Constant\s*$/ && # while (...) {...} + $dstat !~ /^for\s*$Constant$/ && # for (...) + $dstat !~ /^for\s*$Constant\s+(?:$Ident|-?$Constant)$/ && # for (...) bar() + $dstat !~ /^do\s*{/ && # do {... + $dstat !~ /^\(\{/ && # ({... + $ctx !~ /^.\s*#\s*define\s+TRACE_(?:SYSTEM|INCLUDE_FILE|INCLUDE_PATH)\b/) + { + if ($dstat =~ /^\s*if\b/) { + ERROR("MULTISTATEMENT_MACRO_USE_DO_WHILE", + "Macros starting with if should be enclosed by a do - while loop to avoid possible if/else logic defects\n" . "$herectx"); + } elsif ($dstat =~ /;/) { + WARN("MULTISTATEMENT_MACRO_USE_DO_WHILE", + "Non-declarative macros with multiple statements should be enclosed in a do - while loop\n" . "$herectx\nBUT SEE:\n$DO_WHILE_0_ADVICE"); + } else { + ERROR("COMPLEX_MACRO", + "Macros with complex values should be enclosed in parentheses\n" . "$herectx\nBUT SEE:\n$DO_WHILE_0_ADVICE"); + } + + } + + # Make $define_stmt single line, comment-free, etc + my @stmt_array = split('\n', $define_stmt); + my $first = 1; + $define_stmt = ""; + foreach my $l (@stmt_array) { + $l =~ s/\\$//; + if ($first) { + $define_stmt = $l; + $first = 0; + } elsif ($l =~ /^[\+ ]/) { + $define_stmt .= substr($l, 1); + } + } + $define_stmt =~ s/$;//g; + $define_stmt =~ s/\s+/ /g; + $define_stmt = trim($define_stmt); + +# check if any macro arguments are reused (ignore '...' and 'type') + foreach my $arg (@def_args) { + next if ($arg =~ /\.\.\./); + next if ($arg =~ /^type$/i); + my $tmp_stmt = $define_stmt; + $tmp_stmt =~ s/\b(__must_be_array|offsetof|sizeof|sizeof_field|__stringify|typeof|__typeof__|__builtin\w+|typecheck\s*\(\s*$Type\s*,|\#+)\s*\(*\s*$arg\s*\)*\b//g; + $tmp_stmt =~ s/\#+\s*$arg\b//g; + $tmp_stmt =~ s/\b$arg\s*\#\#//g; + my $use_cnt = () = $tmp_stmt =~ /\b$arg\b/g; + if ($use_cnt > 1) { + CHK("MACRO_ARG_REUSE", + "Macro argument reuse '$arg' - possible side-effects?\n" . "$herectx"); + } +# check if any macro arguments may have other precedence issues + if ($tmp_stmt =~ m/($Operators)?\s*\b$arg\b\s*($Operators)?/m && + ((defined($1) && $1 ne ',') || + (defined($2) && $2 ne ','))) { + CHK("MACRO_ARG_PRECEDENCE", + "Macro argument '$arg' may be better as '($arg)' to avoid precedence issues\n" . "$herectx"); + } + +# check if this is an unused argument + if ($define_stmt !~ /\b$arg\b/ && $define_stmt) { + WARN("MACRO_ARG_UNUSED", + "Argument '$arg' is not used in function-like macro\n" . "$herectx"); + } + } + +# check for macros with flow control, but without ## concatenation +# ## concatenation is commonly a macro that defines a function so ignore those + if ($has_flow_statement && !$has_arg_concat) { + my $cnt = statement_rawlines($ctx); + my $herectx = get_stat_here($linenr, $cnt, $here); + + WARN("MACRO_WITH_FLOW_CONTROL", + "Macros with flow control statements should be avoided\n" . "$herectx"); + } + +# check for line continuations outside of #defines, preprocessor #, and asm + + } elsif ($realfile =~ m@/vmlinux.lds.h$@) { + $line =~ s/(\w+)/$maybe_linker_symbol{$1}++/ge; + #print "REAL: $realfile\nln: $line\nkeys:", sort keys %maybe_linker_symbol; + } else { + if ($prevline !~ /^..*\\$/ && + $line !~ /^\+\s*\#.*\\$/ && # preprocessor + $line !~ /^\+.*\b(__asm__|asm)\b.*\\$/ && # asm + $line =~ /^\+.*\\$/) { + WARN("LINE_CONTINUATIONS", + "Avoid unnecessary line continuations\n" . $herecurr); + } + } + +# do {} while (0) macro tests: +# single-statement macros do not need to be enclosed in do while (0) loop, +# macro should not end with a semicolon + if ($perl_version_ok && + $realfile !~ m@/vmlinux.lds.h$@ && + $line =~ /^.\s*\#\s*define\s+$Ident(\()?/) { + my $ln = $linenr; + my $cnt = $realcnt; + my ($off, $dstat, $dcond, $rest); + my $ctx = ''; + ($dstat, $dcond, $ln, $cnt, $off) = + ctx_statement_block($linenr, $realcnt, 0); + $ctx = $dstat; + + $dstat =~ s/\\\n.//g; + $dstat =~ s/$;/ /g; + + if ($dstat =~ /^\+\s*#\s*define\s+$Ident\s*${balanced_parens}\s*do\s*{(.*)\s*}\s*while\s*\(\s*0\s*\)\s*([;\s]*)\s*$/) { + my $stmts = $2; + my $semis = $3; + + $ctx =~ s/\n*$//; + my $cnt = statement_rawlines($ctx); + my $herectx = get_stat_here($linenr, $cnt, $here); + + if (($stmts =~ tr/;/;/) == 1 && + $stmts !~ /^\s*(if|while|for|switch)\b/) { + WARN("SINGLE_STATEMENT_DO_WHILE_MACRO", + "Single statement macros should not use a do {} while (0) loop\n" . "$herectx"); + } + if (defined $semis && $semis ne "") { + WARN("DO_WHILE_MACRO_WITH_TRAILING_SEMICOLON", + "do {} while (0) macros should not be semicolon terminated\n" . "$herectx"); + } + } elsif ($dstat =~ /^\+\s*#\s*define\s+$Ident.*;\s*$/) { + $ctx =~ s/\n*$//; + my $cnt = statement_rawlines($ctx); + my $herectx = get_stat_here($linenr, $cnt, $here); + + WARN("TRAILING_SEMICOLON", + "macros should not use a trailing semicolon\n" . "$herectx"); + } + } + +# check for redundant bracing round if etc + if ($line =~ /(^.*)\bif\b/ && $1 !~ /else\s*$/) { + my ($level, $endln, @chunks) = + ctx_statement_full($linenr, $realcnt, 1); + #print "chunks<$#chunks> linenr<$linenr> endln<$endln> level<$level>\n"; + #print "APW: <<$chunks[1][0]>><<$chunks[1][1]>>\n"; + if ($#chunks > 0 && $level == 0) { + my @allowed = (); + my $allow = 0; + my $seen = 0; + my $herectx = $here . "\n"; + my $ln = $linenr - 1; + for my $chunk (@chunks) { + my ($cond, $block) = @{$chunk}; + + # If the condition carries leading newlines, then count those as offsets. + my ($whitespace) = ($cond =~ /^((?:\s*\n[+-])*\s*)/s); + my $offset = statement_rawlines($whitespace) - 1; + + $allowed[$allow] = 0; + #print "COND<$cond> whitespace<$whitespace> offset<$offset>\n"; + + # We have looked at and allowed this specific line. + $suppress_ifbraces{$ln + $offset} = 1; + + $herectx .= "$rawlines[$ln + $offset]\n[...]\n"; + $ln += statement_rawlines($block) - 1; + + substr($block, 0, length($cond), ''); + + $seen++ if ($block =~ /^\s*{/); + + #print "cond<$cond> block<$block> allowed<$allowed[$allow]>\n"; + if (statement_lines($cond) > 1) { + #print "APW: ALLOWED: cond<$cond>\n"; + $allowed[$allow] = 1; + } + if ($block =~/\b(?:if|for|while)\b/) { + #print "APW: ALLOWED: block<$block>\n"; + $allowed[$allow] = 1; + } + if (statement_block_size($block) > 1) { + #print "APW: ALLOWED: lines block<$block>\n"; + $allowed[$allow] = 1; + } + $allow++; + } + if ($seen) { + my $sum_allowed = 0; + foreach (@allowed) { + $sum_allowed += $_; + } + if ($sum_allowed == 0) { + WARN("BRACES", + "braces {} are not necessary for any arm of this statement\n" . $herectx); + } elsif ($sum_allowed != $allow && + $seen != $allow) { + CHK("BRACES", + "braces {} should be used on all arms of this statement\n" . $herectx); + } + } + } + } + if (!defined $suppress_ifbraces{$linenr - 1} && + $line =~ /\b(if|while|for|else)\b/) { + my $allowed = 0; + + # Check the pre-context. + if (substr($line, 0, $-[0]) =~ /(\}\s*)$/) { + #print "APW: ALLOWED: pre<$1>\n"; + $allowed = 1; + } + + my ($level, $endln, @chunks) = + ctx_statement_full($linenr, $realcnt, $-[0]); + + # Check the condition. + my ($cond, $block) = @{$chunks[0]}; + #print "CHECKING<$linenr> cond<$cond> block<$block>\n"; + if (defined $cond) { + substr($block, 0, length($cond), ''); + } + if (statement_lines($cond) > 1) { + #print "APW: ALLOWED: cond<$cond>\n"; + $allowed = 1; + } + if ($block =~/\b(?:if|for|while)\b/) { + #print "APW: ALLOWED: block<$block>\n"; + $allowed = 1; + } + if (statement_block_size($block) > 1) { + #print "APW: ALLOWED: lines block<$block>\n"; + $allowed = 1; + } + # Check the post-context. + if (defined $chunks[1]) { + my ($cond, $block) = @{$chunks[1]}; + if (defined $cond) { + substr($block, 0, length($cond), ''); + } + if ($block =~ /^\s*\{/) { + #print "APW: ALLOWED: chunk-1 block<$block>\n"; + $allowed = 1; + } + } + if ($level == 0 && $block =~ /^\s*\{/ && !$allowed) { + my $cnt = statement_rawlines($block); + my $herectx = get_stat_here($linenr, $cnt, $here); + + WARN("BRACES", + "braces {} are not necessary for single statement blocks\n" . $herectx); + } + } + +# check for single line unbalanced braces + if ($sline =~ /^.\s*\}\s*else\s*$/ || + $sline =~ /^.\s*else\s*\{\s*$/) { + CHK("BRACES", "Unbalanced braces around else statement\n" . $herecurr); + } + +# check for unnecessary blank lines around braces + if (($line =~ /^.\s*}\s*$/ && $prevrawline =~ /^.\s*$/)) { + if (CHK("BRACES", + "Blank lines aren't necessary before a close brace '}'\n" . $hereprev) && + $fix && $prevrawline =~ /^\+/) { + fix_delete_line($fixlinenr - 1, $prevrawline); + } + } + if (($rawline =~ /^.\s*$/ && $prevline =~ /^..*{\s*$/)) { + if (CHK("BRACES", + "Blank lines aren't necessary after an open brace '{'\n" . $hereprev) && + $fix) { + fix_delete_line($fixlinenr, $rawline); + } + } + +# no volatiles please + my $asm_volatile = qr{\b(__asm__|asm)\s+(__volatile__|volatile)\b}; + if ($line =~ /\bvolatile\b/ && $line !~ /$asm_volatile/) { + WARN("VOLATILE", + "Use of volatile is usually wrong: see Documentation/process/volatile-considered-harmful.rst\n" . $herecurr); + } + +# Check for user-visible strings broken across lines, which breaks the ability +# to grep for the string. Make exceptions when the previous string ends in a +# newline (multiple lines in one string constant) or '\t', '\r', ';', or '{' +# (common in inline assembly) or is a octal \123 or hexadecimal \xaf value + if ($line =~ /^\+\s*$String/ && + $prevline =~ /"\s*$/ && + $prevrawline !~ /(?:\\(?:[ntr]|[0-7]{1,3}|x[0-9a-fA-F]{1,2})|;\s*|\{\s*)"\s*$/) { + if (WARN("SPLIT_STRING", + "quoted string split across lines\n" . $hereprev) && + $fix && + $prevrawline =~ /^\+.*"\s*$/ && + $last_coalesced_string_linenr != $linenr - 1) { + my $extracted_string = get_quoted_string($line, $rawline); + my $comma_close = ""; + if ($rawline =~ /\Q$extracted_string\E(\s*\)\s*;\s*$|\s*,\s*)/) { + $comma_close = $1; + } + + fix_delete_line($fixlinenr - 1, $prevrawline); + fix_delete_line($fixlinenr, $rawline); + my $fixedline = $prevrawline; + $fixedline =~ s/"\s*$//; + $fixedline .= substr($extracted_string, 1) . trim($comma_close); + fix_insert_line($fixlinenr - 1, $fixedline); + $fixedline = $rawline; + $fixedline =~ s/\Q$extracted_string\E\Q$comma_close\E//; + if ($fixedline !~ /\+\s*$/) { + fix_insert_line($fixlinenr, $fixedline); + } + $last_coalesced_string_linenr = $linenr; + } + } + +# check for missing a space in a string concatenation + if ($prevrawline =~ /[^\\]\w"$/ && $rawline =~ /^\+[\t ]+"\w/) { + WARN('MISSING_SPACE', + "break quoted strings at a space character\n" . $hereprev); + } + +# check for an embedded function name in a string when the function is known +# This does not work very well for -f --file checking as it depends on patch +# context providing the function name or a single line form for in-file +# function declarations + if ($line =~ /^\+.*$String/ && + defined($context_function) && + get_quoted_string($line, $rawline) =~ /\b$context_function\b/ && + length(get_quoted_string($line, $rawline)) != (length($context_function) + 2)) { + WARN("EMBEDDED_FUNCTION_NAME", + "Prefer using '\"%s...\", __func__' to using '$context_function', this function's name, in a string\n" . $herecurr); + } + +# check for unnecessary function tracing like uses +# This does not use $logFunctions because there are many instances like +# 'dprintk(FOO, "%s()\n", __func__);' which do not match $logFunctions + if ($rawline =~ /^\+.*\([^"]*"$tracing_logging_tags{0,3}%s(?:\s*\(\s*\)\s*)?$tracing_logging_tags{0,3}(?:\\n)?"\s*,\s*__func__\s*\)\s*;/) { + if (WARN("TRACING_LOGGING", + "Unnecessary ftrace-like logging - prefer using ftrace\n" . $herecurr) && + $fix) { + fix_delete_line($fixlinenr, $rawline); + } + } + +# check for spaces before a quoted newline + if ($rawline =~ /^.*\".*\s\\n/) { + if (WARN("QUOTED_WHITESPACE_BEFORE_NEWLINE", + "unnecessary whitespace before a quoted newline\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/^(\+.*\".*)\s+\\n/$1\\n/; + } + + } + +# concatenated string without spaces between elements + if ($line =~ /$String[A-Z_]/ || + ($line =~ /([A-Za-z0-9_]+)$String/ && $1 !~ /^[Lu]$/)) { + if (CHK("CONCATENATED_STRING", + "Concatenated strings should use spaces between elements\n" . $herecurr) && + $fix) { + while ($line =~ /($String)/g) { + my $extracted_string = substr($rawline, $-[0], $+[0] - $-[0]); + $fixed[$fixlinenr] =~ s/\Q$extracted_string\E([A-Za-z0-9_])/$extracted_string $1/; + $fixed[$fixlinenr] =~ s/([A-Za-z0-9_])\Q$extracted_string\E/$1 $extracted_string/; + } + } + } + +# uncoalesced string fragments + if ($line =~ /$String\s*[Lu]?"/) { + if (WARN("STRING_FRAGMENTS", + "Consecutive strings are generally better as a single string\n" . $herecurr) && + $fix) { + while ($line =~ /($String)(?=\s*")/g) { + my $extracted_string = substr($rawline, $-[0], $+[0] - $-[0]); + $fixed[$fixlinenr] =~ s/\Q$extracted_string\E\s*"/substr($extracted_string, 0, -1)/e; + } + } + } + +# check for non-standard and hex prefixed decimal printf formats + my $show_L = 1; #don't show the same defect twice + my $show_Z = 1; + while ($line =~ /(?:^|")([X\t]*)(?:"|$)/g) { + my $string = substr($rawline, $-[1], $+[1] - $-[1]); + $string =~ s/%%/__/g; + # check for %L + if ($show_L && $string =~ /%[\*\d\.\$]*L([diouxX])/) { + WARN("PRINTF_L", + "\%L$1 is non-standard C, use %ll$1\n" . $herecurr); + $show_L = 0; + } + # check for %Z + if ($show_Z && $string =~ /%[\*\d\.\$]*Z([diouxX])/) { + WARN("PRINTF_Z", + "%Z$1 is non-standard C, use %z$1\n" . $herecurr); + $show_Z = 0; + } + # check for 0x<decimal> + if ($string =~ /0x%[\*\d\.\$\Llzth]*[diou]/) { + ERROR("PRINTF_0XDECIMAL", + "Prefixing 0x with decimal output is defective\n" . $herecurr); + } + } + +# check for line continuations in quoted strings with odd counts of " + if ($rawline =~ /\\$/ && $sline =~ tr/"/"/ % 2) { + WARN("LINE_CONTINUATIONS", + "Avoid line continuations in quoted strings\n" . $herecurr); + } + +# warn about #if 0 + if ($line =~ /^.\s*\#\s*if\s+0\b/) { + WARN("IF_0", + "Consider removing the code enclosed by this #if 0 and its #endif\n" . $herecurr); + } + +# warn about #if 1 + if ($line =~ /^.\s*\#\s*if\s+1\b/) { + WARN("IF_1", + "Consider removing the #if 1 and its #endif\n" . $herecurr); + } + +# check for needless "if (<foo>) fn(<foo>)" uses + if ($prevline =~ /\bif\s*\(\s*($Lval)\s*\)/) { + my $tested = quotemeta($1); + my $expr = '\s*\(\s*' . $tested . '\s*\)\s*;'; + if ($line =~ /\b(kfree|usb_free_urb|debugfs_remove(?:_recursive)?|(?:kmem_cache|mempool|dma_pool)_destroy)$expr/) { + my $func = $1; + if (WARN('NEEDLESS_IF', + "$func(NULL) is safe and this check is probably not required\n" . $hereprev) && + $fix) { + my $do_fix = 1; + my $leading_tabs = ""; + my $new_leading_tabs = ""; + if ($lines[$linenr - 2] =~ /^\+(\t*)if\s*\(\s*$tested\s*\)\s*$/) { + $leading_tabs = $1; + } else { + $do_fix = 0; + } + if ($lines[$linenr - 1] =~ /^\+(\t+)$func\s*\(\s*$tested\s*\)\s*;\s*$/) { + $new_leading_tabs = $1; + if (length($leading_tabs) + 1 ne length($new_leading_tabs)) { + $do_fix = 0; + } + } else { + $do_fix = 0; + } + if ($do_fix) { + fix_delete_line($fixlinenr - 1, $prevrawline); + $fixed[$fixlinenr] =~ s/^\+$new_leading_tabs/\+$leading_tabs/; + } + } + } + } + +# check for unnecessary "Out of Memory" messages + if ($line =~ /^\+.*\b$logFunctions\s*\(/ && + $prevline =~ /^[ \+]\s*if\s*\(\s*(\!\s*|NULL\s*==\s*)?($Lval)(\s*==\s*NULL\s*)?\s*\)/ && + (defined $1 || defined $3) && + $linenr > 3) { + my $testval = $2; + my $testline = $lines[$linenr - 3]; + + my ($s, $c) = ctx_statement_block($linenr - 3, $realcnt, 0); +# print("line: <$line>\nprevline: <$prevline>\ns: <$s>\nc: <$c>\n\n\n"); + + if ($s =~ /(?:^|\n)[ \+]\s*(?:$Type\s*)?\Q$testval\E\s*=\s*(?:\([^\)]*\)\s*)?\s*$allocFunctions\s*\(/ && + $s !~ /\b__GFP_NOWARN\b/ ) { + WARN("OOM_MESSAGE", + "Possible unnecessary 'out of memory' message\n" . $hereprev); + } + } + +# check for logging functions with KERN_<LEVEL> + if ($line !~ /printk(?:_ratelimited|_once)?\s*\(/ && + $line =~ /\b$logFunctions\s*\(.*\b(KERN_[A-Z]+)\b/) { + my $level = $1; + if (WARN("UNNECESSARY_KERN_LEVEL", + "Possible unnecessary $level\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\s*$level\s*//; + } + } + +# check for logging continuations + if ($line =~ /\bprintk\s*\(\s*KERN_CONT\b|\bpr_cont\s*\(/) { + WARN("LOGGING_CONTINUATION", + "Avoid logging continuation uses where feasible\n" . $herecurr); + } + +# check for unnecessary use of %h[xudi] and %hh[xudi] in logging functions + if (defined $stat && + $line =~ /\b$logFunctions\s*\(/ && + index($stat, '"') >= 0) { + my $lc = $stat =~ tr@\n@@; + $lc = $lc + $linenr; + my $stat_real = get_stat_real($linenr, $lc); + pos($stat_real) = index($stat_real, '"'); + while ($stat_real =~ /[^\"%]*(%[\#\d\.\*\-]*(h+)[idux])/g) { + my $pspec = $1; + my $h = $2; + my $lineoff = substr($stat_real, 0, $-[1]) =~ tr@\n@@; + if (WARN("UNNECESSARY_MODIFIER", + "Integer promotion: Using '$h' in '$pspec' is unnecessary\n" . "$here\n$stat_real\n") && + $fix && $fixed[$fixlinenr + $lineoff] =~ /^\+/) { + my $nspec = $pspec; + $nspec =~ s/h//g; + $fixed[$fixlinenr + $lineoff] =~ s/\Q$pspec\E/$nspec/; + } + } + } + +# check for mask then right shift without a parentheses + if ($perl_version_ok && + $line =~ /$LvalOrFunc\s*\&\s*($LvalOrFunc)\s*>>/ && + $4 !~ /^\&/) { # $LvalOrFunc may be &foo, ignore if so + WARN("MASK_THEN_SHIFT", + "Possible precedence defect with mask then right shift - may need parentheses\n" . $herecurr); + } + +# check for pointer comparisons to NULL + if ($perl_version_ok) { + while ($line =~ /\b$LvalOrFunc\s*(==|\!=)\s*NULL\b/g) { + my $val = $1; + my $equal = "!"; + $equal = "" if ($4 eq "!="); + if (CHK("COMPARISON_TO_NULL", + "Comparison to NULL could be written \"${equal}${val}\"\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\b\Q$val\E\s*(?:==|\!=)\s*NULL\b/$equal$val/; + } + } + } + +# check for bad placement of section $InitAttribute (e.g.: __initdata) + if ($line =~ /(\b$InitAttribute\b)/) { + my $attr = $1; + if ($line =~ /^\+\s*static\s+(?:const\s+)?(?:$attr\s+)?($NonptrTypeWithAttr)\s+(?:$attr\s+)?($Ident(?:\[[^]]*\])?)\s*[=;]/) { + my $ptr = $1; + my $var = $2; + if ((($ptr =~ /\b(union|struct)\s+$attr\b/ && + ERROR("MISPLACED_INIT", + "$attr should be placed after $var\n" . $herecurr)) || + ($ptr !~ /\b(union|struct)\s+$attr\b/ && + WARN("MISPLACED_INIT", + "$attr should be placed after $var\n" . $herecurr))) && + $fix) { + $fixed[$fixlinenr] =~ s/(\bstatic\s+(?:const\s+)?)(?:$attr\s+)?($NonptrTypeWithAttr)\s+(?:$attr\s+)?($Ident(?:\[[^]]*\])?)\s*([=;])\s*/"$1" . trim(string_find_replace($2, "\\s*$attr\\s*", " ")) . " " . trim(string_find_replace($3, "\\s*$attr\\s*", "")) . " $attr" . ("$4" eq ";" ? ";" : " = ")/e; + } + } + } + +# check for $InitAttributeData (ie: __initdata) with const + if ($line =~ /\bconst\b/ && $line =~ /($InitAttributeData)/) { + my $attr = $1; + $attr =~ /($InitAttributePrefix)(.*)/; + my $attr_prefix = $1; + my $attr_type = $2; + if (ERROR("INIT_ATTRIBUTE", + "Use of const init definition must use ${attr_prefix}initconst\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ + s/$InitAttributeData/${attr_prefix}initconst/; + } + } + +# check for $InitAttributeConst (ie: __initconst) without const + if ($line !~ /\bconst\b/ && $line =~ /($InitAttributeConst)/) { + my $attr = $1; + if (ERROR("INIT_ATTRIBUTE", + "Use of $attr requires a separate use of const\n" . $herecurr) && + $fix) { + my $lead = $fixed[$fixlinenr] =~ + /(^\+\s*(?:static\s+))/; + $lead = rtrim($1); + $lead = "$lead " if ($lead !~ /^\+$/); + $lead = "${lead}const "; + $fixed[$fixlinenr] =~ s/(^\+\s*(?:static\s+))/$lead/; + } + } + +# check for __read_mostly with const non-pointer (should just be const) + if ($line =~ /\b__read_mostly\b/ && + $line =~ /($Type)\s*$Ident/ && $1 !~ /\*\s*$/ && $1 =~ /\bconst\b/) { + if (ERROR("CONST_READ_MOSTLY", + "Invalid use of __read_mostly with const type\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\s+__read_mostly\b//; + } + } + +# don't use __constant_<foo> functions outside of include/uapi/ + if ($realfile !~ m@^include/uapi/@ && + $line =~ /(__constant_(?:htons|ntohs|[bl]e(?:16|32|64)_to_cpu|cpu_to_[bl]e(?:16|32|64)))\s*\(/) { + my $constant_func = $1; + my $func = $constant_func; + $func =~ s/^__constant_//; + if (WARN("CONSTANT_CONVERSION", + "$constant_func should be $func\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\b$constant_func\b/$func/g; + } + } + +# prefer usleep_range over udelay + if ($line =~ /\budelay\s*\(\s*(\d+)\s*\)/) { + my $delay = $1; + # ignore udelay's < 10, however + if (! ($delay < 10) ) { + CHK("USLEEP_RANGE", + "usleep_range is preferred over udelay; see function description of usleep_range() and udelay().\n" . $herecurr); + } + if ($delay > 2000) { + WARN("LONG_UDELAY", + "long udelay - prefer mdelay; see function description of mdelay().\n" . $herecurr); + } + } + +# warn about unexpectedly long msleep's + if ($line =~ /\bmsleep\s*\((\d+)\);/) { + if ($1 < 20) { + WARN("MSLEEP", + "msleep < 20ms can sleep for up to 20ms; see function description of msleep().\n" . $herecurr); + } + } + +# check for comparisons of jiffies + if ($line =~ /\bjiffies\s*$Compare|$Compare\s*jiffies\b/) { + WARN("JIFFIES_COMPARISON", + "Comparing jiffies is almost always wrong; prefer time_after, time_before and friends\n" . $herecurr); + } + +# check for comparisons of get_jiffies_64() + if ($line =~ /\bget_jiffies_64\s*\(\s*\)\s*$Compare|$Compare\s*get_jiffies_64\s*\(\s*\)/) { + WARN("JIFFIES_COMPARISON", + "Comparing get_jiffies_64() is almost always wrong; prefer time_after64, time_before64 and friends\n" . $herecurr); + } + +# warn about #ifdefs in C files +# if ($line =~ /^.\s*\#\s*if(|n)def/ && ($realfile =~ /\.c$/)) { +# print "#ifdef in C files should be avoided\n"; +# print "$herecurr"; +# $clean = 0; +# } + +# warn about spacing in #ifdefs + if ($line =~ /^.\s*\#\s*(ifdef|ifndef|elif)\s\s+/) { + if (ERROR("SPACING", + "exactly one space required after that #$1\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ + s/^(.\s*\#\s*(ifdef|ifndef|elif))\s{2,}/$1 /; + } + + } + +# check for spinlock_t definitions without a comment. + if ($line =~ /^.\s*(struct\s+mutex|spinlock_t)\s+\S+;/ || + $line =~ /^.\s*(DEFINE_MUTEX)\s*\(/) { + my $which = $1; + if (!ctx_has_comment($first_line, $linenr)) { + CHK("UNCOMMENTED_DEFINITION", + "$1 definition without comment\n" . $herecurr); + } + } +# check for memory barriers without a comment. + + my $barriers = qr{ + mb| + rmb| + wmb + }x; + my $barrier_stems = qr{ + mb__before_atomic| + mb__after_atomic| + store_release| + load_acquire| + store_mb| + (?:$barriers) + }x; + my $all_barriers = qr{ + (?:$barriers)| + smp_(?:$barrier_stems)| + virt_(?:$barrier_stems) + }x; + + if ($line =~ /\b(?:$all_barriers)\s*\(/) { + if (!ctx_has_comment($first_line, $linenr)) { + WARN("MEMORY_BARRIER", + "memory barrier without comment\n" . $herecurr); + } + } + + my $underscore_smp_barriers = qr{__smp_(?:$barrier_stems)}x; + + if ($realfile !~ m@^include/asm-generic/@ && + $realfile !~ m@/barrier\.h$@ && + $line =~ m/\b(?:$underscore_smp_barriers)\s*\(/ && + $line !~ m/^.\s*\#\s*define\s+(?:$underscore_smp_barriers)\s*\(/) { + WARN("MEMORY_BARRIER", + "__smp memory barriers shouldn't be used outside barrier.h and asm-generic\n" . $herecurr); + } + +# check for waitqueue_active without a comment. + if ($line =~ /\bwaitqueue_active\s*\(/) { + if (!ctx_has_comment($first_line, $linenr)) { + WARN("WAITQUEUE_ACTIVE", + "waitqueue_active without comment\n" . $herecurr); + } + } + +# check for data_race without a comment. + if ($line =~ /\bdata_race\s*\(/) { + if (!ctx_has_comment($first_line, $linenr)) { + WARN("DATA_RACE", + "data_race without comment\n" . $herecurr); + } + } + +# check for context_unsafe without a comment. + if ($line =~ /\bcontext_unsafe\b/ && + !ctx_has_comment($first_line, $linenr)) { + WARN("CONTEXT_UNSAFE", + "context_unsafe without comment\n" . $herecurr); + } + +# check of hardware specific defines + if ($line =~ m@^.\s*\#\s*if.*\b(__i386__|__powerpc64__|__sun__|__s390x__)\b@ && $realfile !~ m@include/asm-@) { + CHK("ARCH_DEFINES", + "architecture specific defines should be avoided\n" . $herecurr); + } + +# check that the storage class is not after a type + if ($line =~ /\b($Type)\s+($Storage)\b/) { + WARN("STORAGE_CLASS", + "storage class '$2' should be located before type '$1'\n" . $herecurr); + } +# Check that the storage class is at the beginning of a declaration + if ($line =~ /\b$Storage\b/ && + $line !~ /^.\s*$Storage/ && + $line =~ /^.\s*(.+?)\$Storage\s/ && + $1 !~ /[\,\)]\s*$/) { + WARN("STORAGE_CLASS", + "storage class should be at the beginning of the declaration\n" . $herecurr); + } + +# check the location of the inline attribute, that it is between +# storage class and type. + if ($line =~ /\b$Type\s+$Inline\b/ || + $line =~ /\b$Inline\s+$Storage\b/) { + ERROR("INLINE_LOCATION", + "inline keyword should sit between storage class and type\n" . $herecurr); + } + +# Check for __inline__ and __inline, prefer inline + if ($realfile !~ m@\binclude/uapi/@ && + $line =~ /\b(__inline__|__inline)\b/) { + if (WARN("INLINE", + "plain inline is preferred over $1\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\b(__inline__|__inline)\b/inline/; + + } + } + +# Check for compiler attributes + if ($realfile !~ m@\binclude/uapi/@ && + $rawline =~ /\b__attribute__\s*\(\s*($balanced_parens)\s*\)/) { + my $attr = $1; + $attr =~ s/\s*\(\s*(.*)\)\s*/$1/; + + my %attr_list = ( + "alias" => "__alias", + "aligned" => "__aligned", + "always_inline" => "__always_inline", + "assume_aligned" => "__assume_aligned", + "cold" => "__cold", + "const" => "__attribute_const__", + "copy" => "__copy", + "designated_init" => "__designated_init", + "externally_visible" => "__visible", + "format" => "printf|scanf", + "gnu_inline" => "__gnu_inline", + "malloc" => "__malloc", + "mode" => "__mode", + "no_caller_saved_registers" => "__no_caller_saved_registers", + "noclone" => "__noclone", + "noinline" => "noinline", + "nonstring" => "__nonstring", + "noreturn" => "__noreturn", + "packed" => "__packed", + "pure" => "__pure", + "section" => "__section", + "used" => "__used", + "weak" => "__weak" + ); + + while ($attr =~ /\s*(\w+)\s*(${balanced_parens})?/g) { + my $orig_attr = $1; + my $params = ''; + $params = $2 if defined($2); + my $curr_attr = $orig_attr; + $curr_attr =~ s/^[\s_]+|[\s_]+$//g; + if (exists($attr_list{$curr_attr})) { + my $new = $attr_list{$curr_attr}; + if ($curr_attr eq "format" && $params) { + $params =~ /^\s*\(\s*(\w+)\s*,\s*(.*)/; + $new = "__$1\($2"; + } else { + $new = "$new$params"; + } + if (WARN("PREFER_DEFINED_ATTRIBUTE_MACRO", + "Prefer $new over __attribute__(($orig_attr$params))\n" . $herecurr) && + $fix) { + my $remove = "\Q$orig_attr\E" . '\s*' . "\Q$params\E" . '(?:\s*,\s*)?'; + $fixed[$fixlinenr] =~ s/$remove//; + $fixed[$fixlinenr] =~ s/\b__attribute__/$new __attribute__/; + $fixed[$fixlinenr] =~ s/\}\Q$new\E/} $new/; + $fixed[$fixlinenr] =~ s/ __attribute__\s*\(\s*\(\s*\)\s*\)//; + } + } + } + + # Check for __attribute__ unused, prefer __always_unused or __maybe_unused + if ($attr =~ /^_*unused/) { + WARN("PREFER_DEFINED_ATTRIBUTE_MACRO", + "__always_unused or __maybe_unused is preferred over __attribute__((__unused__))\n" . $herecurr); + } + } + +# Check for __attribute__ weak, or __weak declarations (may have link issues) + if ($perl_version_ok && + $line =~ /(?:$Declare|$DeclareMisordered)\s*$Ident\s*$balanced_parens\s*(?:$Attribute)?\s*;/ && + ($line =~ /\b__attribute__\s*\(\s*\(.*\bweak\b/ || + $line =~ /\b__weak\b/)) { + ERROR("WEAK_DECLARATION", + "Using weak declarations can have unintended link defects\n" . $herecurr); + } + +# check for c99 types like uint8_t used outside of uapi/ and tools/ + if ($realfile !~ m@\binclude/uapi/@ && + $realfile !~ m@\btools/@ && + $line =~ /\b($Declare)\s*$Ident\s*[=;,\[]/) { + my $type = $1; + if ($type =~ /\b($typeC99Typedefs)\b/) { + $type = $1; + my $kernel_type = 'u'; + $kernel_type = 's' if ($type =~ /^_*[si]/); + $type =~ /(\d+)/; + $kernel_type .= $1; + if (CHK("PREFER_KERNEL_TYPES", + "Prefer kernel type '$kernel_type' over '$type'\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\b$type\b/$kernel_type/; + } + } + } + +# check for cast of C90 native int or longer types constants + if ($line =~ /(\(\s*$C90_int_types\s*\)\s*)($Constant)\b/) { + my $cast = $1; + my $const = $2; + my $suffix = ""; + my $newconst = $const; + $newconst =~ s/${Int_type}$//; + $suffix .= 'U' if ($cast =~ /\bunsigned\b/); + if ($cast =~ /\blong\s+long\b/) { + $suffix .= 'LL'; + } elsif ($cast =~ /\blong\b/) { + $suffix .= 'L'; + } + if (WARN("TYPECAST_INT_CONSTANT", + "Unnecessary typecast of c90 int constant - '$cast$const' could be '$const$suffix'\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\Q$cast\E$const\b/$newconst$suffix/; + } + } + +# check for sizeof(&) + if ($line =~ /\bsizeof\s*\(\s*\&/) { + WARN("SIZEOF_ADDRESS", + "sizeof(& should be avoided\n" . $herecurr); + } + +# check for sizeof without parenthesis + if ($line =~ /\bsizeof\s+((?:\*\s*|)$Lval|$Type(?:\s+$Lval|))/) { + if (WARN("SIZEOF_PARENTHESIS", + "sizeof $1 should be sizeof($1)\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\bsizeof\s+((?:\*\s*|)$Lval|$Type(?:\s+$Lval|))/"sizeof(" . trim($1) . ")"/ex; + } + } + +# check for struct spinlock declarations + if ($line =~ /^.\s*\bstruct\s+spinlock\s+\w+\s*;/) { + WARN("USE_SPINLOCK_T", + "struct spinlock should be spinlock_t\n" . $herecurr); + } + +# check for seq_printf uses that could be seq_puts + if ($sline =~ /\bseq_printf\s*\(.*"\s*\)\s*;\s*$/) { + my $fmt = get_quoted_string($line, $rawline); + $fmt =~ s/%%//g; + if ($fmt !~ /%/) { + if (WARN("PREFER_SEQ_PUTS", + "Prefer seq_puts to seq_printf\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\bseq_printf\b/seq_puts/; + } + } + } + +# check for vsprintf extension %p<foo> misuses + if ($perl_version_ok && + defined $stat && + $stat =~ /^\+(?![^\{]*\{\s*).*\b(\w+)\s*\(.*$String\s*,/s && + $1 !~ /^_*volatile_*$/) { + my $stat_real; + + my $lc = $stat =~ tr@\n@@; + $lc = $lc + $linenr; + for (my $count = $linenr; $count <= $lc; $count++) { + my $specifier; + my $extension; + my $qualifier; + my $bad_specifier = ""; + my $fmt = get_quoted_string($lines[$count - 1], raw_line($count, 0)); + $fmt =~ s/%%//g; + + while ($fmt =~ /(\%[\*\d\.]*p(\w)(\w*))/g) { + $specifier = $1; + $extension = $2; + $qualifier = $3; + if ($extension !~ /[4SsBKRraEehMmIiUDdgVCbGNOxtf]/ || + ($extension eq "f" && + defined $qualifier && $qualifier !~ /^w/) || + ($extension eq "4" && + defined $qualifier && $qualifier !~ /^c(?:[hlbc]|hR)$/)) { + $bad_specifier = $specifier; + last; + } + if ($extension eq "x" && !defined($stat_real)) { + if (!defined($stat_real)) { + $stat_real = get_stat_real($linenr, $lc); + } + WARN("VSPRINTF_SPECIFIER_PX", + "Using vsprintf specifier '\%px' potentially exposes the kernel memory layout, if you don't really need the address please consider using '\%p'.\n" . "$here\n$stat_real\n"); + } + } + if ($bad_specifier ne "") { + my $stat_real = get_stat_real($linenr, $lc); + my $msg_level = \&WARN; + my $ext_type = "Invalid"; + my $use = ""; + if ($bad_specifier =~ /p[Ff]/) { + $use = " - use %pS instead"; + $use =~ s/pS/ps/ if ($bad_specifier =~ /pf/); + } elsif ($bad_specifier =~ /pA/) { + $use = " - '%pA' is only intended to be used from Rust code"; + $msg_level = \&ERROR; + } + + &{$msg_level}("VSPRINTF_POINTER_EXTENSION", + "$ext_type vsprintf pointer extension '$bad_specifier'$use\n" . "$here\n$stat_real\n"); + } + } + } + +# Check for misused memsets + if ($perl_version_ok && + defined $stat && + $stat =~ /^\+(?:.*?)\bmemset\s*\(\s*$FuncArg\s*,\s*$FuncArg\s*\,\s*$FuncArg\s*\)/) { + + my $ms_addr = $2; + my $ms_val = $7; + my $ms_size = $12; + + if ($ms_size =~ /^(0x|)0$/i) { + ERROR("MEMSET", + "memset to 0's uses 0 as the 2nd argument, not the 3rd\n" . "$here\n$stat\n"); + } elsif ($ms_size =~ /^(0x|)1$/i) { + WARN("MEMSET", + "single byte memset is suspicious. Swapped 2nd/3rd argument?\n" . "$here\n$stat\n"); + } + } + +# Check for memcpy(foo, bar, ETH_ALEN) that could be ether_addr_copy(foo, bar) +# if ($perl_version_ok && +# defined $stat && +# $stat =~ /^\+(?:.*?)\bmemcpy\s*\(\s*$FuncArg\s*,\s*$FuncArg\s*\,\s*ETH_ALEN\s*\)/) { +# if (WARN("PREFER_ETHER_ADDR_COPY", +# "Prefer ether_addr_copy() over memcpy() if the Ethernet addresses are __aligned(2)\n" . "$here\n$stat\n") && +# $fix) { +# $fixed[$fixlinenr] =~ s/\bmemcpy\s*\(\s*$FuncArg\s*,\s*$FuncArg\s*\,\s*ETH_ALEN\s*\)/ether_addr_copy($2, $7)/; +# } +# } + +# Check for memcmp(foo, bar, ETH_ALEN) that could be ether_addr_equal*(foo, bar) +# if ($perl_version_ok && +# defined $stat && +# $stat =~ /^\+(?:.*?)\bmemcmp\s*\(\s*$FuncArg\s*,\s*$FuncArg\s*\,\s*ETH_ALEN\s*\)/) { +# WARN("PREFER_ETHER_ADDR_EQUAL", +# "Prefer ether_addr_equal() or ether_addr_equal_unaligned() over memcmp()\n" . "$here\n$stat\n") +# } + +# check for memset(foo, 0x0, ETH_ALEN) that could be eth_zero_addr +# check for memset(foo, 0xFF, ETH_ALEN) that could be eth_broadcast_addr +# if ($perl_version_ok && +# defined $stat && +# $stat =~ /^\+(?:.*?)\bmemset\s*\(\s*$FuncArg\s*,\s*$FuncArg\s*\,\s*ETH_ALEN\s*\)/) { +# +# my $ms_val = $7; +# +# if ($ms_val =~ /^(?:0x|)0+$/i) { +# if (WARN("PREFER_ETH_ZERO_ADDR", +# "Prefer eth_zero_addr over memset()\n" . "$here\n$stat\n") && +# $fix) { +# $fixed[$fixlinenr] =~ s/\bmemset\s*\(\s*$FuncArg\s*,\s*$FuncArg\s*,\s*ETH_ALEN\s*\)/eth_zero_addr($2)/; +# } +# } elsif ($ms_val =~ /^(?:0xff|255)$/i) { +# if (WARN("PREFER_ETH_BROADCAST_ADDR", +# "Prefer eth_broadcast_addr() over memset()\n" . "$here\n$stat\n") && +# $fix) { +# $fixed[$fixlinenr] =~ s/\bmemset\s*\(\s*$FuncArg\s*,\s*$FuncArg\s*,\s*ETH_ALEN\s*\)/eth_broadcast_addr($2)/; +# } +# } +# } +# strcpy uses that should likely be strscpy + if ($line =~ /\bstrcpy\s*\(/ && !is_userspace($realfile)) { + WARN("STRCPY", + "Prefer strscpy over strcpy - see: https://github.com/KSPP/linux/issues/88\n" . $herecurr); + } + +# strlcpy uses that should likely be strscpy + if ($line =~ /\bstrlcpy\s*\(/ && !is_userspace($realfile)) { + WARN("STRLCPY", + "Prefer strscpy over strlcpy - see: https://github.com/KSPP/linux/issues/89\n" . $herecurr); + } + +# strncpy uses that should likely be strscpy or strscpy_pad + if ($line =~ /\bstrncpy\s*\(/ && !is_userspace($realfile)) { + WARN("STRNCPY", + "Prefer strscpy, strscpy_pad, or __nonstring over strncpy - see: https://github.com/KSPP/linux/issues/90\n" . $herecurr); + } + +# ethtool_sprintf uses that should likely be ethtool_puts + if ($line =~ /\bethtool_sprintf\s*\(\s*$FuncArg\s*,\s*$FuncArg\s*\)/) { + if (WARN("PREFER_ETHTOOL_PUTS", + "Prefer ethtool_puts over ethtool_sprintf with only two arguments\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\bethtool_sprintf\s*\(\s*($FuncArg)\s*,\s*($FuncArg)/ethtool_puts($1, $7)/; + } + } + + # use $rawline because $line loses %s via sanitization and thus we can't match against it. + if ($rawline =~ /\bethtool_sprintf\s*\(\s*$FuncArg\s*,\s*\"\%s\"\s*,\s*$FuncArg\s*\)/) { + if (WARN("PREFER_ETHTOOL_PUTS", + "Prefer ethtool_puts over ethtool_sprintf with standalone \"%s\" specifier\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\bethtool_sprintf\s*\(\s*($FuncArg)\s*,\s*"\%s"\s*,\s*($FuncArg)/ethtool_puts($1, $7)/; + } + } + + +# typecasts on min/max could be min_t/max_t + if ($perl_version_ok && + defined $stat && + $stat =~ /^\+(?:.*?)\b(min|max)\s*\(\s*$FuncArg\s*,\s*$FuncArg\s*\)/) { + if (defined $2 || defined $7) { + my $call = $1; + my $cast1 = deparenthesize($2); + my $arg1 = $3; + my $cast2 = deparenthesize($7); + my $arg2 = $8; + my $cast; + + if ($cast1 ne "" && $cast2 ne "" && $cast1 ne $cast2) { + $cast = "$cast1 or $cast2"; + } elsif ($cast1 ne "") { + $cast = $cast1; + } else { + $cast = $cast2; + } + WARN("MINMAX", + "$call() should probably be ${call}_t($cast, $arg1, $arg2)\n" . "$here\n$stat\n"); + } + } + +# check usleep_range arguments + if ($perl_version_ok && + defined $stat && + $stat =~ /^\+(?:.*?)\busleep_range\s*\(\s*($FuncArg)\s*,\s*($FuncArg)\s*\)/) { + my $min = $1; + my $max = $7; + if ($min eq $max) { + WARN("USLEEP_RANGE", + "usleep_range should not use min == max args; see function description of usleep_range().\n" . "$here\n$stat\n"); + } elsif ($min =~ /^\d+$/ && $max =~ /^\d+$/ && + $min > $max) { + WARN("USLEEP_RANGE", + "usleep_range args reversed, use min then max; see function description of usleep_range().\n" . "$here\n$stat\n"); + } + } + +# check for naked sscanf + if ($perl_version_ok && + defined $stat && + $line =~ /\bsscanf\b/ && + ($stat !~ /$Ident\s*=\s*sscanf\s*$balanced_parens/ && + $stat !~ /\bsscanf\s*$balanced_parens\s*(?:$Compare)/ && + $stat !~ /(?:$Compare)\s*\bsscanf\s*$balanced_parens/)) { + my $lc = $stat =~ tr@\n@@; + $lc = $lc + $linenr; + my $stat_real = get_stat_real($linenr, $lc); + WARN("NAKED_SSCANF", + "unchecked sscanf return value\n" . "$here\n$stat_real\n"); + } + +# check for simple sscanf that should be kstrto<foo> + if ($perl_version_ok && + defined $stat && + $line =~ /\bsscanf\b/) { + my $lc = $stat =~ tr@\n@@; + $lc = $lc + $linenr; + my $stat_real = get_stat_real($linenr, $lc); + if ($stat_real =~ /\bsscanf\b\s*\(\s*$FuncArg\s*,\s*("[^"]+")/) { + my $format = $6; + my $count = $format =~ tr@%@%@; + if ($count == 1 && + $format =~ /^"\%(?i:ll[udxi]|[udxi]ll|ll|[hl]h?[udxi]|[udxi][hl]h?|[hl]h?|[udxi])"$/) { + WARN("SSCANF_TO_KSTRTO", + "Prefer kstrto<type> to single variable sscanf\n" . "$here\n$stat_real\n"); + } + } + } + +# check for new externs in .h files. + if ($realfile =~ /\.h$/ && + $line =~ /^\+\s*(extern\s+)$Type\s*$Ident\s*\(/s) { + if (CHK("AVOID_EXTERNS", + "extern prototypes should be avoided in .h files\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/(.*)\bextern\b\s*(.*)/$1$2/; + } + } + +# check for new externs in .c files. + if ($realfile =~ /\.c$/ && defined $stat && + $stat =~ /^.\s*(?:extern\s+)?$Type\s+($Ident)(\s*)\(/s) + { + my $function_name = $1; + my $paren_space = $2; + + my $s = $stat; + if (defined $cond) { + substr($s, 0, length($cond), ''); + } + if ($s =~ /^\s*;/) + { + WARN("AVOID_EXTERNS", + "externs should be avoided in .c files\n" . $herecurr); + } + + if ($paren_space =~ /\n/) { + WARN("FUNCTION_ARGUMENTS", + "arguments for function declarations should follow identifier\n" . $herecurr); + } + + } elsif ($realfile =~ /\.c$/ && defined $stat && + $stat =~ /^\+extern struct\s+(\w+)\s+(\w+)\[\];/) + { + my ($st_type, $st_name) = ($1, $2); + + for my $s (keys %maybe_linker_symbol) { + #print "Linker symbol? $st_name : $s\n"; + goto LIKELY_LINKER_SYMBOL + if $st_name =~ /$s/; + } + WARN("AVOID_EXTERNS", + "found a file-scoped extern type:$st_type name:$st_name in .c file\n" + . "is this a linker symbol ?\n" . $herecurr); + LIKELY_LINKER_SYMBOL: + + } elsif ($realfile =~ /\.c$/ && defined $stat && + $stat =~ /^.\s*extern\s+/) + { + WARN("AVOID_EXTERNS", + "externs should be avoided in .c files\n" . $herecurr); + } + +# check for function declarations that have arguments without identifier names + if (defined $stat && + $stat =~ /^.\s*(?:extern\s+)?$Type\s*(?:$Ident|\(\s*\*\s*$Ident\s*\))\s*\(\s*([^{]+)\s*\)\s*;/s && + $1 ne "void") { + my $args = trim($1); + while ($args =~ m/\s*($Type\s*(?:$Ident|\(\s*\*\s*$Ident?\s*\)\s*$balanced_parens)?)/g) { + my $arg = trim($1); + if ($arg =~ /^$Type$/ && $arg !~ /enum\s+$Ident$/) { + WARN("FUNCTION_ARGUMENTS", + "function definition argument '$arg' should also have an identifier name\n" . $herecurr); + } + } + } + +# check for function definitions + if ($perl_version_ok && + defined $stat && + $stat =~ /^.\s*(?:$Storage\s+)?$Type\s*($Ident)\s*$balanced_parens\s*{/s) { + $context_function = $1; + +# check for multiline function definition with misplaced open brace + my $ok = 0; + my $cnt = statement_rawlines($stat); + my $herectx = $here . "\n"; + for (my $n = 0; $n < $cnt; $n++) { + my $rl = raw_line($linenr, $n); + $herectx .= $rl . "\n"; + $ok = 1 if ($rl =~ /^[ \+]\{/); + $ok = 1 if ($rl =~ /\{/ && $n == 0); + last if $rl =~ /^[ \+].*\{/; + } + if (!$ok) { + ERROR("OPEN_BRACE", + "open brace '{' following function definitions go on the next line\n" . $herectx); + } + } + +# checks for new __setup's + if ($rawline =~ /\b__setup\("([^"]*)"/) { + my $name = $1; + + if (!grep(/$name/, @setup_docs)) { + CHK("UNDOCUMENTED_SETUP", + "__setup appears un-documented -- check Documentation/admin-guide/kernel-parameters.txt\n" . $herecurr); + } + } + +# check for pointless casting of alloc functions + if ($line =~ /\*\s*\)\s*$allocFunctions\b/) { + WARN("UNNECESSARY_CASTS", + "unnecessary cast may hide bugs, see http://c-faq.com/malloc/mallocnocast.html\n" . $herecurr); + } + +# alloc style +# p = alloc(sizeof(struct foo), ...) should be p = alloc(sizeof(*p), ...) + if ($perl_version_ok && + $line =~ /\b($Lval)\s*\=\s*(?:$balanced_parens)?\s*((?:kv|k|v)[mz]alloc(?:_node)?)\s*\(\s*(sizeof\s*\(\s*struct\s+$Lval\s*\))/) { + CHK("ALLOC_SIZEOF_STRUCT", + "Prefer $3(sizeof(*$1)...) over $3($4...)\n" . $herecurr); + } + +# check for (kv|k)[mz]alloc that could be kmalloc_obj/kvmalloc_obj/kzalloc_obj/kvzalloc_obj + if ($perl_version_ok && + defined $stat && + $stat =~ /^\+\s*($Lval)\s*\=\s*(?:$balanced_parens)?\s*((?:kv|k)[mz]alloc)\s*\(\s*($FuncArg)\s*,/) { + my $oldfunc = $3; + my $a1 = $4; + my $newfunc = "kmalloc_obj"; + $newfunc = "kvmalloc_obj" if ($oldfunc eq "kvmalloc"); + $newfunc = "kvzalloc_obj" if ($oldfunc eq "kvzalloc"); + $newfunc = "kzalloc_obj" if ($oldfunc eq "kzalloc"); + + if ($a1 =~ s/^sizeof\s*\S\(?([^\)]*)\)?$/$1/) { + my $cnt = statement_rawlines($stat); + my $herectx = get_stat_here($linenr, $cnt, $here); + + if (WARN("ALLOC_WITH_SIZEOF", + "Prefer $newfunc over $oldfunc with sizeof\n" . $herectx) && + $cnt == 1 && + $fix) { + $fixed[$fixlinenr] =~ s/\b($Lval)\s*\=\s*(?:$balanced_parens)?\s*((?:kv|k)[mz]alloc)\s*\(\s*($FuncArg)\s*,/$1 = $newfunc($a1,/; + } + } + } + + +# check for (kv|k)[mz]alloc with multiplies that could be kmalloc_objs/kvmalloc_objs/kzalloc_objs/kvzalloc_objs + if ($perl_version_ok && + defined $stat && + $stat =~ /^\+\s*($Lval)\s*\=\s*(?:$balanced_parens)?\s*((?:kv|k)[mz]alloc)\s*\(\s*($FuncArg)\s*\*\s*($FuncArg)\s*,/) { + my $oldfunc = $3; + my $a1 = $4; + my $a2 = $10; + my $newfunc = "kmalloc_objs"; + $newfunc = "kvmalloc_objs" if ($oldfunc eq "kvmalloc"); + $newfunc = "kvzalloc_objs" if ($oldfunc eq "kvzalloc"); + $newfunc = "kzalloc_objs" if ($oldfunc eq "kzalloc"); + my $r1 = $a1; + my $r2 = $a2; + if ($a1 =~ /^sizeof\s*\S/) { + $r1 = $a2; + $r2 = $a1; + } + if ($r1 !~ /^sizeof\b/ && $r2 =~ /^sizeof\s*\S/ && + !($r1 =~ /^$Constant$/ || $r1 =~ /^[A-Z_][A-Z0-9_]*$/)) { + my $cnt = statement_rawlines($stat); + my $herectx = get_stat_here($linenr, $cnt, $here); + + if (WARN("ALLOC_WITH_MULTIPLY", + "Prefer $newfunc over $oldfunc with multiply\n" . $herectx) && + $cnt == 1 && + $fix) { + my $sized = trim($r2); + $sized =~ s/^sizeof\s*\S\(?([^\)]*)\)?$/$1/; + $fixed[$fixlinenr] =~ s/\b($Lval)\s*\=\s*(?:$balanced_parens)?\s*((?:kv|k)[mz]alloc)\s*\(\s*($FuncArg)\s*\*\s*($FuncArg)/$1 . ' = ' . "$newfunc(" . $sized . ', ' . trim($r1)/e; + } + } + } + +# check for krealloc arg reuse + if ($perl_version_ok && + $line =~ /\b($Lval)\s*\=\s*(?:$balanced_parens)?\s*krealloc\s*\(\s*($Lval)\s*,/ && + $1 eq $3) { + WARN("KREALLOC_ARG_REUSE", + "Reusing the krealloc arg is almost always a bug\n" . $herecurr); + } + +# check for alloc argument mismatch + if ($line =~ /\b((?:devm_)?((?:k|kv)?(calloc|malloc_array)(?:_node)?))\s*\(\s*sizeof\b/) { + WARN("ALLOC_ARRAY_ARGS", + "$1 uses number as first arg, sizeof is generally wrong\n" . $herecurr); + } + +# check for multiple semicolons + if ($line =~ /;\s*;\s*$/) { + if (WARN("ONE_SEMICOLON", + "Statements terminations use 1 semicolon\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/(\s*;\s*){2,}$/;/g; + } + } + +# check for #defines like: 1 << <digit> that could be BIT(digit), it is not exported to uapi + if ($realfile !~ m@^include/uapi/@ && + $line =~ /#\s*define\s+\w+\s+\(?\s*1\s*([ulUL]*)\s*\<\<\s*(?:\d+|$Ident)\s*\)?/) { + my $ull = ""; + $ull = "_ULL" if (defined($1) && $1 =~ /ll/i); + if (CHK("BIT_MACRO", + "Prefer using the BIT$ull macro\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\(?\s*1\s*[ulUL]*\s*<<\s*(\d+|$Ident)\s*\)?/BIT${ull}($1)/; + } + } + +# check for IS_ENABLED() without CONFIG_<FOO> ($rawline for comments too) + if ($rawline =~ /\bIS_ENABLED\s*\(\s*(\w+)\s*\)/ && $1 !~ /^${CONFIG_}/) { + WARN("IS_ENABLED_CONFIG", + "IS_ENABLED($1) is normally used as IS_ENABLED(${CONFIG_}$1)\n" . $herecurr); + } + +# check for #if defined CONFIG_<FOO> || defined CONFIG_<FOO>_MODULE + if ($line =~ /^\+\s*#\s*if\s+defined(?:\s*\(?\s*|\s+)(${CONFIG_}[A-Z_]+)\s*\)?\s*\|\|\s*defined(?:\s*\(?\s*|\s+)\1_MODULE\s*\)?\s*$/) { + my $config = $1; + if (WARN("PREFER_IS_ENABLED", + "Prefer IS_ENABLED(<FOO>) to ${CONFIG_}<FOO> || ${CONFIG_}<FOO>_MODULE\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] = "\+#if IS_ENABLED($config)"; + } + } + +# check for /* fallthrough */ like comment, prefer fallthrough; + my @fallthroughs = ( + 'fallthrough', + '@fallthrough@', + 'lint -fallthrough[ \t]*', + 'intentional(?:ly)?[ \t]*fall(?:(?:s | |-)[Tt]|t)hr(?:ough|u|ew)', + '(?:else,?\s*)?FALL(?:S | |-)?THR(?:OUGH|U|EW)[ \t.!]*(?:-[^\n\r]*)?', + 'Fall(?:(?:s | |-)[Tt]|t)hr(?:ough|u|ew)[ \t.!]*(?:-[^\n\r]*)?', + 'fall(?:s | |-)?thr(?:ough|u|ew)[ \t.!]*(?:-[^\n\r]*)?', + ); + if ($raw_comment ne '') { + foreach my $ft (@fallthroughs) { + if ($raw_comment =~ /$ft/) { + my $msg_level = \&WARN; + $msg_level = \&CHK if ($file); + &{$msg_level}("PREFER_FALLTHROUGH", + "Prefer 'fallthrough;' over fallthrough comment\n" . $herecurr); + last; + } + } + } + +# check for switch/default statements without a break; + if ($perl_version_ok && + defined $stat && + $stat =~ /^\+[$;\s]*(?:case[$;\s]+\w+[$;\s]*:[$;\s]*|)*[$;\s]*\bdefault[$;\s]*:[$;\s]*;/g) { + my $cnt = statement_rawlines($stat); + my $herectx = get_stat_here($linenr, $cnt, $here); + + WARN("DEFAULT_NO_BREAK", + "switch default: should use break\n" . $herectx); + } + +# check for gcc specific __FUNCTION__ + if ($line =~ /\b__FUNCTION__\b/) { + if (WARN("USE_FUNC", + "__func__ should be used instead of gcc specific __FUNCTION__\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\b__FUNCTION__\b/__func__/g; + } + } + +# check for uses of __DATE__, __TIME__, __TIMESTAMP__ + while ($line =~ /\b(__(?:DATE|TIME|TIMESTAMP)__)\b/g) { + ERROR("DATE_TIME", + "Use of the '$1' macro makes the build non-deterministic\n" . $herecurr); + } + +# check for use of yield() + if ($line =~ /\byield\s*\(\s*\)/) { + WARN("YIELD", + "Using yield() is generally wrong. See yield() kernel-doc (sched/core.c)\n" . $herecurr); + } + +# check for comparisons against true and false + if ($line =~ /\+\s*(.*?)\b(true|false|$Lval)\s*(==|\!=)\s*(true|false|$Lval)\b(.*)$/i) { + my $lead = $1; + my $arg = $2; + my $test = $3; + my $otype = $4; + my $trail = $5; + my $op = "!"; + + ($arg, $otype) = ($otype, $arg) if ($arg =~ /^(?:true|false)$/i); + + my $type = lc($otype); + if ($type =~ /^(?:true|false)$/) { + if (("$test" eq "==" && "$type" eq "true") || + ("$test" eq "!=" && "$type" eq "false")) { + $op = ""; + } + + CHK("BOOL_COMPARISON", + "Using comparison to $otype is error prone\n" . $herecurr); + +## maybe suggesting a correct construct would better +## "Using comparison to $otype is error prone. Perhaps use '${lead}${op}${arg}${trail}'\n" . $herecurr); + + } + } + +# check for semaphores initialized locked + if ($line =~ /^.\s*sema_init.+,\W?0\W?\)/) { + WARN("CONSIDER_COMPLETION", + "consider using a completion\n" . $herecurr); + } + +# recommend kstrto* over simple_strto* and strict_strto* + if ($line =~ /\b((simple|strict)_(strto(l|ll|ul|ull)))\s*\(/) { + WARN("CONSIDER_KSTRTO", + "$1 is obsolete, use k$3 instead\n" . $herecurr); + } + +# check for __initcall(), use device_initcall() explicitly or more appropriate function please + if ($line =~ /^.\s*__initcall\s*\(/) { + WARN("USE_DEVICE_INITCALL", + "please use device_initcall() or more appropriate function instead of __initcall() (see include/linux/init.h)\n" . $herecurr); + } + +# check for spin_is_locked(), suggest lockdep instead + if ($line =~ /\bspin_is_locked\(/) { + WARN("USE_LOCKDEP", + "Where possible, use lockdep_assert_held instead of assertions based on spin_is_locked\n" . $herecurr); + } + +# check for deprecated apis + if ($line =~ /\b($deprecated_apis_search)\b\s*\(/) { + my $deprecated_api = $1; + my $new_api = $deprecated_apis{$deprecated_api}; + WARN("DEPRECATED_API", + "Deprecated use of '$deprecated_api', prefer '$new_api' instead\n" . $herecurr); + } + +# check for various structs that are normally const (ops, kgdb, device_tree) +# and avoid what seem like struct definitions 'struct foo {' + if (defined($const_structs) && + $line !~ /\bconst\b/ && + $line =~ /\bstruct\s+($const_structs)\b(?!\s*\{)/) { + WARN("CONST_STRUCT", + "struct $1 should normally be const\n" . $herecurr); + } + +# use of NR_CPUS is usually wrong +# ignore definitions of NR_CPUS and usage to define arrays as likely right +# ignore designated initializers using NR_CPUS + if ($line =~ /\bNR_CPUS\b/ && + $line !~ /^.\s*\s*#\s*if\b.*\bNR_CPUS\b/ && + $line !~ /^.\s*\s*#\s*define\b.*\bNR_CPUS\b/ && + $line !~ /^.\s*$Declare\s.*\[[^\]]*NR_CPUS[^\]]*\]/ && + $line !~ /\[[^\]]*\.\.\.[^\]]*NR_CPUS[^\]]*\]/ && + $line !~ /\[[^\]]*NR_CPUS[^\]]*\.\.\.[^\]]*\]/ && + $line !~ /^.\s*\.\w+\s*=\s*.*\bNR_CPUS\b/) + { + WARN("NR_CPUS", + "usage of NR_CPUS is often wrong - consider using cpu_possible(), num_possible_cpus(), for_each_possible_cpu(), etc\n" . $herecurr); + } + +# Use of __ARCH_HAS_<FOO> or ARCH_HAVE_<BAR> is wrong. + if ($line =~ /\+\s*#\s*define\s+((?:__)?ARCH_(?:HAS|HAVE)\w*)\b/) { + ERROR("DEFINE_ARCH_HAS", + "#define of '$1' is wrong - use Kconfig variables or standard guards instead\n" . $herecurr); + } + +# likely/unlikely comparisons similar to "(likely(foo) > 0)" + if ($perl_version_ok && + $line =~ /\b((?:un)?likely)\s*\(\s*$FuncArg\s*\)\s*$Compare/) { + WARN("LIKELY_MISUSE", + "Using $1 should generally have parentheses around the comparison\n" . $herecurr); + } + +# return sysfs_emit(foo, fmt, ...) fmt without newline + if ($line =~ /\breturn\s+sysfs_emit\s*\(\s*$FuncArg\s*,\s*($String)/ && + substr($rawline, $-[6], $+[6] - $-[6]) !~ /\\n"$/) { + my $offset = $+[6] - 1; + if (WARN("SYSFS_EMIT", + "return sysfs_emit(...) formats should include a terminating newline\n" . $herecurr) && + $fix) { + substr($fixed[$fixlinenr], $offset, 0) = '\\n'; + } + } + +# check for array definition/declarations that should use flexible arrays instead + if ($sline =~ /^[\+ ]\s*\}(?:\s*__packed)?\s*;\s*$/ && + $prevline =~ /^\+\s*(?:\}(?:\s*__packed\s*)?|$Type)\s*$Ident\s*\[\s*(0|1)\s*\]\s*;\s*$/) { + if (ERROR("FLEXIBLE_ARRAY", + "Use C99 flexible arrays - see https://docs.kernel.org/process/deprecated.html#zero-length-and-one-element-arrays\n" . $hereprev) && + $1 == '0' && $fix) { + $fixed[$fixlinenr - 1] =~ s/\[\s*0\s*\]/[]/; + } + } + +# nested likely/unlikely calls + if ($line =~ /\b(?:(?:un)?likely)\s*\(\s*!?\s*(IS_ERR(?:_OR_NULL|_VALUE)?|WARN)/) { + WARN("LIKELY_MISUSE", + "nested (un)?likely() calls, $1 already uses unlikely() internally\n" . $herecurr); + } + +# whine mightly about in_atomic + if ($line =~ /\bin_atomic\s*\(/) { + if ($realfile =~ m@^drivers/@) { + ERROR("IN_ATOMIC", + "do not use in_atomic in drivers\n" . $herecurr); + } elsif ($realfile !~ m@^kernel/@) { + WARN("IN_ATOMIC", + "use of in_atomic() is incorrect outside core kernel code\n" . $herecurr); + } + } + +# Complain about RCU Tasks Trace used outside of BPF (and of course, RCU). + our $rcu_trace_funcs = qr{(?x: + rcu_read_lock_trace | + rcu_read_lock_trace_held | + rcu_read_unlock_trace | + call_rcu_tasks_trace | + synchronize_rcu_tasks_trace | + rcu_barrier_tasks_trace | + rcu_request_urgent_qs_task + )}; + our $rcu_trace_paths = qr{(?x: + kernel/bpf/ | + include/linux/bpf | + net/bpf/ | + kernel/rcu/ | + include/linux/rcu + )}; + if ($line =~ /\b($rcu_trace_funcs)\s*\(/) { + if ($realfile !~ m{^$rcu_trace_paths}) { + WARN("RCU_TASKS_TRACE", + "use of RCU tasks trace is incorrect outside BPF or core RCU code\n" . $herecurr); + } + } + +# check for lockdep_set_novalidate_class + if ($line =~ /^.\s*lockdep_set_novalidate_class\s*\(/ || + $line =~ /__lockdep_no_validate__\s*\)/ ) { + if ($realfile !~ m@^kernel/lockdep@ && + $realfile !~ m@^include/linux/lockdep@ && + $realfile !~ m@^drivers/base/core@) { + ERROR("LOCKDEP", + "lockdep_no_validate class is reserved for device->mutex.\n" . $herecurr); + } + } + + if ($line =~ /debugfs_create_\w+.*\b$mode_perms_world_writable\b/ || + $line =~ /DEVICE_ATTR.*\b$mode_perms_world_writable\b/) { + WARN("EXPORTED_WORLD_WRITABLE", + "Exporting world writable files is usually an error. Consider more restrictive permissions.\n" . $herecurr); + } + +# check for DEVICE_ATTR uses that could be DEVICE_ATTR_<FOO> +# and whether or not function naming is typical and if +# DEVICE_ATTR permissions uses are unusual too + if ($perl_version_ok && + defined $stat && + $stat =~ /\bDEVICE_ATTR\s*\(\s*(\w+)\s*,\s*\(?\s*(\s*(?:${multi_mode_perms_string_search}|0[0-7]{3,3})\s*)\s*\)?\s*,\s*(\w+)\s*,\s*(\w+)\s*\)/) { + my $var = $1; + my $perms = $2; + my $show = $3; + my $store = $4; + my $octal_perms = perms_to_octal($perms); + if ($show =~ /^${var}_show$/ && + $store =~ /^${var}_store$/ && + $octal_perms eq "0644") { + if (WARN("DEVICE_ATTR_RW", + "Use DEVICE_ATTR_RW\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\bDEVICE_ATTR\s*\(\s*$var\s*,\s*\Q$perms\E\s*,\s*$show\s*,\s*$store\s*\)/DEVICE_ATTR_RW(${var})/; + } + } elsif ($show =~ /^${var}_show$/ && + $store =~ /^NULL$/ && + $octal_perms eq "0444") { + if (WARN("DEVICE_ATTR_RO", + "Use DEVICE_ATTR_RO\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\bDEVICE_ATTR\s*\(\s*$var\s*,\s*\Q$perms\E\s*,\s*$show\s*,\s*NULL\s*\)/DEVICE_ATTR_RO(${var})/; + } + } elsif ($show =~ /^NULL$/ && + $store =~ /^${var}_store$/ && + $octal_perms eq "0200") { + if (WARN("DEVICE_ATTR_WO", + "Use DEVICE_ATTR_WO\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\bDEVICE_ATTR\s*\(\s*$var\s*,\s*\Q$perms\E\s*,\s*NULL\s*,\s*$store\s*\)/DEVICE_ATTR_WO(${var})/; + } + } elsif ($octal_perms eq "0644" || + $octal_perms eq "0444" || + $octal_perms eq "0200") { + my $newshow = "$show"; + $newshow = "${var}_show" if ($show ne "NULL" && $show ne "${var}_show"); + my $newstore = $store; + $newstore = "${var}_store" if ($store ne "NULL" && $store ne "${var}_store"); + my $rename = ""; + if ($show ne $newshow) { + $rename .= " '$show' to '$newshow'"; + } + if ($store ne $newstore) { + $rename .= " '$store' to '$newstore'"; + } + WARN("DEVICE_ATTR_FUNCTIONS", + "Consider renaming function(s)$rename\n" . $herecurr); + } else { + WARN("DEVICE_ATTR_PERMS", + "DEVICE_ATTR unusual permissions '$perms' used\n" . $herecurr); + } + } + +# Mode permission misuses where it seems decimal should be octal +# This uses a shortcut match to avoid unnecessary uses of a slow foreach loop +# o Ignore module_param*(...) uses with a decimal 0 permission as that has a +# specific definition of not visible in sysfs. +# o Ignore proc_create*(...) uses with a decimal 0 permission as that means +# use the default permissions + if ($perl_version_ok && + defined $stat && + $line =~ /$mode_perms_search/) { + foreach my $entry (@mode_permission_funcs) { + my $func = $entry->[0]; + my $arg_pos = $entry->[1]; + + my $lc = $stat =~ tr@\n@@; + $lc = $lc + $linenr; + my $stat_real = get_stat_real($linenr, $lc); + + my $skip_args = ""; + if ($arg_pos > 1) { + $arg_pos--; + $skip_args = "(?:\\s*$FuncArg\\s*,\\s*){$arg_pos,$arg_pos}"; + } + my $test = "\\b$func\\s*\\(${skip_args}($FuncArg(?:\\|\\s*$FuncArg)*)\\s*[,\\)]"; + if ($stat =~ /$test/) { + my $val = $1; + $val = $6 if ($skip_args ne ""); + if (!($func =~ /^(?:module_param|proc_create)/ && $val eq "0") && + (($val =~ /^$Int$/ && $val !~ /^$Octal$/) || + ($val =~ /^$Octal$/ && length($val) ne 4))) { + ERROR("NON_OCTAL_PERMISSIONS", + "Use 4 digit octal (0777) not decimal permissions\n" . "$here\n" . $stat_real); + } + if ($val =~ /^$Octal$/ && (oct($val) & 02)) { + ERROR("EXPORTED_WORLD_WRITABLE", + "Exporting writable files is usually an error. Consider more restrictive permissions.\n" . "$here\n" . $stat_real); + } + } + } + } + +# check for uses of S_<PERMS> that could be octal for readability + while ($line =~ m{\b($multi_mode_perms_string_search)\b}g) { + my $oval = $1; + my $octal = perms_to_octal($oval); + if (WARN("SYMBOLIC_PERMS", + "Symbolic permissions '$oval' are not preferred. Consider using octal permissions '$octal'.\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\Q$oval\E/$octal/; + } + } + +# validate content of MODULE_LICENSE against list from include/linux/module.h + if ($line =~ /\bMODULE_LICENSE\s*\(\s*($String)\s*\)/) { + my $extracted_string = get_quoted_string($line, $rawline); + my $valid_licenses = qr{ + GPL| + GPL\ v2| + GPL\ and\ additional\ rights| + Dual\ BSD/GPL| + Dual\ MIT/GPL| + Dual\ MPL/GPL| + Proprietary + }x; + if ($extracted_string !~ /^"(?:$valid_licenses)"$/x) { + WARN("MODULE_LICENSE", + "unknown module license " . $extracted_string . "\n" . $herecurr); + } + if (!$file && $extracted_string eq '"GPL v2"') { + if (WARN("MODULE_LICENSE", + "Prefer \"GPL\" over \"GPL v2\" - see commit bf7fbeeae6db (\"module: Cure the MODULE_LICENSE \"GPL\" vs. \"GPL v2\" bogosity\")\n" . $herecurr) && + $fix) { + $fixed[$fixlinenr] =~ s/\bMODULE_LICENSE\s*\(\s*"GPL v2"\s*\)/MODULE_LICENSE("GPL")/; + } + } + } + +# check for sysctl duplicate constants + if ($line =~ /\.extra[12]\s*=\s*&(zero|one|int_max)\b/) { + WARN("DUPLICATED_SYSCTL_CONST", + "duplicated sysctl range checking value '$1', consider using the shared one in include/linux/sysctl.h\n" . $herecurr); + } + +# Check that *_device_id tables have sentinel entries. + if (defined $stat && $line =~ /struct\s+$dev_id_types\s+\w+\s*\[\s*\]\s*=\s*\{/) { + my $stripped = $stat; + + # Strip diff line prefixes. + $stripped =~ s/(^|\n)./$1/g; + # Line continuations. + $stripped =~ s/\\\n/\n/g; + # Strip whitespace, empty strings, zeroes, and commas. + $stripped =~ s/""//g; + $stripped =~ s/0x0//g; + $stripped =~ s/[\s$;,0]//g; + # Strip field assignments. + $stripped =~ s/\.$Ident=//g; + + if (!(substr($stripped, -4) eq "{}};" || + substr($stripped, -6) eq "{{}}};" || + $stripped =~ /ISAPNP_DEVICE_SINGLE_END}};$/ || + $stripped =~ /ISAPNP_CARD_END}};$/ || + $stripped =~ /NULL};$/ || + $stripped =~ /PCMCIA_DEVICE_NULL};$/)) { + ERROR("MISSING_SENTINEL", "missing sentinel in ID array\n" . "$here\n$stat\n"); + } + } + +# check for uninitialized pointers with __free attribute + while ($line =~ /\*\s*($Ident)\s+__free\s*\(\s*$Ident\s*\)\s*[,;]/g) { + ERROR("UNINITIALIZED_PTR_WITH_FREE", + "pointer '$1' with __free attribute should be initialized\n" . $herecurr); + } + } + + # If we have no input at all, then there is nothing to report on + # so just keep quiet. + if ($#rawlines == -1) { + exit(0); + } + + # In mailback mode only produce a report in the negative, for + # things that appear to be patches. + if ($mailback && ($clean == 1 || !$is_patch)) { + exit(0); + } + + # This is not a patch, and we are in 'no-patch' mode so + # just keep quiet. + if (!$chk_patch && !$is_patch) { + exit(0); + } + + if (!$is_patch && $filename !~ /cover-letter\.patch$/) { + ERROR("NOT_UNIFIED_DIFF", + "Does not appear to be a unified-diff format patch\n"); + } + if ($is_patch && $has_commit_log && $chk_fixes_tag) { + if ($needs_fixes_tag ne "" && !$is_revert && !$fixes_tag) { + WARN("MISSING_FIXES_TAG", + "The commit message has '$needs_fixes_tag', perhaps it also needs a 'Fixes:' tag?\n"); + } + } + if ($is_patch && $has_commit_log && $chk_signoff) { + if ($signoff == 0) { + ERROR("MISSING_SIGN_OFF", + "Missing Signed-off-by: line(s)\n"); + } elsif ($authorsignoff != 1) { + # authorsignoff values: + # 0 -> missing sign off + # 1 -> sign off identical + # 2 -> names and addresses match, comments mismatch + # 3 -> addresses match, names different + # 4 -> names match, addresses different + # 5 -> names match, addresses excluding subaddress details (refer RFC 5233) match + + my $sob_msg = "'From: $author' != 'Signed-off-by: $author_sob'"; + + if ($authorsignoff == 0) { + ERROR("NO_AUTHOR_SIGN_OFF", + "Missing Signed-off-by: line by nominal patch author '$author'\n"); + } elsif ($authorsignoff == 2) { + CHK("FROM_SIGN_OFF_MISMATCH", + "From:/Signed-off-by: email comments mismatch: $sob_msg\n"); + } elsif ($authorsignoff == 3) { + WARN("FROM_SIGN_OFF_MISMATCH", + "From:/Signed-off-by: email name mismatch: $sob_msg\n"); + } elsif ($authorsignoff == 4) { + WARN("FROM_SIGN_OFF_MISMATCH", + "From:/Signed-off-by: email address mismatch: $sob_msg\n"); + } elsif ($authorsignoff == 5) { + WARN("FROM_SIGN_OFF_MISMATCH", + "From:/Signed-off-by: email subaddress mismatch: $sob_msg\n"); + } + } + } + + print report_dump(); + if ($summary && !($clean == 1 && $quiet == 1)) { + print "$filename " if ($summary_file); + print "total: $cnt_error errors, $cnt_warn warnings, " . + (($check)? "$cnt_chk checks, " : "") . + "$cnt_lines lines checked\n"; + } + + if ($quiet == 0) { + # If there were any defects found and not already fixing them + if (!$clean and !$fix) { + print << "EOM" + +NOTE: For some of the reported defects, checkpatch may be able to + mechanically convert to the typical style using --fix or --fix-inplace. +EOM + } + # If there were whitespace errors which cleanpatch can fix + # then suggest that. + if ($rpt_cleaners) { + $rpt_cleaners = 0; + print << "EOM" + +NOTE: Whitespace errors detected. + You may wish to use scripts/cleanpatch or scripts/cleanfile +EOM + } + } + + if ($clean == 0 && $fix && + ("@rawlines" ne "@fixed" || + $#fixed_inserted >= 0 || $#fixed_deleted >= 0)) { + my $newfile = $filename; + $newfile .= ".EXPERIMENTAL-checkpatch-fixes" if (!$fix_inplace); + my $linecount = 0; + my $f; + + @fixed = fix_inserted_deleted_lines(\@fixed, \@fixed_inserted, \@fixed_deleted); + + open($f, '>', $newfile) + or die "$P: Can't open $newfile for write\n"; + foreach my $fixed_line (@fixed) { + $linecount++; + if ($file) { + if ($linecount > 3) { + $fixed_line =~ s/^\+//; + print $f $fixed_line . "\n"; + } + } else { + print $f $fixed_line . "\n"; + } + } + close($f); + + if (!$quiet) { + print << "EOM"; + +Wrote EXPERIMENTAL --fix correction(s) to '$newfile' + +Do _NOT_ trust the results written to this file. +Do _NOT_ submit these changes without inspecting them for correctness. + +This EXPERIMENTAL file is simply a convenience to help rewrite patches. +No warranties, expressed or implied... +EOM + } + } + + if ($quiet == 0) { + print "\n"; + if ($clean == 1) { + print "$vname has no obvious style problems and is ready for submission.\n"; + } else { + print "$vname has style problems, please review.\n"; + } + } + return $clean; +} diff --git a/dev-docs/extend-authors.sh b/dev-docs/extend-authors.sh new file mode 100755 index 0000000..30e7dff --- /dev/null +++ b/dev-docs/extend-authors.sh @@ -0,0 +1,35 @@ +#!/bin/bash + +# Check if a starting tag is provided +if [ $# -eq 0 ]; then + echo "Usage: $0 <starting_tag>" + echo "Example: $0 fuse-3.16.2" + exit 1 +fi + +START_TAG=$1 + +# Extract email addresses from git log +git_emails=$(git log ${START_TAG}..HEAD --format='<%aE>' | sort -u | sed 's/^<//;s/>$//') + +# Extract email addresses from AUTHORS file +authors_emails=$(grep -oP '(?<=<)[^>]+' AUTHORS | sort -u) + +# Find new email addresses (in git_emails but not in authors_emails) +# -1 suppresses lines unique to AUTHORS, -3 suppresses lines common to both +# Result: only lines unique to git_emails (i.e., new authors) +new_emails=$(comm -1 -3 <(echo "$authors_emails") <(echo "$git_emails")) + +# If there are new email addresses, add corresponding authors to the AUTHORS file +if [ -n "$new_emails" ]; then + echo -e "\nNew authors to be added:" + echo -e "\n# New authors since ${START_TAG}" >> AUTHORS + for email in $new_emails; do + author=$(git log -1 --format='%aN <%aE>' --author="$email") + echo "$author" + echo "$author" >> AUTHORS + done + echo "AUTHORS file has been updated." +else + echo "No new authors found since ${START_TAG}." +fi diff --git a/dev-docs/release-process.md b/dev-docs/release-process.md new file mode 100644 index 0000000..d96c873 --- /dev/null +++ b/dev-docs/release-process.md @@ -0,0 +1,61 @@ +Release Process +=============== + +* `set TAG fuse-A.B.C` +* Update version in + * `ChangeLog.rst` + * `meson.build` + * `include/fuse_common.h` (`#define FUSE_{MINOR/MAJOR}_VERSION`) +* When creating new minor release: + * Create signing key for the next release: `P=fuse-<A.B+1> signify-openbsd -G -n -p signify/$P.pub -s + signify/$P.sec; git add signify/$P.pub` + * Expire old release signing keys (keep one around just in case) +* To update authors run : dev-docs/extend-authors.sh +* `git commit --all -m "Released $TAG"` +* `git tag $TAG` +* Build tarball, `./make_release_tarball.sh` +* Test build: + * `cd fuse-x.y.z` + * `md build && (cd build && meson .. && ninja)` + * `sudo sudo chown root:root build/util/fusermount3` + * `sudo chmod 4755 build/util/fusermount3` + * `(cd build; ../test/run-tests.py --build-dir .)` +* Upload API docs: + * `rm -r ../libfuse.github.io/doxygen && cp -a doc/html ../libfuse.github.io/doxygen` + * `git -C ../libfuse.github.io add doxygen/` + * `git -C ../libfuse.github.io commit --all -m "Re-generated doxygen documentation"` + * `git -C ../libfuse.github.io push` +* `git checkout master && git push && git push --tags` +* Create release on Github +* Write announcement to fuse-devel + + +Announcement email template + +``` +To: fuse-devel@lists.linux.dev +Subject: [ANNOUNCE] libfuse XXXX has been released + +Dear all, + +I am pleased to announce the release of libfuse XXX. + +The source code is available for download at https://github.com/libfuse/libfuse/releases. + +Please report any issues on this mailing list or the GitHub issue +tracker at https://github.com/libfuse/libfuse/issues. + +From ChangeLog.rst: + +[INSERT NEW ENTRIES] + +The following people have contributed code to this release: + +[INSERT CONTRIBUTORS] + +(a full list of credits containing all known contributors is included in +the `AUTHORS` file). + +Best, +-Nikolaus +``` diff --git a/doc/Doxyfile b/doc/Doxyfile new file mode 100644 index 0000000..c403ea2 --- /dev/null +++ b/doc/Doxyfile @@ -0,0 +1,2695 @@ +# Doxyfile 1.9.6 + +# This file describes the settings to be used by the documentation system +# doxygen (www.doxygen.org) for a project. +# +# All text after a double hash (##) is considered a comment and is placed in +# front of the TAG it is preceding. +# +# All text after a single hash (#) is considered a comment and will be ignored. +# The format is: +# TAG = value [value, ...] +# For lists, items can also be appended using: +# TAG += value [value, ...] +# Values that contain spaces should be placed between quotes (\" \"). +# +# Note: +# +# Use doxygen to compare the used configuration file with the template +# configuration file: +# doxygen -x [configFile] +# Use doxygen to compare the used configuration file with the template +# configuration file without replacing the environment variables or CMake type +# replacement variables: +# doxygen -x_noenv [configFile] + +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- + +# This tag specifies the encoding used for all characters in the configuration +# file that follow. The default is UTF-8 which is also the encoding used for all +# text before the first occurrence of this tag. Doxygen uses libiconv (or the +# iconv built into libc) for the transcoding. See +# https://www.gnu.org/software/libiconv/ for the list of possible encodings. +# The default value is: UTF-8. + +DOXYFILE_ENCODING = UTF-8 + +# The PROJECT_NAME tag is a single word (or a sequence of words surrounded by +# double-quotes, unless you are using Doxywizard) that should identify the +# project for which the documentation is generated. This name is used in the +# title of most generated pages and in a few other places. +# The default value is: My Project. + +PROJECT_NAME = libfuse + +# The PROJECT_NUMBER tag can be used to enter a project or revision number. This +# could be handy for archiving the generated documentation or if some version +# control system is used. + +PROJECT_NUMBER = + +# Using the PROJECT_BRIEF tag one can provide an optional one line description +# for a project that appears at the top of each page and should give viewer a +# quick idea about the purpose of the project. Keep the description short. + +PROJECT_BRIEF = + +# With the PROJECT_LOGO tag one can specify a logo or an icon that is included +# in the documentation. The maximum height of the logo should not exceed 55 +# pixels and the maximum width should not exceed 200 pixels. Doxygen will copy +# the logo to the output directory. + +PROJECT_LOGO = + +# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path +# into which the generated documentation will be written. If a relative path is +# entered, it will be relative to the location where doxygen was started. If +# left blank the current directory will be used. + +OUTPUT_DIRECTORY = doc + +# If the CREATE_SUBDIRS tag is set to YES then doxygen will create up to 4096 +# sub-directories (in 2 levels) under the output directory of each output format +# and will distribute the generated files over these directories. Enabling this +# option can be useful when feeding doxygen a huge amount of source files, where +# putting all generated files in the same directory would otherwise causes +# performance problems for the file system. Adapt CREATE_SUBDIRS_LEVEL to +# control the number of sub-directories. +# The default value is: NO. + +CREATE_SUBDIRS = NO + +# Controls the number of sub-directories that will be created when +# CREATE_SUBDIRS tag is set to YES. Level 0 represents 16 directories, and every +# level increment doubles the number of directories, resulting in 4096 +# directories at level 8 which is the default and also the maximum value. The +# sub-directories are organized in 2 levels, the first level always has a fixed +# number of 16 directories. +# Minimum value: 0, maximum value: 8, default value: 8. +# This tag requires that the tag CREATE_SUBDIRS is set to YES. + +CREATE_SUBDIRS_LEVEL = 8 + +# If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII +# characters to appear in the names of generated files. If set to NO, non-ASCII +# characters will be escaped, for example _xE3_x81_x84 will be used for Unicode +# U+3044. +# The default value is: NO. + +ALLOW_UNICODE_NAMES = NO + +# The OUTPUT_LANGUAGE tag is used to specify the language in which all +# documentation generated by doxygen is written. Doxygen will use this +# information to generate all constant output in the proper language. +# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Bulgarian, +# Catalan, Chinese, Chinese-Traditional, Croatian, Czech, Danish, Dutch, English +# (United States), Esperanto, Farsi (Persian), Finnish, French, German, Greek, +# Hindi, Hungarian, Indonesian, Italian, Japanese, Japanese-en (Japanese with +# English messages), Korean, Korean-en (Korean with English messages), Latvian, +# Lithuanian, Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, +# Romanian, Russian, Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, +# Swedish, Turkish, Ukrainian and Vietnamese. +# The default value is: English. + +OUTPUT_LANGUAGE = English + +# If the BRIEF_MEMBER_DESC tag is set to YES, doxygen will include brief member +# descriptions after the members that are listed in the file and class +# documentation (similar to Javadoc). Set to NO to disable this. +# The default value is: YES. + +BRIEF_MEMBER_DESC = YES + +# If the REPEAT_BRIEF tag is set to YES, doxygen will prepend the brief +# description of a member or function before the detailed description +# +# Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the +# brief descriptions will be completely suppressed. +# The default value is: YES. + +REPEAT_BRIEF = YES + +# This tag implements a quasi-intelligent brief description abbreviator that is +# used to form the text in various listings. Each string in this list, if found +# as the leading text of the brief description, will be stripped from the text +# and the result, after processing the whole list, is used as the annotated +# text. Otherwise, the brief description is used as-is. If left blank, the +# following values are used ($name is automatically replaced with the name of +# the entity):The $name class, The $name widget, The $name file, is, provides, +# specifies, contains, represents, a, an and the. + +ABBREVIATE_BRIEF = "The $name class" \ + "The $name widget" \ + "The $name file" \ + is \ + provides \ + specifies \ + contains \ + represents \ + a \ + an \ + the + +# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then +# doxygen will generate a detailed section even if there is only a brief +# description. +# The default value is: NO. + +ALWAYS_DETAILED_SEC = NO + +# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all +# inherited members of a class in the documentation of that class as if those +# members were ordinary class members. Constructors, destructors and assignment +# operators of the base classes will not be shown. +# The default value is: NO. + +INLINE_INHERITED_MEMB = NO + +# If the FULL_PATH_NAMES tag is set to YES, doxygen will prepend the full path +# before files name in the file list and in the header files. If set to NO the +# shortest path that makes the file name unique will be used +# The default value is: YES. + +FULL_PATH_NAMES = YES + +# The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path. +# Stripping is only done if one of the specified strings matches the left-hand +# part of the path. The tag can be used to show relative paths in the file list. +# If left blank the directory from which doxygen is run is used as the path to +# strip. +# +# Note that you can specify absolute paths here, but also relative paths, which +# will be relative from the directory where doxygen is started. +# This tag requires that the tag FULL_PATH_NAMES is set to YES. + +STRIP_FROM_PATH = + +# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the +# path mentioned in the documentation of a class, which tells the reader which +# header file to include in order to use a class. If left blank only the name of +# the header file containing the class definition is used. Otherwise one should +# specify the list of include paths that are normally passed to the compiler +# using the -I flag. + +STRIP_FROM_INC_PATH = + +# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but +# less readable) file names. This can be useful is your file systems doesn't +# support long names like on DOS, Mac, or CD-ROM. +# The default value is: NO. + +SHORT_NAMES = NO + +# If the JAVADOC_AUTOBRIEF tag is set to YES then doxygen will interpret the +# first line (until the first dot) of a Javadoc-style comment as the brief +# description. If set to NO, the Javadoc-style will behave just like regular Qt- +# style comments (thus requiring an explicit @brief command for a brief +# description.) +# The default value is: NO. + +JAVADOC_AUTOBRIEF = NO + +# If the JAVADOC_BANNER tag is set to YES then doxygen will interpret a line +# such as +# /*************** +# as being the beginning of a Javadoc-style comment "banner". If set to NO, the +# Javadoc-style will behave just like regular comments and it will not be +# interpreted by doxygen. +# The default value is: NO. + +JAVADOC_BANNER = NO + +# If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first +# line (until the first dot) of a Qt-style comment as the brief description. If +# set to NO, the Qt-style will behave just like regular Qt-style comments (thus +# requiring an explicit \brief command for a brief description.) +# The default value is: NO. + +QT_AUTOBRIEF = NO + +# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a +# multi-line C++ special comment block (i.e. a block of //! or /// comments) as +# a brief description. This used to be the default behavior. The new default is +# to treat a multi-line C++ comment block as a detailed description. Set this +# tag to YES if you prefer the old behavior instead. +# +# Note that setting this tag to YES also means that rational rose comments are +# not recognized any more. +# The default value is: NO. + +MULTILINE_CPP_IS_BRIEF = NO + +# By default Python docstrings are displayed as preformatted text and doxygen's +# special commands cannot be used. By setting PYTHON_DOCSTRING to NO the +# doxygen's special commands can be used and the contents of the docstring +# documentation blocks is shown as doxygen documentation. +# The default value is: YES. + +PYTHON_DOCSTRING = YES + +# If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the +# documentation from any documented member that it re-implements. +# The default value is: YES. + +INHERIT_DOCS = YES + +# If the SEPARATE_MEMBER_PAGES tag is set to YES then doxygen will produce a new +# page for each member. If set to NO, the documentation of a member will be part +# of the file/class/namespace that contains it. +# The default value is: NO. + +SEPARATE_MEMBER_PAGES = NO + +# The TAB_SIZE tag can be used to set the number of spaces in a tab. Doxygen +# uses this value to replace tabs by spaces in code fragments. +# Minimum value: 1, maximum value: 16, default value: 4. + +TAB_SIZE = 8 + +# This tag can be used to specify a number of aliases that act as commands in +# the documentation. An alias has the form: +# name=value +# For example adding +# "sideeffect=@par Side Effects:^^" +# will allow you to put the command \sideeffect (or @sideeffect) in the +# documentation, which will result in a user-defined paragraph with heading +# "Side Effects:". Note that you cannot put \n's in the value part of an alias +# to insert newlines (in the resulting output). You can put ^^ in the value part +# of an alias to insert a newline as if a physical newline was in the original +# file. When you need a literal { or } or , in the value part of an alias you +# have to escape them by means of a backslash (\), this can lead to conflicts +# with the commands \{ and \} for these it is advised to use the version @{ and +# @} or use a double escape (\\{ and \\}) + +ALIASES = + +# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources +# only. Doxygen will then generate output that is more tailored for C. For +# instance, some of the names that are used will be different. The list of all +# members will be omitted, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_FOR_C = YES + +# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or +# Python sources only. Doxygen will then generate output that is more tailored +# for that language. For instance, namespaces will be presented as packages, +# qualified scopes will look different, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_JAVA = NO + +# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran +# sources. Doxygen will then generate output that is tailored for Fortran. +# The default value is: NO. + +OPTIMIZE_FOR_FORTRAN = NO + +# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL +# sources. Doxygen will then generate output that is tailored for VHDL. +# The default value is: NO. + +OPTIMIZE_OUTPUT_VHDL = NO + +# Set the OPTIMIZE_OUTPUT_SLICE tag to YES if your project consists of Slice +# sources only. Doxygen will then generate output that is more tailored for that +# language. For instance, namespaces will be presented as modules, types will be +# separated into more groups, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_SLICE = NO + +# Doxygen selects the parser to use depending on the extension of the files it +# parses. With this tag you can assign which parser to use for a given +# extension. Doxygen has a built-in mapping, but you can override or extend it +# using this tag. The format is ext=language, where ext is a file extension, and +# language is one of the parsers supported by doxygen: IDL, Java, JavaScript, +# Csharp (C#), C, C++, Lex, D, PHP, md (Markdown), Objective-C, Python, Slice, +# VHDL, Fortran (fixed format Fortran: FortranFixed, free formatted Fortran: +# FortranFree, unknown formatted Fortran: Fortran. In the later case the parser +# tries to guess whether the code is fixed or free formatted code, this is the +# default for Fortran type files). For instance to make doxygen treat .inc files +# as Fortran files (default is PHP), and .f files as C (default is Fortran), +# use: inc=Fortran f=C. +# +# Note: For files without extension you can use no_extension as a placeholder. +# +# Note that for custom extensions you also need to set FILE_PATTERNS otherwise +# the files are not read by doxygen. When specifying no_extension you should add +# * to the FILE_PATTERNS. +# +# Note see also the list of default file extension mappings. + +EXTENSION_MAPPING = + +# If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments +# according to the Markdown format, which allows for more readable +# documentation. See https://daringfireball.net/projects/markdown/ for details. +# The output of markdown processing is further processed by doxygen, so you can +# mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in +# case of backward compatibilities issues. +# The default value is: YES. + +MARKDOWN_SUPPORT = YES + +# When the TOC_INCLUDE_HEADINGS tag is set to a non-zero value, all headings up +# to that level are automatically included in the table of contents, even if +# they do not have an id attribute. +# Note: This feature currently applies only to Markdown headings. +# Minimum value: 0, maximum value: 99, default value: 5. +# This tag requires that the tag MARKDOWN_SUPPORT is set to YES. + +TOC_INCLUDE_HEADINGS = 5 + +# When enabled doxygen tries to link words that correspond to documented +# classes, or namespaces to their corresponding documentation. Such a link can +# be prevented in individual cases by putting a % sign in front of the word or +# globally by setting AUTOLINK_SUPPORT to NO. +# The default value is: YES. + +AUTOLINK_SUPPORT = YES + +# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want +# to include (a tag file for) the STL sources as input, then you should set this +# tag to YES in order to let doxygen match functions declarations and +# definitions whose arguments contain STL classes (e.g. func(std::string); +# versus func(std::string) {}). This also make the inheritance and collaboration +# diagrams that involve STL classes more complete and accurate. +# The default value is: NO. + +BUILTIN_STL_SUPPORT = NO + +# If you use Microsoft's C++/CLI language, you should set this option to YES to +# enable parsing support. +# The default value is: NO. + +CPP_CLI_SUPPORT = NO + +# Set the SIP_SUPPORT tag to YES if your project consists of sip (see: +# https://www.riverbankcomputing.com/software/sip/intro) sources only. Doxygen +# will parse them like normal C++ but will assume all classes use public instead +# of private inheritance when no explicit protection keyword is present. +# The default value is: NO. + +SIP_SUPPORT = NO + +# For Microsoft's IDL there are propget and propput attributes to indicate +# getter and setter methods for a property. Setting this option to YES will make +# doxygen to replace the get and set methods by a property in the documentation. +# This will only work if the methods are indeed getting or setting a simple +# type. If this is not the case, or you want to show the methods anyway, you +# should set this option to NO. +# The default value is: YES. + +IDL_PROPERTY_SUPPORT = YES + +# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC +# tag is set to YES then doxygen will reuse the documentation of the first +# member in the group (if any) for the other members of the group. By default +# all members of a group must be documented explicitly. +# The default value is: NO. + +DISTRIBUTE_GROUP_DOC = NO + +# If one adds a struct or class to a group and this option is enabled, then also +# any nested class or struct is added to the same group. By default this option +# is disabled and one has to add nested compounds explicitly via \ingroup. +# The default value is: NO. + +GROUP_NESTED_COMPOUNDS = NO + +# Set the SUBGROUPING tag to YES to allow class member groups of the same type +# (for instance a group of public functions) to be put as a subgroup of that +# type (e.g. under the Public Functions section). Set it to NO to prevent +# subgrouping. Alternatively, this can be done per class using the +# \nosubgrouping command. +# The default value is: YES. + +SUBGROUPING = YES + +# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions +# are shown inside the group in which they are included (e.g. using \ingroup) +# instead of on a separate page (for HTML and Man pages) or section (for LaTeX +# and RTF). +# +# Note that this feature does not work in combination with +# SEPARATE_MEMBER_PAGES. +# The default value is: NO. + +INLINE_GROUPED_CLASSES = NO + +# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions +# with only public data fields or simple typedef fields will be shown inline in +# the documentation of the scope in which they are defined (i.e. file, +# namespace, or group documentation), provided this scope is documented. If set +# to NO, structs, classes, and unions are shown on a separate page (for HTML and +# Man pages) or section (for LaTeX and RTF). +# The default value is: NO. + +INLINE_SIMPLE_STRUCTS = NO + +# When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or +# enum is documented as struct, union, or enum with the name of the typedef. So +# typedef struct TypeS {} TypeT, will appear in the documentation as a struct +# with name TypeT. When disabled the typedef will appear as a member of a file, +# namespace, or class. And the struct will be named TypeS. This can typically be +# useful for C code in case the coding convention dictates that all compound +# types are typedef'ed and only the typedef is referenced, never the tag name. +# The default value is: NO. + +TYPEDEF_HIDES_STRUCT = NO + +# The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This +# cache is used to resolve symbols given their name and scope. Since this can be +# an expensive process and often the same symbol appears multiple times in the +# code, doxygen keeps a cache of pre-resolved symbols. If the cache is too small +# doxygen will become slower. If the cache is too large, memory is wasted. The +# cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range +# is 0..9, the default is 0, corresponding to a cache size of 2^16=65536 +# symbols. At the end of a run doxygen will report the cache usage and suggest +# the optimal cache size from a speed point of view. +# Minimum value: 0, maximum value: 9, default value: 0. + +LOOKUP_CACHE_SIZE = 0 + +# The NUM_PROC_THREADS specifies the number of threads doxygen is allowed to use +# during processing. When set to 0 doxygen will based this on the number of +# cores available in the system. You can set it explicitly to a value larger +# than 0 to get more control over the balance between CPU load and processing +# speed. At this moment only the input processing can be done using multiple +# threads. Since this is still an experimental feature the default is set to 1, +# which effectively disables parallel processing. Please report any issues you +# encounter. Generating dot graphs in parallel is controlled by the +# DOT_NUM_THREADS setting. +# Minimum value: 0, maximum value: 32, default value: 1. + +NUM_PROC_THREADS = 1 + +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- + +# If the EXTRACT_ALL tag is set to YES, doxygen will assume all entities in +# documentation are documented, even if no documentation was available. Private +# class members and static file members will be hidden unless the +# EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES. +# Note: This will also disable the warnings about undocumented members that are +# normally produced when WARNINGS is set to YES. +# The default value is: NO. + +EXTRACT_ALL = NO + +# If the EXTRACT_PRIVATE tag is set to YES, all private members of a class will +# be included in the documentation. +# The default value is: NO. + +EXTRACT_PRIVATE = NO + +# If the EXTRACT_PRIV_VIRTUAL tag is set to YES, documented private virtual +# methods of a class will be included in the documentation. +# The default value is: NO. + +EXTRACT_PRIV_VIRTUAL = NO + +# If the EXTRACT_PACKAGE tag is set to YES, all members with package or internal +# scope will be included in the documentation. +# The default value is: NO. + +EXTRACT_PACKAGE = NO + +# If the EXTRACT_STATIC tag is set to YES, all static members of a file will be +# included in the documentation. +# The default value is: NO. + +EXTRACT_STATIC = NO + +# If the EXTRACT_LOCAL_CLASSES tag is set to YES, classes (and structs) defined +# locally in source files will be included in the documentation. If set to NO, +# only classes defined in header files are included. Does not have any effect +# for Java sources. +# The default value is: YES. + +EXTRACT_LOCAL_CLASSES = YES + +# This flag is only useful for Objective-C code. If set to YES, local methods, +# which are defined in the implementation section but not in the interface are +# included in the documentation. If set to NO, only methods in the interface are +# included. +# The default value is: NO. + +EXTRACT_LOCAL_METHODS = NO + +# If this flag is set to YES, the members of anonymous namespaces will be +# extracted and appear in the documentation as a namespace called +# 'anonymous_namespace{file}', where file will be replaced with the base name of +# the file that contains the anonymous namespace. By default anonymous namespace +# are hidden. +# The default value is: NO. + +EXTRACT_ANON_NSPACES = NO + +# If this flag is set to YES, the name of an unnamed parameter in a declaration +# will be determined by the corresponding definition. By default unnamed +# parameters remain unnamed in the output. +# The default value is: YES. + +RESOLVE_UNNAMED_PARAMS = YES + +# If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all +# undocumented members inside documented classes or files. If set to NO these +# members will be included in the various overviews, but no documentation +# section is generated. This option has no effect if EXTRACT_ALL is enabled. +# The default value is: NO. + +HIDE_UNDOC_MEMBERS = YES + +# If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all +# undocumented classes that are normally visible in the class hierarchy. If set +# to NO, these classes will be included in the various overviews. This option +# will also hide undocumented C++ concepts if enabled. This option has no effect +# if EXTRACT_ALL is enabled. +# The default value is: NO. + +HIDE_UNDOC_CLASSES = YES + +# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend +# declarations. If set to NO, these declarations will be included in the +# documentation. +# The default value is: NO. + +HIDE_FRIEND_COMPOUNDS = NO + +# If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any +# documentation blocks found inside the body of a function. If set to NO, these +# blocks will be appended to the function's detailed documentation block. +# The default value is: NO. + +HIDE_IN_BODY_DOCS = NO + +# The INTERNAL_DOCS tag determines if documentation that is typed after a +# \internal command is included. If the tag is set to NO then the documentation +# will be excluded. Set it to YES to include the internal documentation. +# The default value is: NO. + +INTERNAL_DOCS = NO + +# With the correct setting of option CASE_SENSE_NAMES doxygen will better be +# able to match the capabilities of the underlying filesystem. In case the +# filesystem is case sensitive (i.e. it supports files in the same directory +# whose names only differ in casing), the option must be set to YES to properly +# deal with such files in case they appear in the input. For filesystems that +# are not case sensitive the option should be set to NO to properly deal with +# output files written for symbols that only differ in casing, such as for two +# classes, one named CLASS and the other named Class, and to also support +# references to files without having to specify the exact matching casing. On +# Windows (including Cygwin) and MacOS, users should typically set this option +# to NO, whereas on Linux or other Unix flavors it should typically be set to +# YES. +# Possible values are: SYSTEM, NO and YES. +# The default value is: SYSTEM. + +CASE_SENSE_NAMES = YES + +# If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with +# their full class and namespace scopes in the documentation. If set to YES, the +# scope will be hidden. +# The default value is: NO. + +HIDE_SCOPE_NAMES = NO + +# If the HIDE_COMPOUND_REFERENCE tag is set to NO (default) then doxygen will +# append additional text to a page's title, such as Class Reference. If set to +# YES the compound reference will be hidden. +# The default value is: NO. + +HIDE_COMPOUND_REFERENCE= NO + +# If the SHOW_HEADERFILE tag is set to YES then the documentation for a class +# will show which file needs to be included to use the class. +# The default value is: YES. + +SHOW_HEADERFILE = YES + +# If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of +# the files that are included by a file in the documentation of that file. +# The default value is: YES. + +SHOW_INCLUDE_FILES = YES + +# If the SHOW_GROUPED_MEMB_INC tag is set to YES then Doxygen will add for each +# grouped member an include statement to the documentation, telling the reader +# which file to include in order to use the member. +# The default value is: NO. + +SHOW_GROUPED_MEMB_INC = NO + +# If the FORCE_LOCAL_INCLUDES tag is set to YES then doxygen will list include +# files with double quotes in the documentation rather than with sharp brackets. +# The default value is: NO. + +FORCE_LOCAL_INCLUDES = NO + +# If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the +# documentation for inline members. +# The default value is: YES. + +INLINE_INFO = YES + +# If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the +# (detailed) documentation of file and class members alphabetically by member +# name. If set to NO, the members will appear in declaration order. +# The default value is: YES. + +SORT_MEMBER_DOCS = YES + +# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief +# descriptions of file, namespace and class members alphabetically by member +# name. If set to NO, the members will appear in declaration order. Note that +# this will also influence the order of the classes in the class list. +# The default value is: NO. + +SORT_BRIEF_DOCS = NO + +# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the +# (brief and detailed) documentation of class members so that constructors and +# destructors are listed first. If set to NO the constructors will appear in the +# respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS. +# Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief +# member documentation. +# Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting +# detailed member documentation. +# The default value is: NO. + +SORT_MEMBERS_CTORS_1ST = NO + +# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the hierarchy +# of group names into alphabetical order. If set to NO the group names will +# appear in their defined order. +# The default value is: NO. + +SORT_GROUP_NAMES = NO + +# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by +# fully-qualified names, including namespaces. If set to NO, the class list will +# be sorted only by class name, not including the namespace part. +# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. +# Note: This option applies only to the class list, not to the alphabetical +# list. +# The default value is: NO. + +SORT_BY_SCOPE_NAME = NO + +# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper +# type resolution of all parameters of a function it will reject a match between +# the prototype and the implementation of a member function even if there is +# only one candidate or it is obvious which candidate to choose by doing a +# simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still +# accept a match between prototype and implementation in such cases. +# The default value is: NO. + +STRICT_PROTO_MATCHING = NO + +# The GENERATE_TODOLIST tag can be used to enable (YES) or disable (NO) the todo +# list. This list is created by putting \todo commands in the documentation. +# The default value is: YES. + +GENERATE_TODOLIST = YES + +# The GENERATE_TESTLIST tag can be used to enable (YES) or disable (NO) the test +# list. This list is created by putting \test commands in the documentation. +# The default value is: YES. + +GENERATE_TESTLIST = YES + +# The GENERATE_BUGLIST tag can be used to enable (YES) or disable (NO) the bug +# list. This list is created by putting \bug commands in the documentation. +# The default value is: YES. + +GENERATE_BUGLIST = YES + +# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or disable (NO) +# the deprecated list. This list is created by putting \deprecated commands in +# the documentation. +# The default value is: YES. + +GENERATE_DEPRECATEDLIST= YES + +# The ENABLED_SECTIONS tag can be used to enable conditional documentation +# sections, marked by \if <section_label> ... \endif and \cond <section_label> +# ... \endcond blocks. + +ENABLED_SECTIONS = + +# The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the +# initial value of a variable or macro / define can have for it to appear in the +# documentation. If the initializer consists of more lines than specified here +# it will be hidden. Use a value of 0 to hide initializers completely. The +# appearance of the value of individual variables and macros / defines can be +# controlled using \showinitializer or \hideinitializer command in the +# documentation regardless of this setting. +# Minimum value: 0, maximum value: 10000, default value: 30. + +MAX_INITIALIZER_LINES = 30 + +# Set the SHOW_USED_FILES tag to NO to disable the list of files generated at +# the bottom of the documentation of classes and structs. If set to YES, the +# list will mention the files that were used to generate the documentation. +# The default value is: YES. + +SHOW_USED_FILES = YES + +# Set the SHOW_FILES tag to NO to disable the generation of the Files page. This +# will remove the Files entry from the Quick Index and from the Folder Tree View +# (if specified). +# The default value is: YES. + +SHOW_FILES = YES + +# Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces +# page. This will remove the Namespaces entry from the Quick Index and from the +# Folder Tree View (if specified). +# The default value is: YES. + +SHOW_NAMESPACES = YES + +# The FILE_VERSION_FILTER tag can be used to specify a program or script that +# doxygen should invoke to get the current version for each file (typically from +# the version control system). Doxygen will invoke the program by executing (via +# popen()) the command command input-file, where command is the value of the +# FILE_VERSION_FILTER tag, and input-file is the name of an input file provided +# by doxygen. Whatever the program writes to standard output is used as the file +# version. For an example see the documentation. + +FILE_VERSION_FILTER = + +# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed +# by doxygen. The layout file controls the global structure of the generated +# output files in an output format independent way. To create the layout file +# that represents doxygen's defaults, run doxygen with the -l option. You can +# optionally specify a file name after the option, if omitted DoxygenLayout.xml +# will be used as the name of the layout file. See also section "Changing the +# layout of pages" for information. +# +# Note that if you run doxygen from a directory containing a file called +# DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE +# tag is left empty. + +LAYOUT_FILE = + +# The CITE_BIB_FILES tag can be used to specify one or more bib files containing +# the reference definitions. This must be a list of .bib files. The .bib +# extension is automatically appended if omitted. This requires the bibtex tool +# to be installed. See also https://en.wikipedia.org/wiki/BibTeX for more info. +# For LaTeX the style of the bibliography can be controlled using +# LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the +# search path. See also \cite for info how to create references. + +CITE_BIB_FILES = + +#--------------------------------------------------------------------------- +# Configuration options related to warning and progress messages +#--------------------------------------------------------------------------- + +# The QUIET tag can be used to turn on/off the messages that are generated to +# standard output by doxygen. If QUIET is set to YES this implies that the +# messages are off. +# The default value is: NO. + +QUIET = YES + +# The WARNINGS tag can be used to turn on/off the warning messages that are +# generated to standard error (stderr) by doxygen. If WARNINGS is set to YES +# this implies that the warnings are on. +# +# Tip: Turn warnings on while writing the documentation. +# The default value is: YES. + +WARNINGS = YES + +# If the WARN_IF_UNDOCUMENTED tag is set to YES then doxygen will generate +# warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag +# will automatically be disabled. +# The default value is: YES. + +WARN_IF_UNDOCUMENTED = YES + +# If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for +# potential errors in the documentation, such as documenting some parameters in +# a documented function twice, or documenting parameters that don't exist or +# using markup commands wrongly. +# The default value is: YES. + +WARN_IF_DOC_ERROR = YES + +# If WARN_IF_INCOMPLETE_DOC is set to YES, doxygen will warn about incomplete +# function parameter documentation. If set to NO, doxygen will accept that some +# parameters have no documentation without warning. +# The default value is: YES. + +WARN_IF_INCOMPLETE_DOC = YES + +# This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that +# are documented, but have no documentation for their parameters or return +# value. If set to NO, doxygen will only warn about wrong parameter +# documentation, but not about the absence of documentation. If EXTRACT_ALL is +# set to YES then this flag will automatically be disabled. See also +# WARN_IF_INCOMPLETE_DOC +# The default value is: NO. + +WARN_NO_PARAMDOC = NO + +# If WARN_IF_UNDOC_ENUM_VAL option is set to YES, doxygen will warn about +# undocumented enumeration values. If set to NO, doxygen will accept +# undocumented enumeration values. If EXTRACT_ALL is set to YES then this flag +# will automatically be disabled. +# The default value is: NO. + +WARN_IF_UNDOC_ENUM_VAL = NO + +# If the WARN_AS_ERROR tag is set to YES then doxygen will immediately stop when +# a warning is encountered. If the WARN_AS_ERROR tag is set to FAIL_ON_WARNINGS +# then doxygen will continue running as if WARN_AS_ERROR tag is set to NO, but +# at the end of the doxygen process doxygen will return with a non-zero status. +# Possible values are: NO, YES and FAIL_ON_WARNINGS. +# The default value is: NO. + +WARN_AS_ERROR = NO + +# The WARN_FORMAT tag determines the format of the warning messages that doxygen +# can produce. The string should contain the $file, $line, and $text tags, which +# will be replaced by the file and line number from which the warning originated +# and the warning text. Optionally the format may contain $version, which will +# be replaced by the version of the file (if it could be obtained via +# FILE_VERSION_FILTER) +# See also: WARN_LINE_FORMAT +# The default value is: $file:$line: $text. + +WARN_FORMAT = "$file:$line: $text" + +# In the $text part of the WARN_FORMAT command it is possible that a reference +# to a more specific place is given. To make it easier to jump to this place +# (outside of doxygen) the user can define a custom "cut" / "paste" string. +# Example: +# WARN_LINE_FORMAT = "'vi $file +$line'" +# See also: WARN_FORMAT +# The default value is: at line $line of file $file. + +WARN_LINE_FORMAT = "at line $line of file $file" + +# The WARN_LOGFILE tag can be used to specify a file to which warning and error +# messages should be written. If left blank the output is written to standard +# error (stderr). In case the file specified cannot be opened for writing the +# warning and error messages are written to standard error. When as file - is +# specified the warning and error messages are written to standard output +# (stdout). + +WARN_LOGFILE = + +#--------------------------------------------------------------------------- +# Configuration options related to the input files +#--------------------------------------------------------------------------- + +# The INPUT tag is used to specify the files and/or directories that contain +# documented source files. You may enter file names like myfile.cpp or +# directories like /usr/src/myproject. Separate the files or directories with +# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING +# Note: If this tag is empty the current directory is searched. + +INPUT = . + +# This tag can be used to specify the character encoding of the source files +# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses +# libiconv (or the iconv built into libc) for the transcoding. See the libiconv +# documentation (see: +# https://www.gnu.org/software/libiconv/) for the list of possible encodings. +# See also: INPUT_FILE_ENCODING +# The default value is: UTF-8. + +INPUT_ENCODING = UTF-8 + +# This tag can be used to specify the character encoding of the source files +# that doxygen parses The INPUT_FILE_ENCODING tag can be used to specify +# character encoding on a per file pattern basis. Doxygen will compare the file +# name with each pattern and apply the encoding instead of the default +# INPUT_ENCODING) if there is a match. The character encodings are a list of the +# form: pattern=encoding (like *.php=ISO-8859-1). See cfg_input_encoding +# "INPUT_ENCODING" for further information on supported encodings. + +INPUT_FILE_ENCODING = + +# If the value of the INPUT tag contains directories, you can use the +# FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and +# *.h) to filter out the source-files in the directories. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# read by doxygen. +# +# Note the list of default checked file patterns might differ from the list of +# default file extension mappings. +# +# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cpp, +# *.c++, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h, +# *.hh, *.hxx, *.hpp, *.h++, *.l, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, +# *.inc, *.m, *.markdown, *.md, *.mm, *.dox (to be provided as doxygen C +# comment), *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, *.f18, *.f, *.for, *.vhd, +# *.vhdl, *.ucf, *.qsf and *.ice. + +FILE_PATTERNS = *.h \ + *.c \ + *.h \ + *.dox + +# The RECURSIVE tag can be used to specify whether or not subdirectories should +# be searched for input files as well. +# The default value is: NO. + +RECURSIVE = YES + +# The EXCLUDE tag can be used to specify files and/or directories that should be +# excluded from the INPUT source files. This way you can easily exclude a +# subdirectory from a directory tree whose root is specified with the INPUT tag. +# +# Note that relative paths are relative to the directory from which doxygen is +# run. + +EXCLUDE = + +# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or +# directories that are symbolic links (a Unix file system feature) are excluded +# from the input. +# The default value is: NO. + +EXCLUDE_SYMLINKS = NO + +# If the value of the INPUT tag contains directories, you can use the +# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude +# certain files from those directories. +# +# Note that the wildcards are matched against the file with absolute path, so to +# exclude all test directories for example use the pattern */test/* + +EXCLUDE_PATTERNS = + +# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names +# (namespaces, classes, functions, etc.) that should be excluded from the +# output. The symbol name can be a fully qualified name, a word, or if the +# wildcard * is used, a substring. Examples: ANamespace, AClass, +# ANamespace::AClass, ANamespace::*Test +# +# Note that the wildcards are matched against the file with absolute path, so to +# exclude all test directories use the pattern */test/* + +EXCLUDE_SYMBOLS = + +# The EXAMPLE_PATH tag can be used to specify one or more files or directories +# that contain example code fragments that are included (see the \include +# command). + +EXAMPLE_PATH = example + +# If the value of the EXAMPLE_PATH tag contains directories, you can use the +# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and +# *.h) to filter out the source-files in the directories. If left blank all +# files are included. + +EXAMPLE_PATTERNS = *.c \ + *.h + +# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be +# searched for input files to be used with the \include or \dontinclude commands +# irrespective of the value of the RECURSIVE tag. +# The default value is: NO. + +EXAMPLE_RECURSIVE = NO + +# The IMAGE_PATH tag can be used to specify one or more files or directories +# that contain images that are to be included in the documentation (see the +# \image command). + +IMAGE_PATH = + +# The INPUT_FILTER tag can be used to specify a program that doxygen should +# invoke to filter for each input file. Doxygen will invoke the filter program +# by executing (via popen()) the command: +# +# <filter> <input-file> +# +# where <filter> is the value of the INPUT_FILTER tag, and <input-file> is the +# name of an input file. Doxygen will then use the output that the filter +# program writes to standard output. If FILTER_PATTERNS is specified, this tag +# will be ignored. +# +# Note that the filter must not add or remove lines; it is applied before the +# code is scanned, but not when the output code is generated. If lines are added +# or removed, the anchors will not be placed correctly. +# +# Note that doxygen will use the data processed and written to standard output +# for further processing, therefore nothing else, like debug statements or used +# commands (so in case of a Windows batch file always use @echo OFF), should be +# written to standard output. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# properly processed by doxygen. + +INPUT_FILTER = + +# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern +# basis. Doxygen will compare the file name with each pattern and apply the +# filter if there is a match. The filters are a list of the form: pattern=filter +# (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how +# filters are used. If the FILTER_PATTERNS tag is empty or if none of the +# patterns match the file name, INPUT_FILTER is applied. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# properly processed by doxygen. + +FILTER_PATTERNS = + +# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using +# INPUT_FILTER) will also be used to filter the input files that are used for +# producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES). +# The default value is: NO. + +FILTER_SOURCE_FILES = NO + +# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file +# pattern. A pattern will override the setting for FILTER_PATTERN (if any) and +# it is also possible to disable source filtering for a specific pattern using +# *.ext= (so without naming a filter). +# This tag requires that the tag FILTER_SOURCE_FILES is set to YES. + +FILTER_SOURCE_PATTERNS = + +# If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that +# is part of the input, its contents will be placed on the main page +# (index.html). This can be useful if you have a project on for instance GitHub +# and want to reuse the introduction page also for the doxygen output. + +USE_MDFILE_AS_MAINPAGE = + +# The Fortran standard specifies that for fixed formatted Fortran code all +# characters from position 72 are to be considered as comment. A common +# extension is to allow longer lines before the automatic comment starts. The +# setting FORTRAN_COMMENT_AFTER will also make it possible that longer lines can +# be processed before the automatic comment starts. +# Minimum value: 7, maximum value: 10000, default value: 72. + +FORTRAN_COMMENT_AFTER = 72 + +#--------------------------------------------------------------------------- +# Configuration options related to source browsing +#--------------------------------------------------------------------------- + +# If the SOURCE_BROWSER tag is set to YES then a list of source files will be +# generated. Documented entities will be cross-referenced with these sources. +# +# Note: To get rid of all source code in the generated output, make sure that +# also VERBATIM_HEADERS is set to NO. +# The default value is: NO. + +SOURCE_BROWSER = YES + +# Setting the INLINE_SOURCES tag to YES will include the body of functions, +# classes and enums directly into the documentation. +# The default value is: NO. + +INLINE_SOURCES = NO + +# Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any +# special comment blocks from generated source code fragments. Normal C, C++ and +# Fortran comments will always remain visible. +# The default value is: YES. + +STRIP_CODE_COMMENTS = YES + +# If the REFERENCED_BY_RELATION tag is set to YES then for each documented +# entity all documented functions referencing it will be listed. +# The default value is: NO. + +REFERENCED_BY_RELATION = NO + +# If the REFERENCES_RELATION tag is set to YES then for each documented function +# all documented entities called/used by that function will be listed. +# The default value is: NO. + +REFERENCES_RELATION = NO + +# If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set +# to YES then the hyperlinks from functions in REFERENCES_RELATION and +# REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will +# link to the documentation. +# The default value is: YES. + +REFERENCES_LINK_SOURCE = YES + +# If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the +# source code will show a tooltip with additional information such as prototype, +# brief description and links to the definition and documentation. Since this +# will make the HTML file larger and loading of large files a bit slower, you +# can opt to disable this feature. +# The default value is: YES. +# This tag requires that the tag SOURCE_BROWSER is set to YES. + +SOURCE_TOOLTIPS = YES + +# If the USE_HTAGS tag is set to YES then the references to source code will +# point to the HTML generated by the htags(1) tool instead of doxygen built-in +# source browser. The htags tool is part of GNU's global source tagging system +# (see https://www.gnu.org/software/global/global.html). You will need version +# 4.8.6 or higher. +# +# To use it do the following: +# - Install the latest version of global +# - Enable SOURCE_BROWSER and USE_HTAGS in the configuration file +# - Make sure the INPUT points to the root of the source tree +# - Run doxygen as normal +# +# Doxygen will invoke htags (and that will in turn invoke gtags), so these +# tools must be available from the command line (i.e. in the search path). +# +# The result: instead of the source browser generated by doxygen, the links to +# source code will now point to the output of htags. +# The default value is: NO. +# This tag requires that the tag SOURCE_BROWSER is set to YES. + +USE_HTAGS = NO + +# If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a +# verbatim copy of the header file for each class for which an include is +# specified. Set to NO to disable this. +# See also: Section \class. +# The default value is: YES. + +VERBATIM_HEADERS = NO + +#--------------------------------------------------------------------------- +# Configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- + +# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all +# compounds will be generated. Enable this if the project contains a lot of +# classes, structs, unions or interfaces. +# The default value is: YES. + +ALPHABETICAL_INDEX = NO + +# The IGNORE_PREFIX tag can be used to specify a prefix (or a list of prefixes) +# that should be ignored while generating the index headers. The IGNORE_PREFIX +# tag works for classes, function and member names. The entity will be placed in +# the alphabetical list under the first letter of the entity name that remains +# after removing the prefix. +# This tag requires that the tag ALPHABETICAL_INDEX is set to YES. + +IGNORE_PREFIX = + +#--------------------------------------------------------------------------- +# Configuration options related to the HTML output +#--------------------------------------------------------------------------- + +# If the GENERATE_HTML tag is set to YES, doxygen will generate HTML output +# The default value is: YES. + +GENERATE_HTML = YES + +# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a +# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of +# it. +# The default directory is: html. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_OUTPUT = html + +# The HTML_FILE_EXTENSION tag can be used to specify the file extension for each +# generated HTML page (for example: .htm, .php, .asp). +# The default value is: .html. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FILE_EXTENSION = .html + +# The HTML_HEADER tag can be used to specify a user-defined HTML header file for +# each generated HTML page. If the tag is left blank doxygen will generate a +# standard header. +# +# To get valid HTML the header file that includes any scripts and style sheets +# that doxygen needs, which is dependent on the configuration options used (e.g. +# the setting GENERATE_TREEVIEW). It is highly recommended to start with a +# default header using +# doxygen -w html new_header.html new_footer.html new_stylesheet.css +# YourConfigFile +# and then modify the file new_header.html. See also section "Doxygen usage" +# for information on how to generate the default header that doxygen normally +# uses. +# Note: The header is subject to change so you typically have to regenerate the +# default header when upgrading to a newer version of doxygen. For a description +# of the possible markers and block names see the documentation. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_HEADER = + +# The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each +# generated HTML page. If the tag is left blank doxygen will generate a standard +# footer. See HTML_HEADER for more information on how to generate a default +# footer and what special commands can be used inside the footer. See also +# section "Doxygen usage" for information on how to generate the default footer +# that doxygen normally uses. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FOOTER = + +# The HTML_STYLESHEET tag can be used to specify a user-defined cascading style +# sheet that is used by each HTML page. It can be used to fine-tune the look of +# the HTML output. If left blank doxygen will generate a default style sheet. +# See also section "Doxygen usage" for information on how to generate the style +# sheet that doxygen normally uses. +# Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as +# it is more robust and this tag (HTML_STYLESHEET) will in the future become +# obsolete. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_STYLESHEET = + +# The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined +# cascading style sheets that are included after the standard style sheets +# created by doxygen. Using this option one can overrule certain style aspects. +# This is preferred over using HTML_STYLESHEET since it does not replace the +# standard style sheet and is therefore more robust against future updates. +# Doxygen will copy the style sheet files to the output directory. +# Note: The order of the extra style sheet files is of importance (e.g. the last +# style sheet in the list overrules the setting of the previous ones in the +# list). +# Note: Since the styling of scrollbars can currently not be overruled in +# Webkit/Chromium, the styling will be left out of the default doxygen.css if +# one or more extra stylesheets have been specified. So if scrollbar +# customization is desired it has to be added explicitly. For an example see the +# documentation. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_EXTRA_STYLESHEET = + +# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or +# other source files which should be copied to the HTML output directory. Note +# that these files will be copied to the base HTML output directory. Use the +# $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these +# files. In the HTML_STYLESHEET file, use the file name only. Also note that the +# files will be copied as-is; there are no commands or markers available. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_EXTRA_FILES = doc/fast17-vangoor.pdf + +# The HTML_COLORSTYLE tag can be used to specify if the generated HTML output +# should be rendered with a dark or light theme. +# Possible values are: LIGHT always generate light mode output, DARK always +# generate dark mode output, AUTO_LIGHT automatically set the mode according to +# the user preference, use light mode if no preference is set (the default), +# AUTO_DARK automatically set the mode according to the user preference, use +# dark mode if no preference is set and TOGGLE allow to user to switch between +# light and dark mode via a button. +# The default value is: AUTO_LIGHT. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE = AUTO_LIGHT + +# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen +# will adjust the colors in the style sheet and background images according to +# this color. Hue is specified as an angle on a color-wheel, see +# https://en.wikipedia.org/wiki/Hue for more information. For instance the value +# 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300 +# purple, and 360 is red again. +# Minimum value: 0, maximum value: 359, default value: 220. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_HUE = 220 + +# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors +# in the HTML output. For a value of 0 the output will use gray-scales only. A +# value of 255 will produce the most vivid colors. +# Minimum value: 0, maximum value: 255, default value: 100. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_SAT = 100 + +# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the +# luminance component of the colors in the HTML output. Values below 100 +# gradually make the output lighter, whereas values above 100 make the output +# darker. The value divided by 100 is the actual gamma applied, so 80 represents +# a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not +# change the gamma. +# Minimum value: 40, maximum value: 240, default value: 80. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_GAMMA = 80 + +# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML +# page will contain the date and time when the page was generated. Setting this +# to YES can help to show when doxygen was last run and thus if the +# documentation is up to date. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_TIMESTAMP = NO + +# If the HTML_DYNAMIC_MENUS tag is set to YES then the generated HTML +# documentation will contain a main index with vertical navigation menus that +# are dynamically created via JavaScript. If disabled, the navigation index will +# consists of multiple levels of tabs that are statically embedded in every HTML +# page. Disable this option to support browsers that do not have JavaScript, +# like the Qt help browser. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_DYNAMIC_MENUS = YES + +# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML +# documentation will contain sections that can be hidden and shown after the +# page has loaded. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_DYNAMIC_SECTIONS = YES + +# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries +# shown in the various tree structured indices initially; the user can expand +# and collapse entries dynamically later on. Doxygen will expand the tree to +# such a level that at most the specified number of entries are visible (unless +# a fully collapsed tree already exceeds this amount). So setting the number of +# entries 1 will produce a full collapsed tree by default. 0 is a special value +# representing an infinite number of entries and will result in a full expanded +# tree by default. +# Minimum value: 0, maximum value: 9999, default value: 100. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_INDEX_NUM_ENTRIES = 100 + +# If the GENERATE_DOCSET tag is set to YES, additional index files will be +# generated that can be used as input for Apple's Xcode 3 integrated development +# environment (see: +# https://developer.apple.com/xcode/), introduced with OSX 10.5 (Leopard). To +# create a documentation set, doxygen will generate a Makefile in the HTML +# output directory. Running make will produce the docset in that directory and +# running make install will install the docset in +# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at +# startup. See https://developer.apple.com/library/archive/featuredarticles/Doxy +# genXcode/_index.html for more information. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_DOCSET = NO + +# This tag determines the name of the docset feed. A documentation feed provides +# an umbrella under which multiple documentation sets from a single provider +# (such as a company or product suite) can be grouped. +# The default value is: Doxygen generated docs. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_FEEDNAME = "Doxygen generated docs" + +# This tag determines the URL of the docset feed. A documentation feed provides +# an umbrella under which multiple documentation sets from a single provider +# (such as a company or product suite) can be grouped. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_FEEDURL = + +# This tag specifies a string that should uniquely identify the documentation +# set bundle. This should be a reverse domain-name style string, e.g. +# com.mycompany.MyDocSet. Doxygen will append .docset to the name. +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_BUNDLE_ID = org.doxygen.Project + +# The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify +# the documentation publisher. This should be a reverse domain-name style +# string, e.g. com.mycompany.MyDocSet.documentation. +# The default value is: org.doxygen.Publisher. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_PUBLISHER_ID = org.doxygen.Publisher + +# The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher. +# The default value is: Publisher. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_PUBLISHER_NAME = Publisher + +# If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three +# additional HTML index files: index.hhp, index.hhc, and index.hhk. The +# index.hhp is a project file that can be read by Microsoft's HTML Help Workshop +# on Windows. In the beginning of 2021 Microsoft took the original page, with +# a.o. the download links, offline the HTML help workshop was already many years +# in maintenance mode). You can download the HTML help workshop from the web +# archives at Installation executable (see: +# http://web.archive.org/web/20160201063255/http://download.microsoft.com/downlo +# ad/0/A/9/0A939EF6-E31C-430F-A3DF-DFAE7960D564/htmlhelp.exe). +# +# The HTML Help Workshop contains a compiler that can convert all HTML output +# generated by doxygen into a single compiled HTML file (.chm). Compiled HTML +# files are now used as the Windows 98 help format, and will replace the old +# Windows help format (.hlp) on all Windows platforms in the future. Compressed +# HTML files also contain an index, a table of contents, and you can search for +# words in the documentation. The HTML workshop also contains a viewer for +# compressed HTML files. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_HTMLHELP = NO + +# The CHM_FILE tag can be used to specify the file name of the resulting .chm +# file. You can add a path in front of the file if the result should not be +# written to the html output directory. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +CHM_FILE = + +# The HHC_LOCATION tag can be used to specify the location (absolute path +# including file name) of the HTML help compiler (hhc.exe). If non-empty, +# doxygen will try to run the HTML help compiler on the generated index.hhp. +# The file has to be specified with full path. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +HHC_LOCATION = + +# The GENERATE_CHI flag controls if a separate .chi index file is generated +# (YES) or that it should be included in the main .chm file (NO). +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +GENERATE_CHI = NO + +# The CHM_INDEX_ENCODING is used to encode HtmlHelp index (hhk), content (hhc) +# and project file content. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +CHM_INDEX_ENCODING = + +# The BINARY_TOC flag controls whether a binary table of contents is generated +# (YES) or a normal table of contents (NO) in the .chm file. Furthermore it +# enables the Previous and Next buttons. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +BINARY_TOC = NO + +# The TOC_EXPAND flag can be set to YES to add extra items for group members to +# the table of contents of the HTML help documentation and to the tree view. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +TOC_EXPAND = NO + +# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and +# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that +# can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help +# (.qch) of the generated HTML documentation. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_QHP = NO + +# If the QHG_LOCATION tag is specified, the QCH_FILE tag can be used to specify +# the file name of the resulting .qch file. The path specified is relative to +# the HTML output folder. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QCH_FILE = + +# The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help +# Project output. For more information please see Qt Help Project / Namespace +# (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#namespace). +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_NAMESPACE = org.doxygen.Project + +# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt +# Help Project output. For more information please see Qt Help Project / Virtual +# Folders (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#virtual-folders). +# The default value is: doc. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_VIRTUAL_FOLDER = doc + +# If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom +# filter to add. For more information please see Qt Help Project / Custom +# Filters (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-filters). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_CUST_FILTER_NAME = + +# The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the +# custom filter to add. For more information please see Qt Help Project / Custom +# Filters (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-filters). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_CUST_FILTER_ATTRS = + +# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this +# project's filter section matches. Qt Help Project / Filter Attributes (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#filter-attributes). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_SECT_FILTER_ATTRS = + +# The QHG_LOCATION tag can be used to specify the location (absolute path +# including file name) of Qt's qhelpgenerator. If non-empty doxygen will try to +# run qhelpgenerator on the generated .qhp file. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHG_LOCATION = + +# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be +# generated, together with the HTML files, they form an Eclipse help plugin. To +# install this plugin and make it available under the help contents menu in +# Eclipse, the contents of the directory containing the HTML and XML files needs +# to be copied into the plugins directory of eclipse. The name of the directory +# within the plugins directory should be the same as the ECLIPSE_DOC_ID value. +# After copying Eclipse needs to be restarted before the help appears. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_ECLIPSEHELP = NO + +# A unique identifier for the Eclipse help plugin. When installing the plugin +# the directory name containing the HTML and XML files should also have this +# name. Each documentation set should have its own identifier. +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES. + +ECLIPSE_DOC_ID = org.doxygen.Project + +# If you want full control over the layout of the generated HTML pages it might +# be necessary to disable the index and replace it with your own. The +# DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top +# of each HTML page. A value of NO enables the index and the value YES disables +# it. Since the tabs in the index contain the same information as the navigation +# tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +DISABLE_INDEX = NO + +# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index +# structure should be generated to display hierarchical information. If the tag +# value is set to YES, a side panel will be generated containing a tree-like +# index structure (just like the one that is generated for HTML Help). For this +# to work a browser that supports JavaScript, DHTML, CSS and frames is required +# (i.e. any modern browser). Windows users are probably better off using the +# HTML help feature. Via custom style sheets (see HTML_EXTRA_STYLESHEET) one can +# further fine tune the look of the index (see "Fine-tuning the output"). As an +# example, the default style sheet generated by doxygen has an example that +# shows how to put an image at the root of the tree instead of the PROJECT_NAME. +# Since the tree basically has the same information as the tab index, you could +# consider setting DISABLE_INDEX to YES when enabling this option. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_TREEVIEW = NO + +# When both GENERATE_TREEVIEW and DISABLE_INDEX are set to YES, then the +# FULL_SIDEBAR option determines if the side bar is limited to only the treeview +# area (value NO) or if it should extend to the full height of the window (value +# YES). Setting this to YES gives a layout similar to +# https://docs.readthedocs.io with more room for contents, but less room for the +# project logo, title, and description. If either GENERATE_TREEVIEW or +# DISABLE_INDEX is set to NO, this option has no effect. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +FULL_SIDEBAR = NO + +# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that +# doxygen will group on one line in the generated HTML documentation. +# +# Note that a value of 0 will completely suppress the enum values from appearing +# in the overview section. +# Minimum value: 0, maximum value: 20, default value: 4. +# This tag requires that the tag GENERATE_HTML is set to YES. + +ENUM_VALUES_PER_LINE = 4 + +# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used +# to set the initial width (in pixels) of the frame in which the tree is shown. +# Minimum value: 0, maximum value: 1500, default value: 250. +# This tag requires that the tag GENERATE_HTML is set to YES. + +TREEVIEW_WIDTH = 250 + +# If the EXT_LINKS_IN_WINDOW option is set to YES, doxygen will open links to +# external symbols imported via tag files in a separate window. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +EXT_LINKS_IN_WINDOW = NO + +# If the OBFUSCATE_EMAILS tag is set to YES, doxygen will obfuscate email +# addresses. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +OBFUSCATE_EMAILS = YES + +# If the HTML_FORMULA_FORMAT option is set to svg, doxygen will use the pdf2svg +# tool (see https://github.com/dawbarton/pdf2svg) or inkscape (see +# https://inkscape.org) to generate formulas as SVG images instead of PNGs for +# the HTML output. These images will generally look nicer at scaled resolutions. +# Possible values are: png (the default) and svg (looks nicer but requires the +# pdf2svg or inkscape tool). +# The default value is: png. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FORMULA_FORMAT = png + +# Use this tag to change the font size of LaTeX formulas included as images in +# the HTML documentation. When you change the font size after a successful +# doxygen run you need to manually remove any form_*.png images from the HTML +# output directory to force them to be regenerated. +# Minimum value: 8, maximum value: 50, default value: 10. +# This tag requires that the tag GENERATE_HTML is set to YES. + +FORMULA_FONTSIZE = 10 + +# The FORMULA_MACROFILE can contain LaTeX \newcommand and \renewcommand commands +# to create new LaTeX commands to be used in formulas as building blocks. See +# the section "Including formulas" for details. + +FORMULA_MACROFILE = + +# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see +# https://www.mathjax.org) which uses client side JavaScript for the rendering +# instead of using pre-rendered bitmaps. Use this if you do not have LaTeX +# installed or if you want to formulas look prettier in the HTML output. When +# enabled you may also need to install MathJax separately and configure the path +# to it using the MATHJAX_RELPATH option. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +USE_MATHJAX = NO + +# With MATHJAX_VERSION it is possible to specify the MathJax version to be used. +# Note that the different versions of MathJax have different requirements with +# regards to the different settings, so it is possible that also other MathJax +# settings have to be changed when switching between the different MathJax +# versions. +# Possible values are: MathJax_2 and MathJax_3. +# The default value is: MathJax_2. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_VERSION = MathJax_2 + +# When MathJax is enabled you can set the default output format to be used for +# the MathJax output. For more details about the output format see MathJax +# version 2 (see: +# http://docs.mathjax.org/en/v2.7-latest/output.html) and MathJax version 3 +# (see: +# http://docs.mathjax.org/en/latest/web/components/output.html). +# Possible values are: HTML-CSS (which is slower, but has the best +# compatibility. This is the name for Mathjax version 2, for MathJax version 3 +# this will be translated into chtml), NativeMML (i.e. MathML. Only supported +# for NathJax 2. For MathJax version 3 chtml will be used instead.), chtml (This +# is the name for Mathjax version 3, for MathJax version 2 this will be +# translated into HTML-CSS) and SVG. +# The default value is: HTML-CSS. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_FORMAT = HTML-CSS + +# When MathJax is enabled you need to specify the location relative to the HTML +# output directory using the MATHJAX_RELPATH option. The destination directory +# should contain the MathJax.js script. For instance, if the mathjax directory +# is located at the same level as the HTML output directory, then +# MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax +# Content Delivery Network so you can quickly see the result without installing +# MathJax. However, it is strongly recommended to install a local copy of +# MathJax from https://www.mathjax.org before deployment. The default value is: +# - in case of MathJax version 2: https://cdn.jsdelivr.net/npm/mathjax@2 +# - in case of MathJax version 3: https://cdn.jsdelivr.net/npm/mathjax@3 +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_RELPATH = + +# The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax +# extension names that should be enabled during MathJax rendering. For example +# for MathJax version 2 (see +# https://docs.mathjax.org/en/v2.7-latest/tex.html#tex-and-latex-extensions): +# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols +# For example for MathJax version 3 (see +# http://docs.mathjax.org/en/latest/input/tex/extensions/index.html): +# MATHJAX_EXTENSIONS = ams +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_EXTENSIONS = + +# The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces +# of code that will be used on startup of the MathJax code. See the MathJax site +# (see: +# http://docs.mathjax.org/en/v2.7-latest/output.html) for more details. For an +# example see the documentation. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_CODEFILE = + +# When the SEARCHENGINE tag is enabled doxygen will generate a search box for +# the HTML output. The underlying search engine uses javascript and DHTML and +# should work on any modern browser. Note that when using HTML help +# (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET) +# there is already a search function so this one should typically be disabled. +# For large projects the javascript based search engine can be slow, then +# enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to +# search using the keyboard; to jump to the search box use <access key> + S +# (what the <access key> is depends on the OS and browser, but it is typically +# <CTRL>, <ALT>/<option>, or both). Inside the search box use the <cursor down +# key> to jump into the search results window, the results can be navigated +# using the <cursor keys>. Press <Enter> to select an item or <escape> to cancel +# the search. The filter options can be selected when the cursor is inside the +# search box by pressing <Shift>+<cursor down>. Also here use the <cursor keys> +# to select a filter and <Enter> or <escape> to activate or cancel the filter +# option. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +SEARCHENGINE = NO + +# When the SERVER_BASED_SEARCH tag is enabled the search engine will be +# implemented using a web server instead of a web client using JavaScript. There +# are two flavors of web server based searching depending on the EXTERNAL_SEARCH +# setting. When disabled, doxygen will generate a PHP script for searching and +# an index file used by the script. When EXTERNAL_SEARCH is enabled the indexing +# and searching needs to be provided by external tools. See the section +# "External Indexing and Searching" for details. +# The default value is: NO. +# This tag requires that the tag SEARCHENGINE is set to YES. + +SERVER_BASED_SEARCH = NO + +# When EXTERNAL_SEARCH tag is enabled doxygen will no longer generate the PHP +# script for searching. Instead the search results are written to an XML file +# which needs to be processed by an external indexer. Doxygen will invoke an +# external search engine pointed to by the SEARCHENGINE_URL option to obtain the +# search results. +# +# Doxygen ships with an example indexer (doxyindexer) and search engine +# (doxysearch.cgi) which are based on the open source search engine library +# Xapian (see: +# https://xapian.org/). +# +# See the section "External Indexing and Searching" for details. +# The default value is: NO. +# This tag requires that the tag SEARCHENGINE is set to YES. + +EXTERNAL_SEARCH = NO + +# The SEARCHENGINE_URL should point to a search engine hosted by a web server +# which will return the search results when EXTERNAL_SEARCH is enabled. +# +# Doxygen ships with an example indexer (doxyindexer) and search engine +# (doxysearch.cgi) which are based on the open source search engine library +# Xapian (see: +# https://xapian.org/). See the section "External Indexing and Searching" for +# details. +# This tag requires that the tag SEARCHENGINE is set to YES. + +SEARCHENGINE_URL = + +# When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the unindexed +# search data is written to a file for indexing by an external tool. With the +# SEARCHDATA_FILE tag the name of this file can be specified. +# The default file is: searchdata.xml. +# This tag requires that the tag SEARCHENGINE is set to YES. + +SEARCHDATA_FILE = searchdata.xml + +# When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the +# EXTERNAL_SEARCH_ID tag can be used as an identifier for the project. This is +# useful in combination with EXTRA_SEARCH_MAPPINGS to search through multiple +# projects and redirect the results back to the right project. +# This tag requires that the tag SEARCHENGINE is set to YES. + +EXTERNAL_SEARCH_ID = + +# The EXTRA_SEARCH_MAPPINGS tag can be used to enable searching through doxygen +# projects other than the one defined by this configuration file, but that are +# all added to the same external search index. Each project needs to have a +# unique id set via EXTERNAL_SEARCH_ID. The search mapping then maps the id of +# to a relative location where the documentation can be found. The format is: +# EXTRA_SEARCH_MAPPINGS = tagname1=loc1 tagname2=loc2 ... +# This tag requires that the tag SEARCHENGINE is set to YES. + +EXTRA_SEARCH_MAPPINGS = + +#--------------------------------------------------------------------------- +# Configuration options related to the LaTeX output +#--------------------------------------------------------------------------- + +# If the GENERATE_LATEX tag is set to YES, doxygen will generate LaTeX output. +# The default value is: YES. + +GENERATE_LATEX = NO + +# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. If a +# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of +# it. +# The default directory is: latex. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_OUTPUT = latex + +# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be +# invoked. +# +# Note that when not enabling USE_PDFLATEX the default is latex when enabling +# USE_PDFLATEX the default is pdflatex and when in the later case latex is +# chosen this is overwritten by pdflatex. For specific output languages the +# default can have been set differently, this depends on the implementation of +# the output language. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_CMD_NAME = + +# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to generate +# index for LaTeX. +# Note: This tag is used in the Makefile / make.bat. +# See also: LATEX_MAKEINDEX_CMD for the part in the generated output file +# (.tex). +# The default file is: makeindex. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +MAKEINDEX_CMD_NAME = makeindex + +# The LATEX_MAKEINDEX_CMD tag can be used to specify the command name to +# generate index for LaTeX. In case there is no backslash (\) as first character +# it will be automatically added in the LaTeX code. +# Note: This tag is used in the generated output file (.tex). +# See also: MAKEINDEX_CMD_NAME for the part in the Makefile / make.bat. +# The default value is: makeindex. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_MAKEINDEX_CMD = makeindex + +# If the COMPACT_LATEX tag is set to YES, doxygen generates more compact LaTeX +# documents. This may be useful for small projects and may help to save some +# trees in general. +# The default value is: NO. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +COMPACT_LATEX = NO + +# The PAPER_TYPE tag can be used to set the paper type that is used by the +# printer. +# Possible values are: a4 (210 x 297 mm), letter (8.5 x 11 inches), legal (8.5 x +# 14 inches) and executive (7.25 x 10.5 inches). +# The default value is: a4. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +PAPER_TYPE = a4 + +# The EXTRA_PACKAGES tag can be used to specify one or more LaTeX package names +# that should be included in the LaTeX output. The package can be specified just +# by its name or with the correct syntax as to be used with the LaTeX +# \usepackage command. To get the times font for instance you can specify : +# EXTRA_PACKAGES=times or EXTRA_PACKAGES={times} +# To use the option intlimits with the amsmath package you can specify: +# EXTRA_PACKAGES=[intlimits]{amsmath} +# If left blank no extra packages will be included. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +EXTRA_PACKAGES = + +# The LATEX_HEADER tag can be used to specify a user-defined LaTeX header for +# the generated LaTeX document. The header should contain everything until the +# first chapter. If it is left blank doxygen will generate a standard header. It +# is highly recommended to start with a default header using +# doxygen -w latex new_header.tex new_footer.tex new_stylesheet.sty +# and then modify the file new_header.tex. See also section "Doxygen usage" for +# information on how to generate the default header that doxygen normally uses. +# +# Note: Only use a user-defined header if you know what you are doing! +# Note: The header is subject to change so you typically have to regenerate the +# default header when upgrading to a newer version of doxygen. The following +# commands have a special meaning inside the header (and footer): For a +# description of the possible markers and block names see the documentation. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_HEADER = + +# The LATEX_FOOTER tag can be used to specify a user-defined LaTeX footer for +# the generated LaTeX document. The footer should contain everything after the +# last chapter. If it is left blank doxygen will generate a standard footer. See +# LATEX_HEADER for more information on how to generate a default footer and what +# special commands can be used inside the footer. See also section "Doxygen +# usage" for information on how to generate the default footer that doxygen +# normally uses. Note: Only use a user-defined footer if you know what you are +# doing! +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_FOOTER = + +# The LATEX_EXTRA_STYLESHEET tag can be used to specify additional user-defined +# LaTeX style sheets that are included after the standard style sheets created +# by doxygen. Using this option one can overrule certain style aspects. Doxygen +# will copy the style sheet files to the output directory. +# Note: The order of the extra style sheet files is of importance (e.g. the last +# style sheet in the list overrules the setting of the previous ones in the +# list). +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_EXTRA_STYLESHEET = + +# The LATEX_EXTRA_FILES tag can be used to specify one or more extra images or +# other source files which should be copied to the LATEX_OUTPUT output +# directory. Note that the files will be copied as-is; there are no commands or +# markers available. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_EXTRA_FILES = + +# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated is +# prepared for conversion to PDF (using ps2pdf or pdflatex). The PDF file will +# contain links (just like the HTML output) instead of page references. This +# makes the output suitable for online browsing using a PDF viewer. +# The default value is: YES. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +PDF_HYPERLINKS = YES + +# If the USE_PDFLATEX tag is set to YES, doxygen will use the engine as +# specified with LATEX_CMD_NAME to generate the PDF file directly from the LaTeX +# files. Set this option to YES, to get a higher quality PDF documentation. +# +# See also section LATEX_CMD_NAME for selecting the engine. +# The default value is: YES. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +USE_PDFLATEX = YES + +# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \batchmode +# command to the generated LaTeX files. This will instruct LaTeX to keep running +# if errors occur, instead of asking the user for help. +# The default value is: NO. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_BATCHMODE = NO + +# If the LATEX_HIDE_INDICES tag is set to YES then doxygen will not include the +# index chapters (such as File Index, Compound Index, etc.) in the output. +# The default value is: NO. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_HIDE_INDICES = NO + +# The LATEX_BIB_STYLE tag can be used to specify the style to use for the +# bibliography, e.g. plainnat, or ieeetr. See +# https://en.wikipedia.org/wiki/BibTeX and \cite for more info. +# The default value is: plain. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_BIB_STYLE = plain + +# If the LATEX_TIMESTAMP tag is set to YES then the footer of each generated +# page will contain the date and time when the page was generated. Setting this +# to NO can help when comparing the output of multiple runs. +# The default value is: NO. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_TIMESTAMP = NO + +# The LATEX_EMOJI_DIRECTORY tag is used to specify the (relative or absolute) +# path from which the emoji images will be read. If a relative path is entered, +# it will be relative to the LATEX_OUTPUT directory. If left blank the +# LATEX_OUTPUT directory will be used. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_EMOJI_DIRECTORY = + +#--------------------------------------------------------------------------- +# Configuration options related to the RTF output +#--------------------------------------------------------------------------- + +# If the GENERATE_RTF tag is set to YES, doxygen will generate RTF output. The +# RTF output is optimized for Word 97 and may not look too pretty with other RTF +# readers/editors. +# The default value is: NO. + +GENERATE_RTF = NO + +# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. If a +# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of +# it. +# The default directory is: rtf. +# This tag requires that the tag GENERATE_RTF is set to YES. + +RTF_OUTPUT = rtf + +# If the COMPACT_RTF tag is set to YES, doxygen generates more compact RTF +# documents. This may be useful for small projects and may help to save some +# trees in general. +# The default value is: NO. +# This tag requires that the tag GENERATE_RTF is set to YES. + +COMPACT_RTF = NO + +# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated will +# contain hyperlink fields. The RTF file will contain links (just like the HTML +# output) instead of page references. This makes the output suitable for online +# browsing using Word or some other Word compatible readers that support those +# fields. +# +# Note: WordPad (write) and others do not support links. +# The default value is: NO. +# This tag requires that the tag GENERATE_RTF is set to YES. + +RTF_HYPERLINKS = NO + +# Load stylesheet definitions from file. Syntax is similar to doxygen's +# configuration file, i.e. a series of assignments. You only have to provide +# replacements, missing definitions are set to their default value. +# +# See also section "Doxygen usage" for information on how to generate the +# default style sheet that doxygen normally uses. +# This tag requires that the tag GENERATE_RTF is set to YES. + +RTF_STYLESHEET_FILE = + +# Set optional variables used in the generation of an RTF document. Syntax is +# similar to doxygen's configuration file. A template extensions file can be +# generated using doxygen -e rtf extensionFile. +# This tag requires that the tag GENERATE_RTF is set to YES. + +RTF_EXTENSIONS_FILE = + +#--------------------------------------------------------------------------- +# Configuration options related to the man page output +#--------------------------------------------------------------------------- + +# If the GENERATE_MAN tag is set to YES, doxygen will generate man pages for +# classes and files. +# The default value is: NO. + +GENERATE_MAN = NO + +# The MAN_OUTPUT tag is used to specify where the man pages will be put. If a +# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of +# it. A directory man3 will be created inside the directory specified by +# MAN_OUTPUT. +# The default directory is: man. +# This tag requires that the tag GENERATE_MAN is set to YES. + +MAN_OUTPUT = man + +# The MAN_EXTENSION tag determines the extension that is added to the generated +# man pages. In case the manual section does not start with a number, the number +# 3 is prepended. The dot (.) at the beginning of the MAN_EXTENSION tag is +# optional. +# The default value is: .3. +# This tag requires that the tag GENERATE_MAN is set to YES. + +MAN_EXTENSION = .3 + +# The MAN_SUBDIR tag determines the name of the directory created within +# MAN_OUTPUT in which the man pages are placed. If defaults to man followed by +# MAN_EXTENSION with the initial . removed. +# This tag requires that the tag GENERATE_MAN is set to YES. + +MAN_SUBDIR = + +# If the MAN_LINKS tag is set to YES and doxygen generates man output, then it +# will generate one additional man file for each entity documented in the real +# man page(s). These additional files only source the real man page, but without +# them the man command would be unable to find the correct page. +# The default value is: NO. +# This tag requires that the tag GENERATE_MAN is set to YES. + +MAN_LINKS = NO + +#--------------------------------------------------------------------------- +# Configuration options related to the XML output +#--------------------------------------------------------------------------- + +# If the GENERATE_XML tag is set to YES, doxygen will generate an XML file that +# captures the structure of the code including all documentation. +# The default value is: NO. + +GENERATE_XML = NO + +# The XML_OUTPUT tag is used to specify where the XML pages will be put. If a +# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of +# it. +# The default directory is: xml. +# This tag requires that the tag GENERATE_XML is set to YES. + +XML_OUTPUT = xml + +# If the XML_PROGRAMLISTING tag is set to YES, doxygen will dump the program +# listings (including syntax highlighting and cross-referencing information) to +# the XML output. Note that enabling this will significantly increase the size +# of the XML output. +# The default value is: YES. +# This tag requires that the tag GENERATE_XML is set to YES. + +XML_PROGRAMLISTING = YES + +# If the XML_NS_MEMB_FILE_SCOPE tag is set to YES, doxygen will include +# namespace members in file scope as well, matching the HTML output. +# The default value is: NO. +# This tag requires that the tag GENERATE_XML is set to YES. + +XML_NS_MEMB_FILE_SCOPE = NO + +#--------------------------------------------------------------------------- +# Configuration options related to the DOCBOOK output +#--------------------------------------------------------------------------- + +# If the GENERATE_DOCBOOK tag is set to YES, doxygen will generate Docbook files +# that can be used to generate PDF. +# The default value is: NO. + +GENERATE_DOCBOOK = NO + +# The DOCBOOK_OUTPUT tag is used to specify where the Docbook pages will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be put in +# front of it. +# The default directory is: docbook. +# This tag requires that the tag GENERATE_DOCBOOK is set to YES. + +DOCBOOK_OUTPUT = docbook + +#--------------------------------------------------------------------------- +# Configuration options for the AutoGen Definitions output +#--------------------------------------------------------------------------- + +# If the GENERATE_AUTOGEN_DEF tag is set to YES, doxygen will generate an +# AutoGen Definitions (see http://autogen.sourceforge.net/) file that captures +# the structure of the code including all documentation. Note that this feature +# is still experimental and incomplete at the moment. +# The default value is: NO. + +GENERATE_AUTOGEN_DEF = NO + +#--------------------------------------------------------------------------- +# Configuration options related to the Perl module output +#--------------------------------------------------------------------------- + +# If the GENERATE_PERLMOD tag is set to YES, doxygen will generate a Perl module +# file that captures the structure of the code including all documentation. +# +# Note that this feature is still experimental and incomplete at the moment. +# The default value is: NO. + +GENERATE_PERLMOD = NO + +# If the PERLMOD_LATEX tag is set to YES, doxygen will generate the necessary +# Makefile rules, Perl scripts and LaTeX code to be able to generate PDF and DVI +# output from the Perl module output. +# The default value is: NO. +# This tag requires that the tag GENERATE_PERLMOD is set to YES. + +PERLMOD_LATEX = NO + +# If the PERLMOD_PRETTY tag is set to YES, the Perl module output will be nicely +# formatted so it can be parsed by a human reader. This is useful if you want to +# understand what is going on. On the other hand, if this tag is set to NO, the +# size of the Perl module output will be much smaller and Perl will parse it +# just the same. +# The default value is: YES. +# This tag requires that the tag GENERATE_PERLMOD is set to YES. + +PERLMOD_PRETTY = YES + +# The names of the make variables in the generated doxyrules.make file are +# prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. This is useful +# so different doxyrules.make files included by the same Makefile don't +# overwrite each other's variables. +# This tag requires that the tag GENERATE_PERLMOD is set to YES. + +PERLMOD_MAKEVAR_PREFIX = + +#--------------------------------------------------------------------------- +# Configuration options related to the preprocessor +#--------------------------------------------------------------------------- + +# If the ENABLE_PREPROCESSING tag is set to YES, doxygen will evaluate all +# C-preprocessor directives found in the sources and include files. +# The default value is: YES. + +ENABLE_PREPROCESSING = YES + +# If the MACRO_EXPANSION tag is set to YES, doxygen will expand all macro names +# in the source code. If set to NO, only conditional compilation will be +# performed. Macro expansion can be done in a controlled way by setting +# EXPAND_ONLY_PREDEF to YES. +# The default value is: NO. +# This tag requires that the tag ENABLE_PREPROCESSING is set to YES. + +MACRO_EXPANSION = NO + +# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES then +# the macro expansion is limited to the macros specified with the PREDEFINED and +# EXPAND_AS_DEFINED tags. +# The default value is: NO. +# This tag requires that the tag ENABLE_PREPROCESSING is set to YES. + +EXPAND_ONLY_PREDEF = NO + +# If the SEARCH_INCLUDES tag is set to YES, the include files in the +# INCLUDE_PATH will be searched if a #include is found. +# The default value is: YES. +# This tag requires that the tag ENABLE_PREPROCESSING is set to YES. + +SEARCH_INCLUDES = YES + +# The INCLUDE_PATH tag can be used to specify one or more directories that +# contain include files that are not input files but should be processed by the +# preprocessor. Note that the INCLUDE_PATH is not recursive, so the setting of +# RECURSIVE has no effect here. +# This tag requires that the tag SEARCH_INCLUDES is set to YES. + +INCLUDE_PATH = + +# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard +# patterns (like *.h and *.hpp) to filter out the header-files in the +# directories. If left blank, the patterns specified with FILE_PATTERNS will be +# used. +# This tag requires that the tag ENABLE_PREPROCESSING is set to YES. + +INCLUDE_FILE_PATTERNS = + +# The PREDEFINED tag can be used to specify one or more macro names that are +# defined before the preprocessor is started (similar to the -D option of e.g. +# gcc). The argument of the tag is a list of macros of the form: name or +# name=definition (no spaces). If the definition and the "=" are omitted, "=1" +# is assumed. To prevent a macro definition from being undefined via #undef or +# recursively expanded use the := operator instead of the = operator. +# This tag requires that the tag ENABLE_PREPROCESSING is set to YES. + +PREDEFINED = FUSE_USE_VERSION=35 + +# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then this +# tag can be used to specify a list of macro names that should be expanded. The +# macro definition that is found in the sources will be used. Use the PREDEFINED +# tag if you want to use a different macro definition that overrules the +# definition found in the source code. +# This tag requires that the tag ENABLE_PREPROCESSING is set to YES. + +EXPAND_AS_DEFINED = + +# If the SKIP_FUNCTION_MACROS tag is set to YES then doxygen's preprocessor will +# remove all references to function-like macros that are alone on a line, have +# an all uppercase name, and do not end with a semicolon. Such function macros +# are typically used for boiler-plate code, and will confuse the parser if not +# removed. +# The default value is: YES. +# This tag requires that the tag ENABLE_PREPROCESSING is set to YES. + +SKIP_FUNCTION_MACROS = YES + +#--------------------------------------------------------------------------- +# Configuration options related to external references +#--------------------------------------------------------------------------- + +# The TAGFILES tag can be used to specify one or more tag files. For each tag +# file the location of the external documentation should be added. The format of +# a tag file without this location is as follows: +# TAGFILES = file1 file2 ... +# Adding location for the tag files is done as follows: +# TAGFILES = file1=loc1 "file2 = loc2" ... +# where loc1 and loc2 can be relative or absolute paths or URLs. See the +# section "Linking to external documentation" for more information about the use +# of tag files. +# Note: Each tag file must have a unique name (where the name does NOT include +# the path). If a tag file is not located in the directory in which doxygen is +# run, you must also specify the path to the tagfile here. + +TAGFILES = + +# When a file name is specified after GENERATE_TAGFILE, doxygen will create a +# tag file that is based on the input files it reads. See section "Linking to +# external documentation" for more information about the usage of tag files. + +GENERATE_TAGFILE = + +# If the ALLEXTERNALS tag is set to YES, all external class will be listed in +# the class index. If set to NO, only the inherited external classes will be +# listed. +# The default value is: NO. + +ALLEXTERNALS = NO + +# If the EXTERNAL_GROUPS tag is set to YES, all external groups will be listed +# in the modules index. If set to NO, only the current project's groups will be +# listed. +# The default value is: YES. + +EXTERNAL_GROUPS = YES + +# If the EXTERNAL_PAGES tag is set to YES, all external pages will be listed in +# the related pages index. If set to NO, only the current project's pages will +# be listed. +# The default value is: YES. + +EXTERNAL_PAGES = YES + +#--------------------------------------------------------------------------- +# Configuration options related to the dot tool +#--------------------------------------------------------------------------- + +# You can include diagrams made with dia in doxygen documentation. Doxygen will +# then run dia to produce the diagram and insert it in the documentation. The +# DIA_PATH tag allows you to specify the directory where the dia binary resides. +# If left empty dia is assumed to be found in the default search path. + +DIA_PATH = + +# If set to YES the inheritance and collaboration graphs will hide inheritance +# and usage relations if the target is undocumented or is not a class. +# The default value is: YES. + +HIDE_UNDOC_RELATIONS = YES + +# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is +# available from the path. This tool is part of Graphviz (see: +# http://www.graphviz.org/), a graph visualization toolkit from AT&T and Lucent +# Bell Labs. The other options in this section have no effect if this option is +# set to NO +# The default value is: NO. + +HAVE_DOT = NO + +# The DOT_NUM_THREADS specifies the number of dot invocations doxygen is allowed +# to run in parallel. When set to 0 doxygen will base this on the number of +# processors available in the system. You can set it explicitly to a value +# larger than 0 to get control over the balance between CPU load and processing +# speed. +# Minimum value: 0, maximum value: 32, default value: 0. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_NUM_THREADS = 0 + +# DOT_COMMON_ATTR is common attributes for nodes, edges and labels of +# subgraphs. When you want a differently looking font in the dot files that +# doxygen generates you can specify fontname, fontcolor and fontsize attributes. +# For details please see <a href=https://graphviz.org/doc/info/attrs.html>Node, +# Edge and Graph Attributes specification</a> You need to make sure dot is able +# to find the font, which can be done by putting it in a standard location or by +# setting the DOTFONTPATH environment variable or by setting DOT_FONTPATH to the +# directory containing the font. Default graphviz fontsize is 14. +# The default value is: fontname=Helvetica,fontsize=10. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_COMMON_ATTR = "fontname=Helvetica,fontsize=10" + +# DOT_EDGE_ATTR is concatenated with DOT_COMMON_ATTR. For elegant style you can +# add 'arrowhead=open, arrowtail=open, arrowsize=0.5'. <a +# href=https://graphviz.org/doc/info/arrows.html>Complete documentation about +# arrows shapes.</a> +# The default value is: labelfontname=Helvetica,labelfontsize=10. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_EDGE_ATTR = "labelfontname=Helvetica,labelfontsize=10" + +# DOT_NODE_ATTR is concatenated with DOT_COMMON_ATTR. For view without boxes +# around nodes set 'shape=plain' or 'shape=plaintext' <a +# href=https://www.graphviz.org/doc/info/shapes.html>Shapes specification</a> +# The default value is: shape=box,height=0.2,width=0.4. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_NODE_ATTR = "shape=box,height=0.2,width=0.4" + +# You can set the path where dot can find font specified with fontname in +# DOT_COMMON_ATTR and others dot attributes. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_FONTPATH = + +# If the CLASS_GRAPH tag is set to YES (or GRAPH) then doxygen will generate a +# graph for each documented class showing the direct and indirect inheritance +# relations. In case HAVE_DOT is set as well dot will be used to draw the graph, +# otherwise the built-in generator will be used. If the CLASS_GRAPH tag is set +# to TEXT the direct and indirect inheritance relations will be shown as texts / +# links. +# Possible values are: NO, YES, TEXT and GRAPH. +# The default value is: YES. + +CLASS_GRAPH = TEXT + +# If the COLLABORATION_GRAPH tag is set to YES then doxygen will generate a +# graph for each documented class showing the direct and indirect implementation +# dependencies (inheritance, containment, and class references variables) of the +# class with other documented classes. +# The default value is: YES. +# This tag requires that the tag HAVE_DOT is set to YES. + +COLLABORATION_GRAPH = YES + +# If the GROUP_GRAPHS tag is set to YES then doxygen will generate a graph for +# groups, showing the direct groups dependencies. See also the chapter Grouping +# in the manual. +# The default value is: YES. +# This tag requires that the tag HAVE_DOT is set to YES. + +GROUP_GRAPHS = YES + +# If the UML_LOOK tag is set to YES, doxygen will generate inheritance and +# collaboration diagrams in a style similar to the OMG's Unified Modeling +# Language. +# The default value is: NO. +# This tag requires that the tag HAVE_DOT is set to YES. + +UML_LOOK = NO + +# If the UML_LOOK tag is enabled, the fields and methods are shown inside the +# class node. If there are many fields or methods and many nodes the graph may +# become too big to be useful. The UML_LIMIT_NUM_FIELDS threshold limits the +# number of items for each type to make the size more manageable. Set this to 0 +# for no limit. Note that the threshold may be exceeded by 50% before the limit +# is enforced. So when you set the threshold to 10, up to 15 fields may appear, +# but if the number exceeds 15, the total amount of fields shown is limited to +# 10. +# Minimum value: 0, maximum value: 100, default value: 10. +# This tag requires that the tag UML_LOOK is set to YES. + +UML_LIMIT_NUM_FIELDS = 10 + +# If the DOT_UML_DETAILS tag is set to NO, doxygen will show attributes and +# methods without types and arguments in the UML graphs. If the DOT_UML_DETAILS +# tag is set to YES, doxygen will add type and arguments for attributes and +# methods in the UML graphs. If the DOT_UML_DETAILS tag is set to NONE, doxygen +# will not generate fields with class member information in the UML graphs. The +# class diagrams will look similar to the default class diagrams but using UML +# notation for the relationships. +# Possible values are: NO, YES and NONE. +# The default value is: NO. +# This tag requires that the tag UML_LOOK is set to YES. + +DOT_UML_DETAILS = NO + +# The DOT_WRAP_THRESHOLD tag can be used to set the maximum number of characters +# to display on a single line. If the actual line length exceeds this threshold +# significantly it will wrapped across multiple lines. Some heuristics are apply +# to avoid ugly line breaks. +# Minimum value: 0, maximum value: 1000, default value: 17. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_WRAP_THRESHOLD = 17 + +# If the TEMPLATE_RELATIONS tag is set to YES then the inheritance and +# collaboration graphs will show the relations between templates and their +# instances. +# The default value is: NO. +# This tag requires that the tag HAVE_DOT is set to YES. + +TEMPLATE_RELATIONS = NO + +# If the INCLUDE_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are set to +# YES then doxygen will generate a graph for each documented file showing the +# direct and indirect include dependencies of the file with other documented +# files. +# The default value is: YES. +# This tag requires that the tag HAVE_DOT is set to YES. + +INCLUDE_GRAPH = YES + +# If the INCLUDED_BY_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are +# set to YES then doxygen will generate a graph for each documented file showing +# the direct and indirect include dependencies of the file with other documented +# files. +# The default value is: YES. +# This tag requires that the tag HAVE_DOT is set to YES. + +INCLUDED_BY_GRAPH = YES + +# If the CALL_GRAPH tag is set to YES then doxygen will generate a call +# dependency graph for every global function or class method. +# +# Note that enabling this option will significantly increase the time of a run. +# So in most cases it will be better to enable call graphs for selected +# functions only using the \callgraph command. Disabling a call graph can be +# accomplished by means of the command \hidecallgraph. +# The default value is: NO. +# This tag requires that the tag HAVE_DOT is set to YES. + +CALL_GRAPH = NO + +# If the CALLER_GRAPH tag is set to YES then doxygen will generate a caller +# dependency graph for every global function or class method. +# +# Note that enabling this option will significantly increase the time of a run. +# So in most cases it will be better to enable caller graphs for selected +# functions only using the \callergraph command. Disabling a caller graph can be +# accomplished by means of the command \hidecallergraph. +# The default value is: NO. +# This tag requires that the tag HAVE_DOT is set to YES. + +CALLER_GRAPH = NO + +# If the GRAPHICAL_HIERARCHY tag is set to YES then doxygen will graphical +# hierarchy of all classes instead of a textual one. +# The default value is: YES. +# This tag requires that the tag HAVE_DOT is set to YES. + +GRAPHICAL_HIERARCHY = YES + +# If the DIRECTORY_GRAPH tag is set to YES then doxygen will show the +# dependencies a directory has on other directories in a graphical way. The +# dependency relations are determined by the #include relations between the +# files in the directories. +# The default value is: YES. +# This tag requires that the tag HAVE_DOT is set to YES. + +DIRECTORY_GRAPH = YES + +# The DIR_GRAPH_MAX_DEPTH tag can be used to limit the maximum number of levels +# of child directories generated in directory dependency graphs by dot. +# Minimum value: 1, maximum value: 25, default value: 1. +# This tag requires that the tag DIRECTORY_GRAPH is set to YES. + +DIR_GRAPH_MAX_DEPTH = 1 + +# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images +# generated by dot. For an explanation of the image formats see the section +# output formats in the documentation of the dot tool (Graphviz (see: +# http://www.graphviz.org/)). +# Note: If you choose svg you need to set HTML_FILE_EXTENSION to xhtml in order +# to make the SVG files visible in IE 9+ (other browsers do not have this +# requirement). +# Possible values are: png, jpg, gif, svg, png:gd, png:gd:gd, png:cairo, +# png:cairo:gd, png:cairo:cairo, png:cairo:gdiplus, png:gdiplus and +# png:gdiplus:gdiplus. +# The default value is: png. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_IMAGE_FORMAT = png + +# If DOT_IMAGE_FORMAT is set to svg, then this option can be set to YES to +# enable generation of interactive SVG images that allow zooming and panning. +# +# Note that this requires a modern browser other than Internet Explorer. Tested +# and working are Firefox, Chrome, Safari, and Opera. +# Note: For IE 9+ you need to set HTML_FILE_EXTENSION to xhtml in order to make +# the SVG files visible. Older versions of IE do not have SVG support. +# The default value is: NO. +# This tag requires that the tag HAVE_DOT is set to YES. + +INTERACTIVE_SVG = NO + +# The DOT_PATH tag can be used to specify the path where the dot tool can be +# found. If left blank, it is assumed the dot tool can be found in the path. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_PATH = + +# The DOTFILE_DIRS tag can be used to specify one or more directories that +# contain dot files that are included in the documentation (see the \dotfile +# command). +# This tag requires that the tag HAVE_DOT is set to YES. + +DOTFILE_DIRS = + +# The MSCFILE_DIRS tag can be used to specify one or more directories that +# contain msc files that are included in the documentation (see the \mscfile +# command). + +MSCFILE_DIRS = + +# The DIAFILE_DIRS tag can be used to specify one or more directories that +# contain dia files that are included in the documentation (see the \diafile +# command). + +DIAFILE_DIRS = + +# When using plantuml, the PLANTUML_JAR_PATH tag should be used to specify the +# path where java can find the plantuml.jar file or to the filename of jar file +# to be used. If left blank, it is assumed PlantUML is not used or called during +# a preprocessing step. Doxygen will generate a warning when it encounters a +# \startuml command in this case and will not generate output for the diagram. + +PLANTUML_JAR_PATH = + +# When using plantuml, the PLANTUML_CFG_FILE tag can be used to specify a +# configuration file for plantuml. + +PLANTUML_CFG_FILE = + +# When using plantuml, the specified paths are searched for files specified by +# the !include statement in a plantuml block. + +PLANTUML_INCLUDE_PATH = + +# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of nodes +# that will be shown in the graph. If the number of nodes in a graph becomes +# larger than this value, doxygen will truncate the graph, which is visualized +# by representing a node as a red box. Note that doxygen if the number of direct +# children of the root node in a graph is already larger than +# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note that +# the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH. +# Minimum value: 0, maximum value: 10000, default value: 50. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_GRAPH_MAX_NODES = 50 + +# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the graphs +# generated by dot. A depth value of 3 means that only nodes reachable from the +# root by following a path via at most 3 edges will be shown. Nodes that lay +# further from the root node will be omitted. Note that setting this option to 1 +# or 2 may greatly reduce the computation time needed for large code bases. Also +# note that the size of a graph can be further restricted by +# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction. +# Minimum value: 0, maximum value: 1000, default value: 0. +# This tag requires that the tag HAVE_DOT is set to YES. + +MAX_DOT_GRAPH_DEPTH = 1000 + +# Set the DOT_MULTI_TARGETS tag to YES to allow dot to generate multiple output +# files in one run (i.e. multiple -o and -T options on the command line). This +# makes dot run faster, but since only newer versions of dot (>1.8.10) support +# this, this feature is disabled by default. +# The default value is: NO. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_MULTI_TARGETS = NO + +# If the GENERATE_LEGEND tag is set to YES doxygen will generate a legend page +# explaining the meaning of the various boxes and arrows in the dot generated +# graphs. +# Note: This tag requires that UML_LOOK isn't set, i.e. the doxygen internal +# graphical representation for inheritance and collaboration diagrams is used. +# The default value is: YES. +# This tag requires that the tag HAVE_DOT is set to YES. + +GENERATE_LEGEND = YES + +# If the DOT_CLEANUP tag is set to YES, doxygen will remove the intermediate +# files that are used to generate the various graphs. +# +# Note: This setting is not only used for dot files but also for msc temporary +# files. +# The default value is: YES. + +DOT_CLEANUP = YES diff --git a/doc/README.NFS b/doc/README.NFS new file mode 100644 index 0000000..edf5482 --- /dev/null +++ b/doc/README.NFS @@ -0,0 +1,44 @@ +NFS exporting is supported in Linux kernels 2.6.27 or later. + +You need to add an fsid=NNN option to /etc/exports to make exporting a +FUSE directory work. + +Filesystem support +------------------ + +NFS exporting works to some extent on all fuse filesystems, but not +perfectly. This is due to the stateless nature of the protocol, the +server has no way of knowing whether the client is keeping a reference +to a file or not, and hence that file may be removed from the server's +cache. In that case there has to be a way to look up that object +using the inode number, otherwise an ESTALE error will be returned. + +1) low-level interface + +Filesystems need to set FUSE_CAP_EXPORT_SUPPORT in conn->wants and +implement special lookups for the names "." and "..". The former may +be requested on any inode, including non-directories, while the latter +is only requested for directories. Otherwise these special lookups +should behave identically to ordinary lookups. + +Furthermore, setting FUSE_CAP_EXPORT_SUPPORT requires the file system +to handle node-ids (fuse_ino_t) that the file system may does not know +about - e.g. a fuse FORGET request might have been received or the node-id +was used in a previous instance of the file system daemon. The node-id might +not be valid at all when an invalid handle is passed to open_by_handle_at(). +This implies that the filesystem *must not* reuse node-ids even if +generation numbers are set correctly. This is because generation numbers +are not provided by the kernel to e.g. the getattr() handler, so the +handler would be unable to tell if the provided node-id refers to the +"known" current one, or a previous one that has been forgotten and re-used. + +2) high-level interface + +Because the high-level interface is path based, it is not possible to +delegate looking up by inode to the filesystem. + +To work around this, currently a "noforget" option is provided, which +makes the library remember nodes forever. This will make the NFS +server happy, but also results in an ever growing memory footprint for +the filesystem. For this reason if the filesystem is large (or the +memory is small), then this option is not recommended. diff --git a/doc/README.custom-io b/doc/README.custom-io new file mode 100644 index 0000000..7b82a21 --- /dev/null +++ b/doc/README.custom-io @@ -0,0 +1,187 @@ +FUSE over a Custom Transport +============================ + +This document describes fuse_session_custom_io(), which lets a session speak +the FUSE protocol over a file descriptor the caller provides instead of over +/dev/fuse, and the validation such a caller has to do. + +The example that ships with libfuse, example/hello_ll_uds.c, uses an AF_UNIX +socket, which is where the shorthand "UDS" comes from. The API itself does not +care what the descriptor is: a socket, a pipe pair, anything a read() and a +writev() work on. + + +Disabled by Default +------------------- + +fuse_session_custom_io() returns -ENOTSUP and logs a message unless libfuse +was built with: + + meson setup build -Denable-custom-io=true + +The hello_ll_uds example is built only with that option, and the test that +drives it skips without it. The reason is the trust model described below, +not the transport itself. + +The symbols remain exported in every build, so an application that calls +fuse_session_custom_io() keeps linking and loading; it is refused at run time. + + +How It Works +------------ + +A normal session gets its descriptor from the kernel and the kernel is the +peer at the other end: + + fuse_session_new() + | + v + fuse_session_mount() --> open /dev/fuse, mount() + | + v + fuse_session_loop() --> read()/writev() on the /dev/fuse fd + peer: the kernel FUSE module + +A custom io session never mounts anything. The caller connects the descriptor +itself and hands it, plus the io callbacks, to the session: + + fuse_session_new() + | + v + caller creates fd --> socket(), connect()/accept(), ... + | + v + fuse_session_custom_io(se, &io, sizeof(io), fd) + | + v + fuse_session_loop() --> io->read()/io->writev() on that fd + peer: whatever is at the other end + +From FUSE_INIT onwards the protocol is identical. The peer sends requests in +the same wire format the kernel would, and the session replies the same way. + + +The io Callbacks +---------------- + +struct fuse_custom_io has five members. read and writev are mandatory: +fuse_session_custom_io() returns -EINVAL without them, because libfuse cannot +fall back to plain read()/writev() on a descriptor it does not own. + +1. **read(fd, buf, buf_len, userdata)**: must deliver exactly one request per + call. buf_len is the session buffer size. A return value smaller than + sizeof(struct fuse_in_header) ends the session loop with -EIO and the + message "short read on fuse device", which is also what a peer closing the + connection looks like. + +2. **writev(fd, iov, count, userdata)**: must write every iovec. + +3. **splice_send** and **splice_receive**: optional. Without them the session + does not offer FUSE_CAP_SPLICE_WRITE, FUSE_CAP_SPLICE_MOVE and + FUSE_CAP_SPLICE_READ, so replies and requests are copied instead. + +4. **clone_fd**: optional, called by the multi-threaded loop to get a + descriptor per worker. Without it every worker shares the one descriptor. + +The descriptor is closed by fuse_session_destroy(). + + +What a Custom io Session Does Not Have +-------------------------------------- + +1. **No mountpoint.** fuse_session_mount() and fusermount3 are not involved, + so none of the mount options, the setuid helper or /etc/mtab bookkeeping + apply. + +2. **No kernel.** FUSE_INIT arrives from the peer, so protocol version and + feature negotiation are whatever the peer asks for. + +3. **No io_uring.** fuse_session_custom_io() clears se->uring.enable: the ring + is issued against a /dev/fuse descriptor and would bypass the io callbacks + entirely. A session reports this on its own stderr as + "FUSE_INIT: io_uring=off:custom_io". + +4. **No trustworthy credentials.** The uid, gid and pid that fuse_req_ctx() + returns are the ones the peer wrote into fuse_in_header. The allow_root + option compares that same uid against the session owner, so it states what + the peer claims, not who the peer is. + + +The Trust Model: Unverified Opcodes +----------------------------------- + +libfuse was written against one producer, the Linux kernel, which does not +need to validate the requests it sends because it builds them itself. A +kernel-built request always has: + +- a fuse_in_header whose len matches the bytes written, +- the per-opcode header the opcode calls for, +- name arguments NUL terminated inside the message, +- count and size fields describing data that is actually present. + +libfuse repeats almost none of these checks. What it does check: + +- FUSE_INIT and CUSE_INIT: enough bytes for the major/minor fields, at + dispatch time, otherwise EPROTO. +- FUSE_WRITE handled by op.write_buf: fuse_write_in.size against the bytes + received, otherwise EIO. +- FUSE_NOTIFY_REPLY: the same check. +- The extension block: FUSE_EXT_SIZE(total_extlen) against the buffer length. + +Every other opcode is taken at face value, in three classes: + +1. **Entry counts.** fuse_batch_forget_in.count bounds the loop over the + fuse_forget_one array that follows it. Nothing compares it to the bytes + that arrived, so an inflated count reads count * 16 bytes past the array + and hands them to op.forget or op.forget_multi as inode numbers and + lookup counts. + +2. **Payload lengths.** FUSE_SETXATTR passes fuse_setxattr_in.size to + op.setxattr as the length of the value, without checking that many bytes + were received. + +3. **Strings.** Name arguments are handed to the filesystem as a const char * + and are expected to be terminated. FUSE_SYMLINK, FUSE_RENAME, FUSE_RENAME2 + and FUSE_SETXATTR locate their second argument at name + strlen(name) + 1. + A name without a NUL runs off the end of the receive buffer. + +A malformed request therefore reads memory outside the receive buffer. The +usual outcome is a crash of the filesystem process; the batch forget case also +feeds the out-of-bounds words into the filesystem's inode bookkeeping. + +**A custom io peer owes the session the validation the kernel would have made +unnecessary.** The read callback is the place for it, because that is the last +point before libfuse parses the bytes. At minimum, per message: + +- reject a fuse_in_header whose len is below sizeof(struct fuse_in_header) or + above the buffer size, and deliver exactly len bytes; +- reject an opcode whose per-opcode header does not fit in what arrived; +- check every count and size field against the remaining bytes; +- check that every name argument is NUL terminated within the message. + +A peer that forwards requests it did not build, or that accepts connections +from anything but itself, has to treat this as the security boundary it is. + + +Example +------- + +example/hello_ll_uds.c serves the protocol on an AF_UNIX socket: + + se = fuse_session_new(&args, &hello_ll_oper, sizeof(hello_ll_oper), NULL); + cfd = create_socket(socket_path); /* socket, bind, listen, accept */ + fuse_session_custom_io(se, &io, sizeof(io), cfd); + fuse_session_loop(se); + +Its stream_read() reads the header, takes the length from it and reads the +rest of the message, which is the framing the read callback contract asks for +but none of the validation the section above asks for. It is an example of the +API, not a model for a peer that is not trusted. + + +See Also +-------- + +- doc/README.fuse-io-uring - the other non-default transport +- doc/README.mount - what a /dev/fuse session does instead +- include/fuse_lowlevel.h - fuse_session_custom_io() and struct fuse_custom_io diff --git a/doc/README.fuse-io-uring b/doc/README.fuse-io-uring new file mode 100644 index 0000000..62c71bf --- /dev/null +++ b/doc/README.fuse-io-uring @@ -0,0 +1,38 @@ +fuse-over-io-uring uses io-uring for transport of kernel/userspace +messages. See also https://docs.kernel.org/filesystems/fuse/fuse-io-uring.html + +In order to enable it, the kernel module needs to have it enabled: +echo 1 > /sys/module/fuse/parameters/enable_uring + +Additionally, FUSE_CAP_OVER_IO_URING needs to be set and +se->uring.enable has to be true. The latter can be +achieved with the libfuse option '-o io_uring'. + +Default queue-depth is 8 and can be changed with the parameter +'-oio_uring_q_depth'. + +As of now there is always one queue per core. A reduced number +of cores in development. + +Benefits: +- Improved performance by using io_uring for kernel/userspace communication +- Reduced system call overhead compared to traditional FUSE +- Asynchronous I/O operations + +Usage: +To enable io_uring support when mounting a FUSE filesystem: +1. Enable kernel support: echo 1 > /sys/module/fuse/parameters/enable_uring +2. Mount with io_uring option: -o io_uring +3. Optionally adjust queue depth: -o io_uring_q_depth=<depth> + +Example: +./my_fuse_fs /source /mountpoint -o io_uring -o io_uring_q_depth=16 + +Requirements: +- Linux kernel with io_uring and FUSE io_uring support enabled +- libfuse compiled with io_uring support + +Build Dependencies: +- liburing (for io_uring support) +- libnuma (required alongside liburing) +- meson build system with option: -Denable-io-uring=true diff --git a/doc/README.fuse_reply_errors b/doc/README.fuse_reply_errors new file mode 100644 index 0000000..d60fd7a --- /dev/null +++ b/doc/README.fuse_reply_errors @@ -0,0 +1,5 @@ +Under normal operation, a call to any fuse_reply_* function should not result +in an error. + +Should the kernel abort the fuse connection, a fuse_reply_* call can return +-ENOENT. diff --git a/doc/README.notifications b/doc/README.notifications new file mode 100644 index 0000000..2ca6204 --- /dev/null +++ b/doc/README.notifications @@ -0,0 +1,37 @@ +During the life-cycle of a user-space filesystem the usual flow is: + + 1. User-space application does a filesystem-related syscall + 2. Kernel VFS calls into the FUSE kernel driver + 3. FUSE kernel redirects request to the user-space filesystem + 4. User-space server replies to request + 5. FUSE returns reply to VFS + 6. User-space application gets reply from the kernel + +However, there are occasions where the filesystem needs to send notifications to +the kernel that are not in reply to any particular request. If, for example, +when a READ request of 4096 bytes results in the filesystem having more data +available for the specific inode, it may be useful to provide this extra data to +the kernel cache so that future read operations will be faster. + +FUSE provides mechanisms for a user-space server to send the kernel certain +types of asynchronous notifications. Currently, these are the available +notifications: + +|-------------+----------------------------------| +| Operation | libfuse function | +|-------------+----------------------------------| +| POLL | fuse_lowlevel_notify_poll | +| INVAL_INODE | fuse_lowlevel_notify_inval_inode | +| ENTRY | fuse_lowlevel_notify_inval_entry | +| STORE | fuse_lowlevel_notify_store | +| RETRIEVE | fuse_lowlevel_notify_retrieve | +| DELETE | fuse_lowlevel_notify_delete | +| RESEND | - | +|-------------+----------------------------------| + +One important restriction is that these asynchronous operations SHALL NOT be +performed while executing other FUSE requests. Doing so will likely result in +deadlocking the user-space filesystem server. In the example above, if the +server is replying to a READ request and has extra data to add to the kernel +cache, it needs to reply to the READ request first, and, e.g., signal a +different thread to do the STORE. diff --git a/doc/fast17-vangoor.pdf b/doc/fast17-vangoor.pdf new file mode 100644 index 0000000..cef7237 Binary files /dev/null and b/doc/fast17-vangoor.pdf differ diff --git a/doc/fusermount3.1 b/doc/fusermount3.1 new file mode 100644 index 0000000..d45f061 --- /dev/null +++ b/doc/fusermount3.1 @@ -0,0 +1,44 @@ +.TH FUSERMOUNT3 1 2011\-10\-23 2.8.6 "Filesystem in Userspace (FUSE)" + +.SH NAME +\fBfusermount3\fR \- mount and unmount FUSE filesystems + +.SH SYNOPSIS +\fBfusermount3\fR [\fIOPTIONS\fR] \fIMOUNTPOINT\fR + +.SH DESCRIPTION +Filesystem in Userspace (FUSE) is a simple interface for userspace programs to export a virtual filesystem to the Linux kernel. It provides a secure method for non privileged users to create and mount their own filesystem implementations. +.PP +\fBfusermount3\fR is a program to mount and unmount FUSE +filesystems. It should be called directly only for unmounting FUSE +file systems. To allow mounting and unmounting by unprivileged users, +\fBfusermount3\fR needs to be installed set-uid root. +.SH OPTIONS +.IP "\-h" 4 +print help. +.IP "\-V" 4 +print version. +.IP "-o \fIOPTION\fR[,\fIOPTION\fR...]" 4 +mount options. +.IP "-u" 4 +unmount. +.IP "-q" 4 +quiet. +.IP "-z" 4 +lazy unmount. + +.SH SEE ALSO +\fImount\fR(8), +\fImount.fuse3\fR(8), +\fIfuse\fR(4), + +.SH HOMEPAGE +More information about fusermount3 and the FUSE project can be found at <\fIhttps://github.com/libfuse/\fR>. + +.SH AUTHORS +.LP +FUSE is currently maintained by Nikolaus Rath <Nikolaus@rath.org> +.LP +The original author of FUSE is Miklos Szeredi <\fImiklos@szeredi.hu\fR>. +.LP +This manual page was originally written by Daniel Baumann <\fIdaniel.baumann@progress\-technologies.net\fR>. diff --git a/doc/html/annotated.html b/doc/html/annotated.html new file mode 100644 index 0000000..0c3022a --- /dev/null +++ b/doc/html/annotated.html @@ -0,0 +1,80 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "https://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" lang="en-US"> +<head> +<meta http-equiv="Content-Type" content="text/xhtml;charset=UTF-8"/> +<meta http-equiv="X-UA-Compatible" content="IE=11"/> +<meta name="generator" content="Doxygen 1.15.0"/> +<meta name="viewport" content="width=device-width, initial-scale=1"/> +<title>libfuse: Data Structures + + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
+
Data Structures
+
+ + + +
+ + diff --git a/doc/html/buffer_8c_source.html b/doc/html/buffer_8c_source.html new file mode 100644 index 0000000..740634b --- /dev/null +++ b/doc/html/buffer_8c_source.html @@ -0,0 +1,409 @@ + + + + + + + +libfuse: lib/buffer.c Source File + + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
+
buffer.c
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2010 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 Functions for dealing with `struct fuse_buf` and `struct
+
6 fuse_bufvec`.
+
7
+
8 This program can be distributed under the terms of the GNU LGPLv2.
+
9 See the file LGPL2.txt
+
10*/
+
11
+
12#define _GNU_SOURCE
+
13
+
14#include "fuse_config.h"
+
15#include "fuse_i.h"
+
16#include "fuse_lowlevel.h"
+
17#include <string.h>
+
18#include <unistd.h>
+
19#include <errno.h>
+
20#include <assert.h>
+
21
+
+
22size_t fuse_buf_size(const struct fuse_bufvec *bufv)
+
23{
+
24 size_t i;
+
25 size_t size = 0;
+
26
+
27 for (i = 0; i < bufv->count; i++) {
+
28 if (bufv->buf[i].size >= SIZE_MAX - size)
+
29 return SIZE_MAX;
+
30
+
31 size += bufv->buf[i].size;
+
32 }
+
33
+
34 return size;
+
35}
+
+
36
+
37static size_t min_size(size_t s1, size_t s2)
+
38{
+
39 return s1 < s2 ? s1 : s2;
+
40}
+
41
+
42static ssize_t fuse_buf_write(const struct fuse_buf *dst, size_t dst_off,
+
43 const struct fuse_buf *src, size_t src_off,
+
44 size_t len)
+
45{
+
46 ssize_t res = 0;
+
47 size_t copied = 0;
+
48
+
49 while (len) {
+
50 if (dst->flags & FUSE_BUF_FD_SEEK) {
+
51 res = pwrite(dst->fd, (char *)src->mem + src_off, len,
+
52 dst->pos + dst_off);
+
53 } else {
+
54 res = write(dst->fd, (char *)src->mem + src_off, len);
+
55 }
+
56 if (res == -1) {
+
57 if (!copied)
+
58 return -errno;
+
59 break;
+
60 }
+
61 if (res == 0)
+
62 break;
+
63
+
64 copied += res;
+
65 if (!(dst->flags & FUSE_BUF_FD_RETRY))
+
66 break;
+
67
+
68 src_off += res;
+
69 dst_off += res;
+
70 len -= res;
+
71 }
+
72
+
73 return copied;
+
74}
+
75
+
76static ssize_t fuse_buf_read(const struct fuse_buf *dst, size_t dst_off,
+
77 const struct fuse_buf *src, size_t src_off,
+
78 size_t len)
+
79{
+
80 ssize_t res = 0;
+
81 size_t copied = 0;
+
82
+
83 while (len) {
+
84 if (src->flags & FUSE_BUF_FD_SEEK) {
+
85 res = pread(src->fd, (char *)dst->mem + dst_off, len,
+
86 src->pos + src_off);
+
87 } else {
+
88 res = read(src->fd, (char *)dst->mem + dst_off, len);
+
89 }
+
90 if (res == -1) {
+
91 if (!copied)
+
92 return -errno;
+
93 break;
+
94 }
+
95 if (res == 0)
+
96 break;
+
97
+
98 copied += res;
+
99 if (!(src->flags & FUSE_BUF_FD_RETRY))
+
100 break;
+
101
+
102 dst_off += res;
+
103 src_off += res;
+
104 len -= res;
+
105 }
+
106
+
107 return copied;
+
108}
+
109
+
110static ssize_t fuse_buf_fd_to_fd(const struct fuse_buf *dst, size_t dst_off,
+
111 const struct fuse_buf *src, size_t src_off,
+
112 size_t len)
+
113{
+
114 char buf[4096];
+
115 struct fuse_buf tmp = {
+
116 .size = sizeof(buf),
+
117 .flags = 0,
+
118 };
+
119 ssize_t res;
+
120 size_t copied = 0;
+
121
+
122 tmp.mem = buf;
+
123
+
124 while (len) {
+
125 size_t this_len = min_size(tmp.size, len);
+
126 size_t read_len;
+
127
+
128 res = fuse_buf_read(&tmp, 0, src, src_off, this_len);
+
129 if (res < 0) {
+
130 if (!copied)
+
131 return res;
+
132 break;
+
133 }
+
134 if (res == 0)
+
135 break;
+
136
+
137 read_len = res;
+
138 res = fuse_buf_write(dst, dst_off, &tmp, 0, read_len);
+
139 if (res < 0) {
+
140 if (!copied)
+
141 return res;
+
142 break;
+
143 }
+
144 if (res == 0)
+
145 break;
+
146
+
147 copied += res;
+
148
+
149 if (res < this_len)
+
150 break;
+
151
+
152 dst_off += res;
+
153 src_off += res;
+
154 len -= res;
+
155 }
+
156
+
157 return copied;
+
158}
+
159
+
160#ifdef HAVE_SPLICE
+
161static ssize_t fuse_buf_splice(const struct fuse_buf *dst, size_t dst_off,
+
162 const struct fuse_buf *src, size_t src_off,
+
163 size_t len, enum fuse_buf_copy_flags flags)
+
164{
+
165 int splice_flags = 0;
+
166 off_t *srcpos = NULL;
+
167 off_t *dstpos = NULL;
+
168 off_t srcpos_val;
+
169 off_t dstpos_val;
+
170 ssize_t res;
+
171 size_t copied = 0;
+
172
+ +
174 splice_flags |= SPLICE_F_MOVE;
+ +
176 splice_flags |= SPLICE_F_NONBLOCK;
+
177
+
178 if (src->flags & FUSE_BUF_FD_SEEK) {
+
179 srcpos_val = src->pos + src_off;
+
180 srcpos = &srcpos_val;
+
181 }
+
182 if (dst->flags & FUSE_BUF_FD_SEEK) {
+
183 dstpos_val = dst->pos + dst_off;
+
184 dstpos = &dstpos_val;
+
185 }
+
186
+
187 while (len) {
+
188 res = splice(src->fd, srcpos, dst->fd, dstpos, len,
+
189 splice_flags);
+
190 if (res == -1) {
+
191 if (copied)
+
192 break;
+
193
+
194 if (errno != EINVAL || (flags & FUSE_BUF_FORCE_SPLICE))
+
195 return -errno;
+
196
+
197 /* Maybe splice is not supported for this combination */
+
198 return fuse_buf_fd_to_fd(dst, dst_off, src, src_off,
+
199 len);
+
200 }
+
201 if (res == 0)
+
202 break;
+
203
+
204 copied += res;
+
205 if (!(src->flags & FUSE_BUF_FD_RETRY) &&
+
206 !(dst->flags & FUSE_BUF_FD_RETRY)) {
+
207 break;
+
208 }
+
209
+
210 len -= res;
+
211 }
+
212
+
213 return copied;
+
214}
+
215#else
+
216static ssize_t fuse_buf_splice(const struct fuse_buf *dst, size_t dst_off,
+
217 const struct fuse_buf *src, size_t src_off,
+
218 size_t len, enum fuse_buf_copy_flags flags)
+
219{
+
220 (void) flags;
+
221
+
222 return fuse_buf_fd_to_fd(dst, dst_off, src, src_off, len);
+
223}
+
224#endif
+
225
+
226
+
227static ssize_t fuse_buf_copy_one(const struct fuse_buf *dst, size_t dst_off,
+
228 const struct fuse_buf *src, size_t src_off,
+
229 size_t len, enum fuse_buf_copy_flags flags)
+
230{
+
231 int src_is_fd = src->flags & FUSE_BUF_IS_FD;
+
232 int dst_is_fd = dst->flags & FUSE_BUF_IS_FD;
+
233
+
234 if (!src_is_fd && !dst_is_fd) {
+
235 char *dstmem = (char *)dst->mem + dst_off;
+
236 char *srcmem = (char *)src->mem + src_off;
+
237
+
238 if (dstmem != srcmem) {
+
239 if (dstmem + len <= srcmem || srcmem + len <= dstmem)
+
240 memcpy(dstmem, srcmem, len);
+
241 else
+
242 memmove(dstmem, srcmem, len);
+
243 }
+
244
+
245 return len;
+
246 } else if (!src_is_fd) {
+
247 return fuse_buf_write(dst, dst_off, src, src_off, len);
+
248 } else if (!dst_is_fd) {
+
249 return fuse_buf_read(dst, dst_off, src, src_off, len);
+
250 } else if (flags & FUSE_BUF_NO_SPLICE) {
+
251 return fuse_buf_fd_to_fd(dst, dst_off, src, src_off, len);
+
252 } else {
+
253 return fuse_buf_splice(dst, dst_off, src, src_off, len, flags);
+
254 }
+
255}
+
256
+
257static const struct fuse_buf *fuse_bufvec_current(struct fuse_bufvec *bufv)
+
258{
+
259 if (bufv->idx < bufv->count)
+
260 return &bufv->buf[bufv->idx];
+
261 else
+
262 return NULL;
+
263}
+
264
+
265static int fuse_bufvec_advance(struct fuse_bufvec *bufv, size_t len)
+
266{
+
267 const struct fuse_buf *buf = fuse_bufvec_current(bufv);
+
268
+
269 if (!buf)
+
270 return 0;
+
271
+
272 bufv->off += len;
+
273 assert(bufv->off <= buf->size);
+
274 if (bufv->off == buf->size) {
+
275 assert(bufv->idx < bufv->count);
+
276 bufv->idx++;
+
277 if (bufv->idx == bufv->count)
+
278 return 0;
+
279 bufv->off = 0;
+
280 }
+
281 return 1;
+
282}
+
283
+
+
284ssize_t fuse_buf_copy(struct fuse_bufvec *dstv, struct fuse_bufvec *srcv,
+ +
286{
+
287 size_t copied = 0;
+
288
+
289 if (dstv == srcv)
+
290 return fuse_buf_size(dstv);
+
291
+
292 for (;;) {
+
293 const struct fuse_buf *src = fuse_bufvec_current(srcv);
+
294 const struct fuse_buf *dst = fuse_bufvec_current(dstv);
+
295 size_t src_len;
+
296 size_t dst_len;
+
297 size_t len;
+
298 ssize_t res;
+
299
+
300 if (src == NULL || dst == NULL)
+
301 break;
+
302
+
303 src_len = src->size - srcv->off;
+
304 dst_len = dst->size - dstv->off;
+
305 len = min_size(src_len, dst_len);
+
306
+
307 res = fuse_buf_copy_one(dst, dstv->off, src, srcv->off, len, flags);
+
308 if (res < 0) {
+
309 if (!copied)
+
310 return res;
+
311 break;
+
312 }
+
313 copied += res;
+
314
+
315 if (!fuse_bufvec_advance(srcv, res) ||
+
316 !fuse_bufvec_advance(dstv, res))
+
317 break;
+
318
+
319 if (res < len)
+
320 break;
+
321 }
+
322
+
323 return copied;
+
324}
+
+
size_t fuse_buf_size(const struct fuse_bufvec *bufv)
Definition buffer.c:22
+
@ FUSE_BUF_FD_SEEK
+
@ FUSE_BUF_FD_RETRY
+
@ FUSE_BUF_IS_FD
+
ssize_t fuse_buf_copy(struct fuse_bufvec *dst, struct fuse_bufvec *src, enum fuse_buf_copy_flags flags)
Definition buffer.c:284
+
fuse_buf_copy_flags
+
@ FUSE_BUF_SPLICE_NONBLOCK
+
@ FUSE_BUF_FORCE_SPLICE
+
@ FUSE_BUF_NO_SPLICE
+
@ FUSE_BUF_SPLICE_MOVE
+ + +
enum fuse_buf_flags flags
+
void * mem
+ +
off_t pos
+
size_t size
+ + + +
struct fuse_buf buf[1]
+ +
+ + +
+ + diff --git a/doc/html/classes.html b/doc/html/classes.html new file mode 100644 index 0000000..a082b21 --- /dev/null +++ b/doc/html/classes.html @@ -0,0 +1,64 @@ + + + + + + + +libfuse: Data Structure Index + + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+ + + diff --git a/doc/html/clipboard.js b/doc/html/clipboard.js new file mode 100644 index 0000000..9da9f3c --- /dev/null +++ b/doc/html/clipboard.js @@ -0,0 +1,61 @@ +/** + +The code below is based on the Doxygen Awesome project, see +https://github.com/jothepro/doxygen-awesome-css + +MIT License + +Copyright (c) 2021 - 2022 jothepro + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +*/ + +let clipboard_title = "Copy to clipboard" +let clipboard_icon = `` +let clipboard_successIcon = `` +let clipboard_successDuration = 1000 + +$(function() { + if(navigator.clipboard) { + const fragments = document.getElementsByClassName("fragment") + for(const fragment of fragments) { + const clipboard_div = document.createElement("div") + clipboard_div.classList.add("clipboard") + clipboard_div.innerHTML = clipboard_icon + clipboard_div.title = clipboard_title + $(clipboard_div).click(function() { + const content = this.parentNode.cloneNode(true) + // filter out line number and folded fragments from file listings + content.querySelectorAll(".lineno, .ttc, .foldclosed").forEach((node) => { node.remove() }) + let text = content.textContent + // remove trailing newlines and trailing spaces from empty lines + text = text.replace(/^\s*\n/gm,'\n').replace(/\n*$/,'') + navigator.clipboard.writeText(text); + this.classList.add("success") + this.innerHTML = clipboard_successIcon + window.setTimeout(() => { // switch back to normal icon after timeout + this.classList.remove("success") + this.innerHTML = clipboard_icon + }, clipboard_successDuration); + }) + fragment.insertBefore(clipboard_div, fragment.firstChild) + } + } +}) diff --git a/doc/html/compat_8c_source.html b/doc/html/compat_8c_source.html new file mode 100644 index 0000000..15778f4 --- /dev/null +++ b/doc/html/compat_8c_source.html @@ -0,0 +1,147 @@ + + + + + + + +libfuse: lib/compat.c Source File + + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
+
compat.c
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 Helper functions to create (simple) standalone programs. With the
+
6 aid of these functions it should be possible to create full FUSE
+
7 file system by implementing nothing but the request handlers.
+
8
+
9 This program can be distributed under the terms of the GNU LGPLv2.
+
10 See the file LGPL2.txt.
+
11*/
+
12
+
13/* Description:
+
14 This file has compatibility symbols for platforms that do not
+
15 support version symboling
+
16*/
+
17
+
18#include "libfuse_config.h"
+
19
+
20#include <stddef.h>
+
21#include <stdint.h>
+
22
+
23struct fuse_args;
+ + +
26struct fuse_session;
+
27struct fuse_custom_io;
+
28struct fuse_operations;
+ +
30
+
34#if (!defined(LIBFUSE_BUILT_WITH_VERSIONED_SYMBOLS))
+
35/* With current libfuse fuse_parse_cmdline is a macro pointing to the
+
36 * versioned function. Here in this file we need to provide the ABI symbol
+
37 * and the redirecting macro is conflicting.
+
38 */
+
39#ifdef fuse_parse_cmdline
+
40#undef fuse_parse_cmdline
+
41#endif
+
42int fuse_parse_cmdline_30(struct fuse_args *args,
+
43 struct fuse_cmdline_opts *opts);
+
44int fuse_parse_cmdline(struct fuse_args *args,
+
45 struct fuse_cmdline_opts *opts);
+
46int fuse_parse_cmdline(struct fuse_args *args,
+
47 struct fuse_cmdline_opts *opts)
+
48{
+
49 return fuse_parse_cmdline_30(args, opts);
+
50}
+
51
+
52int fuse_session_custom_io_30(struct fuse_session *se,
+
53 const struct fuse_custom_io *io, int fd);
+
54int fuse_session_custom_io(struct fuse_session *se,
+
55 const struct fuse_custom_io *io, int fd);
+
56int fuse_session_custom_io(struct fuse_session *se,
+
57 const struct fuse_custom_io *io, int fd)
+
58
+
59{
+
60 return fuse_session_custom_io_30(se, io, fd);
+
61}
+
62
+
63#endif /* LIBFUSE_BUILT_WITH_VERSIONED_SYMBOLS */
+
64
+
65int fuse_main_real_30(int argc, char *argv[], const struct fuse_operations *op,
+
66 size_t op_size, void *user_data);
+
67int fuse_main_real(int argc, char *argv[], const struct fuse_operations *op,
+
68 size_t op_size, void *user_data);
+
69int fuse_main_real(int argc, char *argv[], const struct fuse_operations *op,
+
70 size_t op_size, void *user_data)
+
71{
+
72 return fuse_main_real_30(argc, argv, op, op_size, user_data);
+
73}
+
74
+
75struct fuse_session *fuse_session_new_30(struct fuse_args *args,
+
76 const struct fuse_lowlevel_ops *op,
+
77 size_t op_size, void *userdata);
+
78struct fuse_session *fuse_session_new(struct fuse_args *args,
+
79 const struct fuse_lowlevel_ops *op,
+
80 size_t op_size, void *userdata);
+
81struct fuse_session *fuse_session_new(struct fuse_args *args,
+
82 const struct fuse_lowlevel_ops *op,
+
83 size_t op_size, void *userdata)
+
84{
+
85 return fuse_session_new_30(args, op, op_size, userdata);
+
86}
+
int fuse_parse_cmdline_30(struct fuse_args *args, struct fuse_cmdline_opts *opts)
Definition helper.c:237
+ + + + +
+ + +
+ + diff --git a/doc/html/cuse_8c.html b/doc/html/cuse_8c.html new file mode 100644 index 0000000..c9aea77 --- /dev/null +++ b/doc/html/cuse_8c.html @@ -0,0 +1,417 @@ + + + + + + + +libfuse: example/cuse.c File Reference + + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
+
cuse.c File Reference
+
+
+
#include <cuse_lowlevel.h>
+#include <fuse_opt.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#include "ioctl.h"
+
+

Go to the source code of this file.

+

Detailed Description

+

This example demonstrates how to implement a character device in userspace ("CUSE"). This is only allowed for root. The character device should appear in /dev under the specified name. It can be tested with the cuse_client.c program.

+

Mount the file system with:

cuse -f --name=mydevice
+

You should now have a new /dev/mydevice character device. To "unmount" it, kill the "cuse" process.

+

To compile this example, run

gcc -Wall cuse.c `pkg-config fuse3 --cflags --libs` -o cuse
+

+Source code

+
/*
+
CUSE example: Character device in Userspace
+
Copyright (C) 2008-2009 SUSE Linux Products GmbH
+
Copyright (C) 2008-2009 Tejun Heo <tj@kernel.org>
+
+
This program can be distributed under the terms of the GNU GPLv2.
+
See the file GPL2.txt.
+
+
*/
+
+
+
+
#define FUSE_USE_VERSION 31
+
+
#include <cuse_lowlevel.h>
+
#include <fuse_opt.h>
+
#include <stddef.h>
+
#include <stdio.h>
+
#include <stdlib.h>
+
#include <string.h>
+
#include <unistd.h>
+
#include <errno.h>
+
+
#include "ioctl.h"
+
+
static void *cusexmp_buf;
+
static size_t cusexmp_size;
+
+
static const char *usage =
+
"usage: cusexmp [options]\n"
+
"\n"
+
"options:\n"
+
" --help|-h print this help message\n"
+
" --maj=MAJ|-M MAJ device major number\n"
+
" --min=MIN|-m MIN device minor number\n"
+
" --name=NAME|-n NAME device name (mandatory)\n"
+
" -d -o debug enable debug output (implies -f)\n"
+
" -f foreground operation\n"
+
" -s disable multi-threaded operation\n"
+
"\n";
+
+
static int cusexmp_resize(size_t new_size)
+
{
+
void *new_buf;
+
+
if (new_size == cusexmp_size)
+
return 0;
+
+
new_buf = realloc(cusexmp_buf, new_size);
+
if (!new_buf && new_size)
+
return -ENOMEM;
+
+
if (new_size > cusexmp_size)
+
memset(new_buf + cusexmp_size, 0, new_size - cusexmp_size);
+
+
cusexmp_buf = new_buf;
+
cusexmp_size = new_size;
+
+
return 0;
+
}
+
+
static int cusexmp_expand(size_t new_size)
+
{
+
if (new_size > cusexmp_size)
+
return cusexmp_resize(new_size);
+
return 0;
+
}
+
+
static void cusexmp_init(void *userdata, struct fuse_conn_info *conn)
+
{
+
(void)userdata;
+
+
/* Disable the receiving and processing of FUSE_INTERRUPT requests */
+
conn->no_interrupt = 1;
+
}
+
+
static void cusexmp_open(fuse_req_t req, struct fuse_file_info *fi)
+
{
+
fuse_reply_open(req, fi);
+
}
+
+
static void cusexmp_read(fuse_req_t req, size_t size, off_t off,
+
struct fuse_file_info *fi)
+
{
+
(void)fi;
+
+
if (off >= cusexmp_size)
+
off = cusexmp_size;
+
if (size > cusexmp_size - off)
+
size = cusexmp_size - off;
+
+
fuse_reply_buf(req, cusexmp_buf + off, size);
+
}
+
+
static void cusexmp_write(fuse_req_t req, const char *buf, size_t size,
+
off_t off, struct fuse_file_info *fi)
+
{
+
(void)fi;
+
+
if (cusexmp_expand(off + size)) {
+
fuse_reply_err(req, ENOMEM);
+
return;
+
}
+
+
memcpy(cusexmp_buf + off, buf, size);
+
fuse_reply_write(req, size);
+
}
+
+
static void fioc_do_rw(fuse_req_t req, void *addr, const void *in_buf,
+
size_t in_bufsz, size_t out_bufsz, int is_read)
+
{
+
const struct fioc_rw_arg *arg;
+
struct iovec in_iov[2], out_iov[3], iov[3];
+
size_t cur_size;
+
+
/* read in arg */
+
in_iov[0].iov_base = addr;
+
in_iov[0].iov_len = sizeof(*arg);
+
if (!in_bufsz) {
+
fuse_reply_ioctl_retry(req, in_iov, 1, NULL, 0);
+
return;
+
}
+
arg = in_buf;
+
in_buf += sizeof(*arg);
+
in_bufsz -= sizeof(*arg);
+
+
/* prepare size outputs */
+
out_iov[0].iov_base =
+
addr + offsetof(struct fioc_rw_arg, prev_size);
+
out_iov[0].iov_len = sizeof(arg->prev_size);
+
+
out_iov[1].iov_base =
+
addr + offsetof(struct fioc_rw_arg, new_size);
+
out_iov[1].iov_len = sizeof(arg->new_size);
+
+
/* prepare client buf */
+
if (is_read) {
+
out_iov[2].iov_base = arg->buf;
+
out_iov[2].iov_len = arg->size;
+
if (!out_bufsz) {
+
fuse_reply_ioctl_retry(req, in_iov, 1, out_iov, 3);
+
return;
+
}
+
} else {
+
in_iov[1].iov_base = arg->buf;
+
in_iov[1].iov_len = arg->size;
+
if (arg->size && !in_bufsz) {
+
fuse_reply_ioctl_retry(req, in_iov, 2, out_iov, 2);
+
return;
+
}
+
}
+
+
/* we're all set */
+
cur_size = cusexmp_size;
+
iov[0].iov_base = &cur_size;
+
iov[0].iov_len = sizeof(cur_size);
+
+
iov[1].iov_base = &cusexmp_size;
+
iov[1].iov_len = sizeof(cusexmp_size);
+
+
if (is_read) {
+
size_t off = arg->offset;
+
size_t size = arg->size;
+
+
if (off >= cusexmp_size)
+
off = cusexmp_size;
+
if (size > cusexmp_size - off)
+
size = cusexmp_size - off;
+
+
iov[2].iov_base = cusexmp_buf + off;
+
iov[2].iov_len = size;
+
fuse_reply_ioctl_iov(req, size, iov, 3);
+
} else {
+
if (cusexmp_expand(arg->offset + in_bufsz)) {
+
fuse_reply_err(req, ENOMEM);
+
return;
+
}
+
+
memcpy(cusexmp_buf + arg->offset, in_buf, in_bufsz);
+
fuse_reply_ioctl_iov(req, in_bufsz, iov, 2);
+
}
+
}
+
+
static void cusexmp_ioctl(fuse_req_t req, int cmd, void *arg,
+
struct fuse_file_info *fi, unsigned flags,
+
const void *in_buf, size_t in_bufsz, size_t out_bufsz)
+
{
+
int is_read = 0;
+
+
(void)fi;
+
+
if (flags & FUSE_IOCTL_COMPAT) {
+
fuse_reply_err(req, ENOSYS);
+
return;
+
}
+
+
switch (cmd) {
+
case FIOC_GET_SIZE:
+
if (!out_bufsz) {
+
struct iovec iov = { arg, sizeof(size_t) };
+
+
fuse_reply_ioctl_retry(req, NULL, 0, &iov, 1);
+
} else
+
fuse_reply_ioctl(req, 0, &cusexmp_size,
+
sizeof(cusexmp_size));
+
break;
+
+
case FIOC_SET_SIZE:
+
if (!in_bufsz) {
+
struct iovec iov = { arg, sizeof(size_t) };
+
+
fuse_reply_ioctl_retry(req, &iov, 1, NULL, 0);
+
} else {
+
cusexmp_resize(*(size_t *)in_buf);
+
fuse_reply_ioctl(req, 0, NULL, 0);
+
}
+
break;
+
+
case FIOC_READ:
+
is_read = 1;
+
/* fall through */
+
case FIOC_WRITE:
+
fioc_do_rw(req, arg, in_buf, in_bufsz, out_bufsz, is_read);
+
break;
+
+
default:
+
fuse_reply_err(req, EINVAL);
+
}
+
}
+
+
struct cusexmp_param {
+
unsigned major;
+
unsigned minor;
+
char *dev_name;
+
int is_help;
+
};
+
+
#define CUSEXMP_OPT(t, p) { t, offsetof(struct cusexmp_param, p), 1 }
+
+
static const struct fuse_opt cusexmp_opts[] = {
+
CUSEXMP_OPT("-M %u", major),
+
CUSEXMP_OPT("--maj=%u", major),
+
CUSEXMP_OPT("-m %u", minor),
+
CUSEXMP_OPT("--min=%u", minor),
+
CUSEXMP_OPT("-n %s", dev_name),
+
CUSEXMP_OPT("--name=%s", dev_name),
+
FUSE_OPT_KEY("-h", 0),
+
FUSE_OPT_KEY("--help", 0),
+ +
};
+
+
static int cusexmp_process_arg(void *data, const char *arg, int key,
+
struct fuse_args *outargs)
+
{
+
struct cusexmp_param *param = data;
+
+
(void)outargs;
+
(void)arg;
+
+
switch (key) {
+
case 0:
+
param->is_help = 1;
+
fprintf(stderr, "%s", usage);
+
return fuse_opt_add_arg(outargs, "-ho");
+
default:
+
return 1;
+
}
+
}
+
+
static const struct cuse_lowlevel_ops cusexmp_clop = {
+
.init = cusexmp_init,
+
.open = cusexmp_open,
+
.read = cusexmp_read,
+
.write = cusexmp_write,
+
.ioctl = cusexmp_ioctl,
+
};
+
+
int main(int argc, char **argv)
+
{
+
struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
struct cusexmp_param param = { 0, 0, NULL, 0 };
+
char dev_name[128] = "DEVNAME=";
+
const char *dev_info_argv[] = { dev_name };
+
struct cuse_info ci;
+
int ret = 1;
+
+
if (fuse_opt_parse(&args, &param, cusexmp_opts, cusexmp_process_arg)) {
+
printf("failed to parse option\n");
+
free(param.dev_name);
+
goto out;
+
}
+
+
if (!param.is_help) {
+
if (!param.dev_name) {
+
fprintf(stderr, "Error: device name missing\n");
+
goto out;
+
}
+
strncat(dev_name, param.dev_name, sizeof(dev_name) - sizeof("DEVNAME="));
+
free(param.dev_name);
+
}
+
+
memset(&ci, 0, sizeof(ci));
+
ci.dev_major = param.major;
+
ci.dev_minor = param.minor;
+
ci.dev_info_argc = 1;
+
ci.dev_info_argv = dev_info_argv;
+
ci.flags = CUSE_UNRESTRICTED_IOCTL;
+
+
ret = cuse_lowlevel_main(args.argc, args.argv, &ci, &cusexmp_clop, NULL);
+
+
out:
+ +
return ret;
+
}
+
#define FUSE_IOCTL_COMPAT
+
int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi)
+
int fuse_reply_err(fuse_req_t req, int err)
+
int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
+
struct fuse_req * fuse_req_t
+
int fuse_reply_ioctl_iov(fuse_req_t req, int result, const struct iovec *iov, int count)
+
int fuse_reply_ioctl_retry(fuse_req_t req, const struct iovec *in_iov, size_t in_count, const struct iovec *out_iov, size_t out_count)
+
int fuse_reply_write(fuse_req_t req, size_t count)
+
int fuse_reply_ioctl(fuse_req_t req, int result, const void *buf, size_t size)
+ +
int fuse_opt_add_arg(struct fuse_args *args, const char *arg)
Definition fuse_opt.c:55
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
#define FUSE_OPT_KEY(templ, key)
Definition fuse_opt.h:98
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+ + + +
char ** argv
Definition fuse_opt.h:114
+ +
uint32_t no_interrupt
+ + +
+

Definition in file cuse.c.

+
+ + +
+ + diff --git a/doc/html/cuse_8c_source.html b/doc/html/cuse_8c_source.html new file mode 100644 index 0000000..15b4c34 --- /dev/null +++ b/doc/html/cuse_8c_source.html @@ -0,0 +1,397 @@ + + + + + + + +libfuse: example/cuse.c Source File + + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
+
cuse.c
+
+
+Go to the documentation of this file.
1/*
+
2 CUSE example: Character device in Userspace
+
3 Copyright (C) 2008-2009 SUSE Linux Products GmbH
+
4 Copyright (C) 2008-2009 Tejun Heo <tj@kernel.org>
+
5
+
6 This program can be distributed under the terms of the GNU GPLv2.
+
7 See the file GPL2.txt.
+
8
+
9*/
+
10
+
32
+
33
+
34#define FUSE_USE_VERSION 31
+
35
+
36#include <cuse_lowlevel.h>
+
37#include <fuse_opt.h>
+
38#include <stddef.h>
+
39#include <stdio.h>
+
40#include <stdlib.h>
+
41#include <string.h>
+
42#include <unistd.h>
+
43#include <errno.h>
+
44
+
45#include "ioctl.h"
+
46
+
47static void *cusexmp_buf;
+
48static size_t cusexmp_size;
+
49
+
50static const char *usage =
+
51"usage: cusexmp [options]\n"
+
52"\n"
+
53"options:\n"
+
54" --help|-h print this help message\n"
+
55" --maj=MAJ|-M MAJ device major number\n"
+
56" --min=MIN|-m MIN device minor number\n"
+
57" --name=NAME|-n NAME device name (mandatory)\n"
+
58" -d -o debug enable debug output (implies -f)\n"
+
59" -f foreground operation\n"
+
60" -s disable multi-threaded operation\n"
+
61"\n";
+
62
+
63static int cusexmp_resize(size_t new_size)
+
64{
+
65 void *new_buf;
+
66
+
67 if (new_size == cusexmp_size)
+
68 return 0;
+
69
+
70 new_buf = realloc(cusexmp_buf, new_size);
+
71 if (!new_buf && new_size)
+
72 return -ENOMEM;
+
73
+
74 if (new_size > cusexmp_size)
+
75 memset(new_buf + cusexmp_size, 0, new_size - cusexmp_size);
+
76
+
77 cusexmp_buf = new_buf;
+
78 cusexmp_size = new_size;
+
79
+
80 return 0;
+
81}
+
82
+
83static int cusexmp_expand(size_t new_size)
+
84{
+
85 if (new_size > cusexmp_size)
+
86 return cusexmp_resize(new_size);
+
87 return 0;
+
88}
+
89
+
90static void cusexmp_init(void *userdata, struct fuse_conn_info *conn)
+
91{
+
92 (void)userdata;
+
93
+
94 /* Disable the receiving and processing of FUSE_INTERRUPT requests */
+
95 conn->no_interrupt = 1;
+
96}
+
97
+
98static void cusexmp_open(fuse_req_t req, struct fuse_file_info *fi)
+
99{
+
100 fuse_reply_open(req, fi);
+
101}
+
102
+
103static void cusexmp_read(fuse_req_t req, size_t size, off_t off,
+
104 struct fuse_file_info *fi)
+
105{
+
106 (void)fi;
+
107
+
108 if (off >= cusexmp_size)
+
109 off = cusexmp_size;
+
110 if (size > cusexmp_size - off)
+
111 size = cusexmp_size - off;
+
112
+
113 fuse_reply_buf(req, cusexmp_buf + off, size);
+
114}
+
115
+
116static void cusexmp_write(fuse_req_t req, const char *buf, size_t size,
+
117 off_t off, struct fuse_file_info *fi)
+
118{
+
119 (void)fi;
+
120
+
121 if (cusexmp_expand(off + size)) {
+
122 fuse_reply_err(req, ENOMEM);
+
123 return;
+
124 }
+
125
+
126 memcpy(cusexmp_buf + off, buf, size);
+
127 fuse_reply_write(req, size);
+
128}
+
129
+
130static void fioc_do_rw(fuse_req_t req, void *addr, const void *in_buf,
+
131 size_t in_bufsz, size_t out_bufsz, int is_read)
+
132{
+
133 const struct fioc_rw_arg *arg;
+
134 struct iovec in_iov[2], out_iov[3], iov[3];
+
135 size_t cur_size;
+
136
+
137 /* read in arg */
+
138 in_iov[0].iov_base = addr;
+
139 in_iov[0].iov_len = sizeof(*arg);
+
140 if (!in_bufsz) {
+
141 fuse_reply_ioctl_retry(req, in_iov, 1, NULL, 0);
+
142 return;
+
143 }
+
144 arg = in_buf;
+
145 in_buf += sizeof(*arg);
+
146 in_bufsz -= sizeof(*arg);
+
147
+
148 /* prepare size outputs */
+
149 out_iov[0].iov_base =
+
150 addr + offsetof(struct fioc_rw_arg, prev_size);
+
151 out_iov[0].iov_len = sizeof(arg->prev_size);
+
152
+
153 out_iov[1].iov_base =
+
154 addr + offsetof(struct fioc_rw_arg, new_size);
+
155 out_iov[1].iov_len = sizeof(arg->new_size);
+
156
+
157 /* prepare client buf */
+
158 if (is_read) {
+
159 out_iov[2].iov_base = arg->buf;
+
160 out_iov[2].iov_len = arg->size;
+
161 if (!out_bufsz) {
+
162 fuse_reply_ioctl_retry(req, in_iov, 1, out_iov, 3);
+
163 return;
+
164 }
+
165 } else {
+
166 in_iov[1].iov_base = arg->buf;
+
167 in_iov[1].iov_len = arg->size;
+
168 if (arg->size && !in_bufsz) {
+
169 fuse_reply_ioctl_retry(req, in_iov, 2, out_iov, 2);
+
170 return;
+
171 }
+
172 }
+
173
+
174 /* we're all set */
+
175 cur_size = cusexmp_size;
+
176 iov[0].iov_base = &cur_size;
+
177 iov[0].iov_len = sizeof(cur_size);
+
178
+
179 iov[1].iov_base = &cusexmp_size;
+
180 iov[1].iov_len = sizeof(cusexmp_size);
+
181
+
182 if (is_read) {
+
183 size_t off = arg->offset;
+
184 size_t size = arg->size;
+
185
+
186 if (off >= cusexmp_size)
+
187 off = cusexmp_size;
+
188 if (size > cusexmp_size - off)
+
189 size = cusexmp_size - off;
+
190
+
191 iov[2].iov_base = cusexmp_buf + off;
+
192 iov[2].iov_len = size;
+
193 fuse_reply_ioctl_iov(req, size, iov, 3);
+
194 } else {
+
195 if (cusexmp_expand(arg->offset + in_bufsz)) {
+
196 fuse_reply_err(req, ENOMEM);
+
197 return;
+
198 }
+
199
+
200 memcpy(cusexmp_buf + arg->offset, in_buf, in_bufsz);
+
201 fuse_reply_ioctl_iov(req, in_bufsz, iov, 2);
+
202 }
+
203}
+
204
+
205static void cusexmp_ioctl(fuse_req_t req, int cmd, void *arg,
+
206 struct fuse_file_info *fi, unsigned flags,
+
207 const void *in_buf, size_t in_bufsz, size_t out_bufsz)
+
208{
+
209 int is_read = 0;
+
210
+
211 (void)fi;
+
212
+
213 if (flags & FUSE_IOCTL_COMPAT) {
+
214 fuse_reply_err(req, ENOSYS);
+
215 return;
+
216 }
+
217
+
218 switch (cmd) {
+
219 case FIOC_GET_SIZE:
+
220 if (!out_bufsz) {
+
221 struct iovec iov = { arg, sizeof(size_t) };
+
222
+
223 fuse_reply_ioctl_retry(req, NULL, 0, &iov, 1);
+
224 } else
+
225 fuse_reply_ioctl(req, 0, &cusexmp_size,
+
226 sizeof(cusexmp_size));
+
227 break;
+
228
+
229 case FIOC_SET_SIZE:
+
230 if (!in_bufsz) {
+
231 struct iovec iov = { arg, sizeof(size_t) };
+
232
+
233 fuse_reply_ioctl_retry(req, &iov, 1, NULL, 0);
+
234 } else {
+
235 cusexmp_resize(*(size_t *)in_buf);
+
236 fuse_reply_ioctl(req, 0, NULL, 0);
+
237 }
+
238 break;
+
239
+
240 case FIOC_READ:
+
241 is_read = 1;
+
242 /* fall through */
+
243 case FIOC_WRITE:
+
244 fioc_do_rw(req, arg, in_buf, in_bufsz, out_bufsz, is_read);
+
245 break;
+
246
+
247 default:
+
248 fuse_reply_err(req, EINVAL);
+
249 }
+
250}
+
251
+
252struct cusexmp_param {
+
253 unsigned major;
+
254 unsigned minor;
+
255 char *dev_name;
+
256 int is_help;
+
257};
+
258
+
259#define CUSEXMP_OPT(t, p) { t, offsetof(struct cusexmp_param, p), 1 }
+
260
+
261static const struct fuse_opt cusexmp_opts[] = {
+
262 CUSEXMP_OPT("-M %u", major),
+
263 CUSEXMP_OPT("--maj=%u", major),
+
264 CUSEXMP_OPT("-m %u", minor),
+
265 CUSEXMP_OPT("--min=%u", minor),
+
266 CUSEXMP_OPT("-n %s", dev_name),
+
267 CUSEXMP_OPT("--name=%s", dev_name),
+
268 FUSE_OPT_KEY("-h", 0),
+
269 FUSE_OPT_KEY("--help", 0),
+ +
271};
+
272
+
273static int cusexmp_process_arg(void *data, const char *arg, int key,
+
274 struct fuse_args *outargs)
+
275{
+
276 struct cusexmp_param *param = data;
+
277
+
278 (void)outargs;
+
279 (void)arg;
+
280
+
281 switch (key) {
+
282 case 0:
+
283 param->is_help = 1;
+
284 fprintf(stderr, "%s", usage);
+
285 return fuse_opt_add_arg(outargs, "-ho");
+
286 default:
+
287 return 1;
+
288 }
+
289}
+
290
+
291static const struct cuse_lowlevel_ops cusexmp_clop = {
+
292 .init = cusexmp_init,
+
293 .open = cusexmp_open,
+
294 .read = cusexmp_read,
+
295 .write = cusexmp_write,
+
296 .ioctl = cusexmp_ioctl,
+
297};
+
298
+
299int main(int argc, char **argv)
+
300{
+
301 struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
302 struct cusexmp_param param = { 0, 0, NULL, 0 };
+
303 char dev_name[128] = "DEVNAME=";
+
304 const char *dev_info_argv[] = { dev_name };
+
305 struct cuse_info ci;
+
306 int ret = 1;
+
307
+
308 if (fuse_opt_parse(&args, &param, cusexmp_opts, cusexmp_process_arg)) {
+
309 printf("failed to parse option\n");
+
310 free(param.dev_name);
+
311 goto out;
+
312 }
+
313
+
314 if (!param.is_help) {
+
315 if (!param.dev_name) {
+
316 fprintf(stderr, "Error: device name missing\n");
+
317 goto out;
+
318 }
+
319 strncat(dev_name, param.dev_name, sizeof(dev_name) - sizeof("DEVNAME="));
+
320 free(param.dev_name);
+
321 }
+
322
+
323 memset(&ci, 0, sizeof(ci));
+
324 ci.dev_major = param.major;
+
325 ci.dev_minor = param.minor;
+
326 ci.dev_info_argc = 1;
+
327 ci.dev_info_argv = dev_info_argv;
+
328 ci.flags = CUSE_UNRESTRICTED_IOCTL;
+
329
+
330 ret = cuse_lowlevel_main(args.argc, args.argv, &ci, &cusexmp_clop, NULL);
+
331
+
332out:
+
333 fuse_opt_free_args(&args);
+
334 return ret;
+
335}
+
#define FUSE_IOCTL_COMPAT
+
int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi)
+
int fuse_reply_err(fuse_req_t req, int err)
+
int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
+
struct fuse_req * fuse_req_t
+
int fuse_reply_ioctl_iov(fuse_req_t req, int result, const struct iovec *iov, int count)
+
int fuse_reply_ioctl_retry(fuse_req_t req, const struct iovec *in_iov, size_t in_count, const struct iovec *out_iov, size_t out_count)
+
int fuse_reply_write(fuse_req_t req, size_t count)
+
int fuse_reply_ioctl(fuse_req_t req, int result, const void *buf, size_t size)
+ +
int fuse_opt_add_arg(struct fuse_args *args, const char *arg)
Definition fuse_opt.c:55
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
#define FUSE_OPT_KEY(templ, key)
Definition fuse_opt.h:98
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+ + + +
char ** argv
Definition fuse_opt.h:114
+ +
uint32_t no_interrupt
+ + +
+ + +
+ + diff --git a/doc/html/cuse__client_8c.html b/doc/html/cuse__client_8c.html new file mode 100644 index 0000000..c3f82a7 --- /dev/null +++ b/doc/html/cuse__client_8c.html @@ -0,0 +1,230 @@ + + + + + + + +libfuse: example/cuse_client.c File Reference + + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
+
cuse_client.c File Reference
+
+
+
#include <sys/types.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/ioctl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <errno.h>
+#include <unistd.h>
+#include "ioctl.h"
+
+

Go to the source code of this file.

+

Detailed Description

+

This program tests the cuse.c example file system.

+

Example usage (assuming that /dev/foobar is a CUSE device provided by the cuse.c example file system):

$ cuse_client /dev/foobar s
+0
+
+$ echo "hello" | cuse_client /dev/foobar w 6
+Writing 6 bytes
+transferred 6 bytes (0 -> 6)
+
+$ cuse_client /dev/foobar s
+6
+
+$ cuse_client /dev/foobar r 10
+hello
+transferred 6 bytes (6 -> 6)
+

Compiling this example

gcc -Wall cuse_client.c -o cuse_client
+

+Source Code

+
/*
+
FUSE fioclient: FUSE ioctl example client
+
Copyright (C) 2008 SUSE Linux Products GmbH
+
Copyright (C) 2008 Tejun Heo <teheo@suse.de>
+
+
This program can be distributed under the terms of the GNU GPLv2.
+
See the file GPL2.txt.
+
*/
+
+
+
+
#include <sys/types.h>
+
#include <fcntl.h>
+
#include <sys/stat.h>
+
#include <sys/ioctl.h>
+
#include <stdio.h>
+
#include <stdlib.h>
+
#include <ctype.h>
+
#include <errno.h>
+
#include <unistd.h>
+
#include "ioctl.h"
+
+
/* keeps a mistyped SIZE from asking for the machine's memory */
+
#define MAX_XFER_SIZE (16 * 1024 * 1024)
+
+
const char *usage =
+
"Usage: cuse_client FIOC_FILE COMMAND\n"
+
"\n"
+
"COMMANDS\n"
+
" s [SIZE] : get size if SIZE is omitted, set size otherwise\n"
+
" r SIZE [OFF] : read SIZE bytes @ OFF (dfl 0) and output to stdout\n"
+
" w SIZE [OFF] : write SIZE bytes @ OFF (dfl 0) from stdin\n"
+
"\n";
+
+
static int do_rw(int fd, int is_read, size_t size, off_t offset,
+
size_t *prev_size, size_t *new_size)
+
{
+
struct fioc_rw_arg arg = { .offset = offset };
+
ssize_t ret;
+
+
if (size > MAX_XFER_SIZE) {
+
fprintf(stderr, "size %zu exceeds limit of %d bytes\n",
+
size, MAX_XFER_SIZE);
+
return -1;
+
}
+
+
arg.buf = calloc(1, size);
+
if (!arg.buf) {
+
fprintf(stderr, "failed to allocated %zu bytes\n", size);
+
return -1;
+
}
+
+
if (is_read) {
+
arg.size = size;
+
ret = ioctl(fd, FIOC_READ, &arg);
+
if (ret >= 0)
+
fwrite(arg.buf, 1, ret, stdout);
+
} else {
+
arg.size = fread(arg.buf, 1, size, stdin);
+
fprintf(stderr, "Writing %zu bytes\n", arg.size);
+
ret = ioctl(fd, FIOC_WRITE, &arg);
+
}
+
+
if (ret >= 0) {
+
*prev_size = arg.prev_size;
+
*new_size = arg.new_size;
+
} else
+
perror("ioctl");
+
+
free(arg.buf);
+
return ret;
+
}
+
+
int main(int argc, char **argv)
+
{
+
size_t param[2] = { };
+
size_t size, prev_size = 0, new_size = 0;
+
char cmd;
+
int fd, i, rc;
+
+
if (argc < 3)
+
goto usage;
+
+
fd = open(argv[1], O_RDWR);
+
if (fd < 0) {
+
perror("open");
+
return 1;
+
}
+
+
cmd = tolower(argv[2][0]);
+
argc -= 3;
+
argv += 3;
+
+
for (i = 0; i < argc; i++) {
+
char *endp;
+
param[i] = strtoul(argv[i], &endp, 0);
+
if (endp == argv[i] || *endp != '\0')
+
goto usage;
+
}
+
+
switch (cmd) {
+
case 's':
+
if (!argc) {
+
if (ioctl(fd, FIOC_GET_SIZE, &size)) {
+
perror("ioctl");
+
goto error;
+
}
+
printf("%zu\n", size);
+
} else {
+
size = param[0];
+
if (ioctl(fd, FIOC_SET_SIZE, &size)) {
+
perror("ioctl");
+
goto error;
+
}
+
}
+
close(fd);
+
return 0;
+
+
case 'r':
+
case 'w':
+
rc = do_rw(fd, cmd == 'r', param[0], param[1],
+
&prev_size, &new_size);
+
if (rc < 0)
+
goto error;
+
fprintf(stderr, "transferred %d bytes (%zu -> %zu)\n",
+
rc, prev_size, new_size);
+
close(fd);
+
return 0;
+
}
+
+
usage:
+
fprintf(stderr, "%s", usage);
+
return 1;
+
+
error:
+
close(fd);
+
return 1;
+
}
+ +
+

Definition in file cuse_client.c.

+
+ + +
+ + diff --git a/doc/html/cuse__client_8c_source.html b/doc/html/cuse__client_8c_source.html new file mode 100644 index 0000000..826ca9b --- /dev/null +++ b/doc/html/cuse__client_8c_source.html @@ -0,0 +1,198 @@ + + + + + + + +libfuse: example/cuse_client.c Source File + + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
+
cuse_client.c
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE fioclient: FUSE ioctl example client
+
3 Copyright (C) 2008 SUSE Linux Products GmbH
+
4 Copyright (C) 2008 Tejun Heo <teheo@suse.de>
+
5
+
6 This program can be distributed under the terms of the GNU GPLv2.
+
7 See the file GPL2.txt.
+
8*/
+
9
+
38
+
39
+
40#include <sys/types.h>
+
41#include <fcntl.h>
+
42#include <sys/stat.h>
+
43#include <sys/ioctl.h>
+
44#include <stdio.h>
+
45#include <stdlib.h>
+
46#include <ctype.h>
+
47#include <errno.h>
+
48#include <unistd.h>
+
49#include "ioctl.h"
+
50
+
51/* keeps a mistyped SIZE from asking for the machine's memory */
+
52#define MAX_XFER_SIZE (16 * 1024 * 1024)
+
53
+
54const char *usage =
+
55"Usage: cuse_client FIOC_FILE COMMAND\n"
+
56"\n"
+
57"COMMANDS\n"
+
58" s [SIZE] : get size if SIZE is omitted, set size otherwise\n"
+
59" r SIZE [OFF] : read SIZE bytes @ OFF (dfl 0) and output to stdout\n"
+
60" w SIZE [OFF] : write SIZE bytes @ OFF (dfl 0) from stdin\n"
+
61"\n";
+
62
+
63static int do_rw(int fd, int is_read, size_t size, off_t offset,
+
64 size_t *prev_size, size_t *new_size)
+
65{
+
66 struct fioc_rw_arg arg = { .offset = offset };
+
67 ssize_t ret;
+
68
+
69 if (size > MAX_XFER_SIZE) {
+
70 fprintf(stderr, "size %zu exceeds limit of %d bytes\n",
+
71 size, MAX_XFER_SIZE);
+
72 return -1;
+
73 }
+
74
+
75 arg.buf = calloc(1, size);
+
76 if (!arg.buf) {
+
77 fprintf(stderr, "failed to allocated %zu bytes\n", size);
+
78 return -1;
+
79 }
+
80
+
81 if (is_read) {
+
82 arg.size = size;
+
83 ret = ioctl(fd, FIOC_READ, &arg);
+
84 if (ret >= 0)
+
85 fwrite(arg.buf, 1, ret, stdout);
+
86 } else {
+
87 arg.size = fread(arg.buf, 1, size, stdin);
+
88 fprintf(stderr, "Writing %zu bytes\n", arg.size);
+
89 ret = ioctl(fd, FIOC_WRITE, &arg);
+
90 }
+
91
+
92 if (ret >= 0) {
+
93 *prev_size = arg.prev_size;
+
94 *new_size = arg.new_size;
+
95 } else
+
96 perror("ioctl");
+
97
+
98 free(arg.buf);
+
99 return ret;
+
100}
+
101
+
102int main(int argc, char **argv)
+
103{
+
104 size_t param[2] = { };
+
105 size_t size, prev_size = 0, new_size = 0;
+
106 char cmd;
+
107 int fd, i, rc;
+
108
+
109 if (argc < 3)
+
110 goto usage;
+
111
+
112 fd = open(argv[1], O_RDWR);
+
113 if (fd < 0) {
+
114 perror("open");
+
115 return 1;
+
116 }
+
117
+
118 cmd = tolower(argv[2][0]);
+
119 argc -= 3;
+
120 argv += 3;
+
121
+
122 for (i = 0; i < argc; i++) {
+
123 char *endp;
+
124 param[i] = strtoul(argv[i], &endp, 0);
+
125 if (endp == argv[i] || *endp != '\0')
+
126 goto usage;
+
127 }
+
128
+
129 switch (cmd) {
+
130 case 's':
+
131 if (!argc) {
+
132 if (ioctl(fd, FIOC_GET_SIZE, &size)) {
+
133 perror("ioctl");
+
134 goto error;
+
135 }
+
136 printf("%zu\n", size);
+
137 } else {
+
138 size = param[0];
+
139 if (ioctl(fd, FIOC_SET_SIZE, &size)) {
+
140 perror("ioctl");
+
141 goto error;
+
142 }
+
143 }
+
144 close(fd);
+
145 return 0;
+
146
+
147 case 'r':
+
148 case 'w':
+
149 rc = do_rw(fd, cmd == 'r', param[0], param[1],
+
150 &prev_size, &new_size);
+
151 if (rc < 0)
+
152 goto error;
+
153 fprintf(stderr, "transferred %d bytes (%zu -> %zu)\n",
+
154 rc, prev_size, new_size);
+
155 close(fd);
+
156 return 0;
+
157 }
+
158
+
159usage:
+
160 fprintf(stderr, "%s", usage);
+
161 return 1;
+
162
+
163error:
+
164 close(fd);
+
165 return 1;
+
166}
+ +
+ + +
+ + diff --git a/doc/html/cuse__lowlevel_8c_source.html b/doc/html/cuse__lowlevel_8c_source.html new file mode 100644 index 0000000..40404f0 --- /dev/null +++ b/doc/html/cuse__lowlevel_8c_source.html @@ -0,0 +1,458 @@ + + + + + + + +libfuse: lib/cuse_lowlevel.c Source File + + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
+
cuse_lowlevel.c
+
+
+
1/*
+
2 CUSE: Character device in Userspace
+
3 Copyright (C) 2008 SUSE Linux Products GmbH
+
4 Copyright (C) 2008 Tejun Heo <teheo@suse.de>
+
5
+
6 This program can be distributed under the terms of the GNU LGPLv2.
+
7 See the file LGPL2.txt.
+
8*/
+
9
+
10#include "fuse_config.h"
+
11#include "cuse_lowlevel.h"
+
12#include "fuse_kernel.h"
+
13#include "fuse_i.h"
+
14#include "fuse_opt.h"
+
15
+
16#include <stdio.h>
+
17#include <string.h>
+
18#include <stdlib.h>
+
19#include <stddef.h>
+
20#include <errno.h>
+
21#include <unistd.h>
+
22
+
23struct cuse_data {
+
24 struct cuse_lowlevel_ops clop;
+
25 unsigned max_read;
+
26 unsigned dev_major;
+
27 unsigned dev_minor;
+
28 unsigned flags;
+
29 unsigned dev_info_len;
+
30 char dev_info[];
+
31};
+
32
+
33static struct cuse_lowlevel_ops *req_clop(fuse_req_t req)
+
34{
+
35 return &req->se->cuse_data->clop;
+
36}
+
37
+
38static void cuse_fll_open(fuse_req_t req, fuse_ino_t ino,
+
39 struct fuse_file_info *fi)
+
40{
+
41 (void)ino;
+
42 req_clop(req)->open(req, fi);
+
43}
+
44
+
45static void cuse_fll_read(fuse_req_t req, fuse_ino_t ino, size_t size,
+
46 off_t off, struct fuse_file_info *fi)
+
47{
+
48 (void)ino;
+
49 req_clop(req)->read(req, size, off, fi);
+
50}
+
51
+
52static void cuse_fll_write(fuse_req_t req, fuse_ino_t ino, const char *buf,
+
53 size_t size, off_t off, struct fuse_file_info *fi)
+
54{
+
55 (void)ino;
+
56 req_clop(req)->write(req, buf, size, off, fi);
+
57}
+
58
+
59static void cuse_fll_flush(fuse_req_t req, fuse_ino_t ino,
+
60 struct fuse_file_info *fi)
+
61{
+
62 (void)ino;
+
63 req_clop(req)->flush(req, fi);
+
64}
+
65
+
66static void cuse_fll_release(fuse_req_t req, fuse_ino_t ino,
+
67 struct fuse_file_info *fi)
+
68{
+
69 (void)ino;
+
70 req_clop(req)->release(req, fi);
+
71}
+
72
+
73static void cuse_fll_fsync(fuse_req_t req, fuse_ino_t ino, int datasync,
+
74 struct fuse_file_info *fi)
+
75{
+
76 (void)ino;
+
77 req_clop(req)->fsync(req, datasync, fi);
+
78}
+
79
+
80static void cuse_fll_ioctl(fuse_req_t req, fuse_ino_t ino, unsigned int cmd, void *arg,
+
81 struct fuse_file_info *fi, unsigned int flags,
+
82 const void *in_buf, size_t in_bufsz, size_t out_bufsz)
+
83{
+
84 (void)ino;
+
85 req_clop(req)->ioctl(req, cmd, arg, fi, flags, in_buf, in_bufsz,
+
86 out_bufsz);
+
87}
+
88
+
89static void cuse_fll_poll(fuse_req_t req, fuse_ino_t ino,
+
90 struct fuse_file_info *fi, struct fuse_pollhandle *ph)
+
91{
+
92 (void)ino;
+
93 req_clop(req)->poll(req, fi, ph);
+
94}
+
95
+
96static size_t cuse_pack_info(int argc, const char **argv, char *buf)
+
97{
+
98 size_t size = 0;
+
99 int i;
+
100
+
101 for (i = 0; i < argc; i++) {
+
102 size_t len;
+
103
+
104 len = strlen(argv[i]) + 1;
+
105 size += len;
+
106 if (buf) {
+
107 memcpy(buf, argv[i], len);
+
108 buf += len;
+
109 }
+
110 }
+
111
+
112 return size;
+
113}
+
114
+
115static struct cuse_data *cuse_prep_data(const struct cuse_info *ci,
+
116 const struct cuse_lowlevel_ops *clop)
+
117{
+
118 struct cuse_data *cd;
+
119 size_t dev_info_len;
+
120
+
121 dev_info_len = cuse_pack_info(ci->dev_info_argc, ci->dev_info_argv,
+
122 NULL);
+
123
+
124 if (dev_info_len > CUSE_INIT_INFO_MAX) {
+
125 fuse_log(FUSE_LOG_ERR, "cuse: dev_info (%zu) too large, limit=%u\n",
+
126 dev_info_len, CUSE_INIT_INFO_MAX);
+
127 return NULL;
+
128 }
+
129
+
130 cd = calloc(1, sizeof(*cd) + dev_info_len);
+
131 if (!cd) {
+
132 fuse_log(FUSE_LOG_ERR, "cuse: failed to allocate cuse_data\n");
+
133 return NULL;
+
134 }
+
135
+
136 memcpy(&cd->clop, clop, sizeof(cd->clop));
+
137 cd->max_read = 131072;
+
138 cd->dev_major = ci->dev_major;
+
139 cd->dev_minor = ci->dev_minor;
+
140 cd->dev_info_len = dev_info_len;
+
141 cd->flags = ci->flags;
+
142 cuse_pack_info(ci->dev_info_argc, ci->dev_info_argv, cd->dev_info);
+
143
+
144 return cd;
+
145}
+
146
+
147struct fuse_session *cuse_lowlevel_new(struct fuse_args *args,
+
148 const struct cuse_info *ci,
+
149 const struct cuse_lowlevel_ops *clop,
+
150 void *userdata)
+
151{
+
152 struct fuse_lowlevel_ops lop;
+
153 struct cuse_data *cd;
+
154 struct fuse_session *se;
+
155
+
156 cd = cuse_prep_data(ci, clop);
+
157 if (!cd)
+
158 return NULL;
+
159
+
160 memset(&lop, 0, sizeof(lop));
+
161 lop.init = clop->init;
+
162 lop.destroy = clop->destroy;
+
163 lop.open = clop->open ? cuse_fll_open : NULL;
+
164 lop.read = clop->read ? cuse_fll_read : NULL;
+
165 lop.write = clop->write ? cuse_fll_write : NULL;
+
166 lop.flush = clop->flush ? cuse_fll_flush : NULL;
+
167 lop.release = clop->release ? cuse_fll_release : NULL;
+
168 lop.fsync = clop->fsync ? cuse_fll_fsync : NULL;
+
169 lop.ioctl = clop->ioctl ? cuse_fll_ioctl : NULL;
+
170 lop.poll = clop->poll ? cuse_fll_poll : NULL;
+
171
+
172 se = fuse_session_new(args, &lop, sizeof(lop), userdata);
+
173 if (!se) {
+
174 free(cd);
+
175 return NULL;
+
176 }
+
177 se->cuse_data = cd;
+
178
+
179 return se;
+
180}
+
181
+
182static int cuse_reply_init(fuse_req_t req, struct cuse_init_out *arg,
+
183 char *dev_info, unsigned dev_info_len)
+
184{
+
185 struct iovec iov[3];
+
186
+
187 iov[1].iov_base = arg;
+
188 iov[1].iov_len = sizeof(struct cuse_init_out);
+
189 iov[2].iov_base = dev_info;
+
190 iov[2].iov_len = dev_info_len;
+
191
+
192 return fuse_send_reply_iov_nofree(req, 0, iov, 3);
+
193}
+
194
+
195void _cuse_lowlevel_init(fuse_req_t req, const fuse_ino_t nodeid,
+
196 const void *op_in, const void *req_payload)
+
197{
+
198 const struct fuse_init_in *arg = op_in;
+
199 (void)req_payload;
+
200 struct cuse_init_out outarg;
+
201 struct fuse_session *se = req->se;
+
202 struct cuse_data *cd = se->cuse_data;
+
203 size_t bufsize = se->bufsize;
+
204 struct cuse_lowlevel_ops *clop = req_clop(req);
+
205
+
206 (void) nodeid;
+
207 if (se->debug) {
+
208 fuse_log(FUSE_LOG_DEBUG, "CUSE_INIT: %u.%u\n", arg->major, arg->minor);
+
209 fuse_log(FUSE_LOG_DEBUG, "flags=0x%08x\n", arg->flags);
+
210 }
+
211 se->conn.proto_major = arg->major;
+
212 se->conn.proto_minor = arg->minor;
+
213
+
214 /* XXX This is not right.*/
+
215 se->conn.capable_ext = 0;
+
216 se->conn.want_ext = 0;
+
217
+
218 if (arg->major < 7) {
+
219 fuse_log(FUSE_LOG_ERR, "cuse: unsupported protocol version: %u.%u\n",
+
220 arg->major, arg->minor);
+
221 fuse_reply_err(req, EPROTO);
+
222 return;
+
223 }
+
224
+
225 if (bufsize < FUSE_MIN_READ_BUFFER) {
+
226 fuse_log(FUSE_LOG_ERR, "cuse: warning: buffer size too small: %zu\n",
+
227 bufsize);
+
228 bufsize = FUSE_MIN_READ_BUFFER;
+
229 }
+
230
+
231 bufsize -= 4096;
+
232 if (bufsize < se->conn.max_write)
+
233 se->conn.max_write = bufsize;
+
234
+
235 se->got_init = 1;
+
236 if (se->op.init)
+
237 se->op.init(se->userdata, &se->conn);
+
238
+
239 memset(&outarg, 0, sizeof(outarg));
+
240 outarg.major = FUSE_KERNEL_VERSION;
+
241 outarg.minor = FUSE_KERNEL_MINOR_VERSION;
+
242 outarg.flags = cd->flags;
+
243 outarg.max_read = cd->max_read;
+
244 outarg.max_write = se->conn.max_write;
+
245 outarg.dev_major = cd->dev_major;
+
246 outarg.dev_minor = cd->dev_minor;
+
247
+
248 if (se->debug) {
+
249 fuse_log(FUSE_LOG_DEBUG, " CUSE_INIT: %u.%u\n",
+
250 outarg.major, outarg.minor);
+
251 fuse_log(FUSE_LOG_DEBUG, " flags=0x%08x\n", outarg.flags);
+
252 fuse_log(FUSE_LOG_DEBUG, " max_read=0x%08x\n", outarg.max_read);
+
253 fuse_log(FUSE_LOG_DEBUG, " max_write=0x%08x\n", outarg.max_write);
+
254 fuse_log(FUSE_LOG_DEBUG, " dev_major=%u\n", outarg.dev_major);
+
255 fuse_log(FUSE_LOG_DEBUG, " dev_minor=%u\n", outarg.dev_minor);
+
256 fuse_log(FUSE_LOG_DEBUG, " dev_info: %.*s\n", cd->dev_info_len,
+
257 cd->dev_info);
+
258 }
+
259
+
260 cuse_reply_init(req, &outarg, cd->dev_info, cd->dev_info_len);
+
261
+
262 if (clop->init_done)
+
263 clop->init_done(se->userdata);
+
264
+
265 fuse_free_req(req);
+
266}
+
267
+
268void cuse_lowlevel_init(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
269{
+
270 _cuse_lowlevel_init(req, nodeid, inarg, NULL);
+
271}
+
272
+
273struct fuse_session *cuse_lowlevel_setup(int argc, char *argv[],
+
274 const struct cuse_info *ci,
+
275 const struct cuse_lowlevel_ops *clop,
+
276 int *multithreaded, void *userdata)
+
277{
+
278 const char *devname = "/dev/cuse";
+
279 static const struct fuse_opt kill_subtype_opts[] = {
+ + +
282 };
+
283 struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
284 struct fuse_session *se;
+
285 struct fuse_cmdline_opts opts;
+
286 int fd;
+
287 int res;
+
288
+
289 if (fuse_parse_cmdline(&args, &opts) == -1)
+
290 return NULL;
+
291 *multithreaded = !opts.singlethread;
+
292
+
293 /* Remove subtype= option */
+
294 res = fuse_opt_parse(&args, NULL, kill_subtype_opts, NULL);
+
295 if (res == -1)
+
296 goto out1;
+
297
+
298 /*
+
299 * Make sure file descriptors 0, 1 and 2 are open, otherwise chaos
+
300 * would ensue.
+
301 */
+
302 do {
+
303 fd = open("/dev/null", O_RDWR);
+
304 if (fd > 2)
+
305 close(fd);
+
306 } while (fd >= 0 && fd <= 2);
+
307
+
308 se = cuse_lowlevel_new(&args, ci, clop, userdata);
+
309 if (se == NULL)
+
310 goto out1;
+
311
+
312 fd = open(devname, O_RDWR);
+
313 if (fd == -1) {
+
314 if (errno == ENODEV || errno == ENOENT)
+
315 fuse_log(FUSE_LOG_ERR, "cuse: device not found, try 'modprobe cuse' first\n");
+
316 else
+
317 fuse_log(FUSE_LOG_ERR, "cuse: failed to open %s: %s\n",
+
318 devname, strerror(errno));
+
319 goto err_se;
+
320 }
+
321 se->fd = fd;
+
322
+
323 res = fuse_set_signal_handlers(se);
+
324 if (res == -1)
+
325 goto err_se;
+
326
+
327 res = fuse_daemonize(opts.foreground);
+
328 if (res == -1)
+
329 goto err_sig;
+
330
+
331 fuse_opt_free_args(&args);
+
332 return se;
+
333
+
334err_sig:
+ +
336err_se:
+ +
338out1:
+
339 free(opts.mountpoint);
+
340 fuse_opt_free_args(&args);
+
341 return NULL;
+
342}
+
343
+
344void cuse_lowlevel_teardown(struct fuse_session *se)
+
345{
+ + +
348}
+
349
+
350int cuse_lowlevel_main(int argc, char *argv[], const struct cuse_info *ci,
+
351 const struct cuse_lowlevel_ops *clop, void *userdata)
+
352{
+
353 struct fuse_session *se;
+
354 int multithreaded;
+
355 int res;
+
356
+
357 se = cuse_lowlevel_setup(argc, argv, ci, clop, &multithreaded,
+
358 userdata);
+
359 if (se == NULL)
+
360 return 1;
+
361
+
362 if (multithreaded) {
+
363 struct fuse_loop_config *config = fuse_loop_cfg_create();
+
364 res = fuse_session_loop_mt(se, config);
+
365 fuse_loop_cfg_destroy(config);
+
366 }
+
367 else
+
368 res = fuse_session_loop(se);
+
369
+
370 cuse_lowlevel_teardown(se);
+
371 if (res == -1)
+
372 return 1;
+
373
+
374 return 0;
+
375}
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
int fuse_daemonize(int foreground)
Definition helper.c:253
+
void fuse_log(enum fuse_log_level level, const char *fmt,...) __attribute__((format(printf
+
void fuse_session_destroy(struct fuse_session *se)
+
int fuse_reply_err(fuse_req_t req, int err)
+
struct fuse_req * fuse_req_t
+
int fuse_session_loop(struct fuse_session *se)
Definition fuse_loop.c:19
+
uint64_t fuse_ino_t
+ +
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
#define FUSE_OPT_KEY(templ, key)
Definition fuse_opt.h:98
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_OPT_KEY_DISCARD
Definition fuse_opt.h:153
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+ + +
char ** argv
Definition fuse_opt.h:114
+ + + + + +
+ + +
+ + diff --git a/doc/html/cuse__lowlevel_8h_source.html b/doc/html/cuse__lowlevel_8h_source.html new file mode 100644 index 0000000..8460c75 --- /dev/null +++ b/doc/html/cuse__lowlevel_8h_source.html @@ -0,0 +1,149 @@ + + + + + + + +libfuse: include/cuse_lowlevel.h Source File + + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
+
cuse_lowlevel.h
+
+
+
1/*
+
2 CUSE: Character device in Userspace
+
3 Copyright (C) 2008-2009 SUSE Linux Products GmbH
+
4 Copyright (C) 2008-2009 Tejun Heo <tj@kernel.org>
+
5
+
6 This program can be distributed under the terms of the GNU LGPLv2.
+
7 See the file LGPL2.txt.
+
8
+
9 Read example/cusexmp.c for usages.
+
10*/
+
11
+
12#ifndef CUSE_LOWLEVEL_H_
+
13#define CUSE_LOWLEVEL_H_
+
14
+
15#ifndef FUSE_USE_VERSION
+
16#define FUSE_USE_VERSION 29
+
17#endif
+
18
+
19#include "fuse_lowlevel.h"
+
20
+
21#include <fcntl.h>
+
22#include <sys/types.h>
+
23#include <sys/uio.h>
+
24
+
25#ifdef __cplusplus
+
26extern "C" {
+
27#endif
+
28
+
29#define CUSE_UNRESTRICTED_IOCTL (1 << 0) /* use unrestricted ioctl */
+
30
+
31struct fuse_session;
+
32
+
33struct cuse_info {
+
34 unsigned dev_major;
+
35 unsigned dev_minor;
+
36 unsigned dev_info_argc;
+
37 const char **dev_info_argv;
+
38 unsigned flags;
+
39};
+
40
+
41/*
+
42 * Most ops behave almost identically to the matching fuse_lowlevel
+
43 * ops except that they don't take @ino.
+
44 *
+
45 * init_done : called after initialization is complete
+
46 * read/write : always direct IO, simultaneous operations allowed
+
47 * ioctl : might be in unrestricted mode depending on ci->flags
+
48 */
+
49struct cuse_lowlevel_ops {
+
50 void (*init) (void *userdata, struct fuse_conn_info *conn);
+
51 void (*init_done) (void *userdata);
+
52 void (*destroy) (void *userdata);
+
53 void (*open) (fuse_req_t req, struct fuse_file_info *fi);
+
54 void (*read) (fuse_req_t req, size_t size, off_t off,
+
55 struct fuse_file_info *fi);
+
56 void (*write) (fuse_req_t req, const char *buf, size_t size, off_t off,
+
57 struct fuse_file_info *fi);
+
58 void (*flush) (fuse_req_t req, struct fuse_file_info *fi);
+
59 void (*release) (fuse_req_t req, struct fuse_file_info *fi);
+
60 void (*fsync) (fuse_req_t req, int datasync, struct fuse_file_info *fi);
+
61 void (*ioctl) (fuse_req_t req, int cmd, void *arg,
+
62 struct fuse_file_info *fi, unsigned int flags,
+
63 const void *in_buf, size_t in_bufsz, size_t out_bufsz);
+
64 void (*poll) (fuse_req_t req, struct fuse_file_info *fi,
+
65 struct fuse_pollhandle *ph);
+
66};
+
67
+
68struct fuse_session *cuse_lowlevel_new(struct fuse_args *args,
+
69 const struct cuse_info *ci,
+
70 const struct cuse_lowlevel_ops *clop,
+
71 void *userdata);
+
72
+
73struct fuse_session *cuse_lowlevel_setup(int argc, char *argv[],
+
74 const struct cuse_info *ci,
+
75 const struct cuse_lowlevel_ops *clop,
+
76 int *multithreaded, void *userdata);
+
77
+
78void cuse_lowlevel_teardown(struct fuse_session *se);
+
79
+
80int cuse_lowlevel_main(int argc, char *argv[], const struct cuse_info *ci,
+
81 const struct cuse_lowlevel_ops *clop, void *userdata);
+
82
+
83#ifdef __cplusplus
+
84}
+
85#endif
+
86
+
87#endif /* CUSE_LOWLEVEL_H_ */
+ +
struct fuse_req * fuse_req_t
+ +
+ + +
+ + diff --git a/doc/html/dir_13e138d54eb8818da29c3992edef070a.html b/doc/html/dir_13e138d54eb8818da29c3992edef070a.html new file mode 100644 index 0000000..23b4419 --- /dev/null +++ b/doc/html/dir_13e138d54eb8818da29c3992edef070a.html @@ -0,0 +1,74 @@ + + + + + + + +libfuse: test Directory Reference + + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
+
test Directory Reference
+
+
+ + + + + + + + + + + + + +

+Files

 
hello.c
 
readdir_inode.c
 
stracedecode.c
 
test_abi.c
 
test_api_30.c
 
test_setattr.c
 
test_signals.c
 
test_syscalls.c
 
test_want_conversion.c
 
test_write_cache.c
+
+ + +
+ + diff --git a/doc/html/dir_23ec12649285f9fabf3a6b7380226c28.html b/doc/html/dir_23ec12649285f9fabf3a6b7380226c28.html new file mode 100644 index 0000000..219c79f --- /dev/null +++ b/doc/html/dir_23ec12649285f9fabf3a6b7380226c28.html @@ -0,0 +1,65 @@ + + + + + + + +libfuse: util Directory Reference + + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
+
util Directory Reference
+
+
+ + + + +

+Files

 
fusermount.c
 
mount.fuse.c
+
+ + +
+ + diff --git a/doc/html/dir_97aefd0d527b934f1d99a682da8fe6a9.html b/doc/html/dir_97aefd0d527b934f1d99a682da8fe6a9.html new file mode 100644 index 0000000..3f589f0 --- /dev/null +++ b/doc/html/dir_97aefd0d527b934f1d99a682da8fe6a9.html @@ -0,0 +1,89 @@ + + + + + + + +libfuse: lib Directory Reference + + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
+
lib Directory Reference
+
+
+ + + +

+Directories

 
modules
+ + + + + + + + + + + + + + + + + + + + + + + +

+Files

 
buffer.c
 
compat.c
 
cuse_lowlevel.c
 
fuse.c
 
fuse_i.h
 
fuse_log.c
 
fuse_loop.c
 
fuse_loop_mt.c
 
fuse_lowlevel.c
 
fuse_misc.h
 
fuse_opt.c
 
fuse_signals.c
 
fuse_uring.c
 
fuse_uring_i.h
 
helper.c
 
mount.c
 
mount_bsd.c
 
mount_util.c
 
mount_util.h
 
usdt.h
 
util.c
 
util.h
+
+ + +
+ + diff --git a/doc/html/dir_cfafba98a580ce4b62f8a6fa96d7cbb0.html b/doc/html/dir_cfafba98a580ce4b62f8a6fa96d7cbb0.html new file mode 100644 index 0000000..5331004 --- /dev/null +++ b/doc/html/dir_cfafba98a580ce4b62f8a6fa96d7cbb0.html @@ -0,0 +1,83 @@ + + + + + + + +libfuse: example Directory Reference + + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
+
example Directory Reference
+
+ + + +
+ + diff --git a/doc/html/dir_d44c64559bbebec7f509842c48db8b23.html b/doc/html/dir_d44c64559bbebec7f509842c48db8b23.html new file mode 100644 index 0000000..74f393d --- /dev/null +++ b/doc/html/dir_d44c64559bbebec7f509842c48db8b23.html @@ -0,0 +1,71 @@ + + + + + + + +libfuse: include Directory Reference + + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
+
include Directory Reference
+
+
+ + + + + + + + + + +

+Files

 
cuse_lowlevel.h
 
fuse.h
 
fuse_common.h
 
fuse_kernel.h
 
fuse_log.h
 
fuse_lowlevel.h
 
fuse_mount_compat.h
 
fuse_opt.h
+
+ + +
+ + diff --git a/doc/html/dir_e1dbc8ba94a86723d4c32227b7c46099.html b/doc/html/dir_e1dbc8ba94a86723d4c32227b7c46099.html new file mode 100644 index 0000000..e9ceb1b --- /dev/null +++ b/doc/html/dir_e1dbc8ba94a86723d4c32227b7c46099.html @@ -0,0 +1,65 @@ + + + + + + + +libfuse: lib/modules Directory Reference + + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
+
modules Directory Reference
+
+
+ + + + +

+Files

 
iconv.c
 
subdir.c
+
+ + +
+ + diff --git a/doc/html/dir_e68e8157741866f444e17edd764ebbae.html b/doc/html/dir_e68e8157741866f444e17edd764ebbae.html new file mode 100644 index 0000000..f024418 --- /dev/null +++ b/doc/html/dir_e68e8157741866f444e17edd764ebbae.html @@ -0,0 +1,59 @@ + + + + + + + +libfuse: doc Directory Reference + + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
+
doc Directory Reference
+
+
+
+ + +
+ + diff --git a/doc/html/doxygen.css b/doc/html/doxygen.css new file mode 100644 index 0000000..8c4d583 --- /dev/null +++ b/doc/html/doxygen.css @@ -0,0 +1,2493 @@ +/* The standard CSS for doxygen 1.15.0*/ + +html { +/* page base colors */ +--page-background-color: white; +--page-foreground-color: black; +--page-link-color: #3D578C; +--page-visited-link-color: #3D578C; +--page-external-link-color: #334975; + +/* index */ +--index-odd-item-bg-color: #F8F9FC; +--index-even-item-bg-color: white; +--index-header-color: black; +--index-separator-color: #A0A0A0; + +/* header */ +--header-background-color: #F9FAFC; +--header-separator-color: #C4CFE5; +--group-header-separator-color: #D9E0EE; +--group-header-color: #354C7B; +--inherit-header-color: gray; + +--footer-foreground-color: #2A3D61; +--footer-logo-width: 75px; +--citation-label-color: #334975; +--glow-color: cyan; + +--title-background-color: white; +--title-separator-color: #C4CFE5; +--directory-separator-color: #9CAFD4; +--separator-color: #4A6AAA; + +--blockquote-background-color: #F7F8FB; +--blockquote-border-color: #9CAFD4; + +--scrollbar-thumb-color: #C4CFE5; +--scrollbar-background-color: #F9FAFC; + +--icon-background-color: #728DC1; +--icon-foreground-color: white; +/* +--icon-doc-image: url('doc.svg'); +--icon-folder-open-image: url('folderopen.svg'); +--icon-folder-closed-image: url('folderclosed.svg');*/ +--icon-folder-open-fill-color: #C4CFE5; +--icon-folder-fill-color: #D8DFEE; +--icon-folder-border-color: #4665A2; +--icon-doc-fill-color: #D8DFEE; +--icon-doc-border-color: #4665A2; + +/* brief member declaration list */ +--memdecl-background-color: #F9FAFC; +--memdecl-separator-color: #DEE4F0; +--memdecl-foreground-color: #555; +--memdecl-template-color: #4665A2; +--memdecl-border-color: #D5DDEC; + +/* detailed member list */ +--memdef-border-color: #A8B8D9; +--memdef-title-background-color: #E2E8F2; +--memdef-proto-background-color: #EEF1F7; +--memdef-proto-text-color: #253555; +--memdef-doc-background-color: white; +--memdef-param-name-color: #602020; +--memdef-template-color: #4665A2; + +/* tables */ +--table-cell-border-color: #2D4068; +--table-header-background-color: #374F7F; +--table-header-foreground-color: #FFFFFF; + +/* labels */ +--label-background-color: #728DC1; +--label-left-top-border-color: #5373B4; +--label-right-bottom-border-color: #C4CFE5; +--label-foreground-color: white; + +/** navigation bar/tree/menu */ +--nav-background-color: #F9FAFC; +--nav-foreground-color: #364D7C; +--nav-border-color: #C4CFE5; +--nav-breadcrumb-separator-color: #C4CFE5; +--nav-breadcrumb-active-bg: #EEF1F7; +--nav-breadcrumb-color: #354C7B; +--nav-breadcrumb-border-color: #E1E7F2; +--nav-splitbar-bg-color: #DCE2EF; +--nav-splitbar-handle-color: #9CAFD4; +--nav-font-size-level1: 13px; +--nav-font-size-level2: 10px; +--nav-font-size-level3: 9px; +--nav-text-normal-color: #283A5D; +--nav-text-hover-color: white; +--nav-text-active-color: white; +--nav-menu-button-color: #364D7C; +--nav-menu-background-color: white; +--nav-menu-foreground-color: #555555; +--nav-menu-active-bg: #DCE2EF; +--nav-menu-active-color: #9CAFD4; +--nav-menu-toggle-color: rgba(255, 255, 255, 0.5); +--nav-arrow-color: #B6C4DF; +--nav-arrow-selected-color: #90A5CE; + +/* sync icon */ +--sync-icon-border-color: #C4CFE5; +--sync-icon-background-color: #F9FAFC; +--sync-icon-selected-background-color: #EEF1F7; +--sync-icon-color: #C4CFE5; +--sync-icon-selected-color: #6884BD; + +/* table of contents */ +--toc-background-color: #F4F6FA; +--toc-border-color: #D8DFEE; +--toc-header-color: #4665A2; +--toc-down-arrow-image: url("data:image/svg+xml;utf8,&%238595;"); + +/** search field */ +--search-background-color: white; +--search-foreground-color: #909090; +--search-active-color: black; +--search-filter-background-color: rgba(255,255,255,.7); +--search-filter-backdrop-filter: blur(4px); +--search-filter-foreground-color: black; +--search-filter-border-color: rgba(150,150,150,.4); +--search-filter-highlight-text-color: white; +--search-filter-highlight-bg-color: #3D578C; +--search-results-foreground-color: #425E97; +--search-results-background-color: rgba(255,255,255,.8); +--search-results-backdrop-filter: blur(4px); +--search-results-border-color: rgba(150,150,150,.4); +--search-box-border-color: #B6C4DF; +--search-close-icon-bg-color: #A0A0A0; +--search-close-icon-fg-color: white; + +/** code fragments */ +--code-keyword-color: #008000; +--code-type-keyword-color: #604020; +--code-flow-keyword-color: #E08000; +--code-comment-color: #800000; +--code-preprocessor-color: #806020; +--code-string-literal-color: #002080; +--code-char-literal-color: #008080; +--code-xml-cdata-color: black; +--code-vhdl-digit-color: #FF00FF; +--code-vhdl-char-color: #000000; +--code-vhdl-keyword-color: #700070; +--code-vhdl-logic-color: #FF0000; +--code-link-color: #4665A2; +--code-external-link-color: #4665A2; +--fragment-foreground-color: black; +--fragment-background-color: #FBFCFD; +--fragment-border-color: #C4CFE5; +--fragment-lineno-border-color: #00FF00; +--fragment-lineno-background-color: #E8E8E8; +--fragment-lineno-foreground-color: black; +--fragment-lineno-link-fg-color: #4665A2; +--fragment-lineno-link-bg-color: #D8D8D8; +--fragment-lineno-link-hover-fg-color: #4665A2; +--fragment-lineno-link-hover-bg-color: #C8C8C8; +--fragment-copy-ok-color: #2EC82E; +--fragment-highlight-filter: -3; +--tooltip-foreground-color: black; +--tooltip-background-color: rgba(255,255,255,0.8); +--tooltip-arrow-background-color: white; +--tooltip-border-color: rgba(150,150,150,0.7); +--tooltip-backdrop-filter: blur(3px); +--tooltip-doc-color: grey; +--tooltip-declaration-color: #006318; +--tooltip-link-color: #4665A2; +--tooltip-shadow: 0 4px 8px 0 rgba(0,0,0,.25); +--fold-line-color: #808080; + +/** font-family */ +--font-family-normal: system-ui,-apple-system,Segoe UI,Roboto,Ubuntu,Cantarell,Noto Sans,sans-serif,BlinkMacSystemFont,"Segoe UI",Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol"; +--font-family-monospace: 'JetBrains Mono',Consolas,Monaco,'Andale Mono','Ubuntu Mono',monospace,fixed; +--font-family-nav: 'Lucida Grande',Geneva,Helvetica,Arial,sans-serif; +--font-family-title: system-ui,-apple-system,Segoe UI,Roboto,Ubuntu,Cantarell,Noto Sans,sans-serif,BlinkMacSystemFont,"Segoe UI",Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol"; +--font-family-toc: Verdana,'DejaVu Sans',Geneva,sans-serif; +--font-family-search: Arial,Verdana,sans-serif; +--font-family-icon: Arial,Helvetica; +--font-family-tooltip: Roboto,sans-serif; + +/** special sections */ +--warning-color-bg: #f8d1cc; +--warning-color-hl: #b61825; +--warning-color-text: #75070f; +--note-color-bg: #faf3d8; +--note-color-hl: #f3a600; +--note-color-text: #5f4204; +--todo-color-bg: #e4f3ff; +--todo-color-hl: #1879C4; +--todo-color-text: #274a5c; +--test-color-bg: #e8e8ff; +--test-color-hl: #3939C4; +--test-color-text: #1a1a5c; +--deprecated-color-bg: #ecf0f3; +--deprecated-color-hl: #5b6269; +--deprecated-color-text: #43454a; +--bug-color-bg: #e4dafd; +--bug-color-hl: #5b2bdd; +--bug-color-text: #2a0d72; +--invariant-color-bg: #d8f1e3; +--invariant-color-hl: #44b86f; +--invariant-color-text: #265532; +} + +@media (prefers-color-scheme: dark) { + html:not(.dark-mode) { + color-scheme: dark; + +/* page base colors */ +--page-background-color: black; +--page-foreground-color: #C9D1D9; +--page-link-color: #90A5CE; +--page-visited-link-color: #90A5CE; +--page-external-link-color: #A3B4D7; + +/* index */ +--index-odd-item-bg-color: #0B101A; +--index-even-item-bg-color: black; +--index-header-color: #C4CFE5; +--index-separator-color: #334975; + +/* header */ +--header-background-color: #070B11; +--header-separator-color: #141C2E; +--group-header-separator-color: #1D2A43; +--group-header-color: #90A5CE; +--inherit-header-color: #A0A0A0; + +--footer-foreground-color: #5B7AB7; +--footer-logo-width: 60px; +--citation-label-color: #90A5CE; +--glow-color: cyan; + +--title-background-color: #090D16; +--title-separator-color: #212F4B; +--directory-separator-color: #283A5D; +--separator-color: #283A5D; + +--blockquote-background-color: #101826; +--blockquote-border-color: #283A5D; + +--scrollbar-thumb-color: #2C3F65; +--scrollbar-background-color: #070B11; + +--icon-background-color: #334975; +--icon-foreground-color: #C4CFE5; +--icon-folder-open-fill-color: #4665A2; +--icon-folder-fill-color: #5373B4; +--icon-folder-border-color: #C4CFE5; +--icon-doc-fill-color: #6884BD; +--icon-doc-border-color: #C4CFE5; + +/* brief member declaration list */ +--memdecl-background-color: #0B101A; +--memdecl-separator-color: #2C3F65; +--memdecl-foreground-color: #BBB; +--memdecl-template-color: #7C95C6; +--memdecl-border-color: #233250; + +/* detailed member list */ +--memdef-border-color: #233250; +--memdef-title-background-color: #1B2840; +--memdef-proto-background-color: #19243A; +--memdef-proto-text-color: #9DB0D4; +--memdef-doc-background-color: black; +--memdef-param-name-color: #D28757; +--memdef-template-color: #7C95C6; + +/* tables */ +--table-cell-border-color: #283A5D; +--table-header-background-color: #283A5D; +--table-header-foreground-color: #C4CFE5; + +/* labels */ +--label-background-color: #354C7B; +--label-left-top-border-color: #4665A2; +--label-right-bottom-border-color: #283A5D; +--label-foreground-color: #CCCCCC; + +/** navigation bar/tree/menu */ +--nav-background-color: #101826; +--nav-foreground-color: #364D7C; +--nav-border-color: #212F4B; +--nav-breadcrumb-separator-color: #212F4B; +--nav-breadcrumb-active-bg: #1D2A43; +--nav-breadcrumb-color: #90A5CE; +--nav-breadcrumb-border-color: #2A3D61; +--nav-splitbar-bg-color: #283A5D; +--nav-splitbar-handle-color: #4665A2; +--nav-font-size-level1: 13px; +--nav-font-size-level2: 10px; +--nav-font-size-level3: 9px; +--nav-text-normal-color: #B6C4DF; +--nav-text-hover-color: #DCE2EF; +--nav-text-active-color: #DCE2EF; +--nav-menu-button-color: #B6C4DF; +--nav-menu-background-color: #05070C; +--nav-menu-foreground-color: #BBBBBB; +--nav-menu-active-bg: #1D2A43; +--nav-menu-active-color: #C9D3E7; +--nav-menu-toggle-color: rgba(255, 255, 255, 0.2); +--nav-arrow-color: #4665A2; +--nav-arrow-selected-color: #6884BD; + +/* sync icon */ +--sync-icon-border-color: #212F4B; +--sync-icon-background-color: #101826; +--sync-icon-selected-background-color: #1D2A43; +--sync-icon-color: #4665A2; +--sync-icon-selected-color: #5373B4; + +/* table of contents */ +--toc-background-color: #151E30; +--toc-border-color: #202E4A; +--toc-header-color: #A3B4D7; +--toc-down-arrow-image: url("data:image/svg+xml;utf8,&%238595;"); + +/** search field */ +--search-background-color: black; +--search-foreground-color: #C5C5C5; +--search-active-color: #F5F5F5; +--search-filter-background-color: #101826; +--search-filter-foreground-color: #90A5CE; +--search-filter-backdrop-filter: none; +--search-filter-border-color: #7C95C6; +--search-filter-highlight-text-color: #BCC9E2; +--search-filter-highlight-bg-color: #283A5D; +--search-results-background-color: black; +--search-results-foreground-color: #90A5CE; +--search-results-backdrop-filter: none; +--search-results-border-color: #334975; +--search-box-border-color: #334975; +--search-close-icon-bg-color: #909090; +--search-close-icon-fg-color: black; + +/** code fragments */ +--code-keyword-color: #CC99CD; +--code-type-keyword-color: #AB99CD; +--code-flow-keyword-color: #E08000; +--code-comment-color: #717790; +--code-preprocessor-color: #65CABE; +--code-string-literal-color: #7EC699; +--code-char-literal-color: #00E0F0; +--code-xml-cdata-color: #C9D1D9; +--code-vhdl-digit-color: #FF00FF; +--code-vhdl-char-color: #C0C0C0; +--code-vhdl-keyword-color: #CF53C9; +--code-vhdl-logic-color: #FF0000; +--code-link-color: #79C0FF; +--code-external-link-color: #79C0FF; +--fragment-foreground-color: #C9D1D9; +--fragment-background-color: #090D16; +--fragment-border-color: #30363D; +--fragment-lineno-border-color: #30363D; +--fragment-lineno-background-color: black; +--fragment-lineno-foreground-color: #6E7681; +--fragment-lineno-link-fg-color: #6E7681; +--fragment-lineno-link-bg-color: #303030; +--fragment-lineno-link-hover-fg-color: #8E96A1; +--fragment-lineno-link-hover-bg-color: #505050; +--fragment-copy-ok-color: #0EA80E; +--fragment-highlight-filter: 5; +--tooltip-foreground-color: #C9D1D9; +--tooltip-background-color: #202020; +--tooltip-arrow-background-color: #202020; +--tooltip-backdrop-filter: none; +--tooltip-border-color: #C9D1D9; +--tooltip-doc-color: #D9E1E9; +--tooltip-declaration-color: #20C348; +--tooltip-link-color: #79C0FF; +--tooltip-shadow: none; +--fold-line-color: #808080; + +/** font-family */ +--font-family-normal: system-ui,-apple-system,Segoe UI,Roboto,Ubuntu,Cantarell,Noto Sans,sans-serif,BlinkMacSystemFont,"Segoe UI",Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol"; +--font-family-monospace: 'JetBrains Mono',Consolas,Monaco,'Andale Mono','Ubuntu Mono',monospace,fixed; +--font-family-nav: 'Lucida Grande',Geneva,Helvetica,Arial,sans-serif; +--font-family-title: system-ui,-apple-system,Segoe UI,Roboto,Ubuntu,Cantarell,Noto Sans,sans-serif,BlinkMacSystemFont,"Segoe UI",Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol"; +--font-family-toc: Verdana,'DejaVu Sans',Geneva,sans-serif; +--font-family-search: Arial,Verdana,sans-serif; +--font-family-icon: Arial,Helvetica; +--font-family-tooltip: Roboto,sans-serif; + +/** special sections */ +--warning-color-bg: #2e1917; +--warning-color-hl: #ad2617; +--warning-color-text: #f5b1aa; +--note-color-bg: #3b2e04; +--note-color-hl: #f1b602; +--note-color-text: #ceb670; +--todo-color-bg: #163750; +--todo-color-hl: #1982D2; +--todo-color-text: #dcf0fa; +--test-color-bg: #121258; +--test-color-hl: #4242cf; +--test-color-text: #c0c0da; +--deprecated-color-bg: #2e323b; +--deprecated-color-hl: #738396; +--deprecated-color-text: #abb0bd; +--bug-color-bg: #2a2536; +--bug-color-hl: #7661b3; +--bug-color-text: #ae9ed6; +--invariant-color-bg: #303a35; +--invariant-color-hl: #76ce96; +--invariant-color-text: #cceed5; +}} +body { + background-color: var(--page-background-color); + color: var(--page-foreground-color); +} + +body, table, div, p, dl { + font-weight: 400; + font-size: 14px; + font-family: var(--font-family-normal); + line-height: 22px; +} + +body.resizing { + user-select: none; + -webkit-user-select: none; +} + +#doc-content { + scrollbar-width: thin; +} + +/* @group Heading Levels */ + +.title { + font-family: var(--font-family-normal); + line-height: 28px; + font-size: 160%; + font-weight: 400; + margin: 10px 2px; +} + +h1.groupheader { + font-size: 150%; +} + +h2.groupheader { + box-shadow: 12px 0 var(--page-background-color), + -12px 0 var(--page-background-color), + 12px 1px var(--group-header-separator-color), + -12px 1px var(--group-header-separator-color); + color: var(--group-header-color); + font-size: 150%; + font-weight: normal; + margin-top: 1.75em; + padding-top: 8px; + padding-bottom: 4px; + width: 100%; +} + +td h2.groupheader { + box-shadow: 13px 0 var(--page-background-color), + -13px 0 var(--page-background-color), + 13px 1px var(--group-header-separator-color), + -13px 1px var(--group-header-separator-color); +} + +h3.groupheader { + font-size: 100%; +} + +h1, h2, h3, h4, h5, h6 { + -webkit-transition: text-shadow 0.5s linear; + -moz-transition: text-shadow 0.5s linear; + -ms-transition: text-shadow 0.5s linear; + -o-transition: text-shadow 0.5s linear; + transition: text-shadow 0.5s linear; + margin-right: 15px; +} + +h1.glow, h2.glow, h3.glow, h4.glow, h5.glow, h6.glow { + text-shadow: 0 0 15px var(--glow-color); +} + +dt { + font-weight: bold; +} + +p.startli, p.startdd { + margin-top: 2px; +} + +th p.starttd, th p.intertd, th p.endtd { + font-size: 100%; + font-weight: 700; +} + +p.starttd { + margin-top: 0px; +} + +p.endli { + margin-bottom: 0px; +} + +p.enddd { + margin-bottom: 4px; +} + +p.endtd { + margin-bottom: 2px; +} + +p.interli { +} + +p.interdd { +} + +p.intertd { +} + +/* @end */ + +caption { + font-weight: bold; +} + +span.legend { + font-size: 70%; + text-align: center; +} + +h3.version { + font-size: 90%; + text-align: center; +} + +div.navtab { + margin-right: 6px; + padding-right: 6px; + text-align: right; + line-height: 110%; + background-color: var(--nav-background-color); +} + +div.navtab table { + border-spacing: 0; +} + +td.navtab { + padding-right: 6px; + padding-left: 6px; +} + +td.navtabHL { + padding-right: 6px; + padding-left: 6px; + border-radius: 0 6px 6px 0; + background-color: var(--nav-menu-active-bg); +} + +div.qindex{ + text-align: center; + width: 100%; + line-height: 140%; + font-size: 130%; + color: var(--index-separator-color); +} + +#main-menu a:focus { + outline: auto; + z-index: 10; + position: relative; +} + +dt.alphachar{ + font-size: 180%; + font-weight: bold; +} + +.alphachar a{ + color: var(--index-header-color); +} + +.alphachar a:hover, .alphachar a:visited{ + text-decoration: none; +} + +.classindex dl { + padding: 25px; + column-count:1 +} + +.classindex dd { + display:inline-block; + margin-left: 50px; + width: 90%; + line-height: 1.15em; +} + +.classindex dl.even { + background-color: var(--index-even-item-bg-color); +} + +.classindex dl.odd { + background-color: var(--index-odd-item-bg-color); +} + +@media(min-width: 1120px) { + .classindex dl { + column-count:2 + } +} + +@media(min-width: 1320px) { + .classindex dl { + column-count:3 + } +} + + +/* @group Link Styling */ + +a { + color: var(--page-link-color); + font-weight: normal; + text-decoration: none; +} + +.contents a:visited { + color: var(--page-visited-link-color); +} + +span.label a:hover { + text-decoration: none; + background: linear-gradient(to bottom, transparent 0,transparent calc(100% - 1px), currentColor 100%); +} + +a.el { + font-weight: bold; +} + +a.elRef { +} + +a.el, a.el:visited, a.code, a.code:visited, a.line, a.line:visited { + color: var(--page-link-color); +} + +a.codeRef, a.codeRef:visited, a.lineRef, a.lineRef:visited { + color: var(--page-external-link-color); +} + +a.code.hl_class { /* style for links to class names in code snippets */ } +a.code.hl_struct { /* style for links to struct names in code snippets */ } +a.code.hl_union { /* style for links to union names in code snippets */ } +a.code.hl_interface { /* style for links to interface names in code snippets */ } +a.code.hl_protocol { /* style for links to protocol names in code snippets */ } +a.code.hl_category { /* style for links to category names in code snippets */ } +a.code.hl_exception { /* style for links to exception names in code snippets */ } +a.code.hl_service { /* style for links to service names in code snippets */ } +a.code.hl_singleton { /* style for links to singleton names in code snippets */ } +a.code.hl_concept { /* style for links to concept names in code snippets */ } +a.code.hl_namespace { /* style for links to namespace names in code snippets */ } +a.code.hl_package { /* style for links to package names in code snippets */ } +a.code.hl_define { /* style for links to macro names in code snippets */ } +a.code.hl_function { /* style for links to function names in code snippets */ } +a.code.hl_variable { /* style for links to variable names in code snippets */ } +a.code.hl_typedef { /* style for links to typedef names in code snippets */ } +a.code.hl_enumvalue { /* style for links to enum value names in code snippets */ } +a.code.hl_enumeration { /* style for links to enumeration names in code snippets */ } +a.code.hl_signal { /* style for links to Qt signal names in code snippets */ } +a.code.hl_slot { /* style for links to Qt slot names in code snippets */ } +a.code.hl_friend { /* style for links to friend names in code snippets */ } +a.code.hl_dcop { /* style for links to KDE3 DCOP names in code snippets */ } +a.code.hl_property { /* style for links to property names in code snippets */ } +a.code.hl_event { /* style for links to event names in code snippets */ } +a.code.hl_sequence { /* style for links to sequence names in code snippets */ } +a.code.hl_dictionary { /* style for links to dictionary names in code snippets */ } + +/* @end */ + +dl.el { + margin-left: -1cm; +} + +ul.check { + list-style:none; + text-indent: -16px; + padding-left: 38px; +} +li.unchecked:before { + content: "\2610\A0"; +} +li.checked:before { + content: "\2611\A0"; +} + +ol { + text-indent: 0px; +} + +ul { + text-indent: 0px; + overflow: visible; +} + +ul.multicol { + -moz-column-gap: 1em; + -webkit-column-gap: 1em; + column-gap: 1em; + -moz-column-count: 3; + -webkit-column-count: 3; + column-count: 3; + list-style-type: none; +} + +#side-nav ul { + overflow: visible; /* reset ul rule for scroll bar in GENERATE_TREEVIEW window */ +} + +#main-nav ul { + overflow: visible; /* reset ul rule for the navigation bar drop down lists */ +} + +.fragment { + text-align: left; + direction: ltr; + overflow-x: auto; + overflow-y: hidden; + position: relative; + min-height: 12px; + margin: 10px 0px; + padding: 10px 10px; + border: 1px solid var(--fragment-border-color); + border-radius: 4px; + background-color: var(--fragment-background-color); + color: var(--fragment-foreground-color); +} + +pre.fragment { + word-wrap: break-word; + font-size: 10pt; + line-height: 125%; + font-family: var(--font-family-monospace); +} + +span.tt { + white-space: pre; + font-family: var(--font-family-monospace); + background-color: var(--fragment-background-color); +} + +.clipboard { + width: 24px; + height: 24px; + right: 5px; + top: 5px; + opacity: 0; + position: absolute; + display: inline; + overflow: hidden; + justify-content: center; + align-items: center; + cursor: pointer; +} + +.clipboard.success { + border: 1px solid var(--fragment-foreground-color); + border-radius: 4px; +} + +.fragment:hover .clipboard, .clipboard.success { + opacity: .4; +} + +.clipboard:hover, .clipboard.success { + opacity: 1 !important; +} + +.clipboard:active:not([class~=success]) svg { + transform: scale(.91); +} + +.clipboard.success svg { + fill: var(--fragment-copy-ok-color); +} + +.clipboard.success { + border-color: var(--fragment-copy-ok-color); +} + +div.line { + font-family: var(--font-family-monospace); + font-size: 13px; + min-height: 13px; + line-height: 1.2; + text-wrap: wrap; + word-break: break-all; + white-space: -moz-pre-wrap; /* Moz */ + white-space: -pre-wrap; /* Opera 4-6 */ + white-space: -o-pre-wrap; /* Opera 7 */ + white-space: pre-wrap; /* CSS3 */ + word-wrap: break-word; /* IE 5.5+ */ + text-indent: -62px; + padding-left: 62px; + padding-bottom: 0px; + margin: 0px; + -webkit-transition-property: background-color, box-shadow; + -webkit-transition-duration: 0.5s; + -moz-transition-property: background-color, box-shadow; + -moz-transition-duration: 0.5s; + -ms-transition-property: background-color, box-shadow; + -ms-transition-duration: 0.5s; + -o-transition-property: background-color, box-shadow; + -o-transition-duration: 0.5s; + transition-property: background-color, box-shadow; + transition-duration: 0.5s; +} + +div.line:after { + content:"\000A"; + white-space: pre; +} + +div.line.glow { + background-color: var(--glow-color); + box-shadow: 0 0 10px var(--glow-color); +} + +span.fold { + display: inline-block; + width: 12px; + height: 12px; + margin-left: 4px; + margin-right: 1px; +} + +span.foldnone { + display: inline-block; + position: relative; + cursor: pointer; + user-select: none; +} + +span.fold.plus, span.fold.minus { + width: 10px; + height: 10px; + background-color: var(--fragment-background-color); + position: relative; + border: 1px solid var(--fold-line-color); + margin-right: 1px; +} + +span.fold.plus::before, span.fold.minus::before { + content: ''; + position: absolute; + background-color: var(--fold-line-color); +} + +span.fold.plus::before { + width: 2px; + height: 6px; + top: 2px; + left: 4px; +} + +span.fold.plus::after { + content: ''; + position: absolute; + width: 6px; + height: 2px; + top: 4px; + left: 2px; + background-color: var(--fold-line-color); +} + +span.fold.minus::before { + width: 6px; + height: 2px; + top: 4px; + left: 2px; +} + +span.lineno { + padding-right: 4px; + margin-right: 9px; + text-align: right; + border-right: 2px solid var(--fragment-lineno-border-color); + color: var(--fragment-lineno-foreground-color); + background-color: var(--fragment-lineno-background-color); + white-space: pre; +} +span.lineno a, span.lineno a:visited { + color: var(--fragment-lineno-link-fg-color); + background-color: var(--fragment-lineno-link-bg-color); +} + +span.lineno a:hover { + color: var(--fragment-lineno-link-hover-fg-color); + background-color: var(--fragment-lineno-link-hover-bg-color); +} + +.lineno { + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} + +div.classindex ul { + list-style: none; + padding-left: 0; +} + +div.classindex span.ai { + display: inline-block; +} + +div.groupHeader { + box-shadow: 13px 0 var(--page-background-color), + -13px 0 var(--page-background-color), + 13px 1px var(--group-header-separator-color), + -13px 1px var(--group-header-separator-color); + color: var(--group-header-color); + font-size: 110%; + font-weight: 500; + margin-left: 0px; + margin-top: 0em; + margin-bottom: 6px; + padding-top: 8px; + padding-bottom: 4px; +} + +div.groupText { + margin-left: 16px; + font-style: italic; +} + +body { + color: var(--page-foreground-color); + margin: 0; +} + +div.contents { + margin-top: 10px; + margin-left: 12px; + margin-right: 12px; +} + +p.formulaDsp { + text-align: center; +} + +img.dark-mode-visible { + display: none; +} +img.light-mode-visible { + display: none; +} + +img.formulaInl, img.inline { + vertical-align: middle; +} + +div.center { + text-align: center; + margin-top: 0px; + margin-bottom: 0px; + padding: 0px; +} + +div.center img { + border: 0px; +} + +address.footer { + text-align: right; + padding-right: 12px; +} + +img.footer { + border: 0px; + vertical-align: middle; + width: var(--footer-logo-width); +} + +.compoundTemplParams { + color: var(--memdecl-template-color); + font-size: 80%; + line-height: 120%; +} + +/* @group Code Colorization */ + +span.keyword { + color: var(--code-keyword-color); +} + +span.keywordtype { + color: var(--code-type-keyword-color); +} + +span.keywordflow { + color: var(--code-flow-keyword-color); +} + +span.comment { + color: var(--code-comment-color); +} + +span.preprocessor { + color: var(--code-preprocessor-color); +} + +span.stringliteral { + color: var(--code-string-literal-color); +} + +span.charliteral { + color: var(--code-char-literal-color); +} + +span.xmlcdata { + color: var(--code-xml-cdata-color); +} + +span.vhdldigit { + color: var(--code-vhdl-digit-color); +} + +span.vhdlchar { + color: var(--code-vhdl-char-color); +} + +span.vhdlkeyword { + color: var(--code-vhdl-keyword-color); +} + +span.vhdllogic { + color: var(--code-vhdl-logic-color); +} + +blockquote { + background-color: var(--blockquote-background-color); + border-left: 2px solid var(--blockquote-border-color); + margin: 0 24px 0 4px; + padding: 0 12px 0 16px; +} + +/* @end */ + +td.tiny { + font-size: 75%; +} + +.dirtab { + padding: 4px; + border-collapse: collapse; + border: 1px solid var(--table-cell-border-color); +} + +th.dirtab { + background-color: var(--table-header-background-color); + color: var(--table-header-foreground-color); + font-weight: bold; +} + +hr { + border: none; + margin-top: 16px; + margin-bottom: 16px; + height: 1px; + box-shadow: 13px 0 var(--page-background-color), + -13px 0 var(--page-background-color), + 13px 1px var(--group-header-separator-color), + -13px 1px var(--group-header-separator-color); +} + +hr.footer { + height: 1px; +} + +/* @group Member Descriptions */ + +table.memberdecls { + border-spacing: 0px; + padding: 0px; +} + +.memberdecls td, .fieldtable tr { + transition-property: background-color, box-shadow; + transition-duration: 0.5s; +} + +.memberdecls td.glow, .fieldtable tr.glow { + background-color: var(--glow-color); + box-shadow: 0 0 15px var(--glow-color); +} + +.memberdecls tr[class^='memitem'] { + font-family: var(--font-family-monospace); +} + +.mdescLeft, .mdescRight, +.memItemLeft, .memItemRight { + padding-top: 2px; + padding-bottom: 2px; +} + +.memTemplParams { + padding-left: 10px; + padding-top: 5px; +} + +.memItemLeft, .memItemRight, .memTemplParams { + background-color: var(--memdecl-background-color); +} + +.mdescLeft, .mdescRight { + padding: 0px 8px 4px 8px; + color: var(--memdecl-foreground-color); +} + +tr[class^='memdesc'] { + box-shadow: inset 0px 1px 3px 0px rgba(0,0,0,.075); +} + +.mdescLeft { + border-left: 1px solid var(--memdecl-border-color); + border-bottom: 1px solid var(--memdecl-border-color); +} + +.mdescRight { + border-right: 1px solid var(--memdecl-border-color); + border-bottom: 1px solid var(--memdecl-border-color); +} + +.memTemplParams { + color: var(--memdecl-template-color); + white-space: nowrap; + font-size: 80%; + border-left: 1px solid var(--memdecl-border-color); + border-right: 1px solid var(--memdecl-border-color); +} + +td.ititle { + border: 1px solid var(--memdecl-border-color); + border-top-left-radius: 4px; + border-top-right-radius: 4px; + padding-left: 10px; +} + +tr:not(:first-child) > td.ititle { + border-top: 0; + border-radius: 0; +} + +.memItemLeft { + white-space: nowrap; + border-left: 1px solid var(--memdecl-border-color); + border-bottom: 1px solid var(--memdecl-border-color); + padding-left: 10px; + transition: none; +} + +.memItemRight { + width: 100%; + border-right: 1px solid var(--memdecl-border-color); + border-bottom: 1px solid var(--memdecl-border-color); + padding-right: 10px; + transition: none; +} + +tr.heading + tr[class^='memitem'] td.memItemLeft, +tr.groupHeader + tr[class^='memitem'] td.memItemLeft, +tr.inherit_header + tr[class^='memitem'] td.memItemLeft { + border-top: 1px solid var(--memdecl-border-color); + border-top-left-radius: 4px; +} + +tr.heading + tr[class^='memitem'] td.memItemRight, +tr.groupHeader + tr[class^='memitem'] td.memItemRight, +tr.inherit_header + tr[class^='memitem'] td.memItemRight { + border-top: 1px solid var(--memdecl-border-color); + border-top-right-radius: 4px; +} + +tr.heading + tr[class^='memitem'] td.memTemplParams, +tr.heading + tr td.ititle, +tr.groupHeader + tr[class^='memitem'] td.memTemplParams, +tr.groupHeader + tr td.ititle, +tr.inherit_header + tr[class^='memitem'] td.memTemplParams { + border-top: 1px solid var(--memdecl-border-color); + border-top-left-radius: 4px; + border-top-right-radius: 4px; +} + +table.memberdecls tr:last-child td.memItemLeft, +table.memberdecls tr:last-child td.mdescLeft, +table.memberdecls tr[class^='memitem']:has(+ tr.groupHeader) td.memItemLeft, +table.memberdecls tr[class^='memitem']:has(+ tr.inherit_header) td.memItemLeft, +table.memberdecls tr[class^='memdesc']:has(+ tr.groupHeader) td.mdescLeft, +table.memberdecls tr[class^='memdesc']:has(+ tr.inherit_header) td.mdescLeft { + border-bottom-left-radius: 4px; +} + +table.memberdecls tr:last-child td.memItemRight, +table.memberdecls tr:last-child td.mdescRight, +table.memberdecls tr[class^='memitem']:has(+ tr.groupHeader) td.memItemRight, +table.memberdecls tr[class^='memitem']:has(+ tr.inherit_header) td.memItemRight, +table.memberdecls tr[class^='memdesc']:has(+ tr.groupHeader) td.mdescRight, +table.memberdecls tr[class^='memdesc']:has(+ tr.inherit_header) td.mdescRight { + border-bottom-right-radius: 4px; +} + +tr.template .memItemLeft, tr.template .memItemRight { + border-top: none; + padding-top: 0; +} + + +/* @end */ + +/* @group Member Details */ + +/* Styles for detailed member documentation */ + +.memtitle { + padding: 8px; + border-top: 1px solid var(--memdef-border-color); + border-left: 1px solid var(--memdef-border-color); + border-right: 1px solid var(--memdef-border-color); + border-top-right-radius: 4px; + border-top-left-radius: 4px; + margin-bottom: -1px; + background-color: var(--memdef-proto-background-color); + line-height: 1.25; + font-family: var(--font-family-monospace); + font-weight: 500; + font-size: 16px; + float:left; + box-shadow: 0 10px 0 -1px var(--memdef-proto-background-color), + 0 2px 8px 0 rgba(0,0,0,.075); + position: relative; +} + +.memtitle:after { + content: ''; + display: block; + background: var(--memdef-proto-background-color); + height: 10px; + bottom: -10px; + left: 0px; + right: -14px; + position: absolute; + border-top-right-radius: 6px; +} + +.permalink +{ + font-family: var(--font-family-monospace); + font-weight: 500; + line-height: 1.25; + font-size: 16px; + display: inline-block; + vertical-align: middle; +} + +.memtemplate { + font-size: 80%; + color: var(--memdef-template-color); + font-family: var(--font-family-monospace); + font-weight: normal; + margin-left: 9px; +} + +.mempage { + width: 100%; +} + +.memitem { + padding: 0; + margin-bottom: 10px; + margin-right: 5px; + display: table !important; + width: 100%; + box-shadow: 0 2px 8px 0 rgba(0,0,0,.075); + border-radius: 4px; +} + +.memitem.glow { + box-shadow: 0 0 15px var(--glow-color); +} + +.memname { + font-family: var(--font-family-monospace); + font-size: 13px; + font-weight: 400; + margin-left: 6px; +} + +.memname td { + vertical-align: bottom; +} + +.memproto, dl.reflist dt { + border-top: 1px solid var(--memdef-border-color); + border-left: 1px solid var(--memdef-border-color); + border-right: 1px solid var(--memdef-border-color); + padding: 6px 0px 6px 0px; + color: var(--memdef-proto-text-color); + font-weight: bold; + background-color: var(--memdef-proto-background-color); + border-top-right-radius: 4px; + border-bottom: 1px solid var(--memdef-border-color); +} + +.overload { + font-family: var(--font-family-monospace); + font-size: 65%; +} + +.memdoc, dl.reflist dd { + border-bottom: 1px solid var(--memdef-border-color); + border-left: 1px solid var(--memdef-border-color); + border-right: 1px solid var(--memdef-border-color); + padding: 6px 10px 2px 10px; + border-top-width: 0; + border-bottom-left-radius: 4px; + border-bottom-right-radius: 4px; +} + +dl.reflist dt { + padding: 5px; +} + +dl.reflist dd { + margin: 0px 0px 10px 0px; + padding: 5px; +} + +.paramkey { + text-align: right; +} + +.paramtype { + white-space: nowrap; + padding: 0px; + padding-bottom: 1px; +} + +.paramname { + white-space: nowrap; + padding: 0px; + padding-bottom: 1px; + margin-left: 2px; +} + +.paramname em { + color: var(--memdef-param-name-color); + font-style: normal; + margin-right: 1px; +} + +.paramname .paramdefval { + font-family: var(--font-family-monospace); +} + +.params, .retval, .exception, .tparams { + margin-left: 0px; + padding-left: 0px; +} + +.params .paramname, .retval .paramname, .tparams .paramname, .exception .paramname { + font-weight: bold; + vertical-align: top; +} + +.params .paramtype, .tparams .paramtype { + font-style: italic; + vertical-align: top; +} + +.params .paramdir, .tparams .paramdir { + font-family: var(--font-family-monospace); + vertical-align: top; +} + +table.mlabels { + border-spacing: 0px; +} + +td.mlabels-left { + width: 100%; + padding: 0px; +} + +td.mlabels-right { + vertical-align: bottom; + padding: 0px; + white-space: nowrap; +} + +span.mlabels { + margin-left: 8px; +} + +span.mlabel { + background-color: var(--label-background-color); + border-top:1px solid var(--label-left-top-border-color); + border-left:1px solid var(--label-left-top-border-color); + border-right:1px solid var(--label-right-bottom-border-color); + border-bottom:1px solid var(--label-right-bottom-border-color); + text-shadow: none; + color: var(--label-foreground-color); + margin-right: 4px; + padding: 2px 3px; + border-radius: 3px; + font-size: 7pt; + white-space: nowrap; + vertical-align: middle; +} + + + +/* @end */ + +/* these are for tree view inside a (index) page */ + +div.directory { + margin: 10px 0px; + width: 100%; +} + +.directory table { + border-collapse:collapse; +} + +.directory td { + margin: 0px; + padding: 0px; + vertical-align: top; +} + +.directory td.entry { + white-space: nowrap; + padding-right: 6px; + padding-top: 3px; +} + +.directory td.entry a { + outline:none; +} + +.directory td.entry a img { + border: none; +} + +.directory td.desc { + width: 100%; + padding-left: 6px; + padding-right: 6px; + padding-top: 3px; + border-left: 1px solid rgba(0,0,0,0.05); +} + +.directory tr.odd { + padding-left: 6px; + background-color: var(--index-odd-item-bg-color); +} + +.directory tr.even { + padding-left: 6px; + background-color: var(--index-even-item-bg-color); +} + +.directory img { + vertical-align: -30%; +} + +.directory .levels { + white-space: nowrap; + width: 100%; + text-align: right; + font-size: 9pt; +} + +.directory .levels span { + cursor: pointer; + padding-left: 2px; + padding-right: 2px; + color: var(--page-link-color); +} + +.arrow { + color: var(--nav-background-color); + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + cursor: pointer; + font-size: 80%; + display: inline-block; + width: 16px; + height: 14px; + transition: opacity 0.3s ease; +} + +span.arrowhead { + position: relative; + padding: 0; + margin: 0 0 0 2px; + display: inline-block; + width: 5px; + height: 5px; + border-right: 2px solid var(--nav-arrow-color); + border-bottom: 2px solid var(--nav-arrow-color); + transform: rotate(-45deg); + transition: transform 0.3s ease; +} + +span.arrowhead.opened { + transform: rotate(45deg); +} + +.selected span.arrowhead { + border-right: 2px solid var(--nav-arrow-selected-color); + border-bottom: 2px solid var(--nav-arrow-selected-color); +} + +.icon { + font-family: var(--font-family-icon); + line-height: normal; + font-weight: bold; + font-size: 12px; + height: 14px; + width: 16px; + display: inline-block; + background-color: var(--icon-background-color); + color: var(--icon-foreground-color); + text-align: center; + border-radius: 4px; + margin-left: 2px; + margin-right: 2px; +} + +.icona { + width: 24px; + height: 22px; + display: inline-block; +} + +.iconfolder { + width: 24px; + height: 18px; + margin-top: 6px; + vertical-align:top; + display: inline-block; + position: relative; +} + +.icondoc { + width: 24px; + height: 18px; + margin-top: 3px; + vertical-align:top; + display: inline-block; + position: relative; +} + +.folder-icon { + width: 16px; + height: 11px; + background-color: var(--icon-folder-fill-color); + border: 1px solid var(--icon-folder-border-color); + border-radius: 0 2px 2px 2px; + position: relative; + box-sizing: content-box; +} + +.folder-icon::after { + content: ''; + position: absolute; + top: 2px; + left: -1px; + width: 16px; + height: 7px; + background-color: var(--icon-folder-open-fill-color); + border: 1px solid var(--icon-folder-border-color); + border-radius: 7px 7px 2px 2px; + transform-origin: top left; + opacity: 0; + transition: all 0.3s linear; +} + +.folder-icon::before { + content: ''; + position: absolute; + top: -3px; + left: -1px; + width: 6px; + height: 2px; + background-color: var(--icon-folder-fill-color); + border-top: 1px solid var(--icon-folder-border-color); + border-left: 1px solid var(--icon-folder-border-color); + border-right: 1px solid var(--icon-folder-border-color); + border-radius: 2px 2px 0 0; +} + +.folder-icon.open::after { + top: 3px; + opacity: 1; +} + +.doc-icon { + left: 6px; + width: 12px; + height: 16px; + background-color: var(--icon-doc-border-color); + clip-path: polygon(0 0, 66% 0, 100% 25%, 100% 100%, 0 100%); + position: relative; + display: inline-block; +} +.doc-icon::before { + content: ""; + left: 1px; + top: 1px; + width: 10px; + height: 14px; + background-color: var(--icon-doc-fill-color); + clip-path: polygon(0 0, 66% 0, 100% 25%, 100% 100%, 0 100%); + position: absolute; + box-sizing: border-box; +} +.doc-icon::after { + content: ""; + left: 7px; + top: 0px; + width: 3px; + height: 3px; + background-color: transparent; + position: absolute; + border: 1px solid var(--icon-doc-border-color); +} + + + + +/* @end */ + +div.dynheader { + margin-top: 8px; + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} + +span.dynarrow { + position: relative; + display: inline-block; + width: 12px; + bottom: 1px; +} + +address { + font-style: normal; + color: var(--footer-foreground-color); +} + +table.doxtable caption { + caption-side: top; +} + +table.doxtable { + border-collapse:collapse; + margin-top: 4px; + margin-bottom: 4px; +} + +table.doxtable td, table.doxtable th { + border: 1px solid var(--table-cell-border-color); + padding: 3px 7px 2px; +} + +table.doxtable th { + background-color: var(--table-header-background-color); + color: var(--table-header-foreground-color); + font-size: 110%; + padding-bottom: 4px; + padding-top: 5px; +} + +table.fieldtable { + margin-bottom: 10px; + border: 1px solid var(--memdef-border-color); + border-spacing: 0px; + border-radius: 4px; + box-shadow: 2px 2px 2px rgba(0, 0, 0, 0.15); +} + +.fieldtable td, .fieldtable th { + padding: 3px 7px 2px; +} + +.fieldtable td.fieldtype, .fieldtable td.fieldname, .fieldtable td.fieldinit { + white-space: nowrap; + border-right: 1px solid var(--memdef-border-color); + border-bottom: 1px solid var(--memdef-border-color); + vertical-align: top; +} + +.fieldtable td.fieldname { + padding-top: 3px; +} + +.fieldtable td.fieldinit { + padding-top: 3px; + text-align: right; +} + + +.fieldtable td.fielddoc { + border-bottom: 1px solid var(--memdef-border-color); +} + +.fieldtable td.fielddoc p:first-child { + margin-top: 0px; +} + +.fieldtable td.fielddoc p:last-child { + margin-bottom: 2px; +} + +.fieldtable tr:last-child td { + border-bottom: none; +} + +.fieldtable th { + background-color: var(--memdef-title-background-color); + font-size: 90%; + color: var(--memdef-proto-text-color); + padding-bottom: 4px; + padding-top: 5px; + text-align:left; + font-weight: 400; + border-top-left-radius: 4px; + border-top-right-radius: 4px; + border-bottom: 1px solid var(--memdef-border-color); +} + +/* ----------- navigation breadcrumb styling ----------- */ + +#nav-path ul { + height: 30px; + line-height: 30px; + color: var(--nav-text-normal-color); + overflow: hidden; + margin: 0px; + padding-left: 4px; + background-image: none; + background: var(--page-background-color); + border-bottom: 1px solid var(--nav-breadcrumb-separator-color); + font-size: var(--nav-font-size-level1); + font-family: var(--font-family-nav); + position: relative; + z-index: 100; +} + +#main-nav { + border-bottom: 1px solid var(--nav-border-color); +} + +.navpath li { + list-style-type:none; + float:left; + color: var(--nav-foreground-color); +} + +.navpath li.footer { + list-style-type:none; + float:right; + padding-left:10px; + padding-right:15px; + background-image:none; + background-repeat:no-repeat; + background-position:right; + font-size: 8pt; + color: var(--footer-foreground-color); +} + +#nav-path li.navelem { + background-image: none; + display: flex; + align-items: center; + padding-left: 15px; +} + +.navpath li.navelem a { + text-shadow: none; + display: inline-block; + color: var(--nav-breadcrumb-color); + position: relative; + top: 0px; + height: 30px; + margin-right: -20px; +} + +#nav-path li.navelem:after { + content: ''; + display: inline-block; + position: relative; + top: 0; + right: -15px; + width: 30px; + height: 30px; + transform: scaleX(0.5) scale(0.707) rotate(45deg); + z-index: 10; + background: var(--page-background-color); + box-shadow: 2px -2px 0 2px var(--nav-breadcrumb-separator-color); + border-radius: 0 5px 0 50px; +} + +#nav-path li.navelem:first-child { + margin-left: -6px; +} + +#nav-path li.navelem:hover, +#nav-path li.navelem:hover:after { + background-color: var(--nav-breadcrumb-active-bg); +} + +/* ---------------------- */ + +div.summary +{ + float: right; + font-size: 8pt; + padding-right: 5px; + width: 50%; + text-align: right; +} + +div.summary a +{ + white-space: nowrap; +} + +table.classindex +{ + margin: 10px; + white-space: nowrap; + margin-left: 3%; + margin-right: 3%; + width: 94%; + border: 0; + border-spacing: 0; + padding: 0; +} + +div.ingroups +{ + font-size: 8pt; + width: 50%; + text-align: left; +} + +div.ingroups a +{ + white-space: nowrap; +} + +div.header +{ + margin: 0px; + background-color: var(--header-background-color); + border-bottom: 1px solid var(--header-separator-color); +} + +div.headertitle +{ + padding: 5px 5px 5px 10px; +} + +dl { + padding: 0 0 0 0; +} + +dl.bug dt a, dl.deprecated dt a, dl.todo dt a, dl.test a { + font-weight: bold !important; +} + +dl.warning, dl.attention, dl.important, dl.note, dl.deprecated, dl.bug, +dl.invariant, dl.pre, dl.post, dl.todo, dl.test, dl.remark { + padding: 10px; + margin: 10px 0px; + overflow: hidden; + margin-left: 0; + border-radius: 4px; +} + +dl.section dd { + margin-bottom: 2px; +} + +dl.warning, dl.attention, dl.important { + background: var(--warning-color-bg); + border-left: 8px solid var(--warning-color-hl); + color: var(--warning-color-text); +} + +dl.warning dt, dl.attention dt, dl.important dt { + color: var(--warning-color-hl); +} + +dl.warning .tt, dl.attention .tt, dl.important .tt { + background-color: hsl(from var(--warning-color-bg) h s calc(l + var(--fragment-highlight-filter))); +} + +dl.note, dl.remark { + background: var(--note-color-bg); + border-left: 8px solid var(--note-color-hl); + color: var(--note-color-text); +} + +dl.note dt, dl.remark dt { + color: var(--note-color-hl); +} + +dl.note .tt, dl.remark .tt { + background-color: hsl(from var(--note-color-bg) h s calc(l + var(--fragment-highlight-filter))); +} + +dl.todo { + background: var(--todo-color-bg); + border-left: 8px solid var(--todo-color-hl); + color: var(--todo-color-text); +} + +dl.todo dt { + color: var(--todo-color-hl); +} + +dl.todo .tt { + background-color: hsl(from var(--todo-color-bg) h s calc(l + var(--fragment-highlight-filter))); +} + +dl.test { + background: var(--test-color-bg); + border-left: 8px solid var(--test-color-hl); + color: var(--test-color-text); +} + +dl.test dt { + color: var(--test-color-hl); +} + +dl.test .tt { + background-color: hsl(from var(--test-color-bg) h s calc(l + var(--fragment-highlight-filter))); +} + +dl.bug dt a { + color: var(--bug-color-hl) !important; +} + +dl.bug { + background: var(--bug-color-bg); + border-left: 8px solid var(--bug-color-hl); + color: var(--bug-color-text); +} + +dl.bug dt a { + color: var(--bug-color-hl) !important; +} + +dl.bug .tt { + background-color: hsl(from var(--bug-color-bg) h s calc(l + var(--fragment-highlight-filter))); +} + +dl.deprecated { + background: var(--deprecated-color-bg); + border-left: 8px solid var(--deprecated-color-hl); + color: var(--deprecated-color-text); +} + +dl.deprecated dt a { + color: var(--deprecated-color-hl) !important; +} + +dl.deprecated .tt { + background-color: hsl(from var(--deprecated-color-bg) h s calc(l + var(--fragment-highlight-filter))); +} + + +dl.invariant, dl.pre, dl.post { + background: var(--invariant-color-bg); + border-left: 8px solid var(--invariant-color-hl); + color: var(--invariant-color-text); +} + +dl.invariant dt, dl.pre dt, dl.post dt { + color: var(--invariant-color-hl); +} + +dl.invariant .tt, dl.pre .tt, dl.post .tt { + background-color: hsl(from var(--invariant-color-bg) h s calc(l + var(--fragment-highlight-filter))); +} + +dl.note dd, dl.warning dd, dl.pre dd, dl.post dd, +dl.remark dd, dl.attention dd, dl.important dd, dl.invariant dd, +dl.bug dd, dl.deprecated dd, dl.todo dd, dl.test dd { + margin-inline-start: 0px; +} + + +#projectrow +{ + height: 56px; +} + +#projectlogo +{ + text-align: center; + vertical-align: bottom; + border-collapse: separate; +} + +#projectlogo img +{ + border: 0px none; +} + +#projectalign +{ + vertical-align: middle; + padding-left: 0.5em; +} + +#projectname +{ + font-size: 200%; + font-family: var(--font-family-title); + margin: 0; + padding: 0; +} + +#side-nav #projectname +{ + font-size: 130%; +} + +#projectbrief +{ + font-size: 90%; + font-family: var(--font-family-title); + margin: 0px; + padding: 0px; +} + +#projectnumber +{ + font-size: 50%; + font-family: var(--font-family-title); + margin: 0px; + padding: 0px; +} + +#titlearea +{ + padding: 0 0 0 5px; + margin: 0px; + border-bottom: 1px solid var(--title-separator-color); + background-color: var(--title-background-color); +} + +.image +{ + text-align: center; +} + +.dotgraph +{ + text-align: center; +} + +.mscgraph +{ + text-align: center; +} + +.plantumlgraph +{ + text-align: center; +} + +.diagraph +{ + text-align: center; +} + +.caption +{ + font-weight: bold; +} + +dl.citelist { + margin-bottom:50px; +} + +dl.citelist dt { + color:var(--citation-label-color); + float:left; + font-weight:bold; + margin-right:10px; + padding:5px; + text-align:right; + width:52px; +} + +dl.citelist dd { + margin:2px 0 2px 72px; + padding:5px 0; +} + +div.toc { + padding: 14px 25px; + background-color: var(--toc-background-color); + border: 1px solid var(--toc-border-color); + border-radius: 7px 7px 7px 7px; + float: right; + height: auto; + margin: 0 8px 10px 10px; + width: 200px; +} + +div.toc li { + background: var(--toc-down-arrow-image) no-repeat scroll 0 5px transparent; + font: 10px/1.2 var(--font-family-toc); + margin-top: 5px; + padding-left: 10px; + padding-top: 2px; +} + +div.toc h3 { + font: bold 12px/1.2 var(--font-family-toc); + color: var(--toc-header-color); + border-bottom: 0 none; + margin: 0; +} + +div.toc ul { + list-style: none outside none; + border: medium none; + padding: 0px; +} + +div.toc li[class^='level'] { + margin-left: 15px; +} + +div.toc li.level1 { + margin-left: 0px; +} + +div.toc li.empty { + background-image: none; + margin-top: 0px; +} + +span.emoji { + /* font family used at the site: https://unicode.org/emoji/charts/full-emoji-list.html + * font-family: "Noto Color Emoji", "Apple Color Emoji", "Segoe UI Emoji", Times, Symbola, Aegyptus, Code2000, Code2001, Code2002, Musica, serif, LastResort; + */ +} + +span.obfuscator { + display: none; +} + +.inherit_header { + font-weight: 400; + cursor: pointer; + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} + +.inherit_header td { + padding: 6px 0 2px 0; +} + +.inherit { + display: none; +} + +tr.heading h2 { + margin-top: 12px; + margin-bottom: 12px; +} + +/* tooltip related style info */ + +.ttc { + position: absolute; + display: none; +} + +#powerTip { + cursor: default; + color: var(--tooltip-foreground-color); + background-color: var(--tooltip-background-color); + backdrop-filter: var(--tooltip-backdrop-filter); + -webkit-backdrop-filter: var(--tooltip-backdrop-filter); + border: 1px solid var(--tooltip-border-color); + border-radius: 4px; + box-shadow: var(--tooltip-shadow); + display: none; + font-size: smaller; + max-width: 80%; + padding: 1ex 1em 1em; + position: absolute; + z-index: 2147483647; +} + +#powerTip div.ttdoc { + color: var(--tooltip-doc-color); + font-style: italic; +} + +#powerTip div.ttname a { + font-weight: bold; +} + +#powerTip a { + color: var(--tooltip-link-color); +} + +#powerTip div.ttname { + font-weight: bold; +} + +#powerTip div.ttdeci { + color: var(--tooltip-declaration-color); +} + +#powerTip div { + margin: 0px; + padding: 0px; + font-size: 12px; + font-family: var(--font-family-tooltip); + line-height: 16px; +} + +#powerTip:before, #powerTip:after { + content: ""; + position: absolute; + margin: 0px; +} + +#powerTip.n:after, #powerTip.n:before, +#powerTip.s:after, #powerTip.s:before, +#powerTip.w:after, #powerTip.w:before, +#powerTip.e:after, #powerTip.e:before, +#powerTip.ne:after, #powerTip.ne:before, +#powerTip.se:after, #powerTip.se:before, +#powerTip.nw:after, #powerTip.nw:before, +#powerTip.sw:after, #powerTip.sw:before { + border: solid transparent; + content: " "; + height: 0; + width: 0; + position: absolute; +} + +#powerTip.n:after, #powerTip.s:after, +#powerTip.w:after, #powerTip.e:after, +#powerTip.nw:after, #powerTip.ne:after, +#powerTip.sw:after, #powerTip.se:after { + border-color: rgba(255, 255, 255, 0); +} + +#powerTip.n:before, #powerTip.s:before, +#powerTip.w:before, #powerTip.e:before, +#powerTip.nw:before, #powerTip.ne:before, +#powerTip.sw:before, #powerTip.se:before { + border-color: rgba(128, 128, 128, 0); +} + +#powerTip.n:after, #powerTip.n:before, +#powerTip.ne:after, #powerTip.ne:before, +#powerTip.nw:after, #powerTip.nw:before { + top: 100%; +} + +#powerTip.n:after, #powerTip.ne:after, #powerTip.nw:after { + border-top-color: var(--tooltip-arrow-background-color); + border-width: 10px; + margin: 0px -10px; +} +#powerTip.n:before, #powerTip.ne:before, #powerTip.nw:before { + border-top-color: var(--tooltip-border-color); + border-width: 11px; + margin: 0px -11px; +} +#powerTip.n:after, #powerTip.n:before { + left: 50%; +} + +#powerTip.nw:after, #powerTip.nw:before { + right: 14px; +} + +#powerTip.ne:after, #powerTip.ne:before { + left: 14px; +} + +#powerTip.s:after, #powerTip.s:before, +#powerTip.se:after, #powerTip.se:before, +#powerTip.sw:after, #powerTip.sw:before { + bottom: 100%; +} + +#powerTip.s:after, #powerTip.se:after, #powerTip.sw:after { + border-bottom-color: var(--tooltip-arrow-background-color); + border-width: 10px; + margin: 0px -10px; +} + +#powerTip.s:before, #powerTip.se:before, #powerTip.sw:before { + border-bottom-color: var(--tooltip-border-color); + border-width: 11px; + margin: 0px -11px; +} + +#powerTip.s:after, #powerTip.s:before { + left: 50%; +} + +#powerTip.sw:after, #powerTip.sw:before { + right: 14px; +} + +#powerTip.se:after, #powerTip.se:before { + left: 14px; +} + +#powerTip.e:after, #powerTip.e:before { + left: 100%; +} +#powerTip.e:after { + border-left-color: var(--tooltip-border-color); + border-width: 10px; + top: 50%; + margin-top: -10px; +} +#powerTip.e:before { + border-left-color: var(--tooltip-border-color); + border-width: 11px; + top: 50%; + margin-top: -11px; +} + +#powerTip.w:after, #powerTip.w:before { + right: 100%; +} +#powerTip.w:after { + border-right-color: var(--tooltip-border-color); + border-width: 10px; + top: 50%; + margin-top: -10px; +} +#powerTip.w:before { + border-right-color: var(--tooltip-border-color); + border-width: 11px; + top: 50%; + margin-top: -11px; +} + +@media print +{ + #top { display: none; } + #side-nav { display: none; } + #nav-path { display: none; } + body { overflow:visible; } + h1, h2, h3, h4, h5, h6 { page-break-after: avoid; } + .summary { display: none; } + .memitem { page-break-inside: avoid; } + #doc-content + { + margin-left:0 !important; + height:auto !important; + width:auto !important; + overflow:inherit; + display:inline; + } +} + +/* @group Markdown */ + +table.markdownTable { + border-collapse:collapse; + margin-top: 4px; + margin-bottom: 4px; +} + +table.markdownTable td, table.markdownTable th { + border: 1px solid var(--table-cell-border-color); + padding: 3px 7px 2px; +} + +table.markdownTable tr { +} + +th.markdownTableHeadLeft, th.markdownTableHeadRight, th.markdownTableHeadCenter, th.markdownTableHeadNone { + background-color: var(--table-header-background-color); + color: var(--table-header-foreground-color); + font-size: 110%; + padding-bottom: 4px; + padding-top: 5px; +} + +th.markdownTableHeadLeft, td.markdownTableBodyLeft { + text-align: left +} + +th.markdownTableHeadRight, td.markdownTableBodyRight { + text-align: right +} + +th.markdownTableHeadCenter, td.markdownTableBodyCenter { + text-align: center +} + +tt, code, kbd +{ + display: inline-block; +} +tt, code, kbd +{ + vertical-align: top; +} +/* @end */ + +u { + text-decoration: underline; +} + +details>summary { + list-style-type: none; +} + +details > summary::-webkit-details-marker { + display: none; +} + +details>summary::before { + content: "\25ba"; + padding-right:4px; + font-size: 80%; +} + +details[open]>summary::before { + content: "\25bc"; + padding-right:4px; + font-size: 80%; +} + +:root { + scrollbar-width: thin; + scrollbar-color: var(--scrollbar-thumb-color) var(--scrollbar-background-color); +} + +::-webkit-scrollbar { + background-color: var(--scrollbar-background-color); + height: 12px; + width: 12px; +} +::-webkit-scrollbar-thumb { + border-radius: 6px; + box-shadow: inset 0 0 12px 12px var(--scrollbar-thumb-color); + border: solid 2px transparent; +} +::-webkit-scrollbar-corner { + background-color: var(--scrollbar-background-color); +} + diff --git a/doc/html/doxygen.svg b/doc/html/doxygen.svg new file mode 100644 index 0000000..79a7635 --- /dev/null +++ b/doc/html/doxygen.svg @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/doc/html/doxygen_crawl.html b/doc/html/doxygen_crawl.html new file mode 100644 index 0000000..a29520b --- /dev/null +++ b/doc/html/doxygen_crawl.html @@ -0,0 +1,493 @@ + + + +Validator / crawler helper + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/doc/html/dynsections.js b/doc/html/dynsections.js new file mode 100644 index 0000000..d89724e --- /dev/null +++ b/doc/html/dynsections.js @@ -0,0 +1,198 @@ +/* + @licstart The following is the entire license notice for the JavaScript code in this file. + + The MIT License (MIT) + + Copyright (C) 1997-2020 by Dimitri van Heesch + + Permission is hereby granted, free of charge, to any person obtaining a copy of this software + and associated documentation files (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, publish, distribute, + sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all copies or + substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING + BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + @licend The above is the entire license notice for the JavaScript code in this file + */ + +function toggleVisibility(linkObj) { + return dynsection.toggleVisibility(linkObj); +} + +let dynsection = { + // helper function + updateStripes : function() { + $('table.directory tr'). + removeClass('even').filter(':visible:even').addClass('even'); + $('table.directory tr'). + removeClass('odd').filter(':visible:odd').addClass('odd'); + }, + + toggleVisibility : function(linkObj) { + const base = $(linkObj).attr('id'); + const summary = $('#'+base+'-summary'); + const content = $('#'+base+'-content'); + const trigger = $('#'+base+'-trigger'); + const src=$(trigger).attr('src'); + if (content.is(':visible')===true) { + content.slideUp('fast'); + summary.show(); + $(linkObj).find('.arrowhead').addClass('closed').removeClass('opened'); + } else { + content.slideDown('fast'); + summary.hide(); + $(linkObj).find('.arrowhead').removeClass('closed').addClass('opened'); + } + return false; + }, + + toggleLevel : function(level) { + $('table.directory tr').each(function() { + const l = this.id.split('_').length-1; + const i = $('#img'+this.id.substring(3)); + const a = $('#arr'+this.id.substring(3)); + if (l'); + // add vertical lines to other rows + $('span[class=lineno]').not(':eq(0)').append(''); + // add toggle controls to lines with fold divs + $('div[class=foldopen]').each(function() { + // extract specific id to use + const id = $(this).attr('id').replace('foldopen',''); + // extract start and end foldable fragment attributes + const start = $(this).attr('data-start'); + const end = $(this).attr('data-end'); + // replace normal fold span with controls for the first line of a foldable fragment + $(this).find('span[class=fold]:first').replaceWith(''); + // append div for folded (closed) representation + $(this).after(''); + // extract the first line from the "open" section to represent closed content + const line = $(this).children().first().clone(); + // remove any glow that might still be active on the original line + $(line).removeClass('glow'); + if (start) { + // if line already ends with a start marker (e.g. trailing {), remove it + $(line).html($(line).html().replace(new RegExp('\\s*'+start+'\\s*$','g'),'')); + } + // replace minus with plus symbol + $(line).find('span[class=fold]').addClass('plus').removeClass('minus'); + // append ellipsis + $(line).append(' '+start+'…'+end); + // insert constructed line into closed div + $('#foldclosed'+id).html(line); + }); + }, +}; +/* @license-end */ +$(function() { + $('.code,.codeRef').each(function() { + $(this).data('powertip',$('#a'+$(this).attr('href').replace(/.*\//,'').replace(/[^a-z_A-Z0-9]/g,'_')).html()); + $.fn.powerTip.smartPlacementLists.s = [ 's', 'n', 'ne', 'se' ]; + $(this).powerTip({ placement: 's', smartPlacement: true, mouseOnToPopup: true }); + }); +}); diff --git a/doc/html/example_2hello_8c.html b/doc/html/example_2hello_8c.html new file mode 100644 index 0000000..894d443 --- /dev/null +++ b/doc/html/example_2hello_8c.html @@ -0,0 +1,271 @@ + + + + + + + +libfuse: example/hello.c File Reference + + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
+
hello.c File Reference
+
+
+
#include <fuse.h>
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stddef.h>
+#include <assert.h>
+
+

Go to the source code of this file.

+

Detailed Description

+

minimal example filesystem using high-level API

+

Compile with:

gcc -Wall hello.c `pkg-config fuse3 --cflags --libs` -o hello
+

+Source code

+
/*
+
FUSE: Filesystem in Userspace
+
Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
+
This program can be distributed under the terms of the GNU GPLv2.
+
See the file GPL2.txt.
+
*/
+
+
+
+
#define FUSE_USE_VERSION 31
+
+
#include <fuse.h>
+
#include <stdio.h>
+
#include <string.h>
+
#include <errno.h>
+
#include <fcntl.h>
+
#include <stddef.h>
+
#include <assert.h>
+
+
/*
+
* Command line options
+
*
+
* We can't set default values for the char* fields here because
+
* fuse_opt_parse would attempt to free() them when the user specifies
+
* different values on the command line.
+
*/
+
static struct options {
+
const char *filename;
+
const char *contents;
+
int show_help;
+
} options;
+
+
#define OPTION(t, p) \
+
{ t, offsetof(struct options, p), 1 }
+
static const struct fuse_opt option_spec[] = {
+
OPTION("--name=%s", filename),
+
OPTION("--contents=%s", contents),
+
OPTION("-h", show_help),
+
OPTION("--help", show_help),
+ +
};
+
+
static void *hello_init(struct fuse_conn_info *conn,
+
struct fuse_config *cfg)
+
{
+
(void) conn;
+
cfg->kernel_cache = 1;
+
+
/* Test setting flags the old way */
+ + +
+
return NULL;
+
}
+
+
static int hello_getattr(const char *path, struct stat *stbuf,
+
struct fuse_file_info *fi)
+
{
+
(void) fi;
+
int res = 0;
+
+
memset(stbuf, 0, sizeof(struct stat));
+
if (strcmp(path, "/") == 0) {
+
stbuf->st_mode = S_IFDIR | 0755;
+
stbuf->st_nlink = 2;
+
} else if (strcmp(path+1, options.filename) == 0) {
+
stbuf->st_mode = S_IFREG | 0444;
+
stbuf->st_nlink = 1;
+
stbuf->st_size = strlen(options.contents);
+
} else
+
res = -ENOENT;
+
+
return res;
+
}
+
+
static int hello_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
+
off_t offset, struct fuse_file_info *fi,
+
enum fuse_readdir_flags flags)
+
{
+
(void) offset;
+
(void) fi;
+
(void) flags;
+
+
if (strcmp(path, "/") != 0)
+
return -ENOENT;
+
+
filler(buf, ".", NULL, 0, FUSE_FILL_DIR_DEFAULTS);
+
filler(buf, "..", NULL, 0, FUSE_FILL_DIR_DEFAULTS);
+
filler(buf, options.filename, NULL, 0, FUSE_FILL_DIR_DEFAULTS);
+
+
return 0;
+
}
+
+
static int hello_open(const char *path, struct fuse_file_info *fi)
+
{
+
if (strcmp(path+1, options.filename) != 0)
+
return -ENOENT;
+
+
if ((fi->flags & O_ACCMODE) != O_RDONLY)
+
return -EACCES;
+
+
return 0;
+
}
+
+
static int hello_read(const char *path, char *buf, size_t size, off_t offset,
+
struct fuse_file_info *fi)
+
{
+
size_t len;
+
(void) fi;
+
if(strcmp(path+1, options.filename) != 0)
+
return -ENOENT;
+
+
len = strlen(options.contents);
+
if (offset < len) {
+
if (offset + size > len)
+
size = len - offset;
+
memcpy(buf, options.contents + offset, size);
+
} else
+
size = 0;
+
+
return size;
+
}
+
+
static const struct fuse_operations hello_oper = {
+
.init = hello_init,
+
.getattr = hello_getattr,
+
.readdir = hello_readdir,
+
.open = hello_open,
+
.read = hello_read,
+
};
+
+
static void show_help(const char *progname)
+
{
+
printf("usage: %s [options] <mountpoint>\n\n", progname);
+
printf("File-system specific options:\n"
+
" --name=<s> Name of the \"hello\" file\n"
+
" (default: \"hello\")\n"
+
" --contents=<s> Contents \"hello\" file\n"
+
" (default \"Hello, World!\\n\")\n"
+
"\n");
+
}
+
+
int main(int argc, char *argv[])
+
{
+
int ret;
+
struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
+
/* Set defaults -- we have to use strdup so that
+
fuse_opt_parse can free the defaults if other
+
values are specified */
+
options.filename = strdup("hello");
+
options.contents = strdup("Hello World!\n");
+
+
/* Parse options */
+
if (fuse_opt_parse(&args, &options, option_spec, NULL) == -1)
+
return 1;
+
+
/* When --help is specified, first print our own file-system
+
specific help text, then signal fuse_main to show
+
additional help (by adding `--help` to the options again)
+
without usage: line (by setting argv[0] to the empty
+
string) */
+
if (options.show_help) {
+
show_help(argv[0]);
+
assert(fuse_opt_add_arg(&args, "--help") == 0);
+
args.argv[0][0] = '\0';
+
}
+
+
ret = fuse_main(args.argc, args.argv, &hello_oper, NULL);
+ +
return ret;
+
}
+ +
int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
Definition fuse.h:93
+
@ FUSE_FILL_DIR_DEFAULTS
Definition fuse.h:74
+
fuse_readdir_flags
Definition fuse.h:45
+
void fuse_unset_feature_flag(struct fuse_conn_info *conn, uint64_t flag)
+
bool fuse_set_feature_flag(struct fuse_conn_info *conn, uint64_t flag)
+
#define FUSE_CAP_ASYNC_READ
+
int fuse_opt_add_arg(struct fuse_args *args, const char *arg)
Definition fuse_opt.c:55
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+ + +
char ** argv
Definition fuse_opt.h:114
+ +
int32_t kernel_cache
Definition fuse.h:251
+ + + + + +
unsigned long offset
Definition fuse_opt.h:85
+
+

Definition in file hello.c.

+
+ + +
+ + diff --git a/doc/html/example_2hello_8c_source.html b/doc/html/example_2hello_8c_source.html new file mode 100644 index 0000000..03ee23b --- /dev/null +++ b/doc/html/example_2hello_8c_source.html @@ -0,0 +1,255 @@ + + + + + + + +libfuse: example/hello.c Source File + + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
+
hello.c
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 This program can be distributed under the terms of the GNU GPLv2.
+
6 See the file GPL2.txt.
+
7*/
+
8
+
20
+
21
+
22#define FUSE_USE_VERSION 31
+
23
+
24#include <fuse.h>
+
25#include <stdio.h>
+
26#include <string.h>
+
27#include <errno.h>
+
28#include <fcntl.h>
+
29#include <stddef.h>
+
30#include <assert.h>
+
31
+
32/*
+
33 * Command line options
+
34 *
+
35 * We can't set default values for the char* fields here because
+
36 * fuse_opt_parse would attempt to free() them when the user specifies
+
37 * different values on the command line.
+
38 */
+
39static struct options {
+
40 const char *filename;
+
41 const char *contents;
+
42 int show_help;
+
43} options;
+
44
+
45#define OPTION(t, p) \
+
46 { t, offsetof(struct options, p), 1 }
+
47static const struct fuse_opt option_spec[] = {
+
48 OPTION("--name=%s", filename),
+
49 OPTION("--contents=%s", contents),
+
50 OPTION("-h", show_help),
+
51 OPTION("--help", show_help),
+ +
53};
+
54
+
55static void *hello_init(struct fuse_conn_info *conn,
+
56 struct fuse_config *cfg)
+
57{
+
58 (void) conn;
+
59 cfg->kernel_cache = 1;
+
60
+
61 /* Test setting flags the old way */
+ + +
64
+
65 return NULL;
+
66}
+
67
+
68static int hello_getattr(const char *path, struct stat *stbuf,
+
69 struct fuse_file_info *fi)
+
70{
+
71 (void) fi;
+
72 int res = 0;
+
73
+
74 memset(stbuf, 0, sizeof(struct stat));
+
75 if (strcmp(path, "/") == 0) {
+
76 stbuf->st_mode = S_IFDIR | 0755;
+
77 stbuf->st_nlink = 2;
+
78 } else if (strcmp(path+1, options.filename) == 0) {
+
79 stbuf->st_mode = S_IFREG | 0444;
+
80 stbuf->st_nlink = 1;
+
81 stbuf->st_size = strlen(options.contents);
+
82 } else
+
83 res = -ENOENT;
+
84
+
85 return res;
+
86}
+
87
+
88static int hello_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
+
89 off_t offset, struct fuse_file_info *fi,
+
90 enum fuse_readdir_flags flags)
+
91{
+
92 (void) offset;
+
93 (void) fi;
+
94 (void) flags;
+
95
+
96 if (strcmp(path, "/") != 0)
+
97 return -ENOENT;
+
98
+
99 filler(buf, ".", NULL, 0, FUSE_FILL_DIR_DEFAULTS);
+
100 filler(buf, "..", NULL, 0, FUSE_FILL_DIR_DEFAULTS);
+
101 filler(buf, options.filename, NULL, 0, FUSE_FILL_DIR_DEFAULTS);
+
102
+
103 return 0;
+
104}
+
105
+
106static int hello_open(const char *path, struct fuse_file_info *fi)
+
107{
+
108 if (strcmp(path+1, options.filename) != 0)
+
109 return -ENOENT;
+
110
+
111 if ((fi->flags & O_ACCMODE) != O_RDONLY)
+
112 return -EACCES;
+
113
+
114 return 0;
+
115}
+
116
+
117static int hello_read(const char *path, char *buf, size_t size, off_t offset,
+
118 struct fuse_file_info *fi)
+
119{
+
120 size_t len;
+
121 (void) fi;
+
122 if(strcmp(path+1, options.filename) != 0)
+
123 return -ENOENT;
+
124
+
125 len = strlen(options.contents);
+
126 if (offset < len) {
+
127 if (offset + size > len)
+
128 size = len - offset;
+
129 memcpy(buf, options.contents + offset, size);
+
130 } else
+
131 size = 0;
+
132
+
133 return size;
+
134}
+
135
+
136static const struct fuse_operations hello_oper = {
+
137 .init = hello_init,
+
138 .getattr = hello_getattr,
+
139 .readdir = hello_readdir,
+
140 .open = hello_open,
+
141 .read = hello_read,
+
142};
+
143
+
144static void show_help(const char *progname)
+
145{
+
146 printf("usage: %s [options] <mountpoint>\n\n", progname);
+
147 printf("File-system specific options:\n"
+
148 " --name=<s> Name of the \"hello\" file\n"
+
149 " (default: \"hello\")\n"
+
150 " --contents=<s> Contents \"hello\" file\n"
+
151 " (default \"Hello, World!\\n\")\n"
+
152 "\n");
+
153}
+
154
+
155int main(int argc, char *argv[])
+
156{
+
157 int ret;
+
158 struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
159
+
160 /* Set defaults -- we have to use strdup so that
+
161 fuse_opt_parse can free the defaults if other
+
162 values are specified */
+
163 options.filename = strdup("hello");
+
164 options.contents = strdup("Hello World!\n");
+
165
+
166 /* Parse options */
+
167 if (fuse_opt_parse(&args, &options, option_spec, NULL) == -1)
+
168 return 1;
+
169
+
170 /* When --help is specified, first print our own file-system
+
171 specific help text, then signal fuse_main to show
+
172 additional help (by adding `--help` to the options again)
+
173 without usage: line (by setting argv[0] to the empty
+
174 string) */
+
175 if (options.show_help) {
+
176 show_help(argv[0]);
+
177 assert(fuse_opt_add_arg(&args, "--help") == 0);
+
178 args.argv[0][0] = '\0';
+
179 }
+
180
+
181 ret = fuse_main(args.argc, args.argv, &hello_oper, NULL);
+
182 fuse_opt_free_args(&args);
+
183 return ret;
+
184}
+ +
int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
Definition fuse.h:93
+
@ FUSE_FILL_DIR_DEFAULTS
Definition fuse.h:74
+
fuse_readdir_flags
Definition fuse.h:45
+
void fuse_unset_feature_flag(struct fuse_conn_info *conn, uint64_t flag)
+
bool fuse_set_feature_flag(struct fuse_conn_info *conn, uint64_t flag)
+
#define FUSE_CAP_ASYNC_READ
+
int fuse_opt_add_arg(struct fuse_args *args, const char *arg)
Definition fuse_opt.c:55
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+ + +
char ** argv
Definition fuse_opt.h:114
+ +
int32_t kernel_cache
Definition fuse.h:251
+ + + + + +
unsigned long offset
Definition fuse_opt.h:85
+
+ + +
+ + diff --git a/doc/html/fast17-vangoor.pdf b/doc/html/fast17-vangoor.pdf new file mode 100644 index 0000000..cef7237 Binary files /dev/null and b/doc/html/fast17-vangoor.pdf differ diff --git a/doc/html/files.html b/doc/html/files.html new file mode 100644 index 0000000..27e83ef --- /dev/null +++ b/doc/html/files.html @@ -0,0 +1,130 @@ + + + + + + + +libfuse: File List + + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
+
File List
+
+
+
Here is a list of all documented files with brief descriptions:
+
[detail level 123]
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 
example
 
cuse.c
 
cuse_client.c
 
hello.c
 
hello_ll.c
 
hello_ll_uds.c
 
invalidate_path.c
 
ioctl.c
 
ioctl.h
 
ioctl_client.c
 
notify_inval_entry.c
 
notify_inval_inode.c
 
notify_store_retrieve.c
 
null.c
 
passthrough.c
 
passthrough_fh.c
 
passthrough_helpers.h
 
passthrough_ll.c
 
poll.c
 
poll_client.c
 
printcap.c
 
include
 
cuse_lowlevel.h
 
fuse.h
 
fuse_common.h
 
fuse_kernel.h
 
fuse_log.h
 
fuse_lowlevel.h
 
fuse_mount_compat.h
 
fuse_opt.h
 
lib
 
modules
 
iconv.c
 
subdir.c
 
buffer.c
 
compat.c
 
cuse_lowlevel.c
 
fuse.c
 
fuse_i.h
 
fuse_log.c
 
fuse_loop.c
 
fuse_loop_mt.c
 
fuse_lowlevel.c
 
fuse_misc.h
 
fuse_opt.c
 
fuse_signals.c
 
fuse_uring.c
 
fuse_uring_i.h
 
helper.c
 
mount.c
 
mount_bsd.c
 
mount_util.c
 
mount_util.h
 
usdt.h
 
util.c
 
util.h
 
test
 
hello.c
 
readdir_inode.c
 
release_unlink_race.c
 
stracedecode.c
 
test_abi.c
 
test_api_30.c
 
test_setattr.c
 
test_signals.c
 
test_syscalls.c
 
test_want_conversion.c
 
test_write_cache.c
 
util
 
fusermount.c
 
mount.fuse.c
+
+
+ + +
+ + diff --git a/doc/html/functions.html b/doc/html/functions.html new file mode 100644 index 0000000..1ec83eb --- /dev/null +++ b/doc/html/functions.html @@ -0,0 +1,263 @@ + + + + + + + +libfuse: Data Fields + + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
+
Here is a list of all documented struct and union fields with links to the struct/union documentation for each field:
+ +

- a -

+ + +

- b -

+ + +

- c -

+ + +

- d -

+ + +

- e -

+ + +

- f -

+ + +

- g -

+ + +

- h -

+ + +

- i -

+ + +

- k -

+ + +

- l -

+ + +

- m -

+ + +

- n -

+ + +

- o -

+ + +

- p -

+ + +

- r -

+ + +

- s -

+ + +

- t -

+ + +

- u -

+ + +

- v -

+ + +

- w -

+
+ + +
+ + diff --git a/doc/html/functions_vars.html b/doc/html/functions_vars.html new file mode 100644 index 0000000..a7d0ef3 --- /dev/null +++ b/doc/html/functions_vars.html @@ -0,0 +1,263 @@ + + + + + + + +libfuse: Data Fields - Variables + + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
+
Here is a list of all documented variables with links to the struct/union documentation for each field:
+ +

- a -

+ + +

- b -

+ + +

- c -

+ + +

- d -

+ + +

- e -

+ + +

- f -

+ + +

- g -

+ + +

- h -

+ + +

- i -

+ + +

- k -

+ + +

- l -

+ + +

- m -

+ + +

- n -

+ + +

- o -

+ + +

- p -

+ + +

- r -

+ + +

- s -

+ + +

- t -

+ + +

- u -

+ + +

- v -

+ + +

- w -

+
+ + +
+ + diff --git a/doc/html/fuse_8c_source.html b/doc/html/fuse_8c_source.html new file mode 100644 index 0000000..16a190f --- /dev/null +++ b/doc/html/fuse_8c_source.html @@ -0,0 +1,5564 @@ + + + + + + + +libfuse: lib/fuse.c Source File + + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
+
fuse.c
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 Implementation of the high-level FUSE API on top of the low-level
+
6 API.
+
7
+
8 This program can be distributed under the terms of the GNU LGPLv2.
+
9 See the file LGPL2.txt
+
10*/
+
11
+
12#define _GNU_SOURCE
+
13#include "fuse.h"
+
14#include <pthread.h>
+
15
+
16#include "fuse_config.h"
+
17#include "fuse_i.h"
+
18#include "fuse_lowlevel.h"
+
19#include "fuse_opt.h"
+
20#include "fuse_misc.h"
+
21#include "fuse_kernel.h"
+
22#include "util.h"
+
23
+
24#include <stdint.h>
+
25#include <stdio.h>
+
26#include <string.h>
+
27#include <stdlib.h>
+
28#include <stddef.h>
+
29#include <stdbool.h>
+
30#include <unistd.h>
+
31#include <time.h>
+
32#include <fcntl.h>
+
33#include <limits.h>
+
34#include <errno.h>
+
35#include <signal.h>
+
36#include <dlfcn.h>
+
37#include <assert.h>
+
38#include <poll.h>
+
39#include <sys/param.h>
+
40#include <sys/uio.h>
+
41#include <sys/time.h>
+
42#include <sys/mman.h>
+
43#include <sys/file.h>
+
44
+
45#define FUSE_NODE_SLAB 1
+
46
+
47#ifndef MAP_ANONYMOUS
+
48#undef FUSE_NODE_SLAB
+
49#endif
+
50
+
51#ifndef RENAME_EXCHANGE
+
52#define RENAME_EXCHANGE (1 << 1) /* Exchange source and dest */
+
53#endif
+
54
+
55#define FUSE_DEFAULT_INTR_SIGNAL SIGUSR1
+
56
+
57#define FUSE_UNKNOWN_INO 0xffffffff
+
58#define OFFSET_MAX 0x7fffffffffffffffLL
+
59
+
60#define NODE_TABLE_MIN_SIZE 8192
+
61
+
62struct fuse_fs {
+
63 struct fuse_operations op;
+
64 void *user_data;
+
65 int debug;
+
66};
+
67
+
68struct fusemod_so {
+
69 void *handle;
+
70 int ctr;
+
71};
+
72
+
73struct lock_queue_element {
+
74 struct lock_queue_element *next;
+
75 pthread_cond_t cond;
+
76 fuse_ino_t nodeid1;
+
77 const char *name1;
+
78 char **path1;
+
79 struct node **wnode1;
+
80 fuse_ino_t nodeid2;
+
81 const char *name2;
+
82 char **path2;
+
83 struct node **wnode2;
+
84 int err;
+
85 bool done : 1;
+
86};
+
87
+
88struct node_table {
+
89 struct node **array;
+
90 size_t use;
+
91 size_t size;
+
92 size_t split;
+
93};
+
94
+
95#define list_entry(ptr, type, member) \
+
96 container_of(ptr, type, member)
+
97
+
98struct list_head {
+
99 struct list_head *next;
+
100 struct list_head *prev;
+
101};
+
102
+
103struct node_slab {
+
104 struct list_head list; /* must be the first member */
+
105 struct list_head freelist;
+
106 int used;
+
107};
+
108
+
109struct fuse {
+
110 struct fuse_session *se;
+
111 struct node_table name_table;
+
112 struct node_table id_table;
+
113 struct list_head lru_table;
+
114 fuse_ino_t ctr;
+
115 unsigned int generation;
+
116 unsigned int hidectr;
+
117 pthread_mutex_t lock;
+
118 struct fuse_config conf;
+
119 int intr_installed;
+
120 struct fuse_fs *fs;
+
121 struct lock_queue_element *lockq;
+
122 int pagesize;
+
123 struct list_head partial_slabs;
+
124 struct list_head full_slabs;
+
125 pthread_t prune_thread;
+
126};
+
127
+
128struct lock {
+
129 int type;
+
130 off_t start;
+
131 off_t end;
+
132 pid_t pid;
+
133 uint64_t owner;
+
134 struct lock *next;
+
135};
+
136
+
137struct node {
+
138 struct node *name_next;
+
139 struct node *id_next;
+
140 fuse_ino_t nodeid;
+
141 unsigned int generation;
+
142 int refctr;
+
143 struct node *parent;
+
144 char *name;
+
145 uint64_t nlookup;
+
146 int open_count;
+
147 struct timespec stat_updated;
+
148 struct timespec mtime;
+
149 off_t size;
+
150 struct lock *locks;
+
151 unsigned int is_hidden : 1;
+
152 unsigned int cache_valid : 1;
+
153 int treelock;
+
154 char inline_name[32];
+
155};
+
156
+
157#define TREELOCK_WRITE -1
+
158#define TREELOCK_WAIT_OFFSET INT_MIN
+
159
+
160struct node_lru {
+
161 struct node node;
+
162 struct list_head lru;
+
163 struct timespec forget_time;
+
164};
+
165
+
166struct fuse_direntry {
+
167 struct stat stat;
+
168 enum fuse_fill_dir_flags flags;
+
169 char *name;
+
170 struct fuse_direntry *next;
+
171};
+
172
+
173struct fuse_dh {
+
174 pthread_mutex_t lock;
+
175 struct fuse *fuse;
+
176 fuse_req_t req;
+
177 char *contents;
+
178 struct fuse_direntry *first;
+
179 struct fuse_direntry **last;
+
180 unsigned len;
+
181 unsigned size;
+
182 unsigned needlen;
+
183 int filled;
+
184 uint64_t fh;
+
185 int error;
+
186 fuse_ino_t nodeid;
+
187};
+
188
+
189struct fuse_context_i {
+
190 struct fuse_context ctx;
+
191 fuse_req_t req;
+
192};
+
193
+
194/* Defined by FUSE_REGISTER_MODULE() in lib/modules/subdir.c and iconv.c. */
+
195extern fuse_module_factory_t fuse_module_subdir_factory;
+
196#ifdef HAVE_ICONV
+
197extern fuse_module_factory_t fuse_module_iconv_factory;
+
198#endif
+
199
+
200static pthread_key_t fuse_context_key;
+
201static pthread_mutex_t fuse_context_lock = PTHREAD_MUTEX_INITIALIZER;
+
202static int fuse_context_ref;
+
203static struct fuse_module *fuse_modules = NULL;
+
204
+
205static int fuse_register_module(const char *name,
+
206 fuse_module_factory_t factory,
+
207 struct fusemod_so *so)
+
208{
+
209 struct fuse_module *mod;
+
210
+
211 mod = calloc(1, sizeof(struct fuse_module));
+
212 if (!mod) {
+
213 fuse_log(FUSE_LOG_ERR, "fuse: failed to allocate module\n");
+
214 return -1;
+
215 }
+
216 mod->name = strdup(name);
+
217 if (!mod->name) {
+
218 fuse_log(FUSE_LOG_ERR, "fuse: failed to allocate module name\n");
+
219 free(mod);
+
220 return -1;
+
221 }
+
222 mod->factory = factory;
+
223 mod->ctr = 0;
+
224 mod->so = so;
+
225 if (mod->so)
+
226 mod->so->ctr++;
+
227 mod->next = fuse_modules;
+
228 fuse_modules = mod;
+
229
+
230 return 0;
+
231}
+
232
+
233static void fuse_unregister_module(struct fuse_module *m)
+
234{
+
235 struct fuse_module **mp;
+
236 for (mp = &fuse_modules; *mp; mp = &(*mp)->next) {
+
237 if (*mp == m) {
+
238 *mp = (*mp)->next;
+
239 break;
+
240 }
+
241 }
+
242 free(m->name);
+
243 free(m);
+
244}
+
245
+
246static int fuse_load_so_module(const char *module)
+
247{
+
248 int ret = -1;
+
249 char *tmp;
+
250 struct fusemod_so *so;
+
251 fuse_module_factory_t *factory;
+
252
+
253 tmp = malloc(strlen(module) + 64);
+
254 if (!tmp) {
+
255 fuse_log(FUSE_LOG_ERR, "fuse: memory allocation failed\n");
+
256 return -1;
+
257 }
+
258 sprintf(tmp, "libfusemod_%s.so", module);
+
259 so = calloc(1, sizeof(struct fusemod_so));
+
260 if (!so) {
+
261 fuse_log(FUSE_LOG_ERR, "fuse: failed to allocate module so\n");
+
262 goto out;
+
263 }
+
264
+
265 so->handle = dlopen(tmp, RTLD_NOW);
+
266 if (so->handle == NULL) {
+
267 fuse_log(FUSE_LOG_ERR, "fuse: dlopen(%s) failed: %s\n",
+
268 tmp, dlerror());
+
269 goto out_free_so;
+
270 }
+
271
+
272 sprintf(tmp, "fuse_module_%s_factory", module);
+
273 factory = (fuse_module_factory_t*)dlsym(so->handle, tmp);
+
274 if (factory == NULL) {
+
275 fuse_log(FUSE_LOG_ERR, "fuse: symbol <%s> not found in module: %s\n",
+
276 tmp, dlerror());
+
277 goto out_dlclose;
+
278 }
+
279 ret = fuse_register_module(module, *factory, so);
+
280 if (ret)
+
281 goto out_dlclose;
+
282
+
283out:
+
284 free(tmp);
+
285 return ret;
+
286
+
287out_dlclose:
+
288 dlclose(so->handle);
+
289out_free_so:
+
290 free(so);
+
291 goto out;
+
292}
+
293
+
294static struct fuse_module *fuse_find_module(const char *module)
+
295{
+
296 struct fuse_module *m;
+
297 for (m = fuse_modules; m; m = m->next) {
+
298 if (strcmp(module, m->name) == 0) {
+
299 m->ctr++;
+
300 break;
+
301 }
+
302 }
+
303 return m;
+
304}
+
305
+
306static struct fuse_module *fuse_get_module(const char *module)
+
307{
+
308 struct fuse_module *m;
+
309
+
310 pthread_mutex_lock(&fuse_context_lock);
+
311 m = fuse_find_module(module);
+
312 if (!m) {
+
313 int err = fuse_load_so_module(module);
+
314 if (!err)
+
315 m = fuse_find_module(module);
+
316 }
+
317 pthread_mutex_unlock(&fuse_context_lock);
+
318 return m;
+
319}
+
320
+
321static void fuse_put_module(struct fuse_module *m)
+
322{
+
323 pthread_mutex_lock(&fuse_context_lock);
+
324 if (m->so)
+
325 assert(m->ctr > 0);
+
326 /* Builtin modules may already have m->ctr == 0 */
+
327 if (m->ctr > 0)
+
328 m->ctr--;
+
329 if (!m->ctr && m->so) {
+
330 struct fusemod_so *so = m->so;
+
331 assert(so->ctr > 0);
+
332 so->ctr--;
+
333 if (!so->ctr) {
+
334 struct fuse_module **mp;
+
335 for (mp = &fuse_modules; *mp;) {
+
336 if ((*mp)->so == so)
+
337 fuse_unregister_module(*mp);
+
338 else
+
339 mp = &(*mp)->next;
+
340 }
+
341 dlclose(so->handle);
+
342 free(so);
+
343 }
+
344 } else if (!m->ctr) {
+
345 fuse_unregister_module(m);
+
346 }
+
347 pthread_mutex_unlock(&fuse_context_lock);
+
348}
+
349
+
350static void init_list_head(struct list_head *list)
+
351{
+
352 list->next = list;
+
353 list->prev = list;
+
354}
+
355
+
356static int list_empty(const struct list_head *head)
+
357{
+
358 return head->next == head;
+
359}
+
360
+
361static void list_add(struct list_head *new, struct list_head *prev,
+
362 struct list_head *next)
+
363{
+
364 next->prev = new;
+
365 new->next = next;
+
366 new->prev = prev;
+
367 prev->next = new;
+
368}
+
369
+
370static inline void list_add_head(struct list_head *new, struct list_head *head)
+
371{
+
372 list_add(new, head, head->next);
+
373}
+
374
+
375static inline void list_add_tail(struct list_head *new, struct list_head *head)
+
376{
+
377 list_add(new, head->prev, head);
+
378}
+
379
+
380static inline void list_del(struct list_head *entry)
+
381{
+
382 struct list_head *prev = entry->prev;
+
383 struct list_head *next = entry->next;
+
384
+
385 next->prev = prev;
+
386 prev->next = next;
+
387}
+
388
+
389static inline int lru_enabled(struct fuse *f)
+
390{
+
391 return f->conf.remember > 0;
+
392}
+
393
+
394static struct node_lru *node_lru(struct node *node)
+
395{
+
396 return (struct node_lru *) node;
+
397}
+
398
+
399static size_t get_node_size(struct fuse *f)
+
400{
+
401 if (lru_enabled(f))
+
402 return sizeof(struct node_lru);
+
403 else
+
404 return sizeof(struct node);
+
405}
+
406
+
407#ifdef FUSE_NODE_SLAB
+
408static struct node_slab *list_to_slab(struct list_head *head)
+
409{
+
410 return (struct node_slab *) head;
+
411}
+
412
+
413static struct node_slab *node_to_slab(struct fuse *f, struct node *node)
+
414{
+
415 return (struct node_slab *) (((uintptr_t) node) & ~((uintptr_t) f->pagesize - 1));
+
416}
+
417
+
418static int alloc_slab(struct fuse *f)
+
419{
+
420 void *mem;
+
421 struct node_slab *slab;
+
422 char *start;
+
423 size_t num;
+
424 size_t i;
+
425 size_t node_size = get_node_size(f);
+
426
+
427 mem = mmap(NULL, f->pagesize, PROT_READ | PROT_WRITE,
+
428 MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+
429
+
430 if (mem == MAP_FAILED)
+
431 return -1;
+
432
+
433 slab = mem;
+
434 init_list_head(&slab->freelist);
+
435 slab->used = 0;
+
436 num = (f->pagesize - sizeof(struct node_slab)) / node_size;
+
437
+
438 start = (char *) mem + f->pagesize - num * node_size;
+
439 for (i = 0; i < num; i++) {
+
440 struct list_head *n;
+
441
+
442 n = (struct list_head *) (start + i * node_size);
+
443 list_add_tail(n, &slab->freelist);
+
444 }
+
445 list_add_tail(&slab->list, &f->partial_slabs);
+
446
+
447 return 0;
+
448}
+
449
+
450static struct node *alloc_node(struct fuse *f)
+
451{
+
452 struct node_slab *slab;
+
453 struct list_head *node;
+
454
+
455 if (list_empty(&f->partial_slabs)) {
+
456 int res = alloc_slab(f);
+
457 if (res != 0)
+
458 return NULL;
+
459 }
+
460 slab = list_to_slab(f->partial_slabs.next);
+
461 slab->used++;
+
462 node = slab->freelist.next;
+
463 list_del(node);
+
464 if (list_empty(&slab->freelist)) {
+
465 list_del(&slab->list);
+
466 list_add_tail(&slab->list, &f->full_slabs);
+
467 }
+
468 memset(node, 0, sizeof(struct node));
+
469
+
470 return (struct node *) node;
+
471}
+
472
+
473static void free_slab(struct fuse *f, struct node_slab *slab)
+
474{
+
475 int res;
+
476
+
477 list_del(&slab->list);
+
478 res = munmap(slab, f->pagesize);
+
479 if (res == -1)
+
480 fuse_log(FUSE_LOG_WARNING, "fuse warning: munmap(%p) failed\n",
+
481 slab);
+
482}
+
483
+
484static void free_node_mem(struct fuse *f, struct node *node)
+
485{
+
486 struct node_slab *slab = node_to_slab(f, node);
+
487 struct list_head *n = (struct list_head *) node;
+
488
+
489 slab->used--;
+
490 if (slab->used) {
+
491 if (list_empty(&slab->freelist)) {
+
492 list_del(&slab->list);
+
493 list_add_tail(&slab->list, &f->partial_slabs);
+
494 }
+
495 list_add_head(n, &slab->freelist);
+
496 } else {
+
497 free_slab(f, slab);
+
498 }
+
499}
+
500#else
+
501static struct node *alloc_node(struct fuse *f)
+
502{
+
503 return (struct node *) calloc(1, get_node_size(f));
+
504}
+
505
+
506static void free_node_mem(struct fuse *f, struct node *node)
+
507{
+
508 (void) f;
+
509 free(node);
+
510}
+
511#endif
+
512
+
513static size_t id_hash(struct fuse *f, fuse_ino_t ino)
+
514{
+
515 uint64_t hash = ((uint32_t) ino * 2654435761U) % f->id_table.size;
+
516 uint64_t oldhash = hash % (f->id_table.size / 2);
+
517
+
518 if (oldhash >= f->id_table.split)
+
519 return oldhash;
+
520 else
+
521 return hash;
+
522}
+
523
+
524static struct node *get_node_nocheck(struct fuse *f, fuse_ino_t nodeid)
+
525{
+
526 size_t hash = id_hash(f, nodeid);
+
527 struct node *node;
+
528
+
529 for (node = f->id_table.array[hash]; node != NULL; node = node->id_next)
+
530 if (node->nodeid == nodeid)
+
531 return node;
+
532
+
533 return NULL;
+
534}
+
535
+
536static struct node *get_node(struct fuse *f, fuse_ino_t nodeid)
+
537{
+
538 struct node *node = get_node_nocheck(f, nodeid);
+
539 if (!node) {
+
540 fuse_log(FUSE_LOG_ERR, "fuse internal error: node %llu not found\n",
+
541 (unsigned long long) nodeid);
+
542 abort();
+
543 }
+
544 return node;
+
545}
+
546
+
547static void curr_time(struct timespec *now);
+
548static double diff_timespec(const struct timespec *t1,
+
549 const struct timespec *t2);
+
550
+
551static void remove_node_lru(struct node *node)
+
552{
+
553 struct node_lru *lnode = node_lru(node);
+
554 list_del(&lnode->lru);
+
555 init_list_head(&lnode->lru);
+
556}
+
557
+
558static void set_forget_time(struct fuse *f, struct node *node)
+
559{
+
560 struct node_lru *lnode = node_lru(node);
+
561
+
562 list_del(&lnode->lru);
+
563 list_add_tail(&lnode->lru, &f->lru_table);
+
564 curr_time(&lnode->forget_time);
+
565}
+
566
+
567static void free_node(struct fuse *f, struct node *node)
+
568{
+
569 if (node->name != node->inline_name)
+
570 free(node->name);
+
571 free_node_mem(f, node);
+
572}
+
573
+
574static void node_table_reduce(struct node_table *t)
+
575{
+
576 size_t newsize = t->size / 2;
+
577 void *newarray;
+
578
+
579 if (newsize < NODE_TABLE_MIN_SIZE)
+
580 return;
+
581
+
582 newarray = realloc(t->array, sizeof(struct node *) * newsize);
+
583 if (newarray != NULL)
+
584 t->array = newarray;
+
585
+
586 t->size = newsize;
+
587 t->split = t->size / 2;
+
588}
+
589
+
590static void remerge_id(struct fuse *f)
+
591{
+
592 struct node_table *t = &f->id_table;
+
593 int iter;
+
594
+
595 if (t->split == 0)
+
596 node_table_reduce(t);
+
597
+
598 for (iter = 8; t->split > 0 && iter; iter--) {
+
599 struct node **upper;
+
600
+
601 t->split--;
+
602 upper = &t->array[t->split + t->size / 2];
+
603 if (*upper) {
+
604 struct node **nodep;
+
605
+
606 for (nodep = &t->array[t->split]; *nodep;
+
607 nodep = &(*nodep)->id_next);
+
608
+
609 *nodep = *upper;
+
610 *upper = NULL;
+
611 break;
+
612 }
+
613 }
+
614}
+
615
+
616static void unhash_id(struct fuse *f, struct node *node)
+
617{
+
618 struct node **nodep = &f->id_table.array[id_hash(f, node->nodeid)];
+
619
+
620 for (; *nodep != NULL; nodep = &(*nodep)->id_next)
+
621 if (*nodep == node) {
+
622 *nodep = node->id_next;
+
623 f->id_table.use--;
+
624
+
625 if(f->id_table.use < f->id_table.size / 4)
+
626 remerge_id(f);
+
627 return;
+
628 }
+
629}
+
630
+
631static int node_table_resize(struct node_table *t)
+
632{
+
633 size_t newsize = t->size * 2;
+
634 void *newarray;
+
635
+
636 newarray = realloc(t->array, sizeof(struct node *) * newsize);
+
637 if (newarray == NULL)
+
638 return -1;
+
639
+
640 t->array = newarray;
+
641 memset(t->array + t->size, 0, t->size * sizeof(struct node *));
+
642 t->size = newsize;
+
643 t->split = 0;
+
644
+
645 return 0;
+
646}
+
647
+
648static void rehash_id(struct fuse *f)
+
649{
+
650 struct node_table *t = &f->id_table;
+
651 struct node **nodep;
+
652 struct node **next;
+
653 size_t hash;
+
654
+
655 if (t->split == t->size / 2)
+
656 return;
+
657
+
658 hash = t->split;
+
659 t->split++;
+
660 for (nodep = &t->array[hash]; *nodep != NULL; nodep = next) {
+
661 struct node *node = *nodep;
+
662 size_t newhash = id_hash(f, node->nodeid);
+
663
+
664 if (newhash != hash) {
+
665 next = nodep;
+
666 *nodep = node->id_next;
+
667 node->id_next = t->array[newhash];
+
668 t->array[newhash] = node;
+
669 } else {
+
670 next = &node->id_next;
+
671 }
+
672 }
+
673 if (t->split == t->size / 2)
+
674 node_table_resize(t);
+
675}
+
676
+
677static void hash_id(struct fuse *f, struct node *node)
+
678{
+
679 size_t hash = id_hash(f, node->nodeid);
+
680 node->id_next = f->id_table.array[hash];
+
681 f->id_table.array[hash] = node;
+
682 f->id_table.use++;
+
683
+
684 if (f->id_table.use >= f->id_table.size / 2)
+
685 rehash_id(f);
+
686}
+
687
+
688static size_t name_hash(struct fuse *f, fuse_ino_t parent,
+
689 const char *name)
+
690{
+
691 uint64_t hash = parent;
+
692 uint64_t oldhash;
+
693
+
694 for (; *name; name++)
+
695 hash = hash * 31 + (unsigned char) *name;
+
696
+
697 hash %= f->name_table.size;
+
698 oldhash = hash % (f->name_table.size / 2);
+
699 if (oldhash >= f->name_table.split)
+
700 return oldhash;
+
701 else
+
702 return hash;
+
703}
+
704
+
705static void unref_node(struct fuse *f, struct node *node);
+
706
+
707static void remerge_name(struct fuse *f)
+
708{
+
709 struct node_table *t = &f->name_table;
+
710 int iter;
+
711
+
712 if (t->split == 0)
+
713 node_table_reduce(t);
+
714
+
715 for (iter = 8; t->split > 0 && iter; iter--) {
+
716 struct node **upper;
+
717
+
718 t->split--;
+
719 upper = &t->array[t->split + t->size / 2];
+
720 if (*upper) {
+
721 struct node **nodep;
+
722
+
723 for (nodep = &t->array[t->split]; *nodep;
+
724 nodep = &(*nodep)->name_next);
+
725
+
726 *nodep = *upper;
+
727 *upper = NULL;
+
728 break;
+
729 }
+
730 }
+
731}
+
732
+
733static void unhash_name(struct fuse *f, struct node *node)
+
734{
+
735 if (node->name) {
+
736 size_t hash = name_hash(f, node->parent->nodeid, node->name);
+
737 struct node **nodep = &f->name_table.array[hash];
+
738
+
739 for (; *nodep != NULL; nodep = &(*nodep)->name_next)
+
740 if (*nodep == node) {
+
741 *nodep = node->name_next;
+
742 node->name_next = NULL;
+
743 unref_node(f, node->parent);
+
744 if (node->name != node->inline_name)
+
745 free(node->name);
+
746 node->name = NULL;
+
747 node->parent = NULL;
+
748 f->name_table.use--;
+
749
+
750 if (f->name_table.use < f->name_table.size / 4)
+
751 remerge_name(f);
+
752 return;
+
753 }
+
754 fuse_log(FUSE_LOG_ERR,
+
755 "fuse internal error: unable to unhash node: %llu\n",
+
756 (unsigned long long) node->nodeid);
+
757 abort();
+
758 }
+
759}
+
760
+
761static void rehash_name(struct fuse *f)
+
762{
+
763 struct node_table *t = &f->name_table;
+
764 struct node **nodep;
+
765 struct node **next;
+
766 size_t hash;
+
767
+
768 if (t->split == t->size / 2)
+
769 return;
+
770
+
771 hash = t->split;
+
772 t->split++;
+
773 for (nodep = &t->array[hash]; *nodep != NULL; nodep = next) {
+
774 struct node *node = *nodep;
+
775 size_t newhash = name_hash(f, node->parent->nodeid, node->name);
+
776
+
777 if (newhash != hash) {
+
778 next = nodep;
+
779 *nodep = node->name_next;
+
780 node->name_next = t->array[newhash];
+
781 t->array[newhash] = node;
+
782 } else {
+
783 next = &node->name_next;
+
784 }
+
785 }
+
786 if (t->split == t->size / 2)
+
787 node_table_resize(t);
+
788}
+
789
+
790static int hash_name(struct fuse *f, struct node *node, fuse_ino_t parentid,
+
791 const char *name)
+
792{
+
793 size_t hash = name_hash(f, parentid, name);
+
794 struct node *parent = get_node(f, parentid);
+
795 if (strlen(name) < sizeof(node->inline_name)) {
+
796 strcpy(node->inline_name, name);
+
797 node->name = node->inline_name;
+
798 } else {
+
799 node->name = strdup(name);
+
800 if (node->name == NULL)
+
801 return -1;
+
802 }
+
803
+
804 parent->refctr ++;
+
805 node->parent = parent;
+
806 node->name_next = f->name_table.array[hash];
+
807 f->name_table.array[hash] = node;
+
808 f->name_table.use++;
+
809
+
810 if (f->name_table.use >= f->name_table.size / 2)
+
811 rehash_name(f);
+
812
+
813 return 0;
+
814}
+
815
+
816static void delete_node(struct fuse *f, struct node *node)
+
817{
+
818 if (f->conf.debug)
+
819 fuse_log(FUSE_LOG_DEBUG, "DELETE: %llu\n",
+
820 (unsigned long long) node->nodeid);
+
821
+
822 assert(node->treelock == 0);
+
823 unhash_name(f, node);
+
824 if (lru_enabled(f))
+
825 remove_node_lru(node);
+
826 unhash_id(f, node);
+
827 free_node(f, node);
+
828}
+
829
+
830static void unref_node(struct fuse *f, struct node *node)
+
831{
+
832 assert(node->refctr > 0);
+
833 node->refctr --;
+
834 if (!node->refctr)
+
835 delete_node(f, node);
+
836}
+
837
+
838static fuse_ino_t next_id(struct fuse *f)
+
839{
+
840 do {
+
841 f->ctr = (f->ctr + 1) & 0xffffffff;
+
842 if (!f->ctr)
+
843 f->generation ++;
+
844 } while (f->ctr == 0 || f->ctr == FUSE_UNKNOWN_INO ||
+
845 get_node_nocheck(f, f->ctr) != NULL);
+
846 return f->ctr;
+
847}
+
848
+
849static struct node *lookup_node(struct fuse *f, fuse_ino_t parent,
+
850 const char *name)
+
851{
+
852 size_t hash = name_hash(f, parent, name);
+
853 struct node *node;
+
854
+
855 for (node = f->name_table.array[hash]; node != NULL; node = node->name_next)
+
856 if (node->parent->nodeid == parent &&
+
857 strcmp(node->name, name) == 0)
+
858 return node;
+
859
+
860 return NULL;
+
861}
+
862
+
863static void inc_nlookup(struct node *node)
+
864{
+
865 if (!node->nlookup)
+
866 node->refctr++;
+
867 node->nlookup++;
+
868}
+
869
+
870static struct node *find_node(struct fuse *f, fuse_ino_t parent,
+
871 const char *name)
+
872{
+
873 struct node *node;
+
874
+
875 pthread_mutex_lock(&f->lock);
+
876 if (!name)
+
877 node = get_node(f, parent);
+
878 else
+
879 node = lookup_node(f, parent, name);
+
880 if (node == NULL) {
+
881 node = alloc_node(f);
+
882 if (node == NULL)
+
883 goto out_err;
+
884
+
885 node->nodeid = next_id(f);
+
886 node->generation = f->generation;
+
887 if (f->conf.remember)
+
888 inc_nlookup(node);
+
889
+
890 if (hash_name(f, node, parent, name) == -1) {
+
891 free_node(f, node);
+
892 node = NULL;
+
893 goto out_err;
+
894 }
+
895 hash_id(f, node);
+
896 if (lru_enabled(f)) {
+
897 struct node_lru *lnode = node_lru(node);
+
898 init_list_head(&lnode->lru);
+
899 }
+
900 } else if (lru_enabled(f) && node->nlookup == 1) {
+
901 remove_node_lru(node);
+
902 }
+
903 inc_nlookup(node);
+
904out_err:
+
905 pthread_mutex_unlock(&f->lock);
+
906 return node;
+
907}
+
908
+
909static int lookup_path_in_cache(struct fuse *f,
+
910 const char *path, fuse_ino_t *inop)
+
911{
+
912 char *tmp = strdup(path);
+
913 if (!tmp)
+
914 return -ENOMEM;
+
915
+
916 pthread_mutex_lock(&f->lock);
+
917 fuse_ino_t ino = FUSE_ROOT_ID;
+
918
+
919 int err = 0;
+
920 char *save_ptr;
+
921 char *path_element = strtok_r(tmp, "/", &save_ptr);
+
922 while (path_element != NULL) {
+
923 struct node *node = lookup_node(f, ino, path_element);
+
924 if (node == NULL) {
+
925 err = -ENOENT;
+
926 break;
+
927 }
+
928 ino = node->nodeid;
+
929 path_element = strtok_r(NULL, "/", &save_ptr);
+
930 }
+
931 pthread_mutex_unlock(&f->lock);
+
932 free(tmp);
+
933
+
934 if (!err)
+
935 *inop = ino;
+
936 return err;
+
937}
+
938
+
939static char *add_name(char **buf, unsigned *bufsize, char *s, const char *name)
+
940{
+
941 size_t len = strlen(name);
+
942
+
943 if (s - len <= *buf) {
+
944 unsigned pathlen = *bufsize - (s - *buf);
+
945 unsigned newbufsize = *bufsize;
+
946 char *newbuf;
+
947
+
948 while (newbufsize < pathlen + len + 1) {
+
949 if (newbufsize >= 0x80000000)
+
950 newbufsize = 0xffffffff;
+
951 else
+
952 newbufsize *= 2;
+
953 }
+
954
+
955 newbuf = realloc(*buf, newbufsize);
+
956 if (newbuf == NULL)
+
957 return NULL;
+
958
+
959 *buf = newbuf;
+
960 s = newbuf + newbufsize - pathlen;
+
961 memmove(s, newbuf + *bufsize - pathlen, pathlen);
+
962 *bufsize = newbufsize;
+
963 }
+
964 s -= len;
+
965 memcpy(s, name, len);
+
966 s--;
+
967 *s = '/';
+
968
+
969 return s;
+
970}
+
971
+
972static void unlock_path(struct fuse *f, fuse_ino_t nodeid, struct node *wnode,
+
973 struct node *end)
+
974{
+
975 struct node *node;
+
976
+
977 if (wnode) {
+
978 assert(wnode->treelock == TREELOCK_WRITE);
+
979 wnode->treelock = 0;
+
980 }
+
981
+
982 for (node = get_node(f, nodeid);
+
983 node != end && node->nodeid != FUSE_ROOT_ID; node = node->parent) {
+
984 assert(node->treelock != 0);
+
985 assert(node->treelock != TREELOCK_WAIT_OFFSET);
+
986 assert(node->treelock != TREELOCK_WRITE);
+
987 node->treelock--;
+
988 if (node->treelock == TREELOCK_WAIT_OFFSET)
+
989 node->treelock = 0;
+
990 }
+
991}
+
992
+
993static int try_get_path(struct fuse *f, fuse_ino_t nodeid, const char *name,
+
994 char **path, struct node **wnodep, bool need_lock)
+
995{
+
996 unsigned bufsize = 256;
+
997 char *buf;
+
998 char *s;
+
999 struct node *node;
+
1000 struct node *wnode = NULL;
+
1001 int err;
+
1002
+
1003 *path = NULL;
+
1004
+
1005 err = -ENOMEM;
+
1006 buf = malloc(bufsize);
+
1007 if (buf == NULL)
+
1008 goto out_err;
+
1009
+
1010 s = buf + bufsize - 1;
+
1011 *s = '\0';
+
1012
+
1013 if (name != NULL) {
+
1014 s = add_name(&buf, &bufsize, s, name);
+
1015 err = -ENOMEM;
+
1016 if (s == NULL)
+
1017 goto out_free;
+
1018 }
+
1019
+
1020 if (wnodep) {
+
1021 assert(need_lock);
+
1022 assert(name != NULL);
+
1023 wnode = lookup_node(f, nodeid, name);
+
1024 if (wnode) {
+
1025 if (wnode->treelock != 0) {
+
1026 if (wnode->treelock > 0)
+
1027 wnode->treelock += TREELOCK_WAIT_OFFSET;
+
1028 err = -EAGAIN;
+
1029 goto out_free;
+
1030 }
+
1031 wnode->treelock = TREELOCK_WRITE;
+
1032 }
+
1033 }
+
1034
+
1035 for (node = get_node(f, nodeid); node->nodeid != FUSE_ROOT_ID;
+
1036 node = node->parent) {
+
1037 err = -ESTALE;
+
1038 if (node->name == NULL || node->parent == NULL)
+
1039 goto out_unlock;
+
1040
+
1041 err = -ENOMEM;
+
1042 s = add_name(&buf, &bufsize, s, node->name);
+
1043 if (s == NULL)
+
1044 goto out_unlock;
+
1045
+
1046 if (need_lock) {
+
1047 err = -EAGAIN;
+
1048 if (node->treelock < 0)
+
1049 goto out_unlock;
+
1050
+
1051 node->treelock++;
+
1052 }
+
1053 }
+
1054
+
1055 if (s[0])
+
1056 memmove(buf, s, bufsize - (s - buf));
+
1057 else
+
1058 strcpy(buf, "/");
+
1059
+
1060 *path = buf;
+
1061 if (wnodep)
+
1062 *wnodep = wnode;
+
1063
+
1064 return 0;
+
1065
+
1066 out_unlock:
+
1067 if (need_lock)
+
1068 unlock_path(f, nodeid, wnode, node);
+
1069 out_free:
+
1070 free(buf);
+
1071
+
1072 out_err:
+
1073 return err;
+
1074}
+
1075
+
1076static int try_get_path2(struct fuse *f, fuse_ino_t nodeid1, const char *name1,
+
1077 fuse_ino_t nodeid2, const char *name2,
+
1078 char **path1, char **path2,
+
1079 struct node **wnode1, struct node **wnode2)
+
1080{
+
1081 int err;
+
1082
+
1083 /* FIXME: locking two paths needs deadlock checking */
+
1084 err = try_get_path(f, nodeid1, name1, path1, wnode1, true);
+
1085 if (!err) {
+
1086 err = try_get_path(f, nodeid2, name2, path2, wnode2, true);
+
1087 if (err) {
+
1088 struct node *wn1 = wnode1 ? *wnode1 : NULL;
+
1089
+
1090 unlock_path(f, nodeid1, wn1, NULL);
+
1091 free(*path1);
+
1092 }
+
1093 }
+
1094 return err;
+
1095}
+
1096
+
1097static void queue_element_wakeup(struct fuse *f, struct lock_queue_element *qe)
+
1098{
+
1099 int err;
+
1100
+
1101 if (!qe->path1) {
+
1102 /* Just waiting for it to be unlocked */
+
1103 if (get_node(f, qe->nodeid1)->treelock == 0)
+
1104 pthread_cond_signal(&qe->cond);
+
1105
+
1106 return;
+
1107 }
+
1108
+
1109 if (qe->done)
+
1110 return; // Don't try to double-lock the element
+
1111
+
1112 if (!qe->path2) {
+
1113 err = try_get_path(f, qe->nodeid1, qe->name1, qe->path1,
+
1114 qe->wnode1, true);
+
1115 } else {
+
1116 err = try_get_path2(f, qe->nodeid1, qe->name1, qe->nodeid2,
+
1117 qe->name2, qe->path1, qe->path2, qe->wnode1,
+
1118 qe->wnode2);
+
1119 }
+
1120
+
1121 if (err == -EAGAIN)
+
1122 return; /* keep trying */
+
1123
+
1124 qe->err = err;
+
1125 qe->done = true;
+
1126 pthread_cond_signal(&qe->cond);
+
1127}
+
1128
+
1129static void wake_up_queued(struct fuse *f)
+
1130{
+
1131 struct lock_queue_element *qe;
+
1132
+
1133 for (qe = f->lockq; qe != NULL; qe = qe->next)
+
1134 queue_element_wakeup(f, qe);
+
1135}
+
1136
+
1137static void debug_path(struct fuse *f, const char *msg, fuse_ino_t nodeid,
+
1138 const char *name, bool wr)
+
1139{
+
1140 if (f->conf.debug) {
+
1141 struct node *wnode = NULL;
+
1142
+
1143 if (wr)
+
1144 wnode = lookup_node(f, nodeid, name);
+
1145
+
1146 if (wnode) {
+
1147 fuse_log(FUSE_LOG_DEBUG, "%s %llu (w)\n",
+
1148 msg, (unsigned long long) wnode->nodeid);
+
1149 } else {
+
1150 fuse_log(FUSE_LOG_DEBUG, "%s %llu\n",
+
1151 msg, (unsigned long long) nodeid);
+
1152 }
+
1153 }
+
1154}
+
1155
+
1156static void queue_path(struct fuse *f, struct lock_queue_element *qe)
+
1157{
+
1158 struct lock_queue_element **qp;
+
1159
+
1160 qe->done = false;
+
1161 pthread_cond_init(&qe->cond, NULL);
+
1162 qe->next = NULL;
+
1163 for (qp = &f->lockq; *qp != NULL; qp = &(*qp)->next);
+
1164 *qp = qe;
+
1165}
+
1166
+
1167static void dequeue_path(struct fuse *f, struct lock_queue_element *qe)
+
1168{
+
1169 struct lock_queue_element **qp;
+
1170
+
1171 pthread_cond_destroy(&qe->cond);
+
1172 for (qp = &f->lockq; *qp != qe; qp = &(*qp)->next);
+
1173 *qp = qe->next;
+
1174}
+
1175
+
1176static int wait_path(struct fuse *f, struct lock_queue_element *qe)
+
1177{
+
1178 queue_path(f, qe);
+
1179
+
1180 do {
+
1181 pthread_cond_wait(&qe->cond, &f->lock);
+
1182 } while (!qe->done);
+
1183
+
1184 dequeue_path(f, qe);
+
1185
+
1186 return qe->err;
+
1187}
+
1188
+
1189static int get_path_common(struct fuse *f, fuse_ino_t nodeid, const char *name,
+
1190 char **path, struct node **wnode)
+
1191{
+
1192 int err;
+
1193
+
1194 pthread_mutex_lock(&f->lock);
+
1195 err = try_get_path(f, nodeid, name, path, wnode, true);
+
1196 if (err == -EAGAIN) {
+
1197 struct lock_queue_element qe = {
+
1198 .nodeid1 = nodeid,
+
1199 .name1 = name,
+
1200 .path1 = path,
+
1201 .wnode1 = wnode,
+
1202 };
+
1203 debug_path(f, "QUEUE PATH", nodeid, name, !!wnode);
+
1204 err = wait_path(f, &qe);
+
1205 debug_path(f, "DEQUEUE PATH", nodeid, name, !!wnode);
+
1206 }
+
1207 pthread_mutex_unlock(&f->lock);
+
1208
+
1209 return err;
+
1210}
+
1211
+
1212static int get_path(struct fuse *f, fuse_ino_t nodeid, char **path)
+
1213{
+
1214 return get_path_common(f, nodeid, NULL, path, NULL);
+
1215}
+
1216
+
1217static int get_path_nullok(struct fuse *f, fuse_ino_t nodeid, char **path)
+
1218{
+
1219 int err = 0;
+
1220
+
1221 if (f->conf.nullpath_ok) {
+
1222 *path = NULL;
+
1223 } else {
+
1224 err = get_path_common(f, nodeid, NULL, path, NULL);
+
1225 if (err == -ESTALE)
+
1226 err = 0;
+
1227 }
+
1228
+
1229 return err;
+
1230}
+
1231
+
1232static int get_path_name(struct fuse *f, fuse_ino_t nodeid, const char *name,
+
1233 char **path)
+
1234{
+
1235 return get_path_common(f, nodeid, name, path, NULL);
+
1236}
+
1237
+
1238static int get_path_wrlock(struct fuse *f, fuse_ino_t nodeid, const char *name,
+
1239 char **path, struct node **wnode)
+
1240{
+
1241 return get_path_common(f, nodeid, name, path, wnode);
+
1242}
+
1243
+
1244#if defined(__FreeBSD__)
+
1245#define CHECK_DIR_LOOP
+
1246#endif
+
1247
+
1248#if defined(CHECK_DIR_LOOP)
+
1249static int check_dir_loop(struct fuse *f,
+
1250 fuse_ino_t nodeid1, const char *name1,
+
1251 fuse_ino_t nodeid2, const char *name2)
+
1252{
+
1253 struct node *node, *node1, *node2;
+
1254 fuse_ino_t id1, id2;
+
1255
+
1256 node1 = lookup_node(f, nodeid1, name1);
+
1257 id1 = node1 ? node1->nodeid : nodeid1;
+
1258
+
1259 node2 = lookup_node(f, nodeid2, name2);
+
1260 id2 = node2 ? node2->nodeid : nodeid2;
+
1261
+
1262 for (node = get_node(f, id2); node->nodeid != FUSE_ROOT_ID;
+
1263 node = node->parent) {
+
1264 if (node->name == NULL || node->parent == NULL)
+
1265 break;
+
1266
+
1267 if (node->nodeid != id2 && node->nodeid == id1)
+
1268 return -EINVAL;
+
1269 }
+
1270
+
1271 if (node2)
+
1272 {
+
1273 for (node = get_node(f, id1); node->nodeid != FUSE_ROOT_ID;
+
1274 node = node->parent) {
+
1275 if (node->name == NULL || node->parent == NULL)
+
1276 break;
+
1277
+
1278 if (node->nodeid != id1 && node->nodeid == id2)
+
1279 return -ENOTEMPTY;
+
1280 }
+
1281 }
+
1282
+
1283 return 0;
+
1284}
+
1285#endif
+
1286
+
1287static int get_path2(struct fuse *f, fuse_ino_t nodeid1, const char *name1,
+
1288 fuse_ino_t nodeid2, const char *name2,
+
1289 char **path1, char **path2,
+
1290 struct node **wnode1, struct node **wnode2)
+
1291{
+
1292 int err;
+
1293
+
1294 pthread_mutex_lock(&f->lock);
+
1295
+
1296#if defined(CHECK_DIR_LOOP)
+
1297 if (name1)
+
1298 {
+
1299 // called during rename; perform dir loop check
+
1300 err = check_dir_loop(f, nodeid1, name1, nodeid2, name2);
+
1301 if (err)
+
1302 goto out_unlock;
+
1303 }
+
1304#endif
+
1305
+
1306 err = try_get_path2(f, nodeid1, name1, nodeid2, name2,
+
1307 path1, path2, wnode1, wnode2);
+
1308 if (err == -EAGAIN) {
+
1309 struct lock_queue_element qe = {
+
1310 .nodeid1 = nodeid1,
+
1311 .name1 = name1,
+
1312 .path1 = path1,
+
1313 .wnode1 = wnode1,
+
1314 .nodeid2 = nodeid2,
+
1315 .name2 = name2,
+
1316 .path2 = path2,
+
1317 .wnode2 = wnode2,
+
1318 };
+
1319
+
1320 debug_path(f, "QUEUE PATH1", nodeid1, name1, !!wnode1);
+
1321 debug_path(f, " PATH2", nodeid2, name2, !!wnode2);
+
1322 err = wait_path(f, &qe);
+
1323 debug_path(f, "DEQUEUE PATH1", nodeid1, name1, !!wnode1);
+
1324 debug_path(f, " PATH2", nodeid2, name2, !!wnode2);
+
1325 }
+
1326
+
1327#if defined(CHECK_DIR_LOOP)
+
1328out_unlock:
+
1329#endif
+
1330 pthread_mutex_unlock(&f->lock);
+
1331
+
1332 return err;
+
1333}
+
1334
+
1335static void free_path_wrlock(struct fuse *f, fuse_ino_t nodeid,
+
1336 struct node *wnode, char *path)
+
1337{
+
1338 pthread_mutex_lock(&f->lock);
+
1339 unlock_path(f, nodeid, wnode, NULL);
+
1340 if (f->lockq)
+
1341 wake_up_queued(f);
+
1342 pthread_mutex_unlock(&f->lock);
+
1343 free(path);
+
1344}
+
1345
+
1346static void free_path(struct fuse *f, fuse_ino_t nodeid, char *path)
+
1347{
+
1348 if (path)
+
1349 free_path_wrlock(f, nodeid, NULL, path);
+
1350}
+
1351
+
1352static void free_path2(struct fuse *f, fuse_ino_t nodeid1, fuse_ino_t nodeid2,
+
1353 struct node *wnode1, struct node *wnode2,
+
1354 char *path1, char *path2)
+
1355{
+
1356 pthread_mutex_lock(&f->lock);
+
1357 unlock_path(f, nodeid1, wnode1, NULL);
+
1358 unlock_path(f, nodeid2, wnode2, NULL);
+
1359 wake_up_queued(f);
+
1360 pthread_mutex_unlock(&f->lock);
+
1361 free(path1);
+
1362 free(path2);
+
1363}
+
1364
+
1365static void forget_node(struct fuse *f, fuse_ino_t nodeid, uint64_t nlookup)
+
1366{
+
1367 struct node *node;
+
1368 if (nodeid == FUSE_ROOT_ID)
+
1369 return;
+
1370 pthread_mutex_lock(&f->lock);
+
1371 node = get_node(f, nodeid);
+
1372
+
1373 /*
+
1374 * Node may still be locked due to interrupt idiocy in open,
+
1375 * create and opendir
+
1376 */
+
1377 while (node->nlookup == nlookup && node->treelock) {
+
1378 struct lock_queue_element qe = {
+
1379 .nodeid1 = nodeid,
+
1380 };
+
1381
+
1382 debug_path(f, "QUEUE PATH (forget)", nodeid, NULL, false);
+
1383 queue_path(f, &qe);
+
1384
+
1385 do {
+
1386 pthread_cond_wait(&qe.cond, &f->lock);
+
1387 } while (node->nlookup == nlookup && node->treelock);
+
1388
+
1389 dequeue_path(f, &qe);
+
1390 debug_path(f, "DEQUEUE_PATH (forget)", nodeid, NULL, false);
+
1391 }
+
1392
+
1393 assert(node->nlookup >= nlookup);
+
1394 node->nlookup -= nlookup;
+
1395 if (!node->nlookup) {
+
1396 unref_node(f, node);
+
1397 } else if (lru_enabled(f) && node->nlookup == 1) {
+
1398 set_forget_time(f, node);
+
1399 }
+
1400 pthread_mutex_unlock(&f->lock);
+
1401}
+
1402
+
1403static void unlink_node(struct fuse *f, struct node *node)
+
1404{
+
1405 if (f->conf.remember) {
+
1406 assert(node->nlookup > 1);
+
1407 node->nlookup--;
+
1408 }
+
1409 unhash_name(f, node);
+
1410}
+
1411
+
1412static void remove_node(struct fuse *f, fuse_ino_t dir, const char *name)
+
1413{
+
1414 struct node *node;
+
1415
+
1416 pthread_mutex_lock(&f->lock);
+
1417 node = lookup_node(f, dir, name);
+
1418 if (node != NULL)
+
1419 unlink_node(f, node);
+
1420 pthread_mutex_unlock(&f->lock);
+
1421}
+
1422
+
1423static int rename_node(struct fuse *f, fuse_ino_t olddir, const char *oldname,
+
1424 fuse_ino_t newdir, const char *newname, int hide)
+
1425{
+
1426 struct node *node;
+
1427 struct node *newnode;
+
1428 int err = 0;
+
1429
+
1430 pthread_mutex_lock(&f->lock);
+
1431 node = lookup_node(f, olddir, oldname);
+
1432 newnode = lookup_node(f, newdir, newname);
+
1433 if (node == NULL)
+
1434 goto out;
+
1435
+
1436 if (newnode != NULL) {
+
1437 if (hide) {
+
1438 fuse_log(FUSE_LOG_ERR, "fuse: hidden file got created during hiding\n");
+
1439 err = -EBUSY;
+
1440 goto out;
+
1441 }
+
1442 unlink_node(f, newnode);
+
1443 }
+
1444
+
1445 unhash_name(f, node);
+
1446 if (hash_name(f, node, newdir, newname) == -1) {
+
1447 err = -ENOMEM;
+
1448 goto out;
+
1449 }
+
1450
+
1451 if (hide)
+
1452 node->is_hidden = 1;
+
1453
+
1454out:
+
1455 pthread_mutex_unlock(&f->lock);
+
1456 return err;
+
1457}
+
1458
+
1459static int exchange_node(struct fuse *f, fuse_ino_t olddir, const char *oldname,
+
1460 fuse_ino_t newdir, const char *newname)
+
1461{
+
1462 struct node *oldnode;
+
1463 struct node *newnode;
+
1464 int err;
+
1465
+
1466 pthread_mutex_lock(&f->lock);
+
1467 oldnode = lookup_node(f, olddir, oldname);
+
1468 newnode = lookup_node(f, newdir, newname);
+
1469
+
1470 if (oldnode)
+
1471 unhash_name(f, oldnode);
+
1472 if (newnode)
+
1473 unhash_name(f, newnode);
+
1474
+
1475 err = -ENOMEM;
+
1476 if (oldnode) {
+
1477 if (hash_name(f, oldnode, newdir, newname) == -1)
+
1478 goto out;
+
1479 }
+
1480 if (newnode) {
+
1481 if (hash_name(f, newnode, olddir, oldname) == -1)
+
1482 goto out;
+
1483 }
+
1484 err = 0;
+
1485out:
+
1486 pthread_mutex_unlock(&f->lock);
+
1487 return err;
+
1488}
+
1489
+
1490static void set_stat(struct fuse *f, fuse_ino_t nodeid, struct stat *stbuf)
+
1491{
+
1492 if (!f->conf.use_ino)
+
1493 stbuf->st_ino = nodeid;
+
1494 if (f->conf.set_mode) {
+
1495 if (f->conf.dmask && S_ISDIR(stbuf->st_mode))
+
1496 stbuf->st_mode = (stbuf->st_mode & S_IFMT) |
+
1497 (0777 & ~f->conf.dmask);
+
1498 else if (f->conf.fmask)
+
1499 stbuf->st_mode = (stbuf->st_mode & S_IFMT) |
+
1500 (0777 & ~f->conf.fmask);
+
1501 else
+
1502 stbuf->st_mode = (stbuf->st_mode & S_IFMT) |
+
1503 (0777 & ~f->conf.umask);
+
1504 }
+
1505 if (f->conf.set_uid)
+
1506 stbuf->st_uid = f->conf.uid;
+
1507 if (f->conf.set_gid)
+
1508 stbuf->st_gid = f->conf.gid;
+
1509}
+
1510
+
1511#ifdef HAVE_STATX
+
1512static void set_statx(struct fuse *f, fuse_ino_t nodeid, struct statx *stxbuf)
+
1513{
+
1514 if (!f->conf.use_ino)
+
1515 stxbuf->stx_ino = nodeid;
+
1516 if (f->conf.set_mode) {
+
1517 if (f->conf.dmask && S_ISDIR(stxbuf->stx_mode))
+
1518 stxbuf->stx_mode = (stxbuf->stx_mode & S_IFMT) |
+
1519 (0777 & ~f->conf.dmask);
+
1520 else if (f->conf.fmask)
+
1521 stxbuf->stx_mode = (stxbuf->stx_mode & S_IFMT) |
+
1522 (0777 & ~f->conf.fmask);
+
1523 else
+
1524 stxbuf->stx_mode = (stxbuf->stx_mode & S_IFMT) |
+
1525 (0777 & ~f->conf.umask);
+
1526 }
+
1527 if (f->conf.set_uid)
+
1528 stxbuf->stx_uid = f->conf.uid;
+
1529 if (f->conf.set_gid)
+
1530 stxbuf->stx_gid = f->conf.gid;
+
1531}
+
1532#endif
+
1533
+
1534static struct fuse *req_fuse(fuse_req_t req)
+
1535{
+
1536 return (struct fuse *) fuse_req_userdata(req);
+
1537}
+
1538
+
1539static void fuse_intr_sighandler(int sig)
+
1540{
+
1541 (void) sig;
+
1542 /* Nothing to do */
+
1543}
+
1544
+
1545struct fuse_intr_data {
+
1546 pthread_t id;
+
1547 pthread_cond_t cond;
+
1548 int finished;
+
1549};
+
1550
+
1551static void fuse_interrupt(fuse_req_t req, void *d_)
+
1552{
+
1553 struct fuse_intr_data *d = d_;
+
1554 struct fuse *f = req_fuse(req);
+
1555
+
1556 if (d->id == pthread_self())
+
1557 return;
+
1558
+
1559 pthread_mutex_lock(&f->lock);
+
1560 while (!d->finished) {
+
1561 struct timeval now;
+
1562 struct timespec timeout;
+
1563
+
1564 pthread_kill(d->id, f->conf.intr_signal);
+
1565 gettimeofday(&now, NULL);
+
1566 timeout.tv_sec = now.tv_sec + 1;
+
1567 timeout.tv_nsec = now.tv_usec * 1000;
+
1568 pthread_cond_timedwait(&d->cond, &f->lock, &timeout);
+
1569 }
+
1570 pthread_mutex_unlock(&f->lock);
+
1571}
+
1572
+
1573static void fuse_do_finish_interrupt(struct fuse *f, fuse_req_t req,
+
1574 struct fuse_intr_data *d)
+
1575{
+
1576 pthread_mutex_lock(&f->lock);
+
1577 d->finished = 1;
+
1578 pthread_cond_broadcast(&d->cond);
+
1579 pthread_mutex_unlock(&f->lock);
+
1580 fuse_req_interrupt_func(req, NULL, NULL);
+
1581 pthread_cond_destroy(&d->cond);
+
1582}
+
1583
+
1584static void fuse_do_prepare_interrupt(fuse_req_t req, struct fuse_intr_data *d)
+
1585{
+
1586 d->id = pthread_self();
+
1587 pthread_cond_init(&d->cond, NULL);
+
1588 d->finished = 0;
+
1589 fuse_req_interrupt_func(req, fuse_interrupt, d);
+
1590}
+
1591
+
1592static inline void fuse_finish_interrupt(struct fuse *f, fuse_req_t req,
+
1593 struct fuse_intr_data *d)
+
1594{
+
1595 if (f->conf.intr)
+
1596 fuse_do_finish_interrupt(f, req, d);
+
1597}
+
1598
+
1599static inline void fuse_prepare_interrupt(struct fuse *f, fuse_req_t req,
+
1600 struct fuse_intr_data *d)
+
1601{
+
1602 if (f->conf.intr)
+
1603 fuse_do_prepare_interrupt(req, d);
+
1604}
+
1605
+
1606static const char* file_info_string(struct fuse_file_info *fi,
+
1607 char* buf, size_t len)
+
1608{
+
1609 if(fi == NULL)
+
1610 return "NULL";
+
1611 snprintf(buf, len, "%llu", (unsigned long long) fi->fh);
+
1612 return buf;
+
1613}
+
1614
+
1615int fuse_fs_getattr(struct fuse_fs *fs, const char *path, struct stat *buf,
+
1616 struct fuse_file_info *fi)
+
1617{
+
1618 fuse_get_context()->private_data = fs->user_data;
+
1619 if (!fs->op.getattr)
+
1620 return -ENOSYS;
+
1621
+
1622 if (fs->debug) {
+
1623 char buf[10];
+
1624
+
1625 fuse_log(FUSE_LOG_DEBUG, "getattr[%s] %s\n",
+
1626 file_info_string(fi, buf, sizeof(buf)),
+
1627 path);
+
1628 }
+
1629 return fs->op.getattr(path, buf, fi);
+
1630}
+
1631
+
1632int fuse_fs_rename(struct fuse_fs *fs, const char *oldpath,
+
1633 const char *newpath, unsigned int flags)
+
1634{
+
1635 fuse_get_context()->private_data = fs->user_data;
+
1636 if (!fs->op.rename)
+
1637 return -ENOSYS;
+
1638 if (fs->debug)
+
1639 fuse_log(FUSE_LOG_DEBUG, "rename %s %s 0x%x\n", oldpath, newpath,
+
1640 flags);
+
1641
+
1642 return fs->op.rename(oldpath, newpath, flags);
+
1643}
+
1644
+
1645int fuse_fs_unlink(struct fuse_fs *fs, const char *path)
+
1646{
+
1647 fuse_get_context()->private_data = fs->user_data;
+
1648 if (!fs->op.unlink)
+
1649 return -ENOSYS;
+
1650 if (fs->debug)
+
1651 fuse_log(FUSE_LOG_DEBUG, "unlink %s\n", path);
+
1652
+
1653 return fs->op.unlink(path);
+
1654}
+
1655
+
1656int fuse_fs_rmdir(struct fuse_fs *fs, const char *path)
+
1657{
+
1658 fuse_get_context()->private_data = fs->user_data;
+
1659 if (!fs->op.rmdir)
+
1660 return -ENOSYS;
+
1661 if (fs->debug)
+
1662 fuse_log(FUSE_LOG_DEBUG, "rmdir %s\n", path);
+
1663
+
1664 return fs->op.rmdir(path);
+
1665}
+
1666
+
1667int fuse_fs_symlink(struct fuse_fs *fs, const char *linkname, const char *path)
+
1668{
+
1669 fuse_get_context()->private_data = fs->user_data;
+
1670 if (!fs->op.symlink)
+
1671 return -ENOSYS;
+
1672 if (fs->debug)
+
1673 fuse_log(FUSE_LOG_DEBUG, "symlink %s %s\n", linkname, path);
+
1674
+
1675 return fs->op.symlink(linkname, path);
+
1676}
+
1677
+
1678int fuse_fs_link(struct fuse_fs *fs, const char *oldpath, const char *newpath)
+
1679{
+
1680 fuse_get_context()->private_data = fs->user_data;
+
1681 if (!fs->op.link)
+
1682 return -ENOSYS;
+
1683 if (fs->debug)
+
1684 fuse_log(FUSE_LOG_DEBUG, "link %s %s\n", oldpath, newpath);
+
1685
+
1686 return fs->op.link(oldpath, newpath);
+
1687}
+
1688
+
1689int fuse_fs_release(struct fuse_fs *fs, const char *path,
+
1690 struct fuse_file_info *fi)
+
1691{
+
1692 fuse_get_context()->private_data = fs->user_data;
+
1693 if (!fs->op.release)
+
1694 return 0;
+
1695
+
1696 if (fs->debug)
+
1697 fuse_log(FUSE_LOG_DEBUG, "release%s[%llu] flags: 0x%x\n",
+
1698 fi->flush ? "+flush" : "",
+
1699 (unsigned long long) fi->fh, fi->flags);
+
1700
+
1701 return fs->op.release(path, fi);
+
1702}
+
1703
+
1704int fuse_fs_opendir(struct fuse_fs *fs, const char *path,
+
1705 struct fuse_file_info *fi)
+
1706{
+
1707 int err;
+
1708
+
1709 fuse_get_context()->private_data = fs->user_data;
+
1710 if (!fs->op.opendir)
+
1711 return 0;
+
1712
+
1713 if (fs->debug)
+
1714 fuse_log(FUSE_LOG_DEBUG, "opendir flags: 0x%x %s\n", fi->flags,
+
1715 path);
+
1716
+
1717 err = fs->op.opendir(path, fi);
+
1718
+
1719 if (fs->debug && !err)
+
1720 fuse_log(FUSE_LOG_DEBUG, " opendir[%llu] flags: 0x%x %s\n",
+
1721 (unsigned long long) fi->fh, fi->flags, path);
+
1722
+
1723 return err;
+
1724}
+
1725
+
1726int fuse_fs_open(struct fuse_fs *fs, const char *path,
+
1727 struct fuse_file_info *fi)
+
1728{
+
1729 int err;
+
1730
+
1731 fuse_get_context()->private_data = fs->user_data;
+
1732 if (!fs->op.open)
+
1733 return 0;
+
1734
+
1735 if (fs->debug)
+
1736 fuse_log(FUSE_LOG_DEBUG, "open flags: 0x%x %s\n", fi->flags,
+
1737 path);
+
1738
+
1739 err = fs->op.open(path, fi);
+
1740
+
1741 if (fs->debug && !err)
+
1742 fuse_log(FUSE_LOG_DEBUG, " open[%llu] flags: 0x%x %s\n",
+
1743 (unsigned long long) fi->fh, fi->flags, path);
+
1744
+
1745 return err;
+
1746}
+
1747
+
1748static void fuse_free_buf(struct fuse_bufvec *buf)
+
1749{
+
1750 if (buf != NULL) {
+
1751 size_t i;
+
1752
+
1753 for (i = 0; i < buf->count; i++)
+
1754 if (!(buf->buf[i].flags & FUSE_BUF_IS_FD))
+
1755 free(buf->buf[i].mem);
+
1756 free(buf);
+
1757 }
+
1758}
+
1759
+
1760int fuse_fs_read_buf(struct fuse_fs *fs, const char *path,
+
1761 struct fuse_bufvec **bufp, size_t size, off_t off,
+
1762 struct fuse_file_info *fi)
+
1763{
+
1764 int res;
+
1765
+
1766 fuse_get_context()->private_data = fs->user_data;
+
1767 if (!fs->op.read && !fs->op.read_buf)
+
1768 return -ENOSYS;
+
1769
+
1770 if (fs->debug)
+
1771 fuse_log(FUSE_LOG_DEBUG,
+
1772 "read[%llu] %zu bytes from %llu flags: 0x%x\n",
+
1773 (unsigned long long) fi->fh,
+
1774 size, (unsigned long long) off, fi->flags);
+
1775
+
1776 if (fs->op.read_buf) {
+
1777 res = fs->op.read_buf(path, bufp, size, off, fi);
+
1778 } else {
+
1779 struct fuse_bufvec *buf;
+
1780 void *mem;
+
1781
+
1782 buf = malloc(sizeof(struct fuse_bufvec));
+
1783 if (buf == NULL)
+
1784 return -ENOMEM;
+
1785
+
1786 mem = malloc(size);
+
1787 if (mem == NULL) {
+
1788 free(buf);
+
1789 return -ENOMEM;
+
1790 }
+
1791 *buf = FUSE_BUFVEC_INIT(size);
+
1792 buf->buf[0].mem = mem;
+
1793 *bufp = buf;
+
1794
+
1795 res = fs->op.read(path, mem, size, off, fi);
+
1796 if (res >= 0)
+
1797 buf->buf[0].size = res;
+
1798 }
+
1799
+
1800 if (fs->debug && res >= 0)
+
1801 fuse_log(FUSE_LOG_DEBUG, " read[%llu] %zu bytes from %llu\n",
+
1802 (unsigned long long) fi->fh,
+
1803 fuse_buf_size(*bufp),
+
1804 (unsigned long long) off);
+
1805 if (res >= 0 && fuse_buf_size(*bufp) > size)
+
1806 fuse_log(FUSE_LOG_ERR, "fuse: read too many bytes\n");
+
1807
+
1808 if (res < 0)
+
1809 return res;
+
1810
+
1811 return 0;
+
1812}
+
1813
+
1814int fuse_fs_read(struct fuse_fs *fs, const char *path, char *mem, size_t size,
+
1815 off_t off, struct fuse_file_info *fi)
+
1816{
+
1817 int res;
+
1818
+
1819 fuse_get_context()->private_data = fs->user_data;
+
1820 if (!fs->op.read && !fs->op.read_buf)
+
1821 return -ENOSYS;
+
1822
+
1823 if (fs->debug)
+
1824 fuse_log(FUSE_LOG_DEBUG,
+
1825 "read[%llu] %zu bytes from %llu flags: 0x%x\n",
+
1826 (unsigned long long) fi->fh,
+
1827 size, (unsigned long long) off, fi->flags);
+
1828
+
1829 if (fs->op.read_buf) {
+
1830 struct fuse_bufvec *buf = NULL;
+
1831
+
1832 res = fs->op.read_buf(path, &buf, size, off, fi);
+
1833 if (res == 0) {
+
1834 struct fuse_bufvec dst = FUSE_BUFVEC_INIT(size);
+
1835
+
1836 dst.buf[0].mem = mem;
+
1837 res = fuse_buf_copy(&dst, buf, 0);
+
1838 }
+
1839 fuse_free_buf(buf);
+
1840 } else {
+
1841 res = fs->op.read(path, mem, size, off, fi);
+
1842 }
+
1843
+
1844 if (fs->debug && res >= 0)
+
1845 fuse_log(FUSE_LOG_DEBUG, " read[%llu] %u bytes from %llu\n",
+
1846 (unsigned long long) fi->fh,
+
1847 res,
+
1848 (unsigned long long) off);
+
1849 if (res >= 0 && res > (int) size)
+
1850 fuse_log(FUSE_LOG_ERR, "fuse: read too many bytes\n");
+
1851
+
1852 return res;
+
1853}
+
1854
+
1855int fuse_fs_write_buf(struct fuse_fs *fs, const char *path,
+
1856 struct fuse_bufvec *buf, off_t off,
+
1857 struct fuse_file_info *fi)
+
1858{
+
1859 int res;
+
1860 size_t size;
+
1861
+
1862 fuse_get_context()->private_data = fs->user_data;
+
1863 if (!fs->op.write_buf && !fs->op.write)
+
1864 return -ENOSYS;
+
1865
+
1866 size = fuse_buf_size(buf);
+
1867 assert(buf->idx == 0 && buf->off == 0);
+
1868 if (fs->debug)
+
1869 fuse_log(FUSE_LOG_DEBUG,
+
1870 "write%s[%llu] %zu bytes to %llu flags: 0x%x\n",
+
1871 fi->writepage ? "page" : "",
+
1872 (unsigned long long) fi->fh,
+
1873 size,
+
1874 (unsigned long long) off,
+
1875 fi->flags);
+
1876
+
1877 if (fs->op.write_buf) {
+
1878 res = fs->op.write_buf(path, buf, off, fi);
+
1879 } else {
+
1880 void *mem = NULL;
+
1881 struct fuse_buf *flatbuf;
+
1882 struct fuse_bufvec tmp = FUSE_BUFVEC_INIT(size);
+
1883
+
1884 if (buf->count == 1 &&
+
1885 !(buf->buf[0].flags & FUSE_BUF_IS_FD)) {
+
1886 flatbuf = &buf->buf[0];
+
1887 } else {
+
1888 res = -ENOMEM;
+
1889 mem = malloc(size);
+
1890 if (mem == NULL)
+
1891 goto out;
+
1892
+
1893 tmp.buf[0].mem = mem;
+
1894 res = fuse_buf_copy(&tmp, buf, 0);
+
1895 if (res <= 0)
+
1896 goto out_free;
+
1897
+
1898 tmp.buf[0].size = res;
+
1899 flatbuf = &tmp.buf[0];
+
1900 }
+
1901
+
1902 res = fs->op.write(path, flatbuf->mem, flatbuf->size,
+
1903 off, fi);
+
1904out_free:
+
1905 free(mem);
+
1906 }
+
1907out:
+
1908 if (fs->debug && res >= 0)
+
1909 fuse_log(FUSE_LOG_DEBUG, " write%s[%llu] %u bytes to %llu\n",
+
1910 fi->writepage ? "page" : "",
+
1911 (unsigned long long) fi->fh, res,
+
1912 (unsigned long long) off);
+
1913 if (res > (int) size)
+
1914 fuse_log(FUSE_LOG_ERR, "fuse: wrote too many bytes\n");
+
1915
+
1916 return res;
+
1917}
+
1918
+
1919int fuse_fs_write(struct fuse_fs *fs, const char *path, const char *mem,
+
1920 size_t size, off_t off, struct fuse_file_info *fi)
+
1921{
+
1922 struct fuse_bufvec bufv = FUSE_BUFVEC_INIT(size);
+
1923
+
1924 bufv.buf[0].mem = (void *) mem;
+
1925
+
1926 return fuse_fs_write_buf(fs, path, &bufv, off, fi);
+
1927}
+
1928
+
1929int fuse_fs_fsync(struct fuse_fs *fs, const char *path, int datasync,
+
1930 struct fuse_file_info *fi)
+
1931{
+
1932 fuse_get_context()->private_data = fs->user_data;
+
1933 if (!fs->op.fsync)
+
1934 return -ENOSYS;
+
1935
+
1936 if (fs->debug)
+
1937 fuse_log(FUSE_LOG_DEBUG, "fsync[%llu] datasync: %i\n",
+
1938 (unsigned long long) fi->fh, datasync);
+
1939
+
1940 return fs->op.fsync(path, datasync, fi);
+
1941}
+
1942
+
1943int fuse_fs_fsyncdir(struct fuse_fs *fs, const char *path, int datasync,
+
1944 struct fuse_file_info *fi)
+
1945{
+
1946 fuse_get_context()->private_data = fs->user_data;
+
1947 if (!fs->op.fsyncdir)
+
1948 return -ENOSYS;
+
1949
+
1950 if (fs->debug)
+
1951 fuse_log(FUSE_LOG_DEBUG, "fsyncdir[%llu] datasync: %i\n",
+
1952 (unsigned long long) fi->fh, datasync);
+
1953
+
1954 return fs->op.fsyncdir(path, datasync, fi);
+
1955}
+
1956
+
1957int fuse_fs_flush(struct fuse_fs *fs, const char *path,
+
1958 struct fuse_file_info *fi)
+
1959{
+
1960 fuse_get_context()->private_data = fs->user_data;
+
1961 if (!fs->op.flush)
+
1962 return -ENOSYS;
+
1963 if (fs->debug)
+
1964 fuse_log(FUSE_LOG_DEBUG, "flush[%llu]\n",
+
1965 (unsigned long long) fi->fh);
+
1966
+
1967 return fs->op.flush(path, fi);
+
1968}
+
1969
+
1970int fuse_fs_statfs(struct fuse_fs *fs, const char *path, struct statvfs *buf)
+
1971{
+
1972 fuse_get_context()->private_data = fs->user_data;
+
1973 if (fs->op.statfs) {
+
1974 if (fs->debug)
+
1975 fuse_log(FUSE_LOG_DEBUG, "statfs %s\n", path);
+
1976
+
1977 return fs->op.statfs(path, buf);
+
1978 } else {
+
1979 buf->f_namemax = 255;
+
1980 buf->f_bsize = 512;
+
1981 return 0;
+
1982 }
+
1983}
+
1984
+
1985int fuse_fs_releasedir(struct fuse_fs *fs, const char *path,
+
1986 struct fuse_file_info *fi)
+
1987{
+
1988 fuse_get_context()->private_data = fs->user_data;
+
1989 if (!fs->op.releasedir)
+
1990 return 0;
+
1991
+
1992 if (fs->debug)
+
1993 fuse_log(FUSE_LOG_DEBUG, "releasedir[%llu] flags: 0x%x\n",
+
1994 (unsigned long long) fi->fh, fi->flags);
+
1995
+
1996 return fs->op.releasedir(path, fi);
+
1997}
+
1998
+
1999int fuse_fs_readdir(struct fuse_fs *fs, const char *path, void *buf,
+
2000 fuse_fill_dir_t filler, off_t off,
+
2001 struct fuse_file_info *fi,
+
2002 enum fuse_readdir_flags flags)
+
2003{
+
2004 fuse_get_context()->private_data = fs->user_data;
+
2005 if (!fs->op.readdir)
+
2006 return -ENOSYS;
+
2007 if (fs->debug) {
+
2008 fuse_log(FUSE_LOG_DEBUG, "readdir%s[%llu] from %llu\n",
+
2009 (flags & FUSE_READDIR_PLUS) ? "plus" : "",
+
2010 (unsigned long long) fi->fh,
+
2011 (unsigned long long) off);
+
2012 }
+
2013
+
2014 return fs->op.readdir(path, buf, filler, off, fi, flags);
+
2015}
+
2016
+
2017int fuse_fs_create(struct fuse_fs *fs, const char *path, mode_t mode,
+
2018 struct fuse_file_info *fi)
+
2019{
+
2020 int err;
+
2021
+
2022 fuse_get_context()->private_data = fs->user_data;
+
2023 if (!fs->op.create)
+
2024 return -ENOSYS;
+
2025
+
2026 if (fs->debug)
+
2027 fuse_log(FUSE_LOG_DEBUG,
+
2028 "create flags: 0x%x %s 0%o umask=0%03o\n",
+
2029 fi->flags, path, mode,
+
2030 fuse_get_context()->umask);
+
2031
+
2032 err = fs->op.create(path, mode, fi);
+
2033
+
2034 if (fs->debug && !err)
+
2035 fuse_log(FUSE_LOG_DEBUG, " create[%llu] flags: 0x%x %s\n",
+
2036 (unsigned long long) fi->fh, fi->flags, path);
+
2037
+
2038 return err;
+
2039}
+
2040
+
2041int fuse_fs_lock(struct fuse_fs *fs, const char *path,
+
2042 struct fuse_file_info *fi, int cmd, struct flock *lock)
+
2043{
+
2044 fuse_get_context()->private_data = fs->user_data;
+
2045 if (!fs->op.lock)
+
2046 return -ENOSYS;
+
2047
+
2048 if (fs->debug)
+
2049 fuse_log(FUSE_LOG_DEBUG, "lock[%llu] %s %s start: %llu len: %llu pid: %llu\n",
+
2050 (unsigned long long) fi->fh,
+
2051 (cmd == F_GETLK ? "F_GETLK" :
+
2052 (cmd == F_SETLK ? "F_SETLK" :
+
2053 (cmd == F_SETLKW ? "F_SETLKW" : "???"))),
+
2054 (lock->l_type == F_RDLCK ? "F_RDLCK" :
+
2055 (lock->l_type == F_WRLCK ? "F_WRLCK" :
+
2056 (lock->l_type == F_UNLCK ? "F_UNLCK" :
+
2057 "???"))),
+
2058 (unsigned long long) lock->l_start,
+
2059 (unsigned long long) lock->l_len,
+
2060 (unsigned long long) lock->l_pid);
+
2061
+
2062 return fs->op.lock(path, fi, cmd, lock);
+
2063}
+
2064
+
2065int fuse_fs_flock(struct fuse_fs *fs, const char *path,
+
2066 struct fuse_file_info *fi, int op)
+
2067{
+
2068 fuse_get_context()->private_data = fs->user_data;
+
2069 if (!fs->op.flock)
+
2070 return -ENOSYS;
+
2071
+
2072 if (fs->debug) {
+
2073 int xop = op & ~LOCK_NB;
+
2074
+
2075 fuse_log(FUSE_LOG_DEBUG, "lock[%llu] %s%s\n",
+
2076 (unsigned long long) fi->fh,
+
2077 xop == LOCK_SH ? "LOCK_SH" :
+
2078 (xop == LOCK_EX ? "LOCK_EX" :
+
2079 (xop == LOCK_UN ? "LOCK_UN" : "???")),
+
2080 (op & LOCK_NB) ? "|LOCK_NB" : "");
+
2081 }
+
2082 return fs->op.flock(path, fi, op);
+
2083}
+
2084
+
2085int fuse_fs_chown(struct fuse_fs *fs, const char *path, uid_t uid,
+
2086 gid_t gid, struct fuse_file_info *fi)
+
2087{
+
2088 fuse_get_context()->private_data = fs->user_data;
+
2089 if (!fs->op.chown)
+
2090 return -ENOSYS;
+
2091 if (fs->debug) {
+
2092 char buf[10];
+
2093
+
2094 fuse_log(FUSE_LOG_DEBUG, "chown[%s] %s %lu %lu\n",
+
2095 file_info_string(fi, buf, sizeof(buf)),
+
2096 path, (unsigned long) uid, (unsigned long) gid);
+
2097 }
+
2098 return fs->op.chown(path, uid, gid, fi);
+
2099}
+
2100
+
2101int fuse_fs_truncate(struct fuse_fs *fs, const char *path, off_t size,
+
2102 struct fuse_file_info *fi)
+
2103{
+
2104 fuse_get_context()->private_data = fs->user_data;
+
2105 if (!fs->op.truncate)
+
2106 return -ENOSYS;
+
2107 if (fs->debug) {
+
2108 char buf[10];
+
2109
+
2110 fuse_log(FUSE_LOG_DEBUG, "truncate[%s] %llu\n",
+
2111 file_info_string(fi, buf, sizeof(buf)),
+
2112 (unsigned long long) size);
+
2113 }
+
2114 return fs->op.truncate(path, size, fi);
+
2115}
+
2116
+
2117int fuse_fs_utimens(struct fuse_fs *fs, const char *path,
+
2118 const struct timespec tv[2], struct fuse_file_info *fi)
+
2119{
+
2120 fuse_get_context()->private_data = fs->user_data;
+
2121 if (!fs->op.utimens)
+
2122 return -ENOSYS;
+
2123 if (fs->debug) {
+
2124 char buf[10];
+
2125
+
2126 fuse_log(FUSE_LOG_DEBUG, "utimens[%s] %s %jd.%09ld %jd.%09ld\n",
+
2127 file_info_string(fi, buf, sizeof(buf)),
+
2128 path, (intmax_t)tv[0].tv_sec, tv[0].tv_nsec,
+
2129 (intmax_t)tv[1].tv_sec, tv[1].tv_nsec);
+
2130 }
+
2131 return fs->op.utimens(path, tv, fi);
+
2132}
+
2133
+
2134int fuse_fs_access(struct fuse_fs *fs, const char *path, int mask)
+
2135{
+
2136 fuse_get_context()->private_data = fs->user_data;
+
2137 if (!fs->op.access)
+
2138 return -ENOSYS;
+
2139 if (fs->debug)
+
2140 fuse_log(FUSE_LOG_DEBUG, "access %s 0%o\n", path, mask);
+
2141
+
2142 return fs->op.access(path, mask);
+
2143}
+
2144
+
2145int fuse_fs_readlink(struct fuse_fs *fs, const char *path, char *buf,
+
2146 size_t len)
+
2147{
+
2148 fuse_get_context()->private_data = fs->user_data;
+
2149 if (!fs->op.readlink)
+
2150 return -ENOSYS;
+
2151 if (fs->debug)
+
2152 fuse_log(FUSE_LOG_DEBUG, "readlink %s %lu\n", path,
+
2153 (unsigned long) len);
+
2154
+
2155 return fs->op.readlink(path, buf, len);
+
2156}
+
2157
+
2158int fuse_fs_mknod(struct fuse_fs *fs, const char *path, mode_t mode,
+
2159 dev_t rdev)
+
2160{
+
2161 fuse_get_context()->private_data = fs->user_data;
+
2162 if (!fs->op.mknod)
+
2163 return -ENOSYS;
+
2164 if (fs->debug)
+
2165 fuse_log(FUSE_LOG_DEBUG, "mknod %s 0%o 0x%llx umask=0%03o\n",
+
2166 path, mode, (unsigned long long) rdev,
+
2167 fuse_get_context()->umask);
+
2168
+
2169 return fs->op.mknod(path, mode, rdev);
+
2170}
+
2171
+
2172int fuse_fs_mkdir(struct fuse_fs *fs, const char *path, mode_t mode)
+
2173{
+
2174 fuse_get_context()->private_data = fs->user_data;
+
2175 if (!fs->op.mkdir)
+
2176 return -ENOSYS;
+
2177 if (fs->debug)
+
2178 fuse_log(FUSE_LOG_DEBUG, "mkdir %s 0%o umask=0%03o\n",
+
2179 path, mode, fuse_get_context()->umask);
+
2180
+
2181 return fs->op.mkdir(path, mode);
+
2182}
+
2183
+
2184int fuse_fs_setxattr(struct fuse_fs *fs, const char *path, const char *name,
+
2185 const char *value, size_t size, int flags)
+
2186{
+
2187 fuse_get_context()->private_data = fs->user_data;
+
2188 if (!fs->op.setxattr)
+
2189 return -ENOSYS;
+
2190 if (fs->debug)
+
2191 fuse_log(FUSE_LOG_DEBUG, "setxattr %s %s %lu 0x%x\n",
+
2192 path, name, (unsigned long) size, flags);
+
2193
+
2194 return fs->op.setxattr(path, name, value, size, flags);
+
2195}
+
2196
+
2197int fuse_fs_getxattr(struct fuse_fs *fs, const char *path, const char *name,
+
2198 char *value, size_t size)
+
2199{
+
2200 fuse_get_context()->private_data = fs->user_data;
+
2201 if (!fs->op.getxattr)
+
2202 return -ENOSYS;
+
2203 if (fs->debug)
+
2204 fuse_log(FUSE_LOG_DEBUG, "getxattr %s %s %lu\n",
+
2205 path, name, (unsigned long) size);
+
2206
+
2207 return fs->op.getxattr(path, name, value, size);
+
2208}
+
2209
+
2210int fuse_fs_listxattr(struct fuse_fs *fs, const char *path, char *list,
+
2211 size_t size)
+
2212{
+
2213 fuse_get_context()->private_data = fs->user_data;
+
2214 if (!fs->op.listxattr)
+
2215 return -ENOSYS;
+
2216 if (fs->debug)
+
2217 fuse_log(FUSE_LOG_DEBUG, "listxattr %s %lu\n",
+
2218 path, (unsigned long) size);
+
2219
+
2220 return fs->op.listxattr(path, list, size);
+
2221}
+
2222
+
2223int fuse_fs_bmap(struct fuse_fs *fs, const char *path, size_t blocksize,
+
2224 uint64_t *idx)
+
2225{
+
2226 fuse_get_context()->private_data = fs->user_data;
+
2227 if (!fs->op.bmap)
+
2228 return -ENOSYS;
+
2229 if (fs->debug)
+
2230 fuse_log(FUSE_LOG_DEBUG, "bmap %s blocksize: %lu index: %llu\n",
+
2231 path, (unsigned long) blocksize,
+
2232 (unsigned long long) *idx);
+
2233
+
2234 return fs->op.bmap(path, blocksize, idx);
+
2235}
+
2236
+
2237int fuse_fs_removexattr(struct fuse_fs *fs, const char *path, const char *name)
+
2238{
+
2239 fuse_get_context()->private_data = fs->user_data;
+
2240 if (!fs->op.removexattr)
+
2241 return -ENOSYS;
+
2242 if (fs->debug)
+
2243 fuse_log(FUSE_LOG_DEBUG, "removexattr %s %s\n", path, name);
+
2244
+
2245 return fs->op.removexattr(path, name);
+
2246}
+
2247
+
2248int fuse_fs_ioctl(struct fuse_fs *fs, const char *path, unsigned int cmd,
+
2249 void *arg, struct fuse_file_info *fi, unsigned int flags,
+
2250 void *data)
+
2251{
+
2252 fuse_get_context()->private_data = fs->user_data;
+
2253 if (!fs->op.ioctl)
+
2254 return -ENOSYS;
+
2255 if (fs->debug)
+
2256 fuse_log(FUSE_LOG_DEBUG, "ioctl[%llu] 0x%x flags: 0x%x\n",
+
2257 (unsigned long long) fi->fh, cmd, flags);
+
2258
+
2259 return fs->op.ioctl(path, cmd, arg, fi, flags, data);
+
2260}
+
2261
+
2262int fuse_fs_poll(struct fuse_fs *fs, const char *path,
+
2263 struct fuse_file_info *fi, struct fuse_pollhandle *ph,
+
2264 unsigned *reventsp)
+
2265{
+
2266 int res;
+
2267
+
2268 fuse_get_context()->private_data = fs->user_data;
+
2269
+
2270 if (!fs->op.poll) {
+ +
2272 return -ENOSYS;
+
2273 }
+
2274
+
2275 if (fs->debug)
+
2276 fuse_log(FUSE_LOG_DEBUG, "poll[%llu] ph: %p, events 0x%x\n",
+
2277 (unsigned long long) fi->fh, ph,
+
2278 fi->poll_events);
+
2279
+
2280 res = fs->op.poll(path, fi, ph, reventsp);
+
2281
+
2282 if (fs->debug && !res)
+
2283 fuse_log(FUSE_LOG_DEBUG, " poll[%llu] revents: 0x%x\n",
+
2284 (unsigned long long) fi->fh, *reventsp);
+
2285
+
2286 return res;
+
2287}
+
2288
+
2289int fuse_fs_fallocate(struct fuse_fs *fs, const char *path, int mode,
+
2290 off_t offset, off_t length, struct fuse_file_info *fi)
+
2291{
+
2292 fuse_get_context()->private_data = fs->user_data;
+
2293 if (!fs->op.fallocate)
+
2294 return -ENOSYS;
+
2295 if (fs->debug)
+
2296 fuse_log(FUSE_LOG_DEBUG, "fallocate %s mode %x, offset: %llu, length: %llu\n",
+
2297 path,
+
2298 mode,
+
2299 (unsigned long long) offset,
+
2300 (unsigned long long) length);
+
2301
+
2302 return fs->op.fallocate(path, mode, offset, length, fi);
+
2303}
+
2304
+
2305ssize_t fuse_fs_copy_file_range(struct fuse_fs *fs, const char *path_in,
+
2306 struct fuse_file_info *fi_in, off_t off_in,
+
2307 const char *path_out,
+
2308 struct fuse_file_info *fi_out, off_t off_out,
+
2309 size_t len, int flags)
+
2310{
+
2311 fuse_get_context()->private_data = fs->user_data;
+
2312 if (!fs->op.copy_file_range)
+
2313 return -ENOSYS;
+
2314 if (fs->debug)
+
2315 fuse_log(FUSE_LOG_DEBUG, "copy_file_range from %s:%llu to "
+
2316 "%s:%llu, length: %llu\n",
+
2317 path_in,
+
2318 (unsigned long long) off_in,
+
2319 path_out,
+
2320 (unsigned long long) off_out,
+
2321 (unsigned long long) len);
+
2322
+
2323 return fs->op.copy_file_range(path_in, fi_in, off_in, path_out,
+
2324 fi_out, off_out, len, flags);
+
2325}
+
2326
+
2327off_t fuse_fs_lseek(struct fuse_fs *fs, const char *path, off_t off, int whence,
+
2328 struct fuse_file_info *fi)
+
2329{
+
2330 fuse_get_context()->private_data = fs->user_data;
+
2331 if (!fs->op.lseek)
+
2332 return -ENOSYS;
+
2333 if (fs->debug) {
+
2334 char buf[10];
+
2335
+
2336 fuse_log(FUSE_LOG_DEBUG, "lseek[%s] %llu %d\n",
+
2337 file_info_string(fi, buf, sizeof(buf)),
+
2338 (unsigned long long) off, whence);
+
2339 }
+
2340 return fs->op.lseek(path, off, whence, fi);
+
2341}
+
2342
+
2343#ifdef HAVE_STATX
+
2344int fuse_fs_statx(struct fuse_fs *fs, const char *path, int flags, int mask,
+
2345 struct statx *stxbuf, struct fuse_file_info *fi)
+
2346{
+
2347 fuse_get_context()->private_data = fs->user_data;
+
2348 if (fs->op.statx) {
+
2349 if (fs->debug) {
+
2350 char buf[10];
+
2351
+
2352 fuse_log(FUSE_LOG_DEBUG, "statx[%s] %s %d %d\n",
+
2353 file_info_string(fi, buf, sizeof(buf)), path,
+
2354 flags, mask);
+
2355 }
+
2356 return fs->op.statx(path, flags, mask, stxbuf, fi);
+
2357 }
+
2358
+
2359 return -ENOSYS;
+
2360}
+
2361#else
+
2362int fuse_fs_statx(struct fuse_fs *fs, const char *path, int flags, int mask,
+
2363 struct statx *stxbuf, struct fuse_file_info *fi)
+
2364{
+
2365 (void)fs;
+
2366 (void)path;
+
2367 (void)flags;
+
2368 (void)mask;
+
2369 (void)stxbuf;
+
2370 (void)fi;
+
2371
+
2372 return -ENOSYS;
+
2373}
+
2374#endif
+
2375
+
2376static int is_open(struct fuse *f, fuse_ino_t dir, const char *name)
+
2377{
+
2378 struct node *node;
+
2379 int isopen = 0;
+
2380 pthread_mutex_lock(&f->lock);
+
2381 node = lookup_node(f, dir, name);
+
2382 if (node && node->open_count > 0)
+
2383 isopen = 1;
+
2384 pthread_mutex_unlock(&f->lock);
+
2385 return isopen;
+
2386}
+
2387
+
2388static char *hidden_name(struct fuse *f, fuse_ino_t dir, const char *oldname,
+
2389 char *newname, size_t bufsize)
+
2390{
+
2391 struct stat buf;
+
2392 struct node *node;
+
2393 struct node *newnode;
+
2394 char *newpath;
+
2395 int res;
+
2396 int failctr = 10;
+
2397
+
2398 do {
+
2399 pthread_mutex_lock(&f->lock);
+
2400 node = lookup_node(f, dir, oldname);
+
2401 if (node == NULL) {
+
2402 pthread_mutex_unlock(&f->lock);
+
2403 return NULL;
+
2404 }
+
2405 do {
+
2406 f->hidectr ++;
+
2407 snprintf(newname, bufsize, ".fuse_hidden%08x%08x",
+
2408 (unsigned int) node->nodeid, f->hidectr);
+
2409 newnode = lookup_node(f, dir, newname);
+
2410 } while(newnode);
+
2411
+
2412 res = try_get_path(f, dir, newname, &newpath, NULL, false);
+
2413 pthread_mutex_unlock(&f->lock);
+
2414 if (res)
+
2415 break;
+
2416
+
2417 memset(&buf, 0, sizeof(buf));
+
2418 res = fuse_fs_getattr(f->fs, newpath, &buf, NULL);
+
2419 if (res == -ENOENT)
+
2420 break;
+
2421 free(newpath);
+
2422 newpath = NULL;
+
2423 } while(res == 0 && --failctr);
+
2424
+
2425 return newpath;
+
2426}
+
2427
+
2428static int hide_node(struct fuse *f, const char *oldpath,
+
2429 fuse_ino_t dir, const char *oldname)
+
2430{
+
2431 char newname[64];
+
2432 char *newpath;
+
2433 int err = -EBUSY;
+
2434
+
2435 newpath = hidden_name(f, dir, oldname, newname, sizeof(newname));
+
2436 if (newpath) {
+
2437 err = fuse_fs_rename(f->fs, oldpath, newpath, 0);
+
2438 if (!err)
+
2439 err = rename_node(f, dir, oldname, dir, newname, 1);
+
2440 free(newpath);
+
2441 }
+
2442 return err;
+
2443}
+
2444
+
2445static int mtime_eq(const struct stat *stbuf, const struct timespec *ts)
+
2446{
+
2447 return stbuf->st_mtime == ts->tv_sec &&
+
2448 ST_MTIM_NSEC(stbuf) == ts->tv_nsec;
+
2449}
+
2450
+
2451#ifndef CLOCK_MONOTONIC
+
2452#define CLOCK_MONOTONIC CLOCK_REALTIME
+
2453#endif
+
2454
+
2455static void curr_time(struct timespec *now)
+
2456{
+
2457 static clockid_t clockid = CLOCK_MONOTONIC;
+
2458 int res = clock_gettime(clockid, now);
+
2459 if (res == -1 && errno == EINVAL) {
+
2460 clockid = CLOCK_REALTIME;
+
2461 res = clock_gettime(clockid, now);
+
2462 }
+
2463 if (res == -1) {
+
2464 perror("fuse: clock_gettime");
+
2465 abort();
+
2466 }
+
2467}
+
2468
+
2469static void update_stat(struct node *node, const struct stat *stbuf)
+
2470{
+
2471 if (node->cache_valid && (!mtime_eq(stbuf, &node->mtime) ||
+
2472 stbuf->st_size != node->size))
+
2473 node->cache_valid = 0;
+
2474 node->mtime.tv_sec = stbuf->st_mtime;
+
2475 node->mtime.tv_nsec = ST_MTIM_NSEC(stbuf);
+
2476 node->size = stbuf->st_size;
+
2477 curr_time(&node->stat_updated);
+
2478}
+
2479
+
2480static int do_lookup(struct fuse *f, fuse_ino_t nodeid, const char *name,
+
2481 struct fuse_entry_param *e)
+
2482{
+
2483 struct node *node;
+
2484
+
2485 node = find_node(f, nodeid, name);
+
2486 if (node == NULL)
+
2487 return -ENOMEM;
+
2488
+
2489 e->ino = node->nodeid;
+
2490 e->generation = node->generation;
+
2491 e->entry_timeout = f->conf.entry_timeout;
+
2492 e->attr_timeout = f->conf.attr_timeout;
+
2493 if (f->conf.auto_cache) {
+
2494 pthread_mutex_lock(&f->lock);
+
2495 update_stat(node, &e->attr);
+
2496 pthread_mutex_unlock(&f->lock);
+
2497 }
+
2498 set_stat(f, e->ino, &e->attr);
+
2499 return 0;
+
2500}
+
2501
+
2502static int lookup_path(struct fuse *f, fuse_ino_t nodeid,
+
2503 const char *name, const char *path,
+
2504 struct fuse_entry_param *e, struct fuse_file_info *fi)
+
2505{
+
2506 int res;
+
2507
+
2508 memset(e, 0, sizeof(struct fuse_entry_param));
+
2509 res = fuse_fs_getattr(f->fs, path, &e->attr, fi);
+
2510 if (res == 0) {
+
2511 res = do_lookup(f, nodeid, name, e);
+
2512 if (res == 0 && f->conf.debug) {
+
2513 fuse_log(FUSE_LOG_DEBUG, " NODEID: %llu\n",
+
2514 (unsigned long long) e->ino);
+
2515 }
+
2516 }
+
2517 return res;
+
2518}
+
2519
+
2520static struct fuse_context_i *fuse_get_context_internal(void)
+
2521{
+
2522 return (struct fuse_context_i *) pthread_getspecific(fuse_context_key);
+
2523}
+
2524
+
2525static struct fuse_context_i *fuse_create_context(struct fuse *f)
+
2526{
+
2527 struct fuse_context_i *c = fuse_get_context_internal();
+
2528 if (c == NULL) {
+
2529 c = (struct fuse_context_i *)
+
2530 calloc(1, sizeof(struct fuse_context_i));
+
2531 if (c == NULL) {
+
2532 /* This is hard to deal with properly, so just
+
2533 abort. If memory is so low that the
+
2534 context cannot be allocated, there's not
+
2535 much hope for the filesystem anyway */
+
2536 fuse_log(FUSE_LOG_ERR, "fuse: failed to allocate thread specific data\n");
+
2537 abort();
+
2538 }
+
2539 if (pthread_setspecific(fuse_context_key, c) != 0) {
+
2540 fuse_log(FUSE_LOG_ERR, "fuse: failed to set thread specific data\n");
+
2541 free(c);
+
2542 abort();
+
2543 }
+
2544 } else {
+
2545 memset(c, 0, sizeof(*c));
+
2546 }
+
2547 c->ctx.fuse = f;
+
2548
+
2549 return c;
+
2550}
+
2551
+
2552static void fuse_freecontext(void *data)
+
2553{
+
2554 free(data);
+
2555}
+
2556
+
2557static int fuse_create_context_key(void)
+
2558{
+
2559 int err = 0;
+
2560 pthread_mutex_lock(&fuse_context_lock);
+
2561 if (!fuse_context_ref) {
+
2562 err = pthread_key_create(&fuse_context_key, fuse_freecontext);
+
2563 if (err) {
+
2564 fuse_log(FUSE_LOG_ERR, "fuse: failed to create thread specific key: %s\n",
+
2565 strerror(err));
+
2566 pthread_mutex_unlock(&fuse_context_lock);
+
2567 return -1;
+
2568 }
+
2569 }
+
2570 fuse_context_ref++;
+
2571 pthread_mutex_unlock(&fuse_context_lock);
+
2572 return 0;
+
2573}
+
2574
+
2575static void fuse_delete_context_key(void)
+
2576{
+
2577 pthread_mutex_lock(&fuse_context_lock);
+
2578 fuse_context_ref--;
+
2579 if (!fuse_context_ref) {
+
2580 free(pthread_getspecific(fuse_context_key));
+
2581 pthread_key_delete(fuse_context_key);
+
2582 }
+
2583 pthread_mutex_unlock(&fuse_context_lock);
+
2584}
+
2585
+
2586static struct fuse *req_fuse_prepare(fuse_req_t req)
+
2587{
+
2588 struct fuse_context_i *c = fuse_create_context(req_fuse(req));
+
2589 const struct fuse_ctx *ctx = fuse_req_ctx(req);
+
2590 c->req = req;
+
2591 c->ctx.uid = ctx->uid;
+
2592 c->ctx.gid = ctx->gid;
+
2593 c->ctx.pid = ctx->pid;
+
2594 c->ctx.umask = ctx->umask;
+
2595 return c->ctx.fuse;
+
2596}
+
2597
+
2598static inline void reply_err(fuse_req_t req, int err)
+
2599{
+
2600 /* fuse_reply_err() uses non-negated errno values */
+
2601 fuse_reply_err(req, -err);
+
2602}
+
2603
+
2604static void reply_entry(fuse_req_t req, const struct fuse_entry_param *e,
+
2605 int err)
+
2606{
+
2607 if (!err) {
+
2608 struct fuse *f = req_fuse(req);
+
2609 if (fuse_reply_entry(req, e) == -ENOENT) {
+
2610 /* Skip forget for negative result */
+
2611 if (e->ino != 0)
+
2612 forget_node(f, e->ino, 1);
+
2613 }
+
2614 } else
+
2615 reply_err(req, err);
+
2616}
+
2617
+
2618void fuse_fs_init(struct fuse_fs *fs, struct fuse_conn_info *conn,
+
2619 struct fuse_config *cfg)
+
2620{
+
2621 fuse_get_context()->private_data = fs->user_data;
+
2622 if (!fs->op.write_buf)
+ +
2624 if (!fs->op.lock)
+ +
2626 if (!fs->op.flock)
+ +
2628 if (fs->op.init)
+
2629 fs->user_data = fs->op.init(conn, cfg);
+
2630}
+
2631
+
2632static int fuse_init_intr_signal(int signum, int *installed);
+
2633
+
2634static void fuse_lib_init(void *data, struct fuse_conn_info *conn)
+
2635{
+
2636 struct fuse *f = (struct fuse *) data;
+
2637
+
2638 fuse_create_context(f);
+ +
2640 fuse_fs_init(f->fs, conn, &f->conf);
+
2641
+
2642 if (f->conf.intr) {
+
2643 if (fuse_init_intr_signal(f->conf.intr_signal,
+
2644 &f->intr_installed) == -1)
+
2645 fuse_log(FUSE_LOG_ERR, "fuse: failed to init interrupt signal\n");
+
2646 } else {
+
2647 /* Disable the receiving and processing of FUSE_INTERRUPT requests */
+
2648 conn->no_interrupt = 1;
+
2649 }
+
2650}
+
2651
+
2652void fuse_fs_destroy(struct fuse_fs *fs)
+
2653{
+
2654 fuse_get_context()->private_data = fs->user_data;
+
2655 if (fs->op.destroy)
+
2656 fs->op.destroy(fs->user_data);
+
2657}
+
2658
+
2659static void fuse_lib_destroy(void *data)
+
2660{
+
2661 struct fuse *f = (struct fuse *) data;
+
2662
+
2663 fuse_create_context(f);
+
2664 fuse_fs_destroy(f->fs);
+
2665}
+
2666
+
2667static void fuse_lib_lookup(fuse_req_t req, fuse_ino_t parent,
+
2668 const char *name)
+
2669{
+
2670 struct fuse *f = req_fuse_prepare(req);
+
2671 struct fuse_entry_param e = { .ino = 0 }; /* invalid ino */
+
2672 char *path;
+
2673 int err;
+
2674 struct node *dot = NULL;
+
2675
+
2676 if (name[0] == '.') {
+
2677 int len = strlen(name);
+
2678
+
2679 if (len == 1 || (name[1] == '.' && len == 2)) {
+
2680 pthread_mutex_lock(&f->lock);
+
2681 if (len == 1) {
+
2682 if (f->conf.debug)
+
2683 fuse_log(FUSE_LOG_DEBUG, "LOOKUP-DOT\n");
+
2684 dot = get_node_nocheck(f, parent);
+
2685 if (dot == NULL) {
+
2686 pthread_mutex_unlock(&f->lock);
+
2687 reply_entry(req, &e, -ESTALE);
+
2688 return;
+
2689 }
+
2690 dot->refctr++;
+
2691 } else {
+
2692 if (f->conf.debug)
+
2693 fuse_log(FUSE_LOG_DEBUG, "LOOKUP-DOTDOT\n");
+
2694 parent = get_node(f, parent)->parent->nodeid;
+
2695 }
+
2696 pthread_mutex_unlock(&f->lock);
+
2697 name = NULL;
+
2698 }
+
2699 }
+
2700
+
2701 err = get_path_name(f, parent, name, &path);
+
2702 if (!err) {
+
2703 struct fuse_intr_data d;
+
2704 if (f->conf.debug)
+
2705 fuse_log(FUSE_LOG_DEBUG, "LOOKUP %s\n", path);
+
2706 fuse_prepare_interrupt(f, req, &d);
+
2707 err = lookup_path(f, parent, name, path, &e, NULL);
+
2708 if (err == -ENOENT && f->conf.negative_timeout != 0.0) {
+
2709 e.ino = 0;
+
2710 e.entry_timeout = f->conf.negative_timeout;
+
2711 err = 0;
+
2712 }
+
2713 fuse_finish_interrupt(f, req, &d);
+
2714 free_path(f, parent, path);
+
2715 }
+
2716 if (dot) {
+
2717 pthread_mutex_lock(&f->lock);
+
2718 unref_node(f, dot);
+
2719 pthread_mutex_unlock(&f->lock);
+
2720 }
+
2721 reply_entry(req, &e, err);
+
2722}
+
2723
+
2724static void do_forget(struct fuse *f, fuse_ino_t ino, uint64_t nlookup)
+
2725{
+
2726 if (f->conf.debug)
+
2727 fuse_log(FUSE_LOG_DEBUG, "FORGET %llu/%llu\n", (unsigned long long)ino,
+
2728 (unsigned long long) nlookup);
+
2729 forget_node(f, ino, nlookup);
+
2730}
+
2731
+
2732static void fuse_lib_forget(fuse_req_t req, fuse_ino_t ino, uint64_t nlookup)
+
2733{
+
2734 do_forget(req_fuse(req), ino, nlookup);
+
2735 fuse_reply_none(req);
+
2736}
+
2737
+
2738static void fuse_lib_forget_multi(fuse_req_t req, size_t count,
+
2739 struct fuse_forget_data *forgets)
+
2740{
+
2741 struct fuse *f = req_fuse(req);
+
2742 size_t i;
+
2743
+
2744 for (i = 0; i < count; i++)
+
2745 do_forget(f, forgets[i].ino, forgets[i].nlookup);
+
2746
+
2747 fuse_reply_none(req);
+
2748}
+
2749
+
2750
+
2751static void fuse_lib_getattr(fuse_req_t req, fuse_ino_t ino,
+
2752 struct fuse_file_info *fi)
+
2753{
+
2754 struct fuse *f = req_fuse_prepare(req);
+
2755 struct stat buf;
+
2756 char *path;
+
2757 int err;
+
2758
+
2759 memset(&buf, 0, sizeof(buf));
+
2760
+
2761 if (fi != NULL)
+
2762 err = get_path_nullok(f, ino, &path);
+
2763 else
+
2764 err = get_path(f, ino, &path);
+
2765 if (!err) {
+
2766 struct fuse_intr_data d;
+
2767 fuse_prepare_interrupt(f, req, &d);
+
2768 err = fuse_fs_getattr(f->fs, path, &buf, fi);
+
2769 fuse_finish_interrupt(f, req, &d);
+
2770 free_path(f, ino, path);
+
2771 }
+
2772 if (!err) {
+
2773 struct node *node;
+
2774
+
2775 pthread_mutex_lock(&f->lock);
+
2776 node = get_node(f, ino);
+
2777 if (node->is_hidden && buf.st_nlink > 0)
+
2778 buf.st_nlink--;
+
2779 if (f->conf.auto_cache)
+
2780 update_stat(node, &buf);
+
2781 pthread_mutex_unlock(&f->lock);
+
2782 set_stat(f, ino, &buf);
+
2783 fuse_reply_attr(req, &buf, f->conf.attr_timeout);
+
2784 } else
+
2785 reply_err(req, err);
+
2786}
+
2787
+
2788int fuse_fs_chmod(struct fuse_fs *fs, const char *path, mode_t mode,
+
2789 struct fuse_file_info *fi)
+
2790{
+
2791 fuse_get_context()->private_data = fs->user_data;
+
2792 if (!fs->op.chmod)
+
2793 return -ENOSYS;
+
2794
+
2795 if (fs->debug) {
+
2796 char buf[10];
+
2797
+
2798 fuse_log(FUSE_LOG_DEBUG, "chmod[%s] %s %llo\n",
+
2799 file_info_string(fi, buf, sizeof(buf)),
+
2800 path, (unsigned long long) mode);
+
2801 }
+
2802 return fs->op.chmod(path, mode, fi);
+
2803}
+
2804
+
2805static void fuse_lib_setattr(fuse_req_t req, fuse_ino_t ino, struct stat *attr,
+
2806 int valid, struct fuse_file_info *fi)
+
2807{
+
2808 struct fuse *f = req_fuse_prepare(req);
+
2809 struct stat buf;
+
2810 char *path;
+
2811 int err;
+
2812
+
2813 memset(&buf, 0, sizeof(buf));
+
2814 if (fi != NULL)
+
2815 err = get_path_nullok(f, ino, &path);
+
2816 else
+
2817 err = get_path(f, ino, &path);
+
2818 if (!err) {
+
2819 struct fuse_intr_data d;
+
2820 fuse_prepare_interrupt(f, req, &d);
+
2821 err = 0;
+
2822 if (!err && (valid & FUSE_SET_ATTR_MODE))
+
2823 err = fuse_fs_chmod(f->fs, path, attr->st_mode, fi);
+
2824 if (!err && (valid & (FUSE_SET_ATTR_UID | FUSE_SET_ATTR_GID))) {
+
2825 uid_t uid = (valid & FUSE_SET_ATTR_UID) ?
+
2826 attr->st_uid : (uid_t) -1;
+
2827 gid_t gid = (valid & FUSE_SET_ATTR_GID) ?
+
2828 attr->st_gid : (gid_t) -1;
+
2829 err = fuse_fs_chown(f->fs, path, uid, gid, fi);
+
2830 }
+
2831 if (!err && (valid & FUSE_SET_ATTR_SIZE)) {
+
2832 err = fuse_fs_truncate(f->fs, path,
+
2833 attr->st_size, fi);
+
2834 }
+
2835#ifdef HAVE_UTIMENSAT
+
2836 if (!err &&
+
2837 (valid & (FUSE_SET_ATTR_ATIME | FUSE_SET_ATTR_MTIME))) {
+
2838 struct timespec tv[2];
+
2839
+
2840 tv[0].tv_sec = 0;
+
2841 tv[1].tv_sec = 0;
+
2842 tv[0].tv_nsec = UTIME_OMIT;
+
2843 tv[1].tv_nsec = UTIME_OMIT;
+
2844
+
2845 if (valid & FUSE_SET_ATTR_ATIME_NOW)
+
2846 tv[0].tv_nsec = UTIME_NOW;
+
2847 else if (valid & FUSE_SET_ATTR_ATIME)
+
2848 tv[0] = attr->st_atim;
+
2849
+
2850 if (valid & FUSE_SET_ATTR_MTIME_NOW)
+
2851 tv[1].tv_nsec = UTIME_NOW;
+
2852 else if (valid & FUSE_SET_ATTR_MTIME)
+
2853 tv[1] = attr->st_mtim;
+
2854
+
2855 err = fuse_fs_utimens(f->fs, path, tv, fi);
+
2856 } else
+
2857#endif
+
2858 if (!err &&
+
2859 (valid & (FUSE_SET_ATTR_ATIME | FUSE_SET_ATTR_MTIME)) ==
+
2860 (FUSE_SET_ATTR_ATIME | FUSE_SET_ATTR_MTIME)) {
+
2861 struct timespec tv[2];
+
2862 tv[0].tv_sec = attr->st_atime;
+
2863 tv[0].tv_nsec = ST_ATIM_NSEC(attr);
+
2864 tv[1].tv_sec = attr->st_mtime;
+
2865 tv[1].tv_nsec = ST_MTIM_NSEC(attr);
+
2866 err = fuse_fs_utimens(f->fs, path, tv, fi);
+
2867 }
+
2868 if (!err) {
+
2869 err = fuse_fs_getattr(f->fs, path, &buf, fi);
+
2870 }
+
2871 fuse_finish_interrupt(f, req, &d);
+
2872 free_path(f, ino, path);
+
2873 }
+
2874 if (!err) {
+
2875 if (f->conf.auto_cache) {
+
2876 pthread_mutex_lock(&f->lock);
+
2877 update_stat(get_node(f, ino), &buf);
+
2878 pthread_mutex_unlock(&f->lock);
+
2879 }
+
2880 set_stat(f, ino, &buf);
+
2881 fuse_reply_attr(req, &buf, f->conf.attr_timeout);
+
2882 } else
+
2883 reply_err(req, err);
+
2884}
+
2885
+
2886static void fuse_lib_access(fuse_req_t req, fuse_ino_t ino, int mask)
+
2887{
+
2888 struct fuse *f = req_fuse_prepare(req);
+
2889 char *path;
+
2890 int err;
+
2891
+
2892 err = get_path(f, ino, &path);
+
2893 if (!err) {
+
2894 struct fuse_intr_data d;
+
2895
+
2896 fuse_prepare_interrupt(f, req, &d);
+
2897 err = fuse_fs_access(f->fs, path, mask);
+
2898 fuse_finish_interrupt(f, req, &d);
+
2899 free_path(f, ino, path);
+
2900 }
+
2901 reply_err(req, err);
+
2902}
+
2903
+
2904static void fuse_lib_readlink(fuse_req_t req, fuse_ino_t ino)
+
2905{
+
2906 struct fuse *f = req_fuse_prepare(req);
+
2907 char linkname[PATH_MAX + 1];
+
2908 char *path;
+
2909 int err;
+
2910
+
2911 err = get_path(f, ino, &path);
+
2912 if (!err) {
+
2913 struct fuse_intr_data d;
+
2914 fuse_prepare_interrupt(f, req, &d);
+
2915 err = fuse_fs_readlink(f->fs, path, linkname, sizeof(linkname));
+
2916 fuse_finish_interrupt(f, req, &d);
+
2917 free_path(f, ino, path);
+
2918 }
+
2919 if (!err) {
+
2920 linkname[PATH_MAX] = '\0';
+
2921 fuse_reply_readlink(req, linkname);
+
2922 } else
+
2923 reply_err(req, err);
+
2924}
+
2925
+
2926static void fuse_lib_mknod(fuse_req_t req, fuse_ino_t parent, const char *name,
+
2927 mode_t mode, dev_t rdev)
+
2928{
+
2929 struct fuse *f = req_fuse_prepare(req);
+
2930 struct fuse_entry_param e;
+
2931 char *path;
+
2932 int err;
+
2933
+
2934 err = get_path_name(f, parent, name, &path);
+
2935 if (!err) {
+
2936 struct fuse_intr_data d;
+
2937
+
2938 fuse_prepare_interrupt(f, req, &d);
+
2939 err = -ENOSYS;
+
2940 if (S_ISREG(mode)) {
+
2941 struct fuse_file_info fi;
+
2942
+
2943 memset(&fi, 0, sizeof(fi));
+
2944 fi.flags = O_CREAT | O_EXCL | O_WRONLY;
+
2945 err = fuse_fs_create(f->fs, path, mode, &fi);
+
2946 if (!err) {
+
2947 err = lookup_path(f, parent, name, path, &e,
+
2948 &fi);
+
2949 fuse_fs_release(f->fs, path, &fi);
+
2950 }
+
2951 }
+
2952 if (err == -ENOSYS) {
+
2953 err = fuse_fs_mknod(f->fs, path, mode, rdev);
+
2954 if (!err)
+
2955 err = lookup_path(f, parent, name, path, &e,
+
2956 NULL);
+
2957 }
+
2958 fuse_finish_interrupt(f, req, &d);
+
2959 free_path(f, parent, path);
+
2960 }
+
2961 reply_entry(req, &e, err);
+
2962}
+
2963
+
2964static void fuse_lib_mkdir(fuse_req_t req, fuse_ino_t parent, const char *name,
+
2965 mode_t mode)
+
2966{
+
2967 struct fuse *f = req_fuse_prepare(req);
+
2968 struct fuse_entry_param e;
+
2969 char *path;
+
2970 int err;
+
2971
+
2972 err = get_path_name(f, parent, name, &path);
+
2973 if (!err) {
+
2974 struct fuse_intr_data d;
+
2975
+
2976 fuse_prepare_interrupt(f, req, &d);
+
2977 err = fuse_fs_mkdir(f->fs, path, mode);
+
2978 if (!err)
+
2979 err = lookup_path(f, parent, name, path, &e, NULL);
+
2980 fuse_finish_interrupt(f, req, &d);
+
2981 free_path(f, parent, path);
+
2982 }
+
2983 reply_entry(req, &e, err);
+
2984}
+
2985
+
2986static void fuse_lib_unlink(fuse_req_t req, fuse_ino_t parent,
+
2987 const char *name)
+
2988{
+
2989 struct fuse *f = req_fuse_prepare(req);
+
2990 struct node *wnode;
+
2991 char *path;
+
2992 int err;
+
2993
+
2994 err = get_path_wrlock(f, parent, name, &path, &wnode);
+
2995 if (!err) {
+
2996 struct fuse_intr_data d;
+
2997
+
2998 fuse_prepare_interrupt(f, req, &d);
+
2999 if (!f->conf.hard_remove && is_open(f, parent, name)) {
+
3000 err = hide_node(f, path, parent, name);
+
3001 if (!err) {
+
3002 /* we have hidden the node so now check again under a lock in case it is not used any more */
+
3003 if (!is_open(f, parent, wnode->name)) {
+
3004 char *unlinkpath;
+
3005
+
3006 /* get the hidden file path, to unlink it */
+
3007 if (try_get_path(f, wnode->nodeid, NULL, &unlinkpath, NULL, false) == 0) {
+
3008 err = fuse_fs_unlink(f->fs, unlinkpath);
+
3009 if (!err)
+
3010 remove_node(f, parent, wnode->name);
+
3011 free(unlinkpath);
+
3012 }
+
3013 }
+
3014 }
+
3015 } else {
+
3016 err = fuse_fs_unlink(f->fs, path);
+
3017 if (!err)
+
3018 remove_node(f, parent, name);
+
3019 }
+
3020 fuse_finish_interrupt(f, req, &d);
+
3021 free_path_wrlock(f, parent, wnode, path);
+
3022 }
+
3023 reply_err(req, err);
+
3024}
+
3025
+
3026static void fuse_lib_rmdir(fuse_req_t req, fuse_ino_t parent, const char *name)
+
3027{
+
3028 struct fuse *f = req_fuse_prepare(req);
+
3029 struct node *wnode;
+
3030 char *path;
+
3031 int err;
+
3032
+
3033 err = get_path_wrlock(f, parent, name, &path, &wnode);
+
3034 if (!err) {
+
3035 struct fuse_intr_data d;
+
3036
+
3037 fuse_prepare_interrupt(f, req, &d);
+
3038 err = fuse_fs_rmdir(f->fs, path);
+
3039 fuse_finish_interrupt(f, req, &d);
+
3040 if (!err)
+
3041 remove_node(f, parent, name);
+
3042 free_path_wrlock(f, parent, wnode, path);
+
3043 }
+
3044 reply_err(req, err);
+
3045}
+
3046
+
3047static void fuse_lib_symlink(fuse_req_t req, const char *linkname,
+
3048 fuse_ino_t parent, const char *name)
+
3049{
+
3050 struct fuse *f = req_fuse_prepare(req);
+
3051 struct fuse_entry_param e;
+
3052 char *path;
+
3053 int err;
+
3054
+
3055 err = get_path_name(f, parent, name, &path);
+
3056 if (!err) {
+
3057 struct fuse_intr_data d;
+
3058
+
3059 fuse_prepare_interrupt(f, req, &d);
+
3060 err = fuse_fs_symlink(f->fs, linkname, path);
+
3061 if (!err)
+
3062 err = lookup_path(f, parent, name, path, &e, NULL);
+
3063 fuse_finish_interrupt(f, req, &d);
+
3064 free_path(f, parent, path);
+
3065 }
+
3066 reply_entry(req, &e, err);
+
3067}
+
3068
+
3069static void fuse_lib_rename(fuse_req_t req, fuse_ino_t olddir,
+
3070 const char *oldname, fuse_ino_t newdir,
+
3071 const char *newname, unsigned int flags)
+
3072{
+
3073 struct fuse *f = req_fuse_prepare(req);
+
3074 char *oldpath;
+
3075 char *newpath;
+
3076 struct node *wnode1;
+
3077 struct node *wnode2;
+
3078 int err;
+
3079
+
3080 err = get_path2(f, olddir, oldname, newdir, newname,
+
3081 &oldpath, &newpath, &wnode1, &wnode2);
+
3082 if (!err) {
+
3083 struct fuse_intr_data d;
+
3084 err = 0;
+
3085 fuse_prepare_interrupt(f, req, &d);
+
3086 if (!f->conf.hard_remove && !(flags & RENAME_EXCHANGE) &&
+
3087 is_open(f, newdir, newname))
+
3088 err = hide_node(f, newpath, newdir, newname);
+
3089 if (!err) {
+
3090 err = fuse_fs_rename(f->fs, oldpath, newpath, flags);
+
3091 if (!err) {
+
3092 if (flags & RENAME_EXCHANGE) {
+
3093 err = exchange_node(f, olddir, oldname,
+
3094 newdir, newname);
+
3095 } else {
+
3096 err = rename_node(f, olddir, oldname,
+
3097 newdir, newname, 0);
+
3098 }
+
3099 }
+
3100 }
+
3101 fuse_finish_interrupt(f, req, &d);
+
3102 free_path2(f, olddir, newdir, wnode1, wnode2, oldpath, newpath);
+
3103 }
+
3104 reply_err(req, err);
+
3105}
+
3106
+
3107static void fuse_lib_link(fuse_req_t req, fuse_ino_t ino, fuse_ino_t newparent,
+
3108 const char *newname)
+
3109{
+
3110 struct fuse *f = req_fuse_prepare(req);
+
3111 struct fuse_entry_param e;
+
3112 char *oldpath;
+
3113 char *newpath;
+
3114 int err;
+
3115
+
3116 err = get_path2(f, ino, NULL, newparent, newname,
+
3117 &oldpath, &newpath, NULL, NULL);
+
3118 if (!err) {
+
3119 struct fuse_intr_data d;
+
3120
+
3121 fuse_prepare_interrupt(f, req, &d);
+
3122 err = fuse_fs_link(f->fs, oldpath, newpath);
+
3123 if (!err)
+
3124 err = lookup_path(f, newparent, newname, newpath,
+
3125 &e, NULL);
+
3126 fuse_finish_interrupt(f, req, &d);
+
3127 free_path2(f, ino, newparent, NULL, NULL, oldpath, newpath);
+
3128 }
+
3129 reply_entry(req, &e, err);
+
3130}
+
3131
+
3132static void fuse_do_release(struct fuse *f, fuse_ino_t ino, const char *path,
+
3133 struct fuse_file_info *fi)
+
3134{
+
3135 struct node *node;
+
3136 int unlink_hidden = 0;
+
3137
+
3138 fuse_fs_release(f->fs, path, fi);
+
3139
+
3140 pthread_mutex_lock(&f->lock);
+
3141 node = get_node(f, ino);
+
3142 assert(node->open_count > 0);
+
3143 --node->open_count;
+
3144 if (node->is_hidden && !node->open_count) {
+
3145 unlink_hidden = 1;
+
3146 node->is_hidden = 0;
+
3147 }
+
3148 pthread_mutex_unlock(&f->lock);
+
3149
+
3150 if(unlink_hidden) {
+
3151 if (path) {
+
3152 fuse_fs_unlink(f->fs, path);
+
3153 } else if (f->conf.nullpath_ok) {
+
3154 char *unlinkpath;
+
3155
+
3156 if (get_path(f, ino, &unlinkpath) == 0)
+
3157 fuse_fs_unlink(f->fs, unlinkpath);
+
3158
+
3159 free_path(f, ino, unlinkpath);
+
3160 }
+
3161 }
+
3162}
+
3163
+
3164static void fuse_lib_create(fuse_req_t req, fuse_ino_t parent,
+
3165 const char *name, mode_t mode,
+
3166 struct fuse_file_info *fi)
+
3167{
+
3168 struct fuse *f = req_fuse_prepare(req);
+
3169 struct fuse_intr_data d;
+
3170 struct fuse_entry_param e;
+
3171 char *path;
+
3172 int err;
+
3173
+
3174 err = get_path_name(f, parent, name, &path);
+
3175 if (!err) {
+
3176 fuse_prepare_interrupt(f, req, &d);
+
3177 err = fuse_fs_create(f->fs, path, mode, fi);
+
3178 if (!err) {
+
3179 err = lookup_path(f, parent, name, path, &e, fi);
+
3180 if (err)
+
3181 fuse_fs_release(f->fs, path, fi);
+
3182 else if (!S_ISREG(e.attr.st_mode)) {
+
3183 err = -EIO;
+
3184 fuse_fs_release(f->fs, path, fi);
+
3185 forget_node(f, e.ino, 1);
+
3186 } else {
+
3187 if (f->conf.direct_io)
+
3188 fi->direct_io = 1;
+
3189 if (f->conf.kernel_cache)
+
3190 fi->keep_cache = 1;
+
3191 if (fi->direct_io &&
+
3192 f->conf.parallel_direct_writes)
+
3193 fi->parallel_direct_writes = 1;
+
3194 }
+
3195 }
+
3196 fuse_finish_interrupt(f, req, &d);
+
3197 }
+
3198 if (!err) {
+
3199 pthread_mutex_lock(&f->lock);
+
3200 get_node(f, e.ino)->open_count++;
+
3201 pthread_mutex_unlock(&f->lock);
+
3202 if (fuse_reply_create(req, &e, fi) == -ENOENT) {
+
3203 /* The open syscall was interrupted, so it
+
3204 must be cancelled */
+
3205 fuse_do_release(f, e.ino, path, fi);
+
3206 forget_node(f, e.ino, 1);
+
3207 }
+
3208 } else {
+
3209 reply_err(req, err);
+
3210 }
+
3211
+
3212 free_path(f, parent, path);
+
3213}
+
3214
+
3215static double diff_timespec(const struct timespec *t1,
+
3216 const struct timespec *t2)
+
3217{
+
3218 return (t1->tv_sec - t2->tv_sec) +
+
3219 ((double) t1->tv_nsec - (double) t2->tv_nsec) / 1000000000.0;
+
3220}
+
3221
+
3222static void open_auto_cache(struct fuse *f, fuse_ino_t ino, const char *path,
+
3223 struct fuse_file_info *fi)
+
3224{
+
3225 struct node *node;
+
3226
+
3227 pthread_mutex_lock(&f->lock);
+
3228 node = get_node(f, ino);
+
3229 if (node->cache_valid) {
+
3230 struct timespec now;
+
3231
+
3232 curr_time(&now);
+
3233 if (diff_timespec(&now, &node->stat_updated) >
+
3234 f->conf.ac_attr_timeout) {
+
3235 struct stat stbuf;
+
3236 int err;
+
3237 pthread_mutex_unlock(&f->lock);
+
3238 err = fuse_fs_getattr(f->fs, path, &stbuf, fi);
+
3239 pthread_mutex_lock(&f->lock);
+
3240 if (!err)
+
3241 update_stat(node, &stbuf);
+
3242 else
+
3243 node->cache_valid = 0;
+
3244 }
+
3245 }
+
3246 if (node->cache_valid)
+
3247 fi->keep_cache = 1;
+
3248
+
3249 node->cache_valid = 1;
+
3250 pthread_mutex_unlock(&f->lock);
+
3251}
+
3252
+
3253static void fuse_lib_open(fuse_req_t req, fuse_ino_t ino,
+
3254 struct fuse_file_info *fi)
+
3255{
+
3256 struct fuse *f = req_fuse_prepare(req);
+
3257 struct fuse_intr_data d;
+
3258 char *path;
+
3259 int err;
+
3260
+
3261 err = get_path(f, ino, &path);
+
3262 if (!err) {
+
3263 fuse_prepare_interrupt(f, req, &d);
+
3264 err = fuse_fs_open(f->fs, path, fi);
+
3265 if (!err) {
+
3266 if (f->conf.direct_io)
+
3267 fi->direct_io = 1;
+
3268 if (f->conf.kernel_cache)
+
3269 fi->keep_cache = 1;
+
3270
+
3271 if (f->conf.auto_cache)
+
3272 open_auto_cache(f, ino, path, fi);
+
3273
+
3274 if (f->conf.no_rofd_flush &&
+
3275 (fi->flags & O_ACCMODE) == O_RDONLY)
+
3276 fi->noflush = 1;
+
3277
+
3278 if (fi->direct_io && f->conf.parallel_direct_writes)
+
3279 fi->parallel_direct_writes = 1;
+
3280
+
3281 }
+
3282 fuse_finish_interrupt(f, req, &d);
+
3283 }
+
3284 if (!err) {
+
3285 pthread_mutex_lock(&f->lock);
+
3286 get_node(f, ino)->open_count++;
+
3287 pthread_mutex_unlock(&f->lock);
+
3288 if (fuse_reply_open(req, fi) == -ENOENT) {
+
3289 /* The open syscall was interrupted, so it
+
3290 must be cancelled */
+
3291 fuse_do_release(f, ino, path, fi);
+
3292 }
+
3293 } else
+
3294 reply_err(req, err);
+
3295
+
3296 free_path(f, ino, path);
+
3297}
+
3298
+
3299static void fuse_lib_read(fuse_req_t req, fuse_ino_t ino, size_t size,
+
3300 off_t off, struct fuse_file_info *fi)
+
3301{
+
3302 struct fuse *f = req_fuse_prepare(req);
+
3303 struct fuse_bufvec *buf = NULL;
+
3304 char *path;
+
3305 int res;
+
3306
+
3307 res = get_path_nullok(f, ino, &path);
+
3308 if (res == 0) {
+
3309 struct fuse_intr_data d;
+
3310
+
3311 fuse_prepare_interrupt(f, req, &d);
+
3312 res = fuse_fs_read_buf(f->fs, path, &buf, size, off, fi);
+
3313 fuse_finish_interrupt(f, req, &d);
+
3314 free_path(f, ino, path);
+
3315 }
+
3316
+
3317 if (res == 0)
+ +
3319 else
+
3320 reply_err(req, res);
+
3321
+
3322 fuse_free_buf(buf);
+
3323}
+
3324
+
3325static void fuse_lib_write_buf(fuse_req_t req, fuse_ino_t ino,
+
3326 struct fuse_bufvec *buf, off_t off,
+
3327 struct fuse_file_info *fi)
+
3328{
+
3329 struct fuse *f = req_fuse_prepare(req);
+
3330 char *path;
+
3331 int res;
+
3332
+
3333 res = get_path_nullok(f, ino, &path);
+
3334 if (res == 0) {
+
3335 struct fuse_intr_data d;
+
3336
+
3337 fuse_prepare_interrupt(f, req, &d);
+
3338 res = fuse_fs_write_buf(f->fs, path, buf, off, fi);
+
3339 fuse_finish_interrupt(f, req, &d);
+
3340 free_path(f, ino, path);
+
3341 }
+
3342
+
3343 if (res >= 0)
+
3344 fuse_reply_write(req, res);
+
3345 else
+
3346 reply_err(req, res);
+
3347}
+
3348
+
3349static void fuse_lib_fsync(fuse_req_t req, fuse_ino_t ino, int datasync,
+
3350 struct fuse_file_info *fi)
+
3351{
+
3352 struct fuse *f = req_fuse_prepare(req);
+
3353 char *path;
+
3354 int err;
+
3355
+
3356 err = get_path_nullok(f, ino, &path);
+
3357 if (!err) {
+
3358 struct fuse_intr_data d;
+
3359
+
3360 fuse_prepare_interrupt(f, req, &d);
+
3361 err = fuse_fs_fsync(f->fs, path, datasync, fi);
+
3362 fuse_finish_interrupt(f, req, &d);
+
3363 free_path(f, ino, path);
+
3364 }
+
3365 reply_err(req, err);
+
3366}
+
3367
+
3368static struct fuse_dh *get_dirhandle(const struct fuse_file_info *llfi,
+
3369 struct fuse_file_info *fi)
+
3370{
+
3371 struct fuse_dh *dh = (struct fuse_dh *) (uintptr_t) llfi->fh;
+
3372 memset(fi, 0, sizeof(struct fuse_file_info));
+
3373 fi->fh = dh->fh;
+
3374 return dh;
+
3375}
+
3376
+
3377static void fuse_lib_opendir(fuse_req_t req, fuse_ino_t ino,
+
3378 struct fuse_file_info *llfi)
+
3379{
+
3380 struct fuse *f = req_fuse_prepare(req);
+
3381 struct fuse_intr_data d;
+
3382 struct fuse_dh *dh;
+
3383 struct fuse_file_info fi;
+
3384 char *path;
+
3385 int err;
+
3386
+
3387 dh = (struct fuse_dh *) malloc(sizeof(struct fuse_dh));
+
3388 if (dh == NULL) {
+
3389 reply_err(req, -ENOMEM);
+
3390 return;
+
3391 }
+
3392 memset(dh, 0, sizeof(struct fuse_dh));
+
3393 dh->fuse = f;
+
3394 dh->contents = NULL;
+
3395 dh->first = NULL;
+
3396 dh->len = 0;
+
3397 dh->filled = 0;
+
3398 dh->nodeid = ino;
+
3399 pthread_mutex_init(&dh->lock, NULL);
+
3400
+
3401 llfi->fh = (uintptr_t) dh;
+
3402
+
3403 memset(&fi, 0, sizeof(fi));
+
3404 fi.flags = llfi->flags;
+
3405
+
3406 err = get_path(f, ino, &path);
+
3407 if (!err) {
+
3408 fuse_prepare_interrupt(f, req, &d);
+
3409 err = fuse_fs_opendir(f->fs, path, &fi);
+
3410 fuse_finish_interrupt(f, req, &d);
+
3411 dh->fh = fi.fh;
+
3412 llfi->cache_readdir = fi.cache_readdir;
+
3413 llfi->keep_cache = fi.keep_cache;
+
3414 }
+
3415 if (!err) {
+
3416 if (fuse_reply_open(req, llfi) == -ENOENT) {
+
3417 /* The opendir syscall was interrupted, so it
+
3418 must be cancelled */
+
3419 fuse_fs_releasedir(f->fs, path, &fi);
+
3420 pthread_mutex_destroy(&dh->lock);
+
3421 free(dh);
+
3422 }
+
3423 } else {
+
3424 reply_err(req, err);
+
3425 pthread_mutex_destroy(&dh->lock);
+
3426 free(dh);
+
3427 }
+
3428 free_path(f, ino, path);
+
3429}
+
3430
+
3431static int extend_contents(struct fuse_dh *dh, unsigned minsize)
+
3432{
+
3433 if (minsize > dh->size) {
+
3434 char *newptr;
+
3435 unsigned newsize = dh->size;
+
3436 if (!newsize)
+
3437 newsize = 1024;
+
3438 while (newsize < minsize) {
+
3439 if (newsize >= 0x80000000)
+
3440 newsize = 0xffffffff;
+
3441 else
+
3442 newsize *= 2;
+
3443 }
+
3444
+
3445 newptr = (char *) realloc(dh->contents, newsize);
+
3446 if (!newptr) {
+
3447 dh->error = -ENOMEM;
+
3448 return -1;
+
3449 }
+
3450 dh->contents = newptr;
+
3451 dh->size = newsize;
+
3452 }
+
3453 return 0;
+
3454}
+
3455
+
3456static int fuse_add_direntry_to_dh(struct fuse_dh *dh, const char *name,
+
3457 struct stat *st, enum fuse_fill_dir_flags flags)
+
3458{
+
3459 struct fuse_direntry *de;
+
3460
+
3461 de = malloc(sizeof(struct fuse_direntry));
+
3462 if (!de) {
+
3463 dh->error = -ENOMEM;
+
3464 return -1;
+
3465 }
+
3466 de->name = strdup(name);
+
3467 if (!de->name) {
+
3468 dh->error = -ENOMEM;
+
3469 free(de);
+
3470 return -1;
+
3471 }
+
3472 de->flags = flags;
+
3473 de->stat = *st;
+
3474 de->next = NULL;
+
3475
+
3476 *dh->last = de;
+
3477 dh->last = &de->next;
+
3478
+
3479 return 0;
+
3480}
+
3481
+
3482static fuse_ino_t lookup_nodeid(struct fuse *f, fuse_ino_t parent,
+
3483 const char *name)
+
3484{
+
3485 struct node *node;
+
3486 fuse_ino_t res = FUSE_UNKNOWN_INO;
+
3487
+
3488 pthread_mutex_lock(&f->lock);
+
3489 node = lookup_node(f, parent, name);
+
3490 if (node)
+
3491 res = node->nodeid;
+
3492 pthread_mutex_unlock(&f->lock);
+
3493
+
3494 return res;
+
3495}
+
3496
+
3497static int fill_dir(void *dh_, const char *name, const struct stat *statp,
+
3498 off_t off, enum fuse_fill_dir_flags flags)
+
3499{
+
3500 struct fuse_dh *dh = (struct fuse_dh *) dh_;
+
3501 struct stat stbuf;
+
3502
+
3503 if ((flags & ~FUSE_FILL_DIR_PLUS) != 0) {
+
3504 dh->error = -EIO;
+
3505 return 1;
+
3506 }
+
3507
+
3508 if (statp)
+
3509 stbuf = *statp;
+
3510 else {
+
3511 memset(&stbuf, 0, sizeof(stbuf));
+
3512 stbuf.st_ino = FUSE_UNKNOWN_INO;
+
3513 }
+
3514
+
3515 if (!dh->fuse->conf.use_ino) {
+
3516 stbuf.st_ino = FUSE_UNKNOWN_INO;
+
3517 if (dh->fuse->conf.readdir_ino) {
+
3518 stbuf.st_ino = (ino_t)
+
3519 lookup_nodeid(dh->fuse, dh->nodeid, name);
+
3520 }
+
3521 }
+
3522
+
3523 if (off) {
+
3524 size_t newlen;
+
3525
+
3526 if (dh->filled) {
+
3527 dh->error = -EIO;
+
3528 return 1;
+
3529 }
+
3530
+
3531 if (dh->first) {
+
3532 dh->error = -EIO;
+
3533 return 1;
+
3534 }
+
3535
+
3536 if (extend_contents(dh, dh->needlen) == -1)
+
3537 return 1;
+
3538
+
3539 newlen = dh->len +
+
3540 fuse_add_direntry(dh->req, dh->contents + dh->len,
+
3541 dh->needlen - dh->len, name,
+
3542 &stbuf, off);
+
3543 if (newlen > dh->needlen)
+
3544 return 1;
+
3545
+
3546 dh->len = newlen;
+
3547 } else {
+
3548 dh->filled = 1;
+
3549
+
3550 if (fuse_add_direntry_to_dh(dh, name, &stbuf, flags) == -1)
+
3551 return 1;
+
3552 }
+
3553 return 0;
+
3554}
+
3555
+
3556static int is_dot_or_dotdot(const char *name)
+
3557{
+
3558 return name[0] == '.' && (name[1] == '\0' ||
+
3559 (name[1] == '.' && name[2] == '\0'));
+
3560}
+
3561
+
3562static int fill_dir_plus(void *dh_, const char *name, const struct stat *statp,
+
3563 off_t off, enum fuse_fill_dir_flags flags)
+
3564{
+
3565 struct fuse_dh *dh = (struct fuse_dh *) dh_;
+
3566 struct fuse_entry_param e = {
+
3567 /* ino=0 tells the kernel to ignore readdirplus stat info */
+
3568 .ino = 0,
+
3569 };
+
3570 struct fuse *f = dh->fuse;
+
3571 int res;
+
3572
+
3573 if ((flags & ~FUSE_FILL_DIR_PLUS) != 0) {
+
3574 dh->error = -EIO;
+
3575 return 1;
+
3576 }
+
3577
+
3578 if (statp && (flags & FUSE_FILL_DIR_PLUS)) {
+
3579 e.attr = *statp;
+
3580 }
+
3581
+
3582 e.attr.st_ino = FUSE_UNKNOWN_INO;
+
3583 if (statp) {
+
3584 e.attr.st_mode = statp->st_mode;
+
3585 if (f->conf.use_ino)
+
3586 e.attr.st_ino = statp->st_ino;
+
3587 }
+
3588 if (!f->conf.use_ino && f->conf.readdir_ino) {
+
3589 e.attr.st_ino = (ino_t)
+
3590 lookup_nodeid(f, dh->nodeid, name);
+
3591 }
+
3592
+
3593 if (off) {
+
3594 size_t newlen;
+
3595
+
3596 if (dh->filled) {
+
3597 dh->error = -EIO;
+
3598 return 1;
+
3599 }
+
3600
+
3601 if (dh->first) {
+
3602 dh->error = -EIO;
+
3603 return 1;
+
3604 }
+
3605 if (extend_contents(dh, dh->needlen) == -1)
+
3606 return 1;
+
3607
+
3608 if (statp && (flags & FUSE_FILL_DIR_PLUS)) {
+
3609 if (!is_dot_or_dotdot(name)) {
+
3610 res = do_lookup(f, dh->nodeid, name, &e);
+
3611 if (res) {
+
3612 dh->error = res;
+
3613 return 1;
+
3614 }
+
3615 }
+
3616 }
+
3617
+
3618 newlen = dh->len +
+
3619 fuse_add_direntry_plus(dh->req, dh->contents + dh->len,
+
3620 dh->needlen - dh->len, name,
+
3621 &e, off);
+
3622 if (newlen > dh->needlen)
+
3623 return 1;
+
3624 dh->len = newlen;
+
3625 } else {
+
3626 dh->filled = 1;
+
3627
+
3628 if (fuse_add_direntry_to_dh(dh, name, &e.attr, flags) == -1)
+
3629 return 1;
+
3630 }
+
3631
+
3632 return 0;
+
3633}
+
3634
+
3635static void free_direntries(struct fuse_direntry *de)
+
3636{
+
3637 while (de) {
+
3638 struct fuse_direntry *next = de->next;
+
3639 free(de->name);
+
3640 free(de);
+
3641 de = next;
+
3642 }
+
3643}
+
3644
+
3645static int readdir_fill(struct fuse *f, fuse_req_t req, fuse_ino_t ino,
+
3646 size_t size, off_t off, struct fuse_dh *dh,
+
3647 struct fuse_file_info *fi,
+
3648 enum fuse_readdir_flags flags)
+
3649{
+
3650 char *path;
+
3651 int err;
+
3652
+
3653 if (f->fs->op.readdir)
+
3654 err = get_path_nullok(f, ino, &path);
+
3655 else
+
3656 err = get_path(f, ino, &path);
+
3657 if (!err) {
+
3658 struct fuse_intr_data d;
+
3659 fuse_fill_dir_t filler = fill_dir;
+
3660
+
3661 if (flags & FUSE_READDIR_PLUS)
+
3662 filler = fill_dir_plus;
+
3663
+
3664 free_direntries(dh->first);
+
3665 dh->first = NULL;
+
3666 dh->last = &dh->first;
+
3667 dh->len = 0;
+
3668 dh->error = 0;
+
3669 dh->needlen = size;
+
3670 dh->filled = 0;
+
3671 dh->req = req;
+
3672 fuse_prepare_interrupt(f, req, &d);
+
3673 err = fuse_fs_readdir(f->fs, path, dh, filler, off, fi, flags);
+
3674 fuse_finish_interrupt(f, req, &d);
+
3675 dh->req = NULL;
+
3676 if (!err)
+
3677 err = dh->error;
+
3678 if (err)
+
3679 dh->filled = 0;
+
3680 free_path(f, ino, path);
+
3681 }
+
3682 return err;
+
3683}
+
3684
+
3685static int readdir_fill_from_list(fuse_req_t req, struct fuse_dh *dh,
+
3686 off_t off, enum fuse_readdir_flags flags)
+
3687{
+
3688 off_t pos;
+
3689 struct fuse_direntry *de = dh->first;
+
3690 int res;
+
3691
+
3692 dh->len = 0;
+
3693
+
3694 if (extend_contents(dh, dh->needlen) == -1)
+
3695 return dh->error;
+
3696
+
3697 for (pos = 0; pos < off; pos++) {
+
3698 if (!de)
+
3699 break;
+
3700
+
3701 de = de->next;
+
3702 }
+
3703 while (de) {
+
3704 char *p = dh->contents + dh->len;
+
3705 unsigned rem = dh->needlen - dh->len;
+
3706 unsigned thislen;
+
3707 unsigned newlen;
+
3708 pos++;
+
3709
+
3710 if (flags & FUSE_READDIR_PLUS) {
+
3711 struct fuse_entry_param e = {
+
3712 .ino = 0,
+
3713 .attr = de->stat,
+
3714 };
+
3715
+
3716 if (de->flags & FUSE_FILL_DIR_PLUS &&
+
3717 !is_dot_or_dotdot(de->name)) {
+
3718 res = do_lookup(dh->fuse, dh->nodeid,
+
3719 de->name, &e);
+
3720 if (res) {
+
3721 dh->error = res;
+
3722 return 1;
+
3723 }
+
3724 }
+
3725
+
3726 thislen = fuse_add_direntry_plus(req, p, rem,
+
3727 de->name, &e, pos);
+
3728 } else {
+
3729 thislen = fuse_add_direntry(req, p, rem,
+
3730 de->name, &de->stat, pos);
+
3731 }
+
3732 newlen = dh->len + thislen;
+
3733 if (newlen > dh->needlen)
+
3734 break;
+
3735 dh->len = newlen;
+
3736 de = de->next;
+
3737 }
+
3738 return 0;
+
3739}
+
3740
+
3741static void fuse_readdir_common(fuse_req_t req, fuse_ino_t ino, size_t size,
+
3742 off_t off, struct fuse_file_info *llfi,
+
3743 enum fuse_readdir_flags flags)
+
3744{
+
3745 struct fuse *f = req_fuse_prepare(req);
+
3746 struct fuse_file_info fi;
+
3747 struct fuse_dh *dh = get_dirhandle(llfi, &fi);
+
3748 int err;
+
3749
+
3750 pthread_mutex_lock(&dh->lock);
+
3751 /* According to SUS, directory contents need to be refreshed on
+
3752 rewinddir() */
+
3753 if (!off)
+
3754 dh->filled = 0;
+
3755
+
3756 if (!dh->filled) {
+
3757 err = readdir_fill(f, req, ino, size, off, dh, &fi, flags);
+
3758 if (err) {
+
3759 reply_err(req, err);
+
3760 goto out;
+
3761 }
+
3762 }
+
3763 if (dh->filled) {
+
3764 dh->needlen = size;
+
3765 err = readdir_fill_from_list(req, dh, off, flags);
+
3766 if (err) {
+
3767 reply_err(req, err);
+
3768 goto out;
+
3769 }
+
3770 }
+
3771 fuse_reply_buf(req, dh->contents, dh->len);
+
3772out:
+
3773 pthread_mutex_unlock(&dh->lock);
+
3774}
+
3775
+
3776static void fuse_lib_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
+
3777 off_t off, struct fuse_file_info *llfi)
+
3778{
+
3779 fuse_readdir_common(req, ino, size, off, llfi, 0);
+
3780}
+
3781
+
3782static void fuse_lib_readdirplus(fuse_req_t req, fuse_ino_t ino, size_t size,
+
3783 off_t off, struct fuse_file_info *llfi)
+
3784{
+
3785 fuse_readdir_common(req, ino, size, off, llfi, FUSE_READDIR_PLUS);
+
3786}
+
3787
+
3788static void fuse_lib_releasedir(fuse_req_t req, fuse_ino_t ino,
+
3789 struct fuse_file_info *llfi)
+
3790{
+
3791 struct fuse *f = req_fuse_prepare(req);
+
3792 struct fuse_intr_data d;
+
3793 struct fuse_file_info fi;
+
3794 struct fuse_dh *dh = get_dirhandle(llfi, &fi);
+
3795 char *path;
+
3796
+
3797 get_path_nullok(f, ino, &path);
+
3798
+
3799 fuse_prepare_interrupt(f, req, &d);
+
3800 fuse_fs_releasedir(f->fs, path, &fi);
+
3801 fuse_finish_interrupt(f, req, &d);
+
3802 free_path(f, ino, path);
+
3803
+
3804 pthread_mutex_lock(&dh->lock);
+
3805 pthread_mutex_unlock(&dh->lock);
+
3806 pthread_mutex_destroy(&dh->lock);
+
3807 free_direntries(dh->first);
+
3808 free(dh->contents);
+
3809 free(dh);
+
3810 reply_err(req, 0);
+
3811}
+
3812
+
3813static void fuse_lib_fsyncdir(fuse_req_t req, fuse_ino_t ino, int datasync,
+
3814 struct fuse_file_info *llfi)
+
3815{
+
3816 struct fuse *f = req_fuse_prepare(req);
+
3817 struct fuse_file_info fi;
+
3818 char *path;
+
3819 int err;
+
3820
+
3821 get_dirhandle(llfi, &fi);
+
3822
+
3823 err = get_path_nullok(f, ino, &path);
+
3824 if (!err) {
+
3825 struct fuse_intr_data d;
+
3826 fuse_prepare_interrupt(f, req, &d);
+
3827 err = fuse_fs_fsyncdir(f->fs, path, datasync, &fi);
+
3828 fuse_finish_interrupt(f, req, &d);
+
3829 free_path(f, ino, path);
+
3830 }
+
3831 reply_err(req, err);
+
3832}
+
3833
+
3834static void fuse_lib_statfs(fuse_req_t req, fuse_ino_t ino)
+
3835{
+
3836 struct fuse *f = req_fuse_prepare(req);
+
3837 struct statvfs buf;
+
3838 char *path = NULL;
+
3839 int err = 0;
+
3840
+
3841 memset(&buf, 0, sizeof(buf));
+
3842 if (ino)
+
3843 err = get_path(f, ino, &path);
+
3844
+
3845 if (!err) {
+
3846 struct fuse_intr_data d;
+
3847 fuse_prepare_interrupt(f, req, &d);
+
3848 err = fuse_fs_statfs(f->fs, path ? path : "/", &buf);
+
3849 fuse_finish_interrupt(f, req, &d);
+
3850 free_path(f, ino, path);
+
3851 }
+
3852
+
3853 if (!err)
+
3854 fuse_reply_statfs(req, &buf);
+
3855 else
+
3856 reply_err(req, err);
+
3857}
+
3858
+
3859static void fuse_lib_setxattr(fuse_req_t req, fuse_ino_t ino, const char *name,
+
3860 const char *value, size_t size, int flags)
+
3861{
+
3862 struct fuse *f = req_fuse_prepare(req);
+
3863 char *path;
+
3864 int err;
+
3865
+
3866 err = get_path(f, ino, &path);
+
3867 if (!err) {
+
3868 struct fuse_intr_data d;
+
3869 fuse_prepare_interrupt(f, req, &d);
+
3870 err = fuse_fs_setxattr(f->fs, path, name, value, size, flags);
+
3871 fuse_finish_interrupt(f, req, &d);
+
3872 free_path(f, ino, path);
+
3873 }
+
3874 reply_err(req, err);
+
3875}
+
3876
+
3877static int common_getxattr(struct fuse *f, fuse_req_t req, fuse_ino_t ino,
+
3878 const char *name, char *value, size_t size)
+
3879{
+
3880 int err;
+
3881 char *path;
+
3882
+
3883 err = get_path(f, ino, &path);
+
3884 if (!err) {
+
3885 struct fuse_intr_data d;
+
3886 fuse_prepare_interrupt(f, req, &d);
+
3887 err = fuse_fs_getxattr(f->fs, path, name, value, size);
+
3888 fuse_finish_interrupt(f, req, &d);
+
3889 free_path(f, ino, path);
+
3890 }
+
3891 return err;
+
3892}
+
3893
+
3894static void fuse_lib_getxattr(fuse_req_t req, fuse_ino_t ino, const char *name,
+
3895 size_t size)
+
3896{
+
3897 struct fuse *f = req_fuse_prepare(req);
+
3898 int res;
+
3899
+
3900 if (size) {
+
3901 char *value = (char *) malloc(size);
+
3902 if (value == NULL) {
+
3903 reply_err(req, -ENOMEM);
+
3904 return;
+
3905 }
+
3906 res = common_getxattr(f, req, ino, name, value, size);
+
3907 if (res > 0)
+
3908 fuse_reply_buf(req, value, res);
+
3909 else
+
3910 reply_err(req, res);
+
3911 free(value);
+
3912 } else {
+
3913 res = common_getxattr(f, req, ino, name, NULL, 0);
+
3914 if (res >= 0)
+
3915 fuse_reply_xattr(req, res);
+
3916 else
+
3917 reply_err(req, res);
+
3918 }
+
3919}
+
3920
+
3921static int common_listxattr(struct fuse *f, fuse_req_t req, fuse_ino_t ino,
+
3922 char *list, size_t size)
+
3923{
+
3924 char *path;
+
3925 int err;
+
3926
+
3927 err = get_path(f, ino, &path);
+
3928 if (!err) {
+
3929 struct fuse_intr_data d;
+
3930 fuse_prepare_interrupt(f, req, &d);
+
3931 err = fuse_fs_listxattr(f->fs, path, list, size);
+
3932 fuse_finish_interrupt(f, req, &d);
+
3933 free_path(f, ino, path);
+
3934 }
+
3935 return err;
+
3936}
+
3937
+
3938static void fuse_lib_listxattr(fuse_req_t req, fuse_ino_t ino, size_t size)
+
3939{
+
3940 struct fuse *f = req_fuse_prepare(req);
+
3941 int res;
+
3942
+
3943 if (size) {
+
3944 char *list = (char *) malloc(size);
+
3945 if (list == NULL) {
+
3946 reply_err(req, -ENOMEM);
+
3947 return;
+
3948 }
+
3949 res = common_listxattr(f, req, ino, list, size);
+
3950 if (res > 0)
+
3951 fuse_reply_buf(req, list, res);
+
3952 else
+
3953 reply_err(req, res);
+
3954 free(list);
+
3955 } else {
+
3956 res = common_listxattr(f, req, ino, NULL, 0);
+
3957 if (res >= 0)
+
3958 fuse_reply_xattr(req, res);
+
3959 else
+
3960 reply_err(req, res);
+
3961 }
+
3962}
+
3963
+
3964static void fuse_lib_removexattr(fuse_req_t req, fuse_ino_t ino,
+
3965 const char *name)
+
3966{
+
3967 struct fuse *f = req_fuse_prepare(req);
+
3968 char *path;
+
3969 int err;
+
3970
+
3971 err = get_path(f, ino, &path);
+
3972 if (!err) {
+
3973 struct fuse_intr_data d;
+
3974 fuse_prepare_interrupt(f, req, &d);
+
3975 err = fuse_fs_removexattr(f->fs, path, name);
+
3976 fuse_finish_interrupt(f, req, &d);
+
3977 free_path(f, ino, path);
+
3978 }
+
3979 reply_err(req, err);
+
3980}
+
3981
+
3982static struct lock *locks_conflict(struct node *node, const struct lock *lock)
+
3983{
+
3984 struct lock *l;
+
3985
+
3986 for (l = node->locks; l; l = l->next)
+
3987 if (l->owner != lock->owner &&
+
3988 lock->start <= l->end && l->start <= lock->end &&
+
3989 (l->type == F_WRLCK || lock->type == F_WRLCK))
+
3990 break;
+
3991
+
3992 return l;
+
3993}
+
3994
+
3995static void delete_lock(struct lock **lockp)
+
3996{
+
3997 struct lock *l = *lockp;
+
3998 *lockp = l->next;
+
3999 free(l);
+
4000}
+
4001
+
4002static void insert_lock(struct lock **pos, struct lock *lock)
+
4003{
+
4004 lock->next = *pos;
+
4005 *pos = lock;
+
4006}
+
4007
+
4008static int locks_insert(struct node *node, struct lock *lock)
+
4009{
+
4010 struct lock **lp;
+
4011 struct lock *newl1 = NULL;
+
4012 struct lock *newl2 = NULL;
+
4013
+
4014 if (lock->type != F_UNLCK || lock->start != 0 ||
+
4015 lock->end != OFFSET_MAX) {
+
4016 newl1 = malloc(sizeof(struct lock));
+
4017 newl2 = malloc(sizeof(struct lock));
+
4018
+
4019 if (!newl1 || !newl2) {
+
4020 free(newl1);
+
4021 free(newl2);
+
4022 return -ENOLCK;
+
4023 }
+
4024 }
+
4025
+
4026 for (lp = &node->locks; *lp;) {
+
4027 struct lock *l = *lp;
+
4028 if (l->owner != lock->owner)
+
4029 goto skip;
+
4030
+
4031 if (lock->type == l->type) {
+
4032 if (l->end < lock->start - 1)
+
4033 goto skip;
+
4034 if (lock->end < l->start - 1)
+
4035 break;
+
4036 if (l->start <= lock->start && lock->end <= l->end)
+
4037 goto out;
+
4038 if (l->start < lock->start)
+
4039 lock->start = l->start;
+
4040 if (lock->end < l->end)
+
4041 lock->end = l->end;
+
4042 goto delete;
+
4043 } else {
+
4044 if (l->end < lock->start)
+
4045 goto skip;
+
4046 if (lock->end < l->start)
+
4047 break;
+
4048 if (lock->start <= l->start && l->end <= lock->end)
+
4049 goto delete;
+
4050 if (l->end <= lock->end) {
+
4051 l->end = lock->start - 1;
+
4052 goto skip;
+
4053 }
+
4054 if (lock->start <= l->start) {
+
4055 l->start = lock->end + 1;
+
4056 break;
+
4057 }
+
4058 *newl2 = *l;
+
4059 newl2->start = lock->end + 1;
+
4060 l->end = lock->start - 1;
+
4061 insert_lock(&l->next, newl2);
+
4062 newl2 = NULL;
+
4063 }
+
4064 skip:
+
4065 lp = &l->next;
+
4066 continue;
+
4067
+
4068 delete:
+
4069 delete_lock(lp);
+
4070 }
+
4071 if (lock->type != F_UNLCK) {
+
4072 *newl1 = *lock;
+
4073 insert_lock(lp, newl1);
+
4074 newl1 = NULL;
+
4075 }
+
4076out:
+
4077 free(newl1);
+
4078 free(newl2);
+
4079 return 0;
+
4080}
+
4081
+
4082static void flock_to_lock(struct flock *flock, struct lock *lock)
+
4083{
+
4084 memset(lock, 0, sizeof(struct lock));
+
4085 lock->type = flock->l_type;
+
4086 lock->start = flock->l_start;
+
4087 lock->end =
+
4088 flock->l_len ? flock->l_start + flock->l_len - 1 : OFFSET_MAX;
+
4089 lock->pid = flock->l_pid;
+
4090}
+
4091
+
4092static void lock_to_flock(struct lock *lock, struct flock *flock)
+
4093{
+
4094 flock->l_type = lock->type;
+
4095 flock->l_start = lock->start;
+
4096 flock->l_len =
+
4097 (lock->end == OFFSET_MAX) ? 0 : lock->end - lock->start + 1;
+
4098 flock->l_pid = lock->pid;
+
4099}
+
4100
+
4101static int fuse_flush_common(struct fuse *f, fuse_req_t req, fuse_ino_t ino,
+
4102 const char *path, struct fuse_file_info *fi)
+
4103{
+
4104 struct fuse_intr_data d;
+
4105 struct flock lock;
+
4106 struct lock l;
+
4107 int err;
+
4108 int errlock;
+
4109
+
4110 fuse_prepare_interrupt(f, req, &d);
+
4111 memset(&lock, 0, sizeof(lock));
+
4112 lock.l_type = F_UNLCK;
+
4113 lock.l_whence = SEEK_SET;
+
4114 err = fuse_fs_flush(f->fs, path, fi);
+
4115 errlock = fuse_fs_lock(f->fs, path, fi, F_SETLK, &lock);
+
4116 fuse_finish_interrupt(f, req, &d);
+
4117
+
4118 if (errlock != -ENOSYS) {
+
4119 flock_to_lock(&lock, &l);
+
4120 l.owner = fi->lock_owner;
+
4121 pthread_mutex_lock(&f->lock);
+
4122 locks_insert(get_node(f, ino), &l);
+
4123 pthread_mutex_unlock(&f->lock);
+
4124
+
4125 /* if op.lock() is defined FLUSH is needed regardless
+
4126 of op.flush() */
+
4127 if (err == -ENOSYS)
+
4128 err = 0;
+
4129 }
+
4130 return err;
+
4131}
+
4132
+
4133static void fuse_lib_release(fuse_req_t req, fuse_ino_t ino,
+
4134 struct fuse_file_info *fi)
+
4135{
+
4136 struct fuse *f = req_fuse_prepare(req);
+
4137 struct fuse_intr_data d;
+
4138 char *path;
+
4139 int err = 0;
+
4140
+
4141 get_path_nullok(f, ino, &path);
+
4142 if (fi->flush) {
+
4143 err = fuse_flush_common(f, req, ino, path, fi);
+
4144 if (err == -ENOSYS)
+
4145 err = 0;
+
4146 }
+
4147
+
4148 fuse_prepare_interrupt(f, req, &d);
+
4149 fuse_do_release(f, ino, path, fi);
+
4150 fuse_finish_interrupt(f, req, &d);
+
4151 free_path(f, ino, path);
+
4152
+
4153 reply_err(req, err);
+
4154}
+
4155
+
4156static void fuse_lib_flush(fuse_req_t req, fuse_ino_t ino,
+
4157 struct fuse_file_info *fi)
+
4158{
+
4159 struct fuse *f = req_fuse_prepare(req);
+
4160 char *path;
+
4161 int err;
+
4162
+
4163 get_path_nullok(f, ino, &path);
+
4164 err = fuse_flush_common(f, req, ino, path, fi);
+
4165 free_path(f, ino, path);
+
4166
+
4167 reply_err(req, err);
+
4168}
+
4169
+
4170static int fuse_lock_common(fuse_req_t req, fuse_ino_t ino,
+
4171 struct fuse_file_info *fi, struct flock *lock,
+
4172 int cmd)
+
4173{
+
4174 struct fuse *f = req_fuse_prepare(req);
+
4175 char *path;
+
4176 int err;
+
4177
+
4178 err = get_path_nullok(f, ino, &path);
+
4179 if (!err) {
+
4180 struct fuse_intr_data d;
+
4181 fuse_prepare_interrupt(f, req, &d);
+
4182 err = fuse_fs_lock(f->fs, path, fi, cmd, lock);
+
4183 fuse_finish_interrupt(f, req, &d);
+
4184 free_path(f, ino, path);
+
4185 }
+
4186 return err;
+
4187}
+
4188
+
4189static void fuse_lib_getlk(fuse_req_t req, fuse_ino_t ino,
+
4190 struct fuse_file_info *fi, struct flock *lock)
+
4191{
+
4192 int err;
+
4193 struct lock l;
+
4194 struct lock *conflict;
+
4195 struct fuse *f = req_fuse(req);
+
4196
+
4197 flock_to_lock(lock, &l);
+
4198 l.owner = fi->lock_owner;
+
4199 pthread_mutex_lock(&f->lock);
+
4200 conflict = locks_conflict(get_node(f, ino), &l);
+
4201 if (conflict)
+
4202 lock_to_flock(conflict, lock);
+
4203 pthread_mutex_unlock(&f->lock);
+
4204 if (!conflict)
+
4205 err = fuse_lock_common(req, ino, fi, lock, F_GETLK);
+
4206 else
+
4207 err = 0;
+
4208
+
4209 if (!err)
+
4210 fuse_reply_lock(req, lock);
+
4211 else
+
4212 reply_err(req, err);
+
4213}
+
4214
+
4215static void fuse_lib_setlk(fuse_req_t req, fuse_ino_t ino,
+
4216 struct fuse_file_info *fi, struct flock *lock,
+
4217 int sleep)
+
4218{
+
4219 int err = fuse_lock_common(req, ino, fi, lock,
+
4220 sleep ? F_SETLKW : F_SETLK);
+
4221 if (!err) {
+
4222 struct fuse *f = req_fuse(req);
+
4223 struct lock l;
+
4224 flock_to_lock(lock, &l);
+
4225 l.owner = fi->lock_owner;
+
4226 pthread_mutex_lock(&f->lock);
+
4227 locks_insert(get_node(f, ino), &l);
+
4228 pthread_mutex_unlock(&f->lock);
+
4229 }
+
4230 reply_err(req, err);
+
4231}
+
4232
+
4233static void fuse_lib_flock(fuse_req_t req, fuse_ino_t ino,
+
4234 struct fuse_file_info *fi, int op)
+
4235{
+
4236 struct fuse *f = req_fuse_prepare(req);
+
4237 char *path;
+
4238 int err;
+
4239
+
4240 err = get_path_nullok(f, ino, &path);
+
4241 if (err == 0) {
+
4242 struct fuse_intr_data d;
+
4243 fuse_prepare_interrupt(f, req, &d);
+
4244 err = fuse_fs_flock(f->fs, path, fi, op);
+
4245 fuse_finish_interrupt(f, req, &d);
+
4246 free_path(f, ino, path);
+
4247 }
+
4248 reply_err(req, err);
+
4249}
+
4250
+
4251static void fuse_lib_bmap(fuse_req_t req, fuse_ino_t ino, size_t blocksize,
+
4252 uint64_t idx)
+
4253{
+
4254 struct fuse *f = req_fuse_prepare(req);
+
4255 struct fuse_intr_data d;
+
4256 char *path;
+
4257 int err;
+
4258
+
4259 err = get_path(f, ino, &path);
+
4260 if (!err) {
+
4261 fuse_prepare_interrupt(f, req, &d);
+
4262 err = fuse_fs_bmap(f->fs, path, blocksize, &idx);
+
4263 fuse_finish_interrupt(f, req, &d);
+
4264 free_path(f, ino, path);
+
4265 }
+
4266 if (!err)
+
4267 fuse_reply_bmap(req, idx);
+
4268 else
+
4269 reply_err(req, err);
+
4270}
+
4271
+
4272static void fuse_lib_ioctl(fuse_req_t req, fuse_ino_t ino, unsigned int cmd,
+
4273 void *arg, struct fuse_file_info *llfi,
+
4274 unsigned int flags, const void *in_buf,
+
4275 size_t in_bufsz, size_t out_bufsz)
+
4276{
+
4277 struct fuse *f = req_fuse_prepare(req);
+
4278 struct fuse_intr_data d;
+
4279 struct fuse_file_info fi;
+
4280 char *path, *out_buf = NULL;
+
4281 int err;
+
4282
+
4283 err = -EPERM;
+
4284 if (flags & FUSE_IOCTL_UNRESTRICTED)
+
4285 goto err;
+
4286
+
4287 if (flags & FUSE_IOCTL_DIR)
+
4288 get_dirhandle(llfi, &fi);
+
4289 else
+
4290 fi = *llfi;
+
4291
+
4292 if (out_bufsz) {
+
4293 err = -ENOMEM;
+
4294 out_buf = malloc(out_bufsz);
+
4295 if (!out_buf)
+
4296 goto err;
+
4297 }
+
4298
+
4299 assert(!in_bufsz || !out_bufsz || in_bufsz == out_bufsz);
+
4300 if (out_buf && in_bufsz)
+
4301 memcpy(out_buf, in_buf, in_bufsz);
+
4302
+
4303 err = get_path_nullok(f, ino, &path);
+
4304 if (err)
+
4305 goto err;
+
4306
+
4307 fuse_prepare_interrupt(f, req, &d);
+
4308
+
4309 err = fuse_fs_ioctl(f->fs, path, cmd, arg, &fi, flags,
+
4310 out_buf ? out_buf : (void *)in_buf);
+
4311
+
4312 fuse_finish_interrupt(f, req, &d);
+
4313 free_path(f, ino, path);
+
4314
+
4315 if (err < 0)
+
4316 goto err;
+
4317 fuse_reply_ioctl(req, err, out_buf, out_bufsz);
+
4318 goto out;
+
4319err:
+
4320 reply_err(req, err);
+
4321out:
+
4322 free(out_buf);
+
4323}
+
4324
+
4325static void fuse_lib_poll(fuse_req_t req, fuse_ino_t ino,
+
4326 struct fuse_file_info *fi, struct fuse_pollhandle *ph)
+
4327{
+
4328 struct fuse *f = req_fuse_prepare(req);
+
4329 struct fuse_intr_data d;
+
4330 char *path;
+
4331 int err;
+
4332 unsigned revents = 0;
+
4333
+
4334 err = get_path_nullok(f, ino, &path);
+
4335 if (!err) {
+
4336 fuse_prepare_interrupt(f, req, &d);
+
4337 err = fuse_fs_poll(f->fs, path, fi, ph, &revents);
+
4338 fuse_finish_interrupt(f, req, &d);
+
4339 free_path(f, ino, path);
+
4340 } else {
+ +
4342 }
+
4343 if (!err)
+
4344 fuse_reply_poll(req, revents);
+
4345 else
+
4346 reply_err(req, err);
+
4347}
+
4348
+
4349static void fuse_lib_fallocate(fuse_req_t req, fuse_ino_t ino, int mode,
+
4350 off_t offset, off_t length, struct fuse_file_info *fi)
+
4351{
+
4352 struct fuse *f = req_fuse_prepare(req);
+
4353 struct fuse_intr_data d;
+
4354 char *path;
+
4355 int err;
+
4356
+
4357 err = get_path_nullok(f, ino, &path);
+
4358 if (!err) {
+
4359 fuse_prepare_interrupt(f, req, &d);
+
4360 err = fuse_fs_fallocate(f->fs, path, mode, offset, length, fi);
+
4361 fuse_finish_interrupt(f, req, &d);
+
4362 free_path(f, ino, path);
+
4363 }
+
4364 reply_err(req, err);
+
4365}
+
4366
+
4367static void fuse_lib_copy_file_range(fuse_req_t req, fuse_ino_t nodeid_in,
+
4368 off_t off_in, struct fuse_file_info *fi_in,
+
4369 fuse_ino_t nodeid_out, off_t off_out,
+
4370 struct fuse_file_info *fi_out, size_t len,
+
4371 int flags)
+
4372{
+
4373 struct fuse *f = req_fuse_prepare(req);
+
4374 struct fuse_intr_data d;
+
4375 char *path_in, *path_out;
+
4376 int err;
+
4377 ssize_t res;
+
4378
+
4379 err = get_path_nullok(f, nodeid_in, &path_in);
+
4380 if (err) {
+
4381 reply_err(req, err);
+
4382 return;
+
4383 }
+
4384
+
4385 err = get_path_nullok(f, nodeid_out, &path_out);
+
4386 if (err) {
+
4387 free_path(f, nodeid_in, path_in);
+
4388 reply_err(req, err);
+
4389 return;
+
4390 }
+
4391
+
4392 fuse_prepare_interrupt(f, req, &d);
+
4393 res = fuse_fs_copy_file_range(f->fs, path_in, fi_in, off_in, path_out,
+
4394 fi_out, off_out, len, flags);
+
4395 fuse_finish_interrupt(f, req, &d);
+
4396
+
4397 if (res >= 0)
+
4398 fuse_reply_write(req, res);
+
4399 else
+
4400 reply_err(req, res);
+
4401
+
4402 free_path(f, nodeid_in, path_in);
+
4403 free_path(f, nodeid_out, path_out);
+
4404}
+
4405
+
4406static void fuse_lib_lseek(fuse_req_t req, fuse_ino_t ino, off_t off, int whence,
+
4407 struct fuse_file_info *fi)
+
4408{
+
4409 struct fuse *f = req_fuse_prepare(req);
+
4410 struct fuse_intr_data d;
+
4411 char *path;
+
4412 int err;
+
4413 off_t res;
+
4414
+
4415 err = get_path(f, ino, &path);
+
4416 if (err) {
+
4417 reply_err(req, err);
+
4418 return;
+
4419 }
+
4420
+
4421 fuse_prepare_interrupt(f, req, &d);
+
4422 res = fuse_fs_lseek(f->fs, path, off, whence, fi);
+
4423 fuse_finish_interrupt(f, req, &d);
+
4424 free_path(f, ino, path);
+
4425 if (res >= 0)
+
4426 fuse_reply_lseek(req, res);
+
4427 else
+
4428 reply_err(req, res);
+
4429}
+
4430
+
4431#ifdef HAVE_STATX
+
4432static void fuse_lib_statx(fuse_req_t req, fuse_ino_t ino, int flags, int mask,
+
4433 struct fuse_file_info *fi)
+
4434{
+
4435 struct fuse *f = req_fuse_prepare(req);
+
4436 struct statx stxbuf;
+
4437 char *path;
+
4438 int err;
+
4439
+
4440 memset(&stxbuf, 0, sizeof(stxbuf));
+
4441
+
4442 if (fi != NULL)
+
4443 err = get_path_nullok(f, ino, &path);
+
4444 else
+
4445 err = get_path(f, ino, &path);
+
4446
+
4447 if (!err) {
+
4448 struct fuse_intr_data d;
+
4449
+
4450 if (!path)
+
4451 flags |= AT_EMPTY_PATH;
+
4452 fuse_prepare_interrupt(f, req, &d);
+
4453 err = fuse_fs_statx(f->fs, path, flags, mask, &stxbuf, fi);
+
4454 fuse_finish_interrupt(f, req, &d);
+
4455 free_path(f, ino, path);
+
4456 }
+
4457 if (!err) {
+
4458 struct node *node;
+
4459
+
4460 pthread_mutex_lock(&f->lock);
+
4461 node = get_node(f, ino);
+
4462 if (node->is_hidden && stxbuf.stx_nlink > 0)
+
4463 stxbuf.stx_nlink--;
+
4464 if (f->conf.auto_cache) {
+
4465 struct stat stbuf;
+
4466
+
4467 stbuf.st_mtime = stxbuf.stx_mtime.tv_sec;
+
4468 ST_MTIM_NSEC_SET(&stbuf, stxbuf.stx_mtime.tv_nsec);
+
4469 stbuf.st_size = stxbuf.stx_size;
+
4470 update_stat(node, &stbuf);
+
4471 }
+
4472 pthread_mutex_unlock(&f->lock);
+
4473 set_statx(f, ino, &stxbuf);
+
4474 fuse_reply_statx(req, 0, &stxbuf, f->conf.attr_timeout);
+
4475 } else
+
4476 reply_err(req, err);
+
4477}
+
4478#endif
+
4479
+
4480static int clean_delay(struct fuse *f)
+
4481{
+
4482 /*
+
4483 * This is calculating the delay between clean runs. To
+
4484 * reduce the number of cleans we are doing them 10 times
+
4485 * within the remember window.
+
4486 */
+
4487 int min_sleep = 60;
+
4488 int max_sleep = 3600;
+
4489 int sleep_time = f->conf.remember / 10;
+
4490
+
4491 if (sleep_time > max_sleep)
+
4492 return max_sleep;
+
4493 if (sleep_time < min_sleep)
+
4494 return min_sleep;
+
4495 return sleep_time;
+
4496}
+
4497
+
+
4498int fuse_clean_cache(struct fuse *f)
+
4499{
+
4500 struct node_lru *lnode;
+
4501 struct list_head *curr, *next;
+
4502 struct node *node;
+
4503 struct timespec now;
+
4504
+
4505 pthread_mutex_lock(&f->lock);
+
4506
+
4507 curr_time(&now);
+
4508
+
4509 for (curr = f->lru_table.next; curr != &f->lru_table; curr = next) {
+
4510 double age;
+
4511
+
4512 next = curr->next;
+
4513 lnode = list_entry(curr, struct node_lru, lru);
+
4514 node = &lnode->node;
+
4515
+
4516 age = diff_timespec(&now, &lnode->forget_time);
+
4517 if (age <= f->conf.remember)
+
4518 break;
+
4519
+
4520 assert(node->nlookup == 1);
+
4521
+
4522 /* Don't forget active directories */
+
4523 if (node->refctr > 1)
+
4524 continue;
+
4525
+
4526 node->nlookup = 0;
+
4527 unhash_name(f, node);
+
4528 unref_node(f, node);
+
4529 }
+
4530 pthread_mutex_unlock(&f->lock);
+
4531
+
4532 return clean_delay(f);
+
4533}
+
+
4534
+
4535static struct fuse_lowlevel_ops fuse_path_ops = {
+
4536 .init = fuse_lib_init,
+
4537 .destroy = fuse_lib_destroy,
+
4538 .lookup = fuse_lib_lookup,
+
4539 .forget = fuse_lib_forget,
+
4540 .forget_multi = fuse_lib_forget_multi,
+
4541 .getattr = fuse_lib_getattr,
+
4542 .setattr = fuse_lib_setattr,
+
4543 .access = fuse_lib_access,
+
4544 .readlink = fuse_lib_readlink,
+
4545 .mknod = fuse_lib_mknod,
+
4546 .mkdir = fuse_lib_mkdir,
+
4547 .unlink = fuse_lib_unlink,
+
4548 .rmdir = fuse_lib_rmdir,
+
4549 .symlink = fuse_lib_symlink,
+
4550 .rename = fuse_lib_rename,
+
4551 .link = fuse_lib_link,
+
4552 .create = fuse_lib_create,
+
4553 .open = fuse_lib_open,
+
4554 .read = fuse_lib_read,
+
4555 .write_buf = fuse_lib_write_buf,
+
4556 .flush = fuse_lib_flush,
+
4557 .release = fuse_lib_release,
+
4558 .fsync = fuse_lib_fsync,
+
4559 .opendir = fuse_lib_opendir,
+
4560 .readdir = fuse_lib_readdir,
+
4561 .readdirplus = fuse_lib_readdirplus,
+
4562 .releasedir = fuse_lib_releasedir,
+
4563 .fsyncdir = fuse_lib_fsyncdir,
+
4564 .statfs = fuse_lib_statfs,
+
4565 .setxattr = fuse_lib_setxattr,
+
4566 .getxattr = fuse_lib_getxattr,
+
4567 .listxattr = fuse_lib_listxattr,
+
4568 .removexattr = fuse_lib_removexattr,
+
4569 .getlk = fuse_lib_getlk,
+
4570 .setlk = fuse_lib_setlk,
+
4571 .flock = fuse_lib_flock,
+
4572 .bmap = fuse_lib_bmap,
+
4573 .ioctl = fuse_lib_ioctl,
+
4574 .poll = fuse_lib_poll,
+
4575 .fallocate = fuse_lib_fallocate,
+
4576 .copy_file_range = fuse_lib_copy_file_range,
+
4577 .lseek = fuse_lib_lseek,
+
4578#ifdef HAVE_STATX
+
4579 .statx = fuse_lib_statx,
+
4580#endif
+
4581};
+
4582
+
4583int fuse_notify_poll(struct fuse_pollhandle *ph)
+
4584{
+
4585 return fuse_lowlevel_notify_poll(ph);
+
4586}
+
4587
+
+
4588struct fuse_session *fuse_get_session(struct fuse *f)
+
4589{
+
4590 return f->se;
+
4591}
+
+
4592
+
4593static int fuse_session_loop_remember(struct fuse *f)
+
4594{
+
4595 struct fuse_session *se = f->se;
+
4596 int res = 0;
+
4597 struct timespec now;
+
4598 time_t next_clean;
+
4599 struct pollfd fds = {
+
4600 .fd = se->fd,
+
4601 .events = POLLIN
+
4602 };
+
4603 struct fuse_buf fbuf = {
+
4604 .mem = NULL,
+
4605 };
+
4606
+
4607 curr_time(&now);
+
4608 next_clean = now.tv_sec;
+
4609 while (!fuse_session_exited(se)) {
+
4610 unsigned timeout;
+
4611
+
4612 curr_time(&now);
+
4613 if (now.tv_sec < next_clean)
+
4614 timeout = next_clean - now.tv_sec;
+
4615 else
+
4616 timeout = 0;
+
4617
+
4618 res = poll(&fds, 1, timeout * 1000);
+
4619 if (res == -1) {
+
4620 if (errno == EINTR)
+
4621 continue;
+
4622 else
+
4623 break;
+
4624 } else if (res > 0) {
+
4625 res = fuse_session_receive_buf_internal(se, &fbuf,
+
4626 NULL);
+
4627 if (res == -EINTR)
+
4628 continue;
+
4629 if (res <= 0)
+
4630 break;
+
4631
+
4632 fuse_session_process_buf_internal(se, &fbuf, NULL);
+
4633 } else {
+
4634 timeout = fuse_clean_cache(f);
+
4635 curr_time(&now);
+
4636 next_clean = now.tv_sec + timeout;
+
4637 }
+
4638 }
+
4639
+
4640 fuse_buf_free(&fbuf);
+
4641 return res < 0 ? -1 : 0;
+
4642}
+
4643
+
+
4644int fuse_loop(struct fuse *f)
+
4645{
+
4646 if (!f)
+
4647 return -1;
+
4648
+
4649 if (lru_enabled(f))
+
4650 return fuse_session_loop_remember(f);
+
4651
+
4652 return fuse_session_loop(f->se);
+
4653}
+
+
4654
+
4655FUSE_SYMVER("fuse_loop_mt_312", "fuse_loop_mt@@FUSE_3.12")
+
4656int fuse_loop_mt_312(struct fuse *f, struct fuse_loop_config *config)
+
4657{
+
4658 if (f == NULL)
+
4659 return -1;
+
4660
+
4661 int res = fuse_start_cleanup_thread(f);
+
4662 if (res)
+
4663 return -1;
+
4664
+
4665 res = fuse_session_loop_mt_312(fuse_get_session(f), config);
+ +
4667 return res;
+
4668}
+
4669
+
4670int fuse_loop_mt_32(struct fuse *f, struct fuse_loop_config_v1 *config_v1);
+
4671FUSE_SYMVER("fuse_loop_mt_32", "fuse_loop_mt@FUSE_3.2")
+
4672int fuse_loop_mt_32(struct fuse *f, struct fuse_loop_config_v1 *config_v1)
+
4673{
+
4674 struct fuse_loop_config *config = fuse_loop_cfg_create();
+
4675 if (config == NULL)
+
4676 return ENOMEM;
+
4677
+
4678 fuse_loop_cfg_convert(config, config_v1);
+
4679
+
4680 int res = fuse_loop_mt_312(f, config);
+
4681
+
4682 fuse_loop_cfg_destroy(config);
+
4683
+
4684 return res;
+
4685}
+
4686
+
4687int fuse_loop_mt_31(struct fuse *f, int clone_fd);
+
4688FUSE_SYMVER("fuse_loop_mt_31", "fuse_loop_mt@FUSE_3.0")
+
4689int fuse_loop_mt_31(struct fuse *f, int clone_fd)
+
4690{
+
4691 int err;
+
4692 struct fuse_loop_config *config = fuse_loop_cfg_create();
+
4693
+
4694 if (config == NULL)
+
4695 return ENOMEM;
+
4696
+
4697 fuse_loop_cfg_set_clone_fd(config, clone_fd);
+
4698
+
4699 err = fuse_loop_mt_312(f, config);
+
4700
+
4701 fuse_loop_cfg_destroy(config);
+
4702
+
4703 return err;
+
4704}
+
4705
+
+
4706void fuse_exit(struct fuse *f)
+
4707{
+
4708 fuse_session_exit(f->se);
+
4709}
+
+
4710
+
+ +
4712{
+
4713 struct fuse_context_i *c = fuse_get_context_internal();
+
4714
+
4715 if (c)
+
4716 return &c->ctx;
+
4717 else
+
4718 return NULL;
+
4719}
+
+
4720
+
+
4721int fuse_getgroups(int size, gid_t list[])
+
4722{
+
4723 struct fuse_context_i *c = fuse_get_context_internal();
+
4724 if (!c)
+
4725 return -EINVAL;
+
4726
+
4727 return fuse_req_getgroups(c->req, size, list);
+
4728}
+
+
4729
+
+ +
4731{
+
4732 struct fuse_context_i *c = fuse_get_context_internal();
+
4733
+
4734 if (c)
+
4735 return fuse_req_interrupted(c->req);
+
4736 else
+
4737 return 0;
+
4738}
+
+
4739
+
+
4740int fuse_invalidate_path(struct fuse *f, const char *path) {
+
4741 fuse_ino_t ino;
+
4742 int err = lookup_path_in_cache(f, path, &ino);
+
4743 if (err) {
+
4744 return err;
+
4745 }
+
4746
+
4747 return fuse_lowlevel_notify_inval_inode(f->se, ino, 0, 0);
+
4748}
+
+
4749
+
4750#define FUSE_LIB_OPT(t, p, v) { t, offsetof(struct fuse_config, p), v }
+
4751
+
4752static const struct fuse_opt fuse_lib_opts[] = {
+ + +
4755 FUSE_LIB_OPT("debug", debug, 1),
+
4756 FUSE_LIB_OPT("-d", debug, 1),
+
4757 FUSE_LIB_OPT("kernel_cache", kernel_cache, 1),
+
4758 FUSE_LIB_OPT("auto_cache", auto_cache, 1),
+
4759 FUSE_LIB_OPT("noauto_cache", auto_cache, 0),
+
4760 FUSE_LIB_OPT("no_rofd_flush", no_rofd_flush, 1),
+
4761 FUSE_LIB_OPT("umask=", set_mode, 1),
+
4762 FUSE_LIB_OPT("umask=%o", umask, 0),
+
4763 FUSE_LIB_OPT("fmask=", set_mode, 1),
+
4764 FUSE_LIB_OPT("fmask=%o", fmask, 0),
+
4765 FUSE_LIB_OPT("dmask=", set_mode, 1),
+
4766 FUSE_LIB_OPT("dmask=%o", dmask, 0),
+
4767 FUSE_LIB_OPT("uid=", set_uid, 1),
+
4768 FUSE_LIB_OPT("uid=%d", uid, 0),
+
4769 FUSE_LIB_OPT("gid=", set_gid, 1),
+
4770 FUSE_LIB_OPT("gid=%d", gid, 0),
+
4771 FUSE_LIB_OPT("entry_timeout=%lf", entry_timeout, 0),
+
4772 FUSE_LIB_OPT("attr_timeout=%lf", attr_timeout, 0),
+
4773 FUSE_LIB_OPT("ac_attr_timeout=%lf", ac_attr_timeout, 0),
+
4774 FUSE_LIB_OPT("ac_attr_timeout=", ac_attr_timeout_set, 1),
+
4775 FUSE_LIB_OPT("negative_timeout=%lf", negative_timeout, 0),
+
4776 FUSE_LIB_OPT("noforget", remember, -1),
+
4777 FUSE_LIB_OPT("remember=%u", remember, 0),
+
4778 FUSE_LIB_OPT("modules=%s", modules, 0),
+
4779 FUSE_LIB_OPT("parallel_direct_write=%d", parallel_direct_writes, 0),
+ +
4781};
+
4782
+
4783static int fuse_lib_opt_proc(void *data, const char *arg, int key,
+
4784 struct fuse_args *outargs)
+
4785{
+
4786 (void) arg; (void) outargs; (void) data; (void) key;
+
4787
+
4788 /* Pass through unknown options */
+
4789 return 1;
+
4790}
+
4791
+
4792
+
4793static const struct fuse_opt fuse_help_opts[] = {
+
4794 FUSE_LIB_OPT("modules=%s", modules, 1),
+
4795 FUSE_OPT_KEY("modules=%s", FUSE_OPT_KEY_KEEP),
+ +
4797};
+
4798
+
4799static void print_module_help(const char *name,
+ +
4801{
+
4802 struct fuse_args a = FUSE_ARGS_INIT(0, NULL);
+
4803 if (fuse_opt_add_arg(&a, "") == -1 ||
+
4804 fuse_opt_add_arg(&a, "-h") == -1) {
+ +
4806 return;
+
4807 }
+
4808 printf("\nOptions for %s module:\n", name);
+
4809 (*fac)(&a, NULL);
+ +
4811}
+
4812
+
+
4813void fuse_lib_help(struct fuse_args *args)
+
4814{
+
4815 /* These are not all options, but only the ones that
+
4816 may be of interest to an end-user */
+
4817 printf(
+
4818" -o kernel_cache cache files in kernel\n"
+
4819" -o [no]auto_cache enable caching based on modification times (off)\n"
+
4820" -o no_rofd_flush disable flushing of read-only fd on close (off)\n"
+
4821" -o umask=M set file permissions (octal)\n"
+
4822" -o fmask=M set file permissions (octal)\n"
+
4823" -o dmask=M set dir permissions (octal)\n"
+
4824" -o uid=N set file owner\n"
+
4825" -o gid=N set file group\n"
+
4826" -o entry_timeout=T cache timeout for names (1.0s)\n"
+
4827" -o negative_timeout=T cache timeout for deleted names (0.0s)\n"
+
4828" -o attr_timeout=T cache timeout for attributes (1.0s)\n"
+
4829" -o ac_attr_timeout=T auto cache timeout for attributes (attr_timeout)\n"
+
4830" -o noforget never forget cached inodes\n"
+
4831" -o remember=T remember cached inodes for T seconds (0s)\n"
+
4832" -o modules=M1[:M2...] names of modules to push onto filesystem stack\n");
+
4833
+
4834
+
4835 /* Print low-level help */
+ +
4837
+
4838 /* Print help for builtin modules */
+
4839 print_module_help("subdir", &fuse_module_subdir_factory);
+
4840#ifdef HAVE_ICONV
+
4841 print_module_help("iconv", &fuse_module_iconv_factory);
+
4842#endif
+
4843
+
4844 /* Parse command line options in case we need to
+
4845 activate more modules */
+
4846 struct fuse_config conf = { .modules = NULL };
+
4847 if (fuse_opt_parse(args, &conf, fuse_help_opts,
+
4848 fuse_lib_opt_proc) == -1
+
4849 || !conf.modules)
+
4850 return;
+
4851
+
4852 char *module;
+
4853 char *next;
+
4854 struct fuse_module *m;
+
4855
+
4856 // Iterate over all modules
+
4857 for (module = conf.modules; module; module = next) {
+
4858 char *p;
+
4859 for (p = module; *p && *p != ':'; p++);
+
4860 next = *p ? p + 1 : NULL;
+
4861 *p = '\0';
+
4862
+
4863 m = fuse_get_module(module);
+
4864 if (m)
+
4865 print_module_help(module, &m->factory);
+
4866 }
+
4867}
+
+
4868
+
4869static int fuse_init_intr_signal(int signum, int *installed)
+
4870{
+
4871 struct sigaction old_sa;
+
4872
+
4873 if (sigaction(signum, NULL, &old_sa) == -1) {
+
4874 perror("fuse: cannot get old signal handler");
+
4875 return -1;
+
4876 }
+
4877
+
4878 if (old_sa.sa_handler == SIG_DFL) {
+
4879 struct sigaction sa;
+
4880
+
4881 memset(&sa, 0, sizeof(struct sigaction));
+
4882 sa.sa_handler = fuse_intr_sighandler;
+
4883 sigemptyset(&sa.sa_mask);
+
4884
+
4885 if (sigaction(signum, &sa, NULL) == -1) {
+
4886 perror("fuse: cannot set interrupt signal handler");
+
4887 return -1;
+
4888 }
+
4889 *installed = 1;
+
4890 }
+
4891 return 0;
+
4892}
+
4893
+
4894static void fuse_restore_intr_signal(int signum)
+
4895{
+
4896 struct sigaction sa;
+
4897
+
4898 memset(&sa, 0, sizeof(struct sigaction));
+
4899 sa.sa_handler = SIG_DFL;
+
4900 sigaction(signum, &sa, NULL);
+
4901}
+
4902
+
4903
+
4904static int fuse_push_module(struct fuse *f, const char *module,
+
4905 struct fuse_args *args)
+
4906{
+
4907 struct fuse_fs *fs[2] = { f->fs, NULL };
+
4908 struct fuse_fs *newfs;
+
4909 struct fuse_module *m = fuse_get_module(module);
+
4910
+
4911 if (!m)
+
4912 return -1;
+
4913
+
4914 newfs = m->factory(args, fs);
+
4915 if (!newfs) {
+
4916 fuse_put_module(m);
+
4917 return -1;
+
4918 }
+
4919 f->fs = newfs;
+
4920 return 0;
+
4921}
+
4922
+
+
4923struct fuse_fs *fuse_fs_new(const struct fuse_operations *op, size_t op_size,
+
4924 void *user_data)
+
4925{
+
4926 struct fuse_fs *fs;
+
4927
+
4928 if (sizeof(struct fuse_operations) < op_size) {
+
4929 fuse_log(FUSE_LOG_ERR, "fuse: warning: library too old, some operations may not not work\n");
+
4930 op_size = sizeof(struct fuse_operations);
+
4931 }
+
4932
+
4933 fs = (struct fuse_fs *) calloc(1, sizeof(struct fuse_fs));
+
4934 if (!fs) {
+
4935 fuse_log(FUSE_LOG_ERR, "fuse: failed to allocate fuse_fs object\n");
+
4936 return NULL;
+
4937 }
+
4938
+
4939 fs->user_data = user_data;
+
4940 if (op)
+
4941 memcpy(&fs->op, op, op_size);
+
4942 return fs;
+
4943}
+
+
4944
+
4945static int node_table_init(struct node_table *t)
+
4946{
+
4947 t->size = NODE_TABLE_MIN_SIZE;
+
4948 t->array = (struct node **) calloc(1, sizeof(struct node *) * t->size);
+
4949 if (t->array == NULL) {
+
4950 fuse_log(FUSE_LOG_ERR, "fuse: memory allocation failed\n");
+
4951 return -1;
+
4952 }
+
4953 t->use = 0;
+
4954 t->split = 0;
+
4955
+
4956 return 0;
+
4957}
+
4958
+
4959static void *fuse_prune_nodes(void *fuse)
+
4960{
+
4961 struct fuse *f = fuse;
+
4962 int sleep_time;
+
4963
+
4964 fuse_set_thread_name("fuse_prune_nodes");
+
4965
+
4966 while(1) {
+
4967 sleep_time = fuse_clean_cache(f);
+
4968 sleep(sleep_time);
+
4969 }
+
4970 return NULL;
+
4971}
+
4972
+
+
4973int fuse_start_cleanup_thread(struct fuse *f)
+
4974{
+
4975 if (lru_enabled(f))
+
4976 return fuse_start_thread(&f->prune_thread, fuse_prune_nodes, f);
+
4977
+
4978 return 0;
+
4979}
+
+
4980
+
+
4981void fuse_stop_cleanup_thread(struct fuse *f)
+
4982{
+
4983 if (lru_enabled(f)) {
+
4984 pthread_mutex_lock(&f->lock);
+
4985 pthread_cancel(f->prune_thread);
+
4986 pthread_mutex_unlock(&f->lock);
+
4987 pthread_join(f->prune_thread, NULL);
+
4988 }
+
4989}
+
+
4990
+
4991/*
+
4992 * Not supposed to be called directly, but supposed to be called
+
4993 * through the fuse_new macro
+
4994 */
+
4995struct fuse *_fuse_new_31(struct fuse_args *args,
+
4996 const struct fuse_operations *op, size_t op_size,
+
4997 struct libfuse_version *version, void *user_data)
+
4998{
+
4999 struct fuse *f;
+
5000 struct node *root;
+
5001 struct fuse_fs *fs;
+
5002 struct fuse_lowlevel_ops llop = fuse_path_ops;
+
5003
+
5004 f = (struct fuse *) calloc(1, sizeof(struct fuse));
+
5005 if (f == NULL) {
+
5006 fuse_log(FUSE_LOG_ERR, "fuse: failed to allocate fuse object\n");
+
5007 goto out;
+
5008 }
+
5009
+
5010 f->conf.entry_timeout = 1.0;
+
5011 f->conf.attr_timeout = 1.0;
+
5012 f->conf.negative_timeout = 0.0;
+
5013 f->conf.intr_signal = FUSE_DEFAULT_INTR_SIGNAL;
+
5014
+
5015 /* Parse options */
+
5016 if (fuse_opt_parse(args, &f->conf, fuse_lib_opts,
+
5017 fuse_lib_opt_proc) == -1)
+
5018 goto out_free;
+
5019
+
5020 pthread_mutex_lock(&fuse_context_lock);
+
5021 static int builtin_modules_registered = 0;
+
5022 /* Have the builtin modules already been registered? */
+
5023 if (builtin_modules_registered == 0) {
+
5024 /* If not, register them. */
+
5025 fuse_register_module("subdir", fuse_module_subdir_factory, NULL);
+
5026#ifdef HAVE_ICONV
+
5027 fuse_register_module("iconv", fuse_module_iconv_factory, NULL);
+
5028#endif
+
5029 builtin_modules_registered= 1;
+
5030 }
+
5031 pthread_mutex_unlock(&fuse_context_lock);
+
5032
+
5033 if (fuse_create_context_key() == -1)
+
5034 goto out_free;
+
5035
+
5036 fs = fuse_fs_new(op, op_size, user_data);
+
5037 if (!fs)
+
5038 goto out_delete_context_key;
+
5039
+
5040 f->fs = fs;
+
5041
+
5042 /* Oh f**k, this is ugly! */
+
5043 if (!fs->op.lock) {
+
5044 llop.getlk = NULL;
+
5045 llop.setlk = NULL;
+
5046 }
+
5047
+
5048 f->pagesize = getpagesize();
+
5049 init_list_head(&f->partial_slabs);
+
5050 init_list_head(&f->full_slabs);
+
5051 init_list_head(&f->lru_table);
+
5052
+
5053 if (f->conf.modules) {
+
5054 char *module;
+
5055 char *next;
+
5056
+
5057 for (module = f->conf.modules; module; module = next) {
+
5058 char *p;
+
5059 for (p = module; *p && *p != ':'; p++);
+
5060 next = *p ? p + 1 : NULL;
+
5061 *p = '\0';
+
5062 if (module[0] &&
+
5063 fuse_push_module(f, module, args) == -1)
+
5064 goto out_free_fs;
+
5065 }
+
5066 }
+
5067
+
5068 if (!f->conf.ac_attr_timeout_set)
+
5069 f->conf.ac_attr_timeout = f->conf.attr_timeout;
+
5070
+
5071#if defined(__FreeBSD__) || defined(__NetBSD__)
+
5072 /*
+
5073 * In FreeBSD, we always use these settings as inode numbers
+
5074 * are needed to make getcwd(3) work.
+
5075 */
+
5076 f->conf.readdir_ino = 1;
+
5077#endif
+
5078
+
5079 /* not declared globally, to restrict usage of this function */
+
5080 struct fuse_session *fuse_session_new_versioned(
+
5081 struct fuse_args *args, const struct fuse_lowlevel_ops *op,
+
5082 size_t op_size, struct libfuse_version *version,
+
5083 void *userdata);
+
5084 f->se = fuse_session_new_versioned(args, &llop, sizeof(llop), version,
+
5085 f);
+
5086 if (f->se == NULL)
+
5087 goto out_free_fs;
+
5088
+
5089 /* Trace topmost layer by default */
+
5090 f->fs->debug = f->conf.debug;
+
5091 f->ctr = 0;
+
5092 f->generation = 0;
+
5093 if (node_table_init(&f->name_table) == -1)
+
5094 goto out_free_session;
+
5095
+
5096 if (node_table_init(&f->id_table) == -1)
+
5097 goto out_free_name_table;
+
5098
+
5099 pthread_mutex_init(&f->lock, NULL);
+
5100
+
5101 root = alloc_node(f);
+
5102 if (root == NULL) {
+
5103 fuse_log(FUSE_LOG_ERR, "fuse: memory allocation failed\n");
+
5104 goto out_free_id_table;
+
5105 }
+
5106 if (lru_enabled(f)) {
+
5107 struct node_lru *lnode = node_lru(root);
+
5108 init_list_head(&lnode->lru);
+
5109 }
+
5110
+
5111 strcpy(root->inline_name, "/");
+
5112 root->name = root->inline_name;
+
5113 root->parent = NULL;
+
5114 root->nodeid = FUSE_ROOT_ID;
+
5115 inc_nlookup(root);
+
5116 hash_id(f, root);
+
5117
+
5118 return f;
+
5119
+
5120out_free_id_table:
+
5121 free(f->id_table.array);
+
5122out_free_name_table:
+
5123 free(f->name_table.array);
+
5124out_free_session:
+
5125 fuse_session_destroy(f->se);
+
5126out_free_fs:
+
5127 free(f->fs);
+
5128 free(f->conf.modules);
+
5129out_delete_context_key:
+
5130 fuse_delete_context_key();
+
5131out_free:
+
5132 free(f);
+
5133out:
+
5134 return NULL;
+
5135}
+
5136
+
5137/* Emulates 3.0-style fuse_new(), which processes --help */
+
5138FUSE_SYMVER("_fuse_new_30", "_fuse_new@FUSE_3.0")
+
5139struct fuse *_fuse_new_30(struct fuse_args *args,
+
5140 const struct fuse_operations *op,
+
5141 size_t op_size,
+
5142 struct libfuse_version *version,
+
5143 void *user_data)
+
5144{
+
5145 struct fuse_config conf = {0};
+
5146
+
5147 const struct fuse_opt opts[] = {
+
5148 FUSE_LIB_OPT("-h", show_help, 1),
+
5149 FUSE_LIB_OPT("--help", show_help, 1),
+ +
5151 };
+
5152
+
5153 if (fuse_opt_parse(args, &conf, opts,
+
5154 fuse_lib_opt_proc) == -1)
+
5155 return NULL;
+
5156
+
5157 if (conf.show_help) {
+
5158 fuse_lib_help(args);
+
5159 return NULL;
+
5160 } else
+
5161 return _fuse_new_31(args, op, op_size, version, user_data);
+
5162}
+
5163
+
5164/* ABI compat version */
+
5165struct fuse *fuse_new_31(struct fuse_args *args, const struct fuse_operations *op,
+
5166 size_t op_size, void *user_data);
+
5167FUSE_SYMVER("fuse_new_31", "fuse_new@FUSE_3.1")
+
5168struct fuse *fuse_new_31(struct fuse_args *args,
+
5169 const struct fuse_operations *op,
+
5170 size_t op_size, void *user_data)
+
5171{
+
5172 /* unknown version */
+
5173 struct libfuse_version version = { 0 };
+
5174
+
5175 return _fuse_new_31(args, op, op_size, &version, user_data);
+
5176}
+
5177
+
5178/*
+
5179 * ABI compat version
+
5180 * Emulates 3.0-style fuse_new(), which processes --help
+
5181 */
+
5182struct fuse *fuse_new_30(struct fuse_args *args, const struct fuse_operations *op,
+
5183 size_t op_size, void *user_data);
+
5184FUSE_SYMVER("fuse_new_30", "fuse_new@FUSE_3.0")
+
5185struct fuse *fuse_new_30(struct fuse_args *args,
+
5186 const struct fuse_operations *op,
+
5187 size_t op_size, void *user_data)
+
5188{
+
5189 struct fuse_config conf = {0};
+
5190
+
5191 const struct fuse_opt opts[] = {
+
5192 FUSE_LIB_OPT("-h", show_help, 1),
+
5193 FUSE_LIB_OPT("--help", show_help, 1),
+ +
5195 };
+
5196
+
5197 if (fuse_opt_parse(args, &conf, opts,
+
5198 fuse_lib_opt_proc) == -1)
+
5199 return NULL;
+
5200
+
5201 if (conf.show_help) {
+
5202 fuse_lib_help(args);
+
5203 return NULL;
+
5204 } else
+
5205 return fuse_new_31(args, op, op_size, user_data);
+
5206}
+
5207
+
5208
+
+
5209void fuse_destroy(struct fuse *f)
+
5210{
+
5211 size_t i;
+
5212
+
5213 if (f->conf.intr && f->intr_installed)
+
5214 fuse_restore_intr_signal(f->conf.intr_signal);
+
5215
+
5216 if (f->fs) {
+
5217 fuse_create_context(f);
+
5218
+
5219 for (i = 0; i < f->id_table.size; i++) {
+
5220 struct node *node;
+
5221
+
5222 for (node = f->id_table.array[i]; node != NULL;
+
5223 node = node->id_next) {
+
5224 if (node->is_hidden) {
+
5225 char *path;
+
5226 if (try_get_path(f, node->nodeid, NULL, &path, NULL, false) == 0) {
+
5227 fuse_fs_unlink(f->fs, path);
+
5228 free(path);
+
5229 }
+
5230 }
+
5231 }
+
5232 }
+
5233 }
+
5234 for (i = 0; i < f->id_table.size; i++) {
+
5235 struct node *node;
+
5236 struct node *next;
+
5237
+
5238 for (node = f->id_table.array[i]; node != NULL; node = next) {
+
5239 next = node->id_next;
+
5240 free_node(f, node);
+
5241 f->id_table.use--;
+
5242 }
+
5243 }
+
5244 assert(list_empty(&f->partial_slabs));
+
5245 assert(list_empty(&f->full_slabs));
+
5246
+
5247 while (fuse_modules) {
+
5248 fuse_put_module(fuse_modules);
+
5249 }
+
5250 free(f->id_table.array);
+
5251 free(f->name_table.array);
+
5252 pthread_mutex_destroy(&f->lock);
+
5253 fuse_session_destroy(f->se);
+
5254 free(f->fs);
+
5255 free(f->conf.modules);
+
5256 free(f);
+
5257 fuse_delete_context_key();
+
5258}
+
+
5259
+
+
5260int fuse_mount(struct fuse *f, const char *mountpoint) {
+
5261 return fuse_session_mount(fuse_get_session(f), mountpoint);
+
5262}
+
+
5263
+
5264
+
+
5265void fuse_unmount(struct fuse *f) {
+ +
5267}
+
+
5268
+
+ +
5270{
+
5271 return FUSE_VERSION;
+
5272}
+
+
5273
+
+
5274const char *fuse_pkgversion(void)
+
5275{
+
5276 return PACKAGE_VERSION;
+
5277}
+
+ +
int fuse_getgroups(int size, gid_t list[])
Definition fuse.c:4721
+
int fuse_mount(struct fuse *f, const char *mountpoint)
Definition fuse.c:5260
+
int fuse_interrupted(void)
Definition fuse.c:4730
+
void fuse_destroy(struct fuse *f)
Definition fuse.c:5209
+
int fuse_start_cleanup_thread(struct fuse *fuse)
Definition fuse.c:4973
+
int fuse_invalidate_path(struct fuse *f, const char *path)
Definition fuse.c:4740
+
struct fuse_fs *(* fuse_module_factory_t)(struct fuse_args *args, struct fuse_fs *fs[])
Definition fuse.h:1403
+
struct fuse_context * fuse_get_context(void)
Definition fuse.c:4711
+
int fuse_loop(struct fuse *f)
Definition fuse.c:4644
+
int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
Definition fuse.h:93
+
void fuse_exit(struct fuse *f)
Definition fuse.c:4706
+
int fuse_clean_cache(struct fuse *fuse)
Definition fuse.c:4498
+
void fuse_lib_help(struct fuse_args *args)
Definition fuse.c:4813
+
struct fuse_session * fuse_get_session(struct fuse *f)
Definition fuse.c:4588
+
void fuse_unmount(struct fuse *f)
Definition fuse.c:5265
+
struct fuse_fs * fuse_fs_new(const struct fuse_operations *op, size_t op_size, void *private_data)
Definition fuse.c:4923
+
void fuse_stop_cleanup_thread(struct fuse *fuse)
Definition fuse.c:4981
+
fuse_fill_dir_flags
Definition fuse.h:61
+
fuse_readdir_flags
Definition fuse.h:45
+
void fuse_unset_feature_flag(struct fuse_conn_info *conn, uint64_t flag)
+
bool fuse_set_feature_flag(struct fuse_conn_info *conn, uint64_t flag)
+
#define FUSE_CAP_SPLICE_READ
+
size_t fuse_buf_size(const struct fuse_bufvec *bufv)
Definition buffer.c:22
+
@ FUSE_BUF_IS_FD
+
#define FUSE_CAP_EXPORT_SUPPORT
+
#define FUSE_CAP_POSIX_LOCKS
+
ssize_t fuse_buf_copy(struct fuse_bufvec *dst, struct fuse_bufvec *src, enum fuse_buf_copy_flags flags)
Definition buffer.c:284
+
const char * fuse_pkgversion(void)
Definition fuse.c:5274
+
void fuse_pollhandle_destroy(struct fuse_pollhandle *ph)
+
int fuse_version(void)
Definition fuse.c:5269
+
@ FUSE_BUF_SPLICE_MOVE
+
#define FUSE_CAP_FLOCK_LOCKS
+
void fuse_log(enum fuse_log_level level, const char *fmt,...) __attribute__((format(printf
+ +
void fuse_session_destroy(struct fuse_session *se)
+
int fuse_reply_data(fuse_req_t req, struct fuse_bufvec *bufv, enum fuse_buf_copy_flags flags)
+
int fuse_reply_lock(fuse_req_t req, const struct flock *lock)
+
int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi)
+
void fuse_session_exit(struct fuse_session *se)
+
int fuse_reply_poll(fuse_req_t req, unsigned revents)
+
int fuse_reply_err(fuse_req_t req, int err)
+
const struct fuse_ctx * fuse_req_ctx(fuse_req_t req)
+
void * fuse_req_userdata(fuse_req_t req)
+
int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
+
struct fuse_req * fuse_req_t
+
size_t fuse_add_direntry_plus(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct fuse_entry_param *e, off_t off)
+
int fuse_session_exited(struct fuse_session *se)
+
int fuse_req_interrupted(fuse_req_t req)
+
int fuse_req_getgroups(fuse_req_t req, int size, gid_t list[])
+
int fuse_reply_readlink(fuse_req_t req, const char *link)
+
int fuse_session_loop(struct fuse_session *se)
Definition fuse_loop.c:19
+
int fuse_reply_bmap(fuse_req_t req, uint64_t idx)
+
int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
+
void fuse_session_unmount(struct fuse_session *se)
+
void fuse_reply_none(fuse_req_t req)
+
void fuse_lowlevel_help(void)
+
int fuse_lowlevel_notify_inval_inode(struct fuse_session *se, fuse_ino_t ino, off_t off, off_t len)
+
int fuse_reply_statfs(fuse_req_t req, const struct statvfs *stbuf)
+
int fuse_reply_write(fuse_req_t req, size_t count)
+
int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
+
int fuse_lowlevel_notify_poll(struct fuse_pollhandle *ph)
+
void fuse_req_interrupt_func(fuse_req_t req, fuse_interrupt_func_t func, void *data)
+
int fuse_reply_create(fuse_req_t req, const struct fuse_entry_param *e, const struct fuse_file_info *fi)
+
int fuse_reply_lseek(fuse_req_t req, off_t off)
+
int fuse_reply_statx(fuse_req_t req, int flags, struct statx *statx, double attr_timeout)
+
uint64_t fuse_ino_t
+
size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct stat *stbuf, off_t off)
+
int fuse_reply_attr(fuse_req_t req, const struct stat *attr, double attr_timeout)
+
int fuse_reply_ioctl(fuse_req_t req, int result, const void *buf, size_t size)
+
int fuse_reply_xattr(fuse_req_t req, size_t count)
+ +
int fuse_opt_add_arg(struct fuse_args *args, const char *arg)
Definition fuse_opt.c:55
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
#define FUSE_OPT_KEY(templ, key)
Definition fuse_opt.h:98
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_OPT_KEY_KEEP
Definition fuse_opt.h:145
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+ + +
enum fuse_buf_flags flags
+
void * mem
+
size_t size
+ + + +
struct fuse_buf buf[1]
+ + +
uint32_t fmask
Definition fuse.h:294
+
int32_t show_help
Definition fuse.h:285
+
int32_t direct_io
Definition fuse.h:232
+
int32_t no_rofd_flush
Definition fuse.h:303
+
int32_t nullpath_ok
Definition fuse.h:279
+
int32_t parallel_direct_writes
Definition fuse.h:318
+
int32_t intr_signal
Definition fuse.h:161
+
int32_t remember
Definition fuse.h:173
+
int32_t kernel_cache
Definition fuse.h:251
+
int32_t readdir_ino
Definition fuse.h:213
+
int32_t use_ino
Definition fuse.h:204
+
double entry_timeout
Definition fuse.h:133
+
int32_t set_mode
Definition fuse.h:126
+
int32_t auto_cache
Definition fuse.h:259
+
int32_t intr
Definition fuse.h:154
+
double negative_timeout
Definition fuse.h:143
+
int32_t set_gid
Definition fuse.h:112
+
int32_t set_uid
Definition fuse.h:119
+
int32_t hard_remove
Definition fuse.h:191
+
double attr_timeout
Definition fuse.h:149
+ +
uint32_t no_interrupt
+ +
void * private_data
Definition fuse.h:891
+
uid_t uid
Definition fuse.h:882
+
pid_t pid
Definition fuse.h:888
+
struct fuse * fuse
Definition fuse.h:879
+
gid_t gid
Definition fuse.h:885
+
mode_t umask
Definition fuse.h:894
+ + + +
mode_t umask
+ +
+
double entry_timeout
+
fuse_ino_t ino
+
uint64_t generation
+
double attr_timeout
+
struct stat attr
+ +
uint64_t lock_owner
+ +
uint32_t writepage
Definition fuse_common.h:60
+
uint32_t poll_events
+
uint32_t cache_readdir
Definition fuse_common.h:89
+
uint32_t parallel_direct_writes
Definition fuse_common.h:97
+
uint32_t noflush
Definition fuse_common.h:93
+
uint32_t flush
Definition fuse_common.h:74
+
uint32_t direct_io
Definition fuse_common.h:63
+
uint32_t keep_cache
Definition fuse_common.h:69
+ + + + +
void(* setlk)(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi, struct flock *lock, int sleep)
+
void(* getlk)(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi, struct flock *lock)
+ + +
int(* mknod)(const char *, mode_t, dev_t)
Definition fuse.h:385
+
int(* open)(const char *, struct fuse_file_info *)
Definition fuse.h:492
+
int(* readlink)(const char *, char *, size_t)
Definition fuse.h:377
+
int(* write)(const char *, const char *, size_t, off_t, struct fuse_file_info *)
Definition fuse.h:515
+
int(* fallocate)(const char *, int, off_t, off_t, struct fuse_file_info *)
Definition fuse.h:834
+
int(* read)(const char *, char *, size_t, off_t, struct fuse_file_info *)
Definition fuse.h:503
+
int(* symlink)(const char *, const char *)
Definition fuse.h:402
+
int(* write_buf)(const char *, struct fuse_bufvec *buf, off_t off, struct fuse_file_info *)
Definition fuse.h:787
+
int(* release)(const char *, struct fuse_file_info *)
Definition fuse.h:566
+
int(* access)(const char *, int)
Definition fuse.h:666
+
int(* lock)(const char *, struct fuse_file_info *, int cmd, struct flock *)
Definition fuse.h:710
+
int(* fsyncdir)(const char *, int, struct fuse_file_info *)
Definition fuse.h:637
+
int(* mkdir)(const char *, mode_t)
Definition fuse.h:393
+
int(* chown)(const char *, uid_t, gid_t, struct fuse_file_info *fi)
Definition fuse.h:433
+
int(* unlink)(const char *)
Definition fuse.h:396
+
int(* flush)(const char *, struct fuse_file_info *)
Definition fuse.h:552
+
int(* listxattr)(const char *, char *, size_t)
Definition fuse.h:582
+
int(* truncate)(const char *, off_t, struct fuse_file_info *fi)
Definition fuse.h:443
+
int(* statfs)(const char *, struct statvfs *)
Definition fuse.h:522
+
ssize_t(* copy_file_range)(const char *path_in, struct fuse_file_info *fi_in, off_t offset_in, const char *path_out, struct fuse_file_info *fi_out, off_t offset_out, size_t size, int flags)
Definition fuse.h:849
+
int(* create)(const char *, mode_t, struct fuse_file_info *)
Definition fuse.h:678
+
int(* utimens)(const char *, const struct timespec tv[2], struct fuse_file_info *fi)
Definition fuse.h:725
+
off_t(* lseek)(const char *, off_t off, int whence, struct fuse_file_info *)
Definition fuse.h:858
+
int(* getxattr)(const char *, const char *, char *, size_t)
Definition fuse.h:579
+
int(* setxattr)(const char *, const char *, const char *, size_t, int)
Definition fuse.h:576
+
int(* poll)(const char *, struct fuse_file_info *, struct fuse_pollhandle *ph, unsigned *reventsp)
Definition fuse.h:775
+
int(* fsync)(const char *, int, struct fuse_file_info *)
Definition fuse.h:573
+
int(* chmod)(const char *, mode_t, struct fuse_file_info *fi)
Definition fuse.h:423
+
int(* opendir)(const char *, struct fuse_file_info *)
Definition fuse.h:595
+
int(* rmdir)(const char *)
Definition fuse.h:399
+
int(* statx)(const char *path, int flags, int mask, struct statx *stxbuf, struct fuse_file_info *fi)
Definition fuse.h:868
+
int(* releasedir)(const char *, struct fuse_file_info *)
Definition fuse.h:627
+
int(* getattr)(const char *, struct stat *, struct fuse_file_info *fi)
Definition fuse.h:367
+
int(* read_buf)(const char *, struct fuse_bufvec **bufp, size_t size, off_t off, struct fuse_file_info *)
Definition fuse.h:804
+
int(* link)(const char *, const char *)
Definition fuse.h:416
+
int(* flock)(const char *, struct fuse_file_info *, int op)
Definition fuse.h:824
+
int(* removexattr)(const char *, const char *)
Definition fuse.h:585
+
int(* ioctl)(const char *, unsigned int cmd, void *arg, struct fuse_file_info *, unsigned int flags, void *data)
Definition fuse.h:756
+
int(* rename)(const char *, const char *, unsigned int flags)
Definition fuse.h:413
+
int(* readdir)(const char *, void *, fuse_fill_dir_t, off_t, struct fuse_file_info *, enum fuse_readdir_flags)
Definition fuse.h:619
+
void *(* init)(struct fuse_conn_info *conn, struct fuse_config *cfg)
Definition fuse.h:647
+
void(* destroy)(void *private_data)
Definition fuse.h:655
+
int(* bmap)(const char *, size_t blocksize, uint64_t *idx)
Definition fuse.h:734
+ + +
+ + +
+ + diff --git a/doc/html/fuse_8h.html b/doc/html/fuse_8h.html new file mode 100644 index 0000000..9615366 --- /dev/null +++ b/doc/html/fuse_8h.html @@ -0,0 +1,755 @@ + + + + + + + +libfuse: include/fuse.h File Reference + + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
+ +
fuse.h File Reference
+
+
+
#include "fuse_common.h"
+#include <fcntl.h>
+#include <time.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/statvfs.h>
+#include <sys/uio.h>
+
+

Go to the source code of this file.

+ + + + + +

+Data Structures

struct  fuse_config
struct  fuse_operations
struct  fuse_context
+ + +

+Macros

#define FUSE_REGISTER_MODULE(name_, factory_)
+ + + +

+Typedefs

typedef int(* fuse_fill_dir_t) (void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
typedef struct fuse_fs *(* fuse_module_factory_t) (struct fuse_args *args, struct fuse_fs *fs[])
+ + + +

+Enumerations

enum  fuse_readdir_flags { FUSE_READDIR_DEFAULTS = 0 +, FUSE_READDIR_PLUS = (1 << 0) + }
enum  fuse_fill_dir_flags { FUSE_FILL_DIR_DEFAULTS = 0 +, FUSE_FILL_DIR_PLUS = (1 << 1) + }
+ + + + + + + + + + + + + + + + + + +

+Functions

int fuse_main_real_versioned (int argc, char *argv[], const struct fuse_operations *op, size_t op_size, struct libfuse_version *version, void *user_data)
void fuse_lib_help (struct fuse_args *args)
int fuse_mount (struct fuse *f, const char *mountpoint)
void fuse_unmount (struct fuse *f)
void fuse_destroy (struct fuse *f)
int fuse_loop (struct fuse *f)
void fuse_exit (struct fuse *f)
struct fuse_context * fuse_get_context (void)
int fuse_getgroups (int size, gid_t list[])
int fuse_interrupted (void)
int fuse_invalidate_path (struct fuse *f, const char *path)
int fuse_start_cleanup_thread (struct fuse *fuse)
void fuse_stop_cleanup_thread (struct fuse *fuse)
int fuse_clean_cache (struct fuse *fuse)
struct fuse_fs * fuse_fs_new (const struct fuse_operations *op, size_t op_size, void *private_data)
struct fuse_session * fuse_get_session (struct fuse *f)
int fuse_open_channel (const char *mountpoint, const char *options)
+

Detailed Description

+

This file defines the library interface of FUSE

+

IMPORTANT: you should define FUSE_USE_VERSION before including this header.

+ +

Definition in file fuse.h.

+

Macro Definition Documentation

+ +

◆ FUSE_REGISTER_MODULE

+ +
+
+ + + + + + + + + + + +
#define FUSE_REGISTER_MODULE( name_,
factory_ )
+
+Value:
fuse_module_factory_t fuse_module_ ## name_ ## _factory = factory_
+
struct fuse_fs *(* fuse_module_factory_t)(struct fuse_args *args, struct fuse_fs *fs[])
Definition fuse.h:1403
+

Register filesystem module

+

If the "-omodules=*name*_:..." option is present, filesystem objects are created and pushed onto the stack with the factory_ function.

+
Parameters
+ + + +
name_the name of this filesystem module
factory_the factory function for this filesystem module
+
+
+ +

Definition at line 1415 of file fuse.h.

+ +
+
+

Typedef Documentation

+ +

◆ fuse_fill_dir_t

+ +
+
+ + + + +
typedef int(* fuse_fill_dir_t) (void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
+
+

Function to add an entry in a readdir() operation

+

The off parameter can be any non-zero value that enables the filesystem to identify the current point in the directory stream. It does not need to be the actual physical position. A value of zero is reserved to indicate that seeking in directories is not supported.

+
Parameters
+ + + + + + +
bufthe buffer passed to the readdir() operation
namethe file name of the directory entry
stbuffile attributes, can be NULL
offoffset of the next entry or zero
flagsfill flags
+
+
+
Returns
1 if buffer is full, zero otherwise
+ +

Definition at line 93 of file fuse.h.

+ +
+
+ +

◆ fuse_module_factory_t

+ +
+
+ + + + +
typedef struct fuse_fs *(* fuse_module_factory_t) (struct fuse_args *args, struct fuse_fs *fs[])
+
+

Factory for creating filesystem objects

+

The function may use and remove options from 'args' that belong to this module.

+

For now the 'fs' vector always contains exactly one filesystem. This is the filesystem which will be below the newly created filesystem in the stack.

+
Parameters
+ + + +
argsthe command line arguments
fsNULL terminated filesystem object vector
+
+
+
Returns
the new filesystem object
+ +

Definition at line 1386 of file fuse.h.

+ +
+
+

Enumeration Type Documentation

+ +

◆ fuse_fill_dir_flags

+ +
+
+ + + + +
enum fuse_fill_dir_flags
+
+

Readdir flags, passed to fuse_fill_dir_t callback.

+ + +
Enumerator
FUSE_FILL_DIR_DEFAULTS 

"Plus" mode: file attributes are valid

+

The attributes are used by the kernel to prefill the inode cache during a readdir.

+

It is okay to set FUSE_FILL_DIR_PLUS if FUSE_READDIR_PLUS is not set and vice versa.

+

This does not make libfuse honor the 'st_ino' field. That is controlled by the 'use_ino' option instead.

+
+ +

Definition at line 61 of file fuse.h.

+ +
+
+ +

◆ fuse_readdir_flags

+ +
+
+ + + + +
enum fuse_readdir_flags
+
+

Readdir flags, passed to ->readdir()

+ + +
Enumerator
FUSE_READDIR_DEFAULTS 

"Plus" mode.

+

The kernel wants to prefill the inode cache during readdir. The filesystem may honour this by filling in the attributes and setting FUSE_FILL_DIR_FLAGS for the filler function. The filesystem may also just ignore this flag completely.

+
+ +

Definition at line 45 of file fuse.h.

+ +
+
+

Function Documentation

+ +

◆ fuse_clean_cache()

+ +
+
+ + + + + + + +
int fuse_clean_cache (struct fuse * fuse)
+
+

Iterate over cache removing stale entries use in conjunction with "-oremember"

+

NOTE: This is already done for the standard sessions

+
Parameters
+ + +
fusestruct fuse pointer for fuse instance
+
+
+
Returns
the number of seconds until the next cleanup
+ +

Definition at line 4498 of file fuse.c.

+ +
+
+ +

◆ fuse_destroy()

+ +
+
+ + + + + + + +
void fuse_destroy (struct fuse * f)
+
+

Destroy the FUSE handle.

+

NOTE: This function does not unmount the filesystem. If this is needed, call fuse_unmount() before calling this function.

+
Parameters
+ + +
fthe FUSE handle
+
+
+ +

Definition at line 5209 of file fuse.c.

+ +
+
+ +

◆ fuse_exit()

+ +
+
+ + + + + + + +
void fuse_exit (struct fuse * f)
+
+

Flag session as terminated

+

This function will cause any running event loops to exit on the next opportunity.

+
Parameters
+ + +
fthe FUSE handle
+
+
+ +

Definition at line 4706 of file fuse.c.

+ +
+
+ +

◆ fuse_fs_new()

+ +
+
+ + + + + + + + + + + + + + + + +
struct fuse_fs * fuse_fs_new (const struct fuse_operations * op,
size_t op_size,
void * private_data )
+
+

Create a new fuse filesystem object

+

This is usually called from the factory of a fuse module to create a new instance of a filesystem.

+
Parameters
+ + + + +
opthe filesystem operations
op_sizethe size of the fuse_operations structure
private_dataInitial value for the private_data field of struct fuse_context. May be overridden by the struct fuse_operations.init handler.
+
+
+
Returns
a new filesystem object
+ +

Definition at line 4923 of file fuse.c.

+ +
+
+ +

◆ fuse_get_context()

+ +
+
+ + + + + + + +
struct fuse_context * fuse_get_context (void )
+
+

Get the current context

+

The context is only valid for the duration of a filesystem operation, and thus must not be stored and used later.

+
Returns
the context
+ +

Definition at line 4711 of file fuse.c.

+ +
+
+ +

◆ fuse_get_session()

+ +
+
+ + + + + + + +
struct fuse_session * fuse_get_session (struct fuse * f)
+
+

Get session from fuse object

+ +

Definition at line 4588 of file fuse.c.

+ +
+
+ +

◆ fuse_getgroups()

+ +
+
+ + + + + + + + + + + +
int fuse_getgroups (int size,
gid_t list[] )
+
+

Get the current supplementary group IDs for the current request

+

Similar to the getgroups(2) system call, except the return value is always the total number of group IDs, even if it is larger than the specified size.

+

The current fuse kernel module in linux (as of 2.6.30) doesn't pass the group list to userspace, hence this function needs to parse "/proc/$TID/task/$TID/status" to get the group IDs.

+

This feature may not be supported on all operating systems. In such a case this function will return -ENOSYS.

+
Parameters
+ + + +
sizesize of given array
listarray of group IDs to be filled in
+
+
+
Returns
the total number of supplementary group IDs or -errno on failure
+ +

Definition at line 4721 of file fuse.c.

+ +
+
+ +

◆ fuse_interrupted()

+ +
+
+ + + + + + + +
int fuse_interrupted (void )
+
+

Check if the current request has already been interrupted

+
Returns
1 if the request has been interrupted, 0 otherwise
+ +

Definition at line 4730 of file fuse.c.

+ +
+
+ +

◆ fuse_invalidate_path()

+ +
+
+ + + + + + + + + + + +
int fuse_invalidate_path (struct fuse * f,
const char * path )
+
+

Invalidates cache for the given path.

+

This calls fuse_lowlevel_notify_inval_inode internally.

+
Returns
0 on successful invalidation, negative error value otherwise. This routine may return -ENOENT to indicate that there was no entry to be invalidated, e.g., because the path has not been seen before or has been forgotten; this should not be considered to be an error.
+ +

Definition at line 4740 of file fuse.c.

+ +
+
+ +

◆ fuse_lib_help()

+ +
+
+ + + + + + + +
void fuse_lib_help (struct fuse_args * args)
+
+

Print available options (high- and low-level) to stdout. This is not an exhaustive list, but includes only those options that may be of interest to an end-user of a file system.

+

The function looks at the argument vector only to determine if there are additional modules to be loaded (module=foo option), and attempts to call their help functions as well.

+
Parameters
+ + +
argsthe argument vector.
+
+
+ +

Definition at line 4813 of file fuse.c.

+ +
+
+ +

◆ fuse_loop()

+ +
+
+ + + + + + + +
int fuse_loop (struct fuse * f)
+
+

FUSE event loop.

+

Requests from the kernel are processed, and the appropriate operations are called.

+

For a description of the return value and the conditions when the event loop exits, refer to the documentation of fuse_session_loop().

+
Parameters
+ + +
fthe FUSE handle
+
+
+
Returns
see fuse_session_loop()
+

See also: fuse_loop_mt()

+ +

Definition at line 4644 of file fuse.c.

+ +
+
+ +

◆ fuse_main_real_versioned()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
int fuse_main_real_versioned (int argc,
char * argv[],
const struct fuse_operations * op,
size_t op_size,
struct libfuse_version * version,
void * user_data )
+
+

The real main function

+

Do not call this directly, use fuse_main()

+ +

Definition at line 317 of file helper.c.

+ +
+
+ +

◆ fuse_mount()

+ +
+
+ + + + + + + + + + + +
int fuse_mount (struct fuse * f,
const char * mountpoint )
+
+

Mount a FUSE file system.

+
Parameters
+ + + +
mountpointthe mount point path
fthe FUSE handle
+
+
+
Returns
0 on success, -1 on failure.
+ +

Definition at line 5260 of file fuse.c.

+ +
+
+ +

◆ fuse_open_channel()

+ +
+
+ + + + + + + + + + + +
int fuse_open_channel (const char * mountpoint,
const char * options )
+
+

Open a FUSE file descriptor and set up the mount for the given mountpoint and flags.

+
Parameters
+ + + +
mountpointreference to the mount in the file system
optionsmount options
+
+
+
Returns
the FUSE file descriptor or -1 upon error
+ +

Definition at line 492 of file helper.c.

+ +
+
+ +

◆ fuse_start_cleanup_thread()

+ +
+
+ + + + + + + +
int fuse_start_cleanup_thread (struct fuse * fuse)
+
+

Start the cleanup thread when using option "remember".

+

This is done automatically by fuse_loop_mt()

Parameters
+ + +
fusestruct fuse pointer for fuse instance
+
+
+
Returns
0 on success and -1 on error
+ +

Definition at line 4973 of file fuse.c.

+ +
+
+ +

◆ fuse_stop_cleanup_thread()

+ +
+
+ + + + + + + +
void fuse_stop_cleanup_thread (struct fuse * fuse)
+
+

Stop the cleanup thread when using option "remember".

+

This is done automatically by fuse_loop_mt()

Parameters
+ + +
fusestruct fuse pointer for fuse instance
+
+
+ +

Definition at line 4981 of file fuse.c.

+ +
+
+ +

◆ fuse_unmount()

+ +
+
+ + + + + + + +
void fuse_unmount (struct fuse * f)
+
+

Unmount a FUSE file system.

+

See fuse_session_unmount() for additional information.

+
Parameters
+ + +
fthe FUSE handle
+
+
+ +

Definition at line 5265 of file fuse.c.

+ +
+
+
+ + +
+ + diff --git a/doc/html/fuse_8h_source.html b/doc/html/fuse_8h_source.html new file mode 100644 index 0000000..961d92b --- /dev/null +++ b/doc/html/fuse_8h_source.html @@ -0,0 +1,655 @@ + + + + + + + +libfuse: include/fuse.h Source File + + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
+
fuse.h
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 This program can be distributed under the terms of the GNU LGPLv2.
+
6 See the file LGPL2.txt.
+
7*/
+
8
+
9#ifndef FUSE_H_
+
10#define FUSE_H_
+
11
+
18
+
19#include "fuse_common.h"
+
20
+
21#include <fcntl.h>
+
22#include <time.h>
+
23#include <sys/types.h>
+
24#include <sys/stat.h>
+
25#include <sys/statvfs.h>
+
26#include <sys/uio.h>
+
27
+
28#ifdef __cplusplus
+
29extern "C" {
+
30#endif
+
31
+
32/* ----------------------------------------------------------- *
+
33 * Basic FUSE API *
+
34 * ----------------------------------------------------------- */
+
35
+
36/* Forward declaration */
+
37struct statx;
+
38
+
40struct fuse;
+
41
+
+ + +
55 FUSE_READDIR_PLUS = (1 << 0)
+
56};
+
+
57
+
+ + +
75 FUSE_FILL_DIR_PLUS = (1 << 1)
+
76};
+
+
77
+
93typedef int (*fuse_fill_dir_t) (void *buf, const char *name,
+
94 const struct stat *stbuf, off_t off,
+
95 enum fuse_fill_dir_flags flags);
+
+ +
112 int32_t set_gid;
+
113 uint32_t gid;
+
114
+
119 int32_t set_uid;
+
120 uint32_t uid;
+
121
+
126 int32_t set_mode;
+
127 uint32_t umask;
+
128
+ +
134
+ +
144
+ +
150
+
154 int32_t intr;
+
155
+
161 int32_t intr_signal;
+
162
+
173 int32_t remember;
+
174
+
191 int32_t hard_remove;
+
192
+
204 int32_t use_ino;
+
205
+
213 int32_t readdir_ino;
+
214
+
232 int32_t direct_io;
+
233
+ +
252
+
259 int32_t auto_cache;
+
260
+
261 /*
+
262 * The timeout in seconds for which file attributes are cached
+
263 * for the purpose of checking if auto_cache should flush the
+
264 * file data on open.
+
265 */
+
266 int32_t ac_attr_timeout_set;
+
267 double ac_attr_timeout;
+
268
+
279 int32_t nullpath_ok;
+
280
+
285 int32_t show_help;
+
286 char *modules;
+
287 int32_t debug;
+
288
+
294 uint32_t fmask;
+
295 uint32_t dmask;
+
296
+ +
304
+ +
319
+
320
+
324 uint32_t flags;
+
325
+
329 uint64_t reserved[48];
+
330};
+
+
331
+
332
+
+ +
367 int (*getattr) (const char *, struct stat *, struct fuse_file_info *fi);
+
368
+
377 int (*readlink) (const char *, char *, size_t);
+
378
+
385 int (*mknod) (const char *, mode_t, dev_t);
+
386
+
393 int (*mkdir) (const char *, mode_t);
+
394
+
396 int (*unlink) (const char *);
+
397
+
399 int (*rmdir) (const char *);
+
400
+
402 int (*symlink) (const char *, const char *);
+
403
+
413 int (*rename) (const char *, const char *, unsigned int flags);
+
414
+
416 int (*link) (const char *, const char *);
+
417
+
423 int (*chmod) (const char *, mode_t, struct fuse_file_info *fi);
+
424
+
433 int (*chown) (const char *, uid_t, gid_t, struct fuse_file_info *fi);
+
434
+
443 int (*truncate) (const char *, off_t, struct fuse_file_info *fi);
+
444
+
492 int (*open) (const char *, struct fuse_file_info *);
+
493
+
503 int (*read) (const char *, char *, size_t, off_t,
+
504 struct fuse_file_info *);
+
505
+
515 int (*write) (const char *, const char *, size_t, off_t,
+
516 struct fuse_file_info *);
+
517
+
522 int (*statfs) (const char *, struct statvfs *);
+
523
+
552 int (*flush) (const char *, struct fuse_file_info *);
+
553
+
566 int (*release) (const char *, struct fuse_file_info *);
+
567
+
573 int (*fsync) (const char *, int, struct fuse_file_info *);
+
574
+
576 int (*setxattr) (const char *, const char *, const char *, size_t, int);
+
577
+
579 int (*getxattr) (const char *, const char *, char *, size_t);
+
580
+
582 int (*listxattr) (const char *, char *, size_t);
+
583
+
585 int (*removexattr) (const char *, const char *);
+
586
+
595 int (*opendir) (const char *, struct fuse_file_info *);
+
596
+
619 int (*readdir) (const char *, void *, fuse_fill_dir_t, off_t,
+
620 struct fuse_file_info *, enum fuse_readdir_flags);
+
621
+
627 int (*releasedir) (const char *, struct fuse_file_info *);
+
628
+
637 int (*fsyncdir) (const char *, int, struct fuse_file_info *);
+
638
+
647 void *(*init) (struct fuse_conn_info *conn,
+
648 struct fuse_config *cfg);
+
649
+
655 void (*destroy) (void *private_data);
+
656
+
666 int (*access) (const char *, int);
+
667
+
678 int (*create) (const char *, mode_t, struct fuse_file_info *);
+
679
+
710 int (*lock) (const char *, struct fuse_file_info *, int cmd,
+
711 struct flock *);
+
712
+
725 int (*utimens) (const char *, const struct timespec tv[2],
+
726 struct fuse_file_info *fi);
+
727
+
734 int (*bmap) (const char *, size_t blocksize, uint64_t *idx);
+
735
+
736#if FUSE_USE_VERSION < 35
+
737 int (*ioctl) (const char *, int cmd, void *arg,
+
738 struct fuse_file_info *, unsigned int flags, void *data);
+
739#else
+
756 int (*ioctl) (const char *, unsigned int cmd, void *arg,
+
757 struct fuse_file_info *, unsigned int flags, void *data);
+
758#endif
+
759
+
775 int (*poll) (const char *, struct fuse_file_info *,
+
776 struct fuse_pollhandle *ph, unsigned *reventsp);
+
777
+
787 int (*write_buf) (const char *, struct fuse_bufvec *buf, off_t off,
+
788 struct fuse_file_info *);
+
789
+
804 int (*read_buf) (const char *, struct fuse_bufvec **bufp,
+
805 size_t size, off_t off, struct fuse_file_info *);
+
824 int (*flock) (const char *, struct fuse_file_info *, int op);
+
825
+
834 int (*fallocate) (const char *, int, off_t, off_t,
+
835 struct fuse_file_info *);
+
836
+
849 ssize_t (*copy_file_range) (const char *path_in,
+
850 struct fuse_file_info *fi_in,
+
851 off_t offset_in, const char *path_out,
+
852 struct fuse_file_info *fi_out,
+
853 off_t offset_out, size_t size, int flags);
+
854
+
858 off_t (*lseek) (const char *, off_t off, int whence, struct fuse_file_info *);
+
859
+
868 int (*statx)(const char *path, int flags, int mask, struct statx *stxbuf,
+
869 struct fuse_file_info *fi);
+
870};
+
+
871
+
+ +
879 struct fuse *fuse;
+
880
+
882 uid_t uid;
+
883
+
885 gid_t gid;
+
886
+
888 pid_t pid;
+
889
+ +
892
+
894 mode_t umask;
+
895};
+
+
896
+
902int fuse_main_real_versioned(int argc, char *argv[],
+
903 const struct fuse_operations *op, size_t op_size,
+
904 struct libfuse_version *version, void *user_data);
+
905static inline int fuse_main_real(int argc, char *argv[],
+
906 const struct fuse_operations *op,
+
907 size_t op_size, void *user_data)
+
908{
+
909 struct libfuse_version version = { .major = FUSE_MAJOR_VERSION,
+
910 .minor = FUSE_MINOR_VERSION,
+
911 .hotfix = FUSE_HOTFIX_VERSION,
+
912 .padding = 0 };
+
913
+
914 fuse_log(FUSE_LOG_ERR,
+
915 "%s is a libfuse internal function, please use fuse_main()\n",
+
916 __func__);
+
917
+
918 return fuse_main_real_versioned(argc, argv, op, op_size, &version,
+
919 user_data);
+
920}
+
921
+
976static inline int fuse_main_fn(int argc, char *argv[],
+
977 const struct fuse_operations *op,
+
978 void *user_data)
+
979{
+
980 struct libfuse_version version = {
+
981 .major = FUSE_MAJOR_VERSION,
+
982 .minor = FUSE_MINOR_VERSION,
+
983 .hotfix = FUSE_HOTFIX_VERSION,
+
984 .padding = 0
+
985 };
+
986
+
987 return fuse_main_real_versioned(argc, argv, op, sizeof(*(op)), &version,
+
988 user_data);
+
989}
+
990#define fuse_main(argc, argv, op, user_data) \
+
991 fuse_main_fn(argc, argv, op, user_data)
+
992
+
993/* ----------------------------------------------------------- *
+
994 * More detailed API *
+
995 * ----------------------------------------------------------- */
+
996
+
1008void fuse_lib_help(struct fuse_args *args);
+
1009
+
1010/* Do not call this directly, use fuse_new() instead */
+
1011struct fuse *_fuse_new_30(struct fuse_args *args,
+
1012 const struct fuse_operations *op, size_t op_size,
+
1013 struct libfuse_version *version, void *user_data);
+
1014struct fuse *_fuse_new_31(struct fuse_args *args,
+
1015 const struct fuse_operations *op, size_t op_size,
+
1016 struct libfuse_version *version, void *user_data);
+
1017
+
1045#if FUSE_USE_VERSION == 30
+
1046static inline struct fuse *fuse_new_fn(struct fuse_args *args,
+
1047 const struct fuse_operations *op,
+
1048 size_t op_size, void *user_data)
+
1049{
+
1050 struct libfuse_version version = {
+
1051 .major = FUSE_MAJOR_VERSION,
+
1052 .minor = FUSE_MINOR_VERSION,
+
1053 .hotfix = FUSE_HOTFIX_VERSION,
+
1054 .padding = 0
+
1055 };
+
1056
+
1057 return _fuse_new_30(args, op, op_size, &version, user_data);
+
1058}
+
1059#else /* FUSE_USE_VERSION */
+
1060static inline struct fuse *fuse_new_fn(struct fuse_args *args,
+
1061 const struct fuse_operations *op,
+
1062 size_t op_size, void *user_data)
+
1063{
+
1064 struct libfuse_version version = {
+
1065 .major = FUSE_MAJOR_VERSION,
+
1066 .minor = FUSE_MINOR_VERSION,
+
1067 .hotfix = FUSE_HOTFIX_VERSION,
+
1068 .padding = 0
+
1069 };
+
1070
+
1071 return _fuse_new_31(args, op, op_size, &version, user_data);
+
1072}
+
1073#endif
+
1074#define fuse_new(args, op, size, data) fuse_new_fn(args, op, size, data)
+
1075
+
1084int fuse_mount(struct fuse *f, const char *mountpoint);
+
1085
+
1093void fuse_unmount(struct fuse *f);
+
1094
+
1103void fuse_destroy(struct fuse *f);
+
1104
+
1120int fuse_loop(struct fuse *f);
+
1121
+
1130void fuse_exit(struct fuse *f);
+
1131
+
1132#if FUSE_USE_VERSION < 32
+
1133int fuse_loop_mt_31(struct fuse *f, int clone_fd);
+
1134#define fuse_loop_mt(f, clone_fd) fuse_loop_mt_31(f, clone_fd)
+
1135#elif FUSE_USE_VERSION < FUSE_MAKE_VERSION(3, 12)
+
1136int fuse_loop_mt_32(struct fuse *f, struct fuse_loop_config *config);
+
1137#define fuse_loop_mt(f, config) fuse_loop_mt_32(f, config)
+
1138#else
+
1170#if (defined(LIBFUSE_BUILT_WITH_VERSIONED_SYMBOLS))
+
1171int fuse_loop_mt(struct fuse *f, struct fuse_loop_config *config);
+
1172#else
+
1173int fuse_loop_mt_312(struct fuse *f, struct fuse_loop_config *config);
+
1174#define fuse_loop_mt(f, config) fuse_loop_mt_312(f, config)
+
1175#endif /* LIBFUSE_BUILT_WITH_VERSIONED_SYMBOLS */
+
1176#endif
+
1177
+
1178
+
1187struct fuse_context *fuse_get_context(void);
+
1188
+
1207int fuse_getgroups(int size, gid_t list[]);
+
1208
+
1214int fuse_interrupted(void);
+
1215
+
1227int fuse_invalidate_path(struct fuse *f, const char *path);
+
1228
+
1236int fuse_start_cleanup_thread(struct fuse *fuse);
+
1237
+
1244void fuse_stop_cleanup_thread(struct fuse *fuse);
+
1245
+
1255int fuse_clean_cache(struct fuse *fuse);
+
1256
+
1257/*
+
1258 * Stacking API
+
1259 */
+
1260
+
1266struct fuse_fs;
+
1267
+
1268/*
+
1269 * These functions call the relevant filesystem operation, and return
+
1270 * the result.
+
1271 *
+
1272 * If the operation is not defined, they return -ENOSYS, with the
+
1273 * exception of fuse_fs_open, fuse_fs_release, fuse_fs_opendir,
+
1274 * fuse_fs_releasedir and fuse_fs_statfs, which return 0.
+
1275 */
+
1276
+
1277int fuse_fs_getattr(struct fuse_fs *fs, const char *path, struct stat *buf,
+
1278 struct fuse_file_info *fi);
+
1279int fuse_fs_rename(struct fuse_fs *fs, const char *oldpath,
+
1280 const char *newpath, unsigned int flags);
+
1281int fuse_fs_unlink(struct fuse_fs *fs, const char *path);
+
1282int fuse_fs_rmdir(struct fuse_fs *fs, const char *path);
+
1283int fuse_fs_symlink(struct fuse_fs *fs, const char *linkname,
+
1284 const char *path);
+
1285int fuse_fs_link(struct fuse_fs *fs, const char *oldpath, const char *newpath);
+
1286int fuse_fs_release(struct fuse_fs *fs, const char *path,
+
1287 struct fuse_file_info *fi);
+
1288int fuse_fs_open(struct fuse_fs *fs, const char *path,
+
1289 struct fuse_file_info *fi);
+
1290int fuse_fs_read(struct fuse_fs *fs, const char *path, char *buf, size_t size,
+
1291 off_t off, struct fuse_file_info *fi);
+
1292int fuse_fs_read_buf(struct fuse_fs *fs, const char *path,
+
1293 struct fuse_bufvec **bufp, size_t size, off_t off,
+
1294 struct fuse_file_info *fi);
+
1295int fuse_fs_write(struct fuse_fs *fs, const char *path, const char *buf,
+
1296 size_t size, off_t off, struct fuse_file_info *fi);
+
1297int fuse_fs_write_buf(struct fuse_fs *fs, const char *path,
+
1298 struct fuse_bufvec *buf, off_t off,
+
1299 struct fuse_file_info *fi);
+
1300int fuse_fs_fsync(struct fuse_fs *fs, const char *path, int datasync,
+
1301 struct fuse_file_info *fi);
+
1302int fuse_fs_flush(struct fuse_fs *fs, const char *path,
+
1303 struct fuse_file_info *fi);
+
1304int fuse_fs_statfs(struct fuse_fs *fs, const char *path, struct statvfs *buf);
+
1305int fuse_fs_opendir(struct fuse_fs *fs, const char *path,
+
1306 struct fuse_file_info *fi);
+
1307int fuse_fs_readdir(struct fuse_fs *fs, const char *path, void *buf,
+
1308 fuse_fill_dir_t filler, off_t off,
+
1309 struct fuse_file_info *fi, enum fuse_readdir_flags flags);
+
1310int fuse_fs_fsyncdir(struct fuse_fs *fs, const char *path, int datasync,
+
1311 struct fuse_file_info *fi);
+
1312int fuse_fs_releasedir(struct fuse_fs *fs, const char *path,
+
1313 struct fuse_file_info *fi);
+
1314int fuse_fs_create(struct fuse_fs *fs, const char *path, mode_t mode,
+
1315 struct fuse_file_info *fi);
+
1316int fuse_fs_lock(struct fuse_fs *fs, const char *path,
+
1317 struct fuse_file_info *fi, int cmd, struct flock *lock);
+
1318int fuse_fs_flock(struct fuse_fs *fs, const char *path,
+
1319 struct fuse_file_info *fi, int op);
+
1320int fuse_fs_chmod(struct fuse_fs *fs, const char *path, mode_t mode,
+
1321 struct fuse_file_info *fi);
+
1322int fuse_fs_chown(struct fuse_fs *fs, const char *path, uid_t uid, gid_t gid,
+
1323 struct fuse_file_info *fi);
+
1324int fuse_fs_truncate(struct fuse_fs *fs, const char *path, off_t size,
+
1325 struct fuse_file_info *fi);
+
1326int fuse_fs_utimens(struct fuse_fs *fs, const char *path,
+
1327 const struct timespec tv[2], struct fuse_file_info *fi);
+
1328int fuse_fs_access(struct fuse_fs *fs, const char *path, int mask);
+
1329int fuse_fs_readlink(struct fuse_fs *fs, const char *path, char *buf,
+
1330 size_t len);
+
1331int fuse_fs_mknod(struct fuse_fs *fs, const char *path, mode_t mode,
+
1332 dev_t rdev);
+
1333int fuse_fs_mkdir(struct fuse_fs *fs, const char *path, mode_t mode);
+
1334int fuse_fs_setxattr(struct fuse_fs *fs, const char *path, const char *name,
+
1335 const char *value, size_t size, int flags);
+
1336int fuse_fs_getxattr(struct fuse_fs *fs, const char *path, const char *name,
+
1337 char *value, size_t size);
+
1338int fuse_fs_listxattr(struct fuse_fs *fs, const char *path, char *list,
+
1339 size_t size);
+
1340int fuse_fs_removexattr(struct fuse_fs *fs, const char *path,
+
1341 const char *name);
+
1342int fuse_fs_bmap(struct fuse_fs *fs, const char *path, size_t blocksize,
+
1343 uint64_t *idx);
+
1344#if FUSE_USE_VERSION < 35
+
1345int fuse_fs_ioctl(struct fuse_fs *fs, const char *path, int cmd,
+
1346 void *arg, struct fuse_file_info *fi, unsigned int flags,
+
1347 void *data);
+
1348#else
+
1349int fuse_fs_ioctl(struct fuse_fs *fs, const char *path, unsigned int cmd,
+
1350 void *arg, struct fuse_file_info *fi, unsigned int flags,
+
1351 void *data);
+
1352#endif
+
1353int fuse_fs_poll(struct fuse_fs *fs, const char *path,
+
1354 struct fuse_file_info *fi, struct fuse_pollhandle *ph,
+
1355 unsigned *reventsp);
+
1356int fuse_fs_fallocate(struct fuse_fs *fs, const char *path, int mode,
+
1357 off_t offset, off_t length, struct fuse_file_info *fi);
+
1358ssize_t fuse_fs_copy_file_range(struct fuse_fs *fs, const char *path_in,
+
1359 struct fuse_file_info *fi_in, off_t off_in,
+
1360 const char *path_out,
+
1361 struct fuse_file_info *fi_out, off_t off_out,
+
1362 size_t len, int flags);
+
1363off_t fuse_fs_lseek(struct fuse_fs *fs, const char *path, off_t off, int whence,
+
1364 struct fuse_file_info *fi);
+
1365int fuse_fs_statx(struct fuse_fs *fs, const char *path, int flags, int mask,
+
1366 struct statx *stxbuf, struct fuse_file_info *fi);
+
1367void fuse_fs_init(struct fuse_fs *fs, struct fuse_conn_info *conn,
+
1368 struct fuse_config *cfg);
+
1369void fuse_fs_destroy(struct fuse_fs *fs);
+
1370
+
1371int fuse_notify_poll(struct fuse_pollhandle *ph);
+
1372
+
1386struct fuse_fs *fuse_fs_new(const struct fuse_operations *op, size_t op_size,
+
1387 void *private_data);
+
1388
+
1403typedef struct fuse_fs *(*fuse_module_factory_t)(struct fuse_args *args,
+
1404 struct fuse_fs *fs[]);
+
+
1415#define FUSE_REGISTER_MODULE(name_, factory_) \
+
1416 fuse_module_factory_t fuse_module_ ## name_ ## _factory = factory_
+
+
1417
+
1419struct fuse_session *fuse_get_session(struct fuse *f);
+
1420
+
1429int fuse_open_channel(const char *mountpoint, const char *options);
+
1430
+
1431#ifdef __cplusplus
+
1432}
+
1433#endif
+
1434
+
1435#endif /* FUSE_H_ */
+
int fuse_getgroups(int size, gid_t list[])
Definition fuse.c:4721
+
int fuse_mount(struct fuse *f, const char *mountpoint)
Definition fuse.c:5260
+
int fuse_interrupted(void)
Definition fuse.c:4730
+
void fuse_destroy(struct fuse *f)
Definition fuse.c:5209
+
int fuse_main_real_versioned(int argc, char *argv[], const struct fuse_operations *op, size_t op_size, struct libfuse_version *version, void *user_data)
Definition helper.c:317
+
int fuse_start_cleanup_thread(struct fuse *fuse)
Definition fuse.c:4973
+
int fuse_invalidate_path(struct fuse *f, const char *path)
Definition fuse.c:4740
+
struct fuse_context * fuse_get_context(void)
Definition fuse.c:4711
+
int fuse_loop(struct fuse *f)
Definition fuse.c:4644
+
int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
Definition fuse.h:93
+
void fuse_exit(struct fuse *f)
Definition fuse.c:4706
+
int fuse_clean_cache(struct fuse *fuse)
Definition fuse.c:4498
+
void fuse_lib_help(struct fuse_args *args)
Definition fuse.c:4813
+
int fuse_open_channel(const char *mountpoint, const char *options)
Definition helper.c:492
+
struct fuse_session * fuse_get_session(struct fuse *f)
Definition fuse.c:4588
+
void fuse_unmount(struct fuse *f)
Definition fuse.c:5265
+
struct fuse_fs * fuse_fs_new(const struct fuse_operations *op, size_t op_size, void *private_data)
Definition fuse.c:4923
+
void fuse_stop_cleanup_thread(struct fuse *fuse)
Definition fuse.c:4981
+
fuse_fill_dir_flags
Definition fuse.h:61
+
@ FUSE_FILL_DIR_DEFAULTS
Definition fuse.h:74
+
fuse_readdir_flags
Definition fuse.h:45
+
@ FUSE_READDIR_DEFAULTS
Definition fuse.h:54
+ +
void fuse_log(enum fuse_log_level level, const char *fmt,...) __attribute__((format(printf
+ + + +
uint32_t fmask
Definition fuse.h:294
+
int32_t show_help
Definition fuse.h:285
+
uint32_t flags
Definition fuse.h:324
+
int32_t direct_io
Definition fuse.h:232
+
int32_t no_rofd_flush
Definition fuse.h:303
+
int32_t nullpath_ok
Definition fuse.h:279
+
int32_t parallel_direct_writes
Definition fuse.h:318
+
int32_t intr_signal
Definition fuse.h:161
+
int32_t remember
Definition fuse.h:173
+
int32_t kernel_cache
Definition fuse.h:251
+
int32_t readdir_ino
Definition fuse.h:213
+
int32_t use_ino
Definition fuse.h:204
+
double entry_timeout
Definition fuse.h:133
+
int32_t set_mode
Definition fuse.h:126
+
int32_t auto_cache
Definition fuse.h:259
+
uint64_t reserved[48]
Definition fuse.h:329
+
int32_t intr
Definition fuse.h:154
+
double negative_timeout
Definition fuse.h:143
+
int32_t set_gid
Definition fuse.h:112
+
int32_t set_uid
Definition fuse.h:119
+
int32_t hard_remove
Definition fuse.h:191
+
double attr_timeout
Definition fuse.h:149
+ + +
void * private_data
Definition fuse.h:891
+
uid_t uid
Definition fuse.h:882
+
pid_t pid
Definition fuse.h:888
+
struct fuse * fuse
Definition fuse.h:879
+
gid_t gid
Definition fuse.h:885
+
mode_t umask
Definition fuse.h:894
+ + + +
int(* mknod)(const char *, mode_t, dev_t)
Definition fuse.h:385
+
int(* open)(const char *, struct fuse_file_info *)
Definition fuse.h:492
+
int(* readlink)(const char *, char *, size_t)
Definition fuse.h:377
+
int(* write)(const char *, const char *, size_t, off_t, struct fuse_file_info *)
Definition fuse.h:515
+
int(* fallocate)(const char *, int, off_t, off_t, struct fuse_file_info *)
Definition fuse.h:834
+
int(* read)(const char *, char *, size_t, off_t, struct fuse_file_info *)
Definition fuse.h:503
+
int(* symlink)(const char *, const char *)
Definition fuse.h:402
+
int(* write_buf)(const char *, struct fuse_bufvec *buf, off_t off, struct fuse_file_info *)
Definition fuse.h:787
+
int(* release)(const char *, struct fuse_file_info *)
Definition fuse.h:566
+
int(* access)(const char *, int)
Definition fuse.h:666
+
int(* lock)(const char *, struct fuse_file_info *, int cmd, struct flock *)
Definition fuse.h:710
+
int(* fsyncdir)(const char *, int, struct fuse_file_info *)
Definition fuse.h:637
+
int(* mkdir)(const char *, mode_t)
Definition fuse.h:393
+
int(* chown)(const char *, uid_t, gid_t, struct fuse_file_info *fi)
Definition fuse.h:433
+
int(* unlink)(const char *)
Definition fuse.h:396
+
int(* flush)(const char *, struct fuse_file_info *)
Definition fuse.h:552
+
int(* listxattr)(const char *, char *, size_t)
Definition fuse.h:582
+
int(* truncate)(const char *, off_t, struct fuse_file_info *fi)
Definition fuse.h:443
+
int(* statfs)(const char *, struct statvfs *)
Definition fuse.h:522
+
ssize_t(* copy_file_range)(const char *path_in, struct fuse_file_info *fi_in, off_t offset_in, const char *path_out, struct fuse_file_info *fi_out, off_t offset_out, size_t size, int flags)
Definition fuse.h:849
+
int(* create)(const char *, mode_t, struct fuse_file_info *)
Definition fuse.h:678
+
int(* utimens)(const char *, const struct timespec tv[2], struct fuse_file_info *fi)
Definition fuse.h:725
+
off_t(* lseek)(const char *, off_t off, int whence, struct fuse_file_info *)
Definition fuse.h:858
+
int(* getxattr)(const char *, const char *, char *, size_t)
Definition fuse.h:579
+
int(* setxattr)(const char *, const char *, const char *, size_t, int)
Definition fuse.h:576
+
int(* poll)(const char *, struct fuse_file_info *, struct fuse_pollhandle *ph, unsigned *reventsp)
Definition fuse.h:775
+
int(* fsync)(const char *, int, struct fuse_file_info *)
Definition fuse.h:573
+
int(* chmod)(const char *, mode_t, struct fuse_file_info *fi)
Definition fuse.h:423
+
int(* opendir)(const char *, struct fuse_file_info *)
Definition fuse.h:595
+
int(* rmdir)(const char *)
Definition fuse.h:399
+
int(* statx)(const char *path, int flags, int mask, struct statx *stxbuf, struct fuse_file_info *fi)
Definition fuse.h:868
+
int(* releasedir)(const char *, struct fuse_file_info *)
Definition fuse.h:627
+
int(* getattr)(const char *, struct stat *, struct fuse_file_info *fi)
Definition fuse.h:367
+
int(* read_buf)(const char *, struct fuse_bufvec **bufp, size_t size, off_t off, struct fuse_file_info *)
Definition fuse.h:804
+
int(* link)(const char *, const char *)
Definition fuse.h:416
+
int(* flock)(const char *, struct fuse_file_info *, int op)
Definition fuse.h:824
+
int(* removexattr)(const char *, const char *)
Definition fuse.h:585
+
int(* ioctl)(const char *, unsigned int cmd, void *arg, struct fuse_file_info *, unsigned int flags, void *data)
Definition fuse.h:756
+
int(* rename)(const char *, const char *, unsigned int flags)
Definition fuse.h:413
+
int(* readdir)(const char *, void *, fuse_fill_dir_t, off_t, struct fuse_file_info *, enum fuse_readdir_flags)
Definition fuse.h:619
+
void(* destroy)(void *private_data)
Definition fuse.h:655
+
int(* bmap)(const char *, size_t blocksize, uint64_t *idx)
Definition fuse.h:734
+ +
+ + +
+ + diff --git a/doc/html/fuse__common_8h.html b/doc/html/fuse__common_8h.html new file mode 100644 index 0000000..fc46443 --- /dev/null +++ b/doc/html/fuse__common_8h.html @@ -0,0 +1,1208 @@ + + + + + + + +libfuse: include/fuse_common.h File Reference + + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
+ +
fuse_common.h File Reference
+
+
+
#include "libfuse_config.h"
+#include "fuse_opt.h"
+#include "fuse_log.h"
+#include <stdint.h>
+#include <stdbool.h>
+#include <sys/types.h>
+#include <assert.h>
+
+

Go to the source code of this file.

+ + + + + + + + +

+Data Structures

struct  fuse_file_info
struct  fuse_loop_config
struct  fuse_conn_info
struct  fuse_buf
struct  fuse_bufvec
struct  libfuse_version
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+Macros

#define FUSE_CAP_ASYNC_READ   (1UL << 0)
#define FUSE_CAP_POSIX_LOCKS   (1UL << 1)
#define FUSE_CAP_ATOMIC_O_TRUNC   (1UL << 3)
#define FUSE_CAP_EXPORT_SUPPORT   (1UL << 4)
#define FUSE_CAP_DONT_MASK   (1UL << 6)
#define FUSE_CAP_SPLICE_WRITE   (1UL << 7)
#define FUSE_CAP_SPLICE_MOVE   (1UL << 8)
#define FUSE_CAP_SPLICE_READ   (1UL << 9)
#define FUSE_CAP_FLOCK_LOCKS   (1UL << 10)
#define FUSE_CAP_IOCTL_DIR   (1UL << 11)
#define FUSE_CAP_AUTO_INVAL_DATA   (1UL << 12)
#define FUSE_CAP_READDIRPLUS   (1UL << 13)
#define FUSE_CAP_READDIRPLUS_AUTO   (1UL << 14)
#define FUSE_CAP_ASYNC_DIO   (1UL << 15)
#define FUSE_CAP_WRITEBACK_CACHE   (1UL << 16)
#define FUSE_CAP_NO_OPEN_SUPPORT   (1UL << 17)
#define FUSE_CAP_PARALLEL_DIROPS   (1UL << 18)
#define FUSE_CAP_POSIX_ACL   (1UL << 19)
#define FUSE_CAP_HANDLE_KILLPRIV   (1UL << 20)
#define FUSE_CAP_HANDLE_KILLPRIV_V2   (1UL << 21)
#define FUSE_CAP_CACHE_SYMLINKS   (1UL << 23)
#define FUSE_CAP_NO_OPENDIR_SUPPORT   (1UL << 24)
#define FUSE_CAP_EXPLICIT_INVAL_DATA   (1UL << 25)
#define FUSE_CAP_EXPIRE_ONLY   (1UL << 26)
#define FUSE_CAP_SETXATTR_EXT   (1UL << 27)
#define FUSE_CAP_DIRECT_IO_ALLOW_MMAP   (1UL << 28)
#define FUSE_CAP_PASSTHROUGH   (1UL << 29)
#define FUSE_CAP_NO_EXPORT_SUPPORT   (1UL << 30)
#define FUSE_CAP_OVER_IO_URING   (1UL << 31)
#define FUSE_IOCTL_COMPAT   (1 << 0)
#define FUSE_BACKING_STACKED_UNDER   (0)
+ + + +

+Enumerations

enum  fuse_buf_flags { FUSE_BUF_IS_FD = (1 << 1) +, FUSE_BUF_FD_SEEK = (1 << 2) +, FUSE_BUF_FD_RETRY = (1 << 3) + }
enum  fuse_buf_copy_flags { FUSE_BUF_NO_SPLICE = (1 << 1) +, FUSE_BUF_FORCE_SPLICE = (1 << 2) +, FUSE_BUF_SPLICE_MOVE = (1 << 3) +, FUSE_BUF_SPLICE_NONBLOCK = (1 << 4) + }
+ + + + + + + + + + + + + + + + +

+Functions

struct fuse_conn_info_opts * fuse_parse_conn_info_opts (struct fuse_args *args)
void fuse_apply_conn_info_opts (struct fuse_conn_info_opts *opts, struct fuse_conn_info *conn)
int fuse_daemonize (int foreground)
int fuse_version (void)
const char * fuse_pkgversion (void)
void fuse_pollhandle_destroy (struct fuse_pollhandle *ph)
size_t fuse_buf_size (const struct fuse_bufvec *bufv)
ssize_t fuse_buf_copy (struct fuse_bufvec *dst, struct fuse_bufvec *src, enum fuse_buf_copy_flags flags)
int fuse_set_signal_handlers (struct fuse_session *se)
int fuse_set_fail_signal_handlers (struct fuse_session *se)
void fuse_remove_signal_handlers (struct fuse_session *se)
bool fuse_set_feature_flag (struct fuse_conn_info *conn, uint64_t flag)
void fuse_unset_feature_flag (struct fuse_conn_info *conn, uint64_t flag)
bool fuse_get_feature_flag (struct fuse_conn_info *conn, uint64_t flag)
int fuse_convert_to_conn_want_ext (struct fuse_conn_info *conn)
+

Macro Definition Documentation

+ +

◆ FUSE_BACKING_STACKED_UNDER

+ +
+
+ + + + +
#define FUSE_BACKING_STACKED_UNDER   (0)
+
+

When FUSE_CAP_PASSTHROUGH is enabled, this is the maximum allowed stacking depth of the backing files. In current kernel, the maximum allowed stack depth if FILESYSTEM_MAX_STACK_DEPTH (2), which includes the FUSE passthrough layer, so the maximum stacking depth for backing files is 1.

+

The default is FUSE_BACKING_STACKED_UNDER (0), meaning that the backing files cannot be on a stacked filesystem, but another stacked filesystem can be stacked over this FUSE passthrough filesystem.

+

Set this to FUSE_BACKING_STACKED_OVER (1) if backing files may be on a stacked filesystem, such as overlayfs or another FUSE passthrough. In this configuration, another stacked filesystem cannot be stacked over this FUSE passthrough filesystem.

+ +

Definition at line 678 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_ASYNC_DIO

+ +
+
+ + + + +
#define FUSE_CAP_ASYNC_DIO   (1UL << 15)
+
+

Indicates that the filesystem supports asynchronous direct I/O submission.

+

If this capability is not requested/available, the kernel will ensure that there is at most one pending read and one pending write request per direct I/O file-handle at any time.

+

This feature is enabled by default when supported by the kernel.

+ +

Definition at line 336 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_ASYNC_READ

+ +
+
+ + + + +
#define FUSE_CAP_ASYNC_READ   (1UL << 0)
+
+

Indicates that the filesystem supports asynchronous read requests.

+

If this capability is not requested/available, the kernel will ensure that there is at most one pending read request per file-handle at any time, and will attempt to order read requests by increasing offset.

+

This feature is enabled by default when supported by the kernel.

+ +

Definition at line 185 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_ATOMIC_O_TRUNC

+ +
+
+ + + + +
#define FUSE_CAP_ATOMIC_O_TRUNC   (1UL << 3)
+
+

Indicates that the filesystem supports the O_TRUNC open flag. If disabled, and an application specifies O_TRUNC, fuse first calls truncate() and then open() with O_TRUNC filtered out.

+

This feature is enabled by default when supported by the kernel.

+ +

Definition at line 202 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_AUTO_INVAL_DATA

+ +
+
+ + + + +
#define FUSE_CAP_AUTO_INVAL_DATA   (1UL << 12)
+
+

Traditionally, while a file is open the FUSE kernel module only asks the filesystem for an update of the file's attributes when a client attempts to read beyond EOF. This is unsuitable for e.g. network filesystems, where the file contents may change without the kernel knowing about it.

+

If this flag is set, FUSE will check the validity of the attributes on every read. If the attributes are no longer valid (i.e., if the attr_timeout passed to fuse_reply_attr() or set in struct +fuse_entry_param has passed), it will first issue a getattr request. If the new mtime differs from the previous value, any cached file contents will be invalidated as well.

+

This flag should always be set when available. If all file changes go through the kernel, attr_timeout should be set to a very large number to avoid unnecessary getattr() calls.

+

This feature is enabled by default when supported by the kernel.

+ +

Definition at line 289 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_CACHE_SYMLINKS

+ +
+
+ + + + +
#define FUSE_CAP_CACHE_SYMLINKS   (1UL << 23)
+
+

Indicates that the kernel supports caching symlinks in its page cache.

+

When this feature is enabled, symlink targets are saved in the page cache. You can invalidate a cached link by calling: fuse_lowlevel_notify_inval_inode(se, ino, 0, 0);

+

This feature is disabled by default. If the kernel supports it (>= 4.20), you can enable this feature by setting this flag in the want field of the fuse_conn_info structure.

+ +

Definition at line 426 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_DIRECT_IO_ALLOW_MMAP

+ +
+
+ + + + +
#define FUSE_CAP_DIRECT_IO_ALLOW_MMAP   (1UL << 28)
+
+

Files opened with FUSE_DIRECT_IO do not support MAP_SHARED mmap. This restriction is relaxed through FUSE_CAP_DIRECT_IO_RELAX (kernel flag: FUSE_DIRECT_IO_RELAX). MAP_SHARED is disabled by default for FUSE_DIRECT_IO, as this flag can be used to ensure coherency between mount points (or network clients) and with kernel page cache as enforced by mmap that cannot be guaranteed anymore.

+ +

Definition at line 496 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_DONT_MASK

+ +
+
+ + + + +
#define FUSE_CAP_DONT_MASK   (1UL << 6)
+
+

Indicates that the kernel should not apply the umask to the file mode on create operations.

+

This feature is disabled by default.

+ +

Definition at line 222 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_EXPIRE_ONLY

+ +
+
+ + + + +
#define FUSE_CAP_EXPIRE_ONLY   (1UL << 26)
+
+

Indicates support that dentries can be expired.

+

Expiring dentries, instead of invalidating them, makes a difference for overmounted dentries, where plain invalidation would detach all submounts before dropping the dentry from the cache. If only expiry is set on the dentry, then any overmounts are left alone and until ->d_revalidate() is called.

+

Note: ->d_revalidate() is not called for the case of following a submount, so invalidation will only be triggered for the non-overmounted case. The dentry could also be mounted in a different mount instance, in which case any submounts will still be detached.

+ +

Definition at line 480 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_EXPLICIT_INVAL_DATA

+ +
+
+ + + + +
#define FUSE_CAP_EXPLICIT_INVAL_DATA   (1UL << 25)
+
+

Indicates support for invalidating cached pages only on explicit request.

+

If this flag is set in the capable field of the fuse_conn_info structure, then the FUSE kernel module supports invalidating cached pages only on explicit request by the filesystem through fuse_lowlevel_notify_inval_inode() or fuse_invalidate_path().

+

By setting this flag in the want field of the fuse_conn_info structure, the filesystem is responsible for invalidating cached pages through explicit requests to the kernel.

+

Note that setting this flag does not prevent the cached pages from being flushed by OS itself and/or through user actions.

+

Note that if both FUSE_CAP_EXPLICIT_INVAL_DATA and FUSE_CAP_AUTO_INVAL_DATA are set in the capable field of the fuse_conn_info structure then FUSE_CAP_AUTO_INVAL_DATA takes precedence.

+

This feature is disabled by default.

+ +

Definition at line 464 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_EXPORT_SUPPORT

+ +
+
+ + + + +
#define FUSE_CAP_EXPORT_SUPPORT   (1UL << 4)
+
+

Indicates that the filesystem supports lookups of "." and "..".

+

When this flag is set, the filesystem must be prepared to receive requests for invalid inodes (i.e., for which a FORGET request was received or which have been used in a previous instance of the filesystem daemon) and must not reuse node-ids (even when setting generation numbers).

+

This feature is disabled by default.

+ +

Definition at line 214 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_FLOCK_LOCKS

+ +
+
+ + + + +
#define FUSE_CAP_FLOCK_LOCKS   (1UL << 10)
+
+

If set, the calls to flock(2) will be emulated using POSIX locks and must then be handled by the filesystem's setlock() handler.

+

If not set, flock(2) calls will be handled by the FUSE kernel module internally (so any access that does not go through the kernel cannot be taken into account).

+

This feature is enabled by default when supported by the kernel and if the filesystem implements a flock() handler.

+ +

Definition at line 260 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_HANDLE_KILLPRIV

+ +
+
+ + + + +
#define FUSE_CAP_HANDLE_KILLPRIV   (1UL << 20)
+
+

Indicates that the filesystem is responsible for unsetting setuid and setgid bits when a file is written, truncated, or its owner is changed.

+

This feature is disabled by default.

+ +

Definition at line 396 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_HANDLE_KILLPRIV_V2

+ +
+
+ + + + +
#define FUSE_CAP_HANDLE_KILLPRIV_V2   (1UL << 21)
+
+

Indicates that the filesystem is responsible for unsetting setuid and setgid bit and additionally cap (stored as xattr) when a file is written, truncated, or its owner is changed. Upon write/truncate suid/sgid is only killed if caller does not have CAP_FSETID. Additionally upon write/truncate sgid is killed only if file has group execute permission. (Same as Linux VFS behavior). KILLPRIV_V2 requires handling of

    +
  • FUSE_OPEN_KILL_SUIDGID (set in struct fuse_create_in::open_flags)
  • +
  • FATTR_KILL_SUIDGID (set in struct fuse_setattr_in::valid)
  • +
  • FUSE_WRITE_KILL_SUIDGID (set in struct fuse_write_in::write_flags)
  • +
+

This feature is disabled by default.

+ +

Definition at line 413 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_IOCTL_DIR

+ +
+
+ + + + +
#define FUSE_CAP_IOCTL_DIR   (1UL << 11)
+
+

Indicates that the filesystem supports ioctl's on directories.

+

This feature is enabled by default when supported by the kernel.

+ +

Definition at line 267 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_NO_EXPORT_SUPPORT

+ +
+
+ + + + +
#define FUSE_CAP_NO_EXPORT_SUPPORT   (1UL << 30)
+
+

Indicates that the file system cannot handle NFS export

+

If this flag is set NFS export and name_to_handle_at is not going to work at all and will fail with EOPNOTSUPP.

+ +

Definition at line 516 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_NO_OPEN_SUPPORT

+ +
+
+ + + + +
#define FUSE_CAP_NO_OPEN_SUPPORT   (1UL << 17)
+
+

Indicates support for zero-message opens. If this flag is set in the capable field of the fuse_conn_info structure, then the filesystem may return ENOSYS from the open() handler to indicate success. Further attempts to open files will be handled in the kernel. (If this flag is not set, returning ENOSYS will be treated as an error and signaled to the caller).

+

Setting this flag in the want field enables this behavior automatically within libfuse for low level API users. If non-low level users wish to have this behavior you must return ENOSYS from the open() handler on supporting kernels.

+ +

Definition at line 360 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_NO_OPENDIR_SUPPORT

+ +
+
+ + + + +
#define FUSE_CAP_NO_OPENDIR_SUPPORT   (1UL << 24)
+
+

Indicates support for zero-message opendirs. If this flag is set in the capable field of the fuse_conn_info structure, then the filesystem may return ENOSYS from the opendir() handler to indicate success. Further opendir and releasedir messages will be handled in the kernel. (If this flag is not set, returning ENOSYS will be treated as an error and signalled to the caller.)

+

Setting this flag in the want field enables this behavior automatically within libfuse for low level API users. If non-low level users with to have this behavior you must return ENOSYS from the opendir() handler on supporting kernels.

+ +

Definition at line 441 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_OVER_IO_URING

+ +
+
+ + + + +
#define FUSE_CAP_OVER_IO_URING   (1UL << 31)
+
+

Indicates support for io-uring between fuse-server and fuse-client

+ +

Definition at line 521 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_PARALLEL_DIROPS

+ +
+
+ + + + +
#define FUSE_CAP_PARALLEL_DIROPS   (1UL << 18)
+
+

Indicates support for parallel directory operations. If this flag is unset, the FUSE kernel module will ensure that lookup() and readdir() requests are never issued concurrently for the same directory.

+ +

Definition at line 368 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_PASSTHROUGH

+ +
+
+ + + + +
#define FUSE_CAP_PASSTHROUGH   (1UL << 29)
+
+

Indicates support for passthrough mode access for read/write operations.

+

If this flag is set in the capable field of the fuse_conn_info structure, then the FUSE kernel module supports redirecting read/write operations to the backing file instead of letting them to be handled by the FUSE daemon.

+

This feature is disabled by default.

+ +

Definition at line 508 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_POSIX_ACL

+ +
+
+ + + + +
#define FUSE_CAP_POSIX_ACL   (1UL << 19)
+
+

Indicates support for POSIX ACLs.

+

If this feature is enabled, the kernel will cache and have responsibility for enforcing ACLs. ACL will be stored as xattrs and passed to userspace, which is responsible for updating the ACLs in the filesystem, keeping the file mode in sync with the ACL, and ensuring inheritance of default ACLs when new filesystem nodes are created. Note that this requires that the file system is able to parse and interpret the xattr representation of ACLs.

+

Enabling this feature implicitly turns on the default_permissions mount option (even if it was not passed to mount(2)).

+

This feature is disabled by default.

+ +

Definition at line 387 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_POSIX_LOCKS

+ +
+
+ + + + +
#define FUSE_CAP_POSIX_LOCKS   (1UL << 1)
+
+

Indicates that the filesystem supports "remote" locking.

+

This feature is enabled by default when supported by the kernel, and if getlk() and setlk() handlers are implemented.

+ +

Definition at line 193 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_READDIRPLUS

+ +
+
+ + + + +
#define FUSE_CAP_READDIRPLUS   (1UL << 13)
+
+

Indicates that the filesystem supports readdirplus.

+

This feature is enabled by default when supported by the kernel and if the filesystem implements a readdirplus() handler.

+ +

Definition at line 297 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_READDIRPLUS_AUTO

+ +
+
+ + + + +
#define FUSE_CAP_READDIRPLUS_AUTO   (1UL << 14)
+
+

Indicates that the filesystem supports adaptive readdirplus.

+

If FUSE_CAP_READDIRPLUS is not set, this flag has no effect.

+

If FUSE_CAP_READDIRPLUS is set and this flag is not set, the kernel will always issue readdirplus() requests to retrieve directory contents.

+

If FUSE_CAP_READDIRPLUS is set and this flag is set, the kernel will issue both readdir() and readdirplus() requests, depending on how much information is expected to be required.

+

As of Linux 4.20, the algorithm is as follows: when userspace starts to read directory entries, issue a READDIRPLUS request to the filesystem. If any entry attributes have been looked up by the time userspace requests the next batch of entries continue with READDIRPLUS, otherwise switch to plain READDIR. This will reasult in eg plain "ls" triggering READDIRPLUS first then READDIR after that because it doesn't do lookups. "ls -l" should result in all READDIRPLUS, except if dentries are already cached.

+

This feature is enabled by default when supported by the kernel and if the filesystem implements both a readdirplus() and a readdir() handler.

+ +

Definition at line 325 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_SETXATTR_EXT

+ +
+
+ + + + +
#define FUSE_CAP_SETXATTR_EXT   (1UL << 27)
+
+

Indicates that an extended 'struct fuse_setxattr' is used by the kernel side - extra_flags are passed, which are used (as of now by acl) processing. For example FUSE_SETXATTR_ACL_KILL_SGID might be set.

+ +

Definition at line 487 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_SPLICE_MOVE

+ +
+
+ + + + +
#define FUSE_CAP_SPLICE_MOVE   (1UL << 8)
+
+

Indicates that libfuse should try to move pages instead of copying when writing to / reading from the fuse device. This may improve performance.

+

This feature is disabled by default.

+ +

Definition at line 238 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_SPLICE_READ

+ +
+
+ + + + +
#define FUSE_CAP_SPLICE_READ   (1UL << 9)
+
+

Indicates that libfuse should try to use splice() when reading from the fuse device. This may improve performance.

+

This feature is enabled by default when supported by the kernel and if the filesystem implements a write_buf() handler.

+ +

Definition at line 247 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_SPLICE_WRITE

+ +
+
+ + + + +
#define FUSE_CAP_SPLICE_WRITE   (1UL << 7)
+
+

Indicates that libfuse should try to use splice() when writing to the fuse device. This may improve performance.

+

This feature is disabled by default.

+ +

Definition at line 230 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_CAP_WRITEBACK_CACHE

+ +
+
+ + + + +
#define FUSE_CAP_WRITEBACK_CACHE   (1UL << 16)
+
+

Indicates that writeback caching should be enabled. This means that individual write request may be buffered and merged in the kernel before they are send to the filesystem.

+

This feature is disabled by default.

+ +

Definition at line 345 of file fuse_common.h.

+ +
+
+ +

◆ FUSE_IOCTL_COMPAT

+ +
+
+ + + + +
#define FUSE_IOCTL_COMPAT   (1 << 0)
+
+

Ioctl flags

+

FUSE_IOCTL_COMPAT: 32bit compat ioctl on 64bit machine FUSE_IOCTL_UNRESTRICTED: not restricted to well-formed ioctls, retry allowed FUSE_IOCTL_RETRY: retry with new iovecs FUSE_IOCTL_DIR: is a directory

+

FUSE_IOCTL_MAX_IOV: maximum of in_iovecs + out_iovecs

+ +

Definition at line 533 of file fuse_common.h.

+ +
+
+

Enumeration Type Documentation

+ +

◆ fuse_buf_copy_flags

+ +
+
+ + + + +
enum fuse_buf_copy_flags
+
+

Buffer copy flags

+ + + + + +
Enumerator
FUSE_BUF_NO_SPLICE 

Don't use splice(2)

+

Always fall back to using read and write instead of splice(2) to copy data from one file descriptor to another.

+

If this flag is not set, then only fall back if splice is unavailable.

+
FUSE_BUF_FORCE_SPLICE 

Force splice

+

Always use splice(2) to copy data from one file descriptor to another. If splice is not available, return -EINVAL.

+
FUSE_BUF_SPLICE_MOVE 

Try to move data with splice.

+

If splice is used, try to move pages from the source to the destination instead of copying. See documentation of SPLICE_F_MOVE in splice(2) man page.

+
FUSE_BUF_SPLICE_NONBLOCK 

Don't block on the pipe when copying data with splice

+

Makes the operations on the pipe non-blocking (if the pipe is full or empty). See SPLICE_F_NONBLOCK in the splice(2) man page.

+
+ +

Definition at line 848 of file fuse_common.h.

+ +
+
+ +

◆ fuse_buf_flags

+ +
+
+ + + + +
enum fuse_buf_flags
+
+

Buffer flags

+ + + + +
Enumerator
FUSE_BUF_IS_FD 

Buffer contains a file descriptor

+

If this flag is set, the .fd field is valid, otherwise the .mem fields is valid.

+
FUSE_BUF_FD_SEEK 

Seek on the file descriptor

+

If this flag is set then the .pos field is valid and is used to seek to the given offset before performing operation on file descriptor.

+
FUSE_BUF_FD_RETRY 

Retry operation on file descriptor

+

If this flag is set then retry operation on file descriptor until .size bytes have been copied or an error or EOF is detected.

+
+ +

Definition at line 817 of file fuse_common.h.

+ +
+
+

Function Documentation

+ +

◆ fuse_apply_conn_info_opts()

+ +
+
+ + + + + + + + + + + +
void fuse_apply_conn_info_opts (struct fuse_conn_info_opts * opts,
struct fuse_conn_info * conn )
+
+

This function applies the (parsed) parameters in opts to the conn pointer. It may modify the following fields: wants, max_write, max_readahead, congestion_threshold, max_background, time_gran. A field is only set (or unset) if the corresponding option has been explicitly set.

+ +

Definition at line 422 of file helper.c.

+ +
+
+ +

◆ fuse_buf_copy()

+ +
+
+ + + + + + + + + + + + + + + + +
ssize_t fuse_buf_copy (struct fuse_bufvec * dst,
struct fuse_bufvec * src,
enum fuse_buf_copy_flags flags )
+
+

Copy data from one buffer vector to another

+
Parameters
+ + + + +
dstdestination buffer vector
srcsource buffer vector
flagsflags controlling the copy
+
+
+
Returns
actual number of bytes copied or -errno on error
+ +

Definition at line 284 of file buffer.c.

+ +
+
+ +

◆ fuse_buf_size()

+ +
+
+ + + + + + + +
size_t fuse_buf_size (const struct fuse_bufvec * bufv)
+
+

Get total size of data in a fuse buffer vector

+
Parameters
+ + +
bufvbuffer vector
+
+
+
Returns
size of data
+ +

Definition at line 22 of file buffer.c.

+ +
+
+ +

◆ fuse_convert_to_conn_want_ext()

+ +
+
+ + + + + + + +
int fuse_convert_to_conn_want_ext (struct fuse_conn_info * conn)
+
+

Get the wanted capability flags, converting from old format if necessary

+ +

Definition at line 2587 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_daemonize()

+ +
+
+ + + + + + + +
int fuse_daemonize (int foreground)
+
+

Go into the background

+
Parameters
+ + +
foregroundif true, stay in the foreground
+
+
+
Returns
0 on success, -1 on failure
+ +

Definition at line 253 of file helper.c.

+ +
+
+ +

◆ fuse_get_feature_flag()

+ +
+
+ + + + + + + + + + + +
bool fuse_get_feature_flag (struct fuse_conn_info * conn,
uint64_t flag )
+
+

Get the value of a feature flag in the want_ext field of fuse_conn_info.

+
Parameters
+ + + +
connconnection information
flagfeature flag to be checked
+
+
+
Returns
true if the flag is set, false otherwise
+ +

Definition at line 2650 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_parse_conn_info_opts()

+ +
+
+ + + + + + + +
struct fuse_conn_info_opts * fuse_parse_conn_info_opts (struct fuse_args * args)
+
+

This function parses several command-line options that can be used to override elements of struct fuse_conn_info. The pointer returned by this function should be passed to the fuse_apply_conn_info_opts() method by the file system's init() handler.

+

Before using this function, think twice if you really want these parameters to be adjustable from the command line. In most cases, they should be determined by the file system internally.

+

The following options are recognized:

+

-o max_write=N sets conn->max_write -o max_readahead=N sets conn->max_readahead -o max_background=N sets conn->max_background -o congestion_threshold=N sets conn->congestion_threshold -o async_read sets FUSE_CAP_ASYNC_READ in conn->want -o sync_read unsets FUSE_CAP_ASYNC_READ in conn->want -o atomic_o_trunc sets FUSE_CAP_ATOMIC_O_TRUNC in conn->want -o no_remote_lock Equivalent to -o no_remote_flock,no_remote_posix_lock -o no_remote_flock Unsets FUSE_CAP_FLOCK_LOCKS in conn->want -o no_remote_posix_lock Unsets FUSE_CAP_POSIX_LOCKS in conn->want -o [no_]splice_write (un-)sets FUSE_CAP_SPLICE_WRITE in conn->want -o [no_]splice_move (un-)sets FUSE_CAP_SPLICE_MOVE in conn->want -o [no_]splice_read (un-)sets FUSE_CAP_SPLICE_READ in conn->want -o [no_]auto_inval_data (un-)sets FUSE_CAP_AUTO_INVAL_DATA in conn->want -o readdirplus=no unsets FUSE_CAP_READDIRPLUS in conn->want -o readdirplus=yes sets FUSE_CAP_READDIRPLUS and unsets FUSE_CAP_READDIRPLUS_AUTO in conn->want -o readdirplus=auto sets FUSE_CAP_READDIRPLUS and FUSE_CAP_READDIRPLUS_AUTO in conn->want -o [no_]async_dio (un-)sets FUSE_CAP_ASYNC_DIO in conn->want -o [no_]writeback_cache (un-)sets FUSE_CAP_WRITEBACK_CACHE in conn->want -o time_gran=N sets conn->time_gran

+

Known options will be removed from args, unknown options will be passed through unchanged.

+
Parameters
+ + +
argsargument vector (input+output)
+
+
+
Returns
parsed options
+ +

Definition at line 476 of file helper.c.

+ +
+
+ +

◆ fuse_pkgversion()

+ +
+
+ + + + + + + +
const char * fuse_pkgversion (void )
+
+

Get the full package version string of the library

+
Returns
the package version
+ +

Definition at line 5274 of file fuse.c.

+ +
+
+ +

◆ fuse_pollhandle_destroy()

+ +
+
+ + + + + + + +
void fuse_pollhandle_destroy (struct fuse_pollhandle * ph)
+
+

Destroy poll handle

+
Parameters
+ + +
phthe poll handle
+
+
+ +

Definition at line 2389 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_remove_signal_handlers()

+ +
+
+ + + + + + + +
void fuse_remove_signal_handlers (struct fuse_session * se)
+
+

Restore default signal handlers

+

Resets global session. After this fuse_set_signal_handlers() may be called again.

+
Parameters
+ + +
sethe same session as given in fuse_set_signal_handlers()
+
+
+

See also: fuse_set_signal_handlers()

+ +

Definition at line 198 of file fuse_signals.c.

+ +
+
+ +

◆ fuse_set_fail_signal_handlers()

+ +
+
+ + + + + + + +
int fuse_set_fail_signal_handlers (struct fuse_session * se)
+
+

Print a stack backtrace diagnostic on critical signals ()

+

Stores session in a global variable. May only be called once per process until fuse_remove_signal_handlers() is called.

+

Once either of the POSIX signals arrives, the signal handler calls fuse_session_exit().

+
Parameters
+ + +
sethe session to exit
+
+
+
Returns
0 on success, -1 on failure
+

See also: fuse_remove_signal_handlers()

+ +

Definition at line 176 of file fuse_signals.c.

+ +
+
+ +

◆ fuse_set_feature_flag()

+ +
+
+ + + + + + + + + + + +
bool fuse_set_feature_flag (struct fuse_conn_info * conn,
uint64_t flag )
+
+

Config operations. These APIs are introduced in version 312 (FUSE_MAKE_VERSION(3, 12)). Using them in earlier versions will result in errors. Set a feature flag in the want_ext field of fuse_conn_info.

+
Parameters
+ + + +
connconnection information
flagfeature flag to be set
+
+
+
Returns
true if the flag was set, false if the flag is not supported
+ +

Definition at line 2624 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_set_signal_handlers()

+ +
+
+ + + + + + + +
int fuse_set_signal_handlers (struct fuse_session * se)
+
+

Exit session on HUP, TERM and INT signals and ignore PIPE signal

+

Stores session in a global variable. May only be called once per process until fuse_remove_signal_handlers() is called.

+

Once either of the POSIX signals arrives, the signal handler calls fuse_session_exit().

+
Parameters
+ + +
sethe session to exit
+
+
+
Returns
0 on success, -1 on failure
+

See also: fuse_remove_signal_handlers()

+ +

Definition at line 151 of file fuse_signals.c.

+ +
+
+ +

◆ fuse_unset_feature_flag()

+ +
+
+ + + + + + + + + + + +
void fuse_unset_feature_flag (struct fuse_conn_info * conn,
uint64_t flag )
+
+

Unset a feature flag in the want_ext field of fuse_conn_info.

+
Parameters
+ + + +
connconnection information
flagfeature flag to be unset
+
+
+ +

Definition at line 2639 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_version()

+ +
+
+ + + + + + + +
int fuse_version (void )
+
+

Get the version of the library

+
Returns
the version
+ +

Definition at line 5269 of file fuse.c.

+ +
+
+
+ + +
+ + diff --git a/doc/html/fuse__common_8h_source.html b/doc/html/fuse__common_8h_source.html new file mode 100644 index 0000000..80713e7 --- /dev/null +++ b/doc/html/fuse__common_8h_source.html @@ -0,0 +1,511 @@ + + + + + + + +libfuse: include/fuse_common.h Source File + + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
+
fuse_common.h
+
+
+Go to the documentation of this file.
1/* FUSE: Filesystem in Userspace
+
2 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
3
+
4 This program can be distributed under the terms of the GNU LGPLv2.
+
5 See the file LGPL2.txt.
+
6*/
+
7
+
9
+
10#if !defined(FUSE_H_) && !defined(FUSE_LOWLEVEL_H_)
+
11#error "Never include <fuse_common.h> directly; use <fuse.h> or <fuse_lowlevel.h> instead."
+
12#endif
+
13
+
14#ifndef FUSE_COMMON_H_
+
15#define FUSE_COMMON_H_
+
16
+
17#ifdef HAVE_LIBFUSE_PRIVATE_CONFIG_H
+
18#include "fuse_config.h"
+
19#endif
+
20
+
21#include "libfuse_config.h"
+
22
+
23#include "fuse_opt.h"
+
24#include "fuse_log.h"
+
25#include <stdint.h>
+
26#include <stdbool.h>
+
27#include <sys/types.h>
+
28#include <assert.h>
+
29
+
30#define FUSE_MAKE_VERSION(maj, min) ((maj) * 100 + (min))
+
31#define FUSE_VERSION FUSE_MAKE_VERSION(FUSE_MAJOR_VERSION, FUSE_MINOR_VERSION)
+
32
+
33#ifdef __cplusplus
+
34extern "C" {
+
35#endif
+
36
+
+ +
52 int32_t flags;
+
53
+
60 uint32_t writepage : 1;
+
61
+
63 uint32_t direct_io : 1;
+
64
+
69 uint32_t keep_cache : 1;
+
70
+
74 uint32_t flush : 1;
+
75
+
78 uint32_t nonseekable : 1;
+
79
+
80 /* Indicates that flock locks for this file should be
+
81 released. If set, lock_owner shall contain a valid value.
+
82 May only be set in ->release(). */
+
83 uint32_t flock_release : 1;
+
84
+
89 uint32_t cache_readdir : 1;
+
90
+
93 uint32_t noflush : 1;
+
94
+ +
98
+
105 uint32_t kill_suidgid : 1;
+
106
+
108 uint32_t padding : 22;
+
109 uint32_t padding2 : 32;
+
110 uint32_t padding3 : 32;
+
111
+
115 uint64_t fh;
+
116
+
118 uint64_t lock_owner;
+
119
+
122 uint32_t poll_events;
+
123
+
127 int32_t backing_id;
+
128
+
130 uint64_t compat_flags;
+
131
+
132 uint64_t reserved[2];
+
133};
+
+
134
+
145#if FUSE_USE_VERSION < FUSE_MAKE_VERSION(3, 12)
+
146struct fuse_loop_config_v1; /* forward declaration */
+
+ +
148#else
+
149struct fuse_loop_config_v1 {
+
150#endif
+ +
156
+
167 unsigned int max_idle_threads;
+
168};
+
+
169
+
170
+
171/**************************************************************************
+
172 * Capability bits for 'fuse_conn_info.capable' and 'fuse_conn_info.want' *
+
173 **************************************************************************/
+
174
+
185#define FUSE_CAP_ASYNC_READ (1UL << 0)
+
186
+
193#define FUSE_CAP_POSIX_LOCKS (1UL << 1)
+
194
+
202#define FUSE_CAP_ATOMIC_O_TRUNC (1UL << 3)
+
203
+
214#define FUSE_CAP_EXPORT_SUPPORT (1UL << 4)
+
215
+
222#define FUSE_CAP_DONT_MASK (1UL << 6)
+
223
+
230#define FUSE_CAP_SPLICE_WRITE (1UL << 7)
+
231
+
238#define FUSE_CAP_SPLICE_MOVE (1UL << 8)
+
239
+
247#define FUSE_CAP_SPLICE_READ (1UL << 9)
+
248
+
260#define FUSE_CAP_FLOCK_LOCKS (1UL << 10)
+
261
+
267#define FUSE_CAP_IOCTL_DIR (1UL << 11)
+
268
+
289#define FUSE_CAP_AUTO_INVAL_DATA (1UL << 12)
+
290
+
297#define FUSE_CAP_READDIRPLUS (1UL << 13)
+
298
+
325#define FUSE_CAP_READDIRPLUS_AUTO (1UL << 14)
+
326
+
336#define FUSE_CAP_ASYNC_DIO (1UL << 15)
+
337
+
345#define FUSE_CAP_WRITEBACK_CACHE (1UL << 16)
+
346
+
360#define FUSE_CAP_NO_OPEN_SUPPORT (1UL << 17)
+
361
+
368#define FUSE_CAP_PARALLEL_DIROPS (1UL << 18)
+
369
+
387#define FUSE_CAP_POSIX_ACL (1UL << 19)
+
388
+
396#define FUSE_CAP_HANDLE_KILLPRIV (1UL << 20)
+
397
+
413#define FUSE_CAP_HANDLE_KILLPRIV_V2 (1UL << 21)
+
414
+
426#define FUSE_CAP_CACHE_SYMLINKS (1UL << 23)
+
427
+
441#define FUSE_CAP_NO_OPENDIR_SUPPORT (1UL << 24)
+
442
+
464#define FUSE_CAP_EXPLICIT_INVAL_DATA (1UL << 25)
+
465
+
480#define FUSE_CAP_EXPIRE_ONLY (1UL << 26)
+
481
+
487#define FUSE_CAP_SETXATTR_EXT (1UL << 27)
+
488
+
496#define FUSE_CAP_DIRECT_IO_ALLOW_MMAP (1UL << 28)
+
497
+
508#define FUSE_CAP_PASSTHROUGH (1UL << 29)
+
509
+
516#define FUSE_CAP_NO_EXPORT_SUPPORT (1UL << 30)
+
517
+
521#define FUSE_CAP_OVER_IO_URING (1UL << 31)
+
522
+
533#define FUSE_IOCTL_COMPAT (1 << 0)
+
534#define FUSE_IOCTL_UNRESTRICTED (1 << 1)
+
535#define FUSE_IOCTL_RETRY (1 << 2)
+
536#define FUSE_IOCTL_DIR (1 << 4)
+
537
+
538#define FUSE_IOCTL_MAX_IOV 256
+
539
+
+ +
555 uint32_t proto_major;
+
556
+
560 uint32_t proto_minor;
+
561
+
565 uint32_t max_write;
+
566
+
579 uint32_t max_read;
+
580
+ +
585
+
591 uint32_t capable;
+
592
+
603 uint32_t want;
+
604
+ +
634
+ +
644
+
660 uint32_t time_gran;
+
661
+
678#define FUSE_BACKING_STACKED_UNDER (0)
+
679#define FUSE_BACKING_STACKED_OVER (1)
+
680 uint32_t max_backing_stack_depth;
+
681
+
690 uint32_t no_interrupt : 1;
+
691
+
692 /* reserved bits for future use */
+
693 uint32_t padding : 31;
+
694
+
699 uint64_t capable_ext;
+
700
+
709 uint64_t want_ext;
+
710
+ +
716
+
720 uint16_t reserved[31];
+
721};
+
+
722
+
723struct fuse_session;
+
724struct fuse_pollhandle;
+
725struct fuse_conn_info_opts;
+
726
+
769struct fuse_conn_info_opts* fuse_parse_conn_info_opts(struct fuse_args *args);
+
770
+
778void fuse_apply_conn_info_opts(struct fuse_conn_info_opts *opts,
+
779 struct fuse_conn_info *conn);
+
780
+
787int fuse_daemonize(int foreground);
+
788
+
794int fuse_version(void);
+
795
+
801const char *fuse_pkgversion(void);
+
802
+
808void fuse_pollhandle_destroy(struct fuse_pollhandle *ph);
+
809
+
810/* ----------------------------------------------------------- *
+
811 * Data buffer *
+
812 * ----------------------------------------------------------- */
+
813
+
+ +
824 FUSE_BUF_IS_FD = (1 << 1),
+
825
+ +
834
+ +
843};
+
+
844
+
+ + +
859
+ +
867
+ +
876
+ +
885};
+
+
886
+
+
893struct fuse_buf {
+
897 size_t size;
+
898
+ +
903
+
909 void *mem;
+
910
+
916 int fd;
+
917
+
923 off_t pos;
+
924
+
931 size_t mem_size;
+
932};
+
+
933
+
+ +
946 size_t count;
+
947
+
951 size_t idx;
+
952
+
956 size_t off;
+
957
+
961 struct fuse_buf buf[1];
+
962};
+
+
963
+
+ +
969{
+
970 uint32_t major;
+
971 uint32_t minor;
+
972 uint32_t hotfix;
+
973 uint32_t padding;
+
974};
+
+
975
+
976/* Initialize bufvec with a single buffer of given size */
+
977#define FUSE_BUFVEC_INIT(size__) \
+
978 ((struct fuse_bufvec) { \
+
979 /* .count= */ 1, \
+
980 /* .idx = */ 0, \
+
981 /* .off = */ 0, \
+
982 /* .buf = */ { /* [0] = */ { \
+
983 /* .size = */ (size__), \
+
984 /* .flags = */ (enum fuse_buf_flags) 0, \
+
985 /* .mem = */ NULL, \
+
986 /* .fd = */ -1, \
+
987 /* .pos = */ 0, \
+
988 /* .mem_size = */ 0, \
+
989 } } \
+
990 } )
+
991
+
998size_t fuse_buf_size(const struct fuse_bufvec *bufv);
+
999
+
1008ssize_t fuse_buf_copy(struct fuse_bufvec *dst, struct fuse_bufvec *src,
+
1009 enum fuse_buf_copy_flags flags);
+
1010
+
1011/* ----------------------------------------------------------- *
+
1012 * Signal handling *
+
1013 * ----------------------------------------------------------- */
+
1014
+
1030int fuse_set_signal_handlers(struct fuse_session *se);
+
1031
+
1047int fuse_set_fail_signal_handlers(struct fuse_session *se);
+
1048
+
1060void fuse_remove_signal_handlers(struct fuse_session *se);
+
1061
+
1067#if FUSE_USE_VERSION >= FUSE_MAKE_VERSION(3, 12)
+
1073struct fuse_loop_config *fuse_loop_cfg_create(void);
+
1074
+
1078void fuse_loop_cfg_destroy(struct fuse_loop_config *config);
+
1079
+
1083void fuse_loop_cfg_set_idle_threads(struct fuse_loop_config *config,
+
1084 unsigned int value);
+
1085
+
1089void fuse_loop_cfg_set_max_threads(struct fuse_loop_config *config,
+
1090 unsigned int value);
+
1091
+
1095void fuse_loop_cfg_set_clone_fd(struct fuse_loop_config *config,
+
1096 unsigned int value);
+
1097
+
1104void fuse_loop_cfg_convert(struct fuse_loop_config *config,
+
1105 struct fuse_loop_config_v1 *v1_conf);
+
1106#endif
+
1107
+
1115bool fuse_set_feature_flag(struct fuse_conn_info *conn, uint64_t flag);
+
1116
+
1123void fuse_unset_feature_flag(struct fuse_conn_info *conn, uint64_t flag);
+
1124
+
1132bool fuse_get_feature_flag(struct fuse_conn_info *conn, uint64_t flag);
+
1133
+
1134/*
+
1135 * DO NOT USE: Not part of public API, for internal test use only.
+
1136 * The function signature or any use of it is not guaranteeed to
+
1137 * remain stable. And neither are results of what this function does.
+
1138 */
+ +
1140
+
1141
+
1142
+
1143/* ----------------------------------------------------------- *
+
1144 * Compatibility stuff *
+
1145 * ----------------------------------------------------------- */
+
1146
+
1147#if !defined(FUSE_USE_VERSION) || FUSE_USE_VERSION < 30
+
1148# error only API version 30 or greater is supported
+
1149#endif
+
1150
+
1151#ifdef __cplusplus
+
1152}
+
1153#endif
+
1154
+
1155
+
1156/*
+
1157 * This interface uses 64 bit off_t.
+
1158 *
+
1159 * On 32bit systems please add -D_FILE_OFFSET_BITS=64 to your compile flags!
+
1160 */
+
1161
+
1162#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L)
+
1163_Static_assert(sizeof(off_t) == 8, "fuse: off_t must be 64bit");
+
1164#else
+
1165struct _fuse_off_t_must_be_64bit_dummy_struct \
+
1166 { unsigned _fuse_off_t_must_be_64bit:((sizeof(off_t) == 8) ? 1 : -1); };
+
1167#endif
+
1168
+
1169#endif /* FUSE_COMMON_H_ */
+
void fuse_unset_feature_flag(struct fuse_conn_info *conn, uint64_t flag)
+
int fuse_convert_to_conn_want_ext(struct fuse_conn_info *conn)
+
int fuse_set_fail_signal_handlers(struct fuse_session *se)
+
bool fuse_set_feature_flag(struct fuse_conn_info *conn, uint64_t flag)
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
size_t fuse_buf_size(const struct fuse_bufvec *bufv)
Definition buffer.c:22
+
void fuse_apply_conn_info_opts(struct fuse_conn_info_opts *opts, struct fuse_conn_info *conn)
Definition helper.c:422
+
fuse_buf_flags
+
@ FUSE_BUF_FD_SEEK
+
@ FUSE_BUF_FD_RETRY
+
@ FUSE_BUF_IS_FD
+
ssize_t fuse_buf_copy(struct fuse_bufvec *dst, struct fuse_bufvec *src, enum fuse_buf_copy_flags flags)
Definition buffer.c:284
+
struct fuse_conn_info_opts * fuse_parse_conn_info_opts(struct fuse_args *args)
Definition helper.c:476
+
bool fuse_get_feature_flag(struct fuse_conn_info *conn, uint64_t flag)
+
const char * fuse_pkgversion(void)
Definition fuse.c:5274
+
void fuse_pollhandle_destroy(struct fuse_pollhandle *ph)
+
int fuse_version(void)
Definition fuse.c:5269
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
fuse_buf_copy_flags
+
@ FUSE_BUF_SPLICE_NONBLOCK
+
@ FUSE_BUF_FORCE_SPLICE
+
@ FUSE_BUF_NO_SPLICE
+
@ FUSE_BUF_SPLICE_MOVE
+
int fuse_daemonize(int foreground)
Definition helper.c:253
+ + + + +
enum fuse_buf_flags flags
+
size_t mem_size
+
void * mem
+ +
off_t pos
+
size_t size
+ + + +
struct fuse_buf buf[1]
+ + +
uint32_t time_gran
+
uint16_t request_timeout
+
uint32_t proto_major
+ +
uint32_t congestion_threshold
+
uint32_t proto_minor
+
uint32_t max_write
+
uint64_t capable_ext
+
uint32_t max_readahead
+
uint32_t no_interrupt
+
uint32_t max_read
+
uint32_t max_background
+
uint32_t capable
+
uint64_t want_ext
+
uint16_t reserved[31]
+ +
uint64_t lock_owner
+
uint32_t kill_suidgid
+ +
uint32_t writepage
Definition fuse_common.h:60
+
uint32_t poll_events
+
uint32_t cache_readdir
Definition fuse_common.h:89
+
uint32_t nonseekable
Definition fuse_common.h:78
+
int32_t backing_id
+
uint32_t parallel_direct_writes
Definition fuse_common.h:97
+
uint32_t padding
+
uint32_t noflush
Definition fuse_common.h:93
+
uint64_t compat_flags
+
uint32_t flush
Definition fuse_common.h:74
+
uint32_t direct_io
Definition fuse_common.h:63
+
uint32_t keep_cache
Definition fuse_common.h:69
+ + + +
unsigned int max_idle_threads
+ +
+ + +
+ + diff --git a/doc/html/fuse__i_8h_source.html b/doc/html/fuse__i_8h_source.html new file mode 100644 index 0000000..c5dd364 --- /dev/null +++ b/doc/html/fuse__i_8h_source.html @@ -0,0 +1,291 @@ + + + + + + + +libfuse: lib/fuse_i.h Source File + + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
+
fuse_i.h
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 This program can be distributed under the terms of the GNU LGPLv2.
+
6 See the file LGPL2.txt
+
7*/
+
8
+
9#ifndef LIB_FUSE_I_H_
+
10#define LIB_FUSE_I_H_
+
11
+
12#include "fuse.h"
+
13#include "fuse_lowlevel.h"
+
14#include "util.h"
+
15
+
16#include <pthread.h>
+
17#include <semaphore.h>
+
18#include <stdint.h>
+
19#include <stdbool.h>
+
20#include <errno.h>
+
21#include <stdatomic.h>
+
22
+
23#define MIN(a, b) \
+
24({ \
+
25 typeof(a) _a = (a); \
+
26 typeof(b) _b = (b); \
+
27 _a < _b ? _a : _b; \
+
28})
+
29
+
30struct mount_opts;
+
31struct fuse_ring_pool;
+
32
+
33struct fuse_req {
+
34 struct fuse_session *se;
+
35 uint64_t unique;
+
36 _Atomic int ref_cnt;
+
37 pthread_mutex_t lock;
+
38 struct fuse_ctx ctx;
+
39 struct fuse_chan *ch;
+
40 int interrupted;
+
41 struct {
+
42 unsigned int ioctl_64bit : 1;
+
43 unsigned int is_uring : 1;
+
44 unsigned int is_copy_file_range_64 : 1;
+
45 } flags;
+
46 union {
+
47 struct {
+
48 uint64_t unique;
+
49 } i;
+
50 struct {
+ +
52 void *data;
+
53 } ni;
+
54 } u;
+
55 struct fuse_req *next;
+
56 struct fuse_req *prev;
+
57};
+
58
+
59struct fuse_notify_req {
+
60 uint64_t unique;
+
61 void (*reply)(struct fuse_notify_req *, fuse_req_t, fuse_ino_t,
+
62 const void *, const struct fuse_buf *);
+
63 struct fuse_notify_req *next;
+
64 struct fuse_notify_req *prev;
+
65};
+
66
+
67struct fuse_session_uring {
+
68 bool enable;
+
69 unsigned int q_depth;
+
70 struct fuse_ring_pool *pool;
+
71};
+
72
+
73struct fuse_session {
+
74 _Atomic(char *)mountpoint;
+
75 int fd;
+
76 struct fuse_custom_io *io;
+
77 struct mount_opts *mo;
+
78 int debug;
+
79 int deny_others;
+
80 struct fuse_lowlevel_ops op;
+
81 int got_init;
+
82 struct cuse_data *cuse_data;
+
83 void *userdata;
+
84 uid_t owner;
+
85 struct fuse_conn_info conn;
+
86 struct fuse_req list;
+
87 struct fuse_req interrupts;
+
88 pthread_mutex_t lock;
+
89 int got_destroy;
+
90 pthread_key_t pipe_key;
+
91 int broken_splice_nonblock;
+
92 uint64_t notify_ctr;
+
93 struct fuse_notify_req notify_list;
+
94 _Atomic size_t bufsize;
+
95 int error;
+
96
+
97 /*
+
98 * This is useful if any kind of ABI incompatibility is found at
+
99 * a later version, to 'fix' it at run time.
+
100 */
+
101 struct libfuse_version version;
+
102
+
103 /* thread synchronization */
+
104 _Atomic bool mt_exited;
+
105 pthread_mutex_t mt_lock;
+
106 sem_t mt_finish;
+
107
+
108 /* true if reading requests from /dev/fuse are handled internally */
+
109 bool buf_reallocable;
+
110
+
111 /* io_uring */
+
112 struct fuse_session_uring uring;
+
113
+
114 /*
+
115 * conn->want and conn_want_ext options set by libfuse , needed
+
116 * to correctly convert want to want_ext
+
117 */
+
118 uint32_t conn_want;
+
119 uint64_t conn_want_ext;
+
120};
+
121
+
122struct fuse_chan {
+
123 pthread_mutex_t lock;
+
124 int ctr;
+
125 int fd;
+
126};
+
127
+
+ +
136 char *name;
+
137 fuse_module_factory_t factory;
+
138 struct fuse_module *next;
+
139 struct fusemod_so *so;
+
140 int ctr;
+
141};
+
+
142
+
151#if FUSE_USE_VERSION >= FUSE_MAKE_VERSION(3, 12)
+
152struct fuse_loop_config
+
153{
+
154 /* verififier that a correct struct was was passed. This is especially
+
155 * needed, as versions below (3, 12) were using a public struct
+
156 * (now called fuse_loop_config_v1), which was hard to extend with
+
157 * additional parameters, without risking that file system implementations
+
158 * would not have noticed and might either pass uninitialized members
+
159 * or even too small structs.
+
160 * fuse_loop_config_v1 has clone_fd at this offset, which should be either 0
+
161 * or 1. v2 or even higher version just need to set a value here
+
162 * which not conflicting and very unlikely as having been set by
+
163 * file system implementation.
+
164 */
+
165 int version_id;
+
166
+
171 int clone_fd;
+ +
184
+
190 unsigned int max_threads;
+
191};
+
192#endif
+
193
+
194/* ----------------------------------------------------------- *
+
195 * Channel interface (when using -o clone_fd) *
+
196 * ----------------------------------------------------------- */
+
197
+
204struct fuse_chan *fuse_chan_get(struct fuse_chan *ch);
+
205
+
211void fuse_chan_put(struct fuse_chan *ch);
+
212
+
213struct mount_opts *parse_mount_opts(struct fuse_args *args);
+
214void destroy_mount_opts(struct mount_opts *mo);
+
215void fuse_mount_version(void);
+
216unsigned get_max_read(struct mount_opts *o);
+
217void fuse_kern_unmount(const char *mountpoint, int fd);
+
218int fuse_kern_mount(const char *mountpoint, struct mount_opts *mo);
+
219
+
220int fuse_send_reply_iov_nofree(fuse_req_t req, int error, struct iovec *iov,
+
221 int count);
+
222void fuse_free_req(fuse_req_t req);
+
223void list_init_req(struct fuse_req *req);
+
224
+
225void _cuse_lowlevel_init(fuse_req_t req, const fuse_ino_t nodeid,
+
226 const void *req_header, const void *req_payload);
+
227void cuse_lowlevel_init(fuse_req_t req, fuse_ino_t nodeide, const void *inarg);
+
228
+
229int fuse_start_thread(pthread_t *thread_id, void *(*func)(void *), void *arg);
+
230
+
231void fuse_buf_free(struct fuse_buf *buf);
+
232
+
233int fuse_session_receive_buf_internal(struct fuse_session *se,
+
234 struct fuse_buf *buf,
+
235 struct fuse_chan *ch);
+
236void fuse_session_process_buf_internal(struct fuse_session *se,
+
237 const struct fuse_buf *buf,
+
238 struct fuse_chan *ch);
+
239
+
240struct fuse *fuse_new_31(struct fuse_args *args, const struct fuse_operations *op,
+
241 size_t op_size, void *private_data);
+
242int fuse_loop_mt_312(struct fuse *f, struct fuse_loop_config *config);
+
243int fuse_session_loop_mt_312(struct fuse_session *se, struct fuse_loop_config *config);
+
244
+
250int fuse_loop_cfg_verify(struct fuse_loop_config *config);
+
251
+
252
+
253/*
+
254 * This can be changed dynamically on recent kernels through the
+
255 * /proc/sys/fs/fuse/max_pages_limit interface.
+
256 *
+
257 * Older kernels will always use the default value.
+
258 */
+
259#define FUSE_DEFAULT_MAX_PAGES_LIMIT 256
+
260#define FUSE_DEFAULT_MAX_PAGES_PER_REQ 32
+
261
+
262/* room needed in buffer to accommodate header */
+
263#define FUSE_BUFFER_HEADER_SIZE 0x1000
+
264
+
265
+
266#endif /* LIB_FUSE_I_H_*/
+ +
struct fuse_fs *(* fuse_module_factory_t)(struct fuse_args *args, struct fuse_fs *fs[])
Definition fuse.h:1403
+ +
void(* fuse_interrupt_func_t)(fuse_req_t req, void *data)
+
struct fuse_req * fuse_req_t
+
uint64_t fuse_ino_t
+ + + + +
unsigned int max_threads
Definition fuse_i.h:190
+
unsigned int max_idle_threads
+ + + +
+ + +
+ + diff --git a/doc/html/fuse__kernel_8h_source.html b/doc/html/fuse__kernel_8h_source.html new file mode 100644 index 0000000..f15f60a --- /dev/null +++ b/doc/html/fuse__kernel_8h_source.html @@ -0,0 +1,1187 @@ + + + + + + + +libfuse: include/fuse_kernel.h Source File + + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
+
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 <miklos@szeredi.hu>
+
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.1:
+
42 * - add the following messages:
+
43 * FUSE_SETATTR, FUSE_SYMLINK, FUSE_MKNOD, FUSE_MKDIR, FUSE_UNLINK,
+
44 * FUSE_RMDIR, FUSE_RENAME, FUSE_LINK, FUSE_OPEN, FUSE_READ, FUSE_WRITE,
+
45 * FUSE_RELEASE, FUSE_FSYNC, FUSE_FLUSH, FUSE_SETXATTR, FUSE_GETXATTR,
+
46 * FUSE_LISTXATTR, FUSE_REMOVEXATTR, FUSE_OPENDIR, FUSE_READDIR,
+
47 * FUSE_RELEASEDIR
+
48 * - add padding to messages to accommodate 32-bit servers on 64-bit kernels
+
49 *
+
50 * 7.2:
+
51 * - add FOPEN_DIRECT_IO and FOPEN_KEEP_CACHE flags
+
52 * - add FUSE_FSYNCDIR message
+
53 *
+
54 * 7.3:
+
55 * - add FUSE_ACCESS message
+
56 * - add FUSE_CREATE message
+
57 * - add filehandle to fuse_setattr_in
+
58 *
+
59 * 7.4:
+
60 * - add frsize to fuse_kstatfs
+
61 * - clean up request size limit checking
+
62 *
+
63 * 7.5:
+
64 * - add flags and max_write to fuse_init_out
+
65 *
+
66 * 7.6:
+
67 * - add max_readahead to fuse_init_in and fuse_init_out
+
68 *
+
69 * 7.7:
+
70 * - add FUSE_INTERRUPT message
+
71 * - add POSIX file lock support
+
72 *
+
73 * 7.8:
+
74 * - add lock_owner and flags fields to fuse_release_in
+
75 * - add FUSE_BMAP message
+
76 * - add FUSE_DESTROY message
+
77 *
+
78 * 7.9:
+
79 * - new fuse_getattr_in input argument of GETATTR
+
80 * - add lk_flags in fuse_lk_in
+
81 * - add lock_owner field to fuse_setattr_in, fuse_read_in and fuse_write_in
+
82 * - add blksize field to fuse_attr
+
83 * - add file flags field to fuse_read_in and fuse_write_in
+
84 * - Add ATIME_NOW and MTIME_NOW flags to fuse_setattr_in
+
85 *
+
86 * 7.10
+
87 * - add nonseekable open flag
+
88 *
+
89 * 7.11
+
90 * - add IOCTL message
+
91 * - add unsolicited notification support
+
92 * - add POLL message and NOTIFY_POLL notification
+
93 *
+
94 * 7.12
+
95 * - add umask flag to input argument of create, mknod and mkdir
+
96 * - add notification messages for invalidation of inodes and
+
97 * directory entries
+
98 *
+
99 * 7.13
+
100 * - make max number of background requests and congestion threshold
+
101 * tunables
+
102 *
+
103 * 7.14
+
104 * - add splice support to fuse device
+
105 *
+
106 * 7.15
+
107 * - add store notify
+
108 * - add retrieve notify
+
109 *
+
110 * 7.16
+
111 * - add BATCH_FORGET request
+
112 * - FUSE_IOCTL_UNRESTRICTED shall now return with array of 'struct
+
113 * fuse_ioctl_iovec' instead of ambiguous 'struct iovec'
+
114 * - add FUSE_IOCTL_32BIT flag
+
115 *
+
116 * 7.17
+
117 * - add FUSE_FLOCK_LOCKS and FUSE_RELEASE_FLOCK_UNLOCK
+
118 *
+
119 * 7.18
+
120 * - add FUSE_IOCTL_DIR flag
+
121 * - add FUSE_NOTIFY_DELETE
+
122 *
+
123 * 7.19
+
124 * - add FUSE_FALLOCATE
+
125 *
+
126 * 7.20
+
127 * - add FUSE_AUTO_INVAL_DATA
+
128 *
+
129 * 7.21
+
130 * - add FUSE_READDIRPLUS
+
131 * - send the requested events in POLL request
+
132 *
+
133 * 7.22
+
134 * - add FUSE_ASYNC_DIO
+
135 *
+
136 * 7.23
+
137 * - add FUSE_WRITEBACK_CACHE
+
138 * - add time_gran to fuse_init_out
+
139 * - add reserved space to fuse_init_out
+
140 * - add FATTR_CTIME
+
141 * - add ctime and ctimensec to fuse_setattr_in
+
142 * - add FUSE_RENAME2 request
+
143 * - add FUSE_NO_OPEN_SUPPORT flag
+
144 *
+
145 * 7.24
+
146 * - add FUSE_LSEEK for SEEK_HOLE and SEEK_DATA support
+
147 *
+
148 * 7.25
+
149 * - add FUSE_PARALLEL_DIROPS
+
150 *
+
151 * 7.26
+
152 * - add FUSE_HANDLE_KILLPRIV
+
153 * - add FUSE_POSIX_ACL
+
154 *
+
155 * 7.27
+
156 * - add FUSE_ABORT_ERROR
+
157 *
+
158 * 7.28
+
159 * - add FUSE_COPY_FILE_RANGE
+
160 * - add FOPEN_CACHE_DIR
+
161 * - add FUSE_MAX_PAGES, add max_pages to init_out
+
162 * - add FUSE_CACHE_SYMLINKS
+
163 *
+
164 * 7.29
+
165 * - add FUSE_NO_OPENDIR_SUPPORT flag
+
166 *
+
167 * 7.30
+
168 * - add FUSE_EXPLICIT_INVAL_DATA
+
169 * - add FUSE_IOCTL_COMPAT_X32
+
170 *
+
171 * 7.31
+
172 * - add FUSE_WRITE_KILL_PRIV flag
+
173 * - add FUSE_SETUPMAPPING and FUSE_REMOVEMAPPING
+
174 * - add map_alignment to fuse_init_out, add FUSE_MAP_ALIGNMENT flag
+
175 *
+
176 * 7.32
+
177 * - add flags to fuse_attr, add FUSE_ATTR_SUBMOUNT, add FUSE_SUBMOUNTS
+
178 *
+
179 * 7.33
+
180 * - add FUSE_HANDLE_KILLPRIV_V2, FUSE_WRITE_KILL_SUIDGID, FATTR_KILL_SUIDGID
+
181 * - add FUSE_OPEN_KILL_SUIDGID
+
182 * - extend fuse_setxattr_in, add FUSE_SETXATTR_EXT
+
183 * - add FUSE_SETXATTR_ACL_KILL_SGID
+
184 *
+
185 * 7.34
+
186 * - add FUSE_SYNCFS
+
187 *
+
188 * 7.35
+
189 * - add FOPEN_NOFLUSH
+
190 *
+
191 * 7.36
+
192 * - extend fuse_init_in with reserved fields, add FUSE_INIT_EXT init flag
+
193 * - add flags2 to fuse_init_in and fuse_init_out
+
194 * - add FUSE_SECURITY_CTX init flag
+
195 * - add security context to create, mkdir, symlink, and mknod requests
+
196 * - add FUSE_HAS_INODE_DAX, FUSE_ATTR_DAX
+
197 *
+
198 * 7.37
+
199 * - add FUSE_TMPFILE
+
200 *
+
201 * 7.38
+
202 * - add FUSE_EXPIRE_ONLY flag to fuse_notify_inval_entry
+
203 * - add FOPEN_PARALLEL_DIRECT_WRITES
+
204 * - add total_extlen to fuse_in_header
+
205 * - add FUSE_MAX_NR_SECCTX
+
206 * - add extension header
+
207 * - add FUSE_EXT_GROUPS
+
208 * - add FUSE_CREATE_SUPP_GROUP
+
209 * - add FUSE_HAS_EXPIRE_ONLY
+
210 *
+
211 * 7.39
+
212 * - add FUSE_DIRECT_IO_ALLOW_MMAP
+
213 * - add FUSE_STATX and related structures
+
214 *
+
215 * 7.40
+
216 * - add max_stack_depth to fuse_init_out, add FUSE_PASSTHROUGH init flag
+
217 * - add backing_id to fuse_open_out, add FOPEN_PASSTHROUGH open flag
+
218 * - add FUSE_NO_EXPORT_SUPPORT init flag
+
219 * - add FUSE_NOTIFY_RESEND, add FUSE_HAS_RESEND init flag
+
220 *
+
221 * 7.41
+
222 * - add FUSE_ALLOW_IDMAP
+
223 * 7.42
+
224 * - Add FUSE_OVER_IO_URING and all other io-uring related flags and data
+
225 * structures:
+
226 * - struct fuse_uring_ent_in_out
+
227 * - struct fuse_uring_req_header
+
228 * - struct fuse_uring_cmd_req
+
229 * - FUSE_URING_IN_OUT_HEADER_SZ
+
230 * - FUSE_URING_OP_IN_OUT_SZ
+
231 * - enum fuse_uring_cmd
+
232 *
+
233 * 7.43
+
234 * - add FUSE_REQUEST_TIMEOUT
+
235 *
+
236 * 7.44
+
237 * - add FUSE_NOTIFY_INC_EPOCH
+
238 *
+
239 * 7.45
+
240 * - add FUSE_COPY_FILE_RANGE_64
+
241 * - add struct fuse_copy_file_range_out
+
242 */
+
243
+
244#ifndef _LINUX_FUSE_H
+
245#define _LINUX_FUSE_H
+
246
+
247#ifdef __KERNEL__
+
248#include <linux/types.h>
+
249#else
+
250#include <stdint.h>
+
251#endif
+
252
+
253/*
+
254 * Version negotiation:
+
255 *
+
256 * Both the kernel and userspace send the version they support in the
+
257 * INIT request and reply respectively.
+
258 *
+
259 * If the major versions match then both shall use the smallest
+
260 * of the two minor versions for communication.
+
261 *
+
262 * If the kernel supports a larger major version, then userspace shall
+
263 * reply with the major version it supports, ignore the rest of the
+
264 * INIT message and expect a new INIT message from the kernel with a
+
265 * matching major version.
+
266 *
+
267 * If the library supports a larger major version, then it shall fall
+
268 * back to the major protocol version sent by the kernel for
+
269 * communication and reply with that major version (and an arbitrary
+
270 * supported minor version).
+
271 */
+
272
+
274#define FUSE_KERNEL_VERSION 7
+
275
+
277#define FUSE_KERNEL_MINOR_VERSION 45
+
278
+
280#define FUSE_ROOT_ID 1
+
281
+
282/* Make sure all structures are padded to 64bit boundary, so 32bit
+
283 userspace works under 64bit kernels */
+
284
+
285struct fuse_attr {
+
286 uint64_t ino;
+
287 uint64_t size;
+
288 uint64_t blocks;
+
289 uint64_t atime;
+
290 uint64_t mtime;
+
291 uint64_t ctime;
+
292 uint32_t atimensec;
+
293 uint32_t mtimensec;
+
294 uint32_t ctimensec;
+
295 uint32_t mode;
+
296 uint32_t nlink;
+
297 uint32_t uid;
+
298 uint32_t gid;
+
299 uint32_t rdev;
+
300 uint32_t blksize;
+
301 uint32_t flags;
+
302};
+
303
+
304/*
+
305 * The following structures are bit-for-bit compatible with the statx(2) ABI in
+
306 * Linux.
+
307 */
+
308struct fuse_sx_time {
+
309 int64_t tv_sec;
+
310 uint32_t tv_nsec;
+
311 int32_t __reserved;
+
312};
+
313
+
314struct fuse_statx {
+
315 uint32_t mask;
+
316 uint32_t blksize;
+
317 uint64_t attributes;
+
318 uint32_t nlink;
+
319 uint32_t uid;
+
320 uint32_t gid;
+
321 uint16_t mode;
+
322 uint16_t __spare0[1];
+
323 uint64_t ino;
+
324 uint64_t size;
+
325 uint64_t blocks;
+
326 uint64_t attributes_mask;
+
327 struct fuse_sx_time atime;
+
328 struct fuse_sx_time btime;
+
329 struct fuse_sx_time ctime;
+
330 struct fuse_sx_time mtime;
+
331 uint32_t rdev_major;
+
332 uint32_t rdev_minor;
+
333 uint32_t dev_major;
+
334 uint32_t dev_minor;
+
335 uint64_t __spare2[14];
+
336};
+
337
+
338struct fuse_kstatfs {
+
339 uint64_t blocks;
+
340 uint64_t bfree;
+
341 uint64_t bavail;
+
342 uint64_t files;
+
343 uint64_t ffree;
+
344 uint32_t bsize;
+
345 uint32_t namelen;
+
346 uint32_t frsize;
+
347 uint32_t padding;
+
348 uint32_t spare[6];
+
349};
+
350
+
351struct fuse_file_lock {
+
352 uint64_t start;
+
353 uint64_t end;
+
354 uint32_t type;
+
355 uint32_t pid; /* tgid */
+
356};
+
357
+
361#define FATTR_MODE (1 << 0)
+
362#define FATTR_UID (1 << 1)
+
363#define FATTR_GID (1 << 2)
+
364#define FATTR_SIZE (1 << 3)
+
365#define FATTR_ATIME (1 << 4)
+
366#define FATTR_MTIME (1 << 5)
+
367#define FATTR_FH (1 << 6)
+
368#define FATTR_ATIME_NOW (1 << 7)
+
369#define FATTR_MTIME_NOW (1 << 8)
+
370#define FATTR_LOCKOWNER (1 << 9)
+
371#define FATTR_CTIME (1 << 10)
+
372#define FATTR_KILL_SUIDGID (1 << 11)
+
373
+
386#define FOPEN_DIRECT_IO (1 << 0)
+
387#define FOPEN_KEEP_CACHE (1 << 1)
+
388#define FOPEN_NONSEEKABLE (1 << 2)
+
389#define FOPEN_CACHE_DIR (1 << 3)
+
390#define FOPEN_STREAM (1 << 4)
+
391#define FOPEN_NOFLUSH (1 << 5)
+
392#define FOPEN_PARALLEL_DIRECT_WRITES (1 << 6)
+
393#define FOPEN_PASSTHROUGH (1 << 7)
+
394
+
451#define FUSE_ASYNC_READ (1 << 0)
+
452#define FUSE_POSIX_LOCKS (1 << 1)
+
453#define FUSE_FILE_OPS (1 << 2)
+
454#define FUSE_ATOMIC_O_TRUNC (1 << 3)
+
455#define FUSE_EXPORT_SUPPORT (1 << 4)
+
456#define FUSE_BIG_WRITES (1 << 5)
+
457#define FUSE_DONT_MASK (1 << 6)
+
458#define FUSE_SPLICE_WRITE (1 << 7)
+
459#define FUSE_SPLICE_MOVE (1 << 8)
+
460#define FUSE_SPLICE_READ (1 << 9)
+
461#define FUSE_FLOCK_LOCKS (1 << 10)
+
462#define FUSE_HAS_IOCTL_DIR (1 << 11)
+
463#define FUSE_AUTO_INVAL_DATA (1 << 12)
+
464#define FUSE_DO_READDIRPLUS (1 << 13)
+
465#define FUSE_READDIRPLUS_AUTO (1 << 14)
+
466#define FUSE_ASYNC_DIO (1 << 15)
+
467#define FUSE_WRITEBACK_CACHE (1 << 16)
+
468#define FUSE_NO_OPEN_SUPPORT (1 << 17)
+
469#define FUSE_PARALLEL_DIROPS (1 << 18)
+
470#define FUSE_HANDLE_KILLPRIV (1 << 19)
+
471#define FUSE_POSIX_ACL (1 << 20)
+
472#define FUSE_ABORT_ERROR (1 << 21)
+
473#define FUSE_MAX_PAGES (1 << 22)
+
474#define FUSE_CACHE_SYMLINKS (1 << 23)
+
475#define FUSE_NO_OPENDIR_SUPPORT (1 << 24)
+
476#define FUSE_EXPLICIT_INVAL_DATA (1 << 25)
+
477#define FUSE_MAP_ALIGNMENT (1 << 26)
+
478#define FUSE_SUBMOUNTS (1 << 27)
+
479#define FUSE_HANDLE_KILLPRIV_V2 (1 << 28)
+
480#define FUSE_SETXATTR_EXT (1 << 29)
+
481#define FUSE_INIT_EXT (1 << 30)
+
482#define FUSE_INIT_RESERVED (1 << 31)
+
483/* bits 32..63 get shifted down 32 bits into the flags2 field */
+
484#define FUSE_SECURITY_CTX (1ULL << 32)
+
485#define FUSE_HAS_INODE_DAX (1ULL << 33)
+
486#define FUSE_CREATE_SUPP_GROUP (1ULL << 34)
+
487#define FUSE_HAS_EXPIRE_ONLY (1ULL << 35)
+
488#define FUSE_DIRECT_IO_ALLOW_MMAP (1ULL << 36)
+
489#define FUSE_PASSTHROUGH (1ULL << 37)
+
490#define FUSE_NO_EXPORT_SUPPORT (1ULL << 38)
+
491#define FUSE_HAS_RESEND (1ULL << 39)
+
492/* Obsolete alias for FUSE_DIRECT_IO_ALLOW_MMAP */
+
493#define FUSE_DIRECT_IO_RELAX FUSE_DIRECT_IO_ALLOW_MMAP
+
494#define FUSE_ALLOW_IDMAP (1ULL << 40)
+
495#define FUSE_OVER_IO_URING (1ULL << 41)
+
496#define FUSE_REQUEST_TIMEOUT (1ULL << 42)
+
497
+
503#define CUSE_UNRESTRICTED_IOCTL (1 << 0)
+
504
+
508#define FUSE_RELEASE_FLUSH (1 << 0)
+
509#define FUSE_RELEASE_FLOCK_UNLOCK (1 << 1)
+
510
+
514#define FUSE_GETATTR_FH (1 << 0)
+
515
+
519#define FUSE_LK_FLOCK (1 << 0)
+
520
+
528#define FUSE_WRITE_CACHE (1 << 0)
+
529#define FUSE_WRITE_LOCKOWNER (1 << 1)
+
530#define FUSE_WRITE_KILL_SUIDGID (1 << 2)
+
531
+
532/* Obsolete alias; this flag implies killing suid/sgid only. */
+
533#define FUSE_WRITE_KILL_PRIV FUSE_WRITE_KILL_SUIDGID
+
534
+
538#define FUSE_READ_LOCKOWNER (1 << 1)
+
539
+
552#define FUSE_IOCTL_COMPAT (1 << 0)
+
553#define FUSE_IOCTL_UNRESTRICTED (1 << 1)
+
554#define FUSE_IOCTL_RETRY (1 << 2)
+
555#define FUSE_IOCTL_32BIT (1 << 3)
+
556#define FUSE_IOCTL_DIR (1 << 4)
+
557#define FUSE_IOCTL_COMPAT_X32 (1 << 5)
+
558
+
559#define FUSE_IOCTL_MAX_IOV 256
+
560
+
566#define FUSE_POLL_SCHEDULE_NOTIFY (1 << 0)
+
567
+
573#define FUSE_FSYNC_FDATASYNC (1 << 0)
+
574
+
581#define FUSE_ATTR_SUBMOUNT (1 << 0)
+
582#define FUSE_ATTR_DAX (1 << 1)
+
583
+
588#define FUSE_OPEN_KILL_SUIDGID (1 << 0)
+
589
+
594#define FUSE_SETXATTR_ACL_KILL_SGID (1 << 0)
+
595
+
600#define FUSE_EXPIRE_ONLY (1 << 0)
+
601
+
607enum fuse_ext_type {
+
608 /* Types 0..31 are reserved for fuse_secctx_header */
+
609 FUSE_MAX_NR_SECCTX = 31,
+
610 FUSE_EXT_GROUPS = 32,
+
611};
+
612
+
613enum fuse_opcode {
+
614 FUSE_LOOKUP = 1,
+
615 FUSE_FORGET = 2, /* no reply */
+
616 FUSE_GETATTR = 3,
+
617 FUSE_SETATTR = 4,
+
618 FUSE_READLINK = 5,
+
619 FUSE_SYMLINK = 6,
+
620 FUSE_MKNOD = 8,
+
621 FUSE_MKDIR = 9,
+
622 FUSE_UNLINK = 10,
+
623 FUSE_RMDIR = 11,
+
624 FUSE_RENAME = 12,
+
625 FUSE_LINK = 13,
+
626 FUSE_OPEN = 14,
+
627 FUSE_READ = 15,
+
628 FUSE_WRITE = 16,
+
629 FUSE_STATFS = 17,
+
630 FUSE_RELEASE = 18,
+
631 FUSE_FSYNC = 20,
+
632 FUSE_SETXATTR = 21,
+
633 FUSE_GETXATTR = 22,
+
634 FUSE_LISTXATTR = 23,
+
635 FUSE_REMOVEXATTR = 24,
+
636 FUSE_FLUSH = 25,
+
637 FUSE_INIT = 26,
+
638 FUSE_OPENDIR = 27,
+
639 FUSE_READDIR = 28,
+
640 FUSE_RELEASEDIR = 29,
+
641 FUSE_FSYNCDIR = 30,
+
642 FUSE_GETLK = 31,
+
643 FUSE_SETLK = 32,
+
644 FUSE_SETLKW = 33,
+
645 FUSE_ACCESS = 34,
+
646 FUSE_CREATE = 35,
+
647 FUSE_INTERRUPT = 36,
+
648 FUSE_BMAP = 37,
+
649 FUSE_DESTROY = 38,
+
650 FUSE_IOCTL = 39,
+
651 FUSE_POLL = 40,
+
652 FUSE_NOTIFY_REPLY = 41,
+
653 FUSE_BATCH_FORGET = 42,
+
654 FUSE_FALLOCATE = 43,
+
655 FUSE_READDIRPLUS = 44,
+
656 FUSE_RENAME2 = 45,
+
657 FUSE_LSEEK = 46,
+
658 FUSE_COPY_FILE_RANGE = 47,
+
659 FUSE_SETUPMAPPING = 48,
+
660 FUSE_REMOVEMAPPING = 49,
+
661 FUSE_SYNCFS = 50,
+
662 FUSE_TMPFILE = 51,
+
663 FUSE_STATX = 52,
+
664 FUSE_COPY_FILE_RANGE_64 = 53,
+
665
+
666 /* CUSE specific operations */
+
667 CUSE_INIT = 4096,
+
668
+
669 /* Reserved opcodes: helpful to detect structure endian-ness */
+
670 CUSE_INIT_BSWAP_RESERVED = 1048576, /* CUSE_INIT << 8 */
+
671 FUSE_INIT_BSWAP_RESERVED = 436207616, /* FUSE_INIT << 24 */
+
672};
+
673
+
674enum fuse_notify_code {
+
675 FUSE_NOTIFY_POLL = 1,
+
676 FUSE_NOTIFY_INVAL_INODE = 2,
+
677 FUSE_NOTIFY_INVAL_ENTRY = 3,
+
678 FUSE_NOTIFY_STORE = 4,
+
679 FUSE_NOTIFY_RETRIEVE = 5,
+
680 FUSE_NOTIFY_DELETE = 6,
+
681 FUSE_NOTIFY_RESEND = 7,
+
682 FUSE_NOTIFY_INC_EPOCH = 8,
+
683 FUSE_NOTIFY_CODE_MAX,
+
684};
+
685
+
686/* The read buffer is required to be at least 8k, but may be much larger */
+
687#define FUSE_MIN_READ_BUFFER 8192
+
688
+
689#define FUSE_COMPAT_ENTRY_OUT_SIZE 120
+
690
+
691struct fuse_entry_out {
+
692 uint64_t nodeid; /* Inode ID */
+
693 uint64_t generation; /* Inode generation: nodeid:gen must
+
694 be unique for the fs's lifetime */
+
695 uint64_t entry_valid; /* Cache timeout for the name */
+
696 uint64_t attr_valid; /* Cache timeout for the attributes */
+
697 uint32_t entry_valid_nsec;
+
698 uint32_t attr_valid_nsec;
+
699 struct fuse_attr attr;
+
700};
+
701
+
702struct fuse_forget_in {
+
703 uint64_t nlookup;
+
704};
+
705
+
706struct fuse_forget_one {
+
707 uint64_t nodeid;
+
708 uint64_t nlookup;
+
709};
+
710
+
711struct fuse_batch_forget_in {
+
712 uint32_t count;
+
713 uint32_t dummy;
+
714};
+
715
+
716struct fuse_getattr_in {
+
717 uint32_t getattr_flags;
+
718 uint32_t dummy;
+
719 uint64_t fh;
+
720};
+
721
+
722#define FUSE_COMPAT_ATTR_OUT_SIZE 96
+
723
+
724struct fuse_attr_out {
+
725 uint64_t attr_valid; /* Cache timeout for the attributes */
+
726 uint32_t attr_valid_nsec;
+
727 uint32_t dummy;
+
728 struct fuse_attr attr;
+
729};
+
730
+
731struct fuse_statx_in {
+
732 uint32_t getattr_flags;
+
733 uint32_t reserved;
+
734 uint64_t fh;
+
735 uint32_t sx_flags;
+
736 uint32_t sx_mask;
+
737};
+
738
+
739struct fuse_statx_out {
+
740 uint64_t attr_valid; /* Cache timeout for the attributes */
+
741 uint32_t attr_valid_nsec;
+
742 uint32_t flags;
+
743 uint64_t spare[2];
+
744 struct fuse_statx stat;
+
745};
+
746
+
747#define FUSE_COMPAT_MKNOD_IN_SIZE 8
+
748
+
749struct fuse_mknod_in {
+
750 uint32_t mode;
+
751 uint32_t rdev;
+
752 uint32_t umask;
+
753 uint32_t padding;
+
754};
+
755
+
756struct fuse_mkdir_in {
+
757 uint32_t mode;
+
758 uint32_t umask;
+
759};
+
760
+
761struct fuse_rename_in {
+
762 uint64_t newdir;
+
763};
+
764
+
765struct fuse_rename2_in {
+
766 uint64_t newdir;
+
767 uint32_t flags;
+
768 uint32_t padding;
+
769};
+
770
+
771struct fuse_link_in {
+
772 uint64_t oldnodeid;
+
773};
+
774
+
775struct fuse_setattr_in {
+
776 uint32_t valid;
+
777 uint32_t padding;
+
778 uint64_t fh;
+
779 uint64_t size;
+
780 uint64_t lock_owner;
+
781 uint64_t atime;
+
782 uint64_t mtime;
+
783 uint64_t ctime;
+
784 uint32_t atimensec;
+
785 uint32_t mtimensec;
+
786 uint32_t ctimensec;
+
787 uint32_t mode;
+
788 uint32_t unused4;
+
789 uint32_t uid;
+
790 uint32_t gid;
+
791 uint32_t unused5;
+
792};
+
793
+
794struct fuse_open_in {
+
795 uint32_t flags;
+
796 uint32_t open_flags; /* FUSE_OPEN_... */
+
797};
+
798
+
799struct fuse_create_in {
+
800 uint32_t flags;
+
801 uint32_t mode;
+
802 uint32_t umask;
+
803 uint32_t open_flags; /* FUSE_OPEN_... */
+
804};
+
805
+
806struct fuse_open_out {
+
807 uint64_t fh;
+
808 uint32_t open_flags;
+
809 int32_t backing_id;
+
810};
+
811
+
812struct fuse_release_in {
+
813 uint64_t fh;
+
814 uint32_t flags;
+
815 uint32_t release_flags;
+
816 uint64_t lock_owner;
+
817};
+
818
+
819struct fuse_flush_in {
+
820 uint64_t fh;
+
821 uint32_t unused;
+
822 uint32_t padding;
+
823 uint64_t lock_owner;
+
824};
+
825
+
826struct fuse_read_in {
+
827 uint64_t fh;
+
828 uint64_t offset;
+
829 uint32_t size;
+
830 uint32_t read_flags;
+
831 uint64_t lock_owner;
+
832 uint32_t flags;
+
833 uint32_t padding;
+
834};
+
835
+
836#define FUSE_COMPAT_WRITE_IN_SIZE 24
+
837
+
838struct fuse_write_in {
+
839 uint64_t fh;
+
840 uint64_t offset;
+
841 uint32_t size;
+
842 uint32_t write_flags;
+
843 uint64_t lock_owner;
+
844 uint32_t flags;
+
845 uint32_t padding;
+
846};
+
847
+
848struct fuse_write_out {
+
849 uint32_t size;
+
850 uint32_t padding;
+
851};
+
852
+
853#define FUSE_COMPAT_STATFS_SIZE 48
+
854
+
855struct fuse_statfs_out {
+
856 struct fuse_kstatfs st;
+
857};
+
858
+
859struct fuse_fsync_in {
+
860 uint64_t fh;
+
861 uint32_t fsync_flags;
+
862 uint32_t padding;
+
863};
+
864
+
865#define FUSE_COMPAT_SETXATTR_IN_SIZE 8
+
866
+
867struct fuse_setxattr_in {
+
868 uint32_t size;
+
869 uint32_t flags;
+
870 uint32_t setxattr_flags;
+
871 uint32_t padding;
+
872};
+
873
+
874struct fuse_getxattr_in {
+
875 uint32_t size;
+
876 uint32_t padding;
+
877};
+
878
+
879struct fuse_getxattr_out {
+
880 uint32_t size;
+
881 uint32_t padding;
+
882};
+
883
+
884struct fuse_lk_in {
+
885 uint64_t fh;
+
886 uint64_t owner;
+
887 struct fuse_file_lock lk;
+
888 uint32_t lk_flags;
+
889 uint32_t padding;
+
890};
+
891
+
892struct fuse_lk_out {
+
893 struct fuse_file_lock lk;
+
894};
+
895
+
896struct fuse_access_in {
+
897 uint32_t mask;
+
898 uint32_t padding;
+
899};
+
900
+
901struct fuse_init_in {
+
902 uint32_t major;
+
903 uint32_t minor;
+
904 uint32_t max_readahead;
+
905 uint32_t flags;
+
906 uint32_t flags2;
+
907 uint32_t unused[11];
+
908};
+
909
+
910#define FUSE_COMPAT_INIT_OUT_SIZE 8
+
911#define FUSE_COMPAT_22_INIT_OUT_SIZE 24
+
912
+
913struct fuse_init_out {
+
914 uint32_t major;
+
915 uint32_t minor;
+
916 uint32_t max_readahead;
+
917 uint32_t flags;
+
918 uint16_t max_background;
+
919 uint16_t congestion_threshold;
+
920 uint32_t max_write;
+
921 uint32_t time_gran;
+
922 uint16_t max_pages;
+
923 uint16_t map_alignment;
+
924 uint32_t flags2;
+
925 uint32_t max_stack_depth;
+
926 uint16_t request_timeout;
+
927 uint16_t unused[11];
+
928};
+
929
+
930#define CUSE_INIT_INFO_MAX 4096
+
931
+
932struct cuse_init_in {
+
933 uint32_t major;
+
934 uint32_t minor;
+
935 uint32_t unused;
+
936 uint32_t flags;
+
937};
+
938
+
939struct cuse_init_out {
+
940 uint32_t major;
+
941 uint32_t minor;
+
942 uint32_t unused;
+
943 uint32_t flags;
+
944 uint32_t max_read;
+
945 uint32_t max_write;
+
946 uint32_t dev_major; /* chardev major */
+
947 uint32_t dev_minor; /* chardev minor */
+
948 uint32_t spare[10];
+
949};
+
950
+
951struct fuse_interrupt_in {
+
952 uint64_t unique;
+
953};
+
954
+
955struct fuse_bmap_in {
+
956 uint64_t block;
+
957 uint32_t blocksize;
+
958 uint32_t padding;
+
959};
+
960
+
961struct fuse_bmap_out {
+
962 uint64_t block;
+
963};
+
964
+
965struct fuse_ioctl_in {
+
966 uint64_t fh;
+
967 uint32_t flags;
+
968 uint32_t cmd;
+
969 uint64_t arg;
+
970 uint32_t in_size;
+
971 uint32_t out_size;
+
972};
+
973
+
974struct fuse_ioctl_iovec {
+
975 uint64_t base;
+
976 uint64_t len;
+
977};
+
978
+
979struct fuse_ioctl_out {
+
980 int32_t result;
+
981 uint32_t flags;
+
982 uint32_t in_iovs;
+
983 uint32_t out_iovs;
+
984};
+
985
+
986struct fuse_poll_in {
+
987 uint64_t fh;
+
988 uint64_t kh;
+
989 uint32_t flags;
+
990 uint32_t events;
+
991};
+
992
+
993struct fuse_poll_out {
+
994 uint32_t revents;
+
995 uint32_t padding;
+
996};
+
997
+
998struct fuse_notify_poll_wakeup_out {
+
999 uint64_t kh;
+
1000};
+
1001
+
1002struct fuse_fallocate_in {
+
1003 uint64_t fh;
+
1004 uint64_t offset;
+
1005 uint64_t length;
+
1006 uint32_t mode;
+
1007 uint32_t padding;
+
1008};
+
1009
+
1016#define FUSE_UNIQUE_RESEND (1ULL << 63)
+
1017
+
1031#define FUSE_INVALID_UIDGID ((uint32_t)(-1))
+
1032
+
1033struct fuse_in_header {
+
1034 uint32_t len;
+
1035 uint32_t opcode;
+
1036 uint64_t unique;
+
1037 uint64_t nodeid;
+
1038 uint32_t uid;
+
1039 uint32_t gid;
+
1040 uint32_t pid;
+
1041 uint16_t total_extlen; /* length of extensions in 8byte units */
+
1042 uint16_t padding;
+
1043};
+
1044
+
1045struct fuse_out_header {
+
1046 uint32_t len;
+
1047 int32_t error;
+
1048 uint64_t unique;
+
1049};
+
1050
+
1051struct fuse_dirent {
+
1052 uint64_t ino;
+
1053 uint64_t off;
+
1054 uint32_t namelen;
+
1055 uint32_t type;
+
1056 char name[];
+
1057};
+
1058
+
1059/* Align variable length records to 64bit boundary */
+
1060#define FUSE_REC_ALIGN(x) \
+
1061 (((x) + sizeof(uint64_t) - 1) & ~(sizeof(uint64_t) - 1))
+
1062
+
1063#define FUSE_NAME_OFFSET offsetof(struct fuse_dirent, name)
+
1064#define FUSE_DIRENT_ALIGN(x) FUSE_REC_ALIGN(x)
+
1065#define FUSE_DIRENT_SIZE(d) \
+
1066 FUSE_DIRENT_ALIGN(FUSE_NAME_OFFSET + (d)->namelen)
+
1067
+
1068struct fuse_direntplus {
+
1069 struct fuse_entry_out entry_out;
+
1070 struct fuse_dirent dirent;
+
1071};
+
1072
+
1073#define FUSE_NAME_OFFSET_DIRENTPLUS \
+
1074 offsetof(struct fuse_direntplus, dirent.name)
+
1075#define FUSE_DIRENTPLUS_SIZE(d) \
+
1076 FUSE_DIRENT_ALIGN(FUSE_NAME_OFFSET_DIRENTPLUS + (d)->dirent.namelen)
+
1077
+
1078struct fuse_notify_inval_inode_out {
+
1079 uint64_t ino;
+
1080 int64_t off;
+
1081 int64_t len;
+
1082};
+
1083
+
1084struct fuse_notify_inval_entry_out {
+
1085 uint64_t parent;
+
1086 uint32_t namelen;
+
1087 uint32_t flags;
+
1088};
+
1089
+
1090struct fuse_notify_delete_out {
+
1091 uint64_t parent;
+
1092 uint64_t child;
+
1093 uint32_t namelen;
+
1094 uint32_t padding;
+
1095};
+
1096
+
1097struct fuse_notify_store_out {
+
1098 uint64_t nodeid;
+
1099 uint64_t offset;
+
1100 uint32_t size;
+
1101 uint32_t padding;
+
1102};
+
1103
+
1104struct fuse_notify_retrieve_out {
+
1105 uint64_t notify_unique;
+
1106 uint64_t nodeid;
+
1107 uint64_t offset;
+
1108 uint32_t size;
+
1109 uint32_t padding;
+
1110};
+
1111
+
1112/* Matches the size of fuse_write_in */
+
1113struct fuse_notify_retrieve_in {
+
1114 uint64_t dummy1;
+
1115 uint64_t offset;
+
1116 uint32_t size;
+
1117 uint32_t dummy2;
+
1118 uint64_t dummy3;
+
1119 uint64_t dummy4;
+
1120};
+
1121
+
1122struct fuse_backing_map {
+
1123 int32_t fd;
+
1124 uint32_t flags;
+
1125 uint64_t padding;
+
1126};
+
1127
+
1128/* Device ioctls: */
+
1129#define FUSE_DEV_IOC_MAGIC 229
+
1130#define FUSE_DEV_IOC_CLONE _IOR(FUSE_DEV_IOC_MAGIC, 0, uint32_t)
+
1131#define FUSE_DEV_IOC_BACKING_OPEN _IOW(FUSE_DEV_IOC_MAGIC, 1, \
+
1132 struct fuse_backing_map)
+
1133#define FUSE_DEV_IOC_BACKING_CLOSE _IOW(FUSE_DEV_IOC_MAGIC, 2, uint32_t)
+
1134
+
1135struct fuse_lseek_in {
+
1136 uint64_t fh;
+
1137 uint64_t offset;
+
1138 uint32_t whence;
+
1139 uint32_t padding;
+
1140};
+
1141
+
1142struct fuse_lseek_out {
+
1143 uint64_t offset;
+
1144};
+
1145
+
1146struct fuse_copy_file_range_in {
+
1147 uint64_t fh_in;
+
1148 uint64_t off_in;
+
1149 uint64_t nodeid_out;
+
1150 uint64_t fh_out;
+
1151 uint64_t off_out;
+
1152 uint64_t len;
+
1153 uint64_t flags;
+
1154};
+
1155
+
1156/* For FUSE_COPY_FILE_RANGE_64 */
+
1157struct fuse_copy_file_range_out {
+
1158 uint64_t bytes_copied;
+
1159};
+
1160
+
1161#define FUSE_SETUPMAPPING_FLAG_WRITE (1ull << 0)
+
1162#define FUSE_SETUPMAPPING_FLAG_READ (1ull << 1)
+
1163struct fuse_setupmapping_in {
+
1164 /* An already open handle */
+
1165 uint64_t fh;
+
1166 /* Offset into the file to start the mapping */
+
1167 uint64_t foffset;
+
1168 /* Length of mapping required */
+
1169 uint64_t len;
+
1170 /* Flags, FUSE_SETUPMAPPING_FLAG_* */
+
1171 uint64_t flags;
+
1172 /* Offset in Memory Window */
+
1173 uint64_t moffset;
+
1174};
+
1175
+
1176struct fuse_removemapping_in {
+
1177 /* number of fuse_removemapping_one follows */
+
1178 uint32_t count;
+
1179};
+
1180
+
1181struct fuse_removemapping_one {
+
1182 /* Offset into the dax window start the unmapping */
+
1183 uint64_t moffset;
+
1184 /* Length of mapping required */
+
1185 uint64_t len;
+
1186};
+
1187
+
1188#define FUSE_REMOVEMAPPING_MAX_ENTRY \
+
1189 (PAGE_SIZE / sizeof(struct fuse_removemapping_one))
+
1190
+
1191struct fuse_syncfs_in {
+
1192 uint64_t padding;
+
1193};
+
1194
+
1195/*
+
1196 * For each security context, send fuse_secctx with size of security context
+
1197 * fuse_secctx will be followed by security context name and this in turn
+
1198 * will be followed by actual context label.
+
1199 * fuse_secctx, name, context
+
1200 */
+
1201struct fuse_secctx {
+
1202 uint32_t size;
+
1203 uint32_t padding;
+
1204};
+
1205
+
1206/*
+
1207 * Contains the information about how many fuse_secctx structures are being
+
1208 * sent and what's the total size of all security contexts (including
+
1209 * size of fuse_secctx_header).
+
1210 *
+
1211 */
+
1212struct fuse_secctx_header {
+
1213 uint32_t size;
+
1214 uint32_t nr_secctx;
+
1215};
+
1216
+
+ +
1226 uint32_t size;
+
1227 uint32_t type;
+
1228};
+
+
1229
+
+ +
1236 uint32_t nr_groups;
+
1237 uint32_t groups[];
+
1238};
+
+
1239
+
1243#define FUSE_URING_IN_OUT_HEADER_SZ 128
+
1244#define FUSE_URING_OP_IN_OUT_SZ 128
+
1245
+
1246/* Used as part of the fuse_uring_req_header */
+
1247struct fuse_uring_ent_in_out {
+
1248 uint64_t flags;
+
1249
+
1250 /*
+
1251 * commit ID to be used in a reply to a ring request (see also
+
1252 * struct fuse_uring_cmd_req)
+
1253 */
+
1254 uint64_t commit_id;
+
1255
+
1256 /* size of user payload buffer */
+
1257 uint32_t payload_sz;
+
1258 uint32_t padding;
+
1259
+
1260 uint64_t reserved;
+
1261};
+
1262
+
+ +
1267 /* struct fuse_in_header / struct fuse_out_header */
+
1268 char in_out[FUSE_URING_IN_OUT_HEADER_SZ];
+
1269
+
1270 /* per op code header */
+
1271 char op_in[FUSE_URING_OP_IN_OUT_SZ];
+
1272
+
1273 struct fuse_uring_ent_in_out ring_ent_in_out;
+
1274};
+
+
1275
+
1279enum fuse_uring_cmd {
+
1280 FUSE_IO_URING_CMD_INVALID = 0,
+
1281
+
1282 /* register the request buffer and fetch a fuse request */
+
1283 FUSE_IO_URING_CMD_REGISTER = 1,
+
1284
+
1285 /* commit fuse request result and fetch next request */
+
1286 FUSE_IO_URING_CMD_COMMIT_AND_FETCH = 2,
+
1287};
+
1288
+
+ +
1293 uint64_t flags;
+
1294
+
1295 /* entry identifier for commits */
+
1296 uint64_t commit_id;
+
1297
+
1298 /* queue the command is for (queue index) */
+
1299 uint16_t qid;
+
1300 uint8_t padding[6];
+
1301};
+
+
1302
+
1303#endif /* _LINUX_FUSE_H */
+ + + + +
+ + +
+ + diff --git a/doc/html/fuse__log_8c_source.html b/doc/html/fuse__log_8c_source.html new file mode 100644 index 0000000..f51942f --- /dev/null +++ b/doc/html/fuse__log_8c_source.html @@ -0,0 +1,131 @@ + + + + + + + +libfuse: lib/fuse_log.c Source File + + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
+
fuse_log.c
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2019 Red Hat, Inc.
+
4
+
5 Logging API.
+
6
+
7 This program can be distributed under the terms of the GNU LGPLv2.
+
8 See the file LGPL2.txt
+
9*/
+
10
+
11#include "fuse_log.h"
+
12
+
13#include <stdio.h>
+
14#include <stdbool.h>
+
15#include <syslog.h>
+
16#include <stdarg.h>
+
17
+
18#define MAX_SYSLOG_LINE_LEN 512
+
19
+
20static bool to_syslog = false;
+
21
+
22static void default_log_func(enum fuse_log_level level, const char *fmt, va_list ap)
+
23{
+
24 if (to_syslog)
+
25 vsyslog(level, fmt, ap);
+
26 else
+
27 vfprintf(stderr, fmt, ap);
+
28}
+
29
+
30static fuse_log_func_t log_func = default_log_func;
+
31
+
+ +
33{
+
34 if (!func)
+
35 func = default_log_func;
+
36
+
37 log_func = func;
+
38}
+
+
39
+
40void fuse_log(enum fuse_log_level level, const char *fmt, ...)
+
41{
+
42 va_list ap;
+
43
+
44 va_start(ap, fmt);
+
45 log_func(level, fmt, ap);
+
46 va_end(ap);
+
47}
+
48
+
+
49void fuse_log_enable_syslog(const char *ident, int option, int facility)
+
50{
+
51 to_syslog = true;
+
52
+
53 openlog(ident, option, facility);
+
54}
+
+
55
+
+ +
57{
+
58 closelog();
+
59}
+
+ +
void fuse_log_close_syslog(void)
Definition fuse_log.c:56
+
void fuse_log(enum fuse_log_level level, const char *fmt,...) __attribute__((format(printf
+
void(* fuse_log_func_t)(enum fuse_log_level level, const char *fmt, va_list ap)
Definition fuse_log.h:52
+
void void fuse_log_enable_syslog(const char *ident, int option, int facility)
Definition fuse_log.c:49
+
fuse_log_level
Definition fuse_log.h:28
+
void fuse_set_log_func(fuse_log_func_t func)
Definition fuse_log.c:32
+
+ + +
+ + diff --git a/doc/html/fuse__log_8h.html b/doc/html/fuse__log_8h.html new file mode 100644 index 0000000..e18c5e4 --- /dev/null +++ b/doc/html/fuse__log_8h.html @@ -0,0 +1,246 @@ + + + + + + + +libfuse: include/fuse_log.h File Reference + + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
+ +
fuse_log.h File Reference
+
+
+
#include <stdarg.h>
+
+

Go to the source code of this file.

+ + + +

+Typedefs

typedef void(* fuse_log_func_t) (enum fuse_log_level level, const char *fmt, va_list ap)
+ + +

+Enumerations

enum  fuse_log_level
+ + + + + +

+Functions

void fuse_set_log_func (fuse_log_func_t func)
void fuse_log (enum fuse_log_level level, const char *fmt,...) __attribute__((format(printf
void void fuse_log_enable_syslog (const char *ident, int option, int facility)
void fuse_log_close_syslog (void)
+

Detailed Description

+

This file defines the logging interface of FUSE

+ +

Definition in file fuse_log.h.

+

Typedef Documentation

+ +

◆ fuse_log_func_t

+ +
+
+ + + + +
typedef void(* fuse_log_func_t) (enum fuse_log_level level, const char *fmt, va_list ap)
+
+

Log message handler function.

+

This function must be thread-safe. It may be called from any libfuse function, including fuse_parse_cmdline() and other functions invoked before a FUSE filesystem is created.

+

Install a custom log message handler function using fuse_set_log_func().

+
Parameters
+ + + + +
levellog severity level
fmtsprintf-style format string including newline
apformat string arguments
+
+
+ +

Definition at line 52 of file fuse_log.h.

+ +
+
+

Enumeration Type Documentation

+ +

◆ fuse_log_level

+ +
+
+ + + + +
enum fuse_log_level
+
+

Log severity level

+

These levels correspond to syslog(2) log levels since they are widely used.

+ +

Definition at line 28 of file fuse_log.h.

+ +
+
+

Function Documentation

+ +

◆ fuse_log()

+ +
+
+ + + + + + + + + + + + + + + + +
void fuse_log (enum fuse_log_level level,
const char * fmt,
... )
+
+

Emit a log message

+
Parameters
+ + + +
levelseverity level (FUSE_LOG_ERR, FUSE_LOG_DEBUG, etc)
fmtsprintf-style format string including newline
+
+
+ +
+
+ +

◆ fuse_log_close_syslog()

+ +
+
+ + + + + + + +
void fuse_log_close_syslog (void )
+
+

To be called at teardown to close syslog.

+ +

Definition at line 56 of file fuse_log.c.

+ +
+
+ +

◆ fuse_log_enable_syslog()

+ +
+
+ + + + + + + + + + + + + + + + +
void void fuse_log_enable_syslog (const char * ident,
int option,
int facility )
+
+

Switch default log handler from stderr to syslog

+

Passed options are according to 'man 3 openlog'

+ +

Definition at line 49 of file fuse_log.c.

+ +
+
+ +

◆ fuse_set_log_func()

+ +
+
+ + + + + + + +
void fuse_set_log_func (fuse_log_func_t func)
+
+

Install a custom log handler function.

+

Log messages are emitted by libfuse functions to report errors and debug information. Messages are printed to stderr by default but this can be overridden by installing a custom log message handler function.

+

The log message handler function is global and affects all FUSE filesystems created within this process.

+
Parameters
+ + +
funca custom log message handler function or NULL to revert to the default
+
+
+ +

Definition at line 32 of file fuse_log.c.

+ +
+
+
+ + +
+ + diff --git a/doc/html/fuse__log_8h_source.html b/doc/html/fuse__log_8h_source.html new file mode 100644 index 0000000..20ec262 --- /dev/null +++ b/doc/html/fuse__log_8h_source.html @@ -0,0 +1,113 @@ + + + + + + + +libfuse: include/fuse_log.h Source File + + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
+
fuse_log.h
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2019 Red Hat, Inc.
+
4
+
5 This program can be distributed under the terms of the GNU LGPLv2.
+
6 See the file LGPL2.txt.
+
7*/
+
8
+
9#ifndef FUSE_LOG_H_
+
10#define FUSE_LOG_H_
+
11
+
16
+
17#include <stdarg.h>
+
18
+
19#ifdef __cplusplus
+
20extern "C" {
+
21#endif
+
22
+
+ +
29 FUSE_LOG_EMERG,
+
30 FUSE_LOG_ALERT,
+
31 FUSE_LOG_CRIT,
+
32 FUSE_LOG_ERR,
+
33 FUSE_LOG_WARNING,
+
34 FUSE_LOG_NOTICE,
+
35 FUSE_LOG_INFO,
+
36 FUSE_LOG_DEBUG
+
37};
+
+
38
+
52typedef void (*fuse_log_func_t)(enum fuse_log_level level,
+
53 const char *fmt, va_list ap);
+
54
+ +
69
+
76void fuse_log(enum fuse_log_level level, const char *fmt, ...)
+
77 __attribute__((format(printf, 2, 3)));
+
78
+
84void fuse_log_enable_syslog(const char *ident, int option, int facility);
+
85
+
89void fuse_log_close_syslog(void);
+
90
+
91#ifdef __cplusplus
+
92}
+
93#endif
+
94
+
95#endif /* FUSE_LOG_H_ */
+
void fuse_log_close_syslog(void)
Definition fuse_log.c:56
+
void fuse_log(enum fuse_log_level level, const char *fmt,...) __attribute__((format(printf
+
void(* fuse_log_func_t)(enum fuse_log_level level, const char *fmt, va_list ap)
Definition fuse_log.h:52
+
void void fuse_log_enable_syslog(const char *ident, int option, int facility)
Definition fuse_log.c:49
+
fuse_log_level
Definition fuse_log.h:28
+
void fuse_set_log_func(fuse_log_func_t func)
Definition fuse_log.c:32
+
+ + +
+ + diff --git a/doc/html/fuse__loop_8c_source.html b/doc/html/fuse__loop_8c_source.html new file mode 100644 index 0000000..d3515ea --- /dev/null +++ b/doc/html/fuse__loop_8c_source.html @@ -0,0 +1,114 @@ + + + + + + + +libfuse: lib/fuse_loop.c Source File + + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
+
fuse_loop.c
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 Implementation of the single-threaded FUSE session loop.
+
6
+
7 This program can be distributed under the terms of the GNU LGPLv2.
+
8 See the file LGPL2.txt
+
9*/
+
10
+
11#include "fuse_config.h"
+
12#include "fuse_lowlevel.h"
+
13#include "fuse_i.h"
+
14#include "fuse_uring_i.h"
+
15#include <stdio.h>
+
16#include <stdlib.h>
+
17#include <errno.h>
+
18
+
+
19int fuse_session_loop(struct fuse_session *se)
+
20{
+
21 int res = 0;
+
22 struct fuse_buf fbuf = {
+
23 .mem = NULL,
+
24 };
+
25
+
26 while (!fuse_session_exited(se)) {
+
27 res = fuse_session_receive_buf_internal(se, &fbuf, NULL);
+
28
+
29 if (res == -EINTR)
+
30 continue;
+
31 if (res <= 0)
+
32 break;
+
33
+
34 fuse_session_process_buf(se, &fbuf);
+
35 }
+
36
+
37 fuse_buf_free(&fbuf);
+
38 if(res > 0)
+
39 /* No error, just the length of the most recently read
+
40 request */
+
41 res = 0;
+
42 if(se->error != 0)
+
43 res = se->error;
+
44
+
45 if (se->uring.pool)
+
46 fuse_uring_stop(se);
+
47 return res;
+
48}
+
+ +
void fuse_session_process_buf(struct fuse_session *se, const struct fuse_buf *buf)
+
int fuse_session_exited(struct fuse_session *se)
+
int fuse_session_loop(struct fuse_session *se)
Definition fuse_loop.c:19
+ +
+ + +
+ + diff --git a/doc/html/fuse__loop__mt_8c_source.html b/doc/html/fuse__loop__mt_8c_source.html new file mode 100644 index 0000000..e72e626 --- /dev/null +++ b/doc/html/fuse__loop__mt_8c_source.html @@ -0,0 +1,608 @@ + + + + + + + +libfuse: lib/fuse_loop_mt.c Source File + + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
+
fuse_loop_mt.c
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 Implementation of the multi-threaded FUSE session loop.
+
6
+
7 This program can be distributed under the terms of the GNU LGPLv2.
+
8 See the file LGPL2.txt.
+
9*/
+
10
+
11#define _GNU_SOURCE
+
12
+
13#include "fuse_config.h"
+
14#include "fuse_lowlevel.h"
+
15#include "fuse_misc.h"
+
16#include "fuse_kernel.h"
+
17#include "fuse_i.h"
+
18#include "fuse_uring_i.h"
+
19#include "util.h"
+
20
+
21#include <stdio.h>
+
22#include <stdlib.h>
+
23#include <string.h>
+
24#include <unistd.h>
+
25#include <signal.h>
+
26#include <semaphore.h>
+
27#include <errno.h>
+
28#include <sys/time.h>
+
29#include <sys/ioctl.h>
+
30#include <assert.h>
+
31#include <limits.h>
+
32
+
33/* Environment var controlling the thread stack size */
+
34#define ENVNAME_THREAD_STACK "FUSE_THREAD_STACK"
+
35
+
36#define FUSE_LOOP_MT_V2_IDENTIFIER INT_MAX - 2
+
37#define FUSE_LOOP_MT_DEF_CLONE_FD 0
+
38#define FUSE_LOOP_MT_DEF_MAX_THREADS 10
+
39#define FUSE_LOOP_MT_DEF_IDLE_THREADS -1 /* thread destruction is disabled
+
40 * by default */
+
41
+
42/* an arbitrary large value that cannot be valid */
+
43#define FUSE_LOOP_MT_MAX_THREADS (100U * 1000)
+
44
+
45struct fuse_worker {
+
46 struct fuse_worker *prev;
+
47 struct fuse_worker *next;
+
48 pthread_t thread_id;
+
49
+
50 // We need to include fuse_buf so that we can properly free
+
51 // it when a thread is terminated by pthread_cancel().
+
52 struct fuse_buf fbuf;
+
53 struct fuse_chan *ch;
+
54 struct fuse_mt *mt;
+
55};
+
56
+
57/* synchronization via se->mt_lock */
+
58struct fuse_mt {
+
59 int numworker;
+
60 int numavail;
+
61 struct fuse_session *se;
+
62 struct fuse_worker main;
+
63 int error;
+
64 int clone_fd;
+
65 int max_idle;
+
66 int max_threads;
+
67};
+
68
+
69static struct fuse_chan *fuse_chan_new(int fd)
+
70{
+
71 struct fuse_chan *ch = (struct fuse_chan *) malloc(sizeof(*ch));
+
72 if (ch == NULL) {
+
73 fuse_log(FUSE_LOG_ERR, "fuse: failed to allocate channel\n");
+
74 return NULL;
+
75 }
+
76
+
77 memset(ch, 0, sizeof(*ch));
+
78 ch->fd = fd;
+
79 ch->ctr = 1;
+
80 pthread_mutex_init(&ch->lock, NULL);
+
81
+
82 return ch;
+
83}
+
84
+
85struct fuse_chan *fuse_chan_get(struct fuse_chan *ch)
+
86{
+
87 assert(ch->ctr > 0);
+
88 pthread_mutex_lock(&ch->lock);
+
89 ch->ctr++;
+
90 pthread_mutex_unlock(&ch->lock);
+
91
+
92 return ch;
+
93}
+
94
+
95void fuse_chan_put(struct fuse_chan *ch)
+
96{
+
97 if (ch == NULL)
+
98 return;
+
99 pthread_mutex_lock(&ch->lock);
+
100 ch->ctr--;
+
101 if (!ch->ctr) {
+
102 pthread_mutex_unlock(&ch->lock);
+
103 close(ch->fd);
+
104 pthread_mutex_destroy(&ch->lock);
+
105 free(ch);
+
106 } else
+
107 pthread_mutex_unlock(&ch->lock);
+
108}
+
109
+
110static void list_add_worker(struct fuse_worker *w, struct fuse_worker *next)
+
111{
+
112 struct fuse_worker *prev = next->prev;
+
113 w->next = next;
+
114 w->prev = prev;
+
115 prev->next = w;
+
116 next->prev = w;
+
117}
+
118
+
119static void list_del_worker(struct fuse_worker *w)
+
120{
+
121 struct fuse_worker *prev = w->prev;
+
122 struct fuse_worker *next = w->next;
+
123 prev->next = next;
+
124 next->prev = prev;
+
125}
+
126
+
127static int fuse_loop_start_thread(struct fuse_mt *mt);
+
128
+
129static void *fuse_do_work(void *data)
+
130{
+
131 struct fuse_worker *w = (struct fuse_worker *) data;
+
132 struct fuse_mt *mt = w->mt;
+
133 struct fuse_session *se = mt->se;
+
134
+
135 fuse_set_thread_name("fuse_worker");
+
136
+
137 while (!fuse_session_exited(se)) {
+
138 int isforget = 0;
+
139 int res;
+
140
+
141 pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
+
142 res = fuse_session_receive_buf_internal(se, &w->fbuf, w->ch);
+
143 pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
+
144 if (res == -EINTR)
+
145 continue;
+
146 if (res <= 0) {
+
147 if (res < 0) {
+ +
149 mt->error = res;
+
150 }
+
151 break;
+
152 }
+
153
+
154 pthread_mutex_lock(&se->mt_lock);
+
155 if (fuse_session_exited(se)) {
+
156 pthread_mutex_unlock(&se->mt_lock);
+
157 return NULL;
+
158 }
+
159
+
160 /*
+
161 * This disgusting hack is needed so that zillions of threads
+
162 * are not created on a burst of FORGET messages
+
163 */
+
164 if (!(w->fbuf.flags & FUSE_BUF_IS_FD)) {
+
165 struct fuse_in_header *in = w->fbuf.mem;
+
166
+
167 if (in->opcode == FUSE_FORGET ||
+
168 in->opcode == FUSE_BATCH_FORGET)
+
169 isforget = 1;
+
170 }
+
171
+
172 if (!isforget)
+
173 mt->numavail--;
+
174 if (mt->numavail == 0 && mt->numworker < mt->max_threads &&
+
175 likely(se->got_init))
+
176 fuse_loop_start_thread(mt);
+
177 pthread_mutex_unlock(&se->mt_lock);
+
178
+
179 fuse_session_process_buf_internal(se, &w->fbuf, w->ch);
+
180
+
181 pthread_mutex_lock(&se->mt_lock);
+
182 if (!isforget)
+
183 mt->numavail++;
+
184
+
185 /* creating and destroying threads is rather expensive - and there is
+
186 * not much gain from destroying existing threads. It is therefore
+
187 * discouraged to set max_idle to anything else than -1. If there
+
188 * is indeed a good reason to destruct threads it should be done
+
189 * delayed, a moving average might be useful for that.
+
190 */
+
191 if (mt->max_idle != -1 && mt->numavail > mt->max_idle && mt->numworker > 1) {
+
192 if (fuse_session_exited(se)) {
+
193 pthread_mutex_unlock(&se->mt_lock);
+
194 return NULL;
+
195 }
+
196 list_del_worker(w);
+
197 mt->numavail--;
+
198 mt->numworker--;
+
199 pthread_mutex_unlock(&se->mt_lock);
+
200
+
201 pthread_detach(w->thread_id);
+
202 fuse_buf_free(&w->fbuf);
+
203 fuse_chan_put(w->ch);
+
204 free(w);
+
205 return NULL;
+
206 }
+
207 pthread_mutex_unlock(&se->mt_lock);
+
208 }
+
209
+
210 sem_post(&se->mt_finish);
+
211 return NULL;
+
212}
+
213
+
214int fuse_start_thread(pthread_t *thread_id, void *(*func)(void *), void *arg)
+
215{
+
216 sigset_t oldset;
+
217 sigset_t newset;
+
218 int res;
+
219 pthread_attr_t attr;
+
220 char *stack_size;
+
221
+
222 /* Override default stack size
+
223 * XXX: This should ideally be a parameter option. It is rather
+
224 * well hidden here.
+
225 */
+
226 pthread_attr_init(&attr);
+
227 stack_size = getenv(ENVNAME_THREAD_STACK);
+
228 if (stack_size) {
+
229 long size;
+
230
+
231 res = libfuse_strtol(stack_size, &size);
+
232 if (res)
+
233 fuse_log(FUSE_LOG_ERR, "fuse: invalid stack size: %s\n",
+
234 stack_size);
+
235 else if (pthread_attr_setstacksize(&attr, size))
+
236 fuse_log(FUSE_LOG_ERR, "fuse: could not set stack size: %ld\n",
+
237 size);
+
238 }
+
239
+
240 /* Disallow signal reception in worker threads */
+
241 sigemptyset(&newset);
+
242 sigaddset(&newset, SIGTERM);
+
243 sigaddset(&newset, SIGINT);
+
244 sigaddset(&newset, SIGHUP);
+
245 sigaddset(&newset, SIGQUIT);
+
246 pthread_sigmask(SIG_BLOCK, &newset, &oldset);
+
247 res = pthread_create(thread_id, &attr, func, arg);
+
248 pthread_sigmask(SIG_SETMASK, &oldset, NULL);
+
249 pthread_attr_destroy(&attr);
+
250 if (res != 0) {
+
251 fuse_log(FUSE_LOG_ERR, "fuse: error creating thread: %s\n",
+
252 strerror(res));
+
253 return -1;
+
254 }
+
255
+
256 return 0;
+
257}
+
258
+
259static int fuse_clone_chan_fd_default(struct fuse_session *se)
+
260{
+
261 int res;
+
262 int clonefd;
+
263 uint32_t masterfd;
+
264 const char *devname = "/dev/fuse";
+
265
+
266#ifndef O_CLOEXEC
+
267#define O_CLOEXEC 0
+
268#endif
+
269 clonefd = open(devname, O_RDWR | O_CLOEXEC);
+
270 if (clonefd == -1) {
+
271 fuse_log(FUSE_LOG_ERR, "fuse: failed to open %s: %s\n", devname,
+
272 strerror(errno));
+
273 return -1;
+
274 }
+
275 if (!O_CLOEXEC) {
+
276 res = fcntl(clonefd, F_SETFD, FD_CLOEXEC);
+
277 if (res == -1) {
+
278 fuse_log(FUSE_LOG_ERR, "fuse: failed to set CLOEXEC: %s\n",
+
279 strerror(errno));
+
280 close(clonefd);
+
281 return -1;
+
282 }
+
283 }
+
284
+
285 masterfd = se->fd;
+
286 res = ioctl(clonefd, FUSE_DEV_IOC_CLONE, &masterfd);
+
287 if (res == -1) {
+
288 fuse_log(FUSE_LOG_ERR, "fuse: failed to clone device fd: %s\n",
+
289 strerror(errno));
+
290 close(clonefd);
+
291 return -1;
+
292 }
+
293 return clonefd;
+
294}
+
295
+
296static struct fuse_chan *fuse_clone_chan(struct fuse_mt *mt)
+
297{
+
298 int clonefd;
+
299 struct fuse_session *se = mt->se;
+
300 struct fuse_chan *newch;
+
301
+
302 if (se->io != NULL) {
+
303 if (se->io->clone_fd != NULL)
+
304 clonefd = se->io->clone_fd(se->fd);
+
305 else
+
306 return NULL;
+
307 } else {
+
308 clonefd = fuse_clone_chan_fd_default(se);
+
309 }
+
310 if (clonefd < 0)
+
311 return NULL;
+
312
+
313 newch = fuse_chan_new(clonefd);
+
314 if (newch == NULL)
+
315 close(clonefd);
+
316
+
317 return newch;
+
318}
+
319
+
320static int fuse_loop_start_thread(struct fuse_mt *mt)
+
321{
+
322 int res;
+
323
+
324 struct fuse_worker *w = malloc(sizeof(struct fuse_worker));
+
325 if (!w) {
+
326 fuse_log(FUSE_LOG_ERR, "fuse: failed to allocate worker structure\n");
+
327 return -1;
+
328 }
+
329 memset(w, 0, sizeof(struct fuse_worker));
+
330 w->fbuf.mem = NULL;
+
331 w->mt = mt;
+
332
+
333 w->ch = NULL;
+
334 if (mt->clone_fd) {
+
335 w->ch = fuse_clone_chan(mt);
+
336 if(!w->ch) {
+
337 /* Don't attempt this again */
+
338 fuse_log(FUSE_LOG_ERR, "fuse: trying to continue "
+
339 "without -o clone_fd.\n");
+
340 mt->clone_fd = 0;
+
341 }
+
342 }
+
343
+
344 res = fuse_start_thread(&w->thread_id, fuse_do_work, w);
+
345 if (res == -1) {
+
346 fuse_chan_put(w->ch);
+
347 free(w);
+
348 return -1;
+
349 }
+
350 list_add_worker(w, &mt->main);
+
351 mt->numavail ++;
+
352 mt->numworker ++;
+
353
+
354 return 0;
+
355}
+
356
+
357static void fuse_join_worker(struct fuse_mt *mt, struct fuse_worker *w)
+
358{
+
359 pthread_join(w->thread_id, NULL);
+
360 pthread_mutex_lock(&mt->se->mt_lock);
+
361 list_del_worker(w);
+
362 pthread_mutex_unlock(&mt->se->mt_lock);
+
363 fuse_buf_free(&w->fbuf);
+
364 fuse_chan_put(w->ch);
+
365 free(w);
+
366}
+
367
+
368int fuse_session_loop_mt_312(struct fuse_session *se, struct fuse_loop_config *config);
+
369FUSE_SYMVER("fuse_session_loop_mt_312", "fuse_session_loop_mt@@FUSE_3.12")
+
370int fuse_session_loop_mt_312(struct fuse_session *se, struct fuse_loop_config *config)
+
371{
+
372int err;
+
373 struct fuse_mt mt;
+
374 struct fuse_worker *w;
+
375 int created_config = 0;
+
376
+
377 if (config) {
+
378 err = fuse_loop_cfg_verify(config);
+
379 if (err)
+
380 return err;
+
381 } else {
+
382 /* The caller does not care about parameters - use the default */
+
383 config = fuse_loop_cfg_create();
+
384 if (!config)
+
385 return -ENOMEM;
+
386 created_config = 1;
+
387 }
+
388
+
389
+
390 memset(&mt, 0, sizeof(struct fuse_mt));
+
391 mt.se = se;
+
392 mt.clone_fd = config->clone_fd;
+
393 mt.error = 0;
+
394 mt.numworker = 0;
+
395 mt.numavail = 0;
+
396 mt.max_idle = config->max_idle_threads;
+
397 mt.max_threads = config->max_threads;
+
398 mt.main.thread_id = pthread_self();
+
399 mt.main.prev = mt.main.next = &mt.main;
+
400
+
401 pthread_mutex_lock(&se->mt_lock);
+
402 err = fuse_loop_start_thread(&mt);
+
403 pthread_mutex_unlock(&se->mt_lock);
+
404 if (!err) {
+
405 while (!fuse_session_exited(se))
+
406 sem_wait(&se->mt_finish);
+
407 if (se->debug)
+
408 fuse_log(FUSE_LOG_DEBUG,
+
409 "fuse: session exited, terminating workers\n");
+
410
+
411 pthread_mutex_lock(&se->mt_lock);
+
412 for (w = mt.main.next; w != &mt.main; w = w->next)
+
413 pthread_cancel(w->thread_id);
+
414 pthread_mutex_unlock(&se->mt_lock);
+
415
+
416 while (mt.main.next != &mt.main)
+
417 fuse_join_worker(&mt, mt.main.next);
+
418
+
419 err = mt.error;
+
420
+
421 if (se->uring.pool)
+
422 fuse_uring_stop(se);
+
423 }
+
424
+
425 if(se->error != 0)
+
426 err = se->error;
+
427
+
428
+
429 if (created_config) {
+
430 fuse_loop_cfg_destroy(config);
+
431 config = NULL;
+
432 }
+
433
+
434 return err;
+
435}
+
436
+
437int fuse_session_loop_mt_32(struct fuse_session *se, struct fuse_loop_config_v1 *config_v1);
+
438FUSE_SYMVER("fuse_session_loop_mt_32", "fuse_session_loop_mt@FUSE_3.2")
+
439int fuse_session_loop_mt_32(struct fuse_session *se, struct fuse_loop_config_v1 *config_v1)
+
440{
+
441 int err;
+
442 struct fuse_loop_config *config = NULL;
+
443
+
444 if (config_v1 != NULL) {
+
445 /* convert the given v1 config */
+
446 config = fuse_loop_cfg_create();
+
447 if (config == NULL)
+
448 return ENOMEM;
+
449
+
450 fuse_loop_cfg_convert(config, config_v1);
+
451 }
+
452
+
453 err = fuse_session_loop_mt_312(se, config);
+
454
+
455 fuse_loop_cfg_destroy(config);
+
456
+
457 return err;
+
458}
+
459
+
460
+
461int fuse_session_loop_mt_31(struct fuse_session *se, int clone_fd);
+
462FUSE_SYMVER("fuse_session_loop_mt_31", "fuse_session_loop_mt@FUSE_3.0")
+
463int fuse_session_loop_mt_31(struct fuse_session *se, int clone_fd)
+
464{
+
465 int err;
+
466 struct fuse_loop_config *config = fuse_loop_cfg_create();
+
467 if (!config)
+
468 return -ENOMEM;
+
469 if (clone_fd > 0)
+
470 fuse_loop_cfg_set_clone_fd(config, clone_fd);
+
471 err = fuse_session_loop_mt_312(se, config);
+
472
+
473 fuse_loop_cfg_destroy(config);
+
474
+
475 return err;
+
476}
+
477
+
478struct fuse_loop_config *fuse_loop_cfg_create(void)
+
479{
+
480 struct fuse_loop_config *config = calloc(1, sizeof(*config));
+
481 if (config == NULL)
+
482 return NULL;
+
483
+
484 config->version_id = FUSE_LOOP_MT_V2_IDENTIFIER;
+
485 config->max_idle_threads = FUSE_LOOP_MT_DEF_IDLE_THREADS;
+
486 config->max_threads = FUSE_LOOP_MT_DEF_MAX_THREADS;
+
487 config->clone_fd = FUSE_LOOP_MT_DEF_CLONE_FD;
+
488
+
489 return config;
+
490}
+
491
+
492void fuse_loop_cfg_destroy(struct fuse_loop_config *config)
+
493{
+
494 free(config);
+
495}
+
496
+
497int fuse_loop_cfg_verify(struct fuse_loop_config *config)
+
498{
+
499 if (config->version_id != FUSE_LOOP_MT_V2_IDENTIFIER)
+
500 return -EINVAL;
+
501
+
502 return 0;
+
503}
+
504
+
505void fuse_loop_cfg_convert(struct fuse_loop_config *config,
+
506 struct fuse_loop_config_v1 *v1_conf)
+
507{
+
508 fuse_loop_cfg_set_idle_threads(config, v1_conf->max_idle_threads);
+
509
+
510 fuse_loop_cfg_set_clone_fd(config, v1_conf->clone_fd);
+
511}
+
512
+
513void fuse_loop_cfg_set_idle_threads(struct fuse_loop_config *config,
+
514 unsigned int value)
+
515{
+
516 if (value > FUSE_LOOP_MT_MAX_THREADS) {
+
517 if (value != UINT_MAX)
+
518 fuse_log(FUSE_LOG_ERR,
+
519 "Ignoring invalid max threads value "
+
520 "%u > max (%u).\n", value,
+
521 FUSE_LOOP_MT_MAX_THREADS);
+
522 return;
+
523 }
+
524 config->max_idle_threads = value;
+
525}
+
526
+
527void fuse_loop_cfg_set_max_threads(struct fuse_loop_config *config,
+
528 unsigned int value)
+
529{
+
530 config->max_threads = value;
+
531}
+
532
+
533void fuse_loop_cfg_set_clone_fd(struct fuse_loop_config *config,
+
534 unsigned int value)
+
535{
+
536 config->clone_fd = value;
+
537}
+
538
+
@ FUSE_BUF_IS_FD
+
void fuse_log(enum fuse_log_level level, const char *fmt,...) __attribute__((format(printf
+ +
void fuse_session_exit(struct fuse_session *se)
+
int fuse_session_exited(struct fuse_session *se)
+
enum fuse_buf_flags flags
+
void * mem
+ + +
unsigned int max_threads
Definition fuse_i.h:190
+
unsigned int max_idle_threads
+
+ + +
+ + diff --git a/doc/html/fuse__lowlevel_8c_source.html b/doc/html/fuse__lowlevel_8c_source.html new file mode 100644 index 0000000..6154757 --- /dev/null +++ b/doc/html/fuse__lowlevel_8c_source.html @@ -0,0 +1,4857 @@ + + + + + + + +libfuse: lib/fuse_lowlevel.c Source File + + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
+
fuse_lowlevel.c
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 Implementation of (most of) the low-level FUSE API. The session loop
+
6 functions are implemented in separate files.
+
7
+
8 This program can be distributed under the terms of the GNU LGPLv2.
+
9 See the file LGPL2.txt
+
10*/
+
11
+
12#define _GNU_SOURCE
+
13
+
14#include "fuse_config.h"
+
15#include "fuse_i.h"
+
16#include "fuse_kernel.h"
+
17#include "fuse_opt.h"
+
18#include "fuse_misc.h"
+
19#include "mount_util.h"
+
20#include "util.h"
+
21#include "fuse_uring_i.h"
+
22
+
23#include <pthread.h>
+
24#include <stdatomic.h>
+
25#include <stdint.h>
+
26#include <inttypes.h>
+
27#include <stdbool.h>
+
28#include <stdio.h>
+
29#include <stdlib.h>
+
30#include <stddef.h>
+
31#include <stdalign.h>
+
32#include <string.h>
+
33#include <unistd.h>
+
34#include <limits.h>
+
35#include <errno.h>
+
36#include <assert.h>
+
37#include <sys/file.h>
+
38#include <sys/ioctl.h>
+
39#include <stdalign.h>
+
40
+
41#ifdef USDT_ENABLED
+
42#include "usdt.h"
+
43#endif
+
44
+
45#ifndef F_LINUX_SPECIFIC_BASE
+
46#define F_LINUX_SPECIFIC_BASE 1024
+
47#endif
+
48#ifndef F_SETPIPE_SZ
+
49#define F_SETPIPE_SZ (F_LINUX_SPECIFIC_BASE + 7)
+
50#endif
+
51
+
52#ifndef O_CLOEXEC
+
53#define O_CLOEXEC 0
+
54#endif
+
55
+
56#define PARAM(inarg) (((char *)(inarg)) + sizeof(*(inarg)))
+
57#define OFFSET_MAX 0x7fffffffffffffffLL
+
58
+
59struct fuse_pollhandle {
+
60 uint64_t kh;
+
61 struct fuse_session *se;
+
62};
+
63
+
64static size_t pagesize;
+
65
+
66static __attribute__((constructor)) void fuse_ll_init_pagesize(void)
+
67{
+
68 pagesize = getpagesize();
+
69}
+
70
+
71#ifdef USDT_ENABLED
+
72/* tracepoints */
+
73static void trace_request_receive(int err)
+
74{
+
75 USDT(libfuse, request_receive, err);
+
76}
+
77
+
78static void trace_request_process(unsigned int opcode, unsigned int unique)
+
79{
+
80 USDT(libfuse, request_process, opcode, unique);
+
81}
+
82
+
83static void trace_request_reply(uint64_t unique, unsigned int len,
+
84 int error, int reply_err)
+
85{
+
86 USDT(libfuse, request_reply, unique, len, error, reply_err);
+
87}
+
88#else
+
89static void trace_request_receive(int err)
+
90{
+
91 (void)err;
+
92}
+
93
+
94static void trace_request_process(unsigned int opcode, unsigned int unique)
+
95{
+
96 (void)opcode;
+
97 (void)unique;
+
98}
+
99
+
100static void trace_request_reply(uint64_t unique, unsigned int len,
+
101 int error, int reply_err)
+
102{
+
103 (void)unique;
+
104 (void)len;
+
105 (void)error;
+
106 (void)reply_err;
+
107}
+
108#endif
+
109
+
110static void convert_stat(const struct stat *stbuf, struct fuse_attr *attr)
+
111{
+
112 attr->ino = stbuf->st_ino;
+
113 attr->mode = stbuf->st_mode;
+
114 attr->nlink = stbuf->st_nlink;
+
115 attr->uid = stbuf->st_uid;
+
116 attr->gid = stbuf->st_gid;
+
117 attr->rdev = stbuf->st_rdev;
+
118 attr->size = stbuf->st_size;
+
119 attr->blksize = stbuf->st_blksize;
+
120 attr->blocks = stbuf->st_blocks;
+
121 attr->atime = stbuf->st_atime;
+
122 attr->mtime = stbuf->st_mtime;
+
123 attr->ctime = stbuf->st_ctime;
+
124 attr->atimensec = ST_ATIM_NSEC(stbuf);
+
125 attr->mtimensec = ST_MTIM_NSEC(stbuf);
+
126 attr->ctimensec = ST_CTIM_NSEC(stbuf);
+
127}
+
128
+
129static void convert_attr(const struct fuse_setattr_in *attr, struct stat *stbuf)
+
130{
+
131 stbuf->st_mode = attr->mode;
+
132 stbuf->st_uid = attr->uid;
+
133 stbuf->st_gid = attr->gid;
+
134 stbuf->st_size = attr->size;
+
135 stbuf->st_atime = attr->atime;
+
136 stbuf->st_mtime = attr->mtime;
+
137 stbuf->st_ctime = attr->ctime;
+
138 ST_ATIM_NSEC_SET(stbuf, attr->atimensec);
+
139 ST_MTIM_NSEC_SET(stbuf, attr->mtimensec);
+
140 ST_CTIM_NSEC_SET(stbuf, attr->ctimensec);
+
141}
+
142
+
143static size_t iov_length(const struct iovec *iov, size_t count)
+
144{
+
145 size_t seg;
+
146 size_t ret = 0;
+
147
+
148 for (seg = 0; seg < count; seg++)
+
149 ret += iov[seg].iov_len;
+
150 return ret;
+
151}
+
152
+
153void list_init_req(struct fuse_req *req)
+
154{
+
155 req->next = req;
+
156 req->prev = req;
+
157}
+
158
+
159static void list_del_req(struct fuse_req *req)
+
160{
+
161 struct fuse_req *prev = req->prev;
+
162 struct fuse_req *next = req->next;
+
163 prev->next = next;
+
164 next->prev = prev;
+
165}
+
166
+
167static void list_add_req(struct fuse_req *req, struct fuse_req *next)
+
168{
+
169 struct fuse_req *prev = next->prev;
+
170 req->next = next;
+
171 req->prev = prev;
+
172 prev->next = req;
+
173 next->prev = req;
+
174}
+
175
+
176static void destroy_req(fuse_req_t req)
+
177{
+
178 if (req->flags.is_uring) {
+
179 fuse_log(FUSE_LOG_ERR, "Refusing to destruct uring req\n");
+
180 return;
+
181 }
+
182 assert(req->ch == NULL);
+
183 pthread_mutex_destroy(&req->lock);
+
184 free(req);
+
185}
+
186
+
187void fuse_free_req(fuse_req_t req)
+
188{
+
189 int ctr;
+
190 struct fuse_session *se = req->se;
+
191
+
192 /* XXX: for now no support for interrupts with io-uring
+
193 * It actually might work already, though. But then would add
+
194 * a lock across ring queues.
+
195 */
+
196 if (se->conn.no_interrupt || req->flags.is_uring) {
+
197 ctr = --req->ref_cnt;
+
198 fuse_chan_put(req->ch);
+
199 req->ch = NULL;
+
200 } else {
+
201 pthread_mutex_lock(&se->lock);
+
202 req->u.ni.func = NULL;
+
203 req->u.ni.data = NULL;
+
204 list_del_req(req);
+
205 ctr = --req->ref_cnt;
+
206 fuse_chan_put(req->ch);
+
207 req->ch = NULL;
+
208 pthread_mutex_unlock(&se->lock);
+
209 }
+
210 if (!ctr)
+
211 destroy_req(req);
+
212}
+
213
+
214static struct fuse_req *fuse_ll_alloc_req(struct fuse_session *se)
+
215{
+
216 struct fuse_req *req;
+
217
+
218 req = (struct fuse_req *) calloc(1, sizeof(struct fuse_req));
+
219 if (req == NULL) {
+
220 fuse_log(FUSE_LOG_ERR, "fuse: failed to allocate request\n");
+
221 } else {
+
222 req->se = se;
+
223 req->ref_cnt = 1;
+
224 list_init_req(req);
+
225 pthread_mutex_init(&req->lock, NULL);
+
226 }
+
227
+
228 return req;
+
229}
+
230
+
231/*
+
232 * Send data to fuse-kernel using an fd of the fuse device.
+
233 */
+
234static int fuse_write_msg_dev(struct fuse_session *se, struct fuse_chan *ch,
+
235 struct iovec *iov, int count)
+
236{
+
237 ssize_t res;
+
238 int err;
+
239
+
240 if (se->io != NULL)
+
241
+
242 /* se->io->writev is never NULL if se->io is not NULL as
+
243 * specified by fuse_session_custom_io()
+
244 */
+
245 res = se->io->writev(ch ? ch->fd : se->fd, iov, count,
+
246 se->userdata);
+
247 else
+
248 res = writev(ch ? ch->fd : se->fd, iov, count);
+
249
+
250 if (res == -1) {
+
251 /* ENOENT means the operation was interrupted */
+
252 err = errno;
+
253 if (!fuse_session_exited(se) && err != ENOENT)
+
254 perror("fuse: writing device");
+
255 return -err;
+
256 }
+
257
+
258 return 0;
+
259}
+
260
+
261static int fuse_send_msg(struct fuse_session *se, struct fuse_chan *ch,
+
262 struct iovec *iov, int count, fuse_req_t req)
+
263{
+
264 struct fuse_out_header *out = iov[0].iov_base;
+
265 int err;
+
266 bool is_uring = req && req->flags.is_uring ? true : false;
+
267
+
268 if (!is_uring)
+
269 assert(se != NULL);
+
270 out->len = iov_length(iov, count);
+
271
+
272 if (se->debug) {
+
273 if (out->unique == 0) {
+
274 fuse_log(FUSE_LOG_DEBUG, "NOTIFY: code=%d length=%u\n",
+
275 out->error, out->len);
+
276 } else if (out->error) {
+
277 fuse_log(FUSE_LOG_DEBUG,
+
278 " unique: %llu, error: %i (%s), outsize: %i\n",
+
279 (unsigned long long) out->unique, out->error,
+
280 strerror(-out->error), out->len);
+
281 } else {
+
282 fuse_log(FUSE_LOG_DEBUG,
+
283 " unique: %llu, success, outsize: %i\n",
+
284 (unsigned long long) out->unique, out->len);
+
285 }
+
286 }
+
287
+
288 if (is_uring)
+
289 err = fuse_send_msg_uring(req, iov, count);
+
290 else
+
291 err = fuse_write_msg_dev(se, ch, iov, count);
+
292
+
293 trace_request_reply(out->unique, out->len, out->error, err);
+
294 return err;
+
295}
+
296
+
297int fuse_send_reply_iov_nofree(fuse_req_t req, int error, struct iovec *iov,
+
298 int count)
+
299{
+
300 struct fuse_out_header out;
+
301
+
302#if __GLIBC__ >= 2 && __GLIBC_MINOR__ >= 32
+
303 const char *str = strerrordesc_np(error * -1);
+
304 if ((str == NULL && error != 0) || error > 0) {
+
305#else
+
306 if (error <= -1000 || error > 0) {
+
307#endif
+
308 fuse_log(FUSE_LOG_ERR, "fuse: bad error value: %i\n", error);
+
309 error = -ERANGE;
+
310 }
+
311
+
312 out.unique = req->unique;
+
313 out.error = error;
+
314
+
315 iov[0].iov_base = &out;
+
316 iov[0].iov_len = sizeof(struct fuse_out_header);
+
317
+
318 return fuse_send_msg(req->se, req->ch, iov, count, req);
+
319}
+
320
+
321static int send_reply_iov(fuse_req_t req, int error, struct iovec *iov,
+
322 int count)
+
323{
+
324 int res;
+
325
+
326 res = fuse_send_reply_iov_nofree(req, error, iov, count);
+
327 fuse_free_req(req);
+
328 return res;
+
329}
+
330
+
331static int send_reply(fuse_req_t req, int error, const void *arg,
+
332 size_t argsize)
+
333{
+
334 if (req->flags.is_uring)
+
335 return send_reply_uring(req, error, arg, argsize);
+
336
+
337 struct iovec iov[2];
+
338 int count = 1;
+
339 if (argsize) {
+
340 iov[1].iov_base = (void *) arg;
+
341 iov[1].iov_len = argsize;
+
342 count++;
+
343 }
+
344 return send_reply_iov(req, error, iov, count);
+
345}
+
346
+
+
347int fuse_reply_iov(fuse_req_t req, const struct iovec *iov, int count)
+
348{
+
349 int res;
+
350 struct iovec *padded_iov;
+
351
+
352 padded_iov = malloc((count + 1) * sizeof(struct iovec));
+
353 if (padded_iov == NULL)
+
354 return fuse_reply_err(req, ENOMEM);
+
355
+
356 memcpy(padded_iov + 1, iov, count * sizeof(struct iovec));
+
357 count++;
+
358
+
359 res = send_reply_iov(req, 0, padded_iov, count);
+
360 free(padded_iov);
+
361
+
362 return res;
+
363}
+
+
364
+
365
+
366/* `buf` is allowed to be empty so that the proper size may be
+
367 allocated by the caller */
+
+
368size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize,
+
369 const char *name, const struct stat *stbuf, off_t off)
+
370{
+
371 (void)req;
+
372 size_t namelen;
+
373 size_t entlen;
+
374 size_t entlen_padded;
+
375 struct fuse_dirent *dirent;
+
376
+
377 namelen = strlen(name);
+
378 entlen = FUSE_NAME_OFFSET + namelen;
+
379 entlen_padded = FUSE_DIRENT_ALIGN(entlen);
+
380
+
381 if ((buf == NULL) || (entlen_padded > bufsize))
+
382 return entlen_padded;
+
383
+
384 dirent = (struct fuse_dirent*) buf;
+
385 dirent->ino = stbuf->st_ino;
+
386 dirent->off = off;
+
387 dirent->namelen = namelen;
+
388 dirent->type = (stbuf->st_mode & S_IFMT) >> 12;
+
389 memcpy(dirent->name, name, namelen);
+
390 memset(dirent->name + namelen, 0, entlen_padded - entlen);
+
391
+
392 return entlen_padded;
+
393}
+
+
394
+
395static void convert_statfs(const struct statvfs *stbuf,
+
396 struct fuse_kstatfs *kstatfs)
+
397{
+
398 kstatfs->bsize = stbuf->f_bsize;
+
399 kstatfs->frsize = stbuf->f_frsize;
+
400 kstatfs->blocks = stbuf->f_blocks;
+
401 kstatfs->bfree = stbuf->f_bfree;
+
402 kstatfs->bavail = stbuf->f_bavail;
+
403 kstatfs->files = stbuf->f_files;
+
404 kstatfs->ffree = stbuf->f_ffree;
+
405 kstatfs->namelen = stbuf->f_namemax;
+
406}
+
407
+
408static int send_reply_ok(fuse_req_t req, const void *arg, size_t argsize)
+
409{
+
410 return send_reply(req, 0, arg, argsize);
+
411}
+
412
+
+
413int fuse_reply_err(fuse_req_t req, int err)
+
414{
+
415 return send_reply(req, -err, NULL, 0);
+
416}
+
+
417
+
+ +
419{
+
420 if (req->flags.is_uring) {
+
421 send_reply_uring(req, 0, NULL, 0);
+
422 return;
+
423 }
+
424 fuse_free_req(req);
+
425}
+
+
426
+
427static unsigned long calc_timeout_sec(double t)
+
428{
+
429 if (t > (double) ULONG_MAX)
+
430 return ULONG_MAX;
+
431 else if (t < 0.0)
+
432 return 0;
+
433 else
+
434 return (unsigned long) t;
+
435}
+
436
+
437static unsigned int calc_timeout_nsec(double t)
+
438{
+
439 double f = t - (double) calc_timeout_sec(t);
+
440 if (f < 0.0)
+
441 return 0;
+
442 else if (f >= 0.999999999)
+
443 return 999999999;
+
444 else
+
445 return (unsigned int) (f * 1.0e9);
+
446}
+
447
+
448static void fill_entry(struct fuse_entry_out *arg,
+
449 const struct fuse_entry_param *e)
+
450{
+
451 arg->nodeid = e->ino;
+
452 arg->generation = e->generation;
+
453 arg->entry_valid = calc_timeout_sec(e->entry_timeout);
+
454 arg->entry_valid_nsec = calc_timeout_nsec(e->entry_timeout);
+
455 arg->attr_valid = calc_timeout_sec(e->attr_timeout);
+
456 arg->attr_valid_nsec = calc_timeout_nsec(e->attr_timeout);
+
457 convert_stat(&e->attr, &arg->attr);
+
458}
+
459
+
460/* `buf` is allowed to be empty so that the proper size may be
+
461 allocated by the caller */
+
+
462size_t fuse_add_direntry_plus(fuse_req_t req, char *buf, size_t bufsize,
+
463 const char *name,
+
464 const struct fuse_entry_param *e, off_t off)
+
465{
+
466 (void)req;
+
467 size_t namelen;
+
468 size_t entlen;
+
469 size_t entlen_padded;
+
470
+
471 namelen = strlen(name);
+
472 entlen = FUSE_NAME_OFFSET_DIRENTPLUS + namelen;
+
473 entlen_padded = FUSE_DIRENT_ALIGN(entlen);
+
474 if ((buf == NULL) || (entlen_padded > bufsize))
+
475 return entlen_padded;
+
476
+
477 struct fuse_direntplus *dp = (struct fuse_direntplus *) buf;
+
478 memset(&dp->entry_out, 0, sizeof(dp->entry_out));
+
479 fill_entry(&dp->entry_out, e);
+
480
+
481 struct fuse_dirent *dirent = &dp->dirent;
+
482 dirent->ino = e->attr.st_ino;
+
483 dirent->off = off;
+
484 dirent->namelen = namelen;
+
485 dirent->type = (e->attr.st_mode & S_IFMT) >> 12;
+
486 memcpy(dirent->name, name, namelen);
+
487 memset(dirent->name + namelen, 0, entlen_padded - entlen);
+
488
+
489 return entlen_padded;
+
490}
+
+
491
+
492static void fill_open(struct fuse_open_out *arg,
+
493 const struct fuse_file_info *f)
+
494{
+
495 arg->fh = f->fh;
+
496 if (f->backing_id > 0) {
+
497 arg->backing_id = f->backing_id;
+
498 arg->open_flags |= FOPEN_PASSTHROUGH;
+
499 }
+
500 if (f->direct_io)
+
501 arg->open_flags |= FOPEN_DIRECT_IO;
+
502 if (f->keep_cache)
+
503 arg->open_flags |= FOPEN_KEEP_CACHE;
+
504 if (f->cache_readdir)
+
505 arg->open_flags |= FOPEN_CACHE_DIR;
+
506 if (f->nonseekable)
+
507 arg->open_flags |= FOPEN_NONSEEKABLE;
+
508 if (f->noflush)
+
509 arg->open_flags |= FOPEN_NOFLUSH;
+ +
511 arg->open_flags |= FOPEN_PARALLEL_DIRECT_WRITES;
+
512}
+
513
+
+ +
515{
+
516 struct fuse_entry_out arg;
+
517 size_t size = req->se->conn.proto_minor < 9 ?
+
518 FUSE_COMPAT_ENTRY_OUT_SIZE : sizeof(arg);
+
519
+
520 /* before ABI 7.4 e->ino == 0 was invalid, only ENOENT meant
+
521 negative entry */
+
522 if (!e->ino && req->se->conn.proto_minor < 4)
+
523 return fuse_reply_err(req, ENOENT);
+
524
+
525 memset(&arg, 0, sizeof(arg));
+
526 fill_entry(&arg, e);
+
527 return send_reply_ok(req, &arg, size);
+
528}
+
+
529
+
+ +
531 const struct fuse_file_info *f)
+
532{
+
533 alignas(uint64_t) char buf[sizeof(struct fuse_entry_out) + sizeof(struct fuse_open_out)];
+
534 size_t entrysize = req->se->conn.proto_minor < 9 ?
+
535 FUSE_COMPAT_ENTRY_OUT_SIZE : sizeof(struct fuse_entry_out);
+
536 struct fuse_entry_out *earg = (struct fuse_entry_out *) buf;
+
537 struct fuse_open_out *oarg = (struct fuse_open_out *) (buf + entrysize);
+
538
+
539 memset(buf, 0, sizeof(buf));
+
540 fill_entry(earg, e);
+
541 fill_open(oarg, f);
+
542 return send_reply_ok(req, buf,
+
543 entrysize + sizeof(struct fuse_open_out));
+
544}
+
+
545
+
+
546int fuse_reply_attr(fuse_req_t req, const struct stat *attr,
+
547 double attr_timeout)
+
548{
+
549 struct fuse_attr_out arg;
+
550 size_t size = req->se->conn.proto_minor < 9 ?
+
551 FUSE_COMPAT_ATTR_OUT_SIZE : sizeof(arg);
+
552
+
553 memset(&arg, 0, sizeof(arg));
+
554 arg.attr_valid = calc_timeout_sec(attr_timeout);
+
555 arg.attr_valid_nsec = calc_timeout_nsec(attr_timeout);
+
556 convert_stat(attr, &arg.attr);
+
557
+
558 return send_reply_ok(req, &arg, size);
+
559}
+
+
560
+
+
561int fuse_reply_readlink(fuse_req_t req, const char *linkname)
+
562{
+
563 return send_reply_ok(req, linkname, strlen(linkname));
+
564}
+
+
565
+
+ +
567{
+
568 struct fuse_backing_map map = { .fd = fd };
+
569 int ret;
+
570
+
571 ret = ioctl(req->se->fd, FUSE_DEV_IOC_BACKING_OPEN, &map);
+
572 if (ret <= 0) {
+
573 fuse_log(FUSE_LOG_ERR, "fuse: passthrough_open: %s\n", strerror(errno));
+
574 return 0;
+
575 }
+
576
+
577 return ret;
+
578}
+
+
579
+
580int fuse_passthrough_close(fuse_req_t req, int backing_id)
+
581{
+
582 int ret;
+
583
+
584 ret = ioctl(req->se->fd, FUSE_DEV_IOC_BACKING_CLOSE, &backing_id);
+
585 if (ret < 0)
+
586 fuse_log(FUSE_LOG_ERR, "fuse: passthrough_close: %s\n", strerror(errno));
+
587
+
588 return ret;
+
589}
+
590
+
+ +
592{
+
593 struct fuse_open_out arg;
+
594
+
595 memset(&arg, 0, sizeof(arg));
+
596 fill_open(&arg, f);
+
597 return send_reply_ok(req, &arg, sizeof(arg));
+
598}
+
+
599
+
600static int do_fuse_reply_write(fuse_req_t req, size_t count)
+
601{
+
602 struct fuse_write_out arg;
+
603
+
604 memset(&arg, 0, sizeof(arg));
+
605 arg.size = count;
+
606
+
607 return send_reply_ok(req, &arg, sizeof(arg));
+
608}
+
609
+
610static int do_fuse_reply_copy(fuse_req_t req, size_t count)
+
611{
+
612 struct fuse_copy_file_range_out arg;
+
613
+
614 memset(&arg, 0, sizeof(arg));
+
615 arg.bytes_copied = count;
+
616
+
617 return send_reply_ok(req, &arg, sizeof(arg));
+
618}
+
619
+
+
620int fuse_reply_write(fuse_req_t req, size_t count)
+
621{
+
622 /*
+
623 * This function is also used by FUSE_COPY_FILE_RANGE and its 64-bit
+
624 * variant.
+
625 */
+
626 if (req->flags.is_copy_file_range_64)
+
627 return do_fuse_reply_copy(req, count);
+
628 else
+
629 return do_fuse_reply_write(req, count);
+
630}
+
+
631
+
+
632int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
+
633{
+
634 return send_reply_ok(req, buf, size);
+
635}
+
+
636
+
637static int fuse_send_data_iov_fallback(struct fuse_session *se,
+
638 struct fuse_chan *ch,
+
639 struct iovec *iov, int iov_count,
+
640 struct fuse_bufvec *buf,
+
641 size_t len, fuse_req_t req)
+
642{
+
643 struct fuse_bufvec mem_buf = FUSE_BUFVEC_INIT(len);
+
644 void *mbuf;
+
645 int res;
+
646
+
647 /* Optimize common case */
+
648 if (buf->count == 1 && buf->idx == 0 && buf->off == 0 &&
+
649 !(buf->buf[0].flags & FUSE_BUF_IS_FD)) {
+
650 /* FIXME: also avoid memory copy if there are multiple buffers
+
651 but none of them contain an fd */
+
652
+
653 iov[iov_count].iov_base = buf->buf[0].mem;
+
654 iov[iov_count].iov_len = len;
+
655 iov_count++;
+
656 return fuse_send_msg(se, ch, iov, iov_count, req);
+
657 }
+
658
+
659 res = posix_memalign(&mbuf, pagesize, len);
+
660 if (res != 0)
+
661 return res;
+
662
+
663 mem_buf.buf[0].mem = mbuf;
+
664 res = fuse_buf_copy(&mem_buf, buf, 0);
+
665 if (res < 0) {
+
666 free(mbuf);
+
667 return -res;
+
668 }
+
669 len = res;
+
670
+
671 iov[iov_count].iov_base = mbuf;
+
672 iov[iov_count].iov_len = len;
+
673 iov_count++;
+
674 res = fuse_send_msg(se, ch, iov, iov_count, req);
+
675 free(mbuf);
+
676
+
677 return res;
+
678}
+
679
+
680struct fuse_ll_pipe {
+
681 size_t size;
+
682 int can_grow;
+
683 int pipe[2];
+
684};
+
685
+
686static void fuse_ll_pipe_free(struct fuse_ll_pipe *llp)
+
687{
+
688 close(llp->pipe[0]);
+
689 close(llp->pipe[1]);
+
690 free(llp);
+
691}
+
692
+
693#ifdef HAVE_SPLICE
+
694#if !defined(HAVE_PIPE2) || !defined(O_CLOEXEC)
+
695static int fuse_pipe(int fds[2])
+
696{
+
697 int rv = pipe(fds);
+
698
+
699 if (rv == -1)
+
700 return rv;
+
701
+
702 if (fcntl(fds[0], F_SETFL, O_NONBLOCK) == -1 ||
+
703 fcntl(fds[1], F_SETFL, O_NONBLOCK) == -1 ||
+
704 fcntl(fds[0], F_SETFD, FD_CLOEXEC) == -1 ||
+
705 fcntl(fds[1], F_SETFD, FD_CLOEXEC) == -1) {
+
706 close(fds[0]);
+
707 close(fds[1]);
+
708 rv = -1;
+
709 }
+
710 return rv;
+
711}
+
712#else
+
713static int fuse_pipe(int fds[2])
+
714{
+
715 return pipe2(fds, O_CLOEXEC | O_NONBLOCK);
+
716}
+
717#endif
+
718
+
719static struct fuse_ll_pipe *fuse_ll_get_pipe(struct fuse_session *se)
+
720{
+
721 struct fuse_ll_pipe *llp = pthread_getspecific(se->pipe_key);
+
722 if (llp == NULL) {
+
723 int res;
+
724
+
725 llp = malloc(sizeof(struct fuse_ll_pipe));
+
726 if (llp == NULL)
+
727 return NULL;
+
728
+
729 res = fuse_pipe(llp->pipe);
+
730 if (res == -1) {
+
731 free(llp);
+
732 return NULL;
+
733 }
+
734
+
735 /*
+
736 *the default size is 16 pages on linux
+
737 */
+
738 llp->size = pagesize * 16;
+
739 llp->can_grow = 1;
+
740
+
741 pthread_setspecific(se->pipe_key, llp);
+
742 }
+
743
+
744 return llp;
+
745}
+
746#endif
+
747
+
748static void fuse_ll_clear_pipe(struct fuse_session *se)
+
749{
+
750 struct fuse_ll_pipe *llp = pthread_getspecific(se->pipe_key);
+
751 if (llp) {
+
752 pthread_setspecific(se->pipe_key, NULL);
+
753 fuse_ll_pipe_free(llp);
+
754 }
+
755}
+
756
+
757#if defined(HAVE_SPLICE) && defined(HAVE_VMSPLICE)
+
758static int read_back(int fd, char *buf, size_t len)
+
759{
+
760 int res;
+
761
+
762 res = read(fd, buf, len);
+
763 if (res == -1) {
+
764 fuse_log(FUSE_LOG_ERR,
+
765 "fuse: internal error: failed to read back from pipe: %s\n",
+
766 strerror(errno));
+
767 return -EIO;
+
768 }
+
769 if (res != len) {
+
770 fuse_log(FUSE_LOG_ERR,
+
771 "fuse: internal error: short read back from pipe: %i from %zd\n",
+
772 res, len);
+
773 return -EIO;
+
774 }
+
775 return 0;
+
776}
+
777
+
778static int grow_pipe_to_max(int pipefd)
+
779{
+
780 int res;
+
781 long max;
+
782 long maxfd;
+
783 char buf[32];
+
784
+
785 maxfd = open("/proc/sys/fs/pipe-max-size", O_RDONLY | O_CLOEXEC);
+
786 if (maxfd < 0)
+
787 return -errno;
+
788
+
789 res = read(maxfd, buf, sizeof(buf) - 1);
+
790 if (res < 0) {
+
791 int saved_errno;
+
792
+
793 saved_errno = errno;
+
794 close(maxfd);
+
795 return -saved_errno;
+
796 }
+
797 close(maxfd);
+
798 buf[res] = '\0';
+
799
+
800 res = libfuse_strtol(buf, &max);
+
801 if (res)
+
802 return res;
+
803 res = fcntl(pipefd, F_SETPIPE_SZ, max);
+
804 if (res < 0)
+
805 return -errno;
+
806 return max;
+
807}
+
808
+
809static int fuse_send_data_iov(struct fuse_session *se, struct fuse_chan *ch,
+
810 struct iovec *iov, int iov_count,
+
811 struct fuse_bufvec *buf, unsigned int flags,
+
812 fuse_req_t req)
+
813{
+
814 int res;
+
815 size_t len = fuse_buf_size(buf);
+
816 struct fuse_out_header *out = iov[0].iov_base;
+
817 struct fuse_ll_pipe *llp;
+
818 int splice_flags;
+
819 size_t pipesize;
+
820 size_t total_buf_size;
+
821 size_t idx;
+
822 size_t headerlen;
+
823 struct fuse_bufvec pipe_buf = FUSE_BUFVEC_INIT(len);
+
824
+
825 if (se->broken_splice_nonblock)
+
826 goto fallback;
+
827
+
828 if (flags & FUSE_BUF_NO_SPLICE)
+
829 goto fallback;
+
830
+
831 total_buf_size = 0;
+
832 for (idx = buf->idx; idx < buf->count; idx++) {
+
833 total_buf_size += buf->buf[idx].size;
+
834 if (idx == buf->idx)
+
835 total_buf_size -= buf->off;
+
836 }
+
837 if (total_buf_size < 2 * pagesize)
+
838 goto fallback;
+
839
+
840 if (se->conn.proto_minor < 14 ||
+
841 !(se->conn.want_ext & FUSE_CAP_SPLICE_WRITE))
+
842 goto fallback;
+
843
+
844 llp = fuse_ll_get_pipe(se);
+
845 if (llp == NULL)
+
846 goto fallback;
+
847
+
848
+
849 headerlen = iov_length(iov, iov_count);
+
850
+
851 out->len = headerlen + len;
+
852
+
853 /*
+
854 * Heuristic for the required pipe size, does not work if the
+
855 * source contains less than page size fragments
+
856 */
+
857 pipesize = pagesize * (iov_count + buf->count + 1) + out->len;
+
858
+
859 if (llp->size < pipesize) {
+
860 if (llp->can_grow) {
+
861 res = fcntl(llp->pipe[0], F_SETPIPE_SZ, pipesize);
+
862 if (res == -1) {
+
863 res = grow_pipe_to_max(llp->pipe[0]);
+
864 if (res > 0)
+
865 llp->size = res;
+
866 llp->can_grow = 0;
+
867 goto fallback;
+
868 }
+
869 llp->size = res;
+
870 }
+
871 if (llp->size < pipesize)
+
872 goto fallback;
+
873 }
+
874
+
875
+
876 res = vmsplice(llp->pipe[1], iov, iov_count, SPLICE_F_NONBLOCK);
+
877 if (res == -1)
+
878 goto fallback;
+
879
+
880 if (res != headerlen) {
+
881 res = -EIO;
+
882 fuse_log(FUSE_LOG_ERR, "fuse: short vmsplice to pipe: %u/%zu\n", res,
+
883 headerlen);
+
884 goto clear_pipe;
+
885 }
+
886
+
887 pipe_buf.buf[0].flags = FUSE_BUF_IS_FD;
+
888 pipe_buf.buf[0].fd = llp->pipe[1];
+
889
+
890 res = fuse_buf_copy(&pipe_buf, buf,
+ +
892 if (res < 0) {
+
893 if (res == -EAGAIN || res == -EINVAL) {
+
894 /*
+
895 * Should only get EAGAIN on kernels with
+
896 * broken SPLICE_F_NONBLOCK support (<=
+
897 * 2.6.35) where this error or a short read is
+
898 * returned even if the pipe itself is not
+
899 * full
+
900 *
+
901 * EINVAL might mean that splice can't handle
+
902 * this combination of input and output.
+
903 */
+
904 if (res == -EAGAIN)
+
905 se->broken_splice_nonblock = 1;
+
906
+
907 pthread_setspecific(se->pipe_key, NULL);
+
908 fuse_ll_pipe_free(llp);
+
909 goto fallback;
+
910 }
+
911 res = -res;
+
912 goto clear_pipe;
+
913 }
+
914
+
915 if (res != 0 && res < len) {
+
916 struct fuse_bufvec mem_buf = FUSE_BUFVEC_INIT(len);
+
917 void *mbuf;
+
918 size_t now_len = res;
+
919 /*
+
920 * For regular files a short count is either
+
921 * 1) due to EOF, or
+
922 * 2) because of broken SPLICE_F_NONBLOCK (see above)
+
923 *
+
924 * For other inputs it's possible that we overflowed
+
925 * the pipe because of small buffer fragments.
+
926 */
+
927
+
928 res = posix_memalign(&mbuf, pagesize, len);
+
929 if (res != 0)
+
930 goto clear_pipe;
+
931
+
932 mem_buf.buf[0].mem = mbuf;
+
933 mem_buf.off = now_len;
+
934 res = fuse_buf_copy(&mem_buf, buf, 0);
+
935 if (res > 0) {
+
936 char *tmpbuf;
+
937 size_t extra_len = res;
+
938 /*
+
939 * Trickiest case: got more data. Need to get
+
940 * back the data from the pipe and then fall
+
941 * back to regular write.
+
942 */
+
943 tmpbuf = malloc(headerlen);
+
944 if (tmpbuf == NULL) {
+
945 free(mbuf);
+
946 res = ENOMEM;
+
947 goto clear_pipe;
+
948 }
+
949 res = read_back(llp->pipe[0], tmpbuf, headerlen);
+
950 free(tmpbuf);
+
951 if (res != 0) {
+
952 free(mbuf);
+
953 goto clear_pipe;
+
954 }
+
955 res = read_back(llp->pipe[0], mbuf, now_len);
+
956 if (res != 0) {
+
957 free(mbuf);
+
958 goto clear_pipe;
+
959 }
+
960 len = now_len + extra_len;
+
961 iov[iov_count].iov_base = mbuf;
+
962 iov[iov_count].iov_len = len;
+
963 iov_count++;
+
964 res = fuse_send_msg(se, ch, iov, iov_count, req);
+
965 free(mbuf);
+
966 return res;
+
967 }
+
968 free(mbuf);
+
969 res = now_len;
+
970 }
+
971 len = res;
+
972 out->len = headerlen + len;
+
973
+
974 if (se->debug) {
+
975 fuse_log(FUSE_LOG_DEBUG,
+
976 " unique: %llu, success, outsize: %i (splice)\n",
+
977 (unsigned long long) out->unique, out->len);
+
978 }
+
979
+
980 splice_flags = 0;
+
981 if ((flags & FUSE_BUF_SPLICE_MOVE) &&
+
982 (se->conn.want_ext & FUSE_CAP_SPLICE_MOVE))
+
983 splice_flags |= SPLICE_F_MOVE;
+
984
+
985 if (se->io != NULL && se->io->splice_send != NULL) {
+
986 res = se->io->splice_send(llp->pipe[0], NULL,
+
987 ch ? ch->fd : se->fd, NULL, out->len,
+
988 splice_flags, se->userdata);
+
989 } else {
+
990 res = splice(llp->pipe[0], NULL, ch ? ch->fd : se->fd, NULL,
+
991 out->len, splice_flags);
+
992 }
+
993 if (res == -1) {
+
994 res = -errno;
+
995 perror("fuse: splice from pipe");
+
996 goto clear_pipe;
+
997 }
+
998 if (res != out->len) {
+
999 res = -EIO;
+
1000 fuse_log(FUSE_LOG_ERR, "fuse: short splice from pipe: %u/%u\n",
+
1001 res, out->len);
+
1002 goto clear_pipe;
+
1003 }
+
1004 return 0;
+
1005
+
1006clear_pipe:
+
1007 fuse_ll_clear_pipe(se);
+
1008 return res;
+
1009
+
1010fallback:
+
1011 return fuse_send_data_iov_fallback(se, ch, iov, iov_count, buf, len, req);
+
1012}
+
1013#else
+
1014static int fuse_send_data_iov(struct fuse_session *se, struct fuse_chan *ch,
+
1015 struct iovec *iov, int iov_count,
+
1016 struct fuse_bufvec *req_data, unsigned int flags,
+
1017 fuse_req_t req)
+
1018{
+
1019 size_t len = fuse_buf_size(req_data);
+
1020 (void) flags;
+
1021
+
1022 return fuse_send_data_iov_fallback(se, ch, iov, iov_count, req_data, len, req);
+
1023}
+
1024#endif
+
1025
+
+ +
1027 enum fuse_buf_copy_flags flags)
+
1028{
+
1029 struct iovec iov[2];
+
1030 struct fuse_out_header out;
+
1031 int res;
+
1032
+
1033 if (req->flags.is_uring)
+
1034 return fuse_reply_data_uring(req, bufv, flags);
+
1035
+
1036 iov[0].iov_base = &out;
+
1037 iov[0].iov_len = sizeof(struct fuse_out_header);
+
1038
+
1039 out.unique = req->unique;
+
1040 out.error = 0;
+
1041
+
1042 res = fuse_send_data_iov(req->se, req->ch, iov, 1, bufv, flags, req);
+
1043 if (res <= 0) {
+
1044 fuse_free_req(req);
+
1045 return res;
+
1046 } else {
+
1047 return fuse_reply_err(req, res);
+
1048 }
+
1049}
+
+
1050
+
+
1051int fuse_reply_statfs(fuse_req_t req, const struct statvfs *stbuf)
+
1052{
+
1053 struct fuse_statfs_out arg;
+
1054 size_t size = req->se->conn.proto_minor < 4 ?
+
1055 FUSE_COMPAT_STATFS_SIZE : sizeof(arg);
+
1056
+
1057 memset(&arg, 0, sizeof(arg));
+
1058 convert_statfs(stbuf, &arg.st);
+
1059
+
1060 return send_reply_ok(req, &arg, size);
+
1061}
+
+
1062
+
+
1063int fuse_reply_xattr(fuse_req_t req, size_t count)
+
1064{
+
1065 struct fuse_getxattr_out arg;
+
1066
+
1067 memset(&arg, 0, sizeof(arg));
+
1068 arg.size = count;
+
1069
+
1070 return send_reply_ok(req, &arg, sizeof(arg));
+
1071}
+
+
1072
+
+
1073int fuse_reply_lock(fuse_req_t req, const struct flock *lock)
+
1074{
+
1075 struct fuse_lk_out arg;
+
1076
+
1077 memset(&arg, 0, sizeof(arg));
+
1078 arg.lk.type = lock->l_type;
+
1079 if (lock->l_type != F_UNLCK) {
+
1080 arg.lk.start = lock->l_start;
+
1081 if (lock->l_len == 0)
+
1082 arg.lk.end = OFFSET_MAX;
+
1083 else
+
1084 arg.lk.end = lock->l_start + lock->l_len - 1;
+
1085 }
+
1086 arg.lk.pid = lock->l_pid;
+
1087 return send_reply_ok(req, &arg, sizeof(arg));
+
1088}
+
+
1089
+
+
1090int fuse_reply_bmap(fuse_req_t req, uint64_t idx)
+
1091{
+
1092 struct fuse_bmap_out arg;
+
1093
+
1094 memset(&arg, 0, sizeof(arg));
+
1095 arg.block = idx;
+
1096
+
1097 return send_reply_ok(req, &arg, sizeof(arg));
+
1098}
+
+
1099
+
1100static struct fuse_ioctl_iovec *fuse_ioctl_iovec_copy(const struct iovec *iov,
+
1101 size_t count)
+
1102{
+
1103 struct fuse_ioctl_iovec *fiov;
+
1104 size_t i;
+
1105
+
1106 fiov = malloc(sizeof(fiov[0]) * count);
+
1107 if (!fiov)
+
1108 return NULL;
+
1109
+
1110 for (i = 0; i < count; i++) {
+
1111 fiov[i].base = (uintptr_t) iov[i].iov_base;
+
1112 fiov[i].len = iov[i].iov_len;
+
1113 }
+
1114
+
1115 return fiov;
+
1116}
+
1117
+
+ +
1119 const struct iovec *in_iov, size_t in_count,
+
1120 const struct iovec *out_iov, size_t out_count)
+
1121{
+
1122 struct fuse_ioctl_out arg;
+
1123 struct fuse_ioctl_iovec *in_fiov = NULL;
+
1124 struct fuse_ioctl_iovec *out_fiov = NULL;
+
1125 struct iovec iov[4];
+
1126 size_t count = 1;
+
1127 int res;
+
1128
+
1129 memset(&arg, 0, sizeof(arg));
+
1130 arg.flags |= FUSE_IOCTL_RETRY;
+
1131 arg.in_iovs = in_count;
+
1132 arg.out_iovs = out_count;
+
1133 iov[count].iov_base = &arg;
+
1134 iov[count].iov_len = sizeof(arg);
+
1135 count++;
+
1136
+
1137 if (req->se->conn.proto_minor < 16) {
+
1138 if (in_count) {
+
1139 iov[count].iov_base = (void *)in_iov;
+
1140 iov[count].iov_len = sizeof(in_iov[0]) * in_count;
+
1141 count++;
+
1142 }
+
1143
+
1144 if (out_count) {
+
1145 iov[count].iov_base = (void *)out_iov;
+
1146 iov[count].iov_len = sizeof(out_iov[0]) * out_count;
+
1147 count++;
+
1148 }
+
1149 } else {
+
1150 /* Can't handle non-compat 64bit ioctls on 32bit */
+
1151 if (sizeof(void *) == 4 && req->flags.ioctl_64bit) {
+
1152 res = fuse_reply_err(req, EINVAL);
+
1153 goto out;
+
1154 }
+
1155
+
1156 if (in_count) {
+
1157 in_fiov = fuse_ioctl_iovec_copy(in_iov, in_count);
+
1158 if (!in_fiov)
+
1159 goto enomem;
+
1160
+
1161 iov[count].iov_base = (void *)in_fiov;
+
1162 iov[count].iov_len = sizeof(in_fiov[0]) * in_count;
+
1163 count++;
+
1164 }
+
1165 if (out_count) {
+
1166 out_fiov = fuse_ioctl_iovec_copy(out_iov, out_count);
+
1167 if (!out_fiov)
+
1168 goto enomem;
+
1169
+
1170 iov[count].iov_base = (void *)out_fiov;
+
1171 iov[count].iov_len = sizeof(out_fiov[0]) * out_count;
+
1172 count++;
+
1173 }
+
1174 }
+
1175
+
1176 res = send_reply_iov(req, 0, iov, count);
+
1177out:
+
1178 free(in_fiov);
+
1179 free(out_fiov);
+
1180
+
1181 return res;
+
1182
+
1183enomem:
+
1184 res = fuse_reply_err(req, ENOMEM);
+
1185 goto out;
+
1186}
+
+
1187
+
+
1188int fuse_reply_ioctl(fuse_req_t req, int result, const void *buf, size_t size)
+
1189{
+
1190 struct fuse_ioctl_out arg;
+
1191 struct iovec iov[3];
+
1192 size_t count = 1;
+
1193
+
1194 memset(&arg, 0, sizeof(arg));
+
1195 arg.result = result;
+
1196 iov[count].iov_base = &arg;
+
1197 iov[count].iov_len = sizeof(arg);
+
1198 count++;
+
1199
+
1200 if (size) {
+
1201 iov[count].iov_base = (char *) buf;
+
1202 iov[count].iov_len = size;
+
1203 count++;
+
1204 }
+
1205
+
1206 return send_reply_iov(req, 0, iov, count);
+
1207}
+
+
1208
+
+
1209int fuse_reply_ioctl_iov(fuse_req_t req, int result, const struct iovec *iov,
+
1210 int count)
+
1211{
+
1212 struct iovec *padded_iov;
+
1213 struct fuse_ioctl_out arg;
+
1214 int res;
+
1215
+
1216 padded_iov = malloc((count + 2) * sizeof(struct iovec));
+
1217 if (padded_iov == NULL)
+
1218 return fuse_reply_err(req, ENOMEM);
+
1219
+
1220 memset(&arg, 0, sizeof(arg));
+
1221 arg.result = result;
+
1222 padded_iov[1].iov_base = &arg;
+
1223 padded_iov[1].iov_len = sizeof(arg);
+
1224
+
1225 memcpy(&padded_iov[2], iov, count * sizeof(struct iovec));
+
1226
+
1227 res = send_reply_iov(req, 0, padded_iov, count + 2);
+
1228 free(padded_iov);
+
1229
+
1230 return res;
+
1231}
+
+
1232
+
+
1233int fuse_reply_poll(fuse_req_t req, unsigned revents)
+
1234{
+
1235 struct fuse_poll_out arg;
+
1236
+
1237 memset(&arg, 0, sizeof(arg));
+
1238 arg.revents = revents;
+
1239
+
1240 return send_reply_ok(req, &arg, sizeof(arg));
+
1241}
+
+
1242
+
+
1243int fuse_reply_lseek(fuse_req_t req, off_t off)
+
1244{
+
1245 struct fuse_lseek_out arg;
+
1246
+
1247 memset(&arg, 0, sizeof(arg));
+
1248 arg.offset = off;
+
1249
+
1250 return send_reply_ok(req, &arg, sizeof(arg));
+
1251}
+
+
1252
+
1253#ifdef HAVE_STATX
+
1254int fuse_reply_statx(fuse_req_t req, int flags, struct statx *statx,
+
1255 double attr_timeout)
+
1256{
+
1257 struct fuse_statx_out arg;
+
1258
+
1259 memset(&arg, 0, sizeof(arg));
+
1260 arg.flags = flags;
+
1261 arg.attr_valid = calc_timeout_sec(attr_timeout);
+
1262 arg.attr_valid_nsec = calc_timeout_nsec(attr_timeout);
+
1263 memcpy(&arg.stat, statx, sizeof(arg.stat));
+
1264
+
1265 return send_reply_ok(req, &arg, sizeof(arg));
+
1266}
+
1267#else
+
+
1268int fuse_reply_statx(fuse_req_t req, int flags, struct statx *statx,
+
1269 double attr_timeout)
+
1270{
+
1271 (void)req;
+
1272 (void)flags;
+
1273 (void)statx;
+
1274 (void)attr_timeout;
+
1275
+
1276 return -ENOSYS;
+
1277}
+
+
1278#endif
+
1279
+
1280static void _do_lookup(fuse_req_t req, const fuse_ino_t nodeid,
+
1281 const void *op_in, const void *in_payload)
+
1282{
+
1283 (void)op_in;
+
1284
+
1285 char *name = (char *)in_payload;
+
1286
+
1287 if (req->se->op.lookup)
+
1288 req->se->op.lookup(req, nodeid, name);
+
1289 else
+
1290 fuse_reply_err(req, ENOSYS);
+
1291}
+
1292
+
1293static void do_lookup(fuse_req_t req, const fuse_ino_t nodeid,
+
1294 const void *inarg)
+
1295{
+
1296 _do_lookup(req, nodeid, NULL, inarg);
+
1297}
+
1298
+
1299static void _do_forget(fuse_req_t req, const fuse_ino_t nodeid,
+
1300 const void *op_in, const void *in_payload)
+
1301{
+
1302 (void)in_payload;
+
1303
+
1304 struct fuse_forget_in *arg = (struct fuse_forget_in *)op_in;
+
1305
+
1306 if (req->se->op.forget)
+
1307 req->se->op.forget(req, nodeid, arg->nlookup);
+
1308 else
+
1309 fuse_reply_none(req);
+
1310}
+
1311
+
1312static void do_forget(fuse_req_t req, const fuse_ino_t nodeid,
+
1313 const void *inarg)
+
1314{
+
1315 _do_forget(req, nodeid, inarg, NULL);
+
1316}
+
1317
+
1318static void _do_batch_forget(fuse_req_t req, const fuse_ino_t nodeid,
+
1319 const void *op_in, const void *in_payload)
+
1320{
+
1321 (void)nodeid;
+
1322 unsigned int i;
+
1323
+
1324 const struct fuse_batch_forget_in *arg = op_in;
+
1325 const struct fuse_forget_one *forgets = in_payload;
+
1326
+
1327 if (req->se->op.forget_multi) {
+
1328 req->se->op.forget_multi(req, arg->count,
+
1329 (struct fuse_forget_data *)in_payload);
+
1330 } else if (req->se->op.forget) {
+
1331 for (i = 0; i < arg->count; i++) {
+
1332 const struct fuse_forget_one *forget = &forgets[i];
+
1333 struct fuse_req *dummy_req;
+
1334
+
1335 dummy_req = fuse_ll_alloc_req(req->se);
+
1336 if (dummy_req == NULL)
+
1337 break;
+
1338
+
1339 dummy_req->unique = req->unique;
+
1340 dummy_req->ctx = req->ctx;
+
1341 dummy_req->ch = NULL;
+
1342
+
1343 req->se->op.forget(dummy_req, forget->nodeid,
+
1344 forget->nlookup);
+
1345 }
+
1346 fuse_reply_none(req);
+
1347 } else {
+
1348 fuse_reply_none(req);
+
1349 }
+
1350}
+
1351
+
1352static void do_batch_forget(fuse_req_t req, const fuse_ino_t nodeid,
+
1353 const void *inarg)
+
1354{
+
1355 struct fuse_batch_forget_in *arg = (void *)inarg;
+
1356 struct fuse_forget_one *param = (void *)PARAM(arg);
+
1357
+
1358 _do_batch_forget(req, nodeid, inarg, param);
+
1359}
+
1360
+
1361static void _do_getattr(fuse_req_t req, const fuse_ino_t nodeid,
+
1362 const void *op_in, const void *in_payload)
+
1363{
+
1364 struct fuse_getattr_in *arg = (struct fuse_getattr_in *)op_in;
+
1365 (void)in_payload;
+
1366
+
1367 struct fuse_file_info *fip = NULL;
+
1368 struct fuse_file_info fi;
+
1369
+
1370 if (req->se->conn.proto_minor >= 9) {
+
1371 if (arg->getattr_flags & FUSE_GETATTR_FH) {
+
1372 memset(&fi, 0, sizeof(fi));
+
1373 fi.fh = arg->fh;
+
1374 fip = &fi;
+
1375 }
+
1376 }
+
1377
+
1378 if (req->se->op.getattr)
+
1379 req->se->op.getattr(req, nodeid, fip);
+
1380 else
+
1381 fuse_reply_err(req, ENOSYS);
+
1382}
+
1383
+
1384static void do_getattr(fuse_req_t req, const fuse_ino_t nodeid,
+
1385 const void *inarg)
+
1386{
+
1387 _do_getattr(req, nodeid, inarg, NULL);
+
1388}
+
1389
+
1390static void _do_setattr(fuse_req_t req, const fuse_ino_t nodeid,
+
1391 const void *op_in, const void *in_payload)
+
1392{
+
1393 (void)in_payload;
+
1394 const struct fuse_setattr_in *arg = op_in;
+
1395 uint32_t valid = arg->valid;
+
1396
+
1397 if (req->se->op.setattr) {
+
1398 struct fuse_file_info *fi = NULL;
+
1399 struct fuse_file_info fi_store;
+
1400 struct stat stbuf;
+
1401 memset(&stbuf, 0, sizeof(stbuf));
+
1402 convert_attr(arg, &stbuf);
+
1403 if (arg->valid & FATTR_FH) {
+
1404 valid &= ~FATTR_FH;
+
1405 memset(&fi_store, 0, sizeof(fi_store));
+
1406 fi = &fi_store;
+
1407 fi->fh = arg->fh;
+
1408 }
+
1409 valid &= FUSE_SET_ATTR_MODE | FUSE_SET_ATTR_UID |
+
1410 FUSE_SET_ATTR_GID | FUSE_SET_ATTR_SIZE |
+
1411 FUSE_SET_ATTR_ATIME | FUSE_SET_ATTR_MTIME |
+
1412 FUSE_SET_ATTR_KILL_SUID | FUSE_SET_ATTR_KILL_SGID |
+
1413 FUSE_SET_ATTR_ATIME_NOW | FUSE_SET_ATTR_MTIME_NOW |
+
1414 FUSE_SET_ATTR_CTIME;
+
1415
+
1416 req->se->op.setattr(req, nodeid, &stbuf, valid, fi);
+
1417 } else
+
1418 fuse_reply_err(req, ENOSYS);
+
1419}
+
1420
+
1421static void do_setattr(fuse_req_t req, const fuse_ino_t nodeid,
+
1422 const void *inarg)
+
1423{
+
1424 _do_setattr(req, nodeid, inarg, NULL);
+
1425}
+
1426
+
1427static void _do_access(fuse_req_t req, const fuse_ino_t nodeid,
+
1428 const void *op_in, const void *in_payload)
+
1429{
+
1430 (void)in_payload;
+
1431 const struct fuse_access_in *arg = op_in;
+
1432
+
1433 if (req->se->op.access)
+
1434 req->se->op.access(req, nodeid, arg->mask);
+
1435 else
+
1436 fuse_reply_err(req, ENOSYS);
+
1437}
+
1438
+
1439static void do_access(fuse_req_t req, const fuse_ino_t nodeid,
+
1440 const void *inarg)
+
1441{
+
1442 _do_access(req, nodeid, inarg, NULL);
+
1443}
+
1444
+
1445static void _do_readlink(fuse_req_t req, const fuse_ino_t nodeid,
+
1446 const void *op_in, const void *in_payload)
+
1447{
+
1448 (void)op_in;
+
1449 (void)in_payload;
+
1450
+
1451 if (req->se->op.readlink)
+
1452 req->se->op.readlink(req, nodeid);
+
1453 else
+
1454 fuse_reply_err(req, ENOSYS);
+
1455}
+
1456
+
1457static void do_readlink(fuse_req_t req, const fuse_ino_t nodeid,
+
1458 const void *inarg)
+
1459{
+
1460 _do_readlink(req, nodeid, inarg, NULL);
+
1461}
+
1462
+
1463static void _do_mknod(fuse_req_t req, const fuse_ino_t nodeid,
+
1464 const void *op_in, const void *in_payload)
+
1465{
+
1466 const struct fuse_mknod_in *arg = (struct fuse_mknod_in *)op_in;
+
1467 const char *name = in_payload;
+
1468
+
1469 if (req->se->conn.proto_minor >= 12)
+
1470 req->ctx.umask = arg->umask;
+
1471
+
1472 if (req->se->op.mknod)
+
1473 req->se->op.mknod(req, nodeid, name, arg->mode, arg->rdev);
+
1474 else
+
1475 fuse_reply_err(req, ENOSYS);
+
1476}
+
1477
+
1478static void do_mknod(fuse_req_t req, const fuse_ino_t nodeid, const void *inarg)
+
1479{
+
1480 struct fuse_mknod_in *arg = (struct fuse_mknod_in *)inarg;
+
1481 char *name = PARAM(arg);
+
1482
+
1483 if (req->se->conn.proto_minor < 12)
+
1484 name = (char *)inarg + FUSE_COMPAT_MKNOD_IN_SIZE;
+
1485
+
1486 _do_mknod(req, nodeid, inarg, name);
+
1487}
+
1488
+
1489static void _do_mkdir(fuse_req_t req, const fuse_ino_t nodeid,
+
1490 const void *op_in, const void *in_payload)
+
1491{
+
1492 const char *name = in_payload;
+
1493 const struct fuse_mkdir_in *arg = op_in;
+
1494
+
1495 if (req->se->conn.proto_minor >= 12)
+
1496 req->ctx.umask = arg->umask;
+
1497
+
1498 if (req->se->op.mkdir)
+
1499 req->se->op.mkdir(req, nodeid, name, arg->mode);
+
1500 else
+
1501 fuse_reply_err(req, ENOSYS);
+
1502}
+
1503
+
1504static void do_mkdir(fuse_req_t req, const fuse_ino_t nodeid, const void *inarg)
+
1505{
+
1506 const struct fuse_mkdir_in *arg = inarg;
+
1507 const char *name = PARAM(arg);
+
1508
+
1509 _do_mkdir(req, nodeid, inarg, name);
+
1510}
+
1511
+
1512static void _do_unlink(fuse_req_t req, const fuse_ino_t nodeid,
+
1513 const void *op_in, const void *in_payload)
+
1514{
+
1515 (void)op_in;
+
1516 const char *name = in_payload;
+
1517
+
1518 if (req->se->op.unlink)
+
1519 req->se->op.unlink(req, nodeid, name);
+
1520 else
+
1521 fuse_reply_err(req, ENOSYS);
+
1522}
+
1523
+
1524static void do_unlink(fuse_req_t req, const fuse_ino_t nodeid,
+
1525 const void *inarg)
+
1526{
+
1527 _do_unlink(req, nodeid, NULL, inarg);
+
1528}
+
1529
+
1530static void _do_rmdir(fuse_req_t req, const fuse_ino_t nodeid,
+
1531 const void *op_in, const void *in_payload)
+
1532{
+
1533 (void)op_in;
+
1534 const char *name = in_payload;
+
1535
+
1536 if (req->se->op.rmdir)
+
1537 req->se->op.rmdir(req, nodeid, name);
+
1538 else
+
1539 fuse_reply_err(req, ENOSYS);
+
1540}
+
1541
+
1542static void do_rmdir(fuse_req_t req, const fuse_ino_t nodeid, const void *inarg)
+
1543{
+
1544 _do_rmdir(req, nodeid, NULL, inarg);
+
1545}
+
1546
+
1547static void _do_symlink(fuse_req_t req, const fuse_ino_t nodeid,
+
1548 const void *op_in, const void *in_payload)
+
1549{
+
1550 (void)op_in;
+
1551 const char *name = (char *)in_payload;
+
1552 const char *linkname = name + strlen(name) + 1;
+
1553
+
1554 if (req->se->op.symlink)
+
1555 req->se->op.symlink(req, linkname, nodeid, name);
+
1556 else
+
1557 fuse_reply_err(req, ENOSYS);
+
1558}
+
1559
+
1560static void do_symlink(fuse_req_t req, const fuse_ino_t nodeid,
+
1561 const void *inarg)
+
1562{
+
1563 _do_symlink(req, nodeid, NULL, inarg);
+
1564}
+
1565
+
1566static void _do_rename(fuse_req_t req, const fuse_ino_t nodeid,
+
1567 const void *op_in, const void *in_payload)
+
1568{
+
1569 const struct fuse_rename_in *arg = (struct fuse_rename_in *)op_in;
+
1570 const char *oldname = in_payload;
+
1571 const char *newname = oldname + strlen(oldname) + 1;
+
1572
+
1573 if (req->se->op.rename)
+
1574 req->se->op.rename(req, nodeid, oldname, arg->newdir, newname,
+
1575 0);
+
1576 else
+
1577 fuse_reply_err(req, ENOSYS);
+
1578}
+
1579
+
1580static void do_rename(fuse_req_t req, const fuse_ino_t nodeid,
+
1581 const void *inarg)
+
1582{
+
1583 const struct fuse_rename_in *arg = inarg;
+
1584 const void *payload = PARAM(arg);
+
1585
+
1586 _do_rename(req, nodeid, arg, payload);
+
1587}
+
1588
+
1589static void _do_rename2(fuse_req_t req, const fuse_ino_t nodeid,
+
1590 const void *op_in, const void *in_payload)
+
1591{
+
1592 const struct fuse_rename2_in *arg = op_in;
+
1593 const char *oldname = in_payload;
+
1594 const char *newname = oldname + strlen(oldname) + 1;
+
1595
+
1596 if (req->se->op.rename)
+
1597 req->se->op.rename(req, nodeid, oldname, arg->newdir, newname,
+
1598 arg->flags);
+
1599 else
+
1600 fuse_reply_err(req, ENOSYS);
+
1601}
+
1602
+
1603static void do_rename2(fuse_req_t req, const fuse_ino_t nodeid,
+
1604 const void *inarg)
+
1605{
+
1606 const struct fuse_rename2_in *arg = inarg;
+
1607 const void *payload = PARAM(arg);
+
1608
+
1609 _do_rename2(req, nodeid, arg, payload);
+
1610}
+
1611
+
1612static void _do_tmpfile(fuse_req_t req, fuse_ino_t nodeid, const void *op_in,
+
1613 const void *in_payload)
+
1614{
+
1615 (void)in_payload;
+
1616 const struct fuse_create_in *arg = op_in;
+
1617
+
1618 if (req->se->op.tmpfile) {
+
1619 struct fuse_file_info fi;
+
1620
+
1621 memset(&fi, 0, sizeof(fi));
+
1622 fi.flags = arg->flags;
+
1623
+
1624 if (req->se->conn.proto_minor >= 12)
+
1625 req->ctx.umask = arg->umask;
+
1626
+
1627 req->se->op.tmpfile(req, nodeid, arg->mode, &fi);
+
1628 } else
+
1629 fuse_reply_err(req, ENOSYS);
+
1630}
+
1631
+
1632static void do_tmpfile(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1633{
+
1634 struct fuse_create_in *arg = (struct fuse_create_in *) inarg;
+
1635
+
1636 _do_tmpfile(req, nodeid, arg, NULL);
+
1637}
+
1638
+
1639static void _do_link(fuse_req_t req, const fuse_ino_t nodeid, const void *op_in,
+
1640 const void *in_payload)
+
1641{
+
1642 struct fuse_link_in *arg = (struct fuse_link_in *)op_in;
+
1643
+
1644 if (req->se->op.link)
+
1645 req->se->op.link(req, arg->oldnodeid, nodeid, in_payload);
+
1646 else
+
1647 fuse_reply_err(req, ENOSYS);
+
1648}
+
1649
+
1650static void do_link(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
1651{
+
1652 const struct fuse_link_in *arg = inarg;
+
1653 const void *name = PARAM(arg);
+
1654
+
1655 _do_link(req, nodeid, inarg, name);
+
1656}
+
1657
+
1658static void _do_create(fuse_req_t req, const fuse_ino_t nodeid,
+
1659 const void *op_in, const void *in_payload)
+
1660{
+
1661 const struct fuse_create_in *arg = op_in;
+
1662 const char *name = in_payload;
+
1663
+
1664 if (req->se->op.create) {
+
1665 struct fuse_file_info fi;
+
1666
+
1667 memset(&fi, 0, sizeof(fi));
+
1668 fi.flags = arg->flags;
+
1669
+
1670 if (req->se->conn.proto_minor >= 12)
+
1671 req->ctx.umask = arg->umask;
+
1672
+
1673 if (req->se->conn.want_ext & FUSE_CAP_HANDLE_KILLPRIV_V2)
+
1674 fi.kill_suidgid =
+
1675 (arg->open_flags & FUSE_OPEN_KILL_SUIDGID) != 0;
+
1676
+
1677 req->se->op.create(req, nodeid, name, arg->mode, &fi);
+
1678 } else {
+
1679 fuse_reply_err(req, ENOSYS);
+
1680 }
+
1681}
+
1682
+
1683static void do_create(fuse_req_t req, const fuse_ino_t nodeid,
+
1684 const void *inarg)
+
1685{
+
1686 const struct fuse_create_in *arg = (struct fuse_create_in *)inarg;
+
1687 void *payload = PARAM(arg);
+
1688
+
1689 if (req->se->conn.proto_minor < 12)
+
1690 payload = (char *)inarg + sizeof(struct fuse_open_in);
+
1691
+
1692 _do_create(req, nodeid, arg, payload);
+
1693}
+
1694
+
1695static void _do_open(fuse_req_t req, const fuse_ino_t nodeid, const void *op_in,
+
1696 const void *in_payload)
+
1697{
+
1698 (void)in_payload;
+
1699 struct fuse_open_in *arg = (struct fuse_open_in *)op_in;
+
1700 struct fuse_file_info fi;
+
1701
+
1702 memset(&fi, 0, sizeof(fi));
+
1703 fi.flags = arg->flags;
+
1704
+
1705 if (req->se->conn.want_ext & FUSE_CAP_HANDLE_KILLPRIV_V2)
+
1706 fi.kill_suidgid =
+
1707 (arg->open_flags & FUSE_OPEN_KILL_SUIDGID) != 0;
+
1708
+
1709 if (req->se->op.open)
+
1710 req->se->op.open(req, nodeid, &fi);
+
1711 else if (req->se->conn.want_ext & FUSE_CAP_NO_OPEN_SUPPORT)
+
1712 fuse_reply_err(req, ENOSYS);
+
1713 else
+
1714 fuse_reply_open(req, &fi);
+
1715}
+
1716
+
1717static void do_open(fuse_req_t req, const fuse_ino_t nodeid, const void *inarg)
+
1718{
+
1719 _do_open(req, nodeid, inarg, NULL);
+
1720}
+
1721
+
1722static void _do_read(fuse_req_t req, const fuse_ino_t nodeid, const void *op_in,
+
1723 const void *in_payload)
+
1724{
+
1725 (void)in_payload;
+
1726 struct fuse_read_in *arg = (struct fuse_read_in *)op_in;
+
1727
+
1728 if (req->se->op.read) {
+
1729 struct fuse_file_info fi;
+
1730
+
1731 memset(&fi, 0, sizeof(fi));
+
1732 fi.fh = arg->fh;
+
1733 if (req->se->conn.proto_minor >= 9) {
+
1734 fi.lock_owner = arg->lock_owner;
+
1735 fi.flags = arg->flags;
+
1736 }
+
1737 req->se->op.read(req, nodeid, arg->size, arg->offset, &fi);
+
1738 } else
+
1739 fuse_reply_err(req, ENOSYS);
+
1740}
+
1741
+
1742static void do_read(fuse_req_t req, const fuse_ino_t nodeid, const void *inarg)
+
1743{
+
1744 _do_read(req, nodeid, inarg, NULL);
+
1745}
+
1746
+
1747static void _do_write(fuse_req_t req, const fuse_ino_t nodeid,
+
1748 const void *op_in, const void *in_payload)
+
1749{
+
1750 struct fuse_write_in *arg = (struct fuse_write_in *)op_in;
+
1751 const char *buf = in_payload;
+
1752 struct fuse_file_info fi;
+
1753
+
1754 memset(&fi, 0, sizeof(fi));
+
1755 fi.fh = arg->fh;
+
1756 fi.writepage = (arg->write_flags & FUSE_WRITE_CACHE) != 0;
+
1757
+
1758 if (req->se->conn.want_ext & FUSE_CAP_HANDLE_KILLPRIV_V2)
+
1759 fi.kill_suidgid =
+
1760 (arg->write_flags & FUSE_WRITE_KILL_SUIDGID) != 0;
+
1761
+
1762 if (req->se->conn.proto_minor >= 9) {
+
1763 fi.lock_owner = arg->lock_owner;
+
1764 fi.flags = arg->flags;
+
1765 }
+
1766
+
1767 if (req->se->op.write)
+
1768 req->se->op.write(req, nodeid, buf, arg->size, arg->offset,
+
1769 &fi);
+
1770 else
+
1771 fuse_reply_err(req, ENOSYS);
+
1772}
+
1773
+
1774static void do_write(fuse_req_t req, const fuse_ino_t nodeid, const void *inarg)
+
1775{
+
1776 struct fuse_write_in *arg = (struct fuse_write_in *)inarg;
+
1777 const void *payload;
+
1778
+
1779 if (req->se->conn.proto_minor < 9)
+
1780 payload = ((char *)arg) + FUSE_COMPAT_WRITE_IN_SIZE;
+
1781 else
+
1782 payload = PARAM(arg);
+
1783
+
1784 _do_write(req, nodeid, arg, payload);
+
1785}
+
1786
+
1787static void _do_write_buf(fuse_req_t req, const fuse_ino_t nodeid,
+
1788 const void *op_in, struct fuse_bufvec *bufv)
+
1789{
+
1790 struct fuse_session *se = req->se;
+
1791 struct fuse_write_in *arg = (struct fuse_write_in *)op_in;
+
1792 struct fuse_file_info fi;
+
1793
+
1794 memset(&fi, 0, sizeof(fi));
+
1795 fi.fh = arg->fh;
+
1796 fi.writepage = arg->write_flags & FUSE_WRITE_CACHE;
+
1797
+
1798 if (se->conn.want_ext & FUSE_CAP_HANDLE_KILLPRIV_V2)
+
1799 fi.kill_suidgid =
+
1800 (arg->write_flags & FUSE_WRITE_KILL_SUIDGID) != 0;
+
1801
+
1802 if (se->conn.proto_minor >= 9) {
+
1803 fi.lock_owner = arg->lock_owner;
+
1804 fi.flags = arg->flags;
+
1805 }
+
1806
+
1807 se->op.write_buf(req, nodeid, bufv, arg->offset, &fi);
+
1808}
+
1809
+
1810static void do_write_buf(fuse_req_t req, const fuse_ino_t nodeid,
+
1811 const void *inarg, const struct fuse_buf *ibuf)
+
1812{
+
1813 struct fuse_session *se = req->se;
+
1814 struct fuse_bufvec bufv = {
+
1815 .buf[0] = *ibuf,
+
1816 .count = 1,
+
1817 };
+
1818 struct fuse_write_in *arg = (struct fuse_write_in *)inarg;
+
1819
+
1820 if (se->conn.proto_minor < 9) {
+
1821 bufv.buf[0].mem = ((char *)arg) + FUSE_COMPAT_WRITE_IN_SIZE;
+
1822 bufv.buf[0].size -= sizeof(struct fuse_in_header) +
+
1823 FUSE_COMPAT_WRITE_IN_SIZE;
+
1824 assert(!(bufv.buf[0].flags & FUSE_BUF_IS_FD));
+
1825 } else {
+
1826 if (!(bufv.buf[0].flags & FUSE_BUF_IS_FD))
+
1827 bufv.buf[0].mem = PARAM(arg);
+
1828
+
1829 bufv.buf[0].size -= sizeof(struct fuse_in_header) +
+
1830 sizeof(struct fuse_write_in);
+
1831 }
+
1832 if (bufv.buf[0].size < arg->size) {
+
1833 fuse_log(FUSE_LOG_ERR,
+
1834 "fuse: %s: buffer size too small\n", __func__);
+
1835 fuse_reply_err(req, EIO);
+
1836 goto out;
+
1837 }
+
1838 bufv.buf[0].size = arg->size;
+
1839
+
1840 _do_write_buf(req, nodeid, inarg, &bufv);
+
1841
+
1842out:
+
1843 /* Need to reset the pipe if ->write_buf() didn't consume all data */
+
1844 if ((ibuf->flags & FUSE_BUF_IS_FD) && bufv.idx < bufv.count)
+
1845 fuse_ll_clear_pipe(se);
+
1846}
+
1847
+
1848static void _do_flush(fuse_req_t req, const fuse_ino_t nodeid,
+
1849 const void *op_in, const void *in_payload)
+
1850{
+
1851 (void)in_payload;
+
1852 struct fuse_flush_in *arg = (struct fuse_flush_in *)op_in;
+
1853 struct fuse_file_info fi;
+
1854
+
1855 memset(&fi, 0, sizeof(fi));
+
1856 fi.fh = arg->fh;
+
1857 fi.flush = 1;
+
1858 if (req->se->conn.proto_minor >= 7)
+
1859 fi.lock_owner = arg->lock_owner;
+
1860
+
1861 if (req->se->op.flush)
+
1862 req->se->op.flush(req, nodeid, &fi);
+
1863 else
+
1864 fuse_reply_err(req, ENOSYS);
+
1865}
+
1866
+
1867static void do_flush(fuse_req_t req, const fuse_ino_t nodeid, const void *inarg)
+
1868{
+
1869 _do_flush(req, nodeid, inarg, NULL);
+
1870}
+
1871
+
1872static void _do_release(fuse_req_t req, const fuse_ino_t nodeid,
+
1873 const void *op_in, const void *in_payload)
+
1874{
+
1875 (void)in_payload;
+
1876 const struct fuse_release_in *arg = op_in;
+
1877 struct fuse_file_info fi;
+
1878
+
1879 memset(&fi, 0, sizeof(fi));
+
1880 fi.flags = arg->flags;
+
1881 fi.fh = arg->fh;
+
1882 if (req->se->conn.proto_minor >= 8) {
+
1883 fi.flush = (arg->release_flags & FUSE_RELEASE_FLUSH) ? 1 : 0;
+
1884 fi.lock_owner = arg->lock_owner;
+
1885 }
+
1886 if (arg->release_flags & FUSE_RELEASE_FLOCK_UNLOCK) {
+
1887 fi.flock_release = 1;
+
1888 fi.lock_owner = arg->lock_owner;
+
1889 }
+
1890
+
1891 if (req->se->op.release)
+
1892 req->se->op.release(req, nodeid, &fi);
+
1893 else
+
1894 fuse_reply_err(req, 0);
+
1895}
+
1896
+
1897static void do_release(fuse_req_t req, const fuse_ino_t nodeid,
+
1898 const void *inarg)
+
1899{
+
1900 _do_release(req, nodeid, inarg, NULL);
+
1901}
+
1902
+
1903static void _do_fsync(fuse_req_t req, const fuse_ino_t nodeid,
+
1904 const void *op_in, const void *in_payload)
+
1905{
+
1906 (void)in_payload;
+
1907 const struct fuse_fsync_in *arg = op_in;
+
1908 struct fuse_file_info fi;
+
1909 int datasync = arg->fsync_flags & 1;
+
1910
+
1911 memset(&fi, 0, sizeof(fi));
+
1912 fi.fh = arg->fh;
+
1913
+
1914 if (req->se->op.fsync)
+
1915 req->se->op.fsync(req, nodeid, datasync, &fi);
+
1916 else
+
1917 fuse_reply_err(req, ENOSYS);
+
1918}
+
1919
+
1920static void do_fsync(fuse_req_t req, const fuse_ino_t nodeid, const void *inarg)
+
1921{
+
1922 _do_fsync(req, nodeid, inarg, NULL);
+
1923}
+
1924
+
1925static void _do_opendir(fuse_req_t req, const fuse_ino_t nodeid,
+
1926 const void *op_in, const void *in_payload)
+
1927{
+
1928 (void)in_payload;
+
1929 const struct fuse_open_in *arg = op_in;
+
1930 struct fuse_file_info fi;
+
1931
+
1932 memset(&fi, 0, sizeof(fi));
+
1933 fi.flags = arg->flags;
+
1934 /* XXX: fuse_open_in::open_flags */
+
1935
+
1936 if (req->se->op.opendir)
+
1937 req->se->op.opendir(req, nodeid, &fi);
+
1938 else if (req->se->conn.want_ext & FUSE_CAP_NO_OPENDIR_SUPPORT)
+
1939 fuse_reply_err(req, ENOSYS);
+
1940 else
+
1941 fuse_reply_open(req, &fi);
+
1942}
+
1943
+
1944static void do_opendir(fuse_req_t req, const fuse_ino_t nodeid,
+
1945 const void *inarg)
+
1946{
+
1947 _do_opendir(req, nodeid, inarg, NULL);
+
1948}
+
1949
+
1950static void _do_readdir(fuse_req_t req, const fuse_ino_t nodeid,
+
1951 const void *op_in, const void *in_payload)
+
1952{
+
1953 (void)in_payload;
+
1954 struct fuse_read_in *arg = (struct fuse_read_in *)op_in;
+
1955 struct fuse_file_info fi;
+
1956
+
1957 memset(&fi, 0, sizeof(fi));
+
1958 fi.fh = arg->fh;
+
1959
+
1960 if (req->se->op.readdir)
+
1961 req->se->op.readdir(req, nodeid, arg->size, arg->offset, &fi);
+
1962 else
+
1963 fuse_reply_err(req, ENOSYS);
+
1964}
+
1965
+
1966static void do_readdir(fuse_req_t req, const fuse_ino_t nodeid,
+
1967 const void *inarg)
+
1968{
+
1969 _do_readdir(req, nodeid, inarg, NULL);
+
1970}
+
1971
+
1972static void _do_readdirplus(fuse_req_t req, const fuse_ino_t nodeid,
+
1973 const void *op_in, const void *in_payload)
+
1974{
+
1975 (void)in_payload;
+
1976 struct fuse_read_in *arg = (struct fuse_read_in *)op_in;
+
1977 struct fuse_file_info fi;
+
1978
+
1979 memset(&fi, 0, sizeof(fi));
+
1980 fi.fh = arg->fh;
+
1981
+
1982 if (req->se->op.readdirplus)
+
1983 req->se->op.readdirplus(req, nodeid, arg->size, arg->offset, &fi);
+
1984 else
+
1985 fuse_reply_err(req, ENOSYS);
+
1986}
+
1987
+
1988static void do_readdirplus(fuse_req_t req, const fuse_ino_t nodeid,
+
1989 const void *inarg)
+
1990{
+
1991 _do_readdirplus(req, nodeid, inarg, NULL);
+
1992}
+
1993
+
1994static void _do_releasedir(fuse_req_t req, const fuse_ino_t nodeid,
+
1995 const void *op_in, const void *in_payload)
+
1996{
+
1997 (void)in_payload;
+
1998 struct fuse_release_in *arg = (struct fuse_release_in *)op_in;
+
1999 struct fuse_file_info fi;
+
2000
+
2001 memset(&fi, 0, sizeof(fi));
+
2002 fi.flags = arg->flags;
+
2003 fi.fh = arg->fh;
+
2004
+
2005 if (req->se->op.releasedir)
+
2006 req->se->op.releasedir(req, nodeid, &fi);
+
2007 else
+
2008 fuse_reply_err(req, 0);
+
2009}
+
2010
+
2011static void do_releasedir(fuse_req_t req, const fuse_ino_t nodeid,
+
2012 const void *inarg)
+
2013{
+
2014 _do_releasedir(req, nodeid, inarg, NULL);
+
2015}
+
2016
+
2017static void _do_fsyncdir(fuse_req_t req, const fuse_ino_t nodeid,
+
2018 const void *op_in, const void *in_payload)
+
2019{
+
2020 (void)in_payload;
+
2021 struct fuse_fsync_in *arg = (struct fuse_fsync_in *)op_in;
+
2022 struct fuse_file_info fi;
+
2023 int datasync = arg->fsync_flags & 1;
+
2024
+
2025 memset(&fi, 0, sizeof(fi));
+
2026 fi.fh = arg->fh;
+
2027
+
2028 if (req->se->op.fsyncdir)
+
2029 req->se->op.fsyncdir(req, nodeid, datasync, &fi);
+
2030 else
+
2031 fuse_reply_err(req, ENOSYS);
+
2032}
+
2033static void do_fsyncdir(fuse_req_t req, const fuse_ino_t nodeid,
+
2034 const void *inarg)
+
2035{
+
2036 _do_fsyncdir(req, nodeid, inarg, NULL);
+
2037}
+
2038
+
2039static void _do_statfs(fuse_req_t req, const fuse_ino_t nodeid,
+
2040 const void *op_in, const void *in_payload)
+
2041{
+
2042 (void) nodeid;
+
2043 (void)op_in;
+
2044 (void)in_payload;
+
2045
+
2046 if (req->se->op.statfs)
+
2047 req->se->op.statfs(req, nodeid);
+
2048 else {
+
2049 struct statvfs buf = {
+
2050 .f_namemax = 255,
+
2051 .f_bsize = 512,
+
2052 };
+
2053 fuse_reply_statfs(req, &buf);
+
2054 }
+
2055}
+
2056static void do_statfs(fuse_req_t req, const fuse_ino_t nodeid,
+
2057 const void *inarg)
+
2058{
+
2059 _do_statfs(req, nodeid, inarg, NULL);
+
2060}
+
2061
+
2062static void _do_setxattr(fuse_req_t req, const fuse_ino_t nodeid,
+
2063 const void *op_in, const void *in_payload)
+
2064{
+
2065 struct fuse_setxattr_in *arg = (struct fuse_setxattr_in *)op_in;
+
2066 const char *name = in_payload;
+
2067 const char *value = name + strlen(name) + 1;
+
2068
+
2069 /* XXX:The API should be extended to support extra_flags/setxattr_flags */
+
2070
+
2071 if (req->se->op.setxattr)
+
2072 req->se->op.setxattr(req, nodeid, name, value, arg->size,
+
2073 arg->flags);
+
2074 else
+
2075 fuse_reply_err(req, ENOSYS);
+
2076}
+
2077static void do_setxattr(fuse_req_t req, const fuse_ino_t nodeid,
+
2078 const void *inarg)
+
2079{
+
2080 struct fuse_session *se = req->se;
+
2081 unsigned int xattr_ext = !!(se->conn.want & FUSE_CAP_SETXATTR_EXT);
+
2082 const struct fuse_setxattr_in *arg = inarg;
+
2083 char *payload = xattr_ext ? PARAM(arg) :
+
2084 (char *)arg + FUSE_COMPAT_SETXATTR_IN_SIZE;
+
2085
+
2086 _do_setxattr(req, nodeid, arg, payload);
+
2087}
+
2088
+
2089static void _do_getxattr(fuse_req_t req, const fuse_ino_t nodeid,
+
2090 const void *op_in, const void *in_payload)
+
2091{
+
2092 const struct fuse_getxattr_in *arg = op_in;
+
2093
+
2094 if (req->se->op.getxattr)
+
2095 req->se->op.getxattr(req, nodeid, in_payload, arg->size);
+
2096 else
+
2097 fuse_reply_err(req, ENOSYS);
+
2098}
+
2099
+
2100static void do_getxattr(fuse_req_t req, const fuse_ino_t nodeid,
+
2101 const void *inarg)
+
2102{
+
2103 const struct fuse_getxattr_in *arg = inarg;
+
2104 const void *payload = PARAM(arg);
+
2105
+
2106 _do_getxattr(req, nodeid, arg, payload);
+
2107}
+
2108
+
2109static void _do_listxattr(fuse_req_t req, const fuse_ino_t nodeid,
+
2110 const void *inarg, const void *in_payload)
+
2111{
+
2112 (void)in_payload;
+
2113 const struct fuse_getxattr_in *arg = inarg;
+
2114
+
2115 if (req->se->op.listxattr)
+
2116 req->se->op.listxattr(req, nodeid, arg->size);
+
2117 else
+
2118 fuse_reply_err(req, ENOSYS);
+
2119}
+
2120static void do_listxattr(fuse_req_t req, const fuse_ino_t nodeid,
+
2121 const void *inarg)
+
2122{
+
2123 _do_listxattr(req, nodeid, inarg, NULL);
+
2124}
+
2125
+
2126static void _do_removexattr(fuse_req_t req, const fuse_ino_t nodeid,
+
2127 const void *inarg, const void *in_payload)
+
2128{
+
2129 (void)inarg;
+
2130 const char *name = in_payload;
+
2131
+
2132 if (req->se->op.removexattr)
+
2133 req->se->op.removexattr(req, nodeid, name);
+
2134 else
+
2135 fuse_reply_err(req, ENOSYS);
+
2136}
+
2137static void do_removexattr(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
2138{
+
2139 _do_removexattr(req, nodeid, NULL, inarg);
+
2140}
+
2141
+
2142static void convert_fuse_file_lock(const struct fuse_file_lock *fl,
+
2143 struct flock *flock)
+
2144{
+
2145 memset(flock, 0, sizeof(struct flock));
+
2146 flock->l_type = fl->type;
+
2147 flock->l_whence = SEEK_SET;
+
2148 flock->l_start = fl->start;
+
2149 if (fl->end == OFFSET_MAX)
+
2150 flock->l_len = 0;
+
2151 else
+
2152 flock->l_len = fl->end - fl->start + 1;
+
2153 flock->l_pid = fl->pid;
+
2154}
+
2155
+
2156static void _do_getlk(fuse_req_t req, const fuse_ino_t nodeid,
+
2157 const void *op_in, const void *in_payload)
+
2158{
+
2159 (void)in_payload;
+
2160 const struct fuse_lk_in *arg = op_in;
+
2161 struct fuse_file_info fi;
+
2162 struct flock flock;
+
2163
+
2164 memset(&fi, 0, sizeof(fi));
+
2165 fi.fh = arg->fh;
+
2166 fi.lock_owner = arg->owner;
+
2167
+
2168 convert_fuse_file_lock(&arg->lk, &flock);
+
2169 if (req->se->op.getlk)
+
2170 req->se->op.getlk(req, nodeid, &fi, &flock);
+
2171 else
+
2172 fuse_reply_err(req, ENOSYS);
+
2173}
+
2174static void do_getlk(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
2175{
+
2176 _do_getlk(req, nodeid, inarg, NULL);
+
2177}
+
2178
+
2179static void do_setlk_common(fuse_req_t req, const fuse_ino_t nodeid,
+
2180 const void *op_in, int sleep)
+
2181{
+
2182 const struct fuse_lk_in *arg = op_in;
+
2183 struct fuse_file_info fi;
+
2184 struct flock flock;
+
2185
+
2186 memset(&fi, 0, sizeof(fi));
+
2187 fi.fh = arg->fh;
+
2188 fi.lock_owner = arg->owner;
+
2189
+
2190 if (arg->lk_flags & FUSE_LK_FLOCK) {
+
2191 int op = 0;
+
2192
+
2193 switch (arg->lk.type) {
+
2194 case F_RDLCK:
+
2195 op = LOCK_SH;
+
2196 break;
+
2197 case F_WRLCK:
+
2198 op = LOCK_EX;
+
2199 break;
+
2200 case F_UNLCK:
+
2201 op = LOCK_UN;
+
2202 break;
+
2203 }
+
2204 if (!sleep)
+
2205 op |= LOCK_NB;
+
2206
+
2207 if (req->se->op.flock)
+
2208 req->se->op.flock(req, nodeid, &fi, op);
+
2209 else
+
2210 fuse_reply_err(req, ENOSYS);
+
2211 } else {
+
2212 convert_fuse_file_lock(&arg->lk, &flock);
+
2213 if (req->se->op.setlk)
+
2214 req->se->op.setlk(req, nodeid, &fi, &flock, sleep);
+
2215 else
+
2216 fuse_reply_err(req, ENOSYS);
+
2217 }
+
2218}
+
2219
+
2220static void _do_setlk(fuse_req_t req, const fuse_ino_t nodeid,
+
2221 const void *op_in, const void *in_payload)
+
2222{
+
2223 (void)in_payload;
+
2224 do_setlk_common(req, nodeid, op_in, 0);
+
2225}
+
2226
+
2227static void do_setlk(fuse_req_t req, const fuse_ino_t nodeid, const void *inarg)
+
2228{
+
2229 _do_setlk(req, nodeid, inarg, NULL);
+
2230}
+
2231
+
2232static void _do_setlkw(fuse_req_t req, const fuse_ino_t nodeid,
+
2233 const void *op_in, const void *in_payload)
+
2234{
+
2235 (void)in_payload;
+
2236 do_setlk_common(req, nodeid, op_in, 1);
+
2237}
+
2238static void do_setlkw(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
2239{
+
2240 _do_setlkw(req, nodeid, inarg, NULL);
+
2241}
+
2242
+
2243static int find_interrupted(struct fuse_session *se, struct fuse_req *req)
+
2244{
+
2245 struct fuse_req *curr;
+
2246
+
2247 for (curr = se->list.next; curr != &se->list; curr = curr->next) {
+
2248 if (curr->unique == req->u.i.unique) {
+ +
2250 void *data;
+
2251
+
2252 curr->ref_cnt++;
+
2253 pthread_mutex_unlock(&se->lock);
+
2254
+
2255 /* Ugh, ugly locking */
+
2256 pthread_mutex_lock(&curr->lock);
+
2257 pthread_mutex_lock(&se->lock);
+
2258 curr->interrupted = 1;
+
2259 func = curr->u.ni.func;
+
2260 data = curr->u.ni.data;
+
2261 pthread_mutex_unlock(&se->lock);
+
2262 if (func)
+
2263 func(curr, data);
+
2264 pthread_mutex_unlock(&curr->lock);
+
2265
+
2266 pthread_mutex_lock(&se->lock);
+
2267 curr->ref_cnt--;
+
2268 if (!curr->ref_cnt) {
+
2269 destroy_req(curr);
+
2270 }
+
2271
+
2272 return 1;
+
2273 }
+
2274 }
+
2275 for (curr = se->interrupts.next; curr != &se->interrupts;
+
2276 curr = curr->next) {
+
2277 if (curr->u.i.unique == req->u.i.unique)
+
2278 return 1;
+
2279 }
+
2280 return 0;
+
2281}
+
2282
+
2283static void _do_interrupt(fuse_req_t req, const fuse_ino_t nodeid,
+
2284 const void *op_in, const void *in_payload)
+
2285{
+
2286 (void)in_payload;
+
2287 const struct fuse_interrupt_in *arg = op_in;
+
2288 struct fuse_session *se = req->se;
+
2289
+
2290 (void) nodeid;
+
2291 if (se->debug)
+
2292 fuse_log(FUSE_LOG_DEBUG, "INTERRUPT: %llu\n",
+
2293 (unsigned long long) arg->unique);
+
2294
+
2295 req->u.i.unique = arg->unique;
+
2296
+
2297 pthread_mutex_lock(&se->lock);
+
2298 if (find_interrupted(se, req)) {
+
2299 fuse_chan_put(req->ch);
+
2300 req->ch = NULL;
+
2301 destroy_req(req);
+
2302 } else
+
2303 list_add_req(req, &se->interrupts);
+
2304 pthread_mutex_unlock(&se->lock);
+
2305}
+
2306static void do_interrupt(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
2307{
+
2308 _do_interrupt(req, nodeid, inarg, NULL);
+
2309}
+
2310
+
2311static struct fuse_req *check_interrupt(struct fuse_session *se,
+
2312 struct fuse_req *req)
+
2313{
+
2314 struct fuse_req *curr;
+
2315
+
2316 for (curr = se->interrupts.next; curr != &se->interrupts;
+
2317 curr = curr->next) {
+
2318 if (curr->u.i.unique == req->unique) {
+
2319 req->interrupted = 1;
+
2320 list_del_req(curr);
+
2321 fuse_chan_put(curr->ch);
+
2322 curr->ch = NULL;
+
2323 destroy_req(curr);
+
2324 return NULL;
+
2325 }
+
2326 }
+
2327 curr = se->interrupts.next;
+
2328 if (curr != &se->interrupts) {
+
2329 list_del_req(curr);
+
2330 list_init_req(curr);
+
2331 return curr;
+
2332 } else
+
2333 return NULL;
+
2334}
+
2335
+
2336static void _do_bmap(fuse_req_t req, const fuse_ino_t nodeid, const void *op_in,
+
2337 const void *in_payload)
+
2338{
+
2339 (void)in_payload;
+
2340 const struct fuse_bmap_in *arg = op_in;
+
2341
+
2342 if (req->se->op.bmap)
+
2343 req->se->op.bmap(req, nodeid, arg->blocksize, arg->block);
+
2344 else
+
2345 fuse_reply_err(req, ENOSYS);
+
2346}
+
2347static void do_bmap(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
2348{
+
2349 _do_bmap(req, nodeid, inarg, NULL);
+
2350}
+
2351
+
2352static void _do_ioctl(fuse_req_t req, const fuse_ino_t nodeid,
+
2353 const void *op_in, const void *in_payload)
+
2354{
+
2355 struct fuse_ioctl_in *arg = (struct fuse_ioctl_in *)op_in;
+
2356 unsigned int flags = arg->flags;
+
2357 const void *in_buf = in_payload;
+
2358 struct fuse_file_info fi;
+
2359
+
2360 if (flags & FUSE_IOCTL_DIR &&
+
2361 !(req->se->conn.want_ext & FUSE_CAP_IOCTL_DIR)) {
+
2362 fuse_reply_err(req, ENOTTY);
+
2363 return;
+
2364 }
+
2365
+
2366 memset(&fi, 0, sizeof(fi));
+
2367 fi.fh = arg->fh;
+
2368
+
2369 if (sizeof(void *) == 4 && req->se->conn.proto_minor >= 16 &&
+
2370 !(flags & FUSE_IOCTL_32BIT)) {
+
2371 req->flags.ioctl_64bit = 1;
+
2372 }
+
2373
+
2374 if (req->se->op.ioctl)
+
2375 req->se->op.ioctl(req, nodeid, arg->cmd,
+
2376 (void *)(uintptr_t)arg->arg, &fi, flags,
+
2377 in_buf, arg->in_size, arg->out_size);
+
2378 else
+
2379 fuse_reply_err(req, ENOSYS);
+
2380}
+
2381static void do_ioctl(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
2382{
+
2383 const struct fuse_ioctl_in *arg = inarg;
+
2384 void *in_buf = arg->in_size ? PARAM(arg) : NULL;
+
2385
+
2386 _do_ioctl(req, nodeid, arg, in_buf);
+
2387}
+
2388
+
+
2389void fuse_pollhandle_destroy(struct fuse_pollhandle *ph)
+
2390{
+
2391 free(ph);
+
2392}
+
+
2393
+
2394static void _do_poll(fuse_req_t req, const fuse_ino_t nodeid, const void *op_in,
+
2395 const void *in_payload)
+
2396{
+
2397 (void)in_payload;
+
2398 struct fuse_poll_in *arg = (struct fuse_poll_in *)op_in;
+
2399 struct fuse_file_info fi;
+
2400
+
2401 memset(&fi, 0, sizeof(fi));
+
2402 fi.fh = arg->fh;
+
2403 fi.poll_events = arg->events;
+
2404
+
2405 if (req->se->op.poll) {
+
2406 struct fuse_pollhandle *ph = NULL;
+
2407
+
2408 if (arg->flags & FUSE_POLL_SCHEDULE_NOTIFY) {
+
2409 ph = malloc(sizeof(struct fuse_pollhandle));
+
2410 if (ph == NULL) {
+
2411 fuse_reply_err(req, ENOMEM);
+
2412 return;
+
2413 }
+
2414 ph->kh = arg->kh;
+
2415 ph->se = req->se;
+
2416 }
+
2417
+
2418 req->se->op.poll(req, nodeid, &fi, ph);
+
2419 } else {
+
2420 fuse_reply_err(req, ENOSYS);
+
2421 }
+
2422}
+
2423
+
2424static void do_poll(fuse_req_t req, const fuse_ino_t nodeid, const void *inarg)
+
2425{
+
2426 _do_poll(req, nodeid, inarg, NULL);
+
2427}
+
2428
+
2429static void _do_fallocate(fuse_req_t req, const fuse_ino_t nodeid,
+
2430 const void *op_in, const void *in_payload)
+
2431{
+
2432 (void)in_payload;
+
2433 const struct fuse_fallocate_in *arg = op_in;
+
2434 struct fuse_file_info fi;
+
2435
+
2436 memset(&fi, 0, sizeof(fi));
+
2437 fi.fh = arg->fh;
+
2438
+
2439 if (req->se->op.fallocate)
+
2440 req->se->op.fallocate(req, nodeid, arg->mode, arg->offset,
+
2441 arg->length, &fi);
+
2442 else
+
2443 fuse_reply_err(req, ENOSYS);
+
2444}
+
2445
+
2446static void do_fallocate(fuse_req_t req, const fuse_ino_t nodeid,
+
2447 const void *inarg)
+
2448{
+
2449 _do_fallocate(req, nodeid, inarg, NULL);
+
2450}
+
2451
+
2452static void copy_file_range_common(fuse_req_t req, const fuse_ino_t nodeid_in,
+
2453 const struct fuse_copy_file_range_in *arg)
+
2454{
+
2455 struct fuse_file_info fi_in, fi_out;
+
2456
+
2457 memset(&fi_in, 0, sizeof(fi_in));
+
2458 fi_in.fh = arg->fh_in;
+
2459
+
2460 memset(&fi_out, 0, sizeof(fi_out));
+
2461 fi_out.fh = arg->fh_out;
+
2462
+
2463 if (req->se->op.copy_file_range)
+
2464 req->se->op.copy_file_range(req, nodeid_in, arg->off_in, &fi_in,
+
2465 arg->nodeid_out, arg->off_out,
+
2466 &fi_out, arg->len, arg->flags);
+
2467 else
+
2468 fuse_reply_err(req, ENOSYS);
+
2469}
+
2470
+
2471static void _do_copy_file_range(fuse_req_t req, const fuse_ino_t nodeid_in,
+
2472 const void *op_in, const void *in_payload)
+
2473{
+
2474 const struct fuse_copy_file_range_in *arg = op_in;
+
2475 struct fuse_copy_file_range_in arg_tmp;
+
2476
+
2477 (void) in_payload;
+
2478 /* fuse_write_out can only handle 32bit copy size */
+
2479 if (arg->len > 0xfffff000) {
+
2480 arg_tmp = *arg;
+
2481 arg_tmp.len = 0xfffff000;
+
2482 arg = &arg_tmp;
+
2483 }
+
2484 copy_file_range_common(req, nodeid_in, arg);
+
2485}
+
2486
+
2487static void do_copy_file_range(fuse_req_t req, const fuse_ino_t nodeid_in,
+
2488 const void *inarg)
+
2489{
+
2490 _do_copy_file_range(req, nodeid_in, inarg, NULL);
+
2491}
+
2492
+
2493static void _do_copy_file_range_64(fuse_req_t req, const fuse_ino_t nodeid_in,
+
2494 const void *op_in, const void *in_payload)
+
2495{
+
2496 (void) in_payload;
+
2497 req->flags.is_copy_file_range_64 = 1;
+
2498 /* Limit size on 32bit userspace to avoid conversion overflow */
+
2499 if (sizeof(size_t) == 4)
+
2500 _do_copy_file_range(req, nodeid_in, op_in, NULL);
+
2501 else
+
2502 copy_file_range_common(req, nodeid_in, op_in);
+
2503}
+
2504
+
2505static void do_copy_file_range_64(fuse_req_t req, const fuse_ino_t nodeid_in,
+
2506 const void *inarg)
+
2507{
+
2508 _do_copy_file_range_64(req, nodeid_in, inarg, NULL);
+
2509}
+
2510
+
2511/*
+
2512 * Note that the uint64_t offset in struct fuse_lseek_in is derived from
+
2513 * linux kernel loff_t and is therefore signed.
+
2514 */
+
2515static void _do_lseek(fuse_req_t req, const fuse_ino_t nodeid,
+
2516 const void *op_in, const void *in_payload)
+
2517{
+
2518 (void)in_payload;
+
2519 const struct fuse_lseek_in *arg = op_in;
+
2520 struct fuse_file_info fi;
+
2521
+
2522 memset(&fi, 0, sizeof(fi));
+
2523 fi.fh = arg->fh;
+
2524
+
2525 if (req->se->op.lseek)
+
2526 req->se->op.lseek(req, nodeid, arg->offset, arg->whence, &fi);
+
2527 else
+
2528 fuse_reply_err(req, ENOSYS);
+
2529}
+
2530
+
2531static void do_lseek(fuse_req_t req, const fuse_ino_t nodeid, const void *inarg)
+
2532{
+
2533 _do_lseek(req, nodeid, inarg, NULL);
+
2534}
+
2535
+
2536#ifdef HAVE_STATX
+
2537static void _do_statx(fuse_req_t req, const fuse_ino_t nodeid,
+
2538 const void *op_in, const void *in_payload)
+
2539{
+
2540 (void)in_payload;
+
2541 const struct fuse_statx_in *arg = op_in;
+
2542 struct fuse_file_info *fip = NULL;
+
2543 struct fuse_file_info fi;
+
2544
+
2545 if (arg->getattr_flags & FUSE_GETATTR_FH) {
+
2546 memset(&fi, 0, sizeof(fi));
+
2547 fi.fh = arg->fh;
+
2548 fip = &fi;
+
2549 }
+
2550
+
2551 if (req->se->op.statx)
+
2552 req->se->op.statx(req, nodeid, arg->sx_flags, arg->sx_mask, fip);
+
2553 else
+
2554 fuse_reply_err(req, ENOSYS);
+
2555}
+
2556#else
+
2557static void _do_statx(fuse_req_t req, const fuse_ino_t nodeid,
+
2558 const void *op_in, const void *in_payload)
+
2559{
+
2560 (void)in_payload;
+
2561 (void)nodeid;
+
2562 (void)op_in;
+
2563 fuse_reply_err(req, ENOSYS);
+
2564}
+
2565#endif
+
2566
+
2567static void do_statx(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
2568{
+
2569 _do_statx(req, nodeid, inarg, NULL);
+
2570}
+
2571
+
2572static bool want_flags_valid(uint64_t capable, uint64_t want)
+
2573{
+
2574 uint64_t unknown_flags = want & (~capable);
+
2575 if (unknown_flags != 0) {
+
2576 fuse_log(FUSE_LOG_ERR,
+
2577 "fuse: unknown connection 'want' flags: 0x%08llx\n",
+
2578 (unsigned long long)unknown_flags);
+
2579 return false;
+
2580 }
+
2581 return true;
+
2582}
+
2583
+
+ +
2588{
+
2589 struct fuse_session *se = container_of(conn, struct fuse_session, conn);
+
2590
+
2591 /*
+
2592 * Convert want to want_ext if necessary.
+
2593 * For the high level interface this function might be called
+
2594 * twice, once from the high level interface and once from the
+
2595 * low level interface. Both, with different want_ext_default and
+
2596 * want_default values. In order to suppress a failure for the
+
2597 * second call, we check if the lower 32 bits of want_ext are
+
2598 * already set to the value of want.
+
2599 */
+
2600 if (conn->want != se->conn_want &&
+
2601 fuse_lower_32_bits(conn->want_ext) != conn->want) {
+
2602 if (conn->want_ext != se->conn_want_ext) {
+
2603 fuse_log(FUSE_LOG_ERR,
+
2604 "%s: Both conn->want_ext and conn->want are set.\n"
+
2605 "want=%x want_ext=%llx, se->want=%x se->want_ext=%llx\n",
+
2606 __func__, conn->want,
+
2607 (unsigned long long)conn->want_ext,
+
2608 se->conn_want,
+
2609 (unsigned long long)se->conn_want_ext);
+
2610 return -EINVAL;
+
2611 }
+
2612
+
2613 /* high bits from want_ext, low bits from want */
+
2614 conn->want_ext = fuse_higher_32_bits(conn->want_ext) |
+
2615 conn->want;
+
2616 }
+
2617
+
2618 /* ensure there won't be a second conversion */
+
2619 conn->want = fuse_lower_32_bits(conn->want_ext);
+
2620
+
2621 return 0;
+
2622}
+
+
2623
+
+ +
2625 uint64_t flag)
+
2626{
+
2627 struct fuse_session *se = container_of(conn, struct fuse_session, conn);
+
2628
+
2629 if (conn->capable_ext & flag) {
+
2630 conn->want_ext |= flag;
+
2631 se->conn_want_ext |= flag;
+
2632 conn->want |= flag;
+
2633 se->conn_want |= flag;
+
2634 return true;
+
2635 }
+
2636 return false;
+
2637}
+
+
2638
+
+ +
2640 uint64_t flag)
+
2641{
+
2642 struct fuse_session *se = container_of(conn, struct fuse_session, conn);
+
2643
+
2644 conn->want_ext &= ~flag;
+
2645 se->conn_want_ext &= ~flag;
+
2646 conn->want &= ~flag;
+
2647 se->conn_want &= ~flag;
+
2648}
+
+
2649
+
+ +
2651 uint64_t flag)
+
2652{
+
2653 return conn->capable_ext & flag ? true : false;
+
2654}
+
+
2655
+
2656/* Prevent bogus data races (bogus since "init" is called before
+
2657 * multi-threading becomes relevant */
+
2658static __attribute__((no_sanitize("thread"))) void
+
2659_do_init(fuse_req_t req, const fuse_ino_t nodeid, const void *op_in,
+
2660 const void *in_payload)
+
2661{
+
2662 (void)in_payload;
+
2663 const struct fuse_init_in *arg = op_in;
+
2664 struct fuse_init_out outarg;
+
2665 struct fuse_session *se = req->se;
+
2666 size_t bufsize = se->bufsize;
+
2667 size_t outargsize = sizeof(outarg);
+
2668 uint64_t inargflags = 0;
+
2669 uint64_t outargflags = 0;
+
2670 bool buf_reallocable = se->buf_reallocable;
+
2671 (void) nodeid;
+
2672 bool enable_io_uring = false;
+
2673
+
2674 if (se->debug) {
+
2675 fuse_log(FUSE_LOG_DEBUG, "INIT: %u.%u\n", arg->major, arg->minor);
+
2676 if (arg->major == 7 && arg->minor >= 6) {
+
2677 fuse_log(FUSE_LOG_DEBUG, "flags=0x%08x\n", arg->flags);
+
2678 fuse_log(FUSE_LOG_DEBUG, "max_readahead=0x%08x\n",
+
2679 arg->max_readahead);
+
2680 }
+
2681 }
+
2682 se->conn.proto_major = arg->major;
+
2683 se->conn.proto_minor = arg->minor;
+
2684 se->conn.capable_ext = 0;
+
2685 se->conn.want_ext = 0;
+
2686
+
2687 memset(&outarg, 0, sizeof(outarg));
+
2688 outarg.major = FUSE_KERNEL_VERSION;
+
2689 outarg.minor = FUSE_KERNEL_MINOR_VERSION;
+
2690
+
2691 if (arg->major < 7) {
+
2692 fuse_log(FUSE_LOG_ERR, "fuse: unsupported protocol version: %u.%u\n",
+
2693 arg->major, arg->minor);
+
2694 fuse_reply_err(req, EPROTO);
+
2695 return;
+
2696 }
+
2697
+
2698 if (arg->major > 7) {
+
2699 /* Wait for a second INIT request with a 7.X version */
+
2700 send_reply_ok(req, &outarg, sizeof(outarg));
+
2701 return;
+
2702 }
+
2703
+
2704 if (arg->minor >= 6) {
+
2705 if (arg->max_readahead < se->conn.max_readahead)
+
2706 se->conn.max_readahead = arg->max_readahead;
+
2707 inargflags = arg->flags;
+
2708 if (inargflags & FUSE_INIT_EXT)
+
2709 inargflags = inargflags | (uint64_t) arg->flags2 << 32;
+
2710 if (inargflags & FUSE_ASYNC_READ)
+
2711 se->conn.capable_ext |= FUSE_CAP_ASYNC_READ;
+
2712 if (inargflags & FUSE_POSIX_LOCKS)
+
2713 se->conn.capable_ext |= FUSE_CAP_POSIX_LOCKS;
+
2714 if (inargflags & FUSE_ATOMIC_O_TRUNC)
+
2715 se->conn.capable_ext |= FUSE_CAP_ATOMIC_O_TRUNC;
+
2716 if (inargflags & FUSE_EXPORT_SUPPORT)
+
2717 se->conn.capable_ext |= FUSE_CAP_EXPORT_SUPPORT;
+
2718 if (inargflags & FUSE_DONT_MASK)
+
2719 se->conn.capable_ext |= FUSE_CAP_DONT_MASK;
+
2720 if (inargflags & FUSE_FLOCK_LOCKS)
+
2721 se->conn.capable_ext |= FUSE_CAP_FLOCK_LOCKS;
+
2722 if (inargflags & FUSE_AUTO_INVAL_DATA)
+
2723 se->conn.capable_ext |= FUSE_CAP_AUTO_INVAL_DATA;
+
2724 if (inargflags & FUSE_DO_READDIRPLUS)
+
2725 se->conn.capable_ext |= FUSE_CAP_READDIRPLUS;
+
2726 if (inargflags & FUSE_READDIRPLUS_AUTO)
+
2727 se->conn.capable_ext |= FUSE_CAP_READDIRPLUS_AUTO;
+
2728 if (inargflags & FUSE_ASYNC_DIO)
+
2729 se->conn.capable_ext |= FUSE_CAP_ASYNC_DIO;
+
2730 if (inargflags & FUSE_WRITEBACK_CACHE)
+
2731 se->conn.capable_ext |= FUSE_CAP_WRITEBACK_CACHE;
+
2732 if (inargflags & FUSE_NO_OPEN_SUPPORT)
+
2733 se->conn.capable_ext |= FUSE_CAP_NO_OPEN_SUPPORT;
+
2734 if (inargflags & FUSE_PARALLEL_DIROPS)
+
2735 se->conn.capable_ext |= FUSE_CAP_PARALLEL_DIROPS;
+
2736 if (inargflags & FUSE_POSIX_ACL)
+
2737 se->conn.capable_ext |= FUSE_CAP_POSIX_ACL;
+
2738 if (inargflags & FUSE_HANDLE_KILLPRIV)
+
2739 se->conn.capable_ext |= FUSE_CAP_HANDLE_KILLPRIV;
+
2740 if (inargflags & FUSE_HANDLE_KILLPRIV_V2)
+
2741 se->conn.capable_ext |= FUSE_CAP_HANDLE_KILLPRIV_V2;
+
2742 if (inargflags & FUSE_CACHE_SYMLINKS)
+
2743 se->conn.capable_ext |= FUSE_CAP_CACHE_SYMLINKS;
+
2744 if (inargflags & FUSE_NO_OPENDIR_SUPPORT)
+
2745 se->conn.capable_ext |= FUSE_CAP_NO_OPENDIR_SUPPORT;
+
2746 if (inargflags & FUSE_EXPLICIT_INVAL_DATA)
+
2747 se->conn.capable_ext |= FUSE_CAP_EXPLICIT_INVAL_DATA;
+
2748 if (inargflags & FUSE_SETXATTR_EXT)
+
2749 se->conn.capable_ext |= FUSE_CAP_SETXATTR_EXT;
+
2750 if (!(inargflags & FUSE_MAX_PAGES)) {
+
2751 size_t max_bufsize =
+
2752 FUSE_DEFAULT_MAX_PAGES_PER_REQ * getpagesize()
+
2753 + FUSE_BUFFER_HEADER_SIZE;
+
2754 if (bufsize > max_bufsize) {
+
2755 bufsize = max_bufsize;
+
2756 }
+
2757 buf_reallocable = false;
+
2758 }
+
2759 if (inargflags & FUSE_DIRECT_IO_ALLOW_MMAP)
+
2760 se->conn.capable_ext |= FUSE_CAP_DIRECT_IO_ALLOW_MMAP;
+
2761 if (arg->minor >= 38 || (inargflags & FUSE_HAS_EXPIRE_ONLY))
+
2762 se->conn.capable_ext |= FUSE_CAP_EXPIRE_ONLY;
+
2763 if (inargflags & FUSE_PASSTHROUGH)
+
2764 se->conn.capable_ext |= FUSE_CAP_PASSTHROUGH;
+
2765 if (inargflags & FUSE_NO_EXPORT_SUPPORT)
+
2766 se->conn.capable_ext |= FUSE_CAP_NO_EXPORT_SUPPORT;
+
2767 if (inargflags & FUSE_OVER_IO_URING)
+
2768 se->conn.capable_ext |= FUSE_CAP_OVER_IO_URING;
+
2769
+
2770 } else {
+
2771 se->conn.max_readahead = 0;
+
2772 }
+
2773
+
2774 if (se->conn.proto_minor >= 14) {
+
2775#ifdef HAVE_SPLICE
+
2776#ifdef HAVE_VMSPLICE
+
2777 if ((se->io == NULL) || (se->io->splice_send != NULL)) {
+
2778 se->conn.capable_ext |= FUSE_CAP_SPLICE_WRITE |
+ +
2780 }
+
2781#endif
+
2782 if ((se->io == NULL) || (se->io->splice_receive != NULL)) {
+
2783 se->conn.capable_ext |= FUSE_CAP_SPLICE_READ;
+
2784 }
+
2785#endif
+
2786 }
+
2787 if (se->conn.proto_minor >= 18)
+
2788 se->conn.capable_ext |= FUSE_CAP_IOCTL_DIR;
+
2789
+
2790 /* Default settings for modern filesystems.
+
2791 *
+
2792 * Most of these capabilities were disabled by default in
+
2793 * libfuse2 for backwards compatibility reasons. In libfuse3,
+
2794 * we can finally enable them by default (as long as they're
+
2795 * supported by the kernel).
+
2796 */
+
2797#define LL_SET_DEFAULT(cond, cap) \
+
2798 if ((cond)) \
+
2799 fuse_set_feature_flag(&se->conn, cap)
+
2800
+
2801 LL_SET_DEFAULT(1, FUSE_CAP_ASYNC_READ);
+
2802 LL_SET_DEFAULT(1, FUSE_CAP_AUTO_INVAL_DATA);
+
2803 LL_SET_DEFAULT(1, FUSE_CAP_ASYNC_DIO);
+
2804 LL_SET_DEFAULT(1, FUSE_CAP_IOCTL_DIR);
+
2805 LL_SET_DEFAULT(1, FUSE_CAP_ATOMIC_O_TRUNC);
+
2806 LL_SET_DEFAULT(se->op.write_buf, FUSE_CAP_SPLICE_READ);
+
2807 LL_SET_DEFAULT(se->op.getlk && se->op.setlk,
+ +
2809 LL_SET_DEFAULT(se->op.flock, FUSE_CAP_FLOCK_LOCKS);
+
2810 LL_SET_DEFAULT(se->op.readdirplus, FUSE_CAP_READDIRPLUS);
+
2811 LL_SET_DEFAULT(se->op.readdirplus && se->op.readdir,
+ +
2813 LL_SET_DEFAULT(1, FUSE_CAP_OVER_IO_URING);
+
2814
+
2815 /* This could safely become default, but libfuse needs an API extension
+
2816 * to support it
+
2817 * LL_SET_DEFAULT(1, FUSE_CAP_SETXATTR_EXT);
+
2818 */
+
2819
+
2820 se->conn.time_gran = 1;
+
2821
+
2822 if (se->op.init) {
+
2823 // Apply the first 32 bits of capable_ext to capable
+
2824 se->conn.capable = fuse_lower_32_bits(se->conn.capable_ext);
+
2825
+
2826 se->op.init(se->userdata, &se->conn);
+
2827
+
2828 /*
+
2829 * se->conn.want is 32-bit value and deprecated in favour of
+
2830 * se->conn.want_ext
+
2831 * Userspace might still use conn.want - we need to convert it
+
2832 */
+ +
2834 }
+
2835
+
2836 if (!want_flags_valid(se->conn.capable_ext, se->conn.want_ext)) {
+
2837 fuse_reply_err(req, EPROTO);
+
2838 se->error = -EPROTO;
+ +
2840 return;
+
2841 }
+
2842
+
2843 unsigned max_read_mo = get_max_read(se->mo);
+
2844 if (se->conn.max_read != max_read_mo) {
+
2845 fuse_log(FUSE_LOG_ERR, "fuse: error: init() and fuse_session_new() "
+
2846 "requested different maximum read size (%u vs %u)\n",
+
2847 se->conn.max_read, max_read_mo);
+
2848 fuse_reply_err(req, EPROTO);
+
2849 se->error = -EPROTO;
+ +
2851 return;
+
2852 }
+
2853
+
2854 if (bufsize < FUSE_MIN_READ_BUFFER) {
+
2855 fuse_log(FUSE_LOG_ERR,
+
2856 "fuse: warning: buffer size too small: %zu\n",
+
2857 bufsize);
+
2858 bufsize = FUSE_MIN_READ_BUFFER;
+
2859 }
+
2860
+
2861 if (buf_reallocable)
+
2862 bufsize = UINT_MAX;
+
2863 se->conn.max_write = MIN(se->conn.max_write, bufsize - FUSE_BUFFER_HEADER_SIZE);
+
2864 se->bufsize = se->conn.max_write + FUSE_BUFFER_HEADER_SIZE;
+
2865
+
2866 if (arg->flags & FUSE_MAX_PAGES) {
+
2867 outarg.flags |= FUSE_MAX_PAGES;
+
2868 outarg.max_pages = (se->conn.max_write - 1) / getpagesize() + 1;
+
2869 }
+
2870 outargflags = outarg.flags;
+
2871 /* Always enable big writes, this is superseded
+
2872 by the max_write option */
+
2873 outargflags |= FUSE_BIG_WRITES;
+
2874
+
2875 if (se->conn.want_ext & FUSE_CAP_ASYNC_READ)
+
2876 outargflags |= FUSE_ASYNC_READ;
+
2877 if (se->conn.want_ext & FUSE_CAP_POSIX_LOCKS)
+
2878 outargflags |= FUSE_POSIX_LOCKS;
+
2879 if (se->conn.want_ext & FUSE_CAP_ATOMIC_O_TRUNC)
+
2880 outargflags |= FUSE_ATOMIC_O_TRUNC;
+
2881 if (se->conn.want_ext & FUSE_CAP_EXPORT_SUPPORT)
+
2882 outargflags |= FUSE_EXPORT_SUPPORT;
+
2883 if (se->conn.want_ext & FUSE_CAP_DONT_MASK)
+
2884 outargflags |= FUSE_DONT_MASK;
+
2885 if (se->conn.want_ext & FUSE_CAP_FLOCK_LOCKS)
+
2886 outargflags |= FUSE_FLOCK_LOCKS;
+
2887 if (se->conn.want_ext & FUSE_CAP_AUTO_INVAL_DATA)
+
2888 outargflags |= FUSE_AUTO_INVAL_DATA;
+
2889 if (se->conn.want_ext & FUSE_CAP_READDIRPLUS)
+
2890 outargflags |= FUSE_DO_READDIRPLUS;
+
2891 if (se->conn.want_ext & FUSE_CAP_READDIRPLUS_AUTO)
+
2892 outargflags |= FUSE_READDIRPLUS_AUTO;
+
2893 if (se->conn.want_ext & FUSE_CAP_ASYNC_DIO)
+
2894 outargflags |= FUSE_ASYNC_DIO;
+
2895 if (se->conn.want_ext & FUSE_CAP_WRITEBACK_CACHE)
+
2896 outargflags |= FUSE_WRITEBACK_CACHE;
+
2897 if (se->conn.want_ext & FUSE_CAP_PARALLEL_DIROPS)
+
2898 outargflags |= FUSE_PARALLEL_DIROPS;
+
2899 if (se->conn.want_ext & FUSE_CAP_POSIX_ACL)
+
2900 outargflags |= FUSE_POSIX_ACL;
+
2901 if (se->conn.want_ext & FUSE_CAP_HANDLE_KILLPRIV)
+
2902 outargflags |= FUSE_HANDLE_KILLPRIV;
+
2903 if (se->conn.want_ext & FUSE_CAP_HANDLE_KILLPRIV_V2)
+
2904 outargflags |= FUSE_HANDLE_KILLPRIV_V2;
+
2905 if (se->conn.want_ext & FUSE_CAP_CACHE_SYMLINKS)
+
2906 outargflags |= FUSE_CACHE_SYMLINKS;
+
2907 if (se->conn.want_ext & FUSE_CAP_EXPLICIT_INVAL_DATA)
+
2908 outargflags |= FUSE_EXPLICIT_INVAL_DATA;
+
2909 if (se->conn.want_ext & FUSE_CAP_SETXATTR_EXT)
+
2910 outargflags |= FUSE_SETXATTR_EXT;
+
2911 if (se->conn.want_ext & FUSE_CAP_DIRECT_IO_ALLOW_MMAP)
+
2912 outargflags |= FUSE_DIRECT_IO_ALLOW_MMAP;
+
2913 if (se->conn.want_ext & FUSE_CAP_PASSTHROUGH) {
+
2914 outargflags |= FUSE_PASSTHROUGH;
+
2915 /*
+
2916 * outarg.max_stack_depth includes the fuse stack layer,
+
2917 * so it is one more than max_backing_stack_depth.
+
2918 */
+
2919 outarg.max_stack_depth = se->conn.max_backing_stack_depth + 1;
+
2920 }
+
2921 if (se->conn.want_ext & FUSE_CAP_NO_EXPORT_SUPPORT)
+
2922 outargflags |= FUSE_NO_EXPORT_SUPPORT;
+
2923 if (se->uring.enable && se->conn.want_ext & FUSE_CAP_OVER_IO_URING) {
+
2924 outargflags |= FUSE_OVER_IO_URING;
+
2925 enable_io_uring = true;
+
2926 }
+
2927
+
2928 if ((inargflags & FUSE_REQUEST_TIMEOUT) && se->conn.request_timeout) {
+
2929 outargflags |= FUSE_REQUEST_TIMEOUT;
+
2930 outarg.request_timeout = se->conn.request_timeout;
+
2931 }
+
2932
+
2933 outarg.max_readahead = se->conn.max_readahead;
+
2934 outarg.max_write = se->conn.max_write;
+
2935 if (se->conn.proto_minor >= 13) {
+
2936 if (se->conn.max_background >= (1 << 16))
+
2937 se->conn.max_background = (1 << 16) - 1;
+
2938 if (se->conn.congestion_threshold > se->conn.max_background)
+
2939 se->conn.congestion_threshold = se->conn.max_background;
+
2940 if (!se->conn.congestion_threshold) {
+
2941 se->conn.congestion_threshold =
+
2942 se->conn.max_background * 3 / 4;
+
2943 }
+
2944
+
2945 outarg.max_background = se->conn.max_background;
+
2946 outarg.congestion_threshold = se->conn.congestion_threshold;
+
2947 }
+
2948 if (se->conn.proto_minor >= 23)
+
2949 outarg.time_gran = se->conn.time_gran;
+
2950
+
2951 if (se->debug) {
+
2952 fuse_log(FUSE_LOG_DEBUG, " INIT: %u.%u\n", outarg.major, outarg.minor);
+
2953 fuse_log(FUSE_LOG_DEBUG, " flags=0x%08x\n", outarg.flags);
+
2954 fuse_log(FUSE_LOG_DEBUG, " max_readahead=0x%08x\n",
+
2955 outarg.max_readahead);
+
2956 fuse_log(FUSE_LOG_DEBUG, " max_write=0x%08x\n", outarg.max_write);
+
2957 fuse_log(FUSE_LOG_DEBUG, " max_background=%i\n",
+
2958 outarg.max_background);
+
2959 fuse_log(FUSE_LOG_DEBUG, " congestion_threshold=%i\n",
+
2960 outarg.congestion_threshold);
+
2961 fuse_log(FUSE_LOG_DEBUG, " time_gran=%u\n",
+
2962 outarg.time_gran);
+
2963 if (se->conn.want_ext & FUSE_CAP_PASSTHROUGH)
+
2964 fuse_log(FUSE_LOG_DEBUG, " max_stack_depth=%u\n",
+
2965 outarg.max_stack_depth);
+
2966 }
+
2967 if (arg->minor < 5)
+
2968 outargsize = FUSE_COMPAT_INIT_OUT_SIZE;
+
2969 else if (arg->minor < 23)
+
2970 outargsize = FUSE_COMPAT_22_INIT_OUT_SIZE;
+
2971
+
2972 /* XXX: Add an option to make non-available io-uring fatal */
+
2973 if (enable_io_uring) {
+
2974 int ring_rc = fuse_uring_start(se);
+
2975
+
2976 if (ring_rc != 0) {
+
2977 fuse_log(FUSE_LOG_INFO,
+
2978 "fuse: failed to start io-uring: %s\n",
+
2979 strerror(ring_rc));
+
2980 outargflags &= ~FUSE_OVER_IO_URING;
+
2981 enable_io_uring = false;
+
2982 }
+
2983 }
+
2984
+
2985 if (inargflags & FUSE_INIT_EXT) {
+
2986 outargflags |= FUSE_INIT_EXT;
+
2987 outarg.flags2 = outargflags >> 32;
+
2988 }
+
2989 outarg.flags = outargflags;
+
2990
+
2991 /*
+
2992 * Has to be set before replying, as new kernel requests might
+
2993 * immediately arrive and got_init is used for op-code sanity.
+
2994 * Especially with external handlers, where we have no control
+
2995 * over the thread scheduling.
+
2996 */
+
2997 se->got_init = 1;
+
2998 send_reply_ok(req, &outarg, outargsize);
+
2999 if (enable_io_uring)
+
3000 fuse_uring_wake_ring_threads(se);
+
3001}
+
3002
+
3003static __attribute__((no_sanitize("thread"))) void
+
3004do_init(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
3005{
+
3006 _do_init(req, nodeid, inarg, NULL);
+
3007}
+
3008
+
3009static void _do_destroy(fuse_req_t req, const fuse_ino_t nodeid,
+
3010 const void *op_in, const void *in_payload)
+
3011{
+
3012 struct fuse_session *se = req->se;
+
3013 char *mountpoint;
+
3014
+
3015 (void) nodeid;
+
3016 (void)op_in;
+
3017 (void)in_payload;
+
3018
+
3019 mountpoint = atomic_exchange(&se->mountpoint, NULL);
+
3020 free(mountpoint);
+
3021
+
3022 se->got_destroy = 1;
+
3023 se->got_init = 0;
+
3024 if (se->op.destroy)
+
3025 se->op.destroy(se->userdata);
+
3026
+
3027 send_reply_ok(req, NULL, 0);
+
3028}
+
3029
+
3030static void do_destroy(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+
3031{
+
3032 _do_destroy(req, nodeid, inarg, NULL);
+
3033}
+
3034
+
3035static void list_del_nreq(struct fuse_notify_req *nreq)
+
3036{
+
3037 struct fuse_notify_req *prev = nreq->prev;
+
3038 struct fuse_notify_req *next = nreq->next;
+
3039 prev->next = next;
+
3040 next->prev = prev;
+
3041}
+
3042
+
3043static void list_add_nreq(struct fuse_notify_req *nreq,
+
3044 struct fuse_notify_req *next)
+
3045{
+
3046 struct fuse_notify_req *prev = next->prev;
+
3047 nreq->next = next;
+
3048 nreq->prev = prev;
+
3049 prev->next = nreq;
+
3050 next->prev = nreq;
+
3051}
+
3052
+
3053static void list_init_nreq(struct fuse_notify_req *nreq)
+
3054{
+
3055 nreq->next = nreq;
+
3056 nreq->prev = nreq;
+
3057}
+
3058
+
3059static void _do_notify_reply(fuse_req_t req, fuse_ino_t nodeid,
+
3060 const void *inarg, const struct fuse_buf *buf)
+
3061{
+
3062 struct fuse_session *se = req->se;
+
3063 struct fuse_notify_req *nreq;
+
3064 struct fuse_notify_req *head;
+
3065
+
3066 pthread_mutex_lock(&se->lock);
+
3067 head = &se->notify_list;
+
3068 for (nreq = head->next; nreq != head; nreq = nreq->next) {
+
3069 if (nreq->unique == req->unique) {
+
3070 list_del_nreq(nreq);
+
3071 break;
+
3072 }
+
3073 }
+
3074 pthread_mutex_unlock(&se->lock);
+
3075
+
3076 if (nreq != head)
+
3077 nreq->reply(nreq, req, nodeid, inarg, buf);
+
3078}
+
3079
+
3080static void do_notify_reply(fuse_req_t req, fuse_ino_t nodeid,
+
3081 const void *inarg, const struct fuse_buf *ibuf)
+
3082{
+
3083 struct fuse_buf buf = *ibuf;
+
3084
+
3085 buf.size -= sizeof(struct fuse_in_header);
+
3086 _do_notify_reply(req, nodeid, inarg, &buf);
+
3087}
+
3088
+
3089static int send_notify_iov(struct fuse_session *se, int notify_code,
+
3090 struct iovec *iov, int count)
+
3091{
+
3092 struct fuse_out_header out;
+
3093 struct fuse_req *req = NULL;
+
3094
+
3095 if (!se->got_init)
+
3096 return -ENOTCONN;
+
3097
+
3098 out.unique = 0;
+
3099 out.error = notify_code;
+
3100 iov[0].iov_base = &out;
+
3101 iov[0].iov_len = sizeof(struct fuse_out_header);
+
3102
+
3103 return fuse_send_msg(se, NULL, iov, count, req);
+
3104}
+
3105
+
+
3106int fuse_lowlevel_notify_poll(struct fuse_pollhandle *ph)
+
3107{
+
3108 if (ph != NULL) {
+
3109 struct fuse_notify_poll_wakeup_out outarg;
+
3110 struct iovec iov[2];
+
3111
+
3112 outarg.kh = ph->kh;
+
3113
+
3114 iov[1].iov_base = &outarg;
+
3115 iov[1].iov_len = sizeof(outarg);
+
3116
+
3117 return send_notify_iov(ph->se, FUSE_NOTIFY_POLL, iov, 2);
+
3118 } else {
+
3119 return 0;
+
3120 }
+
3121}
+
+
3122
+
+
3123int fuse_lowlevel_notify_inval_inode(struct fuse_session *se, fuse_ino_t ino,
+
3124 off_t off, off_t len)
+
3125{
+
3126 struct fuse_notify_inval_inode_out outarg;
+
3127 struct iovec iov[2];
+
3128
+
3129 if (!se)
+
3130 return -EINVAL;
+
3131
+
3132 if (se->conn.proto_minor < 12)
+
3133 return -ENOSYS;
+
3134
+
3135 outarg.ino = ino;
+
3136 outarg.off = off;
+
3137 outarg.len = len;
+
3138
+
3139 iov[1].iov_base = &outarg;
+
3140 iov[1].iov_len = sizeof(outarg);
+
3141
+
3142 return send_notify_iov(se, FUSE_NOTIFY_INVAL_INODE, iov, 2);
+
3143}
+
+
3144
+
+
3145int fuse_lowlevel_notify_increment_epoch(struct fuse_session *se)
+
3146{
+
3147 struct iovec iov[1];
+
3148
+
3149 if (!se)
+
3150 return -EINVAL;
+
3151
+
3152 if (se->conn.proto_minor < 44)
+
3153 return -ENOSYS;
+
3154
+
3155 return send_notify_iov(se, FUSE_NOTIFY_INC_EPOCH, iov, 1);
+
3156}
+
+
3157
+
3177static int fuse_lowlevel_notify_entry(struct fuse_session *se, fuse_ino_t parent,
+
3178 const char *name, size_t namelen,
+
3179 enum fuse_notify_entry_flags flags)
+
3180{
+
3181 struct fuse_notify_inval_entry_out outarg;
+
3182 struct iovec iov[3];
+
3183
+
3184 if (!se)
+
3185 return -EINVAL;
+
3186
+
3187 if (se->conn.proto_minor < 12)
+
3188 return -ENOSYS;
+
3189
+
3190 outarg.parent = parent;
+
3191 outarg.namelen = namelen;
+
3192 outarg.flags = 0;
+
3193 if (flags & FUSE_LL_EXPIRE_ONLY)
+
3194 outarg.flags |= FUSE_EXPIRE_ONLY;
+
3195
+
3196 iov[1].iov_base = &outarg;
+
3197 iov[1].iov_len = sizeof(outarg);
+
3198 iov[2].iov_base = (void *)name;
+
3199 iov[2].iov_len = namelen + 1;
+
3200
+
3201 return send_notify_iov(se, FUSE_NOTIFY_INVAL_ENTRY, iov, 3);
+
3202}
+
3203
+
+
3204int fuse_lowlevel_notify_inval_entry(struct fuse_session *se, fuse_ino_t parent,
+
3205 const char *name, size_t namelen)
+
3206{
+
3207 return fuse_lowlevel_notify_entry(se, parent, name, namelen, FUSE_LL_INVALIDATE);
+
3208}
+
+
3209
+
+
3210int fuse_lowlevel_notify_expire_entry(struct fuse_session *se, fuse_ino_t parent,
+
3211 const char *name, size_t namelen)
+
3212{
+
3213 if (!se)
+
3214 return -EINVAL;
+
3215
+
3216 if (!(se->conn.capable_ext & FUSE_CAP_EXPIRE_ONLY))
+
3217 return -ENOSYS;
+
3218
+
3219 return fuse_lowlevel_notify_entry(se, parent, name, namelen, FUSE_LL_EXPIRE_ONLY);
+
3220}
+
+
3221
+
3222
+
+
3223int fuse_lowlevel_notify_delete(struct fuse_session *se,
+
3224 fuse_ino_t parent, fuse_ino_t child,
+
3225 const char *name, size_t namelen)
+
3226{
+
3227 struct fuse_notify_delete_out outarg;
+
3228 struct iovec iov[3];
+
3229
+
3230 if (!se)
+
3231 return -EINVAL;
+
3232
+
3233 if (se->conn.proto_minor < 18)
+
3234 return -ENOSYS;
+
3235
+
3236 outarg.parent = parent;
+
3237 outarg.child = child;
+
3238 outarg.namelen = namelen;
+
3239 outarg.padding = 0;
+
3240
+
3241 iov[1].iov_base = &outarg;
+
3242 iov[1].iov_len = sizeof(outarg);
+
3243 iov[2].iov_base = (void *)name;
+
3244 iov[2].iov_len = namelen + 1;
+
3245
+
3246 return send_notify_iov(se, FUSE_NOTIFY_DELETE, iov, 3);
+
3247}
+
+
3248
+
+
3249int fuse_lowlevel_notify_store(struct fuse_session *se, fuse_ino_t ino,
+
3250 off_t offset, struct fuse_bufvec *bufv,
+
3251 enum fuse_buf_copy_flags flags)
+
3252{
+
3253 struct fuse_out_header out;
+
3254 struct fuse_notify_store_out outarg;
+
3255 struct iovec iov[3];
+
3256 size_t size = fuse_buf_size(bufv);
+
3257 int res;
+
3258 struct fuse_req *req = NULL;
+
3259
+
3260 if (!se)
+
3261 return -EINVAL;
+
3262
+
3263 if (se->conn.proto_minor < 15)
+
3264 return -ENOSYS;
+
3265
+
3266 out.unique = 0;
+
3267 out.error = FUSE_NOTIFY_STORE;
+
3268
+
3269 outarg.nodeid = ino;
+
3270 outarg.offset = offset;
+
3271 outarg.size = size;
+
3272 outarg.padding = 0;
+
3273
+
3274 iov[0].iov_base = &out;
+
3275 iov[0].iov_len = sizeof(out);
+
3276 iov[1].iov_base = &outarg;
+
3277 iov[1].iov_len = sizeof(outarg);
+
3278
+
3279 res = fuse_send_data_iov(se, NULL, iov, 2, bufv, flags, req);
+
3280 if (res > 0)
+
3281 res = -res;
+
3282
+
3283 return res;
+
3284}
+
+
3285
+
3286struct fuse_retrieve_req {
+
3287 struct fuse_notify_req nreq;
+
3288 void *cookie;
+
3289};
+
3290
+
3291static void fuse_ll_retrieve_reply(struct fuse_notify_req *nreq,
+
3292 fuse_req_t req, fuse_ino_t ino,
+
3293 const void *inarg,
+
3294 const struct fuse_buf *ibuf)
+
3295{
+
3296 struct fuse_session *se = req->se;
+
3297 struct fuse_retrieve_req *rreq =
+
3298 container_of(nreq, struct fuse_retrieve_req, nreq);
+
3299 const struct fuse_notify_retrieve_in *arg = inarg;
+
3300 struct fuse_bufvec bufv = {
+
3301 .buf[0] = *ibuf,
+
3302 .count = 1,
+
3303 };
+
3304
+
3305 if (!(bufv.buf[0].flags & FUSE_BUF_IS_FD))
+
3306 bufv.buf[0].mem = PARAM(arg);
+
3307
+
3308 bufv.buf[0].size -= sizeof(struct fuse_notify_retrieve_in);
+
3309
+
3310 if (bufv.buf[0].size < arg->size) {
+
3311 fuse_log(FUSE_LOG_ERR, "fuse: retrieve reply: buffer size too small\n");
+
3312 fuse_reply_none(req);
+
3313 goto out;
+
3314 }
+
3315 bufv.buf[0].size = arg->size;
+
3316
+
3317 if (se->op.retrieve_reply) {
+
3318 se->op.retrieve_reply(req, rreq->cookie, ino,
+
3319 arg->offset, &bufv);
+
3320 } else {
+
3321 fuse_reply_none(req);
+
3322 }
+
3323out:
+
3324 free(rreq);
+
3325 if ((ibuf->flags & FUSE_BUF_IS_FD) && bufv.idx < bufv.count)
+
3326 fuse_ll_clear_pipe(se);
+
3327}
+
3328
+
+
3329int fuse_lowlevel_notify_retrieve(struct fuse_session *se, fuse_ino_t ino,
+
3330 size_t size, off_t offset, void *cookie)
+
3331{
+
3332 struct fuse_notify_retrieve_out outarg;
+
3333 struct iovec iov[2];
+
3334 struct fuse_retrieve_req *rreq;
+
3335 int err;
+
3336
+
3337 if (!se)
+
3338 return -EINVAL;
+
3339
+
3340 if (se->conn.proto_minor < 15)
+
3341 return -ENOSYS;
+
3342
+
3343 rreq = malloc(sizeof(*rreq));
+
3344 if (rreq == NULL)
+
3345 return -ENOMEM;
+
3346
+
3347 pthread_mutex_lock(&se->lock);
+
3348 rreq->cookie = cookie;
+
3349 rreq->nreq.unique = se->notify_ctr++;
+
3350 rreq->nreq.reply = fuse_ll_retrieve_reply;
+
3351 list_add_nreq(&rreq->nreq, &se->notify_list);
+
3352 pthread_mutex_unlock(&se->lock);
+
3353
+
3354 outarg.notify_unique = rreq->nreq.unique;
+
3355 outarg.nodeid = ino;
+
3356 outarg.offset = offset;
+
3357 outarg.size = size;
+
3358 outarg.padding = 0;
+
3359
+
3360 iov[1].iov_base = &outarg;
+
3361 iov[1].iov_len = sizeof(outarg);
+
3362
+
3363 err = send_notify_iov(se, FUSE_NOTIFY_RETRIEVE, iov, 2);
+
3364 if (err) {
+
3365 pthread_mutex_lock(&se->lock);
+
3366 list_del_nreq(&rreq->nreq);
+
3367 pthread_mutex_unlock(&se->lock);
+
3368 free(rreq);
+
3369 }
+
3370
+
3371 return err;
+
3372}
+
+
3373
+
+ +
3375{
+
3376 return req->se->userdata;
+
3377}
+
+
3378
+
+ +
3380{
+
3381 return &req->ctx;
+
3382}
+
+
3383
+
+ +
3385 void *data)
+
3386{
+
3387 pthread_mutex_lock(&req->lock);
+
3388 pthread_mutex_lock(&req->se->lock);
+
3389 req->u.ni.func = func;
+
3390 req->u.ni.data = data;
+
3391 pthread_mutex_unlock(&req->se->lock);
+
3392 if (req->interrupted && func)
+
3393 func(req, data);
+
3394 pthread_mutex_unlock(&req->lock);
+
3395}
+
+
3396
+
+ +
3398{
+
3399 int interrupted;
+
3400
+
3401 pthread_mutex_lock(&req->se->lock);
+
3402 interrupted = req->interrupted;
+
3403 pthread_mutex_unlock(&req->se->lock);
+
3404
+
3405 return interrupted;
+
3406}
+
+
3407
+
+ +
3409{
+
3410 return req->flags.is_uring;
+
3411}
+
+
3412
+
3413#ifndef HAVE_URING
+
+
3414int fuse_req_get_payload(fuse_req_t req, char **payload, size_t *payload_sz,
+
3415 void **mr)
+
3416{
+
3417 (void)req;
+
3418 (void)payload;
+
3419 (void)payload_sz;
+
3420 (void)mr;
+
3421 return -ENOTSUP;
+
3422}
+
+
3423#endif
+
3424
+
3425static struct {
+
3426 void (*func)(fuse_req_t req, const fuse_ino_t node, const void *arg);
+
3427 const char *name;
+
3428} fuse_ll_ops[] = {
+
3429 [FUSE_LOOKUP] = { do_lookup, "LOOKUP" },
+
3430 [FUSE_FORGET] = { do_forget, "FORGET" },
+
3431 [FUSE_GETATTR] = { do_getattr, "GETATTR" },
+
3432 [FUSE_SETATTR] = { do_setattr, "SETATTR" },
+
3433 [FUSE_READLINK] = { do_readlink, "READLINK" },
+
3434 [FUSE_SYMLINK] = { do_symlink, "SYMLINK" },
+
3435 [FUSE_MKNOD] = { do_mknod, "MKNOD" },
+
3436 [FUSE_MKDIR] = { do_mkdir, "MKDIR" },
+
3437 [FUSE_UNLINK] = { do_unlink, "UNLINK" },
+
3438 [FUSE_RMDIR] = { do_rmdir, "RMDIR" },
+
3439 [FUSE_RENAME] = { do_rename, "RENAME" },
+
3440 [FUSE_LINK] = { do_link, "LINK" },
+
3441 [FUSE_OPEN] = { do_open, "OPEN" },
+
3442 [FUSE_READ] = { do_read, "READ" },
+
3443 [FUSE_WRITE] = { do_write, "WRITE" },
+
3444 [FUSE_STATFS] = { do_statfs, "STATFS" },
+
3445 [FUSE_RELEASE] = { do_release, "RELEASE" },
+
3446 [FUSE_FSYNC] = { do_fsync, "FSYNC" },
+
3447 [FUSE_SETXATTR] = { do_setxattr, "SETXATTR" },
+
3448 [FUSE_GETXATTR] = { do_getxattr, "GETXATTR" },
+
3449 [FUSE_LISTXATTR] = { do_listxattr, "LISTXATTR" },
+
3450 [FUSE_REMOVEXATTR] = { do_removexattr, "REMOVEXATTR" },
+
3451 [FUSE_FLUSH] = { do_flush, "FLUSH" },
+
3452 [FUSE_INIT] = { do_init, "INIT" },
+
3453 [FUSE_OPENDIR] = { do_opendir, "OPENDIR" },
+
3454 [FUSE_READDIR] = { do_readdir, "READDIR" },
+
3455 [FUSE_RELEASEDIR] = { do_releasedir, "RELEASEDIR" },
+
3456 [FUSE_FSYNCDIR] = { do_fsyncdir, "FSYNCDIR" },
+
3457 [FUSE_GETLK] = { do_getlk, "GETLK" },
+
3458 [FUSE_SETLK] = { do_setlk, "SETLK" },
+
3459 [FUSE_SETLKW] = { do_setlkw, "SETLKW" },
+
3460 [FUSE_ACCESS] = { do_access, "ACCESS" },
+
3461 [FUSE_CREATE] = { do_create, "CREATE" },
+
3462 [FUSE_TMPFILE] = { do_tmpfile, "TMPFILE" },
+
3463 [FUSE_INTERRUPT] = { do_interrupt, "INTERRUPT" },
+
3464 [FUSE_BMAP] = { do_bmap, "BMAP" },
+
3465 [FUSE_IOCTL] = { do_ioctl, "IOCTL" },
+
3466 [FUSE_POLL] = { do_poll, "POLL" },
+
3467 [FUSE_FALLOCATE] = { do_fallocate, "FALLOCATE" },
+
3468 [FUSE_DESTROY] = { do_destroy, "DESTROY" },
+
3469 [FUSE_NOTIFY_REPLY] = { (void *) 1, "NOTIFY_REPLY" },
+
3470 [FUSE_BATCH_FORGET] = { do_batch_forget, "BATCH_FORGET" },
+
3471 [FUSE_READDIRPLUS] = { do_readdirplus, "READDIRPLUS"},
+
3472 [FUSE_RENAME2] = { do_rename2, "RENAME2" },
+
3473 [FUSE_COPY_FILE_RANGE] = { do_copy_file_range, "COPY_FILE_RANGE" },
+
3474 [FUSE_COPY_FILE_RANGE_64] = { do_copy_file_range_64, "COPY_FILE_RANGE_64" },
+
3475 [FUSE_LSEEK] = { do_lseek, "LSEEK" },
+
3476 [FUSE_STATX] = { do_statx, "STATX" },
+
3477 [CUSE_INIT] = { cuse_lowlevel_init, "CUSE_INIT" },
+
3478};
+
3479
+
3480static struct {
+
3481 void (*func)(fuse_req_t req, const fuse_ino_t ino, const void *op_in,
+
3482 const void *op_payload);
+
3483 const char *name;
+
3484} fuse_ll_ops2[] __attribute__((unused)) = {
+
3485 [FUSE_LOOKUP] = { _do_lookup, "LOOKUP" },
+
3486 [FUSE_FORGET] = { _do_forget, "FORGET" },
+
3487 [FUSE_GETATTR] = { _do_getattr, "GETATTR" },
+
3488 [FUSE_SETATTR] = { _do_setattr, "SETATTR" },
+
3489 [FUSE_READLINK] = { _do_readlink, "READLINK" },
+
3490 [FUSE_SYMLINK] = { _do_symlink, "SYMLINK" },
+
3491 [FUSE_MKNOD] = { _do_mknod, "MKNOD" },
+
3492 [FUSE_MKDIR] = { _do_mkdir, "MKDIR" },
+
3493 [FUSE_UNLINK] = { _do_unlink, "UNLINK" },
+
3494 [FUSE_RMDIR] = { _do_rmdir, "RMDIR" },
+
3495 [FUSE_RENAME] = { _do_rename, "RENAME" },
+
3496 [FUSE_LINK] = { _do_link, "LINK" },
+
3497 [FUSE_OPEN] = { _do_open, "OPEN" },
+
3498 [FUSE_READ] = { _do_read, "READ" },
+
3499 [FUSE_WRITE] = { _do_write, "WRITE" },
+
3500 [FUSE_STATFS] = { _do_statfs, "STATFS" },
+
3501 [FUSE_RELEASE] = { _do_release, "RELEASE" },
+
3502 [FUSE_FSYNC] = { _do_fsync, "FSYNC" },
+
3503 [FUSE_SETXATTR] = { _do_setxattr, "SETXATTR" },
+
3504 [FUSE_GETXATTR] = { _do_getxattr, "GETXATTR" },
+
3505 [FUSE_LISTXATTR] = { _do_listxattr, "LISTXATTR" },
+
3506 [FUSE_REMOVEXATTR] = { _do_removexattr, "REMOVEXATTR" },
+
3507 [FUSE_FLUSH] = { _do_flush, "FLUSH" },
+
3508 [FUSE_INIT] = { _do_init, "INIT" },
+
3509 [FUSE_OPENDIR] = { _do_opendir, "OPENDIR" },
+
3510 [FUSE_READDIR] = { _do_readdir, "READDIR" },
+
3511 [FUSE_RELEASEDIR] = { _do_releasedir, "RELEASEDIR" },
+
3512 [FUSE_FSYNCDIR] = { _do_fsyncdir, "FSYNCDIR" },
+
3513 [FUSE_GETLK] = { _do_getlk, "GETLK" },
+
3514 [FUSE_SETLK] = { _do_setlk, "SETLK" },
+
3515 [FUSE_SETLKW] = { _do_setlkw, "SETLKW" },
+
3516 [FUSE_ACCESS] = { _do_access, "ACCESS" },
+
3517 [FUSE_CREATE] = { _do_create, "CREATE" },
+
3518 [FUSE_TMPFILE] = { _do_tmpfile, "TMPFILE" },
+
3519 [FUSE_INTERRUPT] = { _do_interrupt, "INTERRUPT" },
+
3520 [FUSE_BMAP] = { _do_bmap, "BMAP" },
+
3521 [FUSE_IOCTL] = { _do_ioctl, "IOCTL" },
+
3522 [FUSE_POLL] = { _do_poll, "POLL" },
+
3523 [FUSE_FALLOCATE] = { _do_fallocate, "FALLOCATE" },
+
3524 [FUSE_DESTROY] = { _do_destroy, "DESTROY" },
+
3525 [FUSE_NOTIFY_REPLY] = { (void *)1, "NOTIFY_REPLY" },
+
3526 [FUSE_BATCH_FORGET] = { _do_batch_forget, "BATCH_FORGET" },
+
3527 [FUSE_READDIRPLUS] = { _do_readdirplus, "READDIRPLUS" },
+
3528 [FUSE_RENAME2] = { _do_rename2, "RENAME2" },
+
3529 [FUSE_COPY_FILE_RANGE] = { _do_copy_file_range, "COPY_FILE_RANGE" },
+
3530 [FUSE_COPY_FILE_RANGE_64] = { _do_copy_file_range_64, "COPY_FILE_RANGE_64" },
+
3531 [FUSE_LSEEK] = { _do_lseek, "LSEEK" },
+
3532 [FUSE_STATX] = { _do_statx, "STATX" },
+
3533 [CUSE_INIT] = { _cuse_lowlevel_init, "CUSE_INIT" },
+
3534};
+
3535
+
3536/*
+
3537 * For ABI compatibility we cannot allow higher values than CUSE_INIT.
+
3538 * Without ABI compatibility we could use the size of the array.
+
3539 * #define FUSE_MAXOP (sizeof(fuse_ll_ops) / sizeof(fuse_ll_ops[0]))
+
3540 */
+
3541#define FUSE_MAXOP (CUSE_INIT + 1)
+
3542
+
3543
+
3548static inline int
+
3549fuse_req_opcode_sanity_ok(struct fuse_session *se, enum fuse_opcode in_op)
+
3550{
+
3551 int err = EIO;
+
3552
+
3553 if (!se->got_init) {
+
3554 enum fuse_opcode expected;
+
3555
+
3556 expected = se->cuse_data ? CUSE_INIT : FUSE_INIT;
+
3557 if (in_op != expected)
+
3558 return err;
+
3559 } else if (in_op == FUSE_INIT || in_op == CUSE_INIT)
+
3560 return err;
+
3561
+
3562 return 0;
+
3563}
+
3564
+
3565static inline void
+
3566fuse_session_in2req(struct fuse_req *req, struct fuse_in_header *in)
+
3567{
+
3568 req->unique = in->unique;
+
3569 req->ctx.uid = in->uid;
+
3570 req->ctx.gid = in->gid;
+
3571 req->ctx.pid = in->pid;
+
3572}
+
3573
+
3577static inline int
+
3578fuse_req_check_allow_root(struct fuse_session *se, enum fuse_opcode in_op,
+
3579 uid_t in_uid)
+
3580{
+
3581 int err = EACCES;
+
3582
+
3583 if (se->deny_others && in_uid != se->owner && in_uid != 0 &&
+
3584 in_op != FUSE_INIT && in_op != FUSE_READ &&
+
3585 in_op != FUSE_WRITE && in_op != FUSE_FSYNC &&
+
3586 in_op != FUSE_RELEASE && in_op != FUSE_READDIR &&
+
3587 in_op != FUSE_FSYNCDIR && in_op != FUSE_RELEASEDIR &&
+
3588 in_op != FUSE_NOTIFY_REPLY &&
+
3589 in_op != FUSE_READDIRPLUS)
+
3590 return err;
+
3591
+
3592 return 0;
+
3593}
+
3594
+
3595static const char *opname(enum fuse_opcode opcode)
+
3596{
+
3597 if (opcode >= FUSE_MAXOP || !fuse_ll_ops[opcode].name)
+
3598 return "???";
+
3599 else
+
3600 return fuse_ll_ops[opcode].name;
+
3601}
+
3602
+
3603static int fuse_ll_copy_from_pipe(struct fuse_bufvec *dst,
+
3604 struct fuse_bufvec *src)
+
3605{
+
3606 ssize_t res = fuse_buf_copy(dst, src, 0);
+
3607 if (res < 0) {
+
3608 fuse_log(FUSE_LOG_ERR, "fuse: copy from pipe: %s\n", strerror(-res));
+
3609 return res;
+
3610 }
+
3611 if ((size_t)res < fuse_buf_size(dst)) {
+
3612 fuse_log(FUSE_LOG_ERR, "fuse: copy from pipe: short read\n");
+
3613 return -1;
+
3614 }
+
3615 return 0;
+
3616}
+
3617
+
+
3618void fuse_session_process_buf(struct fuse_session *se,
+
3619 const struct fuse_buf *buf)
+
3620{
+
3621 fuse_session_process_buf_internal(se, buf, NULL);
+
3622}
+
+
3623
+
3624/* libfuse internal handler */
+
3625void fuse_session_process_buf_internal(struct fuse_session *se,
+
3626 const struct fuse_buf *buf, struct fuse_chan *ch)
+
3627{
+
3628 const size_t write_header_size = sizeof(struct fuse_in_header) +
+
3629 sizeof(struct fuse_write_in);
+
3630 struct fuse_bufvec bufv = { .buf[0] = *buf, .count = 1 };
+
3631 struct fuse_bufvec tmpbuf = FUSE_BUFVEC_INIT(write_header_size);
+
3632 struct fuse_in_header *in;
+
3633 const void *inarg;
+
3634 struct fuse_req *req;
+
3635 void *mbuf = NULL;
+
3636 int err;
+
3637 int res;
+
3638
+
3639 if (buf->flags & FUSE_BUF_IS_FD) {
+
3640 if (buf->size < tmpbuf.buf[0].size)
+
3641 tmpbuf.buf[0].size = buf->size;
+
3642
+
3643 mbuf = malloc(tmpbuf.buf[0].size);
+
3644 if (mbuf == NULL) {
+
3645 fuse_log(FUSE_LOG_ERR, "fuse: failed to allocate header\n");
+
3646 goto clear_pipe;
+
3647 }
+
3648 tmpbuf.buf[0].mem = mbuf;
+
3649
+
3650 res = fuse_ll_copy_from_pipe(&tmpbuf, &bufv);
+
3651 if (res < 0)
+
3652 goto clear_pipe;
+
3653
+
3654 in = mbuf;
+
3655 } else {
+
3656 in = buf->mem;
+
3657 }
+
3658
+
3659 trace_request_process(in->opcode, in->unique);
+
3660
+
3661 if (se->debug) {
+
3662 fuse_log(FUSE_LOG_DEBUG,
+
3663 "dev unique: %llu, opcode: %s (%i), nodeid: %llu, insize: %zu, pid: %u\n",
+
3664 (unsigned long long) in->unique,
+
3665 opname((enum fuse_opcode) in->opcode), in->opcode,
+
3666 (unsigned long long) in->nodeid, buf->size, in->pid);
+
3667 }
+
3668
+
3669 req = fuse_ll_alloc_req(se);
+
3670 if (req == NULL) {
+
3671 struct fuse_out_header out = {
+
3672 .unique = in->unique,
+
3673 .error = -ENOMEM,
+
3674 };
+
3675 struct iovec iov = {
+
3676 .iov_base = &out,
+
3677 .iov_len = sizeof(struct fuse_out_header),
+
3678 };
+
3679
+
3680 fuse_send_msg(se, ch, &iov, 1, NULL);
+
3681 goto clear_pipe;
+
3682 }
+
3683
+
3684 fuse_session_in2req(req, in);
+
3685 req->ch = ch ? fuse_chan_get(ch) : NULL;
+
3686
+
3687 err = fuse_req_opcode_sanity_ok(se, in->opcode);
+
3688 if (err)
+
3689 goto reply_err;
+
3690
+
3691 err = fuse_req_check_allow_root(se, in->opcode, in->uid);
+
3692 if (err)
+
3693 goto reply_err;
+
3694
+
3695 err = ENOSYS;
+
3696 if (in->opcode >= FUSE_MAXOP || !fuse_ll_ops[in->opcode].func)
+
3697 goto reply_err;
+
3698 /* Do not process interrupt request */
+
3699 if (se->conn.no_interrupt && in->opcode == FUSE_INTERRUPT) {
+
3700 if (se->debug)
+
3701 fuse_log(FUSE_LOG_DEBUG, "FUSE_INTERRUPT: reply to kernel to disable interrupt\n");
+
3702 goto reply_err;
+
3703 }
+
3704 if (!se->conn.no_interrupt && in->opcode != FUSE_INTERRUPT) {
+
3705 struct fuse_req *intr;
+
3706 pthread_mutex_lock(&se->lock);
+
3707 intr = check_interrupt(se, req);
+
3708 list_add_req(req, &se->list);
+
3709 pthread_mutex_unlock(&se->lock);
+
3710 if (intr)
+
3711 fuse_reply_err(intr, EAGAIN);
+
3712 }
+
3713
+
3714 if ((buf->flags & FUSE_BUF_IS_FD) && write_header_size < buf->size &&
+
3715 (in->opcode != FUSE_WRITE || !se->op.write_buf) &&
+
3716 in->opcode != FUSE_NOTIFY_REPLY) {
+
3717 void *newmbuf;
+
3718
+
3719 err = ENOMEM;
+
3720 newmbuf = realloc(mbuf, buf->size);
+
3721 if (newmbuf == NULL)
+
3722 goto reply_err;
+
3723 mbuf = newmbuf;
+
3724
+
3725 tmpbuf = FUSE_BUFVEC_INIT(buf->size - write_header_size);
+
3726 tmpbuf.buf[0].mem = (char *)mbuf + write_header_size;
+
3727
+
3728 res = fuse_ll_copy_from_pipe(&tmpbuf, &bufv);
+
3729 err = -res;
+
3730 if (res < 0)
+
3731 goto reply_err;
+
3732
+
3733 in = mbuf;
+
3734 }
+
3735
+
3736 inarg = (void *) &in[1];
+
3737 if (in->opcode == FUSE_WRITE && se->op.write_buf)
+
3738 do_write_buf(req, in->nodeid, inarg, buf);
+
3739 else if (in->opcode == FUSE_NOTIFY_REPLY)
+
3740 do_notify_reply(req, in->nodeid, inarg, buf);
+
3741 else
+
3742 fuse_ll_ops[in->opcode].func(req, in->nodeid, inarg);
+
3743
+
3744out_free:
+
3745 free(mbuf);
+
3746 return;
+
3747
+
3748reply_err:
+
3749 fuse_reply_err(req, err);
+
3750clear_pipe:
+
3751 if (buf->flags & FUSE_BUF_IS_FD)
+
3752 fuse_ll_clear_pipe(se);
+
3753 goto out_free;
+
3754}
+
3755
+
3756void fuse_session_process_uring_cqe(struct fuse_session *se,
+
3757 struct fuse_req *req,
+
3758 struct fuse_in_header *in, void *op_in,
+
3759 void *op_payload, size_t payload_len)
+
3760{
+
3761 int err;
+
3762
+
3763 fuse_session_in2req(req, in);
+
3764
+
3765 err = fuse_req_opcode_sanity_ok(se, in->opcode);
+
3766 if (err)
+
3767 goto reply_err;
+
3768
+
3769 err = fuse_req_check_allow_root(se, in->opcode, in->uid);
+
3770 if (err)
+
3771 goto reply_err;
+
3772
+
3773 err = ENOSYS;
+
3774 if (in->opcode >= FUSE_MAXOP || !fuse_ll_ops2[in->opcode].func)
+
3775 goto reply_err;
+
3776
+
3777 if (se->debug) {
+
3778 fuse_log(
+
3779 FUSE_LOG_DEBUG,
+
3780 "cqe unique: %llu, opcode: %s (%i), nodeid: %llu, insize: %zu, pid: %u\n",
+
3781 (unsigned long long)in->unique,
+
3782 opname((enum fuse_opcode)in->opcode), in->opcode,
+
3783 (unsigned long long)in->nodeid, payload_len, in->pid);
+
3784 }
+
3785
+
3786 if (in->opcode == FUSE_WRITE && se->op.write_buf) {
+
3787 struct fuse_bufvec bufv = {
+
3788 .buf[0] = { .size = payload_len,
+
3789 .flags = 0,
+
3790 .mem = op_payload },
+
3791 .count = 1,
+
3792 };
+
3793 _do_write_buf(req, in->nodeid, op_in, &bufv);
+
3794 } else if (in->opcode == FUSE_NOTIFY_REPLY) {
+
3795 struct fuse_buf buf = { .size = payload_len,
+
3796 .mem = op_payload };
+
3797 _do_notify_reply(req, in->nodeid, op_payload, &buf);
+
3798 } else {
+
3799 fuse_ll_ops2[in->opcode].func(req, in->nodeid, op_in,
+
3800 op_payload);
+
3801 }
+
3802
+
3803 return;
+
3804
+
3805reply_err:
+
3806 fuse_reply_err(req, err);
+
3807}
+
3808
+
3809#define LL_OPTION(n,o,v) \
+
3810 { n, offsetof(struct fuse_session, o), v }
+
3811
+
3812static const struct fuse_opt fuse_ll_opts[] = {
+
3813 LL_OPTION("debug", debug, 1),
+
3814 LL_OPTION("-d", debug, 1),
+
3815 LL_OPTION("--debug", debug, 1),
+
3816 LL_OPTION("allow_root", deny_others, 1),
+
3817 LL_OPTION("io_uring", uring.enable, 1),
+
3818 LL_OPTION("io_uring_q_depth=%u", uring.q_depth, -1),
+ +
3820};
+
3821
+
+ +
3823{
+
3824 printf("using FUSE kernel interface version %i.%i\n",
+
3825 FUSE_KERNEL_VERSION, FUSE_KERNEL_MINOR_VERSION);
+
3826 fuse_mount_version();
+
3827}
+
+
3828
+
+ +
3830{
+
3831 /* These are not all options, but the ones that are
+
3832 potentially of interest to an end-user */
+
3833 printf(
+
3834" -o allow_other allow access by all users\n"
+
3835" -o allow_root allow access by root\n"
+
3836" -o auto_unmount auto unmount on process termination\n"
+
3837" -o io_uring enable io-uring\n"
+
3838" -o io_uring_q_depth=<n> io-uring queue depth\n"
+
3839);
+
3840}
+
+
3841
+
+
3842void fuse_session_destroy(struct fuse_session *se)
+
3843{
+
3844 struct fuse_ll_pipe *llp;
+
3845
+
3846 if (se->got_init && !se->got_destroy) {
+
3847 if (se->op.destroy)
+
3848 se->op.destroy(se->userdata);
+
3849 }
+
3850 llp = pthread_getspecific(se->pipe_key);
+
3851 if (llp != NULL)
+
3852 fuse_ll_pipe_free(llp);
+
3853 pthread_key_delete(se->pipe_key);
+
3854 sem_destroy(&se->mt_finish);
+
3855 pthread_mutex_destroy(&se->mt_lock);
+
3856 pthread_mutex_destroy(&se->lock);
+
3857 free(se->cuse_data);
+
3858 if (se->fd != -1)
+
3859 close(se->fd);
+
3860 if (se->io != NULL)
+
3861 free(se->io);
+
3862 destroy_mount_opts(se->mo);
+
3863 free(atomic_exchange(&se->mountpoint, NULL));
+
3864 free(se);
+
3865}
+
+
3866
+
3867
+
3868static void fuse_ll_pipe_destructor(void *data)
+
3869{
+
3870 struct fuse_ll_pipe *llp = data;
+
3871 fuse_ll_pipe_free(llp);
+
3872}
+
3873
+
3874void fuse_buf_free(struct fuse_buf *buf)
+
3875{
+
3876 if (buf->mem == NULL)
+
3877 return;
+
3878
+
3879 size_t write_header_sz =
+
3880 sizeof(struct fuse_in_header) + sizeof(struct fuse_write_in);
+
3881
+
3882 char *ptr = (char *)buf->mem - pagesize + write_header_sz;
+
3883 free(ptr);
+
3884 buf->mem = NULL;
+
3885}
+
3886
+
3887/*
+
3888 * This is used to allocate buffers that hold fuse requests
+
3889 */
+
3890static void *buf_alloc(size_t size, bool internal)
+
3891{
+
3892 /*
+
3893 * For libfuse internal caller add in alignment. That cannot be done
+
3894 * for an external caller, as it is not guaranteed that the external
+
3895 * caller frees the raw pointer.
+
3896 */
+
3897 if (internal) {
+
3898 size_t write_header_sz = sizeof(struct fuse_in_header) +
+
3899 sizeof(struct fuse_write_in);
+
3900 size_t new_size = ROUND_UP(size + write_header_sz, pagesize);
+
3901
+
3902 char *buf = aligned_alloc(pagesize, new_size);
+
3903 if (buf == NULL)
+
3904 return NULL;
+
3905
+
3906 buf += pagesize - write_header_sz;
+
3907
+
3908 return buf;
+
3909 } else {
+
3910 return malloc(size);
+
3911 }
+
3912}
+
3913
+
3914/*
+
3915 *@param internal true if called from libfuse internal code
+
3916 */
+
3917static int _fuse_session_receive_buf(struct fuse_session *se,
+
3918 struct fuse_buf *buf, struct fuse_chan *ch,
+
3919 bool internal)
+
3920{
+
3921 int err;
+
3922 ssize_t res;
+
3923 size_t bufsize;
+
3924#ifdef HAVE_SPLICE
+
3925 struct fuse_ll_pipe *llp;
+
3926 struct fuse_buf tmpbuf;
+
3927
+
3928pipe_retry:
+
3929 bufsize = se->bufsize;
+
3930
+
3931 if (se->conn.proto_minor < 14 ||
+
3932 !(se->conn.want_ext & FUSE_CAP_SPLICE_READ))
+
3933 goto fallback;
+
3934
+
3935 llp = fuse_ll_get_pipe(se);
+
3936 if (llp == NULL)
+
3937 goto fallback;
+
3938
+
3939 if (llp->size < bufsize) {
+
3940 if (llp->can_grow) {
+
3941 res = fcntl(llp->pipe[0], F_SETPIPE_SZ, bufsize);
+
3942 if (res == -1) {
+
3943 llp->can_grow = 0;
+
3944 res = grow_pipe_to_max(llp->pipe[0]);
+
3945 if (res > 0)
+
3946 llp->size = res;
+
3947 fuse_ll_clear_pipe(se);
+
3948 goto fallback;
+
3949 }
+
3950 llp->size = res;
+
3951 }
+
3952 if (llp->size < bufsize) {
+
3953 fuse_ll_clear_pipe(se);
+
3954 goto fallback;
+
3955 }
+
3956 }
+
3957
+
3958 if (se->io != NULL && se->io->splice_receive != NULL) {
+
3959 res = se->io->splice_receive(ch ? ch->fd : se->fd, NULL,
+
3960 llp->pipe[1], NULL, bufsize, 0,
+
3961 se->userdata);
+
3962 } else {
+
3963 res = splice(ch ? ch->fd : se->fd, NULL, llp->pipe[1], NULL,
+
3964 bufsize, 0);
+
3965 }
+
3966 err = errno;
+
3967 trace_request_receive(err);
+
3968
+
3969 if (fuse_session_exited(se))
+
3970 return 0;
+
3971
+
3972 if (res == -1) {
+
3973 if (err == ENODEV) {
+
3974 /* Filesystem was unmounted, or connection was aborted
+
3975 via /sys/fs/fuse/connections */
+ +
3977 return 0;
+
3978 }
+
3979
+
3980 /* FUSE_INIT might have increased the required bufsize */
+
3981 if (err == EINVAL && bufsize < se->bufsize) {
+
3982 fuse_ll_clear_pipe(se);
+
3983 goto pipe_retry;
+
3984 }
+
3985
+
3986 if (err != EINTR && err != EAGAIN)
+
3987 perror("fuse: splice from device");
+
3988 return -err;
+
3989 }
+
3990
+
3991 if (res < sizeof(struct fuse_in_header)) {
+
3992 fuse_log(FUSE_LOG_ERR, "short splice from fuse device\n");
+
3993 return -EIO;
+
3994 }
+
3995
+
3996 tmpbuf = (struct fuse_buf){
+
3997 .size = res,
+
3998 .flags = FUSE_BUF_IS_FD,
+
3999 .fd = llp->pipe[0],
+
4000 };
+
4001
+
4002 /*
+
4003 * Don't bother with zero copy for small requests.
+
4004 * fuse_loop_mt() needs to check for FORGET so this more than
+
4005 * just an optimization.
+
4006 */
+
4007 if (res < sizeof(struct fuse_in_header) + sizeof(struct fuse_write_in) +
+
4008 pagesize) {
+
4009 struct fuse_bufvec src = { .buf[0] = tmpbuf, .count = 1 };
+
4010 struct fuse_bufvec dst = { .count = 1 };
+
4011
+
4012 if (!buf->mem) {
+
4013 buf->mem = buf_alloc(bufsize, internal);
+
4014 if (!buf->mem) {
+
4015 fuse_log(
+
4016 FUSE_LOG_ERR,
+
4017 "fuse: failed to allocate read buffer\n");
+
4018 return -ENOMEM;
+
4019 }
+
4020 buf->mem_size = bufsize;
+
4021 }
+
4022 buf->size = bufsize;
+
4023 buf->flags = 0;
+
4024 dst.buf[0] = *buf;
+
4025
+
4026 res = fuse_buf_copy(&dst, &src, 0);
+
4027 if (res < 0) {
+
4028 fuse_log(FUSE_LOG_ERR, "fuse: copy from pipe: %s\n",
+
4029 strerror(-res));
+
4030 fuse_ll_clear_pipe(se);
+
4031 return res;
+
4032 }
+
4033 if (res < tmpbuf.size) {
+
4034 fuse_log(FUSE_LOG_ERR,
+
4035 "fuse: copy from pipe: short read\n");
+
4036 fuse_ll_clear_pipe(se);
+
4037 return -EIO;
+
4038 }
+
4039 assert(res == tmpbuf.size);
+
4040
+
4041 } else {
+
4042 /* Don't overwrite buf->mem, as that would cause a leak */
+
4043 buf->fd = tmpbuf.fd;
+
4044 buf->flags = tmpbuf.flags;
+
4045 }
+
4046 buf->size = tmpbuf.size;
+
4047
+
4048 return res;
+
4049
+
4050fallback:
+
4051#endif
+
4052 bufsize = internal ? buf->mem_size : se->bufsize;
+
4053 if (!buf->mem) {
+
4054 bufsize = se->bufsize; /* might have changed */
+
4055 buf->mem = buf_alloc(bufsize, internal);
+
4056 if (!buf->mem) {
+
4057 fuse_log(FUSE_LOG_ERR,
+
4058 "fuse: failed to allocate read buffer\n");
+
4059 return -ENOMEM;
+
4060 }
+
4061
+
4062 if (internal)
+
4063 buf->mem_size = bufsize;
+
4064 }
+
4065
+
4066restart:
+
4067 if (se->io != NULL) {
+
4068 /* se->io->read is never NULL if se->io is not NULL as
+
4069 specified by fuse_session_custom_io()*/
+
4070 res = se->io->read(ch ? ch->fd : se->fd, buf->mem, bufsize,
+
4071 se->userdata);
+
4072 } else {
+
4073 res = read(ch ? ch->fd : se->fd, buf->mem, bufsize);
+
4074 }
+
4075 err = errno;
+
4076 trace_request_receive(err);
+
4077
+
4078 if (fuse_session_exited(se))
+
4079 return 0;
+
4080 if (res == -1) {
+
4081 if (err == EINVAL && internal && se->bufsize > bufsize) {
+
4082 /* FUSE_INIT might have increased the required bufsize */
+
4083 bufsize = se->bufsize;
+
4084 void *newbuf = buf_alloc(bufsize, internal);
+
4085 if (!newbuf) {
+
4086 fuse_log(
+
4087 FUSE_LOG_ERR,
+
4088 "fuse: failed to (re)allocate read buffer\n");
+
4089 return -ENOMEM;
+
4090 }
+
4091 fuse_buf_free(buf);
+
4092 buf->mem = newbuf;
+
4093 buf->mem_size = bufsize;
+
4094 goto restart;
+
4095 }
+
4096
+
4097 /* ENOENT means the operation was interrupted, it's safe
+
4098 to restart */
+
4099 if (err == ENOENT)
+
4100 goto restart;
+
4101
+
4102 if (err == ENODEV) {
+
4103 /* Filesystem was unmounted, or connection was aborted
+
4104 via /sys/fs/fuse/connections */
+ +
4106 return 0;
+
4107 }
+
4108 /* Errors occurring during normal operation: EINTR (read
+
4109 interrupted), EAGAIN (nonblocking I/O), ENODEV (filesystem
+
4110 umounted) */
+
4111 if (err != EINTR && err != EAGAIN)
+
4112 perror("fuse: reading device");
+
4113 return -err;
+
4114 }
+
4115 if ((size_t)res < sizeof(struct fuse_in_header)) {
+
4116 fuse_log(FUSE_LOG_ERR, "short read on fuse device\n");
+
4117 return -EIO;
+
4118 }
+
4119
+
4120 buf->size = res;
+
4121
+
4122 return res;
+
4123}
+
4124
+
+
4125int fuse_session_receive_buf(struct fuse_session *se, struct fuse_buf *buf)
+
4126{
+
4127 return _fuse_session_receive_buf(se, buf, NULL, false);
+
4128}
+
+
4129
+
4130/* libfuse internal handler */
+
4131int fuse_session_receive_buf_internal(struct fuse_session *se,
+
4132 struct fuse_buf *buf,
+
4133 struct fuse_chan *ch)
+
4134{
+
4135 /*
+
4136 * if run internally thread buffers are from libfuse - we can
+
4137 * reallocate them
+
4138 */
+
4139 if (unlikely(!se->got_init) && !se->buf_reallocable)
+
4140 se->buf_reallocable = true;
+
4141
+
4142 return _fuse_session_receive_buf(se, buf, ch, true);
+
4143}
+
4144
+
4145struct fuse_session *
+
4146fuse_session_new_versioned(struct fuse_args *args,
+
4147 const struct fuse_lowlevel_ops *op, size_t op_size,
+
4148 struct libfuse_version *version, void *userdata)
+
4149{
+
4150 int err;
+
4151 struct fuse_session *se;
+
4152 struct mount_opts *mo;
+
4153
+
4154 if (op == NULL || op_size == 0) {
+
4155 fuse_log(FUSE_LOG_ERR,
+
4156 "fuse: warning: empty op list passed to fuse_session_new()\n");
+
4157 return NULL;
+
4158 }
+
4159
+
4160 if (version == NULL) {
+
4161 fuse_log(FUSE_LOG_ERR, "fuse: warning: version not passed to fuse_session_new()\n");
+
4162 return NULL;
+
4163 }
+
4164
+
4165 if (sizeof(struct fuse_lowlevel_ops) < op_size) {
+
4166 fuse_log(FUSE_LOG_ERR, "fuse: warning: library too old, some operations may not work\n");
+
4167 op_size = sizeof(struct fuse_lowlevel_ops);
+
4168 }
+
4169
+
4170 if (args == NULL || args->argc == 0) {
+
4171 fuse_log(FUSE_LOG_ERR, "fuse: empty argv passed to fuse_session_new().\n");
+
4172 return NULL;
+
4173 }
+
4174
+
4175 se = (struct fuse_session *) calloc(1, sizeof(struct fuse_session));
+
4176 if (se == NULL) {
+
4177 fuse_log(FUSE_LOG_ERR, "fuse: failed to allocate fuse object\n");
+
4178 goto out1;
+
4179 }
+
4180 se->fd = -1;
+
4181 se->conn.max_write = FUSE_DEFAULT_MAX_PAGES_LIMIT * getpagesize();
+
4182 se->bufsize = se->conn.max_write + FUSE_BUFFER_HEADER_SIZE;
+
4183 se->conn.max_readahead = UINT_MAX;
+
4184
+
4185 /*
+
4186 * Allow overriding with env, mostly to avoid the need to modify
+
4187 * all tests. I.e. to test with and without io-uring being enabled.
+
4188 */
+
4189 se->uring.enable = getenv("FUSE_URING_ENABLE") ?
+
4190 atoi(getenv("FUSE_URING_ENABLE")) :
+
4191 SESSION_DEF_URING_ENABLE;
+
4192 se->uring.q_depth = getenv("FUSE_URING_QUEUE_DEPTH") ?
+
4193 atoi(getenv("FUSE_URING_QUEUE_DEPTH")) :
+
4194 SESSION_DEF_URING_Q_DEPTH;
+
4195
+
4196 /* Parse options */
+
4197 if(fuse_opt_parse(args, se, fuse_ll_opts, NULL) == -1)
+
4198 goto out2;
+
4199 if(se->deny_others) {
+
4200 /* Allowing access only by root is done by instructing
+
4201 * kernel to allow access by everyone, and then restricting
+
4202 * access to root and mountpoint owner in libfuse.
+
4203 */
+
4204 // We may be adding the option a second time, but
+
4205 // that doesn't hurt.
+
4206 if(fuse_opt_add_arg(args, "-oallow_other") == -1)
+
4207 goto out2;
+
4208 }
+
4209 mo = parse_mount_opts(args);
+
4210 if (mo == NULL)
+
4211 goto out3;
+
4212
+
4213 if(args->argc == 1 &&
+
4214 args->argv[0][0] == '-') {
+
4215 fuse_log(FUSE_LOG_ERR, "fuse: warning: argv[0] looks like an option, but "
+
4216 "will be ignored\n");
+
4217 } else if (args->argc != 1) {
+
4218 int i;
+
4219 fuse_log(FUSE_LOG_ERR, "fuse: unknown option(s): `");
+
4220 for(i = 1; i < args->argc-1; i++)
+
4221 fuse_log(FUSE_LOG_ERR, "%s ", args->argv[i]);
+
4222 fuse_log(FUSE_LOG_ERR, "%s'\n", args->argv[i]);
+
4223 goto out4;
+
4224 }
+
4225
+
4226 if (se->debug)
+
4227 fuse_log(FUSE_LOG_DEBUG, "FUSE library version: %s\n", PACKAGE_VERSION);
+
4228
+
4229 list_init_req(&se->list);
+
4230 list_init_req(&se->interrupts);
+
4231 list_init_nreq(&se->notify_list);
+
4232 se->notify_ctr = 1;
+
4233 pthread_mutex_init(&se->lock, NULL);
+
4234 sem_init(&se->mt_finish, 0, 0);
+
4235 pthread_mutex_init(&se->mt_lock, NULL);
+
4236
+
4237 err = pthread_key_create(&se->pipe_key, fuse_ll_pipe_destructor);
+
4238 if (err) {
+
4239 fuse_log(FUSE_LOG_ERR, "fuse: failed to create thread specific key: %s\n",
+
4240 strerror(err));
+
4241 goto out5;
+
4242 }
+
4243
+
4244 memcpy(&se->op, op, op_size);
+
4245 se->owner = getuid();
+
4246 se->userdata = userdata;
+
4247
+
4248 se->mo = mo;
+
4249
+
4250 /* Fuse server application should pass the version it was compiled
+
4251 * against and pass it. If a libfuse version accidentally introduces an
+
4252 * ABI incompatibility, it might be possible to 'fix' that at run time,
+
4253 * by checking the version numbers.
+
4254 */
+
4255 se->version = *version;
+
4256
+
4257 return se;
+
4258
+
4259out5:
+
4260 sem_destroy(&se->mt_finish);
+
4261 pthread_mutex_destroy(&se->mt_lock);
+
4262 pthread_mutex_destroy(&se->lock);
+
4263out4:
+
4264 fuse_opt_free_args(args);
+
4265out3:
+
4266 if (mo != NULL)
+
4267 destroy_mount_opts(mo);
+
4268out2:
+
4269 free(se);
+
4270out1:
+
4271 return NULL;
+
4272}
+
4273
+
4274struct fuse_session *fuse_session_new_30(struct fuse_args *args,
+
4275 const struct fuse_lowlevel_ops *op,
+
4276 size_t op_size, void *userdata);
+
4277struct fuse_session *fuse_session_new_30(struct fuse_args *args,
+
4278 const struct fuse_lowlevel_ops *op,
+
4279 size_t op_size,
+
4280 void *userdata)
+
4281{
+
4282 struct fuse_lowlevel_ops null_ops = { 0 };
+
4283
+
4284 /* unknown version */
+
4285 struct libfuse_version version = { 0 };
+
4286
+
4287 /*
+
4288 * This function is the ABI interface function from fuse_session_new in
+
4289 * compat.c. External libraries like "fuser" might call fuse_session_new()
+
4290 * with NULL ops and then pass that session to fuse_session_mount().
+
4291 * The actual FUSE operations are handled in their own library.
+
4292 */
+
4293 if (op == NULL) {
+
4294 op = &null_ops;
+
4295 op_size = sizeof(null_ops);
+
4296 }
+
4297
+
4298 return fuse_session_new_versioned(args, op, op_size, &version,
+
4299 userdata);
+
4300}
+
4301
+
4302FUSE_SYMVER("fuse_session_custom_io_317", "fuse_session_custom_io@@FUSE_3.17")
+
4303int fuse_session_custom_io_317(struct fuse_session *se,
+
4304 const struct fuse_custom_io *io, size_t op_size, int fd)
+
4305{
+
4306#ifndef HAVE_CUSTOM_IO
+
4307 (void)se;
+
4308 (void)io;
+
4309 (void)op_size;
+
4310 (void)fd;
+
4311 fuse_log(FUSE_LOG_ERR, "fuse: custom io is not enabled in this build\n");
+
4312 return -ENOTSUP;
+
4313#else
+
4314 if (sizeof(struct fuse_custom_io) < op_size) {
+
4315 fuse_log(FUSE_LOG_ERR, "fuse: warning: library too old, some operations may not work\n");
+
4316 op_size = sizeof(struct fuse_custom_io);
+
4317 }
+
4318
+
4319 if (fd < 0) {
+
4320 fuse_log(FUSE_LOG_ERR, "Invalid file descriptor value %d passed to "
+
4321 "fuse_session_custom_io()\n", fd);
+
4322 return -EBADF;
+
4323 }
+
4324 if (io == NULL) {
+
4325 fuse_log(FUSE_LOG_ERR, "No custom IO passed to "
+
4326 "fuse_session_custom_io()\n");
+
4327 return -EINVAL;
+
4328 } else if (io->read == NULL || io->writev == NULL) {
+
4329 /* If the user provides their own file descriptor, we can't
+
4330 guarantee that the default behavior of the io operations made
+
4331 in libfuse will function properly. Therefore, we enforce the
+
4332 user to implement these io operations when using custom io. */
+
4333 fuse_log(FUSE_LOG_ERR, "io passed to fuse_session_custom_io() must "
+
4334 "implement both io->read() and io->writev\n");
+
4335 return -EINVAL;
+
4336 }
+
4337
+
4338 se->io = calloc(1, sizeof(struct fuse_custom_io));
+
4339 if (se->io == NULL) {
+
4340 fuse_log(FUSE_LOG_ERR, "Failed to allocate memory for custom io. "
+
4341 "Error: %s\n", strerror(errno));
+
4342 return -errno;
+
4343 }
+
4344
+
4345 se->fd = fd;
+
4346 memcpy(se->io, io, op_size);
+
4347 return 0;
+
4348#endif
+
4349}
+
4350
+
4351int fuse_session_custom_io_30(struct fuse_session *se,
+
4352 const struct fuse_custom_io *io, int fd);
+
4353FUSE_SYMVER("fuse_session_custom_io_30", "fuse_session_custom_io@FUSE_3.0")
+
4354int fuse_session_custom_io_30(struct fuse_session *se,
+
4355 const struct fuse_custom_io *io, int fd)
+
4356{
+
4357 return fuse_session_custom_io_317(se, io,
+
4358 offsetof(struct fuse_custom_io, clone_fd), fd);
+
4359}
+
4360
+
+
4361int fuse_session_mount(struct fuse_session *se, const char *_mountpoint)
+
4362{
+
4363 int fd;
+
4364 char *mountpoint;
+
4365
+
4366 if (_mountpoint == NULL) {
+
4367 fuse_log(FUSE_LOG_ERR, "Invalid null-ptr mountpoint!\n");
+
4368 return -1;
+
4369 }
+
4370
+
4371 mountpoint = strdup(_mountpoint);
+
4372 if (mountpoint == NULL) {
+
4373 fuse_log(FUSE_LOG_ERR, "Failed to allocate memory for mountpoint. Error: %s\n",
+
4374 strerror(errno));
+
4375 return -1;
+
4376 }
+
4377
+
4378 /*
+
4379 * Make sure file descriptors 0, 1 and 2 are open, otherwise chaos
+
4380 * would ensue.
+
4381 */
+
4382 do {
+
4383 fd = open("/dev/null", O_RDWR);
+
4384 if (fd > 2)
+
4385 close(fd);
+
4386 } while (fd >= 0 && fd <= 2);
+
4387
+
4388 /*
+
4389 * To allow FUSE daemons to run without privileges, the caller may open
+
4390 * /dev/fuse before launching the file system and pass on the file
+
4391 * descriptor by specifying /dev/fd/N as the mount point. Note that the
+
4392 * parent process takes care of performing the mount in this case.
+
4393 */
+
4394 fd = fuse_mnt_parse_fuse_fd(mountpoint);
+
4395 if (fd != -1) {
+
4396 if (fcntl(fd, F_GETFD) == -1) {
+
4397 fuse_log(FUSE_LOG_ERR,
+
4398 "fuse: Invalid file descriptor /dev/fd/%u\n",
+
4399 fd);
+
4400 goto error_out;
+
4401 }
+
4402 goto out;
+
4403 }
+
4404
+
4405 /* Open channel */
+
4406 fd = fuse_kern_mount(mountpoint, se->mo);
+
4407 if (fd == -1)
+
4408 goto error_out;
+
4409
+
4410out:
+
4411 se->fd = fd;
+
4412 se->mountpoint = mountpoint;
+
4413
+
4414 return 0;
+
4415
+
4416error_out:
+
4417 free(mountpoint);
+
4418 return -1;
+
4419}
+
+
4420
+
+
4421int fuse_session_fd(struct fuse_session *se)
+
4422{
+
4423 return se->fd;
+
4424}
+
+
4425
+
+
4426void fuse_session_unmount(struct fuse_session *se)
+
4427{
+
4428 if (se->mountpoint != NULL) {
+
4429 char *mountpoint = atomic_exchange(&se->mountpoint, NULL);
+
4430
+
4431 fuse_kern_unmount(mountpoint, se->fd);
+
4432 se->fd = -1;
+
4433 free(mountpoint);
+
4434 }
+
4435}
+
+
4436
+
4437#ifdef linux
+
4438int fuse_req_getgroups(fuse_req_t req, int size, gid_t list[])
+
4439{
+
4440 char *buf;
+
4441 size_t bufsize = 1024;
+
4442 char path[128];
+
4443 int ret;
+
4444 int fd;
+
4445 unsigned long pid = req->ctx.pid;
+
4446 char *s;
+
4447
+
4448 sprintf(path, "/proc/%lu/task/%lu/status", pid, pid);
+
4449
+
4450retry:
+
4451 buf = malloc(bufsize);
+
4452 if (buf == NULL)
+
4453 return -ENOMEM;
+
4454
+
4455 ret = -EIO;
+
4456 fd = open(path, O_RDONLY);
+
4457 if (fd == -1)
+
4458 goto out_free;
+
4459
+
4460 ret = read(fd, buf, bufsize);
+
4461 close(fd);
+
4462 if (ret < 0) {
+
4463 ret = -EIO;
+
4464 goto out_free;
+
4465 }
+
4466
+
4467 if ((size_t)ret == bufsize) {
+
4468 free(buf);
+
4469 bufsize *= 4;
+
4470 goto retry;
+
4471 }
+
4472
+
4473 buf[ret] = '\0';
+
4474 ret = -EIO;
+
4475 s = strstr(buf, "\nGroups:");
+
4476 if (s == NULL)
+
4477 goto out_free;
+
4478
+
4479 s += 8;
+
4480 ret = 0;
+
4481 while (1) {
+
4482 char *end;
+
4483 unsigned long val = strtoul(s, &end, 0);
+
4484 if (end == s)
+
4485 break;
+
4486
+
4487 s = end;
+
4488 if (ret < size)
+
4489 list[ret] = val;
+
4490 ret++;
+
4491 }
+
4492
+
4493out_free:
+
4494 free(buf);
+
4495 return ret;
+
4496}
+
4497#else /* linux */
+
4498/*
+
4499 * This is currently not implemented on other than Linux...
+
4500 */
+
+
4501int fuse_req_getgroups(fuse_req_t req, int size, gid_t list[])
+
4502{
+
4503 (void) req; (void) size; (void) list;
+
4504 return -ENOSYS;
+
4505}
+
+
4506#endif
+
4507
+
4508/* Prevent spurious data race warning - we don't care
+
4509 * about races for this flag */
+
4510__attribute__((no_sanitize_thread))
+
4511void fuse_session_exit(struct fuse_session *se)
+
4512{
+
4513 atomic_store_explicit(&se->mt_exited, 1, memory_order_relaxed);
+
4514 sem_post(&se->mt_finish);
+
4515}
+
4516
+
4517__attribute__((no_sanitize_thread))
+
4518void fuse_session_reset(struct fuse_session *se)
+
4519{
+
4520 se->mt_exited = false;
+
4521 se->error = 0;
+
4522}
+
4523
+
4524__attribute__((no_sanitize_thread))
+
4525int fuse_session_exited(struct fuse_session *se)
+
4526{
+
4527 bool exited =
+
4528 atomic_load_explicit(&se->mt_exited, memory_order_relaxed);
+
4529
+
4530 return exited ? 1 : 0;
+
4531}
+
#define FUSE_CAP_IOCTL_DIR
+
#define FUSE_CAP_DONT_MASK
+
void fuse_unset_feature_flag(struct fuse_conn_info *conn, uint64_t flag)
+
int fuse_convert_to_conn_want_ext(struct fuse_conn_info *conn)
+
bool fuse_set_feature_flag(struct fuse_conn_info *conn, uint64_t flag)
+
#define FUSE_CAP_HANDLE_KILLPRIV
+
#define FUSE_CAP_AUTO_INVAL_DATA
+
#define FUSE_CAP_HANDLE_KILLPRIV_V2
+
#define FUSE_CAP_SPLICE_READ
+
#define FUSE_CAP_PARALLEL_DIROPS
+
size_t fuse_buf_size(const struct fuse_bufvec *bufv)
Definition buffer.c:22
+
#define FUSE_CAP_WRITEBACK_CACHE
+
#define FUSE_CAP_EXPIRE_ONLY
+
#define FUSE_CAP_ATOMIC_O_TRUNC
+
#define FUSE_CAP_ASYNC_READ
+
#define FUSE_CAP_SPLICE_WRITE
+
#define FUSE_CAP_CACHE_SYMLINKS
+
#define FUSE_CAP_POSIX_ACL
+
@ FUSE_BUF_IS_FD
+
#define FUSE_CAP_EXPORT_SUPPORT
+
#define FUSE_CAP_POSIX_LOCKS
+
#define FUSE_CAP_EXPLICIT_INVAL_DATA
+
#define FUSE_CAP_READDIRPLUS_AUTO
+
ssize_t fuse_buf_copy(struct fuse_bufvec *dst, struct fuse_bufvec *src, enum fuse_buf_copy_flags flags)
Definition buffer.c:284
+
#define FUSE_CAP_NO_OPENDIR_SUPPORT
+
#define FUSE_CAP_ASYNC_DIO
+
#define FUSE_CAP_OVER_IO_URING
+
bool fuse_get_feature_flag(struct fuse_conn_info *conn, uint64_t flag)
+
#define FUSE_CAP_PASSTHROUGH
+
#define FUSE_CAP_DIRECT_IO_ALLOW_MMAP
+
#define FUSE_CAP_NO_OPEN_SUPPORT
+
#define FUSE_CAP_READDIRPLUS
+
void fuse_pollhandle_destroy(struct fuse_pollhandle *ph)
+
fuse_buf_copy_flags
+
@ FUSE_BUF_SPLICE_NONBLOCK
+
@ FUSE_BUF_FORCE_SPLICE
+
@ FUSE_BUF_NO_SPLICE
+
@ FUSE_BUF_SPLICE_MOVE
+
#define FUSE_CAP_SETXATTR_EXT
+
#define FUSE_CAP_SPLICE_MOVE
+
#define FUSE_CAP_NO_EXPORT_SUPPORT
+
#define FUSE_CAP_FLOCK_LOCKS
+
void fuse_log(enum fuse_log_level level, const char *fmt,...) __attribute__((format(printf
+
void fuse_session_destroy(struct fuse_session *se)
+
fuse_notify_entry_flags
+
int fuse_reply_data(fuse_req_t req, struct fuse_bufvec *bufv, enum fuse_buf_copy_flags flags)
+
int fuse_reply_lock(fuse_req_t req, const struct flock *lock)
+
int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi)
+
void fuse_session_exit(struct fuse_session *se)
+
void(* fuse_interrupt_func_t)(fuse_req_t req, void *data)
+
int fuse_reply_poll(fuse_req_t req, unsigned revents)
+
int fuse_reply_err(fuse_req_t req, int err)
+
bool fuse_req_is_uring(fuse_req_t req)
+
const struct fuse_ctx * fuse_req_ctx(fuse_req_t req)
+
void * fuse_req_userdata(fuse_req_t req)
+
int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
+
struct fuse_req * fuse_req_t
+
size_t fuse_add_direntry_plus(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct fuse_entry_param *e, off_t off)
+
int fuse_reply_ioctl_iov(fuse_req_t req, int result, const struct iovec *iov, int count)
+
int fuse_lowlevel_notify_delete(struct fuse_session *se, fuse_ino_t parent, fuse_ino_t child, const char *name, size_t namelen)
+
void fuse_session_process_buf(struct fuse_session *se, const struct fuse_buf *buf)
+
int fuse_session_exited(struct fuse_session *se)
+
int fuse_session_fd(struct fuse_session *se)
+
int fuse_req_interrupted(fuse_req_t req)
+
int fuse_req_getgroups(fuse_req_t req, int size, gid_t list[])
+
int fuse_lowlevel_notify_retrieve(struct fuse_session *se, fuse_ino_t ino, size_t size, off_t offset, void *cookie)
+
int fuse_reply_readlink(fuse_req_t req, const char *link)
+
int fuse_reply_iov(fuse_req_t req, const struct iovec *iov, int count)
+
int fuse_reply_bmap(fuse_req_t req, uint64_t idx)
+
int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
+
void fuse_session_unmount(struct fuse_session *se)
+
void fuse_reply_none(fuse_req_t req)
+
int fuse_req_get_payload(fuse_req_t req, char **payload, size_t *payload_sz, void **mr)
+
int fuse_lowlevel_notify_expire_entry(struct fuse_session *se, fuse_ino_t parent, const char *name, size_t namelen)
+
int fuse_reply_ioctl_retry(fuse_req_t req, const struct iovec *in_iov, size_t in_count, const struct iovec *out_iov, size_t out_count)
+
void fuse_lowlevel_help(void)
+
int fuse_lowlevel_notify_inval_inode(struct fuse_session *se, fuse_ino_t ino, off_t off, off_t len)
+
int fuse_reply_statfs(fuse_req_t req, const struct statvfs *stbuf)
+
int fuse_reply_write(fuse_req_t req, size_t count)
+
int fuse_session_receive_buf(struct fuse_session *se, struct fuse_buf *buf)
+
int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
+
int fuse_lowlevel_notify_increment_epoch(struct fuse_session *se)
+
int fuse_lowlevel_notify_poll(struct fuse_pollhandle *ph)
+
int fuse_lowlevel_notify_inval_entry(struct fuse_session *se, fuse_ino_t parent, const char *name, size_t namelen)
+
void fuse_req_interrupt_func(fuse_req_t req, fuse_interrupt_func_t func, void *data)
+
void fuse_session_reset(struct fuse_session *se)
+
int fuse_reply_create(fuse_req_t req, const struct fuse_entry_param *e, const struct fuse_file_info *fi)
+
int fuse_reply_lseek(fuse_req_t req, off_t off)
+
void fuse_lowlevel_version(void)
+
int fuse_reply_statx(fuse_req_t req, int flags, struct statx *statx, double attr_timeout)
+
uint64_t fuse_ino_t
+
size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct stat *stbuf, off_t off)
+
int fuse_reply_attr(fuse_req_t req, const struct stat *attr, double attr_timeout)
+
int fuse_reply_ioctl(fuse_req_t req, int result, const void *buf, size_t size)
+
int fuse_passthrough_open(fuse_req_t req, int fd)
+
int fuse_lowlevel_notify_store(struct fuse_session *se, fuse_ino_t ino, off_t offset, struct fuse_bufvec *bufv, enum fuse_buf_copy_flags flags)
+
int fuse_reply_xattr(fuse_req_t req, size_t count)
+ +
int fuse_opt_add_arg(struct fuse_args *args, const char *arg)
Definition fuse_opt.c:55
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+ + +
char ** argv
Definition fuse_opt.h:114
+ +
enum fuse_buf_flags flags
+
size_t mem_size
+
void * mem
+ +
size_t size
+ + + +
struct fuse_buf buf[1]
+ + + +
uint32_t proto_minor
+
uint64_t capable_ext
+
uint64_t want_ext
+ + + +
mode_t umask
+ +
+
double entry_timeout
+
fuse_ino_t ino
+
uint64_t generation
+
double attr_timeout
+
struct stat attr
+ +
uint64_t lock_owner
+
uint32_t kill_suidgid
+ +
uint32_t writepage
Definition fuse_common.h:60
+
uint32_t poll_events
+
uint32_t cache_readdir
Definition fuse_common.h:89
+
uint32_t nonseekable
Definition fuse_common.h:78
+
int32_t backing_id
+
uint32_t parallel_direct_writes
Definition fuse_common.h:97
+
uint32_t noflush
Definition fuse_common.h:93
+
uint32_t flush
Definition fuse_common.h:74
+
uint32_t direct_io
Definition fuse_common.h:63
+
uint32_t keep_cache
Definition fuse_common.h:69
+ + +
void(* fsync)(fuse_req_t req, fuse_ino_t ino, int datasync, struct fuse_file_info *fi)
+
void(* bmap)(fuse_req_t req, fuse_ino_t ino, size_t blocksize, uint64_t idx)
+
void(* unlink)(fuse_req_t req, fuse_ino_t parent, const char *name)
+
void(* getxattr)(fuse_req_t req, fuse_ino_t ino, const char *name, size_t size)
+
void(* rename)(fuse_req_t req, fuse_ino_t parent, const char *name, fuse_ino_t newparent, const char *newname, unsigned int flags)
+
void(* releasedir)(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
+
void(* setxattr)(fuse_req_t req, fuse_ino_t ino, const char *name, const char *value, size_t size, int flags)
+
void(* symlink)(fuse_req_t req, const char *link, fuse_ino_t parent, const char *name)
+
void(* statfs)(fuse_req_t req, fuse_ino_t ino)
+
void(* tmpfile)(fuse_req_t req, fuse_ino_t parent, mode_t mode, struct fuse_file_info *fi)
+
void(* release)(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
+
void(* readdir)(fuse_req_t req, fuse_ino_t ino, size_t size, off_t off, struct fuse_file_info *fi)
+
void(* fallocate)(fuse_req_t req, fuse_ino_t ino, int mode, off_t offset, off_t length, struct fuse_file_info *fi)
+
void(* create)(fuse_req_t req, fuse_ino_t parent, const char *name, mode_t mode, struct fuse_file_info *fi)
+
void(* link)(fuse_req_t req, fuse_ino_t ino, fuse_ino_t newparent, const char *newname)
+
void(* write)(fuse_req_t req, fuse_ino_t ino, const char *buf, size_t size, off_t off, struct fuse_file_info *fi)
+
void(* lookup)(fuse_req_t req, fuse_ino_t parent, const char *name)
+
void(* readdirplus)(fuse_req_t req, fuse_ino_t ino, size_t size, off_t off, struct fuse_file_info *fi)
+
void(* flock)(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi, int op)
+
void(* getattr)(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
+
void(* copy_file_range)(fuse_req_t req, fuse_ino_t ino_in, off_t off_in, struct fuse_file_info *fi_in, fuse_ino_t ino_out, off_t off_out, struct fuse_file_info *fi_out, size_t len, int flags)
+
void(* setattr)(fuse_req_t req, fuse_ino_t ino, struct stat *attr, int to_set, struct fuse_file_info *fi)
+
void(* open)(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
+
void(* removexattr)(fuse_req_t req, fuse_ino_t ino, const char *name)
+
void(* forget)(fuse_req_t req, fuse_ino_t ino, uint64_t nlookup)
+
void(* poll)(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi, struct fuse_pollhandle *ph)
+
void(* ioctl)(fuse_req_t req, fuse_ino_t ino, unsigned int cmd, void *arg, struct fuse_file_info *fi, unsigned flags, const void *in_buf, size_t in_bufsz, size_t out_bufsz)
+
void(* rmdir)(fuse_req_t req, fuse_ino_t parent, const char *name)
+
void(* setlk)(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi, struct flock *lock, int sleep)
+
void(* fsyncdir)(fuse_req_t req, fuse_ino_t ino, int datasync, struct fuse_file_info *fi)
+
void(* mkdir)(fuse_req_t req, fuse_ino_t parent, const char *name, mode_t mode)
+
void(* statx)(fuse_req_t req, fuse_ino_t ino, int flags, int mask, struct fuse_file_info *fi)
+
void(* access)(fuse_req_t req, fuse_ino_t ino, int mask)
+
void(* mknod)(fuse_req_t req, fuse_ino_t parent, const char *name, mode_t mode, dev_t rdev)
+
void(* lseek)(fuse_req_t req, fuse_ino_t ino, off_t off, int whence, struct fuse_file_info *fi)
+
void(* getlk)(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi, struct flock *lock)
+
void(* readlink)(fuse_req_t req, fuse_ino_t ino)
+
void(* read)(fuse_req_t req, fuse_ino_t ino, size_t size, off_t off, struct fuse_file_info *fi)
+
void(* flush)(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
+
void(* listxattr)(fuse_req_t req, fuse_ino_t ino, size_t size)
+
void(* opendir)(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
+
void(* forget_multi)(fuse_req_t req, size_t count, struct fuse_forget_data *forgets)
+ + +
+ + +
+ + diff --git a/doc/html/fuse__lowlevel_8h.html b/doc/html/fuse__lowlevel_8h.html new file mode 100644 index 0000000..fbc96ed --- /dev/null +++ b/doc/html/fuse__lowlevel_8h.html @@ -0,0 +1,2163 @@ + + + + + + + +libfuse: include/fuse_lowlevel.h File Reference + + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
+ +
fuse_lowlevel.h File Reference
+
+
+
#include "fuse_common.h"
+#include <stddef.h>
+#include <utime.h>
+#include <fcntl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/statvfs.h>
+#include <sys/uio.h>
+
+

Go to the source code of this file.

+ + + + + + +

+Data Structures

struct  fuse_entry_param
struct  fuse_ctx
struct  fuse_lowlevel_ops
struct  fuse_cmdline_opts
+ + +

+Macros

#define FUSE_ROOT_ID   1
+ + + + +

+Typedefs

typedef uint64_t fuse_ino_t
typedef struct fuse_req * fuse_req_t
typedef void(* fuse_interrupt_func_t) (fuse_req_t req, void *data)
+ + +

+Enumerations

enum  fuse_notify_entry_flags
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+Functions

int fuse_reply_err (fuse_req_t req, int err)
void fuse_reply_none (fuse_req_t req)
int fuse_reply_entry (fuse_req_t req, const struct fuse_entry_param *e)
int fuse_reply_create (fuse_req_t req, const struct fuse_entry_param *e, const struct fuse_file_info *fi)
int fuse_reply_attr (fuse_req_t req, const struct stat *attr, double attr_timeout)
int fuse_reply_readlink (fuse_req_t req, const char *link)
int fuse_passthrough_open (fuse_req_t req, int fd)
int fuse_reply_open (fuse_req_t req, const struct fuse_file_info *fi)
int fuse_reply_write (fuse_req_t req, size_t count)
int fuse_reply_buf (fuse_req_t req, const char *buf, size_t size)
int fuse_reply_data (fuse_req_t req, struct fuse_bufvec *bufv, enum fuse_buf_copy_flags flags)
int fuse_reply_iov (fuse_req_t req, const struct iovec *iov, int count)
int fuse_reply_statfs (fuse_req_t req, const struct statvfs *stbuf)
int fuse_reply_xattr (fuse_req_t req, size_t count)
int fuse_reply_lock (fuse_req_t req, const struct flock *lock)
int fuse_reply_bmap (fuse_req_t req, uint64_t idx)
size_t fuse_add_direntry (fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct stat *stbuf, off_t off)
size_t fuse_add_direntry_plus (fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct fuse_entry_param *e, off_t off)
int fuse_reply_ioctl_retry (fuse_req_t req, const struct iovec *in_iov, size_t in_count, const struct iovec *out_iov, size_t out_count)
int fuse_reply_ioctl (fuse_req_t req, int result, const void *buf, size_t size)
int fuse_reply_ioctl_iov (fuse_req_t req, int result, const struct iovec *iov, int count)
int fuse_reply_poll (fuse_req_t req, unsigned revents)
int fuse_reply_lseek (fuse_req_t req, off_t off)
int fuse_reply_statx (fuse_req_t req, int flags, struct statx *statx, double attr_timeout)
int fuse_lowlevel_notify_poll (struct fuse_pollhandle *ph)
int fuse_lowlevel_notify_inval_inode (struct fuse_session *se, fuse_ino_t ino, off_t off, off_t len)
int fuse_lowlevel_notify_increment_epoch (struct fuse_session *se)
int fuse_lowlevel_notify_inval_entry (struct fuse_session *se, fuse_ino_t parent, const char *name, size_t namelen)
int fuse_lowlevel_notify_expire_entry (struct fuse_session *se, fuse_ino_t parent, const char *name, size_t namelen)
int fuse_lowlevel_notify_delete (struct fuse_session *se, fuse_ino_t parent, fuse_ino_t child, const char *name, size_t namelen)
int fuse_lowlevel_notify_store (struct fuse_session *se, fuse_ino_t ino, off_t offset, struct fuse_bufvec *bufv, enum fuse_buf_copy_flags flags)
int fuse_lowlevel_notify_retrieve (struct fuse_session *se, fuse_ino_t ino, size_t size, off_t offset, void *cookie)
void * fuse_req_userdata (fuse_req_t req)
const struct fuse_ctx * fuse_req_ctx (fuse_req_t req)
int fuse_req_getgroups (fuse_req_t req, int size, gid_t list[])
void fuse_req_interrupt_func (fuse_req_t req, fuse_interrupt_func_t func, void *data)
int fuse_req_interrupted (fuse_req_t req)
void fuse_lowlevel_version (void)
void fuse_lowlevel_help (void)
void fuse_cmdline_help (void)
int fuse_parse_cmdline_30 (struct fuse_args *args, struct fuse_cmdline_opts *opts)
int fuse_session_mount (struct fuse_session *se, const char *mountpoint)
int fuse_session_loop (struct fuse_session *se)
void fuse_session_exit (struct fuse_session *se)
void fuse_session_reset (struct fuse_session *se)
int fuse_session_exited (struct fuse_session *se)
void fuse_session_unmount (struct fuse_session *se)
void fuse_session_destroy (struct fuse_session *se)
int fuse_session_fd (struct fuse_session *se)
void fuse_session_process_buf (struct fuse_session *se, const struct fuse_buf *buf)
int fuse_session_receive_buf (struct fuse_session *se, struct fuse_buf *buf)
bool fuse_req_is_uring (fuse_req_t req)
int fuse_req_get_payload (fuse_req_t req, char **payload, size_t *payload_sz, void **mr)
+

Detailed Description

+

Low level API

+

IMPORTANT: you should define FUSE_USE_VERSION before including this header. To use the newest API define it to 35 (recommended for any new application).

+ +

Definition in file fuse_lowlevel.h.

+

Macro Definition Documentation

+ +

◆ FUSE_ROOT_ID

+ +
+
+ + + + +
#define FUSE_ROOT_ID   1
+
+

The node ID of the root inode

+ +

Definition at line 44 of file fuse_lowlevel.h.

+ +
+
+

Typedef Documentation

+ +

◆ fuse_ino_t

+ +
+
+ + + + +
typedef uint64_t fuse_ino_t
+
+

Inode number type

+ +

Definition at line 47 of file fuse_lowlevel.h.

+ +
+
+ +

◆ fuse_interrupt_func_t

+ +
+
+ + + + +
typedef void(* fuse_interrupt_func_t) (fuse_req_t req, void *data)
+
+

Callback function for an interrupt

+
Parameters
+ + + +
reqinterrupted request
datauser data
+
+
+ +

Definition at line 1992 of file fuse_lowlevel.h.

+ +
+
+ +

◆ fuse_req_t

+ +
+
+ + + + +
typedef struct fuse_req* fuse_req_t
+
+

Request pointer type

+ +

Definition at line 50 of file fuse_lowlevel.h.

+ +
+
+

Enumeration Type Documentation

+ +

◆ fuse_notify_entry_flags

+ +
+
+ + + + +
enum fuse_notify_entry_flags
+
+

Flags for fuse_lowlevel_notify_entry() 0 = invalidate entry FUSE_LL_EXPIRE_ONLY = expire entry

+ +

Definition at line 151 of file fuse_lowlevel.h.

+ +
+
+

Function Documentation

+ +

◆ fuse_add_direntry()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
size_t fuse_add_direntry (fuse_req_t req,
char * buf,
size_t bufsize,
const char * name,
const struct stat * stbuf,
off_t off )
+
+

Add a directory entry to the buffer

+

Buffer needs to be large enough to hold the entry. If it's not, then the entry is not filled in but the size of the entry is still returned. The caller can check this by comparing the bufsize parameter with the returned entry size. If the entry size is larger than the buffer size, the operation failed.

+

From the 'stbuf' argument the st_ino field and bits 12-15 of the st_mode field are used. The other fields are ignored.

+

off should be any non-zero value that the filesystem can use to identify the current point in the directory stream. It does not need to be the actual physical position. A value of zero is reserved to mean "from the beginning", and should therefore never be used (the first call to fuse_add_direntry should be passed the offset of the second directory entry).

+
Parameters
+ + + + + + + +
reqrequest handle
bufthe point where the new entry will be added to the buffer
bufsizeremaining size of the buffer
namethe name of the entry
stbufthe file attributes
offthe offset of the next entry
+
+
+
Returns
the space needed for the entry
+ +

Definition at line 368 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_add_direntry_plus()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
size_t fuse_add_direntry_plus (fuse_req_t req,
char * buf,
size_t bufsize,
const char * name,
const struct fuse_entry_param * e,
off_t off )
+
+

Add a directory entry to the buffer with the attributes

+

See documentation of fuse_add_direntry() for more details.

+
Parameters
+ + + + + + + +
reqrequest handle
bufthe point where the new entry will be added to the buffer
bufsizeremaining size of the buffer
namethe name of the entry
ethe directory entry
offthe offset of the next entry
+
+
+
Returns
the space needed for the entry
+ +

Definition at line 462 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_cmdline_help()

+ +
+
+ + + + + + + +
void fuse_cmdline_help (void )
+
+

Print available options for fuse_parse_cmdline().

+ +

Definition at line 130 of file helper.c.

+ +
+
+ +

◆ fuse_lowlevel_help()

+ +
+
+ + + + + + + +
void fuse_lowlevel_help (void )
+
+

Print available low-level options to stdout. This is not an exhaustive list, but includes only those options that may be of interest to an end-user of a file system.

+ +

Definition at line 3829 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_lowlevel_notify_delete()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
int fuse_lowlevel_notify_delete (struct fuse_session * se,
fuse_ino_t parent,
fuse_ino_t child,
const char * name,
size_t namelen )
+
+

This function behaves like fuse_lowlevel_notify_inval_entry() with the following additional effect (at least as of Linux kernel 4.8):

+

If the provided child inode matches the inode that is currently associated with the cached dentry, and if there are any inotify watches registered for the dentry, then the watchers are informed that the dentry has been deleted.

+

To avoid a deadlock this function must not be called while executing a related filesystem operation or while holding a lock that could be needed to execute such an operation (see the description of fuse_lowlevel_notify_inval_entry() for more details).

+

When called correctly, this function will never block.

+

Added in FUSE protocol version 7.18. If the kernel does not support this (or a newer) version, the function will return -ENOSYS and do nothing.

+
Parameters
+ + + + + + +
sethe session object
parentinode number
childinode number
namefile name
namelenstrlen() of file name
+
+
+
Returns
zero for success, -errno for failure
+ +

Definition at line 3223 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_lowlevel_notify_expire_entry()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + +
int fuse_lowlevel_notify_expire_entry (struct fuse_session * se,
fuse_ino_t parent,
const char * name,
size_t namelen )
+
+

Notify to expire parent attributes and the dentry matching parent/name

+

Same restrictions apply as for fuse_lowlevel_notify_inval_entry()

+

Compared to invalidating an entry, expiring the entry results not in a forceful removal of that entry from kernel cache but instead the next access to it forces a lookup from the filesystem.

+

This makes a difference for overmounted dentries, where plain invalidation would detach all submounts before dropping the dentry from the cache. If only expiry is set on the dentry, then any overmounts are left alone and until ->d_revalidate() is called.

+

Note: ->d_revalidate() is not called for the case of following a submount, so invalidation will only be triggered for the non-overmounted case. The dentry could also be mounted in a different mount instance, in which case any submounts will still be detached.

+

Added in FUSE protocol version 7.38. If the kernel does not support this (or a newer) version, the function will return -ENOSYS and do nothing.

+
Parameters
+ + + + + +
sethe session object
parentinode number
namefile name
namelenstrlen() of file name
+
+
+
Returns
zero for success, -errno for failure, -enosys if no kernel support
+ +

Definition at line 3210 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_lowlevel_notify_increment_epoch()

+ +
+
+ + + + + + + +
int fuse_lowlevel_notify_increment_epoch (struct fuse_session * se)
+
+

Notify to increment the epoch for the current

+

Each fuse connection has an 'epoch', which is initialized during INIT. Caching will then be validated against the epoch value: if the current epoch is higher than an object being revalidated, the object is invalid.

+

This function simply increment the current epoch value.

+
Parameters
+ + +
sethe session object
+
+
+
Returns
zero for success, -errno for failure
+ +

Definition at line 3145 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_lowlevel_notify_inval_entry()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + +
int fuse_lowlevel_notify_inval_entry (struct fuse_session * se,
fuse_ino_t parent,
const char * name,
size_t namelen )
+
+

Notify to invalidate parent attributes and the dentry matching parent/name

+

To avoid a deadlock this function must not be called in the execution path of a related filesystem operation or within any code that could hold a lock that could be needed to execute such an operation. As of kernel 4.18, a "related operation" is a lookup(), symlink(), mknod(), mkdir(), unlink(), rename(), link() or create() request for the parent, and a setattr(), unlink(), rmdir(), rename(), setxattr(), removexattr(), readdir() or readdirplus() request for the inode itself.

+

When called correctly, this function will never block.

+

Added in FUSE protocol version 7.12. If the kernel does not support this (or a newer) version, the function will return -ENOSYS and do nothing.

+
Parameters
+ + + + + +
sethe session object
parentinode number
namefile name
namelenstrlen() of file name
+
+
+
Returns
zero for success, -errno for failure
+ +

Definition at line 3204 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_lowlevel_notify_inval_inode()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + +
int fuse_lowlevel_notify_inval_inode (struct fuse_session * se,
fuse_ino_t ino,
off_t off,
off_t len )
+
+

Notify to invalidate cache for an inode.

+

Added in FUSE protocol version 7.12. If the kernel does not support this (or a newer) version, the function will return -ENOSYS and do nothing.

+

If the filesystem has writeback caching enabled, invalidating an inode will first trigger a writeback of all dirty pages. The call will block until all writeback requests have completed and the inode has been invalidated. It will, however, not wait for completion of pending writeback requests that have been issued before.

+

If there are no dirty pages, this function will never block.

+
Parameters
+ + + + + +
sethe session object
inothe inode number
offthe offset in the inode where to start invalidating or negative to invalidate attributes only
lenthe amount of cache to invalidate or 0 for all
+
+
+
Returns
zero for success, -errno for failure
+ +

Definition at line 3123 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_lowlevel_notify_poll()

+ +
+
+ + + + + + + +
int fuse_lowlevel_notify_poll (struct fuse_pollhandle * ph)
+
+

Notify IO readiness event

+

For more information, please read comment for poll operation.

+
Parameters
+ + +
phpoll handle to notify IO readiness event for
+
+
+ +

Definition at line 3106 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_lowlevel_notify_retrieve()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
int fuse_lowlevel_notify_retrieve (struct fuse_session * se,
fuse_ino_t ino,
size_t size,
off_t offset,
void * cookie )
+
+

Retrieve data from the kernel buffers

+

Retrieve data in the kernel buffers belonging to the given inode. If successful then the retrieve_reply() method will be called with the returned data.

+

Only present pages are returned in the retrieve reply. Retrieving stops when it finds a non-present page and only data prior to that is returned.

+

If this function returns an error, then the retrieve will not be completed and no reply will be sent.

+

This function doesn't change the dirty state of pages in the kernel buffer. For dirty pages the write() method will be called regardless of having been retrieved previously.

+

Added in FUSE protocol version 7.15. If the kernel does not support this (or a newer) version, the function will return -ENOSYS and do nothing.

+
Parameters
+ + + + + + +
sethe session object
inothe inode number
sizethe number of bytes to retrieve
offsetthe starting offset into the file to retrieve from
cookieuser data to supply to the reply callback
+
+
+
Returns
zero for success, -errno for failure
+ +

Definition at line 3329 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_lowlevel_notify_store()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
int fuse_lowlevel_notify_store (struct fuse_session * se,
fuse_ino_t ino,
off_t offset,
struct fuse_bufvec * bufv,
enum fuse_buf_copy_flags flags )
+
+

Store data to the kernel buffers

+

Synchronously store data in the kernel buffers belonging to the given inode. The stored data is marked up-to-date (no read will be performed against it, unless it's invalidated or evicted from the cache).

+

If the stored data overflows the current file size, then the size is extended, similarly to a write(2) on the filesystem.

+

If this function returns an error, then the store wasn't fully completed, but it may have been partially completed.

+

Added in FUSE protocol version 7.15. If the kernel does not support this (or a newer) version, the function will return -ENOSYS and do nothing.

+
Parameters
+ + + + + + +
sethe session object
inothe inode number
offsetthe starting offset into the file to store to
bufvbuffer vector
flagsflags controlling the copy
+
+
+
Returns
zero for success, -errno for failure
+ +

Definition at line 3249 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_lowlevel_version()

+ +
+
+ + + + + + + +
void fuse_lowlevel_version (void )
+
+

Print low-level version information to stdout.

+ +

Definition at line 3822 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_parse_cmdline_30()

+ +
+
+ + + + + + + + + + + +
int fuse_parse_cmdline_30 (struct fuse_args * args,
struct fuse_cmdline_opts * opts )
+
+

Utility function to parse common options for simple file systems using the low-level API. A help text that describes the available options can be printed with fuse_cmdline_help. A single non-option argument is treated as the mountpoint. Multiple non-option arguments will result in an error.

+

If neither -o subtype= or -o fsname= options are given, a new subtype option will be added and set to the basename of the program (the fsname will remain unset, and then defaults to "fuse").

+

Known options will be removed from args, unknown options will remain.

+
Parameters
+ + + +
argsargument vector (input+output)
optsoutput argument for parsed options
+
+
+
Returns
0 on success, -1 on failure
+

struct fuse_cmdline_opts got extended in libfuse-3.12

+ +

Definition at line 237 of file helper.c.

+ +
+
+ +

◆ fuse_passthrough_open()

+ +
+
+ + + + + + + + + + + +
int fuse_passthrough_open (fuse_req_t req,
int fd )
+
+

Setup passthrough backing file for open reply

+

Currently there should be only one backing id per node / backing file.

+

Possible requests: open, opendir, create

+
Parameters
+ + + +
reqrequest handle
fdbacking file descriptor
+
+
+
Returns
positive backing id for success, 0 for failure
+ +

Definition at line 566 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_attr()

+ +
+
+ + + + + + + + + + + + + + + + +
int fuse_reply_attr (fuse_req_t req,
const struct stat * attr,
double attr_timeout )
+
+

Reply with attributes

+

Possible requests: getattr, setattr

+
Parameters
+ + + + +
reqrequest handle
attrthe attributes
attr_timeoutvalidity timeout (in seconds) for the attributes
+
+
+
Returns
zero for success, -errno for failure to send reply
+ +

Definition at line 546 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_bmap()

+ +
+
+ + + + + + + + + + + +
int fuse_reply_bmap (fuse_req_t req,
uint64_t idx )
+
+

Reply with block index

+

Possible requests: bmap

+
Parameters
+ + + +
reqrequest handle
idxblock index within device
+
+
+
Returns
zero for success, -errno for failure to send reply
+ +

Definition at line 1090 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_buf()

+ +
+
+ + + + + + + + + + + + + + + + +
int fuse_reply_buf (fuse_req_t req,
const char * buf,
size_t size )
+
+

Reply with data

+

Possible requests: read, readdir, getxattr, listxattr

+
Parameters
+ + + + +
reqrequest handle
bufbuffer containing data
sizethe size of data in bytes
+
+
+
Returns
zero for success, -errno for failure to send reply
+ +

Definition at line 632 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_create()

+ +
+
+ + + + + + + + + + + + + + + + +
int fuse_reply_create (fuse_req_t req,
const struct fuse_entry_param * e,
const struct fuse_file_info * fi )
+
+

Reply with a directory entry and open parameters

+

currently the following members of 'fi' are used: fh, direct_io, keep_cache, cache_readdir, nonseekable, noflush, parallel_direct_writes

+

Possible requests: create

+

Side effects: increments the lookup count on success

+
Parameters
+ + + + +
reqrequest handle
ethe entry parameters
fifile information
+
+
+
Returns
zero for success, -errno for failure to send reply
+ +

Definition at line 530 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_data()

+ +
+
+ + + + + + + + + + + + + + + + +
int fuse_reply_data (fuse_req_t req,
struct fuse_bufvec * bufv,
enum fuse_buf_copy_flags flags )
+
+

Reply with data copied/moved from buffer(s)

+

Zero copy data transfer ("splicing") will be used under the following circumstances:

+
    +
  1. FUSE_CAP_SPLICE_WRITE is set in fuse_conn_info.want, and
  2. +
  3. the kernel supports splicing from the fuse device (FUSE_CAP_SPLICE_WRITE is set in fuse_conn_info.capable), and
  4. +
  5. flags does not contain FUSE_BUF_NO_SPLICE
  6. +
  7. The amount of data that is provided in file-descriptor backed buffers (i.e., buffers for which bufv[n].flags == FUSE_BUF_FD) is at least twice the page size.
  8. +
+

In order for SPLICE_F_MOVE to be used, the following additional conditions have to be fulfilled:

+
    +
  1. FUSE_CAP_SPLICE_MOVE is set in fuse_conn_info.want, and
  2. +
  3. the kernel supports it (i.e, FUSE_CAP_SPLICE_MOVE is set in fuse_conn_info.capable), and
  4. +
  5. flags contains FUSE_BUF_SPLICE_MOVE
  6. +
+

Note that, if splice is used, the data is actually spliced twice: once into a temporary pipe (to prepend header data), and then again into the kernel. If some of the provided buffers are memory-backed, the data in them is copied in step one and spliced in step two.

+

The FUSE_BUF_SPLICE_FORCE_SPLICE and FUSE_BUF_SPLICE_NONBLOCK flags are silently ignored.

+

Possible requests: read, readdir, getxattr, listxattr

+

Side effects: when used to return data from a readdirplus() (but not readdir()) call, increments the lookup count of each returned entry by one on success.

+
Parameters
+ + + + +
reqrequest handle
bufvbuffer vector
flagsflags controlling the copy
+
+
+
Returns
zero for success, -errno for failure to send reply
+ +

Definition at line 1026 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_entry()

+ +
+
+ + + + + + + + + + + +
int fuse_reply_entry (fuse_req_t req,
const struct fuse_entry_param * e )
+
+

Reply with a directory entry

+

Possible requests: lookup, mknod, mkdir, symlink, link

+

Side effects: increments the lookup count on success

+
Parameters
+ + + +
reqrequest handle
ethe entry parameters
+
+
+
Returns
zero for success, -errno for failure to send reply
+ +

Definition at line 514 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_err()

+ +
+
+ + + + + + + + + + + +
int fuse_reply_err (fuse_req_t req,
int err )
+
+

Reply with an error code or success.

+

Possible requests: all except forget, forget_multi, retrieve_reply

+

Wherever possible, error codes should be chosen from the list of documented error conditions in the corresponding system calls manpage.

+

An error code of ENOSYS is sometimes treated specially. This is indicated in the documentation of the affected handler functions.

+

The following requests may be answered with a zero error code: unlink, rmdir, rename, flush, release, fsync, fsyncdir, setxattr, removexattr, setlk.

+
Parameters
+ + + +
reqrequest handle
errthe positive error value, or zero for success
+
+
+
Returns
zero for success, -errno for failure to send reply
+ +

Definition at line 413 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_ioctl()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + +
int fuse_reply_ioctl (fuse_req_t req,
int result,
const void * buf,
size_t size )
+
+

Reply to finish ioctl

+

Possible requests: ioctl

+
Parameters
+ + + + + +
reqrequest handle
resultresult to be passed to the caller
bufbuffer containing output data
sizelength of output data
+
+
+ +

Definition at line 1188 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_ioctl_iov()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + +
int fuse_reply_ioctl_iov (fuse_req_t req,
int result,
const struct iovec * iov,
int count )
+
+

Reply to finish ioctl with iov buffer

+

Possible requests: ioctl

+
Parameters
+ + + + + +
reqrequest handle
resultresult to be passed to the caller
iovthe vector containing the data
countthe size of vector
+
+
+ +

Definition at line 1209 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_ioctl_retry()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
int fuse_reply_ioctl_retry (fuse_req_t req,
const struct iovec * in_iov,
size_t in_count,
const struct iovec * out_iov,
size_t out_count )
+
+

Reply to ask for data fetch and output buffer preparation. ioctl will be retried with the specified input data fetched and output buffer prepared.

+

Possible requests: ioctl

+
Parameters
+ + + + + + +
reqrequest handle
in_ioviovec specifying data to fetch from the caller
in_countnumber of entries in in_iov
out_ioviovec specifying addresses to write output to
out_countnumber of entries in out_iov
+
+
+
Returns
zero for success, -errno for failure to send reply
+ +

Definition at line 1118 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_iov()

+ +
+
+ + + + + + + + + + + + + + + + +
int fuse_reply_iov (fuse_req_t req,
const struct iovec * iov,
int count )
+
+

Reply with data vector

+

Possible requests: read, readdir, getxattr, listxattr

+
Parameters
+ + + + +
reqrequest handle
iovthe vector containing the data
countthe size of vector
+
+
+
Returns
zero for success, -errno for failure to send reply
+ +

Definition at line 347 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_lock()

+ +
+
+ + + + + + + + + + + +
int fuse_reply_lock (fuse_req_t req,
const struct flock * lock )
+
+

Reply with file lock information

+

Possible requests: getlk

+
Parameters
+ + + +
reqrequest handle
lockthe lock information
+
+
+
Returns
zero for success, -errno for failure to send reply
+ +

Definition at line 1073 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_lseek()

+ +
+
+ + + + + + + + + + + +
int fuse_reply_lseek (fuse_req_t req,
off_t off )
+
+

Reply with offset

+

Possible requests: lseek

+
Parameters
+ + + +
reqrequest handle
offoffset of next data or hole
+
+
+
Returns
zero for success, -errno for failure to send reply
+ +

Definition at line 1243 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_none()

+ +
+
+ + + + + + + +
void fuse_reply_none (fuse_req_t req)
+
+

Don't send reply

+

Possible requests: forget forget_multi retrieve_reply

+
Parameters
+ + +
reqrequest handle
+
+
+ +

Definition at line 418 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_open()

+ +
+
+ + + + + + + + + + + +
int fuse_reply_open (fuse_req_t req,
const struct fuse_file_info * fi )
+
+

Reply with open parameters

+

currently the following members of 'fi' are used: fh, direct_io, keep_cache, cache_readdir, nonseekable, noflush, parallel_direct_writes,

+

Possible requests: open, opendir

+
Parameters
+ + + +
reqrequest handle
fifile information
+
+
+
Returns
zero for success, -errno for failure to send reply
+ +

Definition at line 591 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_poll()

+ +
+
+ + + + + + + + + + + +
int fuse_reply_poll (fuse_req_t req,
unsigned revents )
+
+

Reply with poll result event mask

+
Parameters
+ + + +
reqrequest handle
reventspoll result event mask
+
+
+ +

Definition at line 1233 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_readlink()

+ +
+
+ + + + + + + + + + + +
int fuse_reply_readlink (fuse_req_t req,
const char * link )
+
+

Reply with the contents of a symbolic link

+

Possible requests: readlink

+
Parameters
+ + + +
reqrequest handle
linksymbolic link contents
+
+
+
Returns
zero for success, -errno for failure to send reply
+ +

Definition at line 561 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_statfs()

+ +
+
+ + + + + + + + + + + +
int fuse_reply_statfs (fuse_req_t req,
const struct statvfs * stbuf )
+
+

Reply with filesystem statistics

+

Possible requests: statfs

+
Parameters
+ + + +
reqrequest handle
stbuffilesystem statistics
+
+
+
Returns
zero for success, -errno for failure to send reply
+ +

Definition at line 1051 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_statx()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + +
int fuse_reply_statx (fuse_req_t req,
int flags,
struct statx * statx,
double attr_timeout )
+
+

Reply with extended file attributes.

+

Possible requests: statx

+
Parameters
+ + + + + +
reqrequest handle
flagsstatx flags
statxthe attributes
attr_timeoutvalidity timeout (in seconds) for the attributes
+
+
+
Returns
zero for success, -errno for failure to send reply
+ +

Definition at line 1268 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_write()

+ +
+
+ + + + + + + + + + + +
int fuse_reply_write (fuse_req_t req,
size_t count )
+
+

Reply with number of bytes written

+

Possible requests: write

+
Parameters
+ + + +
reqrequest handle
countthe number of bytes written
+
+
+
Returns
zero for success, -errno for failure to send reply
+ +

Definition at line 620 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_reply_xattr()

+ +
+
+ + + + + + + + + + + +
int fuse_reply_xattr (fuse_req_t req,
size_t count )
+
+

Reply with needed buffer size

+

Possible requests: getxattr, listxattr

+
Parameters
+ + + +
reqrequest handle
countthe buffer size needed in bytes
+
+
+
Returns
zero for success, -errno for failure to send reply
+ +

Definition at line 1063 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_req_ctx()

+ +
+
+ + + + + + + +
const struct fuse_ctx * fuse_req_ctx (fuse_req_t req)
+
+

Get the context from the request

+

The pointer returned by this function will only be valid for the request's lifetime

+
Parameters
+ + +
reqrequest handle
+
+
+
Returns
the context structure
+ +

Definition at line 3379 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_req_get_payload()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + +
int fuse_req_get_payload (fuse_req_t req,
char ** payload,
size_t * payload_sz,
void ** mr )
+
+

Get the payload of a request (for requests submitted through fuse-io-uring only)

+

This is useful for a file system that wants to write data directly to the request buffer. With io-uring the req is the buffer owner and the file system can write directly to the buffer and avoid extra copying. For example useful for network file systems.

+
Parameters
+ + + + + +
reqthe request
payloadpointer to the payload
payload_szsize of the payload
mrmemory registration handle, currently unused
+
+
+
Returns
0 on success, -errno on failure
+ +

Definition at line 3414 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_req_getgroups()

+ +
+
+ + + + + + + + + + + + + + + + +
int fuse_req_getgroups (fuse_req_t req,
int size,
gid_t list[] )
+
+

Get the current supplementary group IDs for the specified request

+

Similar to the getgroups(2) system call, except the return value is always the total number of group IDs, even if it is larger than the specified size.

+

The current fuse kernel module in linux (as of 2.6.30) doesn't pass the group list to userspace, hence this function needs to parse "/proc/$TID/task/$TID/status" to get the group IDs.

+

This feature may not be supported on all operating systems. In such a case this function will return -ENOSYS.

+
Parameters
+ + + + +
reqrequest handle
sizesize of given array
listarray of group IDs to be filled in
+
+
+
Returns
the total number of supplementary group IDs or -errno on failure
+ +

Definition at line 4501 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_req_interrupt_func()

+ +
+
+ + + + + + + + + + + + + + + + +
void fuse_req_interrupt_func (fuse_req_t req,
fuse_interrupt_func_t func,
void * data )
+
+

Register/unregister callback for an interrupt

+

If an interrupt has already happened, then the callback function is called from within this function, hence it's not possible for interrupts to be lost.

+
Parameters
+ + + + +
reqrequest handle
functhe callback function or NULL for unregister
datauser data passed to the callback function
+
+
+ +

Definition at line 3384 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_req_interrupted()

+ +
+
+ + + + + + + +
int fuse_req_interrupted (fuse_req_t req)
+
+

Check if a request has already been interrupted

+
Parameters
+ + +
reqrequest handle
+
+
+
Returns
1 if the request has been interrupted, 0 otherwise
+ +

Definition at line 3397 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_req_is_uring()

+ +
+
+ + + + + + + +
bool fuse_req_is_uring (fuse_req_t req)
+
+

Check if the request is submitted through fuse-io-uring

+ +

Definition at line 3408 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_req_userdata()

+ +
+
+ + + + + + + +
void * fuse_req_userdata (fuse_req_t req)
+
+

Get the userdata from the request

+
Parameters
+ + +
reqrequest handle
+
+
+
Returns
the user data passed to fuse_session_new()
+ +

Definition at line 3374 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_session_destroy()

+ +
+
+ + + + + + + +
void fuse_session_destroy (struct fuse_session * se)
+
+

Destroy a session

+
Parameters
+ + +
sethe session
+
+
+ +

Definition at line 3842 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_session_exit()

+ +
+
+ + + + + + + +
void fuse_session_exit (struct fuse_session * se)
+
+

Flag a session as terminated.

+

This will cause any running event loop to terminate on the next opportunity. fuse_session_loop_mt(), and fuse_session_loop() from FUSE_USE_VERSION 3.19 on, are woken up directly and return without waiting for a request, no matter which thread calls this.

+

The deprecated fuse_session_loop() below FUSE_USE_VERSION 3.19 is not woken up and only notices the flag once the next request is received, which may be far in the future if the filesystem is not currently being used by any clients. Sending a signal to its thread afterwards avoids that delay - if fuse_set_signal_handlers() is used, SIGPIPE will cause the thread to wake-up but otherwise be ignored.

+
Parameters
+ + +
sethe session
+
+
+ +
+
+ +

◆ fuse_session_exited()

+ +
+
+ + + + + + + +
int fuse_session_exited (struct fuse_session * se)
+
+

Query the terminated flag of a session

+
Parameters
+ + +
sethe session
+
+
+
Returns
1 if exited, 0 if not exited
+ +
+
+ +

◆ fuse_session_fd()

+ +
+
+ + + + + + + +
int fuse_session_fd (struct fuse_session * se)
+
+

Return file descriptor for communication with kernel.

+

The file selector can be used to integrate FUSE with a custom event loop. Whenever data is available for reading on the provided fd, the event loop should call fuse_session_receive_buf followed by fuse_session_process_buf to process the request.

+

The returned file descriptor is valid until fuse_session_unmount is called.

+
Parameters
+ + +
sethe session
+
+
+
Returns
a file descriptor
+ +

Definition at line 4421 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_session_loop()

+ +
+
+ + + + + + + +
int fuse_session_loop (struct fuse_session * se)
+
+

Enter a single threaded, blocking event loop.

+

When the event loop terminates because the connection to the FUSE kernel module has been closed, this function returns zero. This happens when the filesystem is unmounted regularly (by the filesystem owner or root running the umount(8) or fusermount(1) command), or if connection is explicitly severed by writing 1 to theabort file in /sys/fs/fuse/connections/NNN. The only way to distinguish between these two conditions is to check if the filesystem is still mounted after the session loop returns.

+

When some error occurs during request processing, the function returns a negated errno(3) value.

+

If the loop has been terminated because of a signal handler installed by fuse_set_signal_handlers(), this function returns the (positive) signal value that triggered the exit.

+
Parameters
+ + +
sethe session
+
+
+
Returns
0, -errno, or a signal value
+ +

Definition at line 19 of file fuse_loop.c.

+ +
+
+ +

◆ fuse_session_mount()

+ +
+
+ + + + + + + + + + + +
int fuse_session_mount (struct fuse_session * se,
const char * mountpoint )
+
+

Mount a FUSE file system.

+
Parameters
+ + + +
mountpointthe mount point path
sesession object
+
+
+
Returns
0 on success, -1 on failure.
+ +

Definition at line 4361 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_session_process_buf()

+ +
+
+ + + + + + + + + + + +
void fuse_session_process_buf (struct fuse_session * se,
const struct fuse_buf * buf )
+
+

Process a raw request supplied in a generic buffer

+

The fuse_buf may contain a memory buffer or a pipe file descriptor.

+
Parameters
+ + + +
sethe session
bufthe fuse_buf containing the request
+
+
+ +

Definition at line 3618 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_session_receive_buf()

+ +
+
+ + + + + + + + + + + +
int fuse_session_receive_buf (struct fuse_session * se,
struct fuse_buf * buf )
+
+

Read a raw request from the kernel into the supplied buffer.

+

Depending on file system options, system capabilities, and request size the request is either read into a memory buffer or spliced into a temporary pipe.

+
Parameters
+ + + +
sethe session
bufthe fuse_buf to store the request in
+
+
+
Returns
the actual size of the raw request, or -errno on error
+ +

Definition at line 4125 of file fuse_lowlevel.c.

+ +
+
+ +

◆ fuse_session_reset()

+ +
+
+ + + + + + + +
void fuse_session_reset (struct fuse_session * se)
+
+

Reset the terminated flag of a session

+
Parameters
+ + +
sethe session
+
+
+ +
+
+ +

◆ fuse_session_unmount()

+ +
+
+ + + + + + + +
void fuse_session_unmount (struct fuse_session * se)
+
+

Ensure that file system is unmounted.

+

In regular operation, the file system is typically unmounted by the user calling umount(8) or fusermount(1), which then terminates the FUSE session loop. However, the session loop may also terminate as a result of an explicit call to fuse_session_exit() (e.g. by a signal handler installed by fuse_set_signal_handler()). In this case the filesystem remains mounted, but any attempt to access it will block (while the filesystem process is still running) or give an ESHUTDOWN error (after the filesystem process has terminated).

+

If the communication channel with the FUSE kernel module is still open (i.e., if the session loop was terminated by an explicit call to fuse_session_exit()), this function will close it and unmount the filesystem. If the communication channel has been closed by the kernel, this method will do (almost) nothing.

+

NOTE: The above semantics mean that if the connection to the kernel is terminated via the /sys/fs/fuse/connections/NNN/abort file, this method will not unmount the filesystem.

+
Parameters
+ + +
sethe session
+
+
+ +

Definition at line 4426 of file fuse_lowlevel.c.

+ +
+
+
+ + +
+ + diff --git a/doc/html/fuse__lowlevel_8h_source.html b/doc/html/fuse__lowlevel_8h_source.html new file mode 100644 index 0000000..f5efe2e --- /dev/null +++ b/doc/html/fuse__lowlevel_8h_source.html @@ -0,0 +1,699 @@ + + + + + + + +libfuse: include/fuse_lowlevel.h Source File + + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
+
fuse_lowlevel.h
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 This program can be distributed under the terms of the GNU LGPLv2.
+
6 See the file LGPL2.txt.
+
7*/
+
8
+
9#ifndef FUSE_LOWLEVEL_H_
+
10#define FUSE_LOWLEVEL_H_
+
11
+
20
+
21#ifndef FUSE_USE_VERSION
+
22#error FUSE_USE_VERSION not defined
+
23#endif
+
24
+
25#include "fuse_common.h"
+
26
+
27#include <stddef.h>
+
28#include <utime.h>
+
29#include <fcntl.h>
+
30#include <sys/types.h>
+
31#include <sys/stat.h>
+
32#include <sys/statvfs.h>
+
33#include <sys/uio.h>
+
34
+
35#ifdef __cplusplus
+
36extern "C" {
+
37#endif
+
38
+
39/* ----------------------------------------------------------- *
+
40 * Miscellaneous definitions *
+
41 * ----------------------------------------------------------- */
+
42
+
44#define FUSE_ROOT_ID 1
+
45
+
47typedef uint64_t fuse_ino_t;
+
48
+
50typedef struct fuse_req *fuse_req_t;
+
51
+
52/* Forward declaration */
+
53struct statx;
+
54
+
60struct fuse_session;
+
61
+
+ + +
72
+
83 uint64_t generation;
+
84
+
92 struct stat attr;
+
93
+ +
99
+ +
105};
+
+
106
+
+
115struct fuse_ctx {
+
117 uid_t uid;
+
118
+
120 gid_t gid;
+
121
+
123 pid_t pid;
+
124
+
126 mode_t umask;
+
127};
+
+
128
+
129struct fuse_forget_data {
+
130 fuse_ino_t ino;
+
131 uint64_t nlookup;
+
132};
+
133
+
134struct fuse_custom_io {
+
135 ssize_t (*writev)(int fd, struct iovec *iov, int count, void *userdata);
+
136 ssize_t (*read)(int fd, void *buf, size_t buf_len, void *userdata);
+
137 ssize_t (*splice_receive)(int fdin, off_t *offin, int fdout,
+
138 off_t *offout, size_t len,
+
139 unsigned int flags, void *userdata);
+
140 ssize_t (*splice_send)(int fdin, off_t *offin, int fdout,
+
141 off_t *offout, size_t len,
+
142 unsigned int flags, void *userdata);
+
143 int (*clone_fd)(int master_fd);
+
144};
+
145
+
+ +
152 FUSE_LL_INVALIDATE = 0,
+
153 FUSE_LL_EXPIRE_ONLY = (1 << 0),
+
154};
+
+
155
+
156/* 'to_set' flags in setattr */
+
157#define FUSE_SET_ATTR_MODE (1 << 0)
+
158#define FUSE_SET_ATTR_UID (1 << 1)
+
159#define FUSE_SET_ATTR_GID (1 << 2)
+
160#define FUSE_SET_ATTR_SIZE (1 << 3)
+
161#define FUSE_SET_ATTR_ATIME (1 << 4)
+
162#define FUSE_SET_ATTR_MTIME (1 << 5)
+
163#define FUSE_SET_ATTR_ATIME_NOW (1 << 7)
+
164#define FUSE_SET_ATTR_MTIME_NOW (1 << 8)
+
165#define FUSE_SET_ATTR_FORCE (1 << 9)
+
166#define FUSE_SET_ATTR_CTIME (1 << 10)
+
167#define FUSE_SET_ATTR_KILL_SUID (1 << 11)
+
168#define FUSE_SET_ATTR_KILL_SGID (1 << 12)
+
169#define FUSE_SET_ATTR_FILE (1 << 13)
+
170#define FUSE_SET_ATTR_KILL_PRIV (1 << 14)
+
171#define FUSE_SET_ATTR_OPEN (1 << 15)
+
172#define FUSE_SET_ATTR_TIMES_SET (1 << 16)
+
173#define FUSE_SET_ATTR_TOUCH (1 << 17)
+
174
+
175/* ----------------------------------------------------------- *
+
176 * Request methods and replies *
+
177 * ----------------------------------------------------------- */
+
178
+
+ +
225 void (*init) (void *userdata, struct fuse_conn_info *conn);
+
226
+
238 void (*destroy) (void *userdata);
+
239
+
251 void (*lookup) (fuse_req_t req, fuse_ino_t parent, const char *name);
+
252
+
289 void (*forget) (fuse_req_t req, fuse_ino_t ino, uint64_t nlookup);
+
290
+
310 void (*getattr) (fuse_req_t req, fuse_ino_t ino,
+
311 struct fuse_file_info *fi);
+
312
+
347 void (*setattr) (fuse_req_t req, fuse_ino_t ino, struct stat *attr,
+
348 int to_set, struct fuse_file_info *fi);
+
349
+
360 void (*readlink) (fuse_req_t req, fuse_ino_t ino);
+
361
+
378 void (*mknod) (fuse_req_t req, fuse_ino_t parent, const char *name,
+
379 mode_t mode, dev_t rdev);
+
380
+
393 void (*mkdir) (fuse_req_t req, fuse_ino_t parent, const char *name,
+
394 mode_t mode);
+
395
+
411 void (*unlink) (fuse_req_t req, fuse_ino_t parent, const char *name);
+
412
+
428 void (*rmdir) (fuse_req_t req, fuse_ino_t parent, const char *name);
+
429
+
442 void (*symlink) (fuse_req_t req, const char *link, fuse_ino_t parent,
+
443 const char *name);
+
444
+
474 void (*rename) (fuse_req_t req, fuse_ino_t parent, const char *name,
+
475 fuse_ino_t newparent, const char *newname,
+
476 unsigned int flags);
+
477
+
490 void (*link) (fuse_req_t req, fuse_ino_t ino, fuse_ino_t newparent,
+
491 const char *newname);
+
492
+
556 void (*open) (fuse_req_t req, fuse_ino_t ino,
+
557 struct fuse_file_info *fi);
+
558
+
584 void (*read) (fuse_req_t req, fuse_ino_t ino, size_t size, off_t off,
+
585 struct fuse_file_info *fi);
+
586
+
613 void (*write) (fuse_req_t req, fuse_ino_t ino, const char *buf,
+
614 size_t size, off_t off, struct fuse_file_info *fi);
+
615
+
654 void (*flush) (fuse_req_t req, fuse_ino_t ino,
+
655 struct fuse_file_info *fi);
+
656
+
682 void (*release) (fuse_req_t req, fuse_ino_t ino,
+
683 struct fuse_file_info *fi);
+
684
+
704 void (*fsync) (fuse_req_t req, fuse_ino_t ino, int datasync,
+
705 struct fuse_file_info *fi);
+
706
+
736 void (*opendir) (fuse_req_t req, fuse_ino_t ino,
+
737 struct fuse_file_info *fi);
+
738
+
782 void (*readdir) (fuse_req_t req, fuse_ino_t ino, size_t size, off_t off,
+
783 struct fuse_file_info *fi);
+
784
+ +
802 struct fuse_file_info *fi);
+
803
+
826 void (*fsyncdir) (fuse_req_t req, fuse_ino_t ino, int datasync,
+
827 struct fuse_file_info *fi);
+
828
+
839 void (*statfs) (fuse_req_t req, fuse_ino_t ino);
+
840
+
852 void (*setxattr) (fuse_req_t req, fuse_ino_t ino, const char *name,
+
853 const char *value, size_t size, int flags);
+
854
+
883 void (*getxattr) (fuse_req_t req, fuse_ino_t ino, const char *name,
+
884 size_t size);
+
885
+
914 void (*listxattr) (fuse_req_t req, fuse_ino_t ino, size_t size);
+
915
+
931 void (*removexattr) (fuse_req_t req, fuse_ino_t ino, const char *name);
+
932
+
953 void (*access) (fuse_req_t req, fuse_ino_t ino, int mask);
+
954
+
982 void (*create) (fuse_req_t req, fuse_ino_t parent, const char *name,
+
983 mode_t mode, struct fuse_file_info *fi);
+
984
+
997 void (*getlk) (fuse_req_t req, fuse_ino_t ino,
+
998 struct fuse_file_info *fi, struct flock *lock);
+
999
+
1022 void (*setlk) (fuse_req_t req, fuse_ino_t ino,
+
1023 struct fuse_file_info *fi,
+
1024 struct flock *lock, int sleep);
+
1025
+
1046 void (*bmap) (fuse_req_t req, fuse_ino_t ino, size_t blocksize,
+
1047 uint64_t idx);
+
1048
+
1049#if FUSE_USE_VERSION < 35
+
1050 void (*ioctl) (fuse_req_t req, fuse_ino_t ino, int cmd,
+
1051 void *arg, struct fuse_file_info *fi, unsigned flags,
+
1052 const void *in_buf, size_t in_bufsz, size_t out_bufsz);
+
1053#else
+
1082 void (*ioctl) (fuse_req_t req, fuse_ino_t ino, unsigned int cmd,
+
1083 void *arg, struct fuse_file_info *fi, unsigned flags,
+
1084 const void *in_buf, size_t in_bufsz, size_t out_bufsz);
+
1085#endif
+
1086
+
1119 void (*poll) (fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi,
+
1120 struct fuse_pollhandle *ph);
+
1121
+ +
1150 struct fuse_bufvec *bufv, off_t off,
+
1151 struct fuse_file_info *fi);
+
1152
+
1165 void (*retrieve_reply) (fuse_req_t req, void *cookie, fuse_ino_t ino,
+
1166 off_t offset, struct fuse_bufvec *bufv);
+
1167
+
1179 void (*forget_multi) (fuse_req_t req, size_t count,
+
1180 struct fuse_forget_data *forgets);
+
1181
+
1197 void (*flock) (fuse_req_t req, fuse_ino_t ino,
+
1198 struct fuse_file_info *fi, int op);
+
1199
+
1220 void (*fallocate) (fuse_req_t req, fuse_ino_t ino, int mode,
+
1221 off_t offset, off_t length, struct fuse_file_info *fi);
+
1222
+
1248 void (*readdirplus) (fuse_req_t req, fuse_ino_t ino, size_t size, off_t off,
+
1249 struct fuse_file_info *fi);
+
1250
+ +
1282 off_t off_in, struct fuse_file_info *fi_in,
+
1283 fuse_ino_t ino_out, off_t off_out,
+
1284 struct fuse_file_info *fi_out, size_t len,
+
1285 int flags);
+
1286
+
1305 void (*lseek) (fuse_req_t req, fuse_ino_t ino, off_t off, int whence,
+
1306 struct fuse_file_info *fi);
+
1307
+
1326 void (*tmpfile) (fuse_req_t req, fuse_ino_t parent,
+
1327 mode_t mode, struct fuse_file_info *fi);
+
1328
+
1342 void (*statx)(fuse_req_t req, fuse_ino_t ino, int flags, int mask,
+
1343 struct fuse_file_info *fi);
+
1344};
+
+
1345
+
1367int fuse_reply_err(fuse_req_t req, int err);
+
1368
+
1379void fuse_reply_none(fuse_req_t req);
+
1380
+
1394int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e);
+
1395
+
1414int fuse_reply_create(fuse_req_t req, const struct fuse_entry_param *e,
+
1415 const struct fuse_file_info *fi);
+
1416
+
1428int fuse_reply_attr(fuse_req_t req, const struct stat *attr,
+
1429 double attr_timeout);
+
1430
+
1441int fuse_reply_readlink(fuse_req_t req, const char *link);
+
1442
+
1455int fuse_passthrough_open(fuse_req_t req, int fd);
+
1456int fuse_passthrough_close(fuse_req_t req, int backing_id);
+
1457
+
1472int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi);
+
1473
+
1484int fuse_reply_write(fuse_req_t req, size_t count);
+
1485
+
1497int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size);
+
1498
+
1542int fuse_reply_data(fuse_req_t req, struct fuse_bufvec *bufv,
+ +
1544
+
1556int fuse_reply_iov(fuse_req_t req, const struct iovec *iov, int count);
+
1557
+
1568int fuse_reply_statfs(fuse_req_t req, const struct statvfs *stbuf);
+
1569
+
1580int fuse_reply_xattr(fuse_req_t req, size_t count);
+
1581
+
1592int fuse_reply_lock(fuse_req_t req, const struct flock *lock);
+
1593
+
1604int fuse_reply_bmap(fuse_req_t req, uint64_t idx);
+
1605
+
1606/* ----------------------------------------------------------- *
+
1607 * Filling a buffer in readdir *
+
1608 * ----------------------------------------------------------- */
+
1609
+
1637size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize,
+
1638 const char *name, const struct stat *stbuf,
+
1639 off_t off);
+
1640
+
1654size_t fuse_add_direntry_plus(fuse_req_t req, char *buf, size_t bufsize,
+
1655 const char *name,
+
1656 const struct fuse_entry_param *e, off_t off);
+
1657
+ +
1674 const struct iovec *in_iov, size_t in_count,
+
1675 const struct iovec *out_iov, size_t out_count);
+
1676
+
1688int fuse_reply_ioctl(fuse_req_t req, int result, const void *buf, size_t size);
+
1689
+
1701int fuse_reply_ioctl_iov(fuse_req_t req, int result, const struct iovec *iov,
+
1702 int count);
+
1703
+
1710int fuse_reply_poll(fuse_req_t req, unsigned revents);
+
1711
+
1722int fuse_reply_lseek(fuse_req_t req, off_t off);
+
1723
+
1736int fuse_reply_statx(fuse_req_t req, int flags, struct statx *statx, double attr_timeout);
+
1737
+
1738/* ----------------------------------------------------------- *
+
1739 * Notification *
+
1740 * ----------------------------------------------------------- */
+
1741
+
1749int fuse_lowlevel_notify_poll(struct fuse_pollhandle *ph);
+
1750
+
1774int fuse_lowlevel_notify_inval_inode(struct fuse_session *se, fuse_ino_t ino,
+
1775 off_t off, off_t len);
+
1776
+
1789int fuse_lowlevel_notify_increment_epoch(struct fuse_session *se);
+
1790
+
1815int fuse_lowlevel_notify_inval_entry(struct fuse_session *se, fuse_ino_t parent,
+
1816 const char *name, size_t namelen);
+
1817
+
1846int fuse_lowlevel_notify_expire_entry(struct fuse_session *se, fuse_ino_t parent,
+
1847 const char *name, size_t namelen);
+
1848
+
1877int fuse_lowlevel_notify_delete(struct fuse_session *se,
+
1878 fuse_ino_t parent, fuse_ino_t child,
+
1879 const char *name, size_t namelen);
+
1880
+
1906int fuse_lowlevel_notify_store(struct fuse_session *se, fuse_ino_t ino,
+
1907 off_t offset, struct fuse_bufvec *bufv,
+ +
1938int fuse_lowlevel_notify_retrieve(struct fuse_session *se, fuse_ino_t ino,
+
1939 size_t size, off_t offset, void *cookie);
+
1940
+
1941
+
1942/* ----------------------------------------------------------- *
+
1943 * Utility functions *
+
1944 * ----------------------------------------------------------- */
+
1945
+
1952void *fuse_req_userdata(fuse_req_t req);
+
1953
+
1963const struct fuse_ctx *fuse_req_ctx(fuse_req_t req);
+
1964
+
1984int fuse_req_getgroups(fuse_req_t req, int size, gid_t list[]);
+
1985
+
1992typedef void (*fuse_interrupt_func_t)(fuse_req_t req, void *data);
+
1993
+ +
2006 void *data);
+
2007
+ +
2015
+
2016
+
2017/* ----------------------------------------------------------- *
+
2018 * Inquiry functions *
+
2019 * ----------------------------------------------------------- */
+
2020
+
2024void fuse_lowlevel_version(void);
+
2025
+
2031void fuse_lowlevel_help(void);
+
2032
+
2036void fuse_cmdline_help(void);
+
2037
+
2038/* ----------------------------------------------------------- *
+
2039 * Filesystem setup & teardown *
+
2040 * ----------------------------------------------------------- */
+
2041
+
+ +
2048 int singlethread;
+
2049 int foreground;
+
2050 int debug;
+
2051 int nodefault_subtype;
+
2052 char *mountpoint;
+
2053 int show_version;
+
2054 int show_help;
+
2055 int clone_fd;
+
2056 unsigned int max_idle_threads; /* discouraged, due to thread
+
2057 * destruct overhead */
+
2058
+
2059 /* Added in libfuse-3.12 */
+
2060 unsigned int max_threads;
+
2061};
+
+
2062
+
2081#if (defined(LIBFUSE_BUILT_WITH_VERSIONED_SYMBOLS))
+
2082int fuse_parse_cmdline(struct fuse_args *args,
+
2083 struct fuse_cmdline_opts *opts);
+
2084#else
+
2085#if FUSE_USE_VERSION < FUSE_MAKE_VERSION(3, 12)
+
2086int fuse_parse_cmdline_30(struct fuse_args *args,
+
2087 struct fuse_cmdline_opts *opts);
+
2088#define fuse_parse_cmdline(args, opts) fuse_parse_cmdline_30(args, opts)
+
2089#else
+
2090int fuse_parse_cmdline_312(struct fuse_args *args,
+
2091 struct fuse_cmdline_opts *opts);
+
2092#define fuse_parse_cmdline(args, opts) fuse_parse_cmdline_312(args, opts)
+
2093#endif
+
2094#endif
+
2095
+
2096/* Do not call this directly, use fuse_session_new() instead */
+
2097struct fuse_session *
+
2098fuse_session_new_versioned(struct fuse_args *args,
+
2099 const struct fuse_lowlevel_ops *op, size_t op_size,
+
2100 struct libfuse_version *version, void *userdata);
+
2101
+
2132static inline struct fuse_session *
+
2133fuse_session_new_fn(struct fuse_args *args, const struct fuse_lowlevel_ops *op,
+
2134 size_t op_size, void *userdata)
+
2135{
+
2136 struct libfuse_version version = {
+
2137 .major = FUSE_MAJOR_VERSION,
+
2138 .minor = FUSE_MINOR_VERSION,
+
2139 .hotfix = FUSE_HOTFIX_VERSION,
+
2140 .padding = 0
+
2141 };
+
2142
+
2143 return fuse_session_new_versioned(args, op, op_size, &version,
+
2144 userdata);
+
2145}
+
2146#define fuse_session_new(args, op, op_size, userdata) \
+
2147 fuse_session_new_fn(args, op, op_size, userdata)
+
2148
+
2149/*
+
2150 * This should mostly not be called directly, but instead the
+
2151 * fuse_session_custom_io() should be used.
+
2152 */
+
2153int fuse_session_custom_io_317(struct fuse_session *se,
+
2154 const struct fuse_custom_io *io, size_t op_size, int fd);
+
2155
+
2191#if FUSE_MAKE_VERSION(3, 17) <= FUSE_USE_VERSION
+
2192static inline int fuse_session_custom_io(struct fuse_session *se,
+
2193 const struct fuse_custom_io *io, size_t op_size, int fd)
+
2194{
+
2195 return fuse_session_custom_io_317(se, io, op_size, fd);
+
2196}
+
2197#else
+
2198static inline int fuse_session_custom_io(struct fuse_session *se,
+
2199 const struct fuse_custom_io *io, int fd)
+
2200{
+
2201 return fuse_session_custom_io_317(se, io,
+
2202 offsetof(struct fuse_custom_io, clone_fd), fd);
+
2203}
+
2204#endif
+
2205
+
2214int fuse_session_mount(struct fuse_session *se, const char *mountpoint);
+
2215
+
2238int fuse_session_loop(struct fuse_session *se);
+
2239
+
2240#if FUSE_USE_VERSION < 32
+
2241 int fuse_session_loop_mt_31(struct fuse_session *se, int clone_fd);
+
2242 #define fuse_session_loop_mt(se, clone_fd) fuse_session_loop_mt_31(se, clone_fd)
+
2243#elif FUSE_USE_VERSION < FUSE_MAKE_VERSION(3, 12)
+
2244 int fuse_session_loop_mt_32(struct fuse_session *se, struct fuse_loop_config *config);
+
2245 #define fuse_session_loop_mt(se, config) fuse_session_loop_mt_32(se, config)
+
2246#else
+
2247 #if (defined(LIBFUSE_BUILT_WITH_VERSIONED_SYMBOLS))
+
2259 int fuse_session_loop_mt(struct fuse_session *se, struct fuse_loop_config *config);
+
2260 #else
+
2261 int fuse_session_loop_mt_312(struct fuse_session *se, struct fuse_loop_config *config);
+
2262 #define fuse_session_loop_mt(se, config) fuse_session_loop_mt_312(se, config)
+
2263 #endif
+
2264#endif
+
2265
+
2283void fuse_session_exit(struct fuse_session *se);
+
2284
+
2290void fuse_session_reset(struct fuse_session *se);
+
2291
+
2298int fuse_session_exited(struct fuse_session *se);
+
2299
+
2324void fuse_session_unmount(struct fuse_session *se);
+
2325
+
2331void fuse_session_destroy(struct fuse_session *se);
+
2332
+
2333/* ----------------------------------------------------------- *
+
2334 * Custom event loop support *
+
2335 * ----------------------------------------------------------- */
+
2336
+
2351int fuse_session_fd(struct fuse_session *se);
+
2352
+
2361void fuse_session_process_buf(struct fuse_session *se,
+
2362 const struct fuse_buf *buf);
+
2363
+
2375int fuse_session_receive_buf(struct fuse_session *se, struct fuse_buf *buf);
+
2376
+ +
2381
+
2397int fuse_req_get_payload(fuse_req_t req, char **payload, size_t *payload_sz,
+
2398 void **mr);
+
2399
+
2400#ifdef __cplusplus
+
2401}
+
2402#endif
+
2403
+
2404#endif /* FUSE_LOWLEVEL_H_ */
+ +
fuse_buf_copy_flags
+
void fuse_session_destroy(struct fuse_session *se)
+
fuse_notify_entry_flags
+
int fuse_reply_data(fuse_req_t req, struct fuse_bufvec *bufv, enum fuse_buf_copy_flags flags)
+
int fuse_reply_lock(fuse_req_t req, const struct flock *lock)
+
int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi)
+
void fuse_session_exit(struct fuse_session *se)
+
void(* fuse_interrupt_func_t)(fuse_req_t req, void *data)
+
int fuse_reply_poll(fuse_req_t req, unsigned revents)
+
int fuse_reply_err(fuse_req_t req, int err)
+
bool fuse_req_is_uring(fuse_req_t req)
+
const struct fuse_ctx * fuse_req_ctx(fuse_req_t req)
+
void * fuse_req_userdata(fuse_req_t req)
+
int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
+
struct fuse_req * fuse_req_t
+
size_t fuse_add_direntry_plus(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct fuse_entry_param *e, off_t off)
+
int fuse_reply_ioctl_iov(fuse_req_t req, int result, const struct iovec *iov, int count)
+
int fuse_lowlevel_notify_delete(struct fuse_session *se, fuse_ino_t parent, fuse_ino_t child, const char *name, size_t namelen)
+
void fuse_session_process_buf(struct fuse_session *se, const struct fuse_buf *buf)
+
int fuse_session_exited(struct fuse_session *se)
+
int fuse_session_fd(struct fuse_session *se)
+
int fuse_req_interrupted(fuse_req_t req)
+
int fuse_req_getgroups(fuse_req_t req, int size, gid_t list[])
+
int fuse_lowlevel_notify_retrieve(struct fuse_session *se, fuse_ino_t ino, size_t size, off_t offset, void *cookie)
+
int fuse_reply_readlink(fuse_req_t req, const char *link)
+
int fuse_session_loop(struct fuse_session *se)
Definition fuse_loop.c:19
+
int fuse_reply_iov(fuse_req_t req, const struct iovec *iov, int count)
+
int fuse_reply_bmap(fuse_req_t req, uint64_t idx)
+
int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
+
void fuse_session_unmount(struct fuse_session *se)
+
void fuse_cmdline_help(void)
Definition helper.c:130
+
void fuse_reply_none(fuse_req_t req)
+
int fuse_req_get_payload(fuse_req_t req, char **payload, size_t *payload_sz, void **mr)
+
int fuse_lowlevel_notify_expire_entry(struct fuse_session *se, fuse_ino_t parent, const char *name, size_t namelen)
+
int fuse_reply_ioctl_retry(fuse_req_t req, const struct iovec *in_iov, size_t in_count, const struct iovec *out_iov, size_t out_count)
+
void fuse_lowlevel_help(void)
+
int fuse_lowlevel_notify_inval_inode(struct fuse_session *se, fuse_ino_t ino, off_t off, off_t len)
+
int fuse_reply_statfs(fuse_req_t req, const struct statvfs *stbuf)
+
int fuse_reply_write(fuse_req_t req, size_t count)
+
int fuse_session_receive_buf(struct fuse_session *se, struct fuse_buf *buf)
+
int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
+
int fuse_parse_cmdline_30(struct fuse_args *args, struct fuse_cmdline_opts *opts)
Definition helper.c:237
+
int fuse_lowlevel_notify_increment_epoch(struct fuse_session *se)
+
int fuse_lowlevel_notify_poll(struct fuse_pollhandle *ph)
+
int fuse_lowlevel_notify_inval_entry(struct fuse_session *se, fuse_ino_t parent, const char *name, size_t namelen)
+
void fuse_req_interrupt_func(fuse_req_t req, fuse_interrupt_func_t func, void *data)
+
void fuse_session_reset(struct fuse_session *se)
+
int fuse_reply_create(fuse_req_t req, const struct fuse_entry_param *e, const struct fuse_file_info *fi)
+
int fuse_reply_lseek(fuse_req_t req, off_t off)
+
void fuse_lowlevel_version(void)
+
int fuse_reply_statx(fuse_req_t req, int flags, struct statx *statx, double attr_timeout)
+
uint64_t fuse_ino_t
+
size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct stat *stbuf, off_t off)
+
int fuse_reply_attr(fuse_req_t req, const struct stat *attr, double attr_timeout)
+
int fuse_reply_ioctl(fuse_req_t req, int result, const void *buf, size_t size)
+
int fuse_passthrough_open(fuse_req_t req, int fd)
+
int fuse_lowlevel_notify_store(struct fuse_session *se, fuse_ino_t ino, off_t offset, struct fuse_bufvec *bufv, enum fuse_buf_copy_flags flags)
+
int fuse_reply_xattr(fuse_req_t req, size_t count)
+ + + + + + + + +
mode_t umask
+ +
+
double entry_timeout
+
fuse_ino_t ino
+
uint64_t generation
+
double attr_timeout
+
struct stat attr
+ +
int32_t backing_id
+ + + +
void(* fsync)(fuse_req_t req, fuse_ino_t ino, int datasync, struct fuse_file_info *fi)
+
void(* bmap)(fuse_req_t req, fuse_ino_t ino, size_t blocksize, uint64_t idx)
+
void(* unlink)(fuse_req_t req, fuse_ino_t parent, const char *name)
+
void(* getxattr)(fuse_req_t req, fuse_ino_t ino, const char *name, size_t size)
+
void(* rename)(fuse_req_t req, fuse_ino_t parent, const char *name, fuse_ino_t newparent, const char *newname, unsigned int flags)
+
void(* releasedir)(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
+
void(* setxattr)(fuse_req_t req, fuse_ino_t ino, const char *name, const char *value, size_t size, int flags)
+
void(* write_buf)(fuse_req_t req, fuse_ino_t ino, struct fuse_bufvec *bufv, off_t off, struct fuse_file_info *fi)
+
void(* symlink)(fuse_req_t req, const char *link, fuse_ino_t parent, const char *name)
+
void(* statfs)(fuse_req_t req, fuse_ino_t ino)
+
void(* tmpfile)(fuse_req_t req, fuse_ino_t parent, mode_t mode, struct fuse_file_info *fi)
+
void(* release)(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
+
void(* readdir)(fuse_req_t req, fuse_ino_t ino, size_t size, off_t off, struct fuse_file_info *fi)
+
void(* fallocate)(fuse_req_t req, fuse_ino_t ino, int mode, off_t offset, off_t length, struct fuse_file_info *fi)
+
void(* create)(fuse_req_t req, fuse_ino_t parent, const char *name, mode_t mode, struct fuse_file_info *fi)
+
void(* link)(fuse_req_t req, fuse_ino_t ino, fuse_ino_t newparent, const char *newname)
+
void(* write)(fuse_req_t req, fuse_ino_t ino, const char *buf, size_t size, off_t off, struct fuse_file_info *fi)
+
void(* lookup)(fuse_req_t req, fuse_ino_t parent, const char *name)
+
void(* readdirplus)(fuse_req_t req, fuse_ino_t ino, size_t size, off_t off, struct fuse_file_info *fi)
+
void(* flock)(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi, int op)
+
void(* getattr)(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
+
void(* copy_file_range)(fuse_req_t req, fuse_ino_t ino_in, off_t off_in, struct fuse_file_info *fi_in, fuse_ino_t ino_out, off_t off_out, struct fuse_file_info *fi_out, size_t len, int flags)
+
void(* setattr)(fuse_req_t req, fuse_ino_t ino, struct stat *attr, int to_set, struct fuse_file_info *fi)
+
void(* open)(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
+
void(* removexattr)(fuse_req_t req, fuse_ino_t ino, const char *name)
+
void(* forget)(fuse_req_t req, fuse_ino_t ino, uint64_t nlookup)
+
void(* poll)(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi, struct fuse_pollhandle *ph)
+
void(* ioctl)(fuse_req_t req, fuse_ino_t ino, unsigned int cmd, void *arg, struct fuse_file_info *fi, unsigned flags, const void *in_buf, size_t in_bufsz, size_t out_bufsz)
+
void(* rmdir)(fuse_req_t req, fuse_ino_t parent, const char *name)
+
void(* init)(void *userdata, struct fuse_conn_info *conn)
+
void(* setlk)(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi, struct flock *lock, int sleep)
+
void(* retrieve_reply)(fuse_req_t req, void *cookie, fuse_ino_t ino, off_t offset, struct fuse_bufvec *bufv)
+
void(* fsyncdir)(fuse_req_t req, fuse_ino_t ino, int datasync, struct fuse_file_info *fi)
+
void(* destroy)(void *userdata)
+
void(* mkdir)(fuse_req_t req, fuse_ino_t parent, const char *name, mode_t mode)
+
void(* statx)(fuse_req_t req, fuse_ino_t ino, int flags, int mask, struct fuse_file_info *fi)
+
void(* access)(fuse_req_t req, fuse_ino_t ino, int mask)
+
void(* mknod)(fuse_req_t req, fuse_ino_t parent, const char *name, mode_t mode, dev_t rdev)
+
void(* lseek)(fuse_req_t req, fuse_ino_t ino, off_t off, int whence, struct fuse_file_info *fi)
+
void(* getlk)(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi, struct flock *lock)
+
void(* readlink)(fuse_req_t req, fuse_ino_t ino)
+
void(* read)(fuse_req_t req, fuse_ino_t ino, size_t size, off_t off, struct fuse_file_info *fi)
+
void(* flush)(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
+
void(* listxattr)(fuse_req_t req, fuse_ino_t ino, size_t size)
+
void(* opendir)(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
+
void(* forget_multi)(fuse_req_t req, size_t count, struct fuse_forget_data *forgets)
+ +
+ + +
+ + diff --git a/doc/html/fuse__misc_8h_source.html b/doc/html/fuse__misc_8h_source.html new file mode 100644 index 0000000..031399f --- /dev/null +++ b/doc/html/fuse__misc_8h_source.html @@ -0,0 +1,110 @@ + + + + + + + +libfuse: lib/fuse_misc.h Source File + + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
+
fuse_misc.h
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 This program can be distributed under the terms of the GNU LGPLv2.
+
6 See the file LGPL2.txt
+
7*/
+
8
+
9#include <pthread.h>
+
10
+
11/*
+
12 Versioned symbols cannot be used in some cases because it
+
13 - not supported on MacOSX (in MachO binary format)
+
14
+
15 Note: "@@" denotes the default symbol, "@" is binary a compat version.
+
16
+
17*/
+
18#ifdef LIBFUSE_BUILT_WITH_VERSIONED_SYMBOLS
+
19# if HAVE_SYMVER_ATTRIBUTE
+
20# define FUSE_SYMVER(sym1, sym2) __attribute__ ((symver (sym2)))
+
21# else
+
22# define FUSE_SYMVER(sym1, sym2) __asm__("\t.symver " sym1 "," sym2);
+
23# endif
+
24#else
+
25#define FUSE_SYMVER(sym1, sym2)
+
26#endif
+
27
+
28#ifdef HAVE_STRUCT_STAT_ST_ATIM
+
29/* Linux */
+
30#define ST_ATIM_NSEC(stbuf) ((stbuf)->st_atim.tv_nsec)
+
31#define ST_CTIM_NSEC(stbuf) ((stbuf)->st_ctim.tv_nsec)
+
32#define ST_MTIM_NSEC(stbuf) ((stbuf)->st_mtim.tv_nsec)
+
33#define ST_ATIM_NSEC_SET(stbuf, val) (stbuf)->st_atim.tv_nsec = (val)
+
34#define ST_CTIM_NSEC_SET(stbuf, val) (stbuf)->st_ctim.tv_nsec = (val)
+
35#define ST_MTIM_NSEC_SET(stbuf, val) (stbuf)->st_mtim.tv_nsec = (val)
+
36#elif defined(HAVE_STRUCT_STAT_ST_ATIMESPEC)
+
37/* FreeBSD */
+
38#define ST_ATIM_NSEC(stbuf) ((stbuf)->st_atimespec.tv_nsec)
+
39#define ST_CTIM_NSEC(stbuf) ((stbuf)->st_ctimespec.tv_nsec)
+
40#define ST_MTIM_NSEC(stbuf) ((stbuf)->st_mtimespec.tv_nsec)
+
41#define ST_ATIM_NSEC_SET(stbuf, val) (stbuf)->st_atimespec.tv_nsec = (val)
+
42#define ST_CTIM_NSEC_SET(stbuf, val) (stbuf)->st_ctimespec.tv_nsec = (val)
+
43#define ST_MTIM_NSEC_SET(stbuf, val) (stbuf)->st_mtimespec.tv_nsec = (val)
+
44#else
+
45#define ST_ATIM_NSEC(stbuf) 0
+
46#define ST_CTIM_NSEC(stbuf) 0
+
47#define ST_MTIM_NSEC(stbuf) 0
+
48#define ST_ATIM_NSEC_SET(stbuf, val) do { } while (0)
+
49#define ST_CTIM_NSEC_SET(stbuf, val) do { } while (0)
+
50#define ST_MTIM_NSEC_SET(stbuf, val) do { } while (0)
+
51#endif
+
+ + +
+ + diff --git a/doc/html/fuse__mount__compat_8h_source.html b/doc/html/fuse__mount__compat_8h_source.html new file mode 100644 index 0000000..0a0a676 --- /dev/null +++ b/doc/html/fuse__mount__compat_8h_source.html @@ -0,0 +1,108 @@ + + + + + + + +libfuse: include/fuse_mount_compat.h Source File + + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
+
fuse_mount_compat.h
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2023 Giulio Benetti <giulio.benetti@benettiengineering.com>
+
4
+
5 Logging API.
+
6
+
7 This program can be distributed under the terms of the GNU LGPLv2.
+
8 See the file LICENSE
+
9*/
+
10
+
11#ifndef FUSE_MOUNT_COMPAT_H_
+
12#define FUSE_MOUNT_COMPAT_H_
+
13
+
14#include <sys/mount.h>
+
15
+
16/* Some libc don't define MS_*, so define them manually
+
17 * (values taken from https://elixir.bootlin.com/linux/v6.10/source/include/uapi/linux/mount.h#L13 on)
+
18 */
+
19#ifndef MS_DIRSYNC
+
20#define MS_DIRSYNC 128
+
21#endif
+
22
+
23#ifndef MS_NOSYMFOLLOW
+
24#define MS_NOSYMFOLLOW 256
+
25#endif
+
26
+
27#ifndef MS_REC
+
28#define MS_REC 16384
+
29#endif
+
30
+
31#ifndef MS_PRIVATE
+
32#define MS_PRIVATE (1<<18)
+
33#endif
+
34
+
35#ifndef MS_LAZYTIME
+
36#define MS_LAZYTIME (1<<25)
+
37#endif
+
38
+
39#ifndef UMOUNT_DETACH
+
40#define UMOUNT_DETACH 0x00000002 /* Just detach from the tree */
+
41#endif
+
42#ifndef UMOUNT_NOFOLLOW
+
43#define UMOUNT_NOFOLLOW 0x00000008 /* Don't follow symlink on umount */
+
44#endif
+
45#ifndef UMOUNT_UNUSED
+
46#define UMOUNT_UNUSED 0x80000000 /* Flag guaranteed to be unused */
+
47#endif
+
48
+
49#endif /* FUSE_MOUNT_COMPAT_H_ */
+
+ + +
+ + diff --git a/doc/html/fuse__opt_8c_source.html b/doc/html/fuse__opt_8c_source.html new file mode 100644 index 0000000..c80133e --- /dev/null +++ b/doc/html/fuse__opt_8c_source.html @@ -0,0 +1,516 @@ + + + + + + + +libfuse: lib/fuse_opt.c Source File + + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
+
fuse_opt.c
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 Implementation of option parsing routines (dealing with `struct
+
6 fuse_args`).
+
7
+
8 This program can be distributed under the terms of the GNU LGPLv2.
+
9 See the file LGPL2.txt
+
10*/
+
11
+
12#include "fuse_config.h"
+
13#include "fuse_i.h"
+
14#include "fuse_opt.h"
+
15#include "fuse_misc.h"
+
16
+
17#include <stdio.h>
+
18#include <stdlib.h>
+
19#include <string.h>
+
20#include <assert.h>
+
21
+
22struct fuse_opt_context {
+
23 void *data;
+
24 const struct fuse_opt *opt;
+
25 fuse_opt_proc_t proc;
+
26 int argctr;
+
27 int argc;
+
28 char **argv;
+
29 struct fuse_args outargs;
+
30 char *opts;
+
31 int nonopt;
+
32};
+
33
+
+
34void fuse_opt_free_args(struct fuse_args *args)
+
35{
+
36 if (args) {
+
37 if (args->argv && args->allocated) {
+
38 int i;
+
39 for (i = 0; i < args->argc; i++)
+
40 free(args->argv[i]);
+
41 free(args->argv);
+
42 }
+
43 args->argc = 0;
+
44 args->argv = NULL;
+
45 args->allocated = 0;
+
46 }
+
47}
+
+
48
+
49static int alloc_failed(void)
+
50{
+
51 fuse_log(FUSE_LOG_ERR, "fuse: memory allocation failed\n");
+
52 return -1;
+
53}
+
54
+
+
55int fuse_opt_add_arg(struct fuse_args *args, const char *arg)
+
56{
+
57 char **newargv;
+
58 char *newarg;
+
59
+
60 assert(!args->argv || args->allocated);
+
61
+
62 newarg = strdup(arg);
+
63 if (!newarg)
+
64 return alloc_failed();
+
65
+
66 newargv = realloc(args->argv, (args->argc + 2) * sizeof(char *));
+
67 if (!newargv) {
+
68 free(newarg);
+
69 return alloc_failed();
+
70 }
+
71
+
72 args->argv = newargv;
+
73 args->allocated = 1;
+
74 args->argv[args->argc++] = newarg;
+
75 args->argv[args->argc] = NULL;
+
76 return 0;
+
77}
+
+
78
+
79static int fuse_opt_insert_arg_common(struct fuse_args *args, int pos,
+
80 const char *arg)
+
81{
+
82 assert(pos <= args->argc);
+
83 if (fuse_opt_add_arg(args, arg) == -1)
+
84 return -1;
+
85
+
86 if (pos != args->argc - 1) {
+
87 char *newarg = args->argv[args->argc - 1];
+
88 memmove(&args->argv[pos + 1], &args->argv[pos],
+
89 sizeof(char *) * (args->argc - pos - 1));
+
90 args->argv[pos] = newarg;
+
91 }
+
92 return 0;
+
93}
+
94
+
+
95int fuse_opt_insert_arg(struct fuse_args *args, int pos, const char *arg)
+
96{
+
97 return fuse_opt_insert_arg_common(args, pos, arg);
+
98}
+
+
99
+
100static int next_arg(struct fuse_opt_context *ctx, const char *opt)
+
101{
+
102 if (ctx->argctr + 1 >= ctx->argc) {
+
103 fuse_log(FUSE_LOG_ERR, "fuse: missing argument after `%s'\n", opt);
+
104 return -1;
+
105 }
+
106 ctx->argctr++;
+
107 return 0;
+
108}
+
109
+
110static int add_arg(struct fuse_opt_context *ctx, const char *arg)
+
111{
+
112 return fuse_opt_add_arg(&ctx->outargs, arg);
+
113}
+
114
+
115static int add_opt_common(char **opts, const char *opt, int esc)
+
116{
+
117 unsigned oldlen = *opts ? strlen(*opts) : 0;
+
118 char *d = realloc(*opts, oldlen + 1 + strlen(opt) * 2 + 1);
+
119
+
120 if (!d)
+
121 return alloc_failed();
+
122
+
123 *opts = d;
+
124 if (oldlen) {
+
125 d += oldlen;
+
126 *d++ = ',';
+
127 }
+
128
+
129 for (; *opt; opt++) {
+
130 if (esc && (*opt == ',' || *opt == '\\'))
+
131 *d++ = '\\';
+
132 *d++ = *opt;
+
133 }
+
134 *d = '\0';
+
135
+
136 return 0;
+
137}
+
138
+
+
139int fuse_opt_add_opt(char **opts, const char *opt)
+
140{
+
141 return add_opt_common(opts, opt, 0);
+
142}
+
+
143
+
+
144int fuse_opt_add_opt_escaped(char **opts, const char *opt)
+
145{
+
146 return add_opt_common(opts, opt, 1);
+
147}
+
+
148
+
149static int add_opt(struct fuse_opt_context *ctx, const char *opt)
+
150{
+
151 return add_opt_common(&ctx->opts, opt, 1);
+
152}
+
153
+
154static int call_proc(struct fuse_opt_context *ctx, const char *arg, int key,
+
155 int iso)
+
156{
+
157 if (key == FUSE_OPT_KEY_DISCARD)
+
158 return 0;
+
159
+
160 if (key != FUSE_OPT_KEY_KEEP && ctx->proc) {
+
161 int res = ctx->proc(ctx->data, arg, key, &ctx->outargs);
+
162 if (res == -1 || !res)
+
163 return res;
+
164 }
+
165 if (iso)
+
166 return add_opt(ctx, arg);
+
167 else
+
168 return add_arg(ctx, arg);
+
169}
+
170
+
171static int match_template(const char *t, const char *arg, unsigned *sepp)
+
172{
+
173 int arglen = strlen(arg);
+
174 const char *sep = strchr(t, '=');
+
175 sep = sep ? sep : strchr(t, ' ');
+
176 if (sep && (!sep[1] || sep[1] == '%')) {
+
177 int tlen = sep - t;
+
178 if (sep[0] == '=')
+
179 tlen ++;
+
180 if (arglen >= tlen && strncmp(arg, t, tlen) == 0) {
+
181 *sepp = sep - t;
+
182 return 1;
+
183 }
+
184 }
+
185 if (strcmp(t, arg) == 0) {
+
186 *sepp = 0;
+
187 return 1;
+
188 }
+
189 return 0;
+
190}
+
191
+
192static const struct fuse_opt *find_opt(const struct fuse_opt *opt,
+
193 const char *arg, unsigned *sepp)
+
194{
+
195 for (; opt && opt->templ; opt++)
+
196 if (match_template(opt->templ, arg, sepp))
+
197 return opt;
+
198 return NULL;
+
199}
+
200
+
201int fuse_opt_match(const struct fuse_opt *opts, const char *opt)
+
202{
+
203 unsigned dummy;
+
204 return find_opt(opts, opt, &dummy) ? 1 : 0;
+
205}
+
206
+
207static int process_opt_param(void *var, const char *format, const char *param,
+
208 const char *arg)
+
209{
+
210 assert(format[0] == '%');
+
211 if (format[1] == 's') {
+
212 char **s = var;
+
213 char *copy = strdup(param);
+
214 if (!copy)
+
215 return alloc_failed();
+
216
+
217 free(*s);
+
218 *s = copy;
+
219 } else {
+
220 if (sscanf(param, format, var) != 1) {
+
221 fuse_log(FUSE_LOG_ERR, "fuse: invalid parameter in option `%s'\n", arg);
+
222 return -1;
+
223 }
+
224 }
+
225 return 0;
+
226}
+
227
+
228static int process_opt(struct fuse_opt_context *ctx,
+
229 const struct fuse_opt *opt, unsigned sep,
+
230 const char *arg, int iso)
+
231{
+
232 if (opt->offset == -1U) {
+
233 if (call_proc(ctx, arg, opt->value, iso) == -1)
+
234 return -1;
+
235 } else {
+
236 void *var = (char *)ctx->data + opt->offset;
+
237 if (sep && opt->templ[sep + 1]) {
+
238 const char *param = arg + sep;
+
239 if (opt->templ[sep] == '=')
+
240 param ++;
+
241 if (process_opt_param(var, opt->templ + sep + 1,
+
242 param, arg) == -1)
+
243 return -1;
+
244 } else
+
245 *(int *)var = opt->value;
+
246 }
+
247 return 0;
+
248}
+
249
+
250static int process_opt_sep_arg(struct fuse_opt_context *ctx,
+
251 const struct fuse_opt *opt, unsigned sep,
+
252 const char *arg, int iso)
+
253{
+
254 int res;
+
255 char *newarg;
+
256 char *param;
+
257
+
258 if (next_arg(ctx, arg) == -1)
+
259 return -1;
+
260
+
261 param = ctx->argv[ctx->argctr];
+
262 newarg = malloc(sep + strlen(param) + 1);
+
263 if (!newarg)
+
264 return alloc_failed();
+
265
+
266 memcpy(newarg, arg, sep);
+
267 strcpy(newarg + sep, param);
+
268 res = process_opt(ctx, opt, sep, newarg, iso);
+
269 free(newarg);
+
270
+
271 return res;
+
272}
+
273
+
274static int process_gopt(struct fuse_opt_context *ctx, const char *arg, int iso)
+
275{
+
276 unsigned sep;
+
277 const struct fuse_opt *opt = find_opt(ctx->opt, arg, &sep);
+
278 if (opt) {
+
279 for (; opt; opt = find_opt(opt + 1, arg, &sep)) {
+
280 int res;
+
281 if (sep && opt->templ[sep] == ' ' && !arg[sep])
+
282 res = process_opt_sep_arg(ctx, opt, sep, arg,
+
283 iso);
+
284 else
+
285 res = process_opt(ctx, opt, sep, arg, iso);
+
286 if (res == -1)
+
287 return -1;
+
288 }
+
289 return 0;
+
290 } else
+
291 return call_proc(ctx, arg, FUSE_OPT_KEY_OPT, iso);
+
292}
+
293
+
294static int process_real_option_group(struct fuse_opt_context *ctx, char *opts)
+
295{
+
296 char *s = opts;
+
297 char *d = s;
+
298 int end = 0;
+
299
+
300 while (!end) {
+
301 if (*s == '\0')
+
302 end = 1;
+
303 if (*s == ',' || end) {
+
304 int res;
+
305
+
306 *d = '\0';
+
307 res = process_gopt(ctx, opts, 1);
+
308 if (res == -1)
+
309 return -1;
+
310 d = opts;
+
311 } else {
+
312 if (s[0] == '\\' && s[1] != '\0') {
+
313 s++;
+
314 if (s[0] >= '0' && s[0] <= '3' &&
+
315 s[1] >= '0' && s[1] <= '7' &&
+
316 s[2] >= '0' && s[2] <= '7') {
+
317 *d++ = (s[0] - '0') * 0100 +
+
318 (s[1] - '0') * 0010 +
+
319 (s[2] - '0');
+
320 s += 2;
+
321 } else {
+
322 *d++ = *s;
+
323 }
+
324 } else {
+
325 *d++ = *s;
+
326 }
+
327 }
+
328 s++;
+
329 }
+
330
+
331 return 0;
+
332}
+
333
+
334static int process_option_group(struct fuse_opt_context *ctx, const char *opts)
+
335{
+
336 int res;
+
337 char *copy = strdup(opts);
+
338
+
339 if (!copy) {
+
340 fuse_log(FUSE_LOG_ERR, "fuse: memory allocation failed\n");
+
341 return -1;
+
342 }
+
343 res = process_real_option_group(ctx, copy);
+
344 free(copy);
+
345 return res;
+
346}
+
347
+
348static int process_one(struct fuse_opt_context *ctx, const char *arg)
+
349{
+
350 if (ctx->nonopt || arg[0] != '-')
+
351 return call_proc(ctx, arg, FUSE_OPT_KEY_NONOPT, 0);
+
352 else if (arg[1] == 'o') {
+
353 if (arg[2])
+
354 return process_option_group(ctx, arg + 2);
+
355 else {
+
356 if (next_arg(ctx, arg) == -1)
+
357 return -1;
+
358
+
359 return process_option_group(ctx,
+
360 ctx->argv[ctx->argctr]);
+
361 }
+
362 } else if (arg[1] == '-' && !arg[2]) {
+
363 if (add_arg(ctx, arg) == -1)
+
364 return -1;
+
365 ctx->nonopt = ctx->outargs.argc;
+
366 return 0;
+
367 } else
+
368 return process_gopt(ctx, arg, 0);
+
369}
+
370
+
371static int opt_parse(struct fuse_opt_context *ctx)
+
372{
+
373 if (ctx->argc) {
+
374 if (add_arg(ctx, ctx->argv[0]) == -1)
+
375 return -1;
+
376 }
+
377
+
378 for (ctx->argctr = 1; ctx->argctr < ctx->argc; ctx->argctr++)
+
379 if (process_one(ctx, ctx->argv[ctx->argctr]) == -1)
+
380 return -1;
+
381
+
382 if (ctx->opts) {
+
383 if (fuse_opt_insert_arg(&ctx->outargs, 1, "-o") == -1 ||
+
384 fuse_opt_insert_arg(&ctx->outargs, 2, ctx->opts) == -1)
+
385 return -1;
+
386 }
+
387
+
388 /* If option separator ("--") is the last argument, remove it */
+
389 if (ctx->nonopt && ctx->nonopt == ctx->outargs.argc &&
+
390 strcmp(ctx->outargs.argv[ctx->outargs.argc - 1], "--") == 0) {
+
391 free(ctx->outargs.argv[ctx->outargs.argc - 1]);
+
392 ctx->outargs.argv[--ctx->outargs.argc] = NULL;
+
393 }
+
394
+
395 return 0;
+
396}
+
397
+
+
398int fuse_opt_parse(struct fuse_args *args, void *data,
+
399 const struct fuse_opt opts[], fuse_opt_proc_t proc)
+
400{
+
401 int res;
+
402 struct fuse_opt_context ctx = {
+
403 .data = data,
+
404 .opt = opts,
+
405 .proc = proc,
+
406 };
+
407
+
408 if (!args || !args->argv || !args->argc)
+
409 return 0;
+
410
+
411 ctx.argc = args->argc;
+
412 ctx.argv = args->argv;
+
413
+
414 res = opt_parse(&ctx);
+
415 if (res != -1) {
+
416 struct fuse_args tmp = *args;
+
417 *args = ctx.outargs;
+
418 ctx.outargs = tmp;
+
419 }
+
420 free(ctx.opts);
+
421 fuse_opt_free_args(&ctx.outargs);
+
422 return res;
+
423}
+
+
void fuse_log(enum fuse_log_level level, const char *fmt,...) __attribute__((format(printf
+ +
int fuse_opt_add_arg(struct fuse_args *args, const char *arg)
Definition fuse_opt.c:55
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
int(* fuse_opt_proc_t)(void *data, const char *arg, int key, struct fuse_args *outargs)
Definition fuse_opt.h:180
+
#define FUSE_OPT_KEY_OPT
Definition fuse_opt.h:129
+
int fuse_opt_add_opt_escaped(char **opts, const char *opt)
Definition fuse_opt.c:144
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_OPT_KEY_NONOPT
Definition fuse_opt.h:137
+
#define FUSE_OPT_KEY_DISCARD
Definition fuse_opt.h:153
+
#define FUSE_OPT_KEY_KEEP
Definition fuse_opt.h:145
+
int fuse_opt_add_opt(char **opts, const char *opt)
Definition fuse_opt.c:139
+
int fuse_opt_insert_arg(struct fuse_args *args, int pos, const char *arg)
Definition fuse_opt.c:95
+
int fuse_opt_match(const struct fuse_opt opts[], const char *opt)
+ +
int allocated
Definition fuse_opt.h:117
+ +
char ** argv
Definition fuse_opt.h:114
+ +
const char * templ
Definition fuse_opt.h:79
+
unsigned long offset
Definition fuse_opt.h:85
+
int value
Definition fuse_opt.h:91
+
+ + +
+ + diff --git a/doc/html/fuse__opt_8h.html b/doc/html/fuse__opt_8h.html new file mode 100644 index 0000000..e625971 --- /dev/null +++ b/doc/html/fuse__opt_8h.html @@ -0,0 +1,516 @@ + + + + + + + +libfuse: include/fuse_opt.h File Reference + + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
+ +
fuse_opt.h File Reference
+
+
+ +

Go to the source code of this file.

+ + + + +

+Data Structures

struct  fuse_opt
struct  fuse_args
+ + + + + + + + +

+Macros

#define FUSE_OPT_KEY(templ, key)
#define FUSE_OPT_END   { NULL, 0, 0 }
#define FUSE_ARGS_INIT(argc, argv)
#define FUSE_OPT_KEY_OPT   -1
#define FUSE_OPT_KEY_NONOPT   -2
#define FUSE_OPT_KEY_KEEP   -3
#define FUSE_OPT_KEY_DISCARD   -4
+ + +

+Typedefs

typedef int(* fuse_opt_proc_t) (void *data, const char *arg, int key, struct fuse_args *outargs)
+ + + + + + + + +

+Functions

int fuse_opt_parse (struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
int fuse_opt_add_opt (char **opts, const char *opt)
int fuse_opt_add_opt_escaped (char **opts, const char *opt)
int fuse_opt_add_arg (struct fuse_args *args, const char *arg)
int fuse_opt_insert_arg (struct fuse_args *args, int pos, const char *arg)
void fuse_opt_free_args (struct fuse_args *args)
int fuse_opt_match (const struct fuse_opt opts[], const char *opt)
+

Detailed Description

+

This file defines the option parsing interface of FUSE

+ +

Definition in file fuse_opt.h.

+

Macro Definition Documentation

+ +

◆ FUSE_ARGS_INIT

+ +
+
+ + + + + + + + + + + +
#define FUSE_ARGS_INIT( argc,
argv )
+
+Value:
{ argc, argv, 0 }
+

Initializer for 'struct fuse_args'

+ +

Definition at line 123 of file fuse_opt.h.

+ +
+
+ +

◆ FUSE_OPT_END

+ +
+
+ + + + +
#define FUSE_OPT_END   { NULL, 0, 0 }
+
+

Last option. An array of 'struct fuse_opt' must end with a NULL template value

+ +

Definition at line 104 of file fuse_opt.h.

+ +
+
+ +

◆ FUSE_OPT_KEY

+ +
+
+ + + + + + + + + + + +
#define FUSE_OPT_KEY( templ,
key )
+
+Value:
{ templ, -1U, key }
+

Key option. In case of a match, the processing function will be called with the specified key.

+ +

Definition at line 98 of file fuse_opt.h.

+ +
+
+ +

◆ FUSE_OPT_KEY_DISCARD

+ +
+
+ + + + +
#define FUSE_OPT_KEY_DISCARD   -4
+
+

Special key value for options to discard

+

Argument is not passed to processing function, but behave as if the processing function returned zero

+ +

Definition at line 153 of file fuse_opt.h.

+ +
+
+ +

◆ FUSE_OPT_KEY_KEEP

+ +
+
+ + + + +
#define FUSE_OPT_KEY_KEEP   -3
+
+

Special key value for options to keep

+

Argument is not passed to processing function, but behave as if the processing function returned 1

+ +

Definition at line 145 of file fuse_opt.h.

+ +
+
+ +

◆ FUSE_OPT_KEY_NONOPT

+ +
+
+ + + + +
#define FUSE_OPT_KEY_NONOPT   -2
+
+

Key value passed to the processing function for all non-options

+

Non-options are the arguments beginning with a character other than '-' or all arguments after the special '–' option

+ +

Definition at line 137 of file fuse_opt.h.

+ +
+
+ +

◆ FUSE_OPT_KEY_OPT

+ +
+
+ + + + +
#define FUSE_OPT_KEY_OPT   -1
+
+

Key value passed to the processing function if an option did not match any template

+ +

Definition at line 129 of file fuse_opt.h.

+ +
+
+

Typedef Documentation

+ +

◆ fuse_opt_proc_t

+ +
+
+ + + + +
typedef int(* fuse_opt_proc_t) (void *data, const char *arg, int key, struct fuse_args *outargs)
+
+

Processing function

+

This function is called if

    +
  • option did not match any 'struct fuse_opt'
  • +
  • argument is a non-option
  • +
  • option did match and offset was set to -1
  • +
+

The 'arg' parameter will always contain the whole argument or option including the parameter if exists. A two-argument option ("-x foo") is always converted to single argument option of the form "-xfoo" before this function is called.

+

Options of the form '-ofoo' are passed to this function without the '-o' prefix.

+

The return value of this function determines whether this argument is to be inserted into the output argument vector, or discarded.

+
Parameters
+ + + + + +
datais the user data passed to the fuse_opt_parse() function
argis the whole argument or option
keydetermines why the processing function was called
outargsthe current output argument list
+
+
+
Returns
-1 on error, 0 if arg is to be discarded, 1 if arg should be kept
+ +

Definition at line 180 of file fuse_opt.h.

+ +
+
+

Function Documentation

+ +

◆ fuse_opt_add_arg()

+ +
+
+ + + + + + + + + + + +
int fuse_opt_add_arg (struct fuse_args * args,
const char * arg )
+
+

Add an argument to a NULL terminated argument vector

+
Parameters
+ + + +
argsis the structure containing the current argument list
argis the new argument to add
+
+
+
Returns
-1 on allocation error, 0 on success
+ +

Definition at line 55 of file fuse_opt.c.

+ +
+
+ +

◆ fuse_opt_add_opt()

+ +
+
+ + + + + + + + + + + +
int fuse_opt_add_opt (char ** opts,
const char * opt )
+
+

Add an option to a comma separated option list

+
Parameters
+ + + +
optsis a pointer to an option list, may point to a NULL value
optis the option to add
+
+
+
Returns
-1 on allocation error, 0 on success
+ +

Definition at line 139 of file fuse_opt.c.

+ +
+
+ +

◆ fuse_opt_add_opt_escaped()

+ +
+
+ + + + + + + + + + + +
int fuse_opt_add_opt_escaped (char ** opts,
const char * opt )
+
+

Add an option, escaping commas, to a comma separated option list

+
Parameters
+ + + +
optsis a pointer to an option list, may point to a NULL value
optis the option to add
+
+
+
Returns
-1 on allocation error, 0 on success
+ +

Definition at line 144 of file fuse_opt.c.

+ +
+
+ +

◆ fuse_opt_free_args()

+ +
+
+ + + + + + + +
void fuse_opt_free_args (struct fuse_args * args)
+
+

Free the contents of argument list

+

The structure itself is not freed

+
Parameters
+ + +
argsis the structure containing the argument list
+
+
+ +

Definition at line 34 of file fuse_opt.c.

+ +
+
+ +

◆ fuse_opt_insert_arg()

+ +
+
+ + + + + + + + + + + + + + + + +
int fuse_opt_insert_arg (struct fuse_args * args,
int pos,
const char * arg )
+
+

Add an argument at the specified position in a NULL terminated argument vector

+

Adds the argument to the N-th position. This is useful for adding options at the beginning of the array which must not come after the special '–' option.

+
Parameters
+ + + + +
argsis the structure containing the current argument list
posis the position at which to add the argument
argis the new argument to add
+
+
+
Returns
-1 on allocation error, 0 on success
+ +

Definition at line 95 of file fuse_opt.c.

+ +
+
+ +

◆ fuse_opt_match()

+ +
+
+ + + + + + + + + + + +
int fuse_opt_match (const struct fuse_opt opts[],
const char * opt )
+
+

Check if an option matches

+
Parameters
+ + + +
optsis the option description array
optis the option to match
+
+
+
Returns
1 if a match is found, 0 if not
+ +
+
+ +

◆ fuse_opt_parse()

+ +
+
+ + + + + + + + + + + + + + + + + + + + + +
int fuse_opt_parse (struct fuse_args * args,
void * data,
const struct fuse_opt opts[],
fuse_opt_proc_t proc )
+
+

Option parsing function

+

If 'args' was returned from a previous call to fuse_opt_parse() or it was constructed from

+

A NULL 'args' is equivalent to an empty argument vector

+

A NULL 'opts' is equivalent to an 'opts' array containing a single end marker

+

A NULL 'proc' is equivalent to a processing function always returning '1'

+
Parameters
+ + + + + +
argsis the input and output argument list
datais the user data
optsis the option description array
procis the processing function
+
+
+
Returns
-1 on error, 0 on success
+ +

Definition at line 398 of file fuse_opt.c.

+ +
+
+
+ + +
+ + diff --git a/doc/html/fuse__opt_8h_source.html b/doc/html/fuse__opt_8h_source.html new file mode 100644 index 0000000..5a0f9c3 --- /dev/null +++ b/doc/html/fuse__opt_8h_source.html @@ -0,0 +1,149 @@ + + + + + + + +libfuse: include/fuse_opt.h Source File + + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
+
fuse_opt.h
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 This program can be distributed under the terms of the GNU LGPLv2.
+
6 See the file LGPL2.txt.
+
7*/
+
8
+
9#ifndef FUSE_OPT_H_
+
10#define FUSE_OPT_H_
+
11
+
16
+
17#ifdef __cplusplus
+
18extern "C" {
+
19#endif
+
20
+
+
77struct fuse_opt {
+
79 const char *templ;
+
80
+
85 unsigned long offset;
+
86
+
91 int value;
+
92};
+
+
93
+
98#define FUSE_OPT_KEY(templ, key) { templ, -1U, key }
+
99
+
104#define FUSE_OPT_END { NULL, 0, 0 }
+
105
+
+
109struct fuse_args {
+
111 int argc;
+
112
+
114 char **argv;
+
115
+ +
118};
+
+
119
+
123#define FUSE_ARGS_INIT(argc, argv) { argc, argv, 0 }
+
124
+
129#define FUSE_OPT_KEY_OPT -1
+
130
+
137#define FUSE_OPT_KEY_NONOPT -2
+
138
+
145#define FUSE_OPT_KEY_KEEP -3
+
146
+
153#define FUSE_OPT_KEY_DISCARD -4
+
154
+
180typedef int (*fuse_opt_proc_t)(void *data, const char *arg, int key,
+
181 struct fuse_args *outargs);
+
182
+
203int fuse_opt_parse(struct fuse_args *args, void *data,
+
204 const struct fuse_opt opts[], fuse_opt_proc_t proc);
+
205
+
213int fuse_opt_add_opt(char **opts, const char *opt);
+
214
+
222int fuse_opt_add_opt_escaped(char **opts, const char *opt);
+
223
+
231int fuse_opt_add_arg(struct fuse_args *args, const char *arg);
+
232
+
246int fuse_opt_insert_arg(struct fuse_args *args, int pos, const char *arg);
+
247
+
255void fuse_opt_free_args(struct fuse_args *args);
+
256
+
257
+
265int fuse_opt_match(const struct fuse_opt opts[], const char *opt);
+
266
+
267#ifdef __cplusplus
+
268}
+
269#endif
+
270
+
271#endif /* FUSE_OPT_H_ */
+
int fuse_opt_add_arg(struct fuse_args *args, const char *arg)
Definition fuse_opt.c:55
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
int(* fuse_opt_proc_t)(void *data, const char *arg, int key, struct fuse_args *outargs)
Definition fuse_opt.h:180
+
int fuse_opt_add_opt_escaped(char **opts, const char *opt)
Definition fuse_opt.c:144
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
int fuse_opt_add_opt(char **opts, const char *opt)
Definition fuse_opt.c:139
+
int fuse_opt_insert_arg(struct fuse_args *args, int pos, const char *arg)
Definition fuse_opt.c:95
+
int fuse_opt_match(const struct fuse_opt opts[], const char *opt)
+ +
int allocated
Definition fuse_opt.h:117
+ +
char ** argv
Definition fuse_opt.h:114
+ +
const char * templ
Definition fuse_opt.h:79
+
unsigned long offset
Definition fuse_opt.h:85
+
int value
Definition fuse_opt.h:91
+
+ + +
+ + diff --git a/doc/html/fuse__signals_8c_source.html b/doc/html/fuse__signals_8c_source.html new file mode 100644 index 0000000..1f0cfb2 --- /dev/null +++ b/doc/html/fuse__signals_8c_source.html @@ -0,0 +1,287 @@ + + + + + + + +libfuse: lib/fuse_signals.c Source File + + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
+
fuse_signals.c
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 Utility functions for setting signal handlers.
+
6
+
7 This program can be distributed under the terms of the GNU LGPLv2.
+
8 See the file LGPL2.txt
+
9*/
+
10
+
11#include "fuse_config.h"
+
12#include "fuse_lowlevel.h"
+
13#include "fuse_i.h"
+
14
+
15#include <stdio.h>
+
16#include <string.h>
+
17#include <signal.h>
+
18#include <stdlib.h>
+
19#include <errno.h>
+
20
+
21#ifdef HAVE_BACKTRACE
+
22#include <execinfo.h>
+
23#endif
+
24
+
25/*
+
26 * Must not handle SIGCANCEL, as that is used to wake up threads from
+
27 * syscalls reading requests from /dev/fuse
+
28 */
+
29static int teardown_sigs[] = { SIGHUP, SIGINT, SIGTERM };
+
30
+
31static int ignore_sigs[] = { SIGPIPE};
+
32static int fail_sigs[] = { SIGILL, SIGTRAP, SIGABRT, SIGBUS, SIGFPE, SIGSEGV };
+
33static struct fuse_session *fuse_instance;
+
34
+
35#ifdef HAVE_BACKTRACE
+
36#define BT_STACK_SZ (1024 * 1024)
+
37static void *backtrace_buffer[BT_STACK_SZ];
+
38#endif
+
39
+
40static void dump_stack(void)
+
41{
+
42#ifdef HAVE_BACKTRACE
+
43 char **strings;
+
44
+
45 int nptrs = backtrace(backtrace_buffer, BT_STACK_SZ);
+
46 strings = backtrace_symbols(backtrace_buffer, nptrs);
+
47
+
48 if (strings == NULL) {
+
49 fuse_log(FUSE_LOG_ERR, "Failed to get backtrace symbols: %s\n",
+
50 strerror(errno));
+
51 return;
+
52 }
+
53
+
54 for (int idx = 0; idx < nptrs; idx++)
+
55 fuse_log(FUSE_LOG_ERR, "%s\n", strings[idx]);
+
56
+
57 free(strings);
+
58#endif
+
59}
+
60
+
61static void exit_handler(int sig)
+
62{
+
63 if (fuse_instance == NULL) {
+
64 fuse_log(FUSE_LOG_ERR, "fuse_instance is NULL\n");
+
65 return;
+
66 }
+
67
+
68 if (fuse_instance->debug)
+
69 fuse_log(FUSE_LOG_ERR, "exit_handler called with sig %d\n",
+
70 sig);
+
71
+
72 fuse_session_exit(fuse_instance);
+
73
+
74 if (sig < 0) {
+
75 fuse_log(FUSE_LOG_ERR,
+
76 "assertion error: signal value <= 0\n");
+
77 dump_stack();
+
78 abort();
+
79 fuse_instance->error = sig;
+
80 }
+
81
+
82 fuse_instance->error = sig;
+
83}
+
84
+
85static void exit_backtrace(int sig)
+
86{
+
87 if (fuse_instance == NULL)
+
88 return;
+
89
+
90 fuse_session_exit(fuse_instance);
+
91
+
92 fuse_remove_signal_handlers(fuse_instance);
+
93 fuse_log(FUSE_LOG_ERR, "Got signal: %d\n", sig);
+
94 dump_stack();
+
95 abort();
+
96}
+
97
+
98
+
99static void do_nothing(int sig)
+
100{
+
101 (void) sig;
+
102}
+
103
+
104static int set_one_signal_handler(int sig, void (*handler)(int), int remove)
+
105{
+
106 struct sigaction sa;
+
107 struct sigaction old_sa;
+
108
+
109 memset(&sa, 0, sizeof(struct sigaction));
+
110 sa.sa_handler = remove ? SIG_DFL : handler;
+
111 sigemptyset(&(sa.sa_mask));
+
112 sa.sa_flags = 0;
+
113
+
114 if (sigaction(sig, NULL, &old_sa) == -1) {
+
115 perror("fuse: cannot get old signal handler");
+
116 return -1;
+
117 }
+
118
+
119 if (old_sa.sa_handler == (remove ? handler : SIG_DFL) &&
+
120 sigaction(sig, &sa, NULL) == -1) {
+
121 perror("fuse: cannot set signal handler");
+
122 return -1;
+
123 }
+
124 return 0;
+
125}
+
126
+
127static int _fuse_set_signal_handlers(int signals[], int nr_signals,
+
128 void (*handler)(int))
+
129{
+
130 for (int idx = 0; idx < nr_signals; idx++) {
+
131 int signal = signals[idx];
+
132
+
133 /*
+
134 * If we used SIG_IGN instead of the do_nothing function,
+
135 * then we would be unable to tell if we set SIG_IGN (and
+
136 * thus should reset to SIG_DFL in fuse_remove_signal_handlers)
+
137 * or if it was already set to SIG_IGN (and should be left
+
138 * untouched.
+
139 */
+
140 if (set_one_signal_handler(signal, handler, 0) == -1) {
+
141 fuse_log(FUSE_LOG_ERR,
+
142 "Failed to install signal handler for sig %d\n",
+
143 signal);
+
144 return -1;
+
145 }
+
146 }
+
147
+
148 return 0;
+
149}
+
150
+
+
151int fuse_set_signal_handlers(struct fuse_session *se)
+
152{
+
153 size_t nr_signals;
+
154 int rc;
+
155
+
156 nr_signals = sizeof(teardown_sigs) / sizeof(teardown_sigs[0]);
+
157 rc = _fuse_set_signal_handlers(teardown_sigs, nr_signals, exit_handler);
+
158 if (rc < 0)
+
159 return rc;
+
160
+
161 nr_signals = sizeof(ignore_sigs) / sizeof(ignore_sigs[0]);
+
162 rc = _fuse_set_signal_handlers(ignore_sigs, nr_signals, do_nothing);
+
163 if (rc < 0)
+
164 return rc;
+
165
+
166 /*
+
167 * needs to be set independently if already set, as some applications
+
168 * may have multiple sessions and might rely on traditional behavior
+
169 * that the last session is used.
+
170 */
+
171 fuse_instance = se;
+
172
+
173 return 0;
+
174}
+
+
175
+
+
176int fuse_set_fail_signal_handlers(struct fuse_session *se)
+
177{
+
178 size_t nr_signals = sizeof(fail_sigs) / sizeof(fail_sigs[0]);
+
179
+
180 int rc = _fuse_set_signal_handlers(fail_sigs, nr_signals,
+
181 exit_backtrace);
+
182 if (rc < 0)
+
183 return rc;
+
184
+
185 /* See fuse_set_signal_handlers, why set unconditionally */
+
186 fuse_instance = se;
+
187
+
188 return 0;
+
189}
+
+
190
+
191static void _fuse_remove_signal_handlers(int signals[], int nr_signals,
+
192 void (*handler)(int))
+
193{
+
194 for (int idx = 0; idx < nr_signals; idx++)
+
195 set_one_signal_handler(signals[idx], handler, 1);
+
196}
+
197
+
+
198void fuse_remove_signal_handlers(struct fuse_session *se)
+
199{
+
200 size_t nr_signals;
+
201
+
202 if (fuse_instance != se)
+
203 fuse_log(FUSE_LOG_ERR,
+
204 "fuse: fuse_remove_signal_handlers: unknown session\n");
+
205 else
+
206 fuse_instance = NULL;
+
207
+
208 nr_signals = sizeof(teardown_sigs) / sizeof(teardown_sigs[0]);
+
209 _fuse_remove_signal_handlers(teardown_sigs, nr_signals, exit_handler);
+
210
+
211 nr_signals = sizeof(ignore_sigs) / sizeof(ignore_sigs[0]);
+
212 _fuse_remove_signal_handlers(ignore_sigs, nr_signals, do_nothing);
+
213
+
214 nr_signals = sizeof(fail_sigs) / sizeof(fail_sigs[0]);
+
215 _fuse_remove_signal_handlers(fail_sigs, nr_signals, exit_backtrace);
+
216}
+
+
int fuse_set_fail_signal_handlers(struct fuse_session *se)
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
void fuse_log(enum fuse_log_level level, const char *fmt,...) __attribute__((format(printf
+ +
void fuse_session_exit(struct fuse_session *se)
+
+ + +
+ + diff --git a/doc/html/fuse__uring_8c_source.html b/doc/html/fuse__uring_8c_source.html new file mode 100644 index 0000000..43b5b39 --- /dev/null +++ b/doc/html/fuse__uring_8c_source.html @@ -0,0 +1,1021 @@ + + + + + + + +libfuse: lib/fuse_uring.c Source File + + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
+
fuse_uring.c
+
+
+
1/*
+
2 * FUSE: Filesystem in Userspace
+
3 * Copyright (C) 2025 Bernd Schubert <bschubert@ddn.com>
+
4 *
+
5 * Implementation of (most of) FUSE-over-io-uring.
+
6 *
+
7 * This program can be distributed under the terms of the GNU LGPLv2.
+
8 * See the file LGPL2.txt
+
9 */
+
10
+
11#define _GNU_SOURCE
+
12
+
13#include "fuse_i.h"
+
14#include "fuse_kernel.h"
+
15#include "fuse_uring_i.h"
+
16
+
17#include <stdlib.h>
+
18#include <liburing.h>
+
19#include <sys/sysinfo.h>
+
20#include <stdint.h>
+
21#include <inttypes.h>
+
22#include <stdbool.h>
+
23#include <string.h>
+
24#include <unistd.h>
+
25#include <numa.h>
+
26#include <pthread.h>
+
27#include <stdio.h>
+
28#include <linux/sched.h>
+
29#include <poll.h>
+
30#include <sys/eventfd.h>
+
31
+
32/* Size of command data area in SQE when IORING_SETUP_SQE128 is used */
+
33#define FUSE_URING_MAX_SQE128_CMD_DATA 80
+
34
+
35struct fuse_ring_ent {
+
36 struct fuse_ring_queue *ring_queue; /* back pointer */
+
37 struct fuse_req req;
+
38
+
39 struct fuse_uring_req_header *req_header;
+
40 void *op_payload;
+
41 size_t req_payload_sz;
+
42
+
43 /* commit id of a fuse request */
+
44 uint64_t req_commit_id;
+
45
+
46 enum fuse_uring_cmd last_cmd;
+
47
+
48 /* header and payload */
+
49 struct iovec iov[2];
+
50};
+
51
+
52struct fuse_ring_queue {
+
53 /* back pointer */
+
54 struct fuse_ring_pool *ring_pool;
+
55 int qid;
+
56 int numa_node;
+
57 pthread_t tid;
+
58 int eventfd;
+
59 size_t req_header_sz;
+
60 struct io_uring ring;
+
61
+
62 pthread_mutex_t ring_lock;
+
63 bool cqe_processing;
+
64
+
65 /* size depends on queue depth */
+
66 struct fuse_ring_ent ent[];
+
67};
+
68
+
+ +
73 struct fuse_session *se;
+
74
+
75 /* number of queues */
+
76 size_t nr_queues;
+
77
+
78 /* number of per queue entries */
+
79 size_t queue_depth;
+
80
+
81 /* max payload size for fuse requests*/
+
82 size_t max_req_payload_sz;
+
83
+
84 /* size of a single queue */
+
85 size_t queue_mem_size;
+
86
+
87 unsigned int started_threads;
+
88 unsigned int failed_threads;
+
89
+
90 /* Avoid sending queue entries before FUSE_INIT reply*/
+
91 sem_t init_sem;
+
92
+
93 pthread_cond_t thread_start_cond;
+
94 pthread_mutex_t thread_start_mutex;
+
95
+
96 /* pointer to the first queue */
+
97 struct fuse_ring_queue *queues;
+
98};
+
+
99
+
100static size_t
+
101fuse_ring_queue_size(const size_t q_depth)
+
102{
+
103 const size_t req_size = sizeof(struct fuse_ring_ent) * q_depth;
+
104
+
105 return sizeof(struct fuse_ring_queue) + req_size;
+
106}
+
107
+
108static struct fuse_ring_queue *
+
109fuse_uring_get_queue(struct fuse_ring_pool *fuse_ring, int qid)
+
110{
+
111 void *ptr =
+
112 ((char *)fuse_ring->queues) + (qid * fuse_ring->queue_mem_size);
+
113
+
114 return ptr;
+
115}
+
116
+
120static void *fuse_uring_get_sqe_cmd(struct io_uring_sqe *sqe)
+
121{
+
122 return (void *)&sqe->cmd[0];
+
123}
+
124
+
125static void fuse_uring_sqe_set_req_data(struct fuse_uring_cmd_req *req,
+
126 const unsigned int qid,
+
127 const uint64_t commit_id)
+
128{
+
129 req->qid = qid;
+
130 req->commit_id = commit_id;
+
131 req->flags = 0;
+
132}
+
133
+
134static void
+
135fuse_uring_sqe_prepare(struct io_uring_sqe *sqe, struct fuse_ring_ent *req,
+
136 __u32 cmd_op)
+
137{
+
138 /* These fields should be written once, never change */
+
139 sqe->opcode = IORING_OP_URING_CMD;
+
140
+
141 /*
+
142 * IOSQE_FIXED_FILE: fd is the index to the fd *array*
+
143 * given to io_uring_register_files()
+
144 */
+
145 sqe->flags = IOSQE_FIXED_FILE;
+
146 sqe->fd = 0;
+
147
+
148 sqe->rw_flags = 0;
+
149 sqe->ioprio = 0;
+
150 sqe->off = 0;
+
151
+
152 io_uring_sqe_set_data(sqe, req);
+
153
+
154 sqe->cmd_op = cmd_op;
+
155 sqe->__pad1 = 0;
+
156}
+
157
+
158static int fuse_uring_commit_sqe(struct fuse_ring_pool *ring_pool,
+
159 struct fuse_ring_queue *queue,
+
160 struct fuse_ring_ent *ring_ent)
+
161{
+
162 bool locked = false;
+
163 struct fuse_session *se = ring_pool->se;
+
164 struct fuse_uring_req_header *rrh = ring_ent->req_header;
+
165 struct fuse_out_header *out = (struct fuse_out_header *)&rrh->in_out;
+
166 struct fuse_uring_ent_in_out *ent_in_out =
+
167 (struct fuse_uring_ent_in_out *)&rrh->ring_ent_in_out;
+
168 struct io_uring_sqe *sqe;
+
169
+
170 if (pthread_self() != queue->tid) {
+
171 pthread_mutex_lock(&queue->ring_lock);
+
172 locked = true;
+
173 }
+
174
+
175 sqe = io_uring_get_sqe(&queue->ring);
+
176
+
177 if (sqe == NULL) {
+
178 /* This is an impossible condition, unless there is a bug.
+
179 * The kernel sent back an SQEs, which is assigned to a request.
+
180 * There is no way to get out of SQEs, as the number of
+
181 * SQEs matches the number tof requests.
+
182 */
+
183
+
184 se->error = -EIO;
+
185 fuse_log(FUSE_LOG_ERR, "Failed to get a ring SQEs\n");
+
186
+
187 return -EIO;
+
188 }
+
189
+
190 ring_ent->last_cmd = FUSE_IO_URING_CMD_COMMIT_AND_FETCH;
+
191 fuse_uring_sqe_prepare(sqe, ring_ent, ring_ent->last_cmd);
+
192 fuse_uring_sqe_set_req_data(fuse_uring_get_sqe_cmd(sqe), queue->qid,
+
193 ring_ent->req_commit_id);
+
194
+
195 if (se->debug) {
+
196 fuse_log(FUSE_LOG_DEBUG, " unique: %" PRIu64 ", result=%d\n",
+
197 out->unique, ent_in_out->payload_sz);
+
198 }
+
199
+
200 if (!queue->cqe_processing)
+
201 io_uring_submit(&queue->ring);
+
202
+
203 if (locked)
+
204 pthread_mutex_unlock(&queue->ring_lock);
+
205
+
206 return 0;
+
207}
+
208
+
209int fuse_req_get_payload(fuse_req_t req, char **payload, size_t *payload_sz,
+
210 void **mr)
+
211{
+
212 struct fuse_ring_ent *ring_ent;
+
213
+
214 /* Not possible without io-uring interface */
+
215 if (!req->flags.is_uring)
+
216 return -EINVAL;
+
217
+
218 ring_ent = container_of(req, struct fuse_ring_ent, req);
+
219
+
220 *payload = ring_ent->op_payload;
+
221 *payload_sz = ring_ent->req_payload_sz;
+
222
+
223 /*
+
224 * For now unused, but will be used later when the application can
+
225 * allocate the buffers itself and register them for rdma.
+
226 */
+
227 if (mr)
+
228 *mr = NULL;
+
229
+
230 return 0;
+
231}
+
232
+
233int send_reply_uring(fuse_req_t req, int error, const void *arg, size_t argsize)
+
234{
+
235 int res;
+
236 struct fuse_ring_ent *ring_ent =
+
237 container_of(req, struct fuse_ring_ent, req);
+
238 struct fuse_uring_req_header *rrh = ring_ent->req_header;
+
239 struct fuse_out_header *out = (struct fuse_out_header *)&rrh->in_out;
+
240 struct fuse_uring_ent_in_out *ent_in_out =
+
241 (struct fuse_uring_ent_in_out *)&rrh->ring_ent_in_out;
+
242
+
243 struct fuse_ring_queue *queue = ring_ent->ring_queue;
+
244 struct fuse_ring_pool *ring_pool = queue->ring_pool;
+
245 size_t max_payload_sz = ring_pool->max_req_payload_sz;
+
246
+
247 if (argsize > max_payload_sz) {
+
248 fuse_log(FUSE_LOG_ERR, "argsize %zu exceeds buffer size %zu",
+
249 argsize, max_payload_sz);
+
250 error = -EINVAL;
+
251 } else if (argsize) {
+
252 if (arg != ring_ent->op_payload)
+
253 memcpy(ring_ent->op_payload, arg, argsize);
+
254 }
+
255 ent_in_out->payload_sz = argsize;
+
256
+
257 out->error = error;
+
258 out->unique = req->unique;
+
259
+
260 res = fuse_uring_commit_sqe(ring_pool, queue, ring_ent);
+
261
+
262 fuse_free_req(req);
+
263
+
264 return res;
+
265}
+
266
+
267int fuse_reply_data_uring(fuse_req_t req, struct fuse_bufvec *bufv,
+
268 enum fuse_buf_copy_flags flags)
+
269{
+
270 struct fuse_ring_ent *ring_ent =
+
271 container_of(req, struct fuse_ring_ent, req);
+
272
+
273 struct fuse_ring_queue *queue = ring_ent->ring_queue;
+
274 struct fuse_ring_pool *ring_pool = queue->ring_pool;
+
275 struct fuse_uring_req_header *rrh = ring_ent->req_header;
+
276 struct fuse_out_header *out = (struct fuse_out_header *)&rrh->in_out;
+
277 struct fuse_uring_ent_in_out *ent_in_out =
+
278 (struct fuse_uring_ent_in_out *)&rrh->ring_ent_in_out;
+
279 size_t max_payload_sz = ring_ent->req_payload_sz;
+
280 struct fuse_bufvec dest_vec = FUSE_BUFVEC_INIT(max_payload_sz);
+
281 int res;
+
282
+
283 dest_vec.buf[0].mem = ring_ent->op_payload;
+
284 dest_vec.buf[0].size = max_payload_sz;
+
285
+
286 res = fuse_buf_copy(&dest_vec, bufv, flags);
+
287
+
288 out->error = res < 0 ? res : 0;
+
289 out->unique = req->unique;
+
290
+
291 ent_in_out->payload_sz = res > 0 ? res : 0;
+
292
+
293 res = fuse_uring_commit_sqe(ring_pool, queue, ring_ent);
+
294
+
295 fuse_free_req(req);
+
296
+
297 return res;
+
298}
+
299
+
303int fuse_send_msg_uring(fuse_req_t req, struct iovec *iov, int count)
+
304{
+
305 struct fuse_ring_ent *ring_ent =
+
306 container_of(req, struct fuse_ring_ent, req);
+
307
+
308 struct fuse_ring_queue *queue = ring_ent->ring_queue;
+
309 struct fuse_ring_pool *ring_pool = queue->ring_pool;
+
310 struct fuse_uring_req_header *rrh = ring_ent->req_header;
+
311 struct fuse_out_header *out = (struct fuse_out_header *)&rrh->in_out;
+
312 struct fuse_uring_ent_in_out *ent_in_out =
+
313 (struct fuse_uring_ent_in_out *)&rrh->ring_ent_in_out;
+
314 size_t max_buf = ring_pool->max_req_payload_sz;
+
315 size_t len = 0;
+
316 int res = 0;
+
317
+
318 /* copy iov into the payload, idx=0 is the header section */
+
319 for (int idx = 1; idx < count; idx++) {
+
320 struct iovec *cur = &iov[idx];
+
321
+
322 if (len + cur->iov_len > max_buf) {
+
323 fuse_log(FUSE_LOG_ERR,
+
324 "iov[%d] exceeds buffer size %zu",
+
325 idx, max_buf);
+
326 res = -EINVAL; /* Gracefully handle this? */
+
327 break;
+
328 }
+
329
+
330 memcpy(ring_ent->op_payload + len, cur->iov_base, cur->iov_len);
+
331 len += cur->iov_len;
+
332 }
+
333
+
334 ent_in_out->payload_sz = len;
+
335
+
336 out->error = res;
+
337 out->unique = req->unique;
+
338 out->len = len;
+
339
+
340 return fuse_uring_commit_sqe(ring_pool, queue, ring_ent);
+
341}
+
342
+
343static int fuse_queue_setup_io_uring(struct io_uring *ring, size_t qid,
+
344 size_t depth, int fd, int evfd)
+
345{
+
346 int rc;
+
347 struct io_uring_params params = {0};
+
348 int files[2] = { fd, evfd };
+
349
+
350 depth += 1; /* for the eventfd poll SQE */
+
351
+
352 params.flags = IORING_SETUP_SQE128;
+
353
+
354 /* Replies are batched and flushed in one io_uring_enter; don't let a
+
355 * single failing commit SQE stall submission of the rest of the batch.
+
356 */
+
357 params.flags |= IORING_SETUP_SUBMIT_ALL;
+
358
+
359 /* Avoid cq overflow */
+
360 params.flags |= IORING_SETUP_CQSIZE;
+
361 params.cq_entries = depth * 2;
+
362
+
363 /* These flags should help to increase performance, but actually
+
364 * make it a bit slower - reason should get investigated.
+
365 */
+
366 if (0) {
+
367 /* Has the main slow down effect */
+
368 params.flags |= IORING_SETUP_SINGLE_ISSUER;
+
369
+
370 // params.flags |= IORING_SETUP_DEFER_TASKRUN;
+
371 params.flags |= IORING_SETUP_TASKRUN_FLAG;
+
372
+
373 /* Second main effect to make it slower */
+
374 params.flags |= IORING_SETUP_COOP_TASKRUN;
+
375 }
+
376
+
377 rc = io_uring_queue_init_params(depth, ring, &params);
+
378 if (rc != 0) {
+
379 fuse_log(FUSE_LOG_ERR, "Failed to setup qid %zu: %d (%s)\n",
+
380 qid, rc, strerror(-rc));
+
381 return rc;
+
382 }
+
383
+
384 rc = io_uring_register_files(ring, files, 1);
+
385 if (rc != 0) {
+
386 rc = -errno;
+
387 fuse_log(FUSE_LOG_ERR,
+
388 "Failed to register files for ring idx %zu: %s",
+
389 qid, strerror(errno));
+
390 return rc;
+
391 }
+
392
+
393 return 0;
+
394}
+
395
+
396static void fuse_session_destruct_uring(struct fuse_ring_pool *fuse_ring)
+
397{
+
398 for (size_t qid = 0; qid < fuse_ring->nr_queues; qid++) {
+
399 struct fuse_ring_queue *queue =
+
400 fuse_uring_get_queue(fuse_ring, qid);
+
401
+
402 if (queue->tid != 0) {
+
403 uint64_t value = 1ULL;
+
404 int rc;
+
405
+
406 rc = write(queue->eventfd, &value, sizeof(value));
+
407 if (rc != sizeof(value))
+
408 fprintf(stderr,
+
409 "Wrote to eventfd=%d err=%s: rc=%d\n",
+
410 queue->eventfd, strerror(errno), rc);
+
411 pthread_cancel(queue->tid);
+
412 pthread_join(queue->tid, NULL);
+
413 queue->tid = 0;
+
414 }
+
415
+
416 if (queue->eventfd >= 0) {
+
417 close(queue->eventfd);
+
418 queue->eventfd = -1;
+
419 }
+
420
+
421 if (queue->ring.ring_fd != -1)
+
422 io_uring_queue_exit(&queue->ring);
+
423
+
424 for (size_t idx = 0; idx < fuse_ring->queue_depth; idx++) {
+
425 struct fuse_ring_ent *ent = &queue->ent[idx];
+
426
+
427 numa_free(ent->op_payload, ent->req_payload_sz);
+
428 numa_free(ent->req_header, queue->req_header_sz);
+
429 }
+
430
+
431 pthread_mutex_destroy(&queue->ring_lock);
+
432 }
+
433
+
434 free(fuse_ring->queues);
+
435 pthread_cond_destroy(&fuse_ring->thread_start_cond);
+
436 pthread_mutex_destroy(&fuse_ring->thread_start_mutex);
+
437 free(fuse_ring);
+
438}
+
439
+
440static int fuse_uring_register_ent(struct fuse_ring_queue *queue,
+
441 struct fuse_ring_ent *ent)
+
442{
+
443 struct io_uring_sqe *sqe;
+
444
+
445 sqe = io_uring_get_sqe(&queue->ring);
+
446 if (sqe == NULL) {
+
447 /*
+
448 * All SQEs are idle here - no good reason this
+
449 * could fail
+
450 */
+
451 fuse_log(FUSE_LOG_ERR, "Failed to get all ring SQEs");
+
452 return -EIO;
+
453 }
+
454
+
455 ent->last_cmd = FUSE_IO_URING_CMD_REGISTER;
+
456 fuse_uring_sqe_prepare(sqe, ent, ent->last_cmd);
+
457
+
458 /* only needed for fetch */
+
459 ent->iov[0].iov_base = ent->req_header;
+
460 ent->iov[0].iov_len = queue->req_header_sz;
+
461
+
462 ent->iov[1].iov_base = ent->op_payload;
+
463 ent->iov[1].iov_len = ent->req_payload_sz;
+
464
+
465 sqe->addr = (uint64_t)(ent->iov);
+
466 sqe->len = 2;
+
467
+
468 /* this is a fetch, kernel does not read commit id */
+
469 fuse_uring_sqe_set_req_data(fuse_uring_get_sqe_cmd(sqe), queue->qid, 0);
+
470
+
471 return 0;
+
472
+
473}
+
474
+
475static int fuse_uring_register_queue(struct fuse_ring_queue *queue)
+
476{
+
477 struct fuse_ring_pool *ring_pool = queue->ring_pool;
+
478 unsigned int sq_ready;
+
479 struct io_uring_sqe *sqe;
+
480 int res;
+
481
+
482 for (size_t idx = 0; idx < ring_pool->queue_depth; idx++) {
+
483 struct fuse_ring_ent *ent = &queue->ent[idx];
+
484
+
485 res = fuse_uring_register_ent(queue, ent);
+
486 if (res != 0)
+
487 return res;
+
488 }
+
489
+
490 sq_ready = io_uring_sq_ready(&queue->ring);
+
491 if (sq_ready != ring_pool->queue_depth) {
+
492 fuse_log(FUSE_LOG_ERR,
+
493 "SQE ready mismatch, expected %zu got %u\n",
+
494 ring_pool->queue_depth, sq_ready);
+
495 return -EINVAL;
+
496 }
+
497
+
498 /* Poll SQE for the eventfd to wake up on teardown */
+
499 sqe = io_uring_get_sqe(&queue->ring);
+
500 if (sqe == NULL) {
+
501 fuse_log(FUSE_LOG_ERR, "Failed to get eventfd SQE");
+
502 return -EIO;
+
503 }
+
504
+
505 io_uring_prep_poll_add(sqe, queue->eventfd, POLLIN);
+
506 io_uring_sqe_set_data(sqe, (void *)(uintptr_t)queue->eventfd);
+
507
+
508 /* Only preparation until here, no submission yet */
+
509
+
510 return 0;
+
511}
+
512
+
513static struct fuse_ring_pool *fuse_create_ring(struct fuse_session *se)
+
514{
+
515 struct fuse_ring_pool *fuse_ring = NULL;
+
516 const size_t nr_queues = get_nprocs_conf();
+
517 size_t payload_sz = se->bufsize - FUSE_BUFFER_HEADER_SIZE;
+
518 size_t queue_sz;
+
519
+
520 if (se->debug)
+
521 fuse_log(FUSE_LOG_DEBUG, "starting io-uring q-depth=%d\n",
+
522 se->uring.q_depth);
+
523
+
524 fuse_ring = calloc(1, sizeof(*fuse_ring));
+
525 if (fuse_ring == NULL) {
+
526 fuse_log(FUSE_LOG_ERR, "Allocating the ring failed\n");
+
527 goto err;
+
528 }
+
529
+
530 queue_sz = fuse_ring_queue_size(se->uring.q_depth);
+
531 fuse_ring->queues = calloc(1, queue_sz * nr_queues);
+
532 if (fuse_ring->queues == NULL) {
+
533 fuse_log(FUSE_LOG_ERR, "Allocating the queues failed\n");
+
534 goto err;
+
535 }
+
536
+
537 fuse_ring->se = se;
+
538 fuse_ring->nr_queues = nr_queues;
+
539 fuse_ring->queue_depth = se->uring.q_depth;
+
540 fuse_ring->max_req_payload_sz = payload_sz;
+
541 fuse_ring->queue_mem_size = queue_sz;
+
542
+
543 /*
+
544 * very basic queue initialization, that cannot fail and will
+
545 * allow easy cleanup if something (like mmap) fails in the middle
+
546 * below
+
547 */
+
548 for (size_t qid = 0; qid < nr_queues; qid++) {
+
549 struct fuse_ring_queue *queue =
+
550 fuse_uring_get_queue(fuse_ring, qid);
+
551
+
552 queue->ring.ring_fd = -1;
+
553 queue->numa_node = numa_node_of_cpu(qid);
+
554 queue->qid = qid;
+
555 queue->ring_pool = fuse_ring;
+
556 queue->eventfd = -1;
+
557 pthread_mutex_init(&queue->ring_lock, NULL);
+
558 }
+
559
+
560 pthread_cond_init(&fuse_ring->thread_start_cond, NULL);
+
561 pthread_mutex_init(&fuse_ring->thread_start_mutex, NULL);
+
562 sem_init(&fuse_ring->init_sem, 0, 0);
+
563
+
564 return fuse_ring;
+
565
+
566err:
+
567 if (fuse_ring)
+
568 fuse_session_destruct_uring(fuse_ring);
+
569
+
570 return NULL;
+
571}
+
572
+
573static void fuse_uring_resubmit(struct fuse_ring_queue *queue,
+
574 struct fuse_ring_ent *ent)
+
575{
+
576 struct io_uring_sqe *sqe;
+
577
+
578 sqe = io_uring_get_sqe(&queue->ring);
+
579 if (sqe == NULL) {
+
580 /* This is an impossible condition, unless there is a bug.
+
581 * The kernel sent back an SQEs, which is assigned to a request.
+
582 * There is no way to get out of SQEs, as the number of
+
583 * SQEs matches the number tof requests.
+
584 */
+
585
+
586 queue->ring_pool->se->error = -EIO;
+
587 fuse_log(FUSE_LOG_ERR, "Failed to get a ring SQEs\n");
+
588
+
589 return;
+
590 }
+
591
+
592 fuse_uring_sqe_prepare(sqe, ent, ent->last_cmd);
+
593
+
594 switch (ent->last_cmd) {
+
595 case FUSE_IO_URING_CMD_REGISTER:
+
596 sqe->addr = (uint64_t)(ent->iov);
+
597 sqe->len = 2;
+
598 fuse_uring_sqe_set_req_data(fuse_uring_get_sqe_cmd(sqe),
+
599 queue->qid, 0);
+
600 break;
+
601 case FUSE_IO_URING_CMD_COMMIT_AND_FETCH:
+
602 fuse_uring_sqe_set_req_data(fuse_uring_get_sqe_cmd(sqe),
+
603 queue->qid, ent->req_commit_id);
+
604 break;
+
605 default:
+
606 fuse_log(FUSE_LOG_ERR, "Unknown command type: %d\n",
+
607 ent->last_cmd);
+
608 queue->ring_pool->se->error = -EINVAL;
+
609 break;
+
610 }
+
611
+
612 /* caller submits */
+
613}
+
614
+
615static void fuse_uring_handle_cqe(struct fuse_ring_queue *queue,
+
616 struct io_uring_cqe *cqe)
+
617{
+
618 struct fuse_ring_ent *ent = io_uring_cqe_get_data(cqe);
+
619
+
620 if (!ent) {
+
621 fuse_log(FUSE_LOG_ERR,
+
622 "cqe=%p io_uring_cqe_get_data returned NULL\n", cqe);
+
623 return;
+
624 }
+
625
+
626 struct fuse_req *req = &ent->req;
+
627 struct fuse_ring_pool *fuse_ring = queue->ring_pool;
+
628 struct fuse_uring_req_header *rrh = ent->req_header;
+
629
+
630 struct fuse_in_header *in = (struct fuse_in_header *)&rrh->in_out;
+
631 struct fuse_uring_ent_in_out *ent_in_out = &rrh->ring_ent_in_out;
+
632
+
633 ent->req_commit_id = ent_in_out->commit_id;
+
634 if (unlikely(ent->req_commit_id == 0)) {
+
635 /*
+
636 * If this happens kernel will not find the response - it will
+
637 * be stuck forever - better to abort immediately.
+
638 */
+
639 fuse_log(FUSE_LOG_ERR, "Received invalid commit_id=0\n");
+
640 abort();
+
641 }
+
642
+
643 memset(&req->flags, 0, sizeof(req->flags));
+
644 memset(&req->u, 0, sizeof(req->u));
+
645 req->flags.is_uring = 1;
+
646 req->ref_cnt++;
+
647 req->ch = NULL; /* not needed for uring */
+
648 req->interrupted = 0;
+
649 list_init_req(req);
+
650
+
651 fuse_session_process_uring_cqe(fuse_ring->se, req, in, &rrh->op_in,
+
652 ent->op_payload, ent_in_out->payload_sz);
+
653}
+
654
+
655static int fuse_uring_queue_handle_cqes(struct fuse_ring_queue *queue)
+
656{
+
657 struct fuse_ring_pool *ring_pool = queue->ring_pool;
+
658 struct fuse_session *se = ring_pool->se;
+
659 size_t num_completed = 0;
+
660 struct io_uring_cqe *cqe;
+
661 unsigned int head;
+
662 struct fuse_ring_ent *ent;
+
663 int ret = 0;
+
664
+
665 io_uring_for_each_cqe(&queue->ring, head, cqe) {
+
666 int err = 0;
+
667
+
668 num_completed++;
+
669
+
670 err = cqe->res;
+
671 if (unlikely(err != 0)) {
+
672 if (err > 0 && ((uintptr_t)io_uring_cqe_get_data(cqe) ==
+
673 (unsigned int)queue->eventfd)) {
+
674 /* teardown from eventfd */
+
675 return -ENOTCONN;
+
676 }
+
677
+
678
+
679 switch (err) {
+
680 case -EAGAIN:
+
681 fallthrough;
+
682 case -EINTR:
+
683 ent = io_uring_cqe_get_data(cqe);
+
684 fuse_uring_resubmit(queue, ent);
+
685 continue;
+
686 default:
+
687 break;
+
688 }
+
689
+
690 /* -ENOTCONN is ok on umount */
+
691 if (err != -ENOTCONN) {
+
692 se->error = cqe->res;
+
693
+
694 /* return first error */
+
695 if (ret == 0)
+
696 ret = err;
+
697 }
+
698
+
699 } else {
+
700 fuse_uring_handle_cqe(queue, cqe);
+
701 }
+
702 }
+
703
+
704 if (num_completed)
+
705 io_uring_cq_advance(&queue->ring, num_completed);
+
706
+
707 return ret == 0 ? 0 : num_completed;
+
708}
+
709
+
714static void fuse_uring_set_thread_core(int qid)
+
715{
+
716 cpu_set_t mask;
+
717 int rc;
+
718
+
719 CPU_ZERO(&mask);
+
720 CPU_SET(qid, &mask);
+
721 rc = sched_setaffinity(0, sizeof(cpu_set_t), &mask);
+
722 if (rc != 0)
+
723 fuse_log(FUSE_LOG_ERR, "Failed to bind qid=%d to its core: %s\n",
+
724 qid, strerror(errno));
+
725
+
726 if (0) {
+
727 const int policy = SCHED_IDLE;
+
728 const struct sched_param param = {
+
729 .sched_priority = sched_get_priority_min(policy),
+
730 };
+
731
+
732 /* Set the lowest possible priority, so that the application
+
733 * submitting requests is not moved away from the current core.
+
734 */
+
735 rc = sched_setscheduler(0, policy, &param);
+
736 if (rc != 0)
+
737 fuse_log(FUSE_LOG_ERR, "Failed to set scheduler: %s\n",
+
738 strerror(errno));
+
739 }
+
740}
+
741
+
742/*
+
743 * @return negative error code or io-uring file descriptor
+
744 */
+
745static int fuse_uring_init_queue(struct fuse_ring_queue *queue)
+
746{
+
747 struct fuse_ring_pool *ring = queue->ring_pool;
+
748 struct fuse_session *se = ring->se;
+
749 int res;
+
750 size_t page_sz = sysconf(_SC_PAGESIZE);
+
751
+
752 queue->eventfd = eventfd(0, EFD_CLOEXEC);
+
753 if (queue->eventfd < 0) {
+
754 res = -errno;
+
755 fuse_log(FUSE_LOG_ERR,
+
756 "Failed to create eventfd for qid %d: %s\n",
+
757 queue->qid, strerror(errno));
+
758 return res;
+
759 }
+
760
+
761 res = fuse_queue_setup_io_uring(&queue->ring, queue->qid,
+
762 ring->queue_depth, se->fd,
+
763 queue->eventfd);
+
764 if (res != 0) {
+
765 fuse_log(FUSE_LOG_ERR, "qid=%d io_uring init failed\n",
+
766 queue->qid);
+
767 return res;
+
768 }
+
769
+
770 queue->req_header_sz = ROUND_UP(sizeof(struct fuse_uring_req_header),
+
771 page_sz);
+
772
+
773 for (size_t idx = 0; idx < ring->queue_depth; idx++) {
+
774 struct fuse_ring_ent *ring_ent = &queue->ent[idx];
+
775 struct fuse_req *req = &ring_ent->req;
+
776
+
777 ring_ent->ring_queue = queue;
+
778
+
779 /*
+
780 * Also allocate the header to have it page aligned, which
+
781 * is a requirement for page pinning
+
782 */
+
783 ring_ent->req_header =
+
784 numa_alloc_local(queue->req_header_sz);
+
785 if (!ring_ent->req_header)
+
786 return -ENOMEM;
+
787 ring_ent->req_payload_sz = ring->max_req_payload_sz;
+
788
+
789 ring_ent->op_payload =
+
790 numa_alloc_local(ring_ent->req_payload_sz);
+
791 if (!ring_ent->op_payload)
+
792 return -ENOMEM;
+
793
+
794 req->se = se;
+
795 pthread_mutex_init(&req->lock, NULL);
+
796 req->flags.is_uring = 1;
+
797 req->ref_cnt = 1; /* extra ref to avoid destruction */
+
798 list_init_req(req);
+
799 }
+
800
+
801 res = fuse_uring_register_queue(queue);
+
802 if (res != 0) {
+
803 fuse_log(
+
804 FUSE_LOG_ERR,
+
805 "Grave fuse-uring error on preparing SQEs, aborting\n");
+
806 se->error = -EIO;
+ +
808 return res;
+
809 }
+
810
+
811 return queue->ring.ring_fd;
+
812}
+
813
+
814static void *fuse_uring_thread(void *arg)
+
815{
+
816 struct fuse_ring_queue *queue = arg;
+
817 struct fuse_ring_pool *ring_pool = queue->ring_pool;
+
818 struct fuse_session *se = ring_pool->se;
+
819 int err;
+
820 char thread_name[16] = { 0 };
+
821
+
822 snprintf(thread_name, 16, "fuse-ring-%d", queue->qid);
+
823 thread_name[15] = '\0';
+
824 fuse_set_thread_name(thread_name);
+
825
+
826 fuse_uring_set_thread_core(queue->qid);
+
827
+
828 err = fuse_uring_init_queue(queue);
+
829 pthread_mutex_lock(&ring_pool->thread_start_mutex);
+
830 if (err < 0)
+
831 ring_pool->failed_threads++;
+
832 ring_pool->started_threads++;
+
833 pthread_cond_broadcast(&ring_pool->thread_start_cond);
+
834 pthread_mutex_unlock(&ring_pool->thread_start_mutex);
+
835
+
836 if (err < 0) {
+
837 fuse_log(FUSE_LOG_ERR, "qid=%d queue setup failed\n",
+
838 queue->qid);
+
839 goto err_non_fatal;
+
840 }
+
841
+
842 sem_wait(&ring_pool->init_sem);
+
843
+
844 /* Not using fuse_session_exited(se), as that cannot be inlined */
+
845 while (!atomic_load_explicit(&se->mt_exited, memory_order_relaxed)) {
+
846 io_uring_submit_and_wait(&queue->ring, 1);
+
847
+
848 pthread_mutex_lock(&queue->ring_lock);
+
849 queue->cqe_processing = true;
+
850 err = fuse_uring_queue_handle_cqes(queue);
+
851 queue->cqe_processing = false;
+
852 pthread_mutex_unlock(&queue->ring_lock);
+
853 if (err < 0)
+
854 goto err;
+
855 }
+
856
+
857 return NULL;
+
858
+
859err:
+ +
861err_non_fatal:
+
862 return NULL;
+
863}
+
864
+
865static int fuse_uring_start_ring_threads(struct fuse_ring_pool *ring)
+
866{
+
867 int rc = 0;
+
868
+
869 for (size_t qid = 0; qid < ring->nr_queues; qid++) {
+
870 struct fuse_ring_queue *queue = fuse_uring_get_queue(ring, qid);
+
871
+
872 rc = pthread_create(&queue->tid, NULL, fuse_uring_thread, queue);
+
873 if (rc != 0)
+
874 break;
+
875 }
+
876
+
877 return rc;
+
878}
+
879
+
880static int fuse_uring_sanity_check(struct fuse_session *se)
+
881{
+
882 if (se->uring.q_depth == 0) {
+
883 fuse_log(FUSE_LOG_ERR, "io-uring queue depth must be > 0\n");
+
884 return -EINVAL;
+
885 }
+
886
+
887 _Static_assert(sizeof(struct fuse_uring_cmd_req) <=
+
888 FUSE_URING_MAX_SQE128_CMD_DATA,
+
889 "SQE128_CMD_DATA has 80B cmd data");
+
890
+
891 return 0;
+
892}
+
893
+
894int fuse_uring_start(struct fuse_session *se)
+
895{
+
896 int err = 0;
+
897 struct fuse_ring_pool *fuse_ring;
+
898
+
899 fuse_uring_sanity_check(se);
+
900
+
901 fuse_ring = fuse_create_ring(se);
+
902 if (fuse_ring == NULL) {
+
903 err = -EADDRNOTAVAIL;
+
904 goto err;
+
905 }
+
906
+
907 se->uring.pool = fuse_ring;
+
908
+
909 /* Hold off threads from send fuse ring entries (SQEs) */
+
910 sem_init(&fuse_ring->init_sem, 0, 0);
+
911 pthread_cond_init(&fuse_ring->thread_start_cond, NULL);
+
912 pthread_mutex_init(&fuse_ring->thread_start_mutex, NULL);
+
913
+
914 err = fuse_uring_start_ring_threads(fuse_ring);
+
915 if (err)
+
916 goto err;
+
917
+
918 /*
+
919 * Wait for all threads to start or to fail
+
920 */
+
921 pthread_mutex_lock(&fuse_ring->thread_start_mutex);
+
922 while (fuse_ring->started_threads < fuse_ring->nr_queues)
+
923 pthread_cond_wait(&fuse_ring->thread_start_cond,
+
924 &fuse_ring->thread_start_mutex);
+
925
+
926 if (fuse_ring->failed_threads != 0)
+
927 err = -EADDRNOTAVAIL;
+
928 pthread_mutex_unlock(&fuse_ring->thread_start_mutex);
+
929
+
930err:
+
931 if (err) {
+
932 /* Note all threads need to have been started */
+
933 if (fuse_ring)
+
934 fuse_session_destruct_uring(fuse_ring);
+
935 se->uring.pool = NULL;
+
936 }
+
937 return err;
+
938}
+
939
+
940int fuse_uring_stop(struct fuse_session *se)
+
941{
+
942 struct fuse_ring_pool *ring = se->uring.pool;
+
943
+
944 if (ring == NULL)
+
945 return 0;
+
946
+
947 fuse_session_destruct_uring(ring);
+
948
+
949 return 0;
+
950}
+
951
+
952void fuse_uring_wake_ring_threads(struct fuse_session *se)
+
953{
+
954 struct fuse_ring_pool *ring = se->uring.pool;
+
955
+
956 /* Wake up the threads to let them send SQEs */
+
957 for (size_t qid = 0; qid < ring->nr_queues; qid++)
+
958 sem_post(&ring->init_sem);
+
959}
+
ssize_t fuse_buf_copy(struct fuse_bufvec *dst, struct fuse_bufvec *src, enum fuse_buf_copy_flags flags)
Definition buffer.c:284
+
fuse_buf_copy_flags
+
void fuse_log(enum fuse_log_level level, const char *fmt,...) __attribute__((format(printf
+
void fuse_session_exit(struct fuse_session *se)
+
struct fuse_req * fuse_req_t
+
int fuse_req_get_payload(fuse_req_t req, char **payload, size_t *payload_sz, void **mr)
+
void * mem
+
size_t size
+ +
struct fuse_buf buf[1]
+ + + + +
+ + +
+ + diff --git a/doc/html/fuse__uring__i_8h_source.html b/doc/html/fuse__uring__i_8h_source.html new file mode 100644 index 0000000..08b54c1 --- /dev/null +++ b/doc/html/fuse__uring__i_8h_source.html @@ -0,0 +1,149 @@ + + + + + + + +libfuse: lib/fuse_uring_i.h Source File + + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
+
fuse_uring_i.h
+
+
+
1/*
+
2 * FUSE: Filesystem in Userspace
+
3 * Copyright (C) 2025 Bernd Schubert <bschubert@ddn.com>
+
4 * This program can be distributed under the terms of the GNU LGPLv2.
+
5 * See the file LGPL2.txt
+
6 */
+
7
+
8#ifndef FUSE_URING_I_H_
+
9#define FUSE_URING_I_H_
+
10
+
11#include "fuse_config.h"
+
12#include "fuse_lowlevel.h"
+
13#include "fuse_kernel.h"
+
14
+
15#ifndef HAVE_URING
+
16#include "util.h"
+
17#endif
+
18
+
19#include <errno.h> // IWYU pragma: keep
+
20
+
21/* io-uring defaults */
+
22#define SESSION_DEF_URING_ENABLE (0)
+
23#define SESSION_DEF_URING_Q_DEPTH (8)
+
24
+
25void fuse_session_process_uring_cqe(struct fuse_session *se,
+
26 struct fuse_req *req,
+
27 struct fuse_in_header *in, void *in_header,
+
28 void *in_payload, size_t payload_len);
+
29
+
30#ifdef HAVE_URING
+
31
+
32struct fuse_in_header;
+
33
+
34int fuse_uring_start(struct fuse_session *se);
+
35void fuse_uring_wake_ring_threads(struct fuse_session *se);
+
36int fuse_uring_stop(struct fuse_session *se);
+
37int send_reply_uring(fuse_req_t req, int error, const void *arg,
+
38 size_t argsize);
+
39
+
40int fuse_reply_data_uring(fuse_req_t req, struct fuse_bufvec *bufv,
+
41 enum fuse_buf_copy_flags flags);
+
42int fuse_send_msg_uring(fuse_req_t req, struct iovec *iov, int count);
+
43
+
44#else // HAVE_URING
+
45
+
46static inline int fuse_uring_start(struct fuse_session *se FUSE_VAR_UNUSED)
+
47{
+
48 return -ENOTSUP;
+
49}
+
50
+
51static inline void
+
52fuse_uring_wake_ring_threads(struct fuse_session *se FUSE_VAR_UNUSED)
+
53{
+
54}
+
55
+
56static inline int fuse_uring_stop(struct fuse_session *se FUSE_VAR_UNUSED)
+
57{
+
58 return -ENOTSUP;
+
59}
+
60
+
61static inline int send_reply_uring(fuse_req_t req FUSE_VAR_UNUSED,
+
62 int error FUSE_VAR_UNUSED,
+
63 const void *arg FUSE_VAR_UNUSED,
+
64 size_t argsize FUSE_VAR_UNUSED)
+
65{
+
66 return -ENOTSUP;
+
67}
+
68
+
69static inline int
+
70fuse_reply_data_uring(fuse_req_t req FUSE_VAR_UNUSED,
+
71 struct fuse_bufvec *bufv FUSE_VAR_UNUSED,
+
72 enum fuse_buf_copy_flags flags FUSE_VAR_UNUSED)
+
73{
+
74 return -ENOTSUP;
+
75}
+
76
+
77static inline int fuse_send_msg_uring(fuse_req_t req FUSE_VAR_UNUSED,
+
78 struct iovec *iov FUSE_VAR_UNUSED,
+
79 int count FUSE_VAR_UNUSED)
+
80{
+
81 return -ENOTSUP;
+
82}
+
83
+
84#endif // HAVE_URING
+
85
+
86#endif // FUSE_URING_I_H_
+
fuse_buf_copy_flags
+ +
struct fuse_req * fuse_req_t
+ +
+ + +
+ + diff --git a/doc/html/fusermount_8c_source.html b/doc/html/fusermount_8c_source.html new file mode 100644 index 0000000..d643aea --- /dev/null +++ b/doc/html/fusermount_8c_source.html @@ -0,0 +1,1877 @@ + + + + + + + +libfuse: util/fusermount.c Source File + + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
+
fusermount.c
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 This program can be distributed under the terms of the GNU GPLv2.
+
6 See the file GPL2.txt.
+
7*/
+
8/* This program does the mounting and unmounting of FUSE filesystems */
+
9
+
10#define _GNU_SOURCE /* for clone,strchrnul and close_range */
+
11#include "fuse_config.h"
+
12#include "mount_util.h"
+
13#include "util.h"
+
14
+
15#include <stdio.h>
+
16#include <stdlib.h>
+
17#include <string.h>
+
18#include <ctype.h>
+
19#include <unistd.h>
+
20#include <getopt.h>
+
21#include <errno.h>
+
22#include <fcntl.h>
+
23#include <pwd.h>
+
24#include <paths.h>
+
25#include <mntent.h>
+
26#include <sys/wait.h>
+
27#include <sys/stat.h>
+
28#include <sys/param.h>
+
29
+
30#include "fuse_mount_compat.h"
+
31
+
32#include <sys/fsuid.h>
+
33#include <sys/socket.h>
+
34#include <sys/utsname.h>
+
35#include <sched.h>
+
36#include <stdbool.h>
+
37#include <sys/vfs.h>
+
38
+
39#if defined HAVE_CLOSE_RANGE && defined linux
+
40#include <linux/close_range.h>
+
41#endif
+
42
+
43#if defined HAVE_LISTMOUNT
+
44#include <linux/mount.h>
+
45#include <syscall.h>
+
46#include <stdint.h>
+
47#endif
+
48
+
49#define FUSE_COMMFD_ENV "_FUSE_COMMFD"
+
50#define FUSE_KERN_DEVICE_ENV "FUSE_KERN_DEVICE"
+
51
+
52#define FUSE_DEV "/dev/fuse"
+
53
+
54static const char *progname;
+
55
+
56static int user_allow_other = 0;
+
57static int mount_max = 1000;
+
58
+
59static int auto_unmount = 0;
+
60
+
61#ifdef GETMNTENT_NEEDS_UNESCAPING
+
62// Older versions of musl libc don't unescape entries in /etc/mtab
+
63
+
64// unescapes octal sequences like \040 in-place
+
65// That's ok, because unescaping can not extend the length of the string.
+
66static void unescape(char *buf) {
+
67 char *src = buf;
+
68 char *dest = buf;
+
69 while (1) {
+
70 char *next_src = strchrnul(src, '\\');
+
71 int offset = next_src - src;
+
72 memmove(dest, src, offset);
+
73 src = next_src;
+
74 dest += offset;
+
75
+
76 if(*src == '\0') {
+
77 *dest = *src;
+
78 return;
+
79 }
+
80 src++;
+
81
+
82 if('0' <= src[0] && src[0] < '2' &&
+
83 '0' <= src[1] && src[1] < '8' &&
+
84 '0' <= src[2] && src[2] < '8') {
+
85 *dest++ = (src[0] - '0') << 6
+
86 | (src[1] - '0') << 3
+
87 | (src[2] - '0') << 0;
+
88 src += 3;
+
89 } else if (src[0] == '\\') {
+
90 *dest++ = '\\';
+
91 src += 1;
+
92 } else {
+
93 *dest++ = '\\';
+
94 }
+
95 }
+
96}
+
97
+
98static struct mntent *GETMNTENT(FILE *stream)
+
99{
+
100 struct mntent *entp = getmntent(stream);
+
101 if(entp != NULL) {
+
102 unescape(entp->mnt_fsname);
+
103 unescape(entp->mnt_dir);
+
104 unescape(entp->mnt_type);
+
105 unescape(entp->mnt_opts);
+
106 }
+
107 return entp;
+
108}
+
109#else
+
110#define GETMNTENT getmntent
+
111#endif // GETMNTENT_NEEDS_UNESCAPING
+
112
+
113/*
+
114 * Take a ',' separated option string and extract "x-" options
+
115 */
+
116static int extract_x_options(const char *original, char **non_x_opts,
+
117 char **x_opts)
+
118{
+
119 size_t orig_len;
+
120 const char *opt, *opt_end;
+
121
+
122 orig_len = strlen(original) + 1;
+
123
+
124 *non_x_opts = calloc(1, orig_len);
+
125 *x_opts = calloc(1, orig_len);
+
126
+
127 size_t non_x_opts_len = orig_len;
+
128 size_t x_opts_len = orig_len;
+
129
+
130 if (*non_x_opts == NULL || *x_opts == NULL) {
+
131 fprintf(stderr, "%s: Failed to allocate %zuB.\n",
+
132 __func__, orig_len);
+
133 return -ENOMEM;
+
134 }
+
135
+
136 for (opt = original; opt < original + orig_len; opt = opt_end + 1) {
+
137 char *opt_buf;
+
138
+
139 opt_end = strchr(opt, ',');
+
140 if (opt_end == NULL)
+
141 opt_end = original + orig_len;
+
142
+
143 size_t opt_len = opt_end - opt;
+
144 size_t opt_len_left = orig_len - (opt - original);
+
145 size_t buf_len;
+
146 bool is_x_opts;
+
147
+
148 if (strncmp(opt, "x-", MIN(2, opt_len_left)) == 0) {
+
149 buf_len = x_opts_len;
+
150 is_x_opts = true;
+
151 opt_buf = *x_opts;
+
152 } else {
+
153 buf_len = non_x_opts_len;
+
154 is_x_opts = false;
+
155 opt_buf = *non_x_opts;
+
156 }
+
157
+
158 if (buf_len < orig_len) {
+
159 strncat(opt_buf, ",", 2);
+
160 buf_len -= 1;
+
161 }
+
162
+
163 /* omits ',' */
+
164 if ((ssize_t)(buf_len - opt_len) < 0) {
+
165 /* This would be a bug */
+
166 fprintf(stderr, "%s: no buf space left in copy, orig='%s'\n",
+
167 __func__, original);
+
168 return -EIO;
+
169 }
+
170
+
171 strncat(opt_buf, opt, opt_end - opt);
+
172 buf_len -= opt_len;
+
173
+
174 if (is_x_opts)
+
175 x_opts_len = buf_len;
+
176 else
+
177 non_x_opts_len = buf_len;
+
178 }
+
179
+
180 return 0;
+
181}
+
182
+
183static const char *get_user_name(void)
+
184{
+
185 struct passwd *pw = getpwuid(getuid());
+
186 if (pw != NULL && pw->pw_name != NULL)
+
187 return pw->pw_name;
+
188 else {
+
189 fprintf(stderr, "%s: could not determine username\n", progname);
+
190 return NULL;
+
191 }
+
192}
+
193
+
194static uid_t oldfsuid;
+
195static gid_t oldfsgid;
+
196
+
197static void drop_privs(void)
+
198{
+
199 if (getuid() != 0) {
+
200 oldfsuid = setfsuid(getuid());
+
201 oldfsgid = setfsgid(getgid());
+
202 }
+
203}
+
204
+
205static void restore_privs(void)
+
206{
+
207 if (getuid() != 0) {
+
208 setfsuid(oldfsuid);
+
209 setfsgid(oldfsgid);
+
210 }
+
211}
+
212
+
213#ifndef IGNORE_MTAB
+
214/*
+
215 * Make sure that /etc/mtab is checked and updated atomically
+
216 */
+
217static int lock_umount(void)
+
218{
+
219 const char *mtab_lock = _PATH_MOUNTED ".fuselock";
+
220 int mtablock;
+
221 int res;
+
222 struct stat mtab_stat;
+
223
+
224 /* /etc/mtab could be a symlink to /proc/mounts */
+
225 if (lstat(_PATH_MOUNTED, &mtab_stat) == 0 && S_ISLNK(mtab_stat.st_mode))
+
226 return -1;
+
227
+
228 mtablock = open(mtab_lock, O_RDWR | O_CREAT, 0600);
+
229 if (mtablock == -1) {
+
230 fprintf(stderr, "%s: unable to open fuse lock file: %s\n",
+
231 progname, strerror(errno));
+
232 return -1;
+
233 }
+
234 res = lockf(mtablock, F_LOCK, 0);
+
235 if (res < 0) {
+
236 fprintf(stderr, "%s: error getting lock: %s\n", progname,
+
237 strerror(errno));
+
238 close(mtablock);
+
239 return -1;
+
240 }
+
241
+
242 return mtablock;
+
243}
+
244
+
245static void unlock_umount(int mtablock)
+
246{
+
247 if (mtablock >= 0) {
+
248 int res;
+
249
+
250 res = lockf(mtablock, F_ULOCK, 0);
+
251 if (res < 0) {
+
252 fprintf(stderr, "%s: error releasing lock: %s\n",
+
253 progname, strerror(errno));
+
254 }
+
255 close(mtablock);
+
256 }
+
257}
+
258
+
259static int add_mount(const char *source, const char *mnt, const char *type,
+
260 const char *opts)
+
261{
+
262 return fuse_mnt_add_mount(progname, source, mnt, type, opts);
+
263}
+
264
+
265static int may_unmount(const char *mnt, int quiet)
+
266{
+
267 struct mntent *entp;
+
268 FILE *fp;
+
269 const char *user = NULL;
+
270 char uidstr[32];
+
271 unsigned uidlen = 0;
+
272 int found;
+
273 const char *mtab = _PATH_MOUNTED;
+
274
+
275 user = get_user_name();
+
276 if (user == NULL)
+
277 return -1;
+
278
+
279 fp = setmntent(mtab, "r");
+
280 if (fp == NULL) {
+
281 fprintf(stderr, "%s: failed to open %s: %s\n", progname, mtab,
+
282 strerror(errno));
+
283 return -1;
+
284 }
+
285
+
286 uidlen = sprintf(uidstr, "%u", getuid());
+
287
+
288 found = 0;
+
289 while ((entp = GETMNTENT(fp)) != NULL) {
+
290 if (!found && strcmp(entp->mnt_dir, mnt) == 0 &&
+
291 (strcmp(entp->mnt_type, "fuse") == 0 ||
+
292 strcmp(entp->mnt_type, "fuseblk") == 0 ||
+
293 strncmp(entp->mnt_type, "fuse.", 5) == 0 ||
+
294 strncmp(entp->mnt_type, "fuseblk.", 8) == 0)) {
+
295 char *p = strstr(entp->mnt_opts, "user=");
+
296 if (p &&
+
297 (p == entp->mnt_opts || *(p-1) == ',') &&
+
298 strcmp(p + 5, user) == 0) {
+
299 found = 1;
+
300 break;
+
301 }
+
302 /* /etc/mtab is a link pointing to
+
303 /proc/mounts: */
+
304 else if ((p =
+
305 strstr(entp->mnt_opts, "user_id=")) &&
+
306 (p == entp->mnt_opts ||
+
307 *(p-1) == ',') &&
+
308 strncmp(p + 8, uidstr, uidlen) == 0 &&
+
309 (*(p+8+uidlen) == ',' ||
+
310 *(p+8+uidlen) == '\0')) {
+
311 found = 1;
+
312 break;
+
313 }
+
314 }
+
315 }
+
316 endmntent(fp);
+
317
+
318 if (!found) {
+
319 if (!quiet)
+
320 fprintf(stderr,
+
321 "%s: entry for %s not found in %s\n",
+
322 progname, mnt, mtab);
+
323 return -1;
+
324 }
+
325
+
326 return 0;
+
327}
+
328#endif
+
329
+
330/*
+
331 * Check whether the file specified in "fusermount3 -u" is really a
+
332 * mountpoint and not a symlink. This is necessary otherwise the user
+
333 * could move the mountpoint away and replace it with a symlink
+
334 * pointing to an arbitrary mount, thereby tricking fusermount3 into
+
335 * unmounting that (umount(2) will follow symlinks).
+
336 *
+
337 * This is the child process running in a separate mount namespace, so
+
338 * we don't mess with the global namespace and if the process is
+
339 * killed for any reason, mounts are automatically cleaned up.
+
340 *
+
341 * First make sure nothing is propagated back into the parent
+
342 * namespace by marking all mounts "private".
+
343 *
+
344 * Then bind mount parent onto a stable base where the user can't move
+
345 * it around.
+
346 *
+
347 * Finally check /proc/mounts for an entry matching the requested
+
348 * mountpoint. If it's found then we are OK, and the user can't move
+
349 * it around within the parent directory as rename() will return
+
350 * EBUSY. Be careful to ignore any mounts that existed before the
+
351 * bind.
+
352 */
+
353static int check_is_mount_child(void *p)
+
354{
+
355 const char **a = p;
+
356 const char *last = a[0];
+
357 const char *mnt = a[1];
+
358 const char *type = a[2];
+
359 int res;
+
360 const char *procmounts = "/proc/mounts";
+
361 int found;
+
362 FILE *fp;
+
363 struct mntent *entp;
+
364 int count;
+
365
+
366 res = mount("", "/", "", MS_PRIVATE | MS_REC, NULL);
+
367 if (res == -1) {
+
368 fprintf(stderr, "%s: failed to mark mounts private: %s\n",
+
369 progname, strerror(errno));
+
370 return 1;
+
371 }
+
372
+
373 fp = setmntent(procmounts, "r");
+
374 if (fp == NULL) {
+
375 fprintf(stderr, "%s: failed to open %s: %s\n", progname,
+
376 procmounts, strerror(errno));
+
377 return 1;
+
378 }
+
379
+
380 count = 0;
+
381 while (GETMNTENT(fp) != NULL)
+
382 count++;
+
383 endmntent(fp);
+
384
+
385 fp = setmntent(procmounts, "r");
+
386 if (fp == NULL) {
+
387 fprintf(stderr, "%s: failed to open %s: %s\n", progname,
+
388 procmounts, strerror(errno));
+
389 return 1;
+
390 }
+
391
+
392 res = mount(".", "/", "", MS_BIND | MS_REC, NULL);
+
393 if (res == -1) {
+
394 fprintf(stderr, "%s: failed to bind parent to /: %s\n",
+
395 progname, strerror(errno));
+
396 return 1;
+
397 }
+
398
+
399 found = 0;
+
400 while ((entp = GETMNTENT(fp)) != NULL) {
+
401 if (count > 0) {
+
402 count--;
+
403 continue;
+
404 }
+
405 if (entp->mnt_dir[0] == '/' &&
+
406 strcmp(entp->mnt_dir + 1, last) == 0 &&
+
407 (!type || strcmp(entp->mnt_type, type) == 0)) {
+
408 found = 1;
+
409 break;
+
410 }
+
411 }
+
412 endmntent(fp);
+
413
+
414 if (!found) {
+
415 fprintf(stderr, "%s: %s not mounted\n", progname, mnt);
+
416 return 1;
+
417 }
+
418
+
419 return 0;
+
420}
+
421
+
422static pid_t clone_newns(void *a)
+
423{
+
424 char buf[131072];
+
425 char *stack = buf + (sizeof(buf) / 2 - ((size_t) buf & 15));
+
426
+
427#ifdef __ia64__
+
428 extern int __clone2(int (*fn)(void *),
+
429 void *child_stack_base, size_t stack_size,
+
430 int flags, void *arg, pid_t *ptid,
+
431 void *tls, pid_t *ctid);
+
432
+
433 return __clone2(check_is_mount_child, stack, sizeof(buf) / 2,
+
434 CLONE_NEWNS, a, NULL, NULL, NULL);
+
435#else
+
436 return clone(check_is_mount_child, stack, CLONE_NEWNS, a);
+
437#endif
+
438}
+
439
+
440static int check_is_mount(const char *last, const char *mnt, const char *type)
+
441{
+
442 pid_t pid, p;
+
443 int status;
+
444 const char *a[3] = { last, mnt, type };
+
445
+
446 pid = clone_newns((void *) a);
+
447 if (pid == (pid_t) -1) {
+
448 fprintf(stderr, "%s: failed to clone namespace: %s\n",
+
449 progname, strerror(errno));
+
450 return -1;
+
451 }
+
452 p = waitpid(pid, &status, __WCLONE);
+
453 if (p == (pid_t) -1) {
+
454 fprintf(stderr, "%s: waitpid failed: %s\n",
+
455 progname, strerror(errno));
+
456 return -1;
+
457 }
+
458 if (!WIFEXITED(status)) {
+
459 fprintf(stderr, "%s: child terminated abnormally (status %i)\n",
+
460 progname, status);
+
461 return -1;
+
462 }
+
463 if (WEXITSTATUS(status) != 0)
+
464 return -1;
+
465
+
466 return 0;
+
467}
+
468
+
469static int chdir_to_parent(char *copy, const char **lastp)
+
470{
+
471 char *tmp;
+
472 const char *parent;
+
473 char buf[65536];
+
474 int res;
+
475
+
476 tmp = strrchr(copy, '/');
+
477 if (tmp == NULL || tmp[1] == '\0') {
+
478 fprintf(stderr, "%s: internal error: invalid abs path: <%s>\n",
+
479 progname, copy);
+
480 return -1;
+
481 }
+
482 if (tmp != copy) {
+
483 *tmp = '\0';
+
484 parent = copy;
+
485 *lastp = tmp + 1;
+
486 } else if (tmp[1] != '\0') {
+
487 *lastp = tmp + 1;
+
488 parent = "/";
+
489 } else {
+
490 *lastp = ".";
+
491 parent = "/";
+
492 }
+
493
+
494 res = chdir(parent);
+
495 if (res == -1) {
+
496 fprintf(stderr, "%s: failed to chdir to %s: %s\n",
+
497 progname, parent, strerror(errno));
+
498 return -1;
+
499 }
+
500
+
501 if (getcwd(buf, sizeof(buf)) == NULL) {
+
502 fprintf(stderr, "%s: failed to obtain current directory: %s\n",
+
503 progname, strerror(errno));
+
504 return -1;
+
505 }
+
506 if (strcmp(buf, parent) != 0) {
+
507 fprintf(stderr, "%s: mountpoint moved (%s -> %s)\n", progname,
+
508 parent, buf);
+
509 return -1;
+
510
+
511 }
+
512
+
513 return 0;
+
514}
+
515
+
516#ifndef IGNORE_MTAB
+
517static int unmount_fuse_locked(const char *mnt, int quiet, int lazy)
+
518{
+
519 int res;
+
520 char *copy;
+
521 const char *last;
+
522 int umount_flags = (lazy ? UMOUNT_DETACH : 0) | UMOUNT_NOFOLLOW;
+
523
+
524 if (getuid() != 0) {
+
525 res = may_unmount(mnt, quiet);
+
526 if (res == -1)
+
527 return -1;
+
528 }
+
529
+
530 copy = strdup(mnt);
+
531 if (copy == NULL) {
+
532 fprintf(stderr, "%s: failed to allocate memory\n", progname);
+
533 return -1;
+
534 }
+
535
+
536 drop_privs();
+
537 res = chdir_to_parent(copy, &last);
+
538 if (res == -1) {
+
539 restore_privs();
+
540 goto out;
+
541 }
+
542
+
543 res = umount2(last, umount_flags);
+
544 restore_privs();
+
545 if (res == -1 && !quiet) {
+
546 fprintf(stderr, "%s: failed to unmount %s: %s\n",
+
547 progname, mnt, strerror(errno));
+
548 }
+
549
+
550out:
+
551 free(copy);
+
552 if (res == -1)
+
553 return -1;
+
554
+
555 res = chdir("/");
+
556 if (res == -1) {
+
557 fprintf(stderr, "%s: failed to chdir to '/'\n", progname);
+
558 return -1;
+
559 }
+
560
+
561 return fuse_mnt_remove_mount(progname, mnt);
+
562}
+
563
+
564static int unmount_fuse(const char *mnt, int quiet, int lazy)
+
565{
+
566 int res;
+
567 int mtablock = lock_umount();
+
568
+
569 res = unmount_fuse_locked(mnt, quiet, lazy);
+
570 unlock_umount(mtablock);
+
571
+
572 return res;
+
573}
+
574
+
575static int count_fuse_fs_mtab(void)
+
576{
+
577 struct mntent *entp;
+
578 int count = 0;
+
579 const char *mtab = _PATH_MOUNTED;
+
580 FILE *fp = setmntent(mtab, "r");
+
581 if (fp == NULL) {
+
582 fprintf(stderr, "%s: failed to open %s: %s\n", progname, mtab,
+
583 strerror(errno));
+
584 return -1;
+
585 }
+
586 while ((entp = GETMNTENT(fp)) != NULL) {
+
587 if (strcmp(entp->mnt_type, "fuse") == 0 ||
+
588 strncmp(entp->mnt_type, "fuse.", 5) == 0)
+
589 count ++;
+
590 }
+
591 endmntent(fp);
+
592 return count;
+
593}
+
594
+
595#ifdef HAVE_LISTMOUNT
+
596static int count_fuse_fs_ls_mnt(void)
+
597{
+
598 #define SMBUF_SIZE 1024
+
599 #define MNT_ID_LEN 128
+
600
+
601 int fuse_count = 0;
+
602 int n_mounts = 0;
+
603 int ret = 0;
+
604 uint64_t mnt_ids[MNT_ID_LEN];
+
605 unsigned char smbuf[SMBUF_SIZE];
+
606 struct mnt_id_req req = {
+
607 .size = sizeof(struct mnt_id_req),
+
608 };
+
609 struct statmount *sm;
+
610
+
611 for (;;) {
+
612 req.mnt_id = LSMT_ROOT;
+
613
+
614 n_mounts = syscall(SYS_listmount, &req, &mnt_ids, MNT_ID_LEN, 0);
+
615 if (n_mounts == -1) {
+
616 if (errno != ENOSYS) {
+
617 fprintf(stderr, "%s: failed to list mounts: %s\n", progname,
+
618 strerror(errno));
+
619 }
+
620 return -1;
+
621 }
+
622
+
623 for (int i = 0; i < n_mounts; i++) {
+
624 req.mnt_id = mnt_ids[i];
+
625 req.param = STATMOUNT_FS_TYPE;
+
626 ret = syscall(SYS_statmount, &req, &smbuf, SMBUF_SIZE, 0);
+
627 if (ret) {
+
628 if (errno == ENOENT)
+
629 continue;
+
630
+
631 fprintf(stderr, "%s: failed to stat mount %lld: %s\n", progname,
+
632 req.mnt_id, strerror(errno));
+
633 return -1;
+
634 }
+
635
+
636 sm = (struct statmount *)smbuf;
+
637 if (sm->mask & STATMOUNT_FS_TYPE &&
+
638 strcmp(&sm->str[sm->fs_type], "fuse") == 0)
+
639 fuse_count++;
+
640 }
+
641
+
642 if (n_mounts < MNT_ID_LEN)
+
643 break;
+
644 req.param = mnt_ids[MNT_ID_LEN - 1];
+
645 }
+
646 return fuse_count;
+
647}
+
648
+
649static int count_fuse_fs(void)
+
650{
+
651 int count = count_fuse_fs_ls_mnt();
+
652
+
653 return count >= 0 ? count : count_fuse_fs_mtab();
+
654}
+
655#else
+
656static int count_fuse_fs(void)
+
657{
+
658 return count_fuse_fs_mtab();
+
659}
+
660#endif
+
661
+
662#else /* IGNORE_MTAB */
+
663static int count_fuse_fs(void)
+
664{
+
665 return 0;
+
666}
+
667
+
668static int add_mount(const char *source, const char *mnt, const char *type,
+
669 const char *opts)
+
670{
+
671 (void) source;
+
672 (void) mnt;
+
673 (void) type;
+
674 (void) opts;
+
675 return 0;
+
676}
+
677
+
678static int unmount_fuse(const char *mnt, int quiet, int lazy)
+
679{
+
680 (void) quiet;
+
681 return fuse_mnt_umount(progname, mnt, mnt, lazy);
+
682}
+
683#endif /* IGNORE_MTAB */
+
684
+
685static void strip_line(char *line)
+
686{
+
687 char *s = strchr(line, '#');
+
688 size_t len;
+
689
+
690 if (s != NULL)
+
691 s[0] = '\0';
+
692 /*
+
693 * Count down rather than walk a pointer back: an empty or all-blank
+
694 * line would form line - 1, which is undefined behaviour even though
+
695 * the store that follows lands inside the buffer.
+
696 */
+
697 len = strlen(line);
+
698 while (len > 0 && isspace((unsigned char) line[len - 1]))
+
699 len--;
+
700 line[len] = '\0';
+
701 for (s = line; isspace((unsigned char) *s); s++);
+
702 if (s != line)
+
703 memmove(line, s, strlen(s)+1);
+
704}
+
705
+
706static void parse_line(char *line, int linenum)
+
707{
+
708 int tmp;
+
709 if (strcmp(line, "user_allow_other") == 0)
+
710 user_allow_other = 1;
+
711 else if (sscanf(line, "mount_max = %i", &tmp) == 1) {
+
712 if (tmp < -1)
+
713 fprintf(stderr,
+
714 "%s: invalid mount_max = %i in %s at line %i\n",
+
715 progname, tmp, FUSE_CONF, linenum);
+
716 else
+
717 mount_max = tmp;
+
718 }
+
719 else if(line[0])
+
720 fprintf(stderr,
+
721 "%s: unknown parameter in %s at line %i: '%s'\n",
+
722 progname, FUSE_CONF, linenum, line);
+
723}
+
724
+
725static void read_conf(void)
+
726{
+
727 FILE *fp = fopen(FUSE_CONF, "r");
+
728 if (fp != NULL) {
+
729 int linenum = 1;
+
730 char line[256];
+
731 int isnewline = 1;
+
732 while (fgets(line, sizeof(line), fp) != NULL) {
+
733 if (isnewline) {
+
734 if (line[strlen(line)-1] == '\n') {
+
735 strip_line(line);
+
736 parse_line(line, linenum);
+
737 } else {
+
738 isnewline = 0;
+
739 }
+
740 } else if(line[strlen(line)-1] == '\n') {
+
741 fprintf(stderr, "%s: reading %s: line %i too long\n", progname, FUSE_CONF, linenum);
+
742
+
743 isnewline = 1;
+
744 }
+
745 if (isnewline)
+
746 linenum ++;
+
747 }
+
748 if (!isnewline) {
+
749 fprintf(stderr, "%s: reading %s: missing newline at end of file\n", progname, FUSE_CONF);
+
750
+
751 }
+
752 if (ferror(fp)) {
+
753 fprintf(stderr, "%s: reading %s: read failed\n", progname, FUSE_CONF);
+
754 exit(1);
+
755 }
+
756 fclose(fp);
+
757 } else if (errno != ENOENT) {
+
758 bool fatal = (errno != EACCES && errno != ELOOP &&
+
759 errno != ENAMETOOLONG && errno != ENOTDIR &&
+
760 errno != EOVERFLOW);
+
761 fprintf(stderr, "%s: failed to open %s: %s\n",
+
762 progname, FUSE_CONF, strerror(errno));
+
763 if (fatal)
+
764 exit(1);
+
765 }
+
766}
+
767
+
768static int begins_with(const char *s, const char *beg)
+
769{
+
770 if (strncmp(s, beg, strlen(beg)) == 0)
+
771 return 1;
+
772 else
+
773 return 0;
+
774}
+
775
+
776struct mount_flags {
+
777 const char *opt;
+
778 unsigned long flag;
+
779 int on;
+
780 int safe;
+
781};
+
782
+
783static struct mount_flags mount_flags[] = {
+
784 {"rw", MS_RDONLY, 0, 1},
+
785 {"ro", MS_RDONLY, 1, 1},
+
786 {"suid", MS_NOSUID, 0, 0},
+
787 {"nosuid", MS_NOSUID, 1, 1},
+
788 {"dev", MS_NODEV, 0, 0},
+
789 {"nodev", MS_NODEV, 1, 1},
+
790 {"exec", MS_NOEXEC, 0, 1},
+
791 {"noexec", MS_NOEXEC, 1, 1},
+
792 {"async", MS_SYNCHRONOUS, 0, 1},
+
793 {"sync", MS_SYNCHRONOUS, 1, 1},
+
794 {"atime", MS_NOATIME, 0, 1},
+
795 {"noatime", MS_NOATIME, 1, 1},
+
796 {"diratime", MS_NODIRATIME, 0, 1},
+
797 {"nodiratime", MS_NODIRATIME, 1, 1},
+
798 {"lazytime", MS_LAZYTIME, 1, 1},
+
799 {"nolazytime", MS_LAZYTIME, 0, 1},
+
800 {"relatime", MS_RELATIME, 1, 1},
+
801 {"norelatime", MS_RELATIME, 0, 1},
+
802 {"strictatime", MS_STRICTATIME, 1, 1},
+
803 {"nostrictatime", MS_STRICTATIME, 0, 1},
+
804 {"dirsync", MS_DIRSYNC, 1, 1},
+
805 {"symfollow", MS_NOSYMFOLLOW, 0, 1},
+
806 {"nosymfollow", MS_NOSYMFOLLOW, 1, 1},
+
807 {NULL, 0, 0, 0}
+
808};
+
809
+
810static int find_mount_flag(const char *s, unsigned len, int *on, int *flag)
+
811{
+
812 int i;
+
813
+
814 for (i = 0; mount_flags[i].opt != NULL; i++) {
+
815 const char *opt = mount_flags[i].opt;
+
816 if (strlen(opt) == len && strncmp(opt, s, len) == 0) {
+
817 *on = mount_flags[i].on;
+
818 *flag = mount_flags[i].flag;
+
819 if (!mount_flags[i].safe && getuid() != 0) {
+
820 *flag = 0;
+
821 fprintf(stderr,
+
822 "%s: unsafe option %s ignored\n",
+
823 progname, opt);
+
824 }
+
825 return 1;
+
826 }
+
827 }
+
828 return 0;
+
829}
+
830
+
831static int add_option(char **optsp, const char *opt, unsigned expand)
+
832{
+
833 char *newopts;
+
834 if (*optsp == NULL)
+
835 newopts = strdup(opt);
+
836 else {
+
837 unsigned oldsize = strlen(*optsp);
+
838 unsigned newsize = oldsize + 1 + strlen(opt) + expand + 1;
+
839 newopts = (char *) realloc(*optsp, newsize);
+
840 if (newopts)
+
841 sprintf(newopts + oldsize, ",%s", opt);
+
842 }
+
843 if (newopts == NULL) {
+
844 fprintf(stderr, "%s: failed to allocate memory\n", progname);
+
845 return -1;
+
846 }
+
847 *optsp = newopts;
+
848 return 0;
+
849}
+
850
+
851static int get_mnt_opts(int flags, char *opts, char **mnt_optsp)
+
852{
+
853 int i;
+
854 int l;
+
855
+
856 if (!(flags & MS_RDONLY) && add_option(mnt_optsp, "rw", 0) == -1)
+
857 return -1;
+
858
+
859 for (i = 0; mount_flags[i].opt != NULL; i++) {
+
860 if (mount_flags[i].on && (flags & mount_flags[i].flag) &&
+
861 add_option(mnt_optsp, mount_flags[i].opt, 0) == -1)
+
862 return -1;
+
863 }
+
864
+
865 if (add_option(mnt_optsp, opts, 0) == -1)
+
866 return -1;
+
867 /* remove comma from end of opts*/
+
868 l = strlen(*mnt_optsp);
+
869 if (l > 0 && (*mnt_optsp)[l-1] == ',')
+
870 (*mnt_optsp)[l-1] = '\0';
+
871 if (getuid() != 0) {
+
872 const char *user = get_user_name();
+
873 if (user == NULL)
+
874 return -1;
+
875
+
876 if (add_option(mnt_optsp, "user=", strlen(user)) == -1)
+
877 return -1;
+
878 strcat(*mnt_optsp, user);
+
879 }
+
880 return 0;
+
881}
+
882
+
883static int opt_eq(const char *s, unsigned len, const char *opt)
+
884{
+
885 if(strlen(opt) == len && strncmp(s, opt, len) == 0)
+
886 return 1;
+
887 else
+
888 return 0;
+
889}
+
890
+
891static int get_string_opt(const char *s, unsigned len, const char *opt,
+
892 char **val)
+
893{
+
894 int i;
+
895 unsigned opt_len = strlen(opt);
+
896 char *d;
+
897
+
898 if (*val)
+
899 free(*val);
+
900 *val = (char *) malloc(len - opt_len + 1);
+
901 if (!*val) {
+
902 fprintf(stderr, "%s: failed to allocate memory\n", progname);
+
903 return 0;
+
904 }
+
905
+
906 d = *val;
+
907 s += opt_len;
+
908 len -= opt_len;
+
909 for (i = 0; i < len; i++) {
+
910 if (s[i] == '\\' && i + 1 < len)
+
911 i++;
+
912 *d++ = s[i];
+
913 }
+
914 *d = '\0';
+
915 return 1;
+
916}
+
917
+
918/* The kernel silently truncates the "data" argument to PAGE_SIZE-1 characters.
+
919 * This can be dangerous if it e.g. truncates the option "group_id=1000" to
+
920 * "group_id=1".
+
921 * This wrapper detects this case and bails out with an error.
+
922 */
+
923static int mount_notrunc(const char *source, const char *target,
+
924 const char *filesystemtype, unsigned long mountflags,
+
925 const char *data) {
+
926 if (strlen(data) > sysconf(_SC_PAGESIZE) - 1) {
+
927 fprintf(stderr, "%s: mount options too long\n", progname);
+
928 errno = EINVAL;
+
929 return -1;
+
930 }
+
931 return mount(source, target, filesystemtype, mountflags, data);
+
932}
+
933
+
934
+
935static int do_mount(const char *mnt, const char **typep, mode_t rootmode,
+
936 int fd, const char *opts, const char *dev, char **sourcep,
+
937 char **mnt_optsp)
+
938{
+
939 int res;
+
940 int flags = MS_NOSUID | MS_NODEV;
+
941 char *optbuf;
+
942 char *mnt_opts = NULL;
+
943 const char *s;
+
944 char *d;
+
945 char *fsname = NULL;
+
946 char *subtype = NULL;
+
947 char *source = NULL;
+
948 char *type = NULL;
+
949 int blkdev = 0;
+
950
+
951 optbuf = (char *) malloc(strlen(opts) + 128);
+
952 if (!optbuf) {
+
953 fprintf(stderr, "%s: failed to allocate memory\n", progname);
+
954 return -1;
+
955 }
+
956
+
957 for (s = opts, d = optbuf; *s;) {
+
958 unsigned len;
+
959 const char *fsname_str = "fsname=";
+
960 const char *subtype_str = "subtype=";
+
961 bool escape_ok = begins_with(s, fsname_str) ||
+
962 begins_with(s, subtype_str);
+
963 for (len = 0; s[len]; len++) {
+
964 if (escape_ok && s[len] == '\\' && s[len + 1])
+
965 len++;
+
966 else if (s[len] == ',')
+
967 break;
+
968 }
+
969 if (begins_with(s, fsname_str)) {
+
970 if (!get_string_opt(s, len, fsname_str, &fsname))
+
971 goto err;
+
972 } else if (begins_with(s, subtype_str)) {
+
973 if (!get_string_opt(s, len, subtype_str, &subtype))
+
974 goto err;
+
975 } else if (opt_eq(s, len, "blkdev")) {
+
976 if (getuid() != 0) {
+
977 fprintf(stderr,
+
978 "%s: option blkdev is privileged\n",
+
979 progname);
+
980 goto err;
+
981 }
+
982 blkdev = 1;
+
983 } else if (opt_eq(s, len, "auto_unmount")) {
+
984 auto_unmount = 1;
+
985 } else if (!opt_eq(s, len, "nonempty") &&
+
986 !begins_with(s, "fd=") &&
+
987 !begins_with(s, "rootmode=") &&
+
988 !begins_with(s, "user_id=") &&
+
989 !begins_with(s, "group_id=")) {
+
990 int on;
+
991 int flag;
+
992 int skip_option = 0;
+
993 if (opt_eq(s, len, "large_read")) {
+
994 struct utsname utsname;
+
995 unsigned kmaj, kmin;
+
996 res = uname(&utsname);
+
997 if (res == 0 &&
+
998 sscanf(utsname.release, "%u.%u",
+
999 &kmaj, &kmin) == 2 &&
+
1000 (kmaj > 2 || (kmaj == 2 && kmin > 4))) {
+
1001 fprintf(stderr, "%s: note: 'large_read' mount option is deprecated for %i.%i kernels\n", progname, kmaj, kmin);
+
1002 skip_option = 1;
+
1003 }
+
1004 }
+
1005 if (getuid() != 0 && !user_allow_other &&
+
1006 (opt_eq(s, len, "allow_other") ||
+
1007 opt_eq(s, len, "allow_root"))) {
+
1008 fprintf(stderr, "%s: option %.*s only allowed if 'user_allow_other' is set in %s\n", progname, len, s, FUSE_CONF);
+
1009 goto err;
+
1010 }
+
1011 if (!skip_option) {
+
1012 if (find_mount_flag(s, len, &on, &flag)) {
+
1013 if (on)
+
1014 flags |= flag;
+
1015 else
+
1016 flags &= ~flag;
+
1017 } else if (opt_eq(s, len, "default_permissions") ||
+
1018 opt_eq(s, len, "allow_other") ||
+
1019 begins_with(s, "max_read=") ||
+
1020 begins_with(s, "blksize=")) {
+
1021 memcpy(d, s, len);
+
1022 d += len;
+
1023 *d++ = ',';
+
1024 } else {
+
1025 fprintf(stderr, "%s: unknown option '%.*s'\n", progname, len, s);
+
1026 exit(1);
+
1027 }
+
1028 }
+
1029 }
+
1030 s += len;
+
1031 if (*s)
+
1032 s++;
+
1033 }
+
1034 *d = '\0';
+
1035 res = get_mnt_opts(flags, optbuf, &mnt_opts);
+
1036 if (res == -1)
+
1037 goto err;
+
1038
+
1039 sprintf(d, "fd=%i,rootmode=%o,user_id=%u,group_id=%u",
+
1040 fd, rootmode, getuid(), getgid());
+
1041
+
1042 source = malloc((fsname ? strlen(fsname) : 0) +
+
1043 (subtype ? strlen(subtype) : 0) + strlen(dev) + 32);
+
1044
+
1045 type = malloc((subtype ? strlen(subtype) : 0) + 32);
+
1046 if (!type || !source) {
+
1047 fprintf(stderr, "%s: failed to allocate memory\n", progname);
+
1048 goto err;
+
1049 }
+
1050
+
1051 if (subtype)
+
1052 sprintf(type, "%s.%s", blkdev ? "fuseblk" : "fuse", subtype);
+
1053 else
+
1054 strcpy(type, blkdev ? "fuseblk" : "fuse");
+
1055
+
1056 if (fsname)
+
1057 strcpy(source, fsname);
+
1058 else
+
1059 strcpy(source, subtype ? subtype : dev);
+
1060
+
1061 res = mount_notrunc(source, mnt, type, flags, optbuf);
+
1062 if (res == -1 && errno == ENODEV && subtype) {
+
1063 /* Probably missing subtype support */
+
1064 strcpy(type, blkdev ? "fuseblk" : "fuse");
+
1065 if (fsname) {
+
1066 if (!blkdev)
+
1067 sprintf(source, "%s#%s", subtype, fsname);
+
1068 } else {
+
1069 strcpy(source, type);
+
1070 }
+
1071
+
1072 res = mount_notrunc(source, mnt, type, flags, optbuf);
+
1073 }
+
1074 if (res == -1 && errno == EINVAL) {
+
1075 /* It could be an old version not supporting group_id */
+
1076 sprintf(d, "fd=%i,rootmode=%o,user_id=%u",
+
1077 fd, rootmode, getuid());
+
1078 res = mount_notrunc(source, mnt, type, flags, optbuf);
+
1079 }
+
1080 if (res == -1) {
+
1081 int errno_save = errno;
+
1082 if (blkdev && errno == ENODEV && !fuse_mnt_check_fuseblk())
+
1083 fprintf(stderr, "%s: 'fuseblk' support missing\n",
+
1084 progname);
+
1085 else
+
1086 fprintf(stderr, "%s: mount failed: %s\n", progname,
+
1087 strerror(errno_save));
+
1088 goto err;
+
1089 }
+
1090 *sourcep = source;
+
1091 *typep = type;
+
1092 *mnt_optsp = mnt_opts;
+
1093 free(fsname);
+
1094 free(optbuf);
+
1095
+
1096 return 0;
+
1097
+
1098err:
+
1099 free(fsname);
+
1100 free(subtype);
+
1101 free(source);
+
1102 free(type);
+
1103 free(mnt_opts);
+
1104 free(optbuf);
+
1105 return -1;
+
1106}
+
1107
+
1108/*
+
1109 * Resolve the caller-supplied mountpoint exactly once and hand back a
+
1110 * descriptor for the inode it named. O_NOFOLLOW refuses a symlink outright for
+
1111 * an unprivileged caller; root keeps following them, as it always has.
+
1112 */
+
1113static int pin_mountpoint(const char *mnt, bool is_root, struct stat *stbuf)
+
1114{
+
1115 int open_flags = O_PATH | O_CLOEXEC;
+
1116 int fd;
+
1117
+
1118 if (!is_root)
+
1119 open_flags |= O_NOFOLLOW;
+
1120
+
1121 fd = open(mnt, open_flags);
+
1122 if (fd == -1) {
+
1123 fprintf(stderr, "%s: failed to access mountpoint %s: %s\n",
+
1124 progname, mnt, strerror(errno));
+
1125 return -1;
+
1126 }
+
1127
+
1128 if (fstat(fd, stbuf) == -1) {
+
1129 fprintf(stderr, "%s: failed to access mountpoint %s: %s\n",
+
1130 progname, mnt, strerror(errno));
+
1131 close(fd);
+
1132 return -1;
+
1133 }
+
1134
+
1135 return fd;
+
1136}
+
1137
+
1138static int check_perm(const char **mntp, struct stat *stbuf, int *mountpoint_fd)
+
1139{
+
1140 int res;
+
1141 int fd;
+
1142 const char *mnt = *mntp;
+
1143 const bool is_root = getuid() == 0;
+
1144 struct statfs fs_buf;
+
1145 size_t i;
+
1146
+
1147 fd = pin_mountpoint(mnt, is_root, stbuf);
+
1148 if (fd == -1)
+
1149 return -1;
+
1150
+
1151 /* No permission checking is done for root */
+
1152 if (is_root) {
+
1153 *mountpoint_fd = fd;
+
1154 return 0;
+
1155 }
+
1156
+
1157 if (S_ISDIR(stbuf->st_mode)) {
+
1158 res = fchdir(fd);
+
1159 if (res == -1) {
+
1160 fprintf(stderr,
+
1161 "%s: failed to chdir to mountpoint: %s\n",
+
1162 progname, strerror(errno));
+
1163 goto out_close;
+
1164 }
+
1165 /*
+
1166 * The pinned directory is the CWD, so "." names it without
+
1167 * going through the caller-supplied path a second time.
+
1168 */
+
1169 *mntp = ".";
+
1170
+
1171 if ((stbuf->st_mode & S_ISVTX) && stbuf->st_uid != getuid()) {
+
1172 fprintf(stderr, "%s: mountpoint %s not owned by user\n",
+
1173 progname, mnt);
+
1174 res = -1;
+
1175 goto out_close;
+
1176 }
+
1177
+
1178 res = access(*mntp, W_OK);
+
1179 if (res == -1) {
+
1180 fprintf(stderr, "%s: user has no write access to mountpoint %s\n",
+
1181 progname, mnt);
+
1182 goto out_close;
+
1183 }
+
1184 } else if (S_ISREG(stbuf->st_mode)) {
+
1185 static char procfile[256];
+
1186 int wfd;
+
1187
+
1188 snprintf(procfile, sizeof(procfile), "/proc/self/fd/%i", fd);
+
1189
+
1190 /*
+
1191 * Reopening the pinned inode through its magic link both tests
+
1192 * write access and yields the descriptor mount(2) is pointed
+
1193 * at, so the caller's path is never resolved again.
+
1194 */
+
1195 wfd = open(procfile, O_WRONLY);
+
1196 if (wfd == -1) {
+
1197 fprintf(stderr, "%s: failed to open %s: %s\n",
+
1198 progname, mnt, strerror(errno));
+
1199 res = -1;
+
1200 goto out_close;
+
1201 }
+
1202 close(fd);
+
1203 fd = wfd;
+
1204
+
1205 snprintf(procfile, sizeof(procfile), "/proc/self/fd/%i", fd);
+
1206 *mntp = procfile;
+
1207 } else {
+
1208 fprintf(stderr,
+
1209 "%s: mountpoint %s is not a directory or a regular file\n",
+
1210 progname, mnt);
+
1211 res = -1;
+
1212 goto out_close;
+
1213 }
+
1214
+
1215 /* Do not permit mounting over anything in procfs - it has a couple
+
1216 * places to which we have "write access" without being supposed to be
+
1217 * able to just put anything we want there.
+
1218 * Luckily, without allow_other, we can't get other users to actually
+
1219 * use any fake information we try to put there anyway.
+
1220 * Use a whitelist to be safe. */
+
1221 if (statfs(*mntp, &fs_buf)) {
+
1222 fprintf(stderr, "%s: failed to access mountpoint %s: %s\n",
+
1223 progname, mnt, strerror(errno));
+
1224 res = -1;
+
1225 goto out_close;
+
1226 }
+
1227
+
1228 /* Define permitted filesystems for the mount target. This was
+
1229 * originally the same list as used by the ecryptfs mount helper
+
1230 * (https://bazaar.launchpad.net/~ecryptfs/ecryptfs/trunk/view/head:/src/utils/mount.ecryptfs_private.c#L225)
+
1231 * but got expanded as we found more filesystems that needed to be
+
1232 * overlaid. */
+
1233 typeof(fs_buf.f_type) f_type_whitelist[] = {
+
1234 0x61756673 /* AUFS_SUPER_MAGIC */,
+
1235 0x00000187 /* AUTOFS_SUPER_MAGIC */,
+
1236 0xCA451A4E /* BCACHEFS_STATFS_MAGIC */,
+
1237 0x9123683E /* BTRFS_SUPER_MAGIC */,
+
1238 0x00C36400 /* CEPH_SUPER_MAGIC */,
+
1239 0xFF534D42 /* CIFS_MAGIC_NUMBER */,
+
1240 0x0000F15F /* ECRYPTFS_SUPER_MAGIC */,
+
1241 0X2011BAB0 /* EXFAT_SUPER_MAGIC */,
+
1242 0x0000EF53 /* EXT[234]_SUPER_MAGIC */,
+
1243 0xF2F52010 /* F2FS_SUPER_MAGIC */,
+
1244 0x65735546 /* FUSE_SUPER_MAGIC */,
+
1245 0x01161970 /* GFS2_MAGIC */,
+
1246 0x47504653 /* GPFS_SUPER_MAGIC */,
+
1247 0x0000482b /* HFSPLUS_SUPER_MAGIC */,
+
1248 0x000072B6 /* JFFS2_SUPER_MAGIC */,
+
1249 0x3153464A /* JFS_SUPER_MAGIC */,
+
1250 0x0BD00BD0 /* LL_SUPER_MAGIC */,
+
1251 0X00004D44 /* MSDOS_SUPER_MAGIC */,
+
1252 0x0000564C /* NCP_SUPER_MAGIC */,
+
1253 0x00006969 /* NFS_SUPER_MAGIC */,
+
1254 0x00003434 /* NILFS_SUPER_MAGIC */,
+
1255 0x5346544E /* NTFS_SB_MAGIC */,
+
1256 0x7366746E /* NTFS3_SUPER_MAGIC */,
+
1257 0x5346414f /* OPENAFS_SUPER_MAGIC */,
+
1258 0x794C7630 /* OVERLAYFS_SUPER_MAGIC */,
+
1259 0xAAD7AAEA /* PANFS_SUPER_MAGIC */,
+
1260 0x52654973 /* REISERFS_SUPER_MAGIC */,
+
1261 0xFE534D42 /* SMB2_SUPER_MAGIC */,
+
1262 0x73717368 /* SQUASHFS_MAGIC */,
+
1263 0x01021994 /* TMPFS_MAGIC */,
+
1264 0x24051905 /* UBIFS_SUPER_MAGIC */,
+
1265 0x18031977 /* WEKAFS_SUPER_MAGIC */,
+
1266#if __SIZEOF_LONG__ > 4
+
1267 0x736675005346544e /* UFSD */,
+
1268#endif
+
1269 0x58465342 /* XFS_SB_MAGIC */,
+
1270 0x2FC12FC1 /* ZFS_SUPER_MAGIC */,
+
1271 0x858458f6 /* RAMFS_MAGIC */,
+
1272 };
+
1273 for (i = 0; i < sizeof(f_type_whitelist)/sizeof(f_type_whitelist[0]); i++) {
+
1274 if (f_type_whitelist[i] == fs_buf.f_type) {
+
1275 *mountpoint_fd = fd;
+
1276 return 0;
+
1277 }
+
1278 }
+
1279
+
1280 fprintf(stderr, "%s: mounting over filesystem type %#010lx is forbidden\n",
+
1281 progname, (unsigned long)fs_buf.f_type);
+
1282 res = -1;
+
1283
+
1284out_close:
+
1285 close(fd);
+
1286 return res;
+
1287}
+
1288
+
1289static int open_fuse_device(const char *dev)
+
1290{
+
1291 int fd;
+
1292
+
1293 drop_privs();
+
1294 fd = open(dev, O_RDWR);
+
1295 if (fd == -1) {
+
1296 if (errno == ENODEV || errno == ENOENT)/* check for ENOENT too, for the udev case */
+
1297 fprintf(stderr,
+
1298 "%s: fuse device %s not found. Kernel module not loaded?\n",
+
1299 progname, dev);
+
1300 else
+
1301 fprintf(stderr,
+
1302 "%s: failed to open %s: %s\n", progname, dev, strerror(errno));
+
1303 }
+
1304 restore_privs();
+
1305 return fd;
+
1306}
+
1307
+
1308static int mount_fuse(const char *mnt, const char *opts, const char **type)
+
1309{
+
1310 int res;
+
1311 int fd;
+
1312 const char *dev = getenv(FUSE_KERN_DEVICE_ENV) ?: FUSE_DEV;
+
1313 struct stat stbuf;
+
1314 char *source = NULL;
+
1315 char *mnt_opts = NULL;
+
1316 const char *real_mnt = mnt;
+
1317 int mountpoint_fd = -1;
+
1318 char *do_mount_opts = NULL;
+
1319 char *x_opts = NULL;
+
1320
+
1321 fd = open_fuse_device(dev);
+
1322 if (fd == -1)
+
1323 return -1;
+
1324
+
1325 drop_privs();
+
1326 read_conf();
+
1327
+
1328 if (getuid() != 0 && mount_max != -1) {
+
1329 int mount_count = count_fuse_fs();
+
1330 if (mount_count >= mount_max) {
+
1331 fprintf(stderr, "%s: too many FUSE filesystems mounted; mount_max=N can be set in %s\n", progname, FUSE_CONF);
+
1332 goto fail_close_fd;
+
1333 }
+
1334 }
+
1335
+
1336 // Extract any options starting with "x-"
+
1337 res= extract_x_options(opts, &do_mount_opts, &x_opts);
+
1338 if (res)
+
1339 goto fail_close_fd;
+
1340
+
1341 res = check_perm(&real_mnt, &stbuf, &mountpoint_fd);
+
1342 restore_privs();
+
1343 if (res != -1)
+
1344 res = do_mount(real_mnt, type, stbuf.st_mode & S_IFMT,
+
1345 fd, do_mount_opts, dev, &source, &mnt_opts);
+
1346
+
1347 if (mountpoint_fd != -1)
+
1348 close(mountpoint_fd);
+
1349
+
1350 if (res == -1)
+
1351 goto fail_close_fd;
+
1352
+
1353 res = chdir("/");
+
1354 if (res == -1) {
+
1355 fprintf(stderr, "%s: failed to chdir to '/'\n", progname);
+
1356 goto fail_close_fd;
+
1357 }
+
1358
+
1359 if (geteuid() == 0) {
+
1360 if (x_opts && strlen(x_opts) > 0) {
+
1361 /*
+
1362 * Add back the options starting with "x-" to opts from
+
1363 * do_mount. +2 for ',' and '\0'
+
1364 */
+
1365 size_t mnt_opts_len = strlen(mnt_opts);
+
1366 size_t x_mnt_opts_len = mnt_opts_len+
+
1367 strlen(x_opts) + 2;
+
1368 char *x_mnt_opts = calloc(1, x_mnt_opts_len);
+
1369
+
1370 if (mnt_opts_len) {
+
1371 strcpy(x_mnt_opts, mnt_opts);
+
1372 strncat(x_mnt_opts, ",", 2);
+
1373 }
+
1374
+
1375 strncat(x_mnt_opts, x_opts,
+
1376 x_mnt_opts_len - mnt_opts_len - 2);
+
1377
+
1378 free(mnt_opts);
+
1379 mnt_opts = x_mnt_opts;
+
1380 }
+
1381
+
1382 res = add_mount(source, mnt, *type, mnt_opts);
+
1383 if (res == -1) {
+
1384 /* Can't clean up mount in a non-racy way */
+
1385 goto fail_close_fd;
+
1386 }
+
1387 }
+
1388
+
1389out_free:
+
1390 free(source);
+
1391 free(mnt_opts);
+
1392 free(x_opts);
+
1393 free(do_mount_opts);
+
1394
+
1395 return fd;
+
1396
+
1397fail_close_fd:
+
1398 close(fd);
+
1399 fd = -1;
+
1400 goto out_free;
+
1401}
+
1402
+
1403static int send_fd(int sock_fd, int fd)
+
1404{
+
1405 int retval;
+
1406 struct msghdr msg;
+
1407 struct cmsghdr *p_cmsg;
+
1408 struct iovec vec;
+
1409 size_t cmsgbuf[CMSG_SPACE(sizeof(fd)) / sizeof(size_t)];
+
1410 int *p_fds;
+
1411 char sendchar = 0;
+
1412
+
1413 msg.msg_control = cmsgbuf;
+
1414 msg.msg_controllen = sizeof(cmsgbuf);
+
1415 p_cmsg = CMSG_FIRSTHDR(&msg);
+
1416 p_cmsg->cmsg_level = SOL_SOCKET;
+
1417 p_cmsg->cmsg_type = SCM_RIGHTS;
+
1418 p_cmsg->cmsg_len = CMSG_LEN(sizeof(fd));
+
1419 p_fds = (int *) CMSG_DATA(p_cmsg);
+
1420 *p_fds = fd;
+
1421 msg.msg_controllen = p_cmsg->cmsg_len;
+
1422 msg.msg_name = NULL;
+
1423 msg.msg_namelen = 0;
+
1424 msg.msg_iov = &vec;
+
1425 msg.msg_iovlen = 1;
+
1426 msg.msg_flags = 0;
+
1427 /* "To pass file descriptors or credentials you need to send/read at
+
1428 * least one byte" (man 7 unix) */
+
1429 vec.iov_base = &sendchar;
+
1430 vec.iov_len = sizeof(sendchar);
+
1431 while ((retval = sendmsg(sock_fd, &msg, 0)) == -1 && errno == EINTR);
+
1432 if (retval != 1) {
+
1433 perror("sending file descriptor");
+
1434 return -1;
+
1435 }
+
1436 return 0;
+
1437}
+
1438
+
1439/* Helper for should_auto_unmount
+
1440 *
+
1441 * Try opening `mnt` with uid and gid of the calling process.
+
1442 */
+
1443static int check_ENOTCONN_as_owner(const char *mnt)
+
1444{
+
1445 int pid = fork();
+
1446 if(pid == -1) {
+
1447 perror("fuse: recheck_ENOTCONN_as_owner can't fork");
+
1448 _exit(EXIT_FAILURE);
+
1449 } else if(pid == 0) {
+
1450 uid_t uid = getuid();
+
1451 gid_t gid = getgid();
+
1452 if(setresgid(gid, gid, gid) == -1) {
+
1453 perror("fuse: can't set resgid");
+
1454 _exit(EXIT_FAILURE);
+
1455 }
+
1456 if(setresuid(uid, uid, uid) == -1) {
+
1457 perror("fuse: can't set resuid");
+
1458 _exit(EXIT_FAILURE);
+
1459 }
+
1460
+
1461 int fd = open(mnt, O_RDONLY);
+
1462 if (fd == -1 && (errno == ENOTCONN || errno == ECONNABORTED))
+
1463 _exit(EXIT_SUCCESS);
+
1464 else
+
1465 _exit(EXIT_FAILURE);
+
1466 } else {
+
1467 int status;
+
1468 int res = waitpid(pid, &status, 0);
+
1469 if (res == -1) {
+
1470 perror("fuse: waiting for child failed");
+
1471 _exit(EXIT_FAILURE);
+
1472 }
+
1473 return WIFEXITED(status) && WEXITSTATUS(status) == EXIT_SUCCESS;
+
1474 }
+
1475}
+
1476
+
1477/* The parent fuse process has died: decide whether to auto_unmount.
+
1478 *
+
1479 * In the normal case (umount or fusermount -u), the filesystem
+
1480 * has already been unmounted. If we simply unmount again we can
+
1481 * cause problems with stacked mounts (e.g. autofs).
+
1482 *
+
1483 * So we unmount here only in abnormal case where fuse process has
+
1484 * died without unmount happening. To detect this, we first look in
+
1485 * the mount table to make sure the mountpoint is still mounted and
+
1486 * has proper type. If so, we then see if opening the mount dir is
+
1487 * returning 'Transport endpoint is not connected'.
+
1488 *
+
1489 * The order of these is important, because if autofs is in use,
+
1490 * opening the dir to check for ENOTCONN will cause a new mount
+
1491 * in the normal case where filesystem has been unmounted cleanly.
+
1492 */
+
1493static int should_auto_unmount(const char *mnt, const char *type)
+
1494{
+
1495 char *copy;
+
1496 const char *last;
+
1497 int result = 0;
+
1498
+
1499 copy = strdup(mnt);
+
1500 if (copy == NULL) {
+
1501 fprintf(stderr, "%s: failed to allocate memory\n", progname);
+
1502 return 0;
+
1503 }
+
1504
+
1505 if (chdir_to_parent(copy, &last) == -1)
+
1506 goto out;
+
1507 if (check_is_mount(last, mnt, type) == -1)
+
1508 goto out;
+
1509
+
1510 result = check_ENOTCONN_as_owner(mnt);
+
1511out:
+
1512 free(copy);
+
1513 return result;
+
1514}
+
1515
+
1516static void usage(void)
+
1517{
+
1518 printf("%s: [options] mountpoint\n"
+
1519 "Options:\n"
+
1520 " -h print help\n"
+
1521 " -V print version\n"
+
1522 " -o opt[,opt...] mount options\n"
+
1523 " -u unmount\n"
+
1524 " -q quiet\n"
+
1525 " -z lazy unmount\n",
+
1526 progname);
+
1527 exit(1);
+
1528}
+
1529
+
1530static void show_version(void)
+
1531{
+
1532 printf("fusermount3 version: %s\n", PACKAGE_VERSION);
+
1533 exit(0);
+
1534}
+
1535
+
1536static void close_range_loop(int min_fd, int max_fd, int cfd)
+
1537{
+
1538 for (int fd = min_fd; fd <= max_fd; fd++)
+
1539 if (fd != cfd)
+
1540 close(fd);
+
1541}
+
1542
+
1543/*
+
1544 * Close all inherited fds that are not needed
+
1545 * Ideally these wouldn't come up at all, applications should better
+
1546 * use FD_CLOEXEC / O_CLOEXEC
+
1547 */
+
1548static int close_inherited_fds(int cfd)
+
1549{
+
1550 int rc = -1;
+
1551 int nullfd;
+
1552
+
1553 /* We can't even report an error */
+
1554 if (cfd <= STDERR_FILENO)
+
1555 return -EINVAL;
+
1556
+
1557#ifdef HAVE_CLOSE_RANGE
+
1558 if (cfd < STDERR_FILENO + 2) {
+
1559 close_range_loop(STDERR_FILENO + 1, cfd - 1, cfd);
+
1560 } else {
+
1561 rc = close_range(STDERR_FILENO + 1, cfd - 1, 0);
+
1562 if (rc < 0)
+
1563 goto fallback;
+
1564 }
+
1565
+
1566 /* Close high range */
+
1567 rc = close_range(cfd + 1, ~0U, 0);
+
1568#else
+
1569 goto fallback; /* make use of fallback to avoid compiler warnings */
+
1570#endif
+
1571
+
1572fallback:
+
1573 if (rc < 0) {
+
1574 int max_fd = sysconf(_SC_OPEN_MAX) - 1;
+
1575
+
1576 close_range_loop(STDERR_FILENO + 1, max_fd, cfd);
+
1577 }
+
1578
+
1579 nullfd = open("/dev/null", O_RDWR);
+
1580 if (nullfd < 0) {
+
1581 perror("fusermount: cannot open /dev/null");
+
1582 return -errno;
+
1583 }
+
1584
+
1585 /* Redirect stdin, stdout, stderr to /dev/null */
+
1586 dup2(nullfd, STDIN_FILENO);
+
1587 dup2(nullfd, STDOUT_FILENO);
+
1588 dup2(nullfd, STDERR_FILENO);
+
1589 if (nullfd > STDERR_FILENO)
+
1590 close(nullfd);
+
1591
+
1592 return 0;
+
1593}
+
1594
+
1595int main(int argc, char *argv[])
+
1596{
+
1597 sigset_t sigset;
+
1598 int ch;
+
1599 int fd;
+
1600 int res;
+
1601 char *origmnt;
+
1602 char *mnt;
+
1603 static int unmount = 0;
+
1604 static int lazy = 0;
+
1605 static int quiet = 0;
+
1606 char *commfd = NULL;
+
1607 long cfd;
+
1608 const char *opts = "";
+
1609 const char *type = NULL;
+
1610 int setup_auto_unmount_only = 0;
+
1611
+
1612 static const struct option long_opts[] = {
+
1613 {"unmount", no_argument, NULL, 'u'},
+
1614 {"lazy", no_argument, NULL, 'z'},
+
1615 {"quiet", no_argument, NULL, 'q'},
+
1616 {"help", no_argument, NULL, 'h'},
+
1617 {"version", no_argument, NULL, 'V'},
+
1618 {"options", required_argument, NULL, 'o'},
+
1619 // Note: auto-unmount and comm-fd don't have short versions.
+
1620 // They'ne meant for internal use by mount.c
+
1621 {"auto-unmount", no_argument, NULL, 'U'},
+
1622 {"comm-fd", required_argument, NULL, 'c'},
+
1623 {0, 0, 0, 0}};
+
1624
+
1625 progname = strdup(argc > 0 ? argv[0] : "fusermount");
+
1626 if (progname == NULL) {
+
1627 fprintf(stderr, "%s: failed to allocate memory\n", argv[0]);
+
1628 exit(1);
+
1629 }
+
1630
+
1631 while ((ch = getopt_long(argc, argv, "hVo:uzq", long_opts,
+
1632 NULL)) != -1) {
+
1633 switch (ch) {
+
1634 case 'h':
+
1635 usage();
+
1636 break;
+
1637
+
1638 case 'V':
+
1639 show_version();
+
1640 break;
+
1641
+
1642 case 'o':
+
1643 opts = optarg;
+
1644 break;
+
1645
+
1646 case 'u':
+
1647 unmount = 1;
+
1648 break;
+
1649 case 'U':
+
1650 unmount = 1;
+
1651 auto_unmount = 1;
+
1652 setup_auto_unmount_only = 1;
+
1653 break;
+
1654 case 'c':
+
1655 commfd = optarg;
+
1656 break;
+
1657 case 'z':
+
1658 lazy = 1;
+
1659 break;
+
1660
+
1661 case 'q':
+
1662 quiet = 1;
+
1663 break;
+
1664
+
1665 default:
+
1666 exit(1);
+
1667 }
+
1668 }
+
1669
+
1670 if (lazy && !unmount) {
+
1671 fprintf(stderr, "%s: -z can only be used with -u\n", progname);
+
1672 exit(1);
+
1673 }
+
1674
+
1675 if (optind >= argc) {
+
1676 fprintf(stderr, "%s: missing mountpoint argument\n", progname);
+
1677 exit(1);
+
1678 } else if (argc > optind + 1) {
+
1679 fprintf(stderr, "%s: extra arguments after the mountpoint\n",
+
1680 progname);
+
1681 exit(1);
+
1682 }
+
1683
+
1684 origmnt = argv[optind];
+
1685
+
1686 drop_privs();
+
1687 mnt = fuse_mnt_resolve_path(progname, origmnt);
+
1688 if (mnt != NULL) {
+
1689 res = chdir("/");
+
1690 if (res == -1) {
+
1691 fprintf(stderr, "%s: failed to chdir to '/'\n", progname);
+
1692 goto err_out;
+
1693 }
+
1694 }
+
1695 restore_privs();
+
1696 if (mnt == NULL)
+
1697 exit(1);
+
1698
+
1699 umask(033);
+
1700 if (!setup_auto_unmount_only && unmount)
+
1701 goto do_unmount;
+
1702
+
1703 if(commfd == NULL)
+
1704 commfd = getenv(FUSE_COMMFD_ENV);
+
1705 if (commfd == NULL) {
+
1706 fprintf(stderr, "%s: old style mounting not supported\n",
+
1707 progname);
+
1708 goto err_out;
+
1709 }
+
1710
+
1711 res = libfuse_strtol(commfd, &cfd);
+
1712 if (res) {
+
1713 fprintf(stderr,
+
1714 "%s: invalid _FUSE_COMMFD: %s\n",
+
1715 progname, commfd);
+
1716 goto err_out;
+
1717
+
1718 }
+
1719
+
1720 {
+
1721 struct stat statbuf;
+
1722 if (fstat(cfd, &statbuf) == -1) {
+
1723 fprintf(stderr,
+
1724 "%s: fstat of comm fd %li failed: %s\n",
+
1725 progname, cfd, strerror(errno));
+
1726 goto err_out;
+
1727 }
+
1728 if(!S_ISSOCK(statbuf.st_mode)) {
+
1729 fprintf(stderr,
+
1730 "%s: file descriptor %li is not a socket, can't send fuse fd\n",
+
1731 progname, cfd);
+
1732 goto err_out;
+
1733 }
+
1734 }
+
1735
+
1736 if (setup_auto_unmount_only)
+
1737 goto wait_for_auto_unmount;
+
1738
+
1739 fd = mount_fuse(mnt, opts, &type);
+
1740 if (fd == -1)
+
1741 goto err_out;
+
1742
+
1743 res = send_fd(cfd, fd);
+
1744 if (res != 0) {
+
1745 unmount_fuse(mnt, 1, 1); /* lazy umount */
+
1746 goto err_out;
+
1747 }
+
1748 close(fd);
+
1749
+
1750 if (!auto_unmount) {
+
1751 free(mnt);
+
1752 free((void*) type);
+
1753 return 0;
+
1754 }
+
1755
+
1756wait_for_auto_unmount:
+
1757 /* Become a daemon and wait for the parent to exit or die.
+
1758 ie For the control socket to get closed.
+
1759 Btw, we don't want to use daemon() function here because
+
1760 it forks and messes with the file descriptors. */
+
1761
+
1762 res = close_inherited_fds(cfd);
+
1763 if (res < 0)
+
1764 exit(EXIT_FAILURE);
+
1765
+
1766 setsid();
+
1767 res = chdir("/");
+
1768 if (res == -1) {
+
1769 fprintf(stderr, "%s: failed to chdir to '/'\n", progname);
+
1770 goto err_out;
+
1771 }
+
1772
+
1773 sigfillset(&sigset);
+
1774 sigprocmask(SIG_BLOCK, &sigset, NULL);
+
1775
+
1776 lazy = 1;
+
1777 quiet = 1;
+
1778
+
1779 while (1) {
+
1780 unsigned char buf[16];
+
1781 int n = recv(cfd, buf, sizeof(buf), 0);
+
1782 if (!n)
+
1783 break;
+
1784
+
1785 if (n < 0) {
+
1786 if (errno == EINTR)
+
1787 continue;
+
1788 break;
+
1789 }
+
1790 }
+
1791
+
1792 if (!should_auto_unmount(mnt, type)) {
+
1793 goto success_out;
+
1794 }
+
1795
+
1796do_unmount:
+
1797 if (geteuid() == 0)
+
1798 res = unmount_fuse(mnt, quiet, lazy);
+
1799 else {
+
1800 res = umount2(mnt, (lazy ? UMOUNT_DETACH : 0) | UMOUNT_NOFOLLOW);
+
1801 if (res == -1 && !quiet)
+
1802 fprintf(stderr,
+
1803 "%s: failed to unmount %s: %s\n",
+
1804 progname, mnt, strerror(errno));
+
1805 }
+
1806 if (res == -1)
+
1807 goto err_out;
+
1808
+
1809success_out:
+
1810 free((void*) type);
+
1811 free(mnt);
+
1812 return 0;
+
1813
+
1814err_out:
+
1815 free((void*) type);
+
1816 free(mnt);
+
1817 exit(1);
+
1818}
+
+ + +
+ + diff --git a/doc/html/globals.html b/doc/html/globals.html new file mode 100644 index 0000000..54fa19a --- /dev/null +++ b/doc/html/globals.html @@ -0,0 +1,214 @@ + + + + + + + +libfuse: Globals + + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
+
Here is a list of all documented functions, variables, defines, enums, and typedefs with links to the documentation:
+ +

- f -

+
+ + +
+ + diff --git a/doc/html/globals_defs.html b/doc/html/globals_defs.html new file mode 100644 index 0000000..de6772c --- /dev/null +++ b/doc/html/globals_defs.html @@ -0,0 +1,96 @@ + + + + + + + +libfuse: Globals + + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
+
Here is a list of all documented macros with links to the documentation:
+ +

- f -

+
+ + +
+ + diff --git a/doc/html/globals_enum.html b/doc/html/globals_enum.html new file mode 100644 index 0000000..be676af --- /dev/null +++ b/doc/html/globals_enum.html @@ -0,0 +1,60 @@ + + + + + + + +libfuse: Globals + + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
+
Here is a list of all documented enums with links to the documentation:
+
+ + +
+ + diff --git a/doc/html/globals_eval.html b/doc/html/globals_eval.html new file mode 100644 index 0000000..5ea0f0f --- /dev/null +++ b/doc/html/globals_eval.html @@ -0,0 +1,63 @@ + + + + + + + +libfuse: Globals + + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
+
Here is a list of all documented enum values with links to the documentation:
+
+ + +
+ + diff --git a/doc/html/globals_func.html b/doc/html/globals_func.html new file mode 100644 index 0000000..d6c34d4 --- /dev/null +++ b/doc/html/globals_func.html @@ -0,0 +1,152 @@ + + + + + + + +libfuse: Globals + + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
+
Here is a list of all documented functions with links to the documentation:
+ +

- f -

+
+ + +
+ + diff --git a/doc/html/globals_type.html b/doc/html/globals_type.html new file mode 100644 index 0000000..928e72d --- /dev/null +++ b/doc/html/globals_type.html @@ -0,0 +1,61 @@ + + + + + + + +libfuse: Globals + + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
+
Here is a list of all documented typedefs with links to the documentation:
+
+ + +
+ + diff --git a/doc/html/hello__ll_8c.html b/doc/html/hello__ll_8c.html new file mode 100644 index 0000000..95d1955 --- /dev/null +++ b/doc/html/hello__ll_8c.html @@ -0,0 +1,394 @@ + + + + + + + +libfuse: example/hello_ll.c File Reference + + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
+
hello_ll.c File Reference
+
+
+
#include <fuse_lowlevel.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <assert.h>
+
+

Go to the source code of this file.

+

Detailed Description

+

minimal example filesystem using low-level API

+

Compile with:

gcc -Wall hello_ll.c `pkg-config fuse3 --cflags --libs` -o hello_ll
+

Note: If the pkg-config command fails due to the absence of the fuse3.pc file, you should configure the path to the fuse3.pc file in the PKG_CONFIG_PATH variable.

+

+Source code

+
/*
+
FUSE: Filesystem in Userspace
+
Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
+
This program can be distributed under the terms of the GNU GPLv2.
+
See the file GPL2.txt.
+
*/
+
+
+
#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 12)
+
+
#include <fuse_lowlevel.h>
+
#include <stdio.h>
+
#include <stdlib.h>
+
#include <string.h>
+
#include <errno.h>
+
#include <fcntl.h>
+
#include <unistd.h>
+
#include <assert.h>
+
+
static const char *hello_str = "Hello World!\n";
+
static const char *hello_name = "hello";
+
+
static int hello_stat(fuse_ino_t ino, struct stat *stbuf)
+
{
+
stbuf->st_ino = ino;
+
switch (ino) {
+
case 1:
+
stbuf->st_mode = S_IFDIR | 0755;
+
stbuf->st_nlink = 2;
+
break;
+
+
case 2:
+
stbuf->st_mode = S_IFREG | 0444;
+
stbuf->st_nlink = 1;
+
stbuf->st_size = strlen(hello_str);
+
break;
+
+
default:
+
return -1;
+
}
+
return 0;
+
}
+
+
static void hello_ll_init(void *userdata, struct fuse_conn_info *conn)
+
{
+
(void)userdata;
+
+
/* Disable the receiving and processing of FUSE_INTERRUPT requests */
+
conn->no_interrupt = 1;
+
+
/* Test setting flags the old way */
+ + +
}
+
+
static void hello_ll_getattr(fuse_req_t req, fuse_ino_t ino,
+
struct fuse_file_info *fi)
+
{
+
struct stat stbuf;
+
+
(void) fi;
+
+
memset(&stbuf, 0, sizeof(stbuf));
+
if (hello_stat(ino, &stbuf) == -1)
+
fuse_reply_err(req, ENOENT);
+
else
+
fuse_reply_attr(req, &stbuf, 1.0);
+
}
+
+
static void hello_ll_lookup(fuse_req_t req, fuse_ino_t parent, const char *name)
+
{
+
struct fuse_entry_param e;
+
+
if (parent != 1 || strcmp(name, hello_name) != 0)
+
fuse_reply_err(req, ENOENT);
+
else {
+
memset(&e, 0, sizeof(e));
+
e.ino = 2;
+
e.attr_timeout = 1.0;
+
e.entry_timeout = 1.0;
+
hello_stat(e.ino, &e.attr);
+
+
fuse_reply_entry(req, &e);
+
}
+
}
+
+
struct dirbuf {
+
char *p;
+
size_t size;
+
};
+
+
static void dirbuf_add(fuse_req_t req, struct dirbuf *b, const char *name,
+ +
{
+
struct stat stbuf;
+
size_t oldsize = b->size;
+
b->size += fuse_add_direntry(req, NULL, 0, name, NULL, 0);
+
b->p = (char *) realloc(b->p, b->size);
+
memset(&stbuf, 0, sizeof(stbuf));
+
stbuf.st_ino = ino;
+
fuse_add_direntry(req, b->p + oldsize, b->size - oldsize, name, &stbuf,
+
b->size);
+
}
+
+
#define min(x, y) ((x) < (y) ? (x) : (y))
+
+
static int reply_buf_limited(fuse_req_t req, const char *buf, size_t bufsize,
+
off_t off, size_t maxsize)
+
{
+
if (off < bufsize)
+
return fuse_reply_buf(req, buf + off,
+
min(bufsize - off, maxsize));
+
else
+
return fuse_reply_buf(req, NULL, 0);
+
}
+
+
static void hello_ll_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
+
off_t off, struct fuse_file_info *fi)
+
{
+
(void) fi;
+
+
if (ino != 1)
+
fuse_reply_err(req, ENOTDIR);
+
else {
+
struct dirbuf b;
+
+
memset(&b, 0, sizeof(b));
+
dirbuf_add(req, &b, ".", 1);
+
dirbuf_add(req, &b, "..", 1);
+
dirbuf_add(req, &b, hello_name, 2);
+
reply_buf_limited(req, b.p, b.size, off, size);
+
free(b.p);
+
}
+
}
+
+
static void hello_ll_open(fuse_req_t req, fuse_ino_t ino,
+
struct fuse_file_info *fi)
+
{
+
if (ino != 2)
+
fuse_reply_err(req, EISDIR);
+
else if ((fi->flags & O_ACCMODE) != O_RDONLY)
+
fuse_reply_err(req, EACCES);
+
else
+
fuse_reply_open(req, fi);
+
}
+
+
static void hello_ll_read(fuse_req_t req, fuse_ino_t ino, size_t size,
+
off_t off, struct fuse_file_info *fi)
+
{
+
(void) fi;
+
+
assert(ino == 2);
+
reply_buf_limited(req, hello_str, strlen(hello_str), off, size);
+
}
+
+
static void hello_ll_getxattr(fuse_req_t req, fuse_ino_t ino, const char *name,
+
size_t size)
+
{
+
(void)size;
+
assert(ino == 1 || ino == 2);
+
if (strcmp(name, "hello_ll_getxattr_name") == 0)
+
{
+
const char *buf = "hello_ll_getxattr_value";
+
fuse_reply_buf(req, buf, strlen(buf));
+
}
+
else
+
{
+
fuse_reply_err(req, ENOTSUP);
+
}
+
}
+
+
static void hello_ll_setxattr(fuse_req_t req, fuse_ino_t ino, const char *name,
+
const char *value, size_t size, int flags)
+
{
+
(void)flags;
+
(void)size;
+
assert(ino == 1 || ino == 2);
+
const char* exp_val = "hello_ll_setxattr_value";
+
if (strcmp(name, "hello_ll_setxattr_name") == 0 &&
+
strlen(exp_val) == size &&
+
strncmp(value, exp_val, size) == 0)
+
{
+
fuse_reply_err(req, 0);
+
}
+
else
+
{
+
fuse_reply_err(req, ENOTSUP);
+
}
+
}
+
+
static void hello_ll_removexattr(fuse_req_t req, fuse_ino_t ino, const char *name)
+
{
+
assert(ino == 1 || ino == 2);
+
if (strcmp(name, "hello_ll_removexattr_name") == 0)
+
{
+
fuse_reply_err(req, 0);
+
}
+
else
+
{
+
fuse_reply_err(req, ENOTSUP);
+
}
+
}
+
+
static const struct fuse_lowlevel_ops hello_ll_oper = {
+
.init = hello_ll_init,
+
.lookup = hello_ll_lookup,
+
.getattr = hello_ll_getattr,
+
.readdir = hello_ll_readdir,
+
.open = hello_ll_open,
+
.read = hello_ll_read,
+
.setxattr = hello_ll_setxattr,
+
.getxattr = hello_ll_getxattr,
+
.removexattr = hello_ll_removexattr,
+
};
+
+
int main(int argc, char *argv[])
+
{
+
struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
struct fuse_session *se;
+
struct fuse_cmdline_opts opts;
+
struct fuse_loop_config *config;
+
int ret = -1;
+
+
if (fuse_parse_cmdline(&args, &opts) != 0)
+
return 1;
+
if (opts.show_help) {
+
printf("usage: %s [options] <mountpoint>\n\n", argv[0]);
+ + +
ret = 0;
+
goto err_out1;
+
} else if (opts.show_version) {
+
printf("FUSE library version %s\n", fuse_pkgversion());
+ +
ret = 0;
+
goto err_out1;
+
}
+
+
if(opts.mountpoint == NULL) {
+
printf("usage: %s [options] <mountpoint>\n", argv[0]);
+
printf(" %s --help\n", argv[0]);
+
ret = 1;
+
goto err_out1;
+
}
+
+
se = fuse_session_new(&args, &hello_ll_oper,
+
sizeof(hello_ll_oper), NULL);
+
if (se == NULL)
+
goto err_out1;
+
+ +
goto err_out2;
+
+
if (fuse_session_mount(se, opts.mountpoint) != 0)
+
goto err_out3;
+
+
fuse_daemonize(opts.foreground);
+
+
/* Block until ctrl+c or fusermount -u */
+
if (opts.singlethread)
+
ret = fuse_session_loop(se);
+
else {
+
config = fuse_loop_cfg_create();
+
fuse_loop_cfg_set_clone_fd(config, opts.clone_fd);
+
fuse_loop_cfg_set_max_threads(config, opts.max_threads);
+
ret = fuse_session_loop_mt(se, config);
+
fuse_loop_cfg_destroy(config);
+
config = NULL;
+
}
+
+ +
err_out3:
+ +
err_out2:
+ +
err_out1:
+
free(opts.mountpoint);
+ +
+
return ret ? 1 : 0;
+
}
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
#define FUSE_CAP_ASYNC_READ
+
const char * fuse_pkgversion(void)
Definition fuse.c:5274
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
int fuse_daemonize(int foreground)
Definition helper.c:253
+ +
void fuse_session_destroy(struct fuse_session *se)
+
int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi)
+
int fuse_reply_err(fuse_req_t req, int err)
+
int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
+
struct fuse_req * fuse_req_t
+
int fuse_session_loop(struct fuse_session *se)
Definition fuse_loop.c:19
+
int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
+
void fuse_session_unmount(struct fuse_session *se)
+
void fuse_cmdline_help(void)
Definition helper.c:130
+
void fuse_lowlevel_help(void)
+
int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
+
void fuse_lowlevel_version(void)
+
uint64_t fuse_ino_t
+
size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct stat *stbuf, off_t off)
+
int fuse_reply_attr(fuse_req_t req, const struct stat *attr, double attr_timeout)
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+ + +
char ** argv
Definition fuse_opt.h:114
+ + + +
uint32_t no_interrupt
+
+ + + + +
+

Definition in file hello_ll.c.

+
+ + +
+ + diff --git a/doc/html/hello__ll_8c_source.html b/doc/html/hello__ll_8c_source.html new file mode 100644 index 0000000..2d807a4 --- /dev/null +++ b/doc/html/hello__ll_8c_source.html @@ -0,0 +1,376 @@ + + + + + + + +libfuse: example/hello_ll.c Source File + + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
+
hello_ll.c
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 This program can be distributed under the terms of the GNU GPLv2.
+
6 See the file GPL2.txt.
+
7*/
+
8
+
24
+
25#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 12)
+
26
+
27#include <fuse_lowlevel.h>
+
28#include <stdio.h>
+
29#include <stdlib.h>
+
30#include <string.h>
+
31#include <errno.h>
+
32#include <fcntl.h>
+
33#include <unistd.h>
+
34#include <assert.h>
+
35
+
36static const char *hello_str = "Hello World!\n";
+
37static const char *hello_name = "hello";
+
38
+
39static int hello_stat(fuse_ino_t ino, struct stat *stbuf)
+
40{
+
41 stbuf->st_ino = ino;
+
42 switch (ino) {
+
43 case 1:
+
44 stbuf->st_mode = S_IFDIR | 0755;
+
45 stbuf->st_nlink = 2;
+
46 break;
+
47
+
48 case 2:
+
49 stbuf->st_mode = S_IFREG | 0444;
+
50 stbuf->st_nlink = 1;
+
51 stbuf->st_size = strlen(hello_str);
+
52 break;
+
53
+
54 default:
+
55 return -1;
+
56 }
+
57 return 0;
+
58}
+
59
+
60static void hello_ll_init(void *userdata, struct fuse_conn_info *conn)
+
61{
+
62 (void)userdata;
+
63
+
64 /* Disable the receiving and processing of FUSE_INTERRUPT requests */
+
65 conn->no_interrupt = 1;
+
66
+
67 /* Test setting flags the old way */
+ + +
70}
+
71
+
72static void hello_ll_getattr(fuse_req_t req, fuse_ino_t ino,
+
73 struct fuse_file_info *fi)
+
74{
+
75 struct stat stbuf;
+
76
+
77 (void) fi;
+
78
+
79 memset(&stbuf, 0, sizeof(stbuf));
+
80 if (hello_stat(ino, &stbuf) == -1)
+
81 fuse_reply_err(req, ENOENT);
+
82 else
+
83 fuse_reply_attr(req, &stbuf, 1.0);
+
84}
+
85
+
86static void hello_ll_lookup(fuse_req_t req, fuse_ino_t parent, const char *name)
+
87{
+
88 struct fuse_entry_param e;
+
89
+
90 if (parent != 1 || strcmp(name, hello_name) != 0)
+
91 fuse_reply_err(req, ENOENT);
+
92 else {
+
93 memset(&e, 0, sizeof(e));
+
94 e.ino = 2;
+
95 e.attr_timeout = 1.0;
+
96 e.entry_timeout = 1.0;
+
97 hello_stat(e.ino, &e.attr);
+
98
+
99 fuse_reply_entry(req, &e);
+
100 }
+
101}
+
102
+
103struct dirbuf {
+
104 char *p;
+
105 size_t size;
+
106};
+
107
+
108static void dirbuf_add(fuse_req_t req, struct dirbuf *b, const char *name,
+
109 fuse_ino_t ino)
+
110{
+
111 struct stat stbuf;
+
112 size_t oldsize = b->size;
+
113 b->size += fuse_add_direntry(req, NULL, 0, name, NULL, 0);
+
114 b->p = (char *) realloc(b->p, b->size);
+
115 memset(&stbuf, 0, sizeof(stbuf));
+
116 stbuf.st_ino = ino;
+
117 fuse_add_direntry(req, b->p + oldsize, b->size - oldsize, name, &stbuf,
+
118 b->size);
+
119}
+
120
+
121#define min(x, y) ((x) < (y) ? (x) : (y))
+
122
+
123static int reply_buf_limited(fuse_req_t req, const char *buf, size_t bufsize,
+
124 off_t off, size_t maxsize)
+
125{
+
126 if (off < bufsize)
+
127 return fuse_reply_buf(req, buf + off,
+
128 min(bufsize - off, maxsize));
+
129 else
+
130 return fuse_reply_buf(req, NULL, 0);
+
131}
+
132
+
133static void hello_ll_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
+
134 off_t off, struct fuse_file_info *fi)
+
135{
+
136 (void) fi;
+
137
+
138 if (ino != 1)
+
139 fuse_reply_err(req, ENOTDIR);
+
140 else {
+
141 struct dirbuf b;
+
142
+
143 memset(&b, 0, sizeof(b));
+
144 dirbuf_add(req, &b, ".", 1);
+
145 dirbuf_add(req, &b, "..", 1);
+
146 dirbuf_add(req, &b, hello_name, 2);
+
147 reply_buf_limited(req, b.p, b.size, off, size);
+
148 free(b.p);
+
149 }
+
150}
+
151
+
152static void hello_ll_open(fuse_req_t req, fuse_ino_t ino,
+
153 struct fuse_file_info *fi)
+
154{
+
155 if (ino != 2)
+
156 fuse_reply_err(req, EISDIR);
+
157 else if ((fi->flags & O_ACCMODE) != O_RDONLY)
+
158 fuse_reply_err(req, EACCES);
+
159 else
+
160 fuse_reply_open(req, fi);
+
161}
+
162
+
163static void hello_ll_read(fuse_req_t req, fuse_ino_t ino, size_t size,
+
164 off_t off, struct fuse_file_info *fi)
+
165{
+
166 (void) fi;
+
167
+
168 assert(ino == 2);
+
169 reply_buf_limited(req, hello_str, strlen(hello_str), off, size);
+
170}
+
171
+
172static void hello_ll_getxattr(fuse_req_t req, fuse_ino_t ino, const char *name,
+
173 size_t size)
+
174{
+
175 (void)size;
+
176 assert(ino == 1 || ino == 2);
+
177 if (strcmp(name, "hello_ll_getxattr_name") == 0)
+
178 {
+
179 const char *buf = "hello_ll_getxattr_value";
+
180 fuse_reply_buf(req, buf, strlen(buf));
+
181 }
+
182 else
+
183 {
+
184 fuse_reply_err(req, ENOTSUP);
+
185 }
+
186}
+
187
+
188static void hello_ll_setxattr(fuse_req_t req, fuse_ino_t ino, const char *name,
+
189 const char *value, size_t size, int flags)
+
190{
+
191 (void)flags;
+
192 (void)size;
+
193 assert(ino == 1 || ino == 2);
+
194 const char* exp_val = "hello_ll_setxattr_value";
+
195 if (strcmp(name, "hello_ll_setxattr_name") == 0 &&
+
196 strlen(exp_val) == size &&
+
197 strncmp(value, exp_val, size) == 0)
+
198 {
+
199 fuse_reply_err(req, 0);
+
200 }
+
201 else
+
202 {
+
203 fuse_reply_err(req, ENOTSUP);
+
204 }
+
205}
+
206
+
207static void hello_ll_removexattr(fuse_req_t req, fuse_ino_t ino, const char *name)
+
208{
+
209 assert(ino == 1 || ino == 2);
+
210 if (strcmp(name, "hello_ll_removexattr_name") == 0)
+
211 {
+
212 fuse_reply_err(req, 0);
+
213 }
+
214 else
+
215 {
+
216 fuse_reply_err(req, ENOTSUP);
+
217 }
+
218}
+
219
+
220static const struct fuse_lowlevel_ops hello_ll_oper = {
+
221 .init = hello_ll_init,
+
222 .lookup = hello_ll_lookup,
+
223 .getattr = hello_ll_getattr,
+
224 .readdir = hello_ll_readdir,
+
225 .open = hello_ll_open,
+
226 .read = hello_ll_read,
+
227 .setxattr = hello_ll_setxattr,
+
228 .getxattr = hello_ll_getxattr,
+
229 .removexattr = hello_ll_removexattr,
+
230};
+
231
+
232int main(int argc, char *argv[])
+
233{
+
234 struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
235 struct fuse_session *se;
+
236 struct fuse_cmdline_opts opts;
+
237 struct fuse_loop_config *config;
+
238 int ret = -1;
+
239
+
240 if (fuse_parse_cmdline(&args, &opts) != 0)
+
241 return 1;
+
242 if (opts.show_help) {
+
243 printf("usage: %s [options] <mountpoint>\n\n", argv[0]);
+ + +
246 ret = 0;
+
247 goto err_out1;
+
248 } else if (opts.show_version) {
+
249 printf("FUSE library version %s\n", fuse_pkgversion());
+ +
251 ret = 0;
+
252 goto err_out1;
+
253 }
+
254
+
255 if(opts.mountpoint == NULL) {
+
256 printf("usage: %s [options] <mountpoint>\n", argv[0]);
+
257 printf(" %s --help\n", argv[0]);
+
258 ret = 1;
+
259 goto err_out1;
+
260 }
+
261
+
262 se = fuse_session_new(&args, &hello_ll_oper,
+
263 sizeof(hello_ll_oper), NULL);
+
264 if (se == NULL)
+
265 goto err_out1;
+
266
+
267 if (fuse_set_signal_handlers(se) != 0)
+
268 goto err_out2;
+
269
+
270 if (fuse_session_mount(se, opts.mountpoint) != 0)
+
271 goto err_out3;
+
272
+
273 fuse_daemonize(opts.foreground);
+
274
+
275 /* Block until ctrl+c or fusermount -u */
+
276 if (opts.singlethread)
+
277 ret = fuse_session_loop(se);
+
278 else {
+
279 config = fuse_loop_cfg_create();
+
280 fuse_loop_cfg_set_clone_fd(config, opts.clone_fd);
+
281 fuse_loop_cfg_set_max_threads(config, opts.max_threads);
+
282 ret = fuse_session_loop_mt(se, config);
+
283 fuse_loop_cfg_destroy(config);
+
284 config = NULL;
+
285 }
+
286
+ +
288err_out3:
+ +
290err_out2:
+ +
292err_out1:
+
293 free(opts.mountpoint);
+
294 fuse_opt_free_args(&args);
+
295
+
296 return ret ? 1 : 0;
+
297}
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
#define FUSE_CAP_ASYNC_READ
+
const char * fuse_pkgversion(void)
Definition fuse.c:5274
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
int fuse_daemonize(int foreground)
Definition helper.c:253
+ +
void fuse_session_destroy(struct fuse_session *se)
+
int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi)
+
int fuse_reply_err(fuse_req_t req, int err)
+
int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
+
struct fuse_req * fuse_req_t
+
int fuse_session_loop(struct fuse_session *se)
Definition fuse_loop.c:19
+
int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
+
void fuse_session_unmount(struct fuse_session *se)
+
void fuse_cmdline_help(void)
Definition helper.c:130
+
void fuse_lowlevel_help(void)
+
int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
+
void fuse_lowlevel_version(void)
+
uint64_t fuse_ino_t
+
size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct stat *stbuf, off_t off)
+
int fuse_reply_attr(fuse_req_t req, const struct stat *attr, double attr_timeout)
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+ + +
char ** argv
Definition fuse_opt.h:114
+ + + +
uint32_t no_interrupt
+
+ + + + +
+ + +
+ + diff --git a/doc/html/hello__ll__uds_8c.html b/doc/html/hello__ll__uds_8c.html new file mode 100644 index 0000000..49a7a60 --- /dev/null +++ b/doc/html/hello__ll__uds_8c.html @@ -0,0 +1,396 @@ + + + + + + + +libfuse: example/hello_ll_uds.c File Reference + + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
+
hello_ll_uds.c File Reference
+
+
+
#include <fuse_lowlevel.h>
+#include <fuse_kernel.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <assert.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+
+

Go to the source code of this file.

+

Detailed Description

+

minimal example filesystem using low-level API and a custom io. This custom io is implemented using UNIX domain sockets (of type SOCK_STREAM)

+

Compile with:

gcc -Wall hello_ll_uds.c `pkg-config fuse3 --cflags --libs` -o hello_ll_uds
+

+Source code

+
/*
+
FUSE: Filesystem in Userspace
+
Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
+
This program can be distributed under the terms of the GNU GPLv2.
+
See the file GPL2.txt.
+
*/
+
+
+
#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 12)
+
+
#include <fuse_lowlevel.h>
+
#include <stdio.h>
+
#include <stdlib.h>
+
#include <string.h>
+
#include <errno.h>
+
#include <fcntl.h>
+
#include <unistd.h>
+
#include <assert.h>
+
+
static const char *hello_str = "Hello World!\n";
+
static const char *hello_name = "hello";
+
+
static int hello_stat(fuse_ino_t ino, struct stat *stbuf)
+
{
+
stbuf->st_ino = ino;
+
switch (ino) {
+
case 1:
+
stbuf->st_mode = S_IFDIR | 0755;
+
stbuf->st_nlink = 2;
+
break;
+
+
case 2:
+
stbuf->st_mode = S_IFREG | 0444;
+
stbuf->st_nlink = 1;
+
stbuf->st_size = strlen(hello_str);
+
break;
+
+
default:
+
return -1;
+
}
+
return 0;
+
}
+
+
static void hello_ll_init(void *userdata, struct fuse_conn_info *conn)
+
{
+
(void)userdata;
+
+
/* Disable the receiving and processing of FUSE_INTERRUPT requests */
+
conn->no_interrupt = 1;
+
+
/* Test setting flags the old way */
+ + +
}
+
+
static void hello_ll_getattr(fuse_req_t req, fuse_ino_t ino,
+
struct fuse_file_info *fi)
+
{
+
struct stat stbuf;
+
+
(void) fi;
+
+
memset(&stbuf, 0, sizeof(stbuf));
+
if (hello_stat(ino, &stbuf) == -1)
+
fuse_reply_err(req, ENOENT);
+
else
+
fuse_reply_attr(req, &stbuf, 1.0);
+
}
+
+
static void hello_ll_lookup(fuse_req_t req, fuse_ino_t parent, const char *name)
+
{
+
struct fuse_entry_param e;
+
+
if (parent != 1 || strcmp(name, hello_name) != 0)
+
fuse_reply_err(req, ENOENT);
+
else {
+
memset(&e, 0, sizeof(e));
+
e.ino = 2;
+
e.attr_timeout = 1.0;
+
e.entry_timeout = 1.0;
+
hello_stat(e.ino, &e.attr);
+
+
fuse_reply_entry(req, &e);
+
}
+
}
+
+
struct dirbuf {
+
char *p;
+
size_t size;
+
};
+
+
static void dirbuf_add(fuse_req_t req, struct dirbuf *b, const char *name,
+ +
{
+
struct stat stbuf;
+
size_t oldsize = b->size;
+
b->size += fuse_add_direntry(req, NULL, 0, name, NULL, 0);
+
b->p = (char *) realloc(b->p, b->size);
+
memset(&stbuf, 0, sizeof(stbuf));
+
stbuf.st_ino = ino;
+
fuse_add_direntry(req, b->p + oldsize, b->size - oldsize, name, &stbuf,
+
b->size);
+
}
+
+
#define min(x, y) ((x) < (y) ? (x) : (y))
+
+
static int reply_buf_limited(fuse_req_t req, const char *buf, size_t bufsize,
+
off_t off, size_t maxsize)
+
{
+
if (off < bufsize)
+
return fuse_reply_buf(req, buf + off,
+
min(bufsize - off, maxsize));
+
else
+
return fuse_reply_buf(req, NULL, 0);
+
}
+
+
static void hello_ll_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
+
off_t off, struct fuse_file_info *fi)
+
{
+
(void) fi;
+
+
if (ino != 1)
+
fuse_reply_err(req, ENOTDIR);
+
else {
+
struct dirbuf b;
+
+
memset(&b, 0, sizeof(b));
+
dirbuf_add(req, &b, ".", 1);
+
dirbuf_add(req, &b, "..", 1);
+
dirbuf_add(req, &b, hello_name, 2);
+
reply_buf_limited(req, b.p, b.size, off, size);
+
free(b.p);
+
}
+
}
+
+
static void hello_ll_open(fuse_req_t req, fuse_ino_t ino,
+
struct fuse_file_info *fi)
+
{
+
if (ino != 2)
+
fuse_reply_err(req, EISDIR);
+
else if ((fi->flags & O_ACCMODE) != O_RDONLY)
+
fuse_reply_err(req, EACCES);
+
else
+
fuse_reply_open(req, fi);
+
}
+
+
static void hello_ll_read(fuse_req_t req, fuse_ino_t ino, size_t size,
+
off_t off, struct fuse_file_info *fi)
+
{
+
(void) fi;
+
+
assert(ino == 2);
+
reply_buf_limited(req, hello_str, strlen(hello_str), off, size);
+
}
+
+
static void hello_ll_getxattr(fuse_req_t req, fuse_ino_t ino, const char *name,
+
size_t size)
+
{
+
(void)size;
+
assert(ino == 1 || ino == 2);
+
if (strcmp(name, "hello_ll_getxattr_name") == 0)
+
{
+
const char *buf = "hello_ll_getxattr_value";
+
fuse_reply_buf(req, buf, strlen(buf));
+
}
+
else
+
{
+
fuse_reply_err(req, ENOTSUP);
+
}
+
}
+
+
static void hello_ll_setxattr(fuse_req_t req, fuse_ino_t ino, const char *name,
+
const char *value, size_t size, int flags)
+
{
+
(void)flags;
+
(void)size;
+
assert(ino == 1 || ino == 2);
+
const char* exp_val = "hello_ll_setxattr_value";
+
if (strcmp(name, "hello_ll_setxattr_name") == 0 &&
+
strlen(exp_val) == size &&
+
strncmp(value, exp_val, size) == 0)
+
{
+
fuse_reply_err(req, 0);
+
}
+
else
+
{
+
fuse_reply_err(req, ENOTSUP);
+
}
+
}
+
+
static void hello_ll_removexattr(fuse_req_t req, fuse_ino_t ino, const char *name)
+
{
+
assert(ino == 1 || ino == 2);
+
if (strcmp(name, "hello_ll_removexattr_name") == 0)
+
{
+
fuse_reply_err(req, 0);
+
}
+
else
+
{
+
fuse_reply_err(req, ENOTSUP);
+
}
+
}
+
+
static const struct fuse_lowlevel_ops hello_ll_oper = {
+
.init = hello_ll_init,
+
.lookup = hello_ll_lookup,
+
.getattr = hello_ll_getattr,
+
.readdir = hello_ll_readdir,
+
.open = hello_ll_open,
+
.read = hello_ll_read,
+
.setxattr = hello_ll_setxattr,
+
.getxattr = hello_ll_getxattr,
+
.removexattr = hello_ll_removexattr,
+
};
+
+
int main(int argc, char *argv[])
+
{
+
struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
struct fuse_session *se;
+
struct fuse_cmdline_opts opts;
+
struct fuse_loop_config *config;
+
int ret = -1;
+
+
if (fuse_parse_cmdline(&args, &opts) != 0)
+
return 1;
+
if (opts.show_help) {
+
printf("usage: %s [options] <mountpoint>\n\n", argv[0]);
+ + +
ret = 0;
+
goto err_out1;
+
} else if (opts.show_version) {
+
printf("FUSE library version %s\n", fuse_pkgversion());
+ +
ret = 0;
+
goto err_out1;
+
}
+
+
if(opts.mountpoint == NULL) {
+
printf("usage: %s [options] <mountpoint>\n", argv[0]);
+
printf(" %s --help\n", argv[0]);
+
ret = 1;
+
goto err_out1;
+
}
+
+
se = fuse_session_new(&args, &hello_ll_oper,
+
sizeof(hello_ll_oper), NULL);
+
if (se == NULL)
+
goto err_out1;
+
+ +
goto err_out2;
+
+
if (fuse_session_mount(se, opts.mountpoint) != 0)
+
goto err_out3;
+
+
fuse_daemonize(opts.foreground);
+
+
/* Block until ctrl+c or fusermount -u */
+
if (opts.singlethread)
+
ret = fuse_session_loop(se);
+
else {
+
config = fuse_loop_cfg_create();
+
fuse_loop_cfg_set_clone_fd(config, opts.clone_fd);
+
fuse_loop_cfg_set_max_threads(config, opts.max_threads);
+
ret = fuse_session_loop_mt(se, config);
+
fuse_loop_cfg_destroy(config);
+
config = NULL;
+
}
+
+ +
err_out3:
+ +
err_out2:
+ +
err_out1:
+
free(opts.mountpoint);
+ +
+
return ret ? 1 : 0;
+
}
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
#define FUSE_CAP_ASYNC_READ
+
const char * fuse_pkgversion(void)
Definition fuse.c:5274
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
int fuse_daemonize(int foreground)
Definition helper.c:253
+ +
void fuse_session_destroy(struct fuse_session *se)
+
int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi)
+
int fuse_reply_err(fuse_req_t req, int err)
+
int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
+
struct fuse_req * fuse_req_t
+
int fuse_session_loop(struct fuse_session *se)
Definition fuse_loop.c:19
+
int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
+
void fuse_session_unmount(struct fuse_session *se)
+
void fuse_cmdline_help(void)
Definition helper.c:130
+
void fuse_lowlevel_help(void)
+
int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
+
void fuse_lowlevel_version(void)
+
uint64_t fuse_ino_t
+
size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct stat *stbuf, off_t off)
+
int fuse_reply_attr(fuse_req_t req, const struct stat *attr, double attr_timeout)
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+ + +
char ** argv
Definition fuse_opt.h:114
+ + + +
uint32_t no_interrupt
+
+ + + + +
+

Definition in file hello_ll_uds.c.

+
+ + +
+ + diff --git a/doc/html/hello__ll__uds_8c_source.html b/doc/html/hello__ll__uds_8c_source.html new file mode 100644 index 0000000..53b1f71 --- /dev/null +++ b/doc/html/hello__ll__uds_8c_source.html @@ -0,0 +1,467 @@ + + + + + + + +libfuse: example/hello_ll_uds.c Source File + + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
+
hello_ll_uds.c
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4 Copyright (C) 2022 Tofik Sonono <tofik.sonono@intel.com>
+
5
+
6 This program can be distributed under the terms of the GNU GPLv2.
+
7 See the file GPL2.txt.
+
8*/
+
9
+
22
+
23#define FUSE_USE_VERSION 34
+
24
+
25
+
26#ifndef _GNU_SOURCE
+
27#define _GNU_SOURCE
+
28#endif
+
29
+
30#include <fuse_lowlevel.h>
+
31#include <fuse_kernel.h>
+
32#include <stdio.h>
+
33#include <stdlib.h>
+
34#include <string.h>
+
35#include <errno.h>
+
36#include <fcntl.h>
+
37#include <unistd.h>
+
38#include <assert.h>
+
39#include <sys/socket.h>
+
40#include <sys/un.h>
+
41
+
42static const char *hello_str = "Hello World!\n";
+
43static const char *hello_name = "hello";
+
44
+
45#define DEFAULT_SOCKET_PATH "/tmp/libfuse-hello-ll.sock"
+
46
+
47static struct options {
+
48 const char *socket_path;
+
49} options;
+
50
+
51#define OPTION(t, p) \
+
52 { t, offsetof(struct options, p), 1 }
+
53static const struct fuse_opt option_spec[] = {
+
54 OPTION("--socket=%s", socket_path),
+ +
56};
+
57
+
58static int hello_stat(fuse_ino_t ino, struct stat *stbuf)
+
59{
+
60 stbuf->st_ino = ino;
+
61 switch (ino) {
+
62 case 1:
+
63 stbuf->st_mode = S_IFDIR | 0755;
+
64 stbuf->st_nlink = 2;
+
65 break;
+
66
+
67 case 2:
+
68 stbuf->st_mode = S_IFREG | 0444;
+
69 stbuf->st_nlink = 1;
+
70 stbuf->st_size = strlen(hello_str);
+
71 break;
+
72
+
73 default:
+
74 return -1;
+
75 }
+
76 return 0;
+
77}
+
78
+
79static void hello_ll_getattr(fuse_req_t req, fuse_ino_t ino,
+
80 struct fuse_file_info *fi)
+
81{
+
82 struct stat stbuf;
+
83
+
84 (void) fi;
+
85
+
86 memset(&stbuf, 0, sizeof(stbuf));
+
87 if (hello_stat(ino, &stbuf) == -1)
+
88 fuse_reply_err(req, ENOENT);
+
89 else
+
90 fuse_reply_attr(req, &stbuf, 1.0);
+
91}
+
92
+
93static void hello_ll_init(void *userdata, struct fuse_conn_info *conn)
+
94{
+
95 (void)userdata;
+
96
+
97 /* Disable the receiving and processing of FUSE_INTERRUPT requests */
+
98 conn->no_interrupt = 1;
+
99}
+
100
+
101static void hello_ll_lookup(fuse_req_t req, fuse_ino_t parent, const char *name)
+
102{
+
103 struct fuse_entry_param e;
+
104
+
105 if (parent != 1 || strcmp(name, hello_name) != 0)
+
106 fuse_reply_err(req, ENOENT);
+
107 else {
+
108 memset(&e, 0, sizeof(e));
+
109 e.ino = 2;
+
110 e.attr_timeout = 1.0;
+
111 e.entry_timeout = 1.0;
+
112 hello_stat(e.ino, &e.attr);
+
113
+
114 fuse_reply_entry(req, &e);
+
115 }
+
116}
+
117
+
118struct dirbuf {
+
119 char *p;
+
120 size_t size;
+
121};
+
122
+
123static void dirbuf_add(fuse_req_t req, struct dirbuf *b, const char *name,
+
124 fuse_ino_t ino)
+
125{
+
126 struct stat stbuf;
+
127 size_t oldsize = b->size;
+
128 b->size += fuse_add_direntry(req, NULL, 0, name, NULL, 0);
+
129 b->p = (char *) realloc(b->p, b->size);
+
130 memset(&stbuf, 0, sizeof(stbuf));
+
131 stbuf.st_ino = ino;
+
132 fuse_add_direntry(req, b->p + oldsize, b->size - oldsize, name, &stbuf,
+
133 b->size);
+
134}
+
135
+
136#define min(x, y) ((x) < (y) ? (x) : (y))
+
137
+
138static int reply_buf_limited(fuse_req_t req, const char *buf, size_t bufsize,
+
139 off_t off, size_t maxsize)
+
140{
+
141 if (off < bufsize)
+
142 return fuse_reply_buf(req, buf + off,
+
143 min(bufsize - off, maxsize));
+
144 else
+
145 return fuse_reply_buf(req, NULL, 0);
+
146}
+
147
+
148static void hello_ll_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
+
149 off_t off, struct fuse_file_info *fi)
+
150{
+
151 (void) fi;
+
152
+
153 if (ino != 1)
+
154 fuse_reply_err(req, ENOTDIR);
+
155 else {
+
156 struct dirbuf b;
+
157
+
158 memset(&b, 0, sizeof(b));
+
159 dirbuf_add(req, &b, ".", 1);
+
160 dirbuf_add(req, &b, "..", 1);
+
161 dirbuf_add(req, &b, hello_name, 2);
+
162 reply_buf_limited(req, b.p, b.size, off, size);
+
163 free(b.p);
+
164 }
+
165}
+
166
+
167static void hello_ll_open(fuse_req_t req, fuse_ino_t ino,
+
168 struct fuse_file_info *fi)
+
169{
+
170 if (ino != 2)
+
171 fuse_reply_err(req, EISDIR);
+
172 else if ((fi->flags & O_ACCMODE) != O_RDONLY)
+
173 fuse_reply_err(req, EACCES);
+
174 else
+
175 fuse_reply_open(req, fi);
+
176}
+
177
+
178static void hello_ll_read(fuse_req_t req, fuse_ino_t ino, size_t size,
+
179 off_t off, struct fuse_file_info *fi)
+
180{
+
181 (void) fi;
+
182
+
183 assert(ino == 2);
+
184 reply_buf_limited(req, hello_str, strlen(hello_str), off, size);
+
185}
+
186
+
187static const struct fuse_lowlevel_ops hello_ll_oper = {
+
188 .init = hello_ll_init,
+
189 .lookup = hello_ll_lookup,
+
190 .getattr = hello_ll_getattr,
+
191 .readdir = hello_ll_readdir,
+
192 .open = hello_ll_open,
+
193 .read = hello_ll_read,
+
194};
+
195
+
196static int create_socket(const char *socket_path) {
+
197 struct sockaddr_un addr;
+
198
+
199 if (strnlen(socket_path, sizeof(addr.sun_path)) >=
+
200 sizeof(addr.sun_path)) {
+
201 printf("Socket path may not be longer than %zu characters\n",
+
202 sizeof(addr.sun_path) - 1);
+
203 return -1;
+
204 }
+
205
+
206 if (remove(socket_path) == -1 && errno != ENOENT) {
+
207 printf("Could not delete previous socket file entry at %s. Error: "
+
208 "%s\n", socket_path, strerror(errno));
+
209 return -1;
+
210 }
+
211
+
212 memset(&addr, 0, sizeof(struct sockaddr_un));
+
213 strcpy(addr.sun_path, socket_path);
+
214
+
215 int sfd = socket(AF_UNIX, SOCK_STREAM, 0);
+
216 if (sfd == -1) {
+
217 printf("Could not create socket. Error: %s\n", strerror(errno));
+
218 return -1;
+
219 }
+
220
+
221 addr.sun_family = AF_UNIX;
+
222 if (bind(sfd, (struct sockaddr *) &addr,
+
223 sizeof(struct sockaddr_un)) == -1) {
+
224 printf("Could not bind socket. Error: %s\n", strerror(errno));
+
225 return -1;
+
226 }
+
227
+
228 if (listen(sfd, 1) == -1)
+
229 return -1;
+
230
+
231 printf("Awaiting connection on socket at %s...\n", socket_path);
+
232 int cfd = accept(sfd, NULL, NULL);
+
233 if (cfd == -1) {
+
234 printf("Could not accept connection. Error: %s\n",
+
235 strerror(errno));
+
236 return -1;
+
237 } else {
+
238 printf("Accepted connection!\n");
+
239 }
+
240 return cfd;
+
241}
+
242
+
243static ssize_t stream_writev(int fd, struct iovec *iov, int count,
+
244 void *userdata) {
+
245 (void)userdata;
+
246
+
247 ssize_t written = 0;
+
248 int cur = 0;
+
249 for (;;) {
+
250 written = writev(fd, iov+cur, count-cur);
+
251 if (written < 0)
+
252 return written;
+
253
+
254 while (cur < count && written >= iov[cur].iov_len)
+
255 written -= iov[cur++].iov_len;
+
256 if (cur == count)
+
257 break;
+
258
+
259 iov[cur].iov_base = (char *)iov[cur].iov_base + written;
+
260 iov[cur].iov_len -= written;
+
261 }
+
262 return written;
+
263}
+
264
+
265
+
266static ssize_t readall(int fd, void *buf, size_t len) {
+
267 size_t count = 0;
+
268
+
269 while (count < len) {
+
270 int i = read(fd, (char *)buf + count, len - count);
+
271 if (!i)
+
272 break;
+
273
+
274 if (i < 0)
+
275 return i;
+
276
+
277 count += i;
+
278 }
+
279 return count;
+
280}
+
281
+
282static ssize_t stream_read(int fd, void *buf, size_t buf_len, void *userdata) {
+
283 (void)userdata;
+
284
+
285 int res = readall(fd, buf, sizeof(struct fuse_in_header));
+
286 if (res == -1)
+
287 return res;
+
288
+
289
+
290 uint32_t packet_len = ((struct fuse_in_header *)buf)->len;
+
291 if (packet_len > buf_len)
+
292 return -1;
+
293
+
294 int prev_res = res;
+
295
+
296 res = readall(fd, (char *)buf + sizeof(struct fuse_in_header),
+
297 packet_len - sizeof(struct fuse_in_header));
+
298
+
299 return (res == -1) ? res : (res + prev_res);
+
300}
+
301
+
302static ssize_t stream_splice_send(int fdin, off_t *offin, int fdout,
+
303 off_t *offout, size_t len,
+
304 unsigned int flags, void *userdata) {
+
305 (void)userdata;
+
306
+
307 size_t count = 0;
+
308 while (count < len) {
+
309 int i = splice(fdin, offin, fdout, offout, len - count, flags);
+
310 if (i < 1)
+
311 return i;
+
312
+
313 count += i;
+
314 }
+
315 return count;
+
316}
+
317
+
318static void fuse_cmdline_help_uds(void)
+
319{
+
320 printf(" -h --help print help\n"
+
321 " -V --version print version\n"
+
322 " -d -o debug enable debug output (implies -f)\n"
+
323 " --socket=PATH serve on PATH\n"
+
324 " (default: " DEFAULT_SOCKET_PATH ")\n");
+
325}
+
326
+
327int main(int argc, char *argv[])
+
328{
+
329 struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
330 struct fuse_session *se;
+
331 struct fuse_cmdline_opts opts;
+
332 const struct fuse_custom_io io = {
+
333 .writev = stream_writev,
+
334 .read = stream_read,
+
335 .splice_receive = NULL,
+
336 .splice_send = stream_splice_send,
+
337 };
+
338 int cfd = -1;
+
339 int ret = -1;
+
340
+
341 /* strdup: fuse_opt_parse frees the old value if --socket is given */
+
342 options.socket_path = strdup(DEFAULT_SOCKET_PATH);
+
343 if (options.socket_path == NULL)
+
344 return 1;
+
345
+
346 if (fuse_opt_parse(&args, &options, option_spec, NULL) == -1)
+
347 return 1;
+
348 if (fuse_parse_cmdline(&args, &opts) != 0)
+
349 return 1;
+
350 if (opts.show_help) {
+
351 printf("usage: %s [options]\n\n", argv[0]);
+
352 fuse_cmdline_help_uds();
+ +
354 ret = 0;
+
355 goto err_out1;
+
356 } else if (opts.show_version) {
+
357 printf("FUSE library version %s\n", fuse_pkgversion());
+ +
359 ret = 0;
+
360 goto err_out1;
+
361 }
+
362
+
363 se = fuse_session_new(&args, &hello_ll_oper,
+
364 sizeof(hello_ll_oper), NULL);
+
365 if (se == NULL)
+
366 goto err_out1;
+
367
+
368 if (fuse_set_signal_handlers(se) != 0)
+
369 goto err_out2;
+
370
+
371 cfd = create_socket(options.socket_path);
+
372 if (cfd == -1)
+
373 goto err_out3;
+
374
+
375 if (fuse_session_custom_io(se, &io, cfd) != 0)
+
376 goto err_out3;
+
377
+
378 /* Block until ctrl+c */
+
379 ret = fuse_session_loop(se);
+
380err_out3:
+ +
382err_out2:
+ +
384err_out1:
+
385 free(opts.mountpoint);
+
386 fuse_opt_free_args(&args);
+
387
+
388 return ret ? 1 : 0;
+
389}
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
const char * fuse_pkgversion(void)
Definition fuse.c:5274
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+ +
void fuse_session_destroy(struct fuse_session *se)
+
int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi)
+
int fuse_reply_err(fuse_req_t req, int err)
+
int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
+
struct fuse_req * fuse_req_t
+
int fuse_session_loop(struct fuse_session *se)
Definition fuse_loop.c:19
+
int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
+
void fuse_lowlevel_help(void)
+
void fuse_lowlevel_version(void)
+
uint64_t fuse_ino_t
+
size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct stat *stbuf, off_t off)
+
int fuse_reply_attr(fuse_req_t req, const struct stat *attr, double attr_timeout)
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+ + +
char ** argv
Definition fuse_opt.h:114
+ + +
uint32_t no_interrupt
+
+ + + + +
+ + +
+ + diff --git a/doc/html/helper_8c_source.html b/doc/html/helper_8c_source.html new file mode 100644 index 0000000..c12f5cf --- /dev/null +++ b/doc/html/helper_8c_source.html @@ -0,0 +1,631 @@ + + + + + + + +libfuse: lib/helper.c Source File + + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
+
helper.c
+
+
+
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 Helper functions to create (simple) standalone programs. With the
+
6 aid of these functions it should be possible to create full FUSE
+
7 file system by implementing nothing but the request handlers.
+
8
+
9 This program can be distributed under the terms of the GNU LGPLv2.
+
10 See the file LGPL2.txt.
+
11*/
+
12
+
13#include "fuse_config.h"
+
14#include "fuse_i.h"
+
15#include "fuse_misc.h"
+
16#include "fuse_opt.h"
+
17#include "fuse_lowlevel.h"
+
18#include "mount_util.h"
+
19
+
20#include <stdio.h>
+
21#include <stdlib.h>
+
22#include <stddef.h>
+
23#include <unistd.h>
+
24#include <string.h>
+
25#include <limits.h>
+
26#include <errno.h>
+
27#include <sys/param.h>
+
28
+
29#define FUSE_HELPER_OPT(t, p) \
+
30 { t, offsetof(struct fuse_cmdline_opts, p), 1 }
+
31
+
32static const struct fuse_opt fuse_helper_opts[] = {
+
33 FUSE_HELPER_OPT("-h", show_help),
+
34 FUSE_HELPER_OPT("--help", show_help),
+
35 FUSE_HELPER_OPT("-V", show_version),
+
36 FUSE_HELPER_OPT("--version", show_version),
+
37 FUSE_HELPER_OPT("-d", debug),
+
38 FUSE_HELPER_OPT("debug", debug),
+
39 FUSE_HELPER_OPT("-d", foreground),
+
40 FUSE_HELPER_OPT("debug", foreground),
+ + +
43 FUSE_HELPER_OPT("-f", foreground),
+
44 FUSE_HELPER_OPT("-s", singlethread),
+
45 FUSE_HELPER_OPT("fsname=", nodefault_subtype),
+ +
47#ifndef __FreeBSD__
+
48 FUSE_HELPER_OPT("subtype=", nodefault_subtype),
+ +
50#endif
+
51 FUSE_HELPER_OPT("clone_fd", clone_fd),
+
52 FUSE_HELPER_OPT("max_idle_threads=%u", max_idle_threads),
+
53 FUSE_HELPER_OPT("max_threads=%u", max_threads),
+ +
55};
+
56
+
57struct fuse_conn_info_opts {
+
58 int atomic_o_trunc;
+
59 int no_remote_posix_lock;
+
60 int no_remote_flock;
+
61 int splice_write;
+
62 int splice_move;
+
63 int splice_read;
+
64 int no_splice_write;
+
65 int no_splice_move;
+
66 int no_splice_read;
+
67 int auto_inval_data;
+
68 int no_auto_inval_data;
+
69 int no_readdirplus;
+
70 int no_readdirplus_auto;
+
71 int async_dio;
+
72 int no_async_dio;
+
73 int writeback_cache;
+
74 int no_writeback_cache;
+
75 int async_read;
+
76 int sync_read;
+
77 unsigned max_write;
+
78 unsigned max_readahead;
+
79 unsigned max_background;
+
80 unsigned congestion_threshold;
+
81 unsigned time_gran;
+
82 int set_max_write;
+
83 int set_max_readahead;
+
84 int set_max_background;
+
85 int set_congestion_threshold;
+
86 int set_time_gran;
+
87};
+
88
+
89#define CONN_OPTION(t, p, v) \
+
90 { t, offsetof(struct fuse_conn_info_opts, p), v }
+
91static const struct fuse_opt conn_info_opt_spec[] = {
+
92 CONN_OPTION("max_write=%u", max_write, 0),
+
93 CONN_OPTION("max_write=", set_max_write, 1),
+
94 CONN_OPTION("max_readahead=%u", max_readahead, 0),
+
95 CONN_OPTION("max_readahead=", set_max_readahead, 1),
+
96 CONN_OPTION("max_background=%u", max_background, 0),
+
97 CONN_OPTION("max_background=", set_max_background, 1),
+
98 CONN_OPTION("congestion_threshold=%u", congestion_threshold, 0),
+
99 CONN_OPTION("congestion_threshold=", set_congestion_threshold, 1),
+
100 CONN_OPTION("sync_read", sync_read, 1),
+
101 CONN_OPTION("async_read", async_read, 1),
+
102 CONN_OPTION("atomic_o_trunc", atomic_o_trunc, 1),
+
103 CONN_OPTION("no_remote_lock", no_remote_posix_lock, 1),
+
104 CONN_OPTION("no_remote_lock", no_remote_flock, 1),
+
105 CONN_OPTION("no_remote_flock", no_remote_flock, 1),
+
106 CONN_OPTION("no_remote_posix_lock", no_remote_posix_lock, 1),
+
107 CONN_OPTION("splice_write", splice_write, 1),
+
108 CONN_OPTION("no_splice_write", no_splice_write, 1),
+
109 CONN_OPTION("splice_move", splice_move, 1),
+
110 CONN_OPTION("no_splice_move", no_splice_move, 1),
+
111 CONN_OPTION("splice_read", splice_read, 1),
+
112 CONN_OPTION("no_splice_read", no_splice_read, 1),
+
113 CONN_OPTION("auto_inval_data", auto_inval_data, 1),
+
114 CONN_OPTION("no_auto_inval_data", no_auto_inval_data, 1),
+
115 CONN_OPTION("readdirplus=no", no_readdirplus, 1),
+
116 CONN_OPTION("readdirplus=yes", no_readdirplus, 0),
+
117 CONN_OPTION("readdirplus=yes", no_readdirplus_auto, 1),
+
118 CONN_OPTION("readdirplus=auto", no_readdirplus, 0),
+
119 CONN_OPTION("readdirplus=auto", no_readdirplus_auto, 0),
+
120 CONN_OPTION("async_dio", async_dio, 1),
+
121 CONN_OPTION("no_async_dio", no_async_dio, 1),
+
122 CONN_OPTION("writeback_cache", writeback_cache, 1),
+
123 CONN_OPTION("no_writeback_cache", no_writeback_cache, 1),
+
124 CONN_OPTION("time_gran=%u", time_gran, 0),
+
125 CONN_OPTION("time_gran=", set_time_gran, 1),
+ +
127};
+
128
+
129
+
+ +
131{
+
132 printf(" -h --help print help\n"
+
133 " -V --version print version\n"
+
134 " -d -o debug enable debug output (implies -f)\n"
+
135 " -f foreground operation\n"
+
136 " -s disable multi-threaded operation\n"
+
137 " -o clone_fd use separate fuse device fd for each thread\n"
+
138 " (may improve performance)\n"
+
139 " -o max_idle_threads the maximum number of idle worker threads\n"
+
140 " allowed (default: -1)\n"
+
141 " -o max_threads the maximum number of worker threads\n"
+
142 " allowed (default: 10)\n");
+
143}
+
+
144
+
145static int fuse_helper_opt_proc(void *data, const char *arg, int key,
+
146 struct fuse_args *outargs)
+
147{
+
148 (void) outargs;
+
149 struct fuse_cmdline_opts *opts = data;
+
150
+
151 switch (key) {
+ +
153 if (!opts->mountpoint) {
+
154 if (fuse_mnt_parse_fuse_fd(arg) != -1) {
+
155 return fuse_opt_add_opt(&opts->mountpoint, arg);
+
156 }
+
157
+
158 char mountpoint[PATH_MAX] = "";
+
159 if (realpath(arg, mountpoint) == NULL) {
+
160 fuse_log(FUSE_LOG_ERR,
+
161 "fuse: bad mount point `%s': %s\n",
+
162 arg, strerror(errno));
+
163 return -1;
+
164 }
+
165 return fuse_opt_add_opt(&opts->mountpoint, mountpoint);
+
166 } else {
+
167 fuse_log(FUSE_LOG_ERR, "fuse: invalid argument `%s'\n", arg);
+
168 return -1;
+
169 }
+
170
+
171 default:
+
172 /* Pass through unknown options */
+
173 return 1;
+
174 }
+
175}
+
176
+
177/* Under FreeBSD, there is no subtype option so this
+
178 function actually sets the fsname */
+
179static int add_default_subtype(const char *progname, struct fuse_args *args)
+
180{
+
181 int res;
+
182 char *subtype_opt;
+
183
+
184 const char *basename = strrchr(progname, '/');
+
185 if (basename == NULL)
+
186 basename = progname;
+
187 else if (basename[1] != '\0')
+
188 basename++;
+
189
+
190 subtype_opt = (char *) malloc(strlen(basename) + 64);
+
191 if (subtype_opt == NULL) {
+
192 fuse_log(FUSE_LOG_ERR, "fuse: memory allocation failed\n");
+
193 return -1;
+
194 }
+
195#ifdef __FreeBSD__
+
196 sprintf(subtype_opt, "-ofsname=%s", basename);
+
197#else
+
198 sprintf(subtype_opt, "-osubtype=%s", basename);
+
199#endif
+
200 res = fuse_opt_add_arg(args, subtype_opt);
+
201 free(subtype_opt);
+
202 return res;
+
203}
+
204
+
205int fuse_parse_cmdline_312(struct fuse_args *args,
+
206 struct fuse_cmdline_opts *opts);
+
207FUSE_SYMVER("fuse_parse_cmdline_312", "fuse_parse_cmdline@@FUSE_3.12")
+
208int fuse_parse_cmdline_312(struct fuse_args *args,
+
209 struct fuse_cmdline_opts *opts)
+
210{
+
211 memset(opts, 0, sizeof(struct fuse_cmdline_opts));
+
212
+
213 opts->max_idle_threads = UINT_MAX; /* new default in fuse version 3.12 */
+
214 opts->max_threads = 10;
+
215
+
216 if (fuse_opt_parse(args, opts, fuse_helper_opts,
+
217 fuse_helper_opt_proc) == -1)
+
218 return -1;
+
219
+
220 /* *Linux*: if neither -o subtype nor -o fsname are specified,
+
221 set subtype to program's basename.
+
222 *FreeBSD*: if fsname is not specified, set to program's
+
223 basename. */
+
224 if (!opts->nodefault_subtype)
+
225 if (add_default_subtype(args->argv[0], args) == -1)
+
226 return -1;
+
227
+
228 return 0;
+
229}
+
230
+
234int fuse_parse_cmdline_30(struct fuse_args *args,
+
235 struct fuse_cmdline_opts *opts);
+
236FUSE_SYMVER("fuse_parse_cmdline_30", "fuse_parse_cmdline@FUSE_3.0")
+
+ +
238 struct fuse_cmdline_opts *out_opts)
+
239{
+
240 struct fuse_cmdline_opts opts;
+
241
+
242 int rc = fuse_parse_cmdline_312(args, &opts);
+
243 if (rc == 0) {
+
244 /* copy up to the size of the old pre 3.12 struct */
+
245 memcpy(out_opts, &opts,
+
246 offsetof(struct fuse_cmdline_opts, max_idle_threads) +
+
247 sizeof(opts.max_idle_threads));
+
248 }
+
249
+
250 return rc;
+
251}
+
+
252
+
+
253int fuse_daemonize(int foreground)
+
254{
+
255 if (!foreground) {
+
256 int nullfd;
+
257 int waiter[2];
+
258 char completed;
+
259
+
260 if (pipe(waiter)) {
+
261 perror("fuse_daemonize: pipe");
+
262 return -1;
+
263 }
+
264
+
265 /*
+
266 * demonize current process by forking it and killing the
+
267 * parent. This makes current process as a child of 'init'.
+
268 */
+
269 switch(fork()) {
+
270 case -1:
+
271 perror("fuse_daemonize: fork");
+
272 close(waiter[0]);
+
273 close(waiter[1]);
+
274 return -1;
+
275 case 0:
+
276 /* child */
+
277 close(waiter[0]);
+
278 waiter[0] = -1;
+
279 break;
+
280 default:
+
281 /* parent */
+
282 (void) read(waiter[0], &completed, sizeof(completed));
+
283 close(waiter[0]);
+
284 close(waiter[1]);
+
285 _exit(0);
+
286 }
+
287
+
288 if (setsid() == -1) {
+
289 perror("fuse_daemonize: setsid");
+
290 close(waiter[1]);
+
291 return -1;
+
292 }
+
293
+
294 (void) chdir("/");
+
295
+
296 nullfd = open("/dev/null", O_RDWR, 0);
+
297 if (nullfd != -1) {
+
298 (void) dup2(nullfd, 0);
+
299 (void) dup2(nullfd, 1);
+
300 (void) dup2(nullfd, 2);
+
301 if (nullfd > 2)
+
302 close(nullfd);
+
303 }
+
304
+
305 /* Propagate completion of daemon initialization */
+
306 completed = 1;
+
307 (void) write(waiter[1], &completed, sizeof(completed));
+
308 close(waiter[1]);
+
309 waiter[1] = -1;
+
310 } else {
+
311 (void)chdir("/");
+
312 }
+
313
+
314 return 0;
+
315}
+
+
316
+
+
317int fuse_main_real_versioned(int argc, char *argv[],
+
318 const struct fuse_operations *op, size_t op_size,
+
319 struct libfuse_version *version, void *user_data)
+
320{
+
321 struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
322 struct fuse *fuse;
+
323 struct fuse_cmdline_opts opts;
+
324 int res;
+
325 struct fuse_loop_config *loop_config = NULL;
+
326
+
327 if (fuse_parse_cmdline(&args, &opts) != 0)
+
328 return 1;
+
329
+
330 if (opts.show_version) {
+
331 printf("FUSE library version %s\n", PACKAGE_VERSION);
+ +
333 res = 0;
+
334 goto out1;
+
335 }
+
336
+
337 if (opts.show_help) {
+
338 if(args.argv[0][0] != '\0')
+
339 printf("usage: %s [options] <mountpoint>\n\n",
+
340 args.argv[0]);
+
341 printf("FUSE options:\n");
+ +
343 fuse_lib_help(&args);
+
344 res = 0;
+
345 goto out1;
+
346 }
+
347
+
348 if (!opts.show_help &&
+
349 !opts.mountpoint) {
+
350 fuse_log(FUSE_LOG_ERR, "error: no mountpoint specified\n");
+
351 res = 2;
+
352 goto out1;
+
353 }
+
354
+
355 struct fuse *_fuse_new_31(struct fuse_args *args,
+
356 const struct fuse_operations *op, size_t op_size,
+
357 struct libfuse_version *version,
+
358 void *user_data);
+
359 fuse = _fuse_new_31(&args, op, op_size, version, user_data);
+
360 if (fuse == NULL) {
+
361 res = 3;
+
362 goto out1;
+
363 }
+
364
+
365 if (fuse_mount(fuse,opts.mountpoint) != 0) {
+
366 res = 4;
+
367 goto out2;
+
368 }
+
369
+
370 if (fuse_daemonize(opts.foreground) != 0) {
+
371 res = 5;
+
372 goto out3;
+
373 }
+
374
+
375 struct fuse_session *se = fuse_get_session(fuse);
+
376 if (fuse_set_signal_handlers(se) != 0) {
+
377 res = 6;
+
378 goto out3;
+
379 }
+
380
+
381 if (opts.singlethread)
+
382 res = fuse_loop(fuse);
+
383 else {
+
384 loop_config = fuse_loop_cfg_create();
+
385 if (loop_config == NULL) {
+
386 res = 7;
+
387 goto out3;
+
388 }
+
389
+
390 fuse_loop_cfg_set_clone_fd(loop_config, opts.clone_fd);
+
391
+
392 fuse_loop_cfg_set_idle_threads(loop_config, opts.max_idle_threads);
+
393 fuse_loop_cfg_set_max_threads(loop_config, opts.max_threads);
+
394 res = fuse_loop_mt(fuse, loop_config);
+
395 }
+
396 if (res)
+
397 res = 8;
+
398
+ +
400out3:
+
401 fuse_unmount(fuse);
+
402out2:
+
403 fuse_destroy(fuse);
+
404out1:
+
405 fuse_loop_cfg_destroy(loop_config);
+
406 free(opts.mountpoint);
+
407 fuse_opt_free_args(&args);
+
408 return res;
+
409}
+
+
410
+
411/* Not symboled, as not part of the official API */
+
412int fuse_main_real_30(int argc, char *argv[], const struct fuse_operations *op,
+
413 size_t op_size, void *user_data);
+
414int fuse_main_real_30(int argc, char *argv[], const struct fuse_operations *op,
+
415 size_t op_size, void *user_data)
+
416{
+
417 struct libfuse_version version = { 0 };
+
418 return fuse_main_real_versioned(argc, argv, op, op_size, &version,
+
419 user_data);
+
420}
+
421
+
+
422void fuse_apply_conn_info_opts(struct fuse_conn_info_opts *opts,
+
423 struct fuse_conn_info *conn)
+
424{
+
425 if(opts->set_max_write)
+
426 conn->max_write = opts->max_write;
+
427 if(opts->set_max_background)
+
428 conn->max_background = opts->max_background;
+
429 if(opts->set_congestion_threshold)
+
430 conn->congestion_threshold = opts->congestion_threshold;
+
431 if(opts->set_time_gran)
+
432 conn->time_gran = opts->time_gran;
+
433 if(opts->set_max_readahead)
+
434 conn->max_readahead = opts->max_readahead;
+
435
+
436#define LL_ENABLE(cond, cap) \
+
437 do { \
+
438 if (cond) \
+
439 fuse_set_feature_flag(conn, cap); \
+
440 } while (0)
+
441
+
442#define LL_DISABLE(cond, cap) \
+
443 do { \
+
444 if (cond) \
+
445 fuse_unset_feature_flag(conn, cap); \
+
446 } while (0)
+
447
+
448 LL_ENABLE(opts->splice_read, FUSE_CAP_SPLICE_READ);
+
449 LL_DISABLE(opts->no_splice_read, FUSE_CAP_SPLICE_READ);
+
450
+
451 LL_ENABLE(opts->splice_write, FUSE_CAP_SPLICE_WRITE);
+
452 LL_DISABLE(opts->no_splice_write, FUSE_CAP_SPLICE_WRITE);
+
453
+
454 LL_ENABLE(opts->splice_move, FUSE_CAP_SPLICE_MOVE);
+
455 LL_DISABLE(opts->no_splice_move, FUSE_CAP_SPLICE_MOVE);
+
456
+
457 LL_ENABLE(opts->auto_inval_data, FUSE_CAP_AUTO_INVAL_DATA);
+
458 LL_DISABLE(opts->no_auto_inval_data, FUSE_CAP_AUTO_INVAL_DATA);
+
459
+
460 LL_DISABLE(opts->no_readdirplus, FUSE_CAP_READDIRPLUS);
+
461 LL_DISABLE(opts->no_readdirplus_auto, FUSE_CAP_READDIRPLUS_AUTO);
+
462
+
463 LL_ENABLE(opts->async_dio, FUSE_CAP_ASYNC_DIO);
+
464 LL_DISABLE(opts->no_async_dio, FUSE_CAP_ASYNC_DIO);
+
465
+
466 LL_ENABLE(opts->writeback_cache, FUSE_CAP_WRITEBACK_CACHE);
+
467 LL_DISABLE(opts->no_writeback_cache, FUSE_CAP_WRITEBACK_CACHE);
+
468
+
469 LL_ENABLE(opts->async_read, FUSE_CAP_ASYNC_READ);
+
470 LL_DISABLE(opts->sync_read, FUSE_CAP_ASYNC_READ);
+
471
+
472 LL_DISABLE(opts->no_remote_posix_lock, FUSE_CAP_POSIX_LOCKS);
+
473 LL_DISABLE(opts->no_remote_flock, FUSE_CAP_FLOCK_LOCKS);
+
474}
+
+
475
+
+
476struct fuse_conn_info_opts* fuse_parse_conn_info_opts(struct fuse_args *args)
+
477{
+
478 struct fuse_conn_info_opts *opts;
+
479
+
480 opts = calloc(1, sizeof(struct fuse_conn_info_opts));
+
481 if(opts == NULL) {
+
482 fuse_log(FUSE_LOG_ERR, "calloc failed\n");
+
483 return NULL;
+
484 }
+
485 if(fuse_opt_parse(args, opts, conn_info_opt_spec, NULL) == -1) {
+
486 free(opts);
+
487 return NULL;
+
488 }
+
489 return opts;
+
490}
+
+
491
+
+
492int fuse_open_channel(const char *mountpoint, const char* options)
+
493{
+
494 struct mount_opts *opts = NULL;
+
495 int fd = -1;
+
496 const char *argv[] = { "", "-o", options };
+
497 int argc = sizeof(argv) / sizeof(argv[0]);
+
498 struct fuse_args args = FUSE_ARGS_INIT(argc, (char**) argv);
+
499
+
500 opts = parse_mount_opts(&args);
+
501 if (opts == NULL)
+
502 return -1;
+
503
+
504 fd = fuse_kern_mount(mountpoint, opts);
+
505 destroy_mount_opts(opts);
+
506
+
507 return fd;
+
508}
+
+
int fuse_mount(struct fuse *f, const char *mountpoint)
Definition fuse.c:5260
+
void fuse_destroy(struct fuse *f)
Definition fuse.c:5209
+
int fuse_main_real_versioned(int argc, char *argv[], const struct fuse_operations *op, size_t op_size, struct libfuse_version *version, void *user_data)
Definition helper.c:317
+
int fuse_loop(struct fuse *f)
Definition fuse.c:4644
+
void fuse_lib_help(struct fuse_args *args)
Definition fuse.c:4813
+
int fuse_open_channel(const char *mountpoint, const char *options)
Definition helper.c:492
+
struct fuse_session * fuse_get_session(struct fuse *f)
Definition fuse.c:4588
+
void fuse_unmount(struct fuse *f)
Definition fuse.c:5265
+
#define FUSE_CAP_AUTO_INVAL_DATA
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
#define FUSE_CAP_SPLICE_READ
+
#define FUSE_CAP_WRITEBACK_CACHE
+
#define FUSE_CAP_ASYNC_READ
+
#define FUSE_CAP_SPLICE_WRITE
+
void fuse_apply_conn_info_opts(struct fuse_conn_info_opts *opts, struct fuse_conn_info *conn)
Definition helper.c:422
+
#define FUSE_CAP_POSIX_LOCKS
+
#define FUSE_CAP_READDIRPLUS_AUTO
+
struct fuse_conn_info_opts * fuse_parse_conn_info_opts(struct fuse_args *args)
Definition helper.c:476
+
#define FUSE_CAP_ASYNC_DIO
+
#define FUSE_CAP_READDIRPLUS
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
#define FUSE_CAP_SPLICE_MOVE
+
int fuse_daemonize(int foreground)
Definition helper.c:253
+
#define FUSE_CAP_FLOCK_LOCKS
+
void fuse_log(enum fuse_log_level level, const char *fmt,...) __attribute__((format(printf
+ +
void fuse_cmdline_help(void)
Definition helper.c:130
+
int fuse_parse_cmdline_30(struct fuse_args *args, struct fuse_cmdline_opts *opts)
Definition helper.c:237
+
void fuse_lowlevel_version(void)
+ +
int fuse_opt_add_arg(struct fuse_args *args, const char *arg)
Definition fuse_opt.c:55
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
#define FUSE_OPT_KEY(templ, key)
Definition fuse_opt.h:98
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_OPT_KEY_NONOPT
Definition fuse_opt.h:137
+
#define FUSE_OPT_KEY_KEEP
Definition fuse_opt.h:145
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+
int fuse_opt_add_opt(char **opts, const char *opt)
Definition fuse_opt.c:139
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+ + +
char ** argv
Definition fuse_opt.h:114
+ + +
uint32_t time_gran
+
uint32_t congestion_threshold
+
uint32_t max_write
+
uint32_t max_readahead
+
uint32_t max_background
+ + + + +
+ + +
+ + diff --git a/doc/html/iconv_8c_source.html b/doc/html/iconv_8c_source.html new file mode 100644 index 0000000..b36f7ea --- /dev/null +++ b/doc/html/iconv_8c_source.html @@ -0,0 +1,834 @@ + + + + + + + +libfuse: lib/modules/iconv.c Source File + + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
+
iconv.c
+
+
+
1/*
+
2 fuse iconv module: file name charset conversion
+
3 Copyright (C) 2007 Miklos Szeredi <miklos@szeredi.hu>
+
4
+
5 This program can be distributed under the terms of the GNU LGPLv2.
+
6 See the file LGPL2.txt
+
7*/
+
8
+
9#include <fuse_config.h>
+
10
+
11#include <fuse.h>
+
12#include <stdio.h>
+
13#include <stdlib.h>
+
14#include <stddef.h>
+
15#include <string.h>
+
16#include <errno.h>
+
17#include <iconv.h>
+
18#include <pthread.h>
+
19#include <locale.h>
+
20#include <langinfo.h>
+
21
+
22struct iconv {
+
23 struct fuse_fs *next;
+
24 pthread_mutex_t lock;
+
25 char *from_code;
+
26 char *to_code;
+
27 iconv_t tofs;
+
28 iconv_t fromfs;
+
29};
+
30
+
31struct iconv_dh {
+
32 struct iconv *ic;
+
33 void *prev_buf;
+
34 fuse_fill_dir_t prev_filler;
+
35};
+
36
+
37static struct iconv *iconv_get(void)
+
38{
+ +
40}
+
41
+
42static int iconv_convpath(struct iconv *ic, const char *path, char **newpathp,
+
43 int fromfs)
+
44{
+
45 size_t pathlen;
+
46 size_t newpathlen;
+
47 char *newpath;
+
48 size_t plen;
+
49 char *p;
+
50 size_t res;
+
51 int err;
+
52
+
53 if (path == NULL) {
+
54 *newpathp = NULL;
+
55 return 0;
+
56 }
+
57
+
58 pathlen = strlen(path);
+
59 newpathlen = pathlen * 4;
+
60 newpath = malloc(newpathlen + 1);
+
61 if (!newpath)
+
62 return -ENOMEM;
+
63
+
64 plen = newpathlen;
+
65 p = newpath;
+
66 pthread_mutex_lock(&ic->lock);
+
67 do {
+
68 res = iconv(fromfs ? ic->fromfs : ic->tofs, (char **) &path,
+
69 &pathlen, &p, &plen);
+
70 if (res == (size_t) -1) {
+
71 char *tmp;
+
72 size_t inc;
+
73
+
74 err = -EILSEQ;
+
75 if (errno != E2BIG)
+
76 goto err;
+
77
+
78 inc = (pathlen + 1) * 4;
+
79 newpathlen += inc;
+
80 int dp = p - newpath;
+
81 tmp = realloc(newpath, newpathlen + 1);
+
82 err = -ENOMEM;
+
83 if (!tmp)
+
84 goto err;
+
85
+
86 p = tmp + dp;
+
87 plen += inc;
+
88 newpath = tmp;
+
89 }
+
90 } while (res == (size_t) -1);
+
91 pthread_mutex_unlock(&ic->lock);
+
92 *p = '\0';
+
93 *newpathp = newpath;
+
94 return 0;
+
95
+
96err:
+
97 iconv(fromfs ? ic->fromfs : ic->tofs, NULL, NULL, NULL, NULL);
+
98 pthread_mutex_unlock(&ic->lock);
+
99 free(newpath);
+
100 return err;
+
101}
+
102
+
103static int iconv_getattr(const char *path, struct stat *stbuf,
+
104 struct fuse_file_info *fi)
+
105{
+
106 struct iconv *ic = iconv_get();
+
107 char *newpath;
+
108 int err = iconv_convpath(ic, path, &newpath, 0);
+
109 if (!err) {
+
110 err = fuse_fs_getattr(ic->next, newpath, stbuf, fi);
+
111 free(newpath);
+
112 }
+
113 return err;
+
114}
+
115
+
116static int iconv_access(const char *path, int mask)
+
117{
+
118 struct iconv *ic = iconv_get();
+
119 char *newpath;
+
120 int err = iconv_convpath(ic, path, &newpath, 0);
+
121 if (!err) {
+
122 err = fuse_fs_access(ic->next, newpath, mask);
+
123 free(newpath);
+
124 }
+
125 return err;
+
126}
+
127
+
128static int iconv_readlink(const char *path, char *buf, size_t size)
+
129{
+
130 struct iconv *ic = iconv_get();
+
131 char *newpath;
+
132 int err = iconv_convpath(ic, path, &newpath, 0);
+
133 if (!err) {
+
134 err = fuse_fs_readlink(ic->next, newpath, buf, size);
+
135 if (!err) {
+
136 char *newlink;
+
137 err = iconv_convpath(ic, buf, &newlink, 1);
+
138 if (!err) {
+
139 strncpy(buf, newlink, size - 1);
+
140 buf[size - 1] = '\0';
+
141 free(newlink);
+
142 }
+
143 }
+
144 free(newpath);
+
145 }
+
146 return err;
+
147}
+
148
+
149static int iconv_opendir(const char *path, struct fuse_file_info *fi)
+
150{
+
151 struct iconv *ic = iconv_get();
+
152 char *newpath;
+
153 int err = iconv_convpath(ic, path, &newpath, 0);
+
154 if (!err) {
+
155 err = fuse_fs_opendir(ic->next, newpath, fi);
+
156 free(newpath);
+
157 }
+
158 return err;
+
159}
+
160
+
161static int iconv_dir_fill(void *buf, const char *name,
+
162 const struct stat *stbuf, off_t off,
+
163 enum fuse_fill_dir_flags flags)
+
164{
+
165 struct iconv_dh *dh = buf;
+
166 char *newname;
+
167 int res = 0;
+
168 if (iconv_convpath(dh->ic, name, &newname, 1) == 0) {
+
169 res = dh->prev_filler(dh->prev_buf, newname, stbuf, off, flags);
+
170 free(newname);
+
171 }
+
172 return res;
+
173}
+
174
+
175static int iconv_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
+
176 off_t offset, struct fuse_file_info *fi,
+
177 enum fuse_readdir_flags flags)
+
178{
+
179 struct iconv *ic = iconv_get();
+
180 char *newpath;
+
181 int err = iconv_convpath(ic, path, &newpath, 0);
+
182 if (!err) {
+
183 struct iconv_dh dh;
+
184 dh.ic = ic;
+
185 dh.prev_buf = buf;
+
186 dh.prev_filler = filler;
+
187 err = fuse_fs_readdir(ic->next, newpath, &dh, iconv_dir_fill,
+
188 offset, fi, flags);
+
189 free(newpath);
+
190 }
+
191 return err;
+
192}
+
193
+
194static int iconv_releasedir(const char *path, struct fuse_file_info *fi)
+
195{
+
196 struct iconv *ic = iconv_get();
+
197 char *newpath;
+
198 int err = iconv_convpath(ic, path, &newpath, 0);
+
199 if (!err) {
+
200 err = fuse_fs_releasedir(ic->next, newpath, fi);
+
201 free(newpath);
+
202 }
+
203 return err;
+
204}
+
205
+
206static int iconv_mknod(const char *path, mode_t mode, dev_t rdev)
+
207{
+
208 struct iconv *ic = iconv_get();
+
209 char *newpath;
+
210 int err = iconv_convpath(ic, path, &newpath, 0);
+
211 if (!err) {
+
212 err = fuse_fs_mknod(ic->next, newpath, mode, rdev);
+
213 free(newpath);
+
214 }
+
215 return err;
+
216}
+
217
+
218static int iconv_mkdir(const char *path, mode_t mode)
+
219{
+
220 struct iconv *ic = iconv_get();
+
221 char *newpath;
+
222 int err = iconv_convpath(ic, path, &newpath, 0);
+
223 if (!err) {
+
224 err = fuse_fs_mkdir(ic->next, newpath, mode);
+
225 free(newpath);
+
226 }
+
227 return err;
+
228}
+
229
+
230static int iconv_unlink(const char *path)
+
231{
+
232 struct iconv *ic = iconv_get();
+
233 char *newpath;
+
234 int err = iconv_convpath(ic, path, &newpath, 0);
+
235 if (!err) {
+
236 err = fuse_fs_unlink(ic->next, newpath);
+
237 free(newpath);
+
238 }
+
239 return err;
+
240}
+
241
+
242static int iconv_rmdir(const char *path)
+
243{
+
244 struct iconv *ic = iconv_get();
+
245 char *newpath;
+
246 int err = iconv_convpath(ic, path, &newpath, 0);
+
247 if (!err) {
+
248 err = fuse_fs_rmdir(ic->next, newpath);
+
249 free(newpath);
+
250 }
+
251 return err;
+
252}
+
253
+
254static int iconv_symlink(const char *from, const char *to)
+
255{
+
256 struct iconv *ic = iconv_get();
+
257 char *newfrom;
+
258 char *newto;
+
259 int err = iconv_convpath(ic, from, &newfrom, 0);
+
260 if (!err) {
+
261 err = iconv_convpath(ic, to, &newto, 0);
+
262 if (!err) {
+
263 err = fuse_fs_symlink(ic->next, newfrom, newto);
+
264 free(newto);
+
265 }
+
266 free(newfrom);
+
267 }
+
268 return err;
+
269}
+
270
+
271static int iconv_rename(const char *from, const char *to, unsigned int flags)
+
272{
+
273 struct iconv *ic = iconv_get();
+
274 char *newfrom;
+
275 char *newto;
+
276 int err = iconv_convpath(ic, from, &newfrom, 0);
+
277 if (!err) {
+
278 err = iconv_convpath(ic, to, &newto, 0);
+
279 if (!err) {
+
280 err = fuse_fs_rename(ic->next, newfrom, newto, flags);
+
281 free(newto);
+
282 }
+
283 free(newfrom);
+
284 }
+
285 return err;
+
286}
+
287
+
288static int iconv_link(const char *from, const char *to)
+
289{
+
290 struct iconv *ic = iconv_get();
+
291 char *newfrom;
+
292 char *newto;
+
293 int err = iconv_convpath(ic, from, &newfrom, 0);
+
294 if (!err) {
+
295 err = iconv_convpath(ic, to, &newto, 0);
+
296 if (!err) {
+
297 err = fuse_fs_link(ic->next, newfrom, newto);
+
298 free(newto);
+
299 }
+
300 free(newfrom);
+
301 }
+
302 return err;
+
303}
+
304
+
305static int iconv_chmod(const char *path, mode_t mode,
+
306 struct fuse_file_info *fi)
+
307{
+
308 struct iconv *ic = iconv_get();
+
309 char *newpath;
+
310 int err = iconv_convpath(ic, path, &newpath, 0);
+
311 if (!err) {
+
312 err = fuse_fs_chmod(ic->next, newpath, mode, fi);
+
313 free(newpath);
+
314 }
+
315 return err;
+
316}
+
317
+
318static int iconv_chown(const char *path, uid_t uid, gid_t gid,
+
319 struct fuse_file_info *fi)
+
320{
+
321 struct iconv *ic = iconv_get();
+
322 char *newpath;
+
323 int err = iconv_convpath(ic, path, &newpath, 0);
+
324 if (!err) {
+
325 err = fuse_fs_chown(ic->next, newpath, uid, gid, fi);
+
326 free(newpath);
+
327 }
+
328 return err;
+
329}
+
330
+
331static int iconv_truncate(const char *path, off_t size,
+
332 struct fuse_file_info *fi)
+
333{
+
334 struct iconv *ic = iconv_get();
+
335 char *newpath;
+
336 int err = iconv_convpath(ic, path, &newpath, 0);
+
337 if (!err) {
+
338 err = fuse_fs_truncate(ic->next, newpath, size, fi);
+
339 free(newpath);
+
340 }
+
341 return err;
+
342}
+
343
+
344static int iconv_utimens(const char *path, const struct timespec ts[2],
+
345 struct fuse_file_info *fi)
+
346{
+
347 struct iconv *ic = iconv_get();
+
348 char *newpath;
+
349 int err = iconv_convpath(ic, path, &newpath, 0);
+
350 if (!err) {
+
351 err = fuse_fs_utimens(ic->next, newpath, ts, fi);
+
352 free(newpath);
+
353 }
+
354 return err;
+
355}
+
356
+
357static int iconv_create(const char *path, mode_t mode,
+
358 struct fuse_file_info *fi)
+
359{
+
360 struct iconv *ic = iconv_get();
+
361 char *newpath;
+
362 int err = iconv_convpath(ic, path, &newpath, 0);
+
363 if (!err) {
+
364 err = fuse_fs_create(ic->next, newpath, mode, fi);
+
365 free(newpath);
+
366 }
+
367 return err;
+
368}
+
369
+
370static int iconv_open_file(const char *path, struct fuse_file_info *fi)
+
371{
+
372 struct iconv *ic = iconv_get();
+
373 char *newpath;
+
374 int err = iconv_convpath(ic, path, &newpath, 0);
+
375 if (!err) {
+
376 err = fuse_fs_open(ic->next, newpath, fi);
+
377 free(newpath);
+
378 }
+
379 return err;
+
380}
+
381
+
382static int iconv_read_buf(const char *path, struct fuse_bufvec **bufp,
+
383 size_t size, off_t offset, struct fuse_file_info *fi)
+
384{
+
385 struct iconv *ic = iconv_get();
+
386 char *newpath;
+
387 int err = iconv_convpath(ic, path, &newpath, 0);
+
388 if (!err) {
+
389 err = fuse_fs_read_buf(ic->next, newpath, bufp, size, offset, fi);
+
390 free(newpath);
+
391 }
+
392 return err;
+
393}
+
394
+
395static int iconv_write_buf(const char *path, struct fuse_bufvec *buf,
+
396 off_t offset, struct fuse_file_info *fi)
+
397{
+
398 struct iconv *ic = iconv_get();
+
399 char *newpath;
+
400 int err = iconv_convpath(ic, path, &newpath, 0);
+
401 if (!err) {
+
402 err = fuse_fs_write_buf(ic->next, newpath, buf, offset, fi);
+
403 free(newpath);
+
404 }
+
405 return err;
+
406}
+
407
+
408static int iconv_statfs(const char *path, struct statvfs *stbuf)
+
409{
+
410 struct iconv *ic = iconv_get();
+
411 char *newpath;
+
412 int err = iconv_convpath(ic, path, &newpath, 0);
+
413 if (!err) {
+
414 err = fuse_fs_statfs(ic->next, newpath, stbuf);
+
415 free(newpath);
+
416 }
+
417 return err;
+
418}
+
419
+
420static int iconv_flush(const char *path, struct fuse_file_info *fi)
+
421{
+
422 struct iconv *ic = iconv_get();
+
423 char *newpath;
+
424 int err = iconv_convpath(ic, path, &newpath, 0);
+
425 if (!err) {
+
426 err = fuse_fs_flush(ic->next, newpath, fi);
+
427 free(newpath);
+
428 }
+
429 return err;
+
430}
+
431
+
432static int iconv_release(const char *path, struct fuse_file_info *fi)
+
433{
+
434 struct iconv *ic = iconv_get();
+
435 char *newpath;
+
436 int err = iconv_convpath(ic, path, &newpath, 0);
+
437 if (!err) {
+
438 err = fuse_fs_release(ic->next, newpath, fi);
+
439 free(newpath);
+
440 }
+
441 return err;
+
442}
+
443
+
444static int iconv_fsync(const char *path, int isdatasync,
+
445 struct fuse_file_info *fi)
+
446{
+
447 struct iconv *ic = iconv_get();
+
448 char *newpath;
+
449 int err = iconv_convpath(ic, path, &newpath, 0);
+
450 if (!err) {
+
451 err = fuse_fs_fsync(ic->next, newpath, isdatasync, fi);
+
452 free(newpath);
+
453 }
+
454 return err;
+
455}
+
456
+
457static int iconv_fsyncdir(const char *path, int isdatasync,
+
458 struct fuse_file_info *fi)
+
459{
+
460 struct iconv *ic = iconv_get();
+
461 char *newpath;
+
462 int err = iconv_convpath(ic, path, &newpath, 0);
+
463 if (!err) {
+
464 err = fuse_fs_fsyncdir(ic->next, newpath, isdatasync, fi);
+
465 free(newpath);
+
466 }
+
467 return err;
+
468}
+
469
+
470static int iconv_setxattr(const char *path, const char *name,
+
471 const char *value, size_t size, int flags)
+
472{
+
473 struct iconv *ic = iconv_get();
+
474 char *newpath;
+
475 int err = iconv_convpath(ic, path, &newpath, 0);
+
476 if (!err) {
+
477 err = fuse_fs_setxattr(ic->next, newpath, name, value, size,
+
478 flags);
+
479 free(newpath);
+
480 }
+
481 return err;
+
482}
+
483
+
484static int iconv_getxattr(const char *path, const char *name, char *value,
+
485 size_t size)
+
486{
+
487 struct iconv *ic = iconv_get();
+
488 char *newpath;
+
489 int err = iconv_convpath(ic, path, &newpath, 0);
+
490 if (!err) {
+
491 err = fuse_fs_getxattr(ic->next, newpath, name, value, size);
+
492 free(newpath);
+
493 }
+
494 return err;
+
495}
+
496
+
497static int iconv_listxattr(const char *path, char *list, size_t size)
+
498{
+
499 struct iconv *ic = iconv_get();
+
500 char *newpath;
+
501 int err = iconv_convpath(ic, path, &newpath, 0);
+
502 if (!err) {
+
503 err = fuse_fs_listxattr(ic->next, newpath, list, size);
+
504 free(newpath);
+
505 }
+
506 return err;
+
507}
+
508
+
509static int iconv_removexattr(const char *path, const char *name)
+
510{
+
511 struct iconv *ic = iconv_get();
+
512 char *newpath;
+
513 int err = iconv_convpath(ic, path, &newpath, 0);
+
514 if (!err) {
+
515 err = fuse_fs_removexattr(ic->next, newpath, name);
+
516 free(newpath);
+
517 }
+
518 return err;
+
519}
+
520
+
521static int iconv_lock(const char *path, struct fuse_file_info *fi, int cmd,
+
522 struct flock *lock)
+
523{
+
524 struct iconv *ic = iconv_get();
+
525 char *newpath;
+
526 int err = iconv_convpath(ic, path, &newpath, 0);
+
527 if (!err) {
+
528 err = fuse_fs_lock(ic->next, newpath, fi, cmd, lock);
+
529 free(newpath);
+
530 }
+
531 return err;
+
532}
+
533
+
534static int iconv_flock(const char *path, struct fuse_file_info *fi, int op)
+
535{
+
536 struct iconv *ic = iconv_get();
+
537 char *newpath;
+
538 int err = iconv_convpath(ic, path, &newpath, 0);
+
539 if (!err) {
+
540 err = fuse_fs_flock(ic->next, newpath, fi, op);
+
541 free(newpath);
+
542 }
+
543 return err;
+
544}
+
545
+
546static int iconv_bmap(const char *path, size_t blocksize, uint64_t *idx)
+
547{
+
548 struct iconv *ic = iconv_get();
+
549 char *newpath;
+
550 int err = iconv_convpath(ic, path, &newpath, 0);
+
551 if (!err) {
+
552 err = fuse_fs_bmap(ic->next, newpath, blocksize, idx);
+
553 free(newpath);
+
554 }
+
555 return err;
+
556}
+
557
+
558static off_t iconv_lseek(const char *path, off_t off, int whence,
+
559 struct fuse_file_info *fi)
+
560{
+
561 struct iconv *ic = iconv_get();
+
562 char *newpath;
+
563 int res = iconv_convpath(ic, path, &newpath, 0);
+
564 if (!res) {
+
565 res = fuse_fs_lseek(ic->next, newpath, off, whence, fi);
+
566 free(newpath);
+
567 }
+
568 return res;
+
569}
+
570
+
571#ifdef HAVE_STATX
+
572static int iconv_statx(const char *path, int flags, int mask, struct statx *stxbuf,
+
573 struct fuse_file_info *fi)
+
574{
+
575 struct iconv *ic = iconv_get();
+
576 char *newpath;
+
577 int res = iconv_convpath(ic, path, &newpath, 0);
+
578
+
579 if (!res) {
+
580 res = fuse_fs_statx(ic->next, newpath, flags, mask, stxbuf, fi);
+
581 free(newpath);
+
582 }
+
583 return res;
+
584}
+
585#endif
+
586
+
587static void *iconv_init(struct fuse_conn_info *conn,
+
588 struct fuse_config *cfg)
+
589{
+
590 struct iconv *ic = iconv_get();
+
591 fuse_fs_init(ic->next, conn, cfg);
+
592 /* Don't touch cfg->nullpath_ok, we can work with
+
593 either */
+
594 return ic;
+
595}
+
596
+
597static void iconv_destroy(void *data)
+
598{
+
599 struct iconv *ic = data;
+
600 fuse_fs_destroy(ic->next);
+
601 iconv_close(ic->tofs);
+
602 iconv_close(ic->fromfs);
+
603 pthread_mutex_destroy(&ic->lock);
+
604 free(ic->from_code);
+
605 free(ic->to_code);
+
606 free(ic);
+
607}
+
608
+
609static const struct fuse_operations iconv_oper = {
+
610 .destroy = iconv_destroy,
+
611 .init = iconv_init,
+
612 .getattr = iconv_getattr,
+
613 .access = iconv_access,
+
614 .readlink = iconv_readlink,
+
615 .opendir = iconv_opendir,
+
616 .readdir = iconv_readdir,
+
617 .releasedir = iconv_releasedir,
+
618 .mknod = iconv_mknod,
+
619 .mkdir = iconv_mkdir,
+
620 .symlink = iconv_symlink,
+
621 .unlink = iconv_unlink,
+
622 .rmdir = iconv_rmdir,
+
623 .rename = iconv_rename,
+
624 .link = iconv_link,
+
625 .chmod = iconv_chmod,
+
626 .chown = iconv_chown,
+
627 .truncate = iconv_truncate,
+
628 .utimens = iconv_utimens,
+
629 .create = iconv_create,
+
630 .open = iconv_open_file,
+
631 .read_buf = iconv_read_buf,
+
632 .write_buf = iconv_write_buf,
+
633 .statfs = iconv_statfs,
+
634 .flush = iconv_flush,
+
635 .release = iconv_release,
+
636 .fsync = iconv_fsync,
+
637 .fsyncdir = iconv_fsyncdir,
+
638 .setxattr = iconv_setxattr,
+
639 .getxattr = iconv_getxattr,
+
640 .listxattr = iconv_listxattr,
+
641 .removexattr = iconv_removexattr,
+
642 .lock = iconv_lock,
+
643 .flock = iconv_flock,
+
644 .bmap = iconv_bmap,
+
645 .lseek = iconv_lseek,
+
646#ifdef HAVE_STATX
+
647 .statx = iconv_statx,
+
648#endif
+
649};
+
650
+
651static const struct fuse_opt iconv_opts[] = {
+
652 FUSE_OPT_KEY("-h", 0),
+
653 FUSE_OPT_KEY("--help", 0),
+
654 { "from_code=%s", offsetof(struct iconv, from_code), 0 },
+
655 { "to_code=%s", offsetof(struct iconv, to_code), 1 },
+ +
657};
+
658
+
659static void iconv_help(void)
+
660{
+
661 char *charmap;
+
662 const char *old = setlocale(LC_CTYPE, "");
+
663
+
664 charmap = strdup(nl_langinfo(CODESET));
+
665 if (old)
+
666 setlocale(LC_CTYPE, old);
+
667 else
+
668 perror("setlocale");
+
669
+
670 printf(
+
671" -o from_code=CHARSET original encoding of file names (default: UTF-8)\n"
+
672" -o to_code=CHARSET new encoding of the file names (default: %s)\n",
+
673 charmap);
+
674 free(charmap);
+
675}
+
676
+
677static int iconv_opt_proc(void *data, const char *arg, int key,
+
678 struct fuse_args *outargs)
+
679{
+
680 (void) data; (void) arg; (void) outargs;
+
681
+
682 if (!key) {
+
683 iconv_help();
+
684 return -1;
+
685 }
+
686
+
687 return 1;
+
688}
+
689
+
690static struct fuse_fs *iconv_new(struct fuse_args *args,
+
691 struct fuse_fs *next[])
+
692{
+
693 struct fuse_fs *fs;
+
694 struct iconv *ic;
+
695 const char *old = NULL;
+
696 const char *from;
+
697 const char *to;
+
698
+
699 ic = calloc(1, sizeof(struct iconv));
+
700 if (ic == NULL) {
+
701 fuse_log(FUSE_LOG_ERR, "fuse-iconv: memory allocation failed\n");
+
702 return NULL;
+
703 }
+
704
+
705 if (fuse_opt_parse(args, ic, iconv_opts, iconv_opt_proc) == -1)
+
706 goto out_free;
+
707
+
708 if (!next[0] || next[1]) {
+
709 fuse_log(FUSE_LOG_ERR, "fuse-iconv: exactly one next filesystem required\n");
+
710 goto out_free;
+
711 }
+
712
+
713 from = ic->from_code ? ic->from_code : "UTF-8";
+
714 to = ic->to_code ? ic->to_code : "";
+
715 /* FIXME: detect charset equivalence? */
+
716 if (!to[0])
+
717 old = setlocale(LC_CTYPE, "");
+
718 ic->tofs = iconv_open(from, to);
+
719 if (ic->tofs == (iconv_t) -1) {
+
720 fuse_log(FUSE_LOG_ERR, "fuse-iconv: cannot convert from %s to %s\n",
+
721 to, from);
+
722 goto out_free;
+
723 }
+
724 ic->fromfs = iconv_open(to, from);
+
725 if (ic->fromfs == (iconv_t) -1) {
+
726 fuse_log(FUSE_LOG_ERR, "fuse-iconv: cannot convert from %s to %s\n",
+
727 from, to);
+
728 goto out_iconv_close_to;
+
729 }
+
730 if (old) {
+
731 setlocale(LC_CTYPE, old);
+
732 old = NULL;
+
733 }
+
734
+
735 ic->next = next[0];
+
736 fs = fuse_fs_new(&iconv_oper, sizeof(iconv_oper), ic);
+
737 if (!fs)
+
738 goto out_iconv_close_from;
+
739
+
740 return fs;
+
741
+
742out_iconv_close_from:
+
743 iconv_close(ic->fromfs);
+
744out_iconv_close_to:
+
745 iconv_close(ic->tofs);
+
746out_free:
+
747 free(ic->from_code);
+
748 free(ic->to_code);
+
749 free(ic);
+
750 if (old) {
+
751 setlocale(LC_CTYPE, old);
+
752 }
+
753 return NULL;
+
754}
+
755
+
756FUSE_REGISTER_MODULE(iconv, iconv_new);
+ +
struct fuse_context * fuse_get_context(void)
Definition fuse.c:4711
+
int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
Definition fuse.h:93
+
struct fuse_fs * fuse_fs_new(const struct fuse_operations *op, size_t op_size, void *private_data)
Definition fuse.c:4923
+
fuse_fill_dir_flags
Definition fuse.h:61
+
fuse_readdir_flags
Definition fuse.h:45
+
#define FUSE_REGISTER_MODULE(name_, factory_)
Definition fuse.h:1415
+
void fuse_log(enum fuse_log_level level, const char *fmt,...) __attribute__((format(printf
+
#define FUSE_OPT_KEY(templ, key)
Definition fuse_opt.h:98
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+ + + + +
void * private_data
Definition fuse.h:891
+ + + +
+ + +
+ + diff --git a/doc/html/index.html b/doc/html/index.html new file mode 100644 index 0000000..5dffcf8 --- /dev/null +++ b/doc/html/index.html @@ -0,0 +1,74 @@ + + + + + + + +libfuse: libfuse API documentation + + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + +
+
+
+
libfuse API documentation
+
+
+

FUSE (Filesystem in Userspace) is an interface for userspace programs to export a filesystem to the Linux kernel. The FUSE project consists of two components: the fuse kernel module (maintained in the regular kernel repositories) and the libfuse userspace library. libfuse provides the reference implementation for communicating with the FUSE kernel module.

+

A FUSE file system is typically implemented as a standalone application that links with libfuse. libfuse provides functions to mount the file system, unmount it, read requests from the kernel, and send responses back.

+

+Getting started

+

libfuse offers two APIs: a "high-level", synchronous API, and a "low-level" asynchronous API. In both cases, incoming requests from the kernel are passed to the main program using callbacks. When using the high-level API, the callbacks may work with file names and paths instead of inodes, and processing of a request finishes when the callback function returns. When using the low-level API, the callbacks must work with inodes and responses must be sent explicitly using a separate set of API functions.

+

The high-level API that is primarily specified in fuse.h. The low-level API that is primarily documented in fuse_lowlevel.h.

+

+Examples

+

FUSE comes with several examples in the examples directory. A good starting point are hello.c (for the high-level API) and hello_ll.c (for the low-level API).

+

+FUSE internals

+

The authoritative source of information about libfuse internals (including the protocol used for communication with the FUSE kernel module) is the source code.

+

However, some people have kindly documented different aspects of FUSE in a more beginner friendly way. While this information is increasingly out of date, it still provides a good overview:

+ +
+ +
+ + +
+ + diff --git a/doc/html/invalidate__path_8c.html b/doc/html/invalidate__path_8c.html new file mode 100644 index 0000000..0566390 --- /dev/null +++ b/doc/html/invalidate__path_8c.html @@ -0,0 +1,397 @@ + + + + + + + +libfuse: example/invalidate_path.c File Reference + + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
+
invalidate_path.c File Reference
+
+
+
#include <fuse.h>
+#include <fuse_lowlevel.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <assert.h>
+#include <stddef.h>
+#include <unistd.h>
+#include <pthread.h>
+
+

Go to the source code of this file.

+

Detailed Description

+

This example implements a file system with two files:

    +
  • 'current-time', whose contents change dynamically: it always contains the current time (same as in notify_inval_inode.c).
  • +
  • 'growing', whose size changes dynamically, growing by 1 byte after each update. This aims to check if cached file metadata is also invalidated.
  • +
+

+Compilation

+
gcc -Wall invalidate_path.c `pkg-config fuse3 --cflags --libs` -o invalidate_path
+

+Source code

+
/*
+
FUSE: Filesystem in Userspace
+
Copyright (C) 2016 Nikolaus Rath <Nikolaus@rath.org>
+
(C) 2017 EditShare LLC <slawek.rudnicki@editshare.com>
+
+
This program can be distributed under the terms of the GNU GPLv2.
+
See the file GPL2.txt.
+
*/
+
+
+
#define FUSE_USE_VERSION 34
+
+
#include <fuse.h>
+
#include <fuse_lowlevel.h> /* for fuse_cmdline_opts */
+
+
#include <stdio.h>
+
#include <stdlib.h>
+
#include <string.h>
+
#include <errno.h>
+
#include <fcntl.h>
+
#include <assert.h>
+
#include <stddef.h>
+
#include <unistd.h>
+
#include <pthread.h>
+
+
/* We can't actually tell the kernel that there is no
+
timeout, so we just send a big value */
+
#define NO_TIMEOUT 500000
+
+
#define MAX_STR_LEN 128
+
#define TIME_FILE_NAME "current_time"
+
#define TIME_FILE_INO 2
+
#define GROW_FILE_NAME "growing"
+
#define GROW_FILE_INO 3
+
+
static char time_file_contents[MAX_STR_LEN];
+
static size_t grow_file_size;
+
+
/* Command line parsing */
+
struct options {
+
int no_notify;
+
int update_interval;
+
};
+
static struct options options = {
+
.no_notify = 0,
+
.update_interval = 1,
+
};
+
+
#define OPTION(t, p) { t, offsetof(struct options, p), 1 }
+
static const struct fuse_opt option_spec[] = {
+
OPTION("--no-notify", no_notify),
+
OPTION("--update-interval=%d", update_interval),
+ +
};
+
+
static void *xmp_init(struct fuse_conn_info *conn, struct fuse_config *cfg)
+
{
+
(void) conn;
+
cfg->entry_timeout = NO_TIMEOUT;
+
cfg->attr_timeout = NO_TIMEOUT;
+
cfg->negative_timeout = 0;
+
+
return NULL;
+
}
+
+
static int xmp_getattr(const char *path,
+
struct stat *stbuf, struct fuse_file_info* fi) {
+
(void) fi;
+
if (strcmp(path, "/") == 0) {
+
stbuf->st_ino = 1;
+
stbuf->st_mode = S_IFDIR | 0755;
+
stbuf->st_nlink = 1;
+
} else if (strcmp(path, "/" TIME_FILE_NAME) == 0) {
+
stbuf->st_ino = TIME_FILE_INO;
+
stbuf->st_mode = S_IFREG | 0444;
+
stbuf->st_nlink = 1;
+
stbuf->st_size = strlen(time_file_contents);
+
} else if (strcmp(path, "/" GROW_FILE_NAME) == 0) {
+
stbuf->st_ino = GROW_FILE_INO;
+
stbuf->st_mode = S_IFREG | 0444;
+
stbuf->st_nlink = 1;
+
stbuf->st_size = grow_file_size;
+
} else {
+
return -ENOENT;
+
}
+
+
return 0;
+
}
+
+
static int xmp_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
+
off_t offset, struct fuse_file_info *fi,
+
enum fuse_readdir_flags flags) {
+
(void) fi;
+
(void) offset;
+
(void) flags;
+
if (strcmp(path, "/") != 0) {
+
return -ENOTDIR;
+
} else {
+
(void) filler;
+
(void) buf;
+
struct stat file_stat;
+
xmp_getattr("/" TIME_FILE_NAME, &file_stat, NULL);
+
filler(buf, TIME_FILE_NAME, &file_stat, 0, FUSE_FILL_DIR_DEFAULTS);
+
xmp_getattr("/" GROW_FILE_NAME, &file_stat, NULL);
+
filler(buf, GROW_FILE_NAME, &file_stat, 0, FUSE_FILL_DIR_DEFAULTS);
+
return 0;
+
}
+
}
+
+
static int xmp_open(const char *path, struct fuse_file_info *fi) {
+
(void) path;
+
/* Make cache persistent even if file is closed,
+
this makes it easier to see the effects */
+
fi->keep_cache = 1;
+
return 0;
+
}
+
+
static int xmp_read(const char *path, char *buf, size_t size, off_t offset,
+
struct fuse_file_info *fi) {
+
(void) fi;
+
(void) offset;
+
if (strcmp(path, "/" TIME_FILE_NAME) == 0) {
+
int file_length = strlen(time_file_contents);
+
int to_copy = offset + size <= file_length
+
? size
+
: file_length - offset;
+
memcpy(buf, time_file_contents, to_copy);
+
return to_copy;
+
} else {
+
assert(strcmp(path, "/" GROW_FILE_NAME) == 0);
+
int to_copy = offset + size <= grow_file_size
+
? size
+
: grow_file_size - offset;
+
memset(buf, 'x', to_copy);
+
return to_copy;
+
}
+
}
+
+
static const struct fuse_operations xmp_oper = {
+
.init = xmp_init,
+
.getattr = xmp_getattr,
+
.readdir = xmp_readdir,
+
.open = xmp_open,
+
.read = xmp_read,
+
};
+
+
static void update_fs(void) {
+
static int count = 0;
+
struct tm tmbuf;
+
struct tm *now;
+
time_t t;
+
t = time(NULL);
+
now = localtime_r(&t, &tmbuf);
+
assert(now != NULL);
+
+
int time_file_size = strftime(time_file_contents, MAX_STR_LEN,
+
"The current time is %H:%M:%S\n", now);
+
assert(time_file_size != 0);
+
+
grow_file_size = count++;
+
}
+
+
static int invalidate(struct fuse *fuse, const char *path) {
+
int status = fuse_invalidate_path(fuse, path);
+
if (status == -ENOENT) {
+
return 0;
+
} else {
+
return status;
+
}
+
}
+
+
static void* update_fs_loop(void *data) {
+
struct fuse *fuse = (struct fuse*) data;
+
+
while (1) {
+
update_fs();
+
if (!options.no_notify) {
+
assert(invalidate(fuse, "/" TIME_FILE_NAME) == 0);
+
assert(invalidate(fuse, "/" GROW_FILE_NAME) == 0);
+
}
+
sleep(options.update_interval);
+
}
+
return NULL;
+
}
+
+
static void show_help(const char *progname)
+
{
+
printf("usage: %s [options] <mountpoint>\n\n", progname);
+
printf("File-system specific options:\n"
+
" --update-interval=<secs> Update-rate of file system contents\n"
+
" --no-notify Disable kernel notifications\n"
+
"\n");
+
}
+
+
int main(int argc, char *argv[]) {
+
struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
struct fuse *fuse;
+
struct fuse_cmdline_opts opts;
+
struct fuse_loop_config config;
+
int res;
+
+
/* Initialize the files */
+
update_fs();
+
+
if (fuse_opt_parse(&args, &options, option_spec, NULL) == -1)
+
return 1;
+
+
if (fuse_parse_cmdline(&args, &opts) != 0)
+
return 1;
+
+
if (opts.show_version) {
+
printf("FUSE library version %s\n", fuse_pkgversion());
+ +
res = 0;
+
goto out1;
+
} else if (opts.show_help) {
+
show_help(argv[0]);
+ +
fuse_lib_help(&args);
+
res = 0;
+
goto out1;
+
} else if (!opts.mountpoint) {
+
fprintf(stderr, "error: no mountpoint specified\n");
+
res = 1;
+
goto out1;
+
}
+
+
fuse = fuse_new(&args, &xmp_oper, sizeof(xmp_oper), NULL);
+
if (fuse == NULL) {
+
res = 1;
+
goto out1;
+
}
+
+
if (fuse_mount(fuse,opts.mountpoint) != 0) {
+
res = 1;
+
goto out2;
+
}
+
+
if (fuse_daemonize(opts.foreground) != 0) {
+
res = 1;
+
goto out3;
+
}
+
+
pthread_t updater; /* Start thread to update file contents */
+
int ret = pthread_create(&updater, NULL, update_fs_loop, (void *) fuse);
+
if (ret != 0) {
+
fprintf(stderr, "pthread_create failed with %s\n", strerror(ret));
+
return 1;
+
};
+
+
struct fuse_session *se = fuse_get_session(fuse);
+
if (fuse_set_signal_handlers(se) != 0) {
+
res = 1;
+
goto out3;
+
}
+
+
if (opts.singlethread)
+
res = fuse_loop(fuse);
+
else {
+
config.clone_fd = opts.clone_fd;
+
config.max_idle_threads = opts.max_idle_threads;
+
res = fuse_loop_mt(fuse, &config);
+
}
+
if (res)
+
res = 1;
+
+ +
out3:
+
fuse_unmount(fuse);
+
out2:
+
fuse_destroy(fuse);
+
out1:
+
free(opts.mountpoint);
+ +
return res;
+
}
+ +
int fuse_mount(struct fuse *f, const char *mountpoint)
Definition fuse.c:5260
+
void fuse_destroy(struct fuse *f)
Definition fuse.c:5209
+
int fuse_invalidate_path(struct fuse *f, const char *path)
Definition fuse.c:4740
+
int fuse_loop(struct fuse *f)
Definition fuse.c:4644
+
int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
Definition fuse.h:93
+
void fuse_lib_help(struct fuse_args *args)
Definition fuse.c:4813
+
struct fuse_session * fuse_get_session(struct fuse *f)
Definition fuse.c:4588
+
void fuse_unmount(struct fuse *f)
Definition fuse.c:5265
+
@ FUSE_FILL_DIR_DEFAULTS
Definition fuse.h:74
+
fuse_readdir_flags
Definition fuse.h:45
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
const char * fuse_pkgversion(void)
Definition fuse.c:5274
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
int fuse_daemonize(int foreground)
Definition helper.c:253
+ +
void fuse_cmdline_help(void)
Definition helper.c:130
+
void fuse_lowlevel_version(void)
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+ + +
char ** argv
Definition fuse_opt.h:114
+ + +
double entry_timeout
Definition fuse.h:133
+
double negative_timeout
Definition fuse.h:143
+
double attr_timeout
Definition fuse.h:149
+ + +
uint32_t keep_cache
Definition fuse_common.h:69
+ + + +
unsigned long offset
Definition fuse_opt.h:85
+
+

Definition in file invalidate_path.c.

+
+ + +
+ + diff --git a/doc/html/invalidate__path_8c_source.html b/doc/html/invalidate__path_8c_source.html new file mode 100644 index 0000000..201d3c8 --- /dev/null +++ b/doc/html/invalidate__path_8c_source.html @@ -0,0 +1,372 @@ + + + + + + + +libfuse: example/invalidate_path.c Source File + + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
+
invalidate_path.c
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE: Filesystem in Userspace
+
3 Copyright (C) 2016 Nikolaus Rath <Nikolaus@rath.org>
+
4 (C) 2017 EditShare LLC <slawek.rudnicki@editshare.com>
+
5
+
6 This program can be distributed under the terms of the GNU GPLv2.
+
7 See the file GPL2.txt.
+
8 */
+
9
+
27
+
28#define FUSE_USE_VERSION 34
+
29
+
30#include <fuse.h>
+
31#include <fuse_lowlevel.h> /* for fuse_cmdline_opts */
+
32
+
33#include <stdio.h>
+
34#include <stdlib.h>
+
35#include <string.h>
+
36#include <errno.h>
+
37#include <fcntl.h>
+
38#include <assert.h>
+
39#include <stddef.h>
+
40#include <unistd.h>
+
41#include <pthread.h>
+
42
+
43/* We can't actually tell the kernel that there is no
+
44 timeout, so we just send a big value */
+
45#define NO_TIMEOUT 500000
+
46
+
47#define MAX_STR_LEN 128
+
48#define TIME_FILE_NAME "current_time"
+
49#define TIME_FILE_INO 2
+
50#define GROW_FILE_NAME "growing"
+
51#define GROW_FILE_INO 3
+
52
+
53static char time_file_contents[MAX_STR_LEN];
+
54static size_t grow_file_size;
+
55
+
56/* Command line parsing */
+
57struct options {
+
58 int no_notify;
+
59 int update_interval;
+
60};
+
61static struct options options = {
+
62 .no_notify = 0,
+
63 .update_interval = 1,
+
64};
+
65
+
66#define OPTION(t, p) { t, offsetof(struct options, p), 1 }
+
67static const struct fuse_opt option_spec[] = {
+
68 OPTION("--no-notify", no_notify),
+
69 OPTION("--update-interval=%d", update_interval),
+ +
71};
+
72
+
73static void *xmp_init(struct fuse_conn_info *conn, struct fuse_config *cfg)
+
74{
+
75 (void) conn;
+
76 cfg->entry_timeout = NO_TIMEOUT;
+
77 cfg->attr_timeout = NO_TIMEOUT;
+
78 cfg->negative_timeout = 0;
+
79
+
80 return NULL;
+
81}
+
82
+
83static int xmp_getattr(const char *path,
+
84 struct stat *stbuf, struct fuse_file_info* fi) {
+
85 (void) fi;
+
86 if (strcmp(path, "/") == 0) {
+
87 stbuf->st_ino = 1;
+
88 stbuf->st_mode = S_IFDIR | 0755;
+
89 stbuf->st_nlink = 1;
+
90 } else if (strcmp(path, "/" TIME_FILE_NAME) == 0) {
+
91 stbuf->st_ino = TIME_FILE_INO;
+
92 stbuf->st_mode = S_IFREG | 0444;
+
93 stbuf->st_nlink = 1;
+
94 stbuf->st_size = strlen(time_file_contents);
+
95 } else if (strcmp(path, "/" GROW_FILE_NAME) == 0) {
+
96 stbuf->st_ino = GROW_FILE_INO;
+
97 stbuf->st_mode = S_IFREG | 0444;
+
98 stbuf->st_nlink = 1;
+
99 stbuf->st_size = grow_file_size;
+
100 } else {
+
101 return -ENOENT;
+
102 }
+
103
+
104 return 0;
+
105}
+
106
+
107static int xmp_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
+
108 off_t offset, struct fuse_file_info *fi,
+
109 enum fuse_readdir_flags flags) {
+
110 (void) fi;
+
111 (void) offset;
+
112 (void) flags;
+
113 if (strcmp(path, "/") != 0) {
+
114 return -ENOTDIR;
+
115 } else {
+
116 (void) filler;
+
117 (void) buf;
+
118 struct stat file_stat;
+
119 xmp_getattr("/" TIME_FILE_NAME, &file_stat, NULL);
+
120 filler(buf, TIME_FILE_NAME, &file_stat, 0, FUSE_FILL_DIR_DEFAULTS);
+
121 xmp_getattr("/" GROW_FILE_NAME, &file_stat, NULL);
+
122 filler(buf, GROW_FILE_NAME, &file_stat, 0, FUSE_FILL_DIR_DEFAULTS);
+
123 return 0;
+
124 }
+
125}
+
126
+
127static int xmp_open(const char *path, struct fuse_file_info *fi) {
+
128 (void) path;
+
129 /* Make cache persistent even if file is closed,
+
130 this makes it easier to see the effects */
+
131 fi->keep_cache = 1;
+
132 return 0;
+
133}
+
134
+
135static int xmp_read(const char *path, char *buf, size_t size, off_t offset,
+
136 struct fuse_file_info *fi) {
+
137 (void) fi;
+
138 (void) offset;
+
139 if (strcmp(path, "/" TIME_FILE_NAME) == 0) {
+
140 int file_length = strlen(time_file_contents);
+
141 int to_copy = offset + size <= file_length
+
142 ? size
+
143 : file_length - offset;
+
144 memcpy(buf, time_file_contents, to_copy);
+
145 return to_copy;
+
146 } else {
+
147 assert(strcmp(path, "/" GROW_FILE_NAME) == 0);
+
148 int to_copy = offset + size <= grow_file_size
+
149 ? size
+
150 : grow_file_size - offset;
+
151 memset(buf, 'x', to_copy);
+
152 return to_copy;
+
153 }
+
154}
+
155
+
156static const struct fuse_operations xmp_oper = {
+
157 .init = xmp_init,
+
158 .getattr = xmp_getattr,
+
159 .readdir = xmp_readdir,
+
160 .open = xmp_open,
+
161 .read = xmp_read,
+
162};
+
163
+
164static void update_fs(void) {
+
165 static int count = 0;
+
166 struct tm tmbuf;
+
167 struct tm *now;
+
168 time_t t;
+
169 t = time(NULL);
+
170 now = localtime_r(&t, &tmbuf);
+
171 assert(now != NULL);
+
172
+
173 int time_file_size = strftime(time_file_contents, MAX_STR_LEN,
+
174 "The current time is %H:%M:%S\n", now);
+
175 assert(time_file_size != 0);
+
176
+
177 grow_file_size = count++;
+
178}
+
179
+
180static int invalidate(struct fuse *fuse, const char *path) {
+
181 int status = fuse_invalidate_path(fuse, path);
+
182 if (status == -ENOENT) {
+
183 return 0;
+
184 } else {
+
185 return status;
+
186 }
+
187}
+
188
+
189static void* update_fs_loop(void *data) {
+
190 struct fuse *fuse = (struct fuse*) data;
+
191
+
192 while (1) {
+
193 update_fs();
+
194 if (!options.no_notify) {
+
195 assert(invalidate(fuse, "/" TIME_FILE_NAME) == 0);
+
196 assert(invalidate(fuse, "/" GROW_FILE_NAME) == 0);
+
197 }
+
198 sleep(options.update_interval);
+
199 }
+
200 return NULL;
+
201}
+
202
+
203static void show_help(const char *progname)
+
204{
+
205 printf("usage: %s [options] <mountpoint>\n\n", progname);
+
206 printf("File-system specific options:\n"
+
207 " --update-interval=<secs> Update-rate of file system contents\n"
+
208 " --no-notify Disable kernel notifications\n"
+
209 "\n");
+
210}
+
211
+
212int main(int argc, char *argv[]) {
+
213 struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
214 struct fuse *fuse;
+
215 struct fuse_cmdline_opts opts;
+
216 struct fuse_loop_config config;
+
217 int res;
+
218
+
219 /* Initialize the files */
+
220 update_fs();
+
221
+
222 if (fuse_opt_parse(&args, &options, option_spec, NULL) == -1)
+
223 return 1;
+
224
+
225 if (fuse_parse_cmdline(&args, &opts) != 0)
+
226 return 1;
+
227
+
228 if (opts.show_version) {
+
229 printf("FUSE library version %s\n", fuse_pkgversion());
+ +
231 res = 0;
+
232 goto out1;
+
233 } else if (opts.show_help) {
+
234 show_help(argv[0]);
+ +
236 fuse_lib_help(&args);
+
237 res = 0;
+
238 goto out1;
+
239 } else if (!opts.mountpoint) {
+
240 fprintf(stderr, "error: no mountpoint specified\n");
+
241 res = 1;
+
242 goto out1;
+
243 }
+
244
+
245 fuse = fuse_new(&args, &xmp_oper, sizeof(xmp_oper), NULL);
+
246 if (fuse == NULL) {
+
247 res = 1;
+
248 goto out1;
+
249 }
+
250
+
251 if (fuse_mount(fuse,opts.mountpoint) != 0) {
+
252 res = 1;
+
253 goto out2;
+
254 }
+
255
+
256 if (fuse_daemonize(opts.foreground) != 0) {
+
257 res = 1;
+
258 goto out3;
+
259 }
+
260
+
261 pthread_t updater; /* Start thread to update file contents */
+
262 int ret = pthread_create(&updater, NULL, update_fs_loop, (void *) fuse);
+
263 if (ret != 0) {
+
264 fprintf(stderr, "pthread_create failed with %s\n", strerror(ret));
+
265 return 1;
+
266 };
+
267
+
268 struct fuse_session *se = fuse_get_session(fuse);
+
269 if (fuse_set_signal_handlers(se) != 0) {
+
270 res = 1;
+
271 goto out3;
+
272 }
+
273
+
274 if (opts.singlethread)
+
275 res = fuse_loop(fuse);
+
276 else {
+
277 config.clone_fd = opts.clone_fd;
+
278 config.max_idle_threads = opts.max_idle_threads;
+
279 res = fuse_loop_mt(fuse, &config);
+
280 }
+
281 if (res)
+
282 res = 1;
+
283
+ +
285out3:
+
286 fuse_unmount(fuse);
+
287out2:
+
288 fuse_destroy(fuse);
+
289out1:
+
290 free(opts.mountpoint);
+
291 fuse_opt_free_args(&args);
+
292 return res;
+
293}
+ +
int fuse_mount(struct fuse *f, const char *mountpoint)
Definition fuse.c:5260
+
void fuse_destroy(struct fuse *f)
Definition fuse.c:5209
+
int fuse_invalidate_path(struct fuse *f, const char *path)
Definition fuse.c:4740
+
int fuse_loop(struct fuse *f)
Definition fuse.c:4644
+
int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
Definition fuse.h:93
+
void fuse_lib_help(struct fuse_args *args)
Definition fuse.c:4813
+
struct fuse_session * fuse_get_session(struct fuse *f)
Definition fuse.c:4588
+
void fuse_unmount(struct fuse *f)
Definition fuse.c:5265
+
@ FUSE_FILL_DIR_DEFAULTS
Definition fuse.h:74
+
fuse_readdir_flags
Definition fuse.h:45
+
int fuse_set_signal_handlers(struct fuse_session *se)
+
const char * fuse_pkgversion(void)
Definition fuse.c:5274
+
void fuse_remove_signal_handlers(struct fuse_session *se)
+
int fuse_daemonize(int foreground)
Definition helper.c:253
+ +
void fuse_cmdline_help(void)
Definition helper.c:130
+
void fuse_lowlevel_version(void)
+
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
+
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
+
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
+
#define FUSE_OPT_END
Definition fuse_opt.h:104
+ + +
char ** argv
Definition fuse_opt.h:114
+ + +
double entry_timeout
Definition fuse.h:133
+
double negative_timeout
Definition fuse.h:143
+
double attr_timeout
Definition fuse.h:149
+ + +
uint32_t keep_cache
Definition fuse_common.h:69
+ + + +
unsigned long offset
Definition fuse_opt.h:85
+
+ + +
+ + diff --git a/doc/html/ioctl_8c.html b/doc/html/ioctl_8c.html new file mode 100644 index 0000000..e45bc34 --- /dev/null +++ b/doc/html/ioctl_8c.html @@ -0,0 +1,300 @@ + + + + + + + +libfuse: example/ioctl.c File Reference + + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
+
ioctl.c File Reference
+
+
+
#include <fuse.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <time.h>
+#include <errno.h>
+#include "ioctl.h"
+
+

Go to the source code of this file.

+

Detailed Description

+

This example illustrates how to write a FUSE file system that can process (a restricted set of) ioctls. It can be tested with the ioctl_client.c program.

+

Compile with:

gcc -Wall ioctl.c `pkg-config fuse3 --cflags --libs` -o ioctl
+

+Source code

+
/*
+
FUSE fioc: FUSE ioctl example
+
Copyright (C) 2008 SUSE Linux Products GmbH
+
Copyright (C) 2008 Tejun Heo <teheo@suse.de>
+
+
This program can be distributed under the terms of the GNU GPLv2.
+
See the file GPL2.txt.
+
*/
+
+
+
#define FUSE_USE_VERSION 35
+
+
#include <fuse.h>
+
#include <stdlib.h>
+
#include <stdio.h>
+
#include <string.h>
+
#include <unistd.h>
+
#include <time.h>
+
#include <errno.h>
+
+
#include "ioctl.h"
+
+
#define FIOC_NAME "fioc"
+
+
enum {
+
FIOC_NONE,
+
FIOC_ROOT,
+
FIOC_FILE,
+
};
+
+
static void *fioc_buf;
+
static size_t fioc_size;
+
+
static int fioc_resize(size_t new_size)
+
{
+
void *new_buf;
+
+
if (new_size == fioc_size)
+
return 0;
+
+
new_buf = realloc(fioc_buf, new_size);
+
if (!new_buf && new_size)
+
return -ENOMEM;
+
+
if (new_size > fioc_size)
+
memset(new_buf + fioc_size, 0, new_size - fioc_size);
+
+
fioc_buf = new_buf;
+
fioc_size = new_size;
+
+
return 0;
+
}
+
+
static int fioc_expand(size_t new_size)
+
{
+
if (new_size > fioc_size)
+
return fioc_resize(new_size);
+
return 0;
+
}
+
+
static int fioc_file_type(const char *path)
+
{
+
if (strcmp(path, "/") == 0)
+
return FIOC_ROOT;
+
if (strcmp(path, "/" FIOC_NAME) == 0)
+
return FIOC_FILE;
+
return FIOC_NONE;
+
}
+
+
static int fioc_getattr(const char *path, struct stat *stbuf,
+
struct fuse_file_info *fi)
+
{
+
(void) fi;
+
stbuf->st_uid = getuid();
+
stbuf->st_gid = getgid();
+
stbuf->st_atime = stbuf->st_mtime = time(NULL);
+
+
switch (fioc_file_type(path)) {
+
case FIOC_ROOT:
+
stbuf->st_mode = S_IFDIR | 0755;
+
stbuf->st_nlink = 2;
+
break;
+
case FIOC_FILE:
+
stbuf->st_mode = S_IFREG | 0644;
+
stbuf->st_nlink = 1;
+
stbuf->st_size = fioc_size;
+
break;
+
case FIOC_NONE:
+
return -ENOENT;
+
}
+
+
return 0;
+
}
+
+
static int fioc_open(const char *path, struct fuse_file_info *fi)
+
{
+
(void) fi;
+
+
if (fioc_file_type(path) != FIOC_NONE)
+
return 0;
+
return -ENOENT;
+
}
+
+
static int fioc_do_read(char *buf, size_t size, off_t offset)
+
{
+
if (offset >= fioc_size)
+
return 0;
+
+
if (size > fioc_size - offset)
+
size = fioc_size - offset;
+
+
memcpy(buf, fioc_buf + offset, size);
+
+
return size;
+
}
+
+
static int fioc_read(const char *path, char *buf, size_t size,
+
off_t offset, struct fuse_file_info *fi)
+
{
+
(void) fi;
+
+
if (fioc_file_type(path) != FIOC_FILE)
+
return -EINVAL;
+
+
return fioc_do_read(buf, size, offset);
+
}
+
+
static int fioc_do_write(const char *buf, size_t size, off_t offset)
+
{
+
if (fioc_expand(offset + size))
+
return -ENOMEM;
+
+
memcpy(fioc_buf + offset, buf, size);
+
+
return size;
+
}
+
+
static int fioc_write(const char *path, const char *buf, size_t size,
+
off_t offset, struct fuse_file_info *fi)
+
{
+
(void) fi;
+
+
if (fioc_file_type(path) != FIOC_FILE)
+
return -EINVAL;
+
+
return fioc_do_write(buf, size, offset);
+
}
+
+
static int fioc_truncate(const char *path, off_t size,
+
struct fuse_file_info *fi)
+
{
+
(void) fi;
+
if (fioc_file_type(path) != FIOC_FILE)
+
return -EINVAL;
+
+
return fioc_resize(size);
+
}
+
+
static int fioc_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
+
off_t offset, struct fuse_file_info *fi,
+
enum fuse_readdir_flags flags)
+
{
+
(void) fi;
+
(void) offset;
+
(void) flags;
+
+
if (fioc_file_type(path) != FIOC_ROOT)
+
return -ENOENT;
+
+
filler(buf, ".", NULL, 0, FUSE_FILL_DIR_DEFAULTS);
+
filler(buf, "..", NULL, 0, FUSE_FILL_DIR_DEFAULTS);
+
filler(buf, FIOC_NAME, NULL, 0, FUSE_FILL_DIR_DEFAULTS);
+
+
return 0;
+
}
+
+
static int fioc_ioctl(const char *path, unsigned int cmd, void *arg,
+
struct fuse_file_info *fi, unsigned int flags, void *data)
+
{
+
(void) arg;
+
(void) fi;
+
(void) flags;
+
+
if (fioc_file_type(path) != FIOC_FILE)
+
return -EINVAL;
+
+
if (flags & FUSE_IOCTL_COMPAT)
+
return -ENOSYS;
+
+
switch (cmd) {
+
case FIOC_GET_SIZE:
+
*(size_t *)data = fioc_size;
+
return 0;
+
+
case FIOC_SET_SIZE:
+
fioc_resize(*(size_t *)data);
+
return 0;
+
}
+
+
return -EINVAL;
+
}
+
+
static const struct fuse_operations fioc_oper = {
+
.getattr = fioc_getattr,
+
.readdir = fioc_readdir,
+
.truncate = fioc_truncate,
+
.open = fioc_open,
+
.read = fioc_read,
+
.write = fioc_write,
+
.ioctl = fioc_ioctl,
+
};
+
+
int main(int argc, char *argv[])
+
{
+
return fuse_main(argc, argv, &fioc_oper, NULL);
+
}
+ +
int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
Definition fuse.h:93
+
@ FUSE_FILL_DIR_DEFAULTS
Definition fuse.h:74
+
fuse_readdir_flags
Definition fuse.h:45
+
#define FUSE_IOCTL_COMPAT
+ + + +
+

Definition in file ioctl.c.

+
+ + +
+ + diff --git a/doc/html/ioctl_8c_source.html b/doc/html/ioctl_8c_source.html new file mode 100644 index 0000000..65505ce --- /dev/null +++ b/doc/html/ioctl_8c_source.html @@ -0,0 +1,283 @@ + + + + + + + +libfuse: example/ioctl.c Source File + + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
+
ioctl.c
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE fioc: FUSE ioctl example
+
3 Copyright (C) 2008 SUSE Linux Products GmbH
+
4 Copyright (C) 2008 Tejun Heo <teheo@suse.de>
+
5
+
6 This program can be distributed under the terms of the GNU GPLv2.
+
7 See the file GPL2.txt.
+
8*/
+
9
+
24
+
25#define FUSE_USE_VERSION 35
+
26
+
27#include <fuse.h>
+
28#include <stdlib.h>
+
29#include <stdio.h>
+
30#include <string.h>
+
31#include <unistd.h>
+
32#include <time.h>
+
33#include <errno.h>
+
34
+
35#include "ioctl.h"
+
36
+
37#define FIOC_NAME "fioc"
+
38
+
39enum {
+
40 FIOC_NONE,
+
41 FIOC_ROOT,
+
42 FIOC_FILE,
+
43};
+
44
+
45static void *fioc_buf;
+
46static size_t fioc_size;
+
47
+
48static int fioc_resize(size_t new_size)
+
49{
+
50 void *new_buf;
+
51
+
52 if (new_size == fioc_size)
+
53 return 0;
+
54
+
55 new_buf = realloc(fioc_buf, new_size);
+
56 if (!new_buf && new_size)
+
57 return -ENOMEM;
+
58
+
59 if (new_size > fioc_size)
+
60 memset(new_buf + fioc_size, 0, new_size - fioc_size);
+
61
+
62 fioc_buf = new_buf;
+
63 fioc_size = new_size;
+
64
+
65 return 0;
+
66}
+
67
+
68static int fioc_expand(size_t new_size)
+
69{
+
70 if (new_size > fioc_size)
+
71 return fioc_resize(new_size);
+
72 return 0;
+
73}
+
74
+
75static int fioc_file_type(const char *path)
+
76{
+
77 if (strcmp(path, "/") == 0)
+
78 return FIOC_ROOT;
+
79 if (strcmp(path, "/" FIOC_NAME) == 0)
+
80 return FIOC_FILE;
+
81 return FIOC_NONE;
+
82}
+
83
+
84static int fioc_getattr(const char *path, struct stat *stbuf,
+
85 struct fuse_file_info *fi)
+
86{
+
87 (void) fi;
+
88 stbuf->st_uid = getuid();
+
89 stbuf->st_gid = getgid();
+
90 stbuf->st_atime = stbuf->st_mtime = time(NULL);
+
91
+
92 switch (fioc_file_type(path)) {
+
93 case FIOC_ROOT:
+
94 stbuf->st_mode = S_IFDIR | 0755;
+
95 stbuf->st_nlink = 2;
+
96 break;
+
97 case FIOC_FILE:
+
98 stbuf->st_mode = S_IFREG | 0644;
+
99 stbuf->st_nlink = 1;
+
100 stbuf->st_size = fioc_size;
+
101 break;
+
102 case FIOC_NONE:
+
103 return -ENOENT;
+
104 }
+
105
+
106 return 0;
+
107}
+
108
+
109static int fioc_open(const char *path, struct fuse_file_info *fi)
+
110{
+
111 (void) fi;
+
112
+
113 if (fioc_file_type(path) != FIOC_NONE)
+
114 return 0;
+
115 return -ENOENT;
+
116}
+
117
+
118static int fioc_do_read(char *buf, size_t size, off_t offset)
+
119{
+
120 if (offset >= fioc_size)
+
121 return 0;
+
122
+
123 if (size > fioc_size - offset)
+
124 size = fioc_size - offset;
+
125
+
126 memcpy(buf, fioc_buf + offset, size);
+
127
+
128 return size;
+
129}
+
130
+
131static int fioc_read(const char *path, char *buf, size_t size,
+
132 off_t offset, struct fuse_file_info *fi)
+
133{
+
134 (void) fi;
+
135
+
136 if (fioc_file_type(path) != FIOC_FILE)
+
137 return -EINVAL;
+
138
+
139 return fioc_do_read(buf, size, offset);
+
140}
+
141
+
142static int fioc_do_write(const char *buf, size_t size, off_t offset)
+
143{
+
144 if (fioc_expand(offset + size))
+
145 return -ENOMEM;
+
146
+
147 memcpy(fioc_buf + offset, buf, size);
+
148
+
149 return size;
+
150}
+
151
+
152static int fioc_write(const char *path, const char *buf, size_t size,
+
153 off_t offset, struct fuse_file_info *fi)
+
154{
+
155 (void) fi;
+
156
+
157 if (fioc_file_type(path) != FIOC_FILE)
+
158 return -EINVAL;
+
159
+
160 return fioc_do_write(buf, size, offset);
+
161}
+
162
+
163static int fioc_truncate(const char *path, off_t size,
+
164 struct fuse_file_info *fi)
+
165{
+
166 (void) fi;
+
167 if (fioc_file_type(path) != FIOC_FILE)
+
168 return -EINVAL;
+
169
+
170 return fioc_resize(size);
+
171}
+
172
+
173static int fioc_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
+
174 off_t offset, struct fuse_file_info *fi,
+
175 enum fuse_readdir_flags flags)
+
176{
+
177 (void) fi;
+
178 (void) offset;
+
179 (void) flags;
+
180
+
181 if (fioc_file_type(path) != FIOC_ROOT)
+
182 return -ENOENT;
+
183
+
184 filler(buf, ".", NULL, 0, FUSE_FILL_DIR_DEFAULTS);
+
185 filler(buf, "..", NULL, 0, FUSE_FILL_DIR_DEFAULTS);
+
186 filler(buf, FIOC_NAME, NULL, 0, FUSE_FILL_DIR_DEFAULTS);
+
187
+
188 return 0;
+
189}
+
190
+
191static int fioc_ioctl(const char *path, unsigned int cmd, void *arg,
+
192 struct fuse_file_info *fi, unsigned int flags, void *data)
+
193{
+
194 (void) arg;
+
195 (void) fi;
+
196 (void) flags;
+
197
+
198 if (fioc_file_type(path) != FIOC_FILE)
+
199 return -EINVAL;
+
200
+
201 if (flags & FUSE_IOCTL_COMPAT)
+
202 return -ENOSYS;
+
203
+
204 switch (cmd) {
+
205 case FIOC_GET_SIZE:
+
206 *(size_t *)data = fioc_size;
+
207 return 0;
+
208
+
209 case FIOC_SET_SIZE:
+
210 fioc_resize(*(size_t *)data);
+
211 return 0;
+
212 }
+
213
+
214 return -EINVAL;
+
215}
+
216
+
217static const struct fuse_operations fioc_oper = {
+
218 .getattr = fioc_getattr,
+
219 .readdir = fioc_readdir,
+
220 .truncate = fioc_truncate,
+
221 .open = fioc_open,
+
222 .read = fioc_read,
+
223 .write = fioc_write,
+
224 .ioctl = fioc_ioctl,
+
225};
+
226
+
227int main(int argc, char *argv[])
+
228{
+
229 return fuse_main(argc, argv, &fioc_oper, NULL);
+
230}
+ +
int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
Definition fuse.h:93
+
@ FUSE_FILL_DIR_DEFAULTS
Definition fuse.h:74
+
fuse_readdir_flags
Definition fuse.h:45
+
#define FUSE_IOCTL_COMPAT
+ + + +
+ + +
+ + diff --git a/doc/html/ioctl_8h.html b/doc/html/ioctl_8h.html new file mode 100644 index 0000000..6c9a570 --- /dev/null +++ b/doc/html/ioctl_8h.html @@ -0,0 +1,102 @@ + + + + + + + +libfuse: example/ioctl.h File Reference + + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
+
ioctl.h File Reference
+
+
+
#include <sys/types.h>
+#include <sys/uio.h>
+#include <sys/ioctl.h>
+
+

Go to the source code of this file.

+

Detailed Description

+

Header file to share definitions between the ioctl.c example file system and the ioctl_client.c test program.

+
/*
+
FUSE-ioctl: ioctl support for FUSE
+
Copyright (C) 2008 SUSE Linux Products GmbH
+
Copyright (C) 2008 Tejun Heo <teheo@suse.de>
+
+
This program can be distributed under the terms of the GNU GPLv2.
+
See the file GPL2.txt.
+
*/
+
+
+
+
#include <sys/types.h>
+
#include <sys/uio.h>
+
#include <sys/ioctl.h>
+
+
enum {
+
FIOC_GET_SIZE = _IOR('E', 0, size_t),
+
FIOC_SET_SIZE = _IOW('E', 1, size_t),
+
+
/*
+
* The following two ioctls don't follow usual encoding rules
+
* and transfer variable amount of data.
+
*/
+
FIOC_READ = _IO('E', 2),
+
FIOC_WRITE = _IO('E', 3),
+
};
+
+
struct fioc_rw_arg {
+
off_t offset;
+
void *buf;
+
size_t size;
+
size_t prev_size; /* out param for previous total size */
+
size_t new_size; /* out param for new total size */
+
};
+
+

Definition in file ioctl.h.

+
+ + +
+ + diff --git a/doc/html/ioctl_8h_source.html b/doc/html/ioctl_8h_source.html new file mode 100644 index 0000000..3681196 --- /dev/null +++ b/doc/html/ioctl_8h_source.html @@ -0,0 +1,93 @@ + + + + + + + +libfuse: example/ioctl.h Source File + + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
+
ioctl.h
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE-ioctl: ioctl support for FUSE
+
3 Copyright (C) 2008 SUSE Linux Products GmbH
+
4 Copyright (C) 2008 Tejun Heo <teheo@suse.de>
+
5
+
6 This program can be distributed under the terms of the GNU GPLv2.
+
7 See the file GPL2.txt.
+
8*/
+
9
+
18
+
19
+
20#include <sys/types.h>
+
21#include <sys/uio.h>
+
22#include <sys/ioctl.h>
+
23
+
24enum {
+
25 FIOC_GET_SIZE = _IOR('E', 0, size_t),
+
26 FIOC_SET_SIZE = _IOW('E', 1, size_t),
+
27
+
28 /*
+
29 * The following two ioctls don't follow usual encoding rules
+
30 * and transfer variable amount of data.
+
31 */
+
32 FIOC_READ = _IO('E', 2),
+
33 FIOC_WRITE = _IO('E', 3),
+
34};
+
35
+
36struct fioc_rw_arg {
+
37 off_t offset;
+
38 void *buf;
+
39 size_t size;
+
40 size_t prev_size; /* out param for previous total size */
+
41 size_t new_size; /* out param for new total size */
+
42};
+
+ + +
+ + diff --git a/doc/html/ioctl__client_8c.html b/doc/html/ioctl__client_8c.html new file mode 100644 index 0000000..5a963e0 --- /dev/null +++ b/doc/html/ioctl__client_8c.html @@ -0,0 +1,142 @@ + + + + + + + +libfuse: example/ioctl_client.c File Reference + + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
+
ioctl_client.c File Reference
+
+
+
#include <sys/types.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/ioctl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <errno.h>
+#include <unistd.h>
+#include "ioctl.h"
+
+

Go to the source code of this file.

+

Detailed Description

+

This program tests the ioctl.c example file systsem.

+

Compile with:

gcc -Wall ioctl_client.c -o ioctl_client
+

+Source code

+
/*
+
FUSE fioclient: FUSE ioctl example client
+
Copyright (C) 2008 SUSE Linux Products GmbH
+
Copyright (C) 2008 Tejun Heo <teheo@suse.de>
+
+
This program can be distributed under the terms of the GNU GPLv2.
+
See the file GPL2.txt.
+
*/
+
+
+
#include <sys/types.h>
+
#include <fcntl.h>
+
#include <sys/stat.h>
+
#include <sys/ioctl.h>
+
#include <stdio.h>
+
#include <stdlib.h>
+
#include <ctype.h>
+
#include <errno.h>
+
#include <unistd.h>
+
#include "ioctl.h"
+
+
const char *usage =
+
"Usage: fioclient FIOC_FILE [size]\n"
+
"\n"
+
"Get size if <size> is omitted, set size otherwise\n"
+
"\n";
+
+
int main(int argc, char **argv)
+
{
+
size_t size;
+
int fd;
+
int ret = 0;
+
+
if (argc < 2) {
+
fprintf(stderr, "%s", usage);
+
return 1;
+
}
+
+
fd = open(argv[1], O_RDWR);
+
if (fd < 0) {
+
perror("open");
+
return 1;
+
}
+
+
if (argc == 2) {
+
if (ioctl(fd, FIOC_GET_SIZE, &size)) {
+
perror("ioctl");
+
ret = 1;
+
goto out;
+
}
+
printf("%zu\n", size);
+
} else {
+
size = strtoul(argv[2], NULL, 0);
+
if (ioctl(fd, FIOC_SET_SIZE, &size)) {
+
perror("ioctl");
+
ret = 1;
+
goto out;
+
}
+
}
+
out:
+
close(fd);
+
return ret;
+
}
+ +
+

Definition in file ioctl_client.c.

+
+ + +
+ + diff --git a/doc/html/ioctl__client_8c_source.html b/doc/html/ioctl__client_8c_source.html new file mode 100644 index 0000000..e75fd7f --- /dev/null +++ b/doc/html/ioctl__client_8c_source.html @@ -0,0 +1,123 @@ + + + + + + + +libfuse: example/ioctl_client.c Source File + + + + + + + +
+
+ + + + + + +
+
libfuse +
+
+
+ + + + + + + + +
+
+
+
ioctl_client.c
+
+
+Go to the documentation of this file.
1/*
+
2 FUSE fioclient: FUSE ioctl example client
+
3 Copyright (C) 2008 SUSE Linux Products GmbH
+
4 Copyright (C) 2008 Tejun Heo <teheo@suse.de>
+
5
+
6 This program can be distributed under the terms of the GNU GPLv2.
+
7 See the file GPL2.txt.
+
8*/
+
9
+
21
+
22#include <sys/types.h>
+
23#include <fcntl.h>
+
24#include <sys/stat.h>
+
25#include <sys/ioctl.h>
+
26#include <stdio.h>
+
27#include <stdlib.h>
+
28#include <ctype.h>
+
29#include <errno.h>
+
30#include <unistd.h>
+
31#include "ioctl.h"
+
32
+
33const char *usage =
+
34"Usage: fioclient FIOC_FILE [size]\n"
+
35"\n"
+
36"Get size if <size> is omitted, set size otherwise\n"
+
37"\n";
+
38
+
39int main(int argc, char **argv)
+
40{
+
41 size_t size;
+
42 int fd;
+
43 int ret = 0;
+
44
+
45 if (argc < 2) {
+
46 fprintf(stderr, "%s", usage);
+
47 return 1;
+
48 }
+
49
+
50 fd = open(argv[1], O_RDWR);
+
51 if (fd < 0) {
+
52 perror("open");
+
53 return 1;
+
54 }
+
55
+
56 if (argc == 2) {
+
57 if (ioctl(fd, FIOC_GET_SIZE, &size)) {
+
58 perror("ioctl");
+
59 ret = 1;
+
60 goto out;
+
61 }
+
62 printf("%zu\n", size);
+
63 } else {
+
64 size = strtoul(argv[2], NULL, 0);
+
65 if (ioctl(fd, FIOC_SET_SIZE, &size)) {
+
66 perror("ioctl");
+
67 ret = 1;
+
68 goto out;
+
69 }
+
70 }
+
71out:
+
72 close(fd);
+
73 return ret;
+
74}
+ +
+ + +
+ + diff --git a/doc/html/jquery.js b/doc/html/jquery.js new file mode 100644 index 0000000..875ada7 --- /dev/null +++ b/doc/html/jquery.js @@ -0,0 +1,204 @@ +/*! jQuery v3.6.0 | (c) OpenJS Foundation and other contributors | jquery.org/license */ +!function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(C,e){"use strict";var t=[],r=Object.getPrototypeOf,s=t.slice,g=t.flat?function(e){return t.flat.call(e)}:function(e){return t.concat.apply([],e)},u=t.push,i=t.indexOf,n={},o=n.toString,v=n.hasOwnProperty,a=v.toString,l=a.call(Object),y={},m=function(e){return"function"==typeof e&&"number"!=typeof e.nodeType&&"function"!=typeof e.item},x=function(e){return null!=e&&e===e.window},E=C.document,c={type:!0,src:!0,nonce:!0,noModule:!0};function b(e,t,n){var r,i,o=(n=n||E).createElement("script");if(o.text=e,t)for(r in c)(i=t[r]||t.getAttribute&&t.getAttribute(r))&&o.setAttribute(r,i);n.head.appendChild(o).parentNode.removeChild(o)}function w(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?n[o.call(e)]||"object":typeof e} +var f="3.6.0",S=function(e,t){return new S.fn.init(e,t)};function p(e){var t=!!e&&"length"in e&&e.length,n=w(e);return!m(e)&&!x(e)&&("array"===n||0===t||"number"==typeof t&&0+~]|"+M+")"+M+"*"),U=new RegExp(M+"|>"),X=new RegExp(F),V=new RegExp("^"+I+"$"),G={ID:new RegExp("^#("+I+")"),CLASS:new RegExp("^\\.("+I+")"),TAG:new RegExp("^("+I+"|[*])"),ATTR:new RegExp("^"+W),PSEUDO:new RegExp("^"+F),CHILD:new RegExp( +"^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+M+"*(even|odd|(([+-]|)(\\d*)n|)"+M+"*(?:([+-]|)"+M+"*(\\d+)|))"+M+"*\\)|)","i"),bool:new RegExp("^(?:"+R+")$","i"),needsContext:new RegExp("^"+M+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+M+"*((?:-\\d)?\\d*)"+M+"*\\)|)(?=[^-]|$)","i")},Y=/HTML$/i,Q=/^(?:input|select|textarea|button)$/i,J=/^h\d$/i,K=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ee=/[+~]/,te=new RegExp("\\\\[\\da-fA-F]{1,6}"+M+"?|\\\\([^\\r\\n\\f])","g"),ne=function(e,t){var n="0x"+e.slice(1)-65536;return t||(n<0?String.fromCharCode(n+65536):String.fromCharCode(n>>10|55296,1023&n|56320))},re=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ie=function(e,t){return t?"\0"===e?"�":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e},oe=function(){T()},ae=be(function(e){return!0===e.disabled&&"fieldset"===e.nodeName.toLowerCase()},{dir:"parentNode",next:"legend"});try{H.apply(t=O.call(p.childNodes),p.childNodes),t[p.childNodes.length].nodeType +}catch(e){H={apply:t.length?function(e,t){L.apply(e,O.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function se(t,e,n,r){var i,o,a,s,u,l,c,f=e&&e.ownerDocument,p=e?e.nodeType:9;if(n=n||[],"string"!=typeof t||!t||1!==p&&9!==p&&11!==p)return n;if(!r&&(T(e),e=e||C,E)){if(11!==p&&(u=Z.exec(t)))if(i=u[1]){if(9===p){if(!(a=e.getElementById(i)))return n;if(a.id===i)return n.push(a),n}else if(f&&(a=f.getElementById(i))&&y(e,a)&&a.id===i)return n.push(a),n}else{if(u[2])return H.apply(n,e.getElementsByTagName(t)),n;if((i=u[3])&&d.getElementsByClassName&&e.getElementsByClassName)return H.apply(n,e.getElementsByClassName(i)),n}if(d.qsa&&!N[t+" "]&&(!v||!v.test(t))&&(1!==p||"object"!==e.nodeName.toLowerCase())){if(c=t,f=e,1===p&&(U.test(t)||z.test(t))){(f=ee.test(t)&&ye(e.parentNode)||e)===e&&d.scope||((s=e.getAttribute("id"))?s=s.replace(re,ie):e.setAttribute("id",s=S)),o=(l=h(t)).length;while(o--)l[o]=(s?"#"+s:":scope")+" "+xe(l[o]);c=l.join(",")}try{return H.apply(n,f.querySelectorAll(c +)),n}catch(e){N(t,!0)}finally{s===S&&e.removeAttribute("id")}}}return g(t.replace($,"$1"),e,n,r)}function ue(){var r=[];return function e(t,n){return r.push(t+" ")>b.cacheLength&&delete e[r.shift()],e[t+" "]=n}}function le(e){return e[S]=!0,e}function ce(e){var t=C.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function fe(e,t){var n=e.split("|"),r=n.length;while(r--)b.attrHandle[n[r]]=t}function pe(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&e.sourceIndex-t.sourceIndex;if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function de(t){return function(e){return"input"===e.nodeName.toLowerCase()&&e.type===t}}function he(n){return function(e){var t=e.nodeName.toLowerCase();return("input"===t||"button"===t)&&e.type===n}}function ge(t){return function(e){ +return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&ae(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function ve(a){return le(function(o){return o=+o,le(function(e,t){var n,r=a([],e.length,o),i=r.length;while(i--)e[n=r[i]]&&(e[n]=!(t[n]=e[n]))})})}function ye(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}for(e in d=se.support={},i=se.isXML=function(e){var t=e&&e.namespaceURI,n=e&&(e.ownerDocument||e).documentElement;return!Y.test(t||n&&n.nodeName||"HTML")},T=se.setDocument=function(e){var t,n,r=e?e.ownerDocument||e:p;return r!=C&&9===r.nodeType&&r.documentElement&&(a=(C=r).documentElement,E=!i(C),p!=C&&(n=C.defaultView)&&n.top!==n&&(n.addEventListener?n.addEventListener("unload",oe,!1):n.attachEvent&&n.attachEvent("onunload",oe)),d.scope=ce(function(e){return a.appendChild(e).appendChild(C.createElement("div")),"undefined"!=typeof e.querySelectorAll&&!e.querySelectorAll( +":scope fieldset div").length}),d.attributes=ce(function(e){return e.className="i",!e.getAttribute("className")}),d.getElementsByTagName=ce(function(e){return e.appendChild(C.createComment("")),!e.getElementsByTagName("*").length}),d.getElementsByClassName=K.test(C.getElementsByClassName),d.getById=ce(function(e){return a.appendChild(e).id=S,!C.getElementsByName||!C.getElementsByName(S).length}),d.getById?(b.filter.ID=function(e){var t=e.replace(te,ne);return function(e){return e.getAttribute("id")===t}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n=t.getElementById(e);return n?[n]:[]}}):(b.filter.ID=function(e){var n=e.replace(te,ne);return function(e){var t="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return t&&t.value===n}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id") +)&&n.value===e)return[o]}return[]}}),b.find.TAG=d.getElementsByTagName?function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):d.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},b.find.CLASS=d.getElementsByClassName&&function(e,t){if("undefined"!=typeof t.getElementsByClassName&&E)return t.getElementsByClassName(e)},s=[],v=[],(d.qsa=K.test(C.querySelectorAll))&&(ce(function(e){var t;a.appendChild(e).innerHTML="",e.querySelectorAll("[msallowcapture^='']").length&&v.push("[*^$]="+M+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||v.push("\\["+M+"*(?:value|"+R+")"),e.querySelectorAll("[id~="+S+"-]").length||v.push("~="),(t=C.createElement("input")).setAttribute("name",""),e.appendChild(t),e.querySelectorAll("[name='']").length||v.push( +"\\["+M+"*name"+M+"*="+M+"*(?:''|\"\")"),e.querySelectorAll(":checked").length||v.push(":checked"),e.querySelectorAll("a#"+S+"+*").length||v.push(".#.+[+~]"),e.querySelectorAll("\\\f"),v.push("[\\r\\n\\f]")}),ce(function(e){e.innerHTML="";var t=C.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&v.push("name"+M+"*[*^$|!~]?="),2!==e.querySelectorAll(":enabled").length&&v.push(":enabled",":disabled"),a.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&v.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),v.push(",.*:")})),(d.matchesSelector=K.test(c=a.matches||a.webkitMatchesSelector||a.mozMatchesSelector||a.oMatchesSelector||a.msMatchesSelector))&&ce(function(e){d.disconnectedMatch=c.call(e,"*"),c.call(e,"[s!='']:x"),s.push("!=",F)}),v=v.length&&new RegExp(v.join("|")),s=s.length&&new RegExp(s.join("|")),t=K.test( +a.compareDocumentPosition),y=t||K.test(a.contains)?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},j=t?function(e,t){if(e===t)return l=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)==(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!d.sortDetached&&t.compareDocumentPosition(e)===n?e==C||e.ownerDocument==p&&y(p,e)?-1:t==C||t.ownerDocument==p&&y(p,t)?1:u?P(u,e)-P(u,t):0:4&n?-1:1)}:function(e,t){if(e===t)return l=!0,0;var n,r=0,i=e.parentNode,o=t.parentNode,a=[e],s=[t];if(!i||!o)return e==C?-1:t==C?1:i?-1:o?1:u?P(u,e)-P(u,t):0;if(i===o)return pe(e,t);n=e;while(n=n.parentNode)a.unshift(n);n=t;while(n=n.parentNode)s.unshift(n);while(a[r]===s[r])r++;return r?pe(a[r],s[r]):a[r]==p?-1:s[r]==p?1:0}),C},se.matches=function(e,t){return se(e,null, +null,t)},se.matchesSelector=function(e,t){if(T(e),d.matchesSelector&&E&&!N[t+" "]&&(!s||!s.test(t))&&(!v||!v.test(t)))try{var n=c.call(e,t);if(n||d.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(e){N(t,!0)}return 0":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(te,ne),e[3]=(e[3]||e[4]||e[5]||"").replace(te,ne),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||se.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&se.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return G.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&X.test(n)&&(t=h(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(te,ne +).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=m[e+" "];return t||(t=new RegExp("(^|"+M+")"+e+"("+M+"|$)"))&&m(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(n,r,i){return function(e){var t=se.attr(e,n);return null==t?"!="===r:!r||(t+="","="===r?t===i:"!="===r?t!==i:"^="===r?i&&0===t.indexOf(i):"*="===r?i&&-1:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function j(e,n,r){return m(n)?S.grep(e,function(e,t){return!!n.call(e,t,e)!==r}):n.nodeType?S.grep(e,function(e){return e===n!==r}):"string"!=typeof n?S.grep(e,function(e){return-1)[^>]*|#([\w-]+))$/;(S.fn.init=function(e,t,n){var r,i;if(!e)return this;if(n=n||D,"string"==typeof e){if(!(r="<"===e[0]&&">"===e[e.length-1]&&3<=e.length?[null,e,null]:q.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof S?t[0]:t,S.merge(this,S.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:E,!0)),N.test(r[1])&&S.isPlainObject(t))for(r in t)m(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return(i=E.getElementById(r[2]))&&(this[0]=i,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):m(e)?void 0!==n.ready?n.ready(e):e(S):S.makeArray(e,this)}).prototype=S.fn,D=S(E);var L=/^(?:parents|prev(?:Until|All))/,H={children:!0,contents:!0,next:!0,prev:!0};function O(e,t){while((e=e[t])&&1!==e.nodeType);return e}S.fn.extend({has:function(e){var t=S(e,this),n=t.length;return this.filter(function(){for( +var e=0;e\x20\t\r\n\f]*)/i,he=/^$|^module$|\/(?:java|ecma)script/i;ce=E.createDocumentFragment().appendChild(E.createElement("div")),(fe=E.createElement("input")).setAttribute("type","radio"),fe.setAttribute("checked","checked"),fe.setAttribute("name","t"),ce.appendChild(fe),y.checkClone=ce.cloneNode(!0).cloneNode(!0).lastChild.checked,ce.innerHTML="",y.noCloneChecked=!!ce.cloneNode(!0).lastChild.defaultValue,ce.innerHTML="",y.option=!!ce.lastChild;var ge={thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};function ve(e,t){var n; +return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&A(e,t)?S.merge([e],n):n}function ye(e,t){for(var n=0,r=e.length;n",""]);var me=/<|&#?\w+;/;function xe(e,t,n,r,i){for(var o,a,s,u,l,c,f=t.createDocumentFragment(),p=[],d=0,h=e.length;d\s*$/g;function je(e,t){return A(e,"table")&&A(11!==t.nodeType?t:t.firstChild,"tr")&&S(e).children("tbody")[0]||e}function De(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function qe(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Le(e,t){var n,r,i,o,a,s;if(1===t.nodeType){if(Y.hasData(e)&&(s=Y.get(e).events))for(i in Y.remove(t,"handle events"),s)for(n=0, +r=s[i].length;n").attr(n.scriptAttrs||{}).prop({charset:n.scriptCharset,src:n.url}).on("load error",i=function(e){r.remove(),i=null,e&&t("error"===e.type?404:200,e.type)}),E.head.appendChild(r[0])},abort:function(){i&&i()}}});var _t,zt=[],Ut=/(=)\?(?=&|$)|\?\?/;S.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=zt.pop()||S.expando+"_"+wt.guid++;return this[e]=!0,e}}),S.ajaxPrefilter("json jsonp",function(e,t,n){var r,i,o,a=!1!==e.jsonp&&(Ut.test(e.url)?"url":"string"==typeof e.data&&0===(e.contentType||"").indexOf("application/x-www-form-urlencoded")&&Ut.test(e.data)&&"data");if(a||"jsonp"===e.dataTypes[0])return r=e.jsonpCallback=m(e.jsonpCallback)?e.jsonpCallback():e.jsonpCallback,a?e[a]=e[a].replace(Ut,"$1"+r):!1!==e.jsonp&&(e.url+=(Tt.test(e.url)?"&":"?")+e.jsonp+"="+r),e.converters["script json"]=function(){return o||S.error(r+" was not called"),o[0]},e.dataTypes[0]="json",i=C[r] +,C[r]=function(){o=arguments},n.always(function(){void 0===i?S(C).removeProp(r):C[r]=i,e[r]&&(e.jsonpCallback=t.jsonpCallback,zt.push(r)),o&&m(i)&&i(o[0]),o=i=void 0}),"script"}),y.createHTMLDocument=((_t=E.implementation.createHTMLDocument("").body).innerHTML="
",2===_t.childNodes.length),S.parseHTML=function(e,t,n){return"string"!=typeof e?[]:("boolean"==typeof t&&(n=t,t=!1),t||(y.createHTMLDocument?((r=(t=E.implementation.createHTMLDocument("")).createElement("base")).href=E.location.href,t.head.appendChild(r)):t=E),o=!n&&[],(i=N.exec(e))?[t.createElement(i[1])]:(i=xe([e],t,o),o&&o.length&&S(o).remove(),S.merge([],i.childNodes)));var r,i,o},S.fn.load=function(e,t,n){var r,i,o,a=this,s=e.indexOf(" ");return-1").append(S.parseHTML(e)).find(r):e)}).always(n&&function(e,t){a.each( +function(){n.apply(this,o||[e.responseText,t,e])})}),this},S.expr.pseudos.animated=function(t){return S.grep(S.timers,function(e){return t===e.elem}).length},S.offset={setOffset:function(e,t,n){var r,i,o,a,s,u,l=S.css(e,"position"),c=S(e),f={};"static"===l&&(e.style.position="relative"),s=c.offset(),o=S.css(e,"top"),u=S.css(e,"left"),("absolute"===l||"fixed"===l)&&-1<(o+u).indexOf("auto")?(a=(r=c.position()).top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(u)||0),m(t)&&(t=t.call(e,n,S.extend({},s))),null!=t.top&&(f.top=t.top-s.top+a),null!=t.left&&(f.left=t.left-s.left+i),"using"in t?t.using.call(e,f):c.css(f)}},S.fn.extend({offset:function(t){if(arguments.length)return void 0===t?this:this.each(function(e){S.offset.setOffset(this,t,e)});var e,n,r=this[0];return r?r.getClientRects().length?(e=r.getBoundingClientRect(),n=r.ownerDocument.defaultView,{top:e.top+n.pageYOffset,left:e.left+n.pageXOffset}):{top:0,left:0}:void 0},position:function(){if(this[0]){var e,t,n,r=this[0],i={top:0,left:0};if("fixed"===S.css(r, +"position"))t=r.getBoundingClientRect();else{t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;while(e&&(e===n.body||e===n.documentElement)&&"static"===S.css(e,"position"))e=e.parentNode;e&&e!==r&&1===e.nodeType&&((i=S(e).offset()).top+=S.css(e,"borderTopWidth",!0),i.left+=S.css(e,"borderLeftWidth",!0))}return{top:t.top-i.top-S.css(r,"marginTop",!0),left:t.left-i.left-S.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent;while(e&&"static"===S.css(e,"position"))e=e.offsetParent;return e||re})}}),S.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(t,i){var o="pageYOffset"===i;S.fn[t]=function(e){return $(this,function(e,t,n){var r;if(x(e)?r=e:9===e.nodeType&&(r=e.defaultView),void 0===n)return r?r[i]:e[t];r?r.scrollTo(o?r.pageXOffset:n,o?n:r.pageYOffset):e[t]=n},t,e,arguments.length)}}),S.each(["top","left"],function(e,n){S.cssHooks[n]=Fe(y.pixelPosition,function(e,t){if(t)return t=We(e,n),Pe.test(t)?S(e).position()[n]+"px":t})} +),S.each({Height:"height",Width:"width"},function(a,s){S.each({padding:"inner"+a,content:s,"":"outer"+a},function(r,o){S.fn[o]=function(e,t){var n=arguments.length&&(r||"boolean"!=typeof e),i=r||(!0===e||!0===t?"margin":"border");return $(this,function(e,t,n){var r;return x(e)?0===o.indexOf("outer")?e["inner"+a]:e.document.documentElement["client"+a]:9===e.nodeType?(r=e.documentElement,Math.max(e.body["scroll"+a],r["scroll"+a],e.body["offset"+a],r["offset"+a],r["client"+a])):void 0===n?S.css(e,t,i):S.style(e,t,n,i)},s,n?e:void 0,n)}})}),S.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){S.fn[t]=function(e){return this.on(t,e)}}),S.fn.extend({bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)},hover:function(e,t){return this.mouseenter(e).mouseleave(t||e)}}),S.each( +"blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,n){S.fn[n]=function(e,t){return 0",options:{classes:{},disabled:!1,create:null},_createWidget:function(t,e){e=y(e||this.defaultElement||this)[0],this.element=y(e),this.uuid=i++,this.eventNamespace="."+this.widgetName+this.uuid,this.bindings=y(),this.hoverable=y(),this.focusable=y(),this.classesElementLookup={},e!==this&&(y.data(e,this.widgetFullName,this),this._on(!0,this.element,{remove:function(t +){t.target===e&&this.destroy()}}),this.document=y(e.style?e.ownerDocument:e.document||e),this.window=y(this.document[0].defaultView||this.document[0].parentWindow)),this.options=y.widget.extend({},this.options,this._getCreateOptions(),t),this._create(),this.options.disabled&&this._setOptionDisabled(this.options.disabled),this._trigger("create",null,this._getCreateEventData()),this._init()},_getCreateOptions:function(){return{}},_getCreateEventData:y.noop,_create:y.noop,_init:y.noop,destroy:function(){var i=this;this._destroy(),y.each(this.classesElementLookup,function(t,e){i._removeClass(e,t)}),this.element.off(this.eventNamespace).removeData(this.widgetFullName),this.widget().off(this.eventNamespace).removeAttr("aria-disabled"),this.bindings.off(this.eventNamespace)},_destroy:y.noop,widget:function(){return this.element},option:function(t,e){var i,s,n,o=t;if(0===arguments.length)return y.widget.extend({},this.options);if("string"==typeof t)if(o={},t=(i=t.split(".")).shift(),i.length){for(s=o[t +]=y.widget.extend({},this.options[t]),n=0;n
"),i=e.children()[0];return y("body").append(e),t=i.offsetWidth,e.css("overflow","scroll"),t===(i=i.offsetWidth)&&(i=e[0].clientWidth),e.remove(),s=t-i}, +getScrollInfo:function(t){var e=t.isWindow||t.isDocument?"":t.element.css("overflow-x"),i=t.isWindow||t.isDocument?"":t.element.css("overflow-y"),e="scroll"===e||"auto"===e&&t.widthx(D(s),D(n))?o.important="horizontal":o.important="vertical",p.using.call(this,t,o)}),h.offset(y.extend(l,{using:t}))})},y.ui.position={fit:{left:function(t,e){var i=e.within, +s=i.isWindow?i.scrollLeft:i.offset.left,n=i.width,o=t.left-e.collisionPosition.marginLeft,h=s-o,a=o+e.collisionWidth-n-s;e.collisionWidth>n?0n?0=this.options.distance},_mouseDelayMet:function(){return this.mouseDelayMet},_mouseStart:function(){},_mouseDrag:function(){},_mouseStop:function(){},_mouseCapture:function(){return!0}}),y.ui.plugin={add:function(t,e,i){var s,n=y.ui[t].prototype;for(s in i)n.plugins[s]=n.plugins[s]||[],n.plugins[s].push([e,i[s]])},call:function(t,e,i,s){var n,o=t.plugins[e];if(o&&(s||t.element[0].parentNode&&11!==t.element[0].parentNode.nodeType))for(n=0;n").css({overflow:"hidden",position:this.element.css("position"),width:this.element.outerWidth(),height:this.element.outerHeight(),top:this.element.css("top"),left:this.element.css("left")})), +this.element=this.element.parent().data("ui-resizable",this.element.resizable("instance")),this.elementIsWrapper=!0,t={marginTop:this.originalElement.css("marginTop"),marginRight:this.originalElement.css("marginRight"),marginBottom:this.originalElement.css("marginBottom"),marginLeft:this.originalElement.css("marginLeft")},this.element.css(t),this.originalElement.css("margin",0),this.originalResizeStyle=this.originalElement.css("resize"),this.originalElement.css("resize","none"),this._proportionallyResizeElements.push(this.originalElement.css({position:"static",zoom:1,display:"block"})),this.originalElement.css(t),this._proportionallyResize()),this._setupHandles(),e.autoHide&&y(this.element).on("mouseenter",function(){e.disabled||(i._removeClass("ui-resizable-autohide"),i._handles.show())}).on("mouseleave",function(){e.disabled||i.resizing||(i._addClass("ui-resizable-autohide"),i._handles.hide())}),this._mouseInit()},_destroy:function(){this._mouseDestroy(),this._addedHandles.remove();function t(t){y(t +).removeData("resizable").removeData("ui-resizable").off(".resizable")}var e;return this.elementIsWrapper&&(t(this.element),e=this.element,this.originalElement.css({position:e.css("position"),width:e.outerWidth(),height:e.outerHeight(),top:e.css("top"),left:e.css("left")}).insertAfter(e),e.remove()),this.originalElement.css("resize",this.originalResizeStyle),t(this.originalElement),this},_setOption:function(t,e){switch(this._super(t,e),t){case"handles":this._removeHandles(),this._setupHandles();break;case"aspectRatio":this._aspectRatio=!!e}},_setupHandles:function(){var t,e,i,s,n,o=this.options,h=this;if(this.handles=o.handles||(y(".ui-resizable-handle",this.element).length?{n:".ui-resizable-n",e:".ui-resizable-e",s:".ui-resizable-s",w:".ui-resizable-w",se:".ui-resizable-se",sw:".ui-resizable-sw",ne:".ui-resizable-ne",nw:".ui-resizable-nw"}:"e,s,se"),this._handles=y(),this._addedHandles=y(),this.handles.constructor===String)for("all"===this.handles&&(this.handles="n,e,s,w,se,sw,ne,nw"),i=this.handles.split( +","),this.handles={},e=0;e"),this._addClass(n,"ui-resizable-handle "+s),n.css({zIndex:o.zIndex}),this.handles[t]=".ui-resizable-"+t,this.element.children(this.handles[t]).length||(this.element.append(n),this._addedHandles=this._addedHandles.add(n));this._renderAxis=function(t){var e,i,s;for(e in t=t||this.element,this.handles)this.handles[e].constructor===String?this.handles[e]=this.element.children(this.handles[e]).first().show():(this.handles[e].jquery||this.handles[e].nodeType)&&(this.handles[e]=y(this.handles[e]),this._on(this.handles[e],{mousedown:h._mouseDown})),this.elementIsWrapper&&this.originalElement[0].nodeName.match(/^(textarea|input|select|button)$/i)&&(i=y(this.handles[e],this.element),s=/sw|ne|nw|se|n|s/.test(e)?i.outerHeight():i.outerWidth(),i=["padding",/ne|nw|n/.test(e)?"Top":/se|sw|s/.test(e)?"Bottom":/^e$/.test(e)?"Right":"Left"].join(""),t.css(i,s),this._proportionallyResize()),this._handles=this._handles.add( +this.handles[e])},this._renderAxis(this.element),this._handles=this._handles.add(this.element.find(".ui-resizable-handle")),this._handles.disableSelection(),this._handles.on("mouseover",function(){h.resizing||(this.className&&(n=this.className.match(/ui-resizable-(se|sw|ne|nw|n|e|s|w)/i)),h.axis=n&&n[1]?n[1]:"se")}),o.autoHide&&(this._handles.hide(),this._addClass("ui-resizable-autohide"))},_removeHandles:function(){this._addedHandles.remove()},_mouseCapture:function(t){var e,i,s=!1;for(e in this.handles)(i=y(this.handles[e])[0])!==t.target&&!y.contains(i,t.target)||(s=!0);return!this.options.disabled&&s},_mouseStart:function(t){var e,i,s=this.options,n=this.element;return this.resizing=!0,this._renderProxy(),e=this._num(this.helper.css("left")),i=this._num(this.helper.css("top")),s.containment&&(e+=y(s.containment).scrollLeft()||0,i+=y(s.containment).scrollTop()||0),this.offset=this.helper.offset(),this.position={left:e,top:i},this.size=this._helper?{width:this.helper.width(),height:this.helper.height()}:{ +width:n.width(),height:n.height()},this.originalSize=this._helper?{width:n.outerWidth(),height:n.outerHeight()}:{width:n.width(),height:n.height()},this.sizeDiff={width:n.outerWidth()-n.width(),height:n.outerHeight()-n.height()},this.originalPosition={left:e,top:i},this.originalMousePosition={left:t.pageX,top:t.pageY},this.aspectRatio="number"==typeof s.aspectRatio?s.aspectRatio:this.originalSize.width/this.originalSize.height||1,s=y(".ui-resizable-"+this.axis).css("cursor"),y("body").css("cursor","auto"===s?this.axis+"-resize":s),this._addClass("ui-resizable-resizing"),this._propagate("start",t),!0},_mouseDrag:function(t){var e=this.originalMousePosition,i=this.axis,s=t.pageX-e.left||0,e=t.pageY-e.top||0,i=this._change[i];return this._updatePrevProperties(),i&&(e=i.apply(this,[t,s,e]),this._updateVirtualBoundaries(t.shiftKey),(this._aspectRatio||t.shiftKey)&&(e=this._updateRatio(e,t)),e=this._respectSize(e,t),this._updateCache(e),this._propagate("resize",t),e=this._applyChanges(), +!this._helper&&this._proportionallyResizeElements.length&&this._proportionallyResize(),y.isEmptyObject(e)||(this._updatePrevProperties(),this._trigger("resize",t,this.ui()),this._applyChanges())),!1},_mouseStop:function(t){this.resizing=!1;var e,i,s,n=this.options,o=this;return this._helper&&(s=(e=(i=this._proportionallyResizeElements).length&&/textarea/i.test(i[0].nodeName))&&this._hasScroll(i[0],"left")?0:o.sizeDiff.height,i=e?0:o.sizeDiff.width,e={width:o.helper.width()-i,height:o.helper.height()-s},i=parseFloat(o.element.css("left"))+(o.position.left-o.originalPosition.left)||null,s=parseFloat(o.element.css("top"))+(o.position.top-o.originalPosition.top)||null,n.animate||this.element.css(y.extend(e,{top:s,left:i})),o.helper.height(o.size.height),o.helper.width(o.size.width),this._helper&&!n.animate&&this._proportionallyResize()),y("body").css("cursor","auto"),this._removeClass("ui-resizable-resizing"),this._propagate("stop",t),this._helper&&this.helper.remove(),!1},_updatePrevProperties:function(){ +this.prevPosition={top:this.position.top,left:this.position.left},this.prevSize={width:this.size.width,height:this.size.height}},_applyChanges:function(){var t={};return this.position.top!==this.prevPosition.top&&(t.top=this.position.top+"px"),this.position.left!==this.prevPosition.left&&(t.left=this.position.left+"px"),this.size.width!==this.prevSize.width&&(t.width=this.size.width+"px"),this.size.height!==this.prevSize.height&&(t.height=this.size.height+"px"),this.helper.css(t),t},_updateVirtualBoundaries:function(t){var e,i,s=this.options,n={minWidth:this._isNumber(s.minWidth)?s.minWidth:0,maxWidth:this._isNumber(s.maxWidth)?s.maxWidth:1/0,minHeight:this._isNumber(s.minHeight)?s.minHeight:0,maxHeight:this._isNumber(s.maxHeight)?s.maxHeight:1/0};(this._aspectRatio||t)&&(e=n.minHeight*this.aspectRatio,i=n.minWidth/this.aspectRatio,s=n.maxHeight*this.aspectRatio,t=n.maxWidth/this.aspectRatio,e>n.minWidth&&(n.minWidth=e),i>n.minHeight&&(n.minHeight=i),st.width,h=this._isNumber(t.height)&&e.minHeight&&e.minHeight>t.height,a=this.originalPosition.left+this.originalSize.width,r=this.originalPosition.top+this.originalSize.height +,l=/sw|nw|w/.test(i),i=/nw|ne|n/.test(i);return o&&(t.width=e.minWidth),h&&(t.height=e.minHeight),s&&(t.width=e.maxWidth),n&&(t.height=e.maxHeight),o&&l&&(t.left=a-e.minWidth),s&&l&&(t.left=a-e.maxWidth),h&&i&&(t.top=r-e.minHeight),n&&i&&(t.top=r-e.maxHeight),t.width||t.height||t.left||!t.top?t.width||t.height||t.top||!t.left||(t.left=null):t.top=null,t},_getPaddingPlusBorderDimensions:function(t){for(var e=0,i=[],s=[t.css("borderTopWidth"),t.css("borderRightWidth"),t.css("borderBottomWidth"),t.css("borderLeftWidth")],n=[t.css("paddingTop"),t.css("paddingRight"),t.css("paddingBottom"),t.css("paddingLeft")];e<4;e++)i[e]=parseFloat(s[e])||0,i[e]+=parseFloat(n[e])||0;return{height:i[0]+i[2],width:i[1]+i[3]}},_proportionallyResize:function(){if(this._proportionallyResizeElements.length)for(var t,e=0,i=this.helper||this.element;e").css({overflow:"hidden"}),this._addClass(this.helper,this._helper),this.helper.css({width:this.element.outerWidth(),height:this.element.outerHeight(),position:"absolute",left:this.elementOffset.left+"px",top:this.elementOffset.top+"px",zIndex:++e.zIndex}),this.helper.appendTo("body").disableSelection()):this.helper=this.element},_change:{e:function(t,e){return{width:this.originalSize.width+e}},w:function(t,e){var i=this.originalSize;return{left:this.originalPosition.left+e,width:i.width-e}},n:function(t,e,i){var s=this.originalSize;return{top:this.originalPosition.top+i,height:s.height-i}},s:function(t,e,i){return{height:this.originalSize.height+i}},se:function(t,e,i){return y.extend(this._change.s.apply(this,arguments),this._change.e.apply(this,[t,e,i]))},sw:function(t,e, +i){return y.extend(this._change.s.apply(this,arguments),this._change.w.apply(this,[t,e,i]))},ne:function(t,e,i){return y.extend(this._change.n.apply(this,arguments),this._change.e.apply(this,[t,e,i]))},nw:function(t,e,i){return y.extend(this._change.n.apply(this,arguments),this._change.w.apply(this,[t,e,i]))}},_propagate:function(t,e){y.ui.plugin.call(this,t,[e,this.ui()]),"resize"!==t&&this._trigger(t,e,this.ui())},plugins:{},ui:function(){return{originalElement:this.originalElement,element:this.element,helper:this.helper,position:this.position,size:this.size,originalSize:this.originalSize,originalPosition:this.originalPosition}}}),y.ui.plugin.add("resizable","animate",{stop:function(e){var i=y(this).resizable("instance"),t=i.options,s=i._proportionallyResizeElements,n=s.length&&/textarea/i.test(s[0].nodeName),o=n&&i._hasScroll(s[0],"left")?0:i.sizeDiff.height,h=n?0:i.sizeDiff.width,n={width:i.size.width-h,height:i.size.height-o},h=parseFloat(i.element.css("left"))+(i.position.left-i.originalPosition.left +)||null,o=parseFloat(i.element.css("top"))+(i.position.top-i.originalPosition.top)||null;i.element.animate(y.extend(n,o&&h?{top:o,left:h}:{}),{duration:t.animateDuration,easing:t.animateEasing,step:function(){var t={width:parseFloat(i.element.css("width")),height:parseFloat(i.element.css("height")),top:parseFloat(i.element.css("top")),left:parseFloat(i.element.css("left"))};s&&s.length&&y(s[0]).css({width:t.width,height:t.height}),i._updateCache(t),i._propagate("resize",e)}})}}),y.ui.plugin.add("resizable","containment",{start:function(){var i,s,n=y(this).resizable("instance"),t=n.options,e=n.element,o=t.containment,h=o instanceof y?o.get(0):/parent/.test(o)?e.parent().get(0):o;h&&(n.containerElement=y(h),/document/.test(o)||o===document?(n.containerOffset={left:0,top:0},n.containerPosition={left:0,top:0},n.parentData={element:y(document),left:0,top:0,width:y(document).width(),height:y(document).height()||document.body.parentNode.scrollHeight}):(i=y(h),s=[],y(["Top","Right","Left","Bottom"]).each(function(t,e +){s[t]=n._num(i.css("padding"+e))}),n.containerOffset=i.offset(),n.containerPosition=i.position(),n.containerSize={height:i.innerHeight()-s[3],width:i.innerWidth()-s[1]},t=n.containerOffset,e=n.containerSize.height,o=n.containerSize.width,o=n._hasScroll(h,"left")?h.scrollWidth:o,e=n._hasScroll(h)?h.scrollHeight:e,n.parentData={element:h,left:t.left,top:t.top,width:o,height:e}))},resize:function(t){var e=y(this).resizable("instance"),i=e.options,s=e.containerOffset,n=e.position,o=e._aspectRatio||t.shiftKey,h={top:0,left:0},a=e.containerElement,t=!0;a[0]!==document&&/static/.test(a.css("position"))&&(h=s),n.left<(e._helper?s.left:0)&&(e.size.width=e.size.width+(e._helper?e.position.left-s.left:e.position.left-h.left),o&&(e.size.height=e.size.width/e.aspectRatio,t=!1),e.position.left=i.helper?s.left:0),n.top<(e._helper?s.top:0)&&(e.size.height=e.size.height+(e._helper?e.position.top-s.top:e.position.top),o&&(e.size.width=e.size.height*e.aspectRatio,t=!1),e.position.top=e._helper?s.top:0), +i=e.containerElement.get(0)===e.element.parent().get(0),n=/relative|absolute/.test(e.containerElement.css("position")),i&&n?(e.offset.left=e.parentData.left+e.position.left,e.offset.top=e.parentData.top+e.position.top):(e.offset.left=e.element.offset().left,e.offset.top=e.element.offset().top),n=Math.abs(e.sizeDiff.width+(e._helper?e.offset.left-h.left:e.offset.left-s.left)),s=Math.abs(e.sizeDiff.height+(e._helper?e.offset.top-h.top:e.offset.top-s.top)),n+e.size.width>=e.parentData.width&&(e.size.width=e.parentData.width-n,o&&(e.size.height=e.size.width/e.aspectRatio,t=!1)),s+e.size.height>=e.parentData.height&&(e.size.height=e.parentData.height-s,o&&(e.size.width=e.size.height*e.aspectRatio,t=!1)),t||(e.position.left=e.prevPosition.left,e.position.top=e.prevPosition.top,e.size.width=e.prevSize.width,e.size.height=e.prevSize.height)},stop:function(){var t=y(this).resizable("instance"),e=t.options,i=t.containerOffset,s=t.containerPosition,n=t.containerElement,o=y(t.helper),h=o.offset(),a=o.outerWidth( +)-t.sizeDiff.width,o=o.outerHeight()-t.sizeDiff.height;t._helper&&!e.animate&&/relative/.test(n.css("position"))&&y(this).css({left:h.left-s.left-i.left,width:a,height:o}),t._helper&&!e.animate&&/static/.test(n.css("position"))&&y(this).css({left:h.left-s.left-i.left,width:a,height:o})}}),y.ui.plugin.add("resizable","alsoResize",{start:function(){var t=y(this).resizable("instance").options;y(t.alsoResize).each(function(){var t=y(this);t.data("ui-resizable-alsoresize",{width:parseFloat(t.width()),height:parseFloat(t.height()),left:parseFloat(t.css("left")),top:parseFloat(t.css("top"))})})},resize:function(t,i){var e=y(this).resizable("instance"),s=e.options,n=e.originalSize,o=e.originalPosition,h={height:e.size.height-n.height||0,width:e.size.width-n.width||0,top:e.position.top-o.top||0,left:e.position.left-o.left||0};y(s.alsoResize).each(function(){var t=y(this),s=y(this).data("ui-resizable-alsoresize"),n={},e=t.parents(i.originalElement[0]).length?["width","height"]:["width","height","top","left"];y.each(e, +function(t,e){var i=(s[e]||0)+(h[e]||0);i&&0<=i&&(n[e]=i||null)}),t.css(n)})},stop:function(){y(this).removeData("ui-resizable-alsoresize")}}),y.ui.plugin.add("resizable","ghost",{start:function(){var t=y(this).resizable("instance"),e=t.size;t.ghost=t.originalElement.clone(),t.ghost.css({opacity:.25,display:"block",position:"relative",height:e.height,width:e.width,margin:0,left:0,top:0}),t._addClass(t.ghost,"ui-resizable-ghost"),!1!==y.uiBackCompat&&"string"==typeof t.options.ghost&&t.ghost.addClass(this.options.ghost),t.ghost.appendTo(t.helper)},resize:function(){var t=y(this).resizable("instance");t.ghost&&t.ghost.css({position:"relative",height:t.size.height,width:t.size.width})},stop:function(){var t=y(this).resizable("instance");t.ghost&&t.helper&&t.helper.get(0).removeChild(t.ghost.get(0))}}),y.ui.plugin.add("resizable","grid",{resize:function(){var t,e=y(this).resizable("instance"),i=e.options,s=e.size,n=e.originalSize,o=e.originalPosition,h=e.axis,a="number"==typeof i.grid?[i.grid,i.grid]:i.grid,r=a[0 +]||1,l=a[1]||1,u=Math.round((s.width-n.width)/r)*r,p=Math.round((s.height-n.height)/l)*l,d=n.width+u,c=n.height+p,f=i.maxWidth&&i.maxWidthd,s=i.minHeight&&i.minHeight>c;i.grid=a,m&&(d+=r),s&&(c+=l),f&&(d-=r),g&&(c-=l),/^(se|s|e)$/.test(h)?(e.size.width=d,e.size.height=c):/^(ne)$/.test(h)?(e.size.width=d,e.size.height=c,e.position.top=o.top-p):/^(sw)$/.test(h)?(e.size.width=d,e.size.height=c,e.position.left=o.left-u):((c-l<=0||d-r<=0)&&(t=e._getPaddingPlusBorderDimensions(this)),0=f[g]?0:Math.min(f[g],n));!a&&1-1){ +targetElements.on(evt+EVENT_NAMESPACE,function elementToggle(event){$.powerTip.toggle(this,event)})}else{targetElements.on(evt+EVENT_NAMESPACE,function elementOpen(event){$.powerTip.show(this,event)})}});$.each(options.closeEvents,function(idx,evt){if($.inArray(evt,options.openEvents)<0){targetElements.on(evt+EVENT_NAMESPACE,function elementClose(event){$.powerTip.hide(this,!isMouseEvent(event))})}});targetElements.on("keydown"+EVENT_NAMESPACE,function elementKeyDown(event){if(event.keyCode===27){$.powerTip.hide(this,true)}})}return targetElements};$.fn.powerTip.defaults={fadeInTime:200,fadeOutTime:100,followMouse:false,popupId:"powerTip",popupClass:null,intentSensitivity:7,intentPollInterval:100,closeDelay:100,placement:"n",smartPlacement:false,offset:10,mouseOnToPopup:false,manual:false,openEvents:["mouseenter","focus"],closeEvents:["mouseleave","blur"]};$.fn.powerTip.smartPlacementLists={n:["n","ne","nw","s"],e:["e","ne","se","w","nw","sw","n","s","e"],s:["s","se","sw","n"],w:["w","nw","sw","e","ne","se", +"n","s","w"],nw:["nw","w","sw","n","s","se","nw"],ne:["ne","e","se","n","s","sw","ne"],sw:["sw","w","nw","s","n","ne","sw"],se:["se","e","ne","s","n","nw","se"],"nw-alt":["nw-alt","n","ne-alt","sw-alt","s","se-alt","w","e"],"ne-alt":["ne-alt","n","nw-alt","se-alt","s","sw-alt","e","w"],"sw-alt":["sw-alt","s","se-alt","nw-alt","n","ne-alt","w","e"],"se-alt":["se-alt","s","sw-alt","ne-alt","n","nw-alt","e","w"]};$.powerTip={show:function apiShowTip(element,event){if(isMouseEvent(event)){trackMouse(event);session.previousX=event.pageX;session.previousY=event.pageY;$(element).data(DATA_DISPLAYCONTROLLER).show()}else{$(element).first().data(DATA_DISPLAYCONTROLLER).show(true,true)}return element},reposition:function apiResetPosition(element){$(element).first().data(DATA_DISPLAYCONTROLLER).resetPosition();return element},hide:function apiCloseTip(element,immediate){var displayController;immediate=element?immediate:true;if(element){displayController=$(element).first().data(DATA_DISPLAYCONTROLLER)}else if( +session.activeHover){displayController=session.activeHover.data(DATA_DISPLAYCONTROLLER)}if(displayController){displayController.hide(immediate)}return element},toggle:function apiToggle(element,event){if(session.activeHover&&session.activeHover.is(element)){$.powerTip.hide(element,!isMouseEvent(event))}else{$.powerTip.show(element,event)}return element}};$.powerTip.showTip=$.powerTip.show;$.powerTip.closeTip=$.powerTip.hide;function CSSCoordinates(){var me=this;me.top="auto";me.left="auto";me.right="auto";me.bottom="auto";me.set=function(property,value){if($.isNumeric(value)){me[property]=Math.round(value)}}}function DisplayController(element,options,tipController){var hoverTimer=null,myCloseDelay=null;function openTooltip(immediate,forceOpen){cancelTimer();if(!element.data(DATA_HASACTIVEHOVER)){if(!immediate){session.tipOpenImminent=true;hoverTimer=setTimeout(function intentDelay(){hoverTimer=null;checkForIntent()},options.intentPollInterval)}else{if(forceOpen){element.data(DATA_FORCEDOPEN,true)} +closeAnyDelayed();tipController.showTip(element)}}else{cancelClose()}}function closeTooltip(disableDelay){if(myCloseDelay){myCloseDelay=session.closeDelayTimeout=clearTimeout(myCloseDelay);session.delayInProgress=false}cancelTimer();session.tipOpenImminent=false;if(element.data(DATA_HASACTIVEHOVER)){element.data(DATA_FORCEDOPEN,false);if(!disableDelay){session.delayInProgress=true;session.closeDelayTimeout=setTimeout(function closeDelay(){session.closeDelayTimeout=null;tipController.hideTip(element);session.delayInProgress=false;myCloseDelay=null},options.closeDelay);myCloseDelay=session.closeDelayTimeout}else{tipController.hideTip(element)}}}function checkForIntent(){var xDifference=Math.abs(session.previousX-session.currentX),yDifference=Math.abs(session.previousY-session.currentY),totalDifference=xDifference+yDifference;if(totalDifference",{id:options.popupId});if($body.length===0){$body=$("body")}$body.append(tipElement);session.tooltips=session.tooltips?session.tooltips.add(tipElement):tipElement}if(options.followMouse){if(!tipElement.data(DATA_HASMOUSEMOVE)){$document.on("mousemove"+EVENT_NAMESPACE,positionTipOnCursor);$window.on("scroll"+EVENT_NAMESPACE,positionTipOnCursor);tipElement.data(DATA_HASMOUSEMOVE,true)}}function beginShowTip(element){element.data(DATA_HASACTIVEHOVER,true);tipElement.queue(function queueTipInit(next){showTip(element);next()})}function showTip(element){var tipContent;if(!element.data(DATA_HASACTIVEHOVER)){return}if( +session.isTipOpen){if(!session.isClosing){hideTip(session.activeHover)}tipElement.delay(100).queue(function queueTipAgain(next){showTip(element);next()});return}element.trigger("powerTipPreRender");tipContent=getTooltipContent(element);if(tipContent){tipElement.empty().append(tipContent)}else{return}element.trigger("powerTipRender");session.activeHover=element;session.isTipOpen=true;tipElement.data(DATA_MOUSEONTOTIP,options.mouseOnToPopup);tipElement.addClass(options.popupClass);if(!options.followMouse||element.data(DATA_FORCEDOPEN)){positionTipOnElement(element);session.isFixedTipOpen=true}else{positionTipOnCursor()}if(!element.data(DATA_FORCEDOPEN)&&!options.followMouse){$document.on("click"+EVENT_NAMESPACE,function documentClick(event){var target=event.target;if(target!==element[0]){if(options.mouseOnToPopup){if(target!==tipElement[0]&&!$.contains(tipElement[0],target)){$.powerTip.hide()}}else{$.powerTip.hide()}}})}if(options.mouseOnToPopup&&!options.manual){tipElement.on("mouseenter"+EVENT_NAMESPACE, +function tipMouseEnter(){if(session.activeHover){session.activeHover.data(DATA_DISPLAYCONTROLLER).cancel()}});tipElement.on("mouseleave"+EVENT_NAMESPACE,function tipMouseLeave(){if(session.activeHover){session.activeHover.data(DATA_DISPLAYCONTROLLER).hide()}})}tipElement.fadeIn(options.fadeInTime,function fadeInCallback(){if(!session.desyncTimeout){session.desyncTimeout=setInterval(closeDesyncedTip,500)}element.trigger("powerTipOpen")})}function hideTip(element){session.isClosing=true;session.isTipOpen=false;session.desyncTimeout=clearInterval(session.desyncTimeout);element.data(DATA_HASACTIVEHOVER,false);element.data(DATA_FORCEDOPEN,false);$document.off("click"+EVENT_NAMESPACE);tipElement.off(EVENT_NAMESPACE);tipElement.fadeOut(options.fadeOutTime,function fadeOutCallback(){var coords=new CSSCoordinates;session.activeHover=null;session.isClosing=false;session.isFixedTipOpen=false;tipElement.removeClass();coords.set("top",session.currentY+options.offset);coords.set("left",session.currentX+options.offset); +tipElement.css(coords);element.trigger("powerTipClose")})}function positionTipOnCursor(){var tipWidth,tipHeight,coords,collisions,collisionCount;if(!session.isFixedTipOpen&&(session.isTipOpen||session.tipOpenImminent&&tipElement.data(DATA_HASMOUSEMOVE))){tipWidth=tipElement.outerWidth();tipHeight=tipElement.outerHeight();coords=new CSSCoordinates;coords.set("top",session.currentY+options.offset);coords.set("left",session.currentX+options.offset);collisions=getViewportCollisions(coords,tipWidth,tipHeight);if(collisions!==Collision.none){collisionCount=countFlags(collisions);if(collisionCount===1){if(collisions===Collision.right){coords.set("left",session.scrollLeft+session.windowWidth-tipWidth)}else if(collisions===Collision.bottom){coords.set("top",session.scrollTop+session.windowHeight-tipHeight)}}else{coords.set("left",session.currentX-tipWidth-options.offset);coords.set("top",session.currentY-tipHeight-options.offset)}}tipElement.css(coords)}}function positionTipOnElement(element){var priorityList, +finalPlacement;if(options.smartPlacement||options.followMouse&&element.data(DATA_FORCEDOPEN)){priorityList=$.fn.powerTip.smartPlacementLists[options.placement];$.each(priorityList,function(idx,pos){var collisions=getViewportCollisions(placeTooltip(element,pos),tipElement.outerWidth(),tipElement.outerHeight());finalPlacement=pos;return collisions!==Collision.none})}else{placeTooltip(element,options.placement);finalPlacement=options.placement}tipElement.removeClass("w nw sw e ne se n s w se-alt sw-alt ne-alt nw-alt");tipElement.addClass(finalPlacement)}function placeTooltip(element,placement){var iterationCount=0,tipWidth,tipHeight,coords=new CSSCoordinates;coords.set("top",0);coords.set("left",0);tipElement.css(coords);do{tipWidth=tipElement.outerWidth();tipHeight=tipElement.outerHeight();coords=placementCalculator.compute(element,placement,tipWidth,tipHeight,options.offset);tipElement.css(coords)}while(++iterationCount<=5&&(tipWidth!==tipElement.outerWidth()||tipHeight!==tipElement.outerHeight())); +return coords}function closeDesyncedTip(){var isDesynced=false,hasDesyncableCloseEvent=$.grep(["mouseleave","mouseout","blur","focusout"],function(eventType){return $.inArray(eventType,options.closeEvents)!==-1}).length>0;if(session.isTipOpen&&!session.isClosing&&!session.delayInProgress&&hasDesyncableCloseEvent){if(session.activeHover.data(DATA_HASACTIVEHOVER)===false||session.activeHover.is(":disabled")){isDesynced=true}else if(!isMouseOver(session.activeHover)&&!session.activeHover.is(":focus")&&!session.activeHover.data(DATA_FORCEDOPEN)){if(tipElement.data(DATA_MOUSEONTOTIP)){if(!isMouseOver(tipElement)){isDesynced=true}}else{isDesynced=true}}if(isDesynced){hideTip(session.activeHover)}}}this.showTip=beginShowTip;this.hideTip=hideTip;this.resetPosition=positionTipOnElement}function isSvgElement(element){return Boolean(window.SVGElement&&element[0]instanceof SVGElement)}function isMouseEvent(event){return Boolean(event&&$.inArray(event.type,MOUSE_EVENTS)>-1&&typeof event.pageX==="number")} +function initTracking(){if(!session.mouseTrackingActive){session.mouseTrackingActive=true;getViewportDimensions();$(getViewportDimensions);$document.on("mousemove"+EVENT_NAMESPACE,trackMouse);$window.on("resize"+EVENT_NAMESPACE,trackResize);$window.on("scroll"+EVENT_NAMESPACE,trackScroll)}}function getViewportDimensions(){session.scrollLeft=$window.scrollLeft();session.scrollTop=$window.scrollTop();session.windowWidth=$window.width();session.windowHeight=$window.height()}function trackResize(){session.windowWidth=$window.width();session.windowHeight=$window.height()}function trackScroll(){var x=$window.scrollLeft(),y=$window.scrollTop();if(x!==session.scrollLeft){session.currentX+=x-session.scrollLeft;session.scrollLeft=x}if(y!==session.scrollTop){session.currentY+=y-session.scrollTop;session.scrollTop=y}}function trackMouse(event){session.currentX=event.pageX;session.currentY=event.pageY}function isMouseOver(element){var elementPosition=element.offset(),elementBox=element[0].getBoundingClientRect(), +elementWidth=elementBox.right-elementBox.left,elementHeight=elementBox.bottom-elementBox.top;return session.currentX>=elementPosition.left&&session.currentX<=elementPosition.left+elementWidth&&session.currentY>=elementPosition.top&&session.currentY<=elementPosition.top+elementHeight}function getTooltipContent(element){var tipText=element.data(DATA_POWERTIP),tipObject=element.data(DATA_POWERTIPJQ),tipTarget=element.data(DATA_POWERTIPTARGET),targetElement,content;if(tipText){if($.isFunction(tipText)){tipText=tipText.call(element[0])}content=tipText}else if(tipObject){if($.isFunction(tipObject)){tipObject=tipObject.call(element[0])}if(tipObject.length>0){content=tipObject.clone(true,true)}}else if(tipTarget){targetElement=$("#"+tipTarget);if(targetElement.length>0){content=targetElement.html()}}return content}function getViewportCollisions(coords,elementWidth,elementHeight){var viewportTop=session.scrollTop,viewportLeft=session.scrollLeft,viewportBottom=viewportTop+session.windowHeight, +viewportRight=viewportLeft+session.windowWidth,collisions=Collision.none;if(coords.topviewportBottom||Math.abs(coords.bottom-session.windowHeight)>viewportBottom){collisions|=Collision.bottom}if(coords.leftviewportRight){collisions|=Collision.left}if(coords.left+elementWidth>viewportRight||coords.right1)){a.preventDefault();var c=a.originalEvent.changedTouches[0],d=document.createEvent("MouseEvents");d.initMouseEvent(b,!0,!0,window,1,c.screenX,c.screenY,c.clientX,c.clientY,!1,!1,!1,!1,0,null),a.target.dispatchEvent(d)}}if(a.support.touch="ontouchend"in document,a.support.touch){var e,b=a.ui.mouse.prototype,c=b._mouseInit,d=b._mouseDestroy;b._touchStart=function(a){var b=this;!e&&b._mouseCapture(a.originalEvent.changedTouches[0])&&(e=!0,b._touchMoved=!1,f(a,"mouseover"),f(a,"mousemove"),f(a,"mousedown"))},b._touchMove=function(a){e&&(this._touchMoved=!0,f(a,"mousemove"))},b._touchEnd=function(a){e&&(f(a,"mouseup"),f(a,"mouseout"),this._touchMoved||f(a,"click"),e=!1)},b._mouseInit=function(){var b=this;b.element.bind({touchstart:a.proxy(b,"_touchStart"),touchmove:a.proxy(b,"_touchMove"),touchend:a.proxy(b,"_touchEnd")}),c.call(b)},b._mouseDestroy=function(){var b=this;b.element.unbind({touchstart:a.proxy(b,"_touchStart"),touchmove:a.proxy(b, +"_touchMove"),touchend:a.proxy(b,"_touchEnd")}),d.call(b)}}}(jQuery); +/*! SmartMenus jQuery Plugin - v1.1.0 - September 17, 2017 + * http://www.smartmenus.org/ + * Copyright Vasil Dinkov, Vadikom Web Ltd. http://vadikom.com; Licensed MIT */(function(t){"function"==typeof define&&define.amd?define(["jquery"],t):"object"==typeof module&&"object"==typeof module.exports?module.exports=t(require("jquery")):t(jQuery)})(function($){function initMouseDetection(t){var e=".smartmenus_mouse";if(mouseDetectionEnabled||t)mouseDetectionEnabled&&t&&($(document).off(e),mouseDetectionEnabled=!1);else{var i=!0,s=null,o={mousemove:function(t){var e={x:t.pageX,y:t.pageY,timeStamp:(new Date).getTime()};if(s){var o=Math.abs(s.x-e.x),a=Math.abs(s.y-e.y);if((o>0||a>0)&&2>=o&&2>=a&&300>=e.timeStamp-s.timeStamp&&(mouse=!0,i)){var n=$(t.target).closest("a");n.is("a")&&$.each(menuTrees,function(){return $.contains(this.$root[0],n[0])?(this.itemEnter({currentTarget:n[0]}),!1):void 0}),i=!1}}s=e}};o[touchEvents?"touchstart":"pointerover pointermove pointerout MSPointerOver MSPointerMove MSPointerOut"]=function(t){isTouchEvent(t.originalEvent)&&(mouse=!1)},$(document).on(getEventsNS(o,e)), +mouseDetectionEnabled=!0}}function isTouchEvent(t){return!/^(4|mouse)$/.test(t.pointerType)}function getEventsNS(t,e){e||(e="");var i={};for(var s in t)i[s.split(" ").join(e+" ")+e]=t[s];return i}var menuTrees=[],mouse=!1,touchEvents="ontouchstart"in window,mouseDetectionEnabled=!1,requestAnimationFrame=window.requestAnimationFrame||function(t){return setTimeout(t,1e3/60)},cancelAnimationFrame=window.cancelAnimationFrame||function(t){clearTimeout(t)},canAnimate=!!$.fn.animate;return $.SmartMenus=function(t,e){this.$root=$(t),this.opts=e,this.rootId="",this.accessIdPrefix="",this.$subArrow=null,this.activatedItems=[],this.visibleSubMenus=[],this.showTimeout=0,this.hideTimeout=0,this.scrollTimeout=0,this.clickActivated=!1,this.focusActivated=!1,this.zIndexInc=0,this.idInc=0,this.$firstLink=null,this.$firstSub=null,this.disabled=!1,this.$disableOverlay=null,this.$touchScrollingSub=null,this.cssTransforms3d="perspective"in t.style||"webkitPerspective"in t.style,this.wasCollapsible=!1,this.init()},$.extend( +$.SmartMenus,{hideAll:function(){$.each(menuTrees,function(){this.menuHideAll()})},destroy:function(){for(;menuTrees.length;)menuTrees[0].destroy();initMouseDetection(!0)},prototype:{init:function(t){var e=this;if(!t){menuTrees.push(this),this.rootId=((new Date).getTime()+Math.random()+"").replace(/\D/g,""),this.accessIdPrefix="sm-"+this.rootId+"-",this.$root.hasClass("sm-rtl")&&(this.opts.rightToLeftSubMenus=!0);var i=".smartmenus";this.$root.data("smartmenus",this).attr("data-smartmenus-id",this.rootId).dataSM("level",1).on(getEventsNS({"mouseover focusin":$.proxy(this.rootOver,this),"mouseout focusout":$.proxy(this.rootOut,this),keydown:$.proxy(this.rootKeyDown,this)},i)).on(getEventsNS({mouseenter:$.proxy(this.itemEnter,this),mouseleave:$.proxy(this.itemLeave,this),mousedown:$.proxy(this.itemDown,this),focus:$.proxy(this.itemFocus,this),blur:$.proxy(this.itemBlur,this),click:$.proxy(this.itemClick,this)},i),"a"),i+=this.rootId,this.opts.hideOnClick&&$(document).on(getEventsNS({touchstart:$.proxy( +this.docTouchStart,this),touchmove:$.proxy(this.docTouchMove,this),touchend:$.proxy(this.docTouchEnd,this),click:$.proxy(this.docClick,this)},i)),$(window).on(getEventsNS({"resize orientationchange":$.proxy(this.winResize,this)},i)),this.opts.subIndicators&&(this.$subArrow=$("").addClass("sub-arrow"),this.opts.subIndicatorsText&&this.$subArrow.html(this.opts.subIndicatorsText)),initMouseDetection()}if(this.$firstSub=this.$root.find("ul").each(function(){e.menuInit($(this))}).eq(0),this.$firstLink=this.$root.find("a").eq(0),this.opts.markCurrentItem){var s=/(index|default)\.[^#\?\/]*/i,o=/#.*/,a=window.location.href.replace(s,""),n=a.replace(o,"");this.$root.find("a").each(function(){var t=this.href.replace(s,""),i=$(this);(t==a||t==n)&&(i.addClass("current"),e.opts.markCurrentTree&&i.parentsUntil("[data-smartmenus-id]","ul").each(function(){$(this).dataSM("parent-a").addClass("current")}))})}this.wasCollapsible=this.isCollapsible()},destroy:function(t){if(!t){var e=".smartmenus";this.$root.removeData( +"smartmenus").removeAttr("data-smartmenus-id").removeDataSM("level").off(e),e+=this.rootId,$(document).off(e),$(window).off(e),this.opts.subIndicators&&(this.$subArrow=null)}this.menuHideAll();var i=this;this.$root.find("ul").each(function(){var t=$(this);t.dataSM("scroll-arrows")&&t.dataSM("scroll-arrows").remove(),t.dataSM("shown-before")&&((i.opts.subMenusMinWidth||i.opts.subMenusMaxWidth)&&t.css({width:"",minWidth:"",maxWidth:""}).removeClass("sm-nowrap"),t.dataSM("scroll-arrows")&&t.dataSM("scroll-arrows").remove(),t.css({zIndex:"",top:"",left:"",marginLeft:"",marginTop:"",display:""})),0==(t.attr("id")||"").indexOf(i.accessIdPrefix)&&t.removeAttr("id")}).removeDataSM("in-mega").removeDataSM("shown-before").removeDataSM("scroll-arrows").removeDataSM("parent-a").removeDataSM("level").removeDataSM("beforefirstshowfired").removeAttr("role").removeAttr("aria-hidden").removeAttr("aria-labelledby").removeAttr("aria-expanded"),this.$root.find("a.has-submenu").each(function(){var t=$(this);0==t.attr("id" +).indexOf(i.accessIdPrefix)&&t.removeAttr("id")}).removeClass("has-submenu").removeDataSM("sub").removeAttr("aria-haspopup").removeAttr("aria-controls").removeAttr("aria-expanded").closest("li").removeDataSM("sub"),this.opts.subIndicators&&this.$root.find("span.sub-arrow").remove(),this.opts.markCurrentItem&&this.$root.find("a.current").removeClass("current"),t||(this.$root=null,this.$firstLink=null,this.$firstSub=null,this.$disableOverlay&&(this.$disableOverlay.remove(),this.$disableOverlay=null),menuTrees.splice($.inArray(this,menuTrees),1))},disable:function(t){if(!this.disabled){if(this.menuHideAll(),!t&&!this.opts.isPopup&&this.$root.is(":visible")){var e=this.$root.offset();this.$disableOverlay=$('
').css({position:"absolute",top:e.top,left:e.left,width:this.$root.outerWidth(),height:this.$root.outerHeight(),zIndex:this.getStartZIndex(!0),opacity:0}).appendTo(document.body)}this.disabled=!0}},docClick:function(t){return this.$touchScrollingSub?( +this.$touchScrollingSub=null,void 0):((this.visibleSubMenus.length&&!$.contains(this.$root[0],t.target)||$(t.target).closest("a").length)&&this.menuHideAll(),void 0)},docTouchEnd:function(){if(this.lastTouch){if(!(!this.visibleSubMenus.length||void 0!==this.lastTouch.x2&&this.lastTouch.x1!=this.lastTouch.x2||void 0!==this.lastTouch.y2&&this.lastTouch.y1!=this.lastTouch.y2||this.lastTouch.target&&$.contains(this.$root[0],this.lastTouch.target))){this.hideTimeout&&(clearTimeout(this.hideTimeout),this.hideTimeout=0);var t=this;this.hideTimeout=setTimeout(function(){t.menuHideAll()},350)}this.lastTouch=null}},docTouchMove:function(t){if(this.lastTouch){var e=t.originalEvent.touches[0];this.lastTouch.x2=e.pageX,this.lastTouch.y2=e.pageY}},docTouchStart:function(t){var e=t.originalEvent.touches[0];this.lastTouch={x1:e.pageX,y1:e.pageY,target:e.target}},enable:function(){this.disabled&&(this.$disableOverlay&&(this.$disableOverlay.remove(),this.$disableOverlay=null),this.disabled=!1)},getClosestMenu:function(t){for( +var e=$(t).closest("ul");e.dataSM("in-mega");)e=e.parent().closest("ul");return e[0]||null},getHeight:function(t){return this.getOffset(t,!0)},getOffset:function(t,e){var i;"none"==t.css("display")&&(i={position:t[0].style.position,visibility:t[0].style.visibility},t.css({position:"absolute",visibility:"hidden"}).show());var s=t[0].getBoundingClientRect&&t[0].getBoundingClientRect(),o=s&&(e?s.height||s.bottom-s.top:s.width||s.right-s.left);return o||0===o||(o=e?t[0].offsetHeight:t[0].offsetWidth),i&&t.hide().css(i),o},getStartZIndex:function(t){var e=parseInt(this[t?"$root":"$firstSub"].css("z-index"));return!t&&isNaN(e)&&(e=parseInt(this.$root.css("z-index"))),isNaN(e)?1:e},getTouchPoint:function(t){return t.touches&&t.touches[0]||t.changedTouches&&t.changedTouches[0]||t},getViewport:function(t){var e=t?"Height":"Width",i=document.documentElement["client"+e],s=window["inner"+e];return s&&(i=Math.min(i,s)),i},getViewportHeight:function(){return this.getViewport(!0)},getViewportWidth:function(){ +return this.getViewport()},getWidth:function(t){return this.getOffset(t)},handleEvents:function(){return!this.disabled&&this.isCSSOn()},handleItemEvents:function(t){return this.handleEvents()&&!this.isLinkInMegaMenu(t)},isCollapsible:function(){return"static"==this.$firstSub.css("position")},isCSSOn:function(){return"inline"!=this.$firstLink.css("display")},isFixed:function(){var t="fixed"==this.$root.css("position");return t||this.$root.parentsUntil("body").each(function(){return"fixed"==$(this).css("position")?(t=!0,!1):void 0}),t},isLinkInMegaMenu:function(t){return $(this.getClosestMenu(t[0])).hasClass("mega-menu")},isTouchMode:function(){return!mouse||this.opts.noMouseOver||this.isCollapsible()},itemActivate:function(t,e){var i=t.closest("ul"),s=i.dataSM("level");if(s>1&&(!this.activatedItems[s-2]||this.activatedItems[s-2][0]!=i.dataSM("parent-a")[0])){var o=this;$(i.parentsUntil("[data-smartmenus-id]","ul").get().reverse()).add(i).each(function(){o.itemActivate($(this).dataSM("parent-a"))})}if(( +!this.isCollapsible()||e)&&this.menuHideSubMenus(this.activatedItems[s-1]&&this.activatedItems[s-1][0]==t[0]?s:s-1),this.activatedItems[s-1]=t,this.$root.triggerHandler("activate.smapi",t[0])!==!1){var a=t.dataSM("sub");a&&(this.isTouchMode()||!this.opts.showOnClick||this.clickActivated)&&this.menuShow(a)}},itemBlur:function(t){var e=$(t.currentTarget);this.handleItemEvents(e)&&this.$root.triggerHandler("blur.smapi",e[0])},itemClick:function(t){var e=$(t.currentTarget);if(this.handleItemEvents(e)){if(this.$touchScrollingSub&&this.$touchScrollingSub[0]==e.closest("ul")[0])return this.$touchScrollingSub=null,t.stopPropagation(),!1;if(this.$root.triggerHandler("click.smapi",e[0])===!1)return!1;var i=$(t.target).is(".sub-arrow"),s=e.dataSM("sub"),o=s?2==s.dataSM("level"):!1,a=this.isCollapsible(),n=/toggle$/.test(this.opts.collapsibleBehavior),r=/link$/.test(this.opts.collapsibleBehavior),h=/^accordion/.test(this.opts.collapsibleBehavior);if(s&&!s.is(":visible")){if((!r||!a||i)&&(this.opts.showOnClick&&o&&( +this.clickActivated=!0),this.itemActivate(e,h),s.is(":visible")))return this.focusActivated=!0,!1}else if(a&&(n||i))return this.itemActivate(e,h),this.menuHide(s),n&&(this.focusActivated=!1),!1;return this.opts.showOnClick&&o||e.hasClass("disabled")||this.$root.triggerHandler("select.smapi",e[0])===!1?!1:void 0}},itemDown:function(t){var e=$(t.currentTarget);this.handleItemEvents(e)&&e.dataSM("mousedown",!0)},itemEnter:function(t){var e=$(t.currentTarget);if(this.handleItemEvents(e)){if(!this.isTouchMode()){this.showTimeout&&(clearTimeout(this.showTimeout),this.showTimeout=0);var i=this;this.showTimeout=setTimeout(function(){i.itemActivate(e)},this.opts.showOnClick&&1==e.closest("ul").dataSM("level")?1:this.opts.showTimeout)}this.$root.triggerHandler("mouseenter.smapi",e[0])}},itemFocus:function(t){var e=$(t.currentTarget);this.handleItemEvents(e)&&(!this.focusActivated||this.isTouchMode()&&e.dataSM("mousedown")||this.activatedItems.length&&this.activatedItems[this.activatedItems.length-1][0]==e[0 +]||this.itemActivate(e,!0),this.$root.triggerHandler("focus.smapi",e[0]))},itemLeave:function(t){var e=$(t.currentTarget);this.handleItemEvents(e)&&(this.isTouchMode()||(e[0].blur(),this.showTimeout&&(clearTimeout(this.showTimeout),this.showTimeout=0)),e.removeDataSM("mousedown"),this.$root.triggerHandler("mouseleave.smapi",e[0]))},menuHide:function(t){if(this.$root.triggerHandler("beforehide.smapi",t[0])!==!1&&(canAnimate&&t.stop(!0,!0),"none"!=t.css("display"))){var e=function(){t.css("z-index","")};this.isCollapsible()?canAnimate&&this.opts.collapsibleHideFunction?this.opts.collapsibleHideFunction.call(this,t,e):t.hide(this.opts.collapsibleHideDuration,e):canAnimate&&this.opts.hideFunction?this.opts.hideFunction.call(this,t,e):t.hide(this.opts.hideDuration,e),t.dataSM("scroll")&&(this.menuScrollStop(t),t.css({"touch-action":"","-ms-touch-action":"","-webkit-transform":"",transform:""}).off(".smartmenus_scroll").removeDataSM("scroll").dataSM("scroll-arrows").hide()),t.dataSM("parent-a").removeClass( +"highlighted").attr("aria-expanded","false"),t.attr({"aria-expanded":"false","aria-hidden":"true"});var i=t.dataSM("level");this.activatedItems.splice(i-1,1),this.visibleSubMenus.splice($.inArray(t,this.visibleSubMenus),1),this.$root.triggerHandler("hide.smapi",t[0])}},menuHideAll:function(){this.showTimeout&&(clearTimeout(this.showTimeout),this.showTimeout=0);for(var t=this.opts.isPopup?1:0,e=this.visibleSubMenus.length-1;e>=t;e--)this.menuHide(this.visibleSubMenus[e]);this.opts.isPopup&&(canAnimate&&this.$root.stop(!0,!0),this.$root.is(":visible")&&(canAnimate&&this.opts.hideFunction?this.opts.hideFunction.call(this,this.$root):this.$root.hide(this.opts.hideDuration))),this.activatedItems=[],this.visibleSubMenus=[],this.clickActivated=!1,this.focusActivated=!1,this.zIndexInc=0,this.$root.triggerHandler("hideAll.smapi")},menuHideSubMenus:function(t){for(var e=this.activatedItems.length-1;e>=t;e--){var i=this.activatedItems[e].dataSM("sub");i&&this.menuHide(i)}},menuInit:function(t){if(!t.dataSM("in-mega")){ +t.hasClass("mega-menu")&&t.find("ul").dataSM("in-mega",!0);for(var e=2,i=t[0];(i=i.parentNode.parentNode)!=this.$root[0];)e++;var s=t.prevAll("a").eq(-1);s.length||(s=t.prevAll().find("a").eq(-1)),s.addClass("has-submenu").dataSM("sub",t),t.dataSM("parent-a",s).dataSM("level",e).parent().dataSM("sub",t);var o=s.attr("id")||this.accessIdPrefix+ ++this.idInc,a=t.attr("id")||this.accessIdPrefix+ ++this.idInc;s.attr({id:o,"aria-haspopup":"true","aria-controls":a,"aria-expanded":"false"}),t.attr({id:a,role:"group","aria-hidden":"true","aria-labelledby":o,"aria-expanded":"false"}),this.opts.subIndicators&&s[this.opts.subIndicatorsPos](this.$subArrow.clone())}},menuPosition:function(t){var e,i,s=t.dataSM("parent-a"),o=s.closest("li"),a=o.parent(),n=t.dataSM("level"),r=this.getWidth(t),h=this.getHeight(t),u=s.offset(),l=u.left,c=u.top,d=this.getWidth(s),m=this.getHeight(s),p=$(window),f=p.scrollLeft(),v=p.scrollTop(),b=this.getViewportWidth(),S=this.getViewportHeight(),g=a.parent().is("[data-sm-horizontal-sub]" +)||2==n&&!a.hasClass("sm-vertical"),M=this.opts.rightToLeftSubMenus&&!o.is("[data-sm-reverse]")||!this.opts.rightToLeftSubMenus&&o.is("[data-sm-reverse]"),w=2==n?this.opts.mainMenuSubOffsetX:this.opts.subMenusSubOffsetX,T=2==n?this.opts.mainMenuSubOffsetY:this.opts.subMenusSubOffsetY;if(g?(e=M?d-r-w:w,i=this.opts.bottomToTopSubMenus?-h-T:m+T):(e=M?w-r:d-w,i=this.opts.bottomToTopSubMenus?m-T-h:T),this.opts.keepInViewport){var y=l+e,I=c+i;if(M&&f>y?e=g?f-y+e:d-w:!M&&y+r>f+b&&(e=g?f+b-r-y+e:w-r),g||(S>h&&I+h>v+S?i+=v+S-h-I:(h>=S||v>I)&&(i+=v-I)),g&&(I+h>v+S+.49||v>I)||!g&&h>S+.49){var x=this;t.dataSM("scroll-arrows")||t.dataSM("scroll-arrows",$([$('')[0],$('')[0]]).on({mouseenter:function(){t.dataSM("scroll").up=$(this).hasClass("scroll-up"),x.menuScroll(t)},mouseleave:function(e){x.menuScrollStop(t),x.menuScrollOut(t,e)},"mousewheel DOMMouseScroll":function(t){ +t.preventDefault()}}).insertAfter(t));var A=".smartmenus_scroll";if(t.dataSM("scroll",{y:this.cssTransforms3d?0:i-m,step:1,itemH:m,subH:h,arrowDownH:this.getHeight(t.dataSM("scroll-arrows").eq(1))}).on(getEventsNS({mouseover:function(e){x.menuScrollOver(t,e)},mouseout:function(e){x.menuScrollOut(t,e)},"mousewheel DOMMouseScroll":function(e){x.menuScrollMousewheel(t,e)}},A)).dataSM("scroll-arrows").css({top:"auto",left:"0",marginLeft:e+(parseInt(t.css("border-left-width"))||0),width:r-(parseInt(t.css("border-left-width"))||0)-(parseInt(t.css("border-right-width"))||0),zIndex:t.css("z-index")}).eq(g&&this.opts.bottomToTopSubMenus?0:1).show(),this.isFixed()){var C={};C[touchEvents?"touchstart touchmove touchend":"pointerdown pointermove pointerup MSPointerDown MSPointerMove MSPointerUp"]=function(e){x.menuScrollTouch(t,e)},t.css({"touch-action":"none","-ms-touch-action":"none"}).on(getEventsNS(C,A))}}}t.css({top:"auto",left:"0",marginLeft:e,marginTop:i-m})},menuScroll:function(t,e,i){var s,o=t.dataSM("scroll"), +a=t.dataSM("scroll-arrows"),n=o.up?o.upEnd:o.downEnd;if(!e&&o.momentum){if(o.momentum*=.92,s=o.momentum,.5>s)return this.menuScrollStop(t),void 0}else s=i||(e||!this.opts.scrollAccelerate?this.opts.scrollStep:Math.floor(o.step));var r=t.dataSM("level");if(this.activatedItems[r-1]&&this.activatedItems[r-1].dataSM("sub")&&this.activatedItems[r-1].dataSM("sub").is(":visible")&&this.menuHideSubMenus(r-1),o.y=o.up&&o.y>=n||!o.up&&n>=o.y?o.y:Math.abs(n-o.y)>s?o.y+(o.up?s:-s):n,t.css(this.cssTransforms3d?{"-webkit-transform":"translate3d(0, "+o.y+"px, 0)",transform:"translate3d(0, "+o.y+"px, 0)"}:{marginTop:o.y}),mouse&&(o.up&&o.y>o.downEnd||!o.up&&o.y0;t.dataSM("scroll-arrows").eq(i?0:1).is(":visible")&&(t.dataSM("scroll").up=i,this.menuScroll(t,!0))}e.preventDefault()},menuScrollOut:function(t,e){mouse&&(/^scroll-(up|down)/.test((e.relatedTarget||"").className)||(t[0]==e.relatedTarget||$.contains(t[0],e.relatedTarget))&&this.getClosestMenu(e.relatedTarget)==t[0]||t.dataSM("scroll-arrows").css("visibility","hidden"))},menuScrollOver:function(t,e){if(mouse&&!/^scroll-(up|down)/.test(e.target.className)&&this.getClosestMenu(e.target)==t[0]){this.menuScrollRefreshData(t);var i=t.dataSM("scroll"),s=$(window).scrollTop()-t.dataSM("parent-a").offset().top-i.itemH;t.dataSM("scroll-arrows").eq(0).css("margin-top",s).end().eq(1).css("margin-top",s+this.getViewportHeight()-i.arrowDownH).end().css("visibility","visible")}},menuScrollRefreshData:function(t){var e=t.dataSM("scroll"),i=$(window).scrollTop()-t.dataSM("parent-a").offset().top-e.itemH;this.cssTransforms3d&&(i=-(parseFloat(t.css("margin-top"))-i)),$.extend(e,{upEnd:i, +downEnd:i+this.getViewportHeight()-e.subH})},menuScrollStop:function(t){return this.scrollTimeout?(cancelAnimationFrame(this.scrollTimeout),this.scrollTimeout=0,t.dataSM("scroll").step=1,!0):void 0},menuScrollTouch:function(t,e){if(e=e.originalEvent,isTouchEvent(e)){var i=this.getTouchPoint(e);if(this.getClosestMenu(i.target)==t[0]){var s=t.dataSM("scroll");if(/(start|down)$/i.test(e.type))this.menuScrollStop(t)?(e.preventDefault(),this.$touchScrollingSub=t):this.$touchScrollingSub=null,this.menuScrollRefreshData(t),$.extend(s,{touchStartY:i.pageY,touchStartTime:e.timeStamp});else if(/move$/i.test(e.type)){var o=void 0!==s.touchY?s.touchY:s.touchStartY;if(void 0!==o&&o!=i.pageY){this.$touchScrollingSub=t;var a=i.pageY>o;void 0!==s.up&&s.up!=a&&$.extend(s,{touchStartY:i.pageY,touchStartTime:e.timeStamp}),$.extend(s,{up:a,touchY:i.pageY}),this.menuScroll(t,!0,Math.abs(i.pageY-o))}e.preventDefault()}else void 0!==s.touchY&&((s.momentum=15*Math.pow(Math.abs(i.pageY-s.touchStartY)/(e.timeStamp-s.touchStartTime),2) +)&&(this.menuScrollStop(t),this.menuScroll(t),e.preventDefault()),delete s.touchY)}}},menuShow:function(t){if((t.dataSM("beforefirstshowfired")||(t.dataSM("beforefirstshowfired",!0),this.$root.triggerHandler("beforefirstshow.smapi",t[0])!==!1))&&this.$root.triggerHandler("beforeshow.smapi",t[0])!==!1&&(t.dataSM("shown-before",!0),canAnimate&&t.stop(!0,!0),!t.is(":visible"))){var e=t.dataSM("parent-a"),i=this.isCollapsible();if((this.opts.keepHighlighted||i)&&e.addClass("highlighted"),i)t.removeClass("sm-nowrap").css({zIndex:"",width:"auto",minWidth:"",maxWidth:"",top:"",left:"",marginLeft:"",marginTop:""});else{if(t.css("z-index",this.zIndexInc=(this.zIndexInc||this.getStartZIndex())+1),(this.opts.subMenusMinWidth||this.opts.subMenusMaxWidth)&&(t.css({width:"auto",minWidth:"",maxWidth:""}).addClass("sm-nowrap"),this.opts.subMenusMinWidth&&t.css("min-width",this.opts.subMenusMinWidth),this.opts.subMenusMaxWidth)){var s=this.getWidth(t);t.css("max-width",this.opts.subMenusMaxWidth),s>this.getWidth(t +)&&t.removeClass("sm-nowrap").css("width",this.opts.subMenusMaxWidth)}this.menuPosition(t)}var o=function(){t.css("overflow","")};i?canAnimate&&this.opts.collapsibleShowFunction?this.opts.collapsibleShowFunction.call(this,t,o):t.show(this.opts.collapsibleShowDuration,o):canAnimate&&this.opts.showFunction?this.opts.showFunction.call(this,t,o):t.show(this.opts.showDuration,o),e.attr("aria-expanded","true"),t.attr({"aria-expanded":"true","aria-hidden":"false"}),this.visibleSubMenus.push(t),this.$root.triggerHandler("show.smapi",t[0])}},popupHide:function(t){this.hideTimeout&&(clearTimeout(this.hideTimeout),this.hideTimeout=0);var e=this;this.hideTimeout=setTimeout(function(){e.menuHideAll()},t?1:this.opts.hideTimeout)},popupShow:function(t,e){if(!this.opts.isPopup)return alert('SmartMenus jQuery Error:\n\nIf you want to show this menu via the "popupShow" method, set the isPopup:true option.'),void 0;if(this.hideTimeout&&(clearTimeout(this.hideTimeout),this.hideTimeout=0),this.$root.dataSM("shown-before",!0), +canAnimate&&this.$root.stop(!0,!0),!this.$root.is(":visible")){this.$root.css({left:t,top:e});var i=this,s=function(){i.$root.css("overflow","")};canAnimate&&this.opts.showFunction?this.opts.showFunction.call(this,this.$root,s):this.$root.show(this.opts.showDuration,s),this.visibleSubMenus[0]=this.$root}},refresh:function(){this.destroy(!0),this.init(!0)},rootKeyDown:function(t){if(this.handleEvents())switch(t.keyCode){case 27:var e=this.activatedItems[0];if(e){this.menuHideAll(),e[0].focus();var i=e.dataSM("sub");i&&this.menuHide(i)}break;case 32:var s=$(t.target);if(s.is("a")&&this.handleItemEvents(s)){var i=s.dataSM("sub");i&&!i.is(":visible")&&(this.itemClick({currentTarget:t.target}),t.preventDefault())}}},rootOut:function(t){if(this.handleEvents()&&!this.isTouchMode()&&t.target!=this.$root[0]&&(this.hideTimeout&&(clearTimeout(this.hideTimeout),this.hideTimeout=0),!this.opts.showOnClick||!this.opts.hideOnClick)){var e=this;this.hideTimeout=setTimeout(function(){e.menuHideAll()},this.opts.hideTimeout)}}, +rootOver:function(t){this.handleEvents()&&!this.isTouchMode()&&t.target!=this.$root[0]&&this.hideTimeout&&(clearTimeout(this.hideTimeout),this.hideTimeout=0)},winResize:function(t){if(this.handleEvents()){if(!("onorientationchange"in window)||"orientationchange"==t.type){var e=this.isCollapsible();this.wasCollapsible&&e||(this.activatedItems.length&&this.activatedItems[this.activatedItems.length-1][0].blur(),this.menuHideAll()),this.wasCollapsible=e}}else if(this.$disableOverlay){var i=this.$root.offset();this.$disableOverlay.css({top:i.top,left:i.left,width:this.$root.outerWidth(),height:this.$root.outerHeight()})}}}}),$.fn.dataSM=function(t,e){return e?this.data(t+"_smartmenus",e):this.data(t+"_smartmenus")},$.fn.removeDataSM=function(t){return this.removeData(t+"_smartmenus")},$.fn.smartmenus=function(options){if("string"==typeof options){var args=arguments,method=options;return Array.prototype.shift.call(args),this.each(function(){var t=$(this).data("smartmenus");t&&t[method]&&t[method].apply(t,args)})} +return this.each(function(){var dataOpts=$(this).data("sm-options")||null;if(dataOpts)try{dataOpts=eval("("+dataOpts+")")}catch(e){dataOpts=null,alert('ERROR\n\nSmartMenus jQuery init:\nInvalid "data-sm-options" attribute value syntax.')}new $.SmartMenus(this,$.extend({},$.fn.smartmenus.defaults,options,dataOpts))})},$.fn.smartmenus.defaults={isPopup:!1,mainMenuSubOffsetX:0,mainMenuSubOffsetY:0,subMenusSubOffsetX:0,subMenusSubOffsetY:0,subMenusMinWidth:"10em",subMenusMaxWidth:"20em",subIndicators:!0,subIndicatorsPos:"append",subIndicatorsText:"",scrollStep:30,scrollAccelerate:!0,showTimeout:250,hideTimeout:500,showDuration:0,showFunction:null,hideDuration:0,hideFunction:function(t,e){t.fadeOut(200,e)},collapsibleShowDuration:0,collapsibleShowFunction:function(t,e){t.slideDown(200,e)},collapsibleHideDuration:0,collapsibleHideFunction:function(t,e){t.slideUp(200,e)},showOnClick:!1,hideOnClick:!0,noMouseOver:!1,keepInViewport:!0,keepHighlighted:!0,markCurrentItem:!1,markCurrentTree:!0,rightToLeftSubMenus:!1, +bottomToTopSubMenus:!1,collapsibleBehavior:"default"},$}); diff --git a/doc/html/menu.js b/doc/html/menu.js new file mode 100644 index 0000000..15f9c52 --- /dev/null +++ b/doc/html/menu.js @@ -0,0 +1,131 @@ +/* + @licstart The following is the entire license notice for the JavaScript code in this file. + + The MIT License (MIT) + + Copyright (C) 1997-2020 by Dimitri van Heesch + + Permission is hereby granted, free of charge, to any person obtaining a copy of this software + and associated documentation files (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, publish, distribute, + sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all copies or + substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING + BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + @licend The above is the entire license notice for the JavaScript code in this file + */ +function initMenu(relPath,searchEnabled,serverSide,searchPage,search,treeview) { + function makeTree(data,relPath) { + let result=''; + if ('children' in data) { + result+='
    '; + for (let i in data.children) { + let url; + const link = data.children[i].url; + if (link.substring(0,1)=='^') { + url = link.substring(1); + } else { + url = relPath+link; + } + result+='
  • '+ + data.children[i].text+''+ + makeTree(data.children[i],relPath)+'
  • '; + } + result+='
'; + } + return result; + } + let searchBoxHtml; + if (searchEnabled) { + if (serverSide) { + searchBoxHtml='
'+ + '
'+ + '
'+ + ''+ + '
'+ + '
'+ + '
'+ + '
'; + } else { + searchBoxHtml='
'+ + ''+ + ''+ + ''+ + ''+ + ''+ + '
'+ + '
'+ + '
'; + } + } + + $('#main-nav').before('
'+ + ''+ + ''+ + '
'); + $('#main-nav').append(makeTree(menudata,relPath)); + $('#main-nav').children(':first').addClass('sm sm-dox').attr('id','main-menu'); + $('#main-menu').append('
  • '); + const $mainMenuState = $('#main-menu-state'); + let prevWidth = 0; + if ($mainMenuState.length) { + const initResizableIfExists = function() { + if (typeof initResizable==='function') initResizable(treeview); + } + // animate mobile menu + $mainMenuState.change(function() { + const $menu = $('#main-menu'); + let options = { duration: 250, step: initResizableIfExists }; + if (this.checked) { + options['complete'] = () => $menu.css('display', 'block'); + $menu.hide().slideDown(options); + } else { + options['complete'] = () => $menu.css('display', 'none'); + $menu.show().slideUp(options); + } + }); + // set default menu visibility + const resetState = function() { + const $menu = $('#main-menu'); + const newWidth = $(window).outerWidth(); + if (newWidth!=prevWidth) { + if ($(window).outerWidth()<768) { + $mainMenuState.prop('checked',false); $menu.hide(); + $('#searchBoxPos1').html(searchBoxHtml); + $('#searchBoxPos2').hide(); + } else { + $menu.show(); + $('#searchBoxPos1').empty(); + $('#searchBoxPos2').html(searchBoxHtml); + $('#searchBoxPos2').show(); + } + if (typeof searchBox!=='undefined') { + searchBox.CloseResultsWindow(); + } + prevWidth = newWidth; + } + } + $(window).ready(function() { resetState(); initResizableIfExists(); }); + $(window).resize(resetState); + } + $('#main-menu').smartmenus(); +} +/* @license-end */ diff --git a/doc/html/menudata.js b/doc/html/menudata.js new file mode 100644 index 0000000..2341ca8 --- /dev/null +++ b/doc/html/menudata.js @@ -0,0 +1,85 @@ +/* + @licstart The following is the entire license notice for the JavaScript code in this file. + + The MIT License (MIT) + + Copyright (C) 1997-2020 by Dimitri van Heesch + + Permission is hereby granted, free of charge, to any person obtaining a copy of this software + and associated documentation files (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, publish, distribute, + sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all copies or + substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING + BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + @licend The above is the entire license notice for the JavaScript code in this file +*/ +var menudata={children:[ +{text:"Main Page",url:"index.html"}, +{text:"Data Structures",url:"annotated.html",children:[ +{text:"Data Structures",url:"annotated.html"}, +{text:"Data Fields",url:"functions.html",children:[ +{text:"All",url:"functions.html",children:[ +{text:"a",url:"functions.html#index_a"}, +{text:"b",url:"functions.html#index_b"}, +{text:"c",url:"functions.html#index_c"}, +{text:"d",url:"functions.html#index_d"}, +{text:"e",url:"functions.html#index_e"}, +{text:"f",url:"functions.html#index_f"}, +{text:"g",url:"functions.html#index_g"}, +{text:"h",url:"functions.html#index_h"}, +{text:"i",url:"functions.html#index_i"}, +{text:"k",url:"functions.html#index_k"}, +{text:"l",url:"functions.html#index_l"}, +{text:"m",url:"functions.html#index_m"}, +{text:"n",url:"functions.html#index_n"}, +{text:"o",url:"functions.html#index_o"}, +{text:"p",url:"functions.html#index_p"}, +{text:"r",url:"functions.html#index_r"}, +{text:"s",url:"functions.html#index_s"}, +{text:"t",url:"functions.html#index_t"}, +{text:"u",url:"functions.html#index_u"}, +{text:"v",url:"functions.html#index_v"}, +{text:"w",url:"functions.html#index_w"}]}, +{text:"Variables",url:"functions_vars.html",children:[ +{text:"a",url:"functions_vars.html#index_a"}, +{text:"b",url:"functions_vars.html#index_b"}, +{text:"c",url:"functions_vars.html#index_c"}, +{text:"d",url:"functions_vars.html#index_d"}, +{text:"e",url:"functions_vars.html#index_e"}, +{text:"f",url:"functions_vars.html#index_f"}, +{text:"g",url:"functions_vars.html#index_g"}, +{text:"h",url:"functions_vars.html#index_h"}, +{text:"i",url:"functions_vars.html#index_i"}, +{text:"k",url:"functions_vars.html#index_k"}, +{text:"l",url:"functions_vars.html#index_l"}, +{text:"m",url:"functions_vars.html#index_m"}, +{text:"n",url:"functions_vars.html#index_n"}, +{text:"o",url:"functions_vars.html#index_o"}, +{text:"p",url:"functions_vars.html#index_p"}, +{text:"r",url:"functions_vars.html#index_r"}, +{text:"s",url:"functions_vars.html#index_s"}, +{text:"t",url:"functions_vars.html#index_t"}, +{text:"u",url:"functions_vars.html#index_u"}, +{text:"v",url:"functions_vars.html#index_v"}, +{text:"w",url:"functions_vars.html#index_w"}]}]}]}, +{text:"Files",url:"files.html",children:[ +{text:"File List",url:"files.html"}, +{text:"Globals",url:"globals.html",children:[ +{text:"All",url:"globals.html",children:[ +{text:"f",url:"globals.html#index_f"}]}, +{text:"Functions",url:"globals_func.html",children:[ +{text:"f",url:"globals_func.html#index_f"}]}, +{text:"Typedefs",url:"globals_type.html"}, +{text:"Enumerations",url:"globals_enum.html"}, +{text:"Enumerator",url:"globals_eval.html"}, +{text:"Macros",url:"globals_defs.html",children:[ +{text:"f",url:"globals_defs.html#index_f"}]}]}]}]} diff --git a/doc/html/mount_8c_source.html b/doc/html/mount_8c_source.html new file mode 100644 index 0000000..d8d5d56 --- /dev/null +++ b/doc/html/mount_8c_source.html @@ -0,0 +1,799 @@ + + + + + + + +libfuse: lib/mount.c Source File + + + + + + + +
    +
    + + + + + + +
    +
    libfuse +
    +
    +
    + + + + + + + + +
    +
    +
    +
    mount.c
    +
    +
    +
    1/*
    +
    2 FUSE: Filesystem in Userspace
    +
    3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
    +
    4
    +
    5 Architecture specific file system mounting (Linux).
    +
    6
    +
    7 This program can be distributed under the terms of the GNU LGPLv2.
    +
    8 See the file LGPL2.txt.
    +
    9*/
    +
    10
    +
    11/* For environ */
    +
    12#define _GNU_SOURCE
    +
    13
    +
    14#include "fuse_config.h"
    +
    15#include "fuse_i.h"
    +
    16#include "fuse_misc.h"
    +
    17#include "fuse_opt.h"
    +
    18#include "mount_util.h"
    +
    19
    +
    20#include <stdio.h>
    +
    21#include <stdlib.h>
    +
    22#include <unistd.h>
    +
    23#include <stddef.h>
    +
    24#include <string.h>
    +
    25#include <fcntl.h>
    +
    26#include <errno.h>
    +
    27#include <poll.h>
    +
    28#include <spawn.h>
    +
    29#include <sys/socket.h>
    +
    30#include <sys/un.h>
    +
    31#include <sys/wait.h>
    +
    32
    +
    33#include "fuse_mount_compat.h"
    +
    34
    +
    35#ifdef __NetBSD__
    +
    36#include <perfuse.h>
    +
    37
    +
    38#define MS_RDONLY MNT_RDONLY
    +
    39#define MS_NOSUID MNT_NOSUID
    +
    40#define MS_NODEV MNT_NODEV
    +
    41#define MS_NOEXEC MNT_NOEXEC
    +
    42#define MS_SYNCHRONOUS MNT_SYNCHRONOUS
    +
    43#define MS_NOATIME MNT_NOATIME
    +
    44#define MS_NOSYMFOLLOW MNT_NOSYMFOLLOW
    +
    45
    +
    46#define umount2(mnt, flags) unmount(mnt, (flags == 2) ? MNT_FORCE : 0)
    +
    47#endif
    +
    48
    +
    49#define FUSERMOUNT_PROG "fusermount3"
    +
    50#define FUSE_COMMFD_ENV "_FUSE_COMMFD"
    +
    51#define FUSE_COMMFD2_ENV "_FUSE_COMMFD2"
    +
    52#define FUSE_KERN_DEVICE_ENV "FUSE_KERN_DEVICE"
    +
    53
    +
    54#ifndef MS_DIRSYNC
    +
    55#define MS_DIRSYNC 128
    +
    56#endif
    +
    57
    +
    58enum {
    +
    59 KEY_KERN_FLAG,
    +
    60 KEY_KERN_OPT,
    +
    61 KEY_FUSERMOUNT_OPT,
    +
    62 KEY_SUBTYPE_OPT,
    +
    63 KEY_MTAB_OPT,
    +
    64 KEY_ALLOW_OTHER,
    +
    65 KEY_RO,
    +
    66};
    +
    67
    +
    68struct mount_opts {
    +
    69 int allow_other;
    +
    70 int flags;
    +
    71 int auto_unmount;
    +
    72 int blkdev;
    +
    73 char *fsname;
    +
    74 char *subtype;
    +
    75 char *subtype_opt;
    +
    76 char *mtab_opts;
    +
    77 char *fusermount_opts;
    +
    78 char *kernel_opts;
    +
    79 unsigned max_read;
    +
    80};
    +
    81
    +
    82#define FUSE_MOUNT_OPT(t, p) { t, offsetof(struct mount_opts, p), 1 }
    +
    83
    +
    84static const struct fuse_opt fuse_mount_opts[] = {
    +
    85 FUSE_MOUNT_OPT("allow_other", allow_other),
    +
    86 FUSE_MOUNT_OPT("blkdev", blkdev),
    +
    87 FUSE_MOUNT_OPT("auto_unmount", auto_unmount),
    +
    88 FUSE_MOUNT_OPT("fsname=%s", fsname),
    +
    89 FUSE_MOUNT_OPT("max_read=%u", max_read),
    +
    90 FUSE_MOUNT_OPT("subtype=%s", subtype),
    +
    91 FUSE_OPT_KEY("allow_other", KEY_KERN_OPT),
    +
    92 FUSE_OPT_KEY("auto_unmount", KEY_FUSERMOUNT_OPT),
    +
    93 FUSE_OPT_KEY("blkdev", KEY_FUSERMOUNT_OPT),
    +
    94 FUSE_OPT_KEY("fsname=", KEY_FUSERMOUNT_OPT),
    +
    95 FUSE_OPT_KEY("subtype=", KEY_SUBTYPE_OPT),
    +
    96 FUSE_OPT_KEY("blksize=", KEY_KERN_OPT),
    +
    97 FUSE_OPT_KEY("default_permissions", KEY_KERN_OPT),
    +
    98 FUSE_OPT_KEY("context=", KEY_KERN_OPT),
    +
    99 FUSE_OPT_KEY("fscontext=", KEY_KERN_OPT),
    +
    100 FUSE_OPT_KEY("defcontext=", KEY_KERN_OPT),
    +
    101 FUSE_OPT_KEY("rootcontext=", KEY_KERN_OPT),
    +
    102 FUSE_OPT_KEY("max_read=", KEY_KERN_OPT),
    +
    103 FUSE_OPT_KEY("user=", KEY_MTAB_OPT),
    +
    104 FUSE_OPT_KEY("-n", KEY_MTAB_OPT),
    +
    105 FUSE_OPT_KEY("-r", KEY_RO),
    +
    106 FUSE_OPT_KEY("ro", KEY_KERN_FLAG),
    +
    107 FUSE_OPT_KEY("rw", KEY_KERN_FLAG),
    +
    108 FUSE_OPT_KEY("suid", KEY_KERN_FLAG),
    +
    109 FUSE_OPT_KEY("nosuid", KEY_KERN_FLAG),
    +
    110 FUSE_OPT_KEY("dev", KEY_KERN_FLAG),
    +
    111 FUSE_OPT_KEY("nodev", KEY_KERN_FLAG),
    +
    112 FUSE_OPT_KEY("exec", KEY_KERN_FLAG),
    +
    113 FUSE_OPT_KEY("noexec", KEY_KERN_FLAG),
    +
    114 FUSE_OPT_KEY("async", KEY_KERN_FLAG),
    +
    115 FUSE_OPT_KEY("sync", KEY_KERN_FLAG),
    +
    116 FUSE_OPT_KEY("dirsync", KEY_KERN_FLAG),
    +
    117 FUSE_OPT_KEY("noatime", KEY_KERN_FLAG),
    +
    118 FUSE_OPT_KEY("nodiratime", KEY_KERN_FLAG),
    +
    119 FUSE_OPT_KEY("nostrictatime", KEY_KERN_FLAG),
    +
    120 FUSE_OPT_KEY("symfollow", KEY_KERN_FLAG),
    +
    121 FUSE_OPT_KEY("nosymfollow", KEY_KERN_FLAG),
    + +
    123};
    +
    124
    +
    125/*
    +
    126 * Running fusermount by calling 'posix_spawn'
    +
    127 *
    +
    128 * @param out_pid might be NULL
    +
    129 */
    +
    130static int fusermount_posix_spawn(posix_spawn_file_actions_t *action,
    +
    131 char const * const argv[], pid_t *out_pid)
    +
    132{
    +
    133 const char *full_path = FUSERMOUNT_DIR "/" FUSERMOUNT_PROG;
    +
    134 pid_t pid;
    +
    135
    +
    136 /* See man 7 environ for the global environ pointer */
    +
    137
    +
    138 /* first try the install path */
    +
    139 int status = posix_spawn(&pid, full_path, action, NULL,
    +
    140 (char * const *) argv, environ);
    +
    141 if (status != 0) {
    +
    142 /* if that fails, try a system install */
    +
    143 status = posix_spawnp(&pid, FUSERMOUNT_PROG, action, NULL,
    +
    144 (char * const *) argv, environ);
    +
    145 }
    +
    146
    +
    147 if (status != 0) {
    +
    148 fuse_log(FUSE_LOG_ERR, "Failed to call '%s': %s\n",
    +
    149 FUSERMOUNT_PROG, strerror(status));
    +
    150 return -status;
    +
    151 }
    +
    152
    +
    153 if (out_pid)
    +
    154 *out_pid = pid;
    +
    155 else
    +
    156 waitpid(pid, NULL, 0); /* FIXME: check exit code and return error if any */
    +
    157
    +
    158 return 0;
    +
    159}
    +
    160
    +
    161void fuse_mount_version(void)
    +
    162{
    +
    163 char const *const argv[] = {FUSERMOUNT_PROG, "--version", NULL};
    +
    164 int status = fusermount_posix_spawn(NULL, argv, NULL);
    +
    165
    +
    166 if(status != 0)
    +
    167 fuse_log(FUSE_LOG_ERR, "Running '%s --version' failed",
    +
    168 FUSERMOUNT_PROG);
    +
    169}
    +
    170
    +
    171struct mount_flags {
    +
    172 const char *opt;
    +
    173 unsigned long flag;
    +
    174 int on;
    +
    175};
    +
    176
    +
    177static const struct mount_flags mount_flags[] = {
    +
    178 {"rw", MS_RDONLY, 0},
    +
    179 {"ro", MS_RDONLY, 1},
    +
    180 {"suid", MS_NOSUID, 0},
    +
    181 {"nosuid", MS_NOSUID, 1},
    +
    182 {"dev", MS_NODEV, 0},
    +
    183 {"nodev", MS_NODEV, 1},
    +
    184 {"exec", MS_NOEXEC, 0},
    +
    185 {"noexec", MS_NOEXEC, 1},
    +
    186 {"async", MS_SYNCHRONOUS, 0},
    +
    187 {"sync", MS_SYNCHRONOUS, 1},
    +
    188 {"noatime", MS_NOATIME, 1},
    +
    189 {"nodiratime", MS_NODIRATIME, 1},
    +
    190 {"norelatime", MS_RELATIME, 0},
    +
    191 {"nostrictatime", MS_STRICTATIME, 0},
    +
    192 {"symfollow", MS_NOSYMFOLLOW, 0},
    +
    193 {"nosymfollow", MS_NOSYMFOLLOW, 1},
    +
    194#ifndef __NetBSD__
    +
    195 {"dirsync", MS_DIRSYNC, 1},
    +
    196#endif
    +
    197 {NULL, 0, 0}
    +
    198};
    +
    199
    +
    200unsigned get_max_read(struct mount_opts *o)
    +
    201{
    +
    202 return o->max_read;
    +
    203}
    +
    204
    +
    205static void set_mount_flag(const char *s, int *flags)
    +
    206{
    +
    207 int i;
    +
    208
    +
    209 for (i = 0; mount_flags[i].opt != NULL; i++) {
    +
    210 const char *opt = mount_flags[i].opt;
    +
    211 if (strcmp(opt, s) == 0) {
    +
    212 if (mount_flags[i].on)
    +
    213 *flags |= mount_flags[i].flag;
    +
    214 else
    +
    215 *flags &= ~mount_flags[i].flag;
    +
    216 return;
    +
    217 }
    +
    218 }
    +
    219 fuse_log(FUSE_LOG_ERR, "fuse: internal error, can't find mount flag\n");
    +
    220 abort();
    +
    221}
    +
    222
    +
    223static int fuse_mount_opt_proc(void *data, const char *arg, int key,
    +
    224 struct fuse_args *outargs)
    +
    225{
    +
    226 (void) outargs;
    +
    227 struct mount_opts *mo = data;
    +
    228
    +
    229 switch (key) {
    +
    230 case KEY_RO:
    +
    231 arg = "ro";
    +
    232 /* fall through */
    +
    233 case KEY_KERN_FLAG:
    +
    234 set_mount_flag(arg, &mo->flags);
    +
    235 return 0;
    +
    236
    +
    237 case KEY_KERN_OPT:
    +
    238 return fuse_opt_add_opt(&mo->kernel_opts, arg);
    +
    239
    +
    240 case KEY_FUSERMOUNT_OPT:
    +
    241 return fuse_opt_add_opt_escaped(&mo->fusermount_opts, arg);
    +
    242
    +
    243 case KEY_SUBTYPE_OPT:
    +
    244 return fuse_opt_add_opt(&mo->subtype_opt, arg);
    +
    245
    +
    246 case KEY_MTAB_OPT:
    +
    247 return fuse_opt_add_opt(&mo->mtab_opts, arg);
    +
    248
    +
    249 /* Third party options like 'x-gvfs-notrash' */
    +
    250 case FUSE_OPT_KEY_OPT:
    +
    251 return (strncmp("x-", arg, 2) == 0) ?
    +
    252 fuse_opt_add_opt(&mo->mtab_opts, arg) :
    +
    253 1;
    +
    254 }
    +
    255
    +
    256 /* Pass through unknown options */
    +
    257 return 1;
    +
    258}
    +
    259
    +
    260/* return value:
    +
    261 * >= 0 => fd
    +
    262 * -1 => error
    +
    263 */
    +
    264static int receive_fd(int fd)
    +
    265{
    +
    266 struct msghdr msg;
    +
    267 struct iovec iov;
    +
    268 char buf[1];
    +
    269 int rv;
    +
    270 size_t ccmsg[CMSG_SPACE(sizeof(int)) / sizeof(size_t)];
    +
    271 struct cmsghdr *cmsg;
    +
    272
    +
    273 iov.iov_base = buf;
    +
    274 iov.iov_len = 1;
    +
    275
    +
    276 memset(&msg, 0, sizeof(msg));
    +
    277 msg.msg_name = 0;
    +
    278 msg.msg_namelen = 0;
    +
    279 msg.msg_iov = &iov;
    +
    280 msg.msg_iovlen = 1;
    +
    281 /* old BSD implementations should use msg_accrights instead of
    +
    282 * msg_control; the interface is different. */
    +
    283 msg.msg_control = ccmsg;
    +
    284 msg.msg_controllen = sizeof(ccmsg);
    +
    285
    +
    286 while(((rv = recvmsg(fd, &msg, 0)) == -1) && errno == EINTR);
    +
    287 if (rv == -1) {
    +
    288 fuse_log(FUSE_LOG_ERR, "recvmsg failed: %s", strerror(errno));
    +
    289 return -1;
    +
    290 }
    +
    291 if(!rv) {
    +
    292 /* EOF */
    +
    293 return -1;
    +
    294 }
    +
    295
    +
    296 cmsg = CMSG_FIRSTHDR(&msg);
    +
    297 if (!cmsg) {
    +
    298 fuse_log(FUSE_LOG_ERR, "error retrieving control message header\n");
    +
    299 return -1;
    +
    300 }
    +
    301 if (cmsg->cmsg_type != SCM_RIGHTS) {
    +
    302 fuse_log(FUSE_LOG_ERR, "got control message of unknown type %d\n",
    +
    303 cmsg->cmsg_type);
    +
    304 return -1;
    +
    305 }
    +
    306 return *(int*)CMSG_DATA(cmsg);
    +
    307}
    +
    308
    +
    309void fuse_kern_unmount(const char *mountpoint, int fd)
    +
    310{
    +
    311 int res;
    +
    312
    +
    313 if (fd != -1) {
    +
    314 struct pollfd pfd;
    +
    315
    +
    316 pfd.fd = fd;
    +
    317 pfd.events = 0;
    +
    318 res = poll(&pfd, 1, 0);
    +
    319
    +
    320 /* Need to close file descriptor, otherwise synchronous umount
    +
    321 would recurse into filesystem, and deadlock.
    +
    322
    +
    323 Caller expects fuse_kern_unmount to close the fd, so close it
    +
    324 anyway. */
    +
    325 close(fd);
    +
    326
    +
    327 /* If file poll returns POLLERR on the device file descriptor,
    +
    328 then the filesystem is already unmounted or the connection
    +
    329 was severed via /sys/fs/fuse/connections/NNN/abort */
    +
    330 if (res == 1 && (pfd.revents & POLLERR))
    +
    331 return;
    +
    332 }
    +
    333
    +
    334 if (geteuid() == 0) {
    +
    335 fuse_mnt_umount("fuse", mountpoint, mountpoint, 1);
    +
    336 return;
    +
    337 }
    +
    338
    +
    339 res = umount2(mountpoint, MNT_DETACH | UMOUNT_NOFOLLOW);
    +
    340 if (res == 0)
    +
    341 return;
    +
    342
    +
    343 char const * const argv[] =
    +
    344 { FUSERMOUNT_PROG, "--unmount", "--quiet", "--lazy",
    +
    345 "--", mountpoint, NULL };
    +
    346 int status = fusermount_posix_spawn(NULL, argv, NULL);
    +
    347 if(status != 0) {
    +
    348 fuse_log(FUSE_LOG_ERR, "Spawning %s to unmount failed: %s",
    +
    349 FUSERMOUNT_PROG, strerror(-status));
    +
    350 return;
    +
    351 }
    +
    352}
    +
    353
    +
    354static int setup_auto_unmount(const char *mountpoint, int quiet)
    +
    355{
    +
    356 int fds[2];
    +
    357 pid_t pid;
    +
    358 int res;
    +
    359
    +
    360 if (!mountpoint) {
    +
    361 fuse_log(FUSE_LOG_ERR, "fuse: missing mountpoint parameter\n");
    +
    362 return -1;
    +
    363 }
    +
    364
    +
    365 res = socketpair(PF_UNIX, SOCK_STREAM, 0, fds);
    +
    366 if(res == -1) {
    +
    367 fuse_log(FUSE_LOG_ERR, "Setting up auto-unmount socketpair() failed: %s\n",
    +
    368 strerror(errno));
    +
    369 return -1;
    +
    370 }
    +
    371
    +
    372 char arg_fd_entry[30];
    +
    373 snprintf(arg_fd_entry, sizeof(arg_fd_entry), "%i", fds[0]);
    +
    374 setenv(FUSE_COMMFD_ENV, arg_fd_entry, 1);
    +
    375 /*
    +
    376 * This helps to identify the FD hold by parent process.
    +
    377 * In auto-unmount case, parent process can close this FD explicitly to do unmount.
    +
    378 * The FD[1] can be got via getenv(FUSE_COMMFD2_ENV).
    +
    379 * One potential use case is to satisfy FD-Leak checks.
    +
    380 */
    +
    381 snprintf(arg_fd_entry, sizeof(arg_fd_entry), "%i", fds[1]);
    +
    382 setenv(FUSE_COMMFD2_ENV, arg_fd_entry, 1);
    +
    383
    +
    384 char const *const argv[] = {
    +
    385 FUSERMOUNT_PROG,
    +
    386 "--auto-unmount",
    +
    387 "--",
    +
    388 mountpoint,
    +
    389 NULL,
    +
    390 };
    +
    391
    +
    392 // TODO: add error handling for all manipulations of action.
    +
    393 posix_spawn_file_actions_t action;
    +
    394 posix_spawn_file_actions_init(&action);
    +
    395
    +
    396 if (quiet) {
    +
    397 posix_spawn_file_actions_addopen(&action, STDOUT_FILENO, "/dev/null", O_WRONLY, 0);
    +
    398 posix_spawn_file_actions_addopen(&action, STDERR_FILENO, "/dev/null", O_WRONLY, 0);
    +
    399 }
    +
    400 posix_spawn_file_actions_addclose(&action, fds[1]);
    +
    401
    +
    402 /*
    +
    403 * auto-umount runs in the background - it is not waiting for the
    +
    404 * process
    +
    405 */
    +
    406 int status = fusermount_posix_spawn(&action, argv, &pid);
    +
    407
    +
    408 posix_spawn_file_actions_destroy(&action);
    +
    409
    +
    410 if(status != 0) {
    +
    411 close(fds[0]);
    +
    412 close(fds[1]);
    +
    413 fuse_log(FUSE_LOG_ERR, "fuse: Setting up auto-unmount failed (spawn): %s",
    +
    414 strerror(-status));
    +
    415 return -1;
    +
    416 }
    +
    417 // passed to child now, so can close here.
    +
    418 close(fds[0]);
    +
    419
    +
    420 // Now fusermount3 will only exit when fds[1] closes automatically when our
    +
    421 // process exits.
    +
    422 return 0;
    +
    423 // Note: fds[1] is leakend and doesn't get FD_CLOEXEC
    +
    424}
    +
    425
    +
    426static int fuse_mount_fusermount(const char *mountpoint, struct mount_opts *mo,
    +
    427 const char *opts, int quiet)
    +
    428{
    +
    429 int fds[2];
    +
    430 pid_t pid;
    +
    431 int res;
    +
    432
    +
    433 if (!mountpoint) {
    +
    434 fuse_log(FUSE_LOG_ERR, "fuse: missing mountpoint parameter\n");
    +
    435 return -1;
    +
    436 }
    +
    437
    +
    438 res = socketpair(PF_UNIX, SOCK_STREAM, 0, fds);
    +
    439 if(res == -1) {
    +
    440 fuse_log(FUSE_LOG_ERR, "Running %s: socketpair() failed: %s\n",
    +
    441 FUSERMOUNT_PROG, strerror(errno));
    +
    442 return -1;
    +
    443 }
    +
    444
    +
    445 char arg_fd_entry[30];
    +
    446 snprintf(arg_fd_entry, sizeof(arg_fd_entry), "%i", fds[0]);
    +
    447 setenv(FUSE_COMMFD_ENV, arg_fd_entry, 1);
    +
    448 /*
    +
    449 * This helps to identify the FD hold by parent process.
    +
    450 * In auto-unmount case, parent process can close this FD explicitly to do unmount.
    +
    451 * The FD[1] can be got via getenv(FUSE_COMMFD2_ENV).
    +
    452 * One potential use case is to satisfy FD-Leak checks.
    +
    453 */
    +
    454 snprintf(arg_fd_entry, sizeof(arg_fd_entry), "%i", fds[1]);
    +
    455 setenv(FUSE_COMMFD2_ENV, arg_fd_entry, 1);
    +
    456
    +
    457 char const *const argv[] = {
    +
    458 FUSERMOUNT_PROG,
    +
    459 "-o", opts ? opts : "",
    +
    460 "--",
    +
    461 mountpoint,
    +
    462 NULL,
    +
    463 };
    +
    464
    +
    465
    +
    466 posix_spawn_file_actions_t action;
    +
    467 posix_spawn_file_actions_init(&action);
    +
    468
    +
    469 if (quiet) {
    +
    470 posix_spawn_file_actions_addopen(&action, STDOUT_FILENO, "/dev/null", O_WRONLY, 0);
    +
    471 posix_spawn_file_actions_addopen(&action, STDERR_FILENO, "/dev/null", O_WRONLY, 0);
    +
    472 }
    +
    473 posix_spawn_file_actions_addclose(&action, fds[1]);
    +
    474
    +
    475 int status = fusermount_posix_spawn(&action, argv, &pid);
    +
    476
    +
    477 posix_spawn_file_actions_destroy(&action);
    +
    478
    +
    479 if(status != 0) {
    +
    480 close(fds[0]);
    +
    481 close(fds[1]);
    +
    482 fuse_log(FUSE_LOG_ERR, "posix_spawn(p)() for %s failed: %s",
    +
    483 FUSERMOUNT_PROG, strerror(-status));
    +
    484 return -1;
    +
    485 }
    +
    486
    +
    487 // passed to child now, so can close here.
    +
    488 close(fds[0]);
    +
    489
    +
    490 int fd = receive_fd(fds[1]);
    +
    491
    +
    492 if (!mo->auto_unmount) {
    +
    493 /* with auto_unmount option fusermount3 will not exit until
    +
    494 this socket is closed */
    +
    495 close(fds[1]);
    +
    496 waitpid(pid, NULL, 0); /* bury zombie */
    +
    497 }
    +
    498
    +
    499 if (fd >= 0)
    +
    500 fcntl(fd, F_SETFD, FD_CLOEXEC);
    +
    501
    +
    502 return fd;
    +
    503}
    +
    504
    +
    505#ifndef O_CLOEXEC
    +
    506#define O_CLOEXEC 0
    +
    507#endif
    +
    508
    +
    509static int fuse_mount_sys(const char *mnt, struct mount_opts *mo,
    +
    510 const char *mnt_opts)
    +
    511{
    +
    512 char tmp[128];
    +
    513 const char *devname = getenv(FUSE_KERN_DEVICE_ENV) ?: "/dev/fuse";
    +
    514 char *source = NULL;
    +
    515 char *type = NULL;
    +
    516 struct stat stbuf;
    +
    517 int fd;
    +
    518 int res;
    +
    519
    +
    520 if (!mnt) {
    +
    521 fuse_log(FUSE_LOG_ERR, "fuse: missing mountpoint parameter\n");
    +
    522 return -1;
    +
    523 }
    +
    524
    +
    525 res = stat(mnt, &stbuf);
    +
    526 if (res == -1) {
    +
    527 fuse_log(FUSE_LOG_ERR, "fuse: failed to access mountpoint %s: %s\n",
    +
    528 mnt, strerror(errno));
    +
    529 return -1;
    +
    530 }
    +
    531
    +
    532 fd = open(devname, O_RDWR | O_CLOEXEC);
    +
    533 if (fd == -1) {
    +
    534 if (errno == ENODEV || errno == ENOENT)
    +
    535 fuse_log(FUSE_LOG_ERR,
    +
    536 "fuse: device %s not found. Kernel module not loaded?\n",
    +
    537 devname);
    +
    538 else
    +
    539 fuse_log(FUSE_LOG_ERR, "fuse: failed to open %s: %s\n",
    +
    540 devname, strerror(errno));
    +
    541 return -1;
    +
    542 }
    +
    543 if (!O_CLOEXEC)
    +
    544 fcntl(fd, F_SETFD, FD_CLOEXEC);
    +
    545
    +
    546 snprintf(tmp, sizeof(tmp), "fd=%i,rootmode=%o,user_id=%u,group_id=%u",
    +
    547 fd, stbuf.st_mode & S_IFMT, getuid(), getgid());
    +
    548
    +
    549 res = fuse_opt_add_opt(&mo->kernel_opts, tmp);
    +
    550 if (res == -1)
    +
    551 goto out_close;
    +
    552
    +
    553 source = malloc((mo->fsname ? strlen(mo->fsname) : 0) +
    +
    554 (mo->subtype ? strlen(mo->subtype) : 0) +
    +
    555 strlen(devname) + 32);
    +
    556
    +
    557 type = malloc((mo->subtype ? strlen(mo->subtype) : 0) + 32);
    +
    558 if (!type || !source) {
    +
    559 fuse_log(FUSE_LOG_ERR, "fuse: failed to allocate memory\n");
    +
    560 goto out_close;
    +
    561 }
    +
    562
    +
    563 strcpy(type, mo->blkdev ? "fuseblk" : "fuse");
    +
    564 if (mo->subtype) {
    +
    565 strcat(type, ".");
    +
    566 strcat(type, mo->subtype);
    +
    567 }
    +
    568 strcpy(source,
    +
    569 mo->fsname ? mo->fsname : (mo->subtype ? mo->subtype : devname));
    +
    570
    +
    571 res = mount(source, mnt, type, mo->flags, mo->kernel_opts);
    +
    572 if (res == -1 && errno == ENODEV && mo->subtype) {
    +
    573 /* Probably missing subtype support */
    +
    574 strcpy(type, mo->blkdev ? "fuseblk" : "fuse");
    +
    575 if (mo->fsname) {
    +
    576 if (!mo->blkdev)
    +
    577 sprintf(source, "%s#%s", mo->subtype,
    +
    578 mo->fsname);
    +
    579 } else {
    +
    580 strcpy(source, type);
    +
    581 }
    +
    582 res = mount(source, mnt, type, mo->flags, mo->kernel_opts);
    +
    583 }
    +
    584 if (res == -1) {
    +
    585 /*
    +
    586 * Maybe kernel doesn't support unprivileged mounts, in this
    +
    587 * case try falling back to fusermount3
    +
    588 */
    +
    589 if (errno == EPERM) {
    +
    590 res = -2;
    +
    591 } else {
    +
    592 int errno_save = errno;
    +
    593 if (mo->blkdev && errno == ENODEV &&
    +
    594 !fuse_mnt_check_fuseblk())
    +
    595 fuse_log(FUSE_LOG_ERR,
    +
    596 "fuse: 'fuseblk' support missing\n");
    +
    597 else
    +
    598 fuse_log(FUSE_LOG_ERR, "fuse: mount failed: %s\n",
    +
    599 strerror(errno_save));
    +
    600 }
    +
    601
    +
    602 goto out_close;
    +
    603 }
    +
    604
    +
    605#ifndef IGNORE_MTAB
    +
    606 if (geteuid() == 0) {
    +
    607 char *newmnt = fuse_mnt_resolve_path("fuse", mnt);
    +
    608 res = -1;
    +
    609 if (!newmnt)
    +
    610 goto out_umount;
    +
    611
    +
    612 res = fuse_mnt_add_mount("fuse", source, newmnt, type,
    +
    613 mnt_opts);
    +
    614 free(newmnt);
    +
    615 if (res == -1)
    +
    616 goto out_umount;
    +
    617 }
    +
    618#endif /* IGNORE_MTAB */
    +
    619 free(type);
    +
    620 free(source);
    +
    621
    +
    622 return fd;
    +
    623
    +
    624out_umount:
    +
    625 umount2(mnt, MNT_DETACH | UMOUNT_NOFOLLOW); /* lazy umount */
    +
    626out_close:
    +
    627 free(type);
    +
    628 free(source);
    +
    629 close(fd);
    +
    630 return res;
    +
    631}
    +
    632
    +
    633static int get_mnt_flag_opts(char **mnt_optsp, int flags)
    +
    634{
    +
    635 int i;
    +
    636
    +
    637 if (!(flags & MS_RDONLY) && fuse_opt_add_opt(mnt_optsp, "rw") == -1)
    +
    638 return -1;
    +
    639
    +
    640 for (i = 0; mount_flags[i].opt != NULL; i++) {
    +
    641 if (mount_flags[i].on && (flags & mount_flags[i].flag) &&
    +
    642 fuse_opt_add_opt(mnt_optsp, mount_flags[i].opt) == -1)
    +
    643 return -1;
    +
    644 }
    +
    645 return 0;
    +
    646}
    +
    647
    +
    648struct mount_opts *parse_mount_opts(struct fuse_args *args)
    +
    649{
    +
    650 struct mount_opts *mo;
    +
    651
    +
    652 mo = (struct mount_opts*) malloc(sizeof(struct mount_opts));
    +
    653 if (mo == NULL)
    +
    654 return NULL;
    +
    655
    +
    656 memset(mo, 0, sizeof(struct mount_opts));
    +
    657 mo->flags = MS_NOSUID | MS_NODEV;
    +
    658
    +
    659 if (args &&
    +
    660 fuse_opt_parse(args, mo, fuse_mount_opts, fuse_mount_opt_proc) == -1)
    +
    661 goto err_out;
    +
    662
    +
    663 return mo;
    +
    664
    +
    665err_out:
    +
    666 destroy_mount_opts(mo);
    +
    667 return NULL;
    +
    668}
    +
    669
    +
    670void destroy_mount_opts(struct mount_opts *mo)
    +
    671{
    +
    672 free(mo->fsname);
    +
    673 free(mo->subtype);
    +
    674 free(mo->fusermount_opts);
    +
    675 free(mo->subtype_opt);
    +
    676 free(mo->kernel_opts);
    +
    677 free(mo->mtab_opts);
    +
    678 free(mo);
    +
    679}
    +
    680
    +
    681
    +
    682int fuse_kern_mount(const char *mountpoint, struct mount_opts *mo)
    +
    683{
    +
    684 int res = -1;
    +
    685 char *mnt_opts = NULL;
    +
    686
    +
    687 res = -1;
    +
    688 if (get_mnt_flag_opts(&mnt_opts, mo->flags) == -1)
    +
    689 goto out;
    +
    690 if (mo->kernel_opts && fuse_opt_add_opt(&mnt_opts, mo->kernel_opts) == -1)
    +
    691 goto out;
    +
    692 if (mo->mtab_opts && fuse_opt_add_opt(&mnt_opts, mo->mtab_opts) == -1)
    +
    693 goto out;
    +
    694
    +
    695 res = fuse_mount_sys(mountpoint, mo, mnt_opts);
    +
    696 if (res >= 0 && mo->auto_unmount) {
    +
    697 if(0 > setup_auto_unmount(mountpoint, 0)) {
    +
    698 // Something went wrong, let's umount like in fuse_mount_sys.
    +
    699 umount2(mountpoint,
    +
    700 MNT_DETACH | UMOUNT_NOFOLLOW); /* lazy umount */
    +
    701 res = -1;
    +
    702 }
    +
    703 } else if (res == -2) {
    +
    704 if (mo->fusermount_opts &&
    +
    705 fuse_opt_add_opt(&mnt_opts, mo->fusermount_opts) == -1)
    +
    706 goto out;
    +
    707
    +
    708 if (mo->subtype) {
    +
    709 char *tmp_opts = NULL;
    +
    710
    +
    711 res = -1;
    +
    712 if (fuse_opt_add_opt(&tmp_opts, mnt_opts) == -1 ||
    +
    713 fuse_opt_add_opt(&tmp_opts, mo->subtype_opt) == -1) {
    +
    714 free(tmp_opts);
    +
    715 goto out;
    +
    716 }
    +
    717
    +
    718 res = fuse_mount_fusermount(mountpoint, mo, tmp_opts, 1);
    +
    719 free(tmp_opts);
    +
    720 if (res == -1)
    +
    721 res = fuse_mount_fusermount(mountpoint, mo,
    +
    722 mnt_opts, 0);
    +
    723 } else {
    +
    724 res = fuse_mount_fusermount(mountpoint, mo, mnt_opts, 0);
    +
    725 }
    +
    726 }
    +
    727out:
    +
    728 free(mnt_opts);
    +
    729 return res;
    +
    730}
    +
    void fuse_log(enum fuse_log_level level, const char *fmt,...) __attribute__((format(printf
    + +
    #define FUSE_OPT_KEY(templ, key)
    Definition fuse_opt.h:98
    +
    #define FUSE_OPT_KEY_OPT
    Definition fuse_opt.h:129
    +
    int fuse_opt_add_opt_escaped(char **opts, const char *opt)
    Definition fuse_opt.c:144
    +
    int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
    Definition fuse_opt.c:398
    +
    int fuse_opt_add_opt(char **opts, const char *opt)
    Definition fuse_opt.c:139
    +
    #define FUSE_OPT_END
    Definition fuse_opt.h:104
    + + +
    + + +
    + + diff --git a/doc/html/mount_8fuse_8c_source.html b/doc/html/mount_8fuse_8c_source.html new file mode 100644 index 0000000..c77b8d4 --- /dev/null +++ b/doc/html/mount_8fuse_8c_source.html @@ -0,0 +1,515 @@ + + + + + + + +libfuse: util/mount.fuse.c Source File + + + + + + + +
    +
    + + + + + + +
    +
    libfuse +
    +
    +
    + + + + + + + + +
    +
    +
    +
    mount.fuse.c
    +
    +
    +
    1/*
    +
    2 FUSE: Filesystem in Userspace
    +
    3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
    +
    4
    +
    5 This program can be distributed under the terms of the GNU GPLv2.
    +
    6 See the file GPL2.txt.
    +
    7*/
    +
    8
    +
    9#include "fuse_config.h"
    +
    10
    +
    11#include <stdio.h>
    +
    12#include <stdlib.h>
    +
    13#include <string.h>
    +
    14#include <unistd.h>
    +
    15#include <errno.h>
    +
    16#include <stdint.h>
    +
    17#include <fcntl.h>
    +
    18#include <pwd.h>
    +
    19#include <sys/wait.h>
    +
    20
    +
    21#ifdef linux
    +
    22#include <sys/prctl.h>
    +
    23#include <sys/syscall.h>
    +
    24#include <linux/capability.h>
    +
    25#include <linux/securebits.h>
    +
    26/* for 2.6 kernels */
    +
    27#if !defined(SECBIT_KEEP_CAPS) && defined(SECURE_KEEP_CAPS)
    +
    28#define SECBIT_KEEP_CAPS (issecure_mask(SECURE_KEEP_CAPS))
    +
    29#endif
    +
    30#if !defined(SECBIT_KEEP_CAPS_LOCKED) && defined(SECURE_KEEP_CAPS_LOCKED)
    +
    31#define SECBIT_KEEP_CAPS_LOCKED (issecure_mask(SECURE_KEEP_CAPS_LOCKED))
    +
    32#endif
    +
    33#if !defined(SECBIT_NO_SETUID_FIXUP) && defined(SECURE_NO_SETUID_FIXUP)
    +
    34#define SECBIT_NO_SETUID_FIXUP (issecure_mask(SECURE_NO_SETUID_FIXUP))
    +
    35#endif
    +
    36#if !defined(SECBIT_NO_SETUID_FIXUP_LOCKED) && defined(SECURE_NO_SETUID_FIXUP_LOCKED)
    +
    37#define SECBIT_NO_SETUID_FIXUP_LOCKED (issecure_mask(SECURE_NO_SETUID_FIXUP_LOCKED))
    +
    38#endif
    +
    39#if !defined(SECBIT_NOROOT) && defined(SECURE_NOROOT)
    +
    40#define SECBIT_NOROOT (issecure_mask(SECURE_NOROOT))
    +
    41#endif
    +
    42#if !defined(SECBIT_NOROOT_LOCKED) && defined(SECURE_NOROOT_LOCKED)
    +
    43#define SECBIT_NOROOT_LOCKED (issecure_mask(SECURE_NOROOT_LOCKED))
    +
    44#endif
    +
    45#endif
    +
    46/* linux < 3.5 */
    +
    47#ifndef PR_SET_NO_NEW_PRIVS
    +
    48#define PR_SET_NO_NEW_PRIVS 38
    +
    49#endif
    +
    50
    +
    51#include "fuse.h"
    +
    52
    +
    53static char *progname;
    +
    54
    +
    55static char *xstrdup(const char *s)
    +
    56{
    +
    57 char *t = strdup(s);
    +
    58 if (!t) {
    +
    59 fprintf(stderr, "%s: failed to allocate memory\n", progname);
    +
    60 exit(1);
    +
    61 }
    +
    62 return t;
    +
    63}
    +
    64
    +
    65static void *xrealloc(void *oldptr, size_t size)
    +
    66{
    +
    67 void *ptr = realloc(oldptr, size);
    +
    68 if (!ptr) {
    +
    69 fprintf(stderr, "%s: failed to allocate memory\n", progname);
    +
    70 exit(1);
    +
    71 }
    +
    72 return ptr;
    +
    73}
    +
    74
    +
    75static void add_arg(char **cmdp, const char *opt)
    +
    76{
    +
    77 size_t optlen = strlen(opt);
    +
    78 size_t cmdlen = *cmdp ? strlen(*cmdp) : 0;
    +
    79 if (optlen >= (SIZE_MAX - cmdlen - 4)/4) {
    +
    80 fprintf(stderr, "%s: argument too long\n", progname);
    +
    81 exit(1);
    +
    82 }
    +
    83 char *cmd = xrealloc(*cmdp, cmdlen + optlen * 4 + 4);
    +
    84 char *s;
    +
    85 s = cmd + cmdlen;
    +
    86 if (*cmdp)
    +
    87 *s++ = ' ';
    +
    88
    +
    89 *s++ = '\'';
    +
    90 for (; *opt; opt++) {
    +
    91 if (*opt == '\'') {
    +
    92 *s++ = '\'';
    +
    93 *s++ = '\\';
    +
    94 *s++ = '\'';
    +
    95 *s++ = '\'';
    +
    96 } else
    +
    97 *s++ = *opt;
    +
    98 }
    +
    99 *s++ = '\'';
    +
    100 *s = '\0';
    +
    101 *cmdp = cmd;
    +
    102}
    +
    103
    +
    104static char *add_option(const char *opt, char *options)
    +
    105{
    +
    106 int oldlen = options ? strlen(options) : 0;
    +
    107
    +
    108 options = xrealloc(options, oldlen + 1 + strlen(opt) + 1);
    +
    109 if (!oldlen)
    +
    110 strcpy(options, opt);
    +
    111 else {
    +
    112 strcat(options, ",");
    +
    113 strcat(options, opt);
    +
    114 }
    +
    115 return options;
    +
    116}
    +
    117
    +
    118static int prepare_fuse_fd(const char *mountpoint, const char* subtype,
    +
    119 const char *options)
    +
    120{
    +
    121 int fuse_fd = -1;
    +
    122 int flags = -1;
    +
    123 int subtype_len = strlen(subtype) + 9;
    +
    124 char* options_copy = xrealloc(NULL, subtype_len);
    +
    125
    +
    126 snprintf(options_copy, subtype_len, "subtype=%s", subtype);
    +
    127 options_copy = add_option(options, options_copy);
    +
    128 fuse_fd = fuse_open_channel(mountpoint, options_copy);
    +
    129 if (fuse_fd == -1) {
    +
    130 exit(1);
    +
    131 }
    +
    132
    +
    133 flags = fcntl(fuse_fd, F_GETFD);
    +
    134 if (flags == -1 || fcntl(fuse_fd, F_SETFD, flags & ~FD_CLOEXEC) == -1) {
    +
    135 fprintf(stderr, "%s: Failed to clear CLOEXEC: %s\n",
    +
    136 progname, strerror(errno));
    +
    137 exit(1);
    +
    138 }
    +
    139
    +
    140 return fuse_fd;
    +
    141}
    +
    142
    +
    143#ifdef linux
    +
    144static uint64_t get_capabilities(void)
    +
    145{
    +
    146 /*
    +
    147 * This invokes the capset syscall directly to avoid the libcap
    +
    148 * dependency, which isn't really justified just for this.
    +
    149 */
    +
    150 struct __user_cap_header_struct header = {
    +
    151 .version = _LINUX_CAPABILITY_VERSION_3,
    +
    152 .pid = 0,
    +
    153 };
    +
    154 struct __user_cap_data_struct data[2];
    +
    155 memset(data, 0, sizeof(data));
    +
    156 if (syscall(SYS_capget, &header, data) == -1) {
    +
    157 fprintf(stderr, "%s: Failed to get capabilities: %s\n",
    +
    158 progname, strerror(errno));
    +
    159 exit(1);
    +
    160 }
    +
    161
    +
    162 return data[0].effective | ((uint64_t) data[1].effective << 32);
    +
    163}
    +
    164
    +
    165static void set_capabilities(uint64_t caps)
    +
    166{
    +
    167 /*
    +
    168 * This invokes the capset syscall directly to avoid the libcap
    +
    169 * dependency, which isn't really justified just for this.
    +
    170 */
    +
    171 struct __user_cap_header_struct header = {
    +
    172 .version = _LINUX_CAPABILITY_VERSION_3,
    +
    173 .pid = 0,
    +
    174 };
    +
    175 struct __user_cap_data_struct data[2];
    +
    176 memset(data, 0, sizeof(data));
    +
    177 data[0].effective = data[0].permitted = caps;
    +
    178 data[1].effective = data[1].permitted = caps >> 32;
    +
    179 if (syscall(SYS_capset, &header, data) == -1) {
    +
    180 fprintf(stderr, "%s: Failed to set capabilities: %s\n",
    +
    181 progname, strerror(errno));
    +
    182 exit(1);
    +
    183 }
    +
    184}
    +
    185
    +
    186static void drop_and_lock_capabilities(void)
    +
    187{
    +
    188 /* Set and lock securebits. */
    +
    189 if (prctl(PR_SET_SECUREBITS,
    +
    190 SECBIT_KEEP_CAPS_LOCKED |
    +
    191 SECBIT_NO_SETUID_FIXUP |
    +
    192 SECBIT_NO_SETUID_FIXUP_LOCKED |
    +
    193 SECBIT_NOROOT |
    +
    194 SECBIT_NOROOT_LOCKED) == -1) {
    +
    195 fprintf(stderr, "%s: Failed to set securebits %s\n",
    +
    196 progname, strerror(errno));
    +
    197 exit(1);
    +
    198 }
    +
    199
    +
    200 /* Clear the capability bounding set. */
    +
    201 int cap;
    +
    202 for (cap = 0; ; cap++) {
    +
    203 int cap_status = prctl(PR_CAPBSET_READ, cap);
    +
    204 if (cap_status == 0) {
    +
    205 continue;
    +
    206 }
    +
    207 if (cap_status == -1 && errno == EINVAL) {
    +
    208 break;
    +
    209 }
    +
    210
    +
    211 if (cap_status != 1) {
    +
    212 fprintf(stderr,
    +
    213 "%s: Failed to get capability %u: %s\n",
    +
    214 progname, cap, strerror(errno));
    +
    215 exit(1);
    +
    216 }
    +
    217 if (prctl(PR_CAPBSET_DROP, cap) == -1) {
    +
    218 fprintf(stderr,
    +
    219 "%s: Failed to drop capability %u: %s\n",
    +
    220 progname, cap, strerror(errno));
    +
    221 }
    +
    222 }
    +
    223
    +
    224 /* Drop capabilities. */
    +
    225 set_capabilities(0);
    +
    226
    +
    227 /* Prevent re-acquisition of privileges. */
    +
    228 if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) == -1) {
    +
    229 fprintf(stderr, "%s: Failed to set no_new_privs: %s\n",
    +
    230 progname, strerror(errno));
    +
    231 exit(1);
    +
    232 }
    +
    233}
    +
    234#endif
    +
    235
    +
    236int main(int argc, char *argv[])
    +
    237{
    +
    238 char *type = NULL;
    +
    239 char *source;
    +
    240 char *dup_source = NULL;
    +
    241 const char *mountpoint;
    +
    242 char *basename;
    +
    243 char *options = NULL;
    +
    244 char *command = NULL;
    +
    245 char *setuid_name = NULL;
    +
    246 int i;
    +
    247 int dev = 1;
    +
    248 int suid = 1;
    +
    249 int pass_fuse_fd = 0;
    +
    250 int fuse_fd = 0;
    +
    251 int drop_privileges = 0;
    +
    252 char *dev_fd_mountpoint = NULL;
    +
    253
    +
    254 progname = argv[0];
    +
    255 basename = strrchr(argv[0], '/');
    +
    256 if (basename)
    +
    257 basename++;
    +
    258 else
    +
    259 basename = argv[0];
    +
    260
    +
    261 if (strncmp(basename, "mount.fuse.", 11) == 0)
    +
    262 type = basename + 11;
    +
    263 if (strncmp(basename, "mount.fuseblk.", 14) == 0)
    +
    264 type = basename + 14;
    +
    265
    +
    266 if (type && !type[0])
    +
    267 type = NULL;
    +
    268
    +
    269 if (argc < 3) {
    +
    270 fprintf(stderr,
    +
    271 "usage: %s %s destination [-t type] [-o opt[,opts...]]\n",
    +
    272 progname, type ? "source" : "type#[source]");
    +
    273 exit(1);
    +
    274 }
    +
    275
    +
    276 source = argv[1];
    +
    277 if (!source[0])
    +
    278 source = NULL;
    +
    279
    +
    280 mountpoint = argv[2];
    +
    281
    +
    282 for (i = 3; i < argc; i++) {
    +
    283 if (strcmp(argv[i], "-v") == 0) {
    +
    284 continue;
    +
    285 } else if (strcmp(argv[i], "-t") == 0) {
    +
    286 i++;
    +
    287
    +
    288 if (i == argc) {
    +
    289 fprintf(stderr,
    +
    290 "%s: missing argument to option '-t'\n",
    +
    291 progname);
    +
    292 exit(1);
    +
    293 }
    +
    294 type = argv[i];
    +
    295 if (strncmp(type, "fuse.", 5) == 0)
    +
    296 type += 5;
    +
    297 else if (strncmp(type, "fuseblk.", 8) == 0)
    +
    298 type += 8;
    +
    299
    +
    300 if (!type[0]) {
    +
    301 fprintf(stderr,
    +
    302 "%s: empty type given as argument to option '-t'\n",
    +
    303 progname);
    +
    304 exit(1);
    +
    305 }
    +
    306 } else if (strcmp(argv[i], "-o") == 0) {
    +
    307 char *opts;
    +
    308 char *opt;
    +
    309 i++;
    +
    310 if (i == argc)
    +
    311 break;
    +
    312
    +
    313 opts = xstrdup(argv[i]);
    +
    314 opt = strtok(opts, ",");
    +
    315 while (opt) {
    +
    316 int j;
    +
    317 int ignore = 0;
    +
    318 const char *ignore_opts[] = { "",
    +
    319 "user",
    +
    320 "nofail",
    +
    321 "nouser",
    +
    322 "users",
    +
    323 "auto",
    +
    324 "noauto",
    +
    325 "_netdev",
    +
    326 NULL};
    +
    327 if (strncmp(opt, "setuid=", 7) == 0) {
    +
    328 setuid_name = xstrdup(opt + 7);
    +
    329 ignore = 1;
    +
    330 } else if (strcmp(opt,
    +
    331 "drop_privileges") == 0) {
    +
    332 pass_fuse_fd = 1;
    +
    333 drop_privileges = 1;
    +
    334 ignore = 1;
    +
    335 }
    +
    336 for (j = 0; ignore_opts[j]; j++)
    +
    337 if (strcmp(opt, ignore_opts[j]) == 0)
    +
    338 ignore = 1;
    +
    339
    +
    340 if (!ignore) {
    +
    341 if (strcmp(opt, "nodev") == 0)
    +
    342 dev = 0;
    +
    343 else if (strcmp(opt, "nosuid") == 0)
    +
    344 suid = 0;
    +
    345
    +
    346 options = add_option(opt, options);
    +
    347 }
    +
    348 opt = strtok(NULL, ",");
    +
    349 }
    +
    350 free(opts);
    +
    351 }
    +
    352 }
    +
    353
    +
    354 if (drop_privileges) {
    +
    355 uint64_t required_caps = CAP_TO_MASK(CAP_SETPCAP) |
    +
    356 CAP_TO_MASK(CAP_SYS_ADMIN);
    +
    357 if ((get_capabilities() & required_caps) != required_caps) {
    +
    358 fprintf(stderr, "%s: drop_privileges was requested, which launches the FUSE file system fully unprivileged. In order to do so %s must be run with privileges, please invoke with CAP_SYS_ADMIN and CAP_SETPCAP (e.g. as root).\n",
    +
    359 progname, progname);
    +
    360 exit(1);
    +
    361 }
    +
    362 }
    +
    363
    +
    364 if (dev)
    +
    365 options = add_option("dev", options);
    +
    366 if (suid)
    +
    367 options = add_option("suid", options);
    +
    368
    +
    369 if (!type) {
    +
    370 if (source) {
    +
    371 dup_source = xstrdup(source);
    +
    372 type = dup_source;
    +
    373 source = strchr(type, '#');
    +
    374 if (source)
    +
    375 *source++ = '\0';
    +
    376 if (!type[0]) {
    +
    377 fprintf(stderr, "%s: empty filesystem type\n",
    +
    378 progname);
    +
    379 exit(1);
    +
    380 }
    +
    381 } else {
    +
    382 fprintf(stderr, "%s: empty source\n", progname);
    +
    383 exit(1);
    +
    384 }
    +
    385 }
    +
    386
    +
    387 if (setuid_name && setuid_name[0]) {
    +
    388#ifdef linux
    +
    389 if (drop_privileges) {
    +
    390 /*
    +
    391 * Make securebits more permissive before calling
    +
    392 * setuid(). Specifically, if SECBIT_KEEP_CAPS and
    +
    393 * SECBIT_NO_SETUID_FIXUP weren't set, setuid() would
    +
    394 * have the side effect of dropping all capabilities,
    +
    395 * and we need to retain CAP_SETPCAP in order to drop
    +
    396 * all privileges before exec().
    +
    397 */
    +
    398 if (prctl(PR_SET_SECUREBITS,
    +
    399 SECBIT_KEEP_CAPS |
    +
    400 SECBIT_NO_SETUID_FIXUP) == -1) {
    +
    401 fprintf(stderr,
    +
    402 "%s: Failed to set securebits %s\n",
    +
    403 progname, strerror(errno));
    +
    404 exit(1);
    +
    405 }
    +
    406 }
    +
    407#endif
    +
    408
    +
    409 struct passwd *pwd = getpwnam(setuid_name);
    +
    410 if (!pwd || setgid(pwd->pw_gid) == -1 || setuid(pwd->pw_uid) == -1) {
    +
    411 fprintf(stderr, "%s: Failed to setuid to %s: %s\n",
    +
    412 progname, setuid_name, strerror(errno));
    +
    413 exit(1);
    +
    414 }
    +
    415 } else if (!getenv("HOME")) {
    +
    416 /* Hack to make filesystems work in the boot environment */
    +
    417 setenv("HOME", "/root", 0);
    +
    418 }
    +
    419
    +
    420 if (pass_fuse_fd) {
    +
    421 fuse_fd = prepare_fuse_fd(mountpoint, type, options);
    +
    422 dev_fd_mountpoint = xrealloc(NULL, 20);
    +
    423 snprintf(dev_fd_mountpoint, 20, "/dev/fd/%u", fuse_fd);
    +
    424 mountpoint = dev_fd_mountpoint;
    +
    425 }
    +
    426
    +
    427#ifdef linux
    +
    428 if (drop_privileges) {
    +
    429 drop_and_lock_capabilities();
    +
    430 }
    +
    431#endif
    +
    432 add_arg(&command, type);
    +
    433 if (source)
    +
    434 add_arg(&command, source);
    +
    435 add_arg(&command, mountpoint);
    +
    436 if (options) {
    +
    437 add_arg(&command, "-o");
    +
    438 add_arg(&command, options);
    +
    439 }
    +
    440
    +
    441 free(options);
    +
    442 free(dev_fd_mountpoint);
    +
    443 free(dup_source);
    +
    444 free(setuid_name);
    +
    445
    +
    446 execl("/bin/sh", "/bin/sh", "-c", command, NULL);
    +
    447 fprintf(stderr, "%s: failed to execute /bin/sh: %s\n", progname,
    +
    448 strerror(errno));
    +
    449
    +
    450 if (pass_fuse_fd)
    +
    451 close(fuse_fd);
    +
    452 free(command);
    +
    453 return 1;
    +
    454}
    + +
    int fuse_open_channel(const char *mountpoint, const char *options)
    Definition helper.c:492
    +
    + + +
    + + diff --git a/doc/html/mount__bsd_8c_source.html b/doc/html/mount__bsd_8c_source.html new file mode 100644 index 0000000..9e76395 --- /dev/null +++ b/doc/html/mount__bsd_8c_source.html @@ -0,0 +1,336 @@ + + + + + + + +libfuse: lib/mount_bsd.c Source File + + + + + + + +
    +
    + + + + + + +
    +
    libfuse +
    +
    +
    + + + + + + + + +
    +
    +
    +
    mount_bsd.c
    +
    +
    +
    1/*
    +
    2 FUSE: Filesystem in Userspace
    +
    3 Copyright (C) 2005-2008 Csaba Henk <csaba.henk@creo.hu>
    +
    4
    +
    5 Architecture specific file system mounting (FreeBSD).
    +
    6
    +
    7 This program can be distributed under the terms of the GNU LGPLv2.
    +
    8 See the file LGPL2.txt.
    +
    9*/
    +
    10
    +
    11#include "fuse_config.h"
    +
    12#include "fuse_i.h"
    +
    13#include "fuse_misc.h"
    +
    14#include "fuse_opt.h"
    +
    15#include "util.h"
    +
    16
    +
    17#include <sys/param.h>
    +
    18#include "fuse_mount_compat.h"
    +
    19
    +
    20#include <sys/wait.h>
    +
    21#include <stdio.h>
    +
    22#include <stdlib.h>
    +
    23#include <unistd.h>
    +
    24#include <stddef.h>
    +
    25#include <fcntl.h>
    +
    26#include <errno.h>
    +
    27#include <string.h>
    +
    28
    +
    29#define FUSERMOUNT_PROG "mount_fusefs"
    +
    30#define FUSE_DEV_TRUNK "/dev/fuse"
    +
    31
    +
    32enum {
    +
    33 KEY_RO,
    +
    34 KEY_KERN
    +
    35};
    +
    36
    +
    37struct mount_opts {
    +
    38 int allow_other;
    +
    39 char *kernel_opts;
    +
    40 unsigned max_read;
    +
    41};
    +
    42
    +
    43#define FUSE_DUAL_OPT_KEY(templ, key) \
    +
    44 FUSE_OPT_KEY(templ, key), FUSE_OPT_KEY("no" templ, key)
    +
    45
    +
    46static const struct fuse_opt fuse_mount_opts[] = {
    +
    47 { "allow_other", offsetof(struct mount_opts, allow_other), 1 },
    +
    48 { "max_read=%u", offsetof(struct mount_opts, max_read), 1 },
    +
    49 FUSE_OPT_KEY("-r", KEY_RO),
    +
    50 /* standard FreeBSD mount options */
    +
    51 FUSE_DUAL_OPT_KEY("dev", KEY_KERN),
    +
    52 FUSE_DUAL_OPT_KEY("async", KEY_KERN),
    +
    53 FUSE_DUAL_OPT_KEY("atime", KEY_KERN),
    +
    54 FUSE_DUAL_OPT_KEY("dev", KEY_KERN),
    +
    55 FUSE_DUAL_OPT_KEY("exec", KEY_KERN),
    +
    56 FUSE_DUAL_OPT_KEY("suid", KEY_KERN),
    +
    57 FUSE_DUAL_OPT_KEY("symfollow", KEY_KERN),
    +
    58 FUSE_DUAL_OPT_KEY("rdonly", KEY_KERN),
    +
    59 FUSE_DUAL_OPT_KEY("sync", KEY_KERN),
    +
    60 FUSE_DUAL_OPT_KEY("union", KEY_KERN),
    +
    61 FUSE_DUAL_OPT_KEY("userquota", KEY_KERN),
    +
    62 FUSE_DUAL_OPT_KEY("groupquota", KEY_KERN),
    +
    63 FUSE_DUAL_OPT_KEY("clusterr", KEY_KERN),
    +
    64 FUSE_DUAL_OPT_KEY("clusterw", KEY_KERN),
    +
    65 FUSE_DUAL_OPT_KEY("suiddir", KEY_KERN),
    +
    66 FUSE_DUAL_OPT_KEY("snapshot", KEY_KERN),
    +
    67 FUSE_DUAL_OPT_KEY("multilabel", KEY_KERN),
    +
    68 FUSE_DUAL_OPT_KEY("acls", KEY_KERN),
    +
    69 FUSE_DUAL_OPT_KEY("force", KEY_KERN),
    +
    70 FUSE_DUAL_OPT_KEY("update", KEY_KERN),
    +
    71 FUSE_DUAL_OPT_KEY("ro", KEY_KERN),
    +
    72 FUSE_DUAL_OPT_KEY("rw", KEY_KERN),
    +
    73 FUSE_DUAL_OPT_KEY("auto", KEY_KERN),
    +
    74 FUSE_DUAL_OPT_KEY("automounted", KEY_KERN),
    +
    75 /* options supported under both Linux and FBSD */
    +
    76 FUSE_DUAL_OPT_KEY("allow_other", KEY_KERN),
    +
    77 FUSE_DUAL_OPT_KEY("default_permissions",KEY_KERN),
    +
    78 FUSE_OPT_KEY("max_read=", KEY_KERN),
    +
    79 FUSE_OPT_KEY("subtype=", KEY_KERN),
    +
    80 /* FBSD FUSE specific mount options */
    +
    81 FUSE_DUAL_OPT_KEY("private", KEY_KERN),
    +
    82 FUSE_DUAL_OPT_KEY("neglect_shares", KEY_KERN),
    +
    83 FUSE_DUAL_OPT_KEY("push_symlinks_in", KEY_KERN),
    +
    84#if __FreeBSD_version >= 1200519
    +
    85 FUSE_DUAL_OPT_KEY("intr", KEY_KERN),
    +
    86#endif
    +
    87 /* stock FBSD mountopt parsing routine lets anything be negated... */
    +
    88 /*
    +
    89 * Linux specific mount options, but let just the mount util
    +
    90 * handle them
    +
    91 */
    +
    92 FUSE_OPT_KEY("fsname=", KEY_KERN),
    + +
    94};
    +
    95
    +
    96void fuse_mount_version(void)
    +
    97{
    +
    98 system(FUSERMOUNT_PROG " --version");
    +
    99}
    +
    100
    +
    101unsigned get_max_read(struct mount_opts *o)
    +
    102{
    +
    103 return o->max_read;
    +
    104}
    +
    105
    +
    106static int fuse_mount_opt_proc(void *data, const char *arg, int key,
    +
    107 struct fuse_args *outargs)
    +
    108{
    +
    109 (void) outargs;
    +
    110 struct mount_opts *mo = data;
    +
    111
    +
    112 switch (key) {
    +
    113 case KEY_RO:
    +
    114 arg = "ro";
    +
    115 /* fall through */
    +
    116
    +
    117 case KEY_KERN:
    +
    118 return fuse_opt_add_opt(&mo->kernel_opts, arg);
    +
    119 }
    +
    120
    +
    121 /* Pass through unknown options */
    +
    122 return 1;
    +
    123}
    +
    124
    +
    125void fuse_kern_unmount(const char *mountpoint, int fd)
    +
    126{
    +
    127 if (close(fd) < 0)
    +
    128 fuse_log(FUSE_LOG_ERR, "closing FD %d failed: %s", fd, strerror(errno));
    +
    129 if (unmount(mountpoint, MNT_FORCE) < 0)
    +
    130 fuse_log(FUSE_LOG_ERR, "unmounting %s failed: %s",
    +
    131 mountpoint, strerror(errno));
    +
    132}
    +
    133
    +
    134static int fuse_mount_core(const char *mountpoint, const char *opts)
    +
    135{
    +
    136 const char *mountprog = FUSERMOUNT_PROG;
    +
    137 long fd;
    +
    138 char *fdnam, *dev;
    +
    139 pid_t pid, cpid;
    +
    140 int status;
    +
    141 int err;
    +
    142
    +
    143 fdnam = getenv("FUSE_DEV_FD");
    +
    144
    +
    145 if (fdnam) {
    +
    146 err = libfuse_strtol(fdnam, &fd);
    +
    147 if (err || fd < 0) {
    +
    148 fuse_log(FUSE_LOG_ERR, "invalid value given in FUSE_DEV_FD\n");
    +
    149 return -1;
    +
    150 }
    +
    151
    +
    152 goto mount;
    +
    153 }
    +
    154
    +
    155 dev = getenv("FUSE_DEV_NAME");
    +
    156
    +
    157 if (! dev)
    +
    158 dev = (char *)FUSE_DEV_TRUNK;
    +
    159
    +
    160 if ((fd = open(dev, O_RDWR)) < 0) {
    +
    161 perror("fuse: failed to open fuse device");
    +
    162 return -1;
    +
    163 }
    +
    164
    +
    165mount:
    +
    166 if (getenv("FUSE_NO_MOUNT") || ! mountpoint)
    +
    167 goto out;
    +
    168
    +
    169 pid = fork();
    +
    170 cpid = pid;
    +
    171
    +
    172 if (pid == -1) {
    +
    173 perror("fuse: fork() failed");
    +
    174 close(fd);
    +
    175 return -1;
    +
    176 }
    +
    177
    +
    178 if (pid == 0) {
    +
    179 pid = fork();
    +
    180
    +
    181 if (pid == -1) {
    +
    182 perror("fuse: fork() failed");
    +
    183 close(fd);
    +
    184 _exit(EXIT_FAILURE);
    +
    185 }
    +
    186
    +
    187 if (pid == 0) {
    +
    188 const char *argv[32];
    +
    189 int a = 0;
    +
    190 int ret = -1;
    +
    191
    +
    192 if (! fdnam)
    +
    193 {
    +
    194 ret = asprintf(&fdnam, "%ld", fd);
    +
    195 if(ret == -1)
    +
    196 {
    +
    197 perror("fuse: failed to assemble mount arguments");
    +
    198 close(fd);
    +
    199 _exit(EXIT_FAILURE);
    +
    200 }
    +
    201 }
    +
    202
    +
    203 argv[a++] = mountprog;
    +
    204 if (opts) {
    +
    205 argv[a++] = "-o";
    +
    206 argv[a++] = opts;
    +
    207 }
    +
    208 argv[a++] = fdnam;
    +
    209 argv[a++] = mountpoint;
    +
    210 argv[a++] = NULL;
    +
    211 execvp(mountprog, (char **) argv);
    +
    212 perror("fuse: failed to exec mount program");
    +
    213 free(fdnam);
    +
    214 _exit(EXIT_FAILURE);
    +
    215 }
    +
    216
    +
    217 waitpid(pid, &status, 0);
    +
    218 if (!WIFEXITED(status))
    +
    219 _exit(EXIT_FAILURE);
    +
    220 _exit(WEXITSTATUS(status));
    +
    221 }
    +
    222
    +
    223 if (waitpid(cpid, &status, 0) == -1 || WEXITSTATUS(status) != 0) {
    +
    224 perror("fuse: failed to mount file system");
    +
    225 if (close(fd) < 0)
    +
    226 perror("fuse: closing FD");
    +
    227 return -1;
    +
    228 }
    +
    229
    +
    230out:
    +
    231 return fd;
    +
    232}
    +
    233
    +
    234struct mount_opts *parse_mount_opts(struct fuse_args *args)
    +
    235{
    +
    236 struct mount_opts *mo;
    +
    237
    +
    238 mo = (struct mount_opts*) malloc(sizeof(struct mount_opts));
    +
    239 if (mo == NULL)
    +
    240 return NULL;
    +
    241
    +
    242 memset(mo, 0, sizeof(struct mount_opts));
    +
    243
    +
    244 if (args &&
    +
    245 fuse_opt_parse(args, mo, fuse_mount_opts, fuse_mount_opt_proc) == -1)
    +
    246 goto err_out;
    +
    247
    +
    248 return mo;
    +
    249
    +
    250err_out:
    +
    251 destroy_mount_opts(mo);
    +
    252 return NULL;
    +
    253}
    +
    254
    +
    255void destroy_mount_opts(struct mount_opts *mo)
    +
    256{
    +
    257 free(mo->kernel_opts);
    +
    258 free(mo);
    +
    259}
    +
    260
    +
    261int fuse_kern_mount(const char *mountpoint, struct mount_opts *mo)
    +
    262{
    +
    263 /* mount util should not try to spawn the daemon */
    +
    264 setenv("MOUNT_FUSEFS_SAFE", "1", 1);
    +
    265 /* to notify the mount util it's called from lib */
    +
    266 setenv("MOUNT_FUSEFS_CALL_BY_LIB", "1", 1);
    +
    267
    +
    268 return fuse_mount_core(mountpoint, mo->kernel_opts);
    +
    269}
    +
    void fuse_log(enum fuse_log_level level, const char *fmt,...) __attribute__((format(printf
    + +
    #define FUSE_OPT_KEY(templ, key)
    Definition fuse_opt.h:98
    +
    int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
    Definition fuse_opt.c:398
    +
    int fuse_opt_add_opt(char **opts, const char *opt)
    Definition fuse_opt.c:139
    +
    #define FUSE_OPT_END
    Definition fuse_opt.h:104
    + + +
    + + +
    + + diff --git a/doc/html/mount__util_8c_source.html b/doc/html/mount__util_8c_source.html new file mode 100644 index 0000000..d27a6b3 --- /dev/null +++ b/doc/html/mount__util_8c_source.html @@ -0,0 +1,474 @@ + + + + + + + +libfuse: lib/mount_util.c Source File + + + + + + + +
    +
    + + + + + + +
    +
    libfuse +
    +
    +
    + + + + + + + + +
    +
    +
    +
    mount_util.c
    +
    +
    +
    1/*
    +
    2 FUSE: Filesystem in Userspace
    +
    3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
    +
    4
    +
    5 Architecture-independent mounting code.
    +
    6
    +
    7 This program can be distributed under the terms of the GNU LGPLv2.
    +
    8 See the file LGPL2.txt.
    +
    9*/
    +
    10
    +
    11#include "fuse_config.h"
    +
    12#include "mount_util.h"
    +
    13#include "fuse_log.h"
    +
    14
    +
    15#include <stdio.h>
    +
    16#include <unistd.h>
    +
    17#include <stdlib.h>
    +
    18#include <string.h>
    +
    19#include <signal.h>
    +
    20#include <dirent.h>
    +
    21#include <errno.h>
    +
    22#include <fcntl.h>
    +
    23#include <limits.h>
    +
    24#include <paths.h>
    +
    25#if !defined( __NetBSD__) && !defined(__FreeBSD__) && !defined(__DragonFly__) && !defined(__ANDROID__)
    +
    26#include <mntent.h>
    +
    27#else
    +
    28#define IGNORE_MTAB
    +
    29#endif
    +
    30#include <sys/stat.h>
    +
    31#include <sys/wait.h>
    +
    32
    +
    33#include "fuse_mount_compat.h"
    +
    34
    +
    35#include <sys/param.h>
    +
    36
    +
    37#if defined(__NetBSD__) || defined(__FreeBSD__) || defined(__DragonFly__) || defined(__FreeBSD_kernel__)
    +
    38#define umount2(mnt, flags) unmount(mnt, ((flags) == 2) ? MNT_FORCE : 0)
    +
    39#endif
    +
    40
    +
    41#ifdef IGNORE_MTAB
    +
    42#define mtab_needs_update(mnt) 0
    +
    43#else
    +
    44static int mtab_needs_update(const char *mnt)
    +
    45{
    +
    46 int res;
    +
    47 struct stat stbuf;
    +
    48
    +
    49 /* If mtab is within new mount, don't touch it */
    +
    50 if (strncmp(mnt, _PATH_MOUNTED, strlen(mnt)) == 0 &&
    +
    51 _PATH_MOUNTED[strlen(mnt)] == '/')
    +
    52 return 0;
    +
    53
    +
    54 /*
    +
    55 * Skip mtab update if /etc/mtab:
    +
    56 *
    +
    57 * - doesn't exist,
    +
    58 * - is on a read-only filesystem.
    +
    59 */
    +
    60 res = lstat(_PATH_MOUNTED, &stbuf);
    +
    61 if (res == -1) {
    +
    62 if (errno == ENOENT)
    +
    63 return 0;
    +
    64 } else {
    +
    65 uid_t ruid;
    +
    66 int err;
    +
    67
    +
    68 ruid = getuid();
    +
    69 if (ruid != 0)
    +
    70 setreuid(0, -1);
    +
    71
    +
    72 res = access(_PATH_MOUNTED, W_OK);
    +
    73 err = (res == -1) ? errno : 0;
    +
    74 if (ruid != 0)
    +
    75 setreuid(ruid, -1);
    +
    76
    +
    77 if (err == EROFS)
    +
    78 return 0;
    +
    79
    +
    80 res = access("/run/mount/utab", F_OK);
    +
    81 if (res == -1)
    +
    82 return 0;
    +
    83 }
    +
    84
    +
    85 return 1;
    +
    86}
    +
    87#endif /* IGNORE_MTAB */
    +
    88
    +
    89/*
    +
    90 * These values become command line arguments of /bin/mount and /bin/umount.
    +
    91 * BusyBox mount(8) parses an argument starting with '-' as an option even
    +
    92 * behind an end-of-options marker ("--"); NULL would end the argument list
    +
    93 * early.
    +
    94 *
    +
    95 * @return 1 if @value is unsafe as a /bin/mount or /bin/umount argument.
    +
    96 */
    +
    97static int unsafe_operand(const char *value)
    +
    98{
    +
    99 return value == NULL || value[0] == '-';
    +
    100}
    +
    101
    +
    102static int add_mount(const char *progname, const char *fsname,
    +
    103 const char *mnt, const char *type, const char *opts)
    +
    104{
    +
    105 int res;
    +
    106 int status;
    +
    107 sigset_t blockmask;
    +
    108 sigset_t oldmask;
    +
    109
    +
    110 if (unsafe_operand(fsname) || unsafe_operand(mnt) ||
    +
    111 unsafe_operand(type) || unsafe_operand(opts)) {
    +
    112 fuse_log(FUSE_LOG_DEBUG,
    +
    113 "%s: option-like mount argument, skipping mtab update\n",
    +
    114 progname);
    +
    115 return 0;
    +
    116 }
    +
    117
    +
    118 sigemptyset(&blockmask);
    +
    119 sigaddset(&blockmask, SIGCHLD);
    +
    120 res = sigprocmask(SIG_BLOCK, &blockmask, &oldmask);
    +
    121 if (res == -1) {
    +
    122 fprintf(stderr, "%s: sigprocmask: %s\n", progname, strerror(errno));
    +
    123 return -1;
    +
    124 }
    +
    125
    +
    126 res = fork();
    +
    127 if (res == -1) {
    +
    128 fprintf(stderr, "%s: fork: %s\n", progname, strerror(errno));
    +
    129 goto out_restore;
    +
    130 }
    +
    131 if (res == 0) {
    +
    132 char *env = NULL;
    +
    133
    +
    134 sigprocmask(SIG_SETMASK, &oldmask, NULL);
    +
    135
    +
    136 if(setuid(geteuid()) == -1) {
    +
    137 fprintf(stderr, "%s: setuid: %s\n", progname, strerror(errno));
    +
    138 res = -1;
    +
    139 goto out_restore;
    +
    140 }
    +
    141
    +
    142 /*
    +
    143 * fsname comes from -ofsname=, so it can start with '-'. The
    +
    144 * setuid() above raises the real uid to 0, so mount(8) is not
    +
    145 * in restricted mode either. Terminate the options with "--"
    +
    146 * to keep it from parsing the operands as further options.
    +
    147 */
    +
    148 execle("/bin/mount", "/bin/mount", "--no-canonicalize", "-i",
    +
    149 "-f", "-t", type, "-o", opts, "--", fsname, mnt, NULL,
    +
    150 &env);
    +
    151 fprintf(stderr, "%s: failed to execute /bin/mount: %s\n",
    +
    152 progname, strerror(errno));
    +
    153 exit(1);
    +
    154 }
    +
    155 res = waitpid(res, &status, 0);
    +
    156 if (res == -1)
    +
    157 fprintf(stderr, "%s: waitpid: %s\n", progname, strerror(errno));
    +
    158
    +
    159 if (status != 0)
    +
    160 res = -1;
    +
    161
    +
    162 out_restore:
    +
    163 sigprocmask(SIG_SETMASK, &oldmask, NULL);
    +
    164
    +
    165 return res;
    +
    166}
    +
    167
    +
    168int fuse_mnt_add_mount(const char *progname, const char *fsname,
    +
    169 const char *mnt, const char *type, const char *opts)
    +
    170{
    +
    171 if (!mtab_needs_update(mnt))
    +
    172 return 0;
    +
    173
    +
    174 return add_mount(progname, fsname, mnt, type, opts);
    +
    175}
    +
    176
    +
    177static int exec_umount(const char *progname, const char *rel_mnt, int lazy)
    +
    178{
    +
    179 int res;
    +
    180 int status;
    +
    181 sigset_t blockmask;
    +
    182 sigset_t oldmask;
    +
    183
    +
    184 sigemptyset(&blockmask);
    +
    185 sigaddset(&blockmask, SIGCHLD);
    +
    186 res = sigprocmask(SIG_BLOCK, &blockmask, &oldmask);
    +
    187 if (res == -1) {
    +
    188 fprintf(stderr, "%s: sigprocmask: %s\n", progname, strerror(errno));
    +
    189 return -1;
    +
    190 }
    +
    191
    +
    192 res = fork();
    +
    193 if (res == -1) {
    +
    194 fprintf(stderr, "%s: fork: %s\n", progname, strerror(errno));
    +
    195 goto out_restore;
    +
    196 }
    +
    197 if (res == 0) {
    +
    198 char *env = NULL;
    +
    199
    +
    200 sigprocmask(SIG_SETMASK, &oldmask, NULL);
    +
    201
    +
    202 if(setuid(geteuid()) == -1) {
    +
    203 fprintf(stderr, "%s: setuid: %s\n", progname, strerror(errno));
    +
    204 res = -1;
    +
    205 goto out_restore;
    +
    206 }
    +
    207
    +
    208 if (lazy) {
    +
    209 execle("/bin/umount", "/bin/umount", "-i", "-l",
    +
    210 "--", rel_mnt, NULL, &env);
    +
    211 } else {
    +
    212 execle("/bin/umount", "/bin/umount", "-i",
    +
    213 "--", rel_mnt, NULL, &env);
    +
    214 }
    +
    215 fprintf(stderr, "%s: failed to execute /bin/umount: %s\n",
    +
    216 progname, strerror(errno));
    +
    217 exit(1);
    +
    218 }
    +
    219 res = waitpid(res, &status, 0);
    +
    220 if (res == -1)
    +
    221 fprintf(stderr, "%s: waitpid: %s\n", progname, strerror(errno));
    +
    222
    +
    223 if (status != 0) {
    +
    224 res = -1;
    +
    225 }
    +
    226
    +
    227 out_restore:
    +
    228 sigprocmask(SIG_SETMASK, &oldmask, NULL);
    +
    229 return res;
    +
    230
    +
    231}
    +
    232
    +
    233int fuse_mnt_umount(const char *progname, const char *abs_mnt,
    +
    234 const char *rel_mnt, int lazy)
    +
    235{
    +
    236 int res;
    +
    237
    +
    238 /*
    +
    239 * umount(8) may read an option-like rel_mnt as an option: unmount here
    +
    240 * instead and leave the mtab/utab record behind
    +
    241 */
    +
    242 if (!mtab_needs_update(abs_mnt) || unsafe_operand(rel_mnt)) {
    +
    243 res = umount2(rel_mnt, lazy ? 2 : 0);
    +
    244 if (res == -1)
    +
    245 fprintf(stderr, "%s: failed to unmount %s: %s\n",
    +
    246 progname, abs_mnt, strerror(errno));
    +
    247 return res;
    +
    248 }
    +
    249
    +
    250 return exec_umount(progname, rel_mnt, lazy);
    +
    251}
    +
    252
    +
    253static int remove_mount(const char *progname, const char *mnt)
    +
    254{
    +
    255 int res;
    +
    256 int status;
    +
    257 sigset_t blockmask;
    +
    258 sigset_t oldmask;
    +
    259
    +
    260 if (unsafe_operand(mnt))
    +
    261 return 0;
    +
    262
    +
    263 sigemptyset(&blockmask);
    +
    264 sigaddset(&blockmask, SIGCHLD);
    +
    265 res = sigprocmask(SIG_BLOCK, &blockmask, &oldmask);
    +
    266 if (res == -1) {
    +
    267 fprintf(stderr, "%s: sigprocmask: %s\n", progname, strerror(errno));
    +
    268 return -1;
    +
    269 }
    +
    270
    +
    271 res = fork();
    +
    272 if (res == -1) {
    +
    273 fprintf(stderr, "%s: fork: %s\n", progname, strerror(errno));
    +
    274 goto out_restore;
    +
    275 }
    +
    276 if (res == 0) {
    +
    277 char *env = NULL;
    +
    278
    +
    279 sigprocmask(SIG_SETMASK, &oldmask, NULL);
    +
    280
    +
    281 if(setuid(geteuid()) == -1) {
    +
    282 fprintf(stderr, "%s: setuid: %s\n", progname, strerror(errno));
    +
    283 res = -1;
    +
    284 goto out_restore;
    +
    285 }
    +
    286
    +
    287 execle("/bin/umount", "/bin/umount", "--no-canonicalize", "-i",
    +
    288 "--fake", "--", mnt, NULL, &env);
    +
    289 fprintf(stderr, "%s: failed to execute /bin/umount: %s\n",
    +
    290 progname, strerror(errno));
    +
    291 exit(1);
    +
    292 }
    +
    293 res = waitpid(res, &status, 0);
    +
    294 if (res == -1)
    +
    295 fprintf(stderr, "%s: waitpid: %s\n", progname, strerror(errno));
    +
    296
    +
    297 if (status != 0)
    +
    298 res = -1;
    +
    299
    +
    300 out_restore:
    +
    301 sigprocmask(SIG_SETMASK, &oldmask, NULL);
    +
    302 return res;
    +
    303}
    +
    304
    +
    305int fuse_mnt_remove_mount(const char *progname, const char *mnt)
    +
    306{
    +
    307 if (!mtab_needs_update(mnt))
    +
    308 return 0;
    +
    309
    +
    310 return remove_mount(progname, mnt);
    +
    311}
    +
    312
    +
    313char *fuse_mnt_resolve_path(const char *progname, const char *orig)
    +
    314{
    +
    315 char buf[PATH_MAX];
    +
    316 char *copy;
    +
    317 char *dst;
    +
    318 char *end;
    +
    319 char *lastcomp;
    +
    320 const char *toresolv;
    +
    321
    +
    322 if (!orig[0]) {
    +
    323 fprintf(stderr, "%s: invalid mountpoint '%s'\n", progname,
    +
    324 orig);
    +
    325 return NULL;
    +
    326 }
    +
    327
    +
    328 copy = strdup(orig);
    +
    329 if (copy == NULL) {
    +
    330 fprintf(stderr, "%s: failed to allocate memory\n", progname);
    +
    331 return NULL;
    +
    332 }
    +
    333
    +
    334 toresolv = copy;
    +
    335 lastcomp = NULL;
    +
    336 for (end = copy + strlen(copy) - 1; end > copy && *end == '/'; end --);
    +
    337 if (end[0] != '/') {
    +
    338 char *tmp;
    +
    339 end[1] = '\0';
    +
    340 tmp = strrchr(copy, '/');
    +
    341 if (tmp == NULL) {
    +
    342 lastcomp = copy;
    +
    343 toresolv = ".";
    +
    344 } else {
    +
    345 lastcomp = tmp + 1;
    +
    346 if (tmp == copy)
    +
    347 toresolv = "/";
    +
    348 }
    +
    349 if (strcmp(lastcomp, ".") == 0 || strcmp(lastcomp, "..") == 0) {
    +
    350 lastcomp = NULL;
    +
    351 toresolv = copy;
    +
    352 }
    +
    353 else if (tmp)
    +
    354 tmp[0] = '\0';
    +
    355 }
    +
    356 if (realpath(toresolv, buf) == NULL) {
    +
    357 fprintf(stderr, "%s: bad mount point %s: %s\n", progname, orig,
    +
    358 strerror(errno));
    +
    359 free(copy);
    +
    360 return NULL;
    +
    361 }
    +
    362 if (lastcomp == NULL)
    +
    363 dst = strdup(buf);
    +
    364 else {
    +
    365 dst = (char *) malloc(strlen(buf) + 1 + strlen(lastcomp) + 1);
    +
    366 if (dst) {
    +
    367 unsigned buflen = strlen(buf);
    +
    368 if (buflen && buf[buflen-1] == '/')
    +
    369 sprintf(dst, "%s%s", buf, lastcomp);
    +
    370 else
    +
    371 sprintf(dst, "%s/%s", buf, lastcomp);
    +
    372 }
    +
    373 }
    +
    374 free(copy);
    +
    375 if (dst == NULL)
    +
    376 fprintf(stderr, "%s: failed to allocate memory\n", progname);
    +
    377 return dst;
    +
    378}
    +
    379
    +
    380int fuse_mnt_check_fuseblk(void)
    +
    381{
    +
    382 char buf[256];
    +
    383 FILE *f = fopen("/proc/filesystems", "r");
    +
    384 if (!f)
    +
    385 return 1;
    +
    386
    +
    387 while (fgets(buf, sizeof(buf), f))
    +
    388 if (strstr(buf, "fuseblk\n")) {
    +
    389 fclose(f);
    +
    390 return 1;
    +
    391 }
    +
    392
    +
    393 fclose(f);
    +
    394 return 0;
    +
    395}
    +
    396
    +
    397int fuse_mnt_parse_fuse_fd(const char *mountpoint)
    +
    398{
    +
    399 int fd = -1;
    +
    400 int len = 0;
    +
    401
    +
    402 if (mountpoint == NULL) {
    +
    403 fprintf(stderr, "Invalid null-ptr mount-point!\n");
    +
    404 return -1;
    +
    405 }
    +
    406
    +
    407 if (sscanf(mountpoint, "/dev/fd/%u%n", &fd, &len) == 1 &&
    +
    408 len == strlen(mountpoint)) {
    +
    409 return fd;
    +
    410 }
    +
    411
    +
    412 return -1;
    +
    413}
    + +
    void fuse_log(enum fuse_log_level level, const char *fmt,...) __attribute__((format(printf
    +
    + + +
    + + diff --git a/doc/html/mount__util_8h_source.html b/doc/html/mount__util_8h_source.html new file mode 100644 index 0000000..5742440 --- /dev/null +++ b/doc/html/mount__util_8h_source.html @@ -0,0 +1,77 @@ + + + + + + + +libfuse: lib/mount_util.h Source File + + + + + + + +
    +
    + + + + + + +
    +
    libfuse +
    +
    +
    + + + + + + + + +
    +
    +
    +
    mount_util.h
    +
    +
    +
    1/*
    +
    2 FUSE: Filesystem in Userspace
    +
    3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
    +
    4
    +
    5 This program can be distributed under the terms of the GNU LGPLv2.
    +
    6 See the file LGPL2.txt.
    +
    7*/
    +
    8
    +
    9#include <sys/types.h>
    +
    10
    +
    11int fuse_mnt_add_mount(const char *progname, const char *fsname,
    +
    12 const char *mnt, const char *type, const char *opts);
    +
    13int fuse_mnt_remove_mount(const char *progname, const char *mnt);
    +
    14int fuse_mnt_umount(const char *progname, const char *abs_mnt,
    +
    15 const char *rel_mnt, int lazy);
    +
    16char *fuse_mnt_resolve_path(const char *progname, const char *orig);
    +
    17int fuse_mnt_check_fuseblk(void);
    +
    18int fuse_mnt_parse_fuse_fd(const char *mountpoint);
    +
    + + +
    + + diff --git a/doc/html/navtree.css b/doc/html/navtree.css new file mode 100644 index 0000000..0ea3a07 --- /dev/null +++ b/doc/html/navtree.css @@ -0,0 +1,327 @@ +#nav-tree .children_ul { + margin:0; + padding:4px; +} + +#nav-tree ul { + list-style:none outside none; + margin:0px; + padding:0px; +} + +#nav-tree li { + white-space:nowrap; + margin:0; + padding:0; +} + +#nav-tree .plus { + margin:0px; +} + +#nav-tree .selected { + position: relative; + background-color: var(--nav-menu-active-bg); + border-radius: 0 6px 6px 0; + /*margin-right: 5px;*/ +} + +#nav-tree img { + margin:0px; + padding:0px; + border:0px; + vertical-align: middle; +} + +#nav-tree a { + text-decoration:none; + padding:0px; + margin:0px; +} + +#nav-tree .label { + margin:0px; + padding:0px; + font: 12px var(--font-family-nav); + line-height: 22px; +} + +#nav-tree .label a { + padding:2px; +} + +#nav-tree .selected a { + text-decoration:none; + color:var(--page-link-color); +} + +#nav-tree .children_ul { + margin:0px; + padding:0px; +} + +#nav-tree .item { + margin: 0 6px 0 -5px; + padding: 0 0 0 5px; + height: 22px; +} + +#nav-tree { + padding: 0px 0px; + font-size:14px; + overflow:auto; +} + +#doc-content { + overflow:auto; + display:block; + padding:0px; + margin:0px; + -webkit-overflow-scrolling : touch; /* iOS 5+ */ +} + +#side-nav { + padding:0 6px 0 0; + margin: 0px; + display:block; + position: absolute; + left: 0px; + overflow : hidden; +} + +.ui-resizable .ui-resizable-handle { + display:block; +} + +.ui-resizable-e { + transition: opacity 0.5s ease; + background-color: var(--nav-splitbar-bg-color); + opacity:0; + cursor:col-resize; + height:100%; + right:0; + top:0; + width:6px; + position: relative; +} + +.ui-resizable-e:after { + content: ''; + display: block; + top: 50%; + left: 1px; + width: 2px; + height: 15px; + border-left: 1px solid var(--nav-splitbar-handle-color); + border-right: 1px solid var(--nav-splitbar-handle-color); + position: absolute; +} + +.ui-resizable-e:hover { + opacity: 1; +} + +.ui-resizable-handle { + display:none; + font-size:0.1px; + position:absolute; + z-index:1; +} + +#nav-tree-contents { + margin: 6px 0px 0px 0px; +} + +#nav-tree { + background-color: var(--nav-background-color); + -webkit-overflow-scrolling : touch; /* iOS 5+ */ + scrollbar-width: thin; + border-right: 1px solid var(--nav-border-color); + padding-left: 5px; +} + +#nav-sync { + position:absolute; + top:0px; + right:0px; + z-index:1; +} + +#nav-sync img { + opacity:0.3; +} + +div.nav-sync-icon { + position: relative; + width: 24px; + height: 17px; + left: -6px; + top: -1px; + opacity: 0.7; + display: inline-block; + background-color: var(--sync-icon-background-color); + border: 1px solid var(--sync-icon-border-color); + box-sizing: content-box; +} + +div.nav-sync-icon:hover { + background-color: var(--sync-icon-selected-background-color); + opacity: 1.0; +} + +div.nav-sync-icon.active:after { + content: ''; + background-color: var(--sync-icon-background-color); + border-top: 2px solid var(--sync-icon-color); + position: absolute; + width: 16px; + height: 0px; + top: 7px; + left: 4px; +} + +div.nav-sync-icon.active:hover:after { + border-top: 2px solid var(--sync-icon-selected-color); +} + +span.sync-icon-left { + position: absolute; + padding: 0; + margin: 0; + top: 3px; + left: 4px; + display: inline-block; + width: 8px; + height: 8px; + border-left: 2px solid var(--sync-icon-color); + border-top: 2px solid var(--sync-icon-color); + transform: rotate(-45deg); +} + +span.sync-icon-right { + position: absolute; + padding: 0; + margin: 0; + top: 3px; + left: 10px; + display: inline-block; + width: 8px; + height: 8px; + border-right: 2px solid var(--sync-icon-color); + border-bottom: 2px solid var(--sync-icon-color); + transform: rotate(-45deg); +} + +div.nav-sync-icon:hover span.sync-icon-left { + border-left: 2px solid var(--sync-icon-selected-color); + border-top: 2px solid var(--sync-icon-selected-color); +} + +div.nav-sync-icon:hover span.sync-icon-right { + border-right: 2px solid var(--sync-icon-selected-color); + border-bottom: 2px solid var(--sync-icon-selected-color); +} + +#nav-path ul { + border-top: 1px solid var(--nav-breadcrumb-separator-color); +} + +@media print +{ + #nav-tree { display: none; } + div.ui-resizable-handle { display: none; position: relative; } +} + +/*---------------------------*/ +#container { + display: grid; + grid-template-columns: auto auto; + overflow: hidden; +} + +#page-nav { + background: var(--nav-background-color); + display: block; + width: 250px; + box-sizing: content-box; + position: relative; + border-left: 1px solid var(--nav-border-color); +} + +#page-nav-tree { + display: inline-block; +} + +#page-nav-resize-handle { + transition: opacity 0.5s ease; + background-color: var(--nav-splitbar-bg-color); + opacity:0; + cursor:col-resize; + height:100%; + right:0; + top:0; + width:6px; + position: relative; + z-index: 1; + user-select: none; +} + +#page-nav-resize-handle:after { + content: ''; + display: block; + top: 50%; + left: 1px; + width: 2px; + height: 15px; + border-left: 1px solid var(--nav-splitbar-handle-color); + border-right: 1px solid var(--nav-splitbar-handle-color); + position: absolute; +} + +#page-nav-resize-handle.dragging, +#page-nav-resize-handle:hover { + opacity: 1; +} + +#page-nav-contents { + padding: 0; + margin: 0; + display: block; + top: 0; + left: 0; + height: 100%; + width: 100%; + position: absolute; + overflow: auto; + scrollbar-width: thin; + -webkit-overflow-scrolling : touch; /* iOS 5+ */ +} + +ul.page-outline, +ul.page-outline ul { + text-indent: 0; + list-style: none outside none; + padding: 0 0 0 4px; +} + +ul.page-outline { + margin: 0 4px 4px 6px; +} + +ul.page-outline div.item { + font: 12px var(--font-family-nav); + line-height: 22px; +} + +ul.page-outline li { + white-space: nowrap; +} + +ul.page-outline li.vis { + background-color: var(--nav-breadcrumb-active-bg); +} + +#container.resizing { + cursor: col-resize; + user-select: none; +} diff --git a/doc/html/notify__inval__entry_8c.html b/doc/html/notify__inval__entry_8c.html new file mode 100644 index 0000000..173f0d3 --- /dev/null +++ b/doc/html/notify__inval__entry_8c.html @@ -0,0 +1,511 @@ + + + + + + + +libfuse: example/notify_inval_entry.c File Reference + + + + + + + +
    +
    + + + + + + +
    +
    libfuse +
    +
    +
    + + + + + + + + +
    +
    +
    +
    notify_inval_entry.c File Reference
    +
    +
    +
    #include <fuse_lowlevel.h>
    +#include <stdio.h>
    +#include <stdlib.h>
    +#include <string.h>
    +#include <errno.h>
    +#include <fcntl.h>
    +#include <assert.h>
    +#include <signal.h>
    +#include <stddef.h>
    +#include <sys/stat.h>
    +#include <unistd.h>
    +#include <pthread.h>
    +
    +

    Go to the source code of this file.

    +

    Detailed Description

    +

    This example implements a file system with a single file whose file name changes dynamically to reflect the current time.

    +

    It illustrates the use of the fuse_lowlevel_notify_inval_entry() and fuse_lowlevel_notify_expire_entry() functions.

    +

    To see the effect, first start the file system with the --no-notify

    $ notify_inval_entry --update-interval=1 --timeout=30 --no-notify mnt/
    +

    Observe that ls always prints the correct directory contents (since readdir output is not cached)::

    $ ls mnt; sleep 1; ls mnt; sleep 1; ls mnt
    +Time_is_15h_48m_33s  current_time
    +Time_is_15h_48m_34s  current_time
    +Time_is_15h_48m_35s  current_time
    +

    However, if you try to access a file by name the kernel will report that it still exists:

    $ file=$(ls mnt/); echo $file
    +Time_is_15h_50m_09s
    +$ sleep 5; stat mnt/$file
    +  File: ‘mnt/Time_is_15h_50m_09s’
    +  Size: 32                Blocks: 0          IO Block: 4096   regular file
    +Device: 2ah/42d     Inode: 3           Links: 1
    +Access: (0444/-r--r--r--)  Uid: (    0/    root)   Gid: (    0/    root)
    +Access: 1969-12-31 16:00:00.000000000 -0800
    +Modify: 1969-12-31 16:00:00.000000000 -0800
    +Change: 1969-12-31 16:00:00.000000000 -0800
    + Birth: -
    +

    Only once the kernel cache timeout has been reached will the stat call fail:

    $ sleep 30; stat mnt/$file
    +stat: cannot stat ‘mnt/Time_is_15h_50m_09s’: No such file or directory
    +

    In contrast, if you enable notifications you will be unable to stat the file as soon as the file system updates its name:

    $ notify_inval_entry --update-interval=1 --timeout=30 mnt/
    +$ file=$(ls mnt/); stat mnt/$file
    +  File: ‘mnt/Time_is_20h_42m_11s’
    +  Size: 0                 Blocks: 0          IO Block: 4096   regular empty file
    +Device: 2ah/42d     Inode: 2           Links: 1
    +Access: (0000/----------)  Uid: (    0/    root)   Gid: (    0/    root)
    +Access: 1969-12-31 16:00:00.000000000 -0800
    +Modify: 1969-12-31 16:00:00.000000000 -0800
    +Change: 1969-12-31 16:00:00.000000000 -0800
    + Birth: -
    +$ sleep 1; stat mnt/$file
    +stat: cannot stat ‘mnt/Time_is_20h_42m_11s’: No such file or directory
    +

    To use the function fuse_lowlevel_notify_expire_entry() instead of fuse_lowlevel_notify_inval_entry(), use the command line option –only-expire

    +

    Another possible command-line option is –inc-epoch, which will use the FUSE low-level function fuse_lowlevel_notify_increment_epoch() instead. This will function will force the invalidation of all dentries next time they are revalidated. Note that –inc-epoch and –only-expire options are mutually exclusive.

    +

    +Compilation

    +
    gcc -Wall notify_inval_entry.c `pkg-config fuse3 --cflags --libs` -o notify_inval_entry
    +

    +Source code

    +
    /*
    +
    FUSE: Filesystem in Userspace
    +
    Copyright (C) 2016 Nikolaus Rath <Nikolaus@rath.org>
    +
    +
    This program can be distributed under the terms of the GNU GPLv2.
    +
    See the file GPL2.txt.
    +
    */
    +
    +
    +
    #define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 12)
    +
    +
    #include <fuse_lowlevel.h>
    +
    #include <stdio.h>
    +
    #include <stdlib.h>
    +
    #include <string.h>
    +
    #include <errno.h>
    +
    #include <fcntl.h>
    +
    #include <assert.h>
    +
    #include <signal.h>
    +
    #include <stddef.h>
    +
    #include <sys/stat.h>
    +
    #include <unistd.h>
    +
    #include <pthread.h>
    +
    +
    #define MAX_STR_LEN 128
    +
    static char file_name[MAX_STR_LEN];
    +
    static fuse_ino_t file_ino = 2;
    +
    static int lookup_cnt = 0;
    +
    static pthread_t main_thread;
    +
    +
    /* Command line parsing */
    +
    struct options {
    +
    int no_notify;
    +
    float timeout;
    +
    int update_interval;
    +
    int only_expire;
    +
    int inc_epoch;
    +
    };
    +
    static struct options options = {
    +
    .timeout = 5,
    +
    .no_notify = 0,
    +
    .update_interval = 1,
    +
    .only_expire = 0,
    +
    .inc_epoch = 0,
    +
    };
    +
    +
    #define OPTION(t, p) { t, offsetof(struct options, p), 1 }
    +
    static const struct fuse_opt option_spec[] = {
    +
    OPTION("--no-notify", no_notify),
    +
    OPTION("--update-interval=%d", update_interval),
    +
    OPTION("--timeout=%f", timeout),
    +
    OPTION("--only-expire", only_expire),
    +
    OPTION("--inc-epoch", inc_epoch),
    + +
    };
    +
    +
    static int tfs_stat(fuse_ino_t ino, struct stat *stbuf)
    +
    {
    +
    stbuf->st_ino = ino;
    +
    if (ino == FUSE_ROOT_ID) {
    +
    stbuf->st_mode = S_IFDIR | 0755;
    +
    stbuf->st_nlink = 1;
    +
    }
    +
    +
    else if (ino == file_ino) {
    +
    stbuf->st_mode = S_IFREG | 0000;
    +
    stbuf->st_nlink = 1;
    +
    stbuf->st_size = 0;
    +
    }
    +
    +
    else
    +
    return -1;
    +
    +
    return 0;
    +
    }
    +
    +
    static void tfs_init(void *userdata, struct fuse_conn_info *conn)
    +
    {
    +
    (void)userdata;
    +
    +
    /* Disable the receiving and processing of FUSE_INTERRUPT requests */
    +
    conn->no_interrupt = 1;
    +
    }
    +
    +
    static void tfs_lookup(fuse_req_t req, fuse_ino_t parent, const char *name)
    +
    {
    +
    struct fuse_entry_param e;
    +
    memset(&e, 0, sizeof(e));
    +
    +
    if (parent != FUSE_ROOT_ID)
    +
    goto err_out;
    +
    else if (strcmp(name, file_name) == 0) {
    +
    e.ino = file_ino;
    +
    lookup_cnt++;
    +
    } else
    +
    goto err_out;
    +
    +
    e.attr_timeout = options.timeout;
    +
    e.entry_timeout = options.timeout;
    +
    if (tfs_stat(e.ino, &e.attr) != 0)
    +
    goto err_out;
    +
    fuse_reply_entry(req, &e);
    +
    return;
    +
    +
    err_out:
    +
    fuse_reply_err(req, ENOENT);
    +
    }
    +
    +
    static void tfs_forget(fuse_req_t req, fuse_ino_t ino, uint64_t nlookup)
    +
    {
    +
    (void)req;
    +
    if (ino == file_ino)
    +
    lookup_cnt -= nlookup;
    +
    else
    +
    assert(ino == FUSE_ROOT_ID);
    + +
    }
    +
    +
    static void tfs_getattr(fuse_req_t req, fuse_ino_t ino,
    +
    struct fuse_file_info *fi)
    +
    {
    +
    struct stat stbuf;
    +
    +
    (void)fi;
    +
    +
    memset(&stbuf, 0, sizeof(stbuf));
    +
    if (tfs_stat(ino, &stbuf) != 0)
    +
    fuse_reply_err(req, ENOENT);
    +
    else
    +
    fuse_reply_attr(req, &stbuf, options.timeout);
    +
    }
    +
    +
    struct dirbuf {
    +
    char *p;
    +
    size_t size;
    +
    };
    +
    +
    static void dirbuf_add(fuse_req_t req, struct dirbuf *b, const char *name,
    + +
    {
    +
    struct stat stbuf;
    +
    size_t oldsize = b->size;
    +
    b->size += fuse_add_direntry(req, NULL, 0, name, NULL, 0);
    +
    b->p = (char *)realloc(b->p, b->size);
    +
    memset(&stbuf, 0, sizeof(stbuf));
    +
    stbuf.st_ino = ino;
    +
    fuse_add_direntry(req, b->p + oldsize, b->size - oldsize, name, &stbuf,
    +
    b->size);
    +
    }
    +
    +
    #define min(x, y) ((x) < (y) ? (x) : (y))
    +
    +
    static int reply_buf_limited(fuse_req_t req, const char *buf, size_t bufsize,
    +
    off_t off, size_t maxsize)
    +
    {
    +
    if (off < bufsize)
    +
    return fuse_reply_buf(req, buf + off,
    +
    min(bufsize - off, maxsize));
    +
    else
    +
    return fuse_reply_buf(req, NULL, 0);
    +
    }
    +
    +
    static void tfs_readdir(fuse_req_t req, fuse_ino_t ino, size_t size, off_t off,
    +
    struct fuse_file_info *fi)
    +
    {
    +
    (void)fi;
    +
    +
    if (ino != FUSE_ROOT_ID)
    +
    fuse_reply_err(req, ENOTDIR);
    +
    else {
    +
    struct dirbuf b;
    +
    +
    memset(&b, 0, sizeof(b));
    +
    dirbuf_add(req, &b, file_name, file_ino);
    +
    reply_buf_limited(req, b.p, b.size, off, size);
    +
    free(b.p);
    +
    }
    +
    }
    +
    +
    static const struct fuse_lowlevel_ops tfs_oper = {
    +
    .init = tfs_init,
    +
    .lookup = tfs_lookup,
    +
    .getattr = tfs_getattr,
    +
    .readdir = tfs_readdir,
    +
    .forget = tfs_forget,
    +
    };
    +
    +
    static void update_fs(void)
    +
    {
    +
    time_t t;
    +
    struct tm tmbuf;
    +
    struct tm *now;
    +
    ssize_t ret;
    +
    +
    t = time(NULL);
    +
    now = localtime_r(&t, &tmbuf);
    +
    assert(now != NULL);
    +
    +
    ret = strftime(file_name, MAX_STR_LEN, "Time_is_%Hh_%Mm_%Ss", now);
    +
    assert(ret != 0);
    +
    }
    +
    +
    static void *update_fs_loop(void *data)
    +
    {
    +
    struct fuse_session *se = (struct fuse_session *)data;
    +
    char *old_name;
    +
    int ret = 0;
    +
    +
    while (!fuse_session_exited(se)) {
    +
    old_name = strdup(file_name);
    +
    update_fs();
    +
    +
    if (!options.no_notify && lookup_cnt) {
    +
    if (options.only_expire) { // expire entry
    + +
    se, FUSE_ROOT_ID, old_name,
    +
    strlen(old_name));
    +
    +
    // no kernel support
    +
    if (ret == -ENOSYS) {
    +
    printf("%s not supported by kernel\n",
    +
    "fuse_lowlevel_notify_expire_entry");
    +
    break;
    +
    }
    +
    +
    // 1) ret == 0: successful expire of an existing entry
    +
    // 2) ret == -ENOENT: kernel has already expired the entry /
    +
    // entry does not exist anymore in the kernel
    +
    assert(ret == 0 || ret == -ENOENT);
    +
    } else if (options.inc_epoch) { // increment epoch
    + +
    +
    if (ret == -ENOSYS) {
    +
    printf("%s not supported by kernel\n",
    +
    "fuse_lowlevel_notify_increment_epoch");
    +
    break;
    +
    }
    +
    assert(ret == 0);
    +
    } else { // invalidate entry
    + +
    se, FUSE_ROOT_ID, old_name,
    +
    strlen(old_name)) == 0);
    +
    }
    +
    }
    +
    free(old_name);
    +
    sleep(options.update_interval);
    +
    }
    +
    +
    if (ret == -ENOSYS) {
    +
    printf("Exiting...\n");
    +
    + +
    // Make sure to exit now, rather than on next request from userspace
    +
    pthread_kill(main_thread, SIGPIPE);
    +
    }
    +
    +
    return NULL;
    +
    }
    +
    +
    static void show_help(const char *progname)
    +
    {
    +
    printf("usage: %s [options] <mountpoint>\n\n", progname);
    +
    printf("File-system specific options:\n"
    +
    " --timeout=<secs> Timeout for kernel caches\n"
    +
    " --update-interval=<secs> Update-rate of file system contents\n"
    +
    " --no-notify Disable kernel notifications\n"
    +
    " --only-expire Expire entries instead of invalidating them\n"
    +
    " --inc-epoch Increment epoch, invalidating all dentries\n"
    +
    "\n");
    +
    }
    +
    +
    int main(int argc, char *argv[])
    +
    {
    +
    struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
    +
    struct fuse_session *se;
    +
    struct fuse_cmdline_opts opts;
    +
    struct fuse_loop_config *config;
    +
    pthread_t updater;
    +
    int ret = -1;
    +
    +
    if (fuse_opt_parse(&args, &options, option_spec, NULL) == -1)
    +
    return 1;
    +
    +
    if (fuse_parse_cmdline(&args, &opts) != 0)
    +
    return 1;
    +
    if (opts.show_help) {
    +
    show_help(argv[0]);
    + + +
    ret = 0;
    +
    goto err_out1;
    +
    } else if (opts.show_version) {
    +
    printf("FUSE library version %s\n", fuse_pkgversion());
    + +
    ret = 0;
    +
    goto err_out1;
    +
    }
    +
    if (options.only_expire && options.inc_epoch) {
    +
    printf("'only-expire' and 'inc-epoch' options are exclusive\n");
    +
    ret = 0;
    +
    goto err_out1;
    +
    }
    +
    +
    /* Initial contents */
    +
    update_fs();
    +
    +
    se = fuse_session_new(&args, &tfs_oper, sizeof(tfs_oper), &se);
    +
    if (se == NULL)
    +
    goto err_out1;
    +
    + +
    goto err_out2;
    +
    +
    if (fuse_session_mount(se, opts.mountpoint) != 0)
    +
    goto err_out3;
    +
    +
    fuse_daemonize(opts.foreground);
    +
    +
    // Needed to ensure that the main thread continues/restarts processing as soon
    +
    // as the fuse session ends (immediately after calling fuse_session_exit() )
    +
    // and not only on the next request from userspace
    +
    main_thread = pthread_self();
    +
    +
    /* Start thread to update file contents */
    +
    ret = pthread_create(&updater, NULL, update_fs_loop, (void *)se);
    +
    if (ret != 0) {
    +
    fprintf(stderr, "pthread_create failed with %s\n",
    +
    strerror(ret));
    +
    goto err_out3;
    +
    }
    +
    +
    /* Block until ctrl+c or fusermount -u */
    +
    if (opts.singlethread) {
    +
    ret = fuse_session_loop(se);
    +
    } else {
    +
    config = fuse_loop_cfg_create();
    +
    fuse_loop_cfg_set_clone_fd(config, opts.clone_fd);
    +
    fuse_loop_cfg_set_max_threads(config, opts.max_threads);
    +
    ret = fuse_session_loop_mt(se, config);
    +
    fuse_loop_cfg_destroy(config);
    +
    config = NULL;
    +
    }
    +
    + +
    err_out3:
    + +
    err_out2:
    + +
    err_out1:
    +
    free(opts.mountpoint);
    + +
    +
    return ret ? 1 : 0;
    +
    }
    +
    +
    int fuse_set_signal_handlers(struct fuse_session *se)
    +
    const char * fuse_pkgversion(void)
    Definition fuse.c:5274
    +
    void fuse_remove_signal_handlers(struct fuse_session *se)
    +
    int fuse_daemonize(int foreground)
    Definition helper.c:253
    + +
    void fuse_session_destroy(struct fuse_session *se)
    +
    void fuse_session_exit(struct fuse_session *se)
    +
    int fuse_reply_err(fuse_req_t req, int err)
    +
    int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
    +
    struct fuse_req * fuse_req_t
    +
    int fuse_session_exited(struct fuse_session *se)
    +
    int fuse_session_loop(struct fuse_session *se)
    Definition fuse_loop.c:19
    +
    int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
    +
    void fuse_session_unmount(struct fuse_session *se)
    +
    void fuse_cmdline_help(void)
    Definition helper.c:130
    +
    void fuse_reply_none(fuse_req_t req)
    +
    int fuse_lowlevel_notify_expire_entry(struct fuse_session *se, fuse_ino_t parent, const char *name, size_t namelen)
    +
    void fuse_lowlevel_help(void)
    +
    int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
    +
    int fuse_lowlevel_notify_increment_epoch(struct fuse_session *se)
    +
    int fuse_lowlevel_notify_inval_entry(struct fuse_session *se, fuse_ino_t parent, const char *name, size_t namelen)
    +
    void fuse_lowlevel_version(void)
    +
    uint64_t fuse_ino_t
    +
    size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct stat *stbuf, off_t off)
    +
    int fuse_reply_attr(fuse_req_t req, const struct stat *attr, double attr_timeout)
    +
    void fuse_opt_free_args(struct fuse_args *args)
    Definition fuse_opt.c:34
    +
    int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
    Definition fuse_opt.c:398
    +
    #define FUSE_ARGS_INIT(argc, argv)
    Definition fuse_opt.h:123
    +
    #define FUSE_OPT_END
    Definition fuse_opt.h:104
    + + +
    char ** argv
    Definition fuse_opt.h:114
    + + +
    uint32_t no_interrupt
    +
    +
    fuse_ino_t ino
    + + + + +
    +

    Definition in file notify_inval_entry.c.

    +
    + + +
    + + diff --git a/doc/html/notify__inval__entry_8c_source.html b/doc/html/notify__inval__entry_8c_source.html new file mode 100644 index 0000000..fabd3ca --- /dev/null +++ b/doc/html/notify__inval__entry_8c_source.html @@ -0,0 +1,455 @@ + + + + + + + +libfuse: example/notify_inval_entry.c Source File + + + + + + + +
    +
    + + + + + + +
    +
    libfuse +
    +
    +
    + + + + + + + + +
    +
    +
    +
    notify_inval_entry.c
    +
    +
    +Go to the documentation of this file.
    1/*
    +
    2 FUSE: Filesystem in Userspace
    +
    3 Copyright (C) 2016 Nikolaus Rath <Nikolaus@rath.org>
    +
    4
    +
    5 This program can be distributed under the terms of the GNU GPLv2.
    +
    6 See the file GPL2.txt.
    +
    7*/
    +
    8
    +
    83
    +
    84#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 12)
    +
    85
    +
    86#include <fuse_lowlevel.h>
    +
    87#include <stdio.h>
    +
    88#include <stdlib.h>
    +
    89#include <string.h>
    +
    90#include <errno.h>
    +
    91#include <fcntl.h>
    +
    92#include <assert.h>
    +
    93#include <signal.h>
    +
    94#include <stddef.h>
    +
    95#include <sys/stat.h>
    +
    96#include <unistd.h>
    +
    97#include <pthread.h>
    +
    98
    +
    99#define MAX_STR_LEN 128
    +
    100static char file_name[MAX_STR_LEN];
    +
    101static fuse_ino_t file_ino = 2;
    +
    102static int lookup_cnt = 0;
    +
    103static pthread_t main_thread;
    +
    104
    +
    105/* Command line parsing */
    +
    106struct options {
    +
    107 int no_notify;
    +
    108 float timeout;
    +
    109 int update_interval;
    +
    110 int only_expire;
    +
    111 int inc_epoch;
    +
    112};
    +
    113static struct options options = {
    +
    114 .timeout = 5,
    +
    115 .no_notify = 0,
    +
    116 .update_interval = 1,
    +
    117 .only_expire = 0,
    +
    118 .inc_epoch = 0,
    +
    119};
    +
    120
    +
    121#define OPTION(t, p) { t, offsetof(struct options, p), 1 }
    +
    122static const struct fuse_opt option_spec[] = {
    +
    123 OPTION("--no-notify", no_notify),
    +
    124 OPTION("--update-interval=%d", update_interval),
    +
    125 OPTION("--timeout=%f", timeout),
    +
    126 OPTION("--only-expire", only_expire),
    +
    127 OPTION("--inc-epoch", inc_epoch),
    + +
    129};
    +
    130
    +
    131static int tfs_stat(fuse_ino_t ino, struct stat *stbuf)
    +
    132{
    +
    133 stbuf->st_ino = ino;
    +
    134 if (ino == FUSE_ROOT_ID) {
    +
    135 stbuf->st_mode = S_IFDIR | 0755;
    +
    136 stbuf->st_nlink = 1;
    +
    137 }
    +
    138
    +
    139 else if (ino == file_ino) {
    +
    140 stbuf->st_mode = S_IFREG | 0000;
    +
    141 stbuf->st_nlink = 1;
    +
    142 stbuf->st_size = 0;
    +
    143 }
    +
    144
    +
    145 else
    +
    146 return -1;
    +
    147
    +
    148 return 0;
    +
    149}
    +
    150
    +
    151static void tfs_init(void *userdata, struct fuse_conn_info *conn)
    +
    152{
    +
    153 (void)userdata;
    +
    154
    +
    155 /* Disable the receiving and processing of FUSE_INTERRUPT requests */
    +
    156 conn->no_interrupt = 1;
    +
    157}
    +
    158
    +
    159static void tfs_lookup(fuse_req_t req, fuse_ino_t parent, const char *name)
    +
    160{
    +
    161 struct fuse_entry_param e;
    +
    162 memset(&e, 0, sizeof(e));
    +
    163
    +
    164 if (parent != FUSE_ROOT_ID)
    +
    165 goto err_out;
    +
    166 else if (strcmp(name, file_name) == 0) {
    +
    167 e.ino = file_ino;
    +
    168 lookup_cnt++;
    +
    169 } else
    +
    170 goto err_out;
    +
    171
    +
    172 e.attr_timeout = options.timeout;
    +
    173 e.entry_timeout = options.timeout;
    +
    174 if (tfs_stat(e.ino, &e.attr) != 0)
    +
    175 goto err_out;
    +
    176 fuse_reply_entry(req, &e);
    +
    177 return;
    +
    178
    +
    179err_out:
    +
    180 fuse_reply_err(req, ENOENT);
    +
    181}
    +
    182
    +
    183static void tfs_forget(fuse_req_t req, fuse_ino_t ino, uint64_t nlookup)
    +
    184{
    +
    185 (void)req;
    +
    186 if (ino == file_ino)
    +
    187 lookup_cnt -= nlookup;
    +
    188 else
    +
    189 assert(ino == FUSE_ROOT_ID);
    +
    190 fuse_reply_none(req);
    +
    191}
    +
    192
    +
    193static void tfs_getattr(fuse_req_t req, fuse_ino_t ino,
    +
    194 struct fuse_file_info *fi)
    +
    195{
    +
    196 struct stat stbuf;
    +
    197
    +
    198 (void)fi;
    +
    199
    +
    200 memset(&stbuf, 0, sizeof(stbuf));
    +
    201 if (tfs_stat(ino, &stbuf) != 0)
    +
    202 fuse_reply_err(req, ENOENT);
    +
    203 else
    +
    204 fuse_reply_attr(req, &stbuf, options.timeout);
    +
    205}
    +
    206
    +
    207struct dirbuf {
    +
    208 char *p;
    +
    209 size_t size;
    +
    210};
    +
    211
    +
    212static void dirbuf_add(fuse_req_t req, struct dirbuf *b, const char *name,
    +
    213 fuse_ino_t ino)
    +
    214{
    +
    215 struct stat stbuf;
    +
    216 size_t oldsize = b->size;
    +
    217 b->size += fuse_add_direntry(req, NULL, 0, name, NULL, 0);
    +
    218 b->p = (char *)realloc(b->p, b->size);
    +
    219 memset(&stbuf, 0, sizeof(stbuf));
    +
    220 stbuf.st_ino = ino;
    +
    221 fuse_add_direntry(req, b->p + oldsize, b->size - oldsize, name, &stbuf,
    +
    222 b->size);
    +
    223}
    +
    224
    +
    225#define min(x, y) ((x) < (y) ? (x) : (y))
    +
    226
    +
    227static int reply_buf_limited(fuse_req_t req, const char *buf, size_t bufsize,
    +
    228 off_t off, size_t maxsize)
    +
    229{
    +
    230 if (off < bufsize)
    +
    231 return fuse_reply_buf(req, buf + off,
    +
    232 min(bufsize - off, maxsize));
    +
    233 else
    +
    234 return fuse_reply_buf(req, NULL, 0);
    +
    235}
    +
    236
    +
    237static void tfs_readdir(fuse_req_t req, fuse_ino_t ino, size_t size, off_t off,
    +
    238 struct fuse_file_info *fi)
    +
    239{
    +
    240 (void)fi;
    +
    241
    +
    242 if (ino != FUSE_ROOT_ID)
    +
    243 fuse_reply_err(req, ENOTDIR);
    +
    244 else {
    +
    245 struct dirbuf b;
    +
    246
    +
    247 memset(&b, 0, sizeof(b));
    +
    248 dirbuf_add(req, &b, file_name, file_ino);
    +
    249 reply_buf_limited(req, b.p, b.size, off, size);
    +
    250 free(b.p);
    +
    251 }
    +
    252}
    +
    253
    +
    254static const struct fuse_lowlevel_ops tfs_oper = {
    +
    255 .init = tfs_init,
    +
    256 .lookup = tfs_lookup,
    +
    257 .getattr = tfs_getattr,
    +
    258 .readdir = tfs_readdir,
    +
    259 .forget = tfs_forget,
    +
    260};
    +
    261
    +
    262static void update_fs(void)
    +
    263{
    +
    264 time_t t;
    +
    265 struct tm tmbuf;
    +
    266 struct tm *now;
    +
    267 ssize_t ret;
    +
    268
    +
    269 t = time(NULL);
    +
    270 now = localtime_r(&t, &tmbuf);
    +
    271 assert(now != NULL);
    +
    272
    +
    273 ret = strftime(file_name, MAX_STR_LEN, "Time_is_%Hh_%Mm_%Ss", now);
    +
    274 assert(ret != 0);
    +
    275}
    +
    276
    +
    277static void *update_fs_loop(void *data)
    +
    278{
    +
    279 struct fuse_session *se = (struct fuse_session *)data;
    +
    280 char *old_name;
    +
    281 int ret = 0;
    +
    282
    +
    283 while (!fuse_session_exited(se)) {
    +
    284 old_name = strdup(file_name);
    +
    285 update_fs();
    +
    286
    +
    287 if (!options.no_notify && lookup_cnt) {
    +
    288 if (options.only_expire) { // expire entry
    + +
    290 se, FUSE_ROOT_ID, old_name,
    +
    291 strlen(old_name));
    +
    292
    +
    293 // no kernel support
    +
    294 if (ret == -ENOSYS) {
    +
    295 printf("%s not supported by kernel\n",
    +
    296 "fuse_lowlevel_notify_expire_entry");
    +
    297 break;
    +
    298 }
    +
    299
    +
    300 // 1) ret == 0: successful expire of an existing entry
    +
    301 // 2) ret == -ENOENT: kernel has already expired the entry /
    +
    302 // entry does not exist anymore in the kernel
    +
    303 assert(ret == 0 || ret == -ENOENT);
    +
    304 } else if (options.inc_epoch) { // increment epoch
    + +
    306
    +
    307 if (ret == -ENOSYS) {
    +
    308 printf("%s not supported by kernel\n",
    +
    309 "fuse_lowlevel_notify_increment_epoch");
    +
    310 break;
    +
    311 }
    +
    312 assert(ret == 0);
    +
    313 } else { // invalidate entry
    + +
    315 se, FUSE_ROOT_ID, old_name,
    +
    316 strlen(old_name)) == 0);
    +
    317 }
    +
    318 }
    +
    319 free(old_name);
    +
    320 sleep(options.update_interval);
    +
    321 }
    +
    322
    +
    323 if (ret == -ENOSYS) {
    +
    324 printf("Exiting...\n");
    +
    325
    + +
    327 // Make sure to exit now, rather than on next request from userspace
    +
    328 pthread_kill(main_thread, SIGPIPE);
    +
    329 }
    +
    330
    +
    331 return NULL;
    +
    332}
    +
    333
    +
    334static void show_help(const char *progname)
    +
    335{
    +
    336 printf("usage: %s [options] <mountpoint>\n\n", progname);
    +
    337 printf("File-system specific options:\n"
    +
    338 " --timeout=<secs> Timeout for kernel caches\n"
    +
    339 " --update-interval=<secs> Update-rate of file system contents\n"
    +
    340 " --no-notify Disable kernel notifications\n"
    +
    341 " --only-expire Expire entries instead of invalidating them\n"
    +
    342 " --inc-epoch Increment epoch, invalidating all dentries\n"
    +
    343 "\n");
    +
    344}
    +
    345
    +
    346int main(int argc, char *argv[])
    +
    347{
    +
    348 struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
    +
    349 struct fuse_session *se;
    +
    350 struct fuse_cmdline_opts opts;
    +
    351 struct fuse_loop_config *config;
    +
    352 pthread_t updater;
    +
    353 int ret = -1;
    +
    354
    +
    355 if (fuse_opt_parse(&args, &options, option_spec, NULL) == -1)
    +
    356 return 1;
    +
    357
    +
    358 if (fuse_parse_cmdline(&args, &opts) != 0)
    +
    359 return 1;
    +
    360 if (opts.show_help) {
    +
    361 show_help(argv[0]);
    + + +
    364 ret = 0;
    +
    365 goto err_out1;
    +
    366 } else if (opts.show_version) {
    +
    367 printf("FUSE library version %s\n", fuse_pkgversion());
    + +
    369 ret = 0;
    +
    370 goto err_out1;
    +
    371 }
    +
    372 if (options.only_expire && options.inc_epoch) {
    +
    373 printf("'only-expire' and 'inc-epoch' options are exclusive\n");
    +
    374 ret = 0;
    +
    375 goto err_out1;
    +
    376 }
    +
    377
    +
    378 /* Initial contents */
    +
    379 update_fs();
    +
    380
    +
    381 se = fuse_session_new(&args, &tfs_oper, sizeof(tfs_oper), &se);
    +
    382 if (se == NULL)
    +
    383 goto err_out1;
    +
    384
    +
    385 if (fuse_set_signal_handlers(se) != 0)
    +
    386 goto err_out2;
    +
    387
    +
    388 if (fuse_session_mount(se, opts.mountpoint) != 0)
    +
    389 goto err_out3;
    +
    390
    +
    391 fuse_daemonize(opts.foreground);
    +
    392
    +
    393 // Needed to ensure that the main thread continues/restarts processing as soon
    +
    394 // as the fuse session ends (immediately after calling fuse_session_exit() )
    +
    395 // and not only on the next request from userspace
    +
    396 main_thread = pthread_self();
    +
    397
    +
    398 /* Start thread to update file contents */
    +
    399 ret = pthread_create(&updater, NULL, update_fs_loop, (void *)se);
    +
    400 if (ret != 0) {
    +
    401 fprintf(stderr, "pthread_create failed with %s\n",
    +
    402 strerror(ret));
    +
    403 goto err_out3;
    +
    404 }
    +
    405
    +
    406 /* Block until ctrl+c or fusermount -u */
    +
    407 if (opts.singlethread) {
    +
    408 ret = fuse_session_loop(se);
    +
    409 } else {
    +
    410 config = fuse_loop_cfg_create();
    +
    411 fuse_loop_cfg_set_clone_fd(config, opts.clone_fd);
    +
    412 fuse_loop_cfg_set_max_threads(config, opts.max_threads);
    +
    413 ret = fuse_session_loop_mt(se, config);
    +
    414 fuse_loop_cfg_destroy(config);
    +
    415 config = NULL;
    +
    416 }
    +
    417
    + +
    419err_out3:
    + +
    421err_out2:
    + +
    423err_out1:
    +
    424 free(opts.mountpoint);
    +
    425 fuse_opt_free_args(&args);
    +
    426
    +
    427 return ret ? 1 : 0;
    +
    428}
    +
    429
    +
    int fuse_set_signal_handlers(struct fuse_session *se)
    +
    const char * fuse_pkgversion(void)
    Definition fuse.c:5274
    +
    void fuse_remove_signal_handlers(struct fuse_session *se)
    +
    int fuse_daemonize(int foreground)
    Definition helper.c:253
    + +
    void fuse_session_destroy(struct fuse_session *se)
    +
    void fuse_session_exit(struct fuse_session *se)
    +
    int fuse_reply_err(fuse_req_t req, int err)
    +
    int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
    +
    struct fuse_req * fuse_req_t
    +
    int fuse_session_exited(struct fuse_session *se)
    +
    int fuse_session_loop(struct fuse_session *se)
    Definition fuse_loop.c:19
    +
    int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
    +
    void fuse_session_unmount(struct fuse_session *se)
    +
    void fuse_cmdline_help(void)
    Definition helper.c:130
    +
    void fuse_reply_none(fuse_req_t req)
    +
    int fuse_lowlevel_notify_expire_entry(struct fuse_session *se, fuse_ino_t parent, const char *name, size_t namelen)
    +
    void fuse_lowlevel_help(void)
    +
    int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
    +
    int fuse_lowlevel_notify_increment_epoch(struct fuse_session *se)
    +
    int fuse_lowlevel_notify_inval_entry(struct fuse_session *se, fuse_ino_t parent, const char *name, size_t namelen)
    +
    void fuse_lowlevel_version(void)
    +
    uint64_t fuse_ino_t
    +
    size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct stat *stbuf, off_t off)
    +
    int fuse_reply_attr(fuse_req_t req, const struct stat *attr, double attr_timeout)
    +
    void fuse_opt_free_args(struct fuse_args *args)
    Definition fuse_opt.c:34
    +
    int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
    Definition fuse_opt.c:398
    +
    #define FUSE_ARGS_INIT(argc, argv)
    Definition fuse_opt.h:123
    +
    #define FUSE_OPT_END
    Definition fuse_opt.h:104
    + + +
    char ** argv
    Definition fuse_opt.h:114
    + + +
    uint32_t no_interrupt
    +
    +
    fuse_ino_t ino
    + + + + +
    + + +
    + + diff --git a/doc/html/notify__inval__inode_8c.html b/doc/html/notify__inval__inode_8c.html new file mode 100644 index 0000000..929efa0 --- /dev/null +++ b/doc/html/notify__inval__inode_8c.html @@ -0,0 +1,493 @@ + + + + + + + +libfuse: example/notify_inval_inode.c File Reference + + + + + + + +
    +
    + + + + + + +
    +
    libfuse +
    +
    +
    + + + + + + + + +
    +
    +
    +
    notify_inval_inode.c File Reference
    +
    +
    +
    #include <fuse_lowlevel.h>
    +#include <stdio.h>
    +#include <stdlib.h>
    +#include <string.h>
    +#include <errno.h>
    +#include <fcntl.h>
    +#include <assert.h>
    +#include <stddef.h>
    +#include <unistd.h>
    +#include <pthread.h>
    +#include <stdbool.h>
    +#include <stdatomic.h>
    +
    +

    Go to the source code of this file.

    +

    Detailed Description

    +

    This example implements a file system with a single file whose contents change dynamically: it always contains the current time.

    +

    While notify_store_retrieve.c uses fuse_lowlevel_notify_store() to actively push the updated data into the kernel cache, this example uses fuse_lowlevel_notify_inval_inode() to notify the kernel that the cache has to be invalidated - but the kernel still has to explicitly request the updated data on the next read.

    +

    To see the effect, first start the file system with the --no-notify option:

    +

    $ notify_inval_inode –update-interval=1 –no-notify mnt/

    +

    Observe that the output never changes, even though the file system updates it once per second. This is because the contents are cached in the kernel:

    $ for i in 1 2 3 4 5; do
    +>     cat mnt/current_time
    +>     sleep 1
    +> done
    +The current time is 15:58:18
    +The current time is 15:58:18
    +The current time is 15:58:18
    +The current time is 15:58:18
    +The current time is 15:58:18
    +

    If you instead enable the notification functions, the changes become visible:

     $ notify_inval_inode --update-interval=1 mnt/
    + $ for i in 1 2 3 4 5; do
    + >     cat mnt/current_time
    + >     sleep 1
    + > done
    + The current time is 15:58:40
    + The current time is 15:58:41
    + The current time is 15:58:42
    + The current time is 15:58:43
    + The current time is 15:58:44
    +

    +Compilation

    +
    gcc -Wall notify_inval_inode.c `pkg-config fuse3 --cflags --libs` -o notify_inval_inode
    +

    +Source code

    +
    /*
    +
    FUSE: Filesystem in Userspace
    +
    Copyright (C) 2016 Nikolaus Rath <Nikolaus@rath.org>
    +
    +
    This program can be distributed under the terms of the GNU GPLv2.
    +
    See the file GPL2.txt.
    +
    */
    +
    +
    +
    #define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 12)
    +
    +
    #include <fuse_lowlevel.h>
    +
    #include <stdio.h>
    +
    #include <stdlib.h>
    +
    #include <string.h>
    +
    #include <errno.h>
    +
    #include <fcntl.h>
    +
    #include <assert.h>
    +
    #include <stddef.h>
    +
    #include <unistd.h>
    +
    #include <pthread.h>
    +
    #include <stdbool.h>
    +
    #include <stdatomic.h>
    +
    +
    /* We can't actually tell the kernel that there is no
    +
    timeout, so we just send a big value */
    +
    #define NO_TIMEOUT 500000
    +
    +
    #define MAX_STR_LEN 128
    +
    #define FILE_INO 2
    +
    #define FILE_NAME "current_time"
    +
    static char file_contents[MAX_STR_LEN];
    +
    static int lookup_cnt = 0;
    +
    static size_t file_size;
    +
    static _Atomic bool is_stop = false;
    +
    +
    /* Command line parsing */
    +
    struct options {
    +
    int no_notify;
    +
    int update_interval;
    +
    };
    +
    static struct options options = {
    +
    .no_notify = 0,
    +
    .update_interval = 1,
    +
    };
    +
    +
    #define OPTION(t, p) { t, offsetof(struct options, p), 1 }
    +
    static const struct fuse_opt option_spec[] = {
    +
    OPTION("--no-notify", no_notify),
    +
    OPTION("--update-interval=%d", update_interval), FUSE_OPT_END
    +
    };
    +
    +
    static int tfs_stat(fuse_ino_t ino, struct stat *stbuf)
    +
    {
    +
    stbuf->st_ino = ino;
    +
    if (ino == FUSE_ROOT_ID) {
    +
    stbuf->st_mode = S_IFDIR | 0755;
    +
    stbuf->st_nlink = 1;
    +
    }
    +
    +
    else if (ino == FILE_INO) {
    +
    stbuf->st_mode = S_IFREG | 0444;
    +
    stbuf->st_nlink = 1;
    +
    stbuf->st_size = file_size;
    +
    }
    +
    +
    else
    +
    return -1;
    +
    +
    return 0;
    +
    }
    +
    +
    static void tfs_init(void *userdata, struct fuse_conn_info *conn)
    +
    {
    +
    (void)userdata;
    +
    +
    /* Disable the receiving and processing of FUSE_INTERRUPT requests */
    +
    conn->no_interrupt = 1;
    +
    }
    +
    +
    static void tfs_destroy(void *userarg)
    +
    {
    +
    (void)userarg;
    +
    +
    is_stop = true;
    +
    }
    +
    +
    static void tfs_lookup(fuse_req_t req, fuse_ino_t parent, const char *name)
    +
    {
    +
    struct fuse_entry_param e;
    +
    memset(&e, 0, sizeof(e));
    +
    +
    if (parent != FUSE_ROOT_ID)
    +
    goto err_out;
    +
    else if (strcmp(name, FILE_NAME) == 0) {
    +
    e.ino = FILE_INO;
    +
    lookup_cnt++;
    +
    } else
    +
    goto err_out;
    +
    +
    e.attr_timeout = NO_TIMEOUT;
    +
    e.entry_timeout = NO_TIMEOUT;
    +
    if (tfs_stat(e.ino, &e.attr) != 0)
    +
    goto err_out;
    +
    fuse_reply_entry(req, &e);
    +
    return;
    +
    +
    err_out:
    +
    fuse_reply_err(req, ENOENT);
    +
    }
    +
    +
    static void tfs_forget(fuse_req_t req, fuse_ino_t ino, uint64_t nlookup)
    +
    {
    +
    (void)req;
    +
    if (ino == FILE_INO)
    +
    lookup_cnt -= nlookup;
    +
    else
    +
    assert(ino == FUSE_ROOT_ID);
    + +
    }
    +
    +
    static void tfs_getattr(fuse_req_t req, fuse_ino_t ino,
    +
    struct fuse_file_info *fi)
    +
    {
    +
    struct stat stbuf;
    +
    +
    (void)fi;
    +
    +
    memset(&stbuf, 0, sizeof(stbuf));
    +
    if (tfs_stat(ino, &stbuf) != 0)
    +
    fuse_reply_err(req, ENOENT);
    +
    else
    +
    fuse_reply_attr(req, &stbuf, NO_TIMEOUT);
    +
    }
    +
    +
    struct dirbuf {
    +
    char *p;
    +
    size_t size;
    +
    };
    +
    +
    static void dirbuf_add(fuse_req_t req, struct dirbuf *b, const char *name,
    + +
    {
    +
    struct stat stbuf;
    +
    size_t oldsize = b->size;
    +
    b->size += fuse_add_direntry(req, NULL, 0, name, NULL, 0);
    +
    b->p = (char *)realloc(b->p, b->size);
    +
    memset(&stbuf, 0, sizeof(stbuf));
    +
    stbuf.st_ino = ino;
    +
    fuse_add_direntry(req, b->p + oldsize, b->size - oldsize, name, &stbuf,
    +
    b->size);
    +
    }
    +
    +
    #define min(x, y) ((x) < (y) ? (x) : (y))
    +
    +
    static int reply_buf_limited(fuse_req_t req, const char *buf, size_t bufsize,
    +
    off_t off, size_t maxsize)
    +
    {
    +
    if (off < bufsize)
    +
    return fuse_reply_buf(req, buf + off,
    +
    min(bufsize - off, maxsize));
    +
    else
    +
    return fuse_reply_buf(req, NULL, 0);
    +
    }
    +
    +
    static void tfs_readdir(fuse_req_t req, fuse_ino_t ino, size_t size, off_t off,
    +
    struct fuse_file_info *fi)
    +
    {
    +
    (void)fi;
    +
    +
    if (ino != FUSE_ROOT_ID)
    +
    fuse_reply_err(req, ENOTDIR);
    +
    else {
    +
    struct dirbuf b;
    +
    +
    memset(&b, 0, sizeof(b));
    +
    dirbuf_add(req, &b, FILE_NAME, FILE_INO);
    +
    reply_buf_limited(req, b.p, b.size, off, size);
    +
    free(b.p);
    +
    }
    +
    }
    +
    +
    static void tfs_open(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
    +
    {
    +
    /* Make cache persistent even if file is closed,
    +
    this makes it easier to see the effects */
    +
    fi->keep_cache = 1;
    +
    +
    if (ino == FUSE_ROOT_ID)
    +
    fuse_reply_err(req, EISDIR);
    +
    else if ((fi->flags & O_ACCMODE) != O_RDONLY)
    +
    fuse_reply_err(req, EACCES);
    +
    else if (ino == FILE_INO)
    +
    fuse_reply_open(req, fi);
    +
    else {
    +
    // This should not happen
    +
    fprintf(stderr, "Got open for non-existing inode!\n");
    +
    fuse_reply_err(req, ENOENT);
    +
    }
    +
    }
    +
    +
    static void tfs_read(fuse_req_t req, fuse_ino_t ino, size_t size, off_t off,
    +
    struct fuse_file_info *fi)
    +
    {
    +
    (void)fi;
    +
    +
    assert(ino == FILE_INO);
    +
    reply_buf_limited(req, file_contents, file_size, off, size);
    +
    }
    +
    +
    static const struct fuse_lowlevel_ops tfs_oper = {
    +
    .init = tfs_init,
    +
    .destroy = tfs_destroy,
    +
    .lookup = tfs_lookup,
    +
    .getattr = tfs_getattr,
    +
    .readdir = tfs_readdir,
    +
    .open = tfs_open,
    +
    .read = tfs_read,
    +
    .forget = tfs_forget,
    +
    };
    +
    +
    static void update_fs(void)
    +
    {
    +
    struct tm tmbuf;
    +
    struct tm *now;
    +
    time_t t;
    +
    t = time(NULL);
    +
    now = localtime_r(&t, &tmbuf);
    +
    assert(now != NULL);
    +
    +
    file_size = strftime(file_contents, MAX_STR_LEN,
    +
    "The current time is %H:%M:%S\n", now);
    +
    assert(file_size != 0);
    +
    }
    +
    +
    static void *update_fs_loop(void *data)
    +
    {
    +
    struct fuse_session *se = (struct fuse_session *)data;
    +
    +
    while (!is_stop) {
    +
    update_fs();
    +
    if (!options.no_notify && lookup_cnt) {
    +
    /* Only send notification if the kernel is aware of the inode */
    +
    +
    /* Some errors (ENOENT, EBADF, ENODEV) have to be accepted as they
    +
    * might come up during umount, when kernel side already releases
    +
    * all inodes, but does not send FUSE_DESTROY yet.
    +
    */
    +
    int ret = fuse_lowlevel_notify_inval_inode(se, FILE_INO,
    +
    0, 0);
    +
    if ((ret != 0 && !is_stop) && ret != -ENOENT &&
    +
    ret != -EBADF && ret != -ENODEV) {
    +
    fprintf(stderr,
    +
    "ERROR: fuse_lowlevel_notify_store() failed with %s (%d)\n",
    +
    strerror(-ret), -ret);
    +
    abort();
    +
    }
    +
    }
    +
    sleep(options.update_interval);
    +
    }
    +
    return NULL;
    +
    }
    +
    +
    static void show_help(const char *progname)
    +
    {
    +
    printf("usage: %s [options] <mountpoint>\n\n", progname);
    +
    printf("File-system specific options:\n"
    +
    " --update-interval=<secs> Update-rate of file system contents\n"
    +
    " --no-notify Disable kernel notifications\n"
    +
    "\n");
    +
    }
    +
    +
    int main(int argc, char *argv[])
    +
    {
    +
    struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
    +
    struct fuse_session *se;
    +
    struct fuse_cmdline_opts opts;
    +
    struct fuse_loop_config *config;
    +
    pthread_t updater;
    +
    int ret = -1;
    +
    +
    if (fuse_opt_parse(&args, &options, option_spec, NULL) == -1)
    +
    return 1;
    +
    +
    if (fuse_parse_cmdline(&args, &opts) != 0) {
    +
    ret = 1;
    +
    goto err_out1;
    +
    }
    +
    +
    if (opts.show_help) {
    +
    show_help(argv[0]);
    + + +
    ret = 0;
    +
    goto err_out1;
    +
    } else if (opts.show_version) {
    +
    printf("FUSE library version %s\n", fuse_pkgversion());
    + +
    ret = 0;
    +
    goto err_out1;
    +
    }
    +
    +
    /* Initial contents */
    +
    update_fs();
    +
    +
    se = fuse_session_new(&args, &tfs_oper, sizeof(tfs_oper), NULL);
    +
    if (se == NULL)
    +
    goto err_out1;
    +
    + +
    goto err_out2;
    +
    +
    if (fuse_session_mount(se, opts.mountpoint) != 0)
    +
    goto err_out3;
    +
    +
    fuse_daemonize(opts.foreground);
    +
    +
    /* Start thread to update file contents */
    +
    ret = pthread_create(&updater, NULL, update_fs_loop, (void *)se);
    +
    if (ret != 0) {
    +
    fprintf(stderr, "pthread_create failed with %s\n",
    +
    strerror(ret));
    +
    goto err_out3;
    +
    }
    +
    +
    /* Block until ctrl+c or fusermount -u */
    +
    if (opts.singlethread)
    +
    ret = fuse_session_loop(se);
    +
    else {
    +
    config = fuse_loop_cfg_create();
    +
    fuse_loop_cfg_set_clone_fd(config, opts.clone_fd);
    +
    fuse_loop_cfg_set_max_threads(config, opts.max_threads);
    +
    ret = fuse_session_loop_mt(se, config);
    +
    fuse_loop_cfg_destroy(config);
    +
    config = NULL;
    +
    }
    +
    + +
    err_out3:
    + +
    err_out2:
    + +
    err_out1:
    + +
    free(opts.mountpoint);
    +
    +
    return ret ? 1 : 0;
    +
    }
    +
    +
    int fuse_set_signal_handlers(struct fuse_session *se)
    +
    const char * fuse_pkgversion(void)
    Definition fuse.c:5274
    +
    void fuse_remove_signal_handlers(struct fuse_session *se)
    +
    int fuse_daemonize(int foreground)
    Definition helper.c:253
    + +
    void fuse_session_destroy(struct fuse_session *se)
    +
    int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi)
    +
    int fuse_reply_err(fuse_req_t req, int err)
    +
    int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
    +
    struct fuse_req * fuse_req_t
    +
    int fuse_session_loop(struct fuse_session *se)
    Definition fuse_loop.c:19
    +
    int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
    +
    void fuse_session_unmount(struct fuse_session *se)
    +
    void fuse_cmdline_help(void)
    Definition helper.c:130
    +
    void fuse_reply_none(fuse_req_t req)
    +
    void fuse_lowlevel_help(void)
    +
    int fuse_lowlevel_notify_inval_inode(struct fuse_session *se, fuse_ino_t ino, off_t off, off_t len)
    +
    int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
    +
    void fuse_lowlevel_version(void)
    +
    uint64_t fuse_ino_t
    +
    size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct stat *stbuf, off_t off)
    +
    int fuse_reply_attr(fuse_req_t req, const struct stat *attr, double attr_timeout)
    +
    void fuse_opt_free_args(struct fuse_args *args)
    Definition fuse_opt.c:34
    +
    int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
    Definition fuse_opt.c:398
    +
    #define FUSE_ARGS_INIT(argc, argv)
    Definition fuse_opt.h:123
    +
    #define FUSE_OPT_END
    Definition fuse_opt.h:104
    + + +
    char ** argv
    Definition fuse_opt.h:114
    + + +
    uint32_t no_interrupt
    +
    +
    fuse_ino_t ino
    + +
    uint32_t keep_cache
    Definition fuse_common.h:69
    + + + + +
    +

    Definition in file notify_inval_inode.c.

    +
    + + +
    + + diff --git a/doc/html/notify__inval__inode_8c_source.html b/doc/html/notify__inval__inode_8c_source.html new file mode 100644 index 0000000..f306318 --- /dev/null +++ b/doc/html/notify__inval__inode_8c_source.html @@ -0,0 +1,448 @@ + + + + + + + +libfuse: example/notify_inval_inode.c Source File + + + + + + + +
    +
    + + + + + + +
    +
    libfuse +
    +
    +
    + + + + + + + + +
    +
    +
    +
    notify_inval_inode.c
    +
    +
    +Go to the documentation of this file.
    1/*
    +
    2 FUSE: Filesystem in Userspace
    +
    3 Copyright (C) 2016 Nikolaus Rath <Nikolaus@rath.org>
    +
    4
    +
    5 This program can be distributed under the terms of the GNU GPLv2.
    +
    6 See the file GPL2.txt.
    +
    7*/
    +
    8
    +
    60
    +
    61#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 12)
    +
    62
    +
    63#include <fuse_lowlevel.h>
    +
    64#include <stdio.h>
    +
    65#include <stdlib.h>
    +
    66#include <string.h>
    +
    67#include <errno.h>
    +
    68#include <fcntl.h>
    +
    69#include <assert.h>
    +
    70#include <stddef.h>
    +
    71#include <unistd.h>
    +
    72#include <pthread.h>
    +
    73#include <stdbool.h>
    +
    74#include <stdatomic.h>
    +
    75
    +
    76/* We can't actually tell the kernel that there is no
    +
    77 timeout, so we just send a big value */
    +
    78#define NO_TIMEOUT 500000
    +
    79
    +
    80#define MAX_STR_LEN 128
    +
    81#define FILE_INO 2
    +
    82#define FILE_NAME "current_time"
    +
    83static char file_contents[MAX_STR_LEN];
    +
    84static int lookup_cnt = 0;
    +
    85static size_t file_size;
    +
    86static _Atomic bool is_stop = false;
    +
    87
    +
    88/* Command line parsing */
    +
    89struct options {
    +
    90 int no_notify;
    +
    91 int update_interval;
    +
    92};
    +
    93static struct options options = {
    +
    94 .no_notify = 0,
    +
    95 .update_interval = 1,
    +
    96};
    +
    97
    +
    98#define OPTION(t, p) { t, offsetof(struct options, p), 1 }
    +
    99static const struct fuse_opt option_spec[] = {
    +
    100 OPTION("--no-notify", no_notify),
    +
    101 OPTION("--update-interval=%d", update_interval), FUSE_OPT_END
    +
    102};
    +
    103
    +
    104static int tfs_stat(fuse_ino_t ino, struct stat *stbuf)
    +
    105{
    +
    106 stbuf->st_ino = ino;
    +
    107 if (ino == FUSE_ROOT_ID) {
    +
    108 stbuf->st_mode = S_IFDIR | 0755;
    +
    109 stbuf->st_nlink = 1;
    +
    110 }
    +
    111
    +
    112 else if (ino == FILE_INO) {
    +
    113 stbuf->st_mode = S_IFREG | 0444;
    +
    114 stbuf->st_nlink = 1;
    +
    115 stbuf->st_size = file_size;
    +
    116 }
    +
    117
    +
    118 else
    +
    119 return -1;
    +
    120
    +
    121 return 0;
    +
    122}
    +
    123
    +
    124static void tfs_init(void *userdata, struct fuse_conn_info *conn)
    +
    125{
    +
    126 (void)userdata;
    +
    127
    +
    128 /* Disable the receiving and processing of FUSE_INTERRUPT requests */
    +
    129 conn->no_interrupt = 1;
    +
    130}
    +
    131
    +
    132static void tfs_destroy(void *userarg)
    +
    133{
    +
    134 (void)userarg;
    +
    135
    +
    136 is_stop = true;
    +
    137}
    +
    138
    +
    139static void tfs_lookup(fuse_req_t req, fuse_ino_t parent, const char *name)
    +
    140{
    +
    141 struct fuse_entry_param e;
    +
    142 memset(&e, 0, sizeof(e));
    +
    143
    +
    144 if (parent != FUSE_ROOT_ID)
    +
    145 goto err_out;
    +
    146 else if (strcmp(name, FILE_NAME) == 0) {
    +
    147 e.ino = FILE_INO;
    +
    148 lookup_cnt++;
    +
    149 } else
    +
    150 goto err_out;
    +
    151
    +
    152 e.attr_timeout = NO_TIMEOUT;
    +
    153 e.entry_timeout = NO_TIMEOUT;
    +
    154 if (tfs_stat(e.ino, &e.attr) != 0)
    +
    155 goto err_out;
    +
    156 fuse_reply_entry(req, &e);
    +
    157 return;
    +
    158
    +
    159err_out:
    +
    160 fuse_reply_err(req, ENOENT);
    +
    161}
    +
    162
    +
    163static void tfs_forget(fuse_req_t req, fuse_ino_t ino, uint64_t nlookup)
    +
    164{
    +
    165 (void)req;
    +
    166 if (ino == FILE_INO)
    +
    167 lookup_cnt -= nlookup;
    +
    168 else
    +
    169 assert(ino == FUSE_ROOT_ID);
    +
    170 fuse_reply_none(req);
    +
    171}
    +
    172
    +
    173static void tfs_getattr(fuse_req_t req, fuse_ino_t ino,
    +
    174 struct fuse_file_info *fi)
    +
    175{
    +
    176 struct stat stbuf;
    +
    177
    +
    178 (void)fi;
    +
    179
    +
    180 memset(&stbuf, 0, sizeof(stbuf));
    +
    181 if (tfs_stat(ino, &stbuf) != 0)
    +
    182 fuse_reply_err(req, ENOENT);
    +
    183 else
    +
    184 fuse_reply_attr(req, &stbuf, NO_TIMEOUT);
    +
    185}
    +
    186
    +
    187struct dirbuf {
    +
    188 char *p;
    +
    189 size_t size;
    +
    190};
    +
    191
    +
    192static void dirbuf_add(fuse_req_t req, struct dirbuf *b, const char *name,
    +
    193 fuse_ino_t ino)
    +
    194{
    +
    195 struct stat stbuf;
    +
    196 size_t oldsize = b->size;
    +
    197 b->size += fuse_add_direntry(req, NULL, 0, name, NULL, 0);
    +
    198 b->p = (char *)realloc(b->p, b->size);
    +
    199 memset(&stbuf, 0, sizeof(stbuf));
    +
    200 stbuf.st_ino = ino;
    +
    201 fuse_add_direntry(req, b->p + oldsize, b->size - oldsize, name, &stbuf,
    +
    202 b->size);
    +
    203}
    +
    204
    +
    205#define min(x, y) ((x) < (y) ? (x) : (y))
    +
    206
    +
    207static int reply_buf_limited(fuse_req_t req, const char *buf, size_t bufsize,
    +
    208 off_t off, size_t maxsize)
    +
    209{
    +
    210 if (off < bufsize)
    +
    211 return fuse_reply_buf(req, buf + off,
    +
    212 min(bufsize - off, maxsize));
    +
    213 else
    +
    214 return fuse_reply_buf(req, NULL, 0);
    +
    215}
    +
    216
    +
    217static void tfs_readdir(fuse_req_t req, fuse_ino_t ino, size_t size, off_t off,
    +
    218 struct fuse_file_info *fi)
    +
    219{
    +
    220 (void)fi;
    +
    221
    +
    222 if (ino != FUSE_ROOT_ID)
    +
    223 fuse_reply_err(req, ENOTDIR);
    +
    224 else {
    +
    225 struct dirbuf b;
    +
    226
    +
    227 memset(&b, 0, sizeof(b));
    +
    228 dirbuf_add(req, &b, FILE_NAME, FILE_INO);
    +
    229 reply_buf_limited(req, b.p, b.size, off, size);
    +
    230 free(b.p);
    +
    231 }
    +
    232}
    +
    233
    +
    234static void tfs_open(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
    +
    235{
    +
    236 /* Make cache persistent even if file is closed,
    +
    237 this makes it easier to see the effects */
    +
    238 fi->keep_cache = 1;
    +
    239
    +
    240 if (ino == FUSE_ROOT_ID)
    +
    241 fuse_reply_err(req, EISDIR);
    +
    242 else if ((fi->flags & O_ACCMODE) != O_RDONLY)
    +
    243 fuse_reply_err(req, EACCES);
    +
    244 else if (ino == FILE_INO)
    +
    245 fuse_reply_open(req, fi);
    +
    246 else {
    +
    247 // This should not happen
    +
    248 fprintf(stderr, "Got open for non-existing inode!\n");
    +
    249 fuse_reply_err(req, ENOENT);
    +
    250 }
    +
    251}
    +
    252
    +
    253static void tfs_read(fuse_req_t req, fuse_ino_t ino, size_t size, off_t off,
    +
    254 struct fuse_file_info *fi)
    +
    255{
    +
    256 (void)fi;
    +
    257
    +
    258 assert(ino == FILE_INO);
    +
    259 reply_buf_limited(req, file_contents, file_size, off, size);
    +
    260}
    +
    261
    +
    262static const struct fuse_lowlevel_ops tfs_oper = {
    +
    263 .init = tfs_init,
    +
    264 .destroy = tfs_destroy,
    +
    265 .lookup = tfs_lookup,
    +
    266 .getattr = tfs_getattr,
    +
    267 .readdir = tfs_readdir,
    +
    268 .open = tfs_open,
    +
    269 .read = tfs_read,
    +
    270 .forget = tfs_forget,
    +
    271};
    +
    272
    +
    273static void update_fs(void)
    +
    274{
    +
    275 struct tm tmbuf;
    +
    276 struct tm *now;
    +
    277 time_t t;
    +
    278 t = time(NULL);
    +
    279 now = localtime_r(&t, &tmbuf);
    +
    280 assert(now != NULL);
    +
    281
    +
    282 file_size = strftime(file_contents, MAX_STR_LEN,
    +
    283 "The current time is %H:%M:%S\n", now);
    +
    284 assert(file_size != 0);
    +
    285}
    +
    286
    +
    287static void *update_fs_loop(void *data)
    +
    288{
    +
    289 struct fuse_session *se = (struct fuse_session *)data;
    +
    290
    +
    291 while (!is_stop) {
    +
    292 update_fs();
    +
    293 if (!options.no_notify && lookup_cnt) {
    +
    294 /* Only send notification if the kernel is aware of the inode */
    +
    295
    +
    296 /* Some errors (ENOENT, EBADF, ENODEV) have to be accepted as they
    +
    297 * might come up during umount, when kernel side already releases
    +
    298 * all inodes, but does not send FUSE_DESTROY yet.
    +
    299 */
    +
    300 int ret = fuse_lowlevel_notify_inval_inode(se, FILE_INO,
    +
    301 0, 0);
    +
    302 if ((ret != 0 && !is_stop) && ret != -ENOENT &&
    +
    303 ret != -EBADF && ret != -ENODEV) {
    +
    304 fprintf(stderr,
    +
    305 "ERROR: fuse_lowlevel_notify_store() failed with %s (%d)\n",
    +
    306 strerror(-ret), -ret);
    +
    307 abort();
    +
    308 }
    +
    309 }
    +
    310 sleep(options.update_interval);
    +
    311 }
    +
    312 return NULL;
    +
    313}
    +
    314
    +
    315static void show_help(const char *progname)
    +
    316{
    +
    317 printf("usage: %s [options] <mountpoint>\n\n", progname);
    +
    318 printf("File-system specific options:\n"
    +
    319 " --update-interval=<secs> Update-rate of file system contents\n"
    +
    320 " --no-notify Disable kernel notifications\n"
    +
    321 "\n");
    +
    322}
    +
    323
    +
    324int main(int argc, char *argv[])
    +
    325{
    +
    326 struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
    +
    327 struct fuse_session *se;
    +
    328 struct fuse_cmdline_opts opts;
    +
    329 struct fuse_loop_config *config;
    +
    330 pthread_t updater;
    +
    331 int ret = -1;
    +
    332
    +
    333 if (fuse_opt_parse(&args, &options, option_spec, NULL) == -1)
    +
    334 return 1;
    +
    335
    +
    336 if (fuse_parse_cmdline(&args, &opts) != 0) {
    +
    337 ret = 1;
    +
    338 goto err_out1;
    +
    339 }
    +
    340
    +
    341 if (opts.show_help) {
    +
    342 show_help(argv[0]);
    + + +
    345 ret = 0;
    +
    346 goto err_out1;
    +
    347 } else if (opts.show_version) {
    +
    348 printf("FUSE library version %s\n", fuse_pkgversion());
    + +
    350 ret = 0;
    +
    351 goto err_out1;
    +
    352 }
    +
    353
    +
    354 /* Initial contents */
    +
    355 update_fs();
    +
    356
    +
    357 se = fuse_session_new(&args, &tfs_oper, sizeof(tfs_oper), NULL);
    +
    358 if (se == NULL)
    +
    359 goto err_out1;
    +
    360
    +
    361 if (fuse_set_signal_handlers(se) != 0)
    +
    362 goto err_out2;
    +
    363
    +
    364 if (fuse_session_mount(se, opts.mountpoint) != 0)
    +
    365 goto err_out3;
    +
    366
    +
    367 fuse_daemonize(opts.foreground);
    +
    368
    +
    369 /* Start thread to update file contents */
    +
    370 ret = pthread_create(&updater, NULL, update_fs_loop, (void *)se);
    +
    371 if (ret != 0) {
    +
    372 fprintf(stderr, "pthread_create failed with %s\n",
    +
    373 strerror(ret));
    +
    374 goto err_out3;
    +
    375 }
    +
    376
    +
    377 /* Block until ctrl+c or fusermount -u */
    +
    378 if (opts.singlethread)
    +
    379 ret = fuse_session_loop(se);
    +
    380 else {
    +
    381 config = fuse_loop_cfg_create();
    +
    382 fuse_loop_cfg_set_clone_fd(config, opts.clone_fd);
    +
    383 fuse_loop_cfg_set_max_threads(config, opts.max_threads);
    +
    384 ret = fuse_session_loop_mt(se, config);
    +
    385 fuse_loop_cfg_destroy(config);
    +
    386 config = NULL;
    +
    387 }
    +
    388
    + +
    390err_out3:
    + +
    392err_out2:
    + +
    394err_out1:
    +
    395 fuse_opt_free_args(&args);
    +
    396 free(opts.mountpoint);
    +
    397
    +
    398 return ret ? 1 : 0;
    +
    399}
    +
    400
    +
    int fuse_set_signal_handlers(struct fuse_session *se)
    +
    const char * fuse_pkgversion(void)
    Definition fuse.c:5274
    +
    void fuse_remove_signal_handlers(struct fuse_session *se)
    +
    int fuse_daemonize(int foreground)
    Definition helper.c:253
    + +
    void fuse_session_destroy(struct fuse_session *se)
    +
    int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi)
    +
    int fuse_reply_err(fuse_req_t req, int err)
    +
    int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
    +
    struct fuse_req * fuse_req_t
    +
    int fuse_session_loop(struct fuse_session *se)
    Definition fuse_loop.c:19
    +
    int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
    +
    void fuse_session_unmount(struct fuse_session *se)
    +
    void fuse_cmdline_help(void)
    Definition helper.c:130
    +
    void fuse_reply_none(fuse_req_t req)
    +
    void fuse_lowlevel_help(void)
    +
    int fuse_lowlevel_notify_inval_inode(struct fuse_session *se, fuse_ino_t ino, off_t off, off_t len)
    +
    int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
    +
    void fuse_lowlevel_version(void)
    +
    uint64_t fuse_ino_t
    +
    size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct stat *stbuf, off_t off)
    +
    int fuse_reply_attr(fuse_req_t req, const struct stat *attr, double attr_timeout)
    +
    void fuse_opt_free_args(struct fuse_args *args)
    Definition fuse_opt.c:34
    +
    int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
    Definition fuse_opt.c:398
    +
    #define FUSE_ARGS_INIT(argc, argv)
    Definition fuse_opt.h:123
    +
    #define FUSE_OPT_END
    Definition fuse_opt.h:104
    + + +
    char ** argv
    Definition fuse_opt.h:114
    + + +
    uint32_t no_interrupt
    +
    +
    fuse_ino_t ino
    + +
    uint32_t keep_cache
    Definition fuse_common.h:69
    + + + + +
    + + +
    + + diff --git a/doc/html/notify__store__retrieve_8c.html b/doc/html/notify__store__retrieve_8c.html new file mode 100644 index 0000000..49c027a --- /dev/null +++ b/doc/html/notify__store__retrieve_8c.html @@ -0,0 +1,571 @@ + + + + + + + +libfuse: example/notify_store_retrieve.c File Reference + + + + + + + +
    +
    + + + + + + +
    +
    libfuse +
    +
    +
    + + + + + + + + +
    +
    +
    +
    notify_store_retrieve.c File Reference
    +
    +
    +
    #include <fuse_lowlevel.h>
    +#include <stdio.h>
    +#include <stdlib.h>
    +#include <string.h>
    +#include <errno.h>
    +#include <fcntl.h>
    +#include <assert.h>
    +#include <stddef.h>
    +#include <unistd.h>
    +#include <pthread.h>
    +#include <stdbool.h>
    +
    +

    Go to the source code of this file.

    +

    Detailed Description

    +

    This example implements a file system with a single file whose contents change dynamically: it always contains the current time.

    +

    While notify_inval_inode.c uses fuse_lowlevel_notify_inval_inode() to let the kernel know that it has to invalidate the cache, this example actively pushes the updated data into the kernel cache using fuse_lowlevel_notify_store().

    +

    To see the effect, first start the file system with the --no-notify option:

    $ notify_store_retrieve --update-interval=1 --no-notify mnt/
    +

    Observe that the output never changes, even though the file system updates it once per second. This is because the contents are cached in the kernel:

    $ for i in 1 2 3 4 5; do
    +>     cat mnt/current_time
    +>     sleep 1
    +> done
    +The current time is 15:58:18
    +The current time is 15:58:18
    +The current time is 15:58:18
    +The current time is 15:58:18
    +The current time is 15:58:18
    +

    If you instead enable the notification functions, the changes become visible:

    $ notify_store_retrieve --update-interval=1 mnt/
    +$ for i in 1 2 3 4 5; do
    +>     cat mnt/current_time
    +>     sleep 1
    +> done
    +The current time is 15:58:40
    +The current time is 15:58:41
    +The current time is 15:58:42
    +The current time is 15:58:43
    +The current time is 15:58:44
    +

    +Compilation

    +
    gcc -Wall notify_store_retrieve.c `pkg-config fuse3 --cflags --libs` -o notify_store_retrieve
    +

    +Source code

    +
    /*
    +
    FUSE: Filesystem in Userspace
    +
    Copyright (C) 2016 Nikolaus Rath <Nikolaus@rath.org>
    +
    +
    This program can be distributed under the terms of the GNU GPLv2.
    +
    See the file GPL2.txt.
    +
    */
    +
    +
    +
    #define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 12)
    +
    +
    #include <fuse_lowlevel.h>
    +
    #include <stdio.h>
    +
    #include <stdlib.h>
    +
    #include <string.h>
    +
    #include <errno.h>
    +
    #include <fcntl.h>
    +
    #include <assert.h>
    +
    #include <stddef.h>
    +
    #include <unistd.h>
    +
    #include <pthread.h>
    +
    #include <stdbool.h>
    +
    +
    /* We can't actually tell the kernel that there is no
    +
    timeout, so we just send a big value */
    +
    #define NO_TIMEOUT 500000
    +
    +
    #define MAX_STR_LEN 128
    +
    #define FILE_INO 2
    +
    #define FILE_NAME "current_time"
    +
    static char file_contents[MAX_STR_LEN];
    +
    static int lookup_cnt = 0;
    +
    static int open_cnt = 0;
    +
    static size_t file_size;
    +
    static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
    +
    +
    /* Keep track if we ever stored data (==1), and
    +
    received it back correctly (==2) */
    +
    static int retrieve_status = 0;
    +
    +
    static bool is_umount = false;
    +
    +
    /* updater thread tid */
    +
    static pthread_t updater;
    +
    +
    /* Command line parsing */
    +
    struct options {
    +
    int no_notify;
    +
    int update_interval;
    +
    };
    +
    static struct options options = {
    +
    .no_notify = 0,
    +
    .update_interval = 1,
    +
    };
    +
    +
    #define OPTION(t, p) { t, offsetof(struct options, p), 1 }
    +
    static const struct fuse_opt option_spec[] = {
    +
    OPTION("--no-notify", no_notify),
    +
    OPTION("--update-interval=%d", update_interval), FUSE_OPT_END
    +
    };
    +
    +
    static int tfs_stat(fuse_ino_t ino, struct stat *stbuf)
    +
    {
    +
    stbuf->st_ino = ino;
    +
    if (ino == FUSE_ROOT_ID) {
    +
    stbuf->st_mode = S_IFDIR | 0755;
    +
    stbuf->st_nlink = 1;
    +
    }
    +
    +
    else if (ino == FILE_INO) {
    +
    stbuf->st_mode = S_IFREG | 0444;
    +
    stbuf->st_nlink = 1;
    +
    stbuf->st_size = file_size;
    +
    }
    +
    +
    else
    +
    return -1;
    +
    +
    return 0;
    +
    }
    +
    +
    static void tfs_init(void *userdata, struct fuse_conn_info *conn)
    +
    {
    +
    (void)userdata;
    +
    +
    /* Disable the receiving and processing of FUSE_INTERRUPT requests */
    +
    conn->no_interrupt = 1;
    +
    }
    +
    +
    static void tfs_lookup(fuse_req_t req, fuse_ino_t parent, const char *name)
    +
    {
    +
    struct fuse_entry_param e;
    +
    memset(&e, 0, sizeof(e));
    +
    +
    if (parent != FUSE_ROOT_ID)
    +
    goto err_out;
    +
    else if (strcmp(name, FILE_NAME) == 0)
    +
    e.ino = FILE_INO;
    +
    else
    +
    goto err_out;
    +
    +
    e.attr_timeout = NO_TIMEOUT;
    +
    e.entry_timeout = NO_TIMEOUT;
    +
    if (tfs_stat(e.ino, &e.attr) != 0)
    +
    goto err_out;
    +
    fuse_reply_entry(req, &e);
    +
    +
    /*
    +
    * must only be set when the kernel knows about the entry,
    +
    * otherwise update_fs_loop() might see a positive count, but kernel
    +
    * would not have the entry yet
    +
    */
    +
    if (e.ino == FILE_INO) {
    +
    pthread_mutex_lock(&lock);
    +
    lookup_cnt++;
    +
    pthread_mutex_unlock(&lock);
    +
    }
    +
    +
    return;
    +
    +
    err_out:
    +
    fuse_reply_err(req, ENOENT);
    +
    }
    +
    +
    static void tfs_forget(fuse_req_t req, fuse_ino_t ino, uint64_t nlookup)
    +
    {
    +
    (void)req;
    +
    if (ino == FILE_INO) {
    +
    pthread_mutex_lock(&lock);
    +
    lookup_cnt -= nlookup;
    +
    pthread_mutex_unlock(&lock);
    +
    } else
    +
    assert(ino == FUSE_ROOT_ID);
    + +
    }
    +
    +
    static void tfs_getattr(fuse_req_t req, fuse_ino_t ino,
    +
    struct fuse_file_info *fi)
    +
    {
    +
    struct stat stbuf;
    +
    +
    (void)fi;
    +
    +
    memset(&stbuf, 0, sizeof(stbuf));
    +
    if (tfs_stat(ino, &stbuf) != 0)
    +
    fuse_reply_err(req, ENOENT);
    +
    else
    +
    fuse_reply_attr(req, &stbuf, NO_TIMEOUT);
    +
    }
    +
    +
    struct dirbuf {
    +
    char *p;
    +
    size_t size;
    +
    };
    +
    +
    static void dirbuf_add(fuse_req_t req, struct dirbuf *b, const char *name,
    + +
    {
    +
    struct stat stbuf;
    +
    size_t oldsize = b->size;
    +
    b->size += fuse_add_direntry(req, NULL, 0, name, NULL, 0);
    +
    b->p = (char *)realloc(b->p, b->size);
    +
    memset(&stbuf, 0, sizeof(stbuf));
    +
    stbuf.st_ino = ino;
    +
    fuse_add_direntry(req, b->p + oldsize, b->size - oldsize, name, &stbuf,
    +
    b->size);
    +
    }
    +
    +
    #define min(x, y) ((x) < (y) ? (x) : (y))
    +
    +
    static int reply_buf_limited(fuse_req_t req, const char *buf, size_t bufsize,
    +
    off_t off, size_t maxsize)
    +
    {
    +
    if (off < bufsize)
    +
    return fuse_reply_buf(req, buf + off,
    +
    min(bufsize - off, maxsize));
    +
    else
    +
    return fuse_reply_buf(req, NULL, 0);
    +
    }
    +
    +
    static void tfs_readdir(fuse_req_t req, fuse_ino_t ino, size_t size, off_t off,
    +
    struct fuse_file_info *fi)
    +
    {
    +
    (void)fi;
    +
    +
    if (ino != FUSE_ROOT_ID)
    +
    fuse_reply_err(req, ENOTDIR);
    +
    else {
    +
    struct dirbuf b;
    +
    +
    memset(&b, 0, sizeof(b));
    +
    dirbuf_add(req, &b, FILE_NAME, FILE_INO);
    +
    reply_buf_limited(req, b.p, b.size, off, size);
    +
    free(b.p);
    +
    }
    +
    }
    +
    +
    static void tfs_open(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
    +
    {
    +
    /* Make cache persistent even if file is closed,
    +
    this makes it easier to see the effects */
    +
    fi->keep_cache = 1;
    +
    +
    if (ino == FUSE_ROOT_ID)
    +
    fuse_reply_err(req, EISDIR);
    +
    else if ((fi->flags & O_ACCMODE) != O_RDONLY)
    +
    fuse_reply_err(req, EACCES);
    +
    else if (ino == FILE_INO) {
    +
    fuse_reply_open(req, fi);
    +
    pthread_mutex_lock(&lock);
    +
    open_cnt++;
    +
    pthread_mutex_unlock(&lock);
    +
    } else {
    +
    // This should not happen
    +
    fprintf(stderr, "Got open for non-existing inode!\n");
    +
    fuse_reply_err(req, ENOENT);
    +
    }
    +
    }
    +
    +
    static void tfs_read(fuse_req_t req, fuse_ino_t ino, size_t size, off_t off,
    +
    struct fuse_file_info *fi)
    +
    {
    +
    (void)fi;
    +
    +
    assert(ino == FILE_INO);
    +
    reply_buf_limited(req, file_contents, file_size, off, size);
    +
    }
    +
    +
    static void tfs_retrieve_reply(fuse_req_t req, void *cookie, fuse_ino_t ino,
    +
    off_t offset, struct fuse_bufvec *data)
    +
    {
    +
    struct fuse_bufvec bufv;
    +
    char buf[MAX_STR_LEN];
    +
    char *expected;
    +
    ssize_t ret;
    +
    +
    assert(ino == FILE_INO);
    +
    assert(offset == 0);
    +
    expected = (char *)cookie;
    +
    +
    bufv.count = 1;
    +
    bufv.idx = 0;
    +
    bufv.off = 0;
    +
    bufv.buf[0].size = MAX_STR_LEN;
    +
    bufv.buf[0].mem = buf;
    +
    bufv.buf[0].flags = 0;
    +
    +
    ret = fuse_buf_copy(&bufv, data, 0);
    +
    assert(ret > 0);
    +
    assert(strncmp(buf, expected, ret) == 0);
    +
    free(expected);
    +
    retrieve_status = 2;
    + +
    }
    +
    +
    static void tfs_destroy(void *userdata)
    +
    {
    +
    (void)userdata;
    +
    +
    is_umount = true;
    +
    +
    pthread_join(updater, NULL);
    +
    }
    +
    +
    static const struct fuse_lowlevel_ops tfs_oper = {
    +
    .init = tfs_init,
    +
    .lookup = tfs_lookup,
    +
    .getattr = tfs_getattr,
    +
    .readdir = tfs_readdir,
    +
    .open = tfs_open,
    +
    .read = tfs_read,
    +
    .forget = tfs_forget,
    +
    .retrieve_reply = tfs_retrieve_reply,
    +
    .destroy = tfs_destroy,
    +
    };
    +
    +
    static void update_fs(void)
    +
    {
    +
    struct tm tmbuf;
    +
    struct tm *now;
    +
    time_t t;
    +
    t = time(NULL);
    +
    now = localtime_r(&t, &tmbuf);
    +
    assert(now != NULL);
    +
    +
    file_size = strftime(file_contents, MAX_STR_LEN,
    +
    "The current time is %H:%M:%S\n", now);
    +
    assert(file_size != 0);
    +
    }
    +
    +
    static void *update_fs_loop(void *data)
    +
    {
    +
    struct fuse_session *se = (struct fuse_session *)data;
    +
    struct fuse_bufvec bufv;
    +
    int ret;
    +
    +
    while (!is_umount) {
    +
    update_fs();
    +
    pthread_mutex_lock(&lock);
    +
    if (!options.no_notify && open_cnt && lookup_cnt) {
    +
    /* Only send notification if the kernel
    +
    is aware of the inode */
    +
    bufv.count = 1;
    +
    bufv.idx = 0;
    +
    bufv.off = 0;
    +
    bufv.buf[0].size = file_size;
    +
    bufv.buf[0].mem = file_contents;
    +
    bufv.buf[0].flags = 0;
    +
    +
    /*
    +
    * Some errors (ENOENT, EBADF, ENODEV) have to be accepted as they
    +
    * might come up during umount, when kernel side already releases
    +
    * all inodes, but does not send FUSE_DESTROY yet.
    +
    */
    +
    +
    ret = fuse_lowlevel_notify_store(se, FILE_INO, 0, &bufv,
    +
    0);
    +
    if ((ret != 0 && !is_umount) && ret != -ENOENT &&
    +
    ret != -EBADF && ret != -ENODEV) {
    +
    fprintf(stderr,
    +
    "ERROR: fuse_lowlevel_notify_store() failed with %s (%d)\n",
    +
    strerror(-ret), -ret);
    +
    abort();
    +
    }
    +
    +
    /* To make sure that everything worked correctly, ask the
    +
    kernel to send us back the stored data */
    + +
    se, FILE_INO, MAX_STR_LEN, 0,
    +
    (void *)strdup(file_contents));
    +
    assert((ret == 0 || is_umount) || ret == -ENOENT ||
    +
    ret == -EBADF || ret != -ENODEV);
    +
    if (retrieve_status == 0)
    +
    retrieve_status = 1;
    +
    }
    +
    pthread_mutex_unlock(&lock);
    +
    sleep(options.update_interval);
    +
    }
    +
    return NULL;
    +
    }
    +
    +
    static void show_help(const char *progname)
    +
    {
    +
    printf("usage: %s [options] <mountpoint>\n\n", progname);
    +
    printf("File-system specific options:\n"
    +
    " --update-interval=<secs> Update-rate of file system contents\n"
    +
    " --no-notify Disable kernel notifications\n"
    +
    "\n");
    +
    }
    +
    +
    int main(int argc, char *argv[])
    +
    {
    +
    struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
    +
    struct fuse_session *se;
    +
    struct fuse_cmdline_opts opts;
    +
    struct fuse_loop_config *config;
    +
    int ret = -1;
    +
    +
    if (fuse_opt_parse(&args, &options, option_spec, NULL) == -1)
    +
    return 1;
    +
    +
    if (fuse_parse_cmdline(&args, &opts) != 0)
    +
    return 1;
    +
    if (opts.show_help) {
    +
    show_help(argv[0]);
    + + +
    ret = 0;
    +
    goto err_out1;
    +
    } else if (opts.show_version) {
    +
    printf("FUSE library version %s\n", fuse_pkgversion());
    + +
    ret = 0;
    +
    goto err_out1;
    +
    }
    +
    +
    /* Initial contents */
    +
    update_fs();
    +
    +
    se = fuse_session_new(&args, &tfs_oper, sizeof(tfs_oper), NULL);
    +
    if (se == NULL)
    +
    goto err_out1;
    +
    + +
    goto err_out2;
    +
    +
    if (fuse_session_mount(se, opts.mountpoint) != 0)
    +
    goto err_out3;
    +
    +
    fuse_daemonize(opts.foreground);
    +
    +
    /* Start thread to update file contents */
    +
    ret = pthread_create(&updater, NULL, update_fs_loop, (void *)se);
    +
    if (ret != 0) {
    +
    fprintf(stderr, "pthread_create failed with %s\n",
    +
    strerror(ret));
    +
    goto err_out3;
    +
    }
    +
    +
    /* Block until ctrl+c or fusermount -u */
    +
    if (opts.singlethread)
    +
    ret = fuse_session_loop(se);
    +
    else {
    +
    config = fuse_loop_cfg_create();
    +
    fuse_loop_cfg_set_clone_fd(config, opts.clone_fd);
    +
    fuse_loop_cfg_set_max_threads(config, opts.max_threads);
    +
    ret = fuse_session_loop_mt(se, config);
    +
    fuse_loop_cfg_destroy(config);
    +
    config = NULL;
    +
    }
    +
    +
    assert(retrieve_status != 1);
    + +
    err_out3:
    + +
    err_out2:
    + +
    err_out1:
    +
    free(opts.mountpoint);
    + +
    +
    return ret ? 1 : 0;
    +
    }
    +
    +
    int fuse_set_signal_handlers(struct fuse_session *se)
    +
    ssize_t fuse_buf_copy(struct fuse_bufvec *dst, struct fuse_bufvec *src, enum fuse_buf_copy_flags flags)
    Definition buffer.c:284
    +
    const char * fuse_pkgversion(void)
    Definition fuse.c:5274
    +
    void fuse_remove_signal_handlers(struct fuse_session *se)
    +
    int fuse_daemonize(int foreground)
    Definition helper.c:253
    + +
    void fuse_session_destroy(struct fuse_session *se)
    +
    int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi)
    +
    int fuse_reply_err(fuse_req_t req, int err)
    +
    int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
    +
    struct fuse_req * fuse_req_t
    +
    int fuse_lowlevel_notify_retrieve(struct fuse_session *se, fuse_ino_t ino, size_t size, off_t offset, void *cookie)
    +
    int fuse_session_loop(struct fuse_session *se)
    Definition fuse_loop.c:19
    +
    int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
    +
    void fuse_session_unmount(struct fuse_session *se)
    +
    void fuse_cmdline_help(void)
    Definition helper.c:130
    +
    void fuse_reply_none(fuse_req_t req)
    +
    void fuse_lowlevel_help(void)
    +
    int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
    +
    void fuse_lowlevel_version(void)
    +
    uint64_t fuse_ino_t
    +
    size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct stat *stbuf, off_t off)
    +
    int fuse_reply_attr(fuse_req_t req, const struct stat *attr, double attr_timeout)
    +
    int fuse_lowlevel_notify_store(struct fuse_session *se, fuse_ino_t ino, off_t offset, struct fuse_bufvec *bufv, enum fuse_buf_copy_flags flags)
    +
    void fuse_opt_free_args(struct fuse_args *args)
    Definition fuse_opt.c:34
    +
    int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
    Definition fuse_opt.c:398
    +
    #define FUSE_ARGS_INIT(argc, argv)
    Definition fuse_opt.h:123
    +
    #define FUSE_OPT_END
    Definition fuse_opt.h:104
    + + +
    char ** argv
    Definition fuse_opt.h:114
    +
    enum fuse_buf_flags flags
    + +
    struct fuse_buf buf[1]
    + + +
    uint32_t no_interrupt
    +
    +
    fuse_ino_t ino
    + +
    uint32_t keep_cache
    Definition fuse_common.h:69
    + + + + +
    +

    Definition in file notify_store_retrieve.c.

    +
    + + +
    + + diff --git a/doc/html/notify__store__retrieve_8c_source.html b/doc/html/notify__store__retrieve_8c_source.html new file mode 100644 index 0000000..c981e3d --- /dev/null +++ b/doc/html/notify__store__retrieve_8c_source.html @@ -0,0 +1,528 @@ + + + + + + + +libfuse: example/notify_store_retrieve.c Source File + + + + + + + +
    +
    + + + + + + +
    +
    libfuse +
    +
    +
    + + + + + + + + +
    +
    +
    +
    notify_store_retrieve.c
    +
    +
    +Go to the documentation of this file.
    1/*
    +
    2 FUSE: Filesystem in Userspace
    +
    3 Copyright (C) 2016 Nikolaus Rath <Nikolaus@rath.org>
    +
    4
    +
    5 This program can be distributed under the terms of the GNU GPLv2.
    +
    6 See the file GPL2.txt.
    +
    7*/
    +
    8
    +
    59
    +
    60#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 12)
    +
    61
    +
    62#include <fuse_lowlevel.h>
    +
    63#include <stdio.h>
    +
    64#include <stdlib.h>
    +
    65#include <string.h>
    +
    66#include <errno.h>
    +
    67#include <fcntl.h>
    +
    68#include <assert.h>
    +
    69#include <stddef.h>
    +
    70#include <unistd.h>
    +
    71#include <pthread.h>
    +
    72#include <stdbool.h>
    +
    73
    +
    74/* We can't actually tell the kernel that there is no
    +
    75 timeout, so we just send a big value */
    +
    76#define NO_TIMEOUT 500000
    +
    77
    +
    78#define MAX_STR_LEN 128
    +
    79#define FILE_INO 2
    +
    80#define FILE_NAME "current_time"
    +
    81static char file_contents[MAX_STR_LEN];
    +
    82static int lookup_cnt = 0;
    +
    83static int open_cnt = 0;
    +
    84static size_t file_size;
    +
    85static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
    +
    86
    +
    87/* Keep track if we ever stored data (==1), and
    +
    88 received it back correctly (==2) */
    +
    89static int retrieve_status = 0;
    +
    90
    +
    91static bool is_umount = false;
    +
    92
    +
    93/* updater thread tid */
    +
    94static pthread_t updater;
    +
    95
    +
    96/* Command line parsing */
    +
    97struct options {
    +
    98 int no_notify;
    +
    99 int update_interval;
    +
    100};
    +
    101static struct options options = {
    +
    102 .no_notify = 0,
    +
    103 .update_interval = 1,
    +
    104};
    +
    105
    +
    106#define OPTION(t, p) { t, offsetof(struct options, p), 1 }
    +
    107static const struct fuse_opt option_spec[] = {
    +
    108 OPTION("--no-notify", no_notify),
    +
    109 OPTION("--update-interval=%d", update_interval), FUSE_OPT_END
    +
    110};
    +
    111
    +
    112static int tfs_stat(fuse_ino_t ino, struct stat *stbuf)
    +
    113{
    +
    114 stbuf->st_ino = ino;
    +
    115 if (ino == FUSE_ROOT_ID) {
    +
    116 stbuf->st_mode = S_IFDIR | 0755;
    +
    117 stbuf->st_nlink = 1;
    +
    118 }
    +
    119
    +
    120 else if (ino == FILE_INO) {
    +
    121 stbuf->st_mode = S_IFREG | 0444;
    +
    122 stbuf->st_nlink = 1;
    +
    123 stbuf->st_size = file_size;
    +
    124 }
    +
    125
    +
    126 else
    +
    127 return -1;
    +
    128
    +
    129 return 0;
    +
    130}
    +
    131
    +
    132static void tfs_init(void *userdata, struct fuse_conn_info *conn)
    +
    133{
    +
    134 (void)userdata;
    +
    135
    +
    136 /* Disable the receiving and processing of FUSE_INTERRUPT requests */
    +
    137 conn->no_interrupt = 1;
    +
    138}
    +
    139
    +
    140static void tfs_lookup(fuse_req_t req, fuse_ino_t parent, const char *name)
    +
    141{
    +
    142 struct fuse_entry_param e;
    +
    143 memset(&e, 0, sizeof(e));
    +
    144
    +
    145 if (parent != FUSE_ROOT_ID)
    +
    146 goto err_out;
    +
    147 else if (strcmp(name, FILE_NAME) == 0)
    +
    148 e.ino = FILE_INO;
    +
    149 else
    +
    150 goto err_out;
    +
    151
    +
    152 e.attr_timeout = NO_TIMEOUT;
    +
    153 e.entry_timeout = NO_TIMEOUT;
    +
    154 if (tfs_stat(e.ino, &e.attr) != 0)
    +
    155 goto err_out;
    +
    156 fuse_reply_entry(req, &e);
    +
    157
    +
    158 /*
    +
    159 * must only be set when the kernel knows about the entry,
    +
    160 * otherwise update_fs_loop() might see a positive count, but kernel
    +
    161 * would not have the entry yet
    +
    162 */
    +
    163 if (e.ino == FILE_INO) {
    +
    164 pthread_mutex_lock(&lock);
    +
    165 lookup_cnt++;
    +
    166 pthread_mutex_unlock(&lock);
    +
    167 }
    +
    168
    +
    169 return;
    +
    170
    +
    171err_out:
    +
    172 fuse_reply_err(req, ENOENT);
    +
    173}
    +
    174
    +
    175static void tfs_forget(fuse_req_t req, fuse_ino_t ino, uint64_t nlookup)
    +
    176{
    +
    177 (void)req;
    +
    178 if (ino == FILE_INO) {
    +
    179 pthread_mutex_lock(&lock);
    +
    180 lookup_cnt -= nlookup;
    +
    181 pthread_mutex_unlock(&lock);
    +
    182 } else
    +
    183 assert(ino == FUSE_ROOT_ID);
    +
    184 fuse_reply_none(req);
    +
    185}
    +
    186
    +
    187static void tfs_getattr(fuse_req_t req, fuse_ino_t ino,
    +
    188 struct fuse_file_info *fi)
    +
    189{
    +
    190 struct stat stbuf;
    +
    191
    +
    192 (void)fi;
    +
    193
    +
    194 memset(&stbuf, 0, sizeof(stbuf));
    +
    195 if (tfs_stat(ino, &stbuf) != 0)
    +
    196 fuse_reply_err(req, ENOENT);
    +
    197 else
    +
    198 fuse_reply_attr(req, &stbuf, NO_TIMEOUT);
    +
    199}
    +
    200
    +
    201struct dirbuf {
    +
    202 char *p;
    +
    203 size_t size;
    +
    204};
    +
    205
    +
    206static void dirbuf_add(fuse_req_t req, struct dirbuf *b, const char *name,
    +
    207 fuse_ino_t ino)
    +
    208{
    +
    209 struct stat stbuf;
    +
    210 size_t oldsize = b->size;
    +
    211 b->size += fuse_add_direntry(req, NULL, 0, name, NULL, 0);
    +
    212 b->p = (char *)realloc(b->p, b->size);
    +
    213 memset(&stbuf, 0, sizeof(stbuf));
    +
    214 stbuf.st_ino = ino;
    +
    215 fuse_add_direntry(req, b->p + oldsize, b->size - oldsize, name, &stbuf,
    +
    216 b->size);
    +
    217}
    +
    218
    +
    219#define min(x, y) ((x) < (y) ? (x) : (y))
    +
    220
    +
    221static int reply_buf_limited(fuse_req_t req, const char *buf, size_t bufsize,
    +
    222 off_t off, size_t maxsize)
    +
    223{
    +
    224 if (off < bufsize)
    +
    225 return fuse_reply_buf(req, buf + off,
    +
    226 min(bufsize - off, maxsize));
    +
    227 else
    +
    228 return fuse_reply_buf(req, NULL, 0);
    +
    229}
    +
    230
    +
    231static void tfs_readdir(fuse_req_t req, fuse_ino_t ino, size_t size, off_t off,
    +
    232 struct fuse_file_info *fi)
    +
    233{
    +
    234 (void)fi;
    +
    235
    +
    236 if (ino != FUSE_ROOT_ID)
    +
    237 fuse_reply_err(req, ENOTDIR);
    +
    238 else {
    +
    239 struct dirbuf b;
    +
    240
    +
    241 memset(&b, 0, sizeof(b));
    +
    242 dirbuf_add(req, &b, FILE_NAME, FILE_INO);
    +
    243 reply_buf_limited(req, b.p, b.size, off, size);
    +
    244 free(b.p);
    +
    245 }
    +
    246}
    +
    247
    +
    248static void tfs_open(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
    +
    249{
    +
    250 /* Make cache persistent even if file is closed,
    +
    251 this makes it easier to see the effects */
    +
    252 fi->keep_cache = 1;
    +
    253
    +
    254 if (ino == FUSE_ROOT_ID)
    +
    255 fuse_reply_err(req, EISDIR);
    +
    256 else if ((fi->flags & O_ACCMODE) != O_RDONLY)
    +
    257 fuse_reply_err(req, EACCES);
    +
    258 else if (ino == FILE_INO) {
    +
    259 fuse_reply_open(req, fi);
    +
    260 pthread_mutex_lock(&lock);
    +
    261 open_cnt++;
    +
    262 pthread_mutex_unlock(&lock);
    +
    263 } else {
    +
    264 // This should not happen
    +
    265 fprintf(stderr, "Got open for non-existing inode!\n");
    +
    266 fuse_reply_err(req, ENOENT);
    +
    267 }
    +
    268}
    +
    269
    +
    270static void tfs_read(fuse_req_t req, fuse_ino_t ino, size_t size, off_t off,
    +
    271 struct fuse_file_info *fi)
    +
    272{
    +
    273 (void)fi;
    +
    274
    +
    275 assert(ino == FILE_INO);
    +
    276 reply_buf_limited(req, file_contents, file_size, off, size);
    +
    277}
    +
    278
    +
    279static void tfs_retrieve_reply(fuse_req_t req, void *cookie, fuse_ino_t ino,
    +
    280 off_t offset, struct fuse_bufvec *data)
    +
    281{
    +
    282 struct fuse_bufvec bufv;
    +
    283 char buf[MAX_STR_LEN];
    +
    284 char *expected;
    +
    285 ssize_t ret;
    +
    286
    +
    287 assert(ino == FILE_INO);
    +
    288 assert(offset == 0);
    +
    289 expected = (char *)cookie;
    +
    290
    +
    291 bufv.count = 1;
    +
    292 bufv.idx = 0;
    +
    293 bufv.off = 0;
    +
    294 bufv.buf[0].size = MAX_STR_LEN;
    +
    295 bufv.buf[0].mem = buf;
    +
    296 bufv.buf[0].flags = 0;
    +
    297
    +
    298 ret = fuse_buf_copy(&bufv, data, 0);
    +
    299 assert(ret > 0);
    +
    300 assert(strncmp(buf, expected, ret) == 0);
    +
    301 free(expected);
    +
    302 retrieve_status = 2;
    +
    303 fuse_reply_none(req);
    +
    304}
    +
    305
    +
    306static void tfs_destroy(void *userdata)
    +
    307{
    +
    308 (void)userdata;
    +
    309
    +
    310 is_umount = true;
    +
    311
    +
    312 pthread_join(updater, NULL);
    +
    313}
    +
    314
    +
    315static const struct fuse_lowlevel_ops tfs_oper = {
    +
    316 .init = tfs_init,
    +
    317 .lookup = tfs_lookup,
    +
    318 .getattr = tfs_getattr,
    +
    319 .readdir = tfs_readdir,
    +
    320 .open = tfs_open,
    +
    321 .read = tfs_read,
    +
    322 .forget = tfs_forget,
    +
    323 .retrieve_reply = tfs_retrieve_reply,
    +
    324 .destroy = tfs_destroy,
    +
    325};
    +
    326
    +
    327static void update_fs(void)
    +
    328{
    +
    329 struct tm tmbuf;
    +
    330 struct tm *now;
    +
    331 time_t t;
    +
    332 t = time(NULL);
    +
    333 now = localtime_r(&t, &tmbuf);
    +
    334 assert(now != NULL);
    +
    335
    +
    336 file_size = strftime(file_contents, MAX_STR_LEN,
    +
    337 "The current time is %H:%M:%S\n", now);
    +
    338 assert(file_size != 0);
    +
    339}
    +
    340
    +
    341static void *update_fs_loop(void *data)
    +
    342{
    +
    343 struct fuse_session *se = (struct fuse_session *)data;
    +
    344 struct fuse_bufvec bufv;
    +
    345 int ret;
    +
    346
    +
    347 while (!is_umount) {
    +
    348 update_fs();
    +
    349 pthread_mutex_lock(&lock);
    +
    350 if (!options.no_notify && open_cnt && lookup_cnt) {
    +
    351 /* Only send notification if the kernel
    +
    352 is aware of the inode */
    +
    353 bufv.count = 1;
    +
    354 bufv.idx = 0;
    +
    355 bufv.off = 0;
    +
    356 bufv.buf[0].size = file_size;
    +
    357 bufv.buf[0].mem = file_contents;
    +
    358 bufv.buf[0].flags = 0;
    +
    359
    +
    360 /*
    +
    361 * Some errors (ENOENT, EBADF, ENODEV) have to be accepted as they
    +
    362 * might come up during umount, when kernel side already releases
    +
    363 * all inodes, but does not send FUSE_DESTROY yet.
    +
    364 */
    +
    365
    +
    366 ret = fuse_lowlevel_notify_store(se, FILE_INO, 0, &bufv,
    +
    367 0);
    +
    368 if ((ret != 0 && !is_umount) && ret != -ENOENT &&
    +
    369 ret != -EBADF && ret != -ENODEV) {
    +
    370 fprintf(stderr,
    +
    371 "ERROR: fuse_lowlevel_notify_store() failed with %s (%d)\n",
    +
    372 strerror(-ret), -ret);
    +
    373 abort();
    +
    374 }
    +
    375
    +
    376 /* To make sure that everything worked correctly, ask the
    +
    377 kernel to send us back the stored data */
    + +
    379 se, FILE_INO, MAX_STR_LEN, 0,
    +
    380 (void *)strdup(file_contents));
    +
    381 assert((ret == 0 || is_umount) || ret == -ENOENT ||
    +
    382 ret == -EBADF || ret != -ENODEV);
    +
    383 if (retrieve_status == 0)
    +
    384 retrieve_status = 1;
    +
    385 }
    +
    386 pthread_mutex_unlock(&lock);
    +
    387 sleep(options.update_interval);
    +
    388 }
    +
    389 return NULL;
    +
    390}
    +
    391
    +
    392static void show_help(const char *progname)
    +
    393{
    +
    394 printf("usage: %s [options] <mountpoint>\n\n", progname);
    +
    395 printf("File-system specific options:\n"
    +
    396 " --update-interval=<secs> Update-rate of file system contents\n"
    +
    397 " --no-notify Disable kernel notifications\n"
    +
    398 "\n");
    +
    399}
    +
    400
    +
    401int main(int argc, char *argv[])
    +
    402{
    +
    403 struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
    +
    404 struct fuse_session *se;
    +
    405 struct fuse_cmdline_opts opts;
    +
    406 struct fuse_loop_config *config;
    +
    407 int ret = -1;
    +
    408
    +
    409 if (fuse_opt_parse(&args, &options, option_spec, NULL) == -1)
    +
    410 return 1;
    +
    411
    +
    412 if (fuse_parse_cmdline(&args, &opts) != 0)
    +
    413 return 1;
    +
    414 if (opts.show_help) {
    +
    415 show_help(argv[0]);
    + + +
    418 ret = 0;
    +
    419 goto err_out1;
    +
    420 } else if (opts.show_version) {
    +
    421 printf("FUSE library version %s\n", fuse_pkgversion());
    + +
    423 ret = 0;
    +
    424 goto err_out1;
    +
    425 }
    +
    426
    +
    427 /* Initial contents */
    +
    428 update_fs();
    +
    429
    +
    430 se = fuse_session_new(&args, &tfs_oper, sizeof(tfs_oper), NULL);
    +
    431 if (se == NULL)
    +
    432 goto err_out1;
    +
    433
    +
    434 if (fuse_set_signal_handlers(se) != 0)
    +
    435 goto err_out2;
    +
    436
    +
    437 if (fuse_session_mount(se, opts.mountpoint) != 0)
    +
    438 goto err_out3;
    +
    439
    +
    440 fuse_daemonize(opts.foreground);
    +
    441
    +
    442 /* Start thread to update file contents */
    +
    443 ret = pthread_create(&updater, NULL, update_fs_loop, (void *)se);
    +
    444 if (ret != 0) {
    +
    445 fprintf(stderr, "pthread_create failed with %s\n",
    +
    446 strerror(ret));
    +
    447 goto err_out3;
    +
    448 }
    +
    449
    +
    450 /* Block until ctrl+c or fusermount -u */
    +
    451 if (opts.singlethread)
    +
    452 ret = fuse_session_loop(se);
    +
    453 else {
    +
    454 config = fuse_loop_cfg_create();
    +
    455 fuse_loop_cfg_set_clone_fd(config, opts.clone_fd);
    +
    456 fuse_loop_cfg_set_max_threads(config, opts.max_threads);
    +
    457 ret = fuse_session_loop_mt(se, config);
    +
    458 fuse_loop_cfg_destroy(config);
    +
    459 config = NULL;
    +
    460 }
    +
    461
    +
    462 assert(retrieve_status != 1);
    + +
    464err_out3:
    + +
    466err_out2:
    + +
    468err_out1:
    +
    469 free(opts.mountpoint);
    +
    470 fuse_opt_free_args(&args);
    +
    471
    +
    472 return ret ? 1 : 0;
    +
    473}
    +
    474
    +
    int fuse_set_signal_handlers(struct fuse_session *se)
    +
    ssize_t fuse_buf_copy(struct fuse_bufvec *dst, struct fuse_bufvec *src, enum fuse_buf_copy_flags flags)
    Definition buffer.c:284
    +
    const char * fuse_pkgversion(void)
    Definition fuse.c:5274
    +
    void fuse_remove_signal_handlers(struct fuse_session *se)
    +
    int fuse_daemonize(int foreground)
    Definition helper.c:253
    + +
    void fuse_session_destroy(struct fuse_session *se)
    +
    int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi)
    +
    int fuse_reply_err(fuse_req_t req, int err)
    +
    int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
    +
    struct fuse_req * fuse_req_t
    +
    int fuse_lowlevel_notify_retrieve(struct fuse_session *se, fuse_ino_t ino, size_t size, off_t offset, void *cookie)
    +
    int fuse_session_loop(struct fuse_session *se)
    Definition fuse_loop.c:19
    +
    int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
    +
    void fuse_session_unmount(struct fuse_session *se)
    +
    void fuse_cmdline_help(void)
    Definition helper.c:130
    +
    void fuse_reply_none(fuse_req_t req)
    +
    void fuse_lowlevel_help(void)
    +
    int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
    +
    void fuse_lowlevel_version(void)
    +
    uint64_t fuse_ino_t
    +
    size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct stat *stbuf, off_t off)
    +
    int fuse_reply_attr(fuse_req_t req, const struct stat *attr, double attr_timeout)
    +
    int fuse_lowlevel_notify_store(struct fuse_session *se, fuse_ino_t ino, off_t offset, struct fuse_bufvec *bufv, enum fuse_buf_copy_flags flags)
    +
    void fuse_opt_free_args(struct fuse_args *args)
    Definition fuse_opt.c:34
    +
    int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
    Definition fuse_opt.c:398
    +
    #define FUSE_ARGS_INIT(argc, argv)
    Definition fuse_opt.h:123
    +
    #define FUSE_OPT_END
    Definition fuse_opt.h:104
    + + +
    char ** argv
    Definition fuse_opt.h:114
    +
    enum fuse_buf_flags flags
    + +
    struct fuse_buf buf[1]
    + + +
    uint32_t no_interrupt
    +
    +
    fuse_ino_t ino
    + +
    uint32_t keep_cache
    Definition fuse_common.h:69
    + + + + +
    + + +
    + + diff --git a/doc/html/null_8c.html b/doc/html/null_8c.html new file mode 100644 index 0000000..f1de2ea --- /dev/null +++ b/doc/html/null_8c.html @@ -0,0 +1,784 @@ + + + + + + + +libfuse: example/null.c File Reference + + + + + + + +
    +
    + + + + + + +
    +
    libfuse +
    +
    +
    + + + + + + + + +
    +
    +
    +
    null.c File Reference
    +
    +
    +
    #include <fuse.h>
    +#include <fuse_lowlevel.h>
    +#include <stdio.h>
    +#include <stdlib.h>
    +#include <string.h>
    +#include <unistd.h>
    +#include <time.h>
    +#include <errno.h>
    +
    +

    Go to the source code of this file.

    +

    Detailed Description

    +

    This "filesystem" provides only a single file. The mountpoint needs to be a file rather than a directory. All writes to the file will be discarded, and reading the file always returns \0.

    +

    Compile with:

    gcc -Wall null.c `pkg-config fuse3 --cflags --libs` -o null
    +

    +Source code

    +
    /*
    +
    FUSE: Filesystem in Userspace
    +
    Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
    +
    Copyright (C) 2011 Sebastian Pipping <sebastian@pipping.org>
    +
    +
    This program can be distributed under the terms of the GNU GPLv2.
    +
    See the file GPL2.txt.
    +
    */
    +
    +
    +
    #define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 18)
    +
    +
    #define _GNU_SOURCE
    +
    +
    #include <fuse.h>
    +
    +
    #ifdef HAVE_LIBULOCKMGR
    +
    #include <ulockmgr.h>
    +
    #endif
    +
    +
    #include <stdio.h>
    +
    #include <stdlib.h>
    +
    #include <string.h>
    +
    #include <unistd.h>
    +
    #include <fcntl.h>
    +
    #include <sys/stat.h>
    +
    #include <dirent.h>
    +
    #include <errno.h>
    +
    #include <sys/time.h>
    +
    #ifdef HAVE_SETXATTR
    +
    #include <sys/xattr.h>
    +
    #endif
    +
    #include <sys/file.h> /* flock(2) */
    +
    +
    #include "passthrough_helpers.h"
    +
    +
    static void *xmp_init(struct fuse_conn_info *conn,
    +
    struct fuse_config *cfg)
    +
    {
    +
    (void) conn;
    +
    cfg->use_ino = 1;
    +
    cfg->nullpath_ok = 1;
    +
    +
    /* parallel_direct_writes feature depends on direct_io features.
    +
    To make parallel_direct_writes valid, need either set cfg->direct_io
    +
    in current function (recommended in high level API) or set fi->direct_io
    +
    in xmp_create() or xmp_open(). */
    +
    // cfg->direct_io = 1;
    + +
    +
    /* Pick up changes from lower filesystem right away. This is
    +
    also necessary for better hardlink support. When the kernel
    +
    calls the unlink() handler, it does not know the inode of
    +
    the to-be-removed entry and can therefore not invalidate
    +
    the cache of the associated inode - resulting in an
    +
    incorrect st_nlink value being reported for any remaining
    +
    hardlinks to this inode. */
    +
    cfg->entry_timeout = 0;
    +
    cfg->attr_timeout = 0;
    +
    cfg->negative_timeout = 0;
    +
    +
    return NULL;
    +
    }
    +
    +
    static int xmp_getattr(const char *path, struct stat *stbuf,
    +
    struct fuse_file_info *fi)
    +
    {
    +
    int res;
    +
    +
    (void) path;
    +
    +
    if(fi)
    +
    res = fstat(fi->fh, stbuf);
    +
    else
    +
    res = lstat(path, stbuf);
    +
    if (res == -1)
    +
    return -errno;
    +
    +
    return 0;
    +
    }
    +
    +
    static int xmp_access(const char *path, int mask)
    +
    {
    +
    int res;
    +
    +
    res = access(path, mask);
    +
    if (res == -1)
    +
    return -errno;
    +
    +
    return 0;
    +
    }
    +
    +
    static int xmp_readlink(const char *path, char *buf, size_t size)
    +
    {
    +
    int res;
    +
    +
    res = readlink(path, buf, size - 1);
    +
    if (res == -1)
    +
    return -errno;
    +
    +
    buf[res] = '\0';
    +
    return 0;
    +
    }
    +
    +
    struct xmp_dirp {
    +
    DIR *dp;
    +
    struct dirent *entry;
    +
    off_t offset;
    +
    };
    +
    +
    static int xmp_opendir(const char *path, struct fuse_file_info *fi)
    +
    {
    +
    int res;
    +
    struct xmp_dirp *d = malloc(sizeof(struct xmp_dirp));
    +
    if (d == NULL)
    +
    return -ENOMEM;
    +
    +
    d->dp = opendir(path);
    +
    if (d->dp == NULL) {
    +
    res = -errno;
    +
    free(d);
    +
    return res;
    +
    }
    +
    d->offset = 0;
    +
    d->entry = NULL;
    +
    +
    fi->fh = (unsigned long) d;
    +
    return 0;
    +
    }
    +
    +
    static inline struct xmp_dirp *get_dirp(struct fuse_file_info *fi)
    +
    {
    +
    return (struct xmp_dirp *) (uintptr_t) fi->fh;
    +
    }
    +
    +
    static int xmp_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
    +
    off_t offset, struct fuse_file_info *fi,
    +
    enum fuse_readdir_flags flags)
    +
    {
    +
    struct xmp_dirp *d = get_dirp(fi);
    +
    +
    (void) path;
    +
    if (offset != d->offset) {
    +
    #ifndef __FreeBSD__
    +
    seekdir(d->dp, offset);
    +
    #else
    +
    /* Subtract the one that we add when calling
    +
    telldir() below */
    +
    seekdir(d->dp, offset-1);
    +
    #endif
    +
    d->entry = NULL;
    +
    d->offset = offset;
    +
    }
    +
    while (1) {
    +
    struct stat st;
    +
    off_t nextoff;
    + +
    +
    if (!d->entry) {
    +
    d->entry = readdir(d->dp);
    +
    if (!d->entry)
    +
    break;
    +
    }
    +
    #ifdef HAVE_FSTATAT
    +
    if (flags & FUSE_READDIR_PLUS) {
    +
    int res;
    +
    +
    res = fstatat(dirfd(d->dp), d->entry->d_name, &st,
    +
    AT_SYMLINK_NOFOLLOW);
    +
    if (res != -1)
    +
    fill_flags |= FUSE_FILL_DIR_PLUS;
    +
    }
    +
    #endif
    +
    if (!(fill_flags & FUSE_FILL_DIR_PLUS)) {
    +
    memset(&st, 0, sizeof(st));
    +
    st.st_ino = d->entry->d_ino;
    +
    st.st_mode = d->entry->d_type << 12;
    +
    }
    +
    nextoff = telldir(d->dp);
    +
    #ifdef __FreeBSD__
    +
    /* Under FreeBSD, telldir() may return 0 the first time
    +
    it is called. But for libfuse, an offset of zero
    +
    means that offsets are not supported, so we shift
    +
    everything by one. */
    +
    nextoff++;
    +
    #endif
    +
    if (filler(buf, d->entry->d_name, &st, nextoff, fill_flags))
    +
    break;
    +
    +
    d->entry = NULL;
    +
    d->offset = nextoff;
    +
    }
    +
    +
    return 0;
    +
    }
    +
    +
    static int xmp_releasedir(const char *path, struct fuse_file_info *fi)
    +
    {
    +
    struct xmp_dirp *d = get_dirp(fi);
    +
    (void) path;
    +
    closedir(d->dp);
    +
    free(d);
    +
    return 0;
    +
    }
    +
    +
    static int xmp_mknod(const char *path, mode_t mode, dev_t rdev)
    +
    {
    +
    int res;
    +
    +
    if (S_ISFIFO(mode))
    +
    res = mkfifo(path, mode);
    +
    else
    +
    res = mknod(path, mode, rdev);
    +
    if (res == -1)
    +
    return -errno;
    +
    +
    return 0;
    +
    }
    +
    +
    static int xmp_mkdir(const char *path, mode_t mode)
    +
    {
    +
    int res;
    +
    +
    res = mkdir(path, mode);
    +
    if (res == -1)
    +
    return -errno;
    +
    +
    return 0;
    +
    }
    +
    +
    static int xmp_unlink(const char *path)
    +
    {
    +
    int res;
    +
    +
    res = unlink(path);
    +
    if (res == -1)
    +
    return -errno;
    +
    +
    return 0;
    +
    }
    +
    +
    static int xmp_rmdir(const char *path)
    +
    {
    +
    int res;
    +
    +
    res = rmdir(path);
    +
    if (res == -1)
    +
    return -errno;
    +
    +
    return 0;
    +
    }
    +
    +
    static int xmp_symlink(const char *from, const char *to)
    +
    {
    +
    int res;
    +
    +
    res = symlink(from, to);
    +
    if (res == -1)
    +
    return -errno;
    +
    +
    return 0;
    +
    }
    +
    +
    static int xmp_rename(const char *from, const char *to, unsigned int flags)
    +
    {
    +
    int res;
    +
    +
    /* When we have renameat2() in libc, then we can implement flags */
    +
    if (flags)
    +
    return -EINVAL;
    +
    +
    res = rename(from, to);
    +
    if (res == -1)
    +
    return -errno;
    +
    +
    return 0;
    +
    }
    +
    +
    static int xmp_link(const char *from, const char *to)
    +
    {
    +
    int res;
    +
    +
    res = link(from, to);
    +
    if (res == -1)
    +
    return -errno;
    +
    +
    return 0;
    +
    }
    +
    +
    static int xmp_chmod(const char *path, mode_t mode,
    +
    struct fuse_file_info *fi)
    +
    {
    +
    int res;
    +
    +
    if(fi)
    +
    res = fchmod(fi->fh, mode);
    +
    else
    +
    res = chmod(path, mode);
    +
    if (res == -1)
    +
    return -errno;
    +
    +
    return 0;
    +
    }
    +
    +
    static int xmp_chown(const char *path, uid_t uid, gid_t gid,
    +
    struct fuse_file_info *fi)
    +
    {
    +
    int res;
    +
    +
    if (fi)
    +
    res = fchown(fi->fh, uid, gid);
    +
    else
    +
    res = lchown(path, uid, gid);
    +
    if (res == -1)
    +
    return -errno;
    +
    +
    return 0;
    +
    }
    +
    +
    static int xmp_truncate(const char *path, off_t size,
    +
    struct fuse_file_info *fi)
    +
    {
    +
    int res;
    +
    +
    if(fi)
    +
    res = ftruncate(fi->fh, size);
    +
    else
    +
    res = truncate(path, size);
    +
    +
    if (res == -1)
    +
    return -errno;
    +
    +
    return 0;
    +
    }
    +
    +
    #ifdef HAVE_UTIMENSAT
    +
    static int xmp_utimens(const char *path, const struct timespec ts[2],
    +
    struct fuse_file_info *fi)
    +
    {
    +
    int res;
    +
    +
    /* don't use utime/utimes since they follow symlinks */
    +
    if (fi)
    +
    res = futimens(fi->fh, ts);
    +
    else
    +
    res = utimensat(0, path, ts, AT_SYMLINK_NOFOLLOW);
    +
    if (res == -1)
    +
    return -errno;
    +
    +
    return 0;
    +
    }
    +
    #endif
    +
    +
    static int xmp_create(const char *path, mode_t mode, struct fuse_file_info *fi)
    +
    {
    +
    int fd;
    +
    +
    fd = open(path, fi->flags, mode);
    +
    if (fd == -1)
    +
    return -errno;
    +
    +
    fi->fh = fd;
    +
    return 0;
    +
    }
    +
    +
    static int xmp_open(const char *path, struct fuse_file_info *fi)
    +
    {
    +
    int fd;
    +
    +
    fd = open(path, fi->flags);
    +
    if (fd == -1)
    +
    return -errno;
    +
    +
    /* Enable direct_io when open has flags O_DIRECT to enjoy the feature
    +
    parallel_direct_writes (i.e., to get a shared lock, not exclusive lock,
    +
    for writes to the same file). */
    +
    if (fi->flags & O_DIRECT) {
    +
    fi->direct_io = 1;
    + +
    }
    +
    +
    fi->fh = fd;
    +
    return 0;
    +
    }
    +
    +
    static int xmp_read(const char *path, char *buf, size_t size, off_t offset,
    +
    struct fuse_file_info *fi)
    +
    {
    +
    int res;
    +
    +
    (void) path;
    +
    res = pread(fi->fh, buf, size, offset);
    +
    if (res == -1)
    +
    res = -errno;
    +
    +
    return res;
    +
    }
    +
    +
    static int xmp_read_buf(const char *path, struct fuse_bufvec **bufp,
    +
    size_t size, off_t offset, struct fuse_file_info *fi)
    +
    {
    +
    struct fuse_bufvec *src;
    +
    +
    (void) path;
    +
    +
    src = malloc(sizeof(struct fuse_bufvec));
    +
    if (src == NULL)
    +
    return -ENOMEM;
    +
    +
    *src = FUSE_BUFVEC_INIT(size);
    +
    + +
    src->buf[0].fd = fi->fh;
    +
    src->buf[0].pos = offset;
    +
    +
    *bufp = src;
    +
    +
    return 0;
    +
    }
    +
    +
    static int xmp_write(const char *path, const char *buf, size_t size,
    +
    off_t offset, struct fuse_file_info *fi)
    +
    {
    +
    int res;
    +
    +
    (void) path;
    +
    res = pwrite(fi->fh, buf, size, offset);
    +
    if (res == -1)
    +
    res = -errno;
    +
    +
    return res;
    +
    }
    +
    +
    static int xmp_write_buf(const char *path, struct fuse_bufvec *buf,
    +
    off_t offset, struct fuse_file_info *fi)
    +
    {
    +
    struct fuse_bufvec dst = FUSE_BUFVEC_INIT(fuse_buf_size(buf));
    +
    +
    (void) path;
    +
    + +
    dst.buf[0].fd = fi->fh;
    +
    dst.buf[0].pos = offset;
    +
    + +
    }
    +
    +
    static int xmp_statfs(const char *path, struct statvfs *stbuf)
    +
    {
    +
    int res;
    +
    +
    res = statvfs(path, stbuf);
    +
    if (res == -1)
    +
    return -errno;
    +
    +
    return 0;
    +
    }
    +
    +
    static int xmp_flush(const char *path, struct fuse_file_info *fi)
    +
    {
    +
    int res;
    +
    +
    (void) path;
    +
    /* This is called from every close on an open file, so call the
    +
    close on the underlying filesystem. But since flush may be
    +
    called multiple times for an open file, this must not really
    +
    close the file. This is important if used on a network
    +
    filesystem like NFS which flush the data/metadata on close() */
    +
    res = close(dup(fi->fh));
    +
    if (res == -1)
    +
    return -errno;
    +
    +
    return 0;
    +
    }
    +
    +
    static int xmp_release(const char *path, struct fuse_file_info *fi)
    +
    {
    +
    (void) path;
    +
    close(fi->fh);
    +
    +
    return 0;
    +
    }
    +
    +
    static int xmp_fsync(const char *path, int isdatasync,
    +
    struct fuse_file_info *fi)
    +
    {
    +
    int res;
    +
    (void) path;
    +
    +
    #ifndef HAVE_FDATASYNC
    +
    (void) isdatasync;
    +
    #else
    +
    if (isdatasync)
    +
    res = fdatasync(fi->fh);
    +
    else
    +
    #endif
    +
    res = fsync(fi->fh);
    +
    if (res == -1)
    +
    return -errno;
    +
    +
    return 0;
    +
    }
    +
    +
    static int xmp_fallocate(const char *path, int mode,
    +
    off_t offset, off_t length, struct fuse_file_info *fi)
    +
    {
    +
    (void) path;
    +
    +
    return do_fallocate(fi->fh, mode, offset, length);
    +
    }
    +
    +
    #ifdef HAVE_SETXATTR
    +
    /* xattr operations are optional and can safely be left unimplemented */
    +
    static int xmp_setxattr(const char *path, const char *name, const char *value,
    +
    size_t size, int flags)
    +
    {
    +
    int res = lsetxattr(path, name, value, size, flags);
    +
    if (res == -1)
    +
    return -errno;
    +
    return 0;
    +
    }
    +
    +
    static int xmp_getxattr(const char *path, const char *name, char *value,
    +
    size_t size)
    +
    {
    +
    int res = lgetxattr(path, name, value, size);
    +
    if (res == -1)
    +
    return -errno;
    +
    return res;
    +
    }
    +
    +
    static int xmp_listxattr(const char *path, char *list, size_t size)
    +
    {
    +
    int res = llistxattr(path, list, size);
    +
    if (res == -1)
    +
    return -errno;
    +
    return res;
    +
    }
    +
    +
    static int xmp_removexattr(const char *path, const char *name)
    +
    {
    +
    int res = lremovexattr(path, name);
    +
    if (res == -1)
    +
    return -errno;
    +
    return 0;
    +
    }
    +
    #endif /* HAVE_SETXATTR */
    +
    +
    #ifdef HAVE_LIBULOCKMGR
    +
    static int xmp_lock(const char *path, struct fuse_file_info *fi, int cmd,
    +
    struct flock *lock)
    +
    {
    +
    (void) path;
    +
    +
    return ulockmgr_op(fi->fh, cmd, lock, &fi->lock_owner,
    +
    sizeof(fi->lock_owner));
    +
    }
    +
    #endif
    +
    +
    static int xmp_flock(const char *path, struct fuse_file_info *fi, int op)
    +
    {
    +
    int res;
    +
    (void) path;
    +
    +
    res = flock(fi->fh, op);
    +
    if (res == -1)
    +
    return -errno;
    +
    +
    return 0;
    +
    }
    +
    +
    #ifdef HAVE_COPY_FILE_RANGE
    +
    static ssize_t xmp_copy_file_range(const char *path_in,
    +
    struct fuse_file_info *fi_in,
    +
    off_t off_in, const char *path_out,
    +
    struct fuse_file_info *fi_out,
    +
    off_t off_out, size_t len, int flags)
    +
    {
    +
    ssize_t res;
    +
    (void) path_in;
    +
    (void) path_out;
    +
    +
    res = copy_file_range(fi_in->fh, &off_in, fi_out->fh, &off_out, len,
    +
    flags);
    +
    if (res == -1)
    +
    return -errno;
    +
    +
    return res;
    +
    }
    +
    #endif
    +
    +
    static off_t xmp_lseek(const char *path, off_t off, int whence, struct fuse_file_info *fi)
    +
    {
    +
    off_t res;
    +
    (void) path;
    +
    +
    res = lseek(fi->fh, off, whence);
    +
    if (res == -1)
    +
    return -errno;
    +
    +
    return res;
    +
    }
    +
    +
    #ifdef HAVE_STATX
    +
    static int xmp_statx(const char *path, int flags, int mask, struct statx *stxbuf,
    +
    struct fuse_file_info *fi)
    +
    {
    +
    int fd = -1;
    +
    int res;
    +
    +
    if (fi)
    +
    fd = fi->fh;
    +
    +
    res = statx(fd, path, flags | AT_SYMLINK_NOFOLLOW, mask, stxbuf);
    +
    if (res == -1)
    +
    return -errno;
    +
    +
    return 0;
    +
    }
    +
    #endif
    +
    +
    static const struct fuse_operations xmp_oper = {
    +
    .init = xmp_init,
    +
    .getattr = xmp_getattr,
    +
    .access = xmp_access,
    +
    .readlink = xmp_readlink,
    +
    .opendir = xmp_opendir,
    +
    .readdir = xmp_readdir,
    +
    .releasedir = xmp_releasedir,
    +
    .mknod = xmp_mknod,
    +
    .mkdir = xmp_mkdir,
    +
    .symlink = xmp_symlink,
    +
    .unlink = xmp_unlink,
    +
    .rmdir = xmp_rmdir,
    +
    .rename = xmp_rename,
    +
    .link = xmp_link,
    +
    .chmod = xmp_chmod,
    +
    .chown = xmp_chown,
    +
    .truncate = xmp_truncate,
    +
    #ifdef HAVE_UTIMENSAT
    +
    .utimens = xmp_utimens,
    +
    #endif
    +
    .create = xmp_create,
    +
    .open = xmp_open,
    +
    .read = xmp_read,
    +
    .read_buf = xmp_read_buf,
    +
    .write = xmp_write,
    +
    .write_buf = xmp_write_buf,
    +
    .statfs = xmp_statfs,
    +
    .flush = xmp_flush,
    +
    .release = xmp_release,
    +
    .fsync = xmp_fsync,
    +
    .fallocate = xmp_fallocate,
    +
    #ifdef HAVE_SETXATTR
    +
    .setxattr = xmp_setxattr,
    +
    .getxattr = xmp_getxattr,
    +
    .listxattr = xmp_listxattr,
    +
    .removexattr = xmp_removexattr,
    +
    #endif
    +
    #ifdef HAVE_LIBULOCKMGR
    +
    .lock = xmp_lock,
    +
    #endif
    +
    .flock = xmp_flock,
    +
    #ifdef HAVE_COPY_FILE_RANGE
    +
    .copy_file_range = xmp_copy_file_range,
    +
    #endif
    +
    .lseek = xmp_lseek,
    +
    #ifdef HAVE_STATX
    +
    .statx = xmp_statx,
    +
    #endif
    +
    };
    +
    +
    int main(int argc, char *argv[])
    +
    {
    +
    umask(0);
    +
    return fuse_main(argc, argv, &xmp_oper, NULL);
    +
    }
    + +
    int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
    Definition fuse.h:93
    +
    fuse_fill_dir_flags
    Definition fuse.h:61
    +
    @ FUSE_FILL_DIR_DEFAULTS
    Definition fuse.h:74
    +
    fuse_readdir_flags
    Definition fuse.h:45
    +
    size_t fuse_buf_size(const struct fuse_bufvec *bufv)
    Definition buffer.c:22
    +
    @ FUSE_BUF_FD_SEEK
    +
    @ FUSE_BUF_IS_FD
    +
    ssize_t fuse_buf_copy(struct fuse_bufvec *dst, struct fuse_bufvec *src, enum fuse_buf_copy_flags flags)
    Definition buffer.c:284
    +
    @ FUSE_BUF_SPLICE_NONBLOCK
    +
    enum fuse_buf_flags flags
    + +
    off_t pos
    + + +
    struct fuse_buf buf[1]
    + +
    int32_t nullpath_ok
    Definition fuse.h:279
    +
    int32_t parallel_direct_writes
    Definition fuse.h:318
    +
    int32_t use_ino
    Definition fuse.h:204
    +
    double entry_timeout
    Definition fuse.h:133
    +
    double negative_timeout
    Definition fuse.h:143
    +
    double attr_timeout
    Definition fuse.h:149
    + + +
    uint64_t lock_owner
    + +
    uint32_t parallel_direct_writes
    Definition fuse_common.h:97
    +
    uint32_t direct_io
    Definition fuse_common.h:63
    + + +
    +

    Definition in file null.c.

    +
    + + +
    + + diff --git a/doc/html/null_8c_source.html b/doc/html/null_8c_source.html new file mode 100644 index 0000000..895ccec --- /dev/null +++ b/doc/html/null_8c_source.html @@ -0,0 +1,198 @@ + + + + + + + +libfuse: example/null.c Source File + + + + + + + +
    +
    + + + + + + +
    +
    libfuse +
    +
    +
    + + + + + + + + +
    +
    +
    +
    null.c
    +
    +
    +Go to the documentation of this file.
    1/*
    +
    2 FUSE: Filesystem in Userspace
    +
    3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
    +
    4
    +
    5 This program can be distributed under the terms of the GNU GPLv2.
    +
    6 See the file GPL2.txt.
    +
    7*/
    +
    8
    +
    23
    +
    24
    +
    25#define FUSE_USE_VERSION 31
    +
    26
    +
    27#include <fuse.h>
    +
    28#include <fuse_lowlevel.h>
    +
    29#include <stdio.h>
    +
    30#include <stdlib.h>
    +
    31#include <string.h>
    +
    32#include <unistd.h>
    +
    33#include <time.h>
    +
    34#include <errno.h>
    +
    35
    +
    36static int null_getattr(const char *path, struct stat *stbuf,
    +
    37 struct fuse_file_info *fi)
    +
    38{
    +
    39 (void) fi;
    +
    40
    +
    41 if(strcmp(path, "/") != 0)
    +
    42 return -ENOENT;
    +
    43
    +
    44 stbuf->st_mode = S_IFREG | 0644;
    +
    45 stbuf->st_nlink = 1;
    +
    46 stbuf->st_uid = getuid();
    +
    47 stbuf->st_gid = getgid();
    +
    48 stbuf->st_size = (1ULL << 32); /* 4G */
    +
    49 stbuf->st_blocks = 0;
    +
    50 stbuf->st_atime = stbuf->st_mtime = stbuf->st_ctime = time(NULL);
    +
    51
    +
    52 return 0;
    +
    53}
    +
    54
    +
    55static int null_truncate(const char *path, off_t size,
    +
    56 struct fuse_file_info *fi)
    +
    57{
    +
    58 (void) size;
    +
    59 (void) fi;
    +
    60
    +
    61 if(strcmp(path, "/") != 0)
    +
    62 return -ENOENT;
    +
    63
    +
    64 return 0;
    +
    65}
    +
    66
    +
    67static int null_open(const char *path, struct fuse_file_info *fi)
    +
    68{
    +
    69 (void) fi;
    +
    70
    +
    71 if(strcmp(path, "/") != 0)
    +
    72 return -ENOENT;
    +
    73
    +
    74 return 0;
    +
    75}
    +
    76
    +
    77static int null_read(const char *path, char *buf, size_t size,
    +
    78 off_t offset, struct fuse_file_info *fi)
    +
    79{
    +
    80 (void) buf;
    +
    81 (void) offset;
    +
    82 (void) fi;
    +
    83
    +
    84 if(strcmp(path, "/") != 0)
    +
    85 return -ENOENT;
    +
    86
    +
    87 if (offset >= (1ULL << 32))
    +
    88 return 0;
    +
    89
    +
    90 memset(buf, 0, size);
    +
    91 return size;
    +
    92}
    +
    93
    +
    94static int null_write(const char *path, const char *buf, size_t size,
    +
    95 off_t offset, struct fuse_file_info *fi)
    +
    96{
    +
    97 (void) buf;
    +
    98 (void) offset;
    +
    99 (void) fi;
    +
    100
    +
    101 if(strcmp(path, "/") != 0)
    +
    102 return -ENOENT;
    +
    103
    +
    104 return size;
    +
    105}
    +
    106
    +
    107static const struct fuse_operations null_oper = {
    +
    108 .getattr = null_getattr,
    +
    109 .truncate = null_truncate,
    +
    110 .open = null_open,
    +
    111 .read = null_read,
    +
    112 .write = null_write,
    +
    113};
    +
    114
    +
    115int main(int argc, char *argv[])
    +
    116{
    +
    117 struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
    +
    118 struct fuse_cmdline_opts opts;
    +
    119 struct stat stbuf;
    +
    120
    +
    121 if (fuse_parse_cmdline(&args, &opts) != 0)
    +
    122 return 1;
    +
    123 fuse_opt_free_args(&args);
    +
    124
    +
    125 if (!opts.mountpoint) {
    +
    126 fprintf(stderr, "missing mountpoint parameter\n");
    +
    127 return 1;
    +
    128 }
    +
    129
    +
    130 if (stat(opts.mountpoint, &stbuf) == -1) {
    +
    131 fprintf(stderr ,"failed to access mountpoint %s: %s\n",
    +
    132 opts.mountpoint, strerror(errno));
    +
    133 free(opts.mountpoint);
    +
    134 return 1;
    +
    135 }
    +
    136 free(opts.mountpoint);
    +
    137 if (!S_ISREG(stbuf.st_mode)) {
    +
    138 fprintf(stderr, "mountpoint is not a regular file\n");
    +
    139 return 1;
    +
    140 }
    +
    141
    +
    142 return fuse_main(argc, argv, &null_oper, NULL);
    +
    143}
    + + +
    void fuse_opt_free_args(struct fuse_args *args)
    Definition fuse_opt.c:34
    +
    #define FUSE_ARGS_INIT(argc, argv)
    Definition fuse_opt.h:123
    + + +
    char ** argv
    Definition fuse_opt.h:114
    + + + +
    + + +
    + + diff --git a/doc/html/passthrough_8c.html b/doc/html/passthrough_8c.html new file mode 100644 index 0000000..f5a629d --- /dev/null +++ b/doc/html/passthrough_8c.html @@ -0,0 +1,685 @@ + + + + + + + +libfuse: example/passthrough.c File Reference + + + + + + + +
    +
    + + + + + + +
    +
    libfuse +
    +
    +
    + + + + + + + + +
    +
    +
    +
    passthrough.c File Reference
    +
    +
    +
    #include <fuse.h>
    +#include <stdio.h>
    +#include <string.h>
    +#include <unistd.h>
    +#include <fcntl.h>
    +#include <sys/stat.h>
    +#include <dirent.h>
    +#include <errno.h>
    +#include <sys/time.h>
    +#include "passthrough_helpers.h"
    +
    +

    Go to the source code of this file.

    +

    Detailed Description

    +

    This file system mirrors the existing file system hierarchy of the system, starting at the root file system. This is implemented by just "passing through" all requests to the corresponding user-space libc functions. Its performance is terrible.

    +

    Compile with

    gcc -Wall passthrough.c `pkg-config fuse3 --cflags --libs` -o passthrough
    +

    +Source code

    +
    /*
    +
    FUSE: Filesystem in Userspace
    +
    Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
    +
    Copyright (C) 2011 Sebastian Pipping <sebastian@pipping.org>
    +
    +
    This program can be distributed under the terms of the GNU GPLv2.
    +
    See the file GPL2.txt.
    +
    */
    +
    +
    +
    +
    #define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 18)
    +
    +
    #define _GNU_SOURCE
    +
    +
    #ifdef linux
    +
    /* For pread()/pwrite()/utimensat() */
    +
    #define _XOPEN_SOURCE 700
    +
    #endif
    +
    +
    #include <fuse.h>
    +
    #include <stdio.h>
    +
    #include <string.h>
    +
    #include <unistd.h>
    +
    #include <fcntl.h>
    +
    #include <sys/stat.h>
    +
    #include <dirent.h>
    +
    #include <errno.h>
    +
    #include <sys/time.h>
    +
    #ifdef HAVE_SETXATTR
    +
    #include <sys/xattr.h>
    +
    #endif
    +
    +
    #include "passthrough_helpers.h"
    +
    +
    static int fill_dir_plus = 0;
    +
    static int readdir_zero_ino;
    +
    +
    static void *xmp_init(struct fuse_conn_info *conn,
    +
    struct fuse_config *cfg)
    +
    {
    +
    (void) conn;
    +
    cfg->use_ino = !readdir_zero_ino;
    +
    +
    /* parallel_direct_writes feature depends on direct_io features.
    +
    To make parallel_direct_writes valid, need either set cfg->direct_io
    +
    in current function (recommended in high level API) or set fi->direct_io
    +
    in xmp_create() or xmp_open(). */
    +
    // cfg->direct_io = 1;
    + +
    +
    /* Pick up changes from lower filesystem right away. This is
    +
    also necessary for better hardlink support. When the kernel
    +
    calls the unlink() handler, it does not know the inode of
    +
    the to-be-removed entry and can therefore not invalidate
    +
    the cache of the associated inode - resulting in an
    +
    incorrect st_nlink value being reported for any remaining
    +
    hardlinks to this inode. */
    +
    if (!cfg->auto_cache) {
    +
    cfg->entry_timeout = 0;
    +
    cfg->attr_timeout = 0;
    +
    cfg->negative_timeout = 0;
    +
    }
    +
    +
    return NULL;
    +
    }
    +
    +
    static int xmp_getattr(const char *path, struct stat *stbuf,
    +
    struct fuse_file_info *fi)
    +
    {
    +
    (void) fi;
    +
    int res;
    +
    +
    res = lstat(path, stbuf);
    +
    if (res == -1)
    +
    return -errno;
    +
    +
    return 0;
    +
    }
    +
    +
    static int xmp_access(const char *path, int mask)
    +
    {
    +
    int res;
    +
    +
    res = access(path, mask);
    +
    if (res == -1)
    +
    return -errno;
    +
    +
    return 0;
    +
    }
    +
    +
    static int xmp_readlink(const char *path, char *buf, size_t size)
    +
    {
    +
    int res;
    +
    +
    res = readlink(path, buf, size - 1);
    +
    if (res == -1)
    +
    return -errno;
    +
    +
    buf[res] = '\0';
    +
    return 0;
    +
    }
    +
    +
    +
    static int xmp_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
    +
    off_t offset, struct fuse_file_info *fi,
    +
    enum fuse_readdir_flags flags)
    +
    {
    +
    DIR *dp;
    +
    struct dirent *de;
    +
    +
    (void) offset;
    +
    (void) fi;
    +
    (void) flags;
    +
    +
    dp = opendir(path);
    +
    if (dp == NULL)
    +
    return -errno;
    +
    +
    while ((de = readdir(dp)) != NULL) {
    +
    struct stat st;
    +
    if (fill_dir_plus) {
    +
    fstatat(dirfd(dp), de->d_name, &st,
    +
    AT_SYMLINK_NOFOLLOW);
    +
    } else {
    +
    memset(&st, 0, sizeof(st));
    +
    st.st_ino = de->d_ino;
    +
    st.st_mode = de->d_type << 12;
    +
    }
    +
    if (readdir_zero_ino)
    +
    st.st_ino = 0;
    +
    if (filler(buf, de->d_name, &st, 0, fill_dir_plus))
    +
    break;
    +
    }
    +
    +
    closedir(dp);
    +
    return 0;
    +
    }
    +
    +
    static int xmp_mknod(const char *path, mode_t mode, dev_t rdev)
    +
    {
    +
    int res;
    +
    +
    res = mknod_wrapper(AT_FDCWD, path, NULL, mode, rdev);
    +
    if (res == -1)
    +
    return -errno;
    +
    +
    return 0;
    +
    }
    +
    +
    static int xmp_mkdir(const char *path, mode_t mode)
    +
    {
    +
    int res;
    +
    +
    res = mkdir(path, mode);
    +
    if (res == -1)
    +
    return -errno;
    +
    +
    return 0;
    +
    }
    +
    +
    static int xmp_unlink(const char *path)
    +
    {
    +
    int res;
    +
    +
    res = unlink(path);
    +
    if (res == -1)
    +
    return -errno;
    +
    +
    return 0;
    +
    }
    +
    +
    static int xmp_rmdir(const char *path)
    +
    {
    +
    int res;
    +
    +
    res = rmdir(path);
    +
    if (res == -1)
    +
    return -errno;
    +
    +
    return 0;
    +
    }
    +
    +
    static int xmp_symlink(const char *from, const char *to)
    +
    {
    +
    int res;
    +
    +
    res = symlink(from, to);
    +
    if (res == -1)
    +
    return -errno;
    +
    +
    return 0;
    +
    }
    +
    +
    static int xmp_rename(const char *from, const char *to, unsigned int flags)
    +
    {
    +
    int res;
    +
    +
    if (flags)
    +
    return -EINVAL;
    +
    +
    res = rename(from, to);
    +
    if (res == -1)
    +
    return -errno;
    +
    +
    return 0;
    +
    }
    +
    +
    static int xmp_link(const char *from, const char *to)
    +
    {
    +
    int res;
    +
    +
    res = link(from, to);
    +
    if (res == -1)
    +
    return -errno;
    +
    +
    return 0;
    +
    }
    +
    +
    static int xmp_chmod(const char *path, mode_t mode,
    +
    struct fuse_file_info *fi)
    +
    {
    +
    (void) fi;
    +
    int res;
    +
    +
    res = chmod(path, mode);
    +
    if (res == -1)
    +
    return -errno;
    +
    +
    return 0;
    +
    }
    +
    +
    static int xmp_chown(const char *path, uid_t uid, gid_t gid,
    +
    struct fuse_file_info *fi)
    +
    {
    +
    (void) fi;
    +
    int res;
    +
    +
    res = lchown(path, uid, gid);
    +
    if (res == -1)
    +
    return -errno;
    +
    +
    return 0;
    +
    }
    +
    +
    static int xmp_truncate(const char *path, off_t size,
    +
    struct fuse_file_info *fi)
    +
    {
    +
    int res;
    +
    +
    if (fi != NULL)
    +
    res = ftruncate(fi->fh, size);
    +
    else
    +
    res = truncate(path, size);
    +
    if (res == -1)
    +
    return -errno;
    +
    +
    return 0;
    +
    }
    +
    +
    #ifdef HAVE_UTIMENSAT
    +
    static int xmp_utimens(const char *path, const struct timespec ts[2],
    +
    struct fuse_file_info *fi)
    +
    {
    +
    (void) fi;
    +
    int res;
    +
    +
    /* don't use utime/utimes since they follow symlinks */
    +
    res = utimensat(0, path, ts, AT_SYMLINK_NOFOLLOW);
    +
    if (res == -1)
    +
    return -errno;
    +
    +
    return 0;
    +
    }
    +
    #endif
    +
    +
    static int xmp_create(const char *path, mode_t mode,
    +
    struct fuse_file_info *fi)
    +
    {
    +
    int res;
    +
    +
    res = open(path, fi->flags, mode);
    +
    if (res == -1)
    +
    return -errno;
    +
    +
    fi->fh = res;
    +
    return 0;
    +
    }
    +
    +
    static int xmp_open(const char *path, struct fuse_file_info *fi)
    +
    {
    +
    int res;
    +
    +
    res = open(path, fi->flags);
    +
    if (res == -1)
    +
    return -errno;
    +
    +
    /* Enable direct_io when open has flags O_DIRECT to enjoy the feature
    +
    parallel_direct_writes (i.e., to get a shared lock, not exclusive lock,
    +
    for writes to the same file). */
    +
    if (fi->flags & O_DIRECT) {
    +
    fi->direct_io = 1;
    + +
    }
    +
    +
    fi->fh = res;
    +
    return 0;
    +
    }
    +
    +
    static int xmp_read(const char *path, char *buf, size_t size, off_t offset,
    +
    struct fuse_file_info *fi)
    +
    {
    +
    int fd;
    +
    int res;
    +
    +
    if(fi == NULL)
    +
    fd = open(path, O_RDONLY);
    +
    else
    +
    fd = fi->fh;
    +
    +
    if (fd == -1)
    +
    return -errno;
    +
    +
    res = pread(fd, buf, size, offset);
    +
    if (res == -1)
    +
    res = -errno;
    +
    +
    if(fi == NULL)
    +
    close(fd);
    +
    return res;
    +
    }
    +
    +
    static int xmp_write(const char *path, const char *buf, size_t size,
    +
    off_t offset, struct fuse_file_info *fi)
    +
    {
    +
    int fd;
    +
    int res;
    +
    +
    (void) fi;
    +
    if(fi == NULL)
    +
    fd = open(path, O_WRONLY);
    +
    else
    +
    fd = fi->fh;
    +
    +
    if (fd == -1)
    +
    return -errno;
    +
    +
    res = pwrite(fd, buf, size, offset);
    +
    if (res == -1)
    +
    res = -errno;
    +
    +
    if(fi == NULL)
    +
    close(fd);
    +
    return res;
    +
    }
    +
    +
    static int xmp_statfs(const char *path, struct statvfs *stbuf)
    +
    {
    +
    int res;
    +
    +
    res = statvfs(path, stbuf);
    +
    if (res == -1)
    +
    return -errno;
    +
    +
    return 0;
    +
    }
    +
    +
    static int xmp_release(const char *path, struct fuse_file_info *fi)
    +
    {
    +
    (void) path;
    +
    close(fi->fh);
    +
    return 0;
    +
    }
    +
    +
    static int xmp_fsync(const char *path, int isdatasync,
    +
    struct fuse_file_info *fi)
    +
    {
    +
    /* Just a stub. This method is optional and can safely be left
    +
    unimplemented */
    +
    +
    (void) path;
    +
    (void) isdatasync;
    +
    (void) fi;
    +
    return 0;
    +
    }
    +
    +
    static int xmp_fallocate(const char *path, int mode,
    +
    off_t offset, off_t length, struct fuse_file_info *fi)
    +
    {
    +
    int fd;
    +
    int res;
    +
    +
    (void) fi;
    +
    +
    if(fi == NULL)
    +
    fd = open(path, O_WRONLY);
    +
    else
    +
    fd = fi->fh;
    +
    +
    if (fd == -1)
    +
    return -errno;
    +
    +
    res = do_fallocate(fd, mode, offset, length);
    +
    +
    if(fi == NULL)
    +
    close(fd);
    +
    return res;
    +
    }
    +
    +
    #ifdef HAVE_SETXATTR
    +
    /* xattr operations are optional and can safely be left unimplemented */
    +
    static int xmp_setxattr(const char *path, const char *name, const char *value,
    +
    size_t size, int flags)
    +
    {
    +
    int res = lsetxattr(path, name, value, size, flags);
    +
    if (res == -1)
    +
    return -errno;
    +
    return 0;
    +
    }
    +
    +
    static int xmp_getxattr(const char *path, const char *name, char *value,
    +
    size_t size)
    +
    {
    +
    int res = lgetxattr(path, name, value, size);
    +
    if (res == -1)
    +
    return -errno;
    +
    return res;
    +
    }
    +
    +
    static int xmp_listxattr(const char *path, char *list, size_t size)
    +
    {
    +
    int res = llistxattr(path, list, size);
    +
    if (res == -1)
    +
    return -errno;
    +
    return res;
    +
    }
    +
    +
    static int xmp_removexattr(const char *path, const char *name)
    +
    {
    +
    int res = lremovexattr(path, name);
    +
    if (res == -1)
    +
    return -errno;
    +
    return 0;
    +
    }
    +
    #endif /* HAVE_SETXATTR */
    +
    +
    #ifdef HAVE_COPY_FILE_RANGE
    +
    static ssize_t xmp_copy_file_range(const char *path_in,
    +
    struct fuse_file_info *fi_in,
    +
    off_t offset_in, const char *path_out,
    +
    struct fuse_file_info *fi_out,
    +
    off_t offset_out, size_t len, int flags)
    +
    {
    +
    int fd_in, fd_out;
    +
    ssize_t res;
    +
    +
    if(fi_in == NULL)
    +
    fd_in = open(path_in, O_RDONLY);
    +
    else
    +
    fd_in = fi_in->fh;
    +
    +
    if (fd_in == -1)
    +
    return -errno;
    +
    +
    if(fi_out == NULL)
    +
    fd_out = open(path_out, O_WRONLY);
    +
    else
    +
    fd_out = fi_out->fh;
    +
    +
    if (fd_out == -1) {
    +
    close(fd_in);
    +
    return -errno;
    +
    }
    +
    +
    res = copy_file_range(fd_in, &offset_in, fd_out, &offset_out, len,
    +
    flags);
    +
    if (res == -1)
    +
    res = -errno;
    +
    +
    if (fi_out == NULL)
    +
    close(fd_out);
    +
    if (fi_in == NULL)
    +
    close(fd_in);
    +
    +
    return res;
    +
    }
    +
    #endif
    +
    +
    static off_t xmp_lseek(const char *path, off_t off, int whence, struct fuse_file_info *fi)
    +
    {
    +
    int fd;
    +
    off_t res;
    +
    +
    if (fi == NULL)
    +
    fd = open(path, O_RDONLY);
    +
    else
    +
    fd = fi->fh;
    +
    +
    if (fd == -1)
    +
    return -errno;
    +
    +
    res = lseek(fd, off, whence);
    +
    if (res == -1)
    +
    res = -errno;
    +
    +
    if (fi == NULL)
    +
    close(fd);
    +
    return res;
    +
    }
    +
    +
    #ifdef HAVE_STATX
    +
    static int xmp_statx(const char *path, int flags, int mask, struct statx *stxbuf,
    +
    struct fuse_file_info *fi)
    +
    {
    +
    int fd = -1;
    +
    int res;
    +
    +
    if (fi)
    +
    fd = fi->fh;
    +
    +
    res = statx(fd, path, flags | AT_SYMLINK_NOFOLLOW, mask, stxbuf);
    +
    if (res == -1)
    +
    return -errno;
    +
    +
    return 0;
    +
    }
    +
    #endif
    +
    +
    static const struct fuse_operations xmp_oper = {
    +
    .init = xmp_init,
    +
    .getattr = xmp_getattr,
    +
    .access = xmp_access,
    +
    .readlink = xmp_readlink,
    +
    .readdir = xmp_readdir,
    +
    .mknod = xmp_mknod,
    +
    .mkdir = xmp_mkdir,
    +
    .symlink = xmp_symlink,
    +
    .unlink = xmp_unlink,
    +
    .rmdir = xmp_rmdir,
    +
    .rename = xmp_rename,
    +
    .link = xmp_link,
    +
    .chmod = xmp_chmod,
    +
    .chown = xmp_chown,
    +
    .truncate = xmp_truncate,
    +
    #ifdef HAVE_UTIMENSAT
    +
    .utimens = xmp_utimens,
    +
    #endif
    +
    .open = xmp_open,
    +
    .create = xmp_create,
    +
    .read = xmp_read,
    +
    .write = xmp_write,
    +
    .statfs = xmp_statfs,
    +
    .release = xmp_release,
    +
    .fsync = xmp_fsync,
    +
    .fallocate = xmp_fallocate,
    +
    #ifdef HAVE_SETXATTR
    +
    .setxattr = xmp_setxattr,
    +
    .getxattr = xmp_getxattr,
    +
    .listxattr = xmp_listxattr,
    +
    .removexattr = xmp_removexattr,
    +
    #endif
    +
    #ifdef HAVE_COPY_FILE_RANGE
    +
    .copy_file_range = xmp_copy_file_range,
    +
    #endif
    +
    .lseek = xmp_lseek,
    +
    #ifdef HAVE_STATX
    +
    .statx = xmp_statx,
    +
    #endif
    +
    };
    +
    +
    int main(int argc, char *argv[])
    +
    {
    +
    enum { MAX_ARGS = 10 };
    +
    int i,new_argc;
    +
    char *new_argv[MAX_ARGS];
    +
    +
    umask(0);
    +
    /* Process the "--plus" option apart */
    +
    for (i=0, new_argc=0; (i<argc) && (new_argc<MAX_ARGS); i++) {
    +
    if (!strcmp(argv[i], "--plus")) {
    +
    fill_dir_plus = FUSE_FILL_DIR_PLUS;
    +
    } else if (!strcmp(argv[i], "--readdir-zero-inodes")) {
    +
    // Return zero inodes from readdir
    +
    readdir_zero_ino = 1;
    +
    } else {
    +
    new_argv[new_argc++] = argv[i];
    +
    }
    +
    }
    +
    return fuse_main(new_argc, new_argv, &xmp_oper, NULL);
    +
    }
    + +
    int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
    Definition fuse.h:93
    +
    fuse_readdir_flags
    Definition fuse.h:45
    + +
    int32_t parallel_direct_writes
    Definition fuse.h:318
    +
    int32_t use_ino
    Definition fuse.h:204
    +
    double entry_timeout
    Definition fuse.h:133
    +
    int32_t auto_cache
    Definition fuse.h:259
    +
    double negative_timeout
    Definition fuse.h:143
    +
    double attr_timeout
    Definition fuse.h:149
    + + + +
    uint32_t parallel_direct_writes
    Definition fuse_common.h:97
    +
    uint32_t direct_io
    Definition fuse_common.h:63
    + + +
    +

    Definition in file passthrough.c.

    +
    + + +
    + + diff --git a/doc/html/passthrough_8c_source.html b/doc/html/passthrough_8c_source.html new file mode 100644 index 0000000..07a4587 --- /dev/null +++ b/doc/html/passthrough_8c_source.html @@ -0,0 +1,666 @@ + + + + + + + +libfuse: example/passthrough.c Source File + + + + + + + +
    +
    + + + + + + +
    +
    libfuse +
    +
    +
    + + + + + + + + +
    +
    +
    +
    passthrough.c
    +
    +
    +Go to the documentation of this file.
    1/*
    +
    2 FUSE: Filesystem in Userspace
    +
    3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
    +
    4 Copyright (C) 2011 Sebastian Pipping <sebastian@pipping.org>
    +
    5
    +
    6 This program can be distributed under the terms of the GNU GPLv2.
    +
    7 See the file GPL2.txt.
    +
    8*/
    +
    9
    +
    24
    +
    25
    +
    26#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 18)
    +
    27
    +
    28#define _GNU_SOURCE
    +
    29
    +
    30#ifdef linux
    +
    31/* For pread()/pwrite()/utimensat() */
    +
    32#define _XOPEN_SOURCE 700
    +
    33#endif
    +
    34
    +
    35#include <fuse.h>
    +
    36#include <stdio.h>
    +
    37#include <string.h>
    +
    38#include <unistd.h>
    +
    39#include <fcntl.h>
    +
    40#include <sys/stat.h>
    +
    41#include <dirent.h>
    +
    42#include <errno.h>
    +
    43#include <sys/time.h>
    +
    44#ifdef HAVE_SETXATTR
    +
    45#include <sys/xattr.h>
    +
    46#endif
    +
    47
    +
    48#include "passthrough_helpers.h"
    +
    49
    +
    50static int fill_dir_plus = 0;
    +
    51static int readdir_zero_ino;
    +
    52
    +
    53static void *xmp_init(struct fuse_conn_info *conn,
    +
    54 struct fuse_config *cfg)
    +
    55{
    +
    56 (void) conn;
    +
    57 cfg->use_ino = !readdir_zero_ino;
    +
    58
    +
    59 /* parallel_direct_writes feature depends on direct_io features.
    +
    60 To make parallel_direct_writes valid, need either set cfg->direct_io
    +
    61 in current function (recommended in high level API) or set fi->direct_io
    +
    62 in xmp_create() or xmp_open(). */
    +
    63 // cfg->direct_io = 1;
    + +
    65
    +
    66 /* Pick up changes from lower filesystem right away. This is
    +
    67 also necessary for better hardlink support. When the kernel
    +
    68 calls the unlink() handler, it does not know the inode of
    +
    69 the to-be-removed entry and can therefore not invalidate
    +
    70 the cache of the associated inode - resulting in an
    +
    71 incorrect st_nlink value being reported for any remaining
    +
    72 hardlinks to this inode. */
    +
    73 if (!cfg->auto_cache) {
    +
    74 cfg->entry_timeout = 0;
    +
    75 cfg->attr_timeout = 0;
    +
    76 cfg->negative_timeout = 0;
    +
    77 }
    +
    78
    +
    79 return NULL;
    +
    80}
    +
    81
    +
    82static int xmp_getattr(const char *path, struct stat *stbuf,
    +
    83 struct fuse_file_info *fi)
    +
    84{
    +
    85 (void) fi;
    +
    86 int res;
    +
    87
    +
    88 res = lstat(path, stbuf);
    +
    89 if (res == -1)
    +
    90 return -errno;
    +
    91
    +
    92 return 0;
    +
    93}
    +
    94
    +
    95static int xmp_access(const char *path, int mask)
    +
    96{
    +
    97 int res;
    +
    98
    +
    99 res = access(path, mask);
    +
    100 if (res == -1)
    +
    101 return -errno;
    +
    102
    +
    103 return 0;
    +
    104}
    +
    105
    +
    106static int xmp_readlink(const char *path, char *buf, size_t size)
    +
    107{
    +
    108 int res;
    +
    109
    +
    110 res = readlink(path, buf, size - 1);
    +
    111 if (res == -1)
    +
    112 return -errno;
    +
    113
    +
    114 buf[res] = '\0';
    +
    115 return 0;
    +
    116}
    +
    117
    +
    118
    +
    119static int xmp_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
    +
    120 off_t offset, struct fuse_file_info *fi,
    +
    121 enum fuse_readdir_flags flags)
    +
    122{
    +
    123 DIR *dp;
    +
    124 struct dirent *de;
    +
    125
    +
    126 (void) offset;
    +
    127 (void) fi;
    +
    128 (void) flags;
    +
    129
    +
    130 dp = opendir(path);
    +
    131 if (dp == NULL)
    +
    132 return -errno;
    +
    133
    +
    134 while ((de = readdir(dp)) != NULL) {
    +
    135 struct stat st;
    +
    136 if (fill_dir_plus) {
    +
    137 fstatat(dirfd(dp), de->d_name, &st,
    +
    138 AT_SYMLINK_NOFOLLOW);
    +
    139 } else {
    +
    140 memset(&st, 0, sizeof(st));
    +
    141 st.st_ino = de->d_ino;
    +
    142 st.st_mode = de->d_type << 12;
    +
    143 }
    +
    144 if (readdir_zero_ino)
    +
    145 st.st_ino = 0;
    +
    146 if (filler(buf, de->d_name, &st, 0, fill_dir_plus))
    +
    147 break;
    +
    148 }
    +
    149
    +
    150 closedir(dp);
    +
    151 return 0;
    +
    152}
    +
    153
    +
    154static int xmp_mknod(const char *path, mode_t mode, dev_t rdev)
    +
    155{
    +
    156 int res;
    +
    157
    +
    158 res = mknod_wrapper(AT_FDCWD, path, NULL, mode, rdev);
    +
    159 if (res == -1)
    +
    160 return -errno;
    +
    161
    +
    162 return 0;
    +
    163}
    +
    164
    +
    165static int xmp_mkdir(const char *path, mode_t mode)
    +
    166{
    +
    167 int res;
    +
    168
    +
    169 res = mkdir(path, mode);
    +
    170 if (res == -1)
    +
    171 return -errno;
    +
    172
    +
    173 return 0;
    +
    174}
    +
    175
    +
    176static int xmp_unlink(const char *path)
    +
    177{
    +
    178 int res;
    +
    179
    +
    180 res = unlink(path);
    +
    181 if (res == -1)
    +
    182 return -errno;
    +
    183
    +
    184 return 0;
    +
    185}
    +
    186
    +
    187static int xmp_rmdir(const char *path)
    +
    188{
    +
    189 int res;
    +
    190
    +
    191 res = rmdir(path);
    +
    192 if (res == -1)
    +
    193 return -errno;
    +
    194
    +
    195 return 0;
    +
    196}
    +
    197
    +
    198static int xmp_symlink(const char *from, const char *to)
    +
    199{
    +
    200 int res;
    +
    201
    +
    202 res = symlink(from, to);
    +
    203 if (res == -1)
    +
    204 return -errno;
    +
    205
    +
    206 return 0;
    +
    207}
    +
    208
    +
    209static int xmp_rename(const char *from, const char *to, unsigned int flags)
    +
    210{
    +
    211 int res;
    +
    212
    +
    213 if (flags)
    +
    214 return -EINVAL;
    +
    215
    +
    216 res = rename(from, to);
    +
    217 if (res == -1)
    +
    218 return -errno;
    +
    219
    +
    220 return 0;
    +
    221}
    +
    222
    +
    223static int xmp_link(const char *from, const char *to)
    +
    224{
    +
    225 int res;
    +
    226
    +
    227 res = link(from, to);
    +
    228 if (res == -1)
    +
    229 return -errno;
    +
    230
    +
    231 return 0;
    +
    232}
    +
    233
    +
    234static int xmp_chmod(const char *path, mode_t mode,
    +
    235 struct fuse_file_info *fi)
    +
    236{
    +
    237 (void) fi;
    +
    238 int res;
    +
    239
    +
    240 res = chmod(path, mode);
    +
    241 if (res == -1)
    +
    242 return -errno;
    +
    243
    +
    244 return 0;
    +
    245}
    +
    246
    +
    247static int xmp_chown(const char *path, uid_t uid, gid_t gid,
    +
    248 struct fuse_file_info *fi)
    +
    249{
    +
    250 (void) fi;
    +
    251 int res;
    +
    252
    +
    253 res = lchown(path, uid, gid);
    +
    254 if (res == -1)
    +
    255 return -errno;
    +
    256
    +
    257 return 0;
    +
    258}
    +
    259
    +
    260static int xmp_truncate(const char *path, off_t size,
    +
    261 struct fuse_file_info *fi)
    +
    262{
    +
    263 int res;
    +
    264
    +
    265 if (fi != NULL)
    +
    266 res = ftruncate(fi->fh, size);
    +
    267 else
    +
    268 res = truncate(path, size);
    +
    269 if (res == -1)
    +
    270 return -errno;
    +
    271
    +
    272 return 0;
    +
    273}
    +
    274
    +
    275#ifdef HAVE_UTIMENSAT
    +
    276static int xmp_utimens(const char *path, const struct timespec ts[2],
    +
    277 struct fuse_file_info *fi)
    +
    278{
    +
    279 (void) fi;
    +
    280 int res;
    +
    281
    +
    282 /* don't use utime/utimes since they follow symlinks */
    +
    283 res = utimensat(0, path, ts, AT_SYMLINK_NOFOLLOW);
    +
    284 if (res == -1)
    +
    285 return -errno;
    +
    286
    +
    287 return 0;
    +
    288}
    +
    289#endif
    +
    290
    +
    291static int xmp_create(const char *path, mode_t mode,
    +
    292 struct fuse_file_info *fi)
    +
    293{
    +
    294 int res;
    +
    295
    +
    296 res = open(path, fi->flags, mode);
    +
    297 if (res == -1)
    +
    298 return -errno;
    +
    299
    +
    300 fi->fh = res;
    +
    301 return 0;
    +
    302}
    +
    303
    +
    304static int xmp_open(const char *path, struct fuse_file_info *fi)
    +
    305{
    +
    306 int res;
    +
    307
    +
    308 res = open(path, fi->flags);
    +
    309 if (res == -1)
    +
    310 return -errno;
    +
    311
    +
    312 /* Enable direct_io when open has flags O_DIRECT to enjoy the feature
    +
    313 parallel_direct_writes (i.e., to get a shared lock, not exclusive lock,
    +
    314 for writes to the same file). */
    +
    315 if (fi->flags & O_DIRECT) {
    +
    316 fi->direct_io = 1;
    + +
    318 }
    +
    319
    +
    320 fi->fh = res;
    +
    321 return 0;
    +
    322}
    +
    323
    +
    324static int xmp_read(const char *path, char *buf, size_t size, off_t offset,
    +
    325 struct fuse_file_info *fi)
    +
    326{
    +
    327 int fd;
    +
    328 int res;
    +
    329
    +
    330 if(fi == NULL)
    +
    331 fd = open(path, O_RDONLY);
    +
    332 else
    +
    333 fd = fi->fh;
    +
    334
    +
    335 if (fd == -1)
    +
    336 return -errno;
    +
    337
    +
    338 res = pread(fd, buf, size, offset);
    +
    339 if (res == -1)
    +
    340 res = -errno;
    +
    341
    +
    342 if(fi == NULL)
    +
    343 close(fd);
    +
    344 return res;
    +
    345}
    +
    346
    +
    347static int xmp_write(const char *path, const char *buf, size_t size,
    +
    348 off_t offset, struct fuse_file_info *fi)
    +
    349{
    +
    350 int fd;
    +
    351 int res;
    +
    352
    +
    353 (void) fi;
    +
    354 if(fi == NULL)
    +
    355 fd = open(path, O_WRONLY);
    +
    356 else
    +
    357 fd = fi->fh;
    +
    358
    +
    359 if (fd == -1)
    +
    360 return -errno;
    +
    361
    +
    362 res = pwrite(fd, buf, size, offset);
    +
    363 if (res == -1)
    +
    364 res = -errno;
    +
    365
    +
    366 if(fi == NULL)
    +
    367 close(fd);
    +
    368 return res;
    +
    369}
    +
    370
    +
    371static int xmp_statfs(const char *path, struct statvfs *stbuf)
    +
    372{
    +
    373 int res;
    +
    374
    +
    375 res = statvfs(path, stbuf);
    +
    376 if (res == -1)
    +
    377 return -errno;
    +
    378
    +
    379 return 0;
    +
    380}
    +
    381
    +
    382static int xmp_release(const char *path, struct fuse_file_info *fi)
    +
    383{
    +
    384 (void) path;
    +
    385 close(fi->fh);
    +
    386 return 0;
    +
    387}
    +
    388
    +
    389static int xmp_fsync(const char *path, int isdatasync,
    +
    390 struct fuse_file_info *fi)
    +
    391{
    +
    392 /* Just a stub. This method is optional and can safely be left
    +
    393 unimplemented */
    +
    394
    +
    395 (void) path;
    +
    396 (void) isdatasync;
    +
    397 (void) fi;
    +
    398 return 0;
    +
    399}
    +
    400
    +
    401static int xmp_fallocate(const char *path, int mode,
    +
    402 off_t offset, off_t length, struct fuse_file_info *fi)
    +
    403{
    +
    404 int fd;
    +
    405 int res;
    +
    406
    +
    407 (void) fi;
    +
    408
    +
    409 if(fi == NULL)
    +
    410 fd = open(path, O_WRONLY);
    +
    411 else
    +
    412 fd = fi->fh;
    +
    413
    +
    414 if (fd == -1)
    +
    415 return -errno;
    +
    416
    +
    417 res = do_fallocate(fd, mode, offset, length);
    +
    418
    +
    419 if(fi == NULL)
    +
    420 close(fd);
    +
    421 return res;
    +
    422}
    +
    423
    +
    424#ifdef HAVE_SETXATTR
    +
    425/* xattr operations are optional and can safely be left unimplemented */
    +
    426static int xmp_setxattr(const char *path, const char *name, const char *value,
    +
    427 size_t size, int flags)
    +
    428{
    +
    429 int res = lsetxattr(path, name, value, size, flags);
    +
    430 if (res == -1)
    +
    431 return -errno;
    +
    432 return 0;
    +
    433}
    +
    434
    +
    435static int xmp_getxattr(const char *path, const char *name, char *value,
    +
    436 size_t size)
    +
    437{
    +
    438 int res = lgetxattr(path, name, value, size);
    +
    439 if (res == -1)
    +
    440 return -errno;
    +
    441 return res;
    +
    442}
    +
    443
    +
    444static int xmp_listxattr(const char *path, char *list, size_t size)
    +
    445{
    +
    446 int res = llistxattr(path, list, size);
    +
    447 if (res == -1)
    +
    448 return -errno;
    +
    449 return res;
    +
    450}
    +
    451
    +
    452static int xmp_removexattr(const char *path, const char *name)
    +
    453{
    +
    454 int res = lremovexattr(path, name);
    +
    455 if (res == -1)
    +
    456 return -errno;
    +
    457 return 0;
    +
    458}
    +
    459#endif /* HAVE_SETXATTR */
    +
    460
    +
    461#ifdef HAVE_COPY_FILE_RANGE
    +
    462static ssize_t xmp_copy_file_range(const char *path_in,
    +
    463 struct fuse_file_info *fi_in,
    +
    464 off_t offset_in, const char *path_out,
    +
    465 struct fuse_file_info *fi_out,
    +
    466 off_t offset_out, size_t len, int flags)
    +
    467{
    +
    468 int fd_in, fd_out;
    +
    469 ssize_t res;
    +
    470
    +
    471 if(fi_in == NULL)
    +
    472 fd_in = open(path_in, O_RDONLY);
    +
    473 else
    +
    474 fd_in = fi_in->fh;
    +
    475
    +
    476 if (fd_in == -1)
    +
    477 return -errno;
    +
    478
    +
    479 if(fi_out == NULL)
    +
    480 fd_out = open(path_out, O_WRONLY);
    +
    481 else
    +
    482 fd_out = fi_out->fh;
    +
    483
    +
    484 if (fd_out == -1) {
    +
    485 close(fd_in);
    +
    486 return -errno;
    +
    487 }
    +
    488
    +
    489 res = copy_file_range(fd_in, &offset_in, fd_out, &offset_out, len,
    +
    490 flags);
    +
    491 if (res == -1)
    +
    492 res = -errno;
    +
    493
    +
    494 if (fi_out == NULL)
    +
    495 close(fd_out);
    +
    496 if (fi_in == NULL)
    +
    497 close(fd_in);
    +
    498
    +
    499 return res;
    +
    500}
    +
    501#endif
    +
    502
    +
    503static off_t xmp_lseek(const char *path, off_t off, int whence, struct fuse_file_info *fi)
    +
    504{
    +
    505 int fd;
    +
    506 off_t res;
    +
    507
    +
    508 if (fi == NULL)
    +
    509 fd = open(path, O_RDONLY);
    +
    510 else
    +
    511 fd = fi->fh;
    +
    512
    +
    513 if (fd == -1)
    +
    514 return -errno;
    +
    515
    +
    516 res = lseek(fd, off, whence);
    +
    517 if (res == -1)
    +
    518 res = -errno;
    +
    519
    +
    520 if (fi == NULL)
    +
    521 close(fd);
    +
    522 return res;
    +
    523}
    +
    524
    +
    525#ifdef HAVE_STATX
    +
    526static int xmp_statx(const char *path, int flags, int mask, struct statx *stxbuf,
    +
    527 struct fuse_file_info *fi)
    +
    528{
    +
    529 int fd = -1;
    +
    530 int res;
    +
    531
    +
    532 if (fi)
    +
    533 fd = fi->fh;
    +
    534
    +
    535 res = statx(fd, path, flags | AT_SYMLINK_NOFOLLOW, mask, stxbuf);
    +
    536 if (res == -1)
    +
    537 return -errno;
    +
    538
    +
    539 return 0;
    +
    540}
    +
    541#endif
    +
    542
    +
    543static const struct fuse_operations xmp_oper = {
    +
    544 .init = xmp_init,
    +
    545 .getattr = xmp_getattr,
    +
    546 .access = xmp_access,
    +
    547 .readlink = xmp_readlink,
    +
    548 .readdir = xmp_readdir,
    +
    549 .mknod = xmp_mknod,
    +
    550 .mkdir = xmp_mkdir,
    +
    551 .symlink = xmp_symlink,
    +
    552 .unlink = xmp_unlink,
    +
    553 .rmdir = xmp_rmdir,
    +
    554 .rename = xmp_rename,
    +
    555 .link = xmp_link,
    +
    556 .chmod = xmp_chmod,
    +
    557 .chown = xmp_chown,
    +
    558 .truncate = xmp_truncate,
    +
    559#ifdef HAVE_UTIMENSAT
    +
    560 .utimens = xmp_utimens,
    +
    561#endif
    +
    562 .open = xmp_open,
    +
    563 .create = xmp_create,
    +
    564 .read = xmp_read,
    +
    565 .write = xmp_write,
    +
    566 .statfs = xmp_statfs,
    +
    567 .release = xmp_release,
    +
    568 .fsync = xmp_fsync,
    +
    569 .fallocate = xmp_fallocate,
    +
    570#ifdef HAVE_SETXATTR
    +
    571 .setxattr = xmp_setxattr,
    +
    572 .getxattr = xmp_getxattr,
    +
    573 .listxattr = xmp_listxattr,
    +
    574 .removexattr = xmp_removexattr,
    +
    575#endif
    +
    576#ifdef HAVE_COPY_FILE_RANGE
    +
    577 .copy_file_range = xmp_copy_file_range,
    +
    578#endif
    +
    579 .lseek = xmp_lseek,
    +
    580#ifdef HAVE_STATX
    +
    581 .statx = xmp_statx,
    +
    582#endif
    +
    583};
    +
    584
    +
    585int main(int argc, char *argv[])
    +
    586{
    +
    587 enum { MAX_ARGS = 10 };
    +
    588 int i,new_argc;
    +
    589 char *new_argv[MAX_ARGS];
    +
    590
    +
    591 umask(0);
    +
    592 /* Process the "--plus" option apart */
    +
    593 for (i=0, new_argc=0; (i<argc) && (new_argc<MAX_ARGS); i++) {
    +
    594 if (!strcmp(argv[i], "--plus")) {
    +
    595 fill_dir_plus = FUSE_FILL_DIR_PLUS;
    +
    596 } else if (!strcmp(argv[i], "--readdir-zero-inodes")) {
    +
    597 // Return zero inodes from readdir
    +
    598 readdir_zero_ino = 1;
    +
    599 } else {
    +
    600 new_argv[new_argc++] = argv[i];
    +
    601 }
    +
    602 }
    +
    603 return fuse_main(new_argc, new_argv, &xmp_oper, NULL);
    +
    604}
    + +
    int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
    Definition fuse.h:93
    +
    fuse_readdir_flags
    Definition fuse.h:45
    + +
    int32_t parallel_direct_writes
    Definition fuse.h:318
    +
    int32_t use_ino
    Definition fuse.h:204
    +
    double entry_timeout
    Definition fuse.h:133
    +
    int32_t auto_cache
    Definition fuse.h:259
    +
    double negative_timeout
    Definition fuse.h:143
    +
    double attr_timeout
    Definition fuse.h:149
    + + + +
    uint32_t parallel_direct_writes
    Definition fuse_common.h:97
    +
    uint32_t direct_io
    Definition fuse_common.h:63
    + + +
    + + +
    + + diff --git a/doc/html/passthrough__fh_8c.html b/doc/html/passthrough__fh_8c.html new file mode 100644 index 0000000..6e3826f --- /dev/null +++ b/doc/html/passthrough__fh_8c.html @@ -0,0 +1,788 @@ + + + + + + + +libfuse: example/passthrough_fh.c File Reference + + + + + + + +
    +
    + + + + + + +
    +
    libfuse +
    +
    +
    + + + + + + + + +
    +
    +
    +
    passthrough_fh.c File Reference
    +
    +
    +
    #include <fuse.h>
    +#include <stdio.h>
    +#include <stdlib.h>
    +#include <string.h>
    +#include <unistd.h>
    +#include <fcntl.h>
    +#include <sys/stat.h>
    +#include <dirent.h>
    +#include <errno.h>
    +#include <sys/time.h>
    +#include <sys/file.h>
    +#include "passthrough_helpers.h"
    +
    +

    Go to the source code of this file.

    +

    Detailed Description

    +

    This file system mirrors the existing file system hierarchy of the system, starting at the root file system. This is implemented by just "passing through" all requests to the corresponding user-space libc functions. This implementation is a little more sophisticated than the one in passthrough.c, so performance is not quite as bad.

    +

    Compile with:

    gcc -Wall passthrough_fh.c `pkg-config fuse3 --cflags --libs` -lulockmgr -o passthrough_fh
    +

    +Source code

    +
    /*
    +
    FUSE: Filesystem in Userspace
    +
    Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
    +
    Copyright (C) 2011 Sebastian Pipping <sebastian@pipping.org>
    +
    +
    This program can be distributed under the terms of the GNU GPLv2.
    +
    See the file GPL2.txt.
    +
    */
    +
    +
    +
    #define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 18)
    +
    +
    #define _GNU_SOURCE
    +
    +
    #include <fuse.h>
    +
    +
    #ifdef HAVE_LIBULOCKMGR
    +
    #include <ulockmgr.h>
    +
    #endif
    +
    +
    #include <stdio.h>
    +
    #include <stdlib.h>
    +
    #include <string.h>
    +
    #include <unistd.h>
    +
    #include <fcntl.h>
    +
    #include <sys/stat.h>
    +
    #include <dirent.h>
    +
    #include <errno.h>
    +
    #include <sys/time.h>
    +
    #ifdef HAVE_SETXATTR
    +
    #include <sys/xattr.h>
    +
    #endif
    +
    #include <sys/file.h> /* flock(2) */
    +
    +
    #include "passthrough_helpers.h"
    +
    +
    static void *xmp_init(struct fuse_conn_info *conn,
    +
    struct fuse_config *cfg)
    +
    {
    +
    (void) conn;
    +
    cfg->use_ino = 1;
    +
    cfg->nullpath_ok = 1;
    +
    +
    /* parallel_direct_writes feature depends on direct_io features.
    +
    To make parallel_direct_writes valid, need either set cfg->direct_io
    +
    in current function (recommended in high level API) or set fi->direct_io
    +
    in xmp_create() or xmp_open(). */
    +
    // cfg->direct_io = 1;
    + +
    +
    /* Pick up changes from lower filesystem right away. This is
    +
    also necessary for better hardlink support. When the kernel
    +
    calls the unlink() handler, it does not know the inode of
    +
    the to-be-removed entry and can therefore not invalidate
    +
    the cache of the associated inode - resulting in an
    +
    incorrect st_nlink value being reported for any remaining
    +
    hardlinks to this inode. */
    +
    cfg->entry_timeout = 0;
    +
    cfg->attr_timeout = 0;
    +
    cfg->negative_timeout = 0;
    +
    +
    return NULL;
    +
    }
    +
    +
    static int xmp_getattr(const char *path, struct stat *stbuf,
    +
    struct fuse_file_info *fi)
    +
    {
    +
    int res;
    +
    +
    (void) path;
    +
    +
    if(fi)
    +
    res = fstat(fi->fh, stbuf);
    +
    else
    +
    res = lstat(path, stbuf);
    +
    if (res == -1)
    +
    return -errno;
    +
    +
    return 0;
    +
    }
    +
    +
    static int xmp_access(const char *path, int mask)
    +
    {
    +
    int res;
    +
    +
    res = access(path, mask);
    +
    if (res == -1)
    +
    return -errno;
    +
    +
    return 0;
    +
    }
    +
    +
    static int xmp_readlink(const char *path, char *buf, size_t size)
    +
    {
    +
    int res;
    +
    +
    res = readlink(path, buf, size - 1);
    +
    if (res == -1)
    +
    return -errno;
    +
    +
    buf[res] = '\0';
    +
    return 0;
    +
    }
    +
    +
    struct xmp_dirp {
    +
    DIR *dp;
    +
    struct dirent *entry;
    +
    off_t offset;
    +
    };
    +
    +
    static int xmp_opendir(const char *path, struct fuse_file_info *fi)
    +
    {
    +
    int res;
    +
    struct xmp_dirp *d = malloc(sizeof(struct xmp_dirp));
    +
    if (d == NULL)
    +
    return -ENOMEM;
    +
    +
    d->dp = opendir(path);
    +
    if (d->dp == NULL) {
    +
    res = -errno;
    +
    free(d);
    +
    return res;
    +
    }
    +
    d->offset = 0;
    +
    d->entry = NULL;
    +
    +
    fi->fh = (unsigned long) d;
    +
    return 0;
    +
    }
    +
    +
    static inline struct xmp_dirp *get_dirp(struct fuse_file_info *fi)
    +
    {
    +
    return (struct xmp_dirp *) (uintptr_t) fi->fh;
    +
    }
    +
    +
    static int xmp_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
    +
    off_t offset, struct fuse_file_info *fi,
    +
    enum fuse_readdir_flags flags)
    +
    {
    +
    struct xmp_dirp *d = get_dirp(fi);
    +
    +
    (void) path;
    +
    if (offset != d->offset) {
    +
    #ifndef __FreeBSD__
    +
    seekdir(d->dp, offset);
    +
    #else
    +
    /* Subtract the one that we add when calling
    +
    telldir() below */
    +
    seekdir(d->dp, offset-1);
    +
    #endif
    +
    d->entry = NULL;
    +
    d->offset = offset;
    +
    }
    +
    while (1) {
    +
    struct stat st;
    +
    off_t nextoff;
    + +
    +
    if (!d->entry) {
    +
    d->entry = readdir(d->dp);
    +
    if (!d->entry)
    +
    break;
    +
    }
    +
    #ifdef HAVE_FSTATAT
    +
    if (flags & FUSE_READDIR_PLUS) {
    +
    int res;
    +
    +
    res = fstatat(dirfd(d->dp), d->entry->d_name, &st,
    +
    AT_SYMLINK_NOFOLLOW);
    +
    if (res != -1)
    +
    fill_flags |= FUSE_FILL_DIR_PLUS;
    +
    }
    +
    #endif
    +
    if (!(fill_flags & FUSE_FILL_DIR_PLUS)) {
    +
    memset(&st, 0, sizeof(st));
    +
    st.st_ino = d->entry->d_ino;
    +
    st.st_mode = d->entry->d_type << 12;
    +
    }
    +
    nextoff = telldir(d->dp);
    +
    #ifdef __FreeBSD__
    +
    /* Under FreeBSD, telldir() may return 0 the first time
    +
    it is called. But for libfuse, an offset of zero
    +
    means that offsets are not supported, so we shift
    +
    everything by one. */
    +
    nextoff++;
    +
    #endif
    +
    if (filler(buf, d->entry->d_name, &st, nextoff, fill_flags))
    +
    break;
    +
    +
    d->entry = NULL;
    +
    d->offset = nextoff;
    +
    }
    +
    +
    return 0;
    +
    }
    +
    +
    static int xmp_releasedir(const char *path, struct fuse_file_info *fi)
    +
    {
    +
    struct xmp_dirp *d = get_dirp(fi);
    +
    (void) path;
    +
    closedir(d->dp);
    +
    free(d);
    +
    return 0;
    +
    }
    +
    +
    static int xmp_mknod(const char *path, mode_t mode, dev_t rdev)
    +
    {
    +
    int res;
    +
    +
    if (S_ISFIFO(mode))
    +
    res = mkfifo(path, mode);
    +
    else
    +
    res = mknod(path, mode, rdev);
    +
    if (res == -1)
    +
    return -errno;
    +
    +
    return 0;
    +
    }
    +
    +
    static int xmp_mkdir(const char *path, mode_t mode)
    +
    {
    +
    int res;
    +
    +
    res = mkdir(path, mode);
    +
    if (res == -1)
    +
    return -errno;
    +
    +
    return 0;
    +
    }
    +
    +
    static int xmp_unlink(const char *path)
    +
    {
    +
    int res;
    +
    +
    res = unlink(path);
    +
    if (res == -1)
    +
    return -errno;
    +
    +
    return 0;
    +
    }
    +
    +
    static int xmp_rmdir(const char *path)
    +
    {
    +
    int res;
    +
    +
    res = rmdir(path);
    +
    if (res == -1)
    +
    return -errno;
    +
    +
    return 0;
    +
    }
    +
    +
    static int xmp_symlink(const char *from, const char *to)
    +
    {
    +
    int res;
    +
    +
    res = symlink(from, to);
    +
    if (res == -1)
    +
    return -errno;
    +
    +
    return 0;
    +
    }
    +
    +
    static int xmp_rename(const char *from, const char *to, unsigned int flags)
    +
    {
    +
    int res;
    +
    +
    /* When we have renameat2() in libc, then we can implement flags */
    +
    if (flags)
    +
    return -EINVAL;
    +
    +
    res = rename(from, to);
    +
    if (res == -1)
    +
    return -errno;
    +
    +
    return 0;
    +
    }
    +
    +
    static int xmp_link(const char *from, const char *to)
    +
    {
    +
    int res;
    +
    +
    res = link(from, to);
    +
    if (res == -1)
    +
    return -errno;
    +
    +
    return 0;
    +
    }
    +
    +
    static int xmp_chmod(const char *path, mode_t mode,
    +
    struct fuse_file_info *fi)
    +
    {
    +
    int res;
    +
    +
    if(fi)
    +
    res = fchmod(fi->fh, mode);
    +
    else
    +
    res = chmod(path, mode);
    +
    if (res == -1)
    +
    return -errno;
    +
    +
    return 0;
    +
    }
    +
    +
    static int xmp_chown(const char *path, uid_t uid, gid_t gid,
    +
    struct fuse_file_info *fi)
    +
    {
    +
    int res;
    +
    +
    if (fi)
    +
    res = fchown(fi->fh, uid, gid);
    +
    else
    +
    res = lchown(path, uid, gid);
    +
    if (res == -1)
    +
    return -errno;
    +
    +
    return 0;
    +
    }
    +
    +
    static int xmp_truncate(const char *path, off_t size,
    +
    struct fuse_file_info *fi)
    +
    {
    +
    int res;
    +
    +
    if(fi)
    +
    res = ftruncate(fi->fh, size);
    +
    else
    +
    res = truncate(path, size);
    +
    +
    if (res == -1)
    +
    return -errno;
    +
    +
    return 0;
    +
    }
    +
    +
    #ifdef HAVE_UTIMENSAT
    +
    static int xmp_utimens(const char *path, const struct timespec ts[2],
    +
    struct fuse_file_info *fi)
    +
    {
    +
    int res;
    +
    +
    /* don't use utime/utimes since they follow symlinks */
    +
    if (fi)
    +
    res = futimens(fi->fh, ts);
    +
    else
    +
    res = utimensat(0, path, ts, AT_SYMLINK_NOFOLLOW);
    +
    if (res == -1)
    +
    return -errno;
    +
    +
    return 0;
    +
    }
    +
    #endif
    +
    +
    static int xmp_create(const char *path, mode_t mode, struct fuse_file_info *fi)
    +
    {
    +
    int fd;
    +
    +
    fd = open(path, fi->flags, mode);
    +
    if (fd == -1)
    +
    return -errno;
    +
    +
    fi->fh = fd;
    +
    return 0;
    +
    }
    +
    +
    static int xmp_open(const char *path, struct fuse_file_info *fi)
    +
    {
    +
    int fd;
    +
    +
    fd = open(path, fi->flags);
    +
    if (fd == -1)
    +
    return -errno;
    +
    +
    /* Enable direct_io when open has flags O_DIRECT to enjoy the feature
    +
    parallel_direct_writes (i.e., to get a shared lock, not exclusive lock,
    +
    for writes to the same file). */
    +
    if (fi->flags & O_DIRECT) {
    +
    fi->direct_io = 1;
    + +
    }
    +
    +
    fi->fh = fd;
    +
    return 0;
    +
    }
    +
    +
    static int xmp_read(const char *path, char *buf, size_t size, off_t offset,
    +
    struct fuse_file_info *fi)
    +
    {
    +
    int res;
    +
    +
    (void) path;
    +
    res = pread(fi->fh, buf, size, offset);
    +
    if (res == -1)
    +
    res = -errno;
    +
    +
    return res;
    +
    }
    +
    +
    static int xmp_read_buf(const char *path, struct fuse_bufvec **bufp,
    +
    size_t size, off_t offset, struct fuse_file_info *fi)
    +
    {
    +
    struct fuse_bufvec *src;
    +
    +
    (void) path;
    +
    +
    src = malloc(sizeof(struct fuse_bufvec));
    +
    if (src == NULL)
    +
    return -ENOMEM;
    +
    +
    *src = FUSE_BUFVEC_INIT(size);
    +
    + +
    src->buf[0].fd = fi->fh;
    +
    src->buf[0].pos = offset;
    +
    +
    *bufp = src;
    +
    +
    return 0;
    +
    }
    +
    +
    static int xmp_write(const char *path, const char *buf, size_t size,
    +
    off_t offset, struct fuse_file_info *fi)
    +
    {
    +
    int res;
    +
    +
    (void) path;
    +
    res = pwrite(fi->fh, buf, size, offset);
    +
    if (res == -1)
    +
    res = -errno;
    +
    +
    return res;
    +
    }
    +
    +
    static int xmp_write_buf(const char *path, struct fuse_bufvec *buf,
    +
    off_t offset, struct fuse_file_info *fi)
    +
    {
    +
    struct fuse_bufvec dst = FUSE_BUFVEC_INIT(fuse_buf_size(buf));
    +
    +
    (void) path;
    +
    + +
    dst.buf[0].fd = fi->fh;
    +
    dst.buf[0].pos = offset;
    +
    + +
    }
    +
    +
    static int xmp_statfs(const char *path, struct statvfs *stbuf)
    +
    {
    +
    int res;
    +
    +
    res = statvfs(path, stbuf);
    +
    if (res == -1)
    +
    return -errno;
    +
    +
    return 0;
    +
    }
    +
    +
    static int xmp_flush(const char *path, struct fuse_file_info *fi)
    +
    {
    +
    int res;
    +
    +
    (void) path;
    +
    /* This is called from every close on an open file, so call the
    +
    close on the underlying filesystem. But since flush may be
    +
    called multiple times for an open file, this must not really
    +
    close the file. This is important if used on a network
    +
    filesystem like NFS which flush the data/metadata on close() */
    +
    res = close(dup(fi->fh));
    +
    if (res == -1)
    +
    return -errno;
    +
    +
    return 0;
    +
    }
    +
    +
    static int xmp_release(const char *path, struct fuse_file_info *fi)
    +
    {
    +
    (void) path;
    +
    close(fi->fh);
    +
    +
    return 0;
    +
    }
    +
    +
    static int xmp_fsync(const char *path, int isdatasync,
    +
    struct fuse_file_info *fi)
    +
    {
    +
    int res;
    +
    (void) path;
    +
    +
    #ifndef HAVE_FDATASYNC
    +
    (void) isdatasync;
    +
    #else
    +
    if (isdatasync)
    +
    res = fdatasync(fi->fh);
    +
    else
    +
    #endif
    +
    res = fsync(fi->fh);
    +
    if (res == -1)
    +
    return -errno;
    +
    +
    return 0;
    +
    }
    +
    +
    static int xmp_fallocate(const char *path, int mode,
    +
    off_t offset, off_t length, struct fuse_file_info *fi)
    +
    {
    +
    (void) path;
    +
    +
    return do_fallocate(fi->fh, mode, offset, length);
    +
    }
    +
    +
    #ifdef HAVE_SETXATTR
    +
    /* xattr operations are optional and can safely be left unimplemented */
    +
    static int xmp_setxattr(const char *path, const char *name, const char *value,
    +
    size_t size, int flags)
    +
    {
    +
    int res = lsetxattr(path, name, value, size, flags);
    +
    if (res == -1)
    +
    return -errno;
    +
    return 0;
    +
    }
    +
    +
    static int xmp_getxattr(const char *path, const char *name, char *value,
    +
    size_t size)
    +
    {
    +
    int res = lgetxattr(path, name, value, size);
    +
    if (res == -1)
    +
    return -errno;
    +
    return res;
    +
    }
    +
    +
    static int xmp_listxattr(const char *path, char *list, size_t size)
    +
    {
    +
    int res = llistxattr(path, list, size);
    +
    if (res == -1)
    +
    return -errno;
    +
    return res;
    +
    }
    +
    +
    static int xmp_removexattr(const char *path, const char *name)
    +
    {
    +
    int res = lremovexattr(path, name);
    +
    if (res == -1)
    +
    return -errno;
    +
    return 0;
    +
    }
    +
    #endif /* HAVE_SETXATTR */
    +
    +
    #ifdef HAVE_LIBULOCKMGR
    +
    static int xmp_lock(const char *path, struct fuse_file_info *fi, int cmd,
    +
    struct flock *lock)
    +
    {
    +
    (void) path;
    +
    +
    return ulockmgr_op(fi->fh, cmd, lock, &fi->lock_owner,
    +
    sizeof(fi->lock_owner));
    +
    }
    +
    #endif
    +
    +
    static int xmp_flock(const char *path, struct fuse_file_info *fi, int op)
    +
    {
    +
    int res;
    +
    (void) path;
    +
    +
    res = flock(fi->fh, op);
    +
    if (res == -1)
    +
    return -errno;
    +
    +
    return 0;
    +
    }
    +
    +
    #ifdef HAVE_COPY_FILE_RANGE
    +
    static ssize_t xmp_copy_file_range(const char *path_in,
    +
    struct fuse_file_info *fi_in,
    +
    off_t off_in, const char *path_out,
    +
    struct fuse_file_info *fi_out,
    +
    off_t off_out, size_t len, int flags)
    +
    {
    +
    ssize_t res;
    +
    (void) path_in;
    +
    (void) path_out;
    +
    +
    res = copy_file_range(fi_in->fh, &off_in, fi_out->fh, &off_out, len,
    +
    flags);
    +
    if (res == -1)
    +
    return -errno;
    +
    +
    return res;
    +
    }
    +
    #endif
    +
    +
    static off_t xmp_lseek(const char *path, off_t off, int whence, struct fuse_file_info *fi)
    +
    {
    +
    off_t res;
    +
    (void) path;
    +
    +
    res = lseek(fi->fh, off, whence);
    +
    if (res == -1)
    +
    return -errno;
    +
    +
    return res;
    +
    }
    +
    +
    #ifdef HAVE_STATX
    +
    static int xmp_statx(const char *path, int flags, int mask, struct statx *stxbuf,
    +
    struct fuse_file_info *fi)
    +
    {
    +
    int fd = -1;
    +
    int res;
    +
    +
    if (fi)
    +
    fd = fi->fh;
    +
    +
    res = statx(fd, path, flags | AT_SYMLINK_NOFOLLOW, mask, stxbuf);
    +
    if (res == -1)
    +
    return -errno;
    +
    +
    return 0;
    +
    }
    +
    #endif
    +
    +
    static const struct fuse_operations xmp_oper = {
    +
    .init = xmp_init,
    +
    .getattr = xmp_getattr,
    +
    .access = xmp_access,
    +
    .readlink = xmp_readlink,
    +
    .opendir = xmp_opendir,
    +
    .readdir = xmp_readdir,
    +
    .releasedir = xmp_releasedir,
    +
    .mknod = xmp_mknod,
    +
    .mkdir = xmp_mkdir,
    +
    .symlink = xmp_symlink,
    +
    .unlink = xmp_unlink,
    +
    .rmdir = xmp_rmdir,
    +
    .rename = xmp_rename,
    +
    .link = xmp_link,
    +
    .chmod = xmp_chmod,
    +
    .chown = xmp_chown,
    +
    .truncate = xmp_truncate,
    +
    #ifdef HAVE_UTIMENSAT
    +
    .utimens = xmp_utimens,
    +
    #endif
    +
    .create = xmp_create,
    +
    .open = xmp_open,
    +
    .read = xmp_read,
    +
    .read_buf = xmp_read_buf,
    +
    .write = xmp_write,
    +
    .write_buf = xmp_write_buf,
    +
    .statfs = xmp_statfs,
    +
    .flush = xmp_flush,
    +
    .release = xmp_release,
    +
    .fsync = xmp_fsync,
    +
    .fallocate = xmp_fallocate,
    +
    #ifdef HAVE_SETXATTR
    +
    .setxattr = xmp_setxattr,
    +
    .getxattr = xmp_getxattr,
    +
    .listxattr = xmp_listxattr,
    +
    .removexattr = xmp_removexattr,
    +
    #endif
    +
    #ifdef HAVE_LIBULOCKMGR
    +
    .lock = xmp_lock,
    +
    #endif
    +
    .flock = xmp_flock,
    +
    #ifdef HAVE_COPY_FILE_RANGE
    +
    .copy_file_range = xmp_copy_file_range,
    +
    #endif
    +
    .lseek = xmp_lseek,
    +
    #ifdef HAVE_STATX
    +
    .statx = xmp_statx,
    +
    #endif
    +
    };
    +
    +
    int main(int argc, char *argv[])
    +
    {
    +
    umask(0);
    +
    return fuse_main(argc, argv, &xmp_oper, NULL);
    +
    }
    + +
    int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
    Definition fuse.h:93
    +
    fuse_fill_dir_flags
    Definition fuse.h:61
    +
    @ FUSE_FILL_DIR_DEFAULTS
    Definition fuse.h:74
    +
    fuse_readdir_flags
    Definition fuse.h:45
    +
    size_t fuse_buf_size(const struct fuse_bufvec *bufv)
    Definition buffer.c:22
    +
    @ FUSE_BUF_FD_SEEK
    +
    @ FUSE_BUF_IS_FD
    +
    ssize_t fuse_buf_copy(struct fuse_bufvec *dst, struct fuse_bufvec *src, enum fuse_buf_copy_flags flags)
    Definition buffer.c:284
    +
    @ FUSE_BUF_SPLICE_NONBLOCK
    +
    enum fuse_buf_flags flags
    + +
    off_t pos
    + + +
    struct fuse_buf buf[1]
    + +
    int32_t nullpath_ok
    Definition fuse.h:279
    +
    int32_t parallel_direct_writes
    Definition fuse.h:318
    +
    int32_t use_ino
    Definition fuse.h:204
    +
    double entry_timeout
    Definition fuse.h:133
    +
    double negative_timeout
    Definition fuse.h:143
    +
    double attr_timeout
    Definition fuse.h:149
    + + +
    uint64_t lock_owner
    + +
    uint32_t parallel_direct_writes
    Definition fuse_common.h:97
    +
    uint32_t direct_io
    Definition fuse_common.h:63
    + + +
    +

    Definition in file passthrough_fh.c.

    +
    + + +
    + + diff --git a/doc/html/passthrough__fh_8c_source.html b/doc/html/passthrough__fh_8c_source.html new file mode 100644 index 0000000..89c41e5 --- /dev/null +++ b/doc/html/passthrough__fh_8c_source.html @@ -0,0 +1,767 @@ + + + + + + + +libfuse: example/passthrough_fh.c Source File + + + + + + + +
    +
    + + + + + + +
    +
    libfuse +
    +
    +
    + + + + + + + + +
    +
    +
    +
    passthrough_fh.c
    +
    +
    +Go to the documentation of this file.
    1/*
    +
    2 FUSE: Filesystem in Userspace
    +
    3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
    +
    4 Copyright (C) 2011 Sebastian Pipping <sebastian@pipping.org>
    +
    5
    +
    6 This program can be distributed under the terms of the GNU GPLv2.
    +
    7 See the file GPL2.txt.
    +
    8*/
    +
    9
    +
    25
    +
    26#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 18)
    +
    27
    +
    28#define _GNU_SOURCE
    +
    29
    +
    30#include <fuse.h>
    +
    31
    +
    32#ifdef HAVE_LIBULOCKMGR
    +
    33#include <ulockmgr.h>
    +
    34#endif
    +
    35
    +
    36#include <stdio.h>
    +
    37#include <stdlib.h>
    +
    38#include <string.h>
    +
    39#include <unistd.h>
    +
    40#include <fcntl.h>
    +
    41#include <sys/stat.h>
    +
    42#include <dirent.h>
    +
    43#include <errno.h>
    +
    44#include <sys/time.h>
    +
    45#ifdef HAVE_SETXATTR
    +
    46#include <sys/xattr.h>
    +
    47#endif
    +
    48#include <sys/file.h> /* flock(2) */
    +
    49
    +
    50#include "passthrough_helpers.h"
    +
    51
    +
    52static void *xmp_init(struct fuse_conn_info *conn,
    +
    53 struct fuse_config *cfg)
    +
    54{
    +
    55 (void) conn;
    +
    56 cfg->use_ino = 1;
    +
    57 cfg->nullpath_ok = 1;
    +
    58
    +
    59 /* parallel_direct_writes feature depends on direct_io features.
    +
    60 To make parallel_direct_writes valid, need either set cfg->direct_io
    +
    61 in current function (recommended in high level API) or set fi->direct_io
    +
    62 in xmp_create() or xmp_open(). */
    +
    63 // cfg->direct_io = 1;
    + +
    65
    +
    66 /* Pick up changes from lower filesystem right away. This is
    +
    67 also necessary for better hardlink support. When the kernel
    +
    68 calls the unlink() handler, it does not know the inode of
    +
    69 the to-be-removed entry and can therefore not invalidate
    +
    70 the cache of the associated inode - resulting in an
    +
    71 incorrect st_nlink value being reported for any remaining
    +
    72 hardlinks to this inode. */
    +
    73 cfg->entry_timeout = 0;
    +
    74 cfg->attr_timeout = 0;
    +
    75 cfg->negative_timeout = 0;
    +
    76
    +
    77 return NULL;
    +
    78}
    +
    79
    +
    80static int xmp_getattr(const char *path, struct stat *stbuf,
    +
    81 struct fuse_file_info *fi)
    +
    82{
    +
    83 int res;
    +
    84
    +
    85 (void) path;
    +
    86
    +
    87 if(fi)
    +
    88 res = fstat(fi->fh, stbuf);
    +
    89 else
    +
    90 res = lstat(path, stbuf);
    +
    91 if (res == -1)
    +
    92 return -errno;
    +
    93
    +
    94 return 0;
    +
    95}
    +
    96
    +
    97static int xmp_access(const char *path, int mask)
    +
    98{
    +
    99 int res;
    +
    100
    +
    101 res = access(path, mask);
    +
    102 if (res == -1)
    +
    103 return -errno;
    +
    104
    +
    105 return 0;
    +
    106}
    +
    107
    +
    108static int xmp_readlink(const char *path, char *buf, size_t size)
    +
    109{
    +
    110 int res;
    +
    111
    +
    112 res = readlink(path, buf, size - 1);
    +
    113 if (res == -1)
    +
    114 return -errno;
    +
    115
    +
    116 buf[res] = '\0';
    +
    117 return 0;
    +
    118}
    +
    119
    +
    120struct xmp_dirp {
    +
    121 DIR *dp;
    +
    122 struct dirent *entry;
    +
    123 off_t offset;
    +
    124};
    +
    125
    +
    126static int xmp_opendir(const char *path, struct fuse_file_info *fi)
    +
    127{
    +
    128 int res;
    +
    129 struct xmp_dirp *d = malloc(sizeof(struct xmp_dirp));
    +
    130 if (d == NULL)
    +
    131 return -ENOMEM;
    +
    132
    +
    133 d->dp = opendir(path);
    +
    134 if (d->dp == NULL) {
    +
    135 res = -errno;
    +
    136 free(d);
    +
    137 return res;
    +
    138 }
    +
    139 d->offset = 0;
    +
    140 d->entry = NULL;
    +
    141
    +
    142 fi->fh = (unsigned long) d;
    +
    143 return 0;
    +
    144}
    +
    145
    +
    146static inline struct xmp_dirp *get_dirp(struct fuse_file_info *fi)
    +
    147{
    +
    148 return (struct xmp_dirp *) (uintptr_t) fi->fh;
    +
    149}
    +
    150
    +
    151static int xmp_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
    +
    152 off_t offset, struct fuse_file_info *fi,
    +
    153 enum fuse_readdir_flags flags)
    +
    154{
    +
    155 struct xmp_dirp *d = get_dirp(fi);
    +
    156
    +
    157 (void) path;
    +
    158 if (offset != d->offset) {
    +
    159#ifndef __FreeBSD__
    +
    160 seekdir(d->dp, offset);
    +
    161#else
    +
    162 /* Subtract the one that we add when calling
    +
    163 telldir() below */
    +
    164 seekdir(d->dp, offset-1);
    +
    165#endif
    +
    166 d->entry = NULL;
    +
    167 d->offset = offset;
    +
    168 }
    +
    169 while (1) {
    +
    170 struct stat st;
    +
    171 off_t nextoff;
    + +
    173
    +
    174 if (!d->entry) {
    +
    175 d->entry = readdir(d->dp);
    +
    176 if (!d->entry)
    +
    177 break;
    +
    178 }
    +
    179#ifdef HAVE_FSTATAT
    +
    180 if (flags & FUSE_READDIR_PLUS) {
    +
    181 int res;
    +
    182
    +
    183 res = fstatat(dirfd(d->dp), d->entry->d_name, &st,
    +
    184 AT_SYMLINK_NOFOLLOW);
    +
    185 if (res != -1)
    +
    186 fill_flags |= FUSE_FILL_DIR_PLUS;
    +
    187 }
    +
    188#endif
    +
    189 if (!(fill_flags & FUSE_FILL_DIR_PLUS)) {
    +
    190 memset(&st, 0, sizeof(st));
    +
    191 st.st_ino = d->entry->d_ino;
    +
    192 st.st_mode = d->entry->d_type << 12;
    +
    193 }
    +
    194 nextoff = telldir(d->dp);
    +
    195#ifdef __FreeBSD__
    +
    196 /* Under FreeBSD, telldir() may return 0 the first time
    +
    197 it is called. But for libfuse, an offset of zero
    +
    198 means that offsets are not supported, so we shift
    +
    199 everything by one. */
    +
    200 nextoff++;
    +
    201#endif
    +
    202 if (filler(buf, d->entry->d_name, &st, nextoff, fill_flags))
    +
    203 break;
    +
    204
    +
    205 d->entry = NULL;
    +
    206 d->offset = nextoff;
    +
    207 }
    +
    208
    +
    209 return 0;
    +
    210}
    +
    211
    +
    212static int xmp_releasedir(const char *path, struct fuse_file_info *fi)
    +
    213{
    +
    214 struct xmp_dirp *d = get_dirp(fi);
    +
    215 (void) path;
    +
    216 closedir(d->dp);
    +
    217 free(d);
    +
    218 return 0;
    +
    219}
    +
    220
    +
    221static int xmp_mknod(const char *path, mode_t mode, dev_t rdev)
    +
    222{
    +
    223 int res;
    +
    224
    +
    225 if (S_ISFIFO(mode))
    +
    226 res = mkfifo(path, mode);
    +
    227 else
    +
    228 res = mknod(path, mode, rdev);
    +
    229 if (res == -1)
    +
    230 return -errno;
    +
    231
    +
    232 return 0;
    +
    233}
    +
    234
    +
    235static int xmp_mkdir(const char *path, mode_t mode)
    +
    236{
    +
    237 int res;
    +
    238
    +
    239 res = mkdir(path, mode);
    +
    240 if (res == -1)
    +
    241 return -errno;
    +
    242
    +
    243 return 0;
    +
    244}
    +
    245
    +
    246static int xmp_unlink(const char *path)
    +
    247{
    +
    248 int res;
    +
    249
    +
    250 res = unlink(path);
    +
    251 if (res == -1)
    +
    252 return -errno;
    +
    253
    +
    254 return 0;
    +
    255}
    +
    256
    +
    257static int xmp_rmdir(const char *path)
    +
    258{
    +
    259 int res;
    +
    260
    +
    261 res = rmdir(path);
    +
    262 if (res == -1)
    +
    263 return -errno;
    +
    264
    +
    265 return 0;
    +
    266}
    +
    267
    +
    268static int xmp_symlink(const char *from, const char *to)
    +
    269{
    +
    270 int res;
    +
    271
    +
    272 res = symlink(from, to);
    +
    273 if (res == -1)
    +
    274 return -errno;
    +
    275
    +
    276 return 0;
    +
    277}
    +
    278
    +
    279static int xmp_rename(const char *from, const char *to, unsigned int flags)
    +
    280{
    +
    281 int res;
    +
    282
    +
    283 /* When we have renameat2() in libc, then we can implement flags */
    +
    284 if (flags)
    +
    285 return -EINVAL;
    +
    286
    +
    287 res = rename(from, to);
    +
    288 if (res == -1)
    +
    289 return -errno;
    +
    290
    +
    291 return 0;
    +
    292}
    +
    293
    +
    294static int xmp_link(const char *from, const char *to)
    +
    295{
    +
    296 int res;
    +
    297
    +
    298 res = link(from, to);
    +
    299 if (res == -1)
    +
    300 return -errno;
    +
    301
    +
    302 return 0;
    +
    303}
    +
    304
    +
    305static int xmp_chmod(const char *path, mode_t mode,
    +
    306 struct fuse_file_info *fi)
    +
    307{
    +
    308 int res;
    +
    309
    +
    310 if(fi)
    +
    311 res = fchmod(fi->fh, mode);
    +
    312 else
    +
    313 res = chmod(path, mode);
    +
    314 if (res == -1)
    +
    315 return -errno;
    +
    316
    +
    317 return 0;
    +
    318}
    +
    319
    +
    320static int xmp_chown(const char *path, uid_t uid, gid_t gid,
    +
    321 struct fuse_file_info *fi)
    +
    322{
    +
    323 int res;
    +
    324
    +
    325 if (fi)
    +
    326 res = fchown(fi->fh, uid, gid);
    +
    327 else
    +
    328 res = lchown(path, uid, gid);
    +
    329 if (res == -1)
    +
    330 return -errno;
    +
    331
    +
    332 return 0;
    +
    333}
    +
    334
    +
    335static int xmp_truncate(const char *path, off_t size,
    +
    336 struct fuse_file_info *fi)
    +
    337{
    +
    338 int res;
    +
    339
    +
    340 if(fi)
    +
    341 res = ftruncate(fi->fh, size);
    +
    342 else
    +
    343 res = truncate(path, size);
    +
    344
    +
    345 if (res == -1)
    +
    346 return -errno;
    +
    347
    +
    348 return 0;
    +
    349}
    +
    350
    +
    351#ifdef HAVE_UTIMENSAT
    +
    352static int xmp_utimens(const char *path, const struct timespec ts[2],
    +
    353 struct fuse_file_info *fi)
    +
    354{
    +
    355 int res;
    +
    356
    +
    357 /* don't use utime/utimes since they follow symlinks */
    +
    358 if (fi)
    +
    359 res = futimens(fi->fh, ts);
    +
    360 else
    +
    361 res = utimensat(0, path, ts, AT_SYMLINK_NOFOLLOW);
    +
    362 if (res == -1)
    +
    363 return -errno;
    +
    364
    +
    365 return 0;
    +
    366}
    +
    367#endif
    +
    368
    +
    369static int xmp_create(const char *path, mode_t mode, struct fuse_file_info *fi)
    +
    370{
    +
    371 int fd;
    +
    372
    +
    373 fd = open(path, fi->flags, mode);
    +
    374 if (fd == -1)
    +
    375 return -errno;
    +
    376
    +
    377 fi->fh = fd;
    +
    378 return 0;
    +
    379}
    +
    380
    +
    381static int xmp_open(const char *path, struct fuse_file_info *fi)
    +
    382{
    +
    383 int fd;
    +
    384
    +
    385 fd = open(path, fi->flags);
    +
    386 if (fd == -1)
    +
    387 return -errno;
    +
    388
    +
    389 /* Enable direct_io when open has flags O_DIRECT to enjoy the feature
    +
    390 parallel_direct_writes (i.e., to get a shared lock, not exclusive lock,
    +
    391 for writes to the same file). */
    +
    392 if (fi->flags & O_DIRECT) {
    +
    393 fi->direct_io = 1;
    + +
    395 }
    +
    396
    +
    397 fi->fh = fd;
    +
    398 return 0;
    +
    399}
    +
    400
    +
    401static int xmp_read(const char *path, char *buf, size_t size, off_t offset,
    +
    402 struct fuse_file_info *fi)
    +
    403{
    +
    404 int res;
    +
    405
    +
    406 (void) path;
    +
    407 res = pread(fi->fh, buf, size, offset);
    +
    408 if (res == -1)
    +
    409 res = -errno;
    +
    410
    +
    411 return res;
    +
    412}
    +
    413
    +
    414static int xmp_read_buf(const char *path, struct fuse_bufvec **bufp,
    +
    415 size_t size, off_t offset, struct fuse_file_info *fi)
    +
    416{
    +
    417 struct fuse_bufvec *src;
    +
    418
    +
    419 (void) path;
    +
    420
    +
    421 src = malloc(sizeof(struct fuse_bufvec));
    +
    422 if (src == NULL)
    +
    423 return -ENOMEM;
    +
    424
    +
    425 *src = FUSE_BUFVEC_INIT(size);
    +
    426
    + +
    428 src->buf[0].fd = fi->fh;
    +
    429 src->buf[0].pos = offset;
    +
    430
    +
    431 *bufp = src;
    +
    432
    +
    433 return 0;
    +
    434}
    +
    435
    +
    436static int xmp_write(const char *path, const char *buf, size_t size,
    +
    437 off_t offset, struct fuse_file_info *fi)
    +
    438{
    +
    439 int res;
    +
    440
    +
    441 (void) path;
    +
    442 res = pwrite(fi->fh, buf, size, offset);
    +
    443 if (res == -1)
    +
    444 res = -errno;
    +
    445
    +
    446 return res;
    +
    447}
    +
    448
    +
    449static int xmp_write_buf(const char *path, struct fuse_bufvec *buf,
    +
    450 off_t offset, struct fuse_file_info *fi)
    +
    451{
    +
    452 struct fuse_bufvec dst = FUSE_BUFVEC_INIT(fuse_buf_size(buf));
    +
    453
    +
    454 (void) path;
    +
    455
    + +
    457 dst.buf[0].fd = fi->fh;
    +
    458 dst.buf[0].pos = offset;
    +
    459
    + +
    461}
    +
    462
    +
    463static int xmp_statfs(const char *path, struct statvfs *stbuf)
    +
    464{
    +
    465 int res;
    +
    466
    +
    467 res = statvfs(path, stbuf);
    +
    468 if (res == -1)
    +
    469 return -errno;
    +
    470
    +
    471 return 0;
    +
    472}
    +
    473
    +
    474static int xmp_flush(const char *path, struct fuse_file_info *fi)
    +
    475{
    +
    476 int res;
    +
    477
    +
    478 (void) path;
    +
    479 /* This is called from every close on an open file, so call the
    +
    480 close on the underlying filesystem. But since flush may be
    +
    481 called multiple times for an open file, this must not really
    +
    482 close the file. This is important if used on a network
    +
    483 filesystem like NFS which flush the data/metadata on close() */
    +
    484 res = close(dup(fi->fh));
    +
    485 if (res == -1)
    +
    486 return -errno;
    +
    487
    +
    488 return 0;
    +
    489}
    +
    490
    +
    491static int xmp_release(const char *path, struct fuse_file_info *fi)
    +
    492{
    +
    493 (void) path;
    +
    494 close(fi->fh);
    +
    495
    +
    496 return 0;
    +
    497}
    +
    498
    +
    499static int xmp_fsync(const char *path, int isdatasync,
    +
    500 struct fuse_file_info *fi)
    +
    501{
    +
    502 int res;
    +
    503 (void) path;
    +
    504
    +
    505#ifndef HAVE_FDATASYNC
    +
    506 (void) isdatasync;
    +
    507#else
    +
    508 if (isdatasync)
    +
    509 res = fdatasync(fi->fh);
    +
    510 else
    +
    511#endif
    +
    512 res = fsync(fi->fh);
    +
    513 if (res == -1)
    +
    514 return -errno;
    +
    515
    +
    516 return 0;
    +
    517}
    +
    518
    +
    519static int xmp_fallocate(const char *path, int mode,
    +
    520 off_t offset, off_t length, struct fuse_file_info *fi)
    +
    521{
    +
    522 (void) path;
    +
    523
    +
    524 return do_fallocate(fi->fh, mode, offset, length);
    +
    525}
    +
    526
    +
    527#ifdef HAVE_SETXATTR
    +
    528/* xattr operations are optional and can safely be left unimplemented */
    +
    529static int xmp_setxattr(const char *path, const char *name, const char *value,
    +
    530 size_t size, int flags)
    +
    531{
    +
    532 int res = lsetxattr(path, name, value, size, flags);
    +
    533 if (res == -1)
    +
    534 return -errno;
    +
    535 return 0;
    +
    536}
    +
    537
    +
    538static int xmp_getxattr(const char *path, const char *name, char *value,
    +
    539 size_t size)
    +
    540{
    +
    541 int res = lgetxattr(path, name, value, size);
    +
    542 if (res == -1)
    +
    543 return -errno;
    +
    544 return res;
    +
    545}
    +
    546
    +
    547static int xmp_listxattr(const char *path, char *list, size_t size)
    +
    548{
    +
    549 int res = llistxattr(path, list, size);
    +
    550 if (res == -1)
    +
    551 return -errno;
    +
    552 return res;
    +
    553}
    +
    554
    +
    555static int xmp_removexattr(const char *path, const char *name)
    +
    556{
    +
    557 int res = lremovexattr(path, name);
    +
    558 if (res == -1)
    +
    559 return -errno;
    +
    560 return 0;
    +
    561}
    +
    562#endif /* HAVE_SETXATTR */
    +
    563
    +
    564#ifdef HAVE_LIBULOCKMGR
    +
    565static int xmp_lock(const char *path, struct fuse_file_info *fi, int cmd,
    +
    566 struct flock *lock)
    +
    567{
    +
    568 (void) path;
    +
    569
    +
    570 return ulockmgr_op(fi->fh, cmd, lock, &fi->lock_owner,
    +
    571 sizeof(fi->lock_owner));
    +
    572}
    +
    573#endif
    +
    574
    +
    575static int xmp_flock(const char *path, struct fuse_file_info *fi, int op)
    +
    576{
    +
    577 int res;
    +
    578 (void) path;
    +
    579
    +
    580 res = flock(fi->fh, op);
    +
    581 if (res == -1)
    +
    582 return -errno;
    +
    583
    +
    584 return 0;
    +
    585}
    +
    586
    +
    587#ifdef HAVE_COPY_FILE_RANGE
    +
    588static ssize_t xmp_copy_file_range(const char *path_in,
    +
    589 struct fuse_file_info *fi_in,
    +
    590 off_t off_in, const char *path_out,
    +
    591 struct fuse_file_info *fi_out,
    +
    592 off_t off_out, size_t len, int flags)
    +
    593{
    +
    594 ssize_t res;
    +
    595 (void) path_in;
    +
    596 (void) path_out;
    +
    597
    +
    598 res = copy_file_range(fi_in->fh, &off_in, fi_out->fh, &off_out, len,
    +
    599 flags);
    +
    600 if (res == -1)
    +
    601 return -errno;
    +
    602
    +
    603 return res;
    +
    604}
    +
    605#endif
    +
    606
    +
    607static off_t xmp_lseek(const char *path, off_t off, int whence, struct fuse_file_info *fi)
    +
    608{
    +
    609 off_t res;
    +
    610 (void) path;
    +
    611
    +
    612 res = lseek(fi->fh, off, whence);
    +
    613 if (res == -1)
    +
    614 return -errno;
    +
    615
    +
    616 return res;
    +
    617}
    +
    618
    +
    619#ifdef HAVE_STATX
    +
    620static int xmp_statx(const char *path, int flags, int mask, struct statx *stxbuf,
    +
    621 struct fuse_file_info *fi)
    +
    622{
    +
    623 int fd = -1;
    +
    624 int res;
    +
    625
    +
    626 if (fi)
    +
    627 fd = fi->fh;
    +
    628
    +
    629 res = statx(fd, path, flags | AT_SYMLINK_NOFOLLOW, mask, stxbuf);
    +
    630 if (res == -1)
    +
    631 return -errno;
    +
    632
    +
    633 return 0;
    +
    634}
    +
    635#endif
    +
    636
    +
    637static const struct fuse_operations xmp_oper = {
    +
    638 .init = xmp_init,
    +
    639 .getattr = xmp_getattr,
    +
    640 .access = xmp_access,
    +
    641 .readlink = xmp_readlink,
    +
    642 .opendir = xmp_opendir,
    +
    643 .readdir = xmp_readdir,
    +
    644 .releasedir = xmp_releasedir,
    +
    645 .mknod = xmp_mknod,
    +
    646 .mkdir = xmp_mkdir,
    +
    647 .symlink = xmp_symlink,
    +
    648 .unlink = xmp_unlink,
    +
    649 .rmdir = xmp_rmdir,
    +
    650 .rename = xmp_rename,
    +
    651 .link = xmp_link,
    +
    652 .chmod = xmp_chmod,
    +
    653 .chown = xmp_chown,
    +
    654 .truncate = xmp_truncate,
    +
    655#ifdef HAVE_UTIMENSAT
    +
    656 .utimens = xmp_utimens,
    +
    657#endif
    +
    658 .create = xmp_create,
    +
    659 .open = xmp_open,
    +
    660 .read = xmp_read,
    +
    661 .read_buf = xmp_read_buf,
    +
    662 .write = xmp_write,
    +
    663 .write_buf = xmp_write_buf,
    +
    664 .statfs = xmp_statfs,
    +
    665 .flush = xmp_flush,
    +
    666 .release = xmp_release,
    +
    667 .fsync = xmp_fsync,
    +
    668 .fallocate = xmp_fallocate,
    +
    669#ifdef HAVE_SETXATTR
    +
    670 .setxattr = xmp_setxattr,
    +
    671 .getxattr = xmp_getxattr,
    +
    672 .listxattr = xmp_listxattr,
    +
    673 .removexattr = xmp_removexattr,
    +
    674#endif
    +
    675#ifdef HAVE_LIBULOCKMGR
    +
    676 .lock = xmp_lock,
    +
    677#endif
    +
    678 .flock = xmp_flock,
    +
    679#ifdef HAVE_COPY_FILE_RANGE
    +
    680 .copy_file_range = xmp_copy_file_range,
    +
    681#endif
    +
    682 .lseek = xmp_lseek,
    +
    683#ifdef HAVE_STATX
    +
    684 .statx = xmp_statx,
    +
    685#endif
    +
    686};
    +
    687
    +
    688int main(int argc, char *argv[])
    +
    689{
    +
    690 umask(0);
    +
    691 return fuse_main(argc, argv, &xmp_oper, NULL);
    +
    692}
    + +
    int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
    Definition fuse.h:93
    +
    fuse_fill_dir_flags
    Definition fuse.h:61
    +
    @ FUSE_FILL_DIR_DEFAULTS
    Definition fuse.h:74
    +
    fuse_readdir_flags
    Definition fuse.h:45
    +
    size_t fuse_buf_size(const struct fuse_bufvec *bufv)
    Definition buffer.c:22
    +
    @ FUSE_BUF_FD_SEEK
    +
    @ FUSE_BUF_IS_FD
    +
    ssize_t fuse_buf_copy(struct fuse_bufvec *dst, struct fuse_bufvec *src, enum fuse_buf_copy_flags flags)
    Definition buffer.c:284
    +
    @ FUSE_BUF_SPLICE_NONBLOCK
    +
    enum fuse_buf_flags flags
    + +
    off_t pos
    + + +
    struct fuse_buf buf[1]
    + +
    int32_t nullpath_ok
    Definition fuse.h:279
    +
    int32_t parallel_direct_writes
    Definition fuse.h:318
    +
    int32_t use_ino
    Definition fuse.h:204
    +
    double entry_timeout
    Definition fuse.h:133
    +
    double negative_timeout
    Definition fuse.h:143
    +
    double attr_timeout
    Definition fuse.h:149
    + + +
    uint64_t lock_owner
    + +
    uint32_t parallel_direct_writes
    Definition fuse_common.h:97
    +
    uint32_t direct_io
    Definition fuse_common.h:63
    + + +
    + + +
    + + diff --git a/doc/html/passthrough__helpers_8h_source.html b/doc/html/passthrough__helpers_8h_source.html new file mode 100644 index 0000000..2eb4fc8 --- /dev/null +++ b/doc/html/passthrough__helpers_8h_source.html @@ -0,0 +1,181 @@ + + + + + + + +libfuse: example/passthrough_helpers.h Source File + + + + + + + +
    +
    + + + + + + +
    +
    libfuse +
    +
    +
    + + + + + + + + +
    +
    +
    +
    passthrough_helpers.h
    +
    +
    +
    1/*
    +
    2 * FUSE: Filesystem in Userspace
    +
    3 *
    +
    4 * Redistribution and use in source and binary forms, with or without
    +
    5 * modification, are permitted provided that the following conditions
    +
    6 * are met:
    +
    7 * 1. Redistributions of source code must retain the above copyright
    +
    8 * notice, this list of conditions and the following disclaimer.
    +
    9 * 2. Redistributions in binary form must reproduce the above copyright
    +
    10 * notice, this list of conditions and the following disclaimer in the
    +
    11 * documentation and/or other materials provided with the distribution.
    +
    12 *
    +
    13 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
    +
    14 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
    +
    15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
    +
    16 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
    +
    17 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
    +
    18 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
    +
    19 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
    +
    20 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
    +
    21 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
    +
    22 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
    +
    23 * SUCH DAMAGE
    +
    24 */
    +
    25
    +
    26#ifndef FUSE_EXAMPLE_PASSTHROUGH_HELPERS_H_
    +
    27#define FUSE_EXAMPLE_PASSTHROUGH_HELPERS_H_
    +
    28
    +
    29#include <errno.h>
    +
    30#include <fcntl.h>
    +
    31#include <string.h>
    +
    32#include <sys/stat.h>
    +
    33#include <unistd.h>
    +
    34
    +
    35#ifdef __FreeBSD__
    +
    36#include <sys/socket.h>
    +
    37#include <sys/un.h>
    +
    38#endif
    +
    39
    +
    40static inline int do_fallocate(int fd, int mode, off_t offset, off_t length)
    +
    41{
    +
    42#ifdef HAVE_FALLOCATE
    +
    43 if (fallocate(fd, mode, offset, length) == -1)
    +
    44 return -errno;
    +
    45 return 0;
    +
    46#else // HAVE_FALLOCATE
    +
    47
    +
    48#ifdef HAVE_POSIX_FALLOCATE
    +
    49 if (mode == 0)
    +
    50 return -posix_fallocate(fd, offset, length);
    +
    51#endif
    +
    52
    +
    53#ifdef HAVE_FSPACECTL
    +
    54 // 0x3 == FALLOC_FL_KEEP_SIZE | FALLOC_FL_PUNCH_HOLE
    +
    55 if (mode == 0x3) {
    +
    56 struct spacectl_range sr;
    +
    57
    +
    58 sr.r_offset = offset;
    +
    59 sr.r_len = length;
    +
    60 if (fspacectl(fd, SPACECTL_DEALLOC, &sr, 0, NULL) == -1)
    +
    61 return -errno;
    +
    62 return 0;
    +
    63 }
    +
    64#endif
    +
    65
    +
    66 return -EOPNOTSUPP;
    +
    67#endif // HAVE_FALLOCATE
    +
    68}
    +
    69
    +
    70/*
    +
    71 * Creates files on the underlying file system in response to a FUSE_MKNOD
    +
    72 * operation
    +
    73 */
    +
    74static inline int mknod_wrapper(int dirfd, const char *path, const char *link,
    +
    75 int mode, dev_t rdev)
    +
    76{
    +
    77 int res;
    +
    78
    +
    79 if (S_ISREG(mode)) {
    +
    80 res = openat(dirfd, path, O_CREAT | O_EXCL | O_WRONLY, mode);
    +
    81 if (res >= 0)
    +
    82 res = close(res);
    +
    83 } else if (S_ISDIR(mode)) {
    +
    84 res = mkdirat(dirfd, path, mode);
    +
    85 } else if (S_ISLNK(mode) && link != NULL) {
    +
    86 res = symlinkat(link, dirfd, path);
    +
    87 } else if (S_ISFIFO(mode)) {
    +
    88 res = mkfifoat(dirfd, path, mode);
    +
    89#ifdef __FreeBSD__
    +
    90 } else if (S_ISSOCK(mode)) {
    +
    91 struct sockaddr_un su;
    +
    92 int fd;
    +
    93
    +
    94 if (strlen(path) >= sizeof(su.sun_path)) {
    +
    95 errno = ENAMETOOLONG;
    +
    96 return -1;
    +
    97 }
    +
    98 fd = socket(AF_UNIX, SOCK_STREAM, 0);
    +
    99 if (fd >= 0) {
    +
    100 /*
    +
    101 * We must bind the socket to the underlying file
    +
    102 * system to create the socket file, even though
    +
    103 * we'll never listen on this socket.
    +
    104 */
    +
    105 su.sun_family = AF_UNIX;
    +
    106 strncpy(su.sun_path, path, sizeof(su.sun_path));
    +
    107 res = bindat(dirfd, fd, (struct sockaddr*)&su,
    +
    108 sizeof(su));
    +
    109 if (res == 0)
    +
    110 close(fd);
    +
    111 } else {
    +
    112 res = -1;
    +
    113 }
    +
    114#endif
    +
    115 } else {
    +
    116 res = mknodat(dirfd, path, mode, rdev);
    +
    117 }
    +
    118
    +
    119 return res;
    +
    120}
    +
    121
    +
    122#endif // FUSE_PASSTHROUGH_HELPERS_H_
    +
    + + +
    + + diff --git a/doc/html/passthrough__ll_8c.html b/doc/html/passthrough__ll_8c.html new file mode 100644 index 0000000..b9b02ae --- /dev/null +++ b/doc/html/passthrough__ll_8c.html @@ -0,0 +1,1551 @@ + + + + + + + +libfuse: example/passthrough_ll.c File Reference + + + + + + + +
    +
    + + + + + + +
    +
    libfuse +
    +
    +
    + + + + + + + + +
    +
    +
    +
    passthrough_ll.c File Reference
    +
    +
    +
    #include <fuse_lowlevel.h>
    +#include <unistd.h>
    +#include <stdlib.h>
    +#include <stdio.h>
    +#include <stddef.h>
    +#include <stdbool.h>
    +#include <string.h>
    +#include <limits.h>
    +#include <dirent.h>
    +#include <assert.h>
    +#include <errno.h>
    +#include <inttypes.h>
    +#include <pthread.h>
    +#include <sys/file.h>
    +#include <sys/xattr.h>
    +#include "passthrough_helpers.h"
    +
    +

    Go to the source code of this file.

    +

    Detailed Description

    +

    This file system mirrors the existing file system hierarchy of the system, starting at the root file system. This is implemented by just "passing through" all requests to the corresponding user-space libc functions. In contrast to passthrough.c and passthrough_fh.c, this implementation uses the low-level API. Its performance should be the least bad among the three.

    +

    When writeback caching is enabled (-o writeback mount option), it is only possible to write to files for which the mounting user has read permissions. This is because the writeback cache requires the kernel to be able to issue read requests for all files (which the passthrough filesystem cannot satisfy if it can't read the file in the underlying filesystem).

    +

    Compile with:

    gcc -Wall passthrough_ll.c `pkg-config fuse3 --cflags --libs` -o passthrough_ll
    +

    +Source code

    +
    /*
    +
    FUSE: Filesystem in Userspace
    +
    Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
    +
    +
    This program can be distributed under the terms of the GNU GPLv2.
    +
    See the file GPL2.txt.
    +
    */
    +
    +
    +
    #define _GNU_SOURCE
    +
    #define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 18)
    +
    +
    #include <fuse_lowlevel.h>
    +
    #include <unistd.h>
    +
    #include <stdlib.h>
    +
    #include <stdio.h>
    +
    #include <stddef.h>
    +
    #include <stdbool.h>
    +
    #include <string.h>
    +
    #include <limits.h>
    +
    #include <dirent.h>
    +
    #include <assert.h>
    +
    #include <errno.h>
    +
    #include <inttypes.h>
    +
    #include <pthread.h>
    +
    #include <sys/file.h>
    +
    #include <sys/xattr.h>
    +
    +
    #include "passthrough_helpers.h"
    +
    +
    /* We are re-using pointers to our `struct lo_inode` and `struct
    +
    lo_dirp` elements as inodes. This means that we must be able to
    +
    store uintptr_t values in a fuse_ino_t variable. The following
    +
    incantation checks this condition at compile time. */
    +
    #if defined(__GNUC__) && (__GNUC__ > 4 || __GNUC__ == 4 && __GNUC_MINOR__ >= 6) && !defined __cplusplus
    +
    _Static_assert(sizeof(fuse_ino_t) >= sizeof(uintptr_t),
    +
    "fuse_ino_t too small to hold uintptr_t values!");
    +
    #else
    +
    struct _uintptr_to_must_hold_fuse_ino_t_dummy_struct \
    +
    { unsigned _uintptr_to_must_hold_fuse_ino_t:
    +
    ((sizeof(fuse_ino_t) >= sizeof(uintptr_t)) ? 1 : -1); };
    +
    #endif
    +
    +
    struct lo_inode {
    +
    struct lo_inode *next; /* protected by lo->mutex */
    +
    struct lo_inode *prev; /* protected by lo->mutex */
    +
    int fd;
    +
    ino_t ino;
    +
    dev_t dev;
    +
    uint64_t refcount; /* protected by lo->mutex */
    +
    };
    +
    +
    enum {
    +
    CACHE_NEVER,
    +
    CACHE_NORMAL,
    +
    CACHE_ALWAYS,
    +
    };
    +
    +
    struct lo_data {
    +
    pthread_mutex_t mutex;
    +
    int debug;
    +
    int writeback;
    +
    int flock;
    +
    int xattr;
    +
    char *source;
    +
    double timeout;
    +
    int cache;
    +
    int timeout_set;
    +
    struct lo_inode root; /* protected by lo->mutex */
    +
    };
    +
    +
    static const struct fuse_opt lo_opts[] = {
    +
    { "writeback",
    +
    offsetof(struct lo_data, writeback), 1 },
    +
    { "no_writeback",
    +
    offsetof(struct lo_data, writeback), 0 },
    +
    { "source=%s",
    +
    offsetof(struct lo_data, source), 0 },
    +
    { "flock",
    +
    offsetof(struct lo_data, flock), 1 },
    +
    { "no_flock",
    +
    offsetof(struct lo_data, flock), 0 },
    +
    { "xattr",
    +
    offsetof(struct lo_data, xattr), 1 },
    +
    { "no_xattr",
    +
    offsetof(struct lo_data, xattr), 0 },
    +
    { "timeout=%lf",
    +
    offsetof(struct lo_data, timeout), 0 },
    +
    { "timeout=",
    +
    offsetof(struct lo_data, timeout_set), 1 },
    +
    { "cache=never",
    +
    offsetof(struct lo_data, cache), CACHE_NEVER },
    +
    { "cache=auto",
    +
    offsetof(struct lo_data, cache), CACHE_NORMAL },
    +
    { "cache=always",
    +
    offsetof(struct lo_data, cache), CACHE_ALWAYS },
    +
    + +
    };
    +
    +
    static void passthrough_ll_help(void)
    +
    {
    +
    printf(
    +
    " -o writeback Enable writeback\n"
    +
    " -o no_writeback Disable write back\n"
    +
    " -o source=/home/dir Source directory to be mounted\n"
    +
    " -o flock Enable flock\n"
    +
    " -o no_flock Disable flock\n"
    +
    " -o xattr Enable xattr\n"
    +
    " -o no_xattr Disable xattr\n"
    +
    " -o timeout=1.0 Caching timeout\n"
    +
    " -o timeout=0/1 Timeout is set\n"
    +
    " -o cache=never Disable cache\n"
    +
    " -o cache=auto Auto enable cache\n"
    +
    " -o cache=always Cache always\n");
    +
    }
    +
    +
    static struct lo_data *lo_data(fuse_req_t req)
    +
    {
    +
    return (struct lo_data *) fuse_req_userdata(req);
    +
    }
    +
    +
    static struct lo_inode *lo_inode(fuse_req_t req, fuse_ino_t ino)
    +
    {
    +
    if (ino == FUSE_ROOT_ID)
    +
    return &lo_data(req)->root;
    +
    else
    +
    return (struct lo_inode *) (uintptr_t) ino;
    +
    }
    +
    +
    static int lo_fd(fuse_req_t req, fuse_ino_t ino)
    +
    {
    +
    return lo_inode(req, ino)->fd;
    +
    }
    +
    +
    static bool lo_debug(fuse_req_t req)
    +
    {
    +
    return lo_data(req)->debug != 0;
    +
    }
    +
    +
    static void lo_init(void *userdata,
    +
    struct fuse_conn_info *conn)
    +
    {
    +
    struct lo_data *lo = (struct lo_data *)userdata;
    +
    bool has_flag;
    +
    +
    if (lo->writeback) {
    + +
    if (lo->debug && has_flag)
    +
    fuse_log(FUSE_LOG_DEBUG,
    +
    "lo_init: activating writeback\n");
    +
    }
    +
    if (lo->flock && conn->capable & FUSE_CAP_FLOCK_LOCKS) {
    + +
    if (lo->debug && has_flag)
    +
    fuse_log(FUSE_LOG_DEBUG,
    +
    "lo_init: activating flock locks\n");
    +
    }
    +
    +
    /* Disable the receiving and processing of FUSE_INTERRUPT requests */
    +
    conn->no_interrupt = 1;
    +
    }
    +
    +
    static void lo_destroy(void *userdata)
    +
    {
    +
    struct lo_data *lo = (struct lo_data*) userdata;
    +
    +
    while (lo->root.next != &lo->root) {
    +
    struct lo_inode* next = lo->root.next;
    +
    lo->root.next = next->next;
    +
    close(next->fd);
    +
    free(next);
    +
    }
    +
    }
    +
    +
    static void lo_getattr(fuse_req_t req, fuse_ino_t ino,
    +
    struct fuse_file_info *fi)
    +
    {
    +
    int res;
    +
    struct stat buf;
    +
    struct lo_data *lo = lo_data(req);
    +
    int fd = fi ? fi->fh : lo_fd(req, ino);
    +
    +
    (void) fi;
    +
    +
    res = fstatat(fd, "", &buf, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW);
    +
    if (res == -1)
    +
    return (void) fuse_reply_err(req, errno);
    +
    +
    fuse_reply_attr(req, &buf, lo->timeout);
    +
    }
    +
    +
    static void lo_setattr(fuse_req_t req, fuse_ino_t ino, struct stat *attr,
    +
    int valid, struct fuse_file_info *fi)
    +
    {
    +
    int saverr;
    +
    char procname[64];
    +
    struct lo_inode *inode = lo_inode(req, ino);
    +
    int ifd = inode->fd;
    +
    int res;
    +
    +
    if (valid & FUSE_SET_ATTR_MODE) {
    +
    if (fi) {
    +
    res = fchmod(fi->fh, attr->st_mode);
    +
    } else {
    +
    sprintf(procname, "/proc/self/fd/%i", ifd);
    +
    res = chmod(procname, attr->st_mode);
    +
    }
    +
    if (res == -1)
    +
    goto out_err;
    +
    }
    +
    if (valid & (FUSE_SET_ATTR_UID | FUSE_SET_ATTR_GID)) {
    +
    uid_t uid = (valid & FUSE_SET_ATTR_UID) ?
    +
    attr->st_uid : (uid_t) -1;
    +
    gid_t gid = (valid & FUSE_SET_ATTR_GID) ?
    +
    attr->st_gid : (gid_t) -1;
    +
    +
    res = fchownat(ifd, "", uid, gid,
    +
    AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW);
    +
    if (res == -1)
    +
    goto out_err;
    +
    }
    +
    if (valid & FUSE_SET_ATTR_SIZE) {
    +
    if (fi) {
    +
    res = ftruncate(fi->fh, attr->st_size);
    +
    } else {
    +
    sprintf(procname, "/proc/self/fd/%i", ifd);
    +
    res = truncate(procname, attr->st_size);
    +
    }
    +
    if (res == -1)
    +
    goto out_err;
    +
    }
    +
    if (valid & (FUSE_SET_ATTR_ATIME | FUSE_SET_ATTR_MTIME)) {
    +
    struct timespec tv[2];
    +
    +
    tv[0].tv_sec = 0;
    +
    tv[1].tv_sec = 0;
    +
    tv[0].tv_nsec = UTIME_OMIT;
    +
    tv[1].tv_nsec = UTIME_OMIT;
    +
    +
    if (valid & FUSE_SET_ATTR_ATIME_NOW)
    +
    tv[0].tv_nsec = UTIME_NOW;
    +
    else if (valid & FUSE_SET_ATTR_ATIME)
    +
    tv[0] = attr->st_atim;
    +
    +
    if (valid & FUSE_SET_ATTR_MTIME_NOW)
    +
    tv[1].tv_nsec = UTIME_NOW;
    +
    else if (valid & FUSE_SET_ATTR_MTIME)
    +
    tv[1] = attr->st_mtim;
    +
    +
    if (fi)
    +
    res = futimens(fi->fh, tv);
    +
    else {
    +
    sprintf(procname, "/proc/self/fd/%i", ifd);
    +
    res = utimensat(AT_FDCWD, procname, tv, 0);
    +
    }
    +
    if (res == -1)
    +
    goto out_err;
    +
    }
    +
    +
    return lo_getattr(req, ino, fi);
    +
    +
    out_err:
    +
    saverr = errno;
    +
    fuse_reply_err(req, saverr);
    +
    }
    +
    +
    static struct lo_inode *lo_find(struct lo_data *lo, struct stat *st)
    +
    {
    +
    struct lo_inode *p;
    +
    struct lo_inode *ret = NULL;
    +
    +
    pthread_mutex_lock(&lo->mutex);
    +
    for (p = lo->root.next; p != &lo->root; p = p->next) {
    +
    if (p->ino == st->st_ino && p->dev == st->st_dev) {
    +
    assert(p->refcount > 0);
    +
    ret = p;
    +
    ret->refcount++;
    +
    break;
    +
    }
    +
    }
    +
    pthread_mutex_unlock(&lo->mutex);
    +
    return ret;
    +
    }
    +
    +
    +
    static struct lo_inode *create_new_inode(int fd, struct fuse_entry_param *e, struct lo_data* lo)
    +
    {
    +
    struct lo_inode *inode = NULL;
    +
    struct lo_inode *prev, *next;
    +
    +
    inode = calloc(1, sizeof(struct lo_inode));
    +
    if (!inode)
    +
    return NULL;
    +
    +
    inode->refcount = 1;
    +
    inode->fd = fd;
    +
    inode->ino = e->attr.st_ino;
    +
    inode->dev = e->attr.st_dev;
    +
    +
    pthread_mutex_lock(&lo->mutex);
    +
    prev = &lo->root;
    +
    next = prev->next;
    +
    next->prev = inode;
    +
    inode->next = next;
    +
    inode->prev = prev;
    +
    prev->next = inode;
    +
    pthread_mutex_unlock(&lo->mutex);
    +
    return inode;
    +
    }
    +
    +
    static int fill_entry_param_new_inode(fuse_req_t req, fuse_ino_t parent, int fd, struct fuse_entry_param *e)
    +
    {
    +
    int res;
    +
    struct lo_data *lo = lo_data(req);
    +
    +
    memset(e, 0, sizeof(*e));
    +
    e->attr_timeout = lo->timeout;
    +
    e->entry_timeout = lo->timeout;
    +
    +
    res = fstatat(fd, "", &e->attr, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW);
    +
    if (res == -1)
    +
    return errno;
    +
    +
    e->ino = (uintptr_t) create_new_inode(dup(fd), e, lo);
    +
    +
    if (lo_debug(req))
    +
    fuse_log(FUSE_LOG_DEBUG, " %lli/%d -> %lli\n",
    +
    (unsigned long long) parent, fd, (unsigned long long) e->ino);
    +
    +
    return 0;
    +
    +
    }
    +
    +
    static int lo_do_lookup(fuse_req_t req, fuse_ino_t parent, const char *name,
    +
    struct fuse_entry_param *e)
    +
    {
    +
    int newfd;
    +
    int res;
    +
    int saverr;
    +
    struct lo_data *lo = lo_data(req);
    +
    struct lo_inode *inode;
    +
    +
    memset(e, 0, sizeof(*e));
    +
    e->attr_timeout = lo->timeout;
    +
    e->entry_timeout = lo->timeout;
    +
    +
    newfd = openat(lo_fd(req, parent), name, O_PATH | O_NOFOLLOW);
    +
    if (newfd == -1)
    +
    goto out_err;
    +
    +
    res = fstatat(newfd, "", &e->attr, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW);
    +
    if (res == -1)
    +
    goto out_err;
    +
    +
    inode = lo_find(lo_data(req), &e->attr);
    +
    if (inode) {
    +
    close(newfd);
    +
    newfd = -1;
    +
    } else {
    +
    inode = create_new_inode(newfd, e, lo);
    +
    if (!inode)
    +
    goto out_err;
    +
    }
    +
    e->ino = (uintptr_t) inode;
    +
    +
    if (lo_debug(req))
    +
    fuse_log(FUSE_LOG_DEBUG, " %lli/%s -> %lli\n",
    +
    (unsigned long long) parent, name, (unsigned long long) e->ino);
    +
    +
    return 0;
    +
    +
    out_err:
    +
    saverr = errno;
    +
    if (newfd != -1)
    +
    close(newfd);
    +
    return saverr;
    +
    }
    +
    +
    static void lo_lookup(fuse_req_t req, fuse_ino_t parent, const char *name)
    +
    {
    +
    struct fuse_entry_param e;
    +
    int err;
    +
    +
    if (lo_debug(req))
    +
    fuse_log(FUSE_LOG_DEBUG, "lo_lookup(parent=%" PRIu64 ", name=%s)\n",
    +
    parent, name);
    +
    +
    err = lo_do_lookup(req, parent, name, &e);
    +
    if (err)
    +
    fuse_reply_err(req, err);
    +
    else
    +
    fuse_reply_entry(req, &e);
    +
    }
    +
    +
    static void lo_mknod_symlink(fuse_req_t req, fuse_ino_t parent,
    +
    const char *name, mode_t mode, dev_t rdev,
    +
    const char *link)
    +
    {
    +
    int res;
    +
    int saverr;
    +
    struct lo_inode *dir = lo_inode(req, parent);
    +
    struct fuse_entry_param e;
    +
    +
    res = mknod_wrapper(dir->fd, name, link, mode, rdev);
    +
    +
    saverr = errno;
    +
    if (res == -1)
    +
    goto out;
    +
    +
    saverr = lo_do_lookup(req, parent, name, &e);
    +
    if (saverr)
    +
    goto out;
    +
    +
    if (lo_debug(req))
    +
    fuse_log(FUSE_LOG_DEBUG, " %lli/%s -> %lli\n",
    +
    (unsigned long long) parent, name, (unsigned long long) e.ino);
    +
    +
    fuse_reply_entry(req, &e);
    +
    return;
    +
    +
    out:
    +
    fuse_reply_err(req, saverr);
    +
    }
    +
    +
    static void lo_mknod(fuse_req_t req, fuse_ino_t parent,
    +
    const char *name, mode_t mode, dev_t rdev)
    +
    {
    +
    lo_mknod_symlink(req, parent, name, mode, rdev, NULL);
    +
    }
    +
    +
    static void lo_mkdir(fuse_req_t req, fuse_ino_t parent, const char *name,
    +
    mode_t mode)
    +
    {
    +
    lo_mknod_symlink(req, parent, name, S_IFDIR | mode, 0, NULL);
    +
    }
    +
    +
    static void lo_symlink(fuse_req_t req, const char *link,
    +
    fuse_ino_t parent, const char *name)
    +
    {
    +
    lo_mknod_symlink(req, parent, name, S_IFLNK, 0, link);
    +
    }
    +
    +
    static void lo_link(fuse_req_t req, fuse_ino_t ino, fuse_ino_t parent,
    +
    const char *name)
    +
    {
    +
    int res;
    +
    struct lo_data *lo = lo_data(req);
    +
    struct lo_inode *inode = lo_inode(req, ino);
    +
    struct fuse_entry_param e;
    +
    char procname[64];
    +
    int saverr;
    +
    +
    memset(&e, 0, sizeof(struct fuse_entry_param));
    +
    e.attr_timeout = lo->timeout;
    +
    e.entry_timeout = lo->timeout;
    +
    +
    sprintf(procname, "/proc/self/fd/%i", inode->fd);
    +
    res = linkat(AT_FDCWD, procname, lo_fd(req, parent), name,
    +
    AT_SYMLINK_FOLLOW);
    +
    if (res == -1)
    +
    goto out_err;
    +
    +
    res = fstatat(inode->fd, "", &e.attr, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW);
    +
    if (res == -1)
    +
    goto out_err;
    +
    +
    pthread_mutex_lock(&lo->mutex);
    +
    inode->refcount++;
    +
    pthread_mutex_unlock(&lo->mutex);
    +
    e.ino = (uintptr_t) inode;
    +
    +
    if (lo_debug(req))
    +
    fuse_log(FUSE_LOG_DEBUG, " %lli/%s -> %lli\n",
    +
    (unsigned long long) parent, name,
    +
    (unsigned long long) e.ino);
    +
    +
    fuse_reply_entry(req, &e);
    +
    return;
    +
    +
    out_err:
    +
    saverr = errno;
    +
    fuse_reply_err(req, saverr);
    +
    }
    +
    +
    static void lo_rmdir(fuse_req_t req, fuse_ino_t parent, const char *name)
    +
    {
    +
    int res;
    +
    +
    res = unlinkat(lo_fd(req, parent), name, AT_REMOVEDIR);
    +
    +
    fuse_reply_err(req, res == -1 ? errno : 0);
    +
    }
    +
    +
    static void lo_rename(fuse_req_t req, fuse_ino_t parent, const char *name,
    +
    fuse_ino_t newparent, const char *newname,
    +
    unsigned int flags)
    +
    {
    +
    int res;
    +
    +
    if (flags) {
    +
    fuse_reply_err(req, EINVAL);
    +
    return;
    +
    }
    +
    +
    res = renameat(lo_fd(req, parent), name,
    +
    lo_fd(req, newparent), newname);
    +
    +
    fuse_reply_err(req, res == -1 ? errno : 0);
    +
    }
    +
    +
    static void lo_unlink(fuse_req_t req, fuse_ino_t parent, const char *name)
    +
    {
    +
    int res;
    +
    +
    res = unlinkat(lo_fd(req, parent), name, 0);
    +
    +
    fuse_reply_err(req, res == -1 ? errno : 0);
    +
    }
    +
    +
    static void unref_inode(struct lo_data *lo, struct lo_inode *inode, uint64_t n)
    +
    {
    +
    if (!inode)
    +
    return;
    +
    +
    pthread_mutex_lock(&lo->mutex);
    +
    assert(inode->refcount >= n);
    +
    inode->refcount -= n;
    +
    if (!inode->refcount) {
    +
    struct lo_inode *prev, *next;
    +
    +
    prev = inode->prev;
    +
    next = inode->next;
    +
    next->prev = prev;
    +
    prev->next = next;
    +
    +
    pthread_mutex_unlock(&lo->mutex);
    +
    close(inode->fd);
    +
    free(inode);
    +
    +
    } else {
    +
    pthread_mutex_unlock(&lo->mutex);
    +
    }
    +
    }
    +
    +
    static void lo_forget_one(fuse_req_t req, fuse_ino_t ino, uint64_t nlookup)
    +
    {
    +
    struct lo_data *lo = lo_data(req);
    +
    struct lo_inode *inode = lo_inode(req, ino);
    +
    +
    if (lo_debug(req)) {
    +
    fuse_log(FUSE_LOG_DEBUG, " forget %lli %lli -%lli\n",
    +
    (unsigned long long) ino,
    +
    (unsigned long long) inode->refcount,
    +
    (unsigned long long) nlookup);
    +
    }
    +
    +
    unref_inode(lo, inode, nlookup);
    +
    }
    +
    +
    static void lo_forget(fuse_req_t req, fuse_ino_t ino, uint64_t nlookup)
    +
    {
    +
    lo_forget_one(req, ino, nlookup);
    + +
    }
    +
    +
    static void lo_forget_multi(fuse_req_t req, size_t count,
    +
    struct fuse_forget_data *forgets)
    +
    {
    +
    int i;
    +
    +
    for (i = 0; i < count; i++)
    +
    lo_forget_one(req, forgets[i].ino, forgets[i].nlookup);
    + +
    }
    +
    +
    static void lo_readlink(fuse_req_t req, fuse_ino_t ino)
    +
    {
    +
    char buf[PATH_MAX + 1];
    +
    int res;
    +
    +
    res = readlinkat(lo_fd(req, ino), "", buf, sizeof(buf));
    +
    if (res == -1)
    +
    return (void) fuse_reply_err(req, errno);
    +
    +
    if (res == sizeof(buf))
    +
    return (void) fuse_reply_err(req, ENAMETOOLONG);
    +
    +
    buf[res] = '\0';
    +
    + +
    }
    +
    +
    struct lo_dirp {
    +
    DIR *dp;
    +
    struct dirent *entry;
    +
    off_t offset;
    +
    };
    +
    +
    static struct lo_dirp *lo_dirp(struct fuse_file_info *fi)
    +
    {
    +
    return (struct lo_dirp *) (uintptr_t) fi->fh;
    +
    }
    +
    +
    static void lo_opendir(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
    +
    {
    +
    int error = ENOMEM;
    +
    struct lo_data *lo = lo_data(req);
    +
    struct lo_dirp *d;
    +
    int fd = -1;
    +
    +
    d = calloc(1, sizeof(struct lo_dirp));
    +
    if (d == NULL)
    +
    goto out_err;
    +
    +
    fd = openat(lo_fd(req, ino), ".", O_RDONLY);
    +
    if (fd == -1)
    +
    goto out_errno;
    +
    +
    d->dp = fdopendir(fd);
    +
    if (d->dp == NULL)
    +
    goto out_errno;
    +
    +
    d->offset = 0;
    +
    d->entry = NULL;
    +
    +
    fi->fh = (uintptr_t) d;
    +
    if (lo->cache != CACHE_NEVER)
    +
    fi->cache_readdir = 1;
    +
    if (lo->cache == CACHE_ALWAYS)
    +
    fi->keep_cache = 1;
    +
    fuse_reply_open(req, fi);
    +
    return;
    +
    +
    out_errno:
    +
    error = errno;
    +
    out_err:
    +
    if (d) {
    +
    if (fd != -1)
    +
    close(fd);
    +
    free(d);
    +
    }
    +
    fuse_reply_err(req, error);
    +
    }
    +
    +
    static int is_dot_or_dotdot(const char *name)
    +
    {
    +
    return name[0] == '.' && (name[1] == '\0' ||
    +
    (name[1] == '.' && name[2] == '\0'));
    +
    }
    +
    +
    static void lo_do_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
    +
    off_t offset, struct fuse_file_info *fi, int plus)
    +
    {
    +
    struct lo_dirp *d = lo_dirp(fi);
    +
    char *buf;
    +
    char *p;
    +
    size_t rem = size;
    +
    int err;
    +
    +
    (void) ino;
    +
    +
    buf = calloc(1, size);
    +
    if (!buf) {
    +
    err = ENOMEM;
    +
    goto error;
    +
    }
    +
    p = buf;
    +
    +
    if (offset != d->offset) {
    +
    seekdir(d->dp, offset);
    +
    d->entry = NULL;
    +
    d->offset = offset;
    +
    }
    +
    while (1) {
    +
    size_t entsize;
    +
    off_t nextoff;
    +
    const char *name;
    +
    +
    if (!d->entry) {
    +
    errno = 0;
    +
    d->entry = readdir(d->dp);
    +
    if (!d->entry) {
    +
    if (errno) { // Error
    +
    err = errno;
    +
    goto error;
    +
    } else { // End of stream
    +
    break;
    +
    }
    +
    }
    +
    }
    +
    nextoff = d->entry->d_off;
    +
    name = d->entry->d_name;
    +
    fuse_ino_t entry_ino = 0;
    +
    if (plus) {
    +
    struct fuse_entry_param e;
    +
    if (is_dot_or_dotdot(name)) {
    +
    e = (struct fuse_entry_param) {
    +
    .attr.st_ino = d->entry->d_ino,
    +
    .attr.st_mode = d->entry->d_type << 12,
    +
    };
    +
    } else {
    +
    err = lo_do_lookup(req, ino, name, &e);
    +
    if (err)
    +
    goto error;
    +
    entry_ino = e.ino;
    +
    }
    +
    +
    entsize = fuse_add_direntry_plus(req, p, rem, name,
    +
    &e, nextoff);
    +
    } else {
    +
    struct stat st = {
    +
    .st_ino = d->entry->d_ino,
    +
    .st_mode = d->entry->d_type << 12,
    +
    };
    +
    entsize = fuse_add_direntry(req, p, rem, name,
    +
    &st, nextoff);
    +
    }
    +
    if (entsize > rem) {
    +
    if (entry_ino != 0)
    +
    lo_forget_one(req, entry_ino, 1);
    +
    break;
    +
    }
    +
    +
    p += entsize;
    +
    rem -= entsize;
    +
    +
    d->entry = NULL;
    +
    d->offset = nextoff;
    +
    }
    +
    +
    err = 0;
    +
    error:
    +
    // If there's an error, we can only signal it if we haven't stored
    +
    // any entries yet - otherwise we'd end up with wrong lookup
    +
    // counts for the entries that are already in the buffer. So we
    +
    // return what we've collected until that point.
    +
    if (err && rem == size)
    +
    fuse_reply_err(req, err);
    +
    else
    +
    fuse_reply_buf(req, buf, size - rem);
    +
    free(buf);
    +
    }
    +
    +
    static void lo_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
    +
    off_t offset, struct fuse_file_info *fi)
    +
    {
    +
    lo_do_readdir(req, ino, size, offset, fi, 0);
    +
    }
    +
    +
    static void lo_readdirplus(fuse_req_t req, fuse_ino_t ino, size_t size,
    +
    off_t offset, struct fuse_file_info *fi)
    +
    {
    +
    lo_do_readdir(req, ino, size, offset, fi, 1);
    +
    }
    +
    +
    static void lo_releasedir(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
    +
    {
    +
    struct lo_dirp *d = lo_dirp(fi);
    +
    (void) ino;
    +
    closedir(d->dp);
    +
    free(d);
    +
    fuse_reply_err(req, 0);
    +
    }
    +
    +
    static void lo_tmpfile(fuse_req_t req, fuse_ino_t parent,
    +
    mode_t mode, struct fuse_file_info *fi)
    +
    {
    +
    int fd;
    +
    struct lo_data *lo = lo_data(req);
    +
    struct fuse_entry_param e;
    +
    int err;
    +
    +
    if (lo_debug(req))
    +
    fuse_log(FUSE_LOG_DEBUG, "lo_tmpfile(parent=%" PRIu64 ")\n",
    +
    parent);
    +
    +
    fd = openat(lo_fd(req, parent), ".",
    +
    (fi->flags | O_TMPFILE) & ~O_NOFOLLOW, mode);
    +
    if (fd == -1)
    +
    return (void) fuse_reply_err(req, errno);
    +
    +
    fi->fh = fd;
    +
    if (lo->cache == CACHE_NEVER)
    +
    fi->direct_io = 1;
    +
    else if (lo->cache == CACHE_ALWAYS)
    +
    fi->keep_cache = 1;
    +
    +
    /* parallel_direct_writes feature depends on direct_io features.
    +
    To make parallel_direct_writes valid, need set fi->direct_io
    +
    in current function. */
    + +
    +
    err = fill_entry_param_new_inode(req, parent, fd, &e);
    +
    if (err)
    +
    fuse_reply_err(req, err);
    +
    else
    +
    fuse_reply_create(req, &e, fi);
    +
    }
    +
    +
    static void lo_create(fuse_req_t req, fuse_ino_t parent, const char *name,
    +
    mode_t mode, struct fuse_file_info *fi)
    +
    {
    +
    int fd;
    +
    struct lo_data *lo = lo_data(req);
    +
    struct fuse_entry_param e;
    +
    int err;
    +
    +
    if (lo_debug(req))
    +
    fuse_log(FUSE_LOG_DEBUG, "lo_create(parent=%" PRIu64 ", name=%s)\n",
    +
    parent, name);
    +
    +
    fd = openat(lo_fd(req, parent), name,
    +
    (fi->flags | O_CREAT) & ~O_NOFOLLOW, mode);
    +
    if (fd == -1)
    +
    return (void) fuse_reply_err(req, errno);
    +
    +
    fi->fh = fd;
    +
    if (lo->cache == CACHE_NEVER)
    +
    fi->direct_io = 1;
    +
    else if (lo->cache == CACHE_ALWAYS)
    +
    fi->keep_cache = 1;
    +
    +
    /* parallel_direct_writes feature depends on direct_io features.
    +
    To make parallel_direct_writes valid, need set fi->direct_io
    +
    in current function. */
    + +
    +
    err = lo_do_lookup(req, parent, name, &e);
    +
    if (err)
    +
    fuse_reply_err(req, err);
    +
    else
    +
    fuse_reply_create(req, &e, fi);
    +
    }
    +
    +
    static void lo_fsyncdir(fuse_req_t req, fuse_ino_t ino, int datasync,
    +
    struct fuse_file_info *fi)
    +
    {
    +
    int res;
    +
    int fd = dirfd(lo_dirp(fi)->dp);
    +
    (void) ino;
    +
    if (datasync)
    +
    res = fdatasync(fd);
    +
    else
    +
    res = fsync(fd);
    +
    fuse_reply_err(req, res == -1 ? errno : 0);
    +
    }
    +
    +
    static void lo_open(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
    +
    {
    +
    int fd;
    +
    char buf[64];
    +
    struct lo_data *lo = lo_data(req);
    +
    +
    if (lo_debug(req))
    +
    fuse_log(FUSE_LOG_DEBUG, "lo_open(ino=%" PRIu64 ", flags=%d)\n",
    +
    ino, fi->flags);
    +
    +
    /* With writeback cache, kernel may send read requests even
    +
    when userspace opened write-only */
    +
    if (lo->writeback && (fi->flags & O_ACCMODE) == O_WRONLY) {
    +
    fi->flags &= ~O_ACCMODE;
    +
    fi->flags |= O_RDWR;
    +
    }
    +
    +
    /* With writeback cache, O_APPEND is handled by the kernel.
    +
    This breaks atomicity (since the file may change in the
    +
    underlying filesystem, so that the kernel's idea of the
    +
    end of the file isn't accurate anymore). In this example,
    +
    we just accept that. A more rigorous filesystem may want
    +
    to return an error here */
    +
    if (lo->writeback && (fi->flags & O_APPEND))
    +
    fi->flags &= ~O_APPEND;
    +
    +
    sprintf(buf, "/proc/self/fd/%i", lo_fd(req, ino));
    +
    fd = open(buf, fi->flags & ~O_NOFOLLOW);
    +
    if (fd == -1)
    +
    return (void) fuse_reply_err(req, errno);
    +
    +
    fi->fh = fd;
    +
    if (lo->cache == CACHE_NEVER)
    +
    fi->direct_io = 1;
    +
    else if (lo->cache == CACHE_ALWAYS)
    +
    fi->keep_cache = 1;
    +
    +
    /* Enable direct_io when open has flags O_DIRECT to enjoy the feature
    +
    parallel_direct_writes (i.e., to get a shared lock, not exclusive lock,
    +
    for writes to the same file in the kernel). */
    +
    if (fi->flags & O_DIRECT)
    +
    fi->direct_io = 1;
    +
    +
    /* parallel_direct_writes feature depends on direct_io features.
    +
    To make parallel_direct_writes valid, need set fi->direct_io
    +
    in current function. */
    + +
    +
    fuse_reply_open(req, fi);
    +
    }
    +
    +
    static void lo_release(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
    +
    {
    +
    (void) ino;
    +
    +
    close(fi->fh);
    +
    fuse_reply_err(req, 0);
    +
    }
    +
    +
    static void lo_flush(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
    +
    {
    +
    int res;
    +
    (void) ino;
    +
    res = close(dup(fi->fh));
    +
    fuse_reply_err(req, res == -1 ? errno : 0);
    +
    }
    +
    +
    static void lo_fsync(fuse_req_t req, fuse_ino_t ino, int datasync,
    +
    struct fuse_file_info *fi)
    +
    {
    +
    int res;
    +
    (void) ino;
    +
    if (datasync)
    +
    res = fdatasync(fi->fh);
    +
    else
    +
    res = fsync(fi->fh);
    +
    fuse_reply_err(req, res == -1 ? errno : 0);
    +
    }
    +
    +
    static void lo_read(fuse_req_t req, fuse_ino_t ino, size_t size,
    +
    off_t offset, struct fuse_file_info *fi)
    +
    {
    +
    struct fuse_bufvec buf = FUSE_BUFVEC_INIT(size);
    +
    +
    if (lo_debug(req))
    +
    fuse_log(FUSE_LOG_DEBUG, "lo_read(ino=%" PRIu64 ", size=%zd, "
    +
    "off=%lu)\n", ino, size, (unsigned long) offset);
    +
    + +
    buf.buf[0].fd = fi->fh;
    +
    buf.buf[0].pos = offset;
    +
    + +
    }
    +
    +
    static void lo_write_buf(fuse_req_t req, fuse_ino_t ino,
    +
    struct fuse_bufvec *in_buf, off_t off,
    +
    struct fuse_file_info *fi)
    +
    {
    +
    (void) ino;
    +
    ssize_t res;
    +
    struct fuse_bufvec out_buf = FUSE_BUFVEC_INIT(fuse_buf_size(in_buf));
    +
    + +
    out_buf.buf[0].fd = fi->fh;
    +
    out_buf.buf[0].pos = off;
    +
    +
    if (lo_debug(req))
    +
    fuse_log(FUSE_LOG_DEBUG, "lo_write(ino=%" PRIu64 ", size=%zd, off=%jd)\n",
    +
    ino, out_buf.buf[0].size, (intmax_t) off);
    +
    +
    res = fuse_buf_copy(&out_buf, in_buf, 0);
    +
    if(res < 0)
    +
    fuse_reply_err(req, -res);
    +
    else
    +
    fuse_reply_write(req, (size_t) res);
    +
    }
    +
    +
    static void lo_statfs(fuse_req_t req, fuse_ino_t ino)
    +
    {
    +
    int res;
    +
    struct statvfs stbuf;
    +
    +
    res = fstatvfs(lo_fd(req, ino), &stbuf);
    +
    if (res == -1)
    +
    fuse_reply_err(req, errno);
    +
    else
    +
    fuse_reply_statfs(req, &stbuf);
    +
    }
    +
    +
    static void lo_fallocate(fuse_req_t req, fuse_ino_t ino, int mode,
    +
    off_t offset, off_t length, struct fuse_file_info *fi)
    +
    {
    +
    int err;
    +
    (void) ino;
    +
    +
    err = -do_fallocate(fi->fh, mode, offset, length);
    +
    +
    fuse_reply_err(req, err);
    +
    }
    +
    +
    static void lo_flock(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi,
    +
    int op)
    +
    {
    +
    int res;
    +
    (void) ino;
    +
    +
    res = flock(fi->fh, op);
    +
    +
    fuse_reply_err(req, res == -1 ? errno : 0);
    +
    }
    +
    +
    static void lo_getxattr(fuse_req_t req, fuse_ino_t ino, const char *name,
    +
    size_t size)
    +
    {
    +
    char *value = NULL;
    +
    char procname[64];
    +
    struct lo_inode *inode = lo_inode(req, ino);
    +
    ssize_t ret;
    +
    int saverr;
    +
    +
    saverr = ENOSYS;
    +
    if (!lo_data(req)->xattr)
    +
    goto out;
    +
    +
    if (lo_debug(req)) {
    +
    fuse_log(FUSE_LOG_DEBUG, "lo_getxattr(ino=%" PRIu64 ", name=%s size=%zd)\n",
    +
    ino, name, size);
    +
    }
    +
    +
    sprintf(procname, "/proc/self/fd/%i", inode->fd);
    +
    +
    if (size) {
    +
    value = malloc(size);
    +
    if (!value)
    +
    goto out_err;
    +
    +
    ret = getxattr(procname, name, value, size);
    +
    if (ret == -1)
    +
    goto out_err;
    +
    saverr = 0;
    +
    if (ret == 0)
    +
    goto out;
    +
    +
    fuse_reply_buf(req, value, ret);
    +
    } else {
    +
    ret = getxattr(procname, name, NULL, 0);
    +
    if (ret == -1)
    +
    goto out_err;
    +
    +
    fuse_reply_xattr(req, ret);
    +
    }
    +
    out_free:
    +
    free(value);
    +
    return;
    +
    +
    out_err:
    +
    saverr = errno;
    +
    out:
    +
    fuse_reply_err(req, saverr);
    +
    goto out_free;
    +
    }
    +
    +
    static void lo_listxattr(fuse_req_t req, fuse_ino_t ino, size_t size)
    +
    {
    +
    char *value = NULL;
    +
    char procname[64];
    +
    struct lo_inode *inode = lo_inode(req, ino);
    +
    ssize_t ret;
    +
    int saverr;
    +
    +
    saverr = ENOSYS;
    +
    if (!lo_data(req)->xattr)
    +
    goto out;
    +
    +
    if (lo_debug(req)) {
    +
    fuse_log(FUSE_LOG_DEBUG, "lo_listxattr(ino=%" PRIu64 ", size=%zd)\n",
    +
    ino, size);
    +
    }
    +
    +
    sprintf(procname, "/proc/self/fd/%i", inode->fd);
    +
    +
    if (size) {
    +
    value = malloc(size);
    +
    if (!value)
    +
    goto out_err;
    +
    +
    ret = listxattr(procname, value, size);
    +
    if (ret == -1)
    +
    goto out_err;
    +
    saverr = 0;
    +
    if (ret == 0)
    +
    goto out;
    +
    +
    fuse_reply_buf(req, value, ret);
    +
    } else {
    +
    ret = listxattr(procname, NULL, 0);
    +
    if (ret == -1)
    +
    goto out_err;
    +
    +
    fuse_reply_xattr(req, ret);
    +
    }
    +
    out_free:
    +
    free(value);
    +
    return;
    +
    +
    out_err:
    +
    saverr = errno;
    +
    out:
    +
    fuse_reply_err(req, saverr);
    +
    goto out_free;
    +
    }
    +
    +
    static void lo_setxattr(fuse_req_t req, fuse_ino_t ino, const char *name,
    +
    const char *value, size_t size, int flags)
    +
    {
    +
    char procname[64];
    +
    struct lo_inode *inode = lo_inode(req, ino);
    +
    ssize_t ret;
    +
    int saverr;
    +
    +
    saverr = ENOSYS;
    +
    if (!lo_data(req)->xattr)
    +
    goto out;
    +
    +
    if (lo_debug(req)) {
    +
    fuse_log(FUSE_LOG_DEBUG, "lo_setxattr(ino=%" PRIu64 ", name=%s value=%s size=%zd)\n",
    +
    ino, name, value, size);
    +
    }
    +
    +
    sprintf(procname, "/proc/self/fd/%i", inode->fd);
    +
    +
    ret = setxattr(procname, name, value, size, flags);
    +
    saverr = ret == -1 ? errno : 0;
    +
    +
    out:
    +
    fuse_reply_err(req, saverr);
    +
    }
    +
    +
    static void lo_removexattr(fuse_req_t req, fuse_ino_t ino, const char *name)
    +
    {
    +
    char procname[64];
    +
    struct lo_inode *inode = lo_inode(req, ino);
    +
    ssize_t ret;
    +
    int saverr;
    +
    +
    saverr = ENOSYS;
    +
    if (!lo_data(req)->xattr)
    +
    goto out;
    +
    +
    if (lo_debug(req)) {
    +
    fuse_log(FUSE_LOG_DEBUG, "lo_removexattr(ino=%" PRIu64 ", name=%s)\n",
    +
    ino, name);
    +
    }
    +
    +
    sprintf(procname, "/proc/self/fd/%i", inode->fd);
    +
    +
    ret = removexattr(procname, name);
    +
    saverr = ret == -1 ? errno : 0;
    +
    +
    out:
    +
    fuse_reply_err(req, saverr);
    +
    }
    +
    +
    #ifdef HAVE_COPY_FILE_RANGE
    +
    static void lo_copy_file_range(fuse_req_t req, fuse_ino_t ino_in, off_t off_in,
    +
    struct fuse_file_info *fi_in,
    +
    fuse_ino_t ino_out, off_t off_out,
    +
    struct fuse_file_info *fi_out, size_t len,
    +
    int flags)
    +
    {
    +
    ssize_t res;
    +
    +
    if (lo_debug(req))
    +
    fuse_log(FUSE_LOG_DEBUG,
    +
    "%s(ino=%lld fd=%lld off=%jd ino=%lld fd=%lld off=%jd, size=%zd, flags=0x%x)\n",
    +
    __func__, (unsigned long long)ino_in,
    +
    (unsigned long long)fi_in->fh,
    +
    (intmax_t) off_in, (unsigned long long)ino_out,
    +
    (unsigned long long)fi_out->fh, (intmax_t) off_out,
    +
    len, flags);
    +
    +
    res = copy_file_range(fi_in->fh, &off_in, fi_out->fh, &off_out, len,
    +
    flags);
    +
    if (res < 0)
    +
    fuse_reply_err(req, errno);
    +
    else
    +
    fuse_reply_write(req, res);
    +
    }
    +
    #endif
    +
    +
    static void lo_lseek(fuse_req_t req, fuse_ino_t ino, off_t off, int whence,
    +
    struct fuse_file_info *fi)
    +
    {
    +
    off_t res;
    +
    +
    (void)ino;
    +
    res = lseek(fi->fh, off, whence);
    +
    if (res != -1)
    +
    fuse_reply_lseek(req, res);
    +
    else
    +
    fuse_reply_err(req, errno);
    +
    }
    +
    +
    #ifdef HAVE_STATX
    +
    static void lo_statx(fuse_req_t req, fuse_ino_t ino, int flags, int mask,
    +
    struct fuse_file_info *fi)
    +
    {
    +
    struct lo_data *lo = lo_data(req);
    +
    struct statx buf;
    +
    int res;
    +
    int fd;
    +
    +
    if (fi)
    +
    fd = fi->fh;
    +
    else
    +
    fd = lo_fd(req, ino);
    +
    +
    res = statx(fd, "", flags | AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW, mask, &buf);
    +
    if (res == -1)
    +
    fuse_reply_err(req, errno);
    +
    else
    +
    fuse_reply_statx(req, 0, &buf, lo->timeout);
    +
    }
    +
    #endif
    +
    +
    static const struct fuse_lowlevel_ops lo_oper = {
    +
    .init = lo_init,
    +
    .destroy = lo_destroy,
    +
    .lookup = lo_lookup,
    +
    .mkdir = lo_mkdir,
    +
    .mknod = lo_mknod,
    +
    .symlink = lo_symlink,
    +
    .link = lo_link,
    +
    .unlink = lo_unlink,
    +
    .rmdir = lo_rmdir,
    +
    .rename = lo_rename,
    +
    .forget = lo_forget,
    +
    .forget_multi = lo_forget_multi,
    +
    .getattr = lo_getattr,
    +
    .setattr = lo_setattr,
    +
    .readlink = lo_readlink,
    +
    .opendir = lo_opendir,
    +
    .readdir = lo_readdir,
    +
    .readdirplus = lo_readdirplus,
    +
    .releasedir = lo_releasedir,
    +
    .fsyncdir = lo_fsyncdir,
    +
    .create = lo_create,
    +
    .tmpfile = lo_tmpfile,
    +
    .open = lo_open,
    +
    .release = lo_release,
    +
    .flush = lo_flush,
    +
    .fsync = lo_fsync,
    +
    .read = lo_read,
    +
    .write_buf = lo_write_buf,
    +
    .statfs = lo_statfs,
    +
    .fallocate = lo_fallocate,
    +
    .flock = lo_flock,
    +
    .getxattr = lo_getxattr,
    +
    .listxattr = lo_listxattr,
    +
    .setxattr = lo_setxattr,
    +
    .removexattr = lo_removexattr,
    +
    #ifdef HAVE_COPY_FILE_RANGE
    +
    .copy_file_range = lo_copy_file_range,
    +
    #endif
    +
    .lseek = lo_lseek,
    +
    #ifdef HAVE_STATX
    +
    .statx = lo_statx,
    +
    #endif
    +
    };
    +
    +
    int main(int argc, char *argv[])
    +
    {
    +
    struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
    +
    struct fuse_session *se;
    +
    struct fuse_cmdline_opts opts;
    +
    struct fuse_loop_config *config;
    +
    struct lo_data lo = { .debug = 0,
    +
    .writeback = 0 };
    +
    int ret = -1;
    +
    +
    /* Don't mask creation mode, kernel already did that */
    +
    umask(0);
    +
    +
    pthread_mutex_init(&lo.mutex, NULL);
    +
    lo.root.next = lo.root.prev = &lo.root;
    +
    lo.root.fd = -1;
    +
    lo.cache = CACHE_NORMAL;
    +
    +
    if (fuse_parse_cmdline(&args, &opts) != 0)
    +
    return 1;
    +
    if (opts.show_help) {
    +
    printf("usage: %s [options] <mountpoint>\n\n", argv[0]);
    + + +
    passthrough_ll_help();
    +
    ret = 0;
    +
    goto err_out1;
    +
    } else if (opts.show_version) {
    +
    printf("FUSE library version %s\n", fuse_pkgversion());
    + +
    ret = 0;
    +
    goto err_out1;
    +
    }
    +
    +
    if(opts.mountpoint == NULL) {
    +
    printf("usage: %s [options] <mountpoint>\n", argv[0]);
    +
    printf(" %s --help\n", argv[0]);
    +
    ret = 1;
    +
    goto err_out1;
    +
    }
    +
    +
    if (fuse_opt_parse(&args, &lo, lo_opts, NULL)== -1)
    +
    return 1;
    +
    +
    lo.debug = opts.debug;
    +
    lo.root.refcount = 2;
    +
    if (lo.source) {
    +
    struct stat stat;
    +
    int res;
    +
    +
    res = lstat(lo.source, &stat);
    +
    if (res == -1) {
    +
    fuse_log(FUSE_LOG_ERR, "failed to stat source (\"%s\"): %m\n",
    +
    lo.source);
    +
    exit(1);
    +
    }
    +
    if (!S_ISDIR(stat.st_mode)) {
    +
    fuse_log(FUSE_LOG_ERR, "source is not a directory\n");
    +
    exit(1);
    +
    }
    +
    +
    } else {
    +
    lo.source = strdup("/");
    +
    if(!lo.source) {
    +
    fuse_log(FUSE_LOG_ERR, "fuse: memory allocation failed\n");
    +
    exit(1);
    +
    }
    +
    }
    +
    if (!lo.timeout_set) {
    +
    switch (lo.cache) {
    +
    case CACHE_NEVER:
    +
    lo.timeout = 0.0;
    +
    break;
    +
    +
    case CACHE_NORMAL:
    +
    lo.timeout = 1.0;
    +
    break;
    +
    +
    case CACHE_ALWAYS:
    +
    lo.timeout = 86400.0;
    +
    break;
    +
    }
    +
    } else if (lo.timeout < 0) {
    +
    fuse_log(FUSE_LOG_ERR, "timeout is negative (%lf)\n",
    +
    lo.timeout);
    +
    exit(1);
    +
    }
    +
    +
    lo.root.fd = open(lo.source, O_PATH);
    +
    if (lo.root.fd == -1) {
    +
    fuse_log(FUSE_LOG_ERR, "open(\"%s\", O_PATH): %m\n",
    +
    lo.source);
    +
    exit(1);
    +
    }
    +
    +
    se = fuse_session_new(&args, &lo_oper, sizeof(lo_oper), &lo);
    +
    if (se == NULL)
    +
    goto err_out1;
    +
    + +
    goto err_out2;
    +
    +
    if (fuse_session_mount(se, opts.mountpoint) != 0)
    +
    goto err_out3;
    +
    +
    fuse_daemonize(opts.foreground);
    +
    +
    /* Block until ctrl+c or fusermount -u */
    +
    if (opts.singlethread)
    +
    ret = fuse_session_loop(se);
    +
    else {
    +
    config = fuse_loop_cfg_create();
    +
    fuse_loop_cfg_set_clone_fd(config, opts.clone_fd);
    +
    fuse_loop_cfg_set_max_threads(config, opts.max_threads);
    +
    ret = fuse_session_loop_mt(se, config);
    +
    fuse_loop_cfg_destroy(config);
    +
    config = NULL;
    +
    }
    +
    + +
    err_out3:
    + +
    err_out2:
    + +
    err_out1:
    +
    free(opts.mountpoint);
    + +
    +
    if (lo.root.fd >= 0)
    +
    close(lo.root.fd);
    +
    +
    free(lo.source);
    +
    return ret ? 1 : 0;
    +
    }
    +
    bool fuse_set_feature_flag(struct fuse_conn_info *conn, uint64_t flag)
    +
    int fuse_set_signal_handlers(struct fuse_session *se)
    +
    size_t fuse_buf_size(const struct fuse_bufvec *bufv)
    Definition buffer.c:22
    +
    #define FUSE_CAP_WRITEBACK_CACHE
    +
    @ FUSE_BUF_FD_SEEK
    +
    @ FUSE_BUF_IS_FD
    +
    ssize_t fuse_buf_copy(struct fuse_bufvec *dst, struct fuse_bufvec *src, enum fuse_buf_copy_flags flags)
    Definition buffer.c:284
    +
    const char * fuse_pkgversion(void)
    Definition fuse.c:5274
    +
    void fuse_remove_signal_handlers(struct fuse_session *se)
    +
    @ FUSE_BUF_SPLICE_MOVE
    +
    int fuse_daemonize(int foreground)
    Definition helper.c:253
    +
    #define FUSE_CAP_FLOCK_LOCKS
    +
    void fuse_log(enum fuse_log_level level, const char *fmt,...) __attribute__((format(printf
    + +
    void fuse_session_destroy(struct fuse_session *se)
    +
    int fuse_reply_data(fuse_req_t req, struct fuse_bufvec *bufv, enum fuse_buf_copy_flags flags)
    +
    int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi)
    +
    int fuse_reply_err(fuse_req_t req, int err)
    +
    void * fuse_req_userdata(fuse_req_t req)
    +
    int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
    +
    struct fuse_req * fuse_req_t
    +
    size_t fuse_add_direntry_plus(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct fuse_entry_param *e, off_t off)
    +
    int fuse_reply_readlink(fuse_req_t req, const char *link)
    +
    int fuse_session_loop(struct fuse_session *se)
    Definition fuse_loop.c:19
    +
    int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
    +
    void fuse_session_unmount(struct fuse_session *se)
    +
    void fuse_cmdline_help(void)
    Definition helper.c:130
    +
    void fuse_reply_none(fuse_req_t req)
    +
    void fuse_lowlevel_help(void)
    +
    int fuse_reply_statfs(fuse_req_t req, const struct statvfs *stbuf)
    +
    int fuse_reply_write(fuse_req_t req, size_t count)
    +
    int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
    +
    int fuse_reply_create(fuse_req_t req, const struct fuse_entry_param *e, const struct fuse_file_info *fi)
    +
    int fuse_reply_lseek(fuse_req_t req, off_t off)
    +
    void fuse_lowlevel_version(void)
    +
    int fuse_reply_statx(fuse_req_t req, int flags, struct statx *statx, double attr_timeout)
    +
    uint64_t fuse_ino_t
    +
    size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct stat *stbuf, off_t off)
    +
    int fuse_reply_attr(fuse_req_t req, const struct stat *attr, double attr_timeout)
    +
    int fuse_reply_xattr(fuse_req_t req, size_t count)
    +
    void fuse_opt_free_args(struct fuse_args *args)
    Definition fuse_opt.c:34
    +
    int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
    Definition fuse_opt.c:398
    +
    #define FUSE_ARGS_INIT(argc, argv)
    Definition fuse_opt.h:123
    +
    #define FUSE_OPT_END
    Definition fuse_opt.h:104
    + + +
    char ** argv
    Definition fuse_opt.h:114
    +
    enum fuse_buf_flags flags
    + +
    off_t pos
    +
    size_t size
    + + +
    struct fuse_buf buf[1]
    + + +
    uint32_t no_interrupt
    +
    uint32_t capable
    +
    +
    double entry_timeout
    +
    fuse_ino_t ino
    +
    double attr_timeout
    +
    struct stat attr
    + + +
    uint32_t cache_readdir
    Definition fuse_common.h:89
    +
    uint32_t parallel_direct_writes
    Definition fuse_common.h:97
    +
    uint32_t direct_io
    Definition fuse_common.h:63
    +
    uint32_t keep_cache
    Definition fuse_common.h:69
    + + + + +
    +

    Definition in file passthrough_ll.c.

    +
    + + +
    + + diff --git a/doc/html/passthrough__ll_8c_source.html b/doc/html/passthrough__ll_8c_source.html new file mode 100644 index 0000000..dfa6a20 --- /dev/null +++ b/doc/html/passthrough__ll_8c_source.html @@ -0,0 +1,1525 @@ + + + + + + + +libfuse: example/passthrough_ll.c Source File + + + + + + + +
    +
    + + + + + + +
    +
    libfuse +
    +
    +
    + + + + + + + + +
    +
    +
    +
    passthrough_ll.c
    +
    +
    +Go to the documentation of this file.
    1/*
    +
    2 FUSE: Filesystem in Userspace
    +
    3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
    +
    4
    +
    5 This program can be distributed under the terms of the GNU GPLv2.
    +
    6 See the file GPL2.txt.
    +
    7*/
    +
    8
    +
    32
    +
    33#define _GNU_SOURCE
    +
    34#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 18)
    +
    35
    +
    36#include <fuse_lowlevel.h>
    +
    37#include <unistd.h>
    +
    38#include <stdlib.h>
    +
    39#include <stdio.h>
    +
    40#include <stddef.h>
    +
    41#include <stdbool.h>
    +
    42#include <string.h>
    +
    43#include <limits.h>
    +
    44#include <dirent.h>
    +
    45#include <assert.h>
    +
    46#include <errno.h>
    +
    47#include <inttypes.h>
    +
    48#include <pthread.h>
    +
    49#include <sys/file.h>
    +
    50#include <sys/xattr.h>
    +
    51
    +
    52#include "passthrough_helpers.h"
    +
    53
    +
    54/* We are re-using pointers to our `struct lo_inode` and `struct
    +
    55 lo_dirp` elements as inodes. This means that we must be able to
    +
    56 store uintptr_t values in a fuse_ino_t variable. The following
    +
    57 incantation checks this condition at compile time. */
    +
    58#if defined(__GNUC__) && (__GNUC__ > 4 || __GNUC__ == 4 && __GNUC_MINOR__ >= 6) && !defined __cplusplus
    +
    59_Static_assert(sizeof(fuse_ino_t) >= sizeof(uintptr_t),
    +
    60 "fuse_ino_t too small to hold uintptr_t values!");
    +
    61#else
    +
    62struct _uintptr_to_must_hold_fuse_ino_t_dummy_struct \
    +
    63 { unsigned _uintptr_to_must_hold_fuse_ino_t:
    +
    64 ((sizeof(fuse_ino_t) >= sizeof(uintptr_t)) ? 1 : -1); };
    +
    65#endif
    +
    66
    +
    67struct lo_inode {
    +
    68 struct lo_inode *next; /* protected by lo->mutex */
    +
    69 struct lo_inode *prev; /* protected by lo->mutex */
    +
    70 int fd;
    +
    71 ino_t ino;
    +
    72 dev_t dev;
    +
    73 uint64_t refcount; /* protected by lo->mutex */
    +
    74};
    +
    75
    +
    76enum {
    +
    77 CACHE_NEVER,
    +
    78 CACHE_NORMAL,
    +
    79 CACHE_ALWAYS,
    +
    80};
    +
    81
    +
    82struct lo_data {
    +
    83 pthread_mutex_t mutex;
    +
    84 int debug;
    +
    85 int writeback;
    +
    86 int flock;
    +
    87 int xattr;
    +
    88 char *source;
    +
    89 double timeout;
    +
    90 int cache;
    +
    91 int timeout_set;
    +
    92 struct lo_inode root; /* protected by lo->mutex */
    +
    93};
    +
    94
    +
    95static const struct fuse_opt lo_opts[] = {
    +
    96 { "writeback",
    +
    97 offsetof(struct lo_data, writeback), 1 },
    +
    98 { "no_writeback",
    +
    99 offsetof(struct lo_data, writeback), 0 },
    +
    100 { "source=%s",
    +
    101 offsetof(struct lo_data, source), 0 },
    +
    102 { "flock",
    +
    103 offsetof(struct lo_data, flock), 1 },
    +
    104 { "no_flock",
    +
    105 offsetof(struct lo_data, flock), 0 },
    +
    106 { "xattr",
    +
    107 offsetof(struct lo_data, xattr), 1 },
    +
    108 { "no_xattr",
    +
    109 offsetof(struct lo_data, xattr), 0 },
    +
    110 { "timeout=%lf",
    +
    111 offsetof(struct lo_data, timeout), 0 },
    +
    112 { "timeout=",
    +
    113 offsetof(struct lo_data, timeout_set), 1 },
    +
    114 { "cache=never",
    +
    115 offsetof(struct lo_data, cache), CACHE_NEVER },
    +
    116 { "cache=auto",
    +
    117 offsetof(struct lo_data, cache), CACHE_NORMAL },
    +
    118 { "cache=always",
    +
    119 offsetof(struct lo_data, cache), CACHE_ALWAYS },
    +
    120
    + +
    122};
    +
    123
    +
    124static void passthrough_ll_help(void)
    +
    125{
    +
    126 printf(
    +
    127" -o writeback Enable writeback\n"
    +
    128" -o no_writeback Disable write back\n"
    +
    129" -o source=/home/dir Source directory to be mounted\n"
    +
    130" -o flock Enable flock\n"
    +
    131" -o no_flock Disable flock\n"
    +
    132" -o xattr Enable xattr\n"
    +
    133" -o no_xattr Disable xattr\n"
    +
    134" -o timeout=1.0 Caching timeout\n"
    +
    135" -o timeout=0/1 Timeout is set\n"
    +
    136" -o cache=never Disable cache\n"
    +
    137" -o cache=auto Auto enable cache\n"
    +
    138" -o cache=always Cache always\n");
    +
    139}
    +
    140
    +
    141static struct lo_data *lo_data(fuse_req_t req)
    +
    142{
    +
    143 return (struct lo_data *) fuse_req_userdata(req);
    +
    144}
    +
    145
    +
    146static struct lo_inode *lo_inode(fuse_req_t req, fuse_ino_t ino)
    +
    147{
    +
    148 if (ino == FUSE_ROOT_ID)
    +
    149 return &lo_data(req)->root;
    +
    150 else
    +
    151 return (struct lo_inode *) (uintptr_t) ino;
    +
    152}
    +
    153
    +
    154static int lo_fd(fuse_req_t req, fuse_ino_t ino)
    +
    155{
    +
    156 return lo_inode(req, ino)->fd;
    +
    157}
    +
    158
    +
    159static bool lo_debug(fuse_req_t req)
    +
    160{
    +
    161 return lo_data(req)->debug != 0;
    +
    162}
    +
    163
    +
    164static void lo_init(void *userdata,
    +
    165 struct fuse_conn_info *conn)
    +
    166{
    +
    167 struct lo_data *lo = (struct lo_data *)userdata;
    +
    168 bool has_flag;
    +
    169
    +
    170 if (lo->writeback) {
    + +
    172 if (lo->debug && has_flag)
    +
    173 fuse_log(FUSE_LOG_DEBUG,
    +
    174 "lo_init: activating writeback\n");
    +
    175 }
    +
    176 if (lo->flock && conn->capable & FUSE_CAP_FLOCK_LOCKS) {
    + +
    178 if (lo->debug && has_flag)
    +
    179 fuse_log(FUSE_LOG_DEBUG,
    +
    180 "lo_init: activating flock locks\n");
    +
    181 }
    +
    182
    +
    183 /* Disable the receiving and processing of FUSE_INTERRUPT requests */
    +
    184 conn->no_interrupt = 1;
    +
    185}
    +
    186
    +
    187static void lo_destroy(void *userdata)
    +
    188{
    +
    189 struct lo_data *lo = (struct lo_data*) userdata;
    +
    190
    +
    191 while (lo->root.next != &lo->root) {
    +
    192 struct lo_inode* next = lo->root.next;
    +
    193 lo->root.next = next->next;
    +
    194 close(next->fd);
    +
    195 free(next);
    +
    196 }
    +
    197}
    +
    198
    +
    199static void lo_getattr(fuse_req_t req, fuse_ino_t ino,
    +
    200 struct fuse_file_info *fi)
    +
    201{
    +
    202 int res;
    +
    203 struct stat buf;
    +
    204 struct lo_data *lo = lo_data(req);
    +
    205 int fd = fi ? fi->fh : lo_fd(req, ino);
    +
    206
    +
    207 (void) fi;
    +
    208
    +
    209 res = fstatat(fd, "", &buf, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW);
    +
    210 if (res == -1)
    +
    211 return (void) fuse_reply_err(req, errno);
    +
    212
    +
    213 fuse_reply_attr(req, &buf, lo->timeout);
    +
    214}
    +
    215
    +
    216static void lo_setattr(fuse_req_t req, fuse_ino_t ino, struct stat *attr,
    +
    217 int valid, struct fuse_file_info *fi)
    +
    218{
    +
    219 int saverr;
    +
    220 char procname[64];
    +
    221 struct lo_inode *inode = lo_inode(req, ino);
    +
    222 int ifd = inode->fd;
    +
    223 int res;
    +
    224
    +
    225 if (valid & FUSE_SET_ATTR_MODE) {
    +
    226 if (fi) {
    +
    227 res = fchmod(fi->fh, attr->st_mode);
    +
    228 } else {
    +
    229 sprintf(procname, "/proc/self/fd/%i", ifd);
    +
    230 res = chmod(procname, attr->st_mode);
    +
    231 }
    +
    232 if (res == -1)
    +
    233 goto out_err;
    +
    234 }
    +
    235 if (valid & (FUSE_SET_ATTR_UID | FUSE_SET_ATTR_GID)) {
    +
    236 uid_t uid = (valid & FUSE_SET_ATTR_UID) ?
    +
    237 attr->st_uid : (uid_t) -1;
    +
    238 gid_t gid = (valid & FUSE_SET_ATTR_GID) ?
    +
    239 attr->st_gid : (gid_t) -1;
    +
    240
    +
    241 res = fchownat(ifd, "", uid, gid,
    +
    242 AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW);
    +
    243 if (res == -1)
    +
    244 goto out_err;
    +
    245 }
    +
    246 if (valid & FUSE_SET_ATTR_SIZE) {
    +
    247 if (fi) {
    +
    248 res = ftruncate(fi->fh, attr->st_size);
    +
    249 } else {
    +
    250 sprintf(procname, "/proc/self/fd/%i", ifd);
    +
    251 res = truncate(procname, attr->st_size);
    +
    252 }
    +
    253 if (res == -1)
    +
    254 goto out_err;
    +
    255 }
    +
    256 if (valid & (FUSE_SET_ATTR_ATIME | FUSE_SET_ATTR_MTIME)) {
    +
    257 struct timespec tv[2];
    +
    258
    +
    259 tv[0].tv_sec = 0;
    +
    260 tv[1].tv_sec = 0;
    +
    261 tv[0].tv_nsec = UTIME_OMIT;
    +
    262 tv[1].tv_nsec = UTIME_OMIT;
    +
    263
    +
    264 if (valid & FUSE_SET_ATTR_ATIME_NOW)
    +
    265 tv[0].tv_nsec = UTIME_NOW;
    +
    266 else if (valid & FUSE_SET_ATTR_ATIME)
    +
    267 tv[0] = attr->st_atim;
    +
    268
    +
    269 if (valid & FUSE_SET_ATTR_MTIME_NOW)
    +
    270 tv[1].tv_nsec = UTIME_NOW;
    +
    271 else if (valid & FUSE_SET_ATTR_MTIME)
    +
    272 tv[1] = attr->st_mtim;
    +
    273
    +
    274 if (fi)
    +
    275 res = futimens(fi->fh, tv);
    +
    276 else {
    +
    277 sprintf(procname, "/proc/self/fd/%i", ifd);
    +
    278 res = utimensat(AT_FDCWD, procname, tv, 0);
    +
    279 }
    +
    280 if (res == -1)
    +
    281 goto out_err;
    +
    282 }
    +
    283
    +
    284 return lo_getattr(req, ino, fi);
    +
    285
    +
    286out_err:
    +
    287 saverr = errno;
    +
    288 fuse_reply_err(req, saverr);
    +
    289}
    +
    290
    +
    291static struct lo_inode *lo_find(struct lo_data *lo, struct stat *st)
    +
    292{
    +
    293 struct lo_inode *p;
    +
    294 struct lo_inode *ret = NULL;
    +
    295
    +
    296 pthread_mutex_lock(&lo->mutex);
    +
    297 for (p = lo->root.next; p != &lo->root; p = p->next) {
    +
    298 if (p->ino == st->st_ino && p->dev == st->st_dev) {
    +
    299 assert(p->refcount > 0);
    +
    300 ret = p;
    +
    301 ret->refcount++;
    +
    302 break;
    +
    303 }
    +
    304 }
    +
    305 pthread_mutex_unlock(&lo->mutex);
    +
    306 return ret;
    +
    307}
    +
    308
    +
    309
    +
    310static struct lo_inode *create_new_inode(int fd, struct fuse_entry_param *e, struct lo_data* lo)
    +
    311{
    +
    312 struct lo_inode *inode = NULL;
    +
    313 struct lo_inode *prev, *next;
    +
    314
    +
    315 inode = calloc(1, sizeof(struct lo_inode));
    +
    316 if (!inode)
    +
    317 return NULL;
    +
    318
    +
    319 inode->refcount = 1;
    +
    320 inode->fd = fd;
    +
    321 inode->ino = e->attr.st_ino;
    +
    322 inode->dev = e->attr.st_dev;
    +
    323
    +
    324 pthread_mutex_lock(&lo->mutex);
    +
    325 prev = &lo->root;
    +
    326 next = prev->next;
    +
    327 next->prev = inode;
    +
    328 inode->next = next;
    +
    329 inode->prev = prev;
    +
    330 prev->next = inode;
    +
    331 pthread_mutex_unlock(&lo->mutex);
    +
    332 return inode;
    +
    333}
    +
    334
    +
    335static int fill_entry_param_new_inode(fuse_req_t req, fuse_ino_t parent, int fd, struct fuse_entry_param *e)
    +
    336{
    +
    337 int res;
    +
    338 struct lo_data *lo = lo_data(req);
    +
    339
    +
    340 memset(e, 0, sizeof(*e));
    +
    341 e->attr_timeout = lo->timeout;
    +
    342 e->entry_timeout = lo->timeout;
    +
    343
    +
    344 res = fstatat(fd, "", &e->attr, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW);
    +
    345 if (res == -1)
    +
    346 return errno;
    +
    347
    +
    348 e->ino = (uintptr_t) create_new_inode(dup(fd), e, lo);
    +
    349
    +
    350 if (lo_debug(req))
    +
    351 fuse_log(FUSE_LOG_DEBUG, " %lli/%d -> %lli\n",
    +
    352 (unsigned long long) parent, fd, (unsigned long long) e->ino);
    +
    353
    +
    354 return 0;
    +
    355
    +
    356}
    +
    357
    +
    358static int lo_do_lookup(fuse_req_t req, fuse_ino_t parent, const char *name,
    +
    359 struct fuse_entry_param *e)
    +
    360{
    +
    361 int newfd;
    +
    362 int res;
    +
    363 int saverr;
    +
    364 struct lo_data *lo = lo_data(req);
    +
    365 struct lo_inode *inode;
    +
    366
    +
    367 memset(e, 0, sizeof(*e));
    +
    368 e->attr_timeout = lo->timeout;
    +
    369 e->entry_timeout = lo->timeout;
    +
    370
    +
    371 newfd = openat(lo_fd(req, parent), name, O_PATH | O_NOFOLLOW);
    +
    372 if (newfd == -1)
    +
    373 goto out_err;
    +
    374
    +
    375 res = fstatat(newfd, "", &e->attr, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW);
    +
    376 if (res == -1)
    +
    377 goto out_err;
    +
    378
    +
    379 inode = lo_find(lo_data(req), &e->attr);
    +
    380 if (inode) {
    +
    381 close(newfd);
    +
    382 newfd = -1;
    +
    383 } else {
    +
    384 inode = create_new_inode(newfd, e, lo);
    +
    385 if (!inode)
    +
    386 goto out_err;
    +
    387 }
    +
    388 e->ino = (uintptr_t) inode;
    +
    389
    +
    390 if (lo_debug(req))
    +
    391 fuse_log(FUSE_LOG_DEBUG, " %lli/%s -> %lli\n",
    +
    392 (unsigned long long) parent, name, (unsigned long long) e->ino);
    +
    393
    +
    394 return 0;
    +
    395
    +
    396out_err:
    +
    397 saverr = errno;
    +
    398 if (newfd != -1)
    +
    399 close(newfd);
    +
    400 return saverr;
    +
    401}
    +
    402
    +
    403static void lo_lookup(fuse_req_t req, fuse_ino_t parent, const char *name)
    +
    404{
    +
    405 struct fuse_entry_param e;
    +
    406 int err;
    +
    407
    +
    408 if (lo_debug(req))
    +
    409 fuse_log(FUSE_LOG_DEBUG, "lo_lookup(parent=%" PRIu64 ", name=%s)\n",
    +
    410 parent, name);
    +
    411
    +
    412 err = lo_do_lookup(req, parent, name, &e);
    +
    413 if (err)
    +
    414 fuse_reply_err(req, err);
    +
    415 else
    +
    416 fuse_reply_entry(req, &e);
    +
    417}
    +
    418
    +
    419static void lo_mknod_symlink(fuse_req_t req, fuse_ino_t parent,
    +
    420 const char *name, mode_t mode, dev_t rdev,
    +
    421 const char *link)
    +
    422{
    +
    423 int res;
    +
    424 int saverr;
    +
    425 struct lo_inode *dir = lo_inode(req, parent);
    +
    426 struct fuse_entry_param e;
    +
    427
    +
    428 res = mknod_wrapper(dir->fd, name, link, mode, rdev);
    +
    429
    +
    430 saverr = errno;
    +
    431 if (res == -1)
    +
    432 goto out;
    +
    433
    +
    434 saverr = lo_do_lookup(req, parent, name, &e);
    +
    435 if (saverr)
    +
    436 goto out;
    +
    437
    +
    438 if (lo_debug(req))
    +
    439 fuse_log(FUSE_LOG_DEBUG, " %lli/%s -> %lli\n",
    +
    440 (unsigned long long) parent, name, (unsigned long long) e.ino);
    +
    441
    +
    442 fuse_reply_entry(req, &e);
    +
    443 return;
    +
    444
    +
    445out:
    +
    446 fuse_reply_err(req, saverr);
    +
    447}
    +
    448
    +
    449static void lo_mknod(fuse_req_t req, fuse_ino_t parent,
    +
    450 const char *name, mode_t mode, dev_t rdev)
    +
    451{
    +
    452 lo_mknod_symlink(req, parent, name, mode, rdev, NULL);
    +
    453}
    +
    454
    +
    455static void lo_mkdir(fuse_req_t req, fuse_ino_t parent, const char *name,
    +
    456 mode_t mode)
    +
    457{
    +
    458 lo_mknod_symlink(req, parent, name, S_IFDIR | mode, 0, NULL);
    +
    459}
    +
    460
    +
    461static void lo_symlink(fuse_req_t req, const char *link,
    +
    462 fuse_ino_t parent, const char *name)
    +
    463{
    +
    464 lo_mknod_symlink(req, parent, name, S_IFLNK, 0, link);
    +
    465}
    +
    466
    +
    467static void lo_link(fuse_req_t req, fuse_ino_t ino, fuse_ino_t parent,
    +
    468 const char *name)
    +
    469{
    +
    470 int res;
    +
    471 struct lo_data *lo = lo_data(req);
    +
    472 struct lo_inode *inode = lo_inode(req, ino);
    +
    473 struct fuse_entry_param e;
    +
    474 char procname[64];
    +
    475 int saverr;
    +
    476
    +
    477 memset(&e, 0, sizeof(struct fuse_entry_param));
    +
    478 e.attr_timeout = lo->timeout;
    +
    479 e.entry_timeout = lo->timeout;
    +
    480
    +
    481 sprintf(procname, "/proc/self/fd/%i", inode->fd);
    +
    482 res = linkat(AT_FDCWD, procname, lo_fd(req, parent), name,
    +
    483 AT_SYMLINK_FOLLOW);
    +
    484 if (res == -1)
    +
    485 goto out_err;
    +
    486
    +
    487 res = fstatat(inode->fd, "", &e.attr, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW);
    +
    488 if (res == -1)
    +
    489 goto out_err;
    +
    490
    +
    491 pthread_mutex_lock(&lo->mutex);
    +
    492 inode->refcount++;
    +
    493 pthread_mutex_unlock(&lo->mutex);
    +
    494 e.ino = (uintptr_t) inode;
    +
    495
    +
    496 if (lo_debug(req))
    +
    497 fuse_log(FUSE_LOG_DEBUG, " %lli/%s -> %lli\n",
    +
    498 (unsigned long long) parent, name,
    +
    499 (unsigned long long) e.ino);
    +
    500
    +
    501 fuse_reply_entry(req, &e);
    +
    502 return;
    +
    503
    +
    504out_err:
    +
    505 saverr = errno;
    +
    506 fuse_reply_err(req, saverr);
    +
    507}
    +
    508
    +
    509static void lo_rmdir(fuse_req_t req, fuse_ino_t parent, const char *name)
    +
    510{
    +
    511 int res;
    +
    512
    +
    513 res = unlinkat(lo_fd(req, parent), name, AT_REMOVEDIR);
    +
    514
    +
    515 fuse_reply_err(req, res == -1 ? errno : 0);
    +
    516}
    +
    517
    +
    518static void lo_rename(fuse_req_t req, fuse_ino_t parent, const char *name,
    +
    519 fuse_ino_t newparent, const char *newname,
    +
    520 unsigned int flags)
    +
    521{
    +
    522 int res;
    +
    523
    +
    524 if (flags) {
    +
    525 fuse_reply_err(req, EINVAL);
    +
    526 return;
    +
    527 }
    +
    528
    +
    529 res = renameat(lo_fd(req, parent), name,
    +
    530 lo_fd(req, newparent), newname);
    +
    531
    +
    532 fuse_reply_err(req, res == -1 ? errno : 0);
    +
    533}
    +
    534
    +
    535static void lo_unlink(fuse_req_t req, fuse_ino_t parent, const char *name)
    +
    536{
    +
    537 int res;
    +
    538
    +
    539 res = unlinkat(lo_fd(req, parent), name, 0);
    +
    540
    +
    541 fuse_reply_err(req, res == -1 ? errno : 0);
    +
    542}
    +
    543
    +
    544static void unref_inode(struct lo_data *lo, struct lo_inode *inode, uint64_t n)
    +
    545{
    +
    546 if (!inode)
    +
    547 return;
    +
    548
    +
    549 pthread_mutex_lock(&lo->mutex);
    +
    550 assert(inode->refcount >= n);
    +
    551 inode->refcount -= n;
    +
    552 if (!inode->refcount) {
    +
    553 struct lo_inode *prev, *next;
    +
    554
    +
    555 prev = inode->prev;
    +
    556 next = inode->next;
    +
    557 next->prev = prev;
    +
    558 prev->next = next;
    +
    559
    +
    560 pthread_mutex_unlock(&lo->mutex);
    +
    561 close(inode->fd);
    +
    562 free(inode);
    +
    563
    +
    564 } else {
    +
    565 pthread_mutex_unlock(&lo->mutex);
    +
    566 }
    +
    567}
    +
    568
    +
    569static void lo_forget_one(fuse_req_t req, fuse_ino_t ino, uint64_t nlookup)
    +
    570{
    +
    571 struct lo_data *lo = lo_data(req);
    +
    572 struct lo_inode *inode = lo_inode(req, ino);
    +
    573
    +
    574 if (lo_debug(req)) {
    +
    575 fuse_log(FUSE_LOG_DEBUG, " forget %lli %lli -%lli\n",
    +
    576 (unsigned long long) ino,
    +
    577 (unsigned long long) inode->refcount,
    +
    578 (unsigned long long) nlookup);
    +
    579 }
    +
    580
    +
    581 unref_inode(lo, inode, nlookup);
    +
    582}
    +
    583
    +
    584static void lo_forget(fuse_req_t req, fuse_ino_t ino, uint64_t nlookup)
    +
    585{
    +
    586 lo_forget_one(req, ino, nlookup);
    +
    587 fuse_reply_none(req);
    +
    588}
    +
    589
    +
    590static void lo_forget_multi(fuse_req_t req, size_t count,
    +
    591 struct fuse_forget_data *forgets)
    +
    592{
    +
    593 int i;
    +
    594
    +
    595 for (i = 0; i < count; i++)
    +
    596 lo_forget_one(req, forgets[i].ino, forgets[i].nlookup);
    +
    597 fuse_reply_none(req);
    +
    598}
    +
    599
    +
    600static void lo_readlink(fuse_req_t req, fuse_ino_t ino)
    +
    601{
    +
    602 char buf[PATH_MAX + 1];
    +
    603 int res;
    +
    604
    +
    605 res = readlinkat(lo_fd(req, ino), "", buf, sizeof(buf));
    +
    606 if (res == -1)
    +
    607 return (void) fuse_reply_err(req, errno);
    +
    608
    +
    609 if (res == sizeof(buf))
    +
    610 return (void) fuse_reply_err(req, ENAMETOOLONG);
    +
    611
    +
    612 buf[res] = '\0';
    +
    613
    +
    614 fuse_reply_readlink(req, buf);
    +
    615}
    +
    616
    +
    617struct lo_dirp {
    +
    618 DIR *dp;
    +
    619 struct dirent *entry;
    +
    620 off_t offset;
    +
    621};
    +
    622
    +
    623static struct lo_dirp *lo_dirp(struct fuse_file_info *fi)
    +
    624{
    +
    625 return (struct lo_dirp *) (uintptr_t) fi->fh;
    +
    626}
    +
    627
    +
    628static void lo_opendir(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
    +
    629{
    +
    630 int error = ENOMEM;
    +
    631 struct lo_data *lo = lo_data(req);
    +
    632 struct lo_dirp *d;
    +
    633 int fd = -1;
    +
    634
    +
    635 d = calloc(1, sizeof(struct lo_dirp));
    +
    636 if (d == NULL)
    +
    637 goto out_err;
    +
    638
    +
    639 fd = openat(lo_fd(req, ino), ".", O_RDONLY);
    +
    640 if (fd == -1)
    +
    641 goto out_errno;
    +
    642
    +
    643 d->dp = fdopendir(fd);
    +
    644 if (d->dp == NULL)
    +
    645 goto out_errno;
    +
    646
    +
    647 d->offset = 0;
    +
    648 d->entry = NULL;
    +
    649
    +
    650 fi->fh = (uintptr_t) d;
    +
    651 if (lo->cache != CACHE_NEVER)
    +
    652 fi->cache_readdir = 1;
    +
    653 if (lo->cache == CACHE_ALWAYS)
    +
    654 fi->keep_cache = 1;
    +
    655 fuse_reply_open(req, fi);
    +
    656 return;
    +
    657
    +
    658out_errno:
    +
    659 error = errno;
    +
    660out_err:
    +
    661 if (d) {
    +
    662 if (fd != -1)
    +
    663 close(fd);
    +
    664 free(d);
    +
    665 }
    +
    666 fuse_reply_err(req, error);
    +
    667}
    +
    668
    +
    669static int is_dot_or_dotdot(const char *name)
    +
    670{
    +
    671 return name[0] == '.' && (name[1] == '\0' ||
    +
    672 (name[1] == '.' && name[2] == '\0'));
    +
    673}
    +
    674
    +
    675static void lo_do_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
    +
    676 off_t offset, struct fuse_file_info *fi, int plus)
    +
    677{
    +
    678 struct lo_dirp *d = lo_dirp(fi);
    +
    679 char *buf;
    +
    680 char *p;
    +
    681 size_t rem = size;
    +
    682 int err;
    +
    683
    +
    684 (void) ino;
    +
    685
    +
    686 buf = calloc(1, size);
    +
    687 if (!buf) {
    +
    688 err = ENOMEM;
    +
    689 goto error;
    +
    690 }
    +
    691 p = buf;
    +
    692
    +
    693 if (offset != d->offset) {
    +
    694 seekdir(d->dp, offset);
    +
    695 d->entry = NULL;
    +
    696 d->offset = offset;
    +
    697 }
    +
    698 while (1) {
    +
    699 size_t entsize;
    +
    700 off_t nextoff;
    +
    701 const char *name;
    +
    702
    +
    703 if (!d->entry) {
    +
    704 errno = 0;
    +
    705 d->entry = readdir(d->dp);
    +
    706 if (!d->entry) {
    +
    707 if (errno) { // Error
    +
    708 err = errno;
    +
    709 goto error;
    +
    710 } else { // End of stream
    +
    711 break;
    +
    712 }
    +
    713 }
    +
    714 }
    +
    715 nextoff = d->entry->d_off;
    +
    716 name = d->entry->d_name;
    +
    717 fuse_ino_t entry_ino = 0;
    +
    718 if (plus) {
    +
    719 struct fuse_entry_param e;
    +
    720 if (is_dot_or_dotdot(name)) {
    +
    721 e = (struct fuse_entry_param) {
    +
    722 .attr.st_ino = d->entry->d_ino,
    +
    723 .attr.st_mode = d->entry->d_type << 12,
    +
    724 };
    +
    725 } else {
    +
    726 err = lo_do_lookup(req, ino, name, &e);
    +
    727 if (err)
    +
    728 goto error;
    +
    729 entry_ino = e.ino;
    +
    730 }
    +
    731
    +
    732 entsize = fuse_add_direntry_plus(req, p, rem, name,
    +
    733 &e, nextoff);
    +
    734 } else {
    +
    735 struct stat st = {
    +
    736 .st_ino = d->entry->d_ino,
    +
    737 .st_mode = d->entry->d_type << 12,
    +
    738 };
    +
    739 entsize = fuse_add_direntry(req, p, rem, name,
    +
    740 &st, nextoff);
    +
    741 }
    +
    742 if (entsize > rem) {
    +
    743 if (entry_ino != 0)
    +
    744 lo_forget_one(req, entry_ino, 1);
    +
    745 break;
    +
    746 }
    +
    747
    +
    748 p += entsize;
    +
    749 rem -= entsize;
    +
    750
    +
    751 d->entry = NULL;
    +
    752 d->offset = nextoff;
    +
    753 }
    +
    754
    +
    755 err = 0;
    +
    756error:
    +
    757 // If there's an error, we can only signal it if we haven't stored
    +
    758 // any entries yet - otherwise we'd end up with wrong lookup
    +
    759 // counts for the entries that are already in the buffer. So we
    +
    760 // return what we've collected until that point.
    +
    761 if (err && rem == size)
    +
    762 fuse_reply_err(req, err);
    +
    763 else
    +
    764 fuse_reply_buf(req, buf, size - rem);
    +
    765 free(buf);
    +
    766}
    +
    767
    +
    768static void lo_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
    +
    769 off_t offset, struct fuse_file_info *fi)
    +
    770{
    +
    771 lo_do_readdir(req, ino, size, offset, fi, 0);
    +
    772}
    +
    773
    +
    774static void lo_readdirplus(fuse_req_t req, fuse_ino_t ino, size_t size,
    +
    775 off_t offset, struct fuse_file_info *fi)
    +
    776{
    +
    777 lo_do_readdir(req, ino, size, offset, fi, 1);
    +
    778}
    +
    779
    +
    780static void lo_releasedir(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
    +
    781{
    +
    782 struct lo_dirp *d = lo_dirp(fi);
    +
    783 (void) ino;
    +
    784 closedir(d->dp);
    +
    785 free(d);
    +
    786 fuse_reply_err(req, 0);
    +
    787}
    +
    788
    +
    789static void lo_tmpfile(fuse_req_t req, fuse_ino_t parent,
    +
    790 mode_t mode, struct fuse_file_info *fi)
    +
    791{
    +
    792 int fd;
    +
    793 struct lo_data *lo = lo_data(req);
    +
    794 struct fuse_entry_param e;
    +
    795 int err;
    +
    796
    +
    797 if (lo_debug(req))
    +
    798 fuse_log(FUSE_LOG_DEBUG, "lo_tmpfile(parent=%" PRIu64 ")\n",
    +
    799 parent);
    +
    800
    +
    801 fd = openat(lo_fd(req, parent), ".",
    +
    802 (fi->flags | O_TMPFILE) & ~O_NOFOLLOW, mode);
    +
    803 if (fd == -1)
    +
    804 return (void) fuse_reply_err(req, errno);
    +
    805
    +
    806 fi->fh = fd;
    +
    807 if (lo->cache == CACHE_NEVER)
    +
    808 fi->direct_io = 1;
    +
    809 else if (lo->cache == CACHE_ALWAYS)
    +
    810 fi->keep_cache = 1;
    +
    811
    +
    812 /* parallel_direct_writes feature depends on direct_io features.
    +
    813 To make parallel_direct_writes valid, need set fi->direct_io
    +
    814 in current function. */
    + +
    816
    +
    817 err = fill_entry_param_new_inode(req, parent, fd, &e);
    +
    818 if (err)
    +
    819 fuse_reply_err(req, err);
    +
    820 else
    +
    821 fuse_reply_create(req, &e, fi);
    +
    822}
    +
    823
    +
    824static void lo_create(fuse_req_t req, fuse_ino_t parent, const char *name,
    +
    825 mode_t mode, struct fuse_file_info *fi)
    +
    826{
    +
    827 int fd;
    +
    828 struct lo_data *lo = lo_data(req);
    +
    829 struct fuse_entry_param e;
    +
    830 int err;
    +
    831
    +
    832 if (lo_debug(req))
    +
    833 fuse_log(FUSE_LOG_DEBUG, "lo_create(parent=%" PRIu64 ", name=%s)\n",
    +
    834 parent, name);
    +
    835
    +
    836 fd = openat(lo_fd(req, parent), name,
    +
    837 (fi->flags | O_CREAT) & ~O_NOFOLLOW, mode);
    +
    838 if (fd == -1)
    +
    839 return (void) fuse_reply_err(req, errno);
    +
    840
    +
    841 fi->fh = fd;
    +
    842 if (lo->cache == CACHE_NEVER)
    +
    843 fi->direct_io = 1;
    +
    844 else if (lo->cache == CACHE_ALWAYS)
    +
    845 fi->keep_cache = 1;
    +
    846
    +
    847 /* parallel_direct_writes feature depends on direct_io features.
    +
    848 To make parallel_direct_writes valid, need set fi->direct_io
    +
    849 in current function. */
    + +
    851
    +
    852 err = lo_do_lookup(req, parent, name, &e);
    +
    853 if (err)
    +
    854 fuse_reply_err(req, err);
    +
    855 else
    +
    856 fuse_reply_create(req, &e, fi);
    +
    857}
    +
    858
    +
    859static void lo_fsyncdir(fuse_req_t req, fuse_ino_t ino, int datasync,
    +
    860 struct fuse_file_info *fi)
    +
    861{
    +
    862 int res;
    +
    863 int fd = dirfd(lo_dirp(fi)->dp);
    +
    864 (void) ino;
    +
    865 if (datasync)
    +
    866 res = fdatasync(fd);
    +
    867 else
    +
    868 res = fsync(fd);
    +
    869 fuse_reply_err(req, res == -1 ? errno : 0);
    +
    870}
    +
    871
    +
    872static void lo_open(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
    +
    873{
    +
    874 int fd;
    +
    875 char buf[64];
    +
    876 struct lo_data *lo = lo_data(req);
    +
    877
    +
    878 if (lo_debug(req))
    +
    879 fuse_log(FUSE_LOG_DEBUG, "lo_open(ino=%" PRIu64 ", flags=%d)\n",
    +
    880 ino, fi->flags);
    +
    881
    +
    882 /* With writeback cache, kernel may send read requests even
    +
    883 when userspace opened write-only */
    +
    884 if (lo->writeback && (fi->flags & O_ACCMODE) == O_WRONLY) {
    +
    885 fi->flags &= ~O_ACCMODE;
    +
    886 fi->flags |= O_RDWR;
    +
    887 }
    +
    888
    +
    889 /* With writeback cache, O_APPEND is handled by the kernel.
    +
    890 This breaks atomicity (since the file may change in the
    +
    891 underlying filesystem, so that the kernel's idea of the
    +
    892 end of the file isn't accurate anymore). In this example,
    +
    893 we just accept that. A more rigorous filesystem may want
    +
    894 to return an error here */
    +
    895 if (lo->writeback && (fi->flags & O_APPEND))
    +
    896 fi->flags &= ~O_APPEND;
    +
    897
    +
    898 sprintf(buf, "/proc/self/fd/%i", lo_fd(req, ino));
    +
    899 fd = open(buf, fi->flags & ~O_NOFOLLOW);
    +
    900 if (fd == -1)
    +
    901 return (void) fuse_reply_err(req, errno);
    +
    902
    +
    903 fi->fh = fd;
    +
    904 if (lo->cache == CACHE_NEVER)
    +
    905 fi->direct_io = 1;
    +
    906 else if (lo->cache == CACHE_ALWAYS)
    +
    907 fi->keep_cache = 1;
    +
    908
    +
    909 /* Enable direct_io when open has flags O_DIRECT to enjoy the feature
    +
    910 parallel_direct_writes (i.e., to get a shared lock, not exclusive lock,
    +
    911 for writes to the same file in the kernel). */
    +
    912 if (fi->flags & O_DIRECT)
    +
    913 fi->direct_io = 1;
    +
    914
    +
    915 /* parallel_direct_writes feature depends on direct_io features.
    +
    916 To make parallel_direct_writes valid, need set fi->direct_io
    +
    917 in current function. */
    + +
    919
    +
    920 fuse_reply_open(req, fi);
    +
    921}
    +
    922
    +
    923static void lo_release(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
    +
    924{
    +
    925 (void) ino;
    +
    926
    +
    927 close(fi->fh);
    +
    928 fuse_reply_err(req, 0);
    +
    929}
    +
    930
    +
    931static void lo_flush(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
    +
    932{
    +
    933 int res;
    +
    934 (void) ino;
    +
    935 res = close(dup(fi->fh));
    +
    936 fuse_reply_err(req, res == -1 ? errno : 0);
    +
    937}
    +
    938
    +
    939static void lo_fsync(fuse_req_t req, fuse_ino_t ino, int datasync,
    +
    940 struct fuse_file_info *fi)
    +
    941{
    +
    942 int res;
    +
    943 (void) ino;
    +
    944 if (datasync)
    +
    945 res = fdatasync(fi->fh);
    +
    946 else
    +
    947 res = fsync(fi->fh);
    +
    948 fuse_reply_err(req, res == -1 ? errno : 0);
    +
    949}
    +
    950
    +
    951static void lo_read(fuse_req_t req, fuse_ino_t ino, size_t size,
    +
    952 off_t offset, struct fuse_file_info *fi)
    +
    953{
    +
    954 struct fuse_bufvec buf = FUSE_BUFVEC_INIT(size);
    +
    955
    +
    956 if (lo_debug(req))
    +
    957 fuse_log(FUSE_LOG_DEBUG, "lo_read(ino=%" PRIu64 ", size=%zd, "
    +
    958 "off=%lu)\n", ino, size, (unsigned long) offset);
    +
    959
    + +
    961 buf.buf[0].fd = fi->fh;
    +
    962 buf.buf[0].pos = offset;
    +
    963
    + +
    965}
    +
    966
    +
    967static void lo_write_buf(fuse_req_t req, fuse_ino_t ino,
    +
    968 struct fuse_bufvec *in_buf, off_t off,
    +
    969 struct fuse_file_info *fi)
    +
    970{
    +
    971 (void) ino;
    +
    972 ssize_t res;
    +
    973 struct fuse_bufvec out_buf = FUSE_BUFVEC_INIT(fuse_buf_size(in_buf));
    +
    974
    + +
    976 out_buf.buf[0].fd = fi->fh;
    +
    977 out_buf.buf[0].pos = off;
    +
    978
    +
    979 if (lo_debug(req))
    +
    980 fuse_log(FUSE_LOG_DEBUG, "lo_write(ino=%" PRIu64 ", size=%zd, off=%jd)\n",
    +
    981 ino, out_buf.buf[0].size, (intmax_t) off);
    +
    982
    +
    983 res = fuse_buf_copy(&out_buf, in_buf, 0);
    +
    984 if(res < 0)
    +
    985 fuse_reply_err(req, -res);
    +
    986 else
    +
    987 fuse_reply_write(req, (size_t) res);
    +
    988}
    +
    989
    +
    990static void lo_statfs(fuse_req_t req, fuse_ino_t ino)
    +
    991{
    +
    992 int res;
    +
    993 struct statvfs stbuf;
    +
    994
    +
    995 res = fstatvfs(lo_fd(req, ino), &stbuf);
    +
    996 if (res == -1)
    +
    997 fuse_reply_err(req, errno);
    +
    998 else
    +
    999 fuse_reply_statfs(req, &stbuf);
    +
    1000}
    +
    1001
    +
    1002static void lo_fallocate(fuse_req_t req, fuse_ino_t ino, int mode,
    +
    1003 off_t offset, off_t length, struct fuse_file_info *fi)
    +
    1004{
    +
    1005 int err;
    +
    1006 (void) ino;
    +
    1007
    +
    1008 err = -do_fallocate(fi->fh, mode, offset, length);
    +
    1009
    +
    1010 fuse_reply_err(req, err);
    +
    1011}
    +
    1012
    +
    1013static void lo_flock(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi,
    +
    1014 int op)
    +
    1015{
    +
    1016 int res;
    +
    1017 (void) ino;
    +
    1018
    +
    1019 res = flock(fi->fh, op);
    +
    1020
    +
    1021 fuse_reply_err(req, res == -1 ? errno : 0);
    +
    1022}
    +
    1023
    +
    1024static void lo_getxattr(fuse_req_t req, fuse_ino_t ino, const char *name,
    +
    1025 size_t size)
    +
    1026{
    +
    1027 char *value = NULL;
    +
    1028 char procname[64];
    +
    1029 struct lo_inode *inode = lo_inode(req, ino);
    +
    1030 ssize_t ret;
    +
    1031 int saverr;
    +
    1032
    +
    1033 saverr = ENOSYS;
    +
    1034 if (!lo_data(req)->xattr)
    +
    1035 goto out;
    +
    1036
    +
    1037 if (lo_debug(req)) {
    +
    1038 fuse_log(FUSE_LOG_DEBUG, "lo_getxattr(ino=%" PRIu64 ", name=%s size=%zd)\n",
    +
    1039 ino, name, size);
    +
    1040 }
    +
    1041
    +
    1042 sprintf(procname, "/proc/self/fd/%i", inode->fd);
    +
    1043
    +
    1044 if (size) {
    +
    1045 value = malloc(size);
    +
    1046 if (!value)
    +
    1047 goto out_err;
    +
    1048
    +
    1049 ret = getxattr(procname, name, value, size);
    +
    1050 if (ret == -1)
    +
    1051 goto out_err;
    +
    1052 saverr = 0;
    +
    1053 if (ret == 0)
    +
    1054 goto out;
    +
    1055
    +
    1056 fuse_reply_buf(req, value, ret);
    +
    1057 } else {
    +
    1058 ret = getxattr(procname, name, NULL, 0);
    +
    1059 if (ret == -1)
    +
    1060 goto out_err;
    +
    1061
    +
    1062 fuse_reply_xattr(req, ret);
    +
    1063 }
    +
    1064out_free:
    +
    1065 free(value);
    +
    1066 return;
    +
    1067
    +
    1068out_err:
    +
    1069 saverr = errno;
    +
    1070out:
    +
    1071 fuse_reply_err(req, saverr);
    +
    1072 goto out_free;
    +
    1073}
    +
    1074
    +
    1075static void lo_listxattr(fuse_req_t req, fuse_ino_t ino, size_t size)
    +
    1076{
    +
    1077 char *value = NULL;
    +
    1078 char procname[64];
    +
    1079 struct lo_inode *inode = lo_inode(req, ino);
    +
    1080 ssize_t ret;
    +
    1081 int saverr;
    +
    1082
    +
    1083 saverr = ENOSYS;
    +
    1084 if (!lo_data(req)->xattr)
    +
    1085 goto out;
    +
    1086
    +
    1087 if (lo_debug(req)) {
    +
    1088 fuse_log(FUSE_LOG_DEBUG, "lo_listxattr(ino=%" PRIu64 ", size=%zd)\n",
    +
    1089 ino, size);
    +
    1090 }
    +
    1091
    +
    1092 sprintf(procname, "/proc/self/fd/%i", inode->fd);
    +
    1093
    +
    1094 if (size) {
    +
    1095 value = malloc(size);
    +
    1096 if (!value)
    +
    1097 goto out_err;
    +
    1098
    +
    1099 ret = listxattr(procname, value, size);
    +
    1100 if (ret == -1)
    +
    1101 goto out_err;
    +
    1102 saverr = 0;
    +
    1103 if (ret == 0)
    +
    1104 goto out;
    +
    1105
    +
    1106 fuse_reply_buf(req, value, ret);
    +
    1107 } else {
    +
    1108 ret = listxattr(procname, NULL, 0);
    +
    1109 if (ret == -1)
    +
    1110 goto out_err;
    +
    1111
    +
    1112 fuse_reply_xattr(req, ret);
    +
    1113 }
    +
    1114out_free:
    +
    1115 free(value);
    +
    1116 return;
    +
    1117
    +
    1118out_err:
    +
    1119 saverr = errno;
    +
    1120out:
    +
    1121 fuse_reply_err(req, saverr);
    +
    1122 goto out_free;
    +
    1123}
    +
    1124
    +
    1125static void lo_setxattr(fuse_req_t req, fuse_ino_t ino, const char *name,
    +
    1126 const char *value, size_t size, int flags)
    +
    1127{
    +
    1128 char procname[64];
    +
    1129 struct lo_inode *inode = lo_inode(req, ino);
    +
    1130 ssize_t ret;
    +
    1131 int saverr;
    +
    1132
    +
    1133 saverr = ENOSYS;
    +
    1134 if (!lo_data(req)->xattr)
    +
    1135 goto out;
    +
    1136
    +
    1137 if (lo_debug(req)) {
    +
    1138 fuse_log(FUSE_LOG_DEBUG, "lo_setxattr(ino=%" PRIu64 ", name=%s value=%s size=%zd)\n",
    +
    1139 ino, name, value, size);
    +
    1140 }
    +
    1141
    +
    1142 sprintf(procname, "/proc/self/fd/%i", inode->fd);
    +
    1143
    +
    1144 ret = setxattr(procname, name, value, size, flags);
    +
    1145 saverr = ret == -1 ? errno : 0;
    +
    1146
    +
    1147out:
    +
    1148 fuse_reply_err(req, saverr);
    +
    1149}
    +
    1150
    +
    1151static void lo_removexattr(fuse_req_t req, fuse_ino_t ino, const char *name)
    +
    1152{
    +
    1153 char procname[64];
    +
    1154 struct lo_inode *inode = lo_inode(req, ino);
    +
    1155 ssize_t ret;
    +
    1156 int saverr;
    +
    1157
    +
    1158 saverr = ENOSYS;
    +
    1159 if (!lo_data(req)->xattr)
    +
    1160 goto out;
    +
    1161
    +
    1162 if (lo_debug(req)) {
    +
    1163 fuse_log(FUSE_LOG_DEBUG, "lo_removexattr(ino=%" PRIu64 ", name=%s)\n",
    +
    1164 ino, name);
    +
    1165 }
    +
    1166
    +
    1167 sprintf(procname, "/proc/self/fd/%i", inode->fd);
    +
    1168
    +
    1169 ret = removexattr(procname, name);
    +
    1170 saverr = ret == -1 ? errno : 0;
    +
    1171
    +
    1172out:
    +
    1173 fuse_reply_err(req, saverr);
    +
    1174}
    +
    1175
    +
    1176#ifdef HAVE_COPY_FILE_RANGE
    +
    1177static void lo_copy_file_range(fuse_req_t req, fuse_ino_t ino_in, off_t off_in,
    +
    1178 struct fuse_file_info *fi_in,
    +
    1179 fuse_ino_t ino_out, off_t off_out,
    +
    1180 struct fuse_file_info *fi_out, size_t len,
    +
    1181 int flags)
    +
    1182{
    +
    1183 ssize_t res;
    +
    1184
    +
    1185 if (lo_debug(req))
    +
    1186 fuse_log(FUSE_LOG_DEBUG,
    +
    1187 "%s(ino=%lld fd=%lld off=%jd ino=%lld fd=%lld off=%jd, size=%zd, flags=0x%x)\n",
    +
    1188 __func__, (unsigned long long)ino_in,
    +
    1189 (unsigned long long)fi_in->fh,
    +
    1190 (intmax_t) off_in, (unsigned long long)ino_out,
    +
    1191 (unsigned long long)fi_out->fh, (intmax_t) off_out,
    +
    1192 len, flags);
    +
    1193
    +
    1194 res = copy_file_range(fi_in->fh, &off_in, fi_out->fh, &off_out, len,
    +
    1195 flags);
    +
    1196 if (res < 0)
    +
    1197 fuse_reply_err(req, errno);
    +
    1198 else
    +
    1199 fuse_reply_write(req, res);
    +
    1200}
    +
    1201#endif
    +
    1202
    +
    1203static void lo_lseek(fuse_req_t req, fuse_ino_t ino, off_t off, int whence,
    +
    1204 struct fuse_file_info *fi)
    +
    1205{
    +
    1206 off_t res;
    +
    1207
    +
    1208 (void)ino;
    +
    1209 res = lseek(fi->fh, off, whence);
    +
    1210 if (res != -1)
    +
    1211 fuse_reply_lseek(req, res);
    +
    1212 else
    +
    1213 fuse_reply_err(req, errno);
    +
    1214}
    +
    1215
    +
    1216#ifdef HAVE_STATX
    +
    1217static void lo_statx(fuse_req_t req, fuse_ino_t ino, int flags, int mask,
    +
    1218 struct fuse_file_info *fi)
    +
    1219{
    +
    1220 struct lo_data *lo = lo_data(req);
    +
    1221 struct statx buf;
    +
    1222 int res;
    +
    1223 int fd;
    +
    1224
    +
    1225 if (fi)
    +
    1226 fd = fi->fh;
    +
    1227 else
    +
    1228 fd = lo_fd(req, ino);
    +
    1229
    +
    1230 res = statx(fd, "", flags | AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW, mask, &buf);
    +
    1231 if (res == -1)
    +
    1232 fuse_reply_err(req, errno);
    +
    1233 else
    +
    1234 fuse_reply_statx(req, 0, &buf, lo->timeout);
    +
    1235}
    +
    1236#endif
    +
    1237
    +
    1238static const struct fuse_lowlevel_ops lo_oper = {
    +
    1239 .init = lo_init,
    +
    1240 .destroy = lo_destroy,
    +
    1241 .lookup = lo_lookup,
    +
    1242 .mkdir = lo_mkdir,
    +
    1243 .mknod = lo_mknod,
    +
    1244 .symlink = lo_symlink,
    +
    1245 .link = lo_link,
    +
    1246 .unlink = lo_unlink,
    +
    1247 .rmdir = lo_rmdir,
    +
    1248 .rename = lo_rename,
    +
    1249 .forget = lo_forget,
    +
    1250 .forget_multi = lo_forget_multi,
    +
    1251 .getattr = lo_getattr,
    +
    1252 .setattr = lo_setattr,
    +
    1253 .readlink = lo_readlink,
    +
    1254 .opendir = lo_opendir,
    +
    1255 .readdir = lo_readdir,
    +
    1256 .readdirplus = lo_readdirplus,
    +
    1257 .releasedir = lo_releasedir,
    +
    1258 .fsyncdir = lo_fsyncdir,
    +
    1259 .create = lo_create,
    +
    1260 .tmpfile = lo_tmpfile,
    +
    1261 .open = lo_open,
    +
    1262 .release = lo_release,
    +
    1263 .flush = lo_flush,
    +
    1264 .fsync = lo_fsync,
    +
    1265 .read = lo_read,
    +
    1266 .write_buf = lo_write_buf,
    +
    1267 .statfs = lo_statfs,
    +
    1268 .fallocate = lo_fallocate,
    +
    1269 .flock = lo_flock,
    +
    1270 .getxattr = lo_getxattr,
    +
    1271 .listxattr = lo_listxattr,
    +
    1272 .setxattr = lo_setxattr,
    +
    1273 .removexattr = lo_removexattr,
    +
    1274#ifdef HAVE_COPY_FILE_RANGE
    +
    1275 .copy_file_range = lo_copy_file_range,
    +
    1276#endif
    +
    1277 .lseek = lo_lseek,
    +
    1278#ifdef HAVE_STATX
    +
    1279 .statx = lo_statx,
    +
    1280#endif
    +
    1281};
    +
    1282
    +
    1283int main(int argc, char *argv[])
    +
    1284{
    +
    1285 struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
    +
    1286 struct fuse_session *se;
    +
    1287 struct fuse_cmdline_opts opts;
    +
    1288 struct fuse_loop_config *config;
    +
    1289 struct lo_data lo = { .debug = 0,
    +
    1290 .writeback = 0 };
    +
    1291 int ret = -1;
    +
    1292
    +
    1293 /* Don't mask creation mode, kernel already did that */
    +
    1294 umask(0);
    +
    1295
    +
    1296 pthread_mutex_init(&lo.mutex, NULL);
    +
    1297 lo.root.next = lo.root.prev = &lo.root;
    +
    1298 lo.root.fd = -1;
    +
    1299 lo.cache = CACHE_NORMAL;
    +
    1300
    +
    1301 if (fuse_parse_cmdline(&args, &opts) != 0)
    +
    1302 return 1;
    +
    1303 if (opts.show_help) {
    +
    1304 printf("usage: %s [options] <mountpoint>\n\n", argv[0]);
    + + +
    1307 passthrough_ll_help();
    +
    1308 ret = 0;
    +
    1309 goto err_out1;
    +
    1310 } else if (opts.show_version) {
    +
    1311 printf("FUSE library version %s\n", fuse_pkgversion());
    + +
    1313 ret = 0;
    +
    1314 goto err_out1;
    +
    1315 }
    +
    1316
    +
    1317 if(opts.mountpoint == NULL) {
    +
    1318 printf("usage: %s [options] <mountpoint>\n", argv[0]);
    +
    1319 printf(" %s --help\n", argv[0]);
    +
    1320 ret = 1;
    +
    1321 goto err_out1;
    +
    1322 }
    +
    1323
    +
    1324 if (fuse_opt_parse(&args, &lo, lo_opts, NULL)== -1)
    +
    1325 return 1;
    +
    1326
    +
    1327 lo.debug = opts.debug;
    +
    1328 lo.root.refcount = 2;
    +
    1329 if (lo.source) {
    +
    1330 struct stat stat;
    +
    1331 int res;
    +
    1332
    +
    1333 res = lstat(lo.source, &stat);
    +
    1334 if (res == -1) {
    +
    1335 fuse_log(FUSE_LOG_ERR, "failed to stat source (\"%s\"): %m\n",
    +
    1336 lo.source);
    +
    1337 exit(1);
    +
    1338 }
    +
    1339 if (!S_ISDIR(stat.st_mode)) {
    +
    1340 fuse_log(FUSE_LOG_ERR, "source is not a directory\n");
    +
    1341 exit(1);
    +
    1342 }
    +
    1343
    +
    1344 } else {
    +
    1345 lo.source = strdup("/");
    +
    1346 if(!lo.source) {
    +
    1347 fuse_log(FUSE_LOG_ERR, "fuse: memory allocation failed\n");
    +
    1348 exit(1);
    +
    1349 }
    +
    1350 }
    +
    1351 if (!lo.timeout_set) {
    +
    1352 switch (lo.cache) {
    +
    1353 case CACHE_NEVER:
    +
    1354 lo.timeout = 0.0;
    +
    1355 break;
    +
    1356
    +
    1357 case CACHE_NORMAL:
    +
    1358 lo.timeout = 1.0;
    +
    1359 break;
    +
    1360
    +
    1361 case CACHE_ALWAYS:
    +
    1362 lo.timeout = 86400.0;
    +
    1363 break;
    +
    1364 }
    +
    1365 } else if (lo.timeout < 0) {
    +
    1366 fuse_log(FUSE_LOG_ERR, "timeout is negative (%lf)\n",
    +
    1367 lo.timeout);
    +
    1368 exit(1);
    +
    1369 }
    +
    1370
    +
    1371 lo.root.fd = open(lo.source, O_PATH);
    +
    1372 if (lo.root.fd == -1) {
    +
    1373 fuse_log(FUSE_LOG_ERR, "open(\"%s\", O_PATH): %m\n",
    +
    1374 lo.source);
    +
    1375 exit(1);
    +
    1376 }
    +
    1377
    +
    1378 se = fuse_session_new(&args, &lo_oper, sizeof(lo_oper), &lo);
    +
    1379 if (se == NULL)
    +
    1380 goto err_out1;
    +
    1381
    +
    1382 if (fuse_set_signal_handlers(se) != 0)
    +
    1383 goto err_out2;
    +
    1384
    +
    1385 if (fuse_session_mount(se, opts.mountpoint) != 0)
    +
    1386 goto err_out3;
    +
    1387
    +
    1388 fuse_daemonize(opts.foreground);
    +
    1389
    +
    1390 /* Block until ctrl+c or fusermount -u */
    +
    1391 if (opts.singlethread)
    +
    1392 ret = fuse_session_loop(se);
    +
    1393 else {
    +
    1394 config = fuse_loop_cfg_create();
    +
    1395 fuse_loop_cfg_set_clone_fd(config, opts.clone_fd);
    +
    1396 fuse_loop_cfg_set_max_threads(config, opts.max_threads);
    +
    1397 ret = fuse_session_loop_mt(se, config);
    +
    1398 fuse_loop_cfg_destroy(config);
    +
    1399 config = NULL;
    +
    1400 }
    +
    1401
    + +
    1403err_out3:
    + +
    1405err_out2:
    + +
    1407err_out1:
    +
    1408 free(opts.mountpoint);
    +
    1409 fuse_opt_free_args(&args);
    +
    1410
    +
    1411 if (lo.root.fd >= 0)
    +
    1412 close(lo.root.fd);
    +
    1413
    +
    1414 free(lo.source);
    +
    1415 return ret ? 1 : 0;
    +
    1416}
    +
    bool fuse_set_feature_flag(struct fuse_conn_info *conn, uint64_t flag)
    +
    int fuse_set_signal_handlers(struct fuse_session *se)
    +
    size_t fuse_buf_size(const struct fuse_bufvec *bufv)
    Definition buffer.c:22
    +
    #define FUSE_CAP_WRITEBACK_CACHE
    +
    @ FUSE_BUF_FD_SEEK
    +
    @ FUSE_BUF_IS_FD
    +
    ssize_t fuse_buf_copy(struct fuse_bufvec *dst, struct fuse_bufvec *src, enum fuse_buf_copy_flags flags)
    Definition buffer.c:284
    +
    const char * fuse_pkgversion(void)
    Definition fuse.c:5274
    +
    void fuse_remove_signal_handlers(struct fuse_session *se)
    +
    @ FUSE_BUF_SPLICE_MOVE
    +
    int fuse_daemonize(int foreground)
    Definition helper.c:253
    +
    #define FUSE_CAP_FLOCK_LOCKS
    +
    void fuse_log(enum fuse_log_level level, const char *fmt,...) __attribute__((format(printf
    + +
    void fuse_session_destroy(struct fuse_session *se)
    +
    int fuse_reply_data(fuse_req_t req, struct fuse_bufvec *bufv, enum fuse_buf_copy_flags flags)
    +
    int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi)
    +
    int fuse_reply_err(fuse_req_t req, int err)
    +
    void * fuse_req_userdata(fuse_req_t req)
    +
    int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
    +
    struct fuse_req * fuse_req_t
    +
    size_t fuse_add_direntry_plus(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct fuse_entry_param *e, off_t off)
    +
    int fuse_reply_readlink(fuse_req_t req, const char *link)
    +
    int fuse_session_loop(struct fuse_session *se)
    Definition fuse_loop.c:19
    +
    int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
    +
    void fuse_session_unmount(struct fuse_session *se)
    +
    void fuse_cmdline_help(void)
    Definition helper.c:130
    +
    void fuse_reply_none(fuse_req_t req)
    +
    void fuse_lowlevel_help(void)
    +
    int fuse_reply_statfs(fuse_req_t req, const struct statvfs *stbuf)
    +
    int fuse_reply_write(fuse_req_t req, size_t count)
    +
    int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
    +
    int fuse_reply_create(fuse_req_t req, const struct fuse_entry_param *e, const struct fuse_file_info *fi)
    +
    int fuse_reply_lseek(fuse_req_t req, off_t off)
    +
    void fuse_lowlevel_version(void)
    +
    int fuse_reply_statx(fuse_req_t req, int flags, struct statx *statx, double attr_timeout)
    +
    uint64_t fuse_ino_t
    +
    size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct stat *stbuf, off_t off)
    +
    int fuse_reply_attr(fuse_req_t req, const struct stat *attr, double attr_timeout)
    +
    int fuse_reply_xattr(fuse_req_t req, size_t count)
    +
    void fuse_opt_free_args(struct fuse_args *args)
    Definition fuse_opt.c:34
    +
    int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
    Definition fuse_opt.c:398
    +
    #define FUSE_ARGS_INIT(argc, argv)
    Definition fuse_opt.h:123
    +
    #define FUSE_OPT_END
    Definition fuse_opt.h:104
    + + +
    char ** argv
    Definition fuse_opt.h:114
    +
    enum fuse_buf_flags flags
    + +
    off_t pos
    +
    size_t size
    + + +
    struct fuse_buf buf[1]
    + + +
    uint32_t no_interrupt
    +
    uint32_t capable
    +
    +
    double entry_timeout
    +
    fuse_ino_t ino
    +
    double attr_timeout
    +
    struct stat attr
    + + +
    uint32_t cache_readdir
    Definition fuse_common.h:89
    +
    uint32_t parallel_direct_writes
    Definition fuse_common.h:97
    +
    uint32_t direct_io
    Definition fuse_common.h:63
    +
    uint32_t keep_cache
    Definition fuse_common.h:69
    + + + + +
    + + +
    + + diff --git a/doc/html/poll_8c.html b/doc/html/poll_8c.html new file mode 100644 index 0000000..6d5160e --- /dev/null +++ b/doc/html/poll_8c.html @@ -0,0 +1,384 @@ + + + + + + + +libfuse: example/poll.c File Reference + + + + + + + +
    +
    + + + + + + +
    +
    libfuse +
    +
    +
    + + + + + + + + +
    +
    +
    +
    poll.c File Reference
    +
    +
    +
    #include <fuse.h>
    +#include <unistd.h>
    +#include <ctype.h>
    +#include <string.h>
    +#include <stdio.h>
    +#include <stdlib.h>
    +#include <errno.h>
    +#include <time.h>
    +#include <pthread.h>
    +#include <poll.h>
    +#include <stdbool.h>
    +
    +

    Go to the source code of this file.

    +

    Detailed Description

    +

    This example illustrates how to write a FUSE file system that supports polling for changes that don't come through the kernel. It can be tested with the poll_client.c program.

    +

    Compile with:

    gcc -Wall poll.c `pkg-config fuse3 --cflags --libs` -o poll
    +

    +Source code

    +
    /*
    +
    FUSE fsel: FUSE select example
    +
    Copyright (C) 2008 SUSE Linux Products GmbH
    +
    Copyright (C) 2008 Tejun Heo <teheo@suse.de>
    +
    +
    This program can be distributed under the terms of the GNU GPLv2.
    +
    See the file GPL2.txt.
    +
    */
    +
    +
    +
    #define FUSE_USE_VERSION 31
    +
    +
    #include <fuse.h>
    +
    #include <unistd.h>
    +
    #include <ctype.h>
    +
    #include <string.h>
    +
    #include <stdio.h>
    +
    #include <stdlib.h>
    +
    #include <errno.h>
    +
    #include <time.h>
    +
    #include <pthread.h>
    +
    #include <poll.h>
    +
    #include <stdbool.h>
    +
    +
    /*
    +
    * fsel_open_mask is used to limit the number of opens to 1 per file.
    +
    * This is to use file index (0-F) as fh as poll support requires
    +
    * unique fh per open file. Lifting this would require proper open
    +
    * file management.
    +
    */
    +
    static unsigned fsel_open_mask;
    +
    static const char fsel_hex_map[] = "0123456789ABCDEF";
    +
    static struct fuse *fsel_fuse; /* needed for poll notification */
    +
    +
    #define FSEL_CNT_MAX 10 /* each file can store up to 10 chars */
    +
    #define FSEL_FILES 16
    +
    +
    static pthread_mutex_t fsel_mutex; /* protects notify_mask and cnt array */
    +
    static unsigned fsel_poll_notify_mask; /* poll notification scheduled? */
    +
    static struct fuse_pollhandle *fsel_poll_handle[FSEL_FILES]; /* poll notify handles */
    +
    static unsigned fsel_cnt[FSEL_FILES]; /* nbytes stored in each file */
    +
    static _Atomic bool fsel_stop = false;
    +
    static pthread_t fsel_producer_thread;
    +
    +
    +
    static int fsel_path_index(const char *path)
    +
    {
    +
    char ch = path[1];
    +
    +
    if (strlen(path) != 2 || path[0] != '/' || !isxdigit(ch) || islower(ch))
    +
    return -1;
    +
    return ch <= '9' ? ch - '0' : ch - 'A' + 10;
    +
    }
    +
    +
    static void fsel_destroy(void *private_data)
    +
    {
    +
    (void)private_data;
    +
    +
    fsel_stop = true;
    +
    +
    pthread_join(fsel_producer_thread, NULL);
    +
    }
    +
    +
    static int fsel_getattr(const char *path, struct stat *stbuf,
    +
    struct fuse_file_info *fi)
    +
    {
    +
    (void) fi;
    +
    int idx;
    +
    +
    memset(stbuf, 0, sizeof(struct stat));
    +
    +
    if (strcmp(path, "/") == 0) {
    +
    stbuf->st_mode = S_IFDIR | 0555;
    +
    stbuf->st_nlink = 2;
    +
    return 0;
    +
    }
    +
    +
    idx = fsel_path_index(path);
    +
    if (idx < 0)
    +
    return -ENOENT;
    +
    +
    stbuf->st_mode = S_IFREG | 0444;
    +
    stbuf->st_nlink = 1;
    +
    stbuf->st_size = fsel_cnt[idx];
    +
    return 0;
    +
    }
    +
    +
    static int fsel_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
    +
    off_t offset, struct fuse_file_info *fi,
    +
    enum fuse_readdir_flags flags)
    +
    {
    +
    char name[2] = { };
    +
    int i;
    +
    +
    (void) offset;
    +
    (void) fi;
    +
    (void) flags;
    +
    +
    if (strcmp(path, "/") != 0)
    +
    return -ENOENT;
    +
    +
    for (i = 0; i < FSEL_FILES; i++) {
    +
    name[0] = fsel_hex_map[i];
    +
    filler(buf, name, NULL, 0, FUSE_FILL_DIR_DEFAULTS);
    +
    }
    +
    +
    return 0;
    +
    }
    +
    +
    static int fsel_open(const char *path, struct fuse_file_info *fi)
    +
    {
    +
    int idx = fsel_path_index(path);
    +
    +
    if (idx < 0)
    +
    return -ENOENT;
    +
    if ((fi->flags & O_ACCMODE) != O_RDONLY)
    +
    return -EACCES;
    +
    if (fsel_open_mask & (1 << idx))
    +
    return -EBUSY;
    +
    fsel_open_mask |= (1 << idx);
    +
    +
    /*
    +
    * fsel files are nonseekable somewhat pipe-like files which
    +
    * gets filled up periodically by producer thread and consumed
    +
    * on read. Tell FUSE as such.
    +
    */
    +
    fi->fh = idx;
    +
    fi->direct_io = 1;
    +
    fi->nonseekable = 1;
    +
    +
    return 0;
    +
    }
    +
    +
    static int fsel_release(const char *path, struct fuse_file_info *fi)
    +
    {
    +
    int idx = fi->fh;
    +
    +
    (void) path;
    +
    +
    fsel_open_mask &= ~(1 << idx);
    +
    return 0;
    +
    }
    +
    +
    static int fsel_read(const char *path, char *buf, size_t size, off_t offset,
    +
    struct fuse_file_info *fi)
    +
    {
    +
    int idx = fi->fh;
    +
    +
    (void) path;
    +
    (void) offset;
    +
    +
    pthread_mutex_lock(&fsel_mutex);
    +
    if (fsel_cnt[idx] < size)
    +
    size = fsel_cnt[idx];
    +
    printf("READ %X transferred=%zu cnt=%u\n", idx, size, fsel_cnt[idx]);
    +
    fsel_cnt[idx] -= size;
    +
    pthread_mutex_unlock(&fsel_mutex);
    +
    +
    memset(buf, fsel_hex_map[idx], size);
    +
    return size;
    +
    }
    +
    +
    static int fsel_poll(const char *path, struct fuse_file_info *fi,
    +
    struct fuse_pollhandle *ph, unsigned *reventsp)
    +
    {
    +
    static unsigned polled_zero;
    +
    int idx = fi->fh;
    +
    +
    (void) path;
    +
    +
    /*
    +
    * Poll notification requires pointer to struct fuse which
    +
    * can't be obtained when using fuse_main(). As notification
    +
    * happens only after poll is called, fill it here from
    +
    * fuse_context.
    +
    */
    +
    if (!fsel_fuse) {
    +
    struct fuse_context *cxt = fuse_get_context();
    +
    if (cxt)
    +
    fsel_fuse = cxt->fuse;
    +
    }
    +
    +
    pthread_mutex_lock(&fsel_mutex);
    +
    +
    if (ph != NULL) {
    +
    struct fuse_pollhandle *oldph = fsel_poll_handle[idx];
    +
    +
    if (oldph)
    + +
    +
    fsel_poll_notify_mask |= (1 << idx);
    +
    fsel_poll_handle[idx] = ph;
    +
    }
    +
    +
    if (fsel_cnt[idx]) {
    +
    *reventsp |= POLLIN;
    +
    printf("POLL %X cnt=%u polled_zero=%u\n",
    +
    idx, fsel_cnt[idx], polled_zero);
    +
    polled_zero = 0;
    +
    } else
    +
    polled_zero++;
    +
    +
    pthread_mutex_unlock(&fsel_mutex);
    +
    return 0;
    +
    }
    +
    +
    static const struct fuse_operations fsel_oper = {
    +
    .destroy = fsel_destroy,
    +
    .getattr = fsel_getattr,
    +
    .readdir = fsel_readdir,
    +
    .open = fsel_open,
    +
    .release = fsel_release,
    +
    .read = fsel_read,
    +
    .poll = fsel_poll,
    +
    };
    +
    +
    static void *fsel_producer(void *data)
    +
    {
    +
    const struct timespec interval = { 0, 250000000 };
    +
    unsigned idx = 0, nr = 1;
    +
    +
    (void) data;
    +
    +
    while (!fsel_stop) {
    +
    int i, t;
    +
    +
    pthread_mutex_lock(&fsel_mutex);
    +
    +
    /*
    +
    * This is the main producer loop which is executed
    +
    * ever 500ms. On each iteration, it fills one byte
    +
    * to 1, 2 or 4 files and sends poll notification if
    +
    * requested.
    +
    */
    +
    for (i = 0, t = idx; i < nr;
    +
    i++, t = (t + FSEL_FILES / nr) % FSEL_FILES) {
    +
    if (fsel_cnt[t] == FSEL_CNT_MAX)
    +
    continue;
    +
    +
    fsel_cnt[t]++;
    +
    if (fsel_fuse && (fsel_poll_notify_mask & (1 << t))) {
    +
    struct fuse_pollhandle *ph;
    +
    +
    printf("NOTIFY %X\n", t);
    +
    ph = fsel_poll_handle[t];
    +
    fuse_notify_poll(ph);
    + +
    fsel_poll_notify_mask &= ~(1 << t);
    +
    fsel_poll_handle[t] = NULL;
    +
    }
    +
    }
    +
    +
    idx = (idx + 1) % FSEL_FILES;
    +
    if (idx == 0)
    +
    nr = (nr * 2) % 7; /* cycle through 1, 2 and 4 */
    +
    +
    pthread_mutex_unlock(&fsel_mutex);
    +
    +
    nanosleep(&interval, NULL);
    +
    }
    +
    +
    return NULL;
    +
    }
    +
    +
    int main(int argc, char *argv[])
    +
    {
    +
    pthread_attr_t attr;
    +
    int ret;
    +
    +
    errno = pthread_mutex_init(&fsel_mutex, NULL);
    +
    if (errno) {
    +
    perror("pthread_mutex_init");
    +
    return 1;
    +
    }
    +
    +
    errno = pthread_attr_init(&attr);
    +
    if (errno) {
    +
    perror("pthread_attr_init");
    +
    return 1;
    +
    }
    +
    +
    errno = pthread_create(&fsel_producer_thread, &attr, fsel_producer, NULL);
    +
    if (errno) {
    +
    perror("pthread_create");
    +
    return 1;
    +
    }
    +
    +
    ret = fuse_main(argc, argv, &fsel_oper, NULL);
    +
    +
    return ret;
    +
    }
    + +
    struct fuse_context * fuse_get_context(void)
    Definition fuse.c:4711
    +
    int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
    Definition fuse.h:93
    +
    @ FUSE_FILL_DIR_DEFAULTS
    Definition fuse.h:74
    +
    fuse_readdir_flags
    Definition fuse.h:45
    +
    void fuse_pollhandle_destroy(struct fuse_pollhandle *ph)
    + +
    struct fuse * fuse
    Definition fuse.h:879
    + + +
    uint32_t nonseekable
    Definition fuse_common.h:78
    +
    uint32_t direct_io
    Definition fuse_common.h:63
    + + +
    +

    Definition in file poll.c.

    +
    + + +
    + + diff --git a/doc/html/poll_8c_source.html b/doc/html/poll_8c_source.html new file mode 100644 index 0000000..2cce396 --- /dev/null +++ b/doc/html/poll_8c_source.html @@ -0,0 +1,364 @@ + + + + + + + +libfuse: example/poll.c Source File + + + + + + + +
    +
    + + + + + + +
    +
    libfuse +
    +
    +
    + + + + + + + + +
    +
    +
    +
    poll.c
    +
    +
    +Go to the documentation of this file.
    1/*
    +
    2 FUSE fsel: FUSE select example
    +
    3 Copyright (C) 2008 SUSE Linux Products GmbH
    +
    4 Copyright (C) 2008 Tejun Heo <teheo@suse.de>
    +
    5
    +
    6 This program can be distributed under the terms of the GNU GPLv2.
    +
    7 See the file GPL2.txt.
    +
    8*/
    +
    9
    +
    23
    +
    24#define FUSE_USE_VERSION 31
    +
    25
    +
    26#include <fuse.h>
    +
    27#include <unistd.h>
    +
    28#include <ctype.h>
    +
    29#include <string.h>
    +
    30#include <stdio.h>
    +
    31#include <stdlib.h>
    +
    32#include <errno.h>
    +
    33#include <time.h>
    +
    34#include <pthread.h>
    +
    35#include <poll.h>
    +
    36#include <stdbool.h>
    +
    37
    +
    38/*
    +
    39 * fsel_open_mask is used to limit the number of opens to 1 per file.
    +
    40 * This is to use file index (0-F) as fh as poll support requires
    +
    41 * unique fh per open file. Lifting this would require proper open
    +
    42 * file management.
    +
    43 */
    +
    44static unsigned fsel_open_mask;
    +
    45static const char fsel_hex_map[] = "0123456789ABCDEF";
    +
    46static struct fuse *fsel_fuse; /* needed for poll notification */
    +
    47
    +
    48#define FSEL_CNT_MAX 10 /* each file can store up to 10 chars */
    +
    49#define FSEL_FILES 16
    +
    50
    +
    51static pthread_mutex_t fsel_mutex; /* protects notify_mask and cnt array */
    +
    52static unsigned fsel_poll_notify_mask; /* poll notification scheduled? */
    +
    53static struct fuse_pollhandle *fsel_poll_handle[FSEL_FILES]; /* poll notify handles */
    +
    54static unsigned fsel_cnt[FSEL_FILES]; /* nbytes stored in each file */
    +
    55static _Atomic bool fsel_stop = false;
    +
    56static pthread_t fsel_producer_thread;
    +
    57
    +
    58
    +
    59static int fsel_path_index(const char *path)
    +
    60{
    +
    61 char ch = path[1];
    +
    62
    +
    63 if (strlen(path) != 2 || path[0] != '/' || !isxdigit(ch) || islower(ch))
    +
    64 return -1;
    +
    65 return ch <= '9' ? ch - '0' : ch - 'A' + 10;
    +
    66}
    +
    67
    +
    68static void fsel_destroy(void *private_data)
    +
    69{
    +
    70 (void)private_data;
    +
    71
    +
    72 fsel_stop = true;
    +
    73
    +
    74 pthread_join(fsel_producer_thread, NULL);
    +
    75}
    +
    76
    +
    77static int fsel_getattr(const char *path, struct stat *stbuf,
    +
    78 struct fuse_file_info *fi)
    +
    79{
    +
    80 (void) fi;
    +
    81 int idx;
    +
    82
    +
    83 memset(stbuf, 0, sizeof(struct stat));
    +
    84
    +
    85 if (strcmp(path, "/") == 0) {
    +
    86 stbuf->st_mode = S_IFDIR | 0555;
    +
    87 stbuf->st_nlink = 2;
    +
    88 return 0;
    +
    89 }
    +
    90
    +
    91 idx = fsel_path_index(path);
    +
    92 if (idx < 0)
    +
    93 return -ENOENT;
    +
    94
    +
    95 stbuf->st_mode = S_IFREG | 0444;
    +
    96 stbuf->st_nlink = 1;
    +
    97 stbuf->st_size = fsel_cnt[idx];
    +
    98 return 0;
    +
    99}
    +
    100
    +
    101static int fsel_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
    +
    102 off_t offset, struct fuse_file_info *fi,
    +
    103 enum fuse_readdir_flags flags)
    +
    104{
    +
    105 char name[2] = { };
    +
    106 int i;
    +
    107
    +
    108 (void) offset;
    +
    109 (void) fi;
    +
    110 (void) flags;
    +
    111
    +
    112 if (strcmp(path, "/") != 0)
    +
    113 return -ENOENT;
    +
    114
    +
    115 for (i = 0; i < FSEL_FILES; i++) {
    +
    116 name[0] = fsel_hex_map[i];
    +
    117 filler(buf, name, NULL, 0, FUSE_FILL_DIR_DEFAULTS);
    +
    118 }
    +
    119
    +
    120 return 0;
    +
    121}
    +
    122
    +
    123static int fsel_open(const char *path, struct fuse_file_info *fi)
    +
    124{
    +
    125 int idx = fsel_path_index(path);
    +
    126
    +
    127 if (idx < 0)
    +
    128 return -ENOENT;
    +
    129 if ((fi->flags & O_ACCMODE) != O_RDONLY)
    +
    130 return -EACCES;
    +
    131 if (fsel_open_mask & (1 << idx))
    +
    132 return -EBUSY;
    +
    133 fsel_open_mask |= (1 << idx);
    +
    134
    +
    135 /*
    +
    136 * fsel files are nonseekable somewhat pipe-like files which
    +
    137 * gets filled up periodically by producer thread and consumed
    +
    138 * on read. Tell FUSE as such.
    +
    139 */
    +
    140 fi->fh = idx;
    +
    141 fi->direct_io = 1;
    +
    142 fi->nonseekable = 1;
    +
    143
    +
    144 return 0;
    +
    145}
    +
    146
    +
    147static int fsel_release(const char *path, struct fuse_file_info *fi)
    +
    148{
    +
    149 int idx = fi->fh;
    +
    150
    +
    151 (void) path;
    +
    152
    +
    153 fsel_open_mask &= ~(1 << idx);
    +
    154 return 0;
    +
    155}
    +
    156
    +
    157static int fsel_read(const char *path, char *buf, size_t size, off_t offset,
    +
    158 struct fuse_file_info *fi)
    +
    159{
    +
    160 int idx = fi->fh;
    +
    161
    +
    162 (void) path;
    +
    163 (void) offset;
    +
    164
    +
    165 pthread_mutex_lock(&fsel_mutex);
    +
    166 if (fsel_cnt[idx] < size)
    +
    167 size = fsel_cnt[idx];
    +
    168 printf("READ %X transferred=%zu cnt=%u\n", idx, size, fsel_cnt[idx]);
    +
    169 fsel_cnt[idx] -= size;
    +
    170 pthread_mutex_unlock(&fsel_mutex);
    +
    171
    +
    172 memset(buf, fsel_hex_map[idx], size);
    +
    173 return size;
    +
    174}
    +
    175
    +
    176static int fsel_poll(const char *path, struct fuse_file_info *fi,
    +
    177 struct fuse_pollhandle *ph, unsigned *reventsp)
    +
    178{
    +
    179 static unsigned polled_zero;
    +
    180 int idx = fi->fh;
    +
    181
    +
    182 (void) path;
    +
    183
    +
    184 /*
    +
    185 * Poll notification requires pointer to struct fuse which
    +
    186 * can't be obtained when using fuse_main(). As notification
    +
    187 * happens only after poll is called, fill it here from
    +
    188 * fuse_context.
    +
    189 */
    +
    190 if (!fsel_fuse) {
    +
    191 struct fuse_context *cxt = fuse_get_context();
    +
    192 if (cxt)
    +
    193 fsel_fuse = cxt->fuse;
    +
    194 }
    +
    195
    +
    196 pthread_mutex_lock(&fsel_mutex);
    +
    197
    +
    198 if (ph != NULL) {
    +
    199 struct fuse_pollhandle *oldph = fsel_poll_handle[idx];
    +
    200
    +
    201 if (oldph)
    + +
    203
    +
    204 fsel_poll_notify_mask |= (1 << idx);
    +
    205 fsel_poll_handle[idx] = ph;
    +
    206 }
    +
    207
    +
    208 if (fsel_cnt[idx]) {
    +
    209 *reventsp |= POLLIN;
    +
    210 printf("POLL %X cnt=%u polled_zero=%u\n",
    +
    211 idx, fsel_cnt[idx], polled_zero);
    +
    212 polled_zero = 0;
    +
    213 } else
    +
    214 polled_zero++;
    +
    215
    +
    216 pthread_mutex_unlock(&fsel_mutex);
    +
    217 return 0;
    +
    218}
    +
    219
    +
    220static const struct fuse_operations fsel_oper = {
    +
    221 .destroy = fsel_destroy,
    +
    222 .getattr = fsel_getattr,
    +
    223 .readdir = fsel_readdir,
    +
    224 .open = fsel_open,
    +
    225 .release = fsel_release,
    +
    226 .read = fsel_read,
    +
    227 .poll = fsel_poll,
    +
    228};
    +
    229
    +
    230static void *fsel_producer(void *data)
    +
    231{
    +
    232 const struct timespec interval = { 0, 250000000 };
    +
    233 unsigned idx = 0, nr = 1;
    +
    234
    +
    235 (void) data;
    +
    236
    +
    237 while (!fsel_stop) {
    +
    238 int i, t;
    +
    239
    +
    240 pthread_mutex_lock(&fsel_mutex);
    +
    241
    +
    242 /*
    +
    243 * This is the main producer loop which is executed
    +
    244 * ever 500ms. On each iteration, it fills one byte
    +
    245 * to 1, 2 or 4 files and sends poll notification if
    +
    246 * requested.
    +
    247 */
    +
    248 for (i = 0, t = idx; i < nr;
    +
    249 i++, t = (t + FSEL_FILES / nr) % FSEL_FILES) {
    +
    250 if (fsel_cnt[t] == FSEL_CNT_MAX)
    +
    251 continue;
    +
    252
    +
    253 fsel_cnt[t]++;
    +
    254 if (fsel_fuse && (fsel_poll_notify_mask & (1 << t))) {
    +
    255 struct fuse_pollhandle *ph;
    +
    256
    +
    257 printf("NOTIFY %X\n", t);
    +
    258 ph = fsel_poll_handle[t];
    +
    259 fuse_notify_poll(ph);
    + +
    261 fsel_poll_notify_mask &= ~(1 << t);
    +
    262 fsel_poll_handle[t] = NULL;
    +
    263 }
    +
    264 }
    +
    265
    +
    266 idx = (idx + 1) % FSEL_FILES;
    +
    267 if (idx == 0)
    +
    268 nr = (nr * 2) % 7; /* cycle through 1, 2 and 4 */
    +
    269
    +
    270 pthread_mutex_unlock(&fsel_mutex);
    +
    271
    +
    272 nanosleep(&interval, NULL);
    +
    273 }
    +
    274
    +
    275 return NULL;
    +
    276}
    +
    277
    +
    278int main(int argc, char *argv[])
    +
    279{
    +
    280 pthread_attr_t attr;
    +
    281 int ret;
    +
    282
    +
    283 errno = pthread_mutex_init(&fsel_mutex, NULL);
    +
    284 if (errno) {
    +
    285 perror("pthread_mutex_init");
    +
    286 return 1;
    +
    287 }
    +
    288
    +
    289 errno = pthread_attr_init(&attr);
    +
    290 if (errno) {
    +
    291 perror("pthread_attr_init");
    +
    292 return 1;
    +
    293 }
    +
    294
    +
    295 errno = pthread_create(&fsel_producer_thread, &attr, fsel_producer, NULL);
    +
    296 if (errno) {
    +
    297 perror("pthread_create");
    +
    298 return 1;
    +
    299 }
    +
    300
    +
    301 ret = fuse_main(argc, argv, &fsel_oper, NULL);
    +
    302
    +
    303 return ret;
    +
    304}
    + +
    struct fuse_context * fuse_get_context(void)
    Definition fuse.c:4711
    +
    int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
    Definition fuse.h:93
    +
    @ FUSE_FILL_DIR_DEFAULTS
    Definition fuse.h:74
    +
    fuse_readdir_flags
    Definition fuse.h:45
    +
    void fuse_pollhandle_destroy(struct fuse_pollhandle *ph)
    + +
    struct fuse * fuse
    Definition fuse.h:879
    + + +
    uint32_t nonseekable
    Definition fuse_common.h:78
    +
    uint32_t direct_io
    Definition fuse_common.h:63
    + + +
    + + +
    + + diff --git a/doc/html/poll__client_8c.html b/doc/html/poll__client_8c.html new file mode 100644 index 0000000..7aed827 --- /dev/null +++ b/doc/html/poll__client_8c.html @@ -0,0 +1,151 @@ + + + + + + + +libfuse: example/poll_client.c File Reference + + + + + + + +
    +
    + + + + + + +
    +
    libfuse +
    +
    +
    + + + + + + + + +
    +
    +
    +
    poll_client.c File Reference
    +
    +
    +
    #include <sys/select.h>
    +#include <sys/time.h>
    +#include <sys/types.h>
    +#include <sys/stat.h>
    +#include <fcntl.h>
    +#include <unistd.h>
    +#include <ctype.h>
    +#include <stdio.h>
    +#include <stdlib.h>
    +#include <errno.h>
    +
    +

    Go to the source code of this file.

    +

    Detailed Description

    +

    This program tests the poll.c example file systsem.

    +

    Compile with:

     gcc -Wall poll_client.c -o poll_client
    +

    +Source code

    +
    /*
    +
    FUSE fselclient: FUSE select example client
    +
    Copyright (C) 2008 SUSE Linux Products GmbH
    +
    Copyright (C) 2008 Tejun Heo <teheo@suse.de>
    +
    +
    This program can be distributed under the terms of the GNU GPLv2.
    +
    See the file GPL2.txt.
    +
    */
    +
    +
    +
    +
    #include <sys/select.h>
    +
    #include <sys/time.h>
    +
    #include <sys/types.h>
    +
    #include <sys/stat.h>
    +
    #include <fcntl.h>
    +
    #include <unistd.h>
    +
    #include <ctype.h>
    +
    #include <stdio.h>
    +
    #include <stdlib.h>
    +
    #include <errno.h>
    +
    +
    int main(void)
    +
    {
    +
    static const char hex_map[] = "0123456789ABCDEF";
    +
    const size_t fsel_files = sizeof(hex_map) - 1;
    +
    int fds[sizeof(hex_map) - 1];
    +
    size_t i, tries;
    +
    int nfds;
    +
    +
    for (i = 0; i < fsel_files; i++) {
    +
    char name[] = { hex_map[i], '\0' };
    +
    fds[i] = open(name, O_RDONLY);
    +
    if (fds[i] < 0) {
    +
    perror("open");
    +
    return 1;
    +
    }
    +
    }
    +
    nfds = fds[fsel_files - 1] + 1;
    +
    +
    for (tries = 0; tries < fsel_files; tries++) {
    +
    static char buf[4096];
    +
    fd_set rfds;
    +
    int rc;
    +
    +
    FD_ZERO(&rfds);
    +
    for (i = 0; i < fsel_files; i++)
    +
    FD_SET(fds[i], &rfds);
    +
    +
    rc = select(nfds, &rfds, NULL, NULL, NULL);
    +
    +
    if (rc < 0) {
    +
    perror("select");
    +
    return 1;
    +
    }
    +
    +
    for (i = 0; i < fsel_files; i++) {
    +
    if (!FD_ISSET(fds[i], &rfds)) {
    +
    printf("_: ");
    +
    continue;
    +
    }
    +
    printf("%zX:", i);
    +
    rc = read(fds[i], buf, sizeof(buf));
    +
    if (rc < 0) {
    +
    perror("read");
    +
    return 1;
    +
    }
    +
    printf("%02d ", rc);
    +
    }
    +
    printf("\n");
    +
    }
    +
    return 0;
    +
    }
    +
    +

    Definition in file poll_client.c.

    +
    + + +
    + + diff --git a/doc/html/poll__client_8c_source.html b/doc/html/poll__client_8c_source.html new file mode 100644 index 0000000..8cfa90e --- /dev/null +++ b/doc/html/poll__client_8c_source.html @@ -0,0 +1,132 @@ + + + + + + + +libfuse: example/poll_client.c Source File + + + + + + + +
    +
    + + + + + + +
    +
    libfuse +
    +
    +
    + + + + + + + + +
    +
    +
    +
    poll_client.c
    +
    +
    +Go to the documentation of this file.
    1/*
    +
    2 FUSE fselclient: FUSE select example client
    +
    3 Copyright (C) 2008 SUSE Linux Products GmbH
    +
    4 Copyright (C) 2008 Tejun Heo <teheo@suse.de>
    +
    5
    +
    6 This program can be distributed under the terms of the GNU GPLv2.
    +
    7 See the file GPL2.txt.
    +
    8*/
    +
    9
    +
    21
    +
    22
    +
    23#include <sys/select.h>
    +
    24#include <sys/time.h>
    +
    25#include <sys/types.h>
    +
    26#include <sys/stat.h>
    +
    27#include <fcntl.h>
    +
    28#include <unistd.h>
    +
    29#include <ctype.h>
    +
    30#include <stdio.h>
    +
    31#include <stdlib.h>
    +
    32#include <errno.h>
    +
    33
    +
    34int main(void)
    +
    35{
    +
    36 static const char hex_map[] = "0123456789ABCDEF";
    +
    37 const size_t fsel_files = sizeof(hex_map) - 1;
    +
    38 int fds[sizeof(hex_map) - 1];
    +
    39 size_t i, tries;
    +
    40 int nfds;
    +
    41
    +
    42 for (i = 0; i < fsel_files; i++) {
    +
    43 char name[] = { hex_map[i], '\0' };
    +
    44 fds[i] = open(name, O_RDONLY);
    +
    45 if (fds[i] < 0) {
    +
    46 perror("open");
    +
    47 return 1;
    +
    48 }
    +
    49 }
    +
    50 nfds = fds[fsel_files - 1] + 1;
    +
    51
    +
    52 for (tries = 0; tries < fsel_files; tries++) {
    +
    53 static char buf[4096];
    +
    54 fd_set rfds;
    +
    55 int rc;
    +
    56
    +
    57 FD_ZERO(&rfds);
    +
    58 for (i = 0; i < fsel_files; i++)
    +
    59 FD_SET(fds[i], &rfds);
    +
    60
    +
    61 rc = select(nfds, &rfds, NULL, NULL, NULL);
    +
    62
    +
    63 if (rc < 0) {
    +
    64 perror("select");
    +
    65 return 1;
    +
    66 }
    +
    67
    +
    68 for (i = 0; i < fsel_files; i++) {
    +
    69 if (!FD_ISSET(fds[i], &rfds)) {
    +
    70 printf("_: ");
    +
    71 continue;
    +
    72 }
    +
    73 printf("%zX:", i);
    +
    74 rc = read(fds[i], buf, sizeof(buf));
    +
    75 if (rc < 0) {
    +
    76 perror("read");
    +
    77 return 1;
    +
    78 }
    +
    79 printf("%02d ", rc);
    +
    80 }
    +
    81 printf("\n");
    +
    82 }
    +
    83 return 0;
    +
    84}
    +
    + + +
    + + diff --git a/doc/html/printcap_8c.html b/doc/html/printcap_8c.html new file mode 100644 index 0000000..6fda5c5 --- /dev/null +++ b/doc/html/printcap_8c.html @@ -0,0 +1,245 @@ + + + + + + + +libfuse: example/printcap.c File Reference + + + + + + + +
    +
    + + + + + + +
    +
    libfuse +
    +
    +
    + + + + + + + + +
    +
    +
    +
    printcap.c File Reference
    +
    +
    +
    #include <fuse_lowlevel.h>
    +#include <stdio.h>
    +#include <unistd.h>
    +#include <string.h>
    +#include <stdlib.h>
    +
    +

    Go to the source code of this file.

    +

    Detailed Description

    +

    minimal example filesystem that prints out all capabilities supported by the kernel and then exits.

    +

    Compile with:

    gcc -Wall printcap.c `pkg-config fuse3 --cflags --libs` -o printcap
    +

    +Source code

    +
    /*
    +
    FUSE: Filesystem in Userspace
    +
    Copyright (C) 2017 Nikolaus Rath <Nikolaus@rath.org>
    +
    +
    This program can be distributed under the terms of the GNU GPLv2.
    +
    See the file GPL2.txt.
    +
    */
    +
    +
    +
    #define FUSE_USE_VERSION 31
    +
    +
    #include <fuse_lowlevel.h>
    +
    #include <stdio.h>
    +
    #include <unistd.h>
    +
    #include <string.h>
    +
    #include <stdlib.h>
    +
    +
    struct fuse_session *se;
    +
    +
    // Define a structure to hold capability information
    +
    struct cap_info {
    +
    uint64_t flag;
    +
    const char *name;
    +
    };
    +
    +
    // Define an array of all capabilities
    +
    static const struct cap_info capabilities[] = {
    +
    {FUSE_CAP_ASYNC_READ, "FUSE_CAP_ASYNC_READ"},
    +
    {FUSE_CAP_POSIX_LOCKS, "FUSE_CAP_POSIX_LOCKS"},
    +
    {FUSE_CAP_ATOMIC_O_TRUNC, "FUSE_CAP_ATOMIC_O_TRUNC"},
    +
    {FUSE_CAP_EXPORT_SUPPORT, "FUSE_CAP_EXPORT_SUPPORT"},
    +
    {FUSE_CAP_DONT_MASK, "FUSE_CAP_DONT_MASK"},
    +
    {FUSE_CAP_SPLICE_MOVE, "FUSE_CAP_SPLICE_MOVE"},
    +
    {FUSE_CAP_SPLICE_READ, "FUSE_CAP_SPLICE_READ"},
    +
    {FUSE_CAP_SPLICE_WRITE, "FUSE_CAP_SPLICE_WRITE"},
    +
    {FUSE_CAP_FLOCK_LOCKS, "FUSE_CAP_FLOCK_LOCKS"},
    +
    {FUSE_CAP_IOCTL_DIR, "FUSE_CAP_IOCTL_DIR"},
    +
    {FUSE_CAP_AUTO_INVAL_DATA, "FUSE_CAP_AUTO_INVAL_DATA"},
    +
    {FUSE_CAP_READDIRPLUS, "FUSE_CAP_READDIRPLUS"},
    +
    {FUSE_CAP_READDIRPLUS_AUTO, "FUSE_CAP_READDIRPLUS_AUTO"},
    +
    {FUSE_CAP_ASYNC_DIO, "FUSE_CAP_ASYNC_DIO"},
    +
    {FUSE_CAP_WRITEBACK_CACHE, "FUSE_CAP_WRITEBACK_CACHE"},
    +
    {FUSE_CAP_NO_OPEN_SUPPORT, "FUSE_CAP_NO_OPEN_SUPPORT"},
    +
    {FUSE_CAP_PARALLEL_DIROPS, "FUSE_CAP_PARALLEL_DIROPS"},
    +
    {FUSE_CAP_POSIX_ACL, "FUSE_CAP_POSIX_ACL"},
    +
    {FUSE_CAP_CACHE_SYMLINKS, "FUSE_CAP_CACHE_SYMLINKS"},
    +
    {FUSE_CAP_NO_OPENDIR_SUPPORT, "FUSE_CAP_NO_OPENDIR_SUPPORT"},
    +
    {FUSE_CAP_EXPLICIT_INVAL_DATA, "FUSE_CAP_EXPLICIT_INVAL_DATA"},
    +
    {FUSE_CAP_EXPIRE_ONLY, "FUSE_CAP_EXPIRE_ONLY"},
    +
    {FUSE_CAP_SETXATTR_EXT, "FUSE_CAP_SETXATTR_EXT"},
    +
    {FUSE_CAP_HANDLE_KILLPRIV, "FUSE_CAP_HANDLE_KILLPRIV"},
    +
    {FUSE_CAP_HANDLE_KILLPRIV_V2, "FUSE_CAP_HANDLE_KILLPRIV_V2"},
    +
    {FUSE_CAP_DIRECT_IO_ALLOW_MMAP, "FUSE_CAP_DIRECT_IO_ALLOW_MMAP"},
    +
    {FUSE_CAP_NO_EXPORT_SUPPORT, "FUSE_CAP_NO_EXPORT_SUPPORT"},
    +
    {FUSE_CAP_PASSTHROUGH, "FUSE_CAP_PASSTHROUGH"},
    +
    // Add any new capabilities here
    +
    {0, NULL} // Sentinel to mark the end of the array
    +
    };
    +
    +
    static void print_capabilities(struct fuse_conn_info *conn)
    +
    {
    +
    printf("Capabilities:\n");
    +
    for (const struct cap_info *cap = capabilities; cap->name != NULL; cap++) {
    +
    if (fuse_get_feature_flag(conn, cap->flag)) {
    +
    printf("\t%s\n", cap->name);
    +
    }
    +
    }
    +
    }
    +
    +
    static void pc_init(void *userdata, struct fuse_conn_info *conn)
    +
    {
    +
    (void) userdata;
    +
    +
    printf("Protocol version: %d.%d\n", conn->proto_major,
    +
    conn->proto_minor);
    +
    print_capabilities(conn);
    + +
    }
    +
    +
    +
    static const struct fuse_lowlevel_ops pc_oper = {
    +
    .init = pc_init,
    +
    };
    +
    +
    int main(int argc, char **argv)
    +
    {
    +
    struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
    +
    char *mountpoint;
    +
    int ret = -1;
    +
    +
    mountpoint = strdup("/tmp/fuse_printcap_XXXXXX");
    +
    if(mkdtemp(mountpoint) == NULL) {
    +
    perror("mkdtemp");
    +
    return 1;
    +
    }
    +
    +
    printf("FUSE library version %s\n", fuse_pkgversion());
    + +
    +
    se = fuse_session_new(&args, &pc_oper,
    +
    sizeof(pc_oper), NULL);
    +
    if (se == NULL)
    +
    goto err_out1;
    +
    + +
    goto err_out2;
    +
    +
    if (fuse_session_mount(se, mountpoint) != 0)
    +
    goto err_out3;
    +
    +
    ret = fuse_session_loop(se);
    +
    + +
    err_out3:
    + +
    err_out2:
    + +
    err_out1:
    +
    rmdir(mountpoint);
    +
    free(mountpoint);
    + +
    +
    return ret ? 1 : 0;
    +
    }
    +
    #define FUSE_CAP_IOCTL_DIR
    +
    #define FUSE_CAP_DONT_MASK
    +
    #define FUSE_CAP_HANDLE_KILLPRIV
    +
    #define FUSE_CAP_AUTO_INVAL_DATA
    +
    int fuse_set_signal_handlers(struct fuse_session *se)
    +
    #define FUSE_CAP_HANDLE_KILLPRIV_V2
    +
    #define FUSE_CAP_SPLICE_READ
    +
    #define FUSE_CAP_PARALLEL_DIROPS
    +
    #define FUSE_CAP_WRITEBACK_CACHE
    +
    #define FUSE_CAP_EXPIRE_ONLY
    +
    #define FUSE_CAP_ATOMIC_O_TRUNC
    +
    #define FUSE_CAP_ASYNC_READ
    +
    #define FUSE_CAP_SPLICE_WRITE
    +
    #define FUSE_CAP_CACHE_SYMLINKS
    +
    #define FUSE_CAP_POSIX_ACL
    +
    #define FUSE_CAP_EXPORT_SUPPORT
    +
    #define FUSE_CAP_POSIX_LOCKS
    +
    #define FUSE_CAP_EXPLICIT_INVAL_DATA
    +
    #define FUSE_CAP_READDIRPLUS_AUTO
    +
    #define FUSE_CAP_NO_OPENDIR_SUPPORT
    +
    #define FUSE_CAP_ASYNC_DIO
    +
    bool fuse_get_feature_flag(struct fuse_conn_info *conn, uint64_t flag)
    +
    #define FUSE_CAP_PASSTHROUGH
    +
    #define FUSE_CAP_DIRECT_IO_ALLOW_MMAP
    +
    #define FUSE_CAP_NO_OPEN_SUPPORT
    +
    #define FUSE_CAP_READDIRPLUS
    +
    const char * fuse_pkgversion(void)
    Definition fuse.c:5274
    +
    void fuse_remove_signal_handlers(struct fuse_session *se)
    +
    #define FUSE_CAP_SETXATTR_EXT
    +
    #define FUSE_CAP_SPLICE_MOVE
    +
    #define FUSE_CAP_NO_EXPORT_SUPPORT
    +
    #define FUSE_CAP_FLOCK_LOCKS
    + +
    void fuse_session_destroy(struct fuse_session *se)
    +
    void fuse_session_exit(struct fuse_session *se)
    +
    int fuse_session_loop(struct fuse_session *se)
    Definition fuse_loop.c:19
    +
    void fuse_session_unmount(struct fuse_session *se)
    +
    int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
    +
    void fuse_lowlevel_version(void)
    +
    void fuse_opt_free_args(struct fuse_args *args)
    Definition fuse_opt.c:34
    +
    #define FUSE_ARGS_INIT(argc, argv)
    Definition fuse_opt.h:123
    + + +
    char ** argv
    Definition fuse_opt.h:114
    + +
    uint32_t proto_major
    +
    uint32_t proto_minor
    + +
    +

    Definition in file printcap.c.

    +
    + + +
    + + diff --git a/doc/html/printcap_8c_source.html b/doc/html/printcap_8c_source.html new file mode 100644 index 0000000..189bb55 --- /dev/null +++ b/doc/html/printcap_8c_source.html @@ -0,0 +1,231 @@ + + + + + + + +libfuse: example/printcap.c Source File + + + + + + + +
    +
    + + + + + + +
    +
    libfuse +
    +
    +
    + + + + + + + + +
    +
    +
    +
    printcap.c
    +
    +
    +Go to the documentation of this file.
    1/*
    +
    2 FUSE: Filesystem in Userspace
    +
    3 Copyright (C) 2017 Nikolaus Rath <Nikolaus@rath.org>
    +
    4
    +
    5 This program can be distributed under the terms of the GNU GPLv2.
    +
    6 See the file GPL2.txt.
    +
    7*/
    +
    8
    +
    21
    +
    22#define FUSE_USE_VERSION 31
    +
    23
    +
    24#include <fuse_lowlevel.h>
    +
    25#include <stdio.h>
    +
    26#include <unistd.h>
    +
    27#include <string.h>
    +
    28#include <stdlib.h>
    +
    29
    +
    30struct fuse_session *se;
    +
    31
    +
    32// Define a structure to hold capability information
    +
    33struct cap_info {
    +
    34 uint64_t flag;
    +
    35 const char *name;
    +
    36};
    +
    37
    +
    38// Define an array of all capabilities
    +
    39static const struct cap_info capabilities[] = {
    +
    40 {FUSE_CAP_ASYNC_READ, "FUSE_CAP_ASYNC_READ"},
    +
    41 {FUSE_CAP_POSIX_LOCKS, "FUSE_CAP_POSIX_LOCKS"},
    +
    42 {FUSE_CAP_ATOMIC_O_TRUNC, "FUSE_CAP_ATOMIC_O_TRUNC"},
    +
    43 {FUSE_CAP_EXPORT_SUPPORT, "FUSE_CAP_EXPORT_SUPPORT"},
    +
    44 {FUSE_CAP_DONT_MASK, "FUSE_CAP_DONT_MASK"},
    +
    45 {FUSE_CAP_SPLICE_MOVE, "FUSE_CAP_SPLICE_MOVE"},
    +
    46 {FUSE_CAP_SPLICE_READ, "FUSE_CAP_SPLICE_READ"},
    +
    47 {FUSE_CAP_SPLICE_WRITE, "FUSE_CAP_SPLICE_WRITE"},
    +
    48 {FUSE_CAP_FLOCK_LOCKS, "FUSE_CAP_FLOCK_LOCKS"},
    +
    49 {FUSE_CAP_IOCTL_DIR, "FUSE_CAP_IOCTL_DIR"},
    +
    50 {FUSE_CAP_AUTO_INVAL_DATA, "FUSE_CAP_AUTO_INVAL_DATA"},
    +
    51 {FUSE_CAP_READDIRPLUS, "FUSE_CAP_READDIRPLUS"},
    +
    52 {FUSE_CAP_READDIRPLUS_AUTO, "FUSE_CAP_READDIRPLUS_AUTO"},
    +
    53 {FUSE_CAP_ASYNC_DIO, "FUSE_CAP_ASYNC_DIO"},
    +
    54 {FUSE_CAP_WRITEBACK_CACHE, "FUSE_CAP_WRITEBACK_CACHE"},
    +
    55 {FUSE_CAP_NO_OPEN_SUPPORT, "FUSE_CAP_NO_OPEN_SUPPORT"},
    +
    56 {FUSE_CAP_PARALLEL_DIROPS, "FUSE_CAP_PARALLEL_DIROPS"},
    +
    57 {FUSE_CAP_POSIX_ACL, "FUSE_CAP_POSIX_ACL"},
    +
    58 {FUSE_CAP_CACHE_SYMLINKS, "FUSE_CAP_CACHE_SYMLINKS"},
    +
    59 {FUSE_CAP_NO_OPENDIR_SUPPORT, "FUSE_CAP_NO_OPENDIR_SUPPORT"},
    +
    60 {FUSE_CAP_EXPLICIT_INVAL_DATA, "FUSE_CAP_EXPLICIT_INVAL_DATA"},
    +
    61 {FUSE_CAP_EXPIRE_ONLY, "FUSE_CAP_EXPIRE_ONLY"},
    +
    62 {FUSE_CAP_SETXATTR_EXT, "FUSE_CAP_SETXATTR_EXT"},
    +
    63 {FUSE_CAP_HANDLE_KILLPRIV, "FUSE_CAP_HANDLE_KILLPRIV"},
    +
    64 {FUSE_CAP_HANDLE_KILLPRIV_V2, "FUSE_CAP_HANDLE_KILLPRIV_V2"},
    +
    65 {FUSE_CAP_DIRECT_IO_ALLOW_MMAP, "FUSE_CAP_DIRECT_IO_ALLOW_MMAP"},
    +
    66 {FUSE_CAP_NO_EXPORT_SUPPORT, "FUSE_CAP_NO_EXPORT_SUPPORT"},
    +
    67 {FUSE_CAP_PASSTHROUGH, "FUSE_CAP_PASSTHROUGH"},
    +
    68 // Add any new capabilities here
    +
    69 {0, NULL} // Sentinel to mark the end of the array
    +
    70};
    +
    71
    +
    72static void print_capabilities(struct fuse_conn_info *conn)
    +
    73{
    +
    74 printf("Capabilities:\n");
    +
    75 for (const struct cap_info *cap = capabilities; cap->name != NULL; cap++) {
    +
    76 if (fuse_get_feature_flag(conn, cap->flag)) {
    +
    77 printf("\t%s\n", cap->name);
    +
    78 }
    +
    79 }
    +
    80}
    +
    81
    +
    82static void pc_init(void *userdata, struct fuse_conn_info *conn)
    +
    83{
    +
    84 (void) userdata;
    +
    85
    +
    86 printf("Protocol version: %d.%d\n", conn->proto_major,
    +
    87 conn->proto_minor);
    +
    88 print_capabilities(conn);
    + +
    90}
    +
    91
    +
    92
    +
    93static const struct fuse_lowlevel_ops pc_oper = {
    +
    94 .init = pc_init,
    +
    95};
    +
    96
    +
    97int main(int argc, char **argv)
    +
    98{
    +
    99 struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
    +
    100 char *mountpoint;
    +
    101 int ret = -1;
    +
    102
    +
    103 mountpoint = strdup("/tmp/fuse_printcap_XXXXXX");
    +
    104 if(mkdtemp(mountpoint) == NULL) {
    +
    105 perror("mkdtemp");
    +
    106 return 1;
    +
    107 }
    +
    108
    +
    109 printf("FUSE library version %s\n", fuse_pkgversion());
    + +
    111
    +
    112 se = fuse_session_new(&args, &pc_oper,
    +
    113 sizeof(pc_oper), NULL);
    +
    114 if (se == NULL)
    +
    115 goto err_out1;
    +
    116
    +
    117 if (fuse_set_signal_handlers(se) != 0)
    +
    118 goto err_out2;
    +
    119
    +
    120 if (fuse_session_mount(se, mountpoint) != 0)
    +
    121 goto err_out3;
    +
    122
    +
    123 ret = fuse_session_loop(se);
    +
    124
    + +
    126err_out3:
    + +
    128err_out2:
    + +
    130err_out1:
    +
    131 rmdir(mountpoint);
    +
    132 free(mountpoint);
    +
    133 fuse_opt_free_args(&args);
    +
    134
    +
    135 return ret ? 1 : 0;
    +
    136}
    +
    #define FUSE_CAP_IOCTL_DIR
    +
    #define FUSE_CAP_DONT_MASK
    +
    #define FUSE_CAP_HANDLE_KILLPRIV
    +
    #define FUSE_CAP_AUTO_INVAL_DATA
    +
    int fuse_set_signal_handlers(struct fuse_session *se)
    +
    #define FUSE_CAP_HANDLE_KILLPRIV_V2
    +
    #define FUSE_CAP_SPLICE_READ
    +
    #define FUSE_CAP_PARALLEL_DIROPS
    +
    #define FUSE_CAP_WRITEBACK_CACHE
    +
    #define FUSE_CAP_EXPIRE_ONLY
    +
    #define FUSE_CAP_ATOMIC_O_TRUNC
    +
    #define FUSE_CAP_ASYNC_READ
    +
    #define FUSE_CAP_SPLICE_WRITE
    +
    #define FUSE_CAP_CACHE_SYMLINKS
    +
    #define FUSE_CAP_POSIX_ACL
    +
    #define FUSE_CAP_EXPORT_SUPPORT
    +
    #define FUSE_CAP_POSIX_LOCKS
    +
    #define FUSE_CAP_EXPLICIT_INVAL_DATA
    +
    #define FUSE_CAP_READDIRPLUS_AUTO
    +
    #define FUSE_CAP_NO_OPENDIR_SUPPORT
    +
    #define FUSE_CAP_ASYNC_DIO
    +
    bool fuse_get_feature_flag(struct fuse_conn_info *conn, uint64_t flag)
    +
    #define FUSE_CAP_PASSTHROUGH
    +
    #define FUSE_CAP_DIRECT_IO_ALLOW_MMAP
    +
    #define FUSE_CAP_NO_OPEN_SUPPORT
    +
    #define FUSE_CAP_READDIRPLUS
    +
    const char * fuse_pkgversion(void)
    Definition fuse.c:5274
    +
    void fuse_remove_signal_handlers(struct fuse_session *se)
    +
    #define FUSE_CAP_SETXATTR_EXT
    +
    #define FUSE_CAP_SPLICE_MOVE
    +
    #define FUSE_CAP_NO_EXPORT_SUPPORT
    +
    #define FUSE_CAP_FLOCK_LOCKS
    + +
    void fuse_session_destroy(struct fuse_session *se)
    +
    void fuse_session_exit(struct fuse_session *se)
    +
    int fuse_session_loop(struct fuse_session *se)
    Definition fuse_loop.c:19
    +
    void fuse_session_unmount(struct fuse_session *se)
    +
    int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
    +
    void fuse_lowlevel_version(void)
    +
    void fuse_opt_free_args(struct fuse_args *args)
    Definition fuse_opt.c:34
    +
    #define FUSE_ARGS_INIT(argc, argv)
    Definition fuse_opt.h:123
    + + +
    char ** argv
    Definition fuse_opt.h:114
    + +
    uint32_t proto_major
    +
    uint32_t proto_minor
    + +
    + + +
    + + diff --git a/doc/html/readdir__inode_8c_source.html b/doc/html/readdir__inode_8c_source.html new file mode 100644 index 0000000..969c6ac --- /dev/null +++ b/doc/html/readdir__inode_8c_source.html @@ -0,0 +1,116 @@ + + + + + + + +libfuse: test/readdir_inode.c Source File + + + + + + + +
    +
    + + + + + + +
    +
    libfuse +
    +
    +
    + + + + + + + + +
    +
    +
    +
    readdir_inode.c
    +
    +
    +
    1/*
    +
    2 * Prints each directory entry, its inode and d_type as returned by 'readdir'.
    +
    3 * Skips '.' and '..' because readdir is not required to return them and
    +
    4 * some of our examples don't. However if they are returned, their d_type
    +
    5 * should be valid.
    +
    6 */
    +
    7
    +
    8#include <stdio.h>
    +
    9#include <string.h>
    +
    10#include <sys/types.h>
    +
    11#include <dirent.h>
    +
    12#include <errno.h>
    +
    13
    +
    14int main(int argc, char* argv[])
    +
    15{
    +
    16 DIR* dirp;
    +
    17 struct dirent* dent;
    +
    18
    +
    19 if (argc != 2) {
    +
    20 fprintf(stderr, "Usage: readdir_inode dir\n");
    +
    21 return 1;
    +
    22 }
    +
    23
    +
    24 dirp = opendir(argv[1]);
    +
    25 if (dirp == NULL) {
    +
    26 perror("failed to open directory");
    +
    27 return 2;
    +
    28 }
    +
    29
    +
    30 errno = 0;
    +
    31 dent = readdir(dirp);
    +
    32 while (dent != NULL) {
    +
    33 if (strcmp(dent->d_name, ".") != 0 && strcmp(dent->d_name, "..") != 0) {
    +
    34 printf("%llu %d %s\n", (unsigned long long)dent->d_ino,
    +
    35 (int)dent->d_type, dent->d_name);
    +
    36 if ((long long)dent->d_ino < 0)
    +
    37 fprintf(stderr,"%s : bad d_ino %llu\n",
    +
    38 dent->d_name, (unsigned long long)dent->d_ino);
    +
    39 if ((dent->d_type < 1) || (dent->d_type > 15))
    +
    40 fprintf(stderr,"%s : bad d_type %d\n",
    +
    41 dent->d_name, (int)dent->d_type);
    +
    42 } else {
    +
    43 if (dent->d_type != DT_DIR)
    +
    44 fprintf(stderr,"%s : bad d_type %d\n",
    +
    45 dent->d_name, (int)dent->d_type);
    +
    46 }
    +
    47 dent = readdir(dirp);
    +
    48 }
    +
    49 if (errno != 0) {
    +
    50 perror("failed to read directory entry");
    +
    51 return 3;
    +
    52 }
    +
    53
    +
    54 closedir(dirp);
    +
    55
    +
    56 return 0;
    +
    57}
    +
    + + +
    + + diff --git a/doc/html/release__unlink__race_8c_source.html b/doc/html/release__unlink__race_8c_source.html new file mode 100644 index 0000000..4281275 --- /dev/null +++ b/doc/html/release__unlink__race_8c_source.html @@ -0,0 +1,182 @@ + + + + + + + +libfuse: test/release_unlink_race.c Source File + + + + + + + +
    +
    + + + + + + +
    +
    libfuse +
    +
    +
    + + + + + + + + +
    +
    +
    +
    release_unlink_race.c
    +
    +
    +
    1/*
    +
    2 This program can be distributed under the terms of the GNU GPLv2.
    +
    3 See the file GPL2.txt.
    +
    4*/
    +
    5
    +
    6#define FUSE_USE_VERSION 31
    +
    7
    +
    8#define _GNU_SOURCE
    +
    9
    +
    10#include <fuse.h>
    +
    11
    +
    12#include <stdio.h>
    +
    13#include <stdlib.h>
    +
    14#include <unistd.h>
    +
    15#include <errno.h>
    +
    16
    +
    17static void *xmp_init(struct fuse_conn_info *conn,
    +
    18 struct fuse_config *cfg)
    +
    19{
    +
    20 (void) conn;
    +
    21
    +
    22 cfg->use_ino = 1;
    +
    23 cfg->nullpath_ok = 1;
    +
    24 cfg->entry_timeout = 0;
    +
    25 cfg->attr_timeout = 0;
    +
    26 cfg->negative_timeout = 0;
    +
    27
    +
    28 return NULL;
    +
    29}
    +
    30
    +
    31static int xmp_getattr(const char *path, struct stat *stbuf,
    +
    32 struct fuse_file_info *fi)
    +
    33{
    +
    34 int res;
    +
    35
    +
    36 (void) path;
    +
    37
    +
    38 if(fi)
    +
    39 res = fstat(fi->fh, stbuf);
    +
    40 else
    +
    41 res = lstat(path, stbuf);
    +
    42 if (res == -1)
    +
    43 return -errno;
    +
    44
    +
    45 return 0;
    +
    46}
    +
    47
    +
    48static int xmp_unlink(const char *path)
    +
    49{
    +
    50 int res;
    +
    51
    +
    52 res = unlink(path);
    +
    53 if (res == -1)
    +
    54 return -errno;
    +
    55
    +
    56 return 0;
    +
    57}
    +
    58
    +
    59static int xmp_rename(const char *from, const char *to, unsigned int flags)
    +
    60{
    +
    61 int res;
    +
    62
    +
    63 if (flags)
    +
    64 return -EINVAL;
    +
    65
    +
    66 if(!getenv("RELEASEUNLINKRACE_DELAY_DISABLE")) usleep(100000);
    +
    67
    +
    68 res = rename(from, to);
    +
    69 if (res == -1)
    +
    70 return -errno;
    +
    71
    +
    72 return 0;
    +
    73}
    +
    74
    +
    75static int xmp_create(const char *path, mode_t mode, struct fuse_file_info *fi)
    +
    76{
    +
    77 int fd;
    +
    78
    +
    79 fd = open(path, fi->flags, mode);
    +
    80 if (fd == -1)
    +
    81 return -errno;
    +
    82
    +
    83 fi->fh = fd;
    +
    84 return 0;
    +
    85}
    +
    86
    +
    87static int xmp_release(const char *path, struct fuse_file_info *fi)
    +
    88{
    +
    89 (void) path;
    +
    90
    +
    91 if(!getenv("RELEASEUNLINKRACE_DELAY_DISABLE")) usleep(100000);
    +
    92
    +
    93 close(fi->fh);
    +
    94
    +
    95 return 0;
    +
    96}
    +
    97
    +
    98static const struct fuse_operations xmp_oper = {
    +
    99 .init = xmp_init,
    +
    100 .getattr = xmp_getattr,
    +
    101 .unlink = xmp_unlink,
    +
    102 .rename = xmp_rename,
    +
    103 .create = xmp_create,
    +
    104 .release = xmp_release,
    +
    105};
    +
    106
    +
    107int main(int argc, char *argv[])
    +
    108{
    +
    109 umask(0);
    +
    110 return fuse_main(argc, argv, &xmp_oper, NULL);
    +
    111}
    + + +
    int32_t nullpath_ok
    Definition fuse.h:279
    +
    int32_t use_ino
    Definition fuse.h:204
    +
    double entry_timeout
    Definition fuse.h:133
    +
    double negative_timeout
    Definition fuse.h:143
    +
    double attr_timeout
    Definition fuse.h:149
    + + + + + +
    + + +
    + + diff --git a/doc/html/stracedecode_8c_source.html b/doc/html/stracedecode_8c_source.html new file mode 100644 index 0000000..8971e46 --- /dev/null +++ b/doc/html/stracedecode_8c_source.html @@ -0,0 +1,258 @@ + + + + + + + +libfuse: test/stracedecode.c Source File + + + + + + + +
    +
    + + + + + + +
    +
    libfuse +
    +
    +
    + + + + + + + + +
    +
    +
    +
    stracedecode.c
    +
    +
    +
    1#include <stdio.h>
    +
    2#include <string.h>
    +
    3#include "fuse_kernel.h"
    +
    4
    +
    5static struct {
    +
    6 const char *name;
    +
    7} fuse_ll_ops[] = {
    +
    8 [FUSE_LOOKUP] = { "LOOKUP" },
    +
    9 [FUSE_FORGET] = { "FORGET" },
    +
    10 [FUSE_GETATTR] = { "GETATTR" },
    +
    11 [FUSE_SETATTR] = { "SETATTR" },
    +
    12 [FUSE_READLINK] = { "READLINK" },
    +
    13 [FUSE_SYMLINK] = { "SYMLINK" },
    +
    14 [FUSE_MKNOD] = { "MKNOD" },
    +
    15 [FUSE_MKDIR] = { "MKDIR" },
    +
    16 [FUSE_UNLINK] = { "UNLINK" },
    +
    17 [FUSE_RMDIR] = { "RMDIR" },
    +
    18 [FUSE_RENAME] = { "RENAME" },
    +
    19 [FUSE_LINK] = { "LINK" },
    +
    20 [FUSE_OPEN] = { "OPEN" },
    +
    21 [FUSE_READ] = { "READ" },
    +
    22 [FUSE_WRITE] = { "WRITE" },
    +
    23 [FUSE_STATFS] = { "STATFS" },
    +
    24 [FUSE_RELEASE] = { "RELEASE" },
    +
    25 [FUSE_FSYNC] = { "FSYNC" },
    +
    26 [FUSE_SETXATTR] = { "SETXATTR" },
    +
    27 [FUSE_GETXATTR] = { "GETXATTR" },
    +
    28 [FUSE_LISTXATTR] = { "LISTXATTR" },
    +
    29 [FUSE_REMOVEXATTR] = { "REMOVEXATTR" },
    +
    30 [FUSE_FLUSH] = { "FLUSH" },
    +
    31 [FUSE_INIT] = { "INIT" },
    +
    32 [FUSE_OPENDIR] = { "OPENDIR" },
    +
    33 [FUSE_READDIR] = { "READDIR" },
    +
    34 [FUSE_RELEASEDIR] = { "RELEASEDIR" },
    +
    35 [FUSE_FSYNCDIR] = { "FSYNCDIR" },
    +
    36 [FUSE_GETLK] = { "GETLK" },
    +
    37 [FUSE_SETLK] = { "SETLK" },
    +
    38 [FUSE_SETLKW] = { "SETLKW" },
    +
    39 [FUSE_ACCESS] = { "ACCESS" },
    +
    40 [FUSE_CREATE] = { "CREATE" },
    +
    41 [FUSE_TMPFILE] = { "TMPFILE" },
    +
    42 [FUSE_INTERRUPT] = { "INTERRUPT" },
    +
    43 [FUSE_BMAP] = { "BMAP" },
    +
    44 [FUSE_DESTROY] = { "DESTROY" },
    +
    45 [FUSE_READDIRPLUS] = { "READDIRPLUS" },
    +
    46};
    +
    47
    +
    48#define FUSE_MAXOP (sizeof(fuse_ll_ops) / sizeof(fuse_ll_ops[0]))
    +
    49
    +
    50static const char *opname(enum fuse_opcode opcode)
    +
    51{
    +
    52 if (opcode >= FUSE_MAXOP || !fuse_ll_ops[opcode].name)
    +
    53 return "???";
    +
    54 else
    +
    55 return fuse_ll_ops[opcode].name;
    +
    56}
    +
    57
    +
    58
    +
    59static void process_buf(int dir, char *buf, int len)
    +
    60{
    +
    61 static unsigned long long prevuniq = -1;
    +
    62 static int prevopcode;
    +
    63
    +
    64 if (!dir) {
    +
    65 struct fuse_in_header *in = (struct fuse_in_header *) buf;
    +
    66 buf += sizeof(struct fuse_in_header);
    +
    67
    +
    68 printf("unique: %llu, opcode: %s (%i), nodeid: %lu, len: %i, insize: %i\n",
    +
    69 (unsigned long long) in->unique,
    +
    70 opname((enum fuse_opcode) in->opcode), in->opcode,
    +
    71 (unsigned long) in->nodeid, in->len, len);
    +
    72
    +
    73 switch (in->opcode) {
    +
    74 case FUSE_READ: {
    +
    75 struct fuse_read_in *arg = (struct fuse_read_in *) buf;
    +
    76 printf("-READ fh:%llu off:%llu siz:%u rfl:%u own:%llu fl:%u\n",
    +
    77 arg->fh, arg->offset, arg->size, arg->read_flags,
    +
    78 arg->lock_owner, arg->flags);
    +
    79 break;
    +
    80 }
    +
    81 case FUSE_WRITE: {
    +
    82 struct fuse_write_in *arg = (struct fuse_write_in *) buf;
    +
    83 printf("-WRITE fh:%llu off:%llu siz:%u wfl:%u own:%llu fl:%u\n",
    +
    84 arg->fh, arg->offset, arg->size, arg->write_flags,
    +
    85 arg->lock_owner, arg->flags);
    +
    86 break;
    +
    87 }
    +
    88 }
    +
    89 prevuniq = in->unique;
    +
    90 prevopcode = in->opcode;
    +
    91 } else {
    +
    92 struct fuse_out_header *out = (struct fuse_out_header *) buf;
    +
    93 buf += sizeof(struct fuse_out_header);
    +
    94
    +
    95 printf(" unique: %llu, error: %i (%s), len: %i, outsize: %i\n",
    +
    96 (unsigned long long) out->unique, out->error,
    +
    97 strerror(-out->error), out->len, len);
    +
    98
    +
    99 if (out->unique == prevuniq) {
    +
    100 switch (prevopcode) {
    +
    101 case FUSE_GETATTR: {
    +
    102 struct fuse_attr_out *arg = (struct fuse_attr_out *) buf;
    +
    103 printf("+ATTR v:%llu.%09u i:%llu s:%llu b:%llu\n",
    +
    104 arg->attr_valid, arg->attr_valid_nsec,
    +
    105 arg->attr.ino, arg->attr.size, arg->attr.blocks);
    +
    106 break;
    +
    107 }
    +
    108 case FUSE_LOOKUP: {
    +
    109 struct fuse_entry_out *arg = (struct fuse_entry_out *) buf;
    +
    110 printf("+ENTRY nodeid:%llu v:%llu.%09u i:%llu s:%llu b:%llu\n",
    +
    111 arg->nodeid, arg->attr_valid, arg->attr_valid_nsec,
    +
    112 arg->attr.ino, arg->attr.size, arg->attr.blocks);
    +
    113 break;
    +
    114 }
    +
    115 }
    +
    116 }
    +
    117 }
    +
    118
    +
    119}
    +
    120
    +
    121int main(void)
    +
    122{
    +
    123 FILE *in = stdin;
    +
    124 while (1) {
    +
    125 int dir;
    +
    126 int res;
    +
    127 char buf[1048576];
    +
    128 unsigned len = 0;
    +
    129
    +
    130 memset(buf, 0, sizeof(buf));
    +
    131 while (1) {
    +
    132 char str[32];
    +
    133
    +
    134 res = fscanf(in, "%30s", str);
    +
    135 if (res != 1 && feof(in))
    +
    136 return 0;
    +
    137
    +
    138 if (res == 0)
    +
    139 continue;
    +
    140
    +
    141 if (strncmp(str, "read(", 5) == 0) {
    +
    142 dir = 0;
    +
    143 break;
    +
    144 } else if (strncmp(str, "writev(", 7) == 0) {
    +
    145 dir = 1;
    +
    146 break;
    +
    147 }
    +
    148 }
    +
    149
    +
    150 while (1) {
    +
    151 int c = getc(in);
    +
    152 if (c == '"') {
    +
    153 while (1) {
    +
    154 int val;
    +
    155
    +
    156 c = getc(in);
    +
    157 if (c == EOF) {
    +
    158 fprintf(stderr, "eof in string\n");
    +
    159 break;
    +
    160 }
    +
    161 if (c == '\n') {
    +
    162 fprintf(stderr, "eol in string\n");
    +
    163 break;
    +
    164 }
    +
    165 if (c == '"')
    +
    166 break;
    +
    167 if (c != '\\') {
    +
    168 val = c;
    +
    169 } else {
    +
    170 c = getc(in);
    +
    171 switch (c) {
    +
    172 case 'n': val = '\n'; break;
    +
    173 case 'r': val = '\r'; break;
    +
    174 case 't': val = '\t'; break;
    +
    175 case '"': val = '"'; break;
    +
    176 case '\\': val = '\\'; break;
    +
    177 case 'x':
    +
    178 res = scanf("%x", &val);
    +
    179 if (res != 1) {
    +
    180 fprintf(stderr, "parse error\n");
    +
    181 continue;
    +
    182 }
    +
    183 break;
    +
    184 default:
    +
    185 fprintf(stderr, "unknown sequence: '\\%c'\n", c);
    +
    186 continue;
    +
    187 }
    +
    188 }
    +
    189 buf[len++] = val;
    +
    190 }
    +
    191 }
    +
    192 if (c == '\n')
    +
    193 break;
    +
    194 }
    +
    195 process_buf(dir, buf, len);
    +
    196 memset(buf, 0, len);
    +
    197 len = 0;
    +
    198 }
    +
    199}
    +
    + + +
    + + diff --git a/doc/html/structfuse__args.html b/doc/html/structfuse__args.html new file mode 100644 index 0000000..719c50a --- /dev/null +++ b/doc/html/structfuse__args.html @@ -0,0 +1,125 @@ + + + + + + + +libfuse: fuse_args Struct Reference + + + + + + + +
    +
    + + + + + + +
    +
    libfuse +
    +
    +
    + + + + + + + +
    +
    +
    + +
    fuse_args Struct Reference
    +
    +
    + +

    #include <fuse_opt.h>

    + + + + + +

    +Data Fields

    int argc
    char ** argv
    int allocated
    +

    Detailed Description

    +

    Argument list

    + +

    Definition at line 109 of file fuse_opt.h.

    +

    Field Documentation

    + +

    ◆ allocated

    + +
    +
    + + + + +
    int fuse_args::allocated
    +
    +

    Is 'argv' allocated?

    + +

    Definition at line 117 of file fuse_opt.h.

    + +
    +
    + +

    ◆ argc

    + +
    +
    + + + + +
    int fuse_args::argc
    +
    +

    Argument count

    + +

    Definition at line 111 of file fuse_opt.h.

    + +
    +
    + +

    ◆ argv

    + +
    +
    + + + + +
    char** fuse_args::argv
    +
    +

    Argument vector. NULL terminated

    + +

    Definition at line 114 of file fuse_opt.h.

    + +
    +
    +
    The documentation for this struct was generated from the following file: +
    + + +
    + + diff --git a/doc/html/structfuse__buf.html b/doc/html/structfuse__buf.html new file mode 100644 index 0000000..dafb2f0 --- /dev/null +++ b/doc/html/structfuse__buf.html @@ -0,0 +1,184 @@ + + + + + + + +libfuse: fuse_buf Struct Reference + + + + + + + +
    +
    + + + + + + +
    +
    libfuse +
    +
    +
    + + + + + + + +
    +
    +
    + +
    fuse_buf Struct Reference
    +
    +
    + +

    #include <fuse_common.h>

    + + + + + + + + +

    +Data Fields

    size_t size
    enum fuse_buf_flags flags
    void * mem
    int fd
    off_t pos
    size_t mem_size
    +

    Detailed Description

    +

    Single data buffer

    +

    Generic data buffer for I/O, extended attributes, etc... Data may be supplied as a memory pointer or as a file descriptor

    + +

    Definition at line 893 of file fuse_common.h.

    +

    Field Documentation

    + +

    ◆ fd

    + +
    +
    + + + + +
    int fuse_buf::fd
    +
    +

    File descriptor

    +

    Used if FUSE_BUF_IS_FD flag is set.

    + +

    Definition at line 916 of file fuse_common.h.

    + +
    +
    + +

    ◆ flags

    + +
    +
    + + + + +
    enum fuse_buf_flags fuse_buf::flags
    +
    +

    Buffer flags

    + +

    Definition at line 902 of file fuse_common.h.

    + +
    +
    + +

    ◆ mem

    + +
    +
    + + + + +
    void* fuse_buf::mem
    +
    +

    Memory pointer

    +

    Used unless FUSE_BUF_IS_FD flag is set.

    + +

    Definition at line 909 of file fuse_common.h.

    + +
    +
    + +

    ◆ mem_size

    + +
    +
    + + + + +
    size_t fuse_buf::mem_size
    +
    +

    Size of memory pointer

    +

    Used only if mem was internally allocated. Not used if mem was user-provided.

    + +

    Definition at line 931 of file fuse_common.h.

    + +
    +
    + +

    ◆ pos

    + +
    +
    + + + + +
    off_t fuse_buf::pos
    +
    +

    File position

    +

    Used if FUSE_BUF_FD_SEEK flag is set.

    + +

    Definition at line 923 of file fuse_common.h.

    + +
    +
    + +

    ◆ size

    + +
    +
    + + + + +
    size_t fuse_buf::size
    +
    +

    Size of data in bytes

    + +

    Definition at line 897 of file fuse_common.h.

    + +
    +
    +
    The documentation for this struct was generated from the following file: +
    + + +
    + + diff --git a/doc/html/structfuse__bufvec.html b/doc/html/structfuse__bufvec.html new file mode 100644 index 0000000..cad005a --- /dev/null +++ b/doc/html/structfuse__bufvec.html @@ -0,0 +1,145 @@ + + + + + + + +libfuse: fuse_bufvec Struct Reference + + + + + + + +
    +
    + + + + + + +
    +
    libfuse +
    +
    +
    + + + + + + + +
    +
    +
    + +
    fuse_bufvec Struct Reference
    +
    +
    + +

    #include <fuse_common.h>

    + + + + + + +

    +Data Fields

    size_t count
    size_t idx
    size_t off
    struct fuse_buf buf [1]
    +

    Detailed Description

    +

    Data buffer vector

    +

    An array of data buffers, each containing a memory pointer or a file descriptor.

    +

    Allocate dynamically to add more than one buffer.

    + +

    Definition at line 942 of file fuse_common.h.

    +

    Field Documentation

    + +

    ◆ buf

    + +
    +
    + + + + +
    struct fuse_buf fuse_bufvec::buf[1]
    +
    +

    Array of buffers

    + +

    Definition at line 961 of file fuse_common.h.

    + +
    +
    + +

    ◆ count

    + +
    +
    + + + + +
    size_t fuse_bufvec::count
    +
    +

    Number of buffers in the array

    + +

    Definition at line 946 of file fuse_common.h.

    + +
    +
    + +

    ◆ idx

    + +
    +
    + + + + +
    size_t fuse_bufvec::idx
    +
    +

    Index of current buffer within the array

    + +

    Definition at line 951 of file fuse_common.h.

    + +
    +
    + +

    ◆ off

    + +
    +
    + + + + +
    size_t fuse_bufvec::off
    +
    +

    Current offset within the current buffer

    + +

    Definition at line 956 of file fuse_common.h.

    + +
    +
    +
    The documentation for this struct was generated from the following file: +
    + + +
    + + diff --git a/doc/html/structfuse__cmdline__opts.html b/doc/html/structfuse__cmdline__opts.html new file mode 100644 index 0000000..76a6b7d --- /dev/null +++ b/doc/html/structfuse__cmdline__opts.html @@ -0,0 +1,64 @@ + + + + + + + +libfuse: fuse_cmdline_opts Struct Reference + + + + + + + +
    +
    + + + + + + +
    +
    libfuse +
    +
    +
    + + + + + + + +
    +
    +
    +
    fuse_cmdline_opts Struct Reference
    +
    +
    + +

    #include <fuse_lowlevel.h>

    +

    Detailed Description

    +

    Note: Any addition to this struct needs to create a compatibility symbol for fuse_parse_cmdline(). For ABI compatibility reasons it is also not possible to remove struct members.

    + +

    Definition at line 2047 of file fuse_lowlevel.h.

    +

    The documentation for this struct was generated from the following file: +
    + + +
    + + diff --git a/doc/html/structfuse__config.html b/doc/html/structfuse__config.html new file mode 100644 index 0000000..501bc90 --- /dev/null +++ b/doc/html/structfuse__config.html @@ -0,0 +1,482 @@ + + + + + + + +libfuse: fuse_config Struct Reference + + + + + + + +
    +
    + + + + + + +
    +
    libfuse +
    +
    +
    + + + + + + + +
    +
    +
    + +
    fuse_config Struct Reference
    +
    +
    + +

    #include <fuse.h>

    + + + + + + + + + + + + + + + + + + + + + + + + +

    +Data Fields

    int32_t set_gid
    int32_t set_uid
    int32_t set_mode
    double entry_timeout
    double negative_timeout
    double attr_timeout
    int32_t intr
    int32_t intr_signal
    int32_t remember
    int32_t hard_remove
    int32_t use_ino
    int32_t readdir_ino
    int32_t direct_io
    int32_t kernel_cache
    int32_t auto_cache
    int32_t nullpath_ok
    int32_t show_help
    uint32_t fmask
    int32_t no_rofd_flush
    int32_t parallel_direct_writes
    uint32_t flags
    uint64_t reserved [48]
    +

    Detailed Description

    +

    Configuration of the high-level API

    +

    This structure is initialized from the arguments passed to fuse_new(), and then passed to the file system's init() handler which should ensure that the configuration is compatible with the file system implementation.

    +

    Note: this data structure is ABI sensitive, new options have to be appended at the end of the structure

    + +

    Definition at line 107 of file fuse.h.

    +

    Field Documentation

    + +

    ◆ attr_timeout

    + +
    +
    + + + + +
    double fuse_config::attr_timeout
    +
    +

    The timeout in seconds for which file/directory attributes (as returned by e.g. the getattr handler) are cached.

    + +

    Definition at line 149 of file fuse.h.

    + +
    +
    + +

    ◆ auto_cache

    + +
    +
    + + + + +
    int32_t fuse_config::auto_cache
    +
    +

    This option is an alternative to kernel_cache. Instead of unconditionally keeping cached data, the cached data is invalidated on open(2) if if the modification time or the size of the file has changed since it was last opened.

    + +

    Definition at line 259 of file fuse.h.

    + +
    +
    + +

    ◆ direct_io

    + +
    +
    + + + + +
    int32_t fuse_config::direct_io
    +
    +

    This option disables the use of page cache (file content cache) in the kernel for this filesystem. This has several affects:

    +
      +
    1. Each read(2) or write(2) system call will initiate one or more read or write operations, data will not be cached in the kernel.
    2. +
    3. The return value of the read() and write() system calls will correspond to the return values of the read and write operations. This is useful for example if the file size is not known in advance (before reading it).
    4. +
    +

    Internally, enabling this option causes fuse to set the direct_io field of struct fuse_file_info - overwriting any value that was put there by the file system.

    + +

    Definition at line 232 of file fuse.h.

    + +
    +
    + +

    ◆ entry_timeout

    + +
    +
    + + + + +
    double fuse_config::entry_timeout
    +
    +

    The timeout in seconds for which name lookups will be cached.

    + +

    Definition at line 133 of file fuse.h.

    + +
    +
    + +

    ◆ flags

    + +
    +
    + + + + +
    uint32_t fuse_config::flags
    +
    +

    Reserved for future use.

    + +

    Definition at line 324 of file fuse.h.

    + +
    +
    + +

    ◆ fmask

    + +
    +
    + + + + +
    uint32_t fuse_config::fmask
    +
    +

    fmask and dmask function the same way as umask, but apply to files and directories separately. If non-zero, fmask and dmask take precedence over the umask setting.

    + +

    Definition at line 294 of file fuse.h.

    + +
    +
    + +

    ◆ hard_remove

    + +
    +
    + + + + +
    int32_t fuse_config::hard_remove
    +
    +

    The default behavior is that if an open file is deleted, the file is renamed to a hidden file (.fuse_hiddenXXX), and only removed when the file is finally released. This relieves the filesystem implementation of having to deal with this problem. This option disables the hiding behavior, and files are removed immediately in an unlink operation (or in a rename operation which overwrites an existing file).

    +

    It is recommended that you not use the hard_remove option. When hard_remove is set, the following libc functions fail on unlinked files (returning errno of ENOENT): read(2), write(2), fsync(2), close(2), f*xattr(2), ftruncate(2), fstat(2), fchmod(2), fchown(2)

    + +

    Definition at line 191 of file fuse.h.

    + +
    +
    + +

    ◆ intr

    + +
    +
    + + + + +
    int32_t fuse_config::intr
    +
    +

    Allow requests to be interrupted

    + +

    Definition at line 154 of file fuse.h.

    + +
    +
    + +

    ◆ intr_signal

    + +
    +
    + + + + +
    int32_t fuse_config::intr_signal
    +
    +

    Specify which signal number to send to the filesystem when a request is interrupted. The default is hardcoded to USR1.

    + +

    Definition at line 161 of file fuse.h.

    + +
    +
    + +

    ◆ kernel_cache

    + +
    +
    + + + + +
    int32_t fuse_config::kernel_cache
    +
    +

    This option disables flushing the cache of the file contents on every open(2). This should only be enabled on filesystems where the file data is never changed externally (not through the mounted FUSE filesystem). Thus it is not suitable for network filesystems and other intermediate filesystems.

    +

    NOTE: if this option is not specified (and neither direct_io) data is still cached after the open(2), so a read(2) system call will not always initiate a read operation.

    +

    Internally, enabling this option causes fuse to set the keep_cache field of struct fuse_file_info - overwriting any value that was put there by the file system.

    + +

    Definition at line 251 of file fuse.h.

    + +
    +
    + +

    ◆ negative_timeout

    + +
    +
    + + + + +
    double fuse_config::negative_timeout
    +
    +

    The timeout in seconds for which a negative lookup will be cached. This means, that if file did not exist (lookup returned ENOENT), the lookup will only be redone after the timeout, and the file/directory will be assumed to not exist until then. A value of zero means that negative lookups are not cached.

    + +

    Definition at line 143 of file fuse.h.

    + +
    +
    + +

    ◆ no_rofd_flush

    + +
    +
    + + + + +
    int32_t fuse_config::no_rofd_flush
    +
    +

    By default, fuse waits for all pending writes to complete and calls the FLUSH operation on close(2) of every fuse fd. With this option, wait and FLUSH are not done for read-only fuse fd, similar to the behavior of NFS/SMB clients.

    + +

    Definition at line 303 of file fuse.h.

    + +
    +
    + +

    ◆ nullpath_ok

    + +
    +
    + + + + +
    int32_t fuse_config::nullpath_ok
    +
    +

    If this option is given the file-system handlers for the following operations will not receive path information: read, write, flush, release, fallocate, fsync, readdir, releasedir, fsyncdir, lock, ioctl and poll.

    +

    For the truncate, getattr, chmod, chown and utimens operations the path will be provided only if the struct fuse_file_info argument is NULL.

    + +

    Definition at line 279 of file fuse.h.

    + +
    +
    + +

    ◆ parallel_direct_writes

    + +
    +
    + + + + +
    int32_t fuse_config::parallel_direct_writes
    +
    +

    Allow parallel direct-io writes to operate on the same file.

    +

    FUSE implementations which do not handle parallel writes on same file/region should NOT enable this option at all as it might lead to data inconsistencies.

    +

    For the FUSE implementations which have their own mechanism of cache/data integrity are beneficiaries of this setting as it now open doors to parallel writes on the same file (without enabling this setting, all direct writes on the same file are serialized, resulting in huge data bandwidth loss).

    + +

    Definition at line 318 of file fuse.h.

    + +
    +
    + +

    ◆ readdir_ino

    + +
    +
    + + + + +
    int32_t fuse_config::readdir_ino
    +
    +

    If use_ino option is not given, still try to fill in the d_ino field in readdir(2). If the name was previously looked up, and is still in the cache, the inode number found there will be used. Otherwise it will be set to -1. If use_ino option is given, this option is ignored.

    + +

    Definition at line 213 of file fuse.h.

    + +
    +
    + +

    ◆ remember

    + +
    +
    + + + + +
    int32_t fuse_config::remember
    +
    +

    Normally, FUSE assigns inodes to paths only for as long as the kernel is aware of them. With this option inodes are instead remembered for at least this many seconds. This will require more memory, but may be necessary when using applications that make use of inode numbers.

    +

    A number of -1 means that inodes will be remembered for the entire life-time of the file-system process.

    + +

    Definition at line 173 of file fuse.h.

    + +
    +
    + +

    ◆ reserved

    + +
    +
    + + + + +
    uint64_t fuse_config::reserved[48]
    +
    +

    Reserved for future use.

    + +

    Definition at line 329 of file fuse.h.

    + +
    +
    + +

    ◆ set_gid

    + +
    +
    + + + + +
    int32_t fuse_config::set_gid
    +
    +

    If set_gid is non-zero, the st_gid attribute of each file is overwritten with the value of gid.

    + +

    Definition at line 112 of file fuse.h.

    + +
    +
    + +

    ◆ set_mode

    + +
    +
    + + + + +
    int32_t fuse_config::set_mode
    +
    +

    If set_mode is non-zero, the any permissions bits set in umask are unset in the st_mode attribute of each file.

    + +

    Definition at line 126 of file fuse.h.

    + +
    +
    + +

    ◆ set_uid

    + +
    +
    + + + + +
    int32_t fuse_config::set_uid
    +
    +

    If set_uid is non-zero, the st_uid attribute of each file is overwritten with the value of uid.

    + +

    Definition at line 119 of file fuse.h.

    + +
    +
    + +

    ◆ show_help

    + +
    +
    + + + + +
    int32_t fuse_config::show_help
    +
    +

    These 3 options are used by libfuse internally and should not be touched.

    + +

    Definition at line 285 of file fuse.h.

    + +
    +
    + +

    ◆ use_ino

    + +
    +
    + + + + +
    int32_t fuse_config::use_ino
    +
    +

    Honor the st_ino field in the functions getattr() and fill_dir(). This value is used to fill in the st_ino field in the stat(2), lstat(2), fstat(2) functions and the d_ino field in the readdir(2) function. The filesystem does not have to guarantee uniqueness, however some applications rely on this value being unique for the whole filesystem.

    +

    Note that this does not affect the inode that libfuse and the kernel use internally (also called the "nodeid").

    + +

    Definition at line 204 of file fuse.h.

    + +
    +
    +
    The documentation for this struct was generated from the following file: +
    + + +
    + + diff --git a/doc/html/structfuse__conn__info.html b/doc/html/structfuse__conn__info.html new file mode 100644 index 0000000..c381533 --- /dev/null +++ b/doc/html/structfuse__conn__info.html @@ -0,0 +1,357 @@ + + + + + + + +libfuse: fuse_conn_info Struct Reference + + + + + + + +
    +
    + + + + + + +
    +
    libfuse +
    +
    +
    + + + + + + + +
    +
    +
    + +
    fuse_conn_info Struct Reference
    +
    +
    + +

    #include <fuse_common.h>

    + + + + + + + + + + + + + + + + + +

    +Data Fields

    uint32_t proto_major
    uint32_t proto_minor
    uint32_t max_write
    uint32_t max_read
    uint32_t max_readahead
    uint32_t capable
    uint32_t want
    uint32_t max_background
    uint32_t congestion_threshold
    uint32_t time_gran
    uint32_t no_interrupt: 1
    uint64_t capable_ext
    uint64_t want_ext
    uint16_t request_timeout
    uint16_t reserved [31]
    +

    Detailed Description

    +

    Connection information, passed to the ->init() method

    +

    Some of the elements are read-write, these can be changed to indicate the value requested by the filesystem. The requested value must usually be smaller than the indicated value.

    +

    Note: The capable and want fields are limited to 32 bits for ABI compatibility. For full 64-bit capability support, use the capable_ext and want_ext fields instead.

    + +

    Definition at line 551 of file fuse_common.h.

    +

    Field Documentation

    + +

    ◆ capable

    + +
    +
    + + + + +
    uint32_t fuse_conn_info::capable
    +
    +

    Capability flags that the kernel supports (read-only)

    +

    Deprecated left over for ABI compatibility, use capable_ext

    + +

    Definition at line 591 of file fuse_common.h.

    + +
    +
    + +

    ◆ capable_ext

    + +
    +
    + + + + +
    uint64_t fuse_conn_info::capable_ext
    +
    +

    Extended capability flags that the kernel supports (read-only) This field provides full 64-bit capability support.

    + +

    Definition at line 699 of file fuse_common.h.

    + +
    +
    + +

    ◆ congestion_threshold

    + +
    +
    + + + + +
    uint32_t fuse_conn_info::congestion_threshold
    +
    +

    Kernel congestion threshold parameter. If the number of pending background requests exceeds this number, the FUSE kernel module will mark the filesystem as "congested". This instructs the kernel to expect that queued requests will take some time to complete, and to adjust its algorithms accordingly (e.g. by putting a waiting thread to sleep instead of using a busy-loop).

    + +

    Definition at line 643 of file fuse_common.h.

    + +
    +
    + +

    ◆ max_background

    + +
    +
    + + + + +
    uint32_t fuse_conn_info::max_background
    +
    +

    Maximum number of pending "background" requests. A background request is any type of request for which the total number is not limited by other means. As of kernel 4.8, only two types of requests fall into this category:

    +
      +
    1. Read-ahead requests
    2. +
    3. Asynchronous direct I/O requests
    4. +
    +

    Read-ahead requests are generated (if max_readahead is non-zero) by the kernel to preemptively fill its caches when it anticipates that userspace will soon read more data.

    +

    Asynchronous direct I/O requests are generated if FUSE_CAP_ASYNC_DIO is enabled and userspace submits a large direct I/O request. In this case the kernel will internally split it up into multiple smaller requests and submit them to the filesystem concurrently.

    +

    Note that the following requests are not background requests: writeback requests (limited by the kernel's flusher algorithm), regular (i.e., synchronous and buffered) userspace read/write requests (limited to one per thread), asynchronous read requests (Linux's io_submit(2) call actually blocks, so these are also limited to one per thread).

    + +

    Definition at line 633 of file fuse_common.h.

    + +
    +
    + +

    ◆ max_read

    + +
    +
    + + + + +
    uint32_t fuse_conn_info::max_read
    +
    +

    Maximum size of read requests. A value of zero indicates no limit. However, even if the filesystem does not specify a limit, the maximum size of read requests will still be limited by the kernel.

    +

    NOTE: For the time being, the maximum size of read requests must be set both here and passed to fuse_session_new() using the -o max_read=<n> mount option. At some point in the future, specifying the mount option will no longer be necessary.

    + +

    Definition at line 579 of file fuse_common.h.

    + +
    +
    + +

    ◆ max_readahead

    + +
    +
    + + + + +
    uint32_t fuse_conn_info::max_readahead
    +
    +

    Maximum readahead

    + +

    Definition at line 584 of file fuse_common.h.

    + +
    +
    + +

    ◆ max_write

    + +
    +
    + + + + +
    uint32_t fuse_conn_info::max_write
    +
    +

    Maximum size of the write buffer

    + +

    Definition at line 565 of file fuse_common.h.

    + +
    +
    + +

    ◆ no_interrupt

    + +
    +
    + + + + +
    uint32_t fuse_conn_info::no_interrupt
    +
    +

    Disable FUSE_INTERRUPT requests.

    +

    Enable no_interrupt option to: 1) Avoid unnecessary locking operations and list operations, 2) Return ENOSYS for the reply of FUSE_INTERRUPT request to inform the kernel not to send the FUSE_INTERRUPT request.

    + +

    Definition at line 690 of file fuse_common.h.

    + +
    +
    + +

    ◆ proto_major

    + +
    +
    + + + + +
    uint32_t fuse_conn_info::proto_major
    +
    +

    Major version of the protocol (read-only)

    + +

    Definition at line 555 of file fuse_common.h.

    + +
    +
    + +

    ◆ proto_minor

    + +
    +
    + + + + +
    uint32_t fuse_conn_info::proto_minor
    +
    +

    Minor version of the protocol (read-only)

    + +

    Definition at line 560 of file fuse_common.h.

    + +
    +
    + +

    ◆ request_timeout

    + +
    +
    + + + + +
    uint16_t fuse_conn_info::request_timeout
    +
    +

    Request timeout (in seconds). If the request is not answered by this timeout, the connection will be aborted by the kernel.

    + +

    Definition at line 715 of file fuse_common.h.

    + +
    +
    + +

    ◆ reserved

    + +
    +
    + + + + +
    uint16_t fuse_conn_info::reserved[31]
    +
    +

    For future use.

    + +

    Definition at line 720 of file fuse_common.h.

    + +
    +
    + +

    ◆ time_gran

    + +
    +
    + + + + +
    uint32_t fuse_conn_info::time_gran
    +
    +

    When FUSE_CAP_WRITEBACK_CACHE is enabled, the kernel is responsible for updating mtime and ctime when write requests are received. The updated values are passed to the filesystem with setattr() requests. However, if the filesystem does not support the full resolution of the kernel timestamps (nanoseconds), the mtime and ctime values used by kernel and filesystem will differ (and result in an apparent change of times after a cache flush).

    +

    To prevent this problem, this variable can be used to inform the kernel about the timestamp granularity supported by the file-system. The value should be power of 10. The default is 1, i.e. full nano-second resolution. Filesystems supporting only second resolution should set this to 1000000000.

    + +

    Definition at line 660 of file fuse_common.h.

    + +
    +
    + +

    ◆ want

    + +
    +
    + + + + +
    uint32_t fuse_conn_info::want
    +
    +

    Capability flags that the filesystem wants to enable.

    +

    libfuse attempts to initialize this field with reasonable default values before calling the init() handler.

    +

    Deprecated left over for ABI compatibility. Use want_ext with the helper functions fuse_set_feature_flag() / fuse_unset_feature_flag()

    + +

    Definition at line 603 of file fuse_common.h.

    + +
    +
    + +

    ◆ want_ext

    + +
    +
    + + + + +
    uint64_t fuse_conn_info::want_ext
    +
    +

    Extended capability flags that the filesystem wants to enable. This field provides full 64-bit capability support.

    +

    Don't set this field directly, but use the helper functions fuse_set_feature_flag() / fuse_unset_feature_flag()

    + +

    Definition at line 709 of file fuse_common.h.

    + +
    +
    +
    The documentation for this struct was generated from the following file: +
    + + +
    + + diff --git a/doc/html/structfuse__context.html b/doc/html/structfuse__context.html new file mode 100644 index 0000000..06375e7 --- /dev/null +++ b/doc/html/structfuse__context.html @@ -0,0 +1,180 @@ + + + + + + + +libfuse: fuse_context Struct Reference + + + + + + + +
    +
    + + + + + + +
    +
    libfuse +
    +
    +
    + + + + + + + +
    +
    +
    + +
    fuse_context Struct Reference
    +
    +
    + +

    #include <fuse.h>

    + + + + + + + + +

    +Data Fields

    struct fuse * fuse
    uid_t uid
    gid_t gid
    pid_t pid
    void * private_data
    mode_t umask
    +

    Detailed Description

    +

    Extra context that may be needed by some filesystems

    +

    The uid, gid and pid fields are not filled in case of a writepage operation.

    + +

    Definition at line 877 of file fuse.h.

    +

    Field Documentation

    + +

    ◆ fuse

    + +
    +
    + + + + +
    struct fuse* fuse_context::fuse
    +
    +

    Pointer to the fuse object

    + +

    Definition at line 879 of file fuse.h.

    + +
    +
    + +

    ◆ gid

    + +
    +
    + + + + +
    gid_t fuse_context::gid
    +
    +

    Group ID of the calling process

    + +

    Definition at line 885 of file fuse.h.

    + +
    +
    + +

    ◆ pid

    + +
    +
    + + + + +
    pid_t fuse_context::pid
    +
    +

    Process ID of the calling thread

    + +

    Definition at line 888 of file fuse.h.

    + +
    +
    + +

    ◆ private_data

    + +
    +
    + + + + +
    void* fuse_context::private_data
    +
    +

    Private filesystem data

    + +

    Definition at line 891 of file fuse.h.

    + +
    +
    + +

    ◆ uid

    + +
    +
    + + + + +
    uid_t fuse_context::uid
    +
    +

    User ID of the calling process

    + +

    Definition at line 882 of file fuse.h.

    + +
    +
    + +

    ◆ umask

    + +
    +
    + + + + +
    mode_t fuse_context::umask
    +
    +

    Umask of the calling process

    + +

    Definition at line 894 of file fuse.h.

    + +
    +
    +
    The documentation for this struct was generated from the following file: +
    + + +
    + + diff --git a/doc/html/structfuse__ctx.html b/doc/html/structfuse__ctx.html new file mode 100644 index 0000000..235e6b9 --- /dev/null +++ b/doc/html/structfuse__ctx.html @@ -0,0 +1,144 @@ + + + + + + + +libfuse: fuse_ctx Struct Reference + + + + + + + +
    +
    + + + + + + +
    +
    libfuse +
    +
    +
    + + + + + + + +
    +
    +
    + +
    fuse_ctx Struct Reference
    +
    +
    + +

    #include <fuse_lowlevel.h>

    + + + + + + +

    +Data Fields

    uid_t uid
    gid_t gid
    pid_t pid
    mode_t umask
    +

    Detailed Description

    +

    Additional context associated with requests.

    +

    Note that the reported client uid, gid and pid may be zero in some situations. For example, if the FUSE file system is running in a PID or user namespace but then accessed from outside the namespace, there is no valid uid/pid/gid that could be reported.

    + +

    Definition at line 115 of file fuse_lowlevel.h.

    +

    Field Documentation

    + +

    ◆ gid

    + +
    +
    + + + + +
    gid_t fuse_ctx::gid
    +
    +

    Group ID of the calling process

    + +

    Definition at line 120 of file fuse_lowlevel.h.

    + +
    +
    + +

    ◆ pid

    + +
    +
    + + + + +
    pid_t fuse_ctx::pid
    +
    +

    Thread ID of the calling process

    + +

    Definition at line 123 of file fuse_lowlevel.h.

    + +
    +
    + +

    ◆ uid

    + +
    +
    + + + + +
    uid_t fuse_ctx::uid
    +
    +

    User ID of the calling process

    + +

    Definition at line 117 of file fuse_lowlevel.h.

    + +
    +
    + +

    ◆ umask

    + +
    +
    + + + + +
    mode_t fuse_ctx::umask
    +
    +

    Umask of the calling process

    + +

    Definition at line 126 of file fuse_lowlevel.h.

    + +
    +
    +
    The documentation for this struct was generated from the following file: +
    + + +
    + + diff --git a/doc/html/structfuse__entry__param.html b/doc/html/structfuse__entry__param.html new file mode 100644 index 0000000..8586e3c --- /dev/null +++ b/doc/html/structfuse__entry__param.html @@ -0,0 +1,164 @@ + + + + + + + +libfuse: fuse_entry_param Struct Reference + + + + + + + +
    +
    + + + + + + +
    +
    libfuse +
    +
    +
    + + + + + + + +
    +
    +
    + +
    fuse_entry_param Struct Reference
    +
    +
    + +

    #include <fuse_lowlevel.h>

    + + + + + + + +

    +Data Fields

    fuse_ino_t ino
    uint64_t generation
    struct stat attr
    double attr_timeout
    double entry_timeout
    +

    Detailed Description

    +

    Directory entry parameters supplied to fuse_reply_entry()

    + +

    Definition at line 63 of file fuse_lowlevel.h.

    +

    Field Documentation

    + +

    ◆ attr

    + +
    +
    + + + + +
    struct stat fuse_entry_param::attr
    +
    +

    Inode attributes.

    +

    Even if attr_timeout == 0, attr must be correct. For example, for open(), FUSE uses attr.st_size from lookup() to determine how many bytes to request. If this value is not correct, incorrect data will be returned.

    + +

    Definition at line 92 of file fuse_lowlevel.h.

    + +
    +
    + +

    ◆ attr_timeout

    + +
    +
    + + + + +
    double fuse_entry_param::attr_timeout
    +
    +

    Validity timeout (in seconds) for inode attributes. If attributes only change as a result of requests that come through the kernel, this should be set to a very large value.

    + +

    Definition at line 98 of file fuse_lowlevel.h.

    + +
    +
    + +

    ◆ entry_timeout

    + +
    +
    + + + + +
    double fuse_entry_param::entry_timeout
    +
    +

    Validity timeout (in seconds) for the name. If directory entries are changed/deleted only as a result of requests that come through the kernel, this should be set to a very large value.

    + +

    Definition at line 104 of file fuse_lowlevel.h.

    + +
    +
    + +

    ◆ generation

    + +
    +
    + + + + +
    uint64_t fuse_entry_param::generation
    +
    +

    Generation number for this entry.

    +

    If the file system will be exported over NFS, the ino/generation pairs need to be unique over the file system's lifetime (rather than just the mount time). So if the file system reuses an inode after it has been deleted, it must assign a new, previously unused generation number to the inode at the same time.

    + +

    Definition at line 83 of file fuse_lowlevel.h.

    + +
    +
    + +

    ◆ ino

    + +
    +
    + + + + +
    fuse_ino_t fuse_entry_param::ino
    +
    +

    Unique inode number

    +

    In lookup, zero means negative entry (from version 2.5) Returning ENOENT also means negative entry, but by setting zero ino the kernel may cache negative entries for entry_timeout seconds.

    + +

    Definition at line 71 of file fuse_lowlevel.h.

    + +
    +
    +
    The documentation for this struct was generated from the following file: +
    + + +
    + + diff --git a/doc/html/structfuse__ext__header.html b/doc/html/structfuse__ext__header.html new file mode 100644 index 0000000..d7d072b --- /dev/null +++ b/doc/html/structfuse__ext__header.html @@ -0,0 +1,65 @@ + + + + + + + +libfuse: fuse_ext_header Struct Reference + + + + + + + +
    +
    + + + + + + +
    +
    libfuse +
    +
    +
    + + + + + + + +
    +
    +
    +
    fuse_ext_header Struct Reference
    +
    +
    + +

    #include <fuse_kernel.h>

    +

    Detailed Description

    +

    struct fuse_ext_header - extension header @size: total size of this extension including this header @type: type of extension

    +

    This is made compatible with fuse_secctx_header by using type values > FUSE_MAX_NR_SECCTX

    + +

    Definition at line 1225 of file fuse_kernel.h.

    +

    The documentation for this struct was generated from the following file: +
    + + +
    + + diff --git a/doc/html/structfuse__file__info.html b/doc/html/structfuse__file__info.html new file mode 100644 index 0000000..05cbb4b --- /dev/null +++ b/doc/html/structfuse__file__info.html @@ -0,0 +1,361 @@ + + + + + + + +libfuse: fuse_file_info Struct Reference + + + + + + + +
    +
    + + + + + + +
    +
    libfuse +
    +
    +
    + + + + + + + +
    +
    +
    + +
    fuse_file_info Struct Reference
    +
    +
    + +

    #include <fuse_common.h>

    + + + + + + + + + + + + + + + + + + +

    +Data Fields

    int32_t flags
    uint32_t writepage: 1
    uint32_t direct_io: 1
    uint32_t keep_cache: 1
    uint32_t flush: 1
    uint32_t nonseekable: 1
    uint32_t cache_readdir: 1
    uint32_t noflush: 1
    uint32_t parallel_direct_writes: 1
    uint32_t kill_suidgid: 1
    uint32_t padding: 22
    uint64_t fh
    uint64_t lock_owner
    uint32_t poll_events
    int32_t backing_id
    uint64_t compat_flags
    +

    Detailed Description

    +

    Information about an open file.

    +

    File Handles are created by the open, opendir, and create methods and closed by the release and releasedir methods. Multiple file handles may be concurrently open for the same file. Generally, a client will create one file handle per file descriptor, though in some cases multiple file descriptors can share a single file handle.

    +

    Note: This data structure is ABI sensitive, new parameters have to be added within padding/padding2 bits and always below existing parameters.

    + +

    Definition at line 50 of file fuse_common.h.

    +

    Field Documentation

    + +

    ◆ backing_id

    + +
    +
    + + + + +
    int32_t fuse_file_info::backing_id
    +
    +

    Passthrough backing file id. May be filled in by filesystem in create and open. It is used to create a passthrough connection between FUSE file and backing file.

    + +

    Definition at line 127 of file fuse_common.h.

    + +
    +
    + +

    ◆ cache_readdir

    + +
    +
    + + + + +
    uint32_t fuse_file_info::cache_readdir
    +
    +

    Can be filled in by opendir. It signals the kernel to enable caching of entries returned by readdir(). Has no effect when set in other contexts (in particular it does nothing when set by open()).

    + +

    Definition at line 89 of file fuse_common.h.

    + +
    +
    + +

    ◆ compat_flags

    + +
    +
    + + + + +
    uint64_t fuse_file_info::compat_flags
    +
    +

    struct fuse_file_info api and abi flags

    + +

    Definition at line 130 of file fuse_common.h.

    + +
    +
    + +

    ◆ direct_io

    + +
    +
    + + + + +
    uint32_t fuse_file_info::direct_io
    +
    +

    Can be filled in by open/create, to use direct I/O on this file.

    + +

    Definition at line 63 of file fuse_common.h.

    + +
    +
    + +

    ◆ fh

    + +
    +
    + + + + +
    uint64_t fuse_file_info::fh
    +
    +

    File handle id. May be filled in by filesystem in create, open, and opendir(). Available in most other file operations on the same file handle.

    + +

    Definition at line 115 of file fuse_common.h.

    + +
    +
    + +

    ◆ flags

    + +
    +
    + + + + +
    int32_t fuse_file_info::flags
    +
    +

    Open flags. Available in open(), release() and create()

    + +

    Definition at line 52 of file fuse_common.h.

    + +
    +
    + +

    ◆ flush

    + +
    +
    + + + + +
    uint32_t fuse_file_info::flush
    +
    +

    Indicates a flush operation. Set in flush operation, also maybe set in highlevel lock operation and lowlevel release operation.

    + +

    Definition at line 74 of file fuse_common.h.

    + +
    +
    + +

    ◆ keep_cache

    + +
    +
    + + + + +
    uint32_t fuse_file_info::keep_cache
    +
    +

    Can be filled in by open and opendir. It signals the kernel that any currently cached data (ie., data that the filesystem provided the last time the file/directory was open) need not be invalidated when the file/directory is closed.

    + +

    Definition at line 69 of file fuse_common.h.

    + +
    +
    + +

    ◆ kill_suidgid

    + +
    +
    + + + + +
    uint32_t fuse_file_info::kill_suidgid
    +
    +

    Set in open, create and write when KILLPRIV_V2 was negotiated and the application issuing the syscall lacks CAP_FSETID; the filesystem's own privileges do not matter. The filesystem then has to remove setuid/setgid bits.

    + +

    Definition at line 105 of file fuse_common.h.

    + +
    +
    + +

    ◆ lock_owner

    + +
    +
    + + + + +
    uint64_t fuse_file_info::lock_owner
    +
    +

    Lock owner id. Available in locking operations and flush

    + +

    Definition at line 118 of file fuse_common.h.

    + +
    +
    + +

    ◆ noflush

    + +
    +
    + + + + +
    uint32_t fuse_file_info::noflush
    +
    +

    Can be filled in by open, to indicate that flush is not needed on close.

    + +

    Definition at line 93 of file fuse_common.h.

    + +
    +
    + +

    ◆ nonseekable

    + +
    +
    + + + + +
    uint32_t fuse_file_info::nonseekable
    +
    +

    Can be filled in by open, to indicate that the file is not seekable.

    + +

    Definition at line 78 of file fuse_common.h.

    + +
    +
    + +

    ◆ padding

    + +
    +
    + + + + +
    uint32_t fuse_file_info::padding
    +
    +

    Padding. Reserved for future use

    + +

    Definition at line 108 of file fuse_common.h.

    + +
    +
    + +

    ◆ parallel_direct_writes

    + +
    +
    + + + + +
    uint32_t fuse_file_info::parallel_direct_writes
    +
    +

    Can be filled by open/create, to allow parallel direct writes on this file

    + +

    Definition at line 97 of file fuse_common.h.

    + +
    +
    + +

    ◆ poll_events

    + +
    +
    + + + + +
    uint32_t fuse_file_info::poll_events
    +
    +

    Requested poll events. Available in ->poll. Only set on kernels which support it. If unsupported, this field is set to zero.

    + +

    Definition at line 122 of file fuse_common.h.

    + +
    +
    + +

    ◆ writepage

    + +
    +
    + + + + +
    uint32_t fuse_file_info::writepage
    +
    +

    In case of a write operation indicates if this was caused by a delayed write from the page cache. If so, then the context's pid, uid, and gid fields will not be valid, and the fh value may not match the fh value that would have been sent with the corresponding individual write requests if write caching had been disabled.

    + +

    Definition at line 60 of file fuse_common.h.

    + +
    +
    +
    The documentation for this struct was generated from the following file: +
    + + +
    + + diff --git a/doc/html/structfuse__loop__config.html b/doc/html/structfuse__loop__config.html new file mode 100644 index 0000000..ced018d --- /dev/null +++ b/doc/html/structfuse__loop__config.html @@ -0,0 +1,148 @@ + + + + + + + +libfuse: fuse_loop_config Struct Reference + + + + + + + +
    +
    + + + + + + +
    +
    libfuse +
    +
    +
    + + + + + + + +
    +
    +
    + +
    fuse_loop_config Struct Reference
    +
    +
    + +

    #include <fuse_i.h>

    + + + + + + +

    +Data Fields

    int clone_fd
    unsigned int max_idle_threads
    int max_idle_threads
    unsigned int max_threads
    +

    Detailed Description

    +

    Configuration parameters passed to fuse_session_loop_mt() and fuse_loop_mt().

    +

    Internal API to avoid exposing the plain data structure and causing compat issues after adding or removing struct members.

    + +

    Definition at line 147 of file fuse_common.h.

    +

    Field Documentation

    + +

    ◆ clone_fd

    + +
    +
    + + + + +
    int fuse_loop_config::clone_fd
    +
    +

    whether to use separate device fds for each thread (may increase performance)

    + +

    Definition at line 155 of file fuse_common.h.

    + +
    +
    + +

    ◆ max_idle_threads [1/2]

    + +
    +
    + + + + +
    unsigned int fuse_loop_config::max_idle_threads
    +
    +

    The maximum number of available worker threads before they start to get deleted when they become idle. If not specified, the default is 10.

    +

    Adjusting this has performance implications; a very small number of threads in the pool will cause a lot of thread creation and deletion overhead and performance may suffer. When set to 0, a new thread will be created to service every operation.

    + +

    Definition at line 167 of file fuse_common.h.

    + +
    +
    + +

    ◆ max_idle_threads [2/2]

    + +
    +
    + + + + +
    int fuse_loop_config::max_idle_threads
    +
    +

    The maximum number of available worker threads before they start to get deleted when they become idle. If not specified, the default is -1.

    +

    Adjusting this has performance implications; a very small number of threads in the pool will cause a lot of thread creation and deletion overhead and performance may suffer. When set to 0, a new thread will be created to service every operation. The special value of -1 means that this parameter is disabled.

    + +

    Definition at line 183 of file fuse_i.h.

    + +
    +
    + +

    ◆ max_threads

    + +
    +
    + + + + +
    unsigned int fuse_loop_config::max_threads
    +
    +

    max number of threads taking and processing kernel requests

    +

    As of now threads are created dynamically

    + +

    Definition at line 190 of file fuse_i.h.

    + +
    +
    +
    The documentation for this struct was generated from the following files: +
    + + +
    + + diff --git a/doc/html/structfuse__lowlevel__ops.html b/doc/html/structfuse__lowlevel__ops.html new file mode 100644 index 0000000..d764367 --- /dev/null +++ b/doc/html/structfuse__lowlevel__ops.html @@ -0,0 +1,1464 @@ + + + + + + + +libfuse: fuse_lowlevel_ops Struct Reference + + + + + + + +
    +
    + + + + + + +
    +
    libfuse +
    +
    +
    + + + + + + + +
    +
    +
    + +
    fuse_lowlevel_ops Struct Reference
    +
    +
    + +

    #include <fuse_lowlevel.h>

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    +Data Fields

    void(* init )(void *userdata, struct fuse_conn_info *conn)
    void(* destroy )(void *userdata)
    void(* lookup )(fuse_req_t req, fuse_ino_t parent, const char *name)
    void(* forget )(fuse_req_t req, fuse_ino_t ino, uint64_t nlookup)
    void(* getattr )(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
    void(* setattr )(fuse_req_t req, fuse_ino_t ino, struct stat *attr, int to_set, struct fuse_file_info *fi)
    void(* readlink )(fuse_req_t req, fuse_ino_t ino)
    void(* mknod )(fuse_req_t req, fuse_ino_t parent, const char *name, mode_t mode, dev_t rdev)
    void(* mkdir )(fuse_req_t req, fuse_ino_t parent, const char *name, mode_t mode)
    void(* unlink )(fuse_req_t req, fuse_ino_t parent, const char *name)
    void(* rmdir )(fuse_req_t req, fuse_ino_t parent, const char *name)
    void(* symlink )(fuse_req_t req, const char *link, fuse_ino_t parent, const char *name)
    void(* rename )(fuse_req_t req, fuse_ino_t parent, const char *name, fuse_ino_t newparent, const char *newname, unsigned int flags)
    void(* link )(fuse_req_t req, fuse_ino_t ino, fuse_ino_t newparent, const char *newname)
    void(* open )(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
    void(* read )(fuse_req_t req, fuse_ino_t ino, size_t size, off_t off, struct fuse_file_info *fi)
    void(* write )(fuse_req_t req, fuse_ino_t ino, const char *buf, size_t size, off_t off, struct fuse_file_info *fi)
    void(* flush )(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
    void(* release )(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
    void(* fsync )(fuse_req_t req, fuse_ino_t ino, int datasync, struct fuse_file_info *fi)
    void(* opendir )(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
    void(* readdir )(fuse_req_t req, fuse_ino_t ino, size_t size, off_t off, struct fuse_file_info *fi)
    void(* releasedir )(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
    void(* fsyncdir )(fuse_req_t req, fuse_ino_t ino, int datasync, struct fuse_file_info *fi)
    void(* statfs )(fuse_req_t req, fuse_ino_t ino)
    void(* setxattr )(fuse_req_t req, fuse_ino_t ino, const char *name, const char *value, size_t size, int flags)
    void(* getxattr )(fuse_req_t req, fuse_ino_t ino, const char *name, size_t size)
    void(* listxattr )(fuse_req_t req, fuse_ino_t ino, size_t size)
    void(* removexattr )(fuse_req_t req, fuse_ino_t ino, const char *name)
    void(* access )(fuse_req_t req, fuse_ino_t ino, int mask)
    void(* create )(fuse_req_t req, fuse_ino_t parent, const char *name, mode_t mode, struct fuse_file_info *fi)
    void(* getlk )(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi, struct flock *lock)
    void(* setlk )(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi, struct flock *lock, int sleep)
    void(* bmap )(fuse_req_t req, fuse_ino_t ino, size_t blocksize, uint64_t idx)
    void(* ioctl )(fuse_req_t req, fuse_ino_t ino, unsigned int cmd, void *arg, struct fuse_file_info *fi, unsigned flags, const void *in_buf, size_t in_bufsz, size_t out_bufsz)
    void(* poll )(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi, struct fuse_pollhandle *ph)
    void(* write_buf )(fuse_req_t req, fuse_ino_t ino, struct fuse_bufvec *bufv, off_t off, struct fuse_file_info *fi)
    void(* retrieve_reply )(fuse_req_t req, void *cookie, fuse_ino_t ino, off_t offset, struct fuse_bufvec *bufv)
    void(* forget_multi )(fuse_req_t req, size_t count, struct fuse_forget_data *forgets)
    void(* flock )(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi, int op)
    void(* fallocate )(fuse_req_t req, fuse_ino_t ino, int mode, off_t offset, off_t length, struct fuse_file_info *fi)
    void(* readdirplus )(fuse_req_t req, fuse_ino_t ino, size_t size, off_t off, struct fuse_file_info *fi)
    void(* copy_file_range )(fuse_req_t req, fuse_ino_t ino_in, off_t off_in, struct fuse_file_info *fi_in, fuse_ino_t ino_out, off_t off_out, struct fuse_file_info *fi_out, size_t len, int flags)
    void(* lseek )(fuse_req_t req, fuse_ino_t ino, off_t off, int whence, struct fuse_file_info *fi)
    void(* tmpfile )(fuse_req_t req, fuse_ino_t parent, mode_t mode, struct fuse_file_info *fi)
    void(* statx )(fuse_req_t req, fuse_ino_t ino, int flags, int mask, struct fuse_file_info *fi)
    +

    Detailed Description

    +

    Low level filesystem operations

    +

    Most of the methods (with the exception of init and destroy) receive a request handle (fuse_req_t) as their first argument. This handle must be passed to one of the specified reply functions.

    +

    This may be done inside the method invocation, or after the call has returned. The request handle is valid until one of the reply functions is called.

    +

    Other pointer arguments (name, fuse_file_info, etc) are not valid after the call has returned, so if they are needed later, their contents have to be copied.

    +

    In general, all methods are expected to perform any necessary permission checking. However, a filesystem may delegate this task to the kernel by passing the default_permissions mount option to fuse_session_new(). In this case, methods will only be called if the kernel's permission check has succeeded.

    +

    It is generally not really necessary to check the fuse_reply_* return values for errors, as any error in sending a reply indicates an unrecoverable problem with the kernel fuse connection, which will also terminate the session loop anyway.

    +

    This data structure is ABI sensitive, on adding new functions these need to be appended at the end of the struct

    + +

    Definition at line 208 of file fuse_lowlevel.h.

    +

    Field Documentation

    + +

    ◆ access

    + +
    +
    + + + + +
    void(* fuse_lowlevel_ops::access) (fuse_req_t req, fuse_ino_t ino, int mask)
    +
    +

    Check file access permissions

    +

    This will be called for the access() and chdir() system calls. If the 'default_permissions' mount option is given, this method is not called.

    +

    This method is not called under Linux kernel versions 2.4.x

    +

    If this request is answered with an error code of ENOSYS, this is treated as a permanent success, i.e. this and all future access() requests will succeed without being send to the filesystem process.

    +

    Valid replies: fuse_reply_err

    +
    Parameters
    + + + + +
    reqrequest handle
    inothe inode number
    maskrequested access mode
    +
    +
    + +

    Definition at line 953 of file fuse_lowlevel.h.

    + +
    +
    + +

    ◆ bmap

    + +
    +
    + + + + +
    void(* fuse_lowlevel_ops::bmap) (fuse_req_t req, fuse_ino_t ino, size_t blocksize, uint64_t idx)
    +
    +

    Map block index within file to block index within device

    +

    Note: This makes sense only for block device backed filesystems mounted with the 'blkdev' option

    +

    If this request is answered with an error code of ENOSYS, this is treated as a permanent failure, i.e. all future bmap() requests will fail with the same error code without being send to the filesystem process.

    +

    Valid replies: fuse_reply_bmap fuse_reply_err

    +
    Parameters
    + + + + + +
    reqrequest handle
    inothe inode number
    blocksizeunit of block index
    idxblock index within file
    +
    +
    + +

    Definition at line 1046 of file fuse_lowlevel.h.

    + +
    +
    + +

    ◆ copy_file_range

    + +
    +
    + + + + +
    void(* fuse_lowlevel_ops::copy_file_range) (fuse_req_t req, fuse_ino_t ino_in, off_t off_in, struct fuse_file_info *fi_in, fuse_ino_t ino_out, off_t off_out, struct fuse_file_info *fi_out, size_t len, int flags)
    +
    +

    Copy a range of data from one file to another

    +

    Performs an optimized copy between two file descriptors without the additional cost of transferring data through the FUSE kernel module to user space (glibc) and then back into the FUSE filesystem again.

    +

    In case this method is not implemented, glibc falls back to reading data from the source and writing to the destination. Effectively doing an inefficient copy of the data.

    +

    If this request is answered with an error code of ENOSYS, this is treated as a permanent failure with error code EOPNOTSUPP, i.e. all future copy_file_range() requests will fail with EOPNOTSUPP without being send to the filesystem process.

    +

    Valid replies: fuse_reply_write fuse_reply_err

    +
    Parameters
    + + + + + + + + + + +
    reqrequest handle
    ino_inthe inode number or the source file
    off_instarting point from were the data should be read
    fi_infile information of the source file
    ino_outthe inode number or the destination file
    off_outstarting point where the data should be written
    fi_outfile information of the destination file
    lenmaximum size of the data to copy
    flagspassed along with the copy_file_range() syscall
    +
    +
    + +

    Definition at line 1281 of file fuse_lowlevel.h.

    + +
    +
    + +

    ◆ create

    + +
    +
    + + + + +
    void(* fuse_lowlevel_ops::create) (fuse_req_t req, fuse_ino_t parent, const char *name, mode_t mode, struct fuse_file_info *fi)
    +
    +

    Create and open a file

    +

    If the file does not exist, first create it with the specified mode, and then open it.

    +

    See the description of the open handler for more information.

    +

    If this method is not implemented or under Linux kernel versions earlier than 2.6.15, the mknod() and open() methods will be called instead.

    +

    If this request is answered with an error code of ENOSYS, the handler is treated as not implemented (i.e., for this and future requests the mknod() and open() handlers will be called instead).

    +

    Valid replies: fuse_reply_create fuse_reply_err

    +
    Parameters
    + + + + + + +
    reqrequest handle
    parentinode number of the parent directory
    nameto create
    modefile type and mode with which to create the new file
    fifile information
    +
    +
    + +

    Definition at line 982 of file fuse_lowlevel.h.

    + +
    +
    + +

    ◆ destroy

    + +
    +
    + + + + +
    void(* fuse_lowlevel_ops::destroy) (void *userdata)
    +
    +

    Clean up filesystem.

    +

    Called on filesystem exit. When this method is called, the connection to the kernel may be gone already, so that eg. calls to fuse_lowlevel_notify_* will fail.

    +

    There's no reply to this function

    +
    Parameters
    + + +
    userdatathe user data passed to fuse_session_new()
    +
    +
    + +

    Definition at line 238 of file fuse_lowlevel.h.

    + +
    +
    + +

    ◆ fallocate

    + +
    +
    + + + + +
    void(* fuse_lowlevel_ops::fallocate) (fuse_req_t req, fuse_ino_t ino, int mode, off_t offset, off_t length, struct fuse_file_info *fi)
    +
    +

    Allocate requested space. If this function returns success then subsequent writes to the specified range shall not fail due to the lack of free space on the file system storage media.

    +

    If this request is answered with an error code of ENOSYS, this is treated as a permanent failure with error code EOPNOTSUPP, i.e. all future fallocate() requests will fail with EOPNOTSUPP without being send to the filesystem process.

    +

    Valid replies: fuse_reply_err

    +
    Parameters
    + + + + + + +
    reqrequest handle
    inothe inode number
    offsetstarting point for allocated region
    lengthsize of allocated region
    modedetermines the operation to be performed on the given range, see fallocate(2)
    +
    +
    + +

    Definition at line 1220 of file fuse_lowlevel.h.

    + +
    +
    + +

    ◆ flock

    + +
    +
    + + + + +
    void(* fuse_lowlevel_ops::flock) (fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi, int op)
    +
    +

    Acquire, modify or release a BSD file lock

    +

    Note: if the locking methods are not implemented, the kernel will still allow file locking to work locally. Hence these are only interesting for network filesystems and similar.

    +

    Valid replies: fuse_reply_err

    +
    Parameters
    + + + + + +
    reqrequest handle
    inothe inode number
    fifile information
    opthe locking operation, see flock(2)
    +
    +
    + +

    Definition at line 1197 of file fuse_lowlevel.h.

    + +
    +
    + +

    ◆ flush

    + +
    +
    + + + + +
    void(* fuse_lowlevel_ops::flush) (fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
    +
    +

    Flush method

    +

    This is called on each close() of the opened file.

    +

    Since file descriptors can be duplicated (dup, dup2, fork), for one open call there may be many flush calls.

    +

    Filesystems shouldn't assume that flush will always be called after some writes, or that if will be called at all.

    +

    fi->fh will contain the value set by the open method, or will be undefined if the open method didn't set any value.

    +

    NOTE: the name of the method is misleading, since (unlike fsync) the filesystem is not forced to flush pending writes. One reason to flush data is if the filesystem wants to return write errors during close. However, such use is non-portable because POSIX does not require close to wait for delayed I/O to complete.

    +

    If the filesystem supports file locking operations (setlk, getlk) it should remove all locks belonging to 'fi->owner'.

    +

    If this request is answered with an error code of ENOSYS, this is treated as success and future calls to flush() will succeed automatically without being send to the filesystem process.

    +

    Valid replies: fuse_reply_err

    +
    Parameters
    + + + + +
    reqrequest handle
    inothe inode number
    fifile information
    +
    +
    + +

    Definition at line 654 of file fuse_lowlevel.h.

    + +
    +
    + +

    ◆ forget

    + +
    +
    + + + + +
    void(* fuse_lowlevel_ops::forget) (fuse_req_t req, fuse_ino_t ino, uint64_t nlookup)
    +
    +

    Forget about an inode

    +

    This function is called when the kernel removes an inode from its internal caches.

    +

    The inode's lookup count increases by one for every call to fuse_reply_entry and fuse_reply_create. The nlookup parameter indicates by how much the lookup count should be decreased.

    +

    Inodes with a non-zero lookup count may receive request from the kernel even after calls to unlink, rmdir or (when overwriting an existing file) rename. Filesystems must handle such requests properly and it is recommended to defer removal of the inode until the lookup count reaches zero. Calls to unlink, rmdir or rename will be followed closely by forget unless the file or directory is open, in which case the kernel issues forget only after the release or releasedir calls.

    +

    Note that if a file system will be exported over NFS the inodes lifetime must extend even beyond forget. See the generation field in struct fuse_entry_param above.

    +

    On unmount the lookup count for all inodes implicitly drops to zero. It is not guaranteed that the file system will receive corresponding forget messages for the affected inodes.

    +

    Valid replies: fuse_reply_none

    +
    Parameters
    + + + + +
    reqrequest handle
    inothe inode number
    nlookupthe number of lookups to forget
    +
    +
    + +

    Definition at line 289 of file fuse_lowlevel.h.

    + +
    +
    + +

    ◆ forget_multi

    + +
    +
    + + + + +
    void(* fuse_lowlevel_ops::forget_multi) (fuse_req_t req, size_t count, struct fuse_forget_data *forgets)
    +
    +

    Forget about multiple inodes

    +

    See description of the forget function for more information.

    +

    Valid replies: fuse_reply_none

    +
    Parameters
    + + +
    reqrequest handle
    +
    +
    + +

    Definition at line 1179 of file fuse_lowlevel.h.

    + +
    +
    + +

    ◆ fsync

    + +
    +
    + + + + +
    void(* fuse_lowlevel_ops::fsync) (fuse_req_t req, fuse_ino_t ino, int datasync, struct fuse_file_info *fi)
    +
    +

    Synchronize file contents

    +

    If the datasync parameter is non-zero, then only the user data should be flushed, not the meta data.

    +

    If this request is answered with an error code of ENOSYS, this is treated as success and future calls to fsync() will succeed automatically without being send to the filesystem process.

    +

    Valid replies: fuse_reply_err

    +
    Parameters
    + + + + + +
    reqrequest handle
    inothe inode number
    datasyncflag indicating if only data should be flushed
    fifile information
    +
    +
    + +

    Definition at line 704 of file fuse_lowlevel.h.

    + +
    +
    + +

    ◆ fsyncdir

    + +
    +
    + + + + +
    void(* fuse_lowlevel_ops::fsyncdir) (fuse_req_t req, fuse_ino_t ino, int datasync, struct fuse_file_info *fi)
    +
    +

    Synchronize directory contents

    +

    If the datasync parameter is non-zero, then only the directory contents should be flushed, not the meta data.

    +

    fi->fh will contain the value set by the opendir method, or will be undefined if the opendir method didn't set any value.

    +

    If this request is answered with an error code of ENOSYS, this is treated as success and future calls to fsyncdir() will succeed automatically without being send to the filesystem process.

    +

    Valid replies: fuse_reply_err

    +
    Parameters
    + + + + + +
    reqrequest handle
    inothe inode number
    datasyncflag indicating if only data should be flushed
    fifile information
    +
    +
    + +

    Definition at line 826 of file fuse_lowlevel.h.

    + +
    +
    + +

    ◆ getattr

    + +
    +
    + + + + +
    void(* fuse_lowlevel_ops::getattr) (fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
    +
    +

    Get file attributes.

    +

    If writeback caching is enabled, the kernel may have a better idea of a file's length than the FUSE file system (eg if there has been a write that extended the file size, but that has not yet been passed to the filesystem.

    +

    In this case, the st_size value provided by the file system will be ignored.

    +

    Valid replies: fuse_reply_attr fuse_reply_err

    +
    Parameters
    + + + + +
    reqrequest handle
    inothe inode number
    fifile information, or NULL
    +
    +
    + +

    Definition at line 310 of file fuse_lowlevel.h.

    + +
    +
    + +

    ◆ getlk

    + +
    +
    + + + + +
    void(* fuse_lowlevel_ops::getlk) (fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi, struct flock *lock)
    +
    +

    Test for a POSIX file lock

    +

    Valid replies: fuse_reply_lock fuse_reply_err

    +
    Parameters
    + + + + + +
    reqrequest handle
    inothe inode number
    fifile information
    lockthe region/type to test
    +
    +
    + +

    Definition at line 997 of file fuse_lowlevel.h.

    + +
    +
    + +

    ◆ getxattr

    + +
    +
    + + + + +
    void(* fuse_lowlevel_ops::getxattr) (fuse_req_t req, fuse_ino_t ino, const char *name, size_t size)
    +
    +

    Get an extended attribute

    +

    If size is zero, the size of the value should be sent with fuse_reply_xattr.

    +

    If the size is non-zero, and the value fits in the buffer, the value should be sent with fuse_reply_buf.

    +

    If the size is too small for the value, the ERANGE error should be sent.

    +

    If this request is answered with an error code of ENOSYS, this is treated as a permanent failure with error code EOPNOTSUPP, i.e. all future getxattr() requests will fail with EOPNOTSUPP without being send to the filesystem process.

    +

    Valid replies: fuse_reply_buf fuse_reply_data fuse_reply_xattr fuse_reply_err

    +
    Parameters
    + + + + + +
    reqrequest handle
    inothe inode number
    nameof the extended attribute
    sizemaximum size of the value to send
    +
    +
    + +

    Definition at line 883 of file fuse_lowlevel.h.

    + +
    +
    + +

    ◆ init

    + +
    +
    + + + + +
    void(* fuse_lowlevel_ops::init) (void *userdata, struct fuse_conn_info *conn)
    +
    +

    Initialize filesystem

    +

    This function is called when libfuse establishes communication with the FUSE kernel module. The file system should use this module to inspect and/or modify the connection parameters provided in the conn structure.

    +

    Note that some parameters may be overwritten by options passed to fuse_session_new() which take precedence over the values set in this handler.

    +

    There's no reply to this function

    +
    Parameters
    + + +
    userdatathe user data passed to fuse_session_new()
    +
    +
    + +

    Definition at line 225 of file fuse_lowlevel.h.

    + +
    +
    + +

    ◆ ioctl

    + +
    +
    + + + + +
    void(* fuse_lowlevel_ops::ioctl) (fuse_req_t req, fuse_ino_t ino, unsigned int cmd, void *arg, struct fuse_file_info *fi, unsigned flags, const void *in_buf, size_t in_bufsz, size_t out_bufsz)
    +
    +

    Ioctl

    +

    Note: For unrestricted ioctls (not allowed for FUSE servers), data in and out areas can be discovered by giving iovs and setting FUSE_IOCTL_RETRY in flags. For restricted ioctls, kernel prepares in/out data area according to the information encoded in cmd.

    +

    Valid replies: fuse_reply_ioctl_retry fuse_reply_ioctl fuse_reply_ioctl_iov fuse_reply_err

    +
    Parameters
    + + + + + + + + + + +
    reqrequest handle
    inothe inode number
    cmdioctl command
    argioctl argument
    fifile information
    flagsfor FUSE_IOCTL_* flags
    in_bufdata fetched from the caller
    in_bufsznumber of fetched bytes
    out_bufszmaximum size of output data
    +
    +
    +

    Note : the unsigned long request submitted by the application is truncated to 32 bits.

    + +

    Definition at line 1082 of file fuse_lowlevel.h.

    + +
    +
    + +

    ◆ link

    + +
    +
    + + + + +
    void(* fuse_lowlevel_ops::link) (fuse_req_t req, fuse_ino_t ino, fuse_ino_t newparent, const char *newname)
    +
    +

    Create a hard link

    +

    Valid replies: fuse_reply_entry fuse_reply_err

    +
    Parameters
    + + + + + +
    reqrequest handle
    inothe old inode number
    newparentinode number of the new parent directory
    newnamenew name to create
    +
    +
    + +

    Definition at line 490 of file fuse_lowlevel.h.

    + +
    +
    + +

    ◆ listxattr

    + +
    +
    + + + + +
    void(* fuse_lowlevel_ops::listxattr) (fuse_req_t req, fuse_ino_t ino, size_t size)
    +
    +

    List extended attribute names

    +

    If size is zero, the total size of the attribute list should be sent with fuse_reply_xattr.

    +

    If the size is non-zero, and the null character separated attribute list fits in the buffer, the list should be sent with fuse_reply_buf.

    +

    If the size is too small for the list, the ERANGE error should be sent.

    +

    If this request is answered with an error code of ENOSYS, this is treated as a permanent failure with error code EOPNOTSUPP, i.e. all future listxattr() requests will fail with EOPNOTSUPP without being send to the filesystem process.

    +

    Valid replies: fuse_reply_buf fuse_reply_data fuse_reply_xattr fuse_reply_err

    +
    Parameters
    + + + + +
    reqrequest handle
    inothe inode number
    sizemaximum size of the list to send
    +
    +
    + +

    Definition at line 914 of file fuse_lowlevel.h.

    + +
    +
    + +

    ◆ lookup

    + +
    +
    + + + + +
    void(* fuse_lowlevel_ops::lookup) (fuse_req_t req, fuse_ino_t parent, const char *name)
    +
    +

    Look up a directory entry by name and get its attributes.

    +

    Valid replies: fuse_reply_entry fuse_reply_err

    +
    Parameters
    + + + + +
    reqrequest handle
    parentinode number of the parent directory
    namethe name to look up
    +
    +
    + +

    Definition at line 251 of file fuse_lowlevel.h.

    + +
    +
    + +

    ◆ lseek

    + +
    +
    + + + + +
    void(* fuse_lowlevel_ops::lseek) (fuse_req_t req, fuse_ino_t ino, off_t off, int whence, struct fuse_file_info *fi)
    +
    +

    Find next data or hole after the specified offset

    +

    If this request is answered with an error code of ENOSYS, this is treated as a permanent failure, i.e. all future lseek() requests will fail with the same error code without being send to the filesystem process.

    +

    Valid replies: fuse_reply_lseek fuse_reply_err

    +
    Parameters
    + + + + + + +
    reqrequest handle
    inothe inode number
    offoffset to start search from
    whenceeither SEEK_DATA or SEEK_HOLE
    fifile information
    +
    +
    + +

    Definition at line 1305 of file fuse_lowlevel.h.

    + +
    +
    + +

    ◆ mkdir

    + +
    +
    + + + + +
    void(* fuse_lowlevel_ops::mkdir) (fuse_req_t req, fuse_ino_t parent, const char *name, mode_t mode)
    +
    +

    Create a directory

    +

    Valid replies: fuse_reply_entry fuse_reply_err

    +
    Parameters
    + + + + + +
    reqrequest handle
    parentinode number of the parent directory
    nameto create
    modewith which to create the new file
    +
    +
    + +

    Definition at line 393 of file fuse_lowlevel.h.

    + +
    +
    + +

    ◆ mknod

    + +
    +
    + + + + +
    void(* fuse_lowlevel_ops::mknod) (fuse_req_t req, fuse_ino_t parent, const char *name, mode_t mode, dev_t rdev)
    +
    +

    Create file node

    +

    Create a regular file, character device, block device, fifo or socket node.

    +

    Valid replies: fuse_reply_entry fuse_reply_err

    +
    Parameters
    + + + + + + +
    reqrequest handle
    parentinode number of the parent directory
    nameto create
    modefile type and mode with which to create the new file
    rdevthe device number (only valid if created file is a device)
    +
    +
    + +

    Definition at line 378 of file fuse_lowlevel.h.

    + +
    +
    + +

    ◆ open

    + +
    +
    + + + + +
    void(* fuse_lowlevel_ops::open) (fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
    +
    +

    Open a file

    +

    Open flags are available in fi->flags. The following rules apply.

    +
      +
    • Creation (O_CREAT, O_EXCL, O_NOCTTY) flags will be filtered out / handled by the kernel.
    • +
    • Access modes (O_RDONLY, O_WRONLY, O_RDWR) should be used by the filesystem to check if the operation is permitted. If the -o default_permissions mount option is given, this check is already done by the kernel before calling open() and may thus be omitted by the filesystem.
    • +
    • When writeback caching is enabled, the kernel may send read requests even for files opened with O_WRONLY. The filesystem should be prepared to handle this.
    • +
    • When writeback caching is disabled, the filesystem is expected to properly handle the O_APPEND flag and ensure that each write is appending to the end of the file.
    • +
    • When writeback caching is enabled, the kernel will handle O_APPEND. However, unless all changes to the file come through the kernel this will not work reliably. The filesystem should thus either ignore the O_APPEND flag (and let the kernel handle it), or return an error (indicating that reliably O_APPEND is not available).
    • +
    +

    Filesystem may store an arbitrary file handle (pointer, index, etc) in fi->fh, and use this in other all other file operations (read, write, flush, release, fsync).

    +

    Filesystem may also implement stateless file I/O and not store anything in fi->fh.

    +

    There are also some flags (direct_io, keep_cache) which the filesystem may set in fi, to change the way the file is opened. See fuse_file_info structure in <fuse_common.h> for more details.

    +

    If this request is answered with an error code of ENOSYS and FUSE_CAP_NO_OPEN_SUPPORT is set in fuse_conn_info.capable, this is treated as success and future calls to open and release will also succeed without being sent to the filesystem process.

    +

    To get this behavior without providing an opendir handler, you may set FUSE_CAP_NO_OPEN_SUPPORT in fuse_conn_info.want on supported kernels to automatically get the zero message open().

    +

    If this callback is not provided and FUSE_CAP_NO_OPEN_SUPPORT is not set in fuse_conn_info.want then an empty reply will be sent.

    +

    Valid replies: fuse_reply_open fuse_reply_err

    +
    Parameters
    + + + + +
    reqrequest handle
    inothe inode number
    fifile information
    +
    +
    + +

    Definition at line 556 of file fuse_lowlevel.h.

    + +
    +
    + +

    ◆ opendir

    + +
    +
    + + + + +
    void(* fuse_lowlevel_ops::opendir) (fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
    +
    +

    Open a directory

    +

    Filesystem may store an arbitrary file handle (pointer, index, etc) in fi->fh, and use this in other all other directory stream operations (readdir, releasedir, fsyncdir).

    +

    If this request is answered with an error code of ENOSYS and FUSE_CAP_NO_OPENDIR_SUPPORT is set in fuse_conn_info.capable, this is treated as success and future calls to opendir and releasedir will also succeed without being sent to the filesystem process. In addition, the kernel will cache readdir results as if opendir returned FOPEN_KEEP_CACHE | FOPEN_CACHE_DIR.

    +

    To get this behavior without providing an opendir handler, you may set FUSE_CAP_NO_OPENDIR_SUPPORT in fuse_conn_info.want on supported kernels to automatically get the zero message opendir().

    +

    If this callback is not provided and FUSE_CAP_NO_OPENDIR_SUPPORT is not set in fuse_conn_info.want then an empty reply will be sent.

    +

    Valid replies: fuse_reply_open fuse_reply_err

    +
    Parameters
    + + + + +
    reqrequest handle
    inothe inode number
    fifile information
    +
    +
    + +

    Definition at line 736 of file fuse_lowlevel.h.

    + +
    +
    + +

    ◆ poll

    + +
    +
    + + + + +
    void(* fuse_lowlevel_ops::poll) (fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi, struct fuse_pollhandle *ph)
    +
    +

    Poll for IO readiness

    +

    The client should immediately respond with fuse_reply_poll(), setting revents appropriately according to which events are ready.

    +

    Additionally, if ph is non-NULL, the client must retain it and notify when all future IO readiness events occur by calling fuse_lowlevel_notify_poll() with the specified ph.

    +

    Regardless of the number of times poll with a non-NULL ph is received, a single notify_poll is enough to service all. (Notifying more times incurs overhead but doesn't harm correctness.) Any additional received handles can be immediately destroyed.

    +

    The callee is responsible for destroying ph with fuse_pollhandle_destroy() when no longer in use.

    +

    If this request is answered with an error code of ENOSYS, this is treated as success (with a kernel-defined default poll-mask) and future calls to poll() will succeed the same way without being send to the filesystem process.

    +

    Valid replies: fuse_reply_poll fuse_reply_err

    +
    Parameters
    + + + + + +
    reqrequest handle
    inothe inode number
    fifile information
    phpoll handle to be used for notification
    +
    +
    + +

    Definition at line 1119 of file fuse_lowlevel.h.

    + +
    +
    + +

    ◆ read

    + +
    +
    + + + + +
    void(* fuse_lowlevel_ops::read) (fuse_req_t req, fuse_ino_t ino, size_t size, off_t off, struct fuse_file_info *fi)
    +
    +

    Read data

    +

    Read should send exactly the number of bytes requested except on EOF or error, otherwise the rest of the data will be substituted with zeroes. An exception to this is when the file has been opened in 'direct_io' mode, in which case the return value of the read system call will reflect the return value of this operation.

    +

    fi->fh will contain the value set by the open method, or will be undefined if the open method didn't set any value.

    +

    Valid replies: fuse_reply_buf fuse_reply_iov fuse_reply_data fuse_reply_err

    +
    Parameters
    + + + + + + +
    reqrequest handle
    inothe inode number
    sizenumber of bytes to read
    offoffset to read from
    fifile information
    +
    +
    + +

    Definition at line 584 of file fuse_lowlevel.h.

    + +
    +
    + +

    ◆ readdir

    + +
    +
    + + + + +
    void(* fuse_lowlevel_ops::readdir) (fuse_req_t req, fuse_ino_t ino, size_t size, off_t off, struct fuse_file_info *fi)
    +
    +

    Read directory

    +

    Send a buffer filled using fuse_add_direntry(), with size not exceeding the requested size. Send an empty buffer on end of stream.

    +

    fi->fh will contain the value set by the opendir method, or will be undefined if the opendir method didn't set any value.

    +

    Returning a directory entry from readdir() does not affect its lookup count.

    +

    If off_t is non-zero, then it will correspond to one of the off_t values that was previously returned by readdir() for the same directory handle. In this case, readdir() should skip over entries coming before the position defined by the off_t value. If entries are added or removed while the directory handle is open, the filesystem may still include the entries that have been removed, and may not report the entries that have been created. However, addition or removal of entries must never cause readdir() to skip over unrelated entries or to report them more than once. This means that off_t can not be a simple index that enumerates the entries that have been returned but must contain sufficient information to uniquely determine the next directory entry to return even when the set of entries is changing.

    +

    The function does not have to report the '.' and '..' entries, but is allowed to do so. Note that, if readdir does not return '.' or '..', they will not be implicitly returned, and this behavior is observable by the caller.

    +

    Valid replies: fuse_reply_buf fuse_reply_data fuse_reply_err

    +
    Parameters
    + + + + + + +
    reqrequest handle
    inothe inode number
    sizemaximum number of bytes to send
    offoffset to continue reading the directory stream
    fifile information
    +
    +
    + +

    Definition at line 782 of file fuse_lowlevel.h.

    + +
    +
    + +

    ◆ readdirplus

    + +
    +
    + + + + +
    void(* fuse_lowlevel_ops::readdirplus) (fuse_req_t req, fuse_ino_t ino, size_t size, off_t off, struct fuse_file_info *fi)
    +
    +

    Read directory with attributes

    +

    Send a buffer filled using fuse_add_direntry_plus(), with size not exceeding the requested size. Send an empty buffer on end of stream.

    +

    fi->fh will contain the value set by the opendir method, or will be undefined if the opendir method didn't set any value.

    +

    In contrast to readdir() (which does not affect the lookup counts), the lookup count of every entry returned by readdirplus(), except "." and "..", is incremented by one.

    +

    Valid replies: fuse_reply_buf fuse_reply_data fuse_reply_err

    +
    Parameters
    + + + + + + +
    reqrequest handle
    inothe inode number
    sizemaximum number of bytes to send
    offoffset to continue reading the directory stream
    fifile information
    +
    +
    + +

    Definition at line 1248 of file fuse_lowlevel.h.

    + +
    +
    + +

    ◆ readlink

    + +
    +
    + + + + +
    void(* fuse_lowlevel_ops::readlink) (fuse_req_t req, fuse_ino_t ino)
    +
    +

    Read symbolic link

    +

    Valid replies: fuse_reply_readlink fuse_reply_err

    +
    Parameters
    + + + +
    reqrequest handle
    inothe inode number
    +
    +
    + +

    Definition at line 360 of file fuse_lowlevel.h.

    + +
    +
    + +

    ◆ release

    + +
    +
    + + + + +
    void(* fuse_lowlevel_ops::release) (fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
    +
    +

    Release an open file

    +

    Release is called when there are no more references to an open file: all file descriptors are closed and all memory mappings are unmapped.

    +

    For every open call there will be exactly one release call (unless the filesystem is force-unmounted).

    +

    The filesystem may reply with an error, but error values are not returned to close() or munmap() which triggered the release.

    +

    fi->fh will contain the value set by the open method, or will be undefined if the open method didn't set any value. fi->flags will contain the same flags as for open.

    +

    Valid replies: fuse_reply_err

    +
    Parameters
    + + + + +
    reqrequest handle
    inothe inode number
    fifile information
    +
    +
    + +

    Definition at line 682 of file fuse_lowlevel.h.

    + +
    +
    + +

    ◆ releasedir

    + +
    +
    + + + + +
    void(* fuse_lowlevel_ops::releasedir) (fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
    +
    +

    Release an open directory

    +

    For every opendir call there will be exactly one releasedir call (unless the filesystem is force-unmounted).

    +

    fi->fh will contain the value set by the opendir method, or will be undefined if the opendir method didn't set any value.

    +

    Valid replies: fuse_reply_err

    +
    Parameters
    + + + + +
    reqrequest handle
    inothe inode number
    fifile information
    +
    +
    + +

    Definition at line 801 of file fuse_lowlevel.h.

    + +
    +
    + +

    ◆ removexattr

    + +
    +
    + + + + +
    void(* fuse_lowlevel_ops::removexattr) (fuse_req_t req, fuse_ino_t ino, const char *name)
    +
    +

    Remove an extended attribute

    +

    If this request is answered with an error code of ENOSYS, this is treated as a permanent failure with error code EOPNOTSUPP, i.e. all future removexattr() requests will fail with EOPNOTSUPP without being send to the filesystem process.

    +

    Valid replies: fuse_reply_err

    +
    Parameters
    + + + + +
    reqrequest handle
    inothe inode number
    nameof the extended attribute
    +
    +
    + +

    Definition at line 931 of file fuse_lowlevel.h.

    + +
    +
    + +

    ◆ rename

    + +
    +
    + + + + +
    void(* fuse_lowlevel_ops::rename) (fuse_req_t req, fuse_ino_t parent, const char *name, fuse_ino_t newparent, const char *newname, unsigned int flags)
    +
    +

    Rename a file

    +

    If the target exists it should be atomically replaced. If the target's inode's lookup count is non-zero, the file system is expected to postpone any removal of the inode until the lookup count reaches zero (see description of the forget function).

    +

    If this request is answered with an error code of ENOSYS, this is treated as a permanent failure with error code EINVAL, i.e. all future rename requests will fail with EINVAL without being send to the filesystem process.

    +

    flags may be RENAME_EXCHANGE or RENAME_NOREPLACE. If RENAME_NOREPLACE is specified, the filesystem must not overwrite newname if it exists and return an error instead. If RENAME_EXCHANGE is specified, the filesystem must atomically exchange the two files, i.e. both must exist and neither may be deleted.

    +

    Valid replies: fuse_reply_err

    +
    Parameters
    + + + + + + +
    reqrequest handle
    parentinode number of the old parent directory
    nameold name
    newparentinode number of the new parent directory
    newnamenew name
    +
    +
    + +

    Definition at line 474 of file fuse_lowlevel.h.

    + +
    +
    + +

    ◆ retrieve_reply

    + +
    +
    + + + + +
    void(* fuse_lowlevel_ops::retrieve_reply) (fuse_req_t req, void *cookie, fuse_ino_t ino, off_t offset, struct fuse_bufvec *bufv)
    +
    +

    Callback function for the retrieve request

    +

    Valid replies: fuse_reply_none

    +
    Parameters
    + + + + + + +
    reqrequest handle
    cookieuser data supplied to fuse_lowlevel_notify_retrieve()
    inothe inode number supplied to fuse_lowlevel_notify_retrieve()
    offsetthe offset supplied to fuse_lowlevel_notify_retrieve()
    bufvthe buffer containing the returned data
    +
    +
    + +

    Definition at line 1165 of file fuse_lowlevel.h.

    + +
    +
    + +

    ◆ rmdir

    + +
    +
    + + + + +
    void(* fuse_lowlevel_ops::rmdir) (fuse_req_t req, fuse_ino_t parent, const char *name)
    +
    +

    Remove a directory

    +

    If the directory's inode's lookup count is non-zero, the file system is expected to postpone any removal of the inode until the lookup count reaches zero (see description of the forget function).

    +

    Valid replies: fuse_reply_err

    +
    Parameters
    + + + + +
    reqrequest handle
    parentinode number of the parent directory
    nameto remove
    +
    +
    + +

    Definition at line 428 of file fuse_lowlevel.h.

    + +
    +
    + +

    ◆ setattr

    + +
    +
    + + + + +
    void(* fuse_lowlevel_ops::setattr) (fuse_req_t req, fuse_ino_t ino, struct stat *attr, int to_set, struct fuse_file_info *fi)
    +
    +

    Set file attributes

    +

    In the 'attr' argument only members indicated by the 'to_set' bitmask contain valid values. Other members contain undefined values.

    +

    Unless FUSE_CAP_HANDLE_KILLPRIV is disabled, this method is expected to reset the setuid and setgid bits if the file size or owner is being changed.

    +

    This method will not be called to update st_atime or st_ctime implicitly (eg. after a read() request), and only be called to implicitly update st_mtime if writeback caching is active. It is the filesystem's responsibility to update these timestamps when needed, and (if desired) to implement mount options like noatime or relatime.

    +

    If the setattr was invoked from the ftruncate() system call under Linux kernel versions 2.6.15 or later, the fi->fh will contain the value set by the open method or will be undefined if the open method didn't set any value. Otherwise (not ftruncate call, or kernel version earlier than 2.6.15) the fi parameter will be NULL.

    +

    Valid replies: fuse_reply_attr fuse_reply_err

    +
    Parameters
    + + + + + + +
    reqrequest handle
    inothe inode number
    attrthe attributes
    to_setbit mask of attributes which should be set
    fifile information, or NULL
    +
    +
    + +

    Definition at line 347 of file fuse_lowlevel.h.

    + +
    +
    + +

    ◆ setlk

    + +
    +
    + + + + +
    void(* fuse_lowlevel_ops::setlk) (fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi, struct flock *lock, int sleep)
    +
    +

    Acquire, modify or release a POSIX file lock

    +

    For POSIX threads (NPTL) there's a 1-1 relation between pid and owner, but otherwise this is not always the case. For checking lock ownership, 'fi->owner' must be used. The l_pid field in 'struct flock' should only be used to fill in this field in getlk().

    +

    Note: if the locking methods are not implemented, the kernel will still allow file locking to work locally. Hence these are only interesting for network filesystems and similar.

    +

    Valid replies: fuse_reply_err

    +
    Parameters
    + + + + + + +
    reqrequest handle
    inothe inode number
    fifile information
    lockthe region/type to set
    sleeplocking operation may sleep
    +
    +
    + +

    Definition at line 1022 of file fuse_lowlevel.h.

    + +
    +
    + +

    ◆ setxattr

    + +
    +
    + + + + +
    void(* fuse_lowlevel_ops::setxattr) (fuse_req_t req, fuse_ino_t ino, const char *name, const char *value, size_t size, int flags)
    +
    +

    Set an extended attribute

    +

    If this request is answered with an error code of ENOSYS, this is treated as a permanent failure with error code EOPNOTSUPP, i.e. all future setxattr() requests will fail with EOPNOTSUPP without being send to the filesystem process.

    +

    Valid replies: fuse_reply_err

    + +

    Definition at line 852 of file fuse_lowlevel.h.

    + +
    +
    + +

    ◆ statfs

    + +
    +
    + + + + +
    void(* fuse_lowlevel_ops::statfs) (fuse_req_t req, fuse_ino_t ino)
    +
    +

    Get file system statistics

    +

    Valid replies: fuse_reply_statfs fuse_reply_err

    +
    Parameters
    + + + +
    reqrequest handle
    inothe inode number, zero means "undefined"
    +
    +
    + +

    Definition at line 839 of file fuse_lowlevel.h.

    + +
    +
    + +

    ◆ statx

    + +
    +
    + + + + +
    void(* fuse_lowlevel_ops::statx) (fuse_req_t req, fuse_ino_t ino, int flags, int mask, struct fuse_file_info *fi)
    +
    +

    Get extended file attributes.

    +

    Valid replies: fuse_reply_statx fuse_reply_err

    +
    Parameters
    + + + + + + +
    reqrequest handle
    inothe inode number
    flagsbitmask of requested flags
    maskbitmask of requested fields
    fifile information (may be NULL)
    +
    +
    + +

    Definition at line 1342 of file fuse_lowlevel.h.

    + +
    +
    + +

    ◆ symlink

    + +
    +
    + + + + +
    void(* fuse_lowlevel_ops::symlink) (fuse_req_t req, const char *link, fuse_ino_t parent, const char *name)
    +
    +

    Create a symbolic link

    +

    Valid replies: fuse_reply_entry fuse_reply_err

    +
    Parameters
    + + + + + +
    reqrequest handle
    linkthe contents of the symbolic link
    parentinode number of the parent directory
    nameto create
    +
    +
    + +

    Definition at line 442 of file fuse_lowlevel.h.

    + +
    +
    + +

    ◆ tmpfile

    + +
    +
    + + + + +
    void(* fuse_lowlevel_ops::tmpfile) (fuse_req_t req, fuse_ino_t parent, mode_t mode, struct fuse_file_info *fi)
    +
    +

    Create a tempfile

    +

    Tempfile means an anonymous file. It can be made into a normal file later by using linkat or such.

    +

    If this is answered with an error ENOSYS this is treated by the kernel as a permanent failure and it will disable the feature and not ask again.

    +

    Valid replies: fuse_reply_create fuse_reply_err

    +
    Parameters
    + + + + + +
    reqrequest handle
    parentinode number of the parent directory
    modefile type and mode with which to create the new file
    fifile information
    +
    +
    + +

    Definition at line 1326 of file fuse_lowlevel.h.

    + +
    +
    + +

    ◆ unlink

    + +
    +
    + + + + +
    void(* fuse_lowlevel_ops::unlink) (fuse_req_t req, fuse_ino_t parent, const char *name)
    +
    +

    Remove a file

    +

    If the file's inode's lookup count is non-zero, the file system is expected to postpone any removal of the inode until the lookup count reaches zero (see description of the forget function).

    +

    Valid replies: fuse_reply_err

    +
    Parameters
    + + + + +
    reqrequest handle
    parentinode number of the parent directory
    nameto remove
    +
    +
    + +

    Definition at line 411 of file fuse_lowlevel.h.

    + +
    +
    + +

    ◆ write

    + +
    +
    + + + + +
    void(* fuse_lowlevel_ops::write) (fuse_req_t req, fuse_ino_t ino, const char *buf, size_t size, off_t off, struct fuse_file_info *fi)
    +
    +

    Write data

    +

    Write should return exactly the number of bytes requested except on error. An exception to this is when the file has been opened in 'direct_io' mode, in which case the return value of the write system call will reflect the return value of this operation.

    +

    Unless FUSE_CAP_HANDLE_KILLPRIV is disabled, this method is expected to reset the setuid and setgid bits.

    +

    fi->fh will contain the value set by the open method, or will be undefined if the open method didn't set any value.

    +

    Valid replies: fuse_reply_write fuse_reply_err

    +
    Parameters
    + + + + + + + +
    reqrequest handle
    inothe inode number
    bufdata to write
    sizenumber of bytes to write
    offoffset to write to
    fifile information
    +
    +
    + +

    Definition at line 613 of file fuse_lowlevel.h.

    + +
    +
    + +

    ◆ write_buf

    + +
    +
    + + + + +
    void(* fuse_lowlevel_ops::write_buf) (fuse_req_t req, fuse_ino_t ino, struct fuse_bufvec *bufv, off_t off, struct fuse_file_info *fi)
    +
    +

    Write data made available in a buffer

    +

    This is a more generic version of the ->write() method. If FUSE_CAP_SPLICE_READ is set in fuse_conn_info.want and the kernel supports splicing from the fuse device, then the data will be made available in pipe for supporting zero copy data transfer.

    +

    buf->count is guaranteed to be one (and thus buf->idx is always zero). The write_buf handler must ensure that bufv->off is correctly updated (reflecting the number of bytes read from bufv->buf[0]).

    +

    Unless FUSE_CAP_HANDLE_KILLPRIV is disabled, this method is expected to reset the setuid and setgid bits.

    +

    Valid replies: fuse_reply_write fuse_reply_err

    +
    Parameters
    + + + + + + +
    reqrequest handle
    inothe inode number
    bufvbuffer containing the data
    offoffset to write to
    fifile information
    +
    +
    + +

    Definition at line 1149 of file fuse_lowlevel.h.

    + +
    +
    +
    The documentation for this struct was generated from the following file: +
    + + +
    + + diff --git a/doc/html/structfuse__module.html b/doc/html/structfuse__module.html new file mode 100644 index 0000000..222bda5 --- /dev/null +++ b/doc/html/structfuse__module.html @@ -0,0 +1,65 @@ + + + + + + + +libfuse: fuse_module Struct Reference + + + + + + + +
    +
    + + + + + + +
    +
    libfuse +
    +
    +
    + + + + + + + +
    +
    +
    +
    fuse_module Struct Reference
    +
    +
    + +

    #include <fuse_i.h>

    +

    Detailed Description

    +

    Filesystem module

    +

    Filesystem modules are registered with the FUSE_REGISTER_MODULE() macro.

    + +

    Definition at line 135 of file fuse_i.h.

    +

    The documentation for this struct was generated from the following file: +
    + + +
    + + diff --git a/doc/html/structfuse__operations.html b/doc/html/structfuse__operations.html new file mode 100644 index 0000000..6086655 --- /dev/null +++ b/doc/html/structfuse__operations.html @@ -0,0 +1,928 @@ + + + + + + + +libfuse: fuse_operations Struct Reference + + + + + + + +
    +
    + + + + + + +
    +
    libfuse +
    +
    +
    + + + + + + + +
    +
    +
    + +
    fuse_operations Struct Reference
    +
    +
    + +

    #include <fuse.h>

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    +Data Fields

    int(* getattr )(const char *, struct stat *, struct fuse_file_info *fi)
    int(* readlink )(const char *, char *, size_t)
    int(* mknod )(const char *, mode_t, dev_t)
    int(* mkdir )(const char *, mode_t)
    int(* unlink )(const char *)
    int(* rmdir )(const char *)
    int(* symlink )(const char *, const char *)
    int(* rename )(const char *, const char *, unsigned int flags)
    int(* link )(const char *, const char *)
    int(* chmod )(const char *, mode_t, struct fuse_file_info *fi)
    int(* chown )(const char *, uid_t, gid_t, struct fuse_file_info *fi)
    int(* truncate )(const char *, off_t, struct fuse_file_info *fi)
    int(* open )(const char *, struct fuse_file_info *)
    int(* read )(const char *, char *, size_t, off_t, struct fuse_file_info *)
    int(* write )(const char *, const char *, size_t, off_t, struct fuse_file_info *)
    int(* statfs )(const char *, struct statvfs *)
    int(* flush )(const char *, struct fuse_file_info *)
    int(* release )(const char *, struct fuse_file_info *)
    int(* fsync )(const char *, int, struct fuse_file_info *)
    int(* setxattr )(const char *, const char *, const char *, size_t, int)
    int(* getxattr )(const char *, const char *, char *, size_t)
    int(* listxattr )(const char *, char *, size_t)
    int(* removexattr )(const char *, const char *)
    int(* opendir )(const char *, struct fuse_file_info *)
    int(* readdir )(const char *, void *, fuse_fill_dir_t, off_t, struct fuse_file_info *, enum fuse_readdir_flags)
    int(* releasedir )(const char *, struct fuse_file_info *)
    int(* fsyncdir )(const char *, int, struct fuse_file_info *)
    void *(* init )(struct fuse_conn_info *conn, struct fuse_config *cfg)
    void(* destroy )(void *private_data)
    int(* access )(const char *, int)
    int(* create )(const char *, mode_t, struct fuse_file_info *)
    int(* lock )(const char *, struct fuse_file_info *, int cmd, struct flock *)
    int(* utimens )(const char *, const struct timespec tv[2], struct fuse_file_info *fi)
    int(* bmap )(const char *, size_t blocksize, uint64_t *idx)
    int(* ioctl )(const char *, unsigned int cmd, void *arg, struct fuse_file_info *, unsigned int flags, void *data)
    int(* poll )(const char *, struct fuse_file_info *, struct fuse_pollhandle *ph, unsigned *reventsp)
    int(* write_buf )(const char *, struct fuse_bufvec *buf, off_t off, struct fuse_file_info *)
    int(* read_buf )(const char *, struct fuse_bufvec **bufp, size_t size, off_t off, struct fuse_file_info *)
    int(* flock )(const char *, struct fuse_file_info *, int op)
    int(* fallocate )(const char *, int, off_t, off_t, struct fuse_file_info *)
    ssize_t(* copy_file_range )(const char *path_in, struct fuse_file_info *fi_in, off_t offset_in, const char *path_out, struct fuse_file_info *fi_out, off_t offset_out, size_t size, int flags)
    off_t(* lseek )(const char *, off_t off, int whence, struct fuse_file_info *)
    int(* statx )(const char *path, int flags, int mask, struct statx *stxbuf, struct fuse_file_info *fi)
    +

    Detailed Description

    +

    The file system operations:

    +

    Most of these should work very similarly to the well known UNIX file system operations. A major exception is that instead of returning an error in 'errno', the operation should return the negated error value (-errno) directly.

    +

    All methods are optional, but some are essential for a useful filesystem (e.g. getattr). Open, flush, release, fsync, opendir, releasedir, fsyncdir, access, create, truncate, lock, init and destroy are special purpose methods, without which a full featured filesystem can still be implemented.

    +

    In general, all methods are expected to perform any necessary permission checking. However, a filesystem may delegate this task to the kernel by passing the default_permissions mount option to fuse_new(). In this case, methods will only be called if the kernel's permission check has succeeded.

    +

    Almost all operations take a path which can be of any length.

    + +

    Definition at line 355 of file fuse.h.

    +

    Field Documentation

    + +

    ◆ access

    + +
    +
    + + + + +
    int(* fuse_operations::access) (const char *, int)
    +
    +

    Check file access permissions

    +

    This will be called for the access() system call. If the 'default_permissions' mount option is given, this method is not called.

    +

    This method is not called under Linux kernel versions 2.4.x

    + +

    Definition at line 666 of file fuse.h.

    + +
    +
    + +

    ◆ bmap

    + +
    +
    + + + + +
    int(* fuse_operations::bmap) (const char *, size_t blocksize, uint64_t *idx)
    +
    +

    Map block index within file to block index within device

    +

    Note: This makes sense only for block device backed filesystems mounted with the 'blkdev' option

    + +

    Definition at line 734 of file fuse.h.

    + +
    +
    + +

    ◆ chmod

    + +
    +
    + + + + +
    int(* fuse_operations::chmod) (const char *, mode_t, struct fuse_file_info *fi)
    +
    +

    Change the permission bits of a file

    +

    fi will always be NULL if the file is not currently open, but may also be NULL if the file is open.

    + +

    Definition at line 423 of file fuse.h.

    + +
    +
    + +

    ◆ chown

    + +
    +
    + + + + +
    int(* fuse_operations::chown) (const char *, uid_t, gid_t, struct fuse_file_info *fi)
    +
    +

    Change the owner and group of a file

    +

    fi will always be NULL if the file is not currently open, but may also be NULL if the file is open.

    +

    Unless FUSE_CAP_HANDLE_KILLPRIV is disabled, this method is expected to reset the setuid and setgid bits.

    + +

    Definition at line 433 of file fuse.h.

    + +
    +
    + +

    ◆ copy_file_range

    + +
    +
    + + + + +
    ssize_t(* fuse_operations::copy_file_range) (const char *path_in, struct fuse_file_info *fi_in, off_t offset_in, const char *path_out, struct fuse_file_info *fi_out, off_t offset_out, size_t size, int flags)
    +
    +

    Copy a range of data from one file to another

    +

    Performs an optimized copy between two file descriptors without the additional cost of transferring data through the FUSE kernel module to user space (glibc) and then back into the FUSE filesystem again.

    +

    In case this method is not implemented, applications are expected to fall back to a regular file copy. (Some glibc versions did this emulation automatically, but the emulation has been removed from all glibc release branches.)

    + +

    Definition at line 849 of file fuse.h.

    + +
    +
    + +

    ◆ create

    + +
    +
    + + + + +
    int(* fuse_operations::create) (const char *, mode_t, struct fuse_file_info *)
    +
    +

    Create and open a file

    +

    If the file does not exist, first create it with the specified mode, and then open it.

    +

    If this method is not implemented or under Linux kernel versions earlier than 2.6.15, the mknod() and open() methods will be called instead.

    + +

    Definition at line 678 of file fuse.h.

    + +
    +
    + +

    ◆ destroy

    + +
    +
    + + + + +
    void(* fuse_operations::destroy) (void *private_data)
    +
    +

    Clean up filesystem

    +

    Called on filesystem exit.

    + +

    Definition at line 655 of file fuse.h.

    + +
    +
    + +

    ◆ fallocate

    + +
    +
    + + + + +
    int(* fuse_operations::fallocate) (const char *, int, off_t, off_t, struct fuse_file_info *)
    +
    +

    Allocates space for an open file

    +

    This function ensures that required space is allocated for specified file. If this function returns success then any subsequent write request to specified range is guaranteed not to fail because of lack of space on the file system media.

    + +

    Definition at line 834 of file fuse.h.

    + +
    +
    + +

    ◆ flock

    + +
    +
    + + + + +
    int(* fuse_operations::flock) (const char *, struct fuse_file_info *, int op)
    +
    +

    Perform BSD file locking operation

    +

    The op argument will be either LOCK_SH, LOCK_EX or LOCK_UN

    +

    Nonblocking requests will be indicated by ORing LOCK_NB to the above operations

    +

    For more information see the flock(2) manual page.

    +

    Additionally fi->owner will be set to a value unique to this open file. This same value will be supplied to ->release() when the file is released.

    +

    Note: if this method is not implemented, the kernel will still allow file locking to work locally. Hence it is only interesting for network filesystems and similar.

    + +

    Definition at line 824 of file fuse.h.

    + +
    +
    + +

    ◆ flush

    + +
    +
    + + + + +
    int(* fuse_operations::flush) (const char *, struct fuse_file_info *)
    +
    +

    Possibly flush cached data

    +

    BIG NOTE: This is not equivalent to fsync(). It's not a request to sync dirty data.

    +

    Flush is called on each close() of a file descriptor, as opposed to release which is called on the close of the last file descriptor for a file. Under Linux, errors returned by flush() will be passed to userspace as errors from close(), so flush() is a good place to write back any cached dirty data. However, many applications ignore errors on close(), and on non-Linux systems, close() may succeed even if flush() returns an error. For these reasons, filesystems should not assume that errors returned by flush will ever be noticed or even delivered.

    +

    NOTE: The flush() method may be called more than once for each open(). This happens if more than one file descriptor refers to an open file handle, e.g. due to dup(), dup2() or fork() calls. It is not possible to determine if a flush is final, so each flush should be treated equally. Multiple write-flush sequences are relatively rare, so this shouldn't be a problem.

    +

    Filesystems shouldn't assume that flush will be called at any particular point. It may be called more times than expected, or not at all.

    + +

    Definition at line 552 of file fuse.h.

    + +
    +
    + +

    ◆ fsync

    + +
    +
    + + + + +
    int(* fuse_operations::fsync) (const char *, int, struct fuse_file_info *)
    +
    +

    Synchronize file contents

    +

    If the datasync parameter is non-zero, then only the user data should be flushed, not the meta data.

    + +

    Definition at line 573 of file fuse.h.

    + +
    +
    + +

    ◆ fsyncdir

    + +
    +
    + + + + +
    int(* fuse_operations::fsyncdir) (const char *, int, struct fuse_file_info *)
    +
    +

    Synchronize directory contents

    +

    If the directory has been removed after the call to opendir, the path parameter will be NULL.

    +

    If the datasync parameter is non-zero, then only the user data should be flushed, not the meta data

    + +

    Definition at line 637 of file fuse.h.

    + +
    +
    + +

    ◆ getattr

    + +
    +
    + + + + +
    int(* fuse_operations::getattr) (const char *, struct stat *, struct fuse_file_info *fi)
    +
    +

    Get file attributes.

    +

    Similar to stat(). The 'st_dev' and 'st_blksize' fields are ignored. The 'st_ino' field is ignored except if the 'use_ino' mount option is given. In that case it is passed to userspace, but libfuse and the kernel will still assign a different inode for internal use (called the "nodeid").

    +

    fi will always be NULL if the file is not currently open, but may also be NULL if the file is open.

    + +

    Definition at line 367 of file fuse.h.

    + +
    +
    + +

    ◆ getxattr

    + +
    +
    + + + + +
    int(* fuse_operations::getxattr) (const char *, const char *, char *, size_t)
    +
    +

    Get extended attributes

    + +

    Definition at line 579 of file fuse.h.

    + +
    +
    + +

    ◆ init

    + +
    +
    + + + + +
    void *(* fuse_operations::init) (struct fuse_conn_info *conn, struct fuse_config *cfg)
    +
    +

    Initialize filesystem

    +

    The return value will passed in the private_data field of struct fuse_context to all file operations, and as a parameter to the destroy() method. It overrides the initial value provided to fuse_main() / fuse_new().

    + +

    Definition at line 647 of file fuse.h.

    + +
    +
    + +

    ◆ ioctl

    + +
    +
    + + + + +
    int(* fuse_operations::ioctl) (const char *, unsigned int cmd, void *arg, struct fuse_file_info *, unsigned int flags, void *data)
    +
    +

    Ioctl

    +

    flags will have FUSE_IOCTL_COMPAT set for 32bit ioctls in 64bit environment. The size and direction of data is determined by IOC*() decoding of cmd. For _IOC_NONE, data will be NULL, for _IOC_WRITE data is out area, for _IOC_READ in area and if both are set in/out area. In all non-NULL cases, the area is of _IOC_SIZE(cmd) bytes.

    +

    If flags has FUSE_IOCTL_DIR then the fuse_file_info refers to a directory file handle.

    +

    Note : the unsigned long request submitted by the application is truncated to 32 bits.

    + +

    Definition at line 756 of file fuse.h.

    + +
    +
    + +

    ◆ link

    + +
    +
    + + + + +
    int(* fuse_operations::link) (const char *, const char *)
    +
    +

    Create a hard link to a file

    + +

    Definition at line 416 of file fuse.h.

    + +
    +
    + +

    ◆ listxattr

    + +
    +
    + + + + +
    int(* fuse_operations::listxattr) (const char *, char *, size_t)
    +
    +

    List extended attributes

    + +

    Definition at line 582 of file fuse.h.

    + +
    +
    + +

    ◆ lock

    + +
    +
    + + + + +
    int(* fuse_operations::lock) (const char *, struct fuse_file_info *, int cmd, struct flock *)
    +
    +

    Perform POSIX file locking operation

    +

    The cmd argument will be either F_GETLK, F_SETLK or F_SETLKW.

    +

    For the meaning of fields in 'struct flock' see the man page for fcntl(2). The l_whence field will always be set to SEEK_SET.

    +

    For checking lock ownership, the 'fuse_file_info->owner' argument must be used.

    +

    For F_GETLK operation, the library will first check currently held locks, and if a conflicting lock is found it will return information without calling this method. This ensures, that for local locks the l_pid field is correctly filled in. The results may not be accurate in case of race conditions and in the presence of hard links, but it's unlikely that an application would rely on accurate GETLK results in these cases. If a conflicting lock is not found, this method will be called, and the filesystem may fill out l_pid by a meaningful value, or it may leave this field zero.

    +

    For F_SETLK and F_SETLKW the l_pid field will be set to the pid of the process performing the locking operation.

    +

    Note: if this method is not implemented, the kernel will still allow file locking to work locally. Hence it is only interesting for network filesystems and similar.

    + +

    Definition at line 710 of file fuse.h.

    + +
    +
    + +

    ◆ lseek

    + +
    +
    + + + + +
    off_t(* fuse_operations::lseek) (const char *, off_t off, int whence, struct fuse_file_info *)
    +
    +

    Find next data or hole after the specified offset

    + +

    Definition at line 858 of file fuse.h.

    + +
    +
    + +

    ◆ mkdir

    + +
    +
    + + + + +
    int(* fuse_operations::mkdir) (const char *, mode_t)
    +
    +

    Create a directory

    +

    Note that the mode argument may not have the type specification bits set, i.e. S_ISDIR(mode) can be false. To obtain the correct directory type bits use mode|S_IFDIR

    + +

    Definition at line 393 of file fuse.h.

    + +
    +
    + +

    ◆ mknod

    + +
    +
    + + + + +
    int(* fuse_operations::mknod) (const char *, mode_t, dev_t)
    +
    +

    Create a file node

    +

    This is called for creation of all non-directory, non-symlink nodes. If the filesystem defines a create() method, then for regular files that will be called instead.

    + +

    Definition at line 385 of file fuse.h.

    + +
    +
    + +

    ◆ open

    + +
    +
    + + + + +
    int(* fuse_operations::open) (const char *, struct fuse_file_info *)
    +
    +

    Open a file

    +

    Open flags are available in fi->flags. The following rules apply.

    +
      +
    • Creation (O_CREAT, O_EXCL, O_NOCTTY) flags will be filtered out / handled by the kernel.
    • +
    • Access modes (O_RDONLY, O_WRONLY, O_RDWR, O_EXEC, O_SEARCH) should be used by the filesystem to check if the operation is permitted. If the -o default_permissions mount option is given, this check is already done by the kernel before calling open() and may thus be omitted by the filesystem.
    • +
    • When writeback caching is enabled, the kernel may send read requests even for files opened with O_WRONLY. The filesystem should be prepared to handle this.
    • +
    • When writeback caching is disabled, the filesystem is expected to properly handle the O_APPEND flag and ensure that each write is appending to the end of the file.
    • +
    • When writeback caching is enabled, the kernel will handle O_APPEND. However, unless all changes to the file come through the kernel this will not work reliably. The filesystem should thus either ignore the O_APPEND flag (and let the kernel handle it), or return an error (indicating that reliably O_APPEND is not available).
    • +
    +

    Filesystem may store an arbitrary file handle (pointer, index, etc) in fi->fh, and use this in other all other file operations (read, write, flush, release, fsync).

    +

    Filesystem may also implement stateless file I/O and not store anything in fi->fh.

    +

    There are also some flags (direct_io, keep_cache) which the filesystem may set in fi, to change the way the file is opened. See fuse_file_info structure in <fuse_common.h> for more details.

    +

    If this request is answered with an error code of ENOSYS and FUSE_CAP_NO_OPEN_SUPPORT is set in fuse_conn_info.capable, this is treated as success and future calls to open will also succeed without being sent to the filesystem process.

    + +

    Definition at line 492 of file fuse.h.

    + +
    +
    + +

    ◆ opendir

    + +
    +
    + + + + +
    int(* fuse_operations::opendir) (const char *, struct fuse_file_info *)
    +
    +

    Open directory

    +

    Unless the 'default_permissions' mount option is given, this method should check if opendir is permitted for this directory. Optionally opendir may also return an arbitrary filehandle in the fuse_file_info structure, which will be passed to readdir, releasedir and fsyncdir.

    + +

    Definition at line 595 of file fuse.h.

    + +
    +
    + +

    ◆ poll

    + +
    +
    + + + + +
    int(* fuse_operations::poll) (const char *, struct fuse_file_info *, struct fuse_pollhandle *ph, unsigned *reventsp)
    +
    +

    Poll for IO readiness events

    +

    Note: If ph is non-NULL, the client should notify when IO readiness events occur by calling fuse_notify_poll() with the specified ph.

    +

    Regardless of the number of times poll with a non-NULL ph is received, single notification is enough to clear all. Notifying more times incurs overhead but doesn't harm correctness.

    +

    The callee is responsible for destroying ph with fuse_pollhandle_destroy() when no longer in use.

    + +

    Definition at line 775 of file fuse.h.

    + +
    +
    + +

    ◆ read

    + +
    +
    + + + + +
    int(* fuse_operations::read) (const char *, char *, size_t, off_t, struct fuse_file_info *)
    +
    +

    Read data from an open file

    +

    Read should return exactly the number of bytes requested except on EOF or error, otherwise the rest of the data will be substituted with zeroes. An exception to this is when the 'direct_io' mount option is specified, in which case the return value of the read system call will reflect the return value of this operation.

    + +

    Definition at line 503 of file fuse.h.

    + +
    +
    + +

    ◆ read_buf

    + +
    +
    + + + + +
    int(* fuse_operations::read_buf) (const char *, struct fuse_bufvec **bufp, size_t size, off_t off, struct fuse_file_info *)
    +
    +

    Store data from an open file in a buffer

    +

    Similar to the read() method, but data is stored and returned in a generic buffer.

    +

    No actual copying of data has to take place, the source file descriptor may simply be stored in the buffer for later data transfer.

    +

    The buffer must be allocated dynamically and stored at the location pointed to by bufp. If the buffer contains memory regions, they too must be allocated using malloc(). The allocated memory will be freed by the caller.

    + +

    Definition at line 804 of file fuse.h.

    + +
    +
    + +

    ◆ readdir

    + +
    +
    + + + + +
    int(* fuse_operations::readdir) (const char *, void *, fuse_fill_dir_t, off_t, struct fuse_file_info *, enum fuse_readdir_flags)
    +
    +

    Read directory

    +

    The filesystem may choose between two modes of operation:

    +

    1) The readdir implementation ignores the offset parameter, and passes zero to the filler function's offset. The filler function will not return '1' (unless an error happens), so the whole directory is read in a single readdir operation.

    +

    2) The readdir implementation keeps track of the offsets of the directory entries. It uses the offset parameter and always passes non-zero offset to the filler function. When the buffer is full (or an error happens) the filler function will return '1'.

    +

    When FUSE_READDIR_PLUS is not set, only some parameters of the fill function (the fuse_fill_dir_t parameter) are actually used: The file type (which is part of stat::st_mode) is used. And if fuse_config::use_ino is set, the inode (stat::st_ino) is also used. The other fields are ignored when FUSE_READDIR_PLUS is not set.

    + +

    Definition at line 619 of file fuse.h.

    + +
    +
    + +

    ◆ readlink

    + +
    +
    + + + + +
    int(* fuse_operations::readlink) (const char *, char *, size_t)
    +
    +

    Read the target of a symbolic link

    +

    The buffer should be filled with a null terminated string. The buffer size argument includes the space for the terminating null character. If the linkname is too long to fit in the buffer, it should be truncated. The return value should be 0 for success.

    + +

    Definition at line 377 of file fuse.h.

    + +
    +
    + +

    ◆ release

    + +
    +
    + + + + +
    int(* fuse_operations::release) (const char *, struct fuse_file_info *)
    +
    +

    Release an open file

    +

    Release is called when there are no more references to an open file: all file descriptors are closed and all memory mappings are unmapped.

    +

    For every open() call there will be exactly one release() call with the same flags and file handle. It is possible to have a file opened more than once, in which case only the last release will mean, that no more reads/writes will happen on the file. The return value of release is ignored.

    + +

    Definition at line 566 of file fuse.h.

    + +
    +
    + +

    ◆ releasedir

    + +
    +
    + + + + +
    int(* fuse_operations::releasedir) (const char *, struct fuse_file_info *)
    +
    +

    Release directory

    +

    If the directory has been removed after the call to opendir, the path parameter will be NULL.

    + +

    Definition at line 627 of file fuse.h.

    + +
    +
    + +

    ◆ removexattr

    + +
    +
    + + + + +
    int(* fuse_operations::removexattr) (const char *, const char *)
    +
    +

    Remove extended attributes

    + +

    Definition at line 585 of file fuse.h.

    + +
    +
    + +

    ◆ rename

    + +
    +
    + + + + +
    int(* fuse_operations::rename) (const char *, const char *, unsigned int flags)
    +
    +

    Rename a file

    +

    flags may be RENAME_EXCHANGE or RENAME_NOREPLACE. If RENAME_NOREPLACE is specified, the filesystem must not overwrite newname if it exists and return an error instead. If RENAME_EXCHANGE is specified, the filesystem must atomically exchange the two files, i.e. both must exist and neither may be deleted.

    + +

    Definition at line 413 of file fuse.h.

    + +
    +
    + +

    ◆ rmdir

    + +
    +
    + + + + +
    int(* fuse_operations::rmdir) (const char *)
    +
    +

    Remove a directory

    + +

    Definition at line 399 of file fuse.h.

    + +
    +
    + +

    ◆ setxattr

    + +
    +
    + + + + +
    int(* fuse_operations::setxattr) (const char *, const char *, const char *, size_t, int)
    +
    +

    Set extended attributes

    + +

    Definition at line 576 of file fuse.h.

    + +
    +
    + +

    ◆ statfs

    + +
    +
    + + + + +
    int(* fuse_operations::statfs) (const char *, struct statvfs *)
    +
    +

    Get file system statistics

    +

    The 'f_favail', 'f_fsid' and 'f_flag' fields are ignored

    + +

    Definition at line 522 of file fuse.h.

    + +
    +
    + +

    ◆ statx

    + +
    +
    + + + + +
    int(* fuse_operations::statx) (const char *path, int flags, int mask, struct statx *stxbuf, struct fuse_file_info *fi)
    +
    +

    Get extended file attributes.

    +

    fi may be NULL.

    +

    If path is NULL, then the AT_EMPTY_PATH bit in flags will be already set.

    + +

    Definition at line 868 of file fuse.h.

    + +
    +
    + +

    ◆ symlink

    + +
    +
    + + + + +
    int(* fuse_operations::symlink) (const char *, const char *)
    +
    +

    Create a symbolic link

    + +

    Definition at line 402 of file fuse.h.

    + +
    +
    + +

    ◆ truncate

    + +
    +
    + + + + +
    int(* fuse_operations::truncate) (const char *, off_t, struct fuse_file_info *fi)
    +
    +

    Change the size of a file

    +

    fi will always be NULL if the file is not currently open, but may also be NULL if the file is open.

    +

    Unless FUSE_CAP_HANDLE_KILLPRIV is disabled, this method is expected to reset the setuid and setgid bits.

    + +

    Definition at line 443 of file fuse.h.

    + +
    +
    + +

    ◆ unlink

    + +
    +
    + + + + +
    int(* fuse_operations::unlink) (const char *)
    +
    +

    Remove a file

    + +

    Definition at line 396 of file fuse.h.

    + +
    +
    + +

    ◆ utimens

    + +
    +
    + + + + +
    int(* fuse_operations::utimens) (const char *, const struct timespec tv[2], struct fuse_file_info *fi)
    +
    +

    Change the access and modification times of a file with nanosecond resolution

    +

    This supersedes the old utime() interface. New applications should use this.

    +

    fi will always be NULL if the file is not currently open, but may also be NULL if the file is open.

    +

    See the utimensat(2) man page for details.

    + +

    Definition at line 725 of file fuse.h.

    + +
    +
    + +

    ◆ write

    + +
    +
    + + + + +
    int(* fuse_operations::write) (const char *, const char *, size_t, off_t, struct fuse_file_info *)
    +
    +

    Write data to an open file

    +

    Write should return exactly the number of bytes requested except on error. An exception to this is when the 'direct_io' mount option is specified (see read operation).

    +

    Unless FUSE_CAP_HANDLE_KILLPRIV is disabled, this method is expected to reset the setuid and setgid bits.

    + +

    Definition at line 515 of file fuse.h.

    + +
    +
    + +

    ◆ write_buf

    + +
    +
    + + + + +
    int(* fuse_operations::write_buf) (const char *, struct fuse_bufvec *buf, off_t off, struct fuse_file_info *)
    +
    +

    Write contents of buffer to an open file

    +

    Similar to the write() method, but data is supplied in a generic buffer. Use fuse_buf_copy() to transfer data to the destination.

    +

    Unless FUSE_CAP_HANDLE_KILLPRIV is disabled, this method is expected to reset the setuid and setgid bits.

    + +

    Definition at line 787 of file fuse.h.

    + +
    +
    +
    The documentation for this struct was generated from the following file: +
    + + +
    + + diff --git a/doc/html/structfuse__opt.html b/doc/html/structfuse__opt.html new file mode 100644 index 0000000..ebfb893 --- /dev/null +++ b/doc/html/structfuse__opt.html @@ -0,0 +1,146 @@ + + + + + + + +libfuse: fuse_opt Struct Reference + + + + + + + +
    +
    + + + + + + +
    +
    libfuse +
    +
    +
    + + + + + + + +
    +
    +
    + +
    fuse_opt Struct Reference
    +
    +
    + +

    #include <fuse_opt.h>

    + + + + + +

    +Data Fields

    const char * templ
    unsigned long offset
    int value
    +

    Detailed Description

    +

    Option description

    +

    This structure describes a single option, and action associated with it, in case it matches.

    +

    More than one such match may occur, in which case the action for each match is executed.

    +

    There are three possible actions in case of a match:

    +

    i) An integer (int or unsigned) variable determined by 'offset' is set to 'value'

    +

    ii) The processing function is called, with 'value' as the key

    +

    iii) An integer (any) or string (char *) variable determined by 'offset' is set to the value of an option parameter

    +

    'offset' should normally be either set to

    +
      +
    • 'offsetof(struct foo, member)' actions i) and iii)
    • +
    • -1 action ii)
    • +
    +

    The 'offsetof()' macro is defined in the <stddef.h> header.

    +

    The template determines which options match, and also have an effect on the action. Normally the action is either i) or ii), but if a format is present in the template, then action iii) is performed.

    +

    The types of templates are:

    +

    1) "-x", "-foo", "--foo", "--foo-bar", etc. These match only themselves. Invalid values are "--" and anything beginning with "-o"

    +

    2) "foo", "foo-bar", etc. These match "-ofoo", "-ofoo-bar" or the relevant option in a comma separated option list

    +

    3) "bar=", "--foo=", etc. These are variations of 1) and 2) which have a parameter

    +

    4) "bar=%s", "--foo=%lu", etc. Same matching as above but perform action iii).

    +

    5) "-x ", etc. Matches either "-xparam" or "-x param" as two separate arguments

    +

    6) "-x %s", etc. Combination of 4) and 5)

    +

    If the format is "%s", memory is allocated for the string unlike with scanf(). The previous value (if non-NULL) stored at the this location is freed.

    + +

    Definition at line 77 of file fuse_opt.h.

    +

    Field Documentation

    + +

    ◆ offset

    + +
    +
    + + + + +
    unsigned long fuse_opt::offset
    +
    +

    Offset of variable within 'data' parameter of fuse_opt_parse() or -1

    + +

    Definition at line 85 of file fuse_opt.h.

    + +
    +
    + +

    ◆ templ

    + +
    +
    + + + + +
    const char* fuse_opt::templ
    +
    +

    Matching template and optional parameter formatting

    + +

    Definition at line 79 of file fuse_opt.h.

    + +
    +
    + +

    ◆ value

    + +
    +
    + + + + +
    int fuse_opt::value
    +
    +

    Value to set the variable to, or to be passed as 'key' to the processing function. Ignored if template has a format

    + +

    Definition at line 91 of file fuse_opt.h.

    + +
    +
    +
    The documentation for this struct was generated from the following file: +
    + + +
    + + diff --git a/doc/html/structfuse__ring__pool.html b/doc/html/structfuse__ring__pool.html new file mode 100644 index 0000000..d7dc018 --- /dev/null +++ b/doc/html/structfuse__ring__pool.html @@ -0,0 +1,62 @@ + + + + + + + +libfuse: fuse_ring_pool Struct Reference + + + + + + + +
    +
    + + + + + + +
    +
    libfuse +
    +
    +
    + + + + + + + +
    +
    +
    +
    fuse_ring_pool Struct Reference
    +
    +
    +

    Detailed Description

    +

    Main fuse_ring structure, holds all fuse-ring data

    + +

    Definition at line 72 of file fuse_uring.c.

    +

    The documentation for this struct was generated from the following file: +
    + + +
    + + diff --git a/doc/html/structfuse__supp__groups.html b/doc/html/structfuse__supp__groups.html new file mode 100644 index 0000000..cde2082 --- /dev/null +++ b/doc/html/structfuse__supp__groups.html @@ -0,0 +1,64 @@ + + + + + + + +libfuse: fuse_supp_groups Struct Reference + + + + + + + +
    +
    + + + + + + +
    +
    libfuse +
    +
    +
    + + + + + + + +
    +
    +
    +
    fuse_supp_groups Struct Reference
    +
    +
    + +

    #include <fuse_kernel.h>

    +

    Detailed Description

    +

    struct fuse_supp_groups - Supplementary group extension @nr_groups: number of supplementary groups @groups: flexible array of group IDs

    + +

    Definition at line 1235 of file fuse_kernel.h.

    +

    The documentation for this struct was generated from the following file: +
    + + +
    + + diff --git a/doc/html/structfuse__uring__cmd__req.html b/doc/html/structfuse__uring__cmd__req.html new file mode 100644 index 0000000..5fb996a --- /dev/null +++ b/doc/html/structfuse__uring__cmd__req.html @@ -0,0 +1,64 @@ + + + + + + + +libfuse: fuse_uring_cmd_req Struct Reference + + + + + + + +
    +
    + + + + + + +
    +
    libfuse +
    +
    +
    + + + + + + + +
    +
    +
    +
    fuse_uring_cmd_req Struct Reference
    +
    +
    + +

    #include <fuse_kernel.h>

    +

    Detailed Description

    +

    In the 80B command area of the SQE.

    + +

    Definition at line 1292 of file fuse_kernel.h.

    +

    The documentation for this struct was generated from the following file: +
    + + +
    + + diff --git a/doc/html/structfuse__uring__req__header.html b/doc/html/structfuse__uring__req__header.html new file mode 100644 index 0000000..f306738 --- /dev/null +++ b/doc/html/structfuse__uring__req__header.html @@ -0,0 +1,64 @@ + + + + + + + +libfuse: fuse_uring_req_header Struct Reference + + + + + + + +
    +
    + + + + + + +
    +
    libfuse +
    +
    +
    + + + + + + + +
    +
    +
    +
    fuse_uring_req_header Struct Reference
    +
    +
    + +

    #include <fuse_kernel.h>

    +

    Detailed Description

    +

    Header for all fuse-io-uring requests

    + +

    Definition at line 1266 of file fuse_kernel.h.

    +

    The documentation for this struct was generated from the following file: +
    + + +
    + + diff --git a/doc/html/structlibfuse__version.html b/doc/html/structlibfuse__version.html new file mode 100644 index 0000000..0df7c40 --- /dev/null +++ b/doc/html/structlibfuse__version.html @@ -0,0 +1,64 @@ + + + + + + + +libfuse: libfuse_version Struct Reference + + + + + + + +
    +
    + + + + + + +
    +
    libfuse +
    +
    +
    + + + + + + + +
    +
    +
    +
    libfuse_version Struct Reference
    +
    +
    + +

    #include <fuse_common.h>

    +

    Detailed Description

    +

    libfuse version a file system was compiled with. Should be filled in from defines in 'libfuse_config.h'

    + +

    Definition at line 968 of file fuse_common.h.

    +

    The documentation for this struct was generated from the following file: +
    + + +
    + + diff --git a/doc/html/subdir_8c_source.html b/doc/html/subdir_8c_source.html new file mode 100644 index 0000000..6fcd1bd --- /dev/null +++ b/doc/html/subdir_8c_source.html @@ -0,0 +1,785 @@ + + + + + + + +libfuse: lib/modules/subdir.c Source File + + + + + + + +
    +
    + + + + + + +
    +
    libfuse +
    +
    +
    + + + + + + + + +
    +
    +
    +
    subdir.c
    +
    +
    +
    1/*
    +
    2 fuse subdir module: offset paths with a base directory
    +
    3 Copyright (C) 2007 Miklos Szeredi <miklos@szeredi.hu>
    +
    4
    +
    5 This program can be distributed under the terms of the GNU LGPLv2.
    +
    6 See the file LGPL2.txt
    +
    7*/
    +
    8
    +
    9#include <fuse_config.h>
    +
    10
    +
    11#include <fuse.h>
    +
    12#include <stdio.h>
    +
    13#include <stdlib.h>
    +
    14#include <stddef.h>
    +
    15#include <string.h>
    +
    16#include <errno.h>
    +
    17
    +
    18struct subdir {
    +
    19 char *base;
    +
    20 size_t baselen;
    +
    21 int rellinks;
    +
    22 struct fuse_fs *next;
    +
    23};
    +
    24
    +
    25static struct subdir *subdir_get(void)
    +
    26{
    + +
    28}
    +
    29
    +
    30static int subdir_addpath(struct subdir *d, const char *path, char **newpathp)
    +
    31{
    +
    32 char *newpath = NULL;
    +
    33
    +
    34 if (path != NULL) {
    +
    35 unsigned newlen = d->baselen + strlen(path);
    +
    36
    +
    37 newpath = malloc(newlen + 2);
    +
    38 if (!newpath)
    +
    39 return -ENOMEM;
    +
    40
    +
    41 if (path[0] == '/')
    +
    42 path++;
    +
    43 strcpy(newpath, d->base);
    +
    44 strcpy(newpath + d->baselen, path);
    +
    45 if (!newpath[0])
    +
    46 strcpy(newpath, ".");
    +
    47 }
    +
    48 *newpathp = newpath;
    +
    49
    +
    50 return 0;
    +
    51}
    +
    52
    +
    53static int subdir_getattr(const char *path, struct stat *stbuf,
    +
    54 struct fuse_file_info *fi)
    +
    55{
    +
    56 struct subdir *d = subdir_get();
    +
    57 char *newpath;
    +
    58 int err = subdir_addpath(d, path, &newpath);
    +
    59 if (!err) {
    +
    60 err = fuse_fs_getattr(d->next, newpath, stbuf, fi);
    +
    61 free(newpath);
    +
    62 }
    +
    63 return err;
    +
    64}
    +
    65
    +
    66static int subdir_access(const char *path, int mask)
    +
    67{
    +
    68 struct subdir *d = subdir_get();
    +
    69 char *newpath;
    +
    70 int err = subdir_addpath(d, path, &newpath);
    +
    71 if (!err) {
    +
    72 err = fuse_fs_access(d->next, newpath, mask);
    +
    73 free(newpath);
    +
    74 }
    +
    75 return err;
    +
    76}
    +
    77
    +
    78
    +
    79static int count_components(const char *p)
    +
    80{
    +
    81 int ctr;
    +
    82
    +
    83 for (; *p == '/'; p++);
    +
    84 for (ctr = 0; *p; ctr++) {
    +
    85 for (; *p && *p != '/'; p++);
    +
    86 for (; *p == '/'; p++);
    +
    87 }
    +
    88 return ctr;
    +
    89}
    +
    90
    +
    91static void strip_common(const char **sp, const char **tp)
    +
    92{
    +
    93 const char *s = *sp;
    +
    94 const char *t = *tp;
    +
    95 do {
    +
    96 for (; *s == '/'; s++);
    +
    97 for (; *t == '/'; t++);
    +
    98 *tp = t;
    +
    99 *sp = s;
    +
    100 for (; *s == *t && *s && *s != '/'; s++, t++);
    +
    101 } while ((*s == *t && *s) || (!*s && *t == '/') || (*s == '/' && !*t));
    +
    102}
    +
    103
    +
    104static void transform_symlink(struct subdir *d, const char *path,
    +
    105 char *buf, size_t size)
    +
    106{
    +
    107 const char *l = buf;
    +
    108 size_t llen;
    +
    109 char *s;
    +
    110 int dotdots;
    +
    111 int i;
    +
    112
    +
    113 if (l[0] != '/' || d->base[0] != '/')
    +
    114 return;
    +
    115
    +
    116 strip_common(&l, &path);
    +
    117 if (l - buf < (long) d->baselen)
    +
    118 return;
    +
    119
    +
    120 dotdots = count_components(path);
    +
    121 if (!dotdots)
    +
    122 return;
    +
    123 dotdots--;
    +
    124
    +
    125 llen = strlen(l);
    +
    126 if (dotdots * 3 + llen + 2 > size)
    +
    127 return;
    +
    128
    +
    129 s = buf + dotdots * 3;
    +
    130 if (llen)
    +
    131 memmove(s, l, llen + 1);
    +
    132 else if (!dotdots)
    +
    133 strcpy(s, ".");
    +
    134 else
    +
    135 *s = '\0';
    +
    136
    +
    137 for (s = buf, i = 0; i < dotdots; i++, s += 3)
    +
    138 memcpy(s, "../", 3);
    +
    139}
    +
    140
    +
    141
    +
    142static int subdir_readlink(const char *path, char *buf, size_t size)
    +
    143{
    +
    144 struct subdir *d = subdir_get();
    +
    145 char *newpath;
    +
    146 int err = subdir_addpath(d, path, &newpath);
    +
    147 if (!err) {
    +
    148 err = fuse_fs_readlink(d->next, newpath, buf, size);
    +
    149 if (!err && d->rellinks)
    +
    150 transform_symlink(d, newpath, buf, size);
    +
    151 free(newpath);
    +
    152 }
    +
    153 return err;
    +
    154}
    +
    155
    +
    156static int subdir_opendir(const char *path, struct fuse_file_info *fi)
    +
    157{
    +
    158 struct subdir *d = subdir_get();
    +
    159 char *newpath;
    +
    160 int err = subdir_addpath(d, path, &newpath);
    +
    161 if (!err) {
    +
    162 err = fuse_fs_opendir(d->next, newpath, fi);
    +
    163 free(newpath);
    +
    164 }
    +
    165 return err;
    +
    166}
    +
    167
    +
    168static int subdir_readdir(const char *path, void *buf,
    +
    169 fuse_fill_dir_t filler, off_t offset,
    +
    170 struct fuse_file_info *fi,
    +
    171 enum fuse_readdir_flags flags)
    +
    172{
    +
    173 struct subdir *d = subdir_get();
    +
    174 char *newpath;
    +
    175 int err = subdir_addpath(d, path, &newpath);
    +
    176 if (!err) {
    +
    177 err = fuse_fs_readdir(d->next, newpath, buf, filler, offset,
    +
    178 fi, flags);
    +
    179 free(newpath);
    +
    180 }
    +
    181 return err;
    +
    182}
    +
    183
    +
    184static int subdir_releasedir(const char *path, struct fuse_file_info *fi)
    +
    185{
    +
    186 struct subdir *d = subdir_get();
    +
    187 char *newpath;
    +
    188 int err = subdir_addpath(d, path, &newpath);
    +
    189 if (!err) {
    +
    190 err = fuse_fs_releasedir(d->next, newpath, fi);
    +
    191 free(newpath);
    +
    192 }
    +
    193 return err;
    +
    194}
    +
    195
    +
    196static int subdir_mknod(const char *path, mode_t mode, dev_t rdev)
    +
    197{
    +
    198 struct subdir *d = subdir_get();
    +
    199 char *newpath;
    +
    200 int err = subdir_addpath(d, path, &newpath);
    +
    201 if (!err) {
    +
    202 err = fuse_fs_mknod(d->next, newpath, mode, rdev);
    +
    203 free(newpath);
    +
    204 }
    +
    205 return err;
    +
    206}
    +
    207
    +
    208static int subdir_mkdir(const char *path, mode_t mode)
    +
    209{
    +
    210 struct subdir *d = subdir_get();
    +
    211 char *newpath;
    +
    212 int err = subdir_addpath(d, path, &newpath);
    +
    213 if (!err) {
    +
    214 err = fuse_fs_mkdir(d->next, newpath, mode);
    +
    215 free(newpath);
    +
    216 }
    +
    217 return err;
    +
    218}
    +
    219
    +
    220static int subdir_unlink(const char *path)
    +
    221{
    +
    222 struct subdir *d = subdir_get();
    +
    223 char *newpath;
    +
    224 int err = subdir_addpath(d, path, &newpath);
    +
    225 if (!err) {
    +
    226 err = fuse_fs_unlink(d->next, newpath);
    +
    227 free(newpath);
    +
    228 }
    +
    229 return err;
    +
    230}
    +
    231
    +
    232static int subdir_rmdir(const char *path)
    +
    233{
    +
    234 struct subdir *d = subdir_get();
    +
    235 char *newpath;
    +
    236 int err = subdir_addpath(d, path, &newpath);
    +
    237 if (!err) {
    +
    238 err = fuse_fs_rmdir(d->next, newpath);
    +
    239 free(newpath);
    +
    240 }
    +
    241 return err;
    +
    242}
    +
    243
    +
    244static int subdir_symlink(const char *from, const char *path)
    +
    245{
    +
    246 struct subdir *d = subdir_get();
    +
    247 char *newpath;
    +
    248 int err = subdir_addpath(d, path, &newpath);
    +
    249 if (!err) {
    +
    250 err = fuse_fs_symlink(d->next, from, newpath);
    +
    251 free(newpath);
    +
    252 }
    +
    253 return err;
    +
    254}
    +
    255
    +
    256static int subdir_rename(const char *from, const char *to, unsigned int flags)
    +
    257{
    +
    258 struct subdir *d = subdir_get();
    +
    259 char *newfrom;
    +
    260 char *newto;
    +
    261 int err = subdir_addpath(d, from, &newfrom);
    +
    262 if (!err) {
    +
    263 err = subdir_addpath(d, to, &newto);
    +
    264 if (!err) {
    +
    265 err = fuse_fs_rename(d->next, newfrom, newto, flags);
    +
    266 free(newto);
    +
    267 }
    +
    268 free(newfrom);
    +
    269 }
    +
    270 return err;
    +
    271}
    +
    272
    +
    273static int subdir_link(const char *from, const char *to)
    +
    274{
    +
    275 struct subdir *d = subdir_get();
    +
    276 char *newfrom;
    +
    277 char *newto;
    +
    278 int err = subdir_addpath(d, from, &newfrom);
    +
    279 if (!err) {
    +
    280 err = subdir_addpath(d, to, &newto);
    +
    281 if (!err) {
    +
    282 err = fuse_fs_link(d->next, newfrom, newto);
    +
    283 free(newto);
    +
    284 }
    +
    285 free(newfrom);
    +
    286 }
    +
    287 return err;
    +
    288}
    +
    289
    +
    290static int subdir_chmod(const char *path, mode_t mode,
    +
    291 struct fuse_file_info *fi)
    +
    292{
    +
    293 struct subdir *d = subdir_get();
    +
    294 char *newpath;
    +
    295 int err = subdir_addpath(d, path, &newpath);
    +
    296 if (!err) {
    +
    297 err = fuse_fs_chmod(d->next, newpath, mode, fi);
    +
    298 free(newpath);
    +
    299 }
    +
    300 return err;
    +
    301}
    +
    302
    +
    303static int subdir_chown(const char *path, uid_t uid, gid_t gid,
    +
    304 struct fuse_file_info *fi)
    +
    305{
    +
    306 struct subdir *d = subdir_get();
    +
    307 char *newpath;
    +
    308 int err = subdir_addpath(d, path, &newpath);
    +
    309 if (!err) {
    +
    310 err = fuse_fs_chown(d->next, newpath, uid, gid, fi);
    +
    311 free(newpath);
    +
    312 }
    +
    313 return err;
    +
    314}
    +
    315
    +
    316static int subdir_truncate(const char *path, off_t size,
    +
    317 struct fuse_file_info *fi)
    +
    318{
    +
    319 struct subdir *d = subdir_get();
    +
    320 char *newpath;
    +
    321 int err = subdir_addpath(d, path, &newpath);
    +
    322 if (!err) {
    +
    323 err = fuse_fs_truncate(d->next, newpath, size, fi);
    +
    324 free(newpath);
    +
    325 }
    +
    326 return err;
    +
    327}
    +
    328
    +
    329static int subdir_utimens(const char *path, const struct timespec ts[2],
    +
    330 struct fuse_file_info *fi)
    +
    331{
    +
    332 struct subdir *d = subdir_get();
    +
    333 char *newpath;
    +
    334 int err = subdir_addpath(d, path, &newpath);
    +
    335 if (!err) {
    +
    336 err = fuse_fs_utimens(d->next, newpath, ts, fi);
    +
    337 free(newpath);
    +
    338 }
    +
    339 return err;
    +
    340}
    +
    341
    +
    342static int subdir_create(const char *path, mode_t mode,
    +
    343 struct fuse_file_info *fi)
    +
    344{
    +
    345 struct subdir *d = subdir_get();
    +
    346 char *newpath;
    +
    347 int err = subdir_addpath(d, path, &newpath);
    +
    348 if (!err) {
    +
    349 err = fuse_fs_create(d->next, newpath, mode, fi);
    +
    350 free(newpath);
    +
    351 }
    +
    352 return err;
    +
    353}
    +
    354
    +
    355static int subdir_open(const char *path, struct fuse_file_info *fi)
    +
    356{
    +
    357 struct subdir *d = subdir_get();
    +
    358 char *newpath;
    +
    359 int err = subdir_addpath(d, path, &newpath);
    +
    360 if (!err) {
    +
    361 err = fuse_fs_open(d->next, newpath, fi);
    +
    362 free(newpath);
    +
    363 }
    +
    364 return err;
    +
    365}
    +
    366
    +
    367static int subdir_read_buf(const char *path, struct fuse_bufvec **bufp,
    +
    368 size_t size, off_t offset, struct fuse_file_info *fi)
    +
    369{
    +
    370 struct subdir *d = subdir_get();
    +
    371 char *newpath;
    +
    372 int err = subdir_addpath(d, path, &newpath);
    +
    373 if (!err) {
    +
    374 err = fuse_fs_read_buf(d->next, newpath, bufp, size, offset, fi);
    +
    375 free(newpath);
    +
    376 }
    +
    377 return err;
    +
    378}
    +
    379
    +
    380static int subdir_write_buf(const char *path, struct fuse_bufvec *buf,
    +
    381 off_t offset, struct fuse_file_info *fi)
    +
    382{
    +
    383 struct subdir *d = subdir_get();
    +
    384 char *newpath;
    +
    385 int err = subdir_addpath(d, path, &newpath);
    +
    386 if (!err) {
    +
    387 err = fuse_fs_write_buf(d->next, newpath, buf, offset, fi);
    +
    388 free(newpath);
    +
    389 }
    +
    390 return err;
    +
    391}
    +
    392
    +
    393static int subdir_statfs(const char *path, struct statvfs *stbuf)
    +
    394{
    +
    395 struct subdir *d = subdir_get();
    +
    396 char *newpath;
    +
    397 int err = subdir_addpath(d, path, &newpath);
    +
    398 if (!err) {
    +
    399 err = fuse_fs_statfs(d->next, newpath, stbuf);
    +
    400 free(newpath);
    +
    401 }
    +
    402 return err;
    +
    403}
    +
    404
    +
    405static int subdir_flush(const char *path, struct fuse_file_info *fi)
    +
    406{
    +
    407 struct subdir *d = subdir_get();
    +
    408 char *newpath;
    +
    409 int err = subdir_addpath(d, path, &newpath);
    +
    410 if (!err) {
    +
    411 err = fuse_fs_flush(d->next, newpath, fi);
    +
    412 free(newpath);
    +
    413 }
    +
    414 return err;
    +
    415}
    +
    416
    +
    417static int subdir_release(const char *path, struct fuse_file_info *fi)
    +
    418{
    +
    419 struct subdir *d = subdir_get();
    +
    420 char *newpath;
    +
    421 int err = subdir_addpath(d, path, &newpath);
    +
    422 if (!err) {
    +
    423 err = fuse_fs_release(d->next, newpath, fi);
    +
    424 free(newpath);
    +
    425 }
    +
    426 return err;
    +
    427}
    +
    428
    +
    429static int subdir_fsync(const char *path, int isdatasync,
    +
    430 struct fuse_file_info *fi)
    +
    431{
    +
    432 struct subdir *d = subdir_get();
    +
    433 char *newpath;
    +
    434 int err = subdir_addpath(d, path, &newpath);
    +
    435 if (!err) {
    +
    436 err = fuse_fs_fsync(d->next, newpath, isdatasync, fi);
    +
    437 free(newpath);
    +
    438 }
    +
    439 return err;
    +
    440}
    +
    441
    +
    442static int subdir_fsyncdir(const char *path, int isdatasync,
    +
    443 struct fuse_file_info *fi)
    +
    444{
    +
    445 struct subdir *d = subdir_get();
    +
    446 char *newpath;
    +
    447 int err = subdir_addpath(d, path, &newpath);
    +
    448 if (!err) {
    +
    449 err = fuse_fs_fsyncdir(d->next, newpath, isdatasync, fi);
    +
    450 free(newpath);
    +
    451 }
    +
    452 return err;
    +
    453}
    +
    454
    +
    455static int subdir_setxattr(const char *path, const char *name,
    +
    456 const char *value, size_t size, int flags)
    +
    457{
    +
    458 struct subdir *d = subdir_get();
    +
    459 char *newpath;
    +
    460 int err = subdir_addpath(d, path, &newpath);
    +
    461 if (!err) {
    +
    462 err = fuse_fs_setxattr(d->next, newpath, name, value, size,
    +
    463 flags);
    +
    464 free(newpath);
    +
    465 }
    +
    466 return err;
    +
    467}
    +
    468
    +
    469static int subdir_getxattr(const char *path, const char *name, char *value,
    +
    470 size_t size)
    +
    471{
    +
    472 struct subdir *d = subdir_get();
    +
    473 char *newpath;
    +
    474 int err = subdir_addpath(d, path, &newpath);
    +
    475 if (!err) {
    +
    476 err = fuse_fs_getxattr(d->next, newpath, name, value, size);
    +
    477 free(newpath);
    +
    478 }
    +
    479 return err;
    +
    480}
    +
    481
    +
    482static int subdir_listxattr(const char *path, char *list, size_t size)
    +
    483{
    +
    484 struct subdir *d = subdir_get();
    +
    485 char *newpath;
    +
    486 int err = subdir_addpath(d, path, &newpath);
    +
    487 if (!err) {
    +
    488 err = fuse_fs_listxattr(d->next, newpath, list, size);
    +
    489 free(newpath);
    +
    490 }
    +
    491 return err;
    +
    492}
    +
    493
    +
    494static int subdir_removexattr(const char *path, const char *name)
    +
    495{
    +
    496 struct subdir *d = subdir_get();
    +
    497 char *newpath;
    +
    498 int err = subdir_addpath(d, path, &newpath);
    +
    499 if (!err) {
    +
    500 err = fuse_fs_removexattr(d->next, newpath, name);
    +
    501 free(newpath);
    +
    502 }
    +
    503 return err;
    +
    504}
    +
    505
    +
    506static int subdir_lock(const char *path, struct fuse_file_info *fi, int cmd,
    +
    507 struct flock *lock)
    +
    508{
    +
    509 struct subdir *d = subdir_get();
    +
    510 char *newpath;
    +
    511 int err = subdir_addpath(d, path, &newpath);
    +
    512 if (!err) {
    +
    513 err = fuse_fs_lock(d->next, newpath, fi, cmd, lock);
    +
    514 free(newpath);
    +
    515 }
    +
    516 return err;
    +
    517}
    +
    518
    +
    519static int subdir_flock(const char *path, struct fuse_file_info *fi, int op)
    +
    520{
    +
    521 struct subdir *d = subdir_get();
    +
    522 char *newpath;
    +
    523 int err = subdir_addpath(d, path, &newpath);
    +
    524 if (!err) {
    +
    525 err = fuse_fs_flock(d->next, newpath, fi, op);
    +
    526 free(newpath);
    +
    527 }
    +
    528 return err;
    +
    529}
    +
    530
    +
    531static int subdir_bmap(const char *path, size_t blocksize, uint64_t *idx)
    +
    532{
    +
    533 struct subdir *d = subdir_get();
    +
    534 char *newpath;
    +
    535 int err = subdir_addpath(d, path, &newpath);
    +
    536 if (!err) {
    +
    537 err = fuse_fs_bmap(d->next, newpath, blocksize, idx);
    +
    538 free(newpath);
    +
    539 }
    +
    540 return err;
    +
    541}
    +
    542
    +
    543static off_t subdir_lseek(const char *path, off_t off, int whence,
    +
    544 struct fuse_file_info *fi)
    +
    545{
    +
    546 struct subdir *ic = subdir_get();
    +
    547 char *newpath;
    +
    548 int res = subdir_addpath(ic, path, &newpath);
    +
    549 if (!res) {
    +
    550 res = fuse_fs_lseek(ic->next, newpath, off, whence, fi);
    +
    551 free(newpath);
    +
    552 }
    +
    553 return res;
    +
    554}
    +
    555
    +
    556#ifdef HAVE_STATX
    +
    557static int subdir_statx(const char *path, int flags, int mask, struct statx *stxbuf,
    +
    558 struct fuse_file_info *fi)
    +
    559{
    +
    560 struct subdir *ic = subdir_get();
    +
    561 char *newpath;
    +
    562 int res = subdir_addpath(ic, path, &newpath);
    +
    563
    +
    564 if (!res) {
    +
    565 res = fuse_fs_statx(ic->next, newpath, flags, mask, stxbuf, fi);
    +
    566 free(newpath);
    +
    567 }
    +
    568 return res;
    +
    569}
    +
    570#endif
    +
    571
    +
    572static void *subdir_init(struct fuse_conn_info *conn,
    +
    573 struct fuse_config *cfg)
    +
    574{
    +
    575 struct subdir *d = subdir_get();
    +
    576 fuse_fs_init(d->next, conn, cfg);
    +
    577 /* Don't touch cfg->nullpath_ok, we can work with
    +
    578 either */
    +
    579 return d;
    +
    580}
    +
    581
    +
    582static void subdir_destroy(void *data)
    +
    583{
    +
    584 struct subdir *d = data;
    +
    585 fuse_fs_destroy(d->next);
    +
    586 free(d->base);
    +
    587 free(d);
    +
    588}
    +
    589
    +
    590static const struct fuse_operations subdir_oper = {
    +
    591 .destroy = subdir_destroy,
    +
    592 .init = subdir_init,
    +
    593 .getattr = subdir_getattr,
    +
    594 .access = subdir_access,
    +
    595 .readlink = subdir_readlink,
    +
    596 .opendir = subdir_opendir,
    +
    597 .readdir = subdir_readdir,
    +
    598 .releasedir = subdir_releasedir,
    +
    599 .mknod = subdir_mknod,
    +
    600 .mkdir = subdir_mkdir,
    +
    601 .symlink = subdir_symlink,
    +
    602 .unlink = subdir_unlink,
    +
    603 .rmdir = subdir_rmdir,
    +
    604 .rename = subdir_rename,
    +
    605 .link = subdir_link,
    +
    606 .chmod = subdir_chmod,
    +
    607 .chown = subdir_chown,
    +
    608 .truncate = subdir_truncate,
    +
    609 .utimens = subdir_utimens,
    +
    610 .create = subdir_create,
    +
    611 .open = subdir_open,
    +
    612 .read_buf = subdir_read_buf,
    +
    613 .write_buf = subdir_write_buf,
    +
    614 .statfs = subdir_statfs,
    +
    615 .flush = subdir_flush,
    +
    616 .release = subdir_release,
    +
    617 .fsync = subdir_fsync,
    +
    618 .fsyncdir = subdir_fsyncdir,
    +
    619 .setxattr = subdir_setxattr,
    +
    620 .getxattr = subdir_getxattr,
    +
    621 .listxattr = subdir_listxattr,
    +
    622 .removexattr = subdir_removexattr,
    +
    623 .lock = subdir_lock,
    +
    624 .flock = subdir_flock,
    +
    625 .bmap = subdir_bmap,
    +
    626 .lseek = subdir_lseek,
    +
    627#ifdef HAVE_STATX
    +
    628 .statx = subdir_statx,
    +
    629#endif
    +
    630};
    +
    631
    +
    632static const struct fuse_opt subdir_opts[] = {
    +
    633 FUSE_OPT_KEY("-h", 0),
    +
    634 FUSE_OPT_KEY("--help", 0),
    +
    635 { "subdir=%s", offsetof(struct subdir, base), 0 },
    +
    636 { "rellinks", offsetof(struct subdir, rellinks), 1 },
    +
    637 { "norellinks", offsetof(struct subdir, rellinks), 0 },
    + +
    639};
    +
    640
    +
    641static void subdir_help(void)
    +
    642{
    +
    643 printf(
    +
    644" -o subdir=DIR prepend this directory to all paths (mandatory)\n"
    +
    645" -o [no]rellinks transform absolute symlinks to relative\n");
    +
    646}
    +
    647
    +
    648static int subdir_opt_proc(void *data, const char *arg, int key,
    +
    649 struct fuse_args *outargs)
    +
    650{
    +
    651 (void) data; (void) arg; (void) outargs;
    +
    652
    +
    653 if (!key) {
    +
    654 subdir_help();
    +
    655 return -1;
    +
    656 }
    +
    657
    +
    658 return 1;
    +
    659}
    +
    660
    +
    661static struct fuse_fs *subdir_new(struct fuse_args *args,
    +
    662 struct fuse_fs *next[])
    +
    663{
    +
    664 struct fuse_fs *fs;
    +
    665 struct subdir *d;
    +
    666
    +
    667 d = calloc(1, sizeof(struct subdir));
    +
    668 if (d == NULL) {
    +
    669 fuse_log(FUSE_LOG_ERR, "fuse-subdir: memory allocation failed\n");
    +
    670 return NULL;
    +
    671 }
    +
    672
    +
    673 if (fuse_opt_parse(args, d, subdir_opts, subdir_opt_proc) == -1)
    +
    674 goto out_free;
    +
    675
    +
    676 if (!next[0] || next[1]) {
    +
    677 fuse_log(FUSE_LOG_ERR, "fuse-subdir: exactly one next filesystem required\n");
    +
    678 goto out_free;
    +
    679 }
    +
    680
    +
    681 if (!d->base) {
    +
    682 fuse_log(FUSE_LOG_ERR, "fuse-subdir: missing 'subdir' option\n");
    +
    683 goto out_free;
    +
    684 }
    +
    685
    +
    686 if (d->base[0] && d->base[strlen(d->base)-1] != '/') {
    +
    687 char *tmp = realloc(d->base, strlen(d->base) + 2);
    +
    688 if (!tmp) {
    +
    689 fuse_log(FUSE_LOG_ERR, "fuse-subdir: memory allocation failed\n");
    +
    690 goto out_free;
    +
    691 }
    +
    692 d->base = tmp;
    +
    693 strcat(d->base, "/");
    +
    694 }
    +
    695 d->baselen = strlen(d->base);
    +
    696 d->next = next[0];
    +
    697 fs = fuse_fs_new(&subdir_oper, sizeof(subdir_oper), d);
    +
    698 if (!fs)
    +
    699 goto out_free;
    +
    700 return fs;
    +
    701
    +
    702out_free:
    +
    703 free(d->base);
    +
    704 free(d);
    +
    705 return NULL;
    +
    706}
    +
    707
    +
    708FUSE_REGISTER_MODULE(subdir, subdir_new);
    + +
    struct fuse_context * fuse_get_context(void)
    Definition fuse.c:4711
    +
    int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
    Definition fuse.h:93
    +
    struct fuse_fs * fuse_fs_new(const struct fuse_operations *op, size_t op_size, void *private_data)
    Definition fuse.c:4923
    +
    fuse_readdir_flags
    Definition fuse.h:45
    +
    #define FUSE_REGISTER_MODULE(name_, factory_)
    Definition fuse.h:1415
    +
    void fuse_log(enum fuse_log_level level, const char *fmt,...) __attribute__((format(printf
    +
    #define FUSE_OPT_KEY(templ, key)
    Definition fuse_opt.h:98
    +
    int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
    Definition fuse_opt.c:398
    +
    #define FUSE_OPT_END
    Definition fuse_opt.h:104
    + + + + +
    void * private_data
    Definition fuse.h:891
    + + + +
    + + +
    + + diff --git a/doc/html/tabs.css b/doc/html/tabs.css new file mode 100644 index 0000000..b6be604 --- /dev/null +++ b/doc/html/tabs.css @@ -0,0 +1 @@ +.sm{position:relative;z-index:9999}.sm,.sm ul,.sm li{display:block;list-style:none;margin:0;padding:0;line-height:normal;direction:ltr;text-align:left;-webkit-tap-highlight-color:rgba(0,0,0,0)}.sm-rtl,.sm-rtl ul,.sm-rtl li{direction:rtl;text-align:right}.sm>li>h1,.sm>li>h2,.sm>li>h3,.sm>li>h4,.sm>li>h5,.sm>li>h6{margin:0;padding:0}.sm ul{display:none}.sm li,.sm a{position:relative}.sm a{display:block}.sm a.disabled{cursor:not-allowed}.sm:after{content:"\00a0";display:block;height:0;font:0px/0 serif;clear:both;visibility:hidden;overflow:hidden}.sm,.sm *,.sm *:before,.sm *:after{-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box}.main-menu-btn{position:relative;display:inline-block;width:36px;height:36px;text-indent:36px;margin-left:8px;white-space:nowrap;overflow:hidden;cursor:pointer;-webkit-tap-highlight-color:rgba(0,0,0,0)}.main-menu-btn-icon,.main-menu-btn-icon:before,.main-menu-btn-icon:after{position:absolute;top:50%;left:2px;height:2px;width:24px;background:var(--nav-menu-button-color);-webkit-transition:all 0.25s;transition:all 0.25s}.main-menu-btn-icon:before{content:'';top:-7px;left:0}.main-menu-btn-icon:after{content:'';top:7px;left:0}#main-menu-state:checked~.main-menu-btn .main-menu-btn-icon{height:0}#main-menu-state:checked~.main-menu-btn .main-menu-btn-icon:before{top:0;-webkit-transform:rotate(-45deg);transform:rotate(-45deg)}#main-menu-state:checked~.main-menu-btn .main-menu-btn-icon:after{top:0;-webkit-transform:rotate(45deg);transform:rotate(45deg)}#main-menu-state{position:absolute;width:1px;height:1px;margin:-1px;border:0;padding:0;overflow:hidden;clip:rect(1px, 1px, 1px, 1px)}#main-menu-state:not(:checked)~#main-menu{display:none}#main-menu-state:checked~#main-menu{display:block}@media (min-width: 768px){.main-menu-btn{position:absolute;top:-99999px}#main-menu-state:not(:checked)~#main-menu{display:block}}.sm-dox{background-color:var(--nav-menu-background-color);sm-dox__round-corners-last-item:5px}.sm-dox a,.sm-dox a:focus,.sm-dox a:hover,.sm-dox a:active{padding:0px 12px;padding-right:43px;font-family:var(--font-family-nav);font-size:13px;line-height:36px;text-decoration:none;color:var(--nav-text-normal-color);outline:none}.sm-dox a:hover{background-color:var(--nav-menu-active-bg);border-radius:5px}.sm-dox a.current{color:#D23600}.sm-dox a.disabled{color:#bbb}.sm-dox a span.sub-arrow{position:absolute;top:50%;margin-top:-14px;left:auto;right:3px;width:28px;height:28px;overflow:hidden;font:bold 12px/28px monospace !important;text-align:center;text-shadow:none;border-radius:5px}.sm-dox a span.sub-arrow:before{display:block;content:'+'}.sm-dox a.highlighted span.sub-arrow:before{display:block;content:'-'}.sm-dox>li:first-child>a,.sm-dox>li:first-child>:not(ul) a{border-radius:5px 5px 0 0}.sm-dox ul{background:var(--nav-menu-background-color);sm-dox__sub-items-indentation:8px}.sm-dox ul a,.sm-dox ul a:focus,.sm-dox ul a:hover,.sm-dox ul a:active{font-size:12px;border-left:8px solid transparent;line-height:36px;text-shadow:none;background-color:var(--nav-menu-background-color);background-image:none}.sm-dox ul a:hover{background-color:var(--nav-menu-active-bg);border-radius:5px}@media (min-width: 768px){.sm-dox ul{position:absolute;width:12em}.sm-dox li{float:left}.sm-dox.sm-rtl li{float:right}.sm-dox ul li,.sm-dox.sm-rtl ul li,.sm-dox.sm-vertical li{float:none}.sm-dox a{white-space:nowrap}.sm-dox ul a,.sm-dox.sm-vertical a{white-space:normal}.sm-dox .sm-nowrap>li>a,.sm-dox .sm-nowrap>li>:not(ul) a{white-space:nowrap}.sm-dox{padding:0 10px;background-color:var(--nav-menu-background-color);line-height:36px}.sm-dox a span.sub-arrow{top:15px;right:10px;box-sizing:content-box;padding:0;margin:0;display:inline-block;width:5px;height:5px;background-color:var(--nav-menu-background-color);border-right:2px solid var(--nav-arrow-color);border-bottom:2px solid var(--nav-arrow-color);transform:rotate(45deg);border-radius:0}.sm-dox a,.sm-dox a:focus,.sm-dox a:active,.sm-dox a:hover,.sm-dox a.highlighted{padding:0px 6px}.sm-dox a:hover{background-color:var(--nav-menu-active-bg);border-radius:5px !important}.sm-dox a:hover span.sub-arrow{background-color:var(--nav-menu-active-bg);border-right:2px solid var(--nav-arrow-selected-color);border-bottom:2px solid var(--nav-arrow-selected-color)}.sm-dox a.has-submenu{padding-right:24px}.sm-dox li{border-top:0;padding:3px}.sm-dox>li>ul:before,.sm-dox>li>ul:after{content:'';position:absolute;top:-18px;left:30px;width:0;height:0;overflow:hidden;border-width:9px;border-style:dashed dashed solid dashed;border-color:transparent transparent #bbb transparent}.sm-dox>li>ul:after{top:-16px;left:31px;border-width:8px;border-color:transparent transparent var(--nav-menu-background-color) transparent}.sm-dox ul{border:1px solid #bbb;padding:5px 0;background:var(--nav-menu-background-color);border-radius:5px !important;box-shadow:0 5px 9px rgba(0,0,0,0.2)}.sm-dox ul a span.sub-arrow{transform:rotate(-45deg)}.sm-dox ul a,.sm-dox ul a:hover,.sm-dox ul a:focus,.sm-dox ul a:active,.sm-dox ul a.highlighted{color:var(--nav-menu-foreground-color);background-image:none;border:0 !important}.sm-dox ul a:hover{background-color:var(--nav-menu-active-bg);border-radius:5px}.sm-dox span.scroll-up,.sm-dox span.scroll-down{position:absolute;display:none;visibility:hidden;overflow:hidden;background:var(--nav-menu-background-color);height:36px}.sm-dox span.scroll-up:hover,.sm-dox span.scroll-down:hover{background:#eee}.sm-dox span.scroll-up:hover span.scroll-up-arrow,.sm-dox span.scroll-up:hover span.scroll-down-arrow{border-color:transparent transparent #D23600 transparent}.sm-dox span.scroll-down:hover span.scroll-down-arrow{border-color:#D23600 transparent transparent transparent}.sm-dox span.scroll-up-arrow,.sm-dox span.scroll-down-arrow{position:absolute;top:0;left:50%;margin-left:-6px;width:0;height:0;overflow:hidden;border-width:6px;border-style:dashed dashed solid dashed;border-color:transparent transparent var(--nav-menu-foreground-color) transparent}.sm-dox span.scroll-down-arrow{top:8px;border-style:solid dashed dashed dashed;border-color:var(--nav-menu-foreground-color) transparent transparent transparent}.sm-dox.sm-rtl a.has-submenu{padding-right:6px;padding-left:24px}.sm-dox.sm-rtl a span.sub-arrow{right:auto;left:6px}.sm-dox.sm-rtl.sm-vertical a.has-submenu{padding:10px 20px}.sm-dox.sm-rtl.sm-vertical a span.sub-arrow{right:auto;left:8px;border-style:dashed solid dashed dashed;border-color:transparent #555 transparent transparent}.sm-dox.sm-rtl>li>ul:before{left:auto;right:30px}.sm-dox.sm-rtl>li>ul:after{left:auto;right:31px}.sm-dox.sm-rtl ul a.has-submenu{padding:10px 20px !important}.sm-dox.sm-rtl ul a span.sub-arrow{right:auto;left:8px;border-style:dashed solid dashed dashed;border-color:transparent #555 transparent transparent}.sm-dox.sm-vertical{padding:10px 0;border-radius:5px}.sm-dox.sm-vertical a{padding:10px 20px}.sm-dox.sm-vertical a:hover,.sm-dox.sm-vertical a:focus,.sm-dox.sm-vertical a:active,.sm-dox.sm-vertical a.highlighted{background:#fff}.sm-dox.sm-vertical a span.sub-arrow{right:8px;top:50%;margin-top:-5px;border-width:5px;border-style:dashed dashed dashed solid;border-color:transparent transparent transparent #555}.sm-dox.sm-vertical>li>ul:before,.sm-dox.sm-vertical>li>ul:after{display:none}.sm-dox.sm-vertical ul a{padding:10px 20px}.sm-dox.sm-vertical ul a:hover,.sm-dox.sm-vertical ul a:focus,.sm-dox.sm-vertical ul a:active,.sm-dox.sm-vertical ul a.highlighted{background:#eee}.sm-dox.sm-vertical ul a.disabled{background:var(--nav-menu-background-color)}} diff --git a/doc/html/test_2hello_8c.html b/doc/html/test_2hello_8c.html new file mode 100644 index 0000000..481f7b6 --- /dev/null +++ b/doc/html/test_2hello_8c.html @@ -0,0 +1,271 @@ + + + + + + + +libfuse: test/hello.c File Reference + + + + + + + +
    +
    + + + + + + +
    +
    libfuse +
    +
    +
    + + + + + + + + +
    +
    +
    +
    hello.c File Reference
    +
    +
    +
    #include <fuse.h>
    +#include <stdio.h>
    +#include <string.h>
    +#include <errno.h>
    +#include <fcntl.h>
    +#include <stddef.h>
    +#include <assert.h>
    +
    +

    Go to the source code of this file.

    +

    Detailed Description

    +

    minimal example filesystem using high-level API

    +

    Compile with:

    gcc -Wall hello.c `pkg-config fuse3 --cflags --libs` -o hello
    +

    +Source code

    +
    /*
    +
    FUSE: Filesystem in Userspace
    +
    Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
    +
    +
    This program can be distributed under the terms of the GNU GPLv2.
    +
    See the file GPL2.txt.
    +
    */
    +
    +
    +
    +
    #define FUSE_USE_VERSION 31
    +
    +
    #include <fuse.h>
    +
    #include <stdio.h>
    +
    #include <string.h>
    +
    #include <errno.h>
    +
    #include <fcntl.h>
    +
    #include <stddef.h>
    +
    #include <assert.h>
    +
    +
    /*
    +
    * Command line options
    +
    *
    +
    * We can't set default values for the char* fields here because
    +
    * fuse_opt_parse would attempt to free() them when the user specifies
    +
    * different values on the command line.
    +
    */
    +
    static struct options {
    +
    const char *filename;
    +
    const char *contents;
    +
    int show_help;
    +
    } options;
    +
    +
    #define OPTION(t, p) \
    +
    { t, offsetof(struct options, p), 1 }
    +
    static const struct fuse_opt option_spec[] = {
    +
    OPTION("--name=%s", filename),
    +
    OPTION("--contents=%s", contents),
    +
    OPTION("-h", show_help),
    +
    OPTION("--help", show_help),
    + +
    };
    +
    +
    static void *hello_init(struct fuse_conn_info *conn,
    +
    struct fuse_config *cfg)
    +
    {
    +
    (void) conn;
    +
    cfg->kernel_cache = 1;
    +
    +
    /* Test setting flags the old way */
    + + +
    +
    return NULL;
    +
    }
    +
    +
    static int hello_getattr(const char *path, struct stat *stbuf,
    +
    struct fuse_file_info *fi)
    +
    {
    +
    (void) fi;
    +
    int res = 0;
    +
    +
    memset(stbuf, 0, sizeof(struct stat));
    +
    if (strcmp(path, "/") == 0) {
    +
    stbuf->st_mode = S_IFDIR | 0755;
    +
    stbuf->st_nlink = 2;
    +
    } else if (strcmp(path+1, options.filename) == 0) {
    +
    stbuf->st_mode = S_IFREG | 0444;
    +
    stbuf->st_nlink = 1;
    +
    stbuf->st_size = strlen(options.contents);
    +
    } else
    +
    res = -ENOENT;
    +
    +
    return res;
    +
    }
    +
    +
    static int hello_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
    +
    off_t offset, struct fuse_file_info *fi,
    +
    enum fuse_readdir_flags flags)
    +
    {
    +
    (void) offset;
    +
    (void) fi;
    +
    (void) flags;
    +
    +
    if (strcmp(path, "/") != 0)
    +
    return -ENOENT;
    +
    +
    filler(buf, ".", NULL, 0, FUSE_FILL_DIR_DEFAULTS);
    +
    filler(buf, "..", NULL, 0, FUSE_FILL_DIR_DEFAULTS);
    +
    filler(buf, options.filename, NULL, 0, FUSE_FILL_DIR_DEFAULTS);
    +
    +
    return 0;
    +
    }
    +
    +
    static int hello_open(const char *path, struct fuse_file_info *fi)
    +
    {
    +
    if (strcmp(path+1, options.filename) != 0)
    +
    return -ENOENT;
    +
    +
    if ((fi->flags & O_ACCMODE) != O_RDONLY)
    +
    return -EACCES;
    +
    +
    return 0;
    +
    }
    +
    +
    static int hello_read(const char *path, char *buf, size_t size, off_t offset,
    +
    struct fuse_file_info *fi)
    +
    {
    +
    size_t len;
    +
    (void) fi;
    +
    if(strcmp(path+1, options.filename) != 0)
    +
    return -ENOENT;
    +
    +
    len = strlen(options.contents);
    +
    if (offset < len) {
    +
    if (offset + size > len)
    +
    size = len - offset;
    +
    memcpy(buf, options.contents + offset, size);
    +
    } else
    +
    size = 0;
    +
    +
    return size;
    +
    }
    +
    +
    static const struct fuse_operations hello_oper = {
    +
    .init = hello_init,
    +
    .getattr = hello_getattr,
    +
    .readdir = hello_readdir,
    +
    .open = hello_open,
    +
    .read = hello_read,
    +
    };
    +
    +
    static void show_help(const char *progname)
    +
    {
    +
    printf("usage: %s [options] <mountpoint>\n\n", progname);
    +
    printf("File-system specific options:\n"
    +
    " --name=<s> Name of the \"hello\" file\n"
    +
    " (default: \"hello\")\n"
    +
    " --contents=<s> Contents \"hello\" file\n"
    +
    " (default \"Hello, World!\\n\")\n"
    +
    "\n");
    +
    }
    +
    +
    int main(int argc, char *argv[])
    +
    {
    +
    int ret;
    +
    struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
    +
    +
    /* Set defaults -- we have to use strdup so that
    +
    fuse_opt_parse can free the defaults if other
    +
    values are specified */
    +
    options.filename = strdup("hello");
    +
    options.contents = strdup("Hello World!\n");
    +
    +
    /* Parse options */
    +
    if (fuse_opt_parse(&args, &options, option_spec, NULL) == -1)
    +
    return 1;
    +
    +
    /* When --help is specified, first print our own file-system
    +
    specific help text, then signal fuse_main to show
    +
    additional help (by adding `--help` to the options again)
    +
    without usage: line (by setting argv[0] to the empty
    +
    string) */
    +
    if (options.show_help) {
    +
    show_help(argv[0]);
    +
    assert(fuse_opt_add_arg(&args, "--help") == 0);
    +
    args.argv[0][0] = '\0';
    +
    }
    +
    +
    ret = fuse_main(args.argc, args.argv, &hello_oper, NULL);
    + +
    return ret;
    +
    }
    + +
    int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
    Definition fuse.h:93
    +
    @ FUSE_FILL_DIR_DEFAULTS
    Definition fuse.h:74
    +
    fuse_readdir_flags
    Definition fuse.h:45
    +
    void fuse_unset_feature_flag(struct fuse_conn_info *conn, uint64_t flag)
    +
    bool fuse_set_feature_flag(struct fuse_conn_info *conn, uint64_t flag)
    +
    #define FUSE_CAP_ASYNC_READ
    +
    int fuse_opt_add_arg(struct fuse_args *args, const char *arg)
    Definition fuse_opt.c:55
    +
    void fuse_opt_free_args(struct fuse_args *args)
    Definition fuse_opt.c:34
    +
    int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
    Definition fuse_opt.c:398
    +
    #define FUSE_ARGS_INIT(argc, argv)
    Definition fuse_opt.h:123
    +
    #define FUSE_OPT_END
    Definition fuse_opt.h:104
    + + +
    char ** argv
    Definition fuse_opt.h:114
    + +
    int32_t kernel_cache
    Definition fuse.h:251
    + + + + + +
    unsigned long offset
    Definition fuse_opt.h:85
    +
    +

    Definition in file hello.c.

    +
    + + +
    + + diff --git a/doc/html/test_2hello_8c_source.html b/doc/html/test_2hello_8c_source.html new file mode 100644 index 0000000..ce5edd0 --- /dev/null +++ b/doc/html/test_2hello_8c_source.html @@ -0,0 +1,250 @@ + + + + + + + +libfuse: test/hello.c Source File + + + + + + + +
    +
    + + + + + + +
    +
    libfuse +
    +
    +
    + + + + + + + + +
    +
    +
    +
    hello.c
    +
    +
    +Go to the documentation of this file.
    1/*
    +
    2 * FUSE: Filesystem in Userspace
    +
    3 * Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
    +
    4 *
    +
    5 * This program can be distributed under the terms of the GNU GPLv2.
    +
    6 * See the file GPL2.txt.
    +
    7 */
    +
    8
    +
    20
    +
    21#define FUSE_USE_VERSION 31
    +
    22
    +
    23#include <fuse.h>
    +
    24#include <stdio.h>
    +
    25#include <string.h>
    +
    26#include <errno.h>
    +
    27#include <fcntl.h>
    +
    28#include <stddef.h>
    +
    29#include <assert.h>
    +
    30
    +
    31/*
    +
    32 * Command line options
    +
    33 *
    +
    34 * We can't set default values for the char* fields here because
    +
    35 * fuse_opt_parse would attempt to free() them when the user specifies
    +
    36 * different values on the command line.
    +
    37 */
    +
    38static struct options {
    +
    39 const char *filename;
    +
    40 const char *contents;
    +
    41 int show_help;
    +
    42} options;
    +
    43
    +
    44#define OPTION(t, p) { t, offsetof(struct options, p), 1 }
    +
    45static const struct fuse_opt option_spec[] = {
    +
    46 OPTION("--name=%s", filename), OPTION("--contents=%s", contents),
    +
    47 OPTION("-h", show_help), OPTION("--help", show_help), FUSE_OPT_END
    +
    48};
    +
    49
    +
    50static void *hello_init(struct fuse_conn_info *conn, struct fuse_config *cfg)
    +
    51{
    +
    52 (void)conn;
    +
    53 cfg->kernel_cache = 1;
    +
    54
    +
    55 /* Test setting flags the old way */
    + + +
    58
    +
    59 return NULL;
    +
    60}
    +
    61
    +
    62static int hello_getattr(const char *path, struct stat *stbuf,
    +
    63 struct fuse_file_info *fi)
    +
    64{
    +
    65 (void)fi;
    +
    66 int res = 0;
    +
    67
    +
    68 memset(stbuf, 0, sizeof(struct stat));
    +
    69 if (strcmp(path, "/") == 0) {
    +
    70 stbuf->st_mode = S_IFDIR | 0755;
    +
    71 stbuf->st_nlink = 2;
    +
    72 } else if (strcmp(path + 1, options.filename) == 0) {
    +
    73 stbuf->st_mode = S_IFREG | 0444;
    +
    74 stbuf->st_nlink = 1;
    +
    75 stbuf->st_size = strlen(options.contents);
    +
    76 } else
    +
    77 res = -ENOENT;
    +
    78
    +
    79 return res;
    +
    80}
    +
    81
    +
    82static int hello_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
    +
    83 off_t offset, struct fuse_file_info *fi,
    +
    84 enum fuse_readdir_flags flags)
    +
    85{
    +
    86 (void)offset;
    +
    87 (void)fi;
    +
    88 (void)flags;
    +
    89
    +
    90 if (strcmp(path, "/") != 0)
    +
    91 return -ENOENT;
    +
    92
    +
    93 filler(buf, ".", NULL, 0, FUSE_FILL_DIR_DEFAULTS);
    +
    94 filler(buf, "..", NULL, 0, FUSE_FILL_DIR_DEFAULTS);
    +
    95 filler(buf, options.filename, NULL, 0, FUSE_FILL_DIR_DEFAULTS);
    +
    96
    +
    97 return 0;
    +
    98}
    +
    99
    +
    100static int hello_open(const char *path, struct fuse_file_info *fi)
    +
    101{
    +
    102 if (strcmp(path + 1, options.filename) != 0)
    +
    103 return -ENOENT;
    +
    104
    +
    105 if ((fi->flags & O_ACCMODE) != O_RDONLY)
    +
    106 return -EACCES;
    +
    107
    +
    108 return 0;
    +
    109}
    +
    110
    +
    111static int hello_read(const char *path, char *buf, size_t size, off_t offset,
    +
    112 struct fuse_file_info *fi)
    +
    113{
    +
    114 size_t len;
    +
    115 (void)fi;
    +
    116 if (strcmp(path + 1, options.filename) != 0)
    +
    117 return -ENOENT;
    +
    118
    +
    119 len = strlen(options.contents);
    +
    120 if (offset < len) {
    +
    121 if (offset + size > len)
    +
    122 size = len - offset;
    +
    123 memcpy(buf, options.contents + offset, size);
    +
    124 } else
    +
    125 size = 0;
    +
    126
    +
    127 return size;
    +
    128}
    +
    129
    +
    130static const struct fuse_operations hello_oper = {
    +
    131 .init = hello_init,
    +
    132 .getattr = hello_getattr,
    +
    133 .readdir = hello_readdir,
    +
    134 .open = hello_open,
    +
    135 .read = hello_read,
    +
    136};
    +
    137
    +
    138static void show_help(const char *progname)
    +
    139{
    +
    140 printf("usage: %s [options] <mountpoint>\n\n", progname);
    +
    141 printf("File-system specific options:\n"
    +
    142 " --name=<s> Name of the \"hello\" file\n"
    +
    143 " (default: \"hello\")\n"
    +
    144 " --contents=<s> Contents \"hello\" file\n"
    +
    145 " (default \"Hello, World!\\n\")\n"
    +
    146 "\n");
    +
    147}
    +
    148
    +
    149int main(int argc, char *argv[])
    +
    150{
    +
    151 int ret;
    +
    152 struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
    +
    153
    +
    154 /* Set defaults -- we have to use strdup so that
    +
    155 * fuse_opt_parse can free the defaults if other
    +
    156 * values are specified
    +
    157 */
    +
    158 options.filename = strdup("hello");
    +
    159 options.contents = strdup("Hello World!\n");
    +
    160
    +
    161 /* Parse options */
    +
    162 if (fuse_opt_parse(&args, &options, option_spec, NULL) == -1)
    +
    163 return 1;
    +
    164
    +
    165 /* When --help is specified, first print our own file-system
    +
    166 * specific help text, then signal fuse_main to show
    +
    167 * additional help (by adding `--help` to the options again)
    +
    168 * without usage: line (by setting argv[0] to the empty
    +
    169 * string)
    +
    170 */
    +
    171 if (options.show_help) {
    +
    172 show_help(argv[0]);
    +
    173 assert(fuse_opt_add_arg(&args, "--help") == 0);
    +
    174 args.argv[0][0] = '\0';
    +
    175 }
    +
    176
    +
    177 ret = fuse_main(args.argc, args.argv, &hello_oper, NULL);
    +
    178 fuse_opt_free_args(&args);
    +
    179 return ret;
    +
    180}
    + +
    int(* fuse_fill_dir_t)(void *buf, const char *name, const struct stat *stbuf, off_t off, enum fuse_fill_dir_flags flags)
    Definition fuse.h:93
    +
    @ FUSE_FILL_DIR_DEFAULTS
    Definition fuse.h:74
    +
    fuse_readdir_flags
    Definition fuse.h:45
    +
    #define FUSE_CAP_ASYNC_READ
    +
    int fuse_opt_add_arg(struct fuse_args *args, const char *arg)
    Definition fuse_opt.c:55
    +
    void fuse_opt_free_args(struct fuse_args *args)
    Definition fuse_opt.c:34
    +
    int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
    Definition fuse_opt.c:398
    +
    #define FUSE_ARGS_INIT(argc, argv)
    Definition fuse_opt.h:123
    +
    #define FUSE_OPT_END
    Definition fuse_opt.h:104
    + + +
    char ** argv
    Definition fuse_opt.h:114
    + +
    int32_t kernel_cache
    Definition fuse.h:251
    + + + + + + +
    unsigned long offset
    Definition fuse_opt.h:85
    +
    + + +
    + + diff --git a/doc/html/test__abi_8c_source.html b/doc/html/test__abi_8c_source.html new file mode 100644 index 0000000..3d4a500 --- /dev/null +++ b/doc/html/test__abi_8c_source.html @@ -0,0 +1,80 @@ + + + + + + + +libfuse: test/test_abi.c Source File + + + + + + + +
    +
    + + + + + + +
    +
    libfuse +
    +
    +
    + + + + + + + + +
    +
    +
    +
    test_abi.c
    +
    +
    +
    1#define FUSE_USE_VERSION 30
    +
    2
    +
    3#include "fuse.h"
    +
    4
    +
    5#include <stdio.h>
    +
    6#include <stdlib.h>
    +
    7
    +
    8int main(void)
    +
    9{
    +
    10 if (sizeof(struct fuse_file_info) != 64) {
    +
    11 fprintf(stderr, "struct fuse_file_info size mismatch\n");
    +
    12 exit(1);
    +
    13 }
    +
    14 if (sizeof(struct fuse_conn_info) != 128) {
    +
    15 fprintf(stderr, "struct fuse_conn_info size mismatch\n");
    +
    16 exit(1);
    +
    17 }
    +
    18}
    + + + +
    + + +
    + + diff --git a/doc/html/test__api__30_8c_source.html b/doc/html/test__api__30_8c_source.html new file mode 100644 index 0000000..71ad019 --- /dev/null +++ b/doc/html/test__api__30_8c_source.html @@ -0,0 +1,344 @@ + + + + + + + +libfuse: test/test_api_30.c Source File + + + + + + + +
    +
    + + + + + + +
    +
    libfuse +
    +
    +
    + + + + + + + + +
    +
    +
    +
    test_api_30.c
    +
    +
    +
    1/*
    +
    2 * FUSE: Filesystem in Userspace
    +
    3 *
    +
    4 * Minimal low level filesystem built against the FUSE_USE_VERSION 30 API,
    +
    5 * so that the old API keeps being compiled and exercised.
    +
    6 *
    +
    7 * This program can be distributed under the terms of the GNU GPLv2.
    +
    8 * See the file GPL2.txt.
    +
    9 */
    +
    10
    +
    11#define FUSE_USE_VERSION 30
    +
    12
    +
    13/* the high level header has to keep building at this version as well */
    +
    14#include <fuse.h>
    +
    15
    +
    16#include <fuse_config.h>
    +
    17#include <fuse_lowlevel.h>
    +
    18#include <assert.h>
    +
    19#include <dirent.h>
    +
    20#include <errno.h>
    +
    21#include <fcntl.h>
    +
    22#include <pthread.h>
    +
    23#include <signal.h>
    +
    24#include <stdio.h>
    +
    25#include <stdlib.h>
    +
    26#include <string.h>
    +
    27#include <unistd.h>
    +
    28
    +
    29#ifndef __linux__
    +
    30#include <limits.h>
    +
    31#else
    +
    32#include <linux/limits.h>
    +
    33#endif
    +
    34
    +
    35#define FILE_INO 2
    +
    36
    +
    37static const char file_name[] = "hello";
    +
    38static const char file_contents[] = "Hello World!\n";
    +
    39
    +
    40static int tfs_stat(fuse_ino_t ino, struct stat *stbuf)
    +
    41{
    +
    42 stbuf->st_ino = ino;
    +
    43 switch (ino) {
    +
    44 case FUSE_ROOT_ID:
    +
    45 stbuf->st_mode = S_IFDIR | 0755;
    +
    46 stbuf->st_nlink = 2;
    +
    47 break;
    +
    48
    +
    49 case FILE_INO:
    +
    50 stbuf->st_mode = S_IFREG | 0444;
    +
    51 stbuf->st_nlink = 1;
    +
    52 stbuf->st_size = strlen(file_contents);
    +
    53 break;
    +
    54
    +
    55 default:
    +
    56 return -1;
    +
    57 }
    +
    58 return 0;
    +
    59}
    +
    60
    +
    61static void tfs_lookup(fuse_req_t req, fuse_ino_t parent, const char *name)
    +
    62{
    +
    63 struct fuse_entry_param e;
    +
    64
    +
    65 if (parent != FUSE_ROOT_ID || strcmp(name, file_name) != 0) {
    +
    66 fuse_reply_err(req, ENOENT);
    +
    67 return;
    +
    68 }
    +
    69
    +
    70 memset(&e, 0, sizeof(e));
    +
    71 e.ino = FILE_INO;
    +
    72 e.attr_timeout = 1.0;
    +
    73 e.entry_timeout = 1.0;
    +
    74 tfs_stat(e.ino, &e.attr);
    +
    75
    +
    76 fuse_reply_entry(req, &e);
    +
    77}
    +
    78
    +
    79static void tfs_getattr(fuse_req_t req, fuse_ino_t ino,
    +
    80 struct fuse_file_info *fi)
    +
    81{
    +
    82 struct stat stbuf;
    +
    83
    +
    84 (void) fi;
    +
    85
    +
    86 memset(&stbuf, 0, sizeof(stbuf));
    +
    87 if (tfs_stat(ino, &stbuf) == -1)
    +
    88 fuse_reply_err(req, ENOENT);
    +
    89 else
    +
    90 fuse_reply_attr(req, &stbuf, 1.0);
    +
    91}
    +
    92
    +
    93struct dirbuf {
    +
    94 char *p;
    +
    95 size_t size;
    +
    96};
    +
    97
    +
    98static void dirbuf_add(fuse_req_t req, struct dirbuf *b, const char *name,
    +
    99 fuse_ino_t ino)
    +
    100{
    +
    101 struct stat stbuf;
    +
    102 size_t oldsize = b->size;
    +
    103
    +
    104 b->size += fuse_add_direntry(req, NULL, 0, name, NULL, 0);
    +
    105 b->p = realloc(b->p, b->size);
    +
    106 assert(b->p != NULL);
    +
    107
    +
    108 memset(&stbuf, 0, sizeof(stbuf));
    +
    109 stbuf.st_ino = ino;
    +
    110 fuse_add_direntry(req, b->p + oldsize, b->size - oldsize, name, &stbuf,
    +
    111 b->size);
    +
    112}
    +
    113
    +
    114static int reply_buf_limited(fuse_req_t req, const char *buf, size_t bufsize,
    +
    115 off_t off, size_t maxsize)
    +
    116{
    +
    117 size_t offset = (size_t) off;
    +
    118
    +
    119 if (off < 0 || offset >= bufsize)
    +
    120 return fuse_reply_buf(req, NULL, 0);
    +
    121
    +
    122 if (bufsize - offset < maxsize)
    +
    123 maxsize = bufsize - offset;
    +
    124
    +
    125 return fuse_reply_buf(req, buf + offset, maxsize);
    +
    126}
    +
    127
    +
    128static void tfs_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
    +
    129 off_t off, struct fuse_file_info *fi)
    +
    130{
    +
    131 struct dirbuf b;
    +
    132
    +
    133 (void) fi;
    +
    134
    +
    135 if (ino != FUSE_ROOT_ID) {
    +
    136 fuse_reply_err(req, ENOTDIR);
    +
    137 return;
    +
    138 }
    +
    139
    +
    140 memset(&b, 0, sizeof(b));
    +
    141 dirbuf_add(req, &b, ".", FUSE_ROOT_ID);
    +
    142 dirbuf_add(req, &b, "..", FUSE_ROOT_ID);
    +
    143 dirbuf_add(req, &b, file_name, FILE_INO);
    +
    144 reply_buf_limited(req, b.p, b.size, off, size);
    +
    145 free(b.p);
    +
    146}
    +
    147
    +
    148static void tfs_open(fuse_req_t req, fuse_ino_t ino,
    +
    149 struct fuse_file_info *fi)
    +
    150{
    +
    151 if (ino != FILE_INO)
    +
    152 fuse_reply_err(req, EISDIR);
    +
    153 else if ((fi->flags & O_ACCMODE) != O_RDONLY)
    +
    154 fuse_reply_err(req, EACCES);
    +
    155 else
    +
    156 fuse_reply_open(req, fi);
    +
    157}
    +
    158
    +
    159static void tfs_read(fuse_req_t req, fuse_ino_t ino, size_t size, off_t off,
    +
    160 struct fuse_file_info *fi)
    +
    161{
    +
    162 (void) fi;
    +
    163
    +
    164 assert(ino == FILE_INO);
    +
    165 reply_buf_limited(req, file_contents, strlen(file_contents), off, size);
    +
    166}
    +
    167
    +
    168static const struct fuse_lowlevel_ops tfs_oper = {
    +
    169 .lookup = tfs_lookup,
    +
    170 .getattr = tfs_getattr,
    +
    171 .readdir = tfs_readdir,
    +
    172 .open = tfs_open,
    +
    173 .read = tfs_read,
    +
    174};
    +
    175
    +
    176static void *run_fs(void *data)
    +
    177{
    +
    178 struct fuse_session *se = (struct fuse_session *)data;
    +
    179
    +
    180 assert(fuse_session_loop(se) == 0);
    +
    181 return NULL;
    +
    182}
    +
    183
    +
    184static void check_readdir(const char *mountpoint)
    +
    185{
    +
    186 DIR *dir = opendir(mountpoint);
    +
    187 const struct dirent *ent;
    +
    188 int found = 0;
    +
    189
    +
    190 assert(dir != NULL);
    +
    191 while ((ent = readdir(dir)) != NULL) {
    +
    192 if (strcmp(ent->d_name, file_name) == 0)
    +
    193 found = 1;
    +
    194 }
    +
    195 assert(closedir(dir) == 0);
    +
    196 assert(found == 1);
    +
    197}
    +
    198
    +
    199static void check_read(const char *mountpoint)
    +
    200{
    +
    201 char path[PATH_MAX];
    +
    202 char buf[sizeof(file_contents)];
    +
    203 int fd;
    +
    204 ssize_t len;
    +
    205
    +
    206 assert(snprintf(path, PATH_MAX, "%s/%s", mountpoint, file_name) > 0);
    +
    207 fd = open(path, O_RDONLY);
    +
    208 if (fd == -1) {
    +
    209 perror(path);
    +
    210 assert(0);
    +
    211 }
    +
    212
    +
    213 len = read(fd, buf, sizeof(buf));
    +
    214 assert(len == (ssize_t) strlen(file_contents));
    +
    215 assert(memcmp(buf, file_contents, len) == 0);
    +
    216 assert(close(fd) == 0);
    +
    217}
    +
    218
    +
    219int main(int argc, char *argv[])
    +
    220{
    +
    221 struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
    +
    222 struct fuse_session *se;
    +
    223 struct fuse_cmdline_opts fuse_opts;
    +
    224 pthread_t fs_thread;
    +
    225
    +
    226 assert(fuse_parse_cmdline(&args, &fuse_opts) == 0);
    +
    227#ifndef __FreeBSD__
    +
    228 assert(fuse_opt_add_arg(&args, "-oauto_unmount") == 0);
    +
    229#endif
    +
    230 se = fuse_session_new(&args, &tfs_oper, sizeof(tfs_oper), NULL);
    +
    231 assert(se != NULL);
    +
    232 assert(fuse_set_signal_handlers(se) == 0);
    +
    233 assert(fuse_session_mount(se, fuse_opts.mountpoint) == 0);
    +
    234
    +
    235 assert(pthread_create(&fs_thread, NULL, run_fs, (void *)se) == 0);
    +
    236
    +
    237 check_readdir(fuse_opts.mountpoint);
    +
    238 check_read(fuse_opts.mountpoint);
    +
    239
    +
    240 /*
    +
    241 * fuse_session_exit() does not interrupt read(), so nudge this thread
    +
    242 * with the ignored signal to let the loop free its receive buffer.
    +
    243 */
    + +
    245 assert(pthread_kill(fs_thread, SIGPIPE) == 0);
    +
    246 assert(pthread_join(fs_thread, NULL) == 0);
    +
    247
    + + + +
    251 free(fuse_opts.mountpoint);
    +
    252 fuse_opt_free_args(&args);
    +
    253
    +
    254 printf("Test completed successfully.\n");
    +
    255 return 0;
    +
    256}
    + +
    int fuse_set_signal_handlers(struct fuse_session *se)
    +
    void fuse_remove_signal_handlers(struct fuse_session *se)
    + +
    void fuse_session_destroy(struct fuse_session *se)
    +
    int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi)
    +
    void fuse_session_exit(struct fuse_session *se)
    +
    int fuse_reply_err(fuse_req_t req, int err)
    +
    int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
    +
    struct fuse_req * fuse_req_t
    +
    int fuse_session_loop(struct fuse_session *se)
    Definition fuse_loop.c:19
    +
    int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
    +
    void fuse_session_unmount(struct fuse_session *se)
    +
    int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
    +
    uint64_t fuse_ino_t
    +
    size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct stat *stbuf, off_t off)
    +
    int fuse_reply_attr(fuse_req_t req, const struct stat *attr, double attr_timeout)
    +
    int fuse_opt_add_arg(struct fuse_args *args, const char *arg)
    Definition fuse_opt.c:55
    +
    void fuse_opt_free_args(struct fuse_args *args)
    Definition fuse_opt.c:34
    +
    #define FUSE_ARGS_INIT(argc, argv)
    Definition fuse_opt.h:123
    + + +
    char ** argv
    Definition fuse_opt.h:114
    + +
    +
    fuse_ino_t ino
    + + + +
    + + +
    + + diff --git a/doc/html/test__setattr_8c_source.html b/doc/html/test__setattr_8c_source.html new file mode 100644 index 0000000..6427d34 --- /dev/null +++ b/doc/html/test__setattr_8c_source.html @@ -0,0 +1,271 @@ + + + + + + + +libfuse: test/test_setattr.c Source File + + + + + + + +
    +
    + + + + + + +
    +
    libfuse +
    +
    +
    + + + + + + + + +
    +
    +
    +
    test_setattr.c
    +
    +
    +
    1/*
    +
    2 FUSE: Filesystem in Userspace
    +
    3 Copyright (C) 2016 Nikolaus Rath <Nikolaus@rath.org>
    +
    4
    +
    5 This program can be distributed under the terms of the GNU GPLv2.
    +
    6 See the file GPL2.txt.
    +
    7*/
    +
    8
    +
    9
    +
    10#define FUSE_USE_VERSION 30
    +
    11
    +
    12/* Not really needed - just to test build with FUSE_USE_VERSION == 30 */
    +
    13#include <fuse.h>
    +
    14
    +
    15#include <fuse_config.h>
    +
    16#include <fuse_lowlevel.h>
    +
    17#include <stdio.h>
    +
    18#include <stdlib.h>
    +
    19#include <string.h>
    +
    20#include <errno.h>
    +
    21#include <fcntl.h>
    +
    22#include <assert.h>
    +
    23#include <stddef.h>
    +
    24#include <unistd.h>
    +
    25#include <pthread.h>
    +
    26
    +
    27#ifndef __linux__
    +
    28#include <limits.h>
    +
    29#else
    +
    30#include <linux/limits.h>
    +
    31#endif
    +
    32
    +
    33#define FILE_INO 2
    +
    34#define FILE_NAME "truncate_me"
    +
    35
    +
    36static int got_fh;
    +
    37static mode_t file_mode = S_IFREG | 0644;
    +
    38
    +
    39static int tfs_stat(fuse_ino_t ino, struct stat *stbuf) {
    +
    40 stbuf->st_ino = ino;
    +
    41 if (ino == FUSE_ROOT_ID) {
    +
    42 stbuf->st_mode = S_IFDIR | 0755;
    +
    43 stbuf->st_nlink = 1;
    +
    44 }
    +
    45
    +
    46 else if (ino == FILE_INO) {
    +
    47 stbuf->st_mode = file_mode;
    +
    48 stbuf->st_nlink = 1;
    +
    49 stbuf->st_size = 0;
    +
    50 }
    +
    51
    +
    52 else
    +
    53 return -1;
    +
    54
    +
    55 return 0;
    +
    56}
    +
    57
    +
    58static void tfs_lookup(fuse_req_t req, fuse_ino_t parent,
    +
    59 const char *name) {
    +
    60 struct fuse_entry_param e;
    +
    61 memset(&e, 0, sizeof(e));
    +
    62
    +
    63 if (parent != FUSE_ROOT_ID)
    +
    64 goto err_out;
    +
    65 else if (strcmp(name, FILE_NAME) == 0)
    +
    66 e.ino = FILE_INO;
    +
    67 else
    +
    68 goto err_out;
    +
    69
    +
    70 if (tfs_stat(e.ino, &e.attr) != 0)
    +
    71 goto err_out;
    +
    72 fuse_reply_entry(req, &e);
    +
    73 return;
    +
    74
    +
    75err_out:
    +
    76 fuse_reply_err(req, ENOENT);
    +
    77}
    +
    78
    +
    79static void tfs_getattr(fuse_req_t req, fuse_ino_t ino,
    +
    80 struct fuse_file_info *fi) {
    +
    81 struct stat stbuf;
    +
    82
    +
    83 (void) fi;
    +
    84
    +
    85 memset(&stbuf, 0, sizeof(stbuf));
    +
    86 if (tfs_stat(ino, &stbuf) != 0)
    +
    87 fuse_reply_err(req, ENOENT);
    +
    88 else
    +
    89 fuse_reply_attr(req, &stbuf, 5);
    +
    90}
    +
    91
    +
    92static void tfs_open(fuse_req_t req, fuse_ino_t ino,
    +
    93 struct fuse_file_info *fi) {
    +
    94 if (ino == FUSE_ROOT_ID)
    +
    95 fuse_reply_err(req, EISDIR);
    +
    96 else {
    +
    97 assert(ino == FILE_INO);
    +
    98 fi->fh = FILE_INO;
    +
    99 fuse_reply_open(req, fi);
    +
    100 }
    +
    101}
    +
    102
    +
    103static void tfs_setattr (fuse_req_t req, fuse_ino_t ino, struct stat *attr,
    +
    104 int to_set, struct fuse_file_info *fi) {
    +
    105 if(ino != FILE_INO ||
    +
    106 !(to_set & FUSE_SET_ATTR_MODE)) {
    +
    107 fuse_reply_err(req, EINVAL);
    +
    108 return;
    +
    109 }
    +
    110
    +
    111 if(fi == NULL)
    +
    112 fprintf(stderr, "setattr with fi == NULL\n");
    +
    113 else if (fi->fh != FILE_INO)
    +
    114 fprintf(stderr, "setattr with wrong fi->fh\n");
    +
    115 else {
    +
    116 fprintf(stderr, "setattr ok\n");
    +
    117 got_fh = 1;
    +
    118 file_mode = attr->st_mode;
    +
    119 }
    +
    120
    +
    121 tfs_getattr(req, ino, fi);
    +
    122}
    +
    123
    +
    124static struct fuse_lowlevel_ops tfs_oper = {
    +
    125 .lookup = tfs_lookup,
    +
    126 .getattr = tfs_getattr,
    +
    127 .open = tfs_open,
    +
    128 .setattr = tfs_setattr,
    +
    129};
    +
    130
    +
    131static void* run_fs(void *data) {
    +
    132 struct fuse_session *se = (struct fuse_session*) data;
    +
    133 assert(fuse_session_loop(se) == 0);
    +
    134 return NULL;
    +
    135}
    +
    136
    +
    137static void test_fs(char *mountpoint) {
    +
    138 char fname[PATH_MAX];
    +
    139 int fd;
    +
    140
    +
    141 assert(snprintf(fname, PATH_MAX, "%s/" FILE_NAME,
    +
    142 mountpoint) > 0);
    +
    143 fd = open(fname, O_WRONLY);
    +
    144 if (fd == -1) {
    +
    145 perror(fname);
    +
    146 assert(0);
    +
    147 }
    +
    148
    +
    149 assert(fchmod(fd, 0600) == 0);
    +
    150 close(fd);
    +
    151}
    +
    152
    +
    153int main(int argc, char *argv[]) {
    +
    154 struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
    +
    155 struct fuse_session *se;
    +
    156 struct fuse_cmdline_opts fuse_opts;
    +
    157 pthread_t fs_thread;
    +
    158
    +
    159 assert(fuse_parse_cmdline(&args, &fuse_opts) == 0);
    +
    160#ifndef __FreeBSD__
    +
    161 assert(fuse_opt_add_arg(&args, "-oauto_unmount") == 0);
    +
    162#endif
    +
    163 se = fuse_session_new(&args, &tfs_oper,
    +
    164 sizeof(tfs_oper), NULL);
    +
    165 assert (se != NULL);
    +
    166 assert(fuse_set_signal_handlers(se) == 0);
    +
    167 assert(fuse_session_mount(se, fuse_opts.mountpoint) == 0);
    +
    168
    +
    169 /* Start file-system thread */
    +
    170 assert(pthread_create(&fs_thread, NULL, run_fs, (void *)se) == 0);
    +
    171
    +
    172 /* Do test */
    +
    173 test_fs(fuse_opts.mountpoint);
    +
    174
    +
    175 /* Stop file system */
    +
    176 assert(pthread_cancel(fs_thread) == 0);
    +
    177
    + +
    179 assert(got_fh == 1);
    + + +
    182
    +
    183 printf("Test completed successfully.\n");
    +
    184 return 0;
    +
    185}
    +
    186
    +
    187
    + +
    int fuse_set_signal_handlers(struct fuse_session *se)
    +
    void fuse_remove_signal_handlers(struct fuse_session *se)
    + +
    void fuse_session_destroy(struct fuse_session *se)
    +
    int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi)
    +
    int fuse_reply_err(fuse_req_t req, int err)
    +
    struct fuse_req * fuse_req_t
    +
    int fuse_session_loop(struct fuse_session *se)
    Definition fuse_loop.c:19
    +
    int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
    +
    void fuse_session_unmount(struct fuse_session *se)
    +
    int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
    +
    uint64_t fuse_ino_t
    +
    int fuse_reply_attr(fuse_req_t req, const struct stat *attr, double attr_timeout)
    +
    int fuse_opt_add_arg(struct fuse_args *args, const char *arg)
    Definition fuse_opt.c:55
    +
    #define FUSE_ARGS_INIT(argc, argv)
    Definition fuse_opt.h:123
    + + +
    char ** argv
    Definition fuse_opt.h:114
    + +
    +
    fuse_ino_t ino
    + + + +
    + + +
    + + diff --git a/doc/html/test__signals_8c_source.html b/doc/html/test__signals_8c_source.html new file mode 100644 index 0000000..7c55942 --- /dev/null +++ b/doc/html/test__signals_8c_source.html @@ -0,0 +1,278 @@ + + + + + + + +libfuse: test/test_signals.c Source File + + + + + + + +
    +
    + + + + + + +
    +
    libfuse +
    +
    +
    + + + + + + + + +
    +
    +
    +
    test_signals.c
    +
    +
    +
    1/*
    +
    2 * FUSE: Filesystem in Userspace
    +
    3 * Copyright (C) 2025 Bernd Schubert <bernd@bsbernd.com>
    +
    4 *
    +
    5 * Test for signal handling in libfuse.
    +
    6 *
    +
    7 * This program can be distributed under the terms of the GNU LGPLv2.
    +
    8 * See the file GPL2.txt
    +
    9 */
    +
    10
    +
    11#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 17)
    +
    12
    +
    13#include "fuse_config.h"
    +
    14#include "fuse_lowlevel.h"
    +
    15#include "fuse_i.h"
    +
    16
    +
    17#include <pthread.h>
    +
    18#include <stdatomic.h>
    +
    19#include <stdio.h>
    +
    20#include <stdlib.h>
    +
    21#include <string.h>
    +
    22#include <unistd.h>
    +
    23#include <signal.h>
    +
    24#include <errno.h>
    +
    25#include <sys/types.h>
    +
    26#include <sys/wait.h>
    +
    27
    +
    28static void test_ll_lookup(fuse_req_t req, fuse_ino_t parent, const char *name)
    +
    29{
    +
    30 (void)parent;
    +
    31 (void)name;
    +
    32 /* Simulate slow lookup to test signal interruption */
    +
    33 sleep(2);
    +
    34 fuse_reply_err(req, ENOENT);
    +
    35}
    +
    36
    +
    37static void test_ll_getattr(fuse_req_t req, fuse_ino_t ino,
    +
    38 struct fuse_file_info *fi)
    +
    39{
    +
    40 (void)ino;
    +
    41 (void)fi;
    +
    42 /* Simulate slow getattr to test signal interruption */
    +
    43 sleep(2);
    +
    44 fuse_reply_err(req, ENOENT);
    +
    45}
    +
    46
    +
    47static const struct fuse_lowlevel_ops test_ll_ops = {
    +
    48 .lookup = test_ll_lookup,
    +
    49 .getattr = test_ll_getattr,
    +
    50};
    +
    51
    +
    52static void *signal_sender_thread(void *arg)
    +
    53{
    +
    54 (void)arg;
    +
    55
    +
    56 usleep(2 * 1000 * 1000);
    +
    57
    +
    58 /* Send SIGTERM to the process */
    +
    59 kill(getpid(), SIGTERM);
    +
    60 return NULL;
    +
    61}
    +
    62
    +
    63static void fork_child(void)
    +
    64{
    +
    65 struct fuse_args args = FUSE_ARGS_INIT(0, NULL);
    +
    66 struct fuse_session *se;
    +
    67 struct fuse_loop_config *loop_config;
    +
    68 pthread_t sig_thread;
    +
    69 char *mountpoint = NULL;
    +
    70 int ret = -1;
    +
    71
    +
    72 /* Add the program name to arg[0] */
    +
    73 if (fuse_opt_add_arg(&args, "test_signals")) {
    +
    74 fprintf(stderr, "Failed to add argument\n");
    +
    75 goto out_free_mountpoint;
    +
    76 }
    +
    77
    +
    78 /* Add debug flag to see more output */
    +
    79 fuse_opt_add_arg(&args, "-d");
    +
    80
    +
    81 /* Create temporary mount point */
    +
    82 mountpoint = strdup("/tmp/fuse_test_XXXXXX");
    +
    83 if (!mountpoint || !mkdtemp(mountpoint)) {
    +
    84 fprintf(stderr, "Failed to create temp dir\n");
    +
    85 goto out_free_args;
    +
    86 }
    +
    87
    +
    88 /* Create session */
    +
    89 se = fuse_session_new(&args, &test_ll_ops, sizeof(test_ll_ops), NULL);
    +
    90 if (!se) {
    +
    91 fprintf(stderr, "Failed to create FUSE session\n");
    +
    92 goto out_free_mountpoint;
    +
    93 }
    +
    94
    +
    95 /* Mount filesystem */
    +
    96 if (fuse_session_mount(se, mountpoint)) {
    +
    97 fprintf(stderr, "Failed to mount FUSE filesystem\n");
    +
    98 goto out_destroy_session;
    +
    99 }
    +
    100
    +
    101 /* Create loop config */
    +
    102 loop_config = fuse_loop_cfg_create();
    +
    103 if (!loop_config) {
    +
    104 fprintf(stderr, "Failed to create loop config\n");
    +
    105 goto out_unmount;
    +
    106 }
    +
    107 fuse_loop_cfg_set_clone_fd(loop_config, 0);
    +
    108 fuse_loop_cfg_set_max_threads(loop_config, 2);
    +
    109
    +
    110 /* Set up signal handlers */
    +
    111 if (fuse_set_signal_handlers(se)) {
    +
    112 fprintf(stderr, "Failed to set up signal handlers\n");
    +
    113 goto out_destroy_config;
    +
    114 }
    +
    115
    +
    116 /* Create thread that will send signals */
    +
    117 if (pthread_create(&sig_thread, NULL, signal_sender_thread, NULL)) {
    +
    118 fprintf(stderr, "Failed to create signal sender thread\n");
    +
    119 goto out_remove_handlers;
    +
    120 }
    +
    121
    +
    122 /* Enter FUSE loop */
    +
    123 ret = fuse_session_loop_mt_312(se, loop_config);
    +
    124
    +
    125 printf("Debug: fuse_session_loop_mt_312 returned %d\n", ret);
    +
    126 printf("Debug: session exited state: %d\n", fuse_session_exited(se));
    +
    127 printf("Debug: session status: %d\n", se->error);
    +
    128
    +
    129 /* Check exit status before cleanup */
    +
    130 int clean_exit = (fuse_session_exited(se) && se->error == SIGTERM);
    +
    131
    +
    132 /* Clean up */
    +
    133 pthread_join(sig_thread, NULL);
    + + + +
    137 fuse_loop_cfg_destroy(loop_config);
    +
    138 rmdir(mountpoint);
    +
    139 free(mountpoint);
    +
    140 fuse_opt_free_args(&args);
    +
    141
    +
    142 /* Use saved exit status */
    +
    143 if (clean_exit) {
    +
    144 printf("Debug: Clean shutdown via SIGTERM\n");
    +
    145 exit(0);
    +
    146 }
    +
    147 printf("Debug: Exiting with status %d\n", ret != 0);
    +
    148 exit(ret != 0);
    +
    149
    +
    150out_remove_handlers:
    + +
    152out_destroy_config:
    +
    153 fuse_loop_cfg_destroy(loop_config);
    +
    154out_unmount:
    + +
    156out_destroy_session:
    + +
    158out_free_mountpoint:
    +
    159 rmdir(mountpoint);
    +
    160 free(mountpoint);
    +
    161out_free_args:
    +
    162 fuse_opt_free_args(&args);
    +
    163 exit(1);
    +
    164}
    +
    165
    +
    166static void run_test_in_child(void)
    +
    167{
    +
    168 pid_t child;
    +
    169 int status;
    +
    170
    +
    171 child = fork();
    +
    172 if (child == -1) {
    +
    173 perror("fork");
    +
    174 exit(1);
    +
    175 }
    +
    176
    +
    177 if (child == 0)
    +
    178 fork_child();
    +
    179
    +
    180 /* In parent process */
    +
    181 if (waitpid(child, &status, 0) == -1) {
    +
    182 perror("waitpid");
    +
    183 exit(1);
    +
    184 }
    +
    185
    +
    186 /* Check if child exited due to SIGTERM - this is expected */
    +
    187 if (WIFSIGNALED(status) && WTERMSIG(status) == SIGTERM) {
    +
    188 printf("Child process terminated by SIGTERM as expected\n");
    +
    189 exit(0);
    +
    190 }
    +
    191
    +
    192 /* For any other type of exit, maintain existing behavior */
    +
    193 exit(WIFEXITED(status) ? WEXITSTATUS(status) : 1);
    +
    194}
    +
    195
    +
    196int main(void)
    +
    197{
    +
    198 printf("Testing SIGTERM handling in libfuse\n");
    +
    199 run_test_in_child();
    +
    200 printf("SIGTERM handling test passed\n");
    +
    201 return 0;
    +
    202}
    +
    int fuse_set_signal_handlers(struct fuse_session *se)
    +
    void fuse_remove_signal_handlers(struct fuse_session *se)
    + +
    void fuse_session_destroy(struct fuse_session *se)
    +
    int fuse_reply_err(fuse_req_t req, int err)
    +
    struct fuse_req * fuse_req_t
    +
    int fuse_session_exited(struct fuse_session *se)
    +
    void fuse_session_unmount(struct fuse_session *se)
    +
    int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
    +
    uint64_t fuse_ino_t
    +
    int fuse_opt_add_arg(struct fuse_args *args, const char *arg)
    Definition fuse_opt.c:55
    +
    void fuse_opt_free_args(struct fuse_args *args)
    Definition fuse_opt.c:34
    +
    #define FUSE_ARGS_INIT(argc, argv)
    Definition fuse_opt.h:123
    + + + + +
    + + +
    + + diff --git a/doc/html/test__syscalls_8c_source.html b/doc/html/test__syscalls_8c_source.html new file mode 100644 index 0000000..c670caa --- /dev/null +++ b/doc/html/test__syscalls_8c_source.html @@ -0,0 +1,2332 @@ + + + + + + + +libfuse: test/test_syscalls.c Source File + + + + + + + +
    +
    + + + + + + +
    +
    libfuse +
    +
    +
    + + + + + + + + +
    +
    +
    +
    test_syscalls.c
    +
    +
    +
    1#define _GNU_SOURCE
    +
    2#include "fuse_config.h"
    +
    3
    +
    4#include <stdio.h>
    +
    5#include <stdlib.h>
    +
    6#include <stdarg.h>
    +
    7#include <string.h>
    +
    8#include <unistd.h>
    +
    9#include <fcntl.h>
    +
    10#include <dirent.h>
    +
    11#include <utime.h>
    +
    12#include <errno.h>
    +
    13#include <assert.h>
    +
    14#include <time.h>
    +
    15#include <sys/socket.h>
    +
    16#include <sys/types.h>
    +
    17#include <sys/stat.h>
    +
    18#include <sys/un.h>
    +
    19
    +
    20#ifndef ALLPERMS
    +
    21# define ALLPERMS (S_ISUID|S_ISGID|S_ISVTX|S_IRWXU|S_IRWXG|S_IRWXO)/* 07777 */
    +
    22#endif
    +
    23
    +
    24
    +
    25static const char *basepath;
    +
    26static const char *basepath_r;
    +
    27static char testfile[1024];
    +
    28static char testfile2[1024];
    +
    29static char testdir[1024];
    +
    30static char testdir2[1024];
    +
    31static char testsock[1024];
    +
    32static char subfile[1280];
    +
    33
    +
    34static char testfile_r[1024];
    +
    35static char testfile2_r[1024];
    +
    36static char testdir_r[1024];
    +
    37static char testdir2_r[1024];
    +
    38static char subfile_r[1280];
    +
    39
    +
    40static char testname[256];
    +
    41static char testdata[] = "abcdefghijklmnopqrstuvwxyz";
    +
    42static char testdata2[] = "1234567890-=qwertyuiop[]\asdfghjkl;'zxcvbnm,./";
    +
    43static const char *testdir_files[] = { "f1", "f2", NULL};
    +
    44static long seekdir_offsets[4];
    +
    45static char zerodata[4096];
    +
    46static int testdatalen = sizeof(testdata) - 1;
    +
    47static int testdata2len = sizeof(testdata2) - 1;
    +
    48static unsigned int testnum = 0;
    +
    49static unsigned int select_test = 0;
    +
    50static unsigned int skip_test = 0;
    +
    51static unsigned int unlinked_test = 0;
    +
    52
    +
    53#define MAX_ENTRIES 1024
    +
    54#define MAX_TESTS 100
    +
    55
    +
    56static struct test {
    +
    57 int fd;
    +
    58 struct stat stat;
    +
    59} tests[MAX_TESTS];
    +
    60
    +
    61static void test_perror(const char *func, const char *msg)
    +
    62{
    +
    63 fprintf(stderr, "%s %s() - %s: %s\n", testname, func, msg,
    +
    64 strerror(errno));
    +
    65}
    +
    66
    +
    67static void test_error(const char *func, const char *msg, ...)
    +
    68 __attribute__ ((format (printf, 2, 3)));
    +
    69
    +
    70static void __start_test(const char *fmt, ...)
    +
    71 __attribute__ ((format (printf, 1, 2)));
    +
    72
    +
    73static void test_error(const char *func, const char *msg, ...)
    +
    74{
    +
    75 va_list ap;
    +
    76 fprintf(stderr, "%s %s() - ", testname, func);
    +
    77 va_start(ap, msg);
    +
    78 vfprintf(stderr, msg, ap);
    +
    79 va_end(ap);
    +
    80 fprintf(stderr, "\n");
    +
    81}
    +
    82
    +
    83static int is_dot_or_dotdot(const char *name) {
    +
    84 return name[0] == '.' &&
    +
    85 (name[1] == '\0' || (name[1] == '.' && name[2] == '\0'));
    +
    86}
    +
    87
    +
    88/*
    +
    89 * Seconds since the first line this process printed. The origin is taken
    +
    90 * lazily so it needs no init call and cannot precede the first test.
    +
    91 */
    +
    92static double test_elapsed(void)
    +
    93{
    +
    94 static struct timespec start;
    +
    95 struct timespec now;
    +
    96
    +
    97 clock_gettime(CLOCK_MONOTONIC, &now);
    +
    98 if (start.tv_sec == 0 && start.tv_nsec == 0)
    +
    99 start = now;
    +
    100 return (now.tv_sec - start.tv_sec) +
    +
    101 (now.tv_nsec - start.tv_nsec) / 1e9;
    +
    102}
    +
    103
    +
    104static void success(void)
    +
    105{
    +
    106 fprintf(stderr, "+%8.3fs %s OK\n", test_elapsed(), testname);
    +
    107}
    +
    108
    +
    109#define this_test (&tests[testnum-1])
    +
    110#define next_test (&tests[testnum])
    +
    111
    +
    112static void __start_test(const char *fmt, ...)
    +
    113{
    +
    114 unsigned int n;
    +
    115 va_list ap;
    +
    116 n = sprintf(testname, "%3i [", testnum);
    +
    117 va_start(ap, fmt);
    +
    118 n += vsprintf(testname + n, fmt, ap);
    +
    119 va_end(ap);
    +
    120 sprintf(testname + n, "]");
    +
    121 // Use dedicated testfile per test
    +
    122 sprintf(testfile, "%s/testfile.%d", basepath, testnum);
    +
    123 sprintf(testfile_r, "%s/testfile.%d", basepath_r, testnum);
    +
    124 if (testnum > MAX_TESTS) {
    +
    125 fprintf(stderr, "%s - too many tests\n", testname);
    +
    126 exit(1);
    +
    127 }
    +
    128 /* A test that never finishes prints nothing otherwise, leaving the log
    +
    129 * ending at the last test that passed. stderr, so a SIGKILL cannot
    +
    130 * swallow it.
    +
    131 */
    +
    132 fprintf(stderr, "+%8.3fs %s START\n", test_elapsed(), testname);
    +
    133 this_test->fd = -1;
    +
    134}
    +
    135
    +
    136#define start_test(msg, args...) { \
    +
    137 testnum++; \
    +
    138 if ((select_test && testnum != select_test) || \
    +
    139 (testnum == skip_test)) { \
    +
    140 return 0; \
    +
    141 } \
    +
    142 __start_test(msg, ##args); \
    +
    143}
    +
    144
    +
    145#define PERROR(msg) test_perror(__FUNCTION__, msg)
    +
    146#define ERROR(msg, args...) test_error(__FUNCTION__, msg, ##args)
    +
    147
    +
    148#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))
    +
    149
    +
    150static int st_check_size(struct stat *st, int len)
    +
    151{
    +
    152 if (st->st_size != len) {
    +
    153 ERROR("length %u instead of %u", (int) st->st_size,
    +
    154 (int) len);
    +
    155 return -1;
    +
    156 }
    +
    157 return 0;
    +
    158}
    +
    159
    +
    160static int check_size(const char *path, int len)
    +
    161{
    +
    162 struct stat stbuf;
    +
    163 int res = stat(path, &stbuf);
    +
    164 if (res == -1) {
    +
    165 PERROR("stat");
    +
    166 return -1;
    +
    167 }
    +
    168 return st_check_size(&stbuf, len);
    +
    169}
    +
    170
    +
    171static int check_testfile_size(const char *path, int len)
    +
    172{
    +
    173 this_test->stat.st_size = len;
    +
    174 return check_size(path, len);
    +
    175}
    +
    176
    +
    177static int st_check_type(struct stat *st, mode_t type)
    +
    178{
    +
    179 if ((st->st_mode & S_IFMT) != type) {
    +
    180 ERROR("type 0%o instead of 0%o", st->st_mode & S_IFMT, type);
    +
    181 return -1;
    +
    182 }
    +
    183 return 0;
    +
    184}
    +
    185
    +
    186static int check_type(const char *path, mode_t type)
    +
    187{
    +
    188 struct stat stbuf;
    +
    189 int res = lstat(path, &stbuf);
    +
    190 if (res == -1) {
    +
    191 PERROR("lstat");
    +
    192 return -1;
    +
    193 }
    +
    194 return st_check_type(&stbuf, type);
    +
    195}
    +
    196
    +
    197static int st_check_mode(struct stat *st, mode_t mode)
    +
    198{
    +
    199 if ((st->st_mode & ALLPERMS) != mode) {
    +
    200 ERROR("mode 0%o instead of 0%o", st->st_mode & ALLPERMS,
    +
    201 mode);
    +
    202 return -1;
    +
    203 }
    +
    204 return 0;
    +
    205}
    +
    206
    +
    207static int check_mode(const char *path, mode_t mode)
    +
    208{
    +
    209 struct stat stbuf;
    +
    210 int res = lstat(path, &stbuf);
    +
    211 if (res == -1) {
    +
    212 PERROR("lstat");
    +
    213 return -1;
    +
    214 }
    +
    215 return st_check_mode(&stbuf, mode);
    +
    216}
    +
    217
    +
    218static int check_testfile_mode(const char *path, mode_t mode)
    +
    219{
    +
    220 this_test->stat.st_mode &= ~ALLPERMS;
    +
    221 this_test->stat.st_mode |= mode;
    +
    222 return check_mode(path, mode);
    +
    223}
    +
    224
    +
    225static int check_times(const char *path, time_t atime, time_t mtime)
    +
    226{
    +
    227 int err = 0;
    +
    228 struct stat stbuf;
    +
    229 int res = lstat(path, &stbuf);
    +
    230 if (res == -1) {
    +
    231 PERROR("lstat");
    +
    232 return -1;
    +
    233 }
    +
    234 if (stbuf.st_atime != atime) {
    +
    235 ERROR("atime %li instead of %li", stbuf.st_atime, atime);
    +
    236 err--;
    +
    237 }
    +
    238 if (stbuf.st_mtime != mtime) {
    +
    239 ERROR("mtime %li instead of %li", stbuf.st_mtime, mtime);
    +
    240 err--;
    +
    241 }
    +
    242 if (err)
    +
    243 return -1;
    +
    244
    +
    245 return 0;
    +
    246}
    +
    247
    +
    248#if 0
    +
    249static int fcheck_times(int fd, time_t atime, time_t mtime)
    +
    250{
    +
    251 int err = 0;
    +
    252 struct stat stbuf;
    +
    253 int res = fstat(fd, &stbuf);
    +
    254 if (res == -1) {
    +
    255 PERROR("fstat");
    +
    256 return -1;
    +
    257 }
    +
    258 if (stbuf.st_atime != atime) {
    +
    259 ERROR("atime %li instead of %li", stbuf.st_atime, atime);
    +
    260 err--;
    +
    261 }
    +
    262 if (stbuf.st_mtime != mtime) {
    +
    263 ERROR("mtime %li instead of %li", stbuf.st_mtime, mtime);
    +
    264 err--;
    +
    265 }
    +
    266 if (err)
    +
    267 return -1;
    +
    268
    +
    269 return 0;
    +
    270}
    +
    271#endif
    +
    272
    +
    273static int st_check_nlink(struct stat *st, nlink_t nlink)
    +
    274{
    +
    275 if (st->st_nlink != nlink) {
    +
    276 ERROR("nlink %li instead of %li", (long) st->st_nlink,
    +
    277 (long) nlink);
    +
    278 return -1;
    +
    279 }
    +
    280 return 0;
    +
    281}
    +
    282
    +
    283static int check_nlink(const char *path, nlink_t nlink)
    +
    284{
    +
    285 struct stat stbuf;
    +
    286 int res = lstat(path, &stbuf);
    +
    287 if (res == -1) {
    +
    288 PERROR("lstat");
    +
    289 return -1;
    +
    290 }
    +
    291 return st_check_nlink(&stbuf, nlink);
    +
    292}
    +
    293
    +
    294static int fcheck_stat(int fd, int flags, struct stat *st)
    +
    295{
    +
    296 struct stat stbuf;
    +
    297 int res = fstat(fd, &stbuf);
    +
    298 if (res == -1) {
    +
    299 if (flags & O_PATH) {
    +
    300 // With O_PATH fd, the server does not have to keep
    +
    301 // the inode alive so FUSE inode may be stale or bad
    +
    302 if (errno == ESTALE || errno == EIO ||
    +
    303 errno == ENOENT || errno == EBADF)
    +
    304 return 0;
    +
    305 }
    +
    306 PERROR("fstat");
    +
    307 return -1;
    +
    308 }
    +
    309
    +
    310 int err = 0;
    +
    311 err += st_check_type(&stbuf, st->st_mode & S_IFMT);
    +
    312 err += st_check_mode(&stbuf, st->st_mode & ALLPERMS);
    +
    313 err += st_check_size(&stbuf, st->st_size);
    +
    314 err += st_check_nlink(&stbuf, st->st_nlink);
    +
    315
    +
    316 return err;
    +
    317}
    +
    318
    +
    319static int check_nonexist(const char *path)
    +
    320{
    +
    321 struct stat stbuf;
    +
    322 int res = lstat(path, &stbuf);
    +
    323 if (res == 0) {
    +
    324 ERROR("file should not exist");
    +
    325 return -1;
    +
    326 }
    +
    327 if (errno != ENOENT) {
    +
    328 ERROR("file should not exist: %s", strerror(errno));
    +
    329 return -1;
    +
    330 }
    +
    331 return 0;
    +
    332}
    +
    333
    +
    334static int check_buffer(const char *buf, const char *data, unsigned len)
    +
    335{
    +
    336 if (memcmp(buf, data, len) != 0) {
    +
    337 ERROR("data mismatch");
    +
    338 return -1;
    +
    339 }
    +
    340 return 0;
    +
    341}
    +
    342
    +
    343static int check_data(const char *path, const char *data, int offset,
    +
    344 unsigned len)
    +
    345{
    +
    346 char buf[4096];
    +
    347 int res;
    +
    348 int fd = open(path, O_RDONLY);
    +
    349 if (fd == -1) {
    +
    350 PERROR("open");
    +
    351 return -1;
    +
    352 }
    +
    353 if (lseek(fd, offset, SEEK_SET) == (off_t) -1) {
    +
    354 PERROR("lseek");
    +
    355 close(fd);
    +
    356 return -1;
    +
    357 }
    +
    358 while (len) {
    +
    359 int rdlen = len < sizeof(buf) ? len : sizeof(buf);
    +
    360 res = read(fd, buf, rdlen);
    +
    361 if (res == -1) {
    +
    362 PERROR("read");
    +
    363 close(fd);
    +
    364 return -1;
    +
    365 }
    +
    366 if (res != rdlen) {
    +
    367 ERROR("short read: %u instead of %u", res, rdlen);
    +
    368 close(fd);
    +
    369 return -1;
    +
    370 }
    +
    371 if (check_buffer(buf, data, rdlen) != 0) {
    +
    372 close(fd);
    +
    373 return -1;
    +
    374 }
    +
    375 data += rdlen;
    +
    376 len -= rdlen;
    +
    377 }
    +
    378 res = close(fd);
    +
    379 if (res == -1) {
    +
    380 PERROR("close");
    +
    381 return -1;
    +
    382 }
    +
    383 return 0;
    +
    384}
    +
    385
    +
    386static int fcheck_data(int fd, const char *data, int offset,
    +
    387 unsigned len)
    +
    388{
    +
    389 char buf[4096];
    +
    390 int res;
    +
    391 if (lseek(fd, offset, SEEK_SET) == (off_t) -1) {
    +
    392 PERROR("lseek");
    +
    393 return -1;
    +
    394 }
    +
    395 while (len) {
    +
    396 int rdlen = len < sizeof(buf) ? len : sizeof(buf);
    +
    397 res = read(fd, buf, rdlen);
    +
    398 if (res == -1) {
    +
    399 PERROR("read");
    +
    400 return -1;
    +
    401 }
    +
    402 if (res != rdlen) {
    +
    403 ERROR("short read: %u instead of %u", res, rdlen);
    +
    404 return -1;
    +
    405 }
    +
    406 if (check_buffer(buf, data, rdlen) != 0) {
    +
    407 return -1;
    +
    408 }
    +
    409 data += rdlen;
    +
    410 len -= rdlen;
    +
    411 }
    +
    412 return 0;
    +
    413}
    +
    414
    +
    415static int check_dir_contents(const char *path, const char **contents)
    +
    416{
    +
    417 int i;
    +
    418 int res;
    +
    419 int err = 0;
    +
    420 int found[MAX_ENTRIES];
    +
    421 const char *cont[MAX_ENTRIES];
    +
    422 DIR *dp;
    +
    423
    +
    424 for (i = 0; contents[i]; i++) {
    +
    425 assert(i < MAX_ENTRIES - 3);
    +
    426 found[i] = 0;
    +
    427 cont[i] = contents[i];
    +
    428 }
    +
    429 cont[i] = NULL;
    +
    430
    +
    431 dp = opendir(path);
    +
    432 if (dp == NULL) {
    +
    433 PERROR("opendir");
    +
    434 return -1;
    +
    435 }
    +
    436 memset(found, 0, sizeof(found));
    +
    437 while(1) {
    +
    438 struct dirent *de;
    +
    439 errno = 0;
    +
    440 de = readdir(dp);
    +
    441 if (de == NULL) {
    +
    442 if (errno) {
    +
    443 PERROR("readdir");
    +
    444 closedir(dp);
    +
    445 return -1;
    +
    446 }
    +
    447 break;
    +
    448 }
    +
    449 if (is_dot_or_dotdot(de->d_name))
    +
    450 continue;
    +
    451 for (i = 0; cont[i] != NULL; i++) {
    +
    452 assert(i < MAX_ENTRIES);
    +
    453 if (strcmp(cont[i], de->d_name) == 0) {
    +
    454 if (found[i]) {
    +
    455 ERROR("duplicate entry <%s>",
    +
    456 de->d_name);
    +
    457 err--;
    +
    458 } else
    +
    459 found[i] = 1;
    +
    460 break;
    +
    461 }
    +
    462 }
    +
    463 if (!cont[i]) {
    +
    464 ERROR("unexpected entry <%s>", de->d_name);
    +
    465 err --;
    +
    466 }
    +
    467 }
    +
    468 for (i = 0; cont[i] != NULL; i++) {
    +
    469 if (!found[i]) {
    +
    470 ERROR("missing entry <%s>", cont[i]);
    +
    471 err--;
    +
    472 }
    +
    473 }
    +
    474 res = closedir(dp);
    +
    475 if (res == -1) {
    +
    476 PERROR("closedir");
    +
    477 return -1;
    +
    478 }
    +
    479 if (err)
    +
    480 return -1;
    +
    481
    +
    482 return 0;
    +
    483}
    +
    484
    +
    485static int create_file(const char *path, const char *data, int len)
    +
    486{
    +
    487 int res;
    +
    488 int fd;
    +
    489
    +
    490 unlink(path);
    +
    491 fd = creat(path, 0644);
    +
    492 if (fd == -1) {
    +
    493 PERROR("creat");
    +
    494 return -1;
    +
    495 }
    +
    496 if (len) {
    +
    497 res = write(fd, data, len);
    +
    498 if (res == -1) {
    +
    499 PERROR("write");
    +
    500 close(fd);
    +
    501 return -1;
    +
    502 }
    +
    503 if (res != len) {
    +
    504 ERROR("write is short: %u instead of %u", res, len);
    +
    505 close(fd);
    +
    506 return -1;
    +
    507 }
    +
    508 }
    +
    509 res = close(fd);
    +
    510 if (res == -1) {
    +
    511 PERROR("close");
    +
    512 return -1;
    +
    513 }
    +
    514 res = check_type(path, S_IFREG);
    +
    515 if (res == -1)
    +
    516 return -1;
    +
    517 res = check_mode(path, 0644);
    +
    518 if (res == -1)
    +
    519 return -1;
    +
    520 res = check_nlink(path, 1);
    +
    521 if (res == -1)
    +
    522 return -1;
    +
    523 res = check_size(path, len);
    +
    524 if (res == -1)
    +
    525 return -1;
    +
    526
    +
    527 if (len) {
    +
    528 res = check_data(path, data, 0, len);
    +
    529 if (res == -1)
    +
    530 return -1;
    +
    531 }
    +
    532
    +
    533 return 0;
    +
    534}
    +
    535
    +
    536static int create_path_fd(const char *path, const char *data, int len)
    +
    537{
    +
    538 int path_fd;
    +
    539 int res;
    +
    540
    +
    541 res = create_file(path, data, len);
    +
    542 if (res == -1)
    +
    543 return -1;
    +
    544
    +
    545 path_fd = open(path, O_PATH);
    +
    546 if (path_fd == -1)
    +
    547 PERROR("open(O_PATH)");
    +
    548
    +
    549 return path_fd;
    +
    550}
    +
    551
    +
    552// Can be called once per test
    +
    553static int create_testfile(const char *path, const char *data, int len)
    +
    554{
    +
    555 struct test *t = this_test;
    +
    556 struct stat *st = &t->stat;
    +
    557 int res, fd;
    +
    558
    +
    559 if (t->fd > 0) {
    +
    560 ERROR("testfile already created");
    +
    561 return -1;
    +
    562 }
    +
    563
    +
    564 fd = create_path_fd(path, data, len);
    +
    565 if (fd == -1)
    +
    566 return -1;
    +
    567
    +
    568 t->fd = fd;
    +
    569
    +
    570 res = fstat(fd, st);
    +
    571 if (res == -1) {
    +
    572 PERROR("fstat");
    +
    573 return -1;
    +
    574 }
    +
    575
    +
    576 return 0;
    +
    577}
    +
    578
    +
    579static int check_unlinked_testfile(int fd)
    +
    580{
    +
    581 struct stat *st = &this_test->stat;
    +
    582
    +
    583 st->st_nlink = 0;
    +
    584 return fcheck_stat(fd, O_PATH, st);
    +
    585}
    +
    586
    +
    587// Check recorded testfiles after all tests completed
    +
    588static int check_unlinked_testfiles(void)
    +
    589{
    +
    590 int fd;
    +
    591 int res, err = 0;
    +
    592 int num = testnum;
    +
    593
    +
    594 if (!unlinked_test)
    +
    595 return 0;
    +
    596
    +
    597 testnum = 0;
    +
    598 while (testnum < num) {
    +
    599 fd = next_test->fd;
    +
    600 start_test("check_unlinked_testfile");
    +
    601 if (fd == -1)
    +
    602 continue;
    +
    603
    +
    604 err += check_unlinked_testfile(fd);
    +
    605 res = close(fd);
    +
    606 if (res == -1) {
    +
    607 PERROR("close(test_fd)");
    +
    608 err--;
    +
    609 }
    +
    610 }
    +
    611
    +
    612 if (err) {
    +
    613 fprintf(stderr, "%i unlinked testfile checks failed\n", -err);
    +
    614 return 1;
    +
    615 }
    +
    616
    +
    617 return err;
    +
    618}
    +
    619
    +
    620static int cleanup_dir(const char *path, const char **dir_files, int quiet)
    +
    621{
    +
    622 int i;
    +
    623 int err = 0;
    +
    624
    +
    625 for (i = 0; dir_files[i]; i++) {
    +
    626 int res;
    +
    627 char fpath[1280];
    +
    628 sprintf(fpath, "%s/%s", path, dir_files[i]);
    +
    629 res = unlink(fpath);
    +
    630 if (res == -1 && !quiet) {
    +
    631 PERROR("unlink");
    +
    632 err --;
    +
    633 }
    +
    634 }
    +
    635 if (err)
    +
    636 return -1;
    +
    637
    +
    638 return 0;
    +
    639}
    +
    640
    +
    641static int create_dir(const char *path, const char **dir_files)
    +
    642{
    +
    643 int res;
    +
    644 int i;
    +
    645
    +
    646 rmdir(path);
    +
    647 res = mkdir(path, 0755);
    +
    648 if (res == -1) {
    +
    649 PERROR("mkdir");
    +
    650 return -1;
    +
    651 }
    +
    652 res = check_type(path, S_IFDIR);
    +
    653 if (res == -1)
    +
    654 return -1;
    +
    655 res = check_mode(path, 0755);
    +
    656 if (res == -1)
    +
    657 return -1;
    +
    658
    +
    659 for (i = 0; dir_files[i]; i++) {
    +
    660 char fpath[1280];
    +
    661 sprintf(fpath, "%s/%s", path, dir_files[i]);
    +
    662 res = create_file(fpath, "", 0);
    +
    663 if (res == -1) {
    +
    664 cleanup_dir(path, dir_files, 1);
    +
    665 return -1;
    +
    666 }
    +
    667 }
    +
    668 res = check_dir_contents(path, dir_files);
    +
    669 if (res == -1) {
    +
    670 cleanup_dir(path, dir_files, 1);
    +
    671 return -1;
    +
    672 }
    +
    673
    +
    674 return 0;
    +
    675}
    +
    676
    +
    677static int test_truncate(int len)
    +
    678{
    +
    679 const char *data = testdata;
    +
    680 int datalen = testdatalen;
    +
    681 int res;
    +
    682
    +
    683 start_test("truncate(%u)", (int) len);
    +
    684 res = create_testfile(testfile, data, datalen);
    +
    685 if (res == -1)
    +
    686 return -1;
    +
    687
    +
    688 res = truncate(testfile, len);
    +
    689 if (res == -1) {
    +
    690 PERROR("truncate");
    +
    691 return -1;
    +
    692 }
    +
    693 res = check_testfile_size(testfile, len);
    +
    694 if (res == -1)
    +
    695 return -1;
    +
    696
    +
    697 if (len > 0) {
    +
    698 if (len <= datalen) {
    +
    699 res = check_data(testfile, data, 0, len);
    +
    700 if (res == -1)
    +
    701 return -1;
    +
    702 } else {
    +
    703 res = check_data(testfile, data, 0, datalen);
    +
    704 if (res == -1)
    +
    705 return -1;
    +
    706 res = check_data(testfile, zerodata, datalen,
    +
    707 len - datalen);
    +
    708 if (res == -1)
    +
    709 return -1;
    +
    710 }
    +
    711 }
    +
    712 res = unlink(testfile);
    +
    713 if (res == -1) {
    +
    714 PERROR("unlink");
    +
    715 return -1;
    +
    716 }
    +
    717 res = check_nonexist(testfile);
    +
    718 if (res == -1)
    +
    719 return -1;
    +
    720
    +
    721 success();
    +
    722 return 0;
    +
    723}
    +
    724
    +
    725static int test_ftruncate(int len, int mode)
    +
    726{
    +
    727 const char *data = testdata;
    +
    728 int datalen = testdatalen;
    +
    729 int res;
    +
    730 int fd;
    +
    731
    +
    732 start_test("ftruncate(%u) mode: 0%03o", len, mode);
    +
    733 res = create_testfile(testfile, data, datalen);
    +
    734 if (res == -1)
    +
    735 return -1;
    +
    736
    +
    737 fd = open(testfile, O_WRONLY);
    +
    738 if (fd == -1) {
    +
    739 PERROR("open");
    +
    740 return -1;
    +
    741 }
    +
    742
    +
    743 res = fchmod(fd, mode);
    +
    744 if (res == -1) {
    +
    745 PERROR("fchmod");
    +
    746 close(fd);
    +
    747 return -1;
    +
    748 }
    +
    749 res = check_testfile_mode(testfile, mode);
    +
    750 if (res == -1) {
    +
    751 close(fd);
    +
    752 return -1;
    +
    753 }
    +
    754 res = ftruncate(fd, len);
    +
    755 if (res == -1) {
    +
    756 PERROR("ftruncate");
    +
    757 close(fd);
    +
    758 return -1;
    +
    759 }
    +
    760 close(fd);
    +
    761 res = check_testfile_size(testfile, len);
    +
    762 if (res == -1)
    +
    763 return -1;
    +
    764
    +
    765 if (len > 0) {
    +
    766 if (len <= datalen) {
    +
    767 res = check_data(testfile, data, 0, len);
    +
    768 if (res == -1)
    +
    769 return -1;
    +
    770 } else {
    +
    771 res = check_data(testfile, data, 0, datalen);
    +
    772 if (res == -1)
    +
    773 return -1;
    +
    774 res = check_data(testfile, zerodata, datalen,
    +
    775 len - datalen);
    +
    776 if (res == -1)
    +
    777 return -1;
    +
    778 }
    +
    779 }
    +
    780 res = unlink(testfile);
    +
    781 if (res == -1) {
    +
    782 PERROR("unlink");
    +
    783 return -1;
    +
    784 }
    +
    785 res = check_nonexist(testfile);
    +
    786 if (res == -1)
    +
    787 return -1;
    +
    788
    +
    789 success();
    +
    790 return 0;
    +
    791}
    +
    792
    +
    793static int test_seekdir(void)
    +
    794{
    +
    795 int i;
    +
    796 int res;
    +
    797 DIR *dp;
    +
    798 struct dirent *de = NULL;
    +
    799
    +
    800 start_test("seekdir");
    +
    801 res = create_dir(testdir, testdir_files);
    +
    802 if (res == -1)
    +
    803 return res;
    +
    804
    +
    805 dp = opendir(testdir);
    +
    806 if (dp == NULL) {
    +
    807 PERROR("opendir");
    +
    808 return -1;
    +
    809 }
    +
    810
    +
    811 /* Remember dir offsets */
    +
    812 for (i = 0; i < ARRAY_SIZE(seekdir_offsets); i++) {
    +
    813 seekdir_offsets[i] = telldir(dp);
    +
    814 errno = 0;
    +
    815 de = readdir(dp);
    +
    816 if (de == NULL) {
    +
    817 if (errno) {
    +
    818 PERROR("readdir");
    +
    819 goto fail;
    +
    820 }
    +
    821 break;
    +
    822 }
    +
    823 }
    +
    824
    +
    825 /* Walk until the end of directory */
    +
    826 while (de)
    +
    827 de = readdir(dp);
    +
    828
    +
    829 /* Start from the last valid dir offset and seek backwards */
    +
    830 for (i--; i >= 0; i--) {
    +
    831 seekdir(dp, seekdir_offsets[i]);
    +
    832 de = readdir(dp);
    +
    833 if (de == NULL) {
    +
    834 ERROR("Unexpected end of directory after seekdir()");
    +
    835 goto fail;
    +
    836 }
    +
    837 }
    +
    838
    +
    839 closedir(dp);
    +
    840 res = cleanup_dir(testdir, testdir_files, 0);
    +
    841 if (!res)
    +
    842 success();
    +
    843 return res;
    +
    844fail:
    +
    845 closedir(dp);
    +
    846 cleanup_dir(testdir, testdir_files, 1);
    +
    847 return -1;
    +
    848}
    +
    849
    +
    850#ifdef HAVE_COPY_FILE_RANGE
    +
    851static int test_copy_file_range(void)
    +
    852{
    +
    853 const char *data = testdata;
    +
    854 int datalen = testdatalen;
    +
    855 int err = 0;
    +
    856 int res;
    +
    857 int fd_in, fd_out;
    +
    858 off_t pos_in = 0, pos_out = 0;
    +
    859
    +
    860 start_test("copy_file_range");
    +
    861 unlink(testfile);
    +
    862 fd_in = open(testfile, O_CREAT | O_RDWR, 0644);
    +
    863 if (fd_in == -1) {
    +
    864 PERROR("creat");
    +
    865 return -1;
    +
    866 }
    +
    867 res = write(fd_in, data, datalen);
    +
    868 if (res == -1) {
    +
    869 PERROR("write");
    +
    870 close(fd_in);
    +
    871 return -1;
    +
    872 }
    +
    873 if (res != datalen) {
    +
    874 ERROR("write is short: %u instead of %u", res, datalen);
    +
    875 close(fd_in);
    +
    876 return -1;
    +
    877 }
    +
    878
    +
    879 unlink(testfile2);
    +
    880 fd_out = creat(testfile2, 0644);
    +
    881 if (fd_out == -1) {
    +
    882 PERROR("creat");
    +
    883 close(fd_in);
    +
    884 return -1;
    +
    885 }
    +
    886 res = copy_file_range(fd_in, &pos_in, fd_out, &pos_out, datalen, 0);
    +
    887 if (res == -1) {
    +
    888 PERROR("copy_file_range");
    +
    889 close(fd_in);
    +
    890 close(fd_out);
    +
    891 return -1;
    +
    892 }
    +
    893 if (res != datalen) {
    +
    894 ERROR("copy is short: %u instead of %u", res, datalen);
    +
    895 close(fd_in);
    +
    896 close(fd_out);
    +
    897 return -1;
    +
    898 }
    +
    899
    +
    900 res = close(fd_in);
    +
    901 if (res == -1) {
    +
    902 PERROR("close");
    +
    903 close(fd_out);
    +
    904 return -1;
    +
    905 }
    +
    906 res = close(fd_out);
    +
    907 if (res == -1) {
    +
    908 PERROR("close");
    +
    909 return -1;
    +
    910 }
    +
    911
    +
    912 err = check_data(testfile2, data, 0, datalen);
    +
    913
    +
    914 res = unlink(testfile);
    +
    915 if (res == -1) {
    +
    916 PERROR("unlink");
    +
    917 return -1;
    +
    918 }
    +
    919 res = check_nonexist(testfile);
    +
    920 if (res == -1)
    +
    921 return -1;
    +
    922 if (err)
    +
    923 return -1;
    +
    924
    +
    925 res = unlink(testfile2);
    +
    926 if (res == -1) {
    +
    927 PERROR("unlink");
    +
    928 return -1;
    +
    929 }
    +
    930 res = check_nonexist(testfile2);
    +
    931 if (res == -1)
    +
    932 return -1;
    +
    933 if (err)
    +
    934 return -1;
    +
    935
    +
    936 success();
    +
    937 return 0;
    +
    938}
    +
    939#else
    +
    940static int test_copy_file_range(void)
    +
    941{
    +
    942 return 0;
    +
    943}
    +
    944#endif
    +
    945
    +
    946#ifdef HAVE_STATX
    +
    947static int test_statx(void)
    +
    948{
    +
    949 struct statx sb;
    +
    950 char msg[] = "hi";
    +
    951 size_t msg_size = sizeof(msg);
    +
    952 struct timespec tp;
    +
    953 int res;
    +
    954
    +
    955 memset(&sb, 0, sizeof(sb));
    +
    956 unlink(testfile);
    +
    957
    +
    958 start_test("statx");
    +
    959
    +
    960 res = create_testfile(testfile, msg, msg_size);
    +
    961 if (res == -1)
    +
    962 return -1;
    +
    963
    +
    964 res = statx(-1, testfile, AT_EMPTY_PATH,
    +
    965 STATX_BASIC_STATS | STATX_BTIME, &sb);
    +
    966 if (res == -1)
    +
    967 return -1;
    +
    968
    +
    969 if (sb.stx_size != msg_size)
    +
    970 return -1;
    +
    971
    +
    972 clock_gettime(CLOCK_REALTIME, &tp);
    +
    973
    +
    974 if (sb.stx_btime.tv_sec > tp.tv_sec)
    +
    975 return -1;
    +
    976
    +
    977 if (sb.stx_btime.tv_sec == tp.tv_sec &&
    +
    978 sb.stx_btime.tv_nsec >= tp.tv_nsec)
    +
    979 return -1;
    +
    980
    +
    981 unlink(testfile);
    +
    982
    +
    983 success();
    +
    984 return 0;
    +
    985}
    +
    986#else
    +
    987static int test_statx(void)
    +
    988{
    +
    989 return 0;
    +
    990}
    +
    991#endif
    +
    992
    +
    993static int test_utime(void)
    +
    994{
    +
    995 struct utimbuf utm;
    +
    996 time_t atime = 987631200;
    +
    997 time_t mtime = 123116400;
    +
    998 int res;
    +
    999
    +
    1000 start_test("utime");
    +
    1001 res = create_testfile(testfile, NULL, 0);
    +
    1002 if (res == -1)
    +
    1003 return -1;
    +
    1004
    +
    1005 utm.actime = atime;
    +
    1006 utm.modtime = mtime;
    +
    1007 res = utime(testfile, &utm);
    +
    1008 if (res == -1) {
    +
    1009 PERROR("utime");
    +
    1010 return -1;
    +
    1011 }
    +
    1012 res = check_times(testfile, atime, mtime);
    +
    1013 if (res == -1) {
    +
    1014 return -1;
    +
    1015 }
    +
    1016 res = unlink(testfile);
    +
    1017 if (res == -1) {
    +
    1018 PERROR("unlink");
    +
    1019 return -1;
    +
    1020 }
    +
    1021 res = check_nonexist(testfile);
    +
    1022 if (res == -1)
    +
    1023 return -1;
    +
    1024
    +
    1025 success();
    +
    1026 return 0;
    +
    1027}
    +
    1028
    +
    1029static int test_create(void)
    +
    1030{
    +
    1031 const char *data = testdata;
    +
    1032 int datalen = testdatalen;
    +
    1033 int err = 0;
    +
    1034 int res;
    +
    1035 int fd;
    +
    1036
    +
    1037 start_test("create");
    +
    1038 unlink(testfile);
    +
    1039 fd = creat(testfile, 0644);
    +
    1040 if (fd == -1) {
    +
    1041 PERROR("creat");
    +
    1042 return -1;
    +
    1043 }
    +
    1044 res = write(fd, data, datalen);
    +
    1045 if (res == -1) {
    +
    1046 PERROR("write");
    +
    1047 close(fd);
    +
    1048 return -1;
    +
    1049 }
    +
    1050 if (res != datalen) {
    +
    1051 ERROR("write is short: %u instead of %u", res, datalen);
    +
    1052 close(fd);
    +
    1053 return -1;
    +
    1054 }
    +
    1055 res = close(fd);
    +
    1056 if (res == -1) {
    +
    1057 PERROR("close");
    +
    1058 return -1;
    +
    1059 }
    +
    1060 res = check_type(testfile, S_IFREG);
    +
    1061 if (res == -1)
    +
    1062 return -1;
    +
    1063 err += check_mode(testfile, 0644);
    +
    1064 err += check_nlink(testfile, 1);
    +
    1065 err += check_size(testfile, datalen);
    +
    1066 err += check_data(testfile, data, 0, datalen);
    +
    1067 res = unlink(testfile);
    +
    1068 if (res == -1) {
    +
    1069 PERROR("unlink");
    +
    1070 return -1;
    +
    1071 }
    +
    1072 res = check_nonexist(testfile);
    +
    1073 if (res == -1)
    +
    1074 return -1;
    +
    1075 if (err)
    +
    1076 return -1;
    +
    1077
    +
    1078 success();
    +
    1079 return 0;
    +
    1080}
    +
    1081
    +
    1082static int test_create_unlink(void)
    +
    1083{
    +
    1084 const char *data = testdata;
    +
    1085 int datalen = testdatalen;
    +
    1086 int err = 0;
    +
    1087 int res;
    +
    1088 int fd;
    +
    1089
    +
    1090 start_test("create+unlink");
    +
    1091 unlink(testfile);
    +
    1092 fd = open(testfile, O_CREAT | O_RDWR | O_TRUNC, 0644);
    +
    1093 if (fd == -1) {
    +
    1094 PERROR("creat");
    +
    1095 return -1;
    +
    1096 }
    +
    1097 res = unlink(testfile);
    +
    1098 if (res == -1) {
    +
    1099 PERROR("unlink");
    +
    1100 close(fd);
    +
    1101 return -1;
    +
    1102 }
    +
    1103 res = check_nonexist(testfile);
    +
    1104 if (res == -1) {
    +
    1105 close(fd);
    +
    1106 return -1;
    +
    1107 }
    +
    1108 res = write(fd, data, datalen);
    +
    1109 if (res == -1) {
    +
    1110 PERROR("write");
    +
    1111 close(fd);
    +
    1112 return -1;
    +
    1113 }
    +
    1114 if (res != datalen) {
    +
    1115 ERROR("write is short: %u instead of %u", res, datalen);
    +
    1116 close(fd);
    +
    1117 return -1;
    +
    1118 }
    +
    1119 struct stat st = {
    +
    1120 .st_mode = S_IFREG | 0644,
    +
    1121 .st_size = datalen,
    +
    1122 };
    +
    1123 err = fcheck_stat(fd, O_RDWR, &st);
    +
    1124 err += fcheck_data(fd, data, 0, datalen);
    +
    1125 res = close(fd);
    +
    1126 if (res == -1) {
    +
    1127 PERROR("close");
    +
    1128 err--;
    +
    1129 }
    +
    1130 if (err)
    +
    1131 return -1;
    +
    1132
    +
    1133 success();
    +
    1134 return 0;
    +
    1135}
    +
    1136
    +
    1137static int test_mknod(void)
    +
    1138{
    +
    1139 int err = 0;
    +
    1140 int res;
    +
    1141
    +
    1142 start_test("mknod");
    +
    1143 unlink(testfile);
    +
    1144 res = mknod(testfile, 0644, 0);
    +
    1145 if (res == -1) {
    +
    1146 PERROR("mknod");
    +
    1147 return -1;
    +
    1148 }
    +
    1149 res = check_type(testfile, S_IFREG);
    +
    1150 if (res == -1)
    +
    1151 return -1;
    +
    1152 err += check_mode(testfile, 0644);
    +
    1153 err += check_nlink(testfile, 1);
    +
    1154 err += check_size(testfile, 0);
    +
    1155 res = unlink(testfile);
    +
    1156 if (res == -1) {
    +
    1157 PERROR("unlink");
    +
    1158 return -1;
    +
    1159 }
    +
    1160 res = check_nonexist(testfile);
    +
    1161 if (res == -1)
    +
    1162 return -1;
    +
    1163 if (err)
    +
    1164 return -1;
    +
    1165
    +
    1166 success();
    +
    1167 return 0;
    +
    1168}
    +
    1169
    +
    1170#define test_open(exist, flags, mode) do_test_open(exist, flags, #flags, mode)
    +
    1171
    +
    1172static int do_test_open(int exist, int flags, const char *flags_str, int mode)
    +
    1173{
    +
    1174 char buf[4096];
    +
    1175 const char *data = testdata;
    +
    1176 int datalen = testdatalen;
    +
    1177 unsigned currlen = 0;
    +
    1178 int err = 0;
    +
    1179 int res;
    +
    1180 int fd;
    +
    1181 off_t off;
    +
    1182
    +
    1183 start_test("open(%s, %s, 0%03o)", exist ? "+" : "-", flags_str, mode);
    +
    1184 unlink(testfile);
    +
    1185 if (exist) {
    +
    1186 res = create_file(testfile_r, testdata2, testdata2len);
    +
    1187 if (res == -1)
    +
    1188 return -1;
    +
    1189
    +
    1190 currlen = testdata2len;
    +
    1191 }
    +
    1192
    +
    1193 fd = open(testfile, flags, mode);
    +
    1194 if ((flags & O_CREAT) && (flags & O_EXCL) && exist) {
    +
    1195 if (fd != -1) {
    +
    1196 ERROR("open should have failed");
    +
    1197 close(fd);
    +
    1198 return -1;
    +
    1199 } else if (errno == EEXIST)
    +
    1200 goto succ;
    +
    1201 }
    +
    1202 if (!(flags & O_CREAT) && !exist) {
    +
    1203 if (fd != -1) {
    +
    1204 ERROR("open should have failed");
    +
    1205 close(fd);
    +
    1206 return -1;
    +
    1207 } else if (errno == ENOENT)
    +
    1208 goto succ;
    +
    1209 }
    +
    1210 if (fd == -1) {
    +
    1211 PERROR("open");
    +
    1212 return -1;
    +
    1213 }
    +
    1214
    +
    1215 if (flags & O_TRUNC)
    +
    1216 currlen = 0;
    +
    1217
    +
    1218 err += check_type(testfile, S_IFREG);
    +
    1219 if (exist)
    +
    1220 err += check_mode(testfile, 0644);
    +
    1221 else
    +
    1222 err += check_mode(testfile, mode);
    +
    1223 err += check_nlink(testfile, 1);
    +
    1224 err += check_size(testfile, currlen);
    +
    1225 if (exist && !(flags & O_TRUNC) && (mode & S_IRUSR))
    +
    1226 err += check_data(testfile, testdata2, 0, testdata2len);
    +
    1227
    +
    1228 res = write(fd, data, datalen);
    +
    1229 if ((flags & O_ACCMODE) != O_RDONLY) {
    +
    1230 if (res == -1) {
    +
    1231 PERROR("write");
    +
    1232 err --;
    +
    1233 } else if (res != datalen) {
    +
    1234 ERROR("write is short: %u instead of %u", res, datalen);
    +
    1235 err --;
    +
    1236 } else {
    +
    1237 if (datalen > (int) currlen)
    +
    1238 currlen = datalen;
    +
    1239
    +
    1240 err += check_size(testfile, currlen);
    +
    1241
    +
    1242 if (mode & S_IRUSR) {
    +
    1243 err += check_data(testfile, data, 0, datalen);
    +
    1244 if (exist && !(flags & O_TRUNC) &&
    +
    1245 testdata2len > datalen)
    +
    1246 err += check_data(testfile,
    +
    1247 testdata2 + datalen,
    +
    1248 datalen,
    +
    1249 testdata2len - datalen);
    +
    1250 }
    +
    1251 }
    +
    1252 } else {
    +
    1253 if (res != -1) {
    +
    1254 ERROR("write should have failed");
    +
    1255 err --;
    +
    1256 } else if (errno != EBADF) {
    +
    1257 PERROR("write");
    +
    1258 err --;
    +
    1259 }
    +
    1260 }
    +
    1261 off = lseek(fd, SEEK_SET, 0);
    +
    1262 if (off == (off_t) -1) {
    +
    1263 PERROR("lseek");
    +
    1264 err--;
    +
    1265 } else if (off != 0) {
    +
    1266 ERROR("offset should have returned 0");
    +
    1267 err --;
    +
    1268 }
    +
    1269 res = read(fd, buf, sizeof(buf));
    +
    1270 if ((flags & O_ACCMODE) != O_WRONLY) {
    +
    1271 if (res == -1) {
    +
    1272 PERROR("read");
    +
    1273 err--;
    +
    1274 } else {
    +
    1275 int readsize =
    +
    1276 currlen < sizeof(buf) ? currlen : sizeof(buf);
    +
    1277 if (res != readsize) {
    +
    1278 ERROR("read is short: %i instead of %u",
    +
    1279 res, readsize);
    +
    1280 err--;
    +
    1281 } else {
    +
    1282 if ((flags & O_ACCMODE) != O_RDONLY) {
    +
    1283 err += check_buffer(buf, data, datalen);
    +
    1284 if (exist && !(flags & O_TRUNC) &&
    +
    1285 testdata2len > datalen)
    +
    1286 err += check_buffer(buf + datalen,
    +
    1287 testdata2 + datalen,
    +
    1288 testdata2len - datalen);
    +
    1289 } else if (exist)
    +
    1290 err += check_buffer(buf, testdata2,
    +
    1291 testdata2len);
    +
    1292 }
    +
    1293 }
    +
    1294 } else {
    +
    1295 if (res != -1) {
    +
    1296 ERROR("read should have failed");
    +
    1297 err --;
    +
    1298 } else if (errno != EBADF) {
    +
    1299 PERROR("read");
    +
    1300 err --;
    +
    1301 }
    +
    1302 }
    +
    1303
    +
    1304 res = close(fd);
    +
    1305 if (res == -1) {
    +
    1306 PERROR("close");
    +
    1307 return -1;
    +
    1308 }
    +
    1309 res = unlink(testfile);
    +
    1310 if (res == -1) {
    +
    1311 PERROR("unlink");
    +
    1312 return -1;
    +
    1313 }
    +
    1314 res = check_nonexist(testfile);
    +
    1315 if (res == -1)
    +
    1316 return -1;
    +
    1317 res = check_nonexist(testfile_r);
    +
    1318 if (res == -1)
    +
    1319 return -1;
    +
    1320 if (err)
    +
    1321 return -1;
    +
    1322
    +
    1323succ:
    +
    1324 success();
    +
    1325 return 0;
    +
    1326}
    +
    1327
    +
    1328#define test_open_acc(flags, mode, err) \
    +
    1329 do_test_open_acc(flags, #flags, mode, err)
    +
    1330
    +
    1331static int do_test_open_acc(int flags, const char *flags_str, int mode, int err)
    +
    1332{
    +
    1333 const char *data = testdata;
    +
    1334 int datalen = testdatalen;
    +
    1335 int res;
    +
    1336 int fd;
    +
    1337
    +
    1338 start_test("open_acc(%s) mode: 0%03o message: '%s'", flags_str, mode,
    +
    1339 strerror(err));
    +
    1340 unlink(testfile);
    +
    1341 res = create_testfile(testfile, data, datalen);
    +
    1342 if (res == -1)
    +
    1343 return -1;
    +
    1344
    +
    1345 res = chmod(testfile, mode);
    +
    1346 if (res == -1) {
    +
    1347 PERROR("chmod");
    +
    1348 return -1;
    +
    1349 }
    +
    1350
    +
    1351 res = check_testfile_mode(testfile, mode);
    +
    1352 if (res == -1)
    +
    1353 return -1;
    +
    1354
    +
    1355 fd = open(testfile, flags);
    +
    1356 if (fd == -1) {
    +
    1357 if (err != errno) {
    +
    1358 PERROR("open");
    +
    1359 return -1;
    +
    1360 }
    +
    1361 } else {
    +
    1362 if (err) {
    +
    1363 ERROR("open should have failed");
    +
    1364 close(fd);
    +
    1365 return -1;
    +
    1366 }
    +
    1367 close(fd);
    +
    1368 }
    +
    1369
    +
    1370 res = unlink(testfile);
    +
    1371 if (res == -1) {
    +
    1372 PERROR("unlink");
    +
    1373 return -1;
    +
    1374 }
    +
    1375 res = check_nonexist(testfile);
    +
    1376 if (res == -1)
    +
    1377 return -1;
    +
    1378 res = check_nonexist(testfile_r);
    +
    1379 if (res == -1)
    +
    1380 return -1;
    +
    1381
    +
    1382 success();
    +
    1383 return 0;
    +
    1384}
    +
    1385
    +
    1386static int test_symlink(void)
    +
    1387{
    +
    1388 char buf[1024];
    +
    1389 const char *data = testdata;
    +
    1390 int datalen = testdatalen;
    +
    1391 int linklen = strlen(testfile);
    +
    1392 int err = 0;
    +
    1393 int res;
    +
    1394
    +
    1395 start_test("symlink");
    +
    1396 res = create_testfile(testfile, data, datalen);
    +
    1397 if (res == -1)
    +
    1398 return -1;
    +
    1399
    +
    1400 unlink(testfile2);
    +
    1401 res = symlink(testfile, testfile2);
    +
    1402 if (res == -1) {
    +
    1403 PERROR("symlink");
    +
    1404 return -1;
    +
    1405 }
    +
    1406 res = check_type(testfile2, S_IFLNK);
    +
    1407 if (res == -1)
    +
    1408 return -1;
    +
    1409 err += check_mode(testfile2, 0777);
    +
    1410 err += check_nlink(testfile2, 1);
    +
    1411 res = readlink(testfile2, buf, sizeof(buf));
    +
    1412 if (res == -1) {
    +
    1413 PERROR("readlink");
    +
    1414 err--;
    +
    1415 }
    +
    1416 if (res != linklen) {
    +
    1417 ERROR("short readlink: %u instead of %u", res, linklen);
    +
    1418 err--;
    +
    1419 }
    +
    1420 if (memcmp(buf, testfile, linklen) != 0) {
    +
    1421 ERROR("link mismatch");
    +
    1422 err--;
    +
    1423 }
    +
    1424 err += check_size(testfile2, datalen);
    +
    1425 err += check_data(testfile2, data, 0, datalen);
    +
    1426 res = unlink(testfile2);
    +
    1427 if (res == -1) {
    +
    1428 PERROR("unlink");
    +
    1429 return -1;
    +
    1430 }
    +
    1431 res = check_nonexist(testfile2);
    +
    1432 if (res == -1)
    +
    1433 return -1;
    +
    1434 if (err)
    +
    1435 return -1;
    +
    1436
    +
    1437 res = unlink(testfile);
    +
    1438 if (res == -1) {
    +
    1439 PERROR("unlink");
    +
    1440 return -1;
    +
    1441 }
    +
    1442 res = check_nonexist(testfile);
    +
    1443 if (res == -1)
    +
    1444 return -1;
    +
    1445
    +
    1446 success();
    +
    1447 return 0;
    +
    1448}
    +
    1449
    +
    1450static int test_link(void)
    +
    1451{
    +
    1452 const char *data = testdata;
    +
    1453 int datalen = testdatalen;
    +
    1454 int err = 0;
    +
    1455 int res;
    +
    1456
    +
    1457 start_test("link");
    +
    1458 res = create_testfile(testfile, data, datalen);
    +
    1459 if (res == -1)
    +
    1460 return -1;
    +
    1461
    +
    1462 unlink(testfile2);
    +
    1463 res = link(testfile, testfile2);
    +
    1464 if (res == -1) {
    +
    1465 PERROR("link");
    +
    1466 return -1;
    +
    1467 }
    +
    1468 res = check_type(testfile2, S_IFREG);
    +
    1469 if (res == -1)
    +
    1470 return -1;
    +
    1471 err += check_mode(testfile2, 0644);
    +
    1472 err += check_nlink(testfile2, 2);
    +
    1473 err += check_size(testfile2, datalen);
    +
    1474 err += check_data(testfile2, data, 0, datalen);
    +
    1475 res = unlink(testfile);
    +
    1476 if (res == -1) {
    +
    1477 PERROR("unlink");
    +
    1478 return -1;
    +
    1479 }
    +
    1480 res = check_nonexist(testfile);
    +
    1481 if (res == -1)
    +
    1482 return -1;
    +
    1483
    +
    1484 err += check_nlink(testfile2, 1);
    +
    1485 res = unlink(testfile2);
    +
    1486 if (res == -1) {
    +
    1487 PERROR("unlink");
    +
    1488 return -1;
    +
    1489 }
    +
    1490 res = check_nonexist(testfile2);
    +
    1491 if (res == -1)
    +
    1492 return -1;
    +
    1493 if (err)
    +
    1494 return -1;
    +
    1495
    +
    1496 success();
    +
    1497 return 0;
    +
    1498}
    +
    1499
    +
    1500static int test_link2(void)
    +
    1501{
    +
    1502 const char *data = testdata;
    +
    1503 int datalen = testdatalen;
    +
    1504 int err = 0;
    +
    1505 int res;
    +
    1506
    +
    1507 start_test("link-unlink-link");
    +
    1508 res = create_testfile(testfile, data, datalen);
    +
    1509 if (res == -1)
    +
    1510 return -1;
    +
    1511
    +
    1512 unlink(testfile2);
    +
    1513 res = link(testfile, testfile2);
    +
    1514 if (res == -1) {
    +
    1515 PERROR("link");
    +
    1516 return -1;
    +
    1517 }
    +
    1518 res = unlink(testfile);
    +
    1519 if (res == -1) {
    +
    1520 PERROR("unlink");
    +
    1521 return -1;
    +
    1522 }
    +
    1523 res = check_nonexist(testfile);
    +
    1524 if (res == -1)
    +
    1525 return -1;
    +
    1526 res = link(testfile2, testfile);
    +
    1527 if (res == -1) {
    +
    1528 PERROR("link");
    +
    1529 }
    +
    1530 res = check_type(testfile, S_IFREG);
    +
    1531 if (res == -1)
    +
    1532 return -1;
    +
    1533 err += check_mode(testfile, 0644);
    +
    1534 err += check_nlink(testfile, 2);
    +
    1535 err += check_size(testfile, datalen);
    +
    1536 err += check_data(testfile, data, 0, datalen);
    +
    1537
    +
    1538 res = unlink(testfile2);
    +
    1539 if (res == -1) {
    +
    1540 PERROR("unlink");
    +
    1541 return -1;
    +
    1542 }
    +
    1543 err += check_nlink(testfile, 1);
    +
    1544 res = unlink(testfile);
    +
    1545 if (res == -1) {
    +
    1546 PERROR("unlink");
    +
    1547 return -1;
    +
    1548 }
    +
    1549 res = check_nonexist(testfile);
    +
    1550 if (res == -1)
    +
    1551 return -1;
    +
    1552 if (err)
    +
    1553 return -1;
    +
    1554
    +
    1555 success();
    +
    1556 return 0;
    +
    1557}
    +
    1558
    +
    1559static int test_rename_file(void)
    +
    1560{
    +
    1561 const char *data = testdata;
    +
    1562 int datalen = testdatalen;
    +
    1563 int err = 0;
    +
    1564 int res;
    +
    1565
    +
    1566 start_test("rename file");
    +
    1567 res = create_testfile(testfile, data, datalen);
    +
    1568 if (res == -1)
    +
    1569 return -1;
    +
    1570
    +
    1571 unlink(testfile2);
    +
    1572 res = rename(testfile, testfile2);
    +
    1573 if (res == -1) {
    +
    1574 PERROR("rename");
    +
    1575 return -1;
    +
    1576 }
    +
    1577 res = check_nonexist(testfile);
    +
    1578 if (res == -1)
    +
    1579 return -1;
    +
    1580 res = check_type(testfile2, S_IFREG);
    +
    1581 if (res == -1)
    +
    1582 return -1;
    +
    1583 err += check_mode(testfile2, 0644);
    +
    1584 err += check_nlink(testfile2, 1);
    +
    1585 err += check_size(testfile2, datalen);
    +
    1586 err += check_data(testfile2, data, 0, datalen);
    +
    1587 res = unlink(testfile2);
    +
    1588 if (res == -1) {
    +
    1589 PERROR("unlink");
    +
    1590 return -1;
    +
    1591 }
    +
    1592 res = check_nonexist(testfile2);
    +
    1593 if (res == -1)
    +
    1594 return -1;
    +
    1595 if (err)
    +
    1596 return -1;
    +
    1597
    +
    1598 success();
    +
    1599 return 0;
    +
    1600}
    +
    1601
    +
    1602static int test_rename_dir(void)
    +
    1603{
    +
    1604 int err = 0;
    +
    1605 int res;
    +
    1606
    +
    1607 start_test("rename dir");
    +
    1608 res = create_dir(testdir, testdir_files);
    +
    1609 if (res == -1)
    +
    1610 return -1;
    +
    1611
    +
    1612 rmdir(testdir2);
    +
    1613 res = rename(testdir, testdir2);
    +
    1614 if (res == -1) {
    +
    1615 PERROR("rename");
    +
    1616 cleanup_dir(testdir, testdir_files, 1);
    +
    1617 return -1;
    +
    1618 }
    +
    1619 res = check_nonexist(testdir);
    +
    1620 if (res == -1) {
    +
    1621 cleanup_dir(testdir, testdir_files, 1);
    +
    1622 return -1;
    +
    1623 }
    +
    1624 res = check_type(testdir2, S_IFDIR);
    +
    1625 if (res == -1) {
    +
    1626 cleanup_dir(testdir2, testdir_files, 1);
    +
    1627 return -1;
    +
    1628 }
    +
    1629 err += check_mode(testdir2, 0755);
    +
    1630 err += check_dir_contents(testdir2, testdir_files);
    +
    1631 err += cleanup_dir(testdir2, testdir_files, 0);
    +
    1632 res = rmdir(testdir2);
    +
    1633 if (res == -1) {
    +
    1634 PERROR("rmdir");
    +
    1635 return -1;
    +
    1636 }
    +
    1637 res = check_nonexist(testdir2);
    +
    1638 if (res == -1)
    +
    1639 return -1;
    +
    1640 if (err)
    +
    1641 return -1;
    +
    1642
    +
    1643 success();
    +
    1644 return 0;
    +
    1645}
    +
    1646
    +
    1647static int test_rename_dir_loop(void)
    +
    1648{
    +
    1649#define PATH(p) (snprintf(path, sizeof path, "%s/%s", testdir, p), path)
    +
    1650#define PATH2(p) (snprintf(path2, sizeof path2, "%s/%s", testdir, p), path2)
    +
    1651
    +
    1652 char path[1280], path2[1280];
    +
    1653 int err = 0;
    +
    1654 int res;
    +
    1655
    +
    1656 start_test("rename dir loop");
    +
    1657
    +
    1658 res = create_dir(testdir, testdir_files);
    +
    1659 if (res == -1)
    +
    1660 return -1;
    +
    1661
    +
    1662 res = mkdir(PATH("a"), 0755);
    +
    1663 if (res == -1) {
    +
    1664 PERROR("mkdir");
    +
    1665 goto fail;
    +
    1666 }
    +
    1667
    +
    1668 res = rename(PATH("a"), PATH2("a"));
    +
    1669 if (res == -1) {
    +
    1670 PERROR("rename");
    +
    1671 goto fail;
    +
    1672 }
    +
    1673
    +
    1674 errno = 0;
    +
    1675 res = rename(PATH("a"), PATH2("a/b"));
    +
    1676 if (res == 0 || errno != EINVAL) {
    +
    1677 PERROR("rename");
    +
    1678 goto fail;
    +
    1679 }
    +
    1680
    +
    1681 res = mkdir(PATH("a/b"), 0755);
    +
    1682 if (res == -1) {
    +
    1683 PERROR("mkdir");
    +
    1684 goto fail;
    +
    1685 }
    +
    1686
    +
    1687 res = mkdir(PATH("a/b/c"), 0755);
    +
    1688 if (res == -1) {
    +
    1689 PERROR("mkdir");
    +
    1690 goto fail;
    +
    1691 }
    +
    1692
    +
    1693 errno = 0;
    +
    1694 res = rename(PATH("a"), PATH2("a/b/c"));
    +
    1695 if (res == 0 || errno != EINVAL) {
    +
    1696 PERROR("rename");
    +
    1697 goto fail;
    +
    1698 }
    +
    1699
    +
    1700 errno = 0;
    +
    1701 res = rename(PATH("a"), PATH2("a/b/c/a"));
    +
    1702 if (res == 0 || errno != EINVAL) {
    +
    1703 PERROR("rename");
    +
    1704 goto fail;
    +
    1705 }
    +
    1706
    +
    1707 errno = 0;
    +
    1708 res = rename(PATH("a/b/c"), PATH2("a"));
    +
    1709 if (res == 0 || errno != ENOTEMPTY) {
    +
    1710 PERROR("rename");
    +
    1711 goto fail;
    +
    1712 }
    +
    1713
    +
    1714 res = open(PATH("a/foo"), O_CREAT, 0644);
    +
    1715 if (res == -1) {
    +
    1716 PERROR("open");
    +
    1717 goto fail;
    +
    1718 }
    +
    1719 close(res);
    +
    1720
    +
    1721 res = rename(PATH("a/foo"), PATH2("a/bar"));
    +
    1722 if (res == -1) {
    +
    1723 PERROR("rename");
    +
    1724 goto fail;
    +
    1725 }
    +
    1726
    +
    1727 res = rename(PATH("a/bar"), PATH2("a/foo"));
    +
    1728 if (res == -1) {
    +
    1729 PERROR("rename");
    +
    1730 goto fail;
    +
    1731 }
    +
    1732
    +
    1733 res = rename(PATH("a/foo"), PATH2("a/b/bar"));
    +
    1734 if (res == -1) {
    +
    1735 PERROR("rename");
    +
    1736 goto fail;
    +
    1737 }
    +
    1738
    +
    1739 res = rename(PATH("a/b/bar"), PATH2("a/foo"));
    +
    1740 if (res == -1) {
    +
    1741 PERROR("rename");
    +
    1742 goto fail;
    +
    1743 }
    +
    1744
    +
    1745 res = rename(PATH("a/foo"), PATH2("a/b/c/bar"));
    +
    1746 if (res == -1) {
    +
    1747 PERROR("rename");
    +
    1748 goto fail;
    +
    1749 }
    +
    1750
    +
    1751 res = rename(PATH("a/b/c/bar"), PATH2("a/foo"));
    +
    1752 if (res == -1) {
    +
    1753 PERROR("rename");
    +
    1754 goto fail;
    +
    1755 }
    +
    1756
    +
    1757 res = open(PATH("a/bar"), O_CREAT, 0644);
    +
    1758 if (res == -1) {
    +
    1759 PERROR("open");
    +
    1760 goto fail;
    +
    1761 }
    +
    1762 close(res);
    +
    1763
    +
    1764 res = rename(PATH("a/foo"), PATH2("a/bar"));
    +
    1765 if (res == -1) {
    +
    1766 PERROR("rename");
    +
    1767 goto fail;
    +
    1768 }
    +
    1769
    +
    1770 unlink(PATH("a/bar"));
    +
    1771
    +
    1772 res = rename(PATH("a/b"), PATH2("a/d"));
    +
    1773 if (res == -1) {
    +
    1774 PERROR("rename");
    +
    1775 goto fail;
    +
    1776 }
    +
    1777
    +
    1778 res = rename(PATH("a/d"), PATH2("a/b"));
    +
    1779 if (res == -1) {
    +
    1780 PERROR("rename");
    +
    1781 goto fail;
    +
    1782 }
    +
    1783
    +
    1784 res = mkdir(PATH("a/d"), 0755);
    +
    1785 if (res == -1) {
    +
    1786 PERROR("mkdir");
    +
    1787 goto fail;
    +
    1788 }
    +
    1789
    +
    1790 res = rename(PATH("a/b"), PATH2("a/d"));
    +
    1791 if (res == -1) {
    +
    1792 PERROR("rename");
    +
    1793 goto fail;
    +
    1794 }
    +
    1795
    +
    1796 res = rename(PATH("a/d"), PATH2("a/b"));
    +
    1797 if (res == -1) {
    +
    1798 PERROR("rename");
    +
    1799 goto fail;
    +
    1800 }
    +
    1801
    +
    1802 res = mkdir(PATH("a/d"), 0755);
    +
    1803 if (res == -1) {
    +
    1804 PERROR("mkdir");
    +
    1805 goto fail;
    +
    1806 }
    +
    1807
    +
    1808 res = mkdir(PATH("a/d/e"), 0755);
    +
    1809 if (res == -1) {
    +
    1810 PERROR("mkdir");
    +
    1811 goto fail;
    +
    1812 }
    +
    1813
    +
    1814 errno = 0;
    +
    1815 res = rename(PATH("a/b"), PATH2("a/d"));
    +
    1816 if (res == 0 || (errno != ENOTEMPTY && errno != EEXIST)) {
    +
    1817 PERROR("rename");
    +
    1818 goto fail;
    +
    1819 }
    +
    1820
    +
    1821 rmdir(PATH("a/d/e"));
    +
    1822 rmdir(PATH("a/d"));
    +
    1823
    +
    1824 rmdir(PATH("a/b/c"));
    +
    1825 rmdir(PATH("a/b"));
    +
    1826 rmdir(PATH("a"));
    +
    1827
    +
    1828 err += cleanup_dir(testdir, testdir_files, 0);
    +
    1829 res = rmdir(testdir);
    +
    1830 if (res == -1) {
    +
    1831 PERROR("rmdir");
    +
    1832 goto fail;
    +
    1833 }
    +
    1834 res = check_nonexist(testdir);
    +
    1835 if (res == -1)
    +
    1836 return -1;
    +
    1837 if (err)
    +
    1838 return -1;
    +
    1839
    +
    1840 success();
    +
    1841 return 0;
    +
    1842
    +
    1843fail:
    +
    1844 unlink(PATH("a/bar"));
    +
    1845
    +
    1846 rmdir(PATH("a/d/e"));
    +
    1847 rmdir(PATH("a/d"));
    +
    1848
    +
    1849 rmdir(PATH("a/b/c"));
    +
    1850 rmdir(PATH("a/b"));
    +
    1851 rmdir(PATH("a"));
    +
    1852
    +
    1853 cleanup_dir(testdir, testdir_files, 1);
    +
    1854 rmdir(testdir);
    +
    1855
    +
    1856 return -1;
    +
    1857
    +
    1858#undef PATH2
    +
    1859#undef PATH
    +
    1860}
    +
    1861
    +
    1862static int test_mkfifo(void)
    +
    1863{
    +
    1864 int res;
    +
    1865 int err = 0;
    +
    1866
    +
    1867 start_test("mkfifo");
    +
    1868 unlink(testfile);
    +
    1869 res = mkfifo(testfile, 0644);
    +
    1870 if (res == -1) {
    +
    1871 PERROR("mkfifo");
    +
    1872 return -1;
    +
    1873 }
    +
    1874 res = check_type(testfile, S_IFIFO);
    +
    1875 if (res == -1)
    +
    1876 return -1;
    +
    1877 err += check_mode(testfile, 0644);
    +
    1878 err += check_nlink(testfile, 1);
    +
    1879 res = unlink(testfile);
    +
    1880 if (res == -1) {
    +
    1881 PERROR("unlink");
    +
    1882 return -1;
    +
    1883 }
    +
    1884 res = check_nonexist(testfile);
    +
    1885 if (res == -1)
    +
    1886 return -1;
    +
    1887 if (err)
    +
    1888 return -1;
    +
    1889
    +
    1890 success();
    +
    1891 return 0;
    +
    1892}
    +
    1893
    +
    1894static int test_mkdir(void)
    +
    1895{
    +
    1896 int res;
    +
    1897 int err = 0;
    +
    1898 const char *dir_contents[] = {NULL};
    +
    1899
    +
    1900 start_test("mkdir");
    +
    1901 rmdir(testdir);
    +
    1902 res = mkdir(testdir, 0755);
    +
    1903 if (res == -1) {
    +
    1904 PERROR("mkdir");
    +
    1905 return -1;
    +
    1906 }
    +
    1907 res = check_type(testdir, S_IFDIR);
    +
    1908 if (res == -1)
    +
    1909 return -1;
    +
    1910 err += check_mode(testdir, 0755);
    +
    1911 /* Some file systems (like btrfs) don't track link
    +
    1912 count for directories */
    +
    1913 //err += check_nlink(testdir, 2);
    +
    1914 err += check_dir_contents(testdir, dir_contents);
    +
    1915 res = rmdir(testdir);
    +
    1916 if (res == -1) {
    +
    1917 PERROR("rmdir");
    +
    1918 return -1;
    +
    1919 }
    +
    1920 res = check_nonexist(testdir);
    +
    1921 if (res == -1)
    +
    1922 return -1;
    +
    1923 if (err)
    +
    1924 return -1;
    +
    1925
    +
    1926 success();
    +
    1927 return 0;
    +
    1928}
    +
    1929
    +
    1930static int test_socket(void)
    +
    1931{
    +
    1932 struct sockaddr_un su;
    +
    1933 int fd;
    +
    1934 int res;
    +
    1935 int err = 0;
    +
    1936 const size_t test_sock_len = strlen(testsock) + 1;
    +
    1937
    +
    1938 start_test("socket");
    +
    1939 if (test_sock_len > sizeof(su.sun_path)) {
    +
    1940 fprintf(stderr, "Need to shorten mount point by %zu chars\n",
    +
    1941 strlen(testsock) + 1 - sizeof(su.sun_path));
    +
    1942 return -1;
    +
    1943 }
    +
    1944 unlink(testsock);
    +
    1945 fd = socket(AF_UNIX, SOCK_STREAM, 0);
    +
    1946 if (fd < 0) {
    +
    1947 PERROR("socket");
    +
    1948 return -1;
    +
    1949 }
    +
    1950 su.sun_family = AF_UNIX;
    +
    1951
    +
    1952 strncpy(su.sun_path, testsock, test_sock_len);
    +
    1953 su.sun_path[sizeof(su.sun_path) - 1] = '\0';
    +
    1954 res = bind(fd, (struct sockaddr*)&su, sizeof(su));
    +
    1955 if (res == -1) {
    +
    1956 PERROR("bind");
    +
    1957 return -1;
    +
    1958 }
    +
    1959
    +
    1960 res = check_type(testsock, S_IFSOCK);
    +
    1961 if (res == -1) {
    +
    1962 close(fd);
    +
    1963 return -1;
    +
    1964 }
    +
    1965 err += check_nlink(testsock, 1);
    +
    1966 close(fd);
    +
    1967 res = unlink(testsock);
    +
    1968 if (res == -1) {
    +
    1969 PERROR("unlink");
    +
    1970 return -1;
    +
    1971 }
    +
    1972 res = check_nonexist(testsock);
    +
    1973 if (res == -1)
    +
    1974 return -1;
    +
    1975 if (err)
    +
    1976 return -1;
    +
    1977
    +
    1978 success();
    +
    1979 return 0;
    +
    1980}
    +
    1981
    +
    1982#define test_create_ro_dir(flags) \
    +
    1983 do_test_create_ro_dir(flags, #flags)
    +
    1984
    +
    1985static int do_test_create_ro_dir(int flags, const char *flags_str)
    +
    1986{
    +
    1987 int res;
    +
    1988 int err = 0;
    +
    1989 int fd;
    +
    1990
    +
    1991 start_test("open(%s) in read-only directory", flags_str);
    +
    1992 rmdir(testdir);
    +
    1993 res = mkdir(testdir, 0555);
    +
    1994 if (res == -1) {
    +
    1995 PERROR("mkdir");
    +
    1996 return -1;
    +
    1997 }
    +
    1998 fd = open(subfile, flags, 0644);
    +
    1999 if (fd != -1) {
    +
    2000 close(fd);
    +
    2001 unlink(subfile);
    +
    2002 ERROR("open should have failed");
    +
    2003 err--;
    +
    2004 } else {
    +
    2005 res = check_nonexist(subfile);
    +
    2006 if (res == -1)
    +
    2007 err--;
    +
    2008 }
    +
    2009 unlink(subfile);
    +
    2010 res = rmdir(testdir);
    +
    2011 if (res == -1) {
    +
    2012 PERROR("rmdir");
    +
    2013 return -1;
    +
    2014 }
    +
    2015 res = check_nonexist(testdir);
    +
    2016 if (res == -1)
    +
    2017 return -1;
    +
    2018 if (err)
    +
    2019 return -1;
    +
    2020
    +
    2021 success();
    +
    2022 return 0;
    +
    2023}
    +
    2024
    +
    2025#ifndef __FreeBSD__
    +
    2026/* this tests open with O_TMPFILE
    +
    2027 note that this will only work with the fuse low level api
    +
    2028 you will get ENOTSUP with the high level api */
    +
    2029static int test_create_tmpfile(void)
    +
    2030{
    +
    2031 rmdir(testdir);
    +
    2032 int res = mkdir(testdir, 0777);
    +
    2033 if (res)
    +
    2034 return -1;
    +
    2035
    +
    2036 start_test("create tmpfile");
    +
    2037
    +
    2038 int fd = open(testdir, O_TMPFILE | O_RDWR, S_IRUSR | S_IWUSR);
    +
    2039 if(fd == -1) {
    +
    2040 if (errno == ENOTSUP) {
    +
    2041 /* don't bother if we're working on an old kernel
    +
    2042 or on the high level API */
    +
    2043 return 0;
    +
    2044 }
    +
    2045
    +
    2046 PERROR("open O_TMPFILE | O_RDWR");
    +
    2047 return -1;
    +
    2048 }
    +
    2049 close(fd);
    +
    2050
    +
    2051 fd = open(testdir, O_TMPFILE | O_WRONLY | O_EXCL, S_IRUSR | S_IWUSR);
    +
    2052 if(fd == -1){
    +
    2053 PERROR("open with O_TMPFILE | O_WRONLY | O_EXCL");
    +
    2054 return -1;
    +
    2055 };
    +
    2056 close(fd);
    +
    2057
    +
    2058 fd = open(testdir, O_TMPFILE | O_RDONLY, S_IRUSR);
    +
    2059 if (fd != -1) {
    +
    2060 ERROR("open with O_TMPFILE | O_RDONLY succeeded");
    +
    2061 return -1;
    +
    2062 }
    +
    2063
    +
    2064 success();
    +
    2065 return 0;
    +
    2066}
    +
    2067
    +
    2068static int test_create_and_link_tmpfile(void)
    +
    2069{
    +
    2070 /* skip this test for now since the github runner will fail in the linkat call below */
    +
    2071 return 0;
    +
    2072
    +
    2073 rmdir(testdir);
    +
    2074 unlink(testfile);
    +
    2075
    +
    2076 int res = mkdir(testdir, 0777);
    +
    2077 if (res)
    +
    2078 return -1;
    +
    2079
    +
    2080 start_test("create and link tmpfile");
    +
    2081
    +
    2082 int fd = open(testdir, O_TMPFILE | O_RDWR | O_EXCL, S_IRUSR | S_IWUSR);
    +
    2083 if(fd == -1) {
    +
    2084 if (errno == ENOTSUP) {
    +
    2085 /* don't bother if we're working on an old kernel
    +
    2086 or on the high level API */
    +
    2087 return 0;
    +
    2088 }
    +
    2089 PERROR("open with O_TMPFILE | O_RDWR | O_EXCL");
    +
    2090 return -1;
    +
    2091 }
    +
    2092
    +
    2093 if (!linkat(fd, "", AT_FDCWD, testfile, AT_EMPTY_PATH)) {
    +
    2094 ERROR("linkat succeeded on a tmpfile opened with O_EXCL");
    +
    2095 return -1;
    +
    2096 }
    +
    2097 close(fd);
    +
    2098
    +
    2099 fd = open(testdir, O_TMPFILE | O_RDWR, S_IRUSR | S_IWUSR);
    +
    2100 if(fd == -1) {
    +
    2101 PERROR("open O_TMPFILE");
    +
    2102 return -1;
    +
    2103 }
    +
    2104
    +
    2105 if (check_nonexist(testfile)) {
    +
    2106 return -1;
    +
    2107 }
    +
    2108
    +
    2109 if (linkat(fd, "", AT_FDCWD, testfile, AT_EMPTY_PATH)) {
    +
    2110 PERROR("linkat tempfile");
    +
    2111 return -1;
    +
    2112 }
    +
    2113 close(fd);
    +
    2114
    +
    2115 if (check_nlink(testfile, 1)) {
    +
    2116 return -1;
    +
    2117 }
    +
    2118 unlink(testfile);
    +
    2119
    +
    2120 success();
    +
    2121 return 0;
    +
    2122}
    +
    2123#endif
    +
    2124
    +
    2125int main(int argc, char *argv[])
    +
    2126{
    +
    2127 int err = 0;
    +
    2128 int a;
    +
    2129 int is_root;
    +
    2130
    +
    2131 /* Piped stdout is fully buffered, so a killed test loses whatever it
    +
    2132 * had printed about what it was doing.
    +
    2133 */
    +
    2134 setvbuf(stdout, NULL, _IOLBF, 0);
    +
    2135
    +
    2136 umask(0);
    +
    2137 if (argc < 2 || argc > 4) {
    +
    2138 fprintf(stderr, "usage: %s testdir [:realdir] [[-]test#] [-u]\n", argv[0]);
    +
    2139 return 1;
    +
    2140 }
    +
    2141 basepath = argv[1];
    +
    2142 basepath_r = basepath;
    +
    2143 for (a = 2; a < argc; a++) {
    +
    2144 char *endptr;
    +
    2145 char *arg = argv[a];
    +
    2146 if (arg[0] == ':') {
    +
    2147 basepath_r = arg + 1;
    +
    2148 } else {
    +
    2149 if (arg[0] == '-') {
    +
    2150 arg++;
    +
    2151 if (arg[0] == 'u') {
    +
    2152 unlinked_test = 1;
    +
    2153 endptr = arg + 1;
    +
    2154 } else {
    +
    2155 skip_test = strtoul(arg, &endptr, 10);
    +
    2156 }
    +
    2157 } else {
    +
    2158 select_test = strtoul(arg, &endptr, 10);
    +
    2159 }
    +
    2160 if (arg[0] == '\0' || *endptr != '\0') {
    +
    2161 fprintf(stderr, "invalid option: '%s'\n", argv[a]);
    +
    2162 return 1;
    +
    2163 }
    +
    2164 }
    +
    2165 }
    +
    2166 assert(strlen(basepath) < 512);
    +
    2167 assert(strlen(basepath_r) < 512);
    +
    2168 if (basepath[0] != '/') {
    +
    2169 fprintf(stderr, "testdir must be an absolute path\n");
    +
    2170 return 1;
    +
    2171 }
    +
    2172
    +
    2173 sprintf(testfile, "%s/testfile", basepath);
    +
    2174 sprintf(testfile2, "%s/testfile2", basepath);
    +
    2175 sprintf(testdir, "%s/testdir", basepath);
    +
    2176 sprintf(testdir2, "%s/testdir2", basepath);
    +
    2177 sprintf(subfile, "%s/subfile", testdir2);
    +
    2178 sprintf(testsock, "%s/testsock", basepath);
    +
    2179
    +
    2180 sprintf(testfile_r, "%s/testfile", basepath_r);
    +
    2181 sprintf(testfile2_r, "%s/testfile2", basepath_r);
    +
    2182 sprintf(testdir_r, "%s/testdir", basepath_r);
    +
    2183 sprintf(testdir2_r, "%s/testdir2", basepath_r);
    +
    2184 sprintf(subfile_r, "%s/subfile", testdir2_r);
    +
    2185
    +
    2186 is_root = (geteuid() == 0);
    +
    2187
    +
    2188 err += test_create();
    +
    2189 err += test_create_unlink();
    +
    2190 err += test_symlink();
    +
    2191 err += test_link();
    +
    2192 err += test_link2();
    +
    2193 err += test_mknod();
    +
    2194 err += test_mkfifo();
    +
    2195 err += test_mkdir();
    +
    2196 err += test_rename_file();
    +
    2197 err += test_rename_dir();
    +
    2198 err += test_rename_dir_loop();
    +
    2199 err += test_seekdir();
    +
    2200 err += test_socket();
    +
    2201 err += test_utime();
    +
    2202 err += test_truncate(0);
    +
    2203 err += test_truncate(testdatalen / 2);
    +
    2204 err += test_truncate(testdatalen);
    +
    2205 err += test_truncate(testdatalen + 100);
    +
    2206 err += test_ftruncate(0, 0600);
    +
    2207 err += test_ftruncate(testdatalen / 2, 0600);
    +
    2208 err += test_ftruncate(testdatalen, 0600);
    +
    2209 err += test_ftruncate(testdatalen + 100, 0600);
    +
    2210 err += test_ftruncate(0, 0400);
    +
    2211 err += test_ftruncate(0, 0200);
    +
    2212 err += test_ftruncate(0, 0000);
    +
    2213 err += test_open(0, O_RDONLY, 0);
    +
    2214 err += test_open(1, O_RDONLY, 0);
    +
    2215 err += test_open(1, O_RDWR, 0);
    +
    2216 err += test_open(1, O_WRONLY, 0);
    +
    2217 err += test_open(0, O_RDWR | O_CREAT, 0600);
    +
    2218 err += test_open(1, O_RDWR | O_CREAT, 0600);
    +
    2219 err += test_open(0, O_RDWR | O_CREAT | O_TRUNC, 0600);
    +
    2220 err += test_open(1, O_RDWR | O_CREAT | O_TRUNC, 0600);
    +
    2221 err += test_open(0, O_RDONLY | O_CREAT, 0600);
    +
    2222 err += test_open(0, O_RDONLY | O_CREAT, 0400);
    +
    2223 err += test_open(0, O_RDONLY | O_CREAT, 0200);
    +
    2224 err += test_open(0, O_RDONLY | O_CREAT, 0000);
    +
    2225 err += test_open(0, O_WRONLY | O_CREAT, 0600);
    +
    2226 err += test_open(0, O_WRONLY | O_CREAT, 0400);
    +
    2227 err += test_open(0, O_WRONLY | O_CREAT, 0200);
    +
    2228 err += test_open(0, O_WRONLY | O_CREAT, 0000);
    +
    2229 err += test_open(0, O_RDWR | O_CREAT, 0400);
    +
    2230 err += test_open(0, O_RDWR | O_CREAT, 0200);
    +
    2231 err += test_open(0, O_RDWR | O_CREAT, 0000);
    +
    2232 err += test_open(0, O_RDWR | O_CREAT | O_EXCL, 0600);
    +
    2233 err += test_open(1, O_RDWR | O_CREAT | O_EXCL, 0600);
    +
    2234 err += test_open(0, O_RDWR | O_CREAT | O_EXCL, 0000);
    +
    2235 err += test_open(1, O_RDWR | O_CREAT | O_EXCL, 0000);
    +
    2236 err += test_open_acc(O_RDONLY, 0600, 0);
    +
    2237 err += test_open_acc(O_WRONLY, 0600, 0);
    +
    2238 err += test_open_acc(O_RDWR, 0600, 0);
    +
    2239 err += test_open_acc(O_RDONLY, 0400, 0);
    +
    2240 err += test_open_acc(O_WRONLY, 0200, 0);
    +
    2241 if(!is_root) {
    +
    2242 err += test_open_acc(O_RDONLY | O_TRUNC, 0400, EACCES);
    +
    2243 err += test_open_acc(O_WRONLY, 0400, EACCES);
    +
    2244 err += test_open_acc(O_RDWR, 0400, EACCES);
    +
    2245 err += test_open_acc(O_RDONLY, 0200, EACCES);
    +
    2246 err += test_open_acc(O_RDWR, 0200, EACCES);
    +
    2247 err += test_open_acc(O_RDONLY, 0000, EACCES);
    +
    2248 err += test_open_acc(O_WRONLY, 0000, EACCES);
    +
    2249 err += test_open_acc(O_RDWR, 0000, EACCES);
    +
    2250 }
    +
    2251 err += test_create_ro_dir(O_CREAT);
    +
    2252 err += test_create_ro_dir(O_CREAT | O_EXCL);
    +
    2253 err += test_create_ro_dir(O_CREAT | O_WRONLY);
    +
    2254 err += test_create_ro_dir(O_CREAT | O_TRUNC);
    +
    2255 err += test_copy_file_range();
    +
    2256 err += test_statx();
    +
    2257#ifndef __FreeBSD__
    +
    2258 err += test_create_tmpfile();
    +
    2259 err += test_create_and_link_tmpfile();
    +
    2260#endif
    +
    2261
    +
    2262 unlink(testfile2);
    +
    2263 unlink(testsock);
    +
    2264 rmdir(testdir);
    +
    2265 rmdir(testdir2);
    +
    2266
    +
    2267 if (err) {
    +
    2268 fprintf(stderr, "%i tests failed\n", -err);
    +
    2269 return 1;
    +
    2270 }
    +
    2271
    +
    2272 return check_unlinked_testfiles();
    +
    2273}
    +
    + + +
    + + diff --git a/doc/html/test__want__conversion_8c_source.html b/doc/html/test__want__conversion_8c_source.html new file mode 100644 index 0000000..b29e1ee --- /dev/null +++ b/doc/html/test__want__conversion_8c_source.html @@ -0,0 +1,264 @@ + + + + + + + +libfuse: test/test_want_conversion.c Source File + + + + + + + +
    +
    + + + + + + +
    +
    libfuse +
    +
    +
    + + + + + + + + +
    +
    +
    +
    test_want_conversion.c
    +
    +
    +
    1#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 17)
    +
    2
    +
    3#include "util.h"
    +
    4#include "fuse_i.h"
    +
    5#include "fuse_lowlevel.h"
    +
    6#include <stdio.h>
    +
    7#include <assert.h>
    +
    8#include <inttypes.h>
    +
    9#include <stdbool.h>
    +
    10#include <err.h>
    +
    11
    +
    12static void print_conn_info(const char *prefix, struct fuse_conn_info *conn)
    +
    13{
    +
    14 struct fuse_session *se = container_of(conn, struct fuse_session, conn);
    +
    15
    +
    16 printf("%s: want=0x%" PRIx32 " want_ext=0x%" PRIx64
    +
    17 " want_default=0x%" PRIx32 " want_ext_default=0x%" PRIx64 "\n",
    +
    18 prefix, conn->want, conn->want_ext, se->conn_want,
    +
    19 se->conn_want_ext);
    +
    20}
    +
    21
    +
    22static void application_init_old_style(struct fuse_conn_info *conn)
    +
    23{
    +
    24 /* Simulate application init the old style */
    + + +
    27
    +
    28 /*
    +
    29 * Also use new style API, as that might happen through
    +
    30 * fuse_apply_conn_info_opts()
    +
    31 */
    + +
    33}
    +
    34
    +
    35static void application_init_new_style(struct fuse_conn_info *conn)
    +
    36{
    +
    37 /* Simulate application init the new style */
    + + + +
    41}
    +
    42
    +
    43static void test_fuse_fs_init(struct fuse_conn_info *conn, bool new_style)
    +
    44{
    +
    45 /* High-level init */
    + +
    47
    +
    48 if (new_style)
    +
    49 application_init_new_style(conn);
    +
    50 else
    +
    51 application_init_old_style(conn);
    +
    52}
    +
    53
    +
    54static void test_do_init(struct fuse_conn_info *conn, bool new_style)
    +
    55{
    +
    56 /* Initial setup */
    + + + + +
    61 conn->capable = fuse_lower_32_bits(conn->capable_ext);
    +
    62
    + + + +
    66
    +
    67 print_conn_info("Initial state", conn);
    +
    68
    +
    69 int rc;
    +
    70
    +
    71 test_fuse_fs_init(conn, new_style);
    +
    72 print_conn_info("After init", conn);
    +
    73
    + +
    75 assert(rc == 0);
    +
    76
    +
    77 /* Verify all expected flags are set */
    +
    78 assert(!(conn->want_ext & FUSE_CAP_SPLICE_READ));
    +
    79 assert(conn->want_ext & FUSE_CAP_SPLICE_WRITE);
    +
    80 assert(conn->want_ext & FUSE_CAP_SPLICE_MOVE);
    +
    81 assert(conn->want_ext & FUSE_CAP_EXPORT_SUPPORT);
    +
    82 assert(conn->want_ext & FUSE_CAP_ASYNC_READ);
    +
    83 assert(conn->want_ext & FUSE_CAP_IOCTL_DIR);
    +
    84
    +
    85 /* Verify no other flags are set */
    +
    86 assert(conn->want_ext ==
    + + + +
    90
    +
    91 print_conn_info("After init", conn);
    +
    92}
    +
    93
    +
    94static void test_want_conversion_basic(void)
    +
    95{
    +
    96 const struct fuse_lowlevel_ops ops = { 0 };
    +
    97 struct fuse_args args = FUSE_ARGS_INIT(0, NULL);
    +
    98 struct fuse_session *se;
    +
    99 struct fuse_conn_info *conn;
    +
    100
    +
    101 /* Add the program name to arg[0] */
    +
    102 if (fuse_opt_add_arg(&args, "test_signals")) {
    +
    103 fprintf(stderr, "Failed to add argument\n");
    +
    104 errx(1, "Failed to add argument");
    +
    105 }
    +
    106
    +
    107
    +
    108 se = fuse_session_new(&args, &ops, sizeof(ops), NULL);
    +
    109 assert(se);
    +
    110 conn = &se->conn;
    +
    111 printf("\nTesting basic want conversion, old style:\n");
    +
    112 test_do_init(conn, false);
    + +
    114
    +
    115 se = fuse_session_new(&args, &ops, sizeof(ops), NULL);
    +
    116 assert(se);
    +
    117 conn = &se->conn;
    +
    118 printf("\nTesting basic want conversion, new style:\n");
    +
    119 test_do_init(conn, true);
    +
    120 print_conn_info("After init", conn);
    + +
    122
    +
    123 fuse_opt_free_args(&args);
    +
    124
    +
    125}
    +
    126
    +
    127static void test_want_conversion_conflict(void)
    +
    128{
    +
    129 struct fuse_conn_info conn = { 0 };
    +
    130 int rc;
    +
    131
    +
    132 printf("\nTesting want conversion conflict:\n");
    +
    133
    +
    134 /* Test conflicting values */
    +
    135 /* Initialize like fuse_lowlevel.c does */
    + + + +
    139 conn.capable = fuse_lower_32_bits(conn.capable_ext);
    +
    140 conn.want_ext = conn.capable_ext;
    +
    141 conn.want = fuse_lower_32_bits(conn.want_ext);
    +
    142 print_conn_info("Test conflict initial", &conn);
    +
    143
    +
    144 /* Simulate application init modifying capabilities */
    +
    145 conn.want_ext |= FUSE_CAP_ATOMIC_O_TRUNC; /* Add new capability */
    +
    146 conn.want &= ~FUSE_CAP_SPLICE_READ; /* Remove a capability */
    +
    147
    + +
    149 assert(rc == -EINVAL);
    +
    150 print_conn_info("Test conflict after", &conn);
    +
    151
    +
    152 printf("Want conversion conflict test passed\n");
    +
    153}
    +
    154
    +
    155static void test_want_conversion_high_bits(void)
    +
    156{
    +
    157 struct fuse_conn_info conn = { 0 };
    +
    158 int rc;
    +
    159
    +
    160 printf("\nTesting want conversion high bits preservation:\n");
    +
    161
    +
    162 /* Test high bits preservation */
    +
    163 conn.want_ext = (1ULL << 33) | FUSE_CAP_ASYNC_READ;
    +
    164 conn.want = fuse_lower_32_bits(conn.want_ext);
    +
    165 print_conn_info("Test high bits initial", &conn);
    +
    166
    + +
    168 assert(rc == 0);
    +
    169 assert(conn.want_ext == ((1ULL << 33) | FUSE_CAP_ASYNC_READ));
    +
    170 print_conn_info("Test high bits after", &conn);
    +
    171
    +
    172 printf("Want conversion high bits test passed\n");
    +
    173}
    +
    174
    +
    175int main(void)
    +
    176{
    +
    177 test_want_conversion_basic();
    +
    178 test_want_conversion_conflict();
    +
    179 test_want_conversion_high_bits();
    +
    180 return 0;
    +
    181}
    +
    #define FUSE_CAP_IOCTL_DIR
    +
    void fuse_unset_feature_flag(struct fuse_conn_info *conn, uint64_t flag)
    +
    int fuse_convert_to_conn_want_ext(struct fuse_conn_info *conn)
    +
    bool fuse_set_feature_flag(struct fuse_conn_info *conn, uint64_t flag)
    +
    #define FUSE_CAP_SPLICE_READ
    +
    #define FUSE_CAP_ATOMIC_O_TRUNC
    +
    #define FUSE_CAP_ASYNC_READ
    +
    #define FUSE_CAP_SPLICE_WRITE
    +
    #define FUSE_CAP_EXPORT_SUPPORT
    +
    #define FUSE_CAP_POSIX_LOCKS
    +
    #define FUSE_CAP_SPLICE_MOVE
    +
    #define FUSE_CAP_FLOCK_LOCKS
    + +
    void fuse_session_destroy(struct fuse_session *se)
    +
    int fuse_opt_add_arg(struct fuse_args *args, const char *arg)
    Definition fuse_opt.c:55
    +
    void fuse_opt_free_args(struct fuse_args *args)
    Definition fuse_opt.c:34
    +
    #define FUSE_ARGS_INIT(argc, argv)
    Definition fuse_opt.h:123
    + + + +
    uint64_t capable_ext
    +
    uint32_t capable
    +
    uint64_t want_ext
    + +
    + + +
    + + diff --git a/doc/html/test__write__cache_8c_source.html b/doc/html/test__write__cache_8c_source.html new file mode 100644 index 0000000..517af13 --- /dev/null +++ b/doc/html/test__write__cache_8c_source.html @@ -0,0 +1,411 @@ + + + + + + + +libfuse: test/test_write_cache.c Source File + + + + + + + +
    +
    + + + + + + +
    +
    libfuse +
    +
    +
    + + + + + + + + +
    +
    +
    +
    test_write_cache.c
    +
    +
    +
    1/*
    +
    2 FUSE: Filesystem in Userspace
    +
    3 Copyright (C) 2016 Nikolaus Rath <Nikolaus@rath.org>
    +
    4
    +
    5 This program can be distributed under the terms of the GNU GPLv2.
    +
    6 See the file GPL2.txt.
    +
    7*/
    +
    8
    +
    9#define FUSE_USE_VERSION 30
    +
    10
    +
    11/* Not really needed - just to test build with FUSE_USE_VERSION == 30 */
    +
    12#include <fuse.h>
    +
    13
    +
    14#include <fuse_config.h>
    +
    15#include <fuse_lowlevel.h>
    +
    16#include <stdio.h>
    +
    17#include <stdlib.h>
    +
    18#include <string.h>
    +
    19#include <errno.h>
    +
    20#include <fcntl.h>
    +
    21#include <assert.h>
    +
    22#include <stddef.h>
    +
    23#include <unistd.h>
    +
    24#include <sys/stat.h>
    +
    25#include <pthread.h>
    +
    26#include <stdatomic.h>
    +
    27
    +
    28#ifndef __linux__
    +
    29#include <limits.h>
    +
    30#else
    +
    31#include <linux/limits.h>
    +
    32#endif
    +
    33
    +
    34#define FILE_INO 2
    +
    35#define FILE_NAME "write_me"
    +
    36
    +
    37/* Command line parsing */
    +
    38struct options {
    +
    39 int writeback;
    +
    40 int data_size;
    +
    41 int delay_ms;
    +
    42} options = {
    +
    43 .writeback = 0,
    +
    44 .data_size = 2048,
    +
    45 .delay_ms = 0,
    +
    46};
    +
    47
    +
    48#define WRITE_SYSCALLS 64
    +
    49
    +
    50#define OPTION(t, p) { t, offsetof(struct options, p), 1 }
    +
    51static const struct fuse_opt option_spec[] = {
    +
    52 OPTION("writeback_cache", writeback),
    +
    53 OPTION("--data-size=%d", data_size), OPTION("--delay_ms=%d", delay_ms),
    + +
    55};
    +
    56static int got_write;
    +
    57static atomic_int write_cnt;
    +
    58
    +
    59pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
    +
    60pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
    +
    61static int write_start, write_done;
    +
    62
    +
    63static void tfs_init(void *userdata, struct fuse_conn_info *conn)
    +
    64{
    +
    65 (void)userdata;
    +
    66
    +
    67 if (options.writeback) {
    + + +
    70 }
    +
    71}
    +
    72
    +
    73static int tfs_stat(fuse_ino_t ino, struct stat *stbuf)
    +
    74{
    +
    75 stbuf->st_ino = ino;
    +
    76 if (ino == FUSE_ROOT_ID) {
    +
    77 stbuf->st_mode = S_IFDIR | 0755;
    +
    78 stbuf->st_nlink = 1;
    +
    79 }
    +
    80
    +
    81 else if (ino == FILE_INO) {
    +
    82 stbuf->st_mode = S_IFREG | 0222;
    +
    83 stbuf->st_nlink = 1;
    +
    84 stbuf->st_size = 0;
    +
    85 }
    +
    86
    +
    87 else
    +
    88 return -1;
    +
    89
    +
    90 return 0;
    +
    91}
    +
    92
    +
    93static void tfs_lookup(fuse_req_t req, fuse_ino_t parent, const char *name)
    +
    94{
    +
    95 struct fuse_entry_param e;
    +
    96
    +
    97 memset(&e, 0, sizeof(e));
    +
    98
    +
    99 if (parent != FUSE_ROOT_ID)
    +
    100 goto err_out;
    +
    101 else if (strcmp(name, FILE_NAME) == 0)
    +
    102 e.ino = FILE_INO;
    +
    103 else
    +
    104 goto err_out;
    +
    105
    +
    106 if (tfs_stat(e.ino, &e.attr) != 0)
    +
    107 goto err_out;
    +
    108 fuse_reply_entry(req, &e);
    +
    109 return;
    +
    110
    +
    111err_out:
    +
    112 fuse_reply_err(req, ENOENT);
    +
    113}
    +
    114
    +
    115static void tfs_getattr(fuse_req_t req, fuse_ino_t ino,
    +
    116 struct fuse_file_info *fi)
    +
    117{
    +
    118 struct stat stbuf;
    +
    119
    +
    120 (void)fi;
    +
    121
    +
    122 memset(&stbuf, 0, sizeof(stbuf));
    +
    123 if (tfs_stat(ino, &stbuf) != 0)
    +
    124 fuse_reply_err(req, ENOENT);
    +
    125 else
    +
    126 fuse_reply_attr(req, &stbuf, 5);
    +
    127}
    +
    128
    +
    129static void tfs_open(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
    +
    130{
    +
    131 if (ino == FUSE_ROOT_ID)
    +
    132 fuse_reply_err(req, EISDIR);
    +
    133 else {
    +
    134 assert(ino == FILE_INO);
    +
    135 /* Test close(rofd) does not block waiting for pending writes */
    +
    136 fi->noflush = !options.writeback && options.delay_ms &&
    +
    137 (fi->flags & O_ACCMODE) == O_RDONLY;
    +
    138 fuse_reply_open(req, fi);
    +
    139 }
    +
    140}
    +
    141
    +
    142static void tfs_write(fuse_req_t req, fuse_ino_t ino, const char *buf,
    +
    143 size_t size, off_t off, struct fuse_file_info *fi)
    +
    144{
    +
    145 (void)fi;
    +
    146 (void)buf;
    +
    147 (void)off;
    +
    148 size_t expected;
    +
    149
    +
    150 assert(ino == FILE_INO);
    +
    151 expected = options.data_size;
    +
    152 if (options.writeback)
    +
    153 expected *= 2;
    +
    154
    +
    155 write_cnt++;
    +
    156
    +
    157 if (size != expected && !options.writeback)
    +
    158 fprintf(stderr, "ERROR: Expected %zd bytes, got %zd\n!",
    +
    159 expected, size);
    +
    160 else
    +
    161 got_write = 1;
    +
    162
    +
    163 /* Simulate waiting for pending writes */
    +
    164 if (options.delay_ms) {
    +
    165 pthread_mutex_lock(&lock);
    +
    166 write_start = 1;
    +
    167 pthread_cond_signal(&cond);
    +
    168 pthread_mutex_unlock(&lock);
    +
    169
    +
    170 usleep(options.delay_ms * 1000);
    +
    171
    +
    172 pthread_mutex_lock(&lock);
    +
    173 write_done = 1;
    +
    174 pthread_cond_signal(&cond);
    +
    175 pthread_mutex_unlock(&lock);
    +
    176 }
    +
    177
    +
    178 fuse_reply_write(req, size);
    +
    179}
    +
    180
    +
    181static struct fuse_lowlevel_ops tfs_oper = {
    +
    182 .init = tfs_init,
    +
    183 .lookup = tfs_lookup,
    +
    184 .getattr = tfs_getattr,
    +
    185 .open = tfs_open,
    +
    186 .write = tfs_write,
    +
    187};
    +
    188
    +
    189static void *close_rofd(void *data)
    +
    190{
    +
    191 int rofd = (int)(long)data;
    +
    192
    +
    193 /* Wait for first write to start */
    +
    194 pthread_mutex_lock(&lock);
    +
    195 while (!write_start && !write_done)
    +
    196 pthread_cond_wait(&cond, &lock);
    +
    197 pthread_mutex_unlock(&lock);
    +
    198
    +
    199 close(rofd);
    +
    200 printf("rofd closed. write_start: %d write_done: %d\n", write_start,
    +
    201 write_done);
    +
    202
    +
    203 /* First write should not have been completed */
    +
    204 if (write_done)
    +
    205 fprintf(stderr, "ERROR: close(rofd) blocked on write!\n");
    +
    206
    +
    207 return NULL;
    +
    208}
    +
    209
    +
    210static void *run_fs(void *data)
    +
    211{
    +
    212 struct fuse_session *se = (struct fuse_session *)data;
    +
    213
    +
    214 assert(fuse_session_loop(se) == 0);
    +
    215 return NULL;
    +
    216}
    +
    217
    +
    218static void test_fs(char *mountpoint)
    +
    219{
    +
    220 char fname[PATH_MAX];
    +
    221 char *buf;
    +
    222 const size_t iosize = options.data_size;
    +
    223 const size_t dsize = options.data_size * WRITE_SYSCALLS;
    +
    224 int fd, rofd;
    +
    225 pthread_t rofd_thread;
    +
    226 off_t off = 0;
    +
    227
    +
    228 buf = malloc(dsize);
    +
    229 assert(buf != NULL);
    +
    230 assert((fd = open("/dev/urandom", O_RDONLY)) != -1);
    +
    231 assert(read(fd, buf, dsize) == dsize);
    +
    232 close(fd);
    +
    233
    +
    234 assert(snprintf(fname, PATH_MAX, "%s/" FILE_NAME, mountpoint) > 0);
    +
    235 fd = open(fname, O_WRONLY);
    +
    236 if (fd == -1) {
    +
    237 perror(fname);
    +
    238 assert(0);
    +
    239 }
    +
    240
    +
    241 if (options.delay_ms) {
    +
    242 /* Verify that close(rofd) does not block waiting for pending writes */
    +
    243 rofd = open(fname, O_RDONLY);
    +
    244 assert(pthread_create(&rofd_thread, NULL, close_rofd,
    +
    245 (void *)(long)rofd) == 0);
    +
    246 /* Give close_rofd time to start */
    +
    247 usleep(options.delay_ms * 1000);
    +
    248 }
    +
    249
    +
    250 for (int cnt = 0; cnt < WRITE_SYSCALLS; cnt++) {
    +
    251 assert(pwrite(fd, buf + off, iosize, off) == iosize);
    +
    252 off += iosize;
    +
    253 assert(off <= dsize);
    +
    254 }
    +
    255 free(buf);
    +
    256 close(fd);
    +
    257
    +
    258 if (options.delay_ms) {
    +
    259 printf("rwfd closed. write_start: %d write_done: %d\n",
    +
    260 write_start, write_done);
    +
    261 assert(pthread_join(rofd_thread, NULL) == 0);
    +
    262 }
    +
    263}
    +
    264
    +
    265int main(int argc, char *argv[])
    +
    266{
    +
    267 struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
    +
    268 struct fuse_session *se;
    +
    269 struct fuse_cmdline_opts fuse_opts;
    +
    270 pthread_t fs_thread;
    +
    271
    +
    272 assert(fuse_opt_parse(&args, &options, option_spec, NULL) == 0);
    +
    273 assert(fuse_parse_cmdline(&args, &fuse_opts) == 0);
    +
    274#ifndef __FreeBSD__
    +
    275 assert(fuse_opt_add_arg(&args, "-oauto_unmount") == 0);
    +
    276#endif
    +
    277 se = fuse_session_new(&args, &tfs_oper, sizeof(tfs_oper), NULL);
    +
    278 fuse_opt_free_args(&args);
    +
    279 assert(se != NULL);
    +
    280 assert(fuse_set_signal_handlers(se) == 0);
    +
    281 assert(fuse_session_mount(se, fuse_opts.mountpoint) == 0);
    +
    282
    +
    283 /* Start file-system thread */
    +
    284 assert(pthread_create(&fs_thread, NULL, run_fs, (void *)se) == 0);
    +
    285
    +
    286 /* Write test data */
    +
    287 test_fs(fuse_opts.mountpoint);
    +
    288 free(fuse_opts.mountpoint);
    +
    289
    +
    290 /* Stop file system */
    + + +
    293 assert(pthread_join(fs_thread, NULL) == 0);
    +
    294
    +
    295 assert(got_write == 1);
    +
    296
    +
    297 /*
    +
    298 * when writeback cache is enabled, kernel side can merge requests, but
    +
    299 * memory pressure, system 'sync' might trigger data flushes before - flush
    +
    300 * might happen in between write syscalls - merging subpage writes into
    +
    301 * a single page and pages into large fuse requests might or might not work.
    +
    302 * Though we can expect that that at least some (but maybe all) write
    +
    303 * system calls can be merged.
    +
    304 */
    +
    305 if (options.writeback)
    +
    306 assert(write_cnt < WRITE_SYSCALLS);
    +
    307 else
    +
    308 assert(write_cnt == WRITE_SYSCALLS);
    +
    309
    + + +
    312
    +
    313 printf("Test completed successfully.\n");
    +
    314 return 0;
    +
    315}
    +
    316
    + +
    bool fuse_set_feature_flag(struct fuse_conn_info *conn, uint64_t flag)
    +
    int fuse_set_signal_handlers(struct fuse_session *se)
    +
    #define FUSE_CAP_WRITEBACK_CACHE
    +
    bool fuse_get_feature_flag(struct fuse_conn_info *conn, uint64_t flag)
    +
    void fuse_remove_signal_handlers(struct fuse_session *se)
    + +
    void fuse_session_destroy(struct fuse_session *se)
    +
    int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi)
    +
    void fuse_session_exit(struct fuse_session *se)
    +
    int fuse_reply_err(fuse_req_t req, int err)
    +
    struct fuse_req * fuse_req_t
    +
    int fuse_session_loop(struct fuse_session *se)
    Definition fuse_loop.c:19
    +
    int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
    +
    void fuse_session_unmount(struct fuse_session *se)
    +
    int fuse_reply_write(fuse_req_t req, size_t count)
    +
    int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
    +
    uint64_t fuse_ino_t
    +
    int fuse_reply_attr(fuse_req_t req, const struct stat *attr, double attr_timeout)
    +
    int fuse_opt_add_arg(struct fuse_args *args, const char *arg)
    Definition fuse_opt.c:55
    +
    void fuse_opt_free_args(struct fuse_args *args)
    Definition fuse_opt.c:34
    +
    int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
    Definition fuse_opt.c:398
    +
    #define FUSE_ARGS_INIT(argc, argv)
    Definition fuse_opt.h:123
    +
    #define FUSE_OPT_END
    Definition fuse_opt.h:104
    + + +
    char ** argv
    Definition fuse_opt.h:114
    + + +
    +
    fuse_ino_t ino
    + +
    uint32_t noflush
    Definition fuse_common.h:93
    + + + +
    + + +
    + + diff --git a/doc/html/usdt_8h_source.html b/doc/html/usdt_8h_source.html new file mode 100644 index 0000000..186e2f6 --- /dev/null +++ b/doc/html/usdt_8h_source.html @@ -0,0 +1,594 @@ + + + + + + + +libfuse: lib/usdt.h Source File + + + + + + + +
    +
    + + + + + + +
    +
    libfuse +
    +
    +
    + + + + + + + + +
    +
    +
    +
    usdt.h
    +
    +
    +
    1/*
    +
    2 * Copied from https://github.com/libbpf/usdt/
    +
    3 */
    +
    4
    +
    5// SPDX-License-Identifier: BSD-2-Clause
    +
    6/*
    +
    7 * This single-header library defines a collection of variadic macros for
    +
    8 * defining and triggering USDTs (User Statically-Defined Tracepoints):
    +
    9 *
    +
    10 * - For USDTs without associated semaphore:
    +
    11 * USDT(group, name, args...)
    +
    12 *
    +
    13 * - For USDTs with implicit (transparent to the user) semaphore:
    +
    14 * USDT_WITH_SEMA(group, name, args...)
    +
    15 * USDT_IS_ACTIVE(group, name)
    +
    16 *
    +
    17 * - For USDTs with explicit (user-defined and provided) semaphore:
    +
    18 * USDT_WITH_EXPLICIT_SEMA(sema, group, name, args...)
    +
    19 * USDT_SEMA_IS_ACTIVE(sema)
    +
    20 *
    +
    21 * all of which emit a NOP instruction into the instruction stream, and so
    +
    22 * have *zero* overhead for the surrounding code. USDTs are identified by
    +
    23 * a combination of `group` and `name` identifiers, which is used by external
    +
    24 * tracing tooling (tracers) for identifying exact USDTs of interest.
    +
    25 *
    +
    26 * USDTs can have an associated (2-byte) activity counter (USDT semaphore),
    +
    27 * automatically maintained by Linux kernel whenever any correctly written
    +
    28 * BPF-based tracer is attached to the USDT. This USDT semaphore can be used
    +
    29 * to check whether there is a need to do any extra data collection and
    +
    30 * processing for a given USDT (if necessary), and otherwise avoid extra work
    +
    31 * for a common case of USDT not being traced ("active").
    +
    32 *
    +
    33 * See documentation for USDT_WITH_SEMA()/USDT_IS_ACTIVE() or
    +
    34 * USDT_WITH_EXPLICIT_SEMA()/USDT_SEMA_IS_ACTIVE() APIs below for details on
    +
    35 * working with USDTs with implicitly or explicitly associated
    +
    36 * USDT semaphores, respectively.
    +
    37 *
    +
    38 * There is also some additional data recorded into an auxiliary note
    +
    39 * section. The data in the note section describes the operands, in terms of
    +
    40 * size and location, used by tracing tooling to know where to find USDT
    +
    41 * arguments. Each location is encoded as an assembler operand string.
    +
    42 * Tracing tools (bpftrace and BPF-based tracers, systemtap, etc) insert
    +
    43 * breakpoints on top of the nop, and decode the location operand-strings,
    +
    44 * like an assembler, to find the values being passed.
    +
    45 *
    +
    46 * The operand strings are selected by the compiler for each operand.
    +
    47 * They are constrained by inline-assembler codes.The default is:
    +
    48 *
    +
    49 * #define USDT_ARG_CONSTRAINT nor
    +
    50 *
    +
    51 * This is a good default if the operands tend to be integral and
    +
    52 * moderate in number (smaller than number of registers). In other
    +
    53 * cases, the compiler may report "'asm' requires impossible reload" or
    +
    54 * similar. In this case, consider simplifying the macro call (fewer
    +
    55 * and simpler operands), reduce optimization, or override the default
    +
    56 * constraints string via:
    +
    57 *
    +
    58 * #define USDT_ARG_CONSTRAINT g
    +
    59 * #include <usdt.h>
    +
    60 *
    +
    61 * For some historical description of USDT v3 format (the one used by this
    +
    62 * library and generally recognized and assumed by BPF-based tracing tools)
    +
    63 * see [0]. The more formal specification can be found at [1]. Additional
    +
    64 * argument constraints information can be found at [2].
    +
    65 *
    +
    66 * Original SystemTap's sys/sdt.h implementation ([3]) was used as a base for
    +
    67 * this USDT library implementation. Current implementation differs *a lot* in
    +
    68 * terms of exposed user API and general usability, which was the main goal
    +
    69 * and focus of the reimplementation work. Nevertheless, underlying recorded
    +
    70 * USDT definitions are fully binary compatible and any USDT-based tooling
    +
    71 * should work equally well with USDTs defined by either SystemTap's or this
    +
    72 * library's USDT implementation.
    +
    73 *
    +
    74 * [0] https://ecos.sourceware.org/ml/systemtap/2010-q3/msg00145.html
    +
    75 * [1] https://sourceware.org/systemtap/wiki/UserSpaceProbeImplementation
    +
    76 * [2] https://gcc.gnu.org/onlinedocs/gcc/Constraints.html
    +
    77 * [3] https://sourceware.org/git/?p=systemtap.git;a=blob;f=includes/sys/sdt.h
    +
    78 */
    +
    79#ifndef __USDT_H
    +
    80#define __USDT_H
    +
    81
    +
    82/*
    +
    83 * Changelog:
    +
    84 *
    +
    85 * 0.1.0
    +
    86 * -----
    +
    87 * - Initial release
    +
    88 */
    +
    89#define USDT_MAJOR_VERSION 0
    +
    90#define USDT_MINOR_VERSION 1
    +
    91#define USDT_PATCH_VERSION 0
    +
    92
    +
    93/* C++20 and C23 added __VA_OPT__ as a standard replacement for non-standard `##__VA_ARGS__` extension */
    +
    94#if (defined(__STDC_VERSION__) && __STDC_VERSION__ > 201710L) || (defined(__cplusplus) && __cplusplus > 201703L)
    +
    95#define __usdt_va_opt 1
    +
    96#define __usdt_va_args(...) __VA_OPT__(,) __VA_ARGS__
    +
    97#else
    +
    98#define __usdt_va_args(...) , ##__VA_ARGS__
    +
    99#endif
    +
    100
    +
    101/*
    +
    102 * Trigger USDT with `group`:`name` identifier and pass through `args` as its
    +
    103 * arguments. Zero arguments are acceptable as well. No USDT semaphore is
    +
    104 * associated with this USDT.
    +
    105 *
    +
    106 * Such "semaphoreless" USDTs are commonly used when there is no extra data
    +
    107 * collection or processing needed to collect and prepare USDT arguments and
    +
    108 * they are just available in the surrounding code. USDT() macro will just
    +
    109 * record their locations in CPU registers or in memory for tracing tooling to
    +
    110 * be able to access them, if necessary.
    +
    111 */
    +
    112#ifdef __usdt_va_opt
    +
    113#define USDT(group, name, ...) \
    +
    114 __usdt_probe(group, name, __usdt_sema_none, 0 __VA_OPT__(,) __VA_ARGS__)
    +
    115#else
    +
    116#define USDT(group, name, ...) \
    +
    117 __usdt_probe(group, name, __usdt_sema_none, 0, ##__VA_ARGS__)
    +
    118#endif
    +
    119
    +
    120/*
    +
    121 * Trigger USDT with `group`:`name` identifier and pass through `args` as its
    +
    122 * arguments. Zero arguments are acceptable as well. USDT also get an
    +
    123 * implicitly-defined associated USDT semaphore, which will be "activated" by
    +
    124 * tracing tooling and can be used to check whether USDT is being actively
    +
    125 * observed.
    +
    126 *
    +
    127 * USDTs with semaphore are commonly used when there is a need to perform
    +
    128 * additional data collection and processing to prepare USDT arguments, which
    +
    129 * otherwise might not be necessary for the rest of application logic. In such
    +
    130 * case, USDT semaphore can be used to avoid unnecessary extra work. If USDT
    +
    131 * is not traced (which is presumed to be a common situation), the associated
    +
    132 * USDT semaphore is "inactive", and so there is no need to waste resources to
    +
    133 * prepare USDT arguments. Use USDT_IS_ACTIVE(group, name) to check whether
    +
    134 * USDT is "active".
    +
    135 *
    +
    136 * N.B. There is an inherent (albeit short) gap between checking whether USDT
    +
    137 * is active and triggering corresponding USDT, in which external tracer can
    +
    138 * be attached to an USDT and activate USDT semaphore after the activity check.
    +
    139 * If such a race occurs, tracers might miss one USDT execution. Tracers are
    +
    140 * expected to accommodate such possibility and this is expected to not be
    +
    141 * a problem for applications and tracers.
    +
    142 *
    +
    143 * N.B. Implicit USDT semaphore defined by USDT_WITH_SEMA() is contained
    +
    144 * within a single executable or shared library and is not shared outside
    +
    145 * them. I.e., if you use USDT_WITH_SEMA() with the same USDT group and name
    +
    146 * identifier across executable and shared library, it will work and won't
    +
    147 * conflict, per se, but will define independent USDT semaphores, one for each
    +
    148 * shared library/executable in which USDT_WITH_SEMA(group, name) is used.
    +
    149 * That is, if you attach to this USDT in one shared library (or executable),
    +
    150 * then only USDT semaphore within that shared library (or executable) will be
    +
    151 * updated by the kernel, while other libraries (or executable) will not see
    +
    152 * activated USDT semaphore. In short, it's best to use unique USDT group:name
    +
    153 * identifiers across different shared libraries (and, equivalently, between
    +
    154 * executable and shared library). This is advanced consideration and is
    +
    155 * rarely (if ever) seen in practice, but just to avoid surprises this is
    +
    156 * called out here. (Static libraries become a part of final executable, once
    +
    157 * linked by linker, so the above considerations don't apply to them.)
    +
    158 */
    +
    159#ifdef __usdt_va_opt
    +
    160#define USDT_WITH_SEMA(group, name, ...) \
    +
    161 __usdt_probe(group, name, \
    +
    162 __usdt_sema_implicit, __usdt_sema_name(group, name) \
    +
    163 __VA_OPT__(,) __VA_ARGS__)
    +
    164#else
    +
    165#define USDT_WITH_SEMA(group, name, ...) \
    +
    166 __usdt_probe(group, name, \
    +
    167 __usdt_sema_implicit, __usdt_sema_name(group, name), \
    +
    168 ##__VA_ARGS__)
    +
    169#endif
    +
    170
    +
    171struct usdt_sema { volatile unsigned short active; };
    +
    172
    +
    173/*
    +
    174 * Check if USDT with `group`:`name` identifier is "active" (i.e., whether it
    +
    175 * is attached to by external tracing tooling and is actively observed).
    +
    176 *
    +
    177 * This macro can be used to decide whether any additional and potentially
    +
    178 * expensive data collection or processing should be done to pass extra
    +
    179 * information into the given USDT. It is assumed that USDT is triggered with
    +
    180 * USDT_WITH_SEMA() macro which will implicitly define associated USDT
    +
    181 * semaphore. (If one needs more control over USDT semaphore, see
    +
    182 * USDT_DEFINE_SEMA() and USDT_WITH_EXPLICIT_SEMA() macros below.)
    +
    183 *
    +
    184 * N.B. Such checks are necessarily racy and speculative. Between checking
    +
    185 * whether USDT is active and triggering the USDT itself, tracer can be
    +
    186 * detached with no notification. This race should be extremely rare and worst
    +
    187 * case should result in one-time wasted extra data collection and processing.
    +
    188 */
    +
    189#define USDT_IS_ACTIVE(group, name) ({ \
    +
    190 extern struct usdt_sema __usdt_sema_name(group, name) \
    +
    191 __usdt_asm_name(__usdt_sema_name(group, name)); \
    +
    192 __usdt_sema_implicit(__usdt_sema_name(group, name)); \
    +
    193 __usdt_sema_name(group, name).active > 0; \
    +
    194})
    +
    195
    +
    196/*
    +
    197 * APIs for working with user-defined explicit USDT semaphores.
    +
    198 *
    +
    199 * This is a less commonly used advanced API for use cases in which user needs
    +
    200 * an explicit control over (potentially shared across multiple USDTs) USDT
    +
    201 * semaphore instance. This can be used when there is a group of logically
    +
    202 * related USDTs that all need extra data collection and processing whenever
    +
    203 * any of a family of related USDTs are "activated" (i.e., traced). In such
    +
    204 * a case, all such related USDTs will be associated with the same shared USDT
    +
    205 * semaphore defined with USDT_DEFINE_SEMA() and the USDTs themselves will be
    +
    206 * triggered with USDT_WITH_EXPLICIT_SEMA() macros, taking an explicit extra
    +
    207 * USDT semaphore identifier as an extra parameter.
    +
    208 */
    +
    209
    +
    215#define USDT_SEMA(sema) __usdt_sema_##sema
    +
    216
    +
    217/*
    +
    218 * Define storage for user-defined USDT semaphore `sema`.
    +
    219 *
    +
    220 * Should be used only once in non-header source file to let compiler allocate
    +
    221 * space for the semaphore variable. Just like with any other global variable.
    +
    222 *
    +
    223 * This macro can be used anywhere where global variable declaration is
    +
    224 * allowed. Just like with global variable definitions, there should be only
    +
    225 * one definition of user-defined USDT semaphore with given `sema` identifier,
    +
    226 * otherwise compiler or linker will complain about duplicate variable
    +
    227 * definition.
    +
    228 *
    +
    229 * For C++, it is allowed to use USDT_DEFINE_SEMA() both in global namespace
    +
    230 * and inside namespaces (including nested namespaces). Just make sure that
    +
    231 * USDT_DECLARE_SEMA() is placed within the namespace where this semaphore is
    +
    232 * referenced, or any of its parent namespaces, so the C++ language-level
    +
    233 * identifier is visible to the code that needs to reference the semaphore.
    +
    234 * At the lowest layer, USDT semaphores have global naming and visibility
    +
    235 * (they have a corresponding `__usdt_sema_<name>` symbol, which can be linked
    +
    236 * against from C or C++ code, if necessary). To keep it simple, putting
    +
    237 * USDT_DECLARE_SEMA() declarations into global namespaces is the simplest
    +
    238 * no-brainer solution. All these aspects are irrelevant for plain C, because
    +
    239 * C doesn't have namespaces and everything is always in the global namespace.
    +
    240 *
    +
    241 * N.B. Due to USDT metadata being recorded in non-allocatable ELF note
    +
    242 * section, it has limitations when it comes to relocations, which, in
    +
    243 * practice, means that it's not possible to correctly share USDT semaphores
    +
    244 * between main executable and shared libraries, or even between multiple
    +
    245 * shared libraries. USDT semaphore has to be contained to individual shared
    +
    246 * library or executable to avoid unpleasant surprises with half-working USDT
    +
    247 * semaphores. We enforce this by marking semaphore ELF symbols as having
    +
    248 * a hidden visibility. This is quite an advanced use case and consideration
    +
    249 * and for most users this should have no consequences whatsoever.
    +
    250 */
    +
    251#define USDT_DEFINE_SEMA(sema) \
    +
    252 struct usdt_sema __usdt_sema_sec USDT_SEMA(sema) \
    +
    253 __usdt_asm_name(USDT_SEMA(sema)) \
    +
    254 __attribute__((visibility("hidden"))) = { 0 }
    +
    255
    +
    256/*
    +
    257 * Declare extern reference to user-defined USDT semaphore `sema`.
    +
    258 *
    +
    259 * Refers to a variable defined in another compilation unit by
    +
    260 * USDT_DEFINE_SEMA() and allows to use the same USDT semaphore across
    +
    261 * multiple compilation units (i.e., .c and .cpp files).
    +
    262 *
    +
    263 * See USDT_DEFINE_SEMA() notes above for C++ language usage peculiarities.
    +
    264 */
    +
    265#define USDT_DECLARE_SEMA(sema) \
    +
    266 extern struct usdt_sema USDT_SEMA(sema) __usdt_asm_name(USDT_SEMA(sema))
    +
    267
    +
    268/*
    +
    269 * Check if user-defined USDT semaphore `sema` is "active" (i.e., whether it
    +
    270 * is attached to by external tracing tooling and is actively observed).
    +
    271 *
    +
    272 * This macro can be used to decide whether any additional and potentially
    +
    273 * expensive data collection or processing should be done to pass extra
    +
    274 * information into USDT(s) associated with USDT semaphore `sema`.
    +
    275 *
    +
    276 * N.B. Such checks are necessarily racy. Between checking the state of USDT
    +
    277 * semaphore and triggering associated USDT(s), the active tracer might attach
    +
    278 * or detach. This race should be extremely rare and worst case should result
    +
    279 * in one-time missed USDT event or wasted extra data collection and
    +
    280 * processing. USDT-using tracers should be written with this in mind and is
    +
    281 * not a concern of the application defining USDTs with associated semaphore.
    +
    282 */
    +
    283#define USDT_SEMA_IS_ACTIVE(sema) (USDT_SEMA(sema).active > 0)
    +
    284
    +
    285/*
    +
    286 * Invoke USDT specified by `group` and `name` identifiers and associate
    +
    287 * explicitly user-defined semaphore `sema` with it. Pass through `args` as
    +
    288 * USDT arguments. `args` are optional and zero arguments are acceptable.
    +
    289 *
    +
    290 * Semaphore is defined with the help of USDT_DEFINE_SEMA() macro and can be
    +
    291 * checked whether active with USDT_SEMA_IS_ACTIVE().
    +
    292 */
    +
    293#ifdef __usdt_va_opt
    +
    294#define USDT_WITH_EXPLICIT_SEMA(sema, group, name, ...) \
    +
    295 __usdt_probe(group, name, __usdt_sema_explicit, USDT_SEMA(sema), ##__VA_ARGS__)
    +
    296#else
    +
    297#define USDT_WITH_EXPLICIT_SEMA(sema, group, name, ...) \
    +
    298 __usdt_probe(group, name, __usdt_sema_explicit, USDT_SEMA(sema) __VA_OPT__(,) __VA_ARGS__)
    +
    299#endif
    +
    300
    +
    301/*
    +
    302 * Adjustable implementation aspects
    +
    303 */
    +
    304#ifndef USDT_ARG_CONSTRAINT
    +
    305#if defined __powerpc__
    +
    306#define USDT_ARG_CONSTRAINT nZr
    +
    307#elif defined __arm__
    +
    308#define USDT_ARG_CONSTRAINT g
    +
    309#elif defined __loongarch__
    +
    310#define USDT_ARG_CONSTRAINT nmr
    +
    311#else
    +
    312#define USDT_ARG_CONSTRAINT nor
    +
    313#endif
    +
    314#endif /* USDT_ARG_CONSTRAINT */
    +
    315
    +
    316#ifndef USDT_NOP
    +
    317#if defined(__ia64__) || defined(__s390__) || defined(__s390x__)
    +
    318#define USDT_NOP nop 0
    +
    319#else
    +
    320#define USDT_NOP nop
    +
    321#endif
    +
    322#endif /* USDT_NOP */
    +
    323
    +
    324/*
    +
    325 * Implementation details
    +
    326 */
    +
    327/* USDT name for implicitly-defined USDT semaphore, derived from group:name */
    +
    328#define __usdt_sema_name(group, name) __usdt_sema_##group##__##name
    +
    329/* ELF section into which USDT semaphores are put */
    +
    330#define __usdt_sema_sec __attribute__((section(".probes")))
    +
    331
    +
    332#define __usdt_concat(a, b) a ## b
    +
    333#define __usdt_apply(fn, n) __usdt_concat(fn, n)
    +
    334
    +
    335#ifndef __usdt_nth
    +
    336#define __usdt_nth(_, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, N, ...) N
    +
    337#endif
    +
    338
    +
    339#ifndef __usdt_narg
    +
    340#ifdef __usdt_va_opt
    +
    341#define __usdt_narg(...) __usdt_nth(_ __VA_OPT__(,) __VA_ARGS__, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0)
    +
    342#else
    +
    343#define __usdt_narg(...) __usdt_nth(_, ##__VA_ARGS__, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0)
    +
    344#endif
    +
    345#endif /* __usdt_narg */
    +
    346
    +
    347#define __usdt_hash #
    +
    348#define __usdt_str_(x) #x
    +
    349#define __usdt_str(x) __usdt_str_(x)
    +
    350
    +
    351#ifndef __usdt_asm_name
    +
    352#define __usdt_asm_name(name) __asm__(__usdt_str(name))
    +
    353#endif
    +
    354
    +
    355#define __usdt_asm1(a) __usdt_str(a) "\n"
    +
    356#define __usdt_asm2(a,b) __usdt_str(a) "," __usdt_str(b) "\n"
    +
    357#define __usdt_asm3(a,b,c) __usdt_str(a) "," __usdt_str(b) "," __usdt_str(c) "\n"
    +
    358#define __usdt_asm5(a,b,c,d,e) __usdt_str(a) "," __usdt_str(b) "," __usdt_str(c) "," \
    +
    359 __usdt_str(d) "," __usdt_str(e) "\n"
    +
    360
    +
    361#ifdef __LP64__
    +
    362#define __usdt_asm_addr .8byte
    +
    363#else
    +
    364#define __usdt_asm_addr .4byte
    +
    365#endif
    +
    366
    +
    367#define __usdt_asm_strz_(x) __usdt_asm1(.asciz #x)
    +
    368#define __usdt_asm_strz(x) __usdt_asm_strz_(x)
    +
    369#define __usdt_asm_str_(x) __usdt_asm1(.ascii #x)
    +
    370#define __usdt_asm_str(x) __usdt_asm_str_(x)
    +
    371
    +
    372/* "semaphoreless" USDT case */
    +
    373#ifndef __usdt_sema_none
    +
    374#define __usdt_sema_none(sema)
    +
    375#endif
    +
    376
    +
    377/* implicitly defined __usdt_sema__group__name semaphore (using weak symbols) */
    +
    378#ifndef __usdt_sema_implicit
    +
    379#define __usdt_sema_implicit(sema) \
    +
    380 __asm__ __volatile__ ( \
    +
    381 __usdt_asm1(.ifndef sema) \
    +
    382 __usdt_asm3( .pushsection .probes, "aw", "progbits") \
    +
    383 __usdt_asm1( .weak sema) \
    +
    384 __usdt_asm1( .hidden sema) \
    +
    385 __usdt_asm1( .align 2) \
    +
    386 __usdt_asm1(sema:) \
    +
    387 __usdt_asm1( .zero 2) \
    +
    388 __usdt_asm2( .type sema, @object) \
    +
    389 __usdt_asm2( .size sema, 2) \
    +
    390 __usdt_asm1( .popsection) \
    +
    391 __usdt_asm1(.endif) \
    +
    392 );
    +
    393#endif
    +
    394
    +
    395/* externally defined semaphore using USDT_DEFINE_SEMA() and passed explicitly by user */
    +
    396#ifndef __usdt_sema_explicit
    +
    397#define __usdt_sema_explicit(sema) \
    +
    398 __asm__ __volatile__ ("" :: "m" (sema));
    +
    399#endif
    +
    400
    +
    401/* main USDT definition (nop and .note.stapsdt metadata) */
    +
    402#define __usdt_probe(group, name, sema_def, sema, ...) do { \
    +
    403 sema_def(sema) \
    +
    404 __asm__ __volatile__ ( \
    +
    405 __usdt_asm1(990: USDT_NOP) \
    +
    406 __usdt_asm3( .pushsection .note.stapsdt, "", "note") \
    +
    407 __usdt_asm1( .balign 4) \
    +
    408 __usdt_asm3( .4byte 992f-991f,994f-993f,3) \
    +
    409 __usdt_asm1(991: .asciz "stapsdt") \
    +
    410 __usdt_asm1(992: .balign 4) \
    +
    411 __usdt_asm1(993: __usdt_asm_addr 990b) \
    +
    412 __usdt_asm1( __usdt_asm_addr _.stapsdt.base) \
    +
    413 __usdt_asm1( __usdt_asm_addr sema) \
    +
    414 __usdt_asm_strz(group) \
    +
    415 __usdt_asm_strz(name) \
    +
    416 __usdt_asm_args(__VA_ARGS__) \
    +
    417 __usdt_asm1( .ascii "\0") \
    +
    418 __usdt_asm1(994: .balign 4) \
    +
    419 __usdt_asm1( .popsection) \
    +
    420 __usdt_asm1(.ifndef _.stapsdt.base) \
    +
    421 __usdt_asm5( .pushsection .stapsdt.base,"aG","progbits",.stapsdt.base,comdat)\
    +
    422 __usdt_asm1( .weak _.stapsdt.base) \
    +
    423 __usdt_asm1( .hidden _.stapsdt.base) \
    +
    424 __usdt_asm1(_.stapsdt.base:) \
    +
    425 __usdt_asm1( .space 1) \
    +
    426 __usdt_asm2( .size _.stapsdt.base, 1) \
    +
    427 __usdt_asm1( .popsection) \
    +
    428 __usdt_asm1(.endif) \
    +
    429 :: __usdt_asm_ops(__VA_ARGS__) \
    +
    430 ); \
    +
    431} while (0)
    +
    432
    +
    433/*
    +
    434 * NB: gdb PR24541 highlighted an unspecified corner of the sdt.h
    +
    435 * operand note format.
    +
    436 *
    +
    437 * The named register may be a longer or shorter (!) alias for the
    +
    438 * storage where the value in question is found. For example, on
    +
    439 * i386, 64-bit value may be put in register pairs, and a register
    +
    440 * name stored would identify just one of them. Previously, gcc was
    +
    441 * asked to emit the %w[id] (16-bit alias of some registers holding
    +
    442 * operands), even when a wider 32-bit value was used.
    +
    443 *
    +
    444 * Bottom line: the byte-width given before the @ sign governs. If
    +
    445 * there is a mismatch between that width and that of the named
    +
    446 * register, then a sys/sdt.h note consumer may need to employ
    +
    447 * architecture-specific heuristics to figure out where the compiler
    +
    448 * has actually put the complete value.
    +
    449 */
    +
    450#if defined(__powerpc__) || defined(__powerpc64__)
    +
    451#define __usdt_argref(id) %I[id]%[id]
    +
    452#elif defined(__i386__)
    +
    453#define __usdt_argref(id) %k[id] /* gcc.gnu.org/PR80115 sourceware.org/PR24541 */
    +
    454#else
    +
    455#define __usdt_argref(id) %[id]
    +
    456#endif
    +
    457
    +
    458#define __usdt_asm_arg(n) __usdt_asm_str(%c[__usdt_asz##n]) \
    +
    459 __usdt_asm1(.ascii "@") \
    +
    460 __usdt_asm_str(__usdt_argref(__usdt_aval##n))
    +
    461
    +
    462#define __usdt_asm_args0 /* no arguments */
    +
    463#define __usdt_asm_args1 __usdt_asm_arg(1)
    +
    464#define __usdt_asm_args2 __usdt_asm_args1 __usdt_asm1(.ascii " ") __usdt_asm_arg(2)
    +
    465#define __usdt_asm_args3 __usdt_asm_args2 __usdt_asm1(.ascii " ") __usdt_asm_arg(3)
    +
    466#define __usdt_asm_args4 __usdt_asm_args3 __usdt_asm1(.ascii " ") __usdt_asm_arg(4)
    +
    467#define __usdt_asm_args5 __usdt_asm_args4 __usdt_asm1(.ascii " ") __usdt_asm_arg(5)
    +
    468#define __usdt_asm_args6 __usdt_asm_args5 __usdt_asm1(.ascii " ") __usdt_asm_arg(6)
    +
    469#define __usdt_asm_args7 __usdt_asm_args6 __usdt_asm1(.ascii " ") __usdt_asm_arg(7)
    +
    470#define __usdt_asm_args8 __usdt_asm_args7 __usdt_asm1(.ascii " ") __usdt_asm_arg(8)
    +
    471#define __usdt_asm_args9 __usdt_asm_args8 __usdt_asm1(.ascii " ") __usdt_asm_arg(9)
    +
    472#define __usdt_asm_args10 __usdt_asm_args9 __usdt_asm1(.ascii " ") __usdt_asm_arg(10)
    +
    473#define __usdt_asm_args11 __usdt_asm_args10 __usdt_asm1(.ascii " ") __usdt_asm_arg(11)
    +
    474#define __usdt_asm_args12 __usdt_asm_args11 __usdt_asm1(.ascii " ") __usdt_asm_arg(12)
    +
    475#define __usdt_asm_args(...) __usdt_apply(__usdt_asm_args, __usdt_narg(__VA_ARGS__))
    +
    476
    +
    477#define __usdt_is_arr(x) (__builtin_classify_type(x) == 14 || __builtin_classify_type(x) == 5)
    +
    478#define __usdt_arg_size(x) (__usdt_is_arr(x) ? sizeof(void *) : sizeof(x))
    +
    479
    +
    480/*
    +
    481 * We can't use __builtin_choose_expr() in C++, so fall back to table-based
    +
    482 * signedness determination for known types, utilizing templates magic.
    +
    483 */
    +
    484#ifdef __cplusplus
    +
    485
    +
    486#define __usdt_is_signed(x) (!__usdt_is_arr(x) && __usdt_t<__typeof(x)>::is_signed)
    +
    487
    +
    488#include <cstddef>
    +
    489
    +
    490template<typename T> struct __usdt_t { static const bool is_signed = false; };
    +
    491template<typename A> struct __usdt_t<A[]> : public __usdt_t<A *> {};
    +
    492template<typename A, size_t N> struct __usdt_t<A[N]> : public __usdt_t<A *> {};
    +
    493
    +
    494#define __usdt_def_signed(T) \
    +
    495template<> struct __usdt_t<T> { static const bool is_signed = true; }; \
    +
    496template<> struct __usdt_t<const T> { static const bool is_signed = true; }; \
    +
    497template<> struct __usdt_t<volatile T> { static const bool is_signed = true; }; \
    +
    498template<> struct __usdt_t<const volatile T> { static const bool is_signed = true; }
    +
    499#define __usdt_maybe_signed(T) \
    +
    500template<> struct __usdt_t<T> { static const bool is_signed = (T)-1 < (T)1; }; \
    +
    501template<> struct __usdt_t<const T> { static const bool is_signed = (T)-1 < (T)1; }; \
    +
    502template<> struct __usdt_t<volatile T> { static const bool is_signed = (T)-1 < (T)1; }; \
    +
    503template<> struct __usdt_t<const volatile T> { static const bool is_signed = (T)-1 < (T)1; }
    +
    504
    +
    505__usdt_def_signed(signed char);
    +
    506__usdt_def_signed(short);
    +
    507__usdt_def_signed(int);
    +
    508__usdt_def_signed(long);
    +
    509__usdt_def_signed(long long);
    +
    510__usdt_maybe_signed(char);
    +
    511__usdt_maybe_signed(wchar_t);
    +
    512
    +
    513#else /* !__cplusplus */
    +
    514
    +
    515#define __usdt_is_inttype(x) (__builtin_classify_type(x) >= 1 && __builtin_classify_type(x) <= 4)
    +
    516#define __usdt_inttype(x) __typeof(__builtin_choose_expr(__usdt_is_inttype(x), (x), 0U))
    +
    517#define __usdt_is_signed(x) ((__usdt_inttype(x))-1 < (__usdt_inttype(x))1)
    +
    518
    +
    519#endif /* __cplusplus */
    +
    520
    +
    521#define __usdt_asm_op(n, x) \
    +
    522 [__usdt_asz##n] "n" ((__usdt_is_signed(x) ? (int)-1 : 1) * (int)__usdt_arg_size(x)), \
    +
    523 [__usdt_aval##n] __usdt_str(USDT_ARG_CONSTRAINT)(x)
    +
    524
    +
    525#define __usdt_asm_ops0() [__usdt_dummy] "g" (0)
    +
    526#define __usdt_asm_ops1(x) __usdt_asm_op(1, x)
    +
    527#define __usdt_asm_ops2(a,x) __usdt_asm_ops1(a), __usdt_asm_op(2, x)
    +
    528#define __usdt_asm_ops3(a,b,x) __usdt_asm_ops2(a,b), __usdt_asm_op(3, x)
    +
    529#define __usdt_asm_ops4(a,b,c,x) __usdt_asm_ops3(a,b,c), __usdt_asm_op(4, x)
    +
    530#define __usdt_asm_ops5(a,b,c,d,x) __usdt_asm_ops4(a,b,c,d), __usdt_asm_op(5, x)
    +
    531#define __usdt_asm_ops6(a,b,c,d,e,x) __usdt_asm_ops5(a,b,c,d,e), __usdt_asm_op(6, x)
    +
    532#define __usdt_asm_ops7(a,b,c,d,e,f,x) __usdt_asm_ops6(a,b,c,d,e,f), __usdt_asm_op(7, x)
    +
    533#define __usdt_asm_ops8(a,b,c,d,e,f,g,x) __usdt_asm_ops7(a,b,c,d,e,f,g), __usdt_asm_op(8, x)
    +
    534#define __usdt_asm_ops9(a,b,c,d,e,f,g,h,x) __usdt_asm_ops8(a,b,c,d,e,f,g,h), __usdt_asm_op(9, x)
    +
    535#define __usdt_asm_ops10(a,b,c,d,e,f,g,h,i,x) __usdt_asm_ops9(a,b,c,d,e,f,g,h,i), __usdt_asm_op(10, x)
    +
    536#define __usdt_asm_ops11(a,b,c,d,e,f,g,h,i,j,x) __usdt_asm_ops10(a,b,c,d,e,f,g,h,i,j), __usdt_asm_op(11, x)
    +
    537#define __usdt_asm_ops12(a,b,c,d,e,f,g,h,i,j,k,x) __usdt_asm_ops11(a,b,c,d,e,f,g,h,i,j,k), __usdt_asm_op(12, x)
    +
    538#define __usdt_asm_ops(...) __usdt_apply(__usdt_asm_ops, __usdt_narg(__VA_ARGS__))(__VA_ARGS__)
    +
    539
    +
    540#endif /* __USDT_H */
    +
    + + +
    + + diff --git a/doc/html/util_8c_source.html b/doc/html/util_8c_source.html new file mode 100644 index 0000000..5d5c7b3 --- /dev/null +++ b/doc/html/util_8c_source.html @@ -0,0 +1,114 @@ + + + + + + + +libfuse: lib/util.c Source File + + + + + + + +
    +
    + + + + + + +
    +
    libfuse +
    +
    +
    + + + + + + + + +
    +
    +
    +
    util.c
    +
    +
    +
    1
    +
    2#include "fuse_config.h"
    +
    3
    +
    4#ifdef HAVE_PTHREAD_SETNAME_NP
    +
    5#define _GNU_SOURCE
    +
    6#include <pthread.h>
    +
    7#endif
    +
    8
    +
    9#include <errno.h>
    +
    10#include <stdlib.h>
    +
    11#include <errno.h>
    +
    12
    +
    13#ifndef FUSE_USE_VERSION
    +
    14#define FUSE_USE_VERSION (FUSE_MAKE_VERSION(3, 18))
    +
    15#endif
    +
    16
    +
    17#include "util.h"
    +
    18#include "fuse_log.h"
    +
    19#include "fuse_lowlevel.h"
    +
    20#include <stdio.h>
    +
    21
    +
    22int libfuse_strtol(const char *str, long *res)
    +
    23{
    +
    24 char *endptr;
    +
    25 int base = 10;
    +
    26 long val;
    +
    27
    +
    28 errno = 0;
    +
    29
    +
    30 if (!str)
    +
    31 return -EINVAL;
    +
    32
    +
    33 val = strtol(str, &endptr, base);
    +
    34
    +
    35 if (errno)
    +
    36 return -errno;
    +
    37
    +
    38 if (endptr == str || *endptr != '\0')
    +
    39 return -EINVAL;
    +
    40
    +
    41 *res = val;
    +
    42 return 0;
    +
    43}
    +
    44
    +
    45void fuse_set_thread_name(const char *name)
    +
    46{
    +
    47#ifdef HAVE_PTHREAD_SETNAME_NP
    +
    48 pthread_setname_np(pthread_self(), name);
    +
    49#else
    +
    50 (void)name;
    +
    51#endif
    +
    52}
    +
    53
    + + +
    + + +
    + + diff --git a/doc/html/util_8h_source.html b/doc/html/util_8h_source.html new file mode 100644 index 0000000..285712e --- /dev/null +++ b/doc/html/util_8h_source.html @@ -0,0 +1,103 @@ + + + + + + + +libfuse: lib/util.h Source File + + + + + + + +
    +
    + + + + + + +
    +
    libfuse +
    +
    +
    + + + + + + + + +
    +
    +
    +
    util.h
    +
    +
    +
    1#ifndef FUSE_UTIL_H_
    +
    2#define FUSE_UTIL_H_
    +
    3
    +
    4#include <stdint.h>
    +
    5#include <stdbool.h>
    +
    6
    +
    7#define ROUND_UP(val, round_to) (((val) + (round_to - 1)) & ~(round_to - 1))
    +
    8
    +
    9#define likely(x) __builtin_expect(!!(x), 1)
    +
    10#define unlikely(x) __builtin_expect(!!(x), 0)
    +
    11
    +
    12struct fuse_conn_info;
    +
    13
    +
    14int libfuse_strtol(const char *str, long *res);
    +
    15void fuse_set_thread_name(const char *name);
    +
    16
    +
    20static inline uint32_t fuse_lower_32_bits(uint64_t nr)
    +
    21{
    +
    22 return (uint32_t)(nr & 0xffffffff);
    +
    23}
    +
    24
    +
    28static inline uint64_t fuse_higher_32_bits(uint64_t nr)
    +
    29{
    +
    30 return nr & ~0xffffffffULL;
    +
    31}
    +
    32
    +
    33#ifndef FUSE_VAR_UNUSED
    +
    34#define FUSE_VAR_UNUSED __attribute__((__unused__))
    +
    35#endif
    +
    36
    +
    37#define container_of(ptr, type, member) \
    +
    38 ({ \
    +
    39 unsigned long __mptr = (unsigned long)(ptr); \
    +
    40 ((type *)(__mptr - offsetof(type, member))); \
    +
    41 })
    +
    42
    +
    43#if __has_attribute(__fallthrough__)
    +
    44#define fallthrough __attribute__((__fallthrough__))
    +
    45#else
    +
    46#define fallthrough do {} while (0)
    +
    47#endif
    +
    48
    +
    49#endif /* FUSE_UTIL_H_ */
    + +
    + + +
    + + diff --git a/doc/kernel.txt b/doc/kernel.txt new file mode 100644 index 0000000..6e7c7a1 --- /dev/null +++ b/doc/kernel.txt @@ -0,0 +1,380 @@ +Definitions +~~~~~~~~~~~ + +Userspace filesystem: + + A filesystem in which data and metadata are provided by an ordinary + userspace process. The filesystem can be accessed normally through + the kernel interface. + +Filesystem daemon: + + The process(es) providing the data and metadata of the filesystem. + +Non-privileged mount (or user mount): + + A userspace filesystem mounted by a non-privileged (non-root) user. + The filesystem daemon is running with the privileges of the mounting + user. NOTE: this is not the same as mounts allowed with the "user" + option in /etc/fstab, which is not discussed here. + +Filesystem connection: + + A connection between the filesystem daemon and the kernel. The + connection exists until either the daemon dies, or the filesystem is + umounted. Note that detaching (or lazy umounting) the filesystem + does _not_ break the connection, in this case it will exist until + the last reference to the filesystem is released. + +Mount owner: + + The user who does the mounting. + +User: + + The user who is performing filesystem operations. + +What is FUSE? +~~~~~~~~~~~~~ + +FUSE is a userspace filesystem framework. It consists of a kernel +module (fuse.ko), a userspace library (libfuse.*) and a mount utility +(fusermount3). + +One of the most important features of FUSE is allowing secure, +non-privileged mounts. This opens up new possibilities for the use of +filesystems. A good example is sshfs: a secure network filesystem +using the sftp protocol. + +The userspace library and utilities are available from the FUSE +homepage: + + https://github.com/libfuse/libfuse/ + +Filesystem type +~~~~~~~~~~~~~~~ + +The filesystem type given to mount(2) can be one of the following: + +'fuse' + + This is the usual way to mount a FUSE filesystem. The first + argument of the mount system call may contain an arbitrary string, + which is not interpreted by the kernel. + +'fuseblk' + + The filesystem is block device based. The first argument of the + mount system call is interpreted as the name of the device. + +Mount options +~~~~~~~~~~~~~ + +See mount.fuse3(8). + +Control filesystem +~~~~~~~~~~~~~~~~~~ + +There's a control filesystem for FUSE, which can be mounted by: + + mount -t fusectl none /sys/fs/fuse/connections + +Mounting it under the '/sys/fs/fuse/connections' directory makes it +backwards compatible with versions before 2.6.0. + +Under the fuse control filesystem each connection has a directory +named by a unique number. + +For each connection the following files exist within this directory: + + 'waiting' + + The number of requests which are waiting to be transferred to + userspace or being processed by the filesystem daemon. If there is + no filesystem activity and 'waiting' is non-zero, then the + filesystem is hung or deadlocked. + + 'abort' + + Writing anything into this file will abort the filesystem + connection. This means that all waiting requests will be aborted an + error returned for all aborted and new requests. + +Only the owner of the mount may read or write these files. + +Interrupting filesystem operations +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +If a process issuing a FUSE filesystem request is interrupted, the +following will happen: + + 1) If the request is not yet sent to userspace AND the signal is + fatal (SIGKILL or unhandled fatal signal), then the request is + dequeued and returns immediately. + + 2) If the request is not yet sent to userspace AND the signal is not + fatal, then an 'interrupted' flag is set for the request. When + the request has been successfully transferred to userspace and + this flag is set, an INTERRUPT request is queued. + + 3) If the request is already sent to userspace, then an INTERRUPT + request is queued. + +INTERRUPT requests take precedence over other requests, so the +userspace filesystem will receive queued INTERRUPTs before any others. + +The userspace filesystem may ignore the INTERRUPT requests entirely, +or may honor them by sending a reply to the _original_ request, with +the error set to EINTR. + +It is also possible that there's a race between processing the +original request and it's INTERRUPT request. There are two possibilities: + + 1) The INTERRUPT request is processed before the original request is + processed + + 2) The INTERRUPT request is processed after the original request has + been answered + +If the filesystem cannot find the original request, it should wait for +some timeout and/or a number of new requests to arrive, after which it +should reply to the INTERRUPT request with an EAGAIN error. In case +1) the INTERRUPT request will be requeued. In case 2) the INTERRUPT +reply will be ignored. + +Aborting a filesystem connection +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +It is possible to get into certain situations where the filesystem is +not responding. Reasons for this may be: + + a) Broken userspace filesystem implementation + + b) Network connection down + + c) Accidental deadlock + + d) Malicious deadlock + +(For more on c) and d) see later sections) + +In either of these cases it may be useful to abort the connection to +the filesystem. There are several ways to do this: + + - Kill the filesystem daemon. Works in case of a) and b) + + - Kill the filesystem daemon and all users of the filesystem. Works + in all cases except some malicious deadlocks + + - Use forced umount (umount -f). Works in all cases but only if + filesystem is still attached (it hasn't been lazy unmounted) + + - Abort filesystem through the FUSE control filesystem. Most + powerful method, always works. + +How do non-privileged mounts work? +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Since the mount() system call is a privileged operation, a helper +program (fusermount3) is needed, which is installed setuid root. + +The implication of providing non-privileged mounts is that the mount +owner must not be able to use this capability to compromise the +system. Obvious requirements arising from this are: + + A) mount owner should not be able to get elevated privileges with the + help of the mounted filesystem + + B) mount owner should not get illegitimate access to information from + other users' and the super user's processes + + C) mount owner should not be able to induce undesired behavior in + other users' or the super user's processes + +How are requirements fulfilled? +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + A) The mount owner could gain elevated privileges by either: + + 1) creating a filesystem containing a device file, then opening + this device + + 2) creating a filesystem containing a suid or sgid application, + then executing this application + + The solution is not to allow opening device files and ignore + setuid and setgid bits when executing programs. To ensure this + fusermount3 always adds "nosuid" and "nodev" to the mount options + for non-privileged mounts. + + B) If another user is accessing files or directories in the + filesystem, the filesystem daemon serving requests can record the + exact sequence and timing of operations performed. This + information is otherwise inaccessible to the mount owner, so this + counts as an information leak. + + The solution to this problem will be presented in point 2) of C). + + C) There are several ways in which the mount owner can induce + undesired behavior in other users' processes, such as: + + 1) mounting a filesystem over a file or directory which the mount + owner could otherwise not be able to modify (or could only + make limited modifications). + + This is solved in fusermount3, by checking the access + permissions on the mountpoint and only allowing the mount if + the mount owner can do unlimited modification (has write + access to the mountpoint, and mountpoint is not a "sticky" + directory) + + 2) Even if 1) is solved the mount owner can change the behavior + of other users' processes. + + i) It can slow down or indefinitely delay the execution of a + filesystem operation creating a DoS against the user or the + whole system. For example a suid application locking a + system file, and then accessing a file on the mount owner's + filesystem could be stopped, and thus causing the system + file to be locked forever. + + ii) It can present files or directories of unlimited length, or + directory structures of unlimited depth, possibly causing a + system process to eat up diskspace, memory or other + resources, again causing DoS. + + The solution to this as well as B) is not to allow processes + to access the filesystem, which could otherwise not be + monitored or manipulated by the mount owner. Since if the + mount owner can ptrace a process, it can do all of the above + without using a FUSE mount, the same criteria as used in + ptrace can be used to check if a process is allowed to access + the filesystem or not. + + Note that the ptrace check is not strictly necessary to + prevent B/2/i, it is enough to check if mount owner has enough + privilege to send signal to the process accessing the + filesystem, since SIGSTOP can be used to get a similar effect. + +I think these limitations are unacceptable? +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +If a sysadmin trusts the users enough, or can ensure through other +measures, that system processes will never enter non-privileged +mounts, it can relax the last limitation with a "user_allow_other" +config option. If this config option is set, the mounting user can +add the "allow_other" mount option which disables the check for other +users' processes. + +Kernel - userspace interface +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The following diagram shows how a filesystem operation (in this +example unlink) is performed in FUSE. + +NOTE: everything in this description is greatly simplified + + | "rm /mnt/fuse/file" | FUSE filesystem daemon + | | + | | >sys_read() + | | >fuse_dev_read() + | | >request_wait() + | | [sleep on fc->waitq] + | | + | >sys_unlink() | + | >fuse_unlink() | + | [get request from | + | fc->unused_list] | + | >request_send() | + | [queue req on fc->pending] | + | [wake up fc->waitq] | [woken up] + | >request_wait_answer() | + | [sleep on req->waitq] | + | | pending] + | | [copy req to read buffer] + | | [add req to fc->processing] + | | sys_write() + | | >fuse_dev_write() + | | [look up req in fc->processing] + | | [remove from fc->processing] + | | [copy write buffer to req] + | [woken up] | [wake up req->waitq] + | | unused_list] | + | sys_unlink("/mnt/fuse/file") | + | [acquire inode semaphore | + | for "file"] | + | >fuse_unlink() | + | [sleep on req->waitq] | + | | sys_unlink("/mnt/fuse/file") + | | [acquire inode semaphore + | | for "file"] + | | *DEADLOCK* + +The solution for this is to allow the filesystem to be aborted. + +Scenario 2 - Tricky deadlock +---------------------------- + +This one needs a carefully crafted filesystem. It's a variation on +the above, only the call back to the filesystem is not explicit, +but is caused by a pagefault. + + | Kamikaze filesystem thread 1 | Kamikaze filesystem thread 2 + | | + | [fd = open("/mnt/fuse/file")] | [request served normally] + | [mmap fd to 'addr'] | + | [close fd] | [FLUSH triggers 'magic' flag] + | [read a byte from addr] | + | >do_page_fault() | + | [find or create page] | + | [lock page] | + | >fuse_readpage() | + | [queue READ request] | + | [sleep on req->waitq] | + | | [read request to buffer] + | | [create reply header before addr] + | | >sys_write(addr - headerlength) + | | >fuse_dev_write() + | | [look up req in fc->processing] + | | [remove from fc->processing] + | | [copy write buffer to req] + | | >do_page_fault() + | | [find or create page] + | | [lock page] + | | * DEADLOCK * + +Solution is basically the same as above. + +An additional problem is that while the write buffer is being copied +to the request, the request must not be interrupted/aborted. This is +because the destination address of the copy may not be valid after the +request has returned. + +This is solved with doing the copy atomically, and allowing abort +while the page(s) belonging to the write buffer are faulted with +get_user_pages(). The 'req->locked' flag indicates when the copy is +taking place, and abort is delayed until this flag is unset. diff --git a/doc/libfuse-operations.txt b/doc/libfuse-operations.txt new file mode 100644 index 0000000..457ff2c --- /dev/null +++ b/doc/libfuse-operations.txt @@ -0,0 +1,426 @@ +List of libfuse operations with their in/out arguments, created with +help of chatgpt. As of kernel 6.18 (protocol 7.45). The list +was only partly human verified - use with care. + +1. FUSE_LOOKUP (1) + - in_args[0]: Variable (up to PATH_MAX for the file name) + - in_args[1]: Not used + - in_args[2]: Not used + - out_args[0]: Size of fuse_entry_out (typically 128 bytes) + - out_args[1]: Not used + - out_args[2]: Not used + +2. FUSE_FORGET (2) + - in_args[0]: Size of fuse_forget_in (typically 8 bytes) + - in_args[1]: Not used + - in_args[2]: Not used + - out_args[0]: Not used + - out_args[1]: Not used + - out_args[2]: Not used + +3. FUSE_GETATTR (3) + - in_args[0]: Size of fuse_getattr_in (16 bytes) + - in_args[1]: Not used + - in_args[2]: Not used + - out_args[0]: Size of fuse_attr_out (typically 96 bytes) + - out_args[1]: Not used + - out_args[2]: Not used + +4. FUSE_SETATTR (4) + - in_args[0]: Size of fuse_setattr_in (typically 32 bytes) + - in_args[1]: Not used + - in_args[2]: Not used + - out_args[0]: Size of fuse_attr_out (typically 96 bytes) + - out_args[1]: Not used + - out_args[2]: Not used + +5. FUSE_READLINK (5) + - in_args[0]: Not used + - in_args[1]: Not used + - in_args[2]: Not used + - out_args[0]: Variable (size of the link target path) + - out_args[1]: Not used + - out_args[2]: Not used + +6. FUSE_SYMLINK (6) + - in_args[0]: Variable (new link file name, up to PATH_MAX) + - in_args[1]: Variable (target path for the symlink, up to PATH_MAX) + - in_args[2]: Not used + - out_args[0]: Size of fuse_entry_out (typically 128 bytes) + - out_args[1]: Not used + - out_args[2]: Not used + +7. Unused (7) + +8. FUSE_MKNOD (8) + - in_args[0]: Size of fuse_mknod_in (24 bytes) + - in_args[1]: Variable (file name, up to PATH_MAX) + - in_args[2]: Not used + - out_args[0]: Size of fuse_entry_out (typically 128 bytes) + - out_args[1]: Not used + - out_args[2]: Not used + +9. FUSE_MKDIR (9) + - in_args[0]: Size of fuse_mkdir_in (16 bytes) + - in_args[1]: Variable (directory name, up to PATH_MAX) + - in_args[2]: Not used + - out_args[0]: Size of fuse_entry_out (typically 128 bytes) + - out_args[1]: Not used + - out_args[2]: Not used + +10. FUSE_UNLINK (10) + - in_args[0]: Variable (file name, up to PATH_MAX) + - in_args[1]: Not used + - in_args[2]: Not used + - out_args[0]: Not used + - out_args[1]: Not used + - out_args[2]: Not used + +11. FUSE_RMDIR (11) + - in_args[0]: Variable (directory name, up to PATH_MAX) + - in_args[1]: Not used + - in_args[2]: Not used + - out_args[0]: Not used + - out_args[1]: Not used + - out_args[2]: Not used + +12. FUSE_RENAME (12) + - in_args[0]: Size of fuse_rename_in (24 bytes) + - in_args[1]: Variable (old path, up to PATH_MAX) + - in_args[2]: Variable (new path, up to PATH_MAX) + - out_args[0]: Not used + - out_args[1]: Not used + - out_args[2]: Not used + +13. FUSE_LINK (13) + - in_args[0]: Size of fuse_link_in (16 bytes) + - in_args[1]: Variable (new link path, up to PATH_MAX) + - in_args[2]: Not used + - out_args[0]: Size of fuse_entry_out (typically 128 bytes) + - out_args[1]: Not used + - out_args[2]: Not used + +14. FUSE_OPEN (14) + - in_args[0]: Size of fuse_open_in (8 bytes) + - in_args[1]: Not used + - in_args[2]: Not used + - out_args[0]: Size of fuse_open_out (24 bytes) + - out_args[1]: Not used + - out_args[2]: Not used + +15. FUSE_READ (15) + - in_args[0]: Size of fuse_read_in (32 bytes) + - in_args[1]: Not used + - in_args[2]: Not used + - out_args[0]: Variable (data read from the file) + - out_args[1]: Not used + - out_args[2]: Not used + +16. FUSE_WRITE (16) + - in_args[0]: Size of fuse_write_in (32 bytes) + - in_args[1]: Variable (data to write) + - in_args[2]: Not used + - out_args[0]: Size of fuse_write_out (24 bytes) + - out_args[1]: Not used + - out_args[2]: Not used + +17. FUSE_STATFS (17) + - in_args[0]: Not used + - in_args[1]: Not used + - in_args[2]: Not used + - out_args[0]: Size of fuse_statfs_out (typically 96 bytes) + - out_args[1]: Not used + - out_args[2]: Not used + +18. FUSE_RELEASE (18) + - in_args[0]: Size of fuse_release_in (16 bytes) + - in_args[1]: Not used + - in_args[2]: Not used + - out_args[0]: Not used + - out_args[1]: Not used + - out_args[2]: Not used + +19. Unused (19) + +20. FUSE_FSYNC (20) + - in_args[0]: Size of fuse_fsync_in (16 bytes) + - in_args[1]: Not used + - in_args[2]: Not used + - out_args[0]: Not used + - out_args[1]: Not used + - out_args[2]: Not used + +21. FUSE_SETXATTR (21) + - in_args[0]: Size of fuse_setxattr_in (24 bytes) + - in_args[1]: Variable (name of attribute, up to 255 bytes) + - in_args[2]: Variable (value of attribute) + - out_args[0]: Not used + - out_args[1]: Not used + - out_args[2]: Not used + - When FUSE_SETXATTR_EXT is not set: Size of fuse_setxattr_in is 16 bytes (FUSE_COMPAT_SETXATTR_IN_SIZE). + +22. FUSE_GETXATTR (22) + - in_args[0]: Size of fuse_getxattr_in (24 bytes) + - in_args[1]: Variable (name of attribute, up to 255 bytes) + - in_args[2]: Not used + - out_args[0]: Variable (value of the attribute) + - out_args[1]: Not used + - out_args[2]: Not used + +23. FUSE_LISTXATTR (23) + - in_args[0]: Size of fuse_getxattr_in (24 bytes) + - in_args[1]: Not used + - in_args[2]: Not used + - out_args[0]: Variable (list of attribute names, each up to 255 bytes) + - out_args[1]: Not used + - out_args[2]: Not used + +24. FUSE_REMOVEXATTR (24) + - in_args[0]: Variable (name of attribute, up to 255 bytes) + - in_args[1]: Not used + - in_args[2]: Not used + - out_args[0]: Not used + - out_args[1]: Not used + - out_args[2]: Not used + +25. FUSE_FLUSH (25) + - in_args[0]: Size of fuse_flush_in (16 bytes) + - in_args[1]: Not used + - in_args[2]: Not used + - out_args[0]: Not used + - out_args[1]: Not used + - out_args[2]: Not used + +26. FUSE_INIT (26) + - in_args[0]: Size of fuse_init_in (64 bytes) + - in_args[1]: Not used + - in_args[2]: Not used + - out_args[0]: Size of fuse_init_out (typically 80 bytes) + - out_args[1]: Not used + - out_args[2]: Not used + - Flags: + - FUSE_SECURITY_CTX + - FUSE_CREATE_SUPP_GROUP + - FUSE_SETXATTR_EXT + - FUSE_COMPAT_SETXATTR_IN_SIZE + - Additional flags may include FUSE_ASYNC_READ, FUSE_POSIX_LOCKS, FUSE_FILE_OPS, etc. + +27. FUSE_OPENDIR (27) + - in_args[0]: Size of fuse_open_in (8 bytes) + - in_args[1]: Not used + - in_args[2]: Not used + - out_args[0]: Size of fuse_open_out (24 bytes) + - out_args[1]: Not used + - out_args[2]: Not used + +28. FUSE_READDIR (28) + - in_args[0]: Size of fuse_read_in (32 bytes) + - in_args[1]: Not used + - in_args[2]: Not used + - out_args[0]: Variable (directory entries) + - out_args[1]: Not used + - out_args[2]: Not used + +29. FUSE_RELEASEDIR (29) + - in_args[0]: Size of fuse_release_in (16 bytes) + - in_args[1]: Not used + - in_args[2]: Not used + - out_args[0]: Not used + - out_args[1]: Not used + - out_args[2]: Not used + +30. FUSE_FSYNCDIR (30) + - in_args[0]: Size of fuse_fsync_in (16 bytes) + - in_args[1]: Not used + - in_args[2]: Not used + - out_args[0]: Not used + - out_args[1]: Not used + - out_args[2]: Not used + +31. FUSE_GETLK (31) + - in_args[0]: Size of fuse_lk_in (32 bytes) + - in_args[1]: Not used + - in_args[2]: Not used + - out_args[0]: Size of fuse_lk_out (typically 32 bytes) + - out_args[1]: Not used + - out_args[2]: Not used + +32. FUSE_SETLK (32) + - in_args[0]: Size of fuse_lk_in (32 bytes) + - in_args[1]: Not used + - in_args[2]: Not used + - out_args[0]: Not used + - out_args[1]: Not used + - out_args[2]: Not used + +33. FUSE_SETLKW (33) + - in_args[0]: Size of fuse_lk_in (32 bytes) + - in_args[1]: Not used + - in_args[2]: Not used + - out_args[0]: Not used + - out_args[1]: Not used + - out_args[2]: Not used + +34. FUSE_ACCESS (34) + - in_args[0]: Size of fuse_access_in (16 bytes) + - in_args[1]: Not used + - in_args[2]: Not used + - out_args[0]: Not used + - out_args[1]: Not used + - out_args[2]: Not used + +35. FUSE_CREATE (35) + - in_args[0]: Size of fuse_create_in (16 bytes) + - in_args[1]: Variable (file name, up to PATH_MAX) + - in_args[2]: Not used + - out_args[0]: Size of fuse_entry_out (typically 128 bytes) + - out_args[1]: Size of fuse_open_out (24 bytes) + - out_args[2]: Not used + - When FUSE_SECURITY_CTX is set (fc->init_security): Additional security context included in in_args and/or out_args. + - When FUSE_CREATE_SUPP_GROUP is set (fc->create_supp_group): Additional supplementary group information included in in_args. + +36. FUSE_INTERRUPT (36) + - in_args[0]: Size of fuse_interrupt_in (8 bytes) + - in_args[1]: Not used + - in_args[2]: Not used + - out_args[0]: Not used + - out_args[1]: Not used + - out_args[2]: Not used + +37. FUSE_BMAP (37) + - in_args[0]: Size of fuse_bmap_in (16 bytes) + - in_args[1]: Not used + - in_args[2]: Not used + - out_args[0]: Size of fuse_bmap_out (16 bytes) + - out_args[1]: Not used + - out_args[2]: Not used + +38. FUSE_DESTROY (38) + - in_args[0]: Not used + - in_args[1]: Not used + - in_args[2]: Not used + - out_args[0]: Not used + - out_args[1]: Not used + - out_args[2]: Not used + +39. FUSE_IOCTL (39) + - in_args[0]: Size of fuse_ioctl_in (64 bytes) + - in_args[1]: Variable (ioctl command data) + - in_args[2]: Not used + - out_args[0]: Size of fuse_ioctl_out (32 bytes) + - out_args[1]: Variable (data returned by the ioctl command) + - out_args[2]: Not used + +40. FUSE_POLL (40) + - in_args[0]: Size of fuse_poll_in (16 bytes) + - in_args[1]: Not used + - in_args[2]: Not used + - out_args[0]: Size of fuse_poll_out (16 bytes) + - out_args[1]: Not used + - out_args[2]: Not used + +41. FUSE_NOTIFY_REPLY (41) + - in_args[0]: Variable (reply data) + - in_args[1]: Not used + - in_args[2]: Not used + - out_args[0]: Not used + - out_args[1]: Not used + - out_args[2]: Not used + +42. FUSE_BATCH_FORGET (42) + - in_args[0]: Variable (size depends on the number of forget requests) + - in_args[1]: Not used + - in_args[2]: Not used + - out_args[0]: Not used + - out_args[1]: Not used + - out_args[2]: Not used + +43. FUSE_FALLOCATE (43) + - in_args[0]: Size of fuse_fallocate_in (24 bytes) + - in_args[1]: Not used + - in_args[2]: Not used + - out_args[0]: Not used + - out_args[1]: Not used + - out_args[2]: Not used + +44. FUSE_READDIRPLUS (44) + - in_args[0]: Size of fuse_read_in (32 bytes) + - in_args[1]: Not used + - in_args[2]: Not used + - out_args[0]: Variable (directory entries) + - out_args[1]: Not used + - out_args[2]: Not used + +45. FUSE_RENAME2 (45) + - in_args[0]: Size of fuse_rename2_in (32 bytes) + - in_args[1]: Variable (old path, up to PATH_MAX) + - in_args[2]: Variable (new path, up to PATH_MAX) + - out_args[0]: Not used + - out_args[1]: Not used + - out_args[2]: Not used + +46. FUSE_LSEEK (46) + - in_args[0]: Size of fuse_lseek_in (16 bytes) + - in_args[1]: Not used + - in_args[2]: Not used + - out_args[0]: Size of fuse_lseek_out (16 bytes) + - out_args[1]: Not used + - out_args[2]: Not used + +47. FUSE_COPY_FILE_RANGE (47) + - in_args[0]: Size of fuse_copy_file_range_in (56 bytes) + - in_args[1]: Not used + - in_args[2]: Not used + - out_args[0]: Size of fuse_write_out (24 bytes) + - out_args[1]: Not used + - out_args[2]: Not used + +48. FUSE_SETUPMAPPING (48) + - in_args[0]: Size of fuse_setupmapping_in (32 bytes) + - in_args[1]: Variable (data to map) + - in_args[2]: Not used + - out_args[0]: Not used + - out_args[1]: Not used + - out_args[2]: Not used + +49. FUSE_REMOVEMAPPING (49) + - in_args[0]: Variable (size depends on the number of mappings to remove) + - in_args[1]: Not used + - in_args[2]: Not used + - out_args[0]: Not used + - out_args[1]: Not used + - out_args[2]: Not used + +50. FUSE_SYNCFS (50) + - in_args[0]: Size of fuse_syncfs_in (16 bytes) + - in_args[1]: Not used + - in_args[2]: Not used + - out_args[0]: Not used + - out_args[1]: Not used + - out_args[2]: Not used + +51. FUSE_TMPFILE (51) + - in_args[0]: Size of fuse_create_in (16 bytes) + - in_args[1]: Variable (file name, up to PATH_MAX) + - in_args[2]: Not used + - out_args[0]: Size of fuse_entry_out (typically 128 bytes) + - out_args[1]: Size of fuse_open_out (24 bytes) + - out_args[2]: Not used + - When FUSE_SECURITY_CTX is set (fc->init_security): Additional security context included in in_args and/or out_args. + - When FUSE_CREATE_SUPP_GROUP is set (fc->create_supp_group): Additional supplementary group information included in in_args. + +52. FUSE_STATX (52) + - in_args[0]: Size of fuse_statx_in (32 bytes) + - in_args[1]: Not used + - in_args[2]: Not used + - out_args[0]: Size of fuse_statx_out (typically 256 bytes) + - out_args[1]: Not used + - out_args[2]: Not used + +53. FUSE_COPY_FILE_RANGE_64 (53) + - in_args[0]: Size of fuse_copy_file_range_in (56 bytes) + - in_args[1]: Not used + - in_args[2]: Not used + - out_args[0]: Size of fuse_copy_file_range_out (8 bytes) + - out_args[1]: Not used + - out_args[2]: Not used diff --git a/doc/mainpage.dox b/doc/mainpage.dox new file mode 100644 index 0000000..36ba3bc --- /dev/null +++ b/doc/mainpage.dox @@ -0,0 +1,54 @@ +/*! +\mainpage libfuse API documentation + +FUSE (Filesystem in Userspace) is an interface for userspace programs +to export a filesystem to the Linux kernel. The FUSE project consists +of two components: the *fuse* kernel module (maintained in the regular +kernel repositories) and the *libfuse* userspace library. libfuse +provides the reference implementation for communicating with the FUSE +kernel module. + +A FUSE file system is typically implemented as a standalone +application that links with libfuse. libfuse provides functions to +mount the file system, unmount it, read requests from the kernel, and +send responses back. + + +## Getting started ## + +libfuse offers two APIs: a "high-level", synchronous API, and a +"low-level" asynchronous API. In both cases, incoming requests from +the kernel are passed to the main program using callbacks. When using +the high-level API, the callbacks may work with file names and paths +instead of inodes, and processing of a request finishes when the +callback function returns. When using the low-level API, the callbacks +must work with inodes and responses must be sent explicitly using a +separate set of API functions. + +The high-level API that is primarily specified in fuse.h. The +low-level API that is primarily documented in fuse_lowlevel.h. + +## Examples ## + +FUSE comes with several examples in the examples directory. A good starting point are +hello.c (for the high-level API) and hello_ll.c (for the low-level +API). + +## FUSE internals ## + +The authoritative source of information about libfuse internals +(including the protocol used for communication with the FUSE kernel +module) is the source code. + +However, some people have kindly documented different aspects of FUSE +in a more beginner friendly way. While this information is +increasingly out of date, it still provides a good overview: + +- Bharat Vangoor et al have included an overview of the FUSE internals + in a paper evaluating FUSE performance. + +- Some documentation of the kernel-userspace protocol is available on + the libfuse wiki. + +*/ diff --git a/doc/meson.build b/doc/meson.build new file mode 100644 index 0000000..db3e0b2 --- /dev/null +++ b/doc/meson.build @@ -0,0 +1,4 @@ +if not platform.endswith('bsd') and platform != 'dragonfly' + install_man('fusermount3.1', 'mount.fuse3.8') +endif + diff --git a/doc/mount.fuse3.8 b/doc/mount.fuse3.8 new file mode 100644 index 0000000..4380c84 --- /dev/null +++ b/doc/mount.fuse3.8 @@ -0,0 +1,273 @@ +.TH fuse "8" +.SH NAME +fuse \- configuration and mount options for FUSE file systems +.SH DESCRIPTION +FUSE (Filesystem in Userspace) is a simple interface for userspace programs to export a virtual filesystem to the Linux kernel. FUSE also provides a secure method for non privileged users to create and mount their own filesystem implementations. +.SH DEFINITIONS +.TP +\fBFUSE\fP +The in-kernel filesystem that forwards requests to a user-space +process. +.TP +\fBfilesystem\fP +The user-space process that responds to requests received from the +kernel. +.TP +\fBlibfuse\fP +The shared library that most (user-space) filesystems use to +communicate with FUSE (the kernel filesystem). libfuse also provides +the \fBfusermount3\fP (or \fBfusermount\fP if you have older version of +libfuse) helper to allow non-privileged users to mount filesystems. +.TP +\fBfilesystem owner\fP +The user that starts the filesystem and instructs the kernel to +associate it with a particular mountpoint. The latter is typically done +by the filesystem itself on start-up. When using libfuse, this is done +by calling the \fBfusermount3\fP utility. +.TP +\fBclient\fP +Any process that interacts with the mountpoint. +.SH CONFIGURATION +Some options regarding mount policy can be set in the file \fI/etc/fuse.conf\fP. Currently these options are: +.TP +\fBmount_max = NNN\fP +Set the maximum number of FUSE mounts allowed to non-root users. The default is 1000. +.TP +\fBuser_allow_other\fP +Allow non-root users to specify the \fBallow_other\fP or +\fBallow_root\fP mount options (see below). +.TP +These limits are enforced by the \fBfusermount3\fP helper, so they can be avoided by filesystems that run as root. +.SH OPTIONS +Most of the generic mount options described in \fBmount\fP are +supported (\fBro\fP, \fBrw\fP, \fBsuid\fP, \fBnosuid\fP, \fBdev\fP, +\fBnodev\fP, \fBexec\fP, \fBnoexec\fP, \fBatime\fP, \fBnoatime\fP, +\fBsync\fP, \fBasync\fP, \fBdirsync\fP). Filesystems are mounted with +\fBnodev,nosuid\fP by default, which can only be overridden by a +privileged user. +.SS "General mount options:" +These are FUSE specific mount options that can be specified for all filesystems: +.TP +\fBdefault_permissions\fP +This option instructs the kernel to perform its own permission check +instead of deferring all permission checking to the +filesystem. The check by the kernel is done in addition to any +permission checks by the filesystem, and both have to succeed for an +operation to be allowed. The kernel performs a standard UNIX permission +check (based on mode bits and ownership of the directory entry, and +uid/gid of the client). + +This mount option is activated implicitly if the filesystem enables +ACL support during the initial feature negotiation when opening the +device fd. In this case, the kernel performs both ACL and standard +unix permission checking. + +Filesystems that do not implement any permission checking should +generally add this option internally. +.TP +\fBallow_other\fP +This option overrides the security measure +restricting file access to the filesystem owner, so that all users +(including root) can access the files. +.TP +\fBrootmode=M\fP +Specifies the file mode of the filesystem's root (in octal +representation). +.TP +\fBblkdev\fP +Mount a filesystem backed by a block device. This is a privileged +option. The device must be specified with the \fBfsname=NAME\fP +option. +.TP +\fBblksize=N\fP +Set the block size for the filesystem. This option is only valid +for 'fuseblk' type mounts. The default is 512. + +In most cases, this option should not be specified by +the filesystem owner but set internally by the filesystem. +.TP +\fBmax_read=N\fP +With this option the maximum size of read operations can be set. The +default is infinite, but typically the kernel enforces its own limit +in addition to this one. A value of zero corresponds to no limit. + +This option should not be specified by the filesystem owner. The +correct (or optimum) value depends on the filesystem implementation +and should thus be set by the filesystem internally. + +This mount option is deprecated in favor of direct negotiation over +the device fd (as done for e.g. the maximum size of write +operations). For the time being, libfuse-using filesystems that want +to limit the read size must therefore use this mount option \fIand\fP +set the same value again in the init() handler. +.TP +\fBfd=N\fP +The file descriptor to use for communication between the userspace +filesystem and the kernel. The file descriptor must have been +obtained by opening the FUSE device (/dev/fuse). + +This option should not be specified by the filesystem owner. It is set +by libfuse (or, if libfuse is not used, must be set by the filesystem +itself). +.TP +\fBuser_id=N\fP +\fBgroup_id=N\fP +Specifies the numeric uid/gid of the mount owner. + +This option should not be specified by the filesystem owner. It is set +by libfuse (or, if libfuse is not used, must be set by the filesystem +itself). +.TP +\fBfsname=NAME\fP +Sets the filesystem source (first field in \fI/etc/mtab\fP). The +default is the name of the filesystem process. +.TP +\fBsubtype=TYPE\fP +Sets the filesystem type (third field in \fI/etc/mtab\fP). The default +is the name of the filesystem process. If the kernel supports it, \fI/etc/mtab\fP and \fI/proc/mounts\fP will show the filesystem type as \fBfuse.TYPE\fP + +If the kernel doesn't support subtypes, the source field will be +\fBTYPE#NAME\fP, or if \fBfsname\fP option is not specified, just +\fBTYPE\fP. + +.SS "libfuse-specific mount options:" +These following options are not actually passed to the kernel but +interpreted by libfuse. They can be specified for all filesystems +that use libfuse: +.TP +\fBallow_root\fP +This option is similar to \fBallow_other\fP but file access is limited +to the filesystem owner and root. This option and \fBallow_other\fP are mutually exclusive. +.TP +\fBauto_unmount\fP +This option enables automatic release of the mountpoint if filesystem +terminates for any reason. Normally the filesystem is +responsible for releasing the mountpoint, which means that the +mountpoint becomes inaccessible if the filesystem terminates +without first unmounting. + +This option is dangerous and should only be used after careful consideration of the +risks. + +Automatically unmounting the filesystem means that if the filesystem crashes the +mountpoint may suddenly appear empty, which may have unintended consequences. For example, +a running backup and mirroring program may conclude that all the data in the filesystem +has been deleted and proceed to propagate this deletion to the backup / remote system. If +the mountpoint instead becomes inaccessible (the default), most programs will behave +correctly (report an error). + +This feature may also accidentally unmount the wrong filesystem due to race +conditions. For example, if another filesystem was mounted underneath the same mountpoint, +or if a new filesystem is mounted after the FUSE process has crashed, it may accidentally +get unmounted. + +At the moment, this option implies that the filesystem will also be +mounted with \fBnodev\fP and \fBnosuid\fP (even when mounted by +root). This restriction may be lifted in the future. + +.SS "High-level mount options:" +These following options are not actually passed to the kernel but +interpreted by libfuse. They can only be specified for filesystems +that use the high-level libfuse API: +.TP +\fBkernel_cache\fP +This option disables flushing the cache of the file contents on every \fBopen\fP(2). This should only be enabled on filesystems, where the file data is never changed externally (not through the mounted FUSE filesystem). Thus it is not suitable for network filesystems and other "intermediate" filesystems. + +\fBNOTE\fP: if this option is not specified (and neither \fBdirect_io\fP) data is still cached after the \fBopen\fP(2), so a \fBread\fP(2) system call will not always initiate a read operation. +.TP +\fBauto_cache\fP +This option is an alternative to +\fBkernel_cache\fP. Instead of unconditionally keeping cached data, the +cached data is invalidated on \fBopen\fP(2) if the modification +time or the size of the file has changed since it was last opened. +.TP +\fBumask=M fmask=M dmask=M\fP +Override the permission bits set by the filesystem in \fIst_mode\fP. The resulting permission bits are the ones missing from the mask value, which is given in octal representation. \fBfmask\fP and \fBdmask\fP (respectively) may be used to control the permission bits of files and directories separately. umask is overridden by the individual fmask and dmask options. +.TP +\fBuid=N\fP +Override the \fIst_uid\fP field set by the filesystem (N is numeric). +.TP +\fBgid=N\fP +Override the \fIst_gid\fP field set by the filesystem (N is numeric). +.TP +\fBentry_timeout=T\fP +The timeout in seconds for which name lookups will be cached. The default is 1.0 second. For all the timeout options, it is possible to give fractions of a second as well (e.g. \fBentry_timeout=2.8\fP) +.TP +\fBnegative_timeout=T\fP +The timeout in seconds for which a negative lookup will be cached. This means, that if file did not exist (lookup returned \fBENOENT\fP), the lookup will only be redone after the timeout, and the file/directory will be assumed to not exist until then. The default is 0.0 second, meaning that caching negative lookups are disabled. +.TP +\fBattr_timeout=T\fP +The timeout in seconds for which file/directory attributes are cached. The default is 1.0 second. +.TP +\fBac_attr_timeout=T\fP +The timeout in seconds for which file attributes are cached for the purpose of checking if \fBauto_cache\fP should flush the file data on open. The default is the value of \fBattr_timeout\fP +.TP +\fBnoforget\fP +.TP +\fBremember=T\fP +Normally, libfuse assigns inodes to paths only for as long as the kernel +is aware of them. With this option inodes are instead assigned +for at least \fBT\fP seconds (or, in the case of \fBnoforget\fP, +the life-time of the filesystem). This will require more +memory, but may be necessary when using applications that make use of +inode numbers. +.TP +\fBmodules=M1[:M2...]\fP +Add modules to the filesystem stack. Modules are pushed in the order they are specified, with the original filesystem being on the bottom of the stack. + +.SS "\fBmount.fuse3\fP options:" +These options are interpreted by \fBmount.fuse3\fP and are thus only available when mounting a file system via \fBmount.fuse3\fP (such as when mounting via the generic \fBmount\fP(1) command or \fI/etc/fstab\fP). Supported options are: +.TP +\fBsetuid=USER\fP +Switch to \fBUSER\fP and its primary group before launching the FUSE file system process. mount.fuse3 must be run as root or with \fBCAP_SETUID\fP and \fBCAP_SETGID\fP for this to work. +.TP +\fBdrop_privileges\fP +Perform setup of the FUSE file descriptor and mounting the file system before launching the FUSE file system process. \fBmount.fuse3\fP requires privilege to do so, i.e. must be run as root or at least with \fBCAP_SYS_ADMIN\fP and \fBCAP_SETPCAP\fP. It will launch the file system process fully unprivileged, i.e. without \fBcapabilities\fP(7) and \fBprctl\fP(2) flags set up such that privileges can't be reacquired (e.g. via setuid or fscaps binaries). This reduces risk in the event of the FUSE file system process getting compromised by malicious file system data. + +.SH FUSE MODULES (STACKING) +Modules are filesystem stacking support to high level API. Filesystem modules can be built into libfuse or loaded from shared object +.SS "iconv" +Perform file name character set conversion. Options are: +.TP +\fBfrom_code=CHARSET\fP +Character set to convert from (see \fBiconv -l\fP for a list of possible values). Default is \fBUTF-8\fP. +.TP +\fBto_code=CHARSET\fP +Character set to convert to. Default is determined by the current locale. +.SS "subdir" +Prepend a given directory to each path. Options are: +.TP +\fBsubdir=DIR\fP +Directory to prepend to all paths. This option is \fImandatory\fP. +.TP +\fBrellinks\fP +Transform absolute symlinks into relative +.TP +\fBnorellinks\fP +Do not transform absolute symlinks into relative. This is the default. +.SH SECURITY +The fusermount3 program is installed set-user-gid to fuse. This is done to allow users from fuse group to mount +their own filesystem implementations. +There must however be some limitations, in order to prevent Bad User from +doing nasty things. Currently those limitations are: +.IP 1. +The user can only mount on a mountpoint, for which it has write permission +.IP 2. +The mountpoint is not a sticky directory which isn't owned by the user (like \fI/tmp\fP usually is) +.IP 3. +No other user (including root) can access the contents of the mounted filesystem. +.SH NOTE +FUSE filesystems are unmounted using the \fBfusermount3\fP(1) command (\fBfusermount3 -u mountpoint\fP). +.SH "AUTHORS" +.LP +FUSE is currently maintained by Nikolaus Rath +.LP +The original author of FUSE is Miklos Szeredi . +.LP +This man page was originally written by Bastien Roucaries for the +Debian GNU/Linux distribution. +.SH SEE ALSO +.BR fusermount3 (1) +.BR fusermount (1) +.BR mount (8) +.BR fuse (4) diff --git a/example/README.compile b/example/README.compile new file mode 100644 index 0000000..a7b6819 --- /dev/null +++ b/example/README.compile @@ -0,0 +1,4 @@ +Note: + * If the pkg-config command fails due to the absence of the fuse3.pc file, + you should configure the path to the fuse3.pc file in the PKG_CONFIG_PATH + variable. \ No newline at end of file diff --git a/example/cuse.c b/example/cuse.c new file mode 100644 index 0000000..92b6b09 --- /dev/null +++ b/example/cuse.c @@ -0,0 +1,335 @@ +/* + CUSE example: Character device in Userspace + Copyright (C) 2008-2009 SUSE Linux Products GmbH + Copyright (C) 2008-2009 Tejun Heo + + This program can be distributed under the terms of the GNU GPLv2. + See the file GPL2.txt. + +*/ + +/** @file + * + * This example demonstrates how to implement a character device in + * userspace ("CUSE"). This is only allowed for root. The character + * device should appear in /dev under the specified name. It can be + * tested with the cuse_client.c program. + * + * Mount the file system with: + * + * cuse -f --name=mydevice + * + * You should now have a new /dev/mydevice character device. To "unmount" it, + * kill the "cuse" process. + * + * To compile this example, run + * + * gcc -Wall cuse.c `pkg-config fuse3 --cflags --libs` -o cuse + * + * ## Source code ## + * \include cuse.c + */ + + +#define FUSE_USE_VERSION 31 + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ioctl.h" + +static void *cusexmp_buf; +static size_t cusexmp_size; + +static const char *usage = +"usage: cusexmp [options]\n" +"\n" +"options:\n" +" --help|-h print this help message\n" +" --maj=MAJ|-M MAJ device major number\n" +" --min=MIN|-m MIN device minor number\n" +" --name=NAME|-n NAME device name (mandatory)\n" +" -d -o debug enable debug output (implies -f)\n" +" -f foreground operation\n" +" -s disable multi-threaded operation\n" +"\n"; + +static int cusexmp_resize(size_t new_size) +{ + void *new_buf; + + if (new_size == cusexmp_size) + return 0; + + new_buf = realloc(cusexmp_buf, new_size); + if (!new_buf && new_size) + return -ENOMEM; + + if (new_size > cusexmp_size) + memset(new_buf + cusexmp_size, 0, new_size - cusexmp_size); + + cusexmp_buf = new_buf; + cusexmp_size = new_size; + + return 0; +} + +static int cusexmp_expand(size_t new_size) +{ + if (new_size > cusexmp_size) + return cusexmp_resize(new_size); + return 0; +} + +static void cusexmp_init(void *userdata, struct fuse_conn_info *conn) +{ + (void)userdata; + + /* Disable the receiving and processing of FUSE_INTERRUPT requests */ + conn->no_interrupt = 1; +} + +static void cusexmp_open(fuse_req_t req, struct fuse_file_info *fi) +{ + fuse_reply_open(req, fi); +} + +static void cusexmp_read(fuse_req_t req, size_t size, off_t off, + struct fuse_file_info *fi) +{ + (void)fi; + + if (off >= cusexmp_size) + off = cusexmp_size; + if (size > cusexmp_size - off) + size = cusexmp_size - off; + + fuse_reply_buf(req, cusexmp_buf + off, size); +} + +static void cusexmp_write(fuse_req_t req, const char *buf, size_t size, + off_t off, struct fuse_file_info *fi) +{ + (void)fi; + + if (cusexmp_expand(off + size)) { + fuse_reply_err(req, ENOMEM); + return; + } + + memcpy(cusexmp_buf + off, buf, size); + fuse_reply_write(req, size); +} + +static void fioc_do_rw(fuse_req_t req, void *addr, const void *in_buf, + size_t in_bufsz, size_t out_bufsz, int is_read) +{ + const struct fioc_rw_arg *arg; + struct iovec in_iov[2], out_iov[3], iov[3]; + size_t cur_size; + + /* read in arg */ + in_iov[0].iov_base = addr; + in_iov[0].iov_len = sizeof(*arg); + if (!in_bufsz) { + fuse_reply_ioctl_retry(req, in_iov, 1, NULL, 0); + return; + } + arg = in_buf; + in_buf += sizeof(*arg); + in_bufsz -= sizeof(*arg); + + /* prepare size outputs */ + out_iov[0].iov_base = + addr + offsetof(struct fioc_rw_arg, prev_size); + out_iov[0].iov_len = sizeof(arg->prev_size); + + out_iov[1].iov_base = + addr + offsetof(struct fioc_rw_arg, new_size); + out_iov[1].iov_len = sizeof(arg->new_size); + + /* prepare client buf */ + if (is_read) { + out_iov[2].iov_base = arg->buf; + out_iov[2].iov_len = arg->size; + if (!out_bufsz) { + fuse_reply_ioctl_retry(req, in_iov, 1, out_iov, 3); + return; + } + } else { + in_iov[1].iov_base = arg->buf; + in_iov[1].iov_len = arg->size; + if (arg->size && !in_bufsz) { + fuse_reply_ioctl_retry(req, in_iov, 2, out_iov, 2); + return; + } + } + + /* we're all set */ + cur_size = cusexmp_size; + iov[0].iov_base = &cur_size; + iov[0].iov_len = sizeof(cur_size); + + iov[1].iov_base = &cusexmp_size; + iov[1].iov_len = sizeof(cusexmp_size); + + if (is_read) { + size_t off = arg->offset; + size_t size = arg->size; + + if (off >= cusexmp_size) + off = cusexmp_size; + if (size > cusexmp_size - off) + size = cusexmp_size - off; + + iov[2].iov_base = cusexmp_buf + off; + iov[2].iov_len = size; + fuse_reply_ioctl_iov(req, size, iov, 3); + } else { + if (cusexmp_expand(arg->offset + in_bufsz)) { + fuse_reply_err(req, ENOMEM); + return; + } + + memcpy(cusexmp_buf + arg->offset, in_buf, in_bufsz); + fuse_reply_ioctl_iov(req, in_bufsz, iov, 2); + } +} + +static void cusexmp_ioctl(fuse_req_t req, int cmd, void *arg, + struct fuse_file_info *fi, unsigned flags, + const void *in_buf, size_t in_bufsz, size_t out_bufsz) +{ + int is_read = 0; + + (void)fi; + + if (flags & FUSE_IOCTL_COMPAT) { + fuse_reply_err(req, ENOSYS); + return; + } + + switch (cmd) { + case FIOC_GET_SIZE: + if (!out_bufsz) { + struct iovec iov = { arg, sizeof(size_t) }; + + fuse_reply_ioctl_retry(req, NULL, 0, &iov, 1); + } else + fuse_reply_ioctl(req, 0, &cusexmp_size, + sizeof(cusexmp_size)); + break; + + case FIOC_SET_SIZE: + if (!in_bufsz) { + struct iovec iov = { arg, sizeof(size_t) }; + + fuse_reply_ioctl_retry(req, &iov, 1, NULL, 0); + } else { + cusexmp_resize(*(size_t *)in_buf); + fuse_reply_ioctl(req, 0, NULL, 0); + } + break; + + case FIOC_READ: + is_read = 1; + /* fall through */ + case FIOC_WRITE: + fioc_do_rw(req, arg, in_buf, in_bufsz, out_bufsz, is_read); + break; + + default: + fuse_reply_err(req, EINVAL); + } +} + +struct cusexmp_param { + unsigned major; + unsigned minor; + char *dev_name; + int is_help; +}; + +#define CUSEXMP_OPT(t, p) { t, offsetof(struct cusexmp_param, p), 1 } + +static const struct fuse_opt cusexmp_opts[] = { + CUSEXMP_OPT("-M %u", major), + CUSEXMP_OPT("--maj=%u", major), + CUSEXMP_OPT("-m %u", minor), + CUSEXMP_OPT("--min=%u", minor), + CUSEXMP_OPT("-n %s", dev_name), + CUSEXMP_OPT("--name=%s", dev_name), + FUSE_OPT_KEY("-h", 0), + FUSE_OPT_KEY("--help", 0), + FUSE_OPT_END +}; + +static int cusexmp_process_arg(void *data, const char *arg, int key, + struct fuse_args *outargs) +{ + struct cusexmp_param *param = data; + + (void)outargs; + (void)arg; + + switch (key) { + case 0: + param->is_help = 1; + fprintf(stderr, "%s", usage); + return fuse_opt_add_arg(outargs, "-ho"); + default: + return 1; + } +} + +static const struct cuse_lowlevel_ops cusexmp_clop = { + .init = cusexmp_init, + .open = cusexmp_open, + .read = cusexmp_read, + .write = cusexmp_write, + .ioctl = cusexmp_ioctl, +}; + +int main(int argc, char **argv) +{ + struct fuse_args args = FUSE_ARGS_INIT(argc, argv); + struct cusexmp_param param = { 0, 0, NULL, 0 }; + char dev_name[128] = "DEVNAME="; + const char *dev_info_argv[] = { dev_name }; + struct cuse_info ci; + int ret = 1; + + if (fuse_opt_parse(&args, ¶m, cusexmp_opts, cusexmp_process_arg)) { + printf("failed to parse option\n"); + free(param.dev_name); + goto out; + } + + if (!param.is_help) { + if (!param.dev_name) { + fprintf(stderr, "Error: device name missing\n"); + goto out; + } + strncat(dev_name, param.dev_name, sizeof(dev_name) - sizeof("DEVNAME=")); + free(param.dev_name); + } + + memset(&ci, 0, sizeof(ci)); + ci.dev_major = param.major; + ci.dev_minor = param.minor; + ci.dev_info_argc = 1; + ci.dev_info_argv = dev_info_argv; + ci.flags = CUSE_UNRESTRICTED_IOCTL; + + ret = cuse_lowlevel_main(args.argc, args.argv, &ci, &cusexmp_clop, NULL); + +out: + fuse_opt_free_args(&args); + return ret; +} diff --git a/example/cuse_client.c b/example/cuse_client.c new file mode 100644 index 0000000..50f70f3 --- /dev/null +++ b/example/cuse_client.c @@ -0,0 +1,166 @@ +/* + FUSE fioclient: FUSE ioctl example client + Copyright (C) 2008 SUSE Linux Products GmbH + Copyright (C) 2008 Tejun Heo + + This program can be distributed under the terms of the GNU GPLv2. + See the file GPL2.txt. +*/ + +/** @file + * + * This program tests the cuse.c example file system. + * + * Example usage (assuming that /dev/foobar is a CUSE device provided + * by the cuse.c example file system): + * + * $ cuse_client /dev/foobar s + * 0 + * + * $ echo "hello" | cuse_client /dev/foobar w 6 + * Writing 6 bytes + * transferred 6 bytes (0 -> 6) + * + * $ cuse_client /dev/foobar s + * 6 + * + * $ cuse_client /dev/foobar r 10 + * hello + * transferred 6 bytes (6 -> 6) + * + * Compiling this example + * + * gcc -Wall cuse_client.c -o cuse_client + * + * ## Source Code ## + * \include cuse_client.c + */ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "ioctl.h" + +/* keeps a mistyped SIZE from asking for the machine's memory */ +#define MAX_XFER_SIZE (16 * 1024 * 1024) + +const char *usage = +"Usage: cuse_client FIOC_FILE COMMAND\n" +"\n" +"COMMANDS\n" +" s [SIZE] : get size if SIZE is omitted, set size otherwise\n" +" r SIZE [OFF] : read SIZE bytes @ OFF (dfl 0) and output to stdout\n" +" w SIZE [OFF] : write SIZE bytes @ OFF (dfl 0) from stdin\n" +"\n"; + +static int do_rw(int fd, int is_read, size_t size, off_t offset, + size_t *prev_size, size_t *new_size) +{ + struct fioc_rw_arg arg = { .offset = offset }; + ssize_t ret; + + if (size > MAX_XFER_SIZE) { + fprintf(stderr, "size %zu exceeds limit of %d bytes\n", + size, MAX_XFER_SIZE); + return -1; + } + + arg.buf = calloc(1, size); + if (!arg.buf) { + fprintf(stderr, "failed to allocated %zu bytes\n", size); + return -1; + } + + if (is_read) { + arg.size = size; + ret = ioctl(fd, FIOC_READ, &arg); + if (ret >= 0) + fwrite(arg.buf, 1, ret, stdout); + } else { + arg.size = fread(arg.buf, 1, size, stdin); + fprintf(stderr, "Writing %zu bytes\n", arg.size); + ret = ioctl(fd, FIOC_WRITE, &arg); + } + + if (ret >= 0) { + *prev_size = arg.prev_size; + *new_size = arg.new_size; + } else + perror("ioctl"); + + free(arg.buf); + return ret; +} + +int main(int argc, char **argv) +{ + size_t param[2] = { }; + size_t size, prev_size = 0, new_size = 0; + char cmd; + int fd, i, rc; + + if (argc < 3) + goto usage; + + fd = open(argv[1], O_RDWR); + if (fd < 0) { + perror("open"); + return 1; + } + + cmd = tolower(argv[2][0]); + argc -= 3; + argv += 3; + + for (i = 0; i < argc; i++) { + char *endp; + param[i] = strtoul(argv[i], &endp, 0); + if (endp == argv[i] || *endp != '\0') + goto usage; + } + + switch (cmd) { + case 's': + if (!argc) { + if (ioctl(fd, FIOC_GET_SIZE, &size)) { + perror("ioctl"); + goto error; + } + printf("%zu\n", size); + } else { + size = param[0]; + if (ioctl(fd, FIOC_SET_SIZE, &size)) { + perror("ioctl"); + goto error; + } + } + close(fd); + return 0; + + case 'r': + case 'w': + rc = do_rw(fd, cmd == 'r', param[0], param[1], + &prev_size, &new_size); + if (rc < 0) + goto error; + fprintf(stderr, "transferred %d bytes (%zu -> %zu)\n", + rc, prev_size, new_size); + close(fd); + return 0; + } + +usage: + fprintf(stderr, "%s", usage); + return 1; + +error: + close(fd); + return 1; +} diff --git a/example/cxxopts.hpp b/example/cxxopts.hpp new file mode 100644 index 0000000..80b695a --- /dev/null +++ b/example/cxxopts.hpp @@ -0,0 +1,2114 @@ +/* + +Copyright (c) 2014, 2015, 2016, 2017 Jarryd Beck + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +*/ + +#ifndef CXXOPTS_HPP_INCLUDED +#define CXXOPTS_HPP_INCLUDED + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef __cpp_lib_optional +#include +#define CXXOPTS_HAS_OPTIONAL +#endif + +#ifndef CXXOPTS_VECTOR_DELIMITER +#define CXXOPTS_VECTOR_DELIMITER ',' +#endif + +#define CXXOPTS__VERSION_MAJOR 2 +#define CXXOPTS__VERSION_MINOR 2 +#define CXXOPTS__VERSION_PATCH 1 + +namespace cxxopts +{ + static constexpr struct { + uint8_t major, minor, patch; + } version = { + CXXOPTS__VERSION_MAJOR, + CXXOPTS__VERSION_MINOR, + CXXOPTS__VERSION_PATCH + }; +} + +//when we ask cxxopts to use Unicode, help strings are processed using ICU, +//which results in the correct lengths being computed for strings when they +//are formatted for the help output +//it is necessary to make sure that can be found by the +//compiler, and that icu-uc is linked in to the binary. + +#ifdef CXXOPTS_USE_UNICODE +#include + +namespace cxxopts +{ + typedef icu::UnicodeString String; + + inline + String + toLocalString(std::string s) + { + return icu::UnicodeString::fromUTF8(std::move(s)); + } + + class UnicodeStringIterator : public + std::iterator + { + public: + + UnicodeStringIterator(const icu::UnicodeString* string, int32_t pos) + : s(string) + , i(pos) + { + } + + value_type + operator*() const + { + return s->char32At(i); + } + + bool + operator==(const UnicodeStringIterator& rhs) const + { + return s == rhs.s && i == rhs.i; + } + + bool + operator!=(const UnicodeStringIterator& rhs) const + { + return !(*this == rhs); + } + + UnicodeStringIterator& + operator++() + { + ++i; + return *this; + } + + UnicodeStringIterator + operator+(int32_t v) + { + return UnicodeStringIterator(s, i + v); + } + + private: + const icu::UnicodeString* s; + int32_t i; + }; + + inline + String& + stringAppend(String&s, String a) + { + return s.append(std::move(a)); + } + + inline + String& + stringAppend(String& s, int n, UChar32 c) + { + for (int i = 0; i != n; ++i) + { + s.append(c); + } + + return s; + } + + template + String& + stringAppend(String& s, Iterator begin, Iterator end) + { + while (begin != end) + { + s.append(*begin); + ++begin; + } + + return s; + } + + inline + size_t + stringLength(const String& s) + { + return s.length(); + } + + inline + std::string + toUTF8String(const String& s) + { + std::string result; + s.toUTF8String(result); + + return result; + } + + inline + bool + empty(const String& s) + { + return s.isEmpty(); + } +} + +namespace std +{ + inline + cxxopts::UnicodeStringIterator + begin(const icu::UnicodeString& s) + { + return cxxopts::UnicodeStringIterator(&s, 0); + } + + inline + cxxopts::UnicodeStringIterator + end(const icu::UnicodeString& s) + { + return cxxopts::UnicodeStringIterator(&s, s.length()); + } +} + +//ifdef CXXOPTS_USE_UNICODE +#else + +namespace cxxopts +{ + typedef std::string String; + + template + T + toLocalString(T&& t) + { + return std::forward(t); + } + + inline + size_t + stringLength(const String& s) + { + return s.length(); + } + + inline + String& + stringAppend(String&s, String a) + { + return s.append(std::move(a)); + } + + inline + String& + stringAppend(String& s, size_t n, char c) + { + return s.append(n, c); + } + + template + String& + stringAppend(String& s, Iterator begin, Iterator end) + { + return s.append(begin, end); + } + + template + std::string + toUTF8String(T&& t) + { + return std::forward(t); + } + + inline + bool + empty(const std::string& s) + { + return s.empty(); + } +} + +//ifdef CXXOPTS_USE_UNICODE +#endif + +namespace cxxopts +{ + namespace + { +#ifdef _WIN32 + const std::string LQUOTE("\'"); + const std::string RQUOTE("\'"); +#else + const std::string LQUOTE("‘"); + const std::string RQUOTE("’"); +#endif + } + + class Value : public std::enable_shared_from_this + { + public: + + virtual ~Value() = default; + + virtual + std::shared_ptr + clone() const = 0; + + virtual void + parse(const std::string& text) const = 0; + + virtual void + parse() const = 0; + + virtual bool + has_default() const = 0; + + virtual bool + is_container() const = 0; + + virtual bool + has_implicit() const = 0; + + virtual std::string + get_default_value() const = 0; + + virtual std::string + get_implicit_value() const = 0; + + virtual std::shared_ptr + default_value(const std::string& value) = 0; + + virtual std::shared_ptr + implicit_value(const std::string& value) = 0; + + virtual std::shared_ptr + no_implicit_value() = 0; + + virtual bool + is_boolean() const = 0; + }; + + class OptionException : public std::exception + { + public: + OptionException(const std::string& message) + : m_message(message) + { + } + + virtual const char* + what() const noexcept + { + return m_message.c_str(); + } + + private: + std::string m_message; + }; + + class OptionSpecException : public OptionException + { + public: + + OptionSpecException(const std::string& message) + : OptionException(message) + { + } + }; + + class OptionParseException : public OptionException + { + public: + OptionParseException(const std::string& message) + : OptionException(message) + { + } + }; + + class option_exists_error : public OptionSpecException + { + public: + option_exists_error(const std::string& option) + : OptionSpecException("Option " + LQUOTE + option + RQUOTE + " already exists") + { + } + }; + + class invalid_option_format_error : public OptionSpecException + { + public: + invalid_option_format_error(const std::string& format) + : OptionSpecException("Invalid option format " + LQUOTE + format + RQUOTE) + { + } + }; + + class option_syntax_exception : public OptionParseException { + public: + option_syntax_exception(const std::string& text) + : OptionParseException("Argument " + LQUOTE + text + RQUOTE + + " starts with a - but has incorrect syntax") + { + } + }; + + class option_not_exists_exception : public OptionParseException + { + public: + option_not_exists_exception(const std::string& option) + : OptionParseException("Option " + LQUOTE + option + RQUOTE + " does not exist") + { + } + }; + + class missing_argument_exception : public OptionParseException + { + public: + missing_argument_exception(const std::string& option) + : OptionParseException( + "Option " + LQUOTE + option + RQUOTE + " is missing an argument" + ) + { + } + }; + + class option_requires_argument_exception : public OptionParseException + { + public: + option_requires_argument_exception(const std::string& option) + : OptionParseException( + "Option " + LQUOTE + option + RQUOTE + " requires an argument" + ) + { + } + }; + + class option_not_has_argument_exception : public OptionParseException + { + public: + option_not_has_argument_exception + ( + const std::string& option, + const std::string& arg + ) + : OptionParseException( + "Option " + LQUOTE + option + RQUOTE + + " does not take an argument, but argument " + + LQUOTE + arg + RQUOTE + " given" + ) + { + } + }; + + class option_not_present_exception : public OptionParseException + { + public: + option_not_present_exception(const std::string& option) + : OptionParseException("Option " + LQUOTE + option + RQUOTE + " not present") + { + } + }; + + class argument_incorrect_type : public OptionParseException + { + public: + argument_incorrect_type + ( + const std::string& arg + ) + : OptionParseException( + "Argument " + LQUOTE + arg + RQUOTE + " failed to parse" + ) + { + } + }; + + class option_required_exception : public OptionParseException + { + public: + option_required_exception(const std::string& option) + : OptionParseException( + "Option " + LQUOTE + option + RQUOTE + " is required but not present" + ) + { + } + }; + + namespace values + { + namespace + { + std::basic_regex integer_pattern + ("(-)?(0x)?([0-9a-zA-Z]+)|((0x)?0)"); + std::basic_regex truthy_pattern + ("(t|T)(rue)?|1"); + std::basic_regex falsy_pattern + ("(f|F)(alse)?|0"); + } + + namespace detail + { + template + struct SignedCheck; + + template + struct SignedCheck + { + template + void + operator()(bool negative, U u, const std::string& text) + { + if (negative) + { + if (u > static_cast((std::numeric_limits::min)())) + { + throw argument_incorrect_type(text); + } + } + else + { + if (u > static_cast((std::numeric_limits::max)())) + { + throw argument_incorrect_type(text); + } + } + } + }; + + template + struct SignedCheck + { + template + void + operator()(bool, U, const std::string&) {} + }; + + template + void + check_signed_range(bool negative, U value, const std::string& text) + { + SignedCheck::is_signed>()(negative, value, text); + } + } + + template + R + checked_negate(T&& t, const std::string&, std::true_type) + { + // if we got to here, then `t` is a positive number that fits into + // `R`. So to avoid MSVC C4146, we first cast it to `R`. + // See https://github.com/jarro2783/cxxopts/issues/62 for more details. + return -static_cast(t-1)-1; + } + + template + T + checked_negate(T&&, const std::string& text, std::false_type) + { + throw argument_incorrect_type(text); + } + + template + void + integer_parser(const std::string& text, T& value) + { + std::smatch match; + std::regex_match(text, match, integer_pattern); + + if (match.length() == 0) + { + throw argument_incorrect_type(text); + } + + if (match.length(4) > 0) + { + value = 0; + return; + } + + using US = typename std::make_unsigned::type; + + constexpr bool is_signed = std::numeric_limits::is_signed; + const bool negative = match.length(1) > 0; + const uint8_t base = match.length(2) > 0 ? 16 : 10; + + auto value_match = match[3]; + + US result = 0; + + for (auto iter = value_match.first; iter != value_match.second; ++iter) + { + US digit = 0; + + if (*iter >= '0' && *iter <= '9') + { + digit = static_cast(*iter - '0'); + } + else if (base == 16 && *iter >= 'a' && *iter <= 'f') + { + digit = static_cast(*iter - 'a' + 10); + } + else if (base == 16 && *iter >= 'A' && *iter <= 'F') + { + digit = static_cast(*iter - 'A' + 10); + } + else + { + throw argument_incorrect_type(text); + } + + US next = result * base + digit; + if (result > next) + { + throw argument_incorrect_type(text); + } + + result = next; + } + + detail::check_signed_range(negative, result, text); + + if (negative) + { + value = checked_negate(result, + text, + std::integral_constant()); + } + else + { + value = static_cast(result); + } + } + + template + void stringstream_parser(const std::string& text, T& value) + { + std::stringstream in(text); + in >> value; + if (!in) { + throw argument_incorrect_type(text); + } + } + + inline + void + parse_value(const std::string& text, uint8_t& value) + { + integer_parser(text, value); + } + + inline + void + parse_value(const std::string& text, int8_t& value) + { + integer_parser(text, value); + } + + inline + void + parse_value(const std::string& text, uint16_t& value) + { + integer_parser(text, value); + } + + inline + void + parse_value(const std::string& text, int16_t& value) + { + integer_parser(text, value); + } + + inline + void + parse_value(const std::string& text, uint32_t& value) + { + integer_parser(text, value); + } + + inline + void + parse_value(const std::string& text, int32_t& value) + { + integer_parser(text, value); + } + + inline + void + parse_value(const std::string& text, uint64_t& value) + { + integer_parser(text, value); + } + + inline + void + parse_value(const std::string& text, int64_t& value) + { + integer_parser(text, value); + } + + inline + void + parse_value(const std::string& text, bool& value) + { + std::smatch result; + std::regex_match(text, result, truthy_pattern); + + if (!result.empty()) + { + value = true; + return; + } + + std::regex_match(text, result, falsy_pattern); + if (!result.empty()) + { + value = false; + return; + } + + throw argument_incorrect_type(text); + } + + inline + void + parse_value(const std::string& text, std::string& value) + { + value = text; + } + + // The fallback parser. It uses the stringstream parser to parse all types + // that have not been overloaded explicitly. It has to be placed in the + // source code before all other more specialized templates. + template + void + parse_value(const std::string& text, T& value) { + stringstream_parser(text, value); + } + + template + void + parse_value(const std::string& text, std::vector& value) + { + std::stringstream in(text); + std::string token; + while(in.eof() == false && std::getline(in, token, CXXOPTS_VECTOR_DELIMITER)) { + T v; + parse_value(token, v); + value.emplace_back(std::move(v)); + } + } + +#ifdef CXXOPTS_HAS_OPTIONAL + template + void + parse_value(const std::string& text, std::optional& value) + { + T result; + parse_value(text, result); + value = std::move(result); + } +#endif + + template + struct type_is_container + { + static constexpr bool value = false; + }; + + template + struct type_is_container> + { + static constexpr bool value = true; + }; + + template + class abstract_value : public Value + { + using Self = abstract_value; + + public: + abstract_value() + : m_result(std::make_shared()) + , m_store(m_result.get()) + { + } + + abstract_value(T* t) + : m_store(t) + { + } + + virtual ~abstract_value() = default; + + abstract_value(const abstract_value& rhs) + { + if (rhs.m_result) + { + m_result = std::make_shared(); + m_store = m_result.get(); + } + else + { + m_store = rhs.m_store; + } + + m_default = rhs.m_default; + m_implicit = rhs.m_implicit; + m_default_value = rhs.m_default_value; + m_implicit_value = rhs.m_implicit_value; + } + + void + parse(const std::string& text) const + { + parse_value(text, *m_store); + } + + bool + is_container() const + { + return type_is_container::value; + } + + void + parse() const + { + parse_value(m_default_value, *m_store); + } + + bool + has_default() const + { + return m_default; + } + + bool + has_implicit() const + { + return m_implicit; + } + + std::shared_ptr + default_value(const std::string& value) + { + m_default = true; + m_default_value = value; + return shared_from_this(); + } + + std::shared_ptr + implicit_value(const std::string& value) + { + m_implicit = true; + m_implicit_value = value; + return shared_from_this(); + } + + std::shared_ptr + no_implicit_value() + { + m_implicit = false; + return shared_from_this(); + } + + std::string + get_default_value() const + { + return m_default_value; + } + + std::string + get_implicit_value() const + { + return m_implicit_value; + } + + bool + is_boolean() const + { + return std::is_same::value; + } + + const T& + get() const + { + if (m_store == nullptr) + { + return *m_result; + } + else + { + return *m_store; + } + } + + protected: + std::shared_ptr m_result; + T* m_store; + + bool m_default = false; + bool m_implicit = false; + + std::string m_default_value; + std::string m_implicit_value; + }; + + template + class standard_value : public abstract_value + { + public: + using abstract_value::abstract_value; + + std::shared_ptr + clone() const + { + return std::make_shared>(*this); + } + }; + + template <> + class standard_value : public abstract_value + { + public: + ~standard_value() = default; + + standard_value() + { + set_default_and_implicit(); + } + + standard_value(bool* b) + : abstract_value(b) + { + set_default_and_implicit(); + } + + std::shared_ptr + clone() const + { + return std::make_shared>(*this); + } + + private: + + void + set_default_and_implicit() + { + m_default = true; + m_default_value = "false"; + m_implicit = true; + m_implicit_value = "true"; + } + }; + } + + template + std::shared_ptr + value() + { + return std::make_shared>(); + } + + template + std::shared_ptr + value(T& t) + { + return std::make_shared>(&t); + } + + class OptionAdder; + + class OptionDetails + { + public: + OptionDetails + ( + const std::string& short_, + const std::string& long_, + const String& desc, + std::shared_ptr val + ) + : m_short(short_) + , m_long(long_) + , m_desc(desc) + , m_value(val) + , m_count(0) + { + } + + OptionDetails(const OptionDetails& rhs) + : m_desc(rhs.m_desc) + , m_count(rhs.m_count) + { + m_value = rhs.m_value->clone(); + } + + OptionDetails(OptionDetails&& rhs) = default; + + const String& + description() const + { + return m_desc; + } + + const Value& value() const { + return *m_value; + } + + std::shared_ptr + make_storage() const + { + return m_value->clone(); + } + + const std::string& + short_name() const + { + return m_short; + } + + const std::string& + long_name() const + { + return m_long; + } + + private: + std::string m_short; + std::string m_long; + String m_desc; + std::shared_ptr m_value; + int m_count; + }; + + struct HelpOptionDetails + { + std::string s; + std::string l; + String desc; + bool has_default; + std::string default_value; + bool has_implicit; + std::string implicit_value; + std::string arg_help; + bool is_container; + bool is_boolean; + }; + + struct HelpGroupDetails + { + std::string name; + std::string description; + std::vector options; + }; + + class OptionValue + { + public: + void + parse + ( + std::shared_ptr details, + const std::string& text + ) + { + ensure_value(details); + ++m_count; + m_value->parse(text); + } + + void + parse_default(std::shared_ptr details) + { + ensure_value(details); + m_default = true; + m_value->parse(); + } + + size_t + count() const noexcept + { + return m_count; + } + + // TODO: maybe default options should count towards the number of arguments + bool + has_default() const noexcept + { + return m_default; + } + + template + const T& + as() const + { + if (m_value == nullptr) { + throw std::domain_error("No value"); + } + +#ifdef CXXOPTS_NO_RTTI + return static_cast&>(*m_value).get(); +#else + return dynamic_cast&>(*m_value).get(); +#endif + } + + private: + void + ensure_value(std::shared_ptr details) + { + if (m_value == nullptr) + { + m_value = details->make_storage(); + } + } + + std::shared_ptr m_value; + size_t m_count = 0; + bool m_default = false; + }; + + class KeyValue + { + public: + KeyValue(std::string key_, std::string value_) + : m_key(std::move(key_)) + , m_value(std::move(value_)) + { + } + + const + std::string& + key() const + { + return m_key; + } + + const + std::string& + value() const + { + return m_value; + } + + template + T + as() const + { + T result; + values::parse_value(m_value, result); + return result; + } + + private: + std::string m_key; + std::string m_value; + }; + + class ParseResult + { + public: + + ParseResult( + const std::shared_ptr< + std::unordered_map> + >, + std::vector, + bool allow_unrecognised, + int&, char**&); + + size_t + count(const std::string& o) const + { + auto iter = m_options->find(o); + if (iter == m_options->end()) + { + return 0; + } + + auto riter = m_results.find(iter->second); + + return riter->second.count(); + } + + const OptionValue& + operator[](const std::string& option) const + { + auto iter = m_options->find(option); + + if (iter == m_options->end()) + { + throw option_not_present_exception(option); + } + + auto riter = m_results.find(iter->second); + + return riter->second; + } + + const std::vector& + arguments() const + { + return m_sequential; + } + + private: + + void + parse(int& argc, char**& argv); + + void + add_to_option(const std::string& option, const std::string& arg); + + bool + consume_positional(std::string a); + + void + parse_option + ( + std::shared_ptr value, + const std::string& name, + const std::string& arg = "" + ); + + void + parse_default(std::shared_ptr details); + + void + checked_parse_arg + ( + int argc, + char* argv[], + int& current, + std::shared_ptr value, + const std::string& name + ); + + const std::shared_ptr< + std::unordered_map> + > m_options; + std::vector m_positional; + std::vector::iterator m_next_positional; + std::unordered_set m_positional_set; + std::unordered_map, OptionValue> m_results; + + bool m_allow_unrecognised; + + std::vector m_sequential; + }; + + class Options + { + typedef std::unordered_map> + OptionMap; + public: + + Options(std::string program, std::string help_string = "") + : m_program(std::move(program)) + , m_help_string(toLocalString(std::move(help_string))) + , m_custom_help("[OPTION...]") + , m_positional_help("positional parameters") + , m_show_positional(false) + , m_allow_unrecognised(false) + , m_options(std::make_shared()) + , m_next_positional(m_positional.end()) + { + } + + Options& + positional_help(std::string help_text) + { + m_positional_help = std::move(help_text); + return *this; + } + + Options& + custom_help(std::string help_text) + { + m_custom_help = std::move(help_text); + return *this; + } + + Options& + show_positional_help() + { + m_show_positional = true; + return *this; + } + + Options& + allow_unrecognised_options() + { + m_allow_unrecognised = true; + return *this; + } + + ParseResult + parse(int& argc, char**& argv); + + OptionAdder + add_options(std::string group = ""); + + void + add_option + ( + const std::string& group, + const std::string& s, + const std::string& l, + std::string desc, + std::shared_ptr value, + std::string arg_help + ); + + //parse positional arguments into the given option + void + parse_positional(std::string option); + + void + parse_positional(std::vector options); + + void + parse_positional(std::initializer_list options); + + template + void + parse_positional(Iterator begin, Iterator end) { + parse_positional(std::vector{begin, end}); + } + + std::string + help(const std::vector& groups = {}) const; + + const std::vector + groups() const; + + const HelpGroupDetails& + group_help(const std::string& group) const; + + private: + + void + add_one_option + ( + const std::string& option, + std::shared_ptr details + ); + + String + help_one_group(const std::string& group) const; + + void + generate_group_help + ( + String& result, + const std::vector& groups + ) const; + + void + generate_all_groups_help(String& result) const; + + std::string m_program; + String m_help_string; + std::string m_custom_help; + std::string m_positional_help; + bool m_show_positional; + bool m_allow_unrecognised; + + std::shared_ptr m_options; + std::vector m_positional; + std::vector::iterator m_next_positional; + std::unordered_set m_positional_set; + + //mapping from groups to help options + std::map m_help; + }; + + class OptionAdder + { + public: + + OptionAdder(Options& options, std::string group) + : m_options(options), m_group(std::move(group)) + { + } + + OptionAdder& + operator() + ( + const std::string& opts, + const std::string& desc, + std::shared_ptr value + = ::cxxopts::value(), + std::string arg_help = "" + ); + + private: + Options& m_options; + std::string m_group; + }; + + namespace + { + constexpr int OPTION_LONGEST = 30; + constexpr int OPTION_DESC_GAP = 2; + + std::basic_regex option_matcher + ("--([[:alnum:]][-_[:alnum:]]+)(=(.*))?|-([[:alnum:]]+)"); + + std::basic_regex option_specifier + ("(([[:alnum:]]),)?[ ]*([[:alnum:]][-_[:alnum:]]*)?"); + + String + format_option + ( + const HelpOptionDetails& o + ) + { + auto& s = o.s; + auto& l = o.l; + + String result = " "; + + if (s.size() > 0) + { + result += "-" + toLocalString(s) + ","; + } + else + { + result += " "; + } + + if (l.size() > 0) + { + result += " --" + toLocalString(l); + } + + auto arg = o.arg_help.size() > 0 ? toLocalString(o.arg_help) : "arg"; + + if (!o.is_boolean) + { + if (o.has_implicit) + { + result += " [=" + arg + "(=" + toLocalString(o.implicit_value) + ")]"; + } + else + { + result += " " + arg; + } + } + + return result; + } + + String + format_description + ( + const HelpOptionDetails& o, + size_t start, + size_t width + ) + { + auto desc = o.desc; + + if (o.has_default && (!o.is_boolean || o.default_value != "false")) + { + desc += toLocalString(" (default: " + o.default_value + ")"); + } + + String result; + + auto current = std::begin(desc); + auto startLine = current; + auto lastSpace = current; + + auto size = size_t{}; + + while (current != std::end(desc)) + { + if (*current == ' ') + { + lastSpace = current; + } + + if (*current == '\n') + { + startLine = current + 1; + lastSpace = startLine; + } + else if (size > width) + { + if (lastSpace == startLine) + { + stringAppend(result, startLine, current + 1); + stringAppend(result, "\n"); + stringAppend(result, start, ' '); + startLine = current + 1; + lastSpace = startLine; + } + else + { + stringAppend(result, startLine, lastSpace); + stringAppend(result, "\n"); + stringAppend(result, start, ' '); + startLine = lastSpace + 1; + lastSpace = startLine; + } + size = 0; + } + else + { + ++size; + } + + ++current; + } + + //append whatever is left + stringAppend(result, startLine, current); + + return result; + } + } + +inline +ParseResult::ParseResult +( + const std::shared_ptr< + std::unordered_map> + > options, + std::vector positional, + bool allow_unrecognised, + int& argc, char**& argv +) +: m_options(options) +, m_positional(std::move(positional)) +, m_next_positional(m_positional.begin()) +, m_allow_unrecognised(allow_unrecognised) +{ + parse(argc, argv); +} + +inline +OptionAdder +Options::add_options(std::string group) +{ + return OptionAdder(*this, std::move(group)); +} + +inline +OptionAdder& +OptionAdder::operator() +( + const std::string& opts, + const std::string& desc, + std::shared_ptr value, + std::string arg_help +) +{ + std::match_results result; + std::regex_match(opts.c_str(), result, option_specifier); + + if (result.empty()) + { + throw invalid_option_format_error(opts); + } + + const auto& short_match = result[2]; + const auto& long_match = result[3]; + + if (!short_match.length() && !long_match.length()) + { + throw invalid_option_format_error(opts); + } else if (long_match.length() == 1 && short_match.length()) + { + throw invalid_option_format_error(opts); + } + + auto option_names = [] + ( + const std::sub_match& short_, + const std::sub_match& long_ + ) + { + if (long_.length() == 1) + { + return std::make_tuple(long_.str(), short_.str()); + } + else + { + return std::make_tuple(short_.str(), long_.str()); + } + }(short_match, long_match); + + m_options.add_option + ( + m_group, + std::get<0>(option_names), + std::get<1>(option_names), + desc, + value, + std::move(arg_help) + ); + + return *this; +} + +inline +void +ParseResult::parse_default(std::shared_ptr details) +{ + m_results[details].parse_default(details); +} + +inline +void +ParseResult::parse_option +( + std::shared_ptr value, + const std::string& /*name*/, + const std::string& arg +) +{ + auto& result = m_results[value]; + result.parse(value, arg); + + m_sequential.emplace_back(value->long_name(), arg); +} + +inline +void +ParseResult::checked_parse_arg +( + int argc, + char* argv[], + int& current, + std::shared_ptr value, + const std::string& name +) +{ + if (current + 1 >= argc) + { + if (value->value().has_implicit()) + { + parse_option(value, name, value->value().get_implicit_value()); + } + else + { + throw missing_argument_exception(name); + } + } + else + { + if (value->value().has_implicit()) + { + parse_option(value, name, value->value().get_implicit_value()); + } + else + { + parse_option(value, name, argv[current + 1]); + ++current; + } + } +} + +inline +void +ParseResult::add_to_option(const std::string& option, const std::string& arg) +{ + auto iter = m_options->find(option); + + if (iter == m_options->end()) + { + throw option_not_exists_exception(option); + } + + parse_option(iter->second, option, arg); +} + +inline +bool +ParseResult::consume_positional(std::string a) +{ + while (m_next_positional != m_positional.end()) + { + auto iter = m_options->find(*m_next_positional); + if (iter != m_options->end()) + { + auto& result = m_results[iter->second]; + if (!iter->second->value().is_container()) + { + if (result.count() == 0) + { + add_to_option(*m_next_positional, a); + ++m_next_positional; + return true; + } + else + { + ++m_next_positional; + continue; + } + } + else + { + add_to_option(*m_next_positional, a); + return true; + } + } + else + { + throw option_not_exists_exception(*m_next_positional); + } + } + + return false; +} + +inline +void +Options::parse_positional(std::string option) +{ + parse_positional(std::vector{std::move(option)}); +} + +inline +void +Options::parse_positional(std::vector options) +{ + m_positional = std::move(options); + m_next_positional = m_positional.begin(); + + m_positional_set.insert(m_positional.begin(), m_positional.end()); +} + +inline +void +Options::parse_positional(std::initializer_list options) +{ + parse_positional(std::vector(std::move(options))); +} + +inline +ParseResult +Options::parse(int& argc, char**& argv) +{ + ParseResult result(m_options, m_positional, m_allow_unrecognised, argc, argv); + return result; +} + +inline +void +ParseResult::parse(int& argc, char**& argv) +{ + int current = 1; + + int nextKeep = 1; + + bool consume_remaining = false; + + while (current != argc) + { + if (strcmp(argv[current], "--") == 0) + { + consume_remaining = true; + ++current; + break; + } + + std::match_results result; + std::regex_match(argv[current], result, option_matcher); + + if (result.empty()) + { + //not a flag + + // but if it starts with a `-`, then it's an error + if (argv[current][0] == '-' && argv[current][1] != '\0') { + if (!m_allow_unrecognised) { + throw option_syntax_exception(argv[current]); + } + } + + //if true is returned here then it was consumed, otherwise it is + //ignored + if (consume_positional(argv[current])) + { + } + else + { + argv[nextKeep] = argv[current]; + ++nextKeep; + } + //if we return from here then it was parsed successfully, so continue + } + else + { + //short or long option? + if (result[4].length() != 0) + { + const std::string& s = result[4]; + + for (std::size_t i = 0; i != s.size(); ++i) + { + std::string name(1, s[i]); + auto iter = m_options->find(name); + + if (iter == m_options->end()) + { + if (m_allow_unrecognised) + { + continue; + } + else + { + //error + throw option_not_exists_exception(name); + } + } + + auto value = iter->second; + + if (i + 1 == s.size()) + { + //it must be the last argument + checked_parse_arg(argc, argv, current, value, name); + } + else if (value->value().has_implicit()) + { + parse_option(value, name, value->value().get_implicit_value()); + } + else + { + //error + throw option_requires_argument_exception(name); + } + } + } + else if (result[1].length() != 0) + { + const std::string& name = result[1]; + + auto iter = m_options->find(name); + + if (iter == m_options->end()) + { + if (m_allow_unrecognised) + { + // keep unrecognised options in argument list, skip to next argument + argv[nextKeep] = argv[current]; + ++nextKeep; + ++current; + continue; + } + else + { + //error + throw option_not_exists_exception(name); + } + } + + auto opt = iter->second; + + //equals provided for long option? + if (result[2].length() != 0) + { + //parse the option given + + parse_option(opt, name, result[3]); + } + else + { + //parse the next argument + checked_parse_arg(argc, argv, current, opt, name); + } + } + + } + + ++current; + } + + for (auto& opt : *m_options) + { + auto& detail = opt.second; + auto& value = detail->value(); + + auto& store = m_results[detail]; + + if(value.has_default() && !store.count() && !store.has_default()){ + parse_default(detail); + } + } + + if (consume_remaining) + { + while (current < argc) + { + if (!consume_positional(argv[current])) { + break; + } + ++current; + } + + //adjust argv for any that couldn't be swallowed + while (current != argc) { + argv[nextKeep] = argv[current]; + ++nextKeep; + ++current; + } + } + + argc = nextKeep; + +} + +inline +void +Options::add_option +( + const std::string& group, + const std::string& s, + const std::string& l, + std::string desc, + std::shared_ptr value, + std::string arg_help +) +{ + auto stringDesc = toLocalString(std::move(desc)); + auto option = std::make_shared(s, l, stringDesc, value); + + if (s.size() > 0) + { + add_one_option(s, option); + } + + if (l.size() > 0) + { + add_one_option(l, option); + } + + //add the help details + auto& options = m_help[group]; + + options.options.emplace_back(HelpOptionDetails{s, l, stringDesc, + value->has_default(), value->get_default_value(), + value->has_implicit(), value->get_implicit_value(), + std::move(arg_help), + value->is_container(), + value->is_boolean()}); +} + +inline +void +Options::add_one_option +( + const std::string& option, + std::shared_ptr details +) +{ + auto in = m_options->emplace(option, details); + + if (!in.second) + { + throw option_exists_error(option); + } +} + +inline +String +Options::help_one_group(const std::string& g) const +{ + typedef std::vector> OptionHelp; + + auto group = m_help.find(g); + if (group == m_help.end()) + { + return ""; + } + + OptionHelp format; + + size_t longest = 0; + + String result; + + if (!g.empty()) + { + result += toLocalString(" " + g + " options:\n"); + } + + for (const auto& o : group->second.options) + { + if (m_positional_set.find(o.l) != m_positional_set.end() && + !m_show_positional) + { + continue; + } + + auto s = format_option(o); + longest = (std::max)(longest, stringLength(s)); + format.push_back(std::make_pair(s, String())); + } + + longest = (std::min)(longest, static_cast(OPTION_LONGEST)); + + //widest allowed description + auto allowed = size_t{76} - longest - OPTION_DESC_GAP; + + auto fiter = format.begin(); + for (const auto& o : group->second.options) + { + if (m_positional_set.find(o.l) != m_positional_set.end() && + !m_show_positional) + { + continue; + } + + auto d = format_description(o, longest + OPTION_DESC_GAP, allowed); + + result += fiter->first; + if (stringLength(fiter->first) > longest) + { + result += '\n'; + result += toLocalString(std::string(longest + OPTION_DESC_GAP, ' ')); + } + else + { + result += toLocalString(std::string(longest + OPTION_DESC_GAP - + stringLength(fiter->first), + ' ')); + } + result += d; + result += '\n'; + + ++fiter; + } + + return result; +} + +inline +void +Options::generate_group_help +( + String& result, + const std::vector& print_groups +) const +{ + for (size_t i = 0; i != print_groups.size(); ++i) + { + const String& group_help_text = help_one_group(print_groups[i]); + if (empty(group_help_text)) + { + continue; + } + result += group_help_text; + if (i < print_groups.size() - 1) + { + result += '\n'; + } + } +} + +inline +void +Options::generate_all_groups_help(String& result) const +{ + std::vector all_groups; + all_groups.reserve(m_help.size()); + + for (auto& group : m_help) + { + all_groups.push_back(group.first); + } + + generate_group_help(result, all_groups); +} + +inline +std::string +Options::help(const std::vector& help_groups) const +{ + String result = m_help_string + "\nUsage:\n " + + toLocalString(m_program) + " " + toLocalString(m_custom_help); + + if (m_positional.size() > 0 && m_positional_help.size() > 0) { + result += " " + toLocalString(m_positional_help); + } + + result += "\n\n"; + + if (help_groups.size() == 0) + { + generate_all_groups_help(result); + } + else + { + generate_group_help(result, help_groups); + } + + return toUTF8String(result); +} + +inline +const std::vector +Options::groups() const +{ + std::vector g; + + std::transform( + m_help.begin(), + m_help.end(), + std::back_inserter(g), + [] (const std::map::value_type& pair) + { + return pair.first; + } + ); + + return g; +} + +inline +const HelpGroupDetails& +Options::group_help(const std::string& group) const +{ + return m_help.at(group); +} + +} + +#endif //CXXOPTS_HPP_INCLUDED diff --git a/example/hello.c b/example/hello.c new file mode 100644 index 0000000..6288162 --- /dev/null +++ b/example/hello.c @@ -0,0 +1,184 @@ +/* + FUSE: Filesystem in Userspace + Copyright (C) 2001-2007 Miklos Szeredi + + This program can be distributed under the terms of the GNU GPLv2. + See the file GPL2.txt. +*/ + +/** @file + * + * minimal example filesystem using high-level API + * + * Compile with: + * + * gcc -Wall hello.c `pkg-config fuse3 --cflags --libs` -o hello + * + * ## Source code ## + * \include hello.c + */ + + +#define FUSE_USE_VERSION 31 + +#include +#include +#include +#include +#include +#include +#include + +/* + * Command line options + * + * We can't set default values for the char* fields here because + * fuse_opt_parse would attempt to free() them when the user specifies + * different values on the command line. + */ +static struct options { + const char *filename; + const char *contents; + int show_help; +} options; + +#define OPTION(t, p) \ + { t, offsetof(struct options, p), 1 } +static const struct fuse_opt option_spec[] = { + OPTION("--name=%s", filename), + OPTION("--contents=%s", contents), + OPTION("-h", show_help), + OPTION("--help", show_help), + FUSE_OPT_END +}; + +static void *hello_init(struct fuse_conn_info *conn, + struct fuse_config *cfg) +{ + (void) conn; + cfg->kernel_cache = 1; + + /* Test setting flags the old way */ + fuse_set_feature_flag(conn, FUSE_CAP_ASYNC_READ); + fuse_unset_feature_flag(conn, FUSE_CAP_ASYNC_READ); + + return NULL; +} + +static int hello_getattr(const char *path, struct stat *stbuf, + struct fuse_file_info *fi) +{ + (void) fi; + int res = 0; + + memset(stbuf, 0, sizeof(struct stat)); + if (strcmp(path, "/") == 0) { + stbuf->st_mode = S_IFDIR | 0755; + stbuf->st_nlink = 2; + } else if (strcmp(path+1, options.filename) == 0) { + stbuf->st_mode = S_IFREG | 0444; + stbuf->st_nlink = 1; + stbuf->st_size = strlen(options.contents); + } else + res = -ENOENT; + + return res; +} + +static int hello_readdir(const char *path, void *buf, fuse_fill_dir_t filler, + off_t offset, struct fuse_file_info *fi, + enum fuse_readdir_flags flags) +{ + (void) offset; + (void) fi; + (void) flags; + + if (strcmp(path, "/") != 0) + return -ENOENT; + + filler(buf, ".", NULL, 0, FUSE_FILL_DIR_DEFAULTS); + filler(buf, "..", NULL, 0, FUSE_FILL_DIR_DEFAULTS); + filler(buf, options.filename, NULL, 0, FUSE_FILL_DIR_DEFAULTS); + + return 0; +} + +static int hello_open(const char *path, struct fuse_file_info *fi) +{ + if (strcmp(path+1, options.filename) != 0) + return -ENOENT; + + if ((fi->flags & O_ACCMODE) != O_RDONLY) + return -EACCES; + + return 0; +} + +static int hello_read(const char *path, char *buf, size_t size, off_t offset, + struct fuse_file_info *fi) +{ + size_t len; + (void) fi; + if(strcmp(path+1, options.filename) != 0) + return -ENOENT; + + len = strlen(options.contents); + if (offset < len) { + if (offset + size > len) + size = len - offset; + memcpy(buf, options.contents + offset, size); + } else + size = 0; + + return size; +} + +static const struct fuse_operations hello_oper = { + .init = hello_init, + .getattr = hello_getattr, + .readdir = hello_readdir, + .open = hello_open, + .read = hello_read, +}; + +static void show_help(const char *progname) +{ + printf("usage: %s [options] \n\n", progname); + printf("File-system specific options:\n" + " --name= Name of the \"hello\" file\n" + " (default: \"hello\")\n" + " --contents= Contents \"hello\" file\n" + " (default \"Hello, World!\\n\")\n" + "\n"); +} + +int main(int argc, char *argv[]) +{ + int ret; + struct fuse_args args = FUSE_ARGS_INIT(argc, argv); + + /* Set defaults -- we have to use strdup so that + fuse_opt_parse can free the defaults if other + values are specified */ + options.filename = strdup("hello"); + options.contents = strdup("Hello World!\n"); + + /* Parse options */ + if (fuse_opt_parse(&args, &options, option_spec, NULL) == -1) + return 1; + + /* When --help is specified, first print our own file-system + specific help text, then signal fuse_main to show + additional help (by adding `--help` to the options again) + without usage: line (by setting argv[0] to the empty + string) */ + if (options.show_help) { + show_help(argv[0]); + assert(fuse_opt_add_arg(&args, "--help") == 0); + args.argv[0][0] = '\0'; + } + + ret = fuse_main(args.argc, args.argv, &hello_oper, NULL); + fuse_opt_free_args(&args); + return ret; +} diff --git a/example/hello_ll.c b/example/hello_ll.c new file mode 100644 index 0000000..bd5fecd --- /dev/null +++ b/example/hello_ll.c @@ -0,0 +1,297 @@ +/* + FUSE: Filesystem in Userspace + Copyright (C) 2001-2007 Miklos Szeredi + + This program can be distributed under the terms of the GNU GPLv2. + See the file GPL2.txt. +*/ + +/** @file + * + * minimal example filesystem using low-level API + * + * Compile with: + * + * gcc -Wall hello_ll.c `pkg-config fuse3 --cflags --libs` -o hello_ll + * + * Note: If the pkg-config command fails due to the absence of the fuse3.pc + * file, you should configure the path to the fuse3.pc file in the + * PKG_CONFIG_PATH variable. + * + * ## Source code ## + * \include hello_ll.c + */ + +#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 12) + +#include +#include +#include +#include +#include +#include +#include +#include + +static const char *hello_str = "Hello World!\n"; +static const char *hello_name = "hello"; + +static int hello_stat(fuse_ino_t ino, struct stat *stbuf) +{ + stbuf->st_ino = ino; + switch (ino) { + case 1: + stbuf->st_mode = S_IFDIR | 0755; + stbuf->st_nlink = 2; + break; + + case 2: + stbuf->st_mode = S_IFREG | 0444; + stbuf->st_nlink = 1; + stbuf->st_size = strlen(hello_str); + break; + + default: + return -1; + } + return 0; +} + +static void hello_ll_init(void *userdata, struct fuse_conn_info *conn) +{ + (void)userdata; + + /* Disable the receiving and processing of FUSE_INTERRUPT requests */ + conn->no_interrupt = 1; + + /* Test setting flags the old way */ + conn->want = FUSE_CAP_ASYNC_READ; + conn->want &= ~FUSE_CAP_ASYNC_READ; +} + +static void hello_ll_getattr(fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info *fi) +{ + struct stat stbuf; + + (void) fi; + + memset(&stbuf, 0, sizeof(stbuf)); + if (hello_stat(ino, &stbuf) == -1) + fuse_reply_err(req, ENOENT); + else + fuse_reply_attr(req, &stbuf, 1.0); +} + +static void hello_ll_lookup(fuse_req_t req, fuse_ino_t parent, const char *name) +{ + struct fuse_entry_param e; + + if (parent != 1 || strcmp(name, hello_name) != 0) + fuse_reply_err(req, ENOENT); + else { + memset(&e, 0, sizeof(e)); + e.ino = 2; + e.attr_timeout = 1.0; + e.entry_timeout = 1.0; + hello_stat(e.ino, &e.attr); + + fuse_reply_entry(req, &e); + } +} + +struct dirbuf { + char *p; + size_t size; +}; + +static void dirbuf_add(fuse_req_t req, struct dirbuf *b, const char *name, + fuse_ino_t ino) +{ + struct stat stbuf; + size_t oldsize = b->size; + b->size += fuse_add_direntry(req, NULL, 0, name, NULL, 0); + b->p = (char *) realloc(b->p, b->size); + memset(&stbuf, 0, sizeof(stbuf)); + stbuf.st_ino = ino; + fuse_add_direntry(req, b->p + oldsize, b->size - oldsize, name, &stbuf, + b->size); +} + +#define min(x, y) ((x) < (y) ? (x) : (y)) + +static int reply_buf_limited(fuse_req_t req, const char *buf, size_t bufsize, + off_t off, size_t maxsize) +{ + if (off < bufsize) + return fuse_reply_buf(req, buf + off, + min(bufsize - off, maxsize)); + else + return fuse_reply_buf(req, NULL, 0); +} + +static void hello_ll_readdir(fuse_req_t req, fuse_ino_t ino, size_t size, + off_t off, struct fuse_file_info *fi) +{ + (void) fi; + + if (ino != 1) + fuse_reply_err(req, ENOTDIR); + else { + struct dirbuf b; + + memset(&b, 0, sizeof(b)); + dirbuf_add(req, &b, ".", 1); + dirbuf_add(req, &b, "..", 1); + dirbuf_add(req, &b, hello_name, 2); + reply_buf_limited(req, b.p, b.size, off, size); + free(b.p); + } +} + +static void hello_ll_open(fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info *fi) +{ + if (ino != 2) + fuse_reply_err(req, EISDIR); + else if ((fi->flags & O_ACCMODE) != O_RDONLY) + fuse_reply_err(req, EACCES); + else + fuse_reply_open(req, fi); +} + +static void hello_ll_read(fuse_req_t req, fuse_ino_t ino, size_t size, + off_t off, struct fuse_file_info *fi) +{ + (void) fi; + + assert(ino == 2); + reply_buf_limited(req, hello_str, strlen(hello_str), off, size); +} + +static void hello_ll_getxattr(fuse_req_t req, fuse_ino_t ino, const char *name, + size_t size) +{ + (void)size; + assert(ino == 1 || ino == 2); + if (strcmp(name, "hello_ll_getxattr_name") == 0) + { + const char *buf = "hello_ll_getxattr_value"; + fuse_reply_buf(req, buf, strlen(buf)); + } + else + { + fuse_reply_err(req, ENOTSUP); + } +} + +static void hello_ll_setxattr(fuse_req_t req, fuse_ino_t ino, const char *name, + const char *value, size_t size, int flags) +{ + (void)flags; + (void)size; + assert(ino == 1 || ino == 2); + const char* exp_val = "hello_ll_setxattr_value"; + if (strcmp(name, "hello_ll_setxattr_name") == 0 && + strlen(exp_val) == size && + strncmp(value, exp_val, size) == 0) + { + fuse_reply_err(req, 0); + } + else + { + fuse_reply_err(req, ENOTSUP); + } +} + +static void hello_ll_removexattr(fuse_req_t req, fuse_ino_t ino, const char *name) +{ + assert(ino == 1 || ino == 2); + if (strcmp(name, "hello_ll_removexattr_name") == 0) + { + fuse_reply_err(req, 0); + } + else + { + fuse_reply_err(req, ENOTSUP); + } +} + +static const struct fuse_lowlevel_ops hello_ll_oper = { + .init = hello_ll_init, + .lookup = hello_ll_lookup, + .getattr = hello_ll_getattr, + .readdir = hello_ll_readdir, + .open = hello_ll_open, + .read = hello_ll_read, + .setxattr = hello_ll_setxattr, + .getxattr = hello_ll_getxattr, + .removexattr = hello_ll_removexattr, +}; + +int main(int argc, char *argv[]) +{ + struct fuse_args args = FUSE_ARGS_INIT(argc, argv); + struct fuse_session *se; + struct fuse_cmdline_opts opts; + struct fuse_loop_config *config; + int ret = -1; + + if (fuse_parse_cmdline(&args, &opts) != 0) + return 1; + if (opts.show_help) { + printf("usage: %s [options] \n\n", argv[0]); + fuse_cmdline_help(); + fuse_lowlevel_help(); + ret = 0; + goto err_out1; + } else if (opts.show_version) { + printf("FUSE library version %s\n", fuse_pkgversion()); + fuse_lowlevel_version(); + ret = 0; + goto err_out1; + } + + if(opts.mountpoint == NULL) { + printf("usage: %s [options] \n", argv[0]); + printf(" %s --help\n", argv[0]); + ret = 1; + goto err_out1; + } + + se = fuse_session_new(&args, &hello_ll_oper, + sizeof(hello_ll_oper), NULL); + if (se == NULL) + goto err_out1; + + if (fuse_set_signal_handlers(se) != 0) + goto err_out2; + + if (fuse_session_mount(se, opts.mountpoint) != 0) + goto err_out3; + + fuse_daemonize(opts.foreground); + + /* Block until ctrl+c or fusermount -u */ + if (opts.singlethread) + ret = fuse_session_loop(se); + else { + config = fuse_loop_cfg_create(); + fuse_loop_cfg_set_clone_fd(config, opts.clone_fd); + fuse_loop_cfg_set_max_threads(config, opts.max_threads); + ret = fuse_session_loop_mt(se, config); + fuse_loop_cfg_destroy(config); + config = NULL; + } + + fuse_session_unmount(se); +err_out3: + fuse_remove_signal_handlers(se); +err_out2: + fuse_session_destroy(se); +err_out1: + free(opts.mountpoint); + fuse_opt_free_args(&args); + + return ret ? 1 : 0; +} diff --git a/example/hello_ll_uds.c b/example/hello_ll_uds.c new file mode 100644 index 0000000..aa906b7 --- /dev/null +++ b/example/hello_ll_uds.c @@ -0,0 +1,389 @@ +/* + FUSE: Filesystem in Userspace + Copyright (C) 2001-2007 Miklos Szeredi + Copyright (C) 2022 Tofik Sonono + + This program can be distributed under the terms of the GNU GPLv2. + See the file GPL2.txt. +*/ + +/** @file + * + * minimal example filesystem using low-level API and a custom io. This custom + * io is implemented using UNIX domain sockets (of type SOCK_STREAM) + * + * Compile with: + * + * gcc -Wall hello_ll_uds.c `pkg-config fuse3 --cflags --libs` -o hello_ll_uds + * + * ## Source code ## + * \include hello_ll.c + */ + +#define FUSE_USE_VERSION 34 + + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static const char *hello_str = "Hello World!\n"; +static const char *hello_name = "hello"; + +#define DEFAULT_SOCKET_PATH "/tmp/libfuse-hello-ll.sock" + +static struct options { + const char *socket_path; +} options; + +#define OPTION(t, p) \ + { t, offsetof(struct options, p), 1 } +static const struct fuse_opt option_spec[] = { + OPTION("--socket=%s", socket_path), + FUSE_OPT_END +}; + +static int hello_stat(fuse_ino_t ino, struct stat *stbuf) +{ + stbuf->st_ino = ino; + switch (ino) { + case 1: + stbuf->st_mode = S_IFDIR | 0755; + stbuf->st_nlink = 2; + break; + + case 2: + stbuf->st_mode = S_IFREG | 0444; + stbuf->st_nlink = 1; + stbuf->st_size = strlen(hello_str); + break; + + default: + return -1; + } + return 0; +} + +static void hello_ll_getattr(fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info *fi) +{ + struct stat stbuf; + + (void) fi; + + memset(&stbuf, 0, sizeof(stbuf)); + if (hello_stat(ino, &stbuf) == -1) + fuse_reply_err(req, ENOENT); + else + fuse_reply_attr(req, &stbuf, 1.0); +} + +static void hello_ll_init(void *userdata, struct fuse_conn_info *conn) +{ + (void)userdata; + + /* Disable the receiving and processing of FUSE_INTERRUPT requests */ + conn->no_interrupt = 1; +} + +static void hello_ll_lookup(fuse_req_t req, fuse_ino_t parent, const char *name) +{ + struct fuse_entry_param e; + + if (parent != 1 || strcmp(name, hello_name) != 0) + fuse_reply_err(req, ENOENT); + else { + memset(&e, 0, sizeof(e)); + e.ino = 2; + e.attr_timeout = 1.0; + e.entry_timeout = 1.0; + hello_stat(e.ino, &e.attr); + + fuse_reply_entry(req, &e); + } +} + +struct dirbuf { + char *p; + size_t size; +}; + +static void dirbuf_add(fuse_req_t req, struct dirbuf *b, const char *name, + fuse_ino_t ino) +{ + struct stat stbuf; + size_t oldsize = b->size; + b->size += fuse_add_direntry(req, NULL, 0, name, NULL, 0); + b->p = (char *) realloc(b->p, b->size); + memset(&stbuf, 0, sizeof(stbuf)); + stbuf.st_ino = ino; + fuse_add_direntry(req, b->p + oldsize, b->size - oldsize, name, &stbuf, + b->size); +} + +#define min(x, y) ((x) < (y) ? (x) : (y)) + +static int reply_buf_limited(fuse_req_t req, const char *buf, size_t bufsize, + off_t off, size_t maxsize) +{ + if (off < bufsize) + return fuse_reply_buf(req, buf + off, + min(bufsize - off, maxsize)); + else + return fuse_reply_buf(req, NULL, 0); +} + +static void hello_ll_readdir(fuse_req_t req, fuse_ino_t ino, size_t size, + off_t off, struct fuse_file_info *fi) +{ + (void) fi; + + if (ino != 1) + fuse_reply_err(req, ENOTDIR); + else { + struct dirbuf b; + + memset(&b, 0, sizeof(b)); + dirbuf_add(req, &b, ".", 1); + dirbuf_add(req, &b, "..", 1); + dirbuf_add(req, &b, hello_name, 2); + reply_buf_limited(req, b.p, b.size, off, size); + free(b.p); + } +} + +static void hello_ll_open(fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info *fi) +{ + if (ino != 2) + fuse_reply_err(req, EISDIR); + else if ((fi->flags & O_ACCMODE) != O_RDONLY) + fuse_reply_err(req, EACCES); + else + fuse_reply_open(req, fi); +} + +static void hello_ll_read(fuse_req_t req, fuse_ino_t ino, size_t size, + off_t off, struct fuse_file_info *fi) +{ + (void) fi; + + assert(ino == 2); + reply_buf_limited(req, hello_str, strlen(hello_str), off, size); +} + +static const struct fuse_lowlevel_ops hello_ll_oper = { + .init = hello_ll_init, + .lookup = hello_ll_lookup, + .getattr = hello_ll_getattr, + .readdir = hello_ll_readdir, + .open = hello_ll_open, + .read = hello_ll_read, +}; + +static int create_socket(const char *socket_path) { + struct sockaddr_un addr; + + if (strnlen(socket_path, sizeof(addr.sun_path)) >= + sizeof(addr.sun_path)) { + printf("Socket path may not be longer than %zu characters\n", + sizeof(addr.sun_path) - 1); + return -1; + } + + if (remove(socket_path) == -1 && errno != ENOENT) { + printf("Could not delete previous socket file entry at %s. Error: " + "%s\n", socket_path, strerror(errno)); + return -1; + } + + memset(&addr, 0, sizeof(struct sockaddr_un)); + strcpy(addr.sun_path, socket_path); + + int sfd = socket(AF_UNIX, SOCK_STREAM, 0); + if (sfd == -1) { + printf("Could not create socket. Error: %s\n", strerror(errno)); + return -1; + } + + addr.sun_family = AF_UNIX; + if (bind(sfd, (struct sockaddr *) &addr, + sizeof(struct sockaddr_un)) == -1) { + printf("Could not bind socket. Error: %s\n", strerror(errno)); + return -1; + } + + if (listen(sfd, 1) == -1) + return -1; + + printf("Awaiting connection on socket at %s...\n", socket_path); + int cfd = accept(sfd, NULL, NULL); + if (cfd == -1) { + printf("Could not accept connection. Error: %s\n", + strerror(errno)); + return -1; + } else { + printf("Accepted connection!\n"); + } + return cfd; +} + +static ssize_t stream_writev(int fd, struct iovec *iov, int count, + void *userdata) { + (void)userdata; + + ssize_t written = 0; + int cur = 0; + for (;;) { + written = writev(fd, iov+cur, count-cur); + if (written < 0) + return written; + + while (cur < count && written >= iov[cur].iov_len) + written -= iov[cur++].iov_len; + if (cur == count) + break; + + iov[cur].iov_base = (char *)iov[cur].iov_base + written; + iov[cur].iov_len -= written; + } + return written; +} + + +static ssize_t readall(int fd, void *buf, size_t len) { + size_t count = 0; + + while (count < len) { + int i = read(fd, (char *)buf + count, len - count); + if (!i) + break; + + if (i < 0) + return i; + + count += i; + } + return count; +} + +static ssize_t stream_read(int fd, void *buf, size_t buf_len, void *userdata) { + (void)userdata; + + int res = readall(fd, buf, sizeof(struct fuse_in_header)); + if (res == -1) + return res; + + + uint32_t packet_len = ((struct fuse_in_header *)buf)->len; + if (packet_len > buf_len) + return -1; + + int prev_res = res; + + res = readall(fd, (char *)buf + sizeof(struct fuse_in_header), + packet_len - sizeof(struct fuse_in_header)); + + return (res == -1) ? res : (res + prev_res); +} + +static ssize_t stream_splice_send(int fdin, off_t *offin, int fdout, + off_t *offout, size_t len, + unsigned int flags, void *userdata) { + (void)userdata; + + size_t count = 0; + while (count < len) { + int i = splice(fdin, offin, fdout, offout, len - count, flags); + if (i < 1) + return i; + + count += i; + } + return count; +} + +static void fuse_cmdline_help_uds(void) +{ + printf(" -h --help print help\n" + " -V --version print version\n" + " -d -o debug enable debug output (implies -f)\n" + " --socket=PATH serve on PATH\n" + " (default: " DEFAULT_SOCKET_PATH ")\n"); +} + +int main(int argc, char *argv[]) +{ + struct fuse_args args = FUSE_ARGS_INIT(argc, argv); + struct fuse_session *se; + struct fuse_cmdline_opts opts; + const struct fuse_custom_io io = { + .writev = stream_writev, + .read = stream_read, + .splice_receive = NULL, + .splice_send = stream_splice_send, + }; + int cfd = -1; + int ret = -1; + + /* strdup: fuse_opt_parse frees the old value if --socket is given */ + options.socket_path = strdup(DEFAULT_SOCKET_PATH); + if (options.socket_path == NULL) + return 1; + + if (fuse_opt_parse(&args, &options, option_spec, NULL) == -1) + return 1; + if (fuse_parse_cmdline(&args, &opts) != 0) + return 1; + if (opts.show_help) { + printf("usage: %s [options]\n\n", argv[0]); + fuse_cmdline_help_uds(); + fuse_lowlevel_help(); + ret = 0; + goto err_out1; + } else if (opts.show_version) { + printf("FUSE library version %s\n", fuse_pkgversion()); + fuse_lowlevel_version(); + ret = 0; + goto err_out1; + } + + se = fuse_session_new(&args, &hello_ll_oper, + sizeof(hello_ll_oper), NULL); + if (se == NULL) + goto err_out1; + + if (fuse_set_signal_handlers(se) != 0) + goto err_out2; + + cfd = create_socket(options.socket_path); + if (cfd == -1) + goto err_out3; + + if (fuse_session_custom_io(se, &io, cfd) != 0) + goto err_out3; + + /* Block until ctrl+c */ + ret = fuse_session_loop(se); +err_out3: + fuse_remove_signal_handlers(se); +err_out2: + fuse_session_destroy(se); +err_out1: + free(opts.mountpoint); + fuse_opt_free_args(&args); + + return ret ? 1 : 0; +} diff --git a/example/invalidate_path.c b/example/invalidate_path.c new file mode 100644 index 0000000..3e2d245 --- /dev/null +++ b/example/invalidate_path.c @@ -0,0 +1,293 @@ +/* + FUSE: Filesystem in Userspace + Copyright (C) 2016 Nikolaus Rath + (C) 2017 EditShare LLC + + This program can be distributed under the terms of the GNU GPLv2. + See the file GPL2.txt. + */ + +/** @file + * + * This example implements a file system with two files: + * * 'current-time', whose contents change dynamically: + * it always contains the current time (same as in + * notify_inval_inode.c). + * * 'growing', whose size changes dynamically, growing + * by 1 byte after each update. This aims to check + * if cached file metadata is also invalidated. + * + * ## Compilation ## + * + * gcc -Wall invalidate_path.c `pkg-config fuse3 --cflags --libs` -o invalidate_path + * + * ## Source code ## + * \include invalidate_path.c + */ + +#define FUSE_USE_VERSION 34 + +#include +#include /* for fuse_cmdline_opts */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* We can't actually tell the kernel that there is no + timeout, so we just send a big value */ +#define NO_TIMEOUT 500000 + +#define MAX_STR_LEN 128 +#define TIME_FILE_NAME "current_time" +#define TIME_FILE_INO 2 +#define GROW_FILE_NAME "growing" +#define GROW_FILE_INO 3 + +static char time_file_contents[MAX_STR_LEN]; +static size_t grow_file_size; + +/* Command line parsing */ +struct options { + int no_notify; + int update_interval; +}; +static struct options options = { + .no_notify = 0, + .update_interval = 1, +}; + +#define OPTION(t, p) { t, offsetof(struct options, p), 1 } +static const struct fuse_opt option_spec[] = { + OPTION("--no-notify", no_notify), + OPTION("--update-interval=%d", update_interval), + FUSE_OPT_END +}; + +static void *xmp_init(struct fuse_conn_info *conn, struct fuse_config *cfg) +{ + (void) conn; + cfg->entry_timeout = NO_TIMEOUT; + cfg->attr_timeout = NO_TIMEOUT; + cfg->negative_timeout = 0; + + return NULL; +} + +static int xmp_getattr(const char *path, + struct stat *stbuf, struct fuse_file_info* fi) { + (void) fi; + if (strcmp(path, "/") == 0) { + stbuf->st_ino = 1; + stbuf->st_mode = S_IFDIR | 0755; + stbuf->st_nlink = 1; + } else if (strcmp(path, "/" TIME_FILE_NAME) == 0) { + stbuf->st_ino = TIME_FILE_INO; + stbuf->st_mode = S_IFREG | 0444; + stbuf->st_nlink = 1; + stbuf->st_size = strlen(time_file_contents); + } else if (strcmp(path, "/" GROW_FILE_NAME) == 0) { + stbuf->st_ino = GROW_FILE_INO; + stbuf->st_mode = S_IFREG | 0444; + stbuf->st_nlink = 1; + stbuf->st_size = grow_file_size; + } else { + return -ENOENT; + } + + return 0; +} + +static int xmp_readdir(const char *path, void *buf, fuse_fill_dir_t filler, + off_t offset, struct fuse_file_info *fi, + enum fuse_readdir_flags flags) { + (void) fi; + (void) offset; + (void) flags; + if (strcmp(path, "/") != 0) { + return -ENOTDIR; + } else { + (void) filler; + (void) buf; + struct stat file_stat; + xmp_getattr("/" TIME_FILE_NAME, &file_stat, NULL); + filler(buf, TIME_FILE_NAME, &file_stat, 0, FUSE_FILL_DIR_DEFAULTS); + xmp_getattr("/" GROW_FILE_NAME, &file_stat, NULL); + filler(buf, GROW_FILE_NAME, &file_stat, 0, FUSE_FILL_DIR_DEFAULTS); + return 0; + } +} + +static int xmp_open(const char *path, struct fuse_file_info *fi) { + (void) path; + /* Make cache persistent even if file is closed, + this makes it easier to see the effects */ + fi->keep_cache = 1; + return 0; +} + +static int xmp_read(const char *path, char *buf, size_t size, off_t offset, + struct fuse_file_info *fi) { + (void) fi; + (void) offset; + if (strcmp(path, "/" TIME_FILE_NAME) == 0) { + int file_length = strlen(time_file_contents); + int to_copy = offset + size <= file_length + ? size + : file_length - offset; + memcpy(buf, time_file_contents, to_copy); + return to_copy; + } else { + assert(strcmp(path, "/" GROW_FILE_NAME) == 0); + int to_copy = offset + size <= grow_file_size + ? size + : grow_file_size - offset; + memset(buf, 'x', to_copy); + return to_copy; + } +} + +static const struct fuse_operations xmp_oper = { + .init = xmp_init, + .getattr = xmp_getattr, + .readdir = xmp_readdir, + .open = xmp_open, + .read = xmp_read, +}; + +static void update_fs(void) { + static int count = 0; + struct tm tmbuf; + struct tm *now; + time_t t; + t = time(NULL); + now = localtime_r(&t, &tmbuf); + assert(now != NULL); + + int time_file_size = strftime(time_file_contents, MAX_STR_LEN, + "The current time is %H:%M:%S\n", now); + assert(time_file_size != 0); + + grow_file_size = count++; +} + +static int invalidate(struct fuse *fuse, const char *path) { + int status = fuse_invalidate_path(fuse, path); + if (status == -ENOENT) { + return 0; + } else { + return status; + } +} + +static void* update_fs_loop(void *data) { + struct fuse *fuse = (struct fuse*) data; + + while (1) { + update_fs(); + if (!options.no_notify) { + assert(invalidate(fuse, "/" TIME_FILE_NAME) == 0); + assert(invalidate(fuse, "/" GROW_FILE_NAME) == 0); + } + sleep(options.update_interval); + } + return NULL; +} + +static void show_help(const char *progname) +{ + printf("usage: %s [options] \n\n", progname); + printf("File-system specific options:\n" + " --update-interval= Update-rate of file system contents\n" + " --no-notify Disable kernel notifications\n" + "\n"); +} + +int main(int argc, char *argv[]) { + struct fuse_args args = FUSE_ARGS_INIT(argc, argv); + struct fuse *fuse; + struct fuse_cmdline_opts opts; + struct fuse_loop_config config; + int res; + + /* Initialize the files */ + update_fs(); + + if (fuse_opt_parse(&args, &options, option_spec, NULL) == -1) + return 1; + + if (fuse_parse_cmdline(&args, &opts) != 0) + return 1; + + if (opts.show_version) { + printf("FUSE library version %s\n", fuse_pkgversion()); + fuse_lowlevel_version(); + res = 0; + goto out1; + } else if (opts.show_help) { + show_help(argv[0]); + fuse_cmdline_help(); + fuse_lib_help(&args); + res = 0; + goto out1; + } else if (!opts.mountpoint) { + fprintf(stderr, "error: no mountpoint specified\n"); + res = 1; + goto out1; + } + + fuse = fuse_new(&args, &xmp_oper, sizeof(xmp_oper), NULL); + if (fuse == NULL) { + res = 1; + goto out1; + } + + if (fuse_mount(fuse,opts.mountpoint) != 0) { + res = 1; + goto out2; + } + + if (fuse_daemonize(opts.foreground) != 0) { + res = 1; + goto out3; + } + + pthread_t updater; /* Start thread to update file contents */ + int ret = pthread_create(&updater, NULL, update_fs_loop, (void *) fuse); + if (ret != 0) { + fprintf(stderr, "pthread_create failed with %s\n", strerror(ret)); + return 1; + }; + + struct fuse_session *se = fuse_get_session(fuse); + if (fuse_set_signal_handlers(se) != 0) { + res = 1; + goto out3; + } + + if (opts.singlethread) + res = fuse_loop(fuse); + else { + config.clone_fd = opts.clone_fd; + config.max_idle_threads = opts.max_idle_threads; + res = fuse_loop_mt(fuse, &config); + } + if (res) + res = 1; + + fuse_remove_signal_handlers(se); +out3: + fuse_unmount(fuse); +out2: + fuse_destroy(fuse); +out1: + free(opts.mountpoint); + fuse_opt_free_args(&args); + return res; +} diff --git a/example/ioctl.c b/example/ioctl.c new file mode 100644 index 0000000..2548fd3 --- /dev/null +++ b/example/ioctl.c @@ -0,0 +1,230 @@ +/* + FUSE fioc: FUSE ioctl example + Copyright (C) 2008 SUSE Linux Products GmbH + Copyright (C) 2008 Tejun Heo + + This program can be distributed under the terms of the GNU GPLv2. + See the file GPL2.txt. +*/ + +/** @file + * @tableofcontents + * + * This example illustrates how to write a FUSE file system that can + * process (a restricted set of) ioctls. It can be tested with the + * ioctl_client.c program. + * + * Compile with: + * + * gcc -Wall ioctl.c `pkg-config fuse3 --cflags --libs` -o ioctl + * + * ## Source code ## + * \include ioctl.c + */ + +#define FUSE_USE_VERSION 35 + +#include +#include +#include +#include +#include +#include +#include + +#include "ioctl.h" + +#define FIOC_NAME "fioc" + +enum { + FIOC_NONE, + FIOC_ROOT, + FIOC_FILE, +}; + +static void *fioc_buf; +static size_t fioc_size; + +static int fioc_resize(size_t new_size) +{ + void *new_buf; + + if (new_size == fioc_size) + return 0; + + new_buf = realloc(fioc_buf, new_size); + if (!new_buf && new_size) + return -ENOMEM; + + if (new_size > fioc_size) + memset(new_buf + fioc_size, 0, new_size - fioc_size); + + fioc_buf = new_buf; + fioc_size = new_size; + + return 0; +} + +static int fioc_expand(size_t new_size) +{ + if (new_size > fioc_size) + return fioc_resize(new_size); + return 0; +} + +static int fioc_file_type(const char *path) +{ + if (strcmp(path, "/") == 0) + return FIOC_ROOT; + if (strcmp(path, "/" FIOC_NAME) == 0) + return FIOC_FILE; + return FIOC_NONE; +} + +static int fioc_getattr(const char *path, struct stat *stbuf, + struct fuse_file_info *fi) +{ + (void) fi; + stbuf->st_uid = getuid(); + stbuf->st_gid = getgid(); + stbuf->st_atime = stbuf->st_mtime = time(NULL); + + switch (fioc_file_type(path)) { + case FIOC_ROOT: + stbuf->st_mode = S_IFDIR | 0755; + stbuf->st_nlink = 2; + break; + case FIOC_FILE: + stbuf->st_mode = S_IFREG | 0644; + stbuf->st_nlink = 1; + stbuf->st_size = fioc_size; + break; + case FIOC_NONE: + return -ENOENT; + } + + return 0; +} + +static int fioc_open(const char *path, struct fuse_file_info *fi) +{ + (void) fi; + + if (fioc_file_type(path) != FIOC_NONE) + return 0; + return -ENOENT; +} + +static int fioc_do_read(char *buf, size_t size, off_t offset) +{ + if (offset >= fioc_size) + return 0; + + if (size > fioc_size - offset) + size = fioc_size - offset; + + memcpy(buf, fioc_buf + offset, size); + + return size; +} + +static int fioc_read(const char *path, char *buf, size_t size, + off_t offset, struct fuse_file_info *fi) +{ + (void) fi; + + if (fioc_file_type(path) != FIOC_FILE) + return -EINVAL; + + return fioc_do_read(buf, size, offset); +} + +static int fioc_do_write(const char *buf, size_t size, off_t offset) +{ + if (fioc_expand(offset + size)) + return -ENOMEM; + + memcpy(fioc_buf + offset, buf, size); + + return size; +} + +static int fioc_write(const char *path, const char *buf, size_t size, + off_t offset, struct fuse_file_info *fi) +{ + (void) fi; + + if (fioc_file_type(path) != FIOC_FILE) + return -EINVAL; + + return fioc_do_write(buf, size, offset); +} + +static int fioc_truncate(const char *path, off_t size, + struct fuse_file_info *fi) +{ + (void) fi; + if (fioc_file_type(path) != FIOC_FILE) + return -EINVAL; + + return fioc_resize(size); +} + +static int fioc_readdir(const char *path, void *buf, fuse_fill_dir_t filler, + off_t offset, struct fuse_file_info *fi, + enum fuse_readdir_flags flags) +{ + (void) fi; + (void) offset; + (void) flags; + + if (fioc_file_type(path) != FIOC_ROOT) + return -ENOENT; + + filler(buf, ".", NULL, 0, FUSE_FILL_DIR_DEFAULTS); + filler(buf, "..", NULL, 0, FUSE_FILL_DIR_DEFAULTS); + filler(buf, FIOC_NAME, NULL, 0, FUSE_FILL_DIR_DEFAULTS); + + return 0; +} + +static int fioc_ioctl(const char *path, unsigned int cmd, void *arg, + struct fuse_file_info *fi, unsigned int flags, void *data) +{ + (void) arg; + (void) fi; + (void) flags; + + if (fioc_file_type(path) != FIOC_FILE) + return -EINVAL; + + if (flags & FUSE_IOCTL_COMPAT) + return -ENOSYS; + + switch (cmd) { + case FIOC_GET_SIZE: + *(size_t *)data = fioc_size; + return 0; + + case FIOC_SET_SIZE: + fioc_resize(*(size_t *)data); + return 0; + } + + return -EINVAL; +} + +static const struct fuse_operations fioc_oper = { + .getattr = fioc_getattr, + .readdir = fioc_readdir, + .truncate = fioc_truncate, + .open = fioc_open, + .read = fioc_read, + .write = fioc_write, + .ioctl = fioc_ioctl, +}; + +int main(int argc, char *argv[]) +{ + return fuse_main(argc, argv, &fioc_oper, NULL); +} diff --git a/example/ioctl.h b/example/ioctl.h new file mode 100644 index 0000000..ca7e975 --- /dev/null +++ b/example/ioctl.h @@ -0,0 +1,42 @@ +/* + FUSE-ioctl: ioctl support for FUSE + Copyright (C) 2008 SUSE Linux Products GmbH + Copyright (C) 2008 Tejun Heo + + This program can be distributed under the terms of the GNU GPLv2. + See the file GPL2.txt. +*/ + +/** @file + * @tableofcontents + * + * Header file to share definitions between the ioctl.c example file + * system and the ioctl_client.c test program. + * + * \include ioctl.h + */ + + +#include +#include +#include + +enum { + FIOC_GET_SIZE = _IOR('E', 0, size_t), + FIOC_SET_SIZE = _IOW('E', 1, size_t), + + /* + * The following two ioctls don't follow usual encoding rules + * and transfer variable amount of data. + */ + FIOC_READ = _IO('E', 2), + FIOC_WRITE = _IO('E', 3), +}; + +struct fioc_rw_arg { + off_t offset; + void *buf; + size_t size; + size_t prev_size; /* out param for previous total size */ + size_t new_size; /* out param for new total size */ +}; diff --git a/example/ioctl_client.c b/example/ioctl_client.c new file mode 100644 index 0000000..99e2750 --- /dev/null +++ b/example/ioctl_client.c @@ -0,0 +1,74 @@ +/* + FUSE fioclient: FUSE ioctl example client + Copyright (C) 2008 SUSE Linux Products GmbH + Copyright (C) 2008 Tejun Heo + + This program can be distributed under the terms of the GNU GPLv2. + See the file GPL2.txt. +*/ + +/** @file + * + * This program tests the ioctl.c example file systsem. + * + * Compile with: + * + * gcc -Wall ioctl_client.c -o ioctl_client + * + * ## Source code ## + * \include ioctl_client.c + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "ioctl.h" + +const char *usage = +"Usage: fioclient FIOC_FILE [size]\n" +"\n" +"Get size if is omitted, set size otherwise\n" +"\n"; + +int main(int argc, char **argv) +{ + size_t size; + int fd; + int ret = 0; + + if (argc < 2) { + fprintf(stderr, "%s", usage); + return 1; + } + + fd = open(argv[1], O_RDWR); + if (fd < 0) { + perror("open"); + return 1; + } + + if (argc == 2) { + if (ioctl(fd, FIOC_GET_SIZE, &size)) { + perror("ioctl"); + ret = 1; + goto out; + } + printf("%zu\n", size); + } else { + size = strtoul(argv[2], NULL, 0); + if (ioctl(fd, FIOC_SET_SIZE, &size)) { + perror("ioctl"); + ret = 1; + goto out; + } + } +out: + close(fd); + return ret; +} diff --git a/example/memfs_ll.cc b/example/memfs_ll.cc new file mode 100644 index 0000000..e0e4283 --- /dev/null +++ b/example/memfs_ll.cc @@ -0,0 +1,1188 @@ +/* + FUSE: Filesystem in Userspace + Copyright (C) 2024 DataDirect Networks. + + This program can be distributed under the terms of the GNU GPLv2. + See the file GPL2.txt. +*/ + +#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 18) + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef HAVE_LINUX_LIMITS_H +#include +#endif + +#define MEMFS_ATTR_TIMEOUT 0.0 +#define MEMFS_ENTRY_TIMEOUT 0.0 + +#define DIV_ROUND_UP(n, d) (((n) + (d) - 1) / (d)) + +class Inodes; +class Inode; +class Dentry; + +static void memfs_panic(std::string_view message); + +struct DirHandle { + std::vector > > entries; + size_t offset; + + DirHandle(const std::vector< + std::pair > > &entries) + : entries(entries) + , offset(0) + { + } +}; + +class Inode { + private: + uint64_t ino; // Unique inode number + std::string name; + bool is_dir_; + time_t ctime; + time_t mtime; + time_t atime; + mode_t mode; + std::vector content; + std::vector dentries; + mutable std::mutex mutex; + uint64_t nlookup; + mutable std::mutex attr_mutex; + std::atomic nlink; + uid_t uid; + gid_t gid; + + friend class Inodes; + + public: + Inode(uint64_t ino, const std::string &n, bool dir) + : ino(ino) + , name(n) + , is_dir_(dir) + , ctime(time(NULL)) + , mtime(ctime) + , atime(ctime) + , mode(dir ? S_IFDIR | 0755 : S_IFREG | 0644) + , nlookup(1) + , nlink(dir ? 2 : 1) + , uid(0) + , gid(0) + { + } + + uint64_t get_ino() const + { + return ino; + } + + // Method to lock the mutex + void lock() const + { + mutex.lock(); + } + + // Method to unlock the mutex + void unlock() const + { + mutex.unlock(); + } + + void inc_lookup() + { + std::lock_guard lock(mutex); + nlookup++; + } + + uint64_t dec_lookup(uint64_t count) + { + std::unique_lock lock(mutex); + if (nlookup < count) { + lock.unlock(); + memfs_panic("Lookup count mismatch detected"); + } + nlookup -= count; + return nlookup; + } + + const std::string &get_name() const + { + return name; + } + bool is_dir() const + { + return is_dir_; + } + time_t get_ctime() const + { + return ctime; + } + time_t get_mtime() const + { + std::lock_guard lock(mutex); + return mtime; + } + mode_t get_mode() const + { + std::lock_guard lock(attr_mutex); + return mode; + } + + size_t content_size() const + { + return content.size(); + } + + void read_content(char *buf, size_t size, off_t offset) const + { + size_t bytes_to_read = std::min(size, content.size() - (size_t)offset); + std::copy(content.begin() + offset, + content.begin() + offset + bytes_to_read, buf); + } + + void write_content(const char *buf, size_t size, off_t offset) + { + std::lock_guard lock(mutex); + if (offset + size > content.size()) { + content.resize(offset + size); + } + std::copy(buf, buf + size, content.begin() + offset); + mtime = time(NULL); + } + + void set_uid(uid_t _uid) + { + std::lock_guard lock(attr_mutex); + uid = _uid; + } + + void set_gid(gid_t _gid) + { + std::lock_guard lock(attr_mutex); + gid = _gid; + } + + void set_mode(mode_t new_mode) + { + std::lock_guard lock(attr_mutex); + mode = new_mode; + } + + void set_atime(const struct timespec &_atime) + { + std::lock_guard lock(attr_mutex); + atime = _atime.tv_sec; + } + + void set_mtime(const struct timespec &_mtime) + { + std::lock_guard lock(mutex); + mtime = _mtime.tv_sec; + } + + void truncate(off_t size) + { + std::lock_guard attr_lock(mutex); + if (size < content.size()) { + content.resize(size); + } else if (size > content.size()) { + content.resize(size, 0); + } + mtime = time(NULL); + } + + void get_attr(struct stat *stbuf) const + { + // consistent cross-domain snapshot: content lock then attr lock + std::lock_guard lock(mutex); + std::lock_guard attr_lock(attr_mutex); + stbuf->st_ino = ino; + stbuf->st_mode = mode; + stbuf->st_nlink = nlink; + stbuf->st_uid = uid; + stbuf->st_gid = gid; + stbuf->st_size = content.size(); + stbuf->st_blocks = DIV_ROUND_UP(content.size(), 512); + stbuf->st_atime = atime; + stbuf->st_mtime = mtime; + stbuf->st_ctime = ctime; + } + + bool is_empty() const + { + std::lock_guard lock(mutex); + return dentries.empty(); + } + + void inc_nlink() + { + nlink++; + } + + nlink_t dec_nlink() + { + nlink_t old_value = + nlink.fetch_sub(1, std::memory_order_relaxed); + if (old_value == 0) { + memfs_panic("Attempting to decrement nlink below zero"); + } + return old_value - 1; + } + + nlink_t get_nlink() const + { + return nlink.load(std::memory_order_relaxed); + } + + /** + * Methods that need Dentry knowledge + */ + int add_child_locked(const std::string &name, Dentry *child_dentry); + int add_child(const std::string &name, Dentry *child_dentry); + int remove_child(const std::string &name); + std::vector > > + get_children() const; + Dentry *find_child_locked(const std::string &name) const; + Dentry *find_child(const std::string &name) const; +}; + +class Dentry { + public: + std::string name; + std::shared_ptr inode; + + Dentry(const std::string &n, std::shared_ptr i) + : name(n) + , inode(std::move(i)) + { + } + + uint64_t get_ino() const + { + return inode->get_ino(); + } + bool is_dir() const + { + return inode->is_dir(); + } + const std::string &get_name() const + { + return name; + } + + time_t get_ctime() const + { + return inode->get_ctime(); + } + time_t get_mtime() const + { + return inode->get_mtime(); + } + mode_t get_mode() const + { + return inode->get_mode(); + } + size_t content_size() const + { + return inode->content_size(); + } + + Inode *get_inode() const + { + return inode.get(); + } + + void inc_lookup() + { + inode->inc_lookup(); + } +}; + +class Inodes { + private: + std::unordered_map > inodes; + mutable std::shared_mutex inodes_mutex; + std::atomic next_ino{ FUSE_ROOT_ID + 1 }; + + public: + Inodes() + { + auto root = std::make_shared(FUSE_ROOT_ID, "/", true); + root->mode = S_IFDIR | 0755; + root->nlink = 2; // . and .. + inodes[FUSE_ROOT_ID] = std::move(root); + } + + // New lock method + void lock() + { + inodes_mutex.lock(); + } + + // New unlock method + void unlock() + { + inodes_mutex.unlock(); + } + + void erase_locked(Inode *inode) + { + if (inode) { + inodes.erase(inode->get_ino()); + } + } + + void erase(Inode *inode) + { + std::unique_lock lock(inodes_mutex); + erase_locked(inode); + } + + std::shared_ptr find_locked(fuse_ino_t ino) + { + auto it = inodes.find(ino); + if (it == inodes.end()) { + return nullptr; + } + return it->second; + } + + std::shared_ptr find(fuse_ino_t ino) + { + std::shared_lock lock(inodes_mutex); + return find_locked(ino); + } + + std::shared_ptr create(const std::string &name, bool is_dir, + mode_t mode) + { + std::unique_lock lock(inodes_mutex); + + uint64_t ino = next_ino.fetch_add(1, std::memory_order_relaxed); + auto new_inode = std::make_shared(ino, name, is_dir); + new_inode->set_mode(mode); + + auto [it, inserted] = inodes.emplace(ino, std::move(new_inode)); + + if (!inserted) { + // This should never happen, but let's handle it just in case + return nullptr; + } + + return it->second; + } + + size_t size() const + { + std::shared_lock lock(inodes_mutex); + return inodes.size(); + } +}; + +int Inode::add_child_locked(const std::string &name, Dentry *child_dentry) +{ + if (!is_dir_) { + return ENOTDIR; + } + + // Check if a child with this name already exists + auto it = std::find_if(dentries.begin(), dentries.end(), + [&name](const Dentry *dentry) { + return dentry->get_name() == name; + }); + + if (it != dentries.end()) { + return EEXIST; + } + + dentries.push_back(child_dentry); + if (child_dentry->is_dir()) { + nlink++; + } + return 0; +} + +int Inode::add_child(const std::string &name, Dentry *child_dentry) +{ + std::lock_guard lock(mutex); + return add_child_locked(name, child_dentry); +} + +int Inode::remove_child(const std::string &name) +{ + if (!is_dir_) { + return ENOTDIR; + } + + auto it = std::find_if(dentries.begin(), dentries.end(), + [&name](const Dentry *dentry) { + return dentry->get_name() == name; + }); + + if (it == dentries.end()) { + return ENOENT; + } + + Dentry *child_dentry = *it; + dentries.erase(it); + + if (child_dentry->is_dir()) { + nlink--; + } + + delete child_dentry; + return 0; +} +Dentry *Inode::find_child_locked(const std::string &name) const +{ + if (!is_dir_) { + return nullptr; + } + + auto it = std::find_if(dentries.begin(), dentries.end(), + [&name](const Dentry *dentry) { + return dentry->get_name() == name; + }); + + return (it != dentries.end()) ? *it : nullptr; +} + +Dentry *Inode::find_child(const std::string &name) const +{ + std::lock_guard lock(mutex); + return find_child_locked(name); +} + +std::vector > > +Inode::get_children() const +{ + std::lock_guard lock(mutex); + if (!is_dir_) { + return {}; // Return an empty vector if this is not a directory + } + + std::vector > > children; + children.reserve(dentries.size()); + + for (const Dentry *dentry : dentries) { + children.emplace_back(dentry->name, dentry->inode); + } + + return children; +} +static Inodes Inodes; + +static void memfs_lookup(fuse_req_t req, fuse_ino_t parent, const char *name) +{ + auto parentInode = Inodes.find(parent); + + if (!parentInode) { + fuse_reply_err(req, ENOENT); + return; + } + + if (!parentInode->is_dir()) { + fuse_reply_err(req, ENOTDIR); + return; + } + + parentInode->lock(); + Dentry *child = parentInode->find_child_locked(name); + if (!child) { + parentInode->unlock(); + fuse_reply_err(req, ENOENT); + return; + } + std::shared_ptr child_inode = child->inode; + parentInode->unlock(); + + struct fuse_entry_param e; + memset(&e, 0, sizeof(e)); + e.ino = child_inode->get_ino(); + e.attr_timeout = MEMFS_ATTR_TIMEOUT; + e.entry_timeout = MEMFS_ENTRY_TIMEOUT; + e.attr.st_ino = child_inode->get_ino(); + e.attr.st_mode = child_inode->get_mode(); + e.attr.st_nlink = child_inode->is_dir() ? 2 : 1; + + child_inode->inc_lookup(); + + fuse_reply_entry(req, &e); +} + +static void memfs_getattr(fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info *fi) +{ + fuse_ino_t actual_ino = fi ? fi->fh : ino; + if (actual_ino == 0) { + fuse_reply_err(req, EBADF); + return; + } + + auto inode_data = Inodes.find(actual_ino); + if (!inode_data) { + fuse_reply_err(req, ENOENT); + return; + } + + struct stat stbuf; + inode_data->get_attr(&stbuf); + stbuf.st_ino = actual_ino; // Ensure the correct inode number is set + + fuse_reply_attr(req, &stbuf, MEMFS_ATTR_TIMEOUT); +} + +static void memfs_create(fuse_req_t req, fuse_ino_t parent, const char *name, + mode_t mode, struct fuse_file_info *fi) +{ + auto parentInode = Inodes.find(parent); + if (!parentInode || !parentInode->is_dir()) { + fuse_reply_err(req, ENOENT); + return; + } + + if (parentInode->find_child(name)) { + fuse_reply_err(req, EEXIST); + return; + } + + auto new_inode = Inodes.create(name, false, mode); + if (!new_inode) { + fuse_reply_err(req, EIO); + return; + } + + // Create a new Dentry and add it to the parent + Dentry *new_dentry = new Dentry(name, new_inode); + + //std::cout << "Debug: Created new Dentry at address " + // << (void *)new_dentry << ", name: '" << name + // << "', inode address: " << (void *)new_inode << std::endl; + + int error = parentInode->add_child(name, new_dentry); + if (error != 0) { + delete new_dentry; + Inodes.erase(new_inode.get()); + fuse_reply_err(req, error); + return; + } + + struct fuse_entry_param e; + memset(&e, 0, sizeof(e)); + e.ino = new_inode->get_ino(); + e.attr_timeout = MEMFS_ATTR_TIMEOUT; + e.entry_timeout = MEMFS_ENTRY_TIMEOUT; + new_inode->get_attr(&e.attr); + + fi->fh = e.ino; + fuse_reply_create(req, &e, fi); +} + +static void memfs_write(fuse_req_t req, fuse_ino_t ino, const char *buf, + size_t size, off_t offset, + [[maybe_unused]] struct fuse_file_info *fi) +{ + auto inode = Inodes.find(ino); + if (!inode) { + fuse_reply_err(req, ENOENT); + return; + } + + if (inode->is_dir()) { + fuse_reply_err(req, EISDIR); + return; + } + + // A negative offset, or an end position past the content vector's index + // type, would convert to a huge value in write_content and abort the + // resize. PTRDIFF_MAX is the std::vector size/index ceiling on both 32- + // and 64-bit builds. + if (offset < 0) { + fuse_reply_err(req, EINVAL); + return; + } + if ((uint64_t)offset > (uint64_t)PTRDIFF_MAX - size) { + fuse_reply_err(req, EFBIG); + return; + } + + inode->write_content(buf, size, offset); + fuse_reply_write(req, size); +} + +static void memfs_read(fuse_req_t req, fuse_ino_t ino, size_t size, + off_t offset, [[maybe_unused]] struct fuse_file_info *fi) +{ + auto inode = Inodes.find(ino); + if (!inode || inode->is_dir()) { + fuse_reply_err(req, ENOENT); + return; + } + + // A negative offset would slip past the offset >= content_size() guard + // on a 32-bit build (signed compare) and index before the buffer. + if (offset < 0) { + fuse_reply_err(req, EINVAL); + return; + } + + inode->lock(); + + if (offset >= inode->content_size()) { + fuse_reply_buf(req, nullptr, 0); + inode->unlock(); + return; + } + + std::vector content( + std::min(size, inode->content_size() - (size_t)offset)); + inode->read_content(content.data(), content.size(), offset); + + inode->unlock(); + + fuse_reply_buf(req, content.data(), content.size()); +} + +static void memfs_open(fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info *fi) +{ + auto inode_data = Inodes.find(ino); + if (!inode_data || inode_data->is_dir()) { + fuse_reply_err(req, ENOENT); + return; + } + + // Use the inode number as the file handle + fi->fh = ino; + fuse_reply_open(req, fi); +} + +static void memfs_opendir(fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info *fi) +{ + auto inode = Inodes.find(ino); + if (!inode || !inode->is_dir()) { + fuse_reply_err(req, ENOTDIR); + return; + } + + // Create a new DirHandle + auto dir_handle = new DirHandle(inode->get_children()); + + // Store the pointer to the DirHandle in fi->fh + fi->fh = reinterpret_cast(dir_handle); + + fuse_reply_open(req, fi); +} + +static void memfs_readdir(fuse_req_t req, [[maybe_unused]] fuse_ino_t ino, + size_t size, off_t offset, struct fuse_file_info *fi) +{ + auto *dir_handle = reinterpret_cast(fi->fh); + if (!dir_handle) { + fuse_reply_err(req, EBADF); + return; + } + + char *buffer = new char[size]; + size_t buf_size = 0; + + for (off_t i = offset; + i < static_cast(dir_handle->entries.size()); ++i) { + const auto &entry = dir_handle->entries[i]; + const std::string &name = entry.first; + const std::shared_ptr &inode = entry.second; + + struct stat stbuf; + memset(&stbuf, 0, sizeof(stbuf)); + stbuf.st_ino = inode->get_ino(); + inode->get_attr(&stbuf); + + size_t entry_size = fuse_add_direntry(req, nullptr, 0, + name.c_str(), nullptr, 0); + if (buf_size + entry_size > size) { + break; + } + + fuse_add_direntry(req, buffer + buf_size, size - buf_size, + name.c_str(), &stbuf, i + 1); + buf_size += entry_size; + } + + fuse_reply_buf(req, buffer, buf_size); + delete[] buffer; +} + +static void memfs_release(fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info *fi) +{ + // No need to remove file handle + (void)fi; + (void)ino; + fuse_reply_err(req, 0); +} + +static void memfs_releasedir(fuse_req_t req, [[maybe_unused]] fuse_ino_t ino, + struct fuse_file_info *fi) +{ + auto *dir_handle = reinterpret_cast(fi->fh); + delete dir_handle; + fuse_reply_err(req, 0); +} + +static void memfs_panic(std::string_view message) +{ + std::cerr << "MEMFS PANIC: " << message << std::endl; + std::abort(); +} + +static void memfs_forget(fuse_req_t req, fuse_ino_t ino, uint64_t nlookup) +{ + Inodes.lock(); + auto inode = Inodes.find_locked(ino); + uint64_t res; + if (inode) { + res = inode->dec_lookup(nlookup); + if (res == 0 && inode->get_nlink() == 0) + Inodes.erase_locked(inode.get()); + } + Inodes.unlock(); + fuse_reply_none(req); +} + +static void memfs_forget_multi(fuse_req_t req, size_t count, + struct fuse_forget_data *forgets) +{ + Inodes.lock(); + for (size_t i = 0; i < count; i++) { + auto inode = Inodes.find_locked(forgets[i].ino); + if (inode && inode->dec_lookup(forgets[i].nlookup) == 0 && + inode->get_nlink() == 0) + Inodes.erase_locked(inode.get()); + } + Inodes.unlock(); + fuse_reply_none(req); +} + +static void memfs_setattr(fuse_req_t req, fuse_ino_t ino, struct stat *attr, + int to_set, struct fuse_file_info *fi) +{ + fuse_ino_t actual_ino = fi ? fi->fh : ino; + if (actual_ino == 0) { + fuse_reply_err(req, EBADF); + return; + } + + auto inode_data = Inodes.find(actual_ino); + if (!inode_data) { + fuse_reply_err(req, ENOENT); + return; + } + + if (to_set & FUSE_SET_ATTR_MODE) + inode_data->set_mode(attr->st_mode); + if (to_set & FUSE_SET_ATTR_UID) + inode_data->set_uid(attr->st_uid); + if (to_set & FUSE_SET_ATTR_GID) + inode_data->set_gid(attr->st_gid); + if (to_set & FUSE_SET_ATTR_SIZE) + inode_data->truncate(attr->st_size); + if (to_set & FUSE_SET_ATTR_ATIME) + inode_data->set_atime(attr->st_atim); + if (to_set & FUSE_SET_ATTR_MTIME) + inode_data->set_mtime(attr->st_mtim); + + struct stat st; + inode_data->get_attr(&st); + + fuse_reply_attr(req, &st, MEMFS_ATTR_TIMEOUT); +} + +static void memfs_mkdir(fuse_req_t req, fuse_ino_t parent, const char *name, + mode_t mode) +{ + int error = 0; + std::shared_ptr parentInode = nullptr; + std::shared_ptr new_inode = nullptr; + Dentry *new_dentry = nullptr; + struct fuse_entry_param e; + + parentInode = Inodes.find(parent); + if (!parentInode || !parentInode->is_dir()) { + error = ENOENT; + goto out; + } + + new_inode = Inodes.create(name, true, mode | S_IFDIR); + if (!new_inode) { + error = EIO; + goto out; + } + + new_dentry = new Dentry(name, new_inode); + error = parentInode->add_child(name, new_dentry); + if (error != 0) { + goto out_cleanup; + } + + memset(&e, 0, sizeof(e)); + e.ino = new_inode->get_ino(); + e.attr_timeout = MEMFS_ATTR_TIMEOUT; + e.entry_timeout = MEMFS_ENTRY_TIMEOUT; + new_inode->get_attr(&e.attr); + +out: + if (error == 0) { + fuse_reply_entry(req, &e); + } else { + fuse_reply_err(req, error); + } + return; + +out_cleanup: + delete new_dentry; + Inodes.erase(new_inode.get()); + goto out; +} + +static void memfs_rmdir(fuse_req_t req, fuse_ino_t parent, const char *name) +{ + auto parentInode = Inodes.find(parent); + if (!parentInode || !parentInode->is_dir()) { + fuse_reply_err(req, ENOENT); + return; + } + + parentInode->lock(); + + auto child_dentry = parentInode->find_child_locked(name); + if (child_dentry == nullptr) { + parentInode->unlock(); + fuse_reply_err(req, ENOENT); + return; + } + + Inode *child = child_dentry->get_inode(); + if (!child || !child->is_dir() || !child->is_empty()) { + parentInode->unlock(); + fuse_reply_err(req, child ? (child->is_empty() ? ENOTDIR : + ENOTEMPTY) : + ENOENT); + return; + } + + parentInode->remove_child(name); + child->dec_nlink(); // This should handle removal if nlink reaches 0 + + parentInode->unlock(); + + fuse_reply_err(req, 0); +} + +static void memfs_unlink(fuse_req_t req, fuse_ino_t parent, const char *name) +{ + auto parentInode = Inodes.find(parent); + if (!parentInode || !parentInode->is_dir()) { + fuse_reply_err(req, ENOENT); + return; + } + + parentInode->lock(); + + auto child_dentry = parentInode->find_child_locked(name); + if (child_dentry == nullptr) { + parentInode->unlock(); + fuse_reply_err(req, ENOENT); + return; + } + + Inode *child = child_dentry->get_inode(); + if (!child || child->is_dir()) { + parentInode->unlock(); + fuse_reply_err(req, child ? EISDIR : ENOENT); + return; + } + + parentInode->remove_child(name); + child->dec_nlink(); + + parentInode->unlock(); + + fuse_reply_err(req, 0); +} + +static void memfs_rename(fuse_req_t req, fuse_ino_t parent, const char *name, + fuse_ino_t newparent, const char *newname, + unsigned int flags) +{ + int error = 0; + std::shared_ptr parentInode = nullptr; + std::shared_ptr newparentInode = nullptr; + Dentry *child_dentry = nullptr; + Dentry *child_dentry_copy = nullptr; + Dentry *existing_dentry = nullptr; + Inode *inode_a = nullptr; + Inode *inode_b = nullptr; + +#if defined(RENAME_EXCHANGE) && defined(RENAME_NOREPLACE) + if (flags & (RENAME_EXCHANGE | RENAME_NOREPLACE)) { + fuse_reply_err(req, EINVAL); + return; + } +#else + (void)flags; +#endif + + Inodes.lock(); + + parentInode = Inodes.find_locked(parent); + newparentInode = Inodes.find_locked(newparent); + if (!parentInode || !parentInode->is_dir() || !newparentInode || + !newparentInode->is_dir()) { + error = ENOENT; + goto out_unlock_global; + } + + inode_a = parentInode.get(); + inode_b = (parent != newparent) ? newparentInode.get() : nullptr; + if (inode_b && inode_a->get_ino() > inode_b->get_ino()) + std::swap(inode_a, inode_b); // always lock lower ino first + inode_a->lock(); + if (inode_b) + inode_b->lock(); + + child_dentry = parentInode->find_child_locked(name); + if (child_dentry == nullptr) { + error = ENOENT; + goto out_unlock; + } + + existing_dentry = newparentInode->find_child_locked(newname); + if (existing_dentry) { + Inode *existing_inode = existing_dentry->get_inode(); + if (existing_inode->is_dir() && !existing_inode->is_empty()) { + error = ENOTEMPTY; + goto out_unlock; + } + // remove_child() frees the dentry and already decrements + // newparent's nlink for a dir child; resolve the inode first. + newparentInode->remove_child(newname); + existing_inode->dec_nlink(); + } + + child_dentry_copy = new Dentry(newname, child_dentry->inode); + parentInode->remove_child(name); + newparentInode->add_child_locked(newname, child_dentry_copy); + +out_unlock: + if (inode_b) + inode_b->unlock(); + inode_a->unlock(); + +out_unlock_global: + Inodes.unlock(); + fuse_reply_err(req, error); +} + +static void memfs_link(fuse_req_t req, fuse_ino_t ino, fuse_ino_t newparent, + const char *newname) +{ + int error = 0; + std::shared_ptr src_inode = nullptr; + std::shared_ptr parent_inode = nullptr; + struct fuse_entry_param e; + Dentry *new_dentry = nullptr; + + Inodes.lock(); + + src_inode = Inodes.find_locked(ino); + if (!src_inode) { + error = ENOENT; + goto out_unlock_global; + } + + parent_inode = Inodes.find_locked(newparent); + if (!parent_inode || !parent_inode->is_dir()) { + error = ENOENT; + goto out_unlock_global; + } + + parent_inode->lock(); + + // Check if the new name already exists in the parent directory + if (parent_inode->find_child_locked(newname) != nullptr) { + error = EEXIST; + goto out_unlock_parent; + } + + src_inode->inc_nlink(); + + new_dentry = new Dentry(newname, src_inode); + error = parent_inode->add_child_locked(newname, new_dentry); + if (error != 0) { + delete new_dentry; + src_inode->dec_nlink(); + goto out_unlock_parent; + } + + // the reply below hands the kernel a new lookup reference + src_inode->inc_lookup(); + + memset(&e, 0, sizeof(e)); + e.ino = ino; + e.attr_timeout = MEMFS_ATTR_TIMEOUT; + e.entry_timeout = MEMFS_ENTRY_TIMEOUT; + src_inode->get_attr(&e.attr); + +out_unlock_parent: + parent_inode->unlock(); + +out_unlock_global: + Inodes.unlock(); + + if (error == 0) { + fuse_reply_entry(req, &e); + } else { + fuse_reply_err(req, error); + } +} + +static void memfs_statfs(fuse_req_t req, [[maybe_unused]] fuse_ino_t ino) +{ + struct statvfs stbuf; + memset(&stbuf, 0, sizeof(stbuf)); + + stbuf.f_bsize = 4096; + stbuf.f_frsize = 4096; + stbuf.f_namemax = PATH_MAX; // Maximum filename length + + stbuf.f_files = Inodes.size(); // Total inodes (files + directories) + + stbuf.f_ffree = std::numeric_limits::max() - + stbuf.f_files; // Free inodes + + // Set total and free blocks + // For simplicity, we'll set a fixed total number of blocks and calculate free blocks based on used inodes + stbuf.f_blocks = 1000000; // arbitrary number, needs to be a parameter + stbuf.f_bfree = stbuf.f_blocks - + (stbuf.f_files * + 10); // Assume each file uses 10 blocks on average + stbuf.f_bavail = stbuf.f_bfree; + + stbuf.f_fsid = 0; + + // Set flags + stbuf.f_flag = ST_NOSUID; + + fuse_reply_statfs(req, &stbuf); +} + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wmissing-field-initializers" +static const struct fuse_lowlevel_ops memfs_oper = { + .lookup = memfs_lookup, + .forget = memfs_forget, + .getattr = memfs_getattr, + .setattr = memfs_setattr, + .mkdir = memfs_mkdir, + .unlink = memfs_unlink, + .rmdir = memfs_rmdir, + .rename = memfs_rename, + .link = memfs_link, + .open = memfs_open, + .read = memfs_read, + .write = memfs_write, + .release = memfs_release, + .opendir = memfs_opendir, + .readdir = memfs_readdir, + .releasedir = memfs_releasedir, + .statfs = memfs_statfs, + .create = memfs_create, + .forget_multi = memfs_forget_multi, +}; +#pragma GCC diagnostic pop + +int main(int argc, char *argv[]) +{ + struct fuse_args args = FUSE_ARGS_INIT(argc, argv); + struct fuse_session *se; + struct fuse_cmdline_opts opts; + int ret = -1; + struct fuse_loop_config *config = fuse_loop_cfg_create(); + + if (config == NULL) { + std::cerr << "fuse_loop_cfg_create failed" << std::endl; + exit(EXIT_FAILURE); + } + + if (fuse_parse_cmdline(&args, &opts) != 0) + return 1; + + if (opts.show_help) { + printf("usage: %s [options] \n\n", argv[0]); + printf("File-system specific options:\n" + " -o opt,[opt...] mount options\n" + " -h --help print help\n" + "\n"); + fuse_cmdline_help(); + fuse_lowlevel_help(); + ret = 0; + goto err_out1; + } else if (opts.show_version) { + printf("FUSE library version %s\n", fuse_pkgversion()); + fuse_lowlevel_version(); + ret = 0; + goto err_out1; + } + + if (opts.mountpoint == NULL) { + printf("usage: %s [options] \n", argv[0]); + printf(" %s --help\n", argv[0]); + ret = 1; + goto err_out1; + } + + se = fuse_session_new(&args, &memfs_oper, sizeof(memfs_oper), NULL); + if (se == NULL) + goto err_out1; + + if (fuse_set_signal_handlers(se) != 0) + goto err_out2; + + if (fuse_session_mount(se, opts.mountpoint) != 0) + goto err_out3; + + fuse_daemonize(opts.foreground); + + ret = fuse_session_loop_mt(se, config); + + fuse_session_unmount(se); +err_out3: + fuse_remove_signal_handlers(se); +err_out2: + fuse_session_destroy(se); +err_out1: + free(opts.mountpoint); + fuse_opt_free_args(&args); + + return ret ? 1 : 0; +} diff --git a/example/meson.build b/example/meson.build new file mode 100644 index 0000000..f32f892 --- /dev/null +++ b/example/meson.build @@ -0,0 +1,48 @@ +examples = [ 'passthrough', 'passthrough_fh', + 'hello', 'hello_ll', + 'printcap', 'ioctl_client', 'poll_client', + 'ioctl', 'cuse', 'cuse_client' ] + +if not platform.endswith('bsd') and platform != 'dragonfly' + examples += [ 'passthrough_ll' ] + + if private_cfg.get('HAVE_CUSTOM_IO', false) + examples += [ 'hello_ll_uds' ] + endif + + # According to Conrad Meyer , FreeBSD doesn't + # support mounting files, This is enforced in vfs_domount_first() + # with the v_type != VDIR check. + examples += [ 'null' ] +endif + +threaded_examples = [ 'notify_inval_inode', + 'invalidate_path', + 'notify_store_retrieve', + 'notify_inval_entry', + 'poll' ] + +foreach ex : examples + executable(ex, ex + '.c', + dependencies: [ libfuse_dep ], + install: false) +endforeach + + +foreach ex : threaded_examples + executable(ex, ex + '.c', + dependencies: [ thread_dep, libfuse_dep ], + install: false) +endforeach + +if platform != 'dragonfly' and add_languages('cpp', required : false) + executable('passthrough_hp', 'passthrough_hp.cc', + dependencies: [ thread_dep, libfuse_dep ], + install: false) + executable('memfs_ll', 'memfs_ll.cc', + dependencies: [ thread_dep, libfuse_dep ], + cpp_args : '-std=c++20', + install: false) +endif + +# TODO: Link passthrough_fh with ulockmgr if available diff --git a/example/notify_inval_entry.c b/example/notify_inval_entry.c new file mode 100644 index 0000000..69ce393 --- /dev/null +++ b/example/notify_inval_entry.c @@ -0,0 +1,436 @@ +/* + FUSE: Filesystem in Userspace + Copyright (C) 2016 Nikolaus Rath + + This program can be distributed under the terms of the GNU GPLv2. + See the file GPL2.txt. +*/ + +/** @file + * + * This example implements a file system with a single file whose + * file name changes dynamically to reflect the current time. + * + * It illustrates the use of the fuse_lowlevel_notify_inval_entry() and + * fuse_lowlevel_notify_expire_entry() functions. + * + * To see the effect, first start the file system with the + * ``--no-notify`` + * + * $ notify_inval_entry --update-interval=1 --timeout=30 --no-notify mnt/ + * + * Observe that `ls` always prints the correct directory contents + * (since `readdir` output is not cached):: + * + * $ ls mnt; sleep 1; ls mnt; sleep 1; ls mnt + * Time_is_15h_48m_33s current_time + * Time_is_15h_48m_34s current_time + * Time_is_15h_48m_35s current_time + * + * However, if you try to access a file by name the kernel will + * report that it still exists: + * + * $ file=$(ls mnt/); echo $file + * Time_is_15h_50m_09s + * $ sleep 5; stat mnt/$file + * File: ‘mnt/Time_is_15h_50m_09s’ + * Size: 32 Blocks: 0 IO Block: 4096 regular file + * Device: 2ah/42d Inode: 3 Links: 1 + * Access: (0444/-r--r--r--) Uid: ( 0/ root) Gid: ( 0/ root) + * Access: 1969-12-31 16:00:00.000000000 -0800 + * Modify: 1969-12-31 16:00:00.000000000 -0800 + * Change: 1969-12-31 16:00:00.000000000 -0800 + * Birth: - + * + * Only once the kernel cache timeout has been reached will the stat + * call fail: + * + * $ sleep 30; stat mnt/$file + * stat: cannot stat ‘mnt/Time_is_15h_50m_09s’: No such file or directory + * + * In contrast, if you enable notifications you will be unable to stat + * the file as soon as the file system updates its name: + * + * $ notify_inval_entry --update-interval=1 --timeout=30 mnt/ + * $ file=$(ls mnt/); stat mnt/$file + * File: ‘mnt/Time_is_20h_42m_11s’ + * Size: 0 Blocks: 0 IO Block: 4096 regular empty file + * Device: 2ah/42d Inode: 2 Links: 1 + * Access: (0000/----------) Uid: ( 0/ root) Gid: ( 0/ root) + * Access: 1969-12-31 16:00:00.000000000 -0800 + * Modify: 1969-12-31 16:00:00.000000000 -0800 + * Change: 1969-12-31 16:00:00.000000000 -0800 + * Birth: - + * $ sleep 1; stat mnt/$file + * stat: cannot stat ‘mnt/Time_is_20h_42m_11s’: No such file or directory + * + * To use the function fuse_lowlevel_notify_expire_entry() instead of + * fuse_lowlevel_notify_inval_entry(), use the command line option --only-expire + * + * Another possible command-line option is --inc-epoch, which will use the FUSE + * low-level function fuse_lowlevel_notify_increment_epoch() instead. This will + * function will force the invalidation of all dentries next time they are + * revalidated. Note that --inc-epoch and --only-expire options are mutually + * exclusive. + * + * ## Compilation ## + * + * gcc -Wall notify_inval_entry.c `pkg-config fuse3 --cflags --libs` -o notify_inval_entry + * + * ## Source code ## + * \include notify_inval_entry.c + */ + +#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 12) + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MAX_STR_LEN 128 +static char file_name[MAX_STR_LEN]; +static fuse_ino_t file_ino = 2; +static int lookup_cnt = 0; +static pthread_t main_thread; + +/* Command line parsing */ +struct options { + int no_notify; + float timeout; + int update_interval; + int only_expire; + int inc_epoch; +}; +static struct options options = { + .timeout = 5, + .no_notify = 0, + .update_interval = 1, + .only_expire = 0, + .inc_epoch = 0, +}; + +#define OPTION(t, p) { t, offsetof(struct options, p), 1 } +static const struct fuse_opt option_spec[] = { + OPTION("--no-notify", no_notify), + OPTION("--update-interval=%d", update_interval), + OPTION("--timeout=%f", timeout), + OPTION("--only-expire", only_expire), + OPTION("--inc-epoch", inc_epoch), + FUSE_OPT_END +}; + +static int tfs_stat(fuse_ino_t ino, struct stat *stbuf) +{ + stbuf->st_ino = ino; + if (ino == FUSE_ROOT_ID) { + stbuf->st_mode = S_IFDIR | 0755; + stbuf->st_nlink = 1; + } + + else if (ino == file_ino) { + stbuf->st_mode = S_IFREG | 0000; + stbuf->st_nlink = 1; + stbuf->st_size = 0; + } + + else + return -1; + + return 0; +} + +static void tfs_init(void *userdata, struct fuse_conn_info *conn) +{ + (void)userdata; + + /* Disable the receiving and processing of FUSE_INTERRUPT requests */ + conn->no_interrupt = 1; +} + +static void tfs_lookup(fuse_req_t req, fuse_ino_t parent, const char *name) +{ + struct fuse_entry_param e; + memset(&e, 0, sizeof(e)); + + if (parent != FUSE_ROOT_ID) + goto err_out; + else if (strcmp(name, file_name) == 0) { + e.ino = file_ino; + lookup_cnt++; + } else + goto err_out; + + e.attr_timeout = options.timeout; + e.entry_timeout = options.timeout; + if (tfs_stat(e.ino, &e.attr) != 0) + goto err_out; + fuse_reply_entry(req, &e); + return; + +err_out: + fuse_reply_err(req, ENOENT); +} + +static void tfs_forget(fuse_req_t req, fuse_ino_t ino, uint64_t nlookup) +{ + (void)req; + if (ino == file_ino) + lookup_cnt -= nlookup; + else + assert(ino == FUSE_ROOT_ID); + fuse_reply_none(req); +} + +static void tfs_getattr(fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info *fi) +{ + struct stat stbuf; + + (void)fi; + + memset(&stbuf, 0, sizeof(stbuf)); + if (tfs_stat(ino, &stbuf) != 0) + fuse_reply_err(req, ENOENT); + else + fuse_reply_attr(req, &stbuf, options.timeout); +} + +struct dirbuf { + char *p; + size_t size; +}; + +static void dirbuf_add(fuse_req_t req, struct dirbuf *b, const char *name, + fuse_ino_t ino) +{ + struct stat stbuf; + size_t oldsize = b->size; + b->size += fuse_add_direntry(req, NULL, 0, name, NULL, 0); + b->p = (char *)realloc(b->p, b->size); + memset(&stbuf, 0, sizeof(stbuf)); + stbuf.st_ino = ino; + fuse_add_direntry(req, b->p + oldsize, b->size - oldsize, name, &stbuf, + b->size); +} + +#define min(x, y) ((x) < (y) ? (x) : (y)) + +static int reply_buf_limited(fuse_req_t req, const char *buf, size_t bufsize, + off_t off, size_t maxsize) +{ + if (off < bufsize) + return fuse_reply_buf(req, buf + off, + min(bufsize - off, maxsize)); + else + return fuse_reply_buf(req, NULL, 0); +} + +static void tfs_readdir(fuse_req_t req, fuse_ino_t ino, size_t size, off_t off, + struct fuse_file_info *fi) +{ + (void)fi; + + if (ino != FUSE_ROOT_ID) + fuse_reply_err(req, ENOTDIR); + else { + struct dirbuf b; + + memset(&b, 0, sizeof(b)); + dirbuf_add(req, &b, file_name, file_ino); + reply_buf_limited(req, b.p, b.size, off, size); + free(b.p); + } +} + +static const struct fuse_lowlevel_ops tfs_oper = { + .init = tfs_init, + .lookup = tfs_lookup, + .getattr = tfs_getattr, + .readdir = tfs_readdir, + .forget = tfs_forget, +}; + +static void update_fs(void) +{ + time_t t; + struct tm tmbuf; + struct tm *now; + ssize_t ret; + + t = time(NULL); + now = localtime_r(&t, &tmbuf); + assert(now != NULL); + + ret = strftime(file_name, MAX_STR_LEN, "Time_is_%Hh_%Mm_%Ss", now); + assert(ret != 0); +} + +static void *update_fs_loop(void *data) +{ + struct fuse_session *se = (struct fuse_session *)data; + char *old_name; + int ret = 0; + + while (!fuse_session_exited(se)) { + old_name = strdup(file_name); + update_fs(); + + if (!options.no_notify && lookup_cnt) { + if (options.only_expire) { // expire entry + ret = fuse_lowlevel_notify_expire_entry( + se, FUSE_ROOT_ID, old_name, + strlen(old_name)); + + // no kernel support + if (ret == -ENOSYS) { + printf("%s not supported by kernel\n", + "fuse_lowlevel_notify_expire_entry"); + break; + } + + // 1) ret == 0: successful expire of an existing entry + // 2) ret == -ENOENT: kernel has already expired the entry / + // entry does not exist anymore in the kernel + assert(ret == 0 || ret == -ENOENT); + } else if (options.inc_epoch) { // increment epoch + ret = fuse_lowlevel_notify_increment_epoch(se); + + if (ret == -ENOSYS) { + printf("%s not supported by kernel\n", + "fuse_lowlevel_notify_increment_epoch"); + break; + } + assert(ret == 0); + } else { // invalidate entry + assert(fuse_lowlevel_notify_inval_entry( + se, FUSE_ROOT_ID, old_name, + strlen(old_name)) == 0); + } + } + free(old_name); + sleep(options.update_interval); + } + + if (ret == -ENOSYS) { + printf("Exiting...\n"); + + fuse_session_exit(se); + // Make sure to exit now, rather than on next request from userspace + pthread_kill(main_thread, SIGPIPE); + } + + return NULL; +} + +static void show_help(const char *progname) +{ + printf("usage: %s [options] \n\n", progname); + printf("File-system specific options:\n" + " --timeout= Timeout for kernel caches\n" + " --update-interval= Update-rate of file system contents\n" + " --no-notify Disable kernel notifications\n" + " --only-expire Expire entries instead of invalidating them\n" + " --inc-epoch Increment epoch, invalidating all dentries\n" + "\n"); +} + +int main(int argc, char *argv[]) +{ + struct fuse_args args = FUSE_ARGS_INIT(argc, argv); + struct fuse_session *se; + struct fuse_cmdline_opts opts; + struct fuse_loop_config *config; + pthread_t updater; + int ret = -1; + + if (fuse_opt_parse(&args, &options, option_spec, NULL) == -1) + return 1; + + if (fuse_parse_cmdline(&args, &opts) != 0) + return 1; + if (opts.show_help) { + show_help(argv[0]); + fuse_cmdline_help(); + fuse_lowlevel_help(); + ret = 0; + goto err_out1; + } else if (opts.show_version) { + printf("FUSE library version %s\n", fuse_pkgversion()); + fuse_lowlevel_version(); + ret = 0; + goto err_out1; + } + if (options.only_expire && options.inc_epoch) { + printf("'only-expire' and 'inc-epoch' options are exclusive\n"); + ret = 0; + goto err_out1; + } + + /* Initial contents */ + update_fs(); + + se = fuse_session_new(&args, &tfs_oper, sizeof(tfs_oper), &se); + if (se == NULL) + goto err_out1; + + if (fuse_set_signal_handlers(se) != 0) + goto err_out2; + + if (fuse_session_mount(se, opts.mountpoint) != 0) + goto err_out3; + + fuse_daemonize(opts.foreground); + + // Needed to ensure that the main thread continues/restarts processing as soon + // as the fuse session ends (immediately after calling fuse_session_exit() ) + // and not only on the next request from userspace + main_thread = pthread_self(); + + /* Start thread to update file contents */ + ret = pthread_create(&updater, NULL, update_fs_loop, (void *)se); + if (ret != 0) { + fprintf(stderr, "pthread_create failed with %s\n", + strerror(ret)); + goto err_out3; + } + + /* Block until ctrl+c or fusermount -u */ + if (opts.singlethread) { + ret = fuse_session_loop(se); + } else { + config = fuse_loop_cfg_create(); + fuse_loop_cfg_set_clone_fd(config, opts.clone_fd); + fuse_loop_cfg_set_max_threads(config, opts.max_threads); + ret = fuse_session_loop_mt(se, config); + fuse_loop_cfg_destroy(config); + config = NULL; + } + + fuse_session_unmount(se); +err_out3: + fuse_remove_signal_handlers(se); +err_out2: + fuse_session_destroy(se); +err_out1: + free(opts.mountpoint); + fuse_opt_free_args(&args); + + return ret ? 1 : 0; +} + +/** + * Local Variables: + * mode: c + * indent-tabs-mode: nil + * c-basic-offset: 4 + * End: + */ diff --git a/example/notify_inval_inode.c b/example/notify_inval_inode.c new file mode 100644 index 0000000..c028530 --- /dev/null +++ b/example/notify_inval_inode.c @@ -0,0 +1,407 @@ +/* + FUSE: Filesystem in Userspace + Copyright (C) 2016 Nikolaus Rath + + This program can be distributed under the terms of the GNU GPLv2. + See the file GPL2.txt. +*/ + +/** @file + * + * This example implements a file system with a single file whose + * contents change dynamically: it always contains the current time. + * + * While notify_store_retrieve.c uses fuse_lowlevel_notify_store() to + * actively push the updated data into the kernel cache, this example + * uses fuse_lowlevel_notify_inval_inode() to notify the kernel that + * the cache has to be invalidated - but the kernel still has to + * explicitly request the updated data on the next read. + * + * To see the effect, first start the file system with the + * ``--no-notify`` option: + * + * $ notify_inval_inode --update-interval=1 --no-notify mnt/ + * + * Observe that the output never changes, even though the file system + * updates it once per second. This is because the contents are cached + * in the kernel: + * + * $ for i in 1 2 3 4 5; do + * > cat mnt/current_time + * > sleep 1 + * > done + * The current time is 15:58:18 + * The current time is 15:58:18 + * The current time is 15:58:18 + * The current time is 15:58:18 + * The current time is 15:58:18 + * + * If you instead enable the notification functions, the changes become + * visible: + * + * $ notify_inval_inode --update-interval=1 mnt/ + * $ for i in 1 2 3 4 5; do + * > cat mnt/current_time + * > sleep 1 + * > done + * The current time is 15:58:40 + * The current time is 15:58:41 + * The current time is 15:58:42 + * The current time is 15:58:43 + * The current time is 15:58:44 + * + * ## Compilation ## + * + * gcc -Wall notify_inval_inode.c `pkg-config fuse3 --cflags --libs` -o notify_inval_inode + * + * ## Source code ## + * \include notify_inval_inode.c + */ + +#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 12) + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* We can't actually tell the kernel that there is no + timeout, so we just send a big value */ +#define NO_TIMEOUT 500000 + +#define MAX_STR_LEN 128 +#define FILE_INO 2 +#define FILE_NAME "current_time" +static char file_contents[MAX_STR_LEN]; +static int lookup_cnt = 0; +static size_t file_size; +static _Atomic bool is_stop = false; + +/* Command line parsing */ +struct options { + int no_notify; + int update_interval; +}; +static struct options options = { + .no_notify = 0, + .update_interval = 1, +}; + +#define OPTION(t, p) { t, offsetof(struct options, p), 1 } +static const struct fuse_opt option_spec[] = { + OPTION("--no-notify", no_notify), + OPTION("--update-interval=%d", update_interval), FUSE_OPT_END +}; + +static int tfs_stat(fuse_ino_t ino, struct stat *stbuf) +{ + stbuf->st_ino = ino; + if (ino == FUSE_ROOT_ID) { + stbuf->st_mode = S_IFDIR | 0755; + stbuf->st_nlink = 1; + } + + else if (ino == FILE_INO) { + stbuf->st_mode = S_IFREG | 0444; + stbuf->st_nlink = 1; + stbuf->st_size = file_size; + } + + else + return -1; + + return 0; +} + +static void tfs_init(void *userdata, struct fuse_conn_info *conn) +{ + (void)userdata; + + /* Disable the receiving and processing of FUSE_INTERRUPT requests */ + conn->no_interrupt = 1; +} + +static void tfs_destroy(void *userarg) +{ + (void)userarg; + + is_stop = true; +} + +static void tfs_lookup(fuse_req_t req, fuse_ino_t parent, const char *name) +{ + struct fuse_entry_param e; + memset(&e, 0, sizeof(e)); + + if (parent != FUSE_ROOT_ID) + goto err_out; + else if (strcmp(name, FILE_NAME) == 0) { + e.ino = FILE_INO; + lookup_cnt++; + } else + goto err_out; + + e.attr_timeout = NO_TIMEOUT; + e.entry_timeout = NO_TIMEOUT; + if (tfs_stat(e.ino, &e.attr) != 0) + goto err_out; + fuse_reply_entry(req, &e); + return; + +err_out: + fuse_reply_err(req, ENOENT); +} + +static void tfs_forget(fuse_req_t req, fuse_ino_t ino, uint64_t nlookup) +{ + (void)req; + if (ino == FILE_INO) + lookup_cnt -= nlookup; + else + assert(ino == FUSE_ROOT_ID); + fuse_reply_none(req); +} + +static void tfs_getattr(fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info *fi) +{ + struct stat stbuf; + + (void)fi; + + memset(&stbuf, 0, sizeof(stbuf)); + if (tfs_stat(ino, &stbuf) != 0) + fuse_reply_err(req, ENOENT); + else + fuse_reply_attr(req, &stbuf, NO_TIMEOUT); +} + +struct dirbuf { + char *p; + size_t size; +}; + +static void dirbuf_add(fuse_req_t req, struct dirbuf *b, const char *name, + fuse_ino_t ino) +{ + struct stat stbuf; + size_t oldsize = b->size; + b->size += fuse_add_direntry(req, NULL, 0, name, NULL, 0); + b->p = (char *)realloc(b->p, b->size); + memset(&stbuf, 0, sizeof(stbuf)); + stbuf.st_ino = ino; + fuse_add_direntry(req, b->p + oldsize, b->size - oldsize, name, &stbuf, + b->size); +} + +#define min(x, y) ((x) < (y) ? (x) : (y)) + +static int reply_buf_limited(fuse_req_t req, const char *buf, size_t bufsize, + off_t off, size_t maxsize) +{ + if (off < bufsize) + return fuse_reply_buf(req, buf + off, + min(bufsize - off, maxsize)); + else + return fuse_reply_buf(req, NULL, 0); +} + +static void tfs_readdir(fuse_req_t req, fuse_ino_t ino, size_t size, off_t off, + struct fuse_file_info *fi) +{ + (void)fi; + + if (ino != FUSE_ROOT_ID) + fuse_reply_err(req, ENOTDIR); + else { + struct dirbuf b; + + memset(&b, 0, sizeof(b)); + dirbuf_add(req, &b, FILE_NAME, FILE_INO); + reply_buf_limited(req, b.p, b.size, off, size); + free(b.p); + } +} + +static void tfs_open(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi) +{ + /* Make cache persistent even if file is closed, + this makes it easier to see the effects */ + fi->keep_cache = 1; + + if (ino == FUSE_ROOT_ID) + fuse_reply_err(req, EISDIR); + else if ((fi->flags & O_ACCMODE) != O_RDONLY) + fuse_reply_err(req, EACCES); + else if (ino == FILE_INO) + fuse_reply_open(req, fi); + else { + // This should not happen + fprintf(stderr, "Got open for non-existing inode!\n"); + fuse_reply_err(req, ENOENT); + } +} + +static void tfs_read(fuse_req_t req, fuse_ino_t ino, size_t size, off_t off, + struct fuse_file_info *fi) +{ + (void)fi; + + assert(ino == FILE_INO); + reply_buf_limited(req, file_contents, file_size, off, size); +} + +static const struct fuse_lowlevel_ops tfs_oper = { + .init = tfs_init, + .destroy = tfs_destroy, + .lookup = tfs_lookup, + .getattr = tfs_getattr, + .readdir = tfs_readdir, + .open = tfs_open, + .read = tfs_read, + .forget = tfs_forget, +}; + +static void update_fs(void) +{ + struct tm tmbuf; + struct tm *now; + time_t t; + t = time(NULL); + now = localtime_r(&t, &tmbuf); + assert(now != NULL); + + file_size = strftime(file_contents, MAX_STR_LEN, + "The current time is %H:%M:%S\n", now); + assert(file_size != 0); +} + +static void *update_fs_loop(void *data) +{ + struct fuse_session *se = (struct fuse_session *)data; + + while (!is_stop) { + update_fs(); + if (!options.no_notify && lookup_cnt) { + /* Only send notification if the kernel is aware of the inode */ + + /* Some errors (ENOENT, EBADF, ENODEV) have to be accepted as they + * might come up during umount, when kernel side already releases + * all inodes, but does not send FUSE_DESTROY yet. + */ + int ret = fuse_lowlevel_notify_inval_inode(se, FILE_INO, + 0, 0); + if ((ret != 0 && !is_stop) && ret != -ENOENT && + ret != -EBADF && ret != -ENODEV) { + fprintf(stderr, + "ERROR: fuse_lowlevel_notify_store() failed with %s (%d)\n", + strerror(-ret), -ret); + abort(); + } + } + sleep(options.update_interval); + } + return NULL; +} + +static void show_help(const char *progname) +{ + printf("usage: %s [options] \n\n", progname); + printf("File-system specific options:\n" + " --update-interval= Update-rate of file system contents\n" + " --no-notify Disable kernel notifications\n" + "\n"); +} + +int main(int argc, char *argv[]) +{ + struct fuse_args args = FUSE_ARGS_INIT(argc, argv); + struct fuse_session *se; + struct fuse_cmdline_opts opts; + struct fuse_loop_config *config; + pthread_t updater; + int ret = -1; + + if (fuse_opt_parse(&args, &options, option_spec, NULL) == -1) + return 1; + + if (fuse_parse_cmdline(&args, &opts) != 0) { + ret = 1; + goto err_out1; + } + + if (opts.show_help) { + show_help(argv[0]); + fuse_cmdline_help(); + fuse_lowlevel_help(); + ret = 0; + goto err_out1; + } else if (opts.show_version) { + printf("FUSE library version %s\n", fuse_pkgversion()); + fuse_lowlevel_version(); + ret = 0; + goto err_out1; + } + + /* Initial contents */ + update_fs(); + + se = fuse_session_new(&args, &tfs_oper, sizeof(tfs_oper), NULL); + if (se == NULL) + goto err_out1; + + if (fuse_set_signal_handlers(se) != 0) + goto err_out2; + + if (fuse_session_mount(se, opts.mountpoint) != 0) + goto err_out3; + + fuse_daemonize(opts.foreground); + + /* Start thread to update file contents */ + ret = pthread_create(&updater, NULL, update_fs_loop, (void *)se); + if (ret != 0) { + fprintf(stderr, "pthread_create failed with %s\n", + strerror(ret)); + goto err_out3; + } + + /* Block until ctrl+c or fusermount -u */ + if (opts.singlethread) + ret = fuse_session_loop(se); + else { + config = fuse_loop_cfg_create(); + fuse_loop_cfg_set_clone_fd(config, opts.clone_fd); + fuse_loop_cfg_set_max_threads(config, opts.max_threads); + ret = fuse_session_loop_mt(se, config); + fuse_loop_cfg_destroy(config); + config = NULL; + } + + fuse_session_unmount(se); +err_out3: + fuse_remove_signal_handlers(se); +err_out2: + fuse_session_destroy(se); +err_out1: + fuse_opt_free_args(&args); + free(opts.mountpoint); + + return ret ? 1 : 0; +} + +/** + * Local Variables: + * mode: c + * indent-tabs-mode: nil + * c-basic-offset: 4 + * End: + */ diff --git a/example/notify_store_retrieve.c b/example/notify_store_retrieve.c new file mode 100644 index 0000000..23d4333 --- /dev/null +++ b/example/notify_store_retrieve.c @@ -0,0 +1,481 @@ +/* + FUSE: Filesystem in Userspace + Copyright (C) 2016 Nikolaus Rath + + This program can be distributed under the terms of the GNU GPLv2. + See the file GPL2.txt. +*/ + +/** @file + * + * This example implements a file system with a single file whose + * contents change dynamically: it always contains the current time. + * + * While notify_inval_inode.c uses fuse_lowlevel_notify_inval_inode() + * to let the kernel know that it has to invalidate the cache, this + * example actively pushes the updated data into the kernel cache + * using fuse_lowlevel_notify_store(). + * + * To see the effect, first start the file system with the + * ``--no-notify`` option: + * + * $ notify_store_retrieve --update-interval=1 --no-notify mnt/ + * + * Observe that the output never changes, even though the file system + * updates it once per second. This is because the contents are cached + * in the kernel: + * + * $ for i in 1 2 3 4 5; do + * > cat mnt/current_time + * > sleep 1 + * > done + * The current time is 15:58:18 + * The current time is 15:58:18 + * The current time is 15:58:18 + * The current time is 15:58:18 + * The current time is 15:58:18 + * + * If you instead enable the notification functions, the changes become + * visible: + * + * $ notify_store_retrieve --update-interval=1 mnt/ + * $ for i in 1 2 3 4 5; do + * > cat mnt/current_time + * > sleep 1 + * > done + * The current time is 15:58:40 + * The current time is 15:58:41 + * The current time is 15:58:42 + * The current time is 15:58:43 + * The current time is 15:58:44 + * + * ## Compilation ## + * + * gcc -Wall notify_store_retrieve.c `pkg-config fuse3 --cflags --libs` -o notify_store_retrieve + * + * ## Source code ## + * \include notify_store_retrieve.c + */ + +#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 12) + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* We can't actually tell the kernel that there is no + timeout, so we just send a big value */ +#define NO_TIMEOUT 500000 + +#define MAX_STR_LEN 128 +#define FILE_INO 2 +#define FILE_NAME "current_time" +static char file_contents[MAX_STR_LEN]; +static int lookup_cnt = 0; +static int open_cnt = 0; +static size_t file_size; +static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; + +/* Keep track if we ever stored data (==1), and + received it back correctly (==2) */ +static int retrieve_status = 0; + +static bool is_umount = false; + +/* updater thread tid */ +static pthread_t updater; + +/* Command line parsing */ +struct options { + int no_notify; + int update_interval; +}; +static struct options options = { + .no_notify = 0, + .update_interval = 1, +}; + +#define OPTION(t, p) { t, offsetof(struct options, p), 1 } +static const struct fuse_opt option_spec[] = { + OPTION("--no-notify", no_notify), + OPTION("--update-interval=%d", update_interval), FUSE_OPT_END +}; + +static int tfs_stat(fuse_ino_t ino, struct stat *stbuf) +{ + stbuf->st_ino = ino; + if (ino == FUSE_ROOT_ID) { + stbuf->st_mode = S_IFDIR | 0755; + stbuf->st_nlink = 1; + } + + else if (ino == FILE_INO) { + stbuf->st_mode = S_IFREG | 0444; + stbuf->st_nlink = 1; + stbuf->st_size = file_size; + } + + else + return -1; + + return 0; +} + +static void tfs_init(void *userdata, struct fuse_conn_info *conn) +{ + (void)userdata; + + /* Disable the receiving and processing of FUSE_INTERRUPT requests */ + conn->no_interrupt = 1; +} + +static void tfs_lookup(fuse_req_t req, fuse_ino_t parent, const char *name) +{ + struct fuse_entry_param e; + memset(&e, 0, sizeof(e)); + + if (parent != FUSE_ROOT_ID) + goto err_out; + else if (strcmp(name, FILE_NAME) == 0) + e.ino = FILE_INO; + else + goto err_out; + + e.attr_timeout = NO_TIMEOUT; + e.entry_timeout = NO_TIMEOUT; + if (tfs_stat(e.ino, &e.attr) != 0) + goto err_out; + fuse_reply_entry(req, &e); + + /* + * must only be set when the kernel knows about the entry, + * otherwise update_fs_loop() might see a positive count, but kernel + * would not have the entry yet + */ + if (e.ino == FILE_INO) { + pthread_mutex_lock(&lock); + lookup_cnt++; + pthread_mutex_unlock(&lock); + } + + return; + +err_out: + fuse_reply_err(req, ENOENT); +} + +static void tfs_forget(fuse_req_t req, fuse_ino_t ino, uint64_t nlookup) +{ + (void)req; + if (ino == FILE_INO) { + pthread_mutex_lock(&lock); + lookup_cnt -= nlookup; + pthread_mutex_unlock(&lock); + } else + assert(ino == FUSE_ROOT_ID); + fuse_reply_none(req); +} + +static void tfs_getattr(fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info *fi) +{ + struct stat stbuf; + + (void)fi; + + memset(&stbuf, 0, sizeof(stbuf)); + if (tfs_stat(ino, &stbuf) != 0) + fuse_reply_err(req, ENOENT); + else + fuse_reply_attr(req, &stbuf, NO_TIMEOUT); +} + +struct dirbuf { + char *p; + size_t size; +}; + +static void dirbuf_add(fuse_req_t req, struct dirbuf *b, const char *name, + fuse_ino_t ino) +{ + struct stat stbuf; + size_t oldsize = b->size; + b->size += fuse_add_direntry(req, NULL, 0, name, NULL, 0); + b->p = (char *)realloc(b->p, b->size); + memset(&stbuf, 0, sizeof(stbuf)); + stbuf.st_ino = ino; + fuse_add_direntry(req, b->p + oldsize, b->size - oldsize, name, &stbuf, + b->size); +} + +#define min(x, y) ((x) < (y) ? (x) : (y)) + +static int reply_buf_limited(fuse_req_t req, const char *buf, size_t bufsize, + off_t off, size_t maxsize) +{ + if (off < bufsize) + return fuse_reply_buf(req, buf + off, + min(bufsize - off, maxsize)); + else + return fuse_reply_buf(req, NULL, 0); +} + +static void tfs_readdir(fuse_req_t req, fuse_ino_t ino, size_t size, off_t off, + struct fuse_file_info *fi) +{ + (void)fi; + + if (ino != FUSE_ROOT_ID) + fuse_reply_err(req, ENOTDIR); + else { + struct dirbuf b; + + memset(&b, 0, sizeof(b)); + dirbuf_add(req, &b, FILE_NAME, FILE_INO); + reply_buf_limited(req, b.p, b.size, off, size); + free(b.p); + } +} + +static void tfs_open(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi) +{ + /* Make cache persistent even if file is closed, + this makes it easier to see the effects */ + fi->keep_cache = 1; + + if (ino == FUSE_ROOT_ID) + fuse_reply_err(req, EISDIR); + else if ((fi->flags & O_ACCMODE) != O_RDONLY) + fuse_reply_err(req, EACCES); + else if (ino == FILE_INO) { + fuse_reply_open(req, fi); + pthread_mutex_lock(&lock); + open_cnt++; + pthread_mutex_unlock(&lock); + } else { + // This should not happen + fprintf(stderr, "Got open for non-existing inode!\n"); + fuse_reply_err(req, ENOENT); + } +} + +static void tfs_read(fuse_req_t req, fuse_ino_t ino, size_t size, off_t off, + struct fuse_file_info *fi) +{ + (void)fi; + + assert(ino == FILE_INO); + reply_buf_limited(req, file_contents, file_size, off, size); +} + +static void tfs_retrieve_reply(fuse_req_t req, void *cookie, fuse_ino_t ino, + off_t offset, struct fuse_bufvec *data) +{ + struct fuse_bufvec bufv; + char buf[MAX_STR_LEN]; + char *expected; + ssize_t ret; + + assert(ino == FILE_INO); + assert(offset == 0); + expected = (char *)cookie; + + bufv.count = 1; + bufv.idx = 0; + bufv.off = 0; + bufv.buf[0].size = MAX_STR_LEN; + bufv.buf[0].mem = buf; + bufv.buf[0].flags = 0; + + ret = fuse_buf_copy(&bufv, data, 0); + assert(ret > 0); + assert(strncmp(buf, expected, ret) == 0); + free(expected); + retrieve_status = 2; + fuse_reply_none(req); +} + +static void tfs_destroy(void *userdata) +{ + (void)userdata; + + is_umount = true; + + pthread_join(updater, NULL); +} + +static const struct fuse_lowlevel_ops tfs_oper = { + .init = tfs_init, + .lookup = tfs_lookup, + .getattr = tfs_getattr, + .readdir = tfs_readdir, + .open = tfs_open, + .read = tfs_read, + .forget = tfs_forget, + .retrieve_reply = tfs_retrieve_reply, + .destroy = tfs_destroy, +}; + +static void update_fs(void) +{ + struct tm tmbuf; + struct tm *now; + time_t t; + t = time(NULL); + now = localtime_r(&t, &tmbuf); + assert(now != NULL); + + file_size = strftime(file_contents, MAX_STR_LEN, + "The current time is %H:%M:%S\n", now); + assert(file_size != 0); +} + +static void *update_fs_loop(void *data) +{ + struct fuse_session *se = (struct fuse_session *)data; + struct fuse_bufvec bufv; + int ret; + + while (!is_umount) { + update_fs(); + pthread_mutex_lock(&lock); + if (!options.no_notify && open_cnt && lookup_cnt) { + /* Only send notification if the kernel + is aware of the inode */ + bufv.count = 1; + bufv.idx = 0; + bufv.off = 0; + bufv.buf[0].size = file_size; + bufv.buf[0].mem = file_contents; + bufv.buf[0].flags = 0; + + /* + * Some errors (ENOENT, EBADF, ENODEV) have to be accepted as they + * might come up during umount, when kernel side already releases + * all inodes, but does not send FUSE_DESTROY yet. + */ + + ret = fuse_lowlevel_notify_store(se, FILE_INO, 0, &bufv, + 0); + if ((ret != 0 && !is_umount) && ret != -ENOENT && + ret != -EBADF && ret != -ENODEV) { + fprintf(stderr, + "ERROR: fuse_lowlevel_notify_store() failed with %s (%d)\n", + strerror(-ret), -ret); + abort(); + } + + /* To make sure that everything worked correctly, ask the + kernel to send us back the stored data */ + ret = fuse_lowlevel_notify_retrieve( + se, FILE_INO, MAX_STR_LEN, 0, + (void *)strdup(file_contents)); + assert((ret == 0 || is_umount) || ret == -ENOENT || + ret == -EBADF || ret != -ENODEV); + if (retrieve_status == 0) + retrieve_status = 1; + } + pthread_mutex_unlock(&lock); + sleep(options.update_interval); + } + return NULL; +} + +static void show_help(const char *progname) +{ + printf("usage: %s [options] \n\n", progname); + printf("File-system specific options:\n" + " --update-interval= Update-rate of file system contents\n" + " --no-notify Disable kernel notifications\n" + "\n"); +} + +int main(int argc, char *argv[]) +{ + struct fuse_args args = FUSE_ARGS_INIT(argc, argv); + struct fuse_session *se; + struct fuse_cmdline_opts opts; + struct fuse_loop_config *config; + int ret = -1; + + if (fuse_opt_parse(&args, &options, option_spec, NULL) == -1) + return 1; + + if (fuse_parse_cmdline(&args, &opts) != 0) + return 1; + if (opts.show_help) { + show_help(argv[0]); + fuse_cmdline_help(); + fuse_lowlevel_help(); + ret = 0; + goto err_out1; + } else if (opts.show_version) { + printf("FUSE library version %s\n", fuse_pkgversion()); + fuse_lowlevel_version(); + ret = 0; + goto err_out1; + } + + /* Initial contents */ + update_fs(); + + se = fuse_session_new(&args, &tfs_oper, sizeof(tfs_oper), NULL); + if (se == NULL) + goto err_out1; + + if (fuse_set_signal_handlers(se) != 0) + goto err_out2; + + if (fuse_session_mount(se, opts.mountpoint) != 0) + goto err_out3; + + fuse_daemonize(opts.foreground); + + /* Start thread to update file contents */ + ret = pthread_create(&updater, NULL, update_fs_loop, (void *)se); + if (ret != 0) { + fprintf(stderr, "pthread_create failed with %s\n", + strerror(ret)); + goto err_out3; + } + + /* Block until ctrl+c or fusermount -u */ + if (opts.singlethread) + ret = fuse_session_loop(se); + else { + config = fuse_loop_cfg_create(); + fuse_loop_cfg_set_clone_fd(config, opts.clone_fd); + fuse_loop_cfg_set_max_threads(config, opts.max_threads); + ret = fuse_session_loop_mt(se, config); + fuse_loop_cfg_destroy(config); + config = NULL; + } + + assert(retrieve_status != 1); + fuse_session_unmount(se); +err_out3: + fuse_remove_signal_handlers(se); +err_out2: + fuse_session_destroy(se); +err_out1: + free(opts.mountpoint); + fuse_opt_free_args(&args); + + return ret ? 1 : 0; +} + +/** + * Local Variables: + * mode: c + * indent-tabs-mode: nil + * c-basic-offset: 4 + * End: + */ diff --git a/example/null.c b/example/null.c new file mode 100644 index 0000000..ec41def --- /dev/null +++ b/example/null.c @@ -0,0 +1,143 @@ +/* + FUSE: Filesystem in Userspace + Copyright (C) 2001-2007 Miklos Szeredi + + This program can be distributed under the terms of the GNU GPLv2. + See the file GPL2.txt. +*/ + +/** @file + * + * This "filesystem" provides only a single file. The mountpoint + * needs to be a file rather than a directory. All writes to the + * file will be discarded, and reading the file always returns + * \0. + * + * Compile with: + * + * gcc -Wall null.c `pkg-config fuse3 --cflags --libs` -o null + * + * ## Source code ## + * \include passthrough_fh.c + */ + + +#define FUSE_USE_VERSION 31 + +#include +#include +#include +#include +#include +#include +#include +#include + +static int null_getattr(const char *path, struct stat *stbuf, + struct fuse_file_info *fi) +{ + (void) fi; + + if(strcmp(path, "/") != 0) + return -ENOENT; + + stbuf->st_mode = S_IFREG | 0644; + stbuf->st_nlink = 1; + stbuf->st_uid = getuid(); + stbuf->st_gid = getgid(); + stbuf->st_size = (1ULL << 32); /* 4G */ + stbuf->st_blocks = 0; + stbuf->st_atime = stbuf->st_mtime = stbuf->st_ctime = time(NULL); + + return 0; +} + +static int null_truncate(const char *path, off_t size, + struct fuse_file_info *fi) +{ + (void) size; + (void) fi; + + if(strcmp(path, "/") != 0) + return -ENOENT; + + return 0; +} + +static int null_open(const char *path, struct fuse_file_info *fi) +{ + (void) fi; + + if(strcmp(path, "/") != 0) + return -ENOENT; + + return 0; +} + +static int null_read(const char *path, char *buf, size_t size, + off_t offset, struct fuse_file_info *fi) +{ + (void) buf; + (void) offset; + (void) fi; + + if(strcmp(path, "/") != 0) + return -ENOENT; + + if (offset >= (1ULL << 32)) + return 0; + + memset(buf, 0, size); + return size; +} + +static int null_write(const char *path, const char *buf, size_t size, + off_t offset, struct fuse_file_info *fi) +{ + (void) buf; + (void) offset; + (void) fi; + + if(strcmp(path, "/") != 0) + return -ENOENT; + + return size; +} + +static const struct fuse_operations null_oper = { + .getattr = null_getattr, + .truncate = null_truncate, + .open = null_open, + .read = null_read, + .write = null_write, +}; + +int main(int argc, char *argv[]) +{ + struct fuse_args args = FUSE_ARGS_INIT(argc, argv); + struct fuse_cmdline_opts opts; + struct stat stbuf; + + if (fuse_parse_cmdline(&args, &opts) != 0) + return 1; + fuse_opt_free_args(&args); + + if (!opts.mountpoint) { + fprintf(stderr, "missing mountpoint parameter\n"); + return 1; + } + + if (stat(opts.mountpoint, &stbuf) == -1) { + fprintf(stderr ,"failed to access mountpoint %s: %s\n", + opts.mountpoint, strerror(errno)); + free(opts.mountpoint); + return 1; + } + free(opts.mountpoint); + if (!S_ISREG(stbuf.st_mode)) { + fprintf(stderr, "mountpoint is not a regular file\n"); + return 1; + } + + return fuse_main(argc, argv, &null_oper, NULL); +} diff --git a/example/passthrough.c b/example/passthrough.c new file mode 100644 index 0000000..81a265a --- /dev/null +++ b/example/passthrough.c @@ -0,0 +1,604 @@ +/* + FUSE: Filesystem in Userspace + Copyright (C) 2001-2007 Miklos Szeredi + Copyright (C) 2011 Sebastian Pipping + + This program can be distributed under the terms of the GNU GPLv2. + See the file GPL2.txt. +*/ + +/** @file + * + * This file system mirrors the existing file system hierarchy of the + * system, starting at the root file system. This is implemented by + * just "passing through" all requests to the corresponding user-space + * libc functions. Its performance is terrible. + * + * Compile with + * + * gcc -Wall passthrough.c `pkg-config fuse3 --cflags --libs` -o passthrough + * + * ## Source code ## + * \include passthrough.c + */ + + +#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 18) + +#define _GNU_SOURCE + +#ifdef linux +/* For pread()/pwrite()/utimensat() */ +#define _XOPEN_SOURCE 700 +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef HAVE_SETXATTR +#include +#endif + +#include "passthrough_helpers.h" + +static int fill_dir_plus = 0; +static int readdir_zero_ino; + +static void *xmp_init(struct fuse_conn_info *conn, + struct fuse_config *cfg) +{ + (void) conn; + cfg->use_ino = !readdir_zero_ino; + + /* parallel_direct_writes feature depends on direct_io features. + To make parallel_direct_writes valid, need either set cfg->direct_io + in current function (recommended in high level API) or set fi->direct_io + in xmp_create() or xmp_open(). */ + // cfg->direct_io = 1; + cfg->parallel_direct_writes = 1; + + /* Pick up changes from lower filesystem right away. This is + also necessary for better hardlink support. When the kernel + calls the unlink() handler, it does not know the inode of + the to-be-removed entry and can therefore not invalidate + the cache of the associated inode - resulting in an + incorrect st_nlink value being reported for any remaining + hardlinks to this inode. */ + if (!cfg->auto_cache) { + cfg->entry_timeout = 0; + cfg->attr_timeout = 0; + cfg->negative_timeout = 0; + } + + return NULL; +} + +static int xmp_getattr(const char *path, struct stat *stbuf, + struct fuse_file_info *fi) +{ + (void) fi; + int res; + + res = lstat(path, stbuf); + if (res == -1) + return -errno; + + return 0; +} + +static int xmp_access(const char *path, int mask) +{ + int res; + + res = access(path, mask); + if (res == -1) + return -errno; + + return 0; +} + +static int xmp_readlink(const char *path, char *buf, size_t size) +{ + int res; + + res = readlink(path, buf, size - 1); + if (res == -1) + return -errno; + + buf[res] = '\0'; + return 0; +} + + +static int xmp_readdir(const char *path, void *buf, fuse_fill_dir_t filler, + off_t offset, struct fuse_file_info *fi, + enum fuse_readdir_flags flags) +{ + DIR *dp; + struct dirent *de; + + (void) offset; + (void) fi; + (void) flags; + + dp = opendir(path); + if (dp == NULL) + return -errno; + + while ((de = readdir(dp)) != NULL) { + struct stat st; + if (fill_dir_plus) { + fstatat(dirfd(dp), de->d_name, &st, + AT_SYMLINK_NOFOLLOW); + } else { + memset(&st, 0, sizeof(st)); + st.st_ino = de->d_ino; + st.st_mode = de->d_type << 12; + } + if (readdir_zero_ino) + st.st_ino = 0; + if (filler(buf, de->d_name, &st, 0, fill_dir_plus)) + break; + } + + closedir(dp); + return 0; +} + +static int xmp_mknod(const char *path, mode_t mode, dev_t rdev) +{ + int res; + + res = mknod_wrapper(AT_FDCWD, path, NULL, mode, rdev); + if (res == -1) + return -errno; + + return 0; +} + +static int xmp_mkdir(const char *path, mode_t mode) +{ + int res; + + res = mkdir(path, mode); + if (res == -1) + return -errno; + + return 0; +} + +static int xmp_unlink(const char *path) +{ + int res; + + res = unlink(path); + if (res == -1) + return -errno; + + return 0; +} + +static int xmp_rmdir(const char *path) +{ + int res; + + res = rmdir(path); + if (res == -1) + return -errno; + + return 0; +} + +static int xmp_symlink(const char *from, const char *to) +{ + int res; + + res = symlink(from, to); + if (res == -1) + return -errno; + + return 0; +} + +static int xmp_rename(const char *from, const char *to, unsigned int flags) +{ + int res; + + if (flags) + return -EINVAL; + + res = rename(from, to); + if (res == -1) + return -errno; + + return 0; +} + +static int xmp_link(const char *from, const char *to) +{ + int res; + + res = link(from, to); + if (res == -1) + return -errno; + + return 0; +} + +static int xmp_chmod(const char *path, mode_t mode, + struct fuse_file_info *fi) +{ + (void) fi; + int res; + + res = chmod(path, mode); + if (res == -1) + return -errno; + + return 0; +} + +static int xmp_chown(const char *path, uid_t uid, gid_t gid, + struct fuse_file_info *fi) +{ + (void) fi; + int res; + + res = lchown(path, uid, gid); + if (res == -1) + return -errno; + + return 0; +} + +static int xmp_truncate(const char *path, off_t size, + struct fuse_file_info *fi) +{ + int res; + + if (fi != NULL) + res = ftruncate(fi->fh, size); + else + res = truncate(path, size); + if (res == -1) + return -errno; + + return 0; +} + +#ifdef HAVE_UTIMENSAT +static int xmp_utimens(const char *path, const struct timespec ts[2], + struct fuse_file_info *fi) +{ + (void) fi; + int res; + + /* don't use utime/utimes since they follow symlinks */ + res = utimensat(0, path, ts, AT_SYMLINK_NOFOLLOW); + if (res == -1) + return -errno; + + return 0; +} +#endif + +static int xmp_create(const char *path, mode_t mode, + struct fuse_file_info *fi) +{ + int res; + + res = open(path, fi->flags, mode); + if (res == -1) + return -errno; + + fi->fh = res; + return 0; +} + +static int xmp_open(const char *path, struct fuse_file_info *fi) +{ + int res; + + res = open(path, fi->flags); + if (res == -1) + return -errno; + + /* Enable direct_io when open has flags O_DIRECT to enjoy the feature + parallel_direct_writes (i.e., to get a shared lock, not exclusive lock, + for writes to the same file). */ + if (fi->flags & O_DIRECT) { + fi->direct_io = 1; + fi->parallel_direct_writes = 1; + } + + fi->fh = res; + return 0; +} + +static int xmp_read(const char *path, char *buf, size_t size, off_t offset, + struct fuse_file_info *fi) +{ + int fd; + int res; + + if(fi == NULL) + fd = open(path, O_RDONLY); + else + fd = fi->fh; + + if (fd == -1) + return -errno; + + res = pread(fd, buf, size, offset); + if (res == -1) + res = -errno; + + if(fi == NULL) + close(fd); + return res; +} + +static int xmp_write(const char *path, const char *buf, size_t size, + off_t offset, struct fuse_file_info *fi) +{ + int fd; + int res; + + (void) fi; + if(fi == NULL) + fd = open(path, O_WRONLY); + else + fd = fi->fh; + + if (fd == -1) + return -errno; + + res = pwrite(fd, buf, size, offset); + if (res == -1) + res = -errno; + + if(fi == NULL) + close(fd); + return res; +} + +static int xmp_statfs(const char *path, struct statvfs *stbuf) +{ + int res; + + res = statvfs(path, stbuf); + if (res == -1) + return -errno; + + return 0; +} + +static int xmp_release(const char *path, struct fuse_file_info *fi) +{ + (void) path; + close(fi->fh); + return 0; +} + +static int xmp_fsync(const char *path, int isdatasync, + struct fuse_file_info *fi) +{ + /* Just a stub. This method is optional and can safely be left + unimplemented */ + + (void) path; + (void) isdatasync; + (void) fi; + return 0; +} + +static int xmp_fallocate(const char *path, int mode, + off_t offset, off_t length, struct fuse_file_info *fi) +{ + int fd; + int res; + + (void) fi; + + if(fi == NULL) + fd = open(path, O_WRONLY); + else + fd = fi->fh; + + if (fd == -1) + return -errno; + + res = do_fallocate(fd, mode, offset, length); + + if(fi == NULL) + close(fd); + return res; +} + +#ifdef HAVE_SETXATTR +/* xattr operations are optional and can safely be left unimplemented */ +static int xmp_setxattr(const char *path, const char *name, const char *value, + size_t size, int flags) +{ + int res = lsetxattr(path, name, value, size, flags); + if (res == -1) + return -errno; + return 0; +} + +static int xmp_getxattr(const char *path, const char *name, char *value, + size_t size) +{ + int res = lgetxattr(path, name, value, size); + if (res == -1) + return -errno; + return res; +} + +static int xmp_listxattr(const char *path, char *list, size_t size) +{ + int res = llistxattr(path, list, size); + if (res == -1) + return -errno; + return res; +} + +static int xmp_removexattr(const char *path, const char *name) +{ + int res = lremovexattr(path, name); + if (res == -1) + return -errno; + return 0; +} +#endif /* HAVE_SETXATTR */ + +#ifdef HAVE_COPY_FILE_RANGE +static ssize_t xmp_copy_file_range(const char *path_in, + struct fuse_file_info *fi_in, + off_t offset_in, const char *path_out, + struct fuse_file_info *fi_out, + off_t offset_out, size_t len, int flags) +{ + int fd_in, fd_out; + ssize_t res; + + if(fi_in == NULL) + fd_in = open(path_in, O_RDONLY); + else + fd_in = fi_in->fh; + + if (fd_in == -1) + return -errno; + + if(fi_out == NULL) + fd_out = open(path_out, O_WRONLY); + else + fd_out = fi_out->fh; + + if (fd_out == -1) { + close(fd_in); + return -errno; + } + + res = copy_file_range(fd_in, &offset_in, fd_out, &offset_out, len, + flags); + if (res == -1) + res = -errno; + + if (fi_out == NULL) + close(fd_out); + if (fi_in == NULL) + close(fd_in); + + return res; +} +#endif + +static off_t xmp_lseek(const char *path, off_t off, int whence, struct fuse_file_info *fi) +{ + int fd; + off_t res; + + if (fi == NULL) + fd = open(path, O_RDONLY); + else + fd = fi->fh; + + if (fd == -1) + return -errno; + + res = lseek(fd, off, whence); + if (res == -1) + res = -errno; + + if (fi == NULL) + close(fd); + return res; +} + +#ifdef HAVE_STATX +static int xmp_statx(const char *path, int flags, int mask, struct statx *stxbuf, + struct fuse_file_info *fi) +{ + int fd = -1; + int res; + + if (fi) + fd = fi->fh; + + res = statx(fd, path, flags | AT_SYMLINK_NOFOLLOW, mask, stxbuf); + if (res == -1) + return -errno; + + return 0; +} +#endif + +static const struct fuse_operations xmp_oper = { + .init = xmp_init, + .getattr = xmp_getattr, + .access = xmp_access, + .readlink = xmp_readlink, + .readdir = xmp_readdir, + .mknod = xmp_mknod, + .mkdir = xmp_mkdir, + .symlink = xmp_symlink, + .unlink = xmp_unlink, + .rmdir = xmp_rmdir, + .rename = xmp_rename, + .link = xmp_link, + .chmod = xmp_chmod, + .chown = xmp_chown, + .truncate = xmp_truncate, +#ifdef HAVE_UTIMENSAT + .utimens = xmp_utimens, +#endif + .open = xmp_open, + .create = xmp_create, + .read = xmp_read, + .write = xmp_write, + .statfs = xmp_statfs, + .release = xmp_release, + .fsync = xmp_fsync, + .fallocate = xmp_fallocate, +#ifdef HAVE_SETXATTR + .setxattr = xmp_setxattr, + .getxattr = xmp_getxattr, + .listxattr = xmp_listxattr, + .removexattr = xmp_removexattr, +#endif +#ifdef HAVE_COPY_FILE_RANGE + .copy_file_range = xmp_copy_file_range, +#endif + .lseek = xmp_lseek, +#ifdef HAVE_STATX + .statx = xmp_statx, +#endif +}; + +int main(int argc, char *argv[]) +{ + enum { MAX_ARGS = 10 }; + int i,new_argc; + char *new_argv[MAX_ARGS]; + + umask(0); + /* Process the "--plus" option apart */ + for (i=0, new_argc=0; (i + Copyright (C) 2011 Sebastian Pipping + + This program can be distributed under the terms of the GNU GPLv2. + See the file GPL2.txt. +*/ + +/** @file + * + * This file system mirrors the existing file system hierarchy of the + * system, starting at the root file system. This is implemented by + * just "passing through" all requests to the corresponding user-space + * libc functions. This implementation is a little more sophisticated + * than the one in passthrough.c, so performance is not quite as bad. + * + * Compile with: + * + * gcc -Wall passthrough_fh.c `pkg-config fuse3 --cflags --libs` -lulockmgr -o passthrough_fh + * + * ## Source code ## + * \include passthrough_fh.c + */ + +#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 18) + +#define _GNU_SOURCE + +#include + +#ifdef HAVE_LIBULOCKMGR +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef HAVE_SETXATTR +#include +#endif +#include /* flock(2) */ + +#include "passthrough_helpers.h" + +static void *xmp_init(struct fuse_conn_info *conn, + struct fuse_config *cfg) +{ + (void) conn; + cfg->use_ino = 1; + cfg->nullpath_ok = 1; + + /* parallel_direct_writes feature depends on direct_io features. + To make parallel_direct_writes valid, need either set cfg->direct_io + in current function (recommended in high level API) or set fi->direct_io + in xmp_create() or xmp_open(). */ + // cfg->direct_io = 1; + cfg->parallel_direct_writes = 1; + + /* Pick up changes from lower filesystem right away. This is + also necessary for better hardlink support. When the kernel + calls the unlink() handler, it does not know the inode of + the to-be-removed entry and can therefore not invalidate + the cache of the associated inode - resulting in an + incorrect st_nlink value being reported for any remaining + hardlinks to this inode. */ + cfg->entry_timeout = 0; + cfg->attr_timeout = 0; + cfg->negative_timeout = 0; + + return NULL; +} + +static int xmp_getattr(const char *path, struct stat *stbuf, + struct fuse_file_info *fi) +{ + int res; + + (void) path; + + if(fi) + res = fstat(fi->fh, stbuf); + else + res = lstat(path, stbuf); + if (res == -1) + return -errno; + + return 0; +} + +static int xmp_access(const char *path, int mask) +{ + int res; + + res = access(path, mask); + if (res == -1) + return -errno; + + return 0; +} + +static int xmp_readlink(const char *path, char *buf, size_t size) +{ + int res; + + res = readlink(path, buf, size - 1); + if (res == -1) + return -errno; + + buf[res] = '\0'; + return 0; +} + +struct xmp_dirp { + DIR *dp; + struct dirent *entry; + off_t offset; +}; + +static int xmp_opendir(const char *path, struct fuse_file_info *fi) +{ + int res; + struct xmp_dirp *d = malloc(sizeof(struct xmp_dirp)); + if (d == NULL) + return -ENOMEM; + + d->dp = opendir(path); + if (d->dp == NULL) { + res = -errno; + free(d); + return res; + } + d->offset = 0; + d->entry = NULL; + + fi->fh = (unsigned long) d; + return 0; +} + +static inline struct xmp_dirp *get_dirp(struct fuse_file_info *fi) +{ + return (struct xmp_dirp *) (uintptr_t) fi->fh; +} + +static int xmp_readdir(const char *path, void *buf, fuse_fill_dir_t filler, + off_t offset, struct fuse_file_info *fi, + enum fuse_readdir_flags flags) +{ + struct xmp_dirp *d = get_dirp(fi); + + (void) path; + if (offset != d->offset) { +#ifndef __FreeBSD__ + seekdir(d->dp, offset); +#else + /* Subtract the one that we add when calling + telldir() below */ + seekdir(d->dp, offset-1); +#endif + d->entry = NULL; + d->offset = offset; + } + while (1) { + struct stat st; + off_t nextoff; + enum fuse_fill_dir_flags fill_flags = FUSE_FILL_DIR_DEFAULTS; + + if (!d->entry) { + d->entry = readdir(d->dp); + if (!d->entry) + break; + } +#ifdef HAVE_FSTATAT + if (flags & FUSE_READDIR_PLUS) { + int res; + + res = fstatat(dirfd(d->dp), d->entry->d_name, &st, + AT_SYMLINK_NOFOLLOW); + if (res != -1) + fill_flags |= FUSE_FILL_DIR_PLUS; + } +#endif + if (!(fill_flags & FUSE_FILL_DIR_PLUS)) { + memset(&st, 0, sizeof(st)); + st.st_ino = d->entry->d_ino; + st.st_mode = d->entry->d_type << 12; + } + nextoff = telldir(d->dp); +#ifdef __FreeBSD__ + /* Under FreeBSD, telldir() may return 0 the first time + it is called. But for libfuse, an offset of zero + means that offsets are not supported, so we shift + everything by one. */ + nextoff++; +#endif + if (filler(buf, d->entry->d_name, &st, nextoff, fill_flags)) + break; + + d->entry = NULL; + d->offset = nextoff; + } + + return 0; +} + +static int xmp_releasedir(const char *path, struct fuse_file_info *fi) +{ + struct xmp_dirp *d = get_dirp(fi); + (void) path; + closedir(d->dp); + free(d); + return 0; +} + +static int xmp_mknod(const char *path, mode_t mode, dev_t rdev) +{ + int res; + + if (S_ISFIFO(mode)) + res = mkfifo(path, mode); + else + res = mknod(path, mode, rdev); + if (res == -1) + return -errno; + + return 0; +} + +static int xmp_mkdir(const char *path, mode_t mode) +{ + int res; + + res = mkdir(path, mode); + if (res == -1) + return -errno; + + return 0; +} + +static int xmp_unlink(const char *path) +{ + int res; + + res = unlink(path); + if (res == -1) + return -errno; + + return 0; +} + +static int xmp_rmdir(const char *path) +{ + int res; + + res = rmdir(path); + if (res == -1) + return -errno; + + return 0; +} + +static int xmp_symlink(const char *from, const char *to) +{ + int res; + + res = symlink(from, to); + if (res == -1) + return -errno; + + return 0; +} + +static int xmp_rename(const char *from, const char *to, unsigned int flags) +{ + int res; + + /* When we have renameat2() in libc, then we can implement flags */ + if (flags) + return -EINVAL; + + res = rename(from, to); + if (res == -1) + return -errno; + + return 0; +} + +static int xmp_link(const char *from, const char *to) +{ + int res; + + res = link(from, to); + if (res == -1) + return -errno; + + return 0; +} + +static int xmp_chmod(const char *path, mode_t mode, + struct fuse_file_info *fi) +{ + int res; + + if(fi) + res = fchmod(fi->fh, mode); + else + res = chmod(path, mode); + if (res == -1) + return -errno; + + return 0; +} + +static int xmp_chown(const char *path, uid_t uid, gid_t gid, + struct fuse_file_info *fi) +{ + int res; + + if (fi) + res = fchown(fi->fh, uid, gid); + else + res = lchown(path, uid, gid); + if (res == -1) + return -errno; + + return 0; +} + +static int xmp_truncate(const char *path, off_t size, + struct fuse_file_info *fi) +{ + int res; + + if(fi) + res = ftruncate(fi->fh, size); + else + res = truncate(path, size); + + if (res == -1) + return -errno; + + return 0; +} + +#ifdef HAVE_UTIMENSAT +static int xmp_utimens(const char *path, const struct timespec ts[2], + struct fuse_file_info *fi) +{ + int res; + + /* don't use utime/utimes since they follow symlinks */ + if (fi) + res = futimens(fi->fh, ts); + else + res = utimensat(0, path, ts, AT_SYMLINK_NOFOLLOW); + if (res == -1) + return -errno; + + return 0; +} +#endif + +static int xmp_create(const char *path, mode_t mode, struct fuse_file_info *fi) +{ + int fd; + + fd = open(path, fi->flags, mode); + if (fd == -1) + return -errno; + + fi->fh = fd; + return 0; +} + +static int xmp_open(const char *path, struct fuse_file_info *fi) +{ + int fd; + + fd = open(path, fi->flags); + if (fd == -1) + return -errno; + + /* Enable direct_io when open has flags O_DIRECT to enjoy the feature + parallel_direct_writes (i.e., to get a shared lock, not exclusive lock, + for writes to the same file). */ + if (fi->flags & O_DIRECT) { + fi->direct_io = 1; + fi->parallel_direct_writes = 1; + } + + fi->fh = fd; + return 0; +} + +static int xmp_read(const char *path, char *buf, size_t size, off_t offset, + struct fuse_file_info *fi) +{ + int res; + + (void) path; + res = pread(fi->fh, buf, size, offset); + if (res == -1) + res = -errno; + + return res; +} + +static int xmp_read_buf(const char *path, struct fuse_bufvec **bufp, + size_t size, off_t offset, struct fuse_file_info *fi) +{ + struct fuse_bufvec *src; + + (void) path; + + src = malloc(sizeof(struct fuse_bufvec)); + if (src == NULL) + return -ENOMEM; + + *src = FUSE_BUFVEC_INIT(size); + + src->buf[0].flags = FUSE_BUF_IS_FD | FUSE_BUF_FD_SEEK; + src->buf[0].fd = fi->fh; + src->buf[0].pos = offset; + + *bufp = src; + + return 0; +} + +static int xmp_write(const char *path, const char *buf, size_t size, + off_t offset, struct fuse_file_info *fi) +{ + int res; + + (void) path; + res = pwrite(fi->fh, buf, size, offset); + if (res == -1) + res = -errno; + + return res; +} + +static int xmp_write_buf(const char *path, struct fuse_bufvec *buf, + off_t offset, struct fuse_file_info *fi) +{ + struct fuse_bufvec dst = FUSE_BUFVEC_INIT(fuse_buf_size(buf)); + + (void) path; + + dst.buf[0].flags = FUSE_BUF_IS_FD | FUSE_BUF_FD_SEEK; + dst.buf[0].fd = fi->fh; + dst.buf[0].pos = offset; + + return fuse_buf_copy(&dst, buf, FUSE_BUF_SPLICE_NONBLOCK); +} + +static int xmp_statfs(const char *path, struct statvfs *stbuf) +{ + int res; + + res = statvfs(path, stbuf); + if (res == -1) + return -errno; + + return 0; +} + +static int xmp_flush(const char *path, struct fuse_file_info *fi) +{ + int res; + + (void) path; + /* This is called from every close on an open file, so call the + close on the underlying filesystem. But since flush may be + called multiple times for an open file, this must not really + close the file. This is important if used on a network + filesystem like NFS which flush the data/metadata on close() */ + res = close(dup(fi->fh)); + if (res == -1) + return -errno; + + return 0; +} + +static int xmp_release(const char *path, struct fuse_file_info *fi) +{ + (void) path; + close(fi->fh); + + return 0; +} + +static int xmp_fsync(const char *path, int isdatasync, + struct fuse_file_info *fi) +{ + int res; + (void) path; + +#ifndef HAVE_FDATASYNC + (void) isdatasync; +#else + if (isdatasync) + res = fdatasync(fi->fh); + else +#endif + res = fsync(fi->fh); + if (res == -1) + return -errno; + + return 0; +} + +static int xmp_fallocate(const char *path, int mode, + off_t offset, off_t length, struct fuse_file_info *fi) +{ + (void) path; + + return do_fallocate(fi->fh, mode, offset, length); +} + +#ifdef HAVE_SETXATTR +/* xattr operations are optional and can safely be left unimplemented */ +static int xmp_setxattr(const char *path, const char *name, const char *value, + size_t size, int flags) +{ + int res = lsetxattr(path, name, value, size, flags); + if (res == -1) + return -errno; + return 0; +} + +static int xmp_getxattr(const char *path, const char *name, char *value, + size_t size) +{ + int res = lgetxattr(path, name, value, size); + if (res == -1) + return -errno; + return res; +} + +static int xmp_listxattr(const char *path, char *list, size_t size) +{ + int res = llistxattr(path, list, size); + if (res == -1) + return -errno; + return res; +} + +static int xmp_removexattr(const char *path, const char *name) +{ + int res = lremovexattr(path, name); + if (res == -1) + return -errno; + return 0; +} +#endif /* HAVE_SETXATTR */ + +#ifdef HAVE_LIBULOCKMGR +static int xmp_lock(const char *path, struct fuse_file_info *fi, int cmd, + struct flock *lock) +{ + (void) path; + + return ulockmgr_op(fi->fh, cmd, lock, &fi->lock_owner, + sizeof(fi->lock_owner)); +} +#endif + +static int xmp_flock(const char *path, struct fuse_file_info *fi, int op) +{ + int res; + (void) path; + + res = flock(fi->fh, op); + if (res == -1) + return -errno; + + return 0; +} + +#ifdef HAVE_COPY_FILE_RANGE +static ssize_t xmp_copy_file_range(const char *path_in, + struct fuse_file_info *fi_in, + off_t off_in, const char *path_out, + struct fuse_file_info *fi_out, + off_t off_out, size_t len, int flags) +{ + ssize_t res; + (void) path_in; + (void) path_out; + + res = copy_file_range(fi_in->fh, &off_in, fi_out->fh, &off_out, len, + flags); + if (res == -1) + return -errno; + + return res; +} +#endif + +static off_t xmp_lseek(const char *path, off_t off, int whence, struct fuse_file_info *fi) +{ + off_t res; + (void) path; + + res = lseek(fi->fh, off, whence); + if (res == -1) + return -errno; + + return res; +} + +#ifdef HAVE_STATX +static int xmp_statx(const char *path, int flags, int mask, struct statx *stxbuf, + struct fuse_file_info *fi) +{ + int fd = -1; + int res; + + if (fi) + fd = fi->fh; + + res = statx(fd, path, flags | AT_SYMLINK_NOFOLLOW, mask, stxbuf); + if (res == -1) + return -errno; + + return 0; +} +#endif + +static const struct fuse_operations xmp_oper = { + .init = xmp_init, + .getattr = xmp_getattr, + .access = xmp_access, + .readlink = xmp_readlink, + .opendir = xmp_opendir, + .readdir = xmp_readdir, + .releasedir = xmp_releasedir, + .mknod = xmp_mknod, + .mkdir = xmp_mkdir, + .symlink = xmp_symlink, + .unlink = xmp_unlink, + .rmdir = xmp_rmdir, + .rename = xmp_rename, + .link = xmp_link, + .chmod = xmp_chmod, + .chown = xmp_chown, + .truncate = xmp_truncate, +#ifdef HAVE_UTIMENSAT + .utimens = xmp_utimens, +#endif + .create = xmp_create, + .open = xmp_open, + .read = xmp_read, + .read_buf = xmp_read_buf, + .write = xmp_write, + .write_buf = xmp_write_buf, + .statfs = xmp_statfs, + .flush = xmp_flush, + .release = xmp_release, + .fsync = xmp_fsync, + .fallocate = xmp_fallocate, +#ifdef HAVE_SETXATTR + .setxattr = xmp_setxattr, + .getxattr = xmp_getxattr, + .listxattr = xmp_listxattr, + .removexattr = xmp_removexattr, +#endif +#ifdef HAVE_LIBULOCKMGR + .lock = xmp_lock, +#endif + .flock = xmp_flock, +#ifdef HAVE_COPY_FILE_RANGE + .copy_file_range = xmp_copy_file_range, +#endif + .lseek = xmp_lseek, +#ifdef HAVE_STATX + .statx = xmp_statx, +#endif +}; + +int main(int argc, char *argv[]) +{ + umask(0); + return fuse_main(argc, argv, &xmp_oper, NULL); +} diff --git a/example/passthrough_helpers.h b/example/passthrough_helpers.h new file mode 100644 index 0000000..326a5c7 --- /dev/null +++ b/example/passthrough_helpers.h @@ -0,0 +1,122 @@ +/* + * FUSE: Filesystem in Userspace + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE + */ + +#ifndef FUSE_EXAMPLE_PASSTHROUGH_HELPERS_H_ +#define FUSE_EXAMPLE_PASSTHROUGH_HELPERS_H_ + +#include +#include +#include +#include +#include + +#ifdef __FreeBSD__ +#include +#include +#endif + +static inline int do_fallocate(int fd, int mode, off_t offset, off_t length) +{ +#ifdef HAVE_FALLOCATE + if (fallocate(fd, mode, offset, length) == -1) + return -errno; + return 0; +#else // HAVE_FALLOCATE + +#ifdef HAVE_POSIX_FALLOCATE + if (mode == 0) + return -posix_fallocate(fd, offset, length); +#endif + +#ifdef HAVE_FSPACECTL + // 0x3 == FALLOC_FL_KEEP_SIZE | FALLOC_FL_PUNCH_HOLE + if (mode == 0x3) { + struct spacectl_range sr; + + sr.r_offset = offset; + sr.r_len = length; + if (fspacectl(fd, SPACECTL_DEALLOC, &sr, 0, NULL) == -1) + return -errno; + return 0; + } +#endif + + return -EOPNOTSUPP; +#endif // HAVE_FALLOCATE +} + +/* + * Creates files on the underlying file system in response to a FUSE_MKNOD + * operation + */ +static inline int mknod_wrapper(int dirfd, const char *path, const char *link, + int mode, dev_t rdev) +{ + int res; + + if (S_ISREG(mode)) { + res = openat(dirfd, path, O_CREAT | O_EXCL | O_WRONLY, mode); + if (res >= 0) + res = close(res); + } else if (S_ISDIR(mode)) { + res = mkdirat(dirfd, path, mode); + } else if (S_ISLNK(mode) && link != NULL) { + res = symlinkat(link, dirfd, path); + } else if (S_ISFIFO(mode)) { + res = mkfifoat(dirfd, path, mode); +#ifdef __FreeBSD__ + } else if (S_ISSOCK(mode)) { + struct sockaddr_un su; + int fd; + + if (strlen(path) >= sizeof(su.sun_path)) { + errno = ENAMETOOLONG; + return -1; + } + fd = socket(AF_UNIX, SOCK_STREAM, 0); + if (fd >= 0) { + /* + * We must bind the socket to the underlying file + * system to create the socket file, even though + * we'll never listen on this socket. + */ + su.sun_family = AF_UNIX; + strncpy(su.sun_path, path, sizeof(su.sun_path)); + res = bindat(dirfd, fd, (struct sockaddr*)&su, + sizeof(su)); + if (res == 0) + close(fd); + } else { + res = -1; + } +#endif + } else { + res = mknodat(dirfd, path, mode, rdev); + } + + return res; +} + +#endif // FUSE_PASSTHROUGH_HELPERS_H_ diff --git a/example/passthrough_hp.cc b/example/passthrough_hp.cc new file mode 100644 index 0000000..64f1ae5 --- /dev/null +++ b/example/passthrough_hp.cc @@ -0,0 +1,1711 @@ +/* + FUSE: Filesystem in Userspace + Copyright (C) 2001-2007 Miklos Szeredi + Copyright (C) 2017 Nikolaus Rath + Copyright (C) 2018 Valve, Inc + + This program can be distributed under the terms of the GNU GPLv2. + See the file GPL2.txt. +*/ + +/** @file + * + * This is a "high-performance" version of passthrough_ll.c. While + * passthrough_ll.c is designed to be as simple as possible, this + * example intended to be as efficient and correct as possible. + * + * passthrough_hp.cc mirrors a specified "source" directory under a + * specified the mountpoint with as much fidelity and performance as + * possible. + * + * If --nocache is specified, the source directory may be changed + * directly even while mounted and the filesystem will continue + * to work correctly. + * + * Without --nocache, the source directory is assumed to be modified + * only through the passthrough filesystem. This enables much better + * performance, but if changes are made directly to the source, they + * may not be immediately visible under the mountpoint and further + * access to the mountpoint may result in incorrect behavior, + * including data-loss. + * + * On its own, this filesystem fulfills no practical purpose. It is + * intended as a template upon which additional functionality can be + * built. + * + * Unless --nocache is specified, is only possible to write to files + * for which the mounting user has read permissions. This is because + * the writeback cache requires the kernel to be able to issue read + * requests for all files (which the passthrough filesystem cannot + * satisfy if it can't read the file in the underlying filesystem). + * + * ## Source code ## + * \include passthrough_hp.cc + */ + +#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 12) + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + +// C includes +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef HAVE_SYS_XATTR_H +#include +#endif +#include +#include +#include +#include +#ifdef __FreeBSD__ +#include +#include +#endif + +// C++ includes +#include +#include +#include +#include "cxxopts.hpp" +#include +#include +#include + +#include "passthrough_helpers.h" + +using namespace std; + +#define SFS_DEFAULT_THREADS "-1" // take libfuse value as default +#define SFS_DEFAULT_CLONE_FD "0" + +/* We are re-using pointers to our `struct sfs_inode` and `struct + sfs_dirp` elements as inodes and file handles. This means that we + must be able to store pointer a pointer in both a fuse_ino_t + variable and a uint64_t variable (used for file handles). */ +static_assert(sizeof(fuse_ino_t) >= sizeof(void *), + "void* must fit into fuse_ino_t"); +static_assert(sizeof(fuse_ino_t) >= sizeof(uint64_t), + "fuse_ino_t must be at least 64 bits"); + +/* Forward declarations */ +struct Inode; +static Inode &get_inode(fuse_ino_t ino); +static void forget_one(fuse_ino_t ino, uint64_t n); + +// Uniquely identifies a file in the source directory tree. This could +// be simplified to just ino_t since we require the source directory +// not to contain any mountpoints. This hasn't been done yet in case +// we need to reconsider this constraint (but relaxing this would have +// the drawback that we can no longer reuse inode numbers, and thus +// readdir() would need to do a full lookup() in order to report the +// right inode number). +typedef std::pair SrcId; + +// Define a hash function for SrcId +namespace std +{ +template <> struct hash { + size_t operator()(const SrcId &id) const + { + return hash{}(id.first) ^ hash{}(id.second); + } +}; +} + +// Maps files in the source directory tree to inodes +typedef std::unordered_map InodeMap; + +struct Inode { + int fd{ -1 }; + dev_t src_dev{ 0 }; + ino_t src_ino{ 0 }; + int generation{ 0 }; + int backing_id{ 0 }; + uint64_t nopen{ 0 }; + std::atomic nlookup{ 0 }; + /* Drop before fuse_reply_*: the reply may be the kernel's last + * reference, so forget_one() can erase the inode right after + */ + std::mutex m; + + // Delete copy constructor and assignments. We could implement + // move if we need it. + Inode() = default; + Inode(const Inode &) = delete; + Inode(Inode &&inode) = delete; + Inode &operator=(Inode &&inode) = delete; + Inode &operator=(const Inode &) = delete; + + ~Inode() + { + if (fd > 0) + close(fd); + } +}; + +struct Fs { + // Must be acquired *before* any Inode.m locks. + std::mutex mutex; + InodeMap inodes; // protected by mutex + Inode root; + double timeout; + bool debug; + bool debug_fuse; + bool foreground; + std::string source; + size_t blocksize; + dev_t src_dev; + bool nosplice; + bool nocache; + size_t num_threads; + bool clone_fd; + + std::string fuse_mount_options; + bool direct_io; + bool passthrough; +}; +static Fs fs{}; + +#define FUSE_BUF_COPY_FLAGS \ + (fs.nosplice ? FUSE_BUF_NO_SPLICE : \ + static_cast(FUSE_BUF_SPLICE_MOVE)) + +static Inode &get_inode(fuse_ino_t ino) +{ + if (ino == FUSE_ROOT_ID) + return fs.root; + + Inode *inode = reinterpret_cast(ino); + if (inode->fd == -1) { + cerr << "INTERNAL ERROR: Unknown inode " << ino << endl; + abort(); + } + return *inode; +} + +static int get_fs_fd(fuse_ino_t ino) +{ + int fd = get_inode(ino).fd; + return fd; +} + +static void sfs_init(void *userdata, fuse_conn_info *conn) +{ + (void)userdata; + + if (!fuse_set_feature_flag(conn, FUSE_CAP_PASSTHROUGH)) + fs.passthrough = false; + + /* Passthrough and writeback cache are conflicting modes */ + if (fs.timeout && !fs.passthrough) + fuse_set_feature_flag(conn, FUSE_CAP_WRITEBACK_CACHE); + + fuse_set_feature_flag(conn, FUSE_CAP_FLOCK_LOCKS); + + if (fs.nosplice) { + // FUSE_CAP_SPLICE_READ is enabled in libfuse3 by default, + // see do_init() in fuse_lowlevel.c + // Just unset all, in case FUSE_CAP_SPLICE_WRITE or + // FUSE_CAP_SPLICE_MOVE would also get enabled by default. + fuse_unset_feature_flag(conn, FUSE_CAP_SPLICE_READ); + fuse_unset_feature_flag(conn, FUSE_CAP_SPLICE_WRITE); + fuse_unset_feature_flag(conn, FUSE_CAP_SPLICE_MOVE); + } else { + fuse_set_feature_flag(conn, FUSE_CAP_SPLICE_WRITE); + fuse_set_feature_flag(conn, FUSE_CAP_SPLICE_READ); + fuse_set_feature_flag(conn, FUSE_CAP_SPLICE_MOVE); + } + + /* This is a local file system - no network coherency needed */ + fuse_set_feature_flag(conn, FUSE_CAP_DIRECT_IO_ALLOW_MMAP); + + /* Disable NFS export support, which also disabled name_to_handle_at. + * Goal is to make xfstests that test name_to_handle_at to fail with + * the right error code (EOPNOTSUPP) than to open_by_handle_at to fail with + * ESTALE and let those test fail. + * Perfect NFS export support is not possible with this FUSE filesystem needs + * more kernel work, in order to passthrough nfs handle encode/decode to + * fuse-server/daemon. + */ + fuse_set_feature_flag(conn, FUSE_CAP_NO_EXPORT_SUPPORT); + + /* Disable the receiving and processing of FUSE_INTERRUPT requests */ + conn->no_interrupt = 1; + + /* Try a large IO by default */ + conn->max_write = 4 * 1024 * 1024; +} + +static void sfs_getattr(fuse_req_t req, fuse_ino_t ino, fuse_file_info *fi) +{ + struct stat attr; + int fd = fi ? fi->fh : get_inode(ino).fd; + + auto res = fstatat(fd, "", &attr, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW); + if (res == -1) { + fuse_reply_err(req, errno); + return; + } + fuse_reply_attr(req, &attr, fs.timeout); +} + +static int with_fd_path(int fd, const std::function &f) +{ +#ifdef __FreeBSD__ + struct kinfo_file kf; + kf.kf_structsize = sizeof(kf); + int ret = fcntl(fd, F_KINFO, &kf); + if (ret == -1) + return ret; + return f(kf.kf_path); +#else // Linux + char procname[64]; + sprintf(procname, "/proc/self/fd/%i", fd); + return f(procname); +#endif +} +static void do_setattr(fuse_req_t req, fuse_ino_t ino, struct stat *attr, + int valid, struct fuse_file_info *fi) +{ + Inode &inode = get_inode(ino); + int ifd = inode.fd; + int res; + + if (valid & FUSE_SET_ATTR_MODE) { + if (fi) { + res = fchmod(fi->fh, attr->st_mode); + } else { + res = with_fd_path(ifd, [attr](const char *procname) { + return chmod(procname, attr->st_mode); + }); + } + if (res == -1) + goto out_err; + } + if (valid & (FUSE_SET_ATTR_UID | FUSE_SET_ATTR_GID)) { + uid_t uid = (valid & FUSE_SET_ATTR_UID) ? + attr->st_uid : + static_cast(-1); + gid_t gid = (valid & FUSE_SET_ATTR_GID) ? + attr->st_gid : + static_cast(-1); + + res = fchownat(ifd, "", uid, gid, + AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW); + if (res == -1) + goto out_err; + } + if (valid & FUSE_SET_ATTR_SIZE) { + if (fi) { + res = ftruncate(fi->fh, attr->st_size); + } else { + res = with_fd_path(ifd, [attr](const char *procname) { + return truncate(procname, attr->st_size); + }); + } + if (res == -1) + goto out_err; + } + if (valid & (FUSE_SET_ATTR_ATIME | FUSE_SET_ATTR_MTIME)) { + struct timespec tv[2]; + + tv[0].tv_sec = 0; + tv[1].tv_sec = 0; + tv[0].tv_nsec = UTIME_OMIT; + tv[1].tv_nsec = UTIME_OMIT; + + if (valid & FUSE_SET_ATTR_ATIME_NOW) + tv[0].tv_nsec = UTIME_NOW; + else if (valid & FUSE_SET_ATTR_ATIME) + tv[0] = attr->st_atim; + + if (valid & FUSE_SET_ATTR_MTIME_NOW) + tv[1].tv_nsec = UTIME_NOW; + else if (valid & FUSE_SET_ATTR_MTIME) + tv[1] = attr->st_mtim; + + if (fi) + res = futimens(fi->fh, tv); + else { +#ifdef HAVE_UTIMENSAT + res = with_fd_path(ifd, [&tv](const char *procname) { + return utimensat(AT_FDCWD, procname, tv, 0); + }); +#else + res = -1; + errno = EOPNOTSUPP; +#endif + } + if (res == -1) + goto out_err; + } + return sfs_getattr(req, ino, fi); + +out_err: + fuse_reply_err(req, errno); +} + +static void sfs_setattr(fuse_req_t req, fuse_ino_t ino, struct stat *attr, + int valid, fuse_file_info *fi) +{ + (void)ino; + do_setattr(req, ino, attr, valid, fi); +} + +static int do_lookup(fuse_ino_t parent, const char *name, fuse_entry_param *e) +{ + if (fs.debug) + cerr << "DEBUG: lookup(): name=" << name + << ", parent=" << parent << endl; + memset(e, 0, sizeof(*e)); + e->attr_timeout = fs.timeout; + e->entry_timeout = fs.timeout; + + auto newfd = openat(get_fs_fd(parent), name, O_PATH | O_NOFOLLOW); + if (newfd == -1) + return errno; + + auto res = fstatat(newfd, "", &e->attr, + AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW); + if (res == -1) { + auto saveerr = errno; + close(newfd); + if (fs.debug) + cerr << "DEBUG: lookup(): fstatat failed" << endl; + return saveerr; + } + + if (e->attr.st_dev != fs.src_dev) { + cerr << "WARNING: Mountpoints in the source directory tree will be hidden." + << endl; + return ENOTSUP; + } else if (e->attr.st_ino == FUSE_ROOT_ID) { + cerr << "ERROR: Source directory tree must not include inode " + << FUSE_ROOT_ID << endl; + return EIO; + } + + SrcId id{ e->attr.st_ino, e->attr.st_dev }; + unique_lock fs_lock{ fs.mutex }; + Inode *inode_p; + try { + inode_p = &fs.inodes[id]; + } catch (std::bad_alloc &) { + return ENOMEM; + } + e->ino = reinterpret_cast(inode_p); + Inode &inode{ *inode_p }; + e->generation = inode.generation; + + if (inode.fd == -ENOENT) { // found unlinked inode + if (fs.debug) + cerr << "DEBUG: lookup(): inode " << e->attr.st_ino + << " recycled; generation=" << inode.generation + << endl; + /* fallthrough to new inode but keep existing inode.nlookup */ + } + + if (inode.fd > 0) { // found existing inode + if (fs.debug) + cerr << "DEBUG: lookup(): inode " << e->attr.st_ino + << " (userspace) already known; fd = " << inode.fd + << endl; + + inode.nlookup++; + if (fs.debug) + cerr << "DEBUG:" << __func__ << ":" << __LINE__ << " " + << "inode " << inode.src_ino << " count " + << inode.nlookup << endl; + + fs_lock.unlock(); + close(newfd); + } else { // no existing inode + /* An unlinked inode (fd == -ENOENT) stays in fs.inodes and is + * reachable by other threads, so this is a real lock + */ + lock_guard g{ inode.m }; + inode.src_ino = e->attr.st_ino; + inode.src_dev = e->attr.st_dev; + + inode.nlookup++; + if (fs.debug) + cerr << "DEBUG:" << __func__ << ":" << __LINE__ << " " + << "inode " << inode.src_ino << " count " + << inode.nlookup << endl; + + inode.fd = newfd; + fs_lock.unlock(); + + if (fs.debug) + cerr << "DEBUG: lookup(): created userspace inode " + << e->attr.st_ino << "; fd = " << inode.fd << endl; + } + + return 0; +} + +static void sfs_lookup(fuse_req_t req, fuse_ino_t parent, const char *name) +{ + fuse_entry_param e{}; + auto err = do_lookup(parent, name, &e); + if (err == ENOENT) { + e.attr_timeout = fs.timeout; + e.entry_timeout = fs.timeout; + e.ino = e.attr.st_ino = 0; + fuse_reply_entry(req, &e); + } else if (err) { + if (err == ENFILE || err == EMFILE) + cerr << "ERROR: Reached maximum number of file descriptors." + << endl; + fuse_reply_err(req, err); + } else { + fuse_reply_entry(req, &e); + } +} + +static void mknod_symlink(fuse_req_t req, fuse_ino_t parent, const char *name, + mode_t mode, dev_t rdev, const char *link) +{ + int res; + Inode &inode_p = get_inode(parent); + auto saverr = ENOMEM; + + if (S_ISDIR(mode)) + res = mkdirat(inode_p.fd, name, mode); + else if (S_ISLNK(mode)) + res = symlinkat(link, inode_p.fd, name); + else + res = mknodat(inode_p.fd, name, mode, rdev); + saverr = errno; + if (res == -1) + goto out; + + fuse_entry_param e; + saverr = do_lookup(parent, name, &e); + if (saverr) + goto out; + + fuse_reply_entry(req, &e); + return; + +out: + if (saverr == ENFILE || saverr == EMFILE) + cerr << "ERROR: Reached maximum number of file descriptors." + << endl; + fuse_reply_err(req, saverr); +} + +static void sfs_mknod(fuse_req_t req, fuse_ino_t parent, const char *name, + mode_t mode, dev_t rdev) +{ + mknod_symlink(req, parent, name, mode, rdev, nullptr); +} + +static void sfs_mkdir(fuse_req_t req, fuse_ino_t parent, const char *name, + mode_t mode) +{ + mknod_symlink(req, parent, name, S_IFDIR | mode, 0, nullptr); +} + +static void sfs_symlink(fuse_req_t req, const char *link, fuse_ino_t parent, + const char *name) +{ + mknod_symlink(req, parent, name, S_IFLNK, 0, link); +} + +static void sfs_link(fuse_req_t req, fuse_ino_t ino, fuse_ino_t parent, + const char *name) +{ + Inode &inode = get_inode(ino); + Inode &inode_p = get_inode(parent); + fuse_entry_param e{}; + + e.attr_timeout = fs.timeout; + e.entry_timeout = fs.timeout; + + char procname[64]; + sprintf(procname, "/proc/self/fd/%i", inode.fd); + auto res = + linkat(AT_FDCWD, procname, inode_p.fd, name, AT_SYMLINK_FOLLOW); + if (res == -1) { + fuse_reply_err(req, errno); + return; + } + + res = fstatat(inode.fd, "", &e.attr, + AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW); + if (res == -1) { + fuse_reply_err(req, errno); + return; + } + e.ino = reinterpret_cast(&inode); + { + /* forget_one() reads the count under fs.mutex to decide that + * an inode is unreferenced, so raise it under fs.mutex too */ + lock_guard g_fs{ fs.mutex }; + inode.nlookup++; + if (fs.debug) + cerr << "DEBUG:" << __func__ << ":" << __LINE__ << " " + << "inode " << inode.src_ino << " count " + << inode.nlookup << endl; + } + + fuse_reply_entry(req, &e); + return; +} + +static void sfs_rmdir(fuse_req_t req, fuse_ino_t parent, const char *name) +{ + Inode &inode_p = get_inode(parent); + auto res = unlinkat(inode_p.fd, name, AT_REMOVEDIR); + fuse_reply_err(req, res == -1 ? errno : 0); +} + +static void sfs_rename(fuse_req_t req, fuse_ino_t parent, const char *name, + fuse_ino_t newparent, const char *newname, + unsigned int flags) +{ + Inode &inode_p = get_inode(parent); + Inode &inode_np = get_inode(newparent); + if (flags) { + fuse_reply_err(req, EINVAL); + return; + } + + auto res = renameat(inode_p.fd, name, inode_np.fd, newname); + fuse_reply_err(req, res == -1 ? errno : 0); +} + +static void sfs_unlink(fuse_req_t req, fuse_ino_t parent, const char *name) +{ + Inode &inode_p = get_inode(parent); + // Release inode.fd before last unlink like nfsd EXPORT_OP_CLOSE_BEFORE_UNLINK + // to test reused inode numbers. + // Skip this when inode has an open file and when writeback cache is enabled. + if (!fs.timeout) { + fuse_entry_param e; + auto err = do_lookup(parent, name, &e); + if (err) { + fuse_reply_err(req, err); + return; + } + if (e.attr.st_nlink == 1) { + Inode &inode = get_inode(e.ino); + int release_fd = -1; + + { + lock_guard g_fs{ fs.mutex }; + lock_guard g{ inode.m }; + if (inode.fd > 0 && !inode.nopen) { + if (fs.debug) + cerr << "DEBUG: unlink: release inode " + << e.attr.st_ino + << "; fd=" << inode.fd + << endl; + release_fd = inode.fd; + inode.fd = -ENOENT; + inode.generation++; + } + } + /* closed only after the fd is unpublished, so no other + * thread can hand it to a syscall once it is reused + */ + if (release_fd != -1) + close(release_fd); + } + + // decrease the ref which lookup above had increased + forget_one(e.ino, 1); + } + auto res = unlinkat(inode_p.fd, name, 0); + fuse_reply_err(req, res == -1 ? errno : 0); +} + +static void forget_one(fuse_ino_t ino, uint64_t n) +{ + Inode &inode = get_inode(ino); + SrcId id; + + { + lock_guard l{ inode.m }; + + if (n > inode.nlookup) { + cerr << "INTERNAL ERROR: Negative lookup count for inode " + << inode.src_ino << endl; + abort(); + } + inode.nlookup -= n; + + if (fs.debug) + cerr << "DEBUG:" << __func__ << ":" << __LINE__ << " " + << "inode " << inode.src_ino << " count " + << inode.nlookup << endl; + + if (inode.nlookup) + return; + + id = { inode.src_ino, inode.src_dev }; + } + + /* The inode may be erased and re-created between the two locks, so + * look it up by key instead of reusing the reference and let a + * resurrecting do_lookup() win the count check + */ + lock_guard g_fs{ fs.mutex }; + auto it = fs.inodes.find(id); + if (it == fs.inodes.end() || it->second.nlookup) + return; + + if (fs.debug) + cerr << "DEBUG: forget: cleaning up inode " << id.first << endl; + fs.inodes.erase(it); +} + +static void sfs_forget(fuse_req_t req, fuse_ino_t ino, uint64_t nlookup) +{ + forget_one(ino, nlookup); + fuse_reply_none(req); +} + +static void sfs_forget_multi(fuse_req_t req, size_t count, + fuse_forget_data *forgets) +{ + for (int i = 0; i < count; i++) + forget_one(forgets[i].ino, forgets[i].nlookup); + fuse_reply_none(req); +} + +static void sfs_readlink(fuse_req_t req, fuse_ino_t ino) +{ + Inode &inode = get_inode(ino); + char buf[PATH_MAX + 1]; + auto res = readlinkat(inode.fd, "", buf, sizeof(buf)); + if (res == -1) + fuse_reply_err(req, errno); + else if (res == sizeof(buf)) + fuse_reply_err(req, ENAMETOOLONG); + else { + buf[res] = '\0'; + fuse_reply_readlink(req, buf); + } +} + +struct DirHandle { + DIR *dp{ nullptr }; + off_t offset; + // serialises readdir(); drop before fuse_reply_*, the reply may let + // RELEASEDIR delete this handle + std::mutex m; + + DirHandle() = default; + DirHandle(const DirHandle &) = delete; + DirHandle &operator=(const DirHandle &) = delete; + + ~DirHandle() + { + if (dp) + closedir(dp); + } +}; + +static DirHandle *get_dir_handle(fuse_file_info *fi) +{ + return reinterpret_cast(fi->fh); +} + +static void sfs_opendir(fuse_req_t req, fuse_ino_t ino, fuse_file_info *fi) +{ + Inode &inode = get_inode(ino); + DIR *dp = nullptr; + int fd; + + auto d = new (nothrow) DirHandle; + if (d == nullptr) { + fuse_reply_err(req, ENOMEM); + return; + } + + fd = openat(inode.fd, ".", O_RDONLY); + if (fd == -1) + goto out_errno; + + // On success, dir stream takes ownership of fd, so we + // do not have to close it. + dp = fdopendir(fd); + if (dp == nullptr) + goto out_errno; + + { + // Make Helgrind happy - it can't know that there's an implicit + // synchronization due to the fact that other threads cannot + // access d until we've called fuse_reply_*. + lock_guard g{ d->m }; + + d->dp = dp; + d->offset = 0; + } + + fi->fh = reinterpret_cast(d); + if (fs.timeout) { + fi->keep_cache = 1; + fi->cache_readdir = 1; + } + fuse_reply_open(req, fi); + return; + +out_errno: + auto error = errno; + delete d; + if (error == ENFILE || error == EMFILE) + cerr << "ERROR: Reached maximum number of file descriptors." + << endl; + fuse_reply_err(req, error); +} + +static bool is_dot_or_dotdot(const char *name) +{ + return name[0] == '.' && + (name[1] == '\0' || (name[1] == '.' && name[2] == '\0')); +} + +static void do_readdir(fuse_req_t req, fuse_ino_t ino, size_t size, + off_t offset, fuse_file_info *fi, const int plus) +{ + auto d = get_dir_handle(fi); + char *p; + auto rem = size; + int err = 0, count = 0; + + if (fs.debug) + cerr << "DEBUG: readdir(): started with offset " << offset + << endl; + + auto buf = new (nothrow) char[size]; + if (!buf) { + fuse_reply_err(req, ENOMEM); + return; + } + p = buf; + + unique_lock l{ d->m }; + + if (offset != d->offset) { + if (fs.debug) + cerr << "DEBUG: readdir(): seeking to " << offset + << endl; + seekdir(d->dp, offset); + d->offset = offset; + } + + while (1) { + bool did_lookup = false; + struct dirent *entry; + errno = 0; + entry = readdir(d->dp); + if (!entry) { + if (errno) { + err = errno; + if (fs.debug) + warn("DEBUG: readdir(): readdir failed with"); + goto error; + } + break; // End of stream + } + d->offset = entry->d_off; + + fuse_entry_param e{}; + size_t entsize; + if (plus) { + if (is_dot_or_dotdot(entry->d_name)) { + /* fuse kernel ignores attributes for these and also does + * not increase lookup count (see fuse_direntplus_link) + */ + e.attr.st_ino = entry->d_ino; + e.attr.st_mode = entry->d_type << 12; + } else { + err = do_lookup(ino, entry->d_name, &e); + if (err) + goto error; + did_lookup = true; + } + entsize = fuse_add_direntry_plus( + req, p, rem, entry->d_name, &e, entry->d_off); + } else { + e.attr.st_ino = entry->d_ino; + e.attr.st_mode = entry->d_type << 12; + entsize = fuse_add_direntry(req, p, rem, entry->d_name, + &e.attr, entry->d_off); + } + + if (entsize > rem) { + if (fs.debug) + cerr << "DEBUG: readdir(): buffer full, returning data. " + << endl; + if (did_lookup) + forget_one(e.ino, 1); + break; + } + + p += entsize; + rem -= entsize; + count++; + if (fs.debug) { + cerr << "DEBUG: readdir(): added to buffer: " + << entry->d_name << ", ino " << e.attr.st_ino + << ", offset " << entry->d_off << endl; + } + } + err = 0; +error: + + // If there's an error, we can only signal it if we haven't stored + // any entries yet - otherwise we'd end up with wrong lookup + // counts for the entries that are already in the buffer. So we + // return what we've collected until that point. + if (err && rem == size) { + if (err == ENFILE || err == EMFILE) + cerr << "ERROR: Reached maximum number of file descriptors." + << endl; + l.unlock(); + fuse_reply_err(req, err); + } else { + if (fs.debug) + cerr << "DEBUG: readdir(): returning " << count + << " entries, curr offset " << d->offset << endl; + l.unlock(); + fuse_reply_buf(req, buf, size - rem); + } + delete[] buf; + return; +} + +static void sfs_readdir(fuse_req_t req, fuse_ino_t ino, size_t size, + off_t offset, fuse_file_info *fi) +{ + // operation logging is done in readdir to reduce code duplication + do_readdir(req, ino, size, offset, fi, 0); +} + +static void sfs_readdirplus(fuse_req_t req, fuse_ino_t ino, size_t size, + off_t offset, fuse_file_info *fi) +{ + // operation logging is done in readdir to reduce code duplication + do_readdir(req, ino, size, offset, fi, 1); +} + +static void sfs_releasedir(fuse_req_t req, fuse_ino_t ino, fuse_file_info *fi) +{ + (void)ino; + auto d = get_dir_handle(fi); + delete d; + fuse_reply_err(req, 0); +} + +static void do_passthrough_open(fuse_req_t req, fuse_ino_t ino, int fd, + fuse_file_info *fi) +{ + Inode &inode = get_inode(ino); + /* Setup a shared backing file on first open of an inode */ + if (inode.backing_id) { + if (fs.debug) + cerr << "DEBUG: reusing shared backing file " + << inode.backing_id << " for inode " << ino + << endl; + fi->backing_id = inode.backing_id; + } else if (!(inode.backing_id = fuse_passthrough_open(req, fd))) { + cerr << "DEBUG: fuse_passthrough_open failed for inode " << ino + << ", disabling rw passthrough." << endl; + fs.passthrough = false; + } else { + if (fs.debug) + cerr << "DEBUG: setup shared backing file " + << inode.backing_id << " for inode " << ino + << endl; + fi->backing_id = inode.backing_id; + } + /* open in passthrough mode must drop old page cache */ + if (fi->backing_id) + fi->keep_cache = false; +} + +static void sfs_create_open_flags(fuse_file_info *fi) +{ + if (fs.direct_io) + fi->direct_io = 1; + + /* + * fi->direct_io (FOPEN_DIRECT_IO) is set to benefit from + * parallel_direct_writes, which kernel cannot do for plain O_DIRECT. + * However, passthrough is preferred, but which is not possible when + * FOPEN_DIRECT_IO is set. + */ + if (!fs.passthrough) { + if (fi->flags & O_DIRECT) + fi->direct_io = 1; + } + + /* parallel_direct_writes feature depends on direct_io features. + * To make parallel_direct_writes valid, need set fi->direct_io + * in current function. + */ + fi->parallel_direct_writes = 1; + + fi->keep_cache = (fs.timeout != 0); + fi->noflush = (fs.timeout == 0 && (fi->flags & O_ACCMODE) == O_RDONLY); +} + +static void sfs_create(fuse_req_t req, fuse_ino_t parent, const char *name, + mode_t mode, fuse_file_info *fi) +{ + Inode &inode_p = get_inode(parent); + + auto fd = openat(inode_p.fd, name, (fi->flags | O_CREAT) & ~O_NOFOLLOW, + mode); + if (fd == -1) { + auto err = errno; + if (err == ENFILE || err == EMFILE) + cerr << "ERROR: Reached maximum number of file descriptors." + << endl; + fuse_reply_err(req, err); + return; + } + + fi->fh = fd; + fuse_entry_param e; + auto err = do_lookup(parent, name, &e); + if (err) { + if (err == ENFILE || err == EMFILE) + cerr << "ERROR: Reached maximum number of file descriptors." + << endl; + fuse_reply_err(req, err); + return; + } + + Inode &inode = get_inode(e.ino); + { + lock_guard g{ inode.m }; + inode.nopen++; + + sfs_create_open_flags(fi); + + if (fs.passthrough) + do_passthrough_open(req, e.ino, fd, fi); + } + fuse_reply_create(req, &e, fi); +} + +#ifdef O_TMPFILE +static Inode *create_new_inode(int fd, fuse_entry_param *e) +{ + memset(e, 0, sizeof(*e)); + e->attr_timeout = fs.timeout; + e->entry_timeout = fs.timeout; + + auto res = + fstatat(fd, "", &e->attr, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW); + if (res == -1) { + if (fs.debug) + cerr << "DEBUG: lookup(): fstatat failed" << endl; + return NULL; + } + + SrcId id{ e->attr.st_ino, e->attr.st_dev }; + unique_lock fs_lock{ fs.mutex }; + Inode *p_inode; + try { + p_inode = &fs.inodes[id]; + } catch (std::bad_alloc &) { + return NULL; + } + + e->ino = reinterpret_cast(p_inode); + e->generation = p_inode->generation; + + lock_guard g{ p_inode->m }; + p_inode->src_ino = e->attr.st_ino; + p_inode->src_dev = e->attr.st_dev; + + p_inode->nlookup++; + if (fs.debug) + cerr << "DEBUG:" << __func__ << ":" << __LINE__ << " " + << "inode " << p_inode->src_ino << " count " + << p_inode->nlookup << endl; + + p_inode->fd = fd; + fs_lock.unlock(); + + if (fs.debug) + cerr << "DEBUG: lookup(): created userspace inode " + << e->attr.st_ino << "; fd = " << p_inode->fd << endl; + return p_inode; +} + +static void sfs_tmpfile(fuse_req_t req, fuse_ino_t parent, mode_t mode, + fuse_file_info *fi) +{ + Inode &parent_inode = get_inode(parent); + + auto fd = openat(parent_inode.fd, ".", + (fi->flags | O_TMPFILE) & ~O_NOFOLLOW, mode); + if (fd == -1) { + auto err = errno; + if (err == ENFILE || err == EMFILE) + cerr << "ERROR: Reached maximum number of file descriptors." + << endl; + fuse_reply_err(req, err); + return; + } + + fi->fh = fd; + fuse_entry_param e; + + Inode *inode = create_new_inode(dup(fd), &e); + if (inode == NULL) { + auto err = errno; + cerr << "ERROR: could not create new inode." << endl; + close(fd); + fuse_reply_err(req, err); + return; + } + + { + lock_guard g{ inode->m }; + + sfs_create_open_flags(fi); + + if (fs.passthrough) + do_passthrough_open(req, e.ino, fd, fi); + } + + fuse_reply_create(req, &e, fi); +} +#endif +static void sfs_fsyncdir(fuse_req_t req, fuse_ino_t ino, int datasync, + fuse_file_info *fi) +{ + (void)ino; + int res; + int fd = dirfd(get_dir_handle(fi)->dp); + if (datasync) + res = fdatasync(fd); + else + res = fsync(fd); + fuse_reply_err(req, res == -1 ? errno : 0); +} + +static void sfs_open(fuse_req_t req, fuse_ino_t ino, fuse_file_info *fi) +{ + Inode &inode = get_inode(ino); + + /* With writeback cache, kernel may send read requests even + when userspace opened write-only */ + if (fs.timeout && (fi->flags & O_ACCMODE) == O_WRONLY) { + fi->flags &= ~O_ACCMODE; + fi->flags |= O_RDWR; + } + + /* With writeback cache, O_APPEND is handled by the kernel. This + * breaks atomicity (since the file may change in the underlying + * filesystem, so that the kernel's idea of the end of the file + * isn't accurate anymore). However, no process should modify the + * file in the underlying filesystem once it has been read, so + * this is not a problem. + */ + if (fs.timeout && fi->flags & O_APPEND) + fi->flags &= ~O_APPEND; + + /* Unfortunately we cannot use inode.fd, because this was opened + with O_PATH (so it doesn't allow read/write access). */ + auto fd = with_fd_path(inode.fd, [fi](const char *buf) { + return open(buf, fi->flags & ~O_NOFOLLOW); + }); + if (fd == -1) { + auto err = errno; + if (err == ENFILE || err == EMFILE) + cerr << "ERROR: Reached maximum number of file descriptors." + << endl; + fuse_reply_err(req, err); + return; + } + + { + lock_guard g{ inode.m }; + inode.nopen++; + + sfs_create_open_flags(fi); + + fi->fh = fd; + if (fs.passthrough) + do_passthrough_open(req, ino, fd, fi); + } + fuse_reply_open(req, fi); +} + +static void sfs_release(fuse_req_t req, fuse_ino_t ino, fuse_file_info *fi) +{ + Inode &inode = get_inode(ino); + int backing_id = 0; + + { + lock_guard g{ inode.m }; + inode.nopen--; + + /* Close the shared backing file on last file close of an inode */ + if (inode.backing_id && !inode.nopen) { + backing_id = inode.backing_id; + inode.backing_id = 0; + } + } + + if (backing_id) { + if (fuse_passthrough_close(req, backing_id) < 0) { + cerr << "DEBUG: fuse_passthrough_close failed for inode " + << ino << " backing file " << backing_id << endl; + } else if (fs.debug) { + cerr << "DEBUG: closed backing file " << backing_id + << " for inode " << ino << endl; + } + } + + close(fi->fh); + fuse_reply_err(req, 0); +} + +static void sfs_flush(fuse_req_t req, fuse_ino_t ino, fuse_file_info *fi) +{ + (void)ino; + auto res = close(dup(fi->fh)); + fuse_reply_err(req, res == -1 ? errno : 0); +} + +static void sfs_fsync(fuse_req_t req, fuse_ino_t ino, int datasync, + fuse_file_info *fi) +{ + (void)ino; + int res; + if (datasync) + res = fdatasync(fi->fh); + else + res = fsync(fi->fh); + fuse_reply_err(req, res == -1 ? errno : 0); +} + +static void do_read(fuse_req_t req, size_t size, off_t off, fuse_file_info *fi) +{ + fuse_bufvec buf = FUSE_BUFVEC_INIT(size); + char *payload = NULL; + size_t payload_size = 0; + int res = fuse_req_get_payload(req, &payload, &payload_size, NULL); + + /* + * This is a demonstration how to use io-uring payload. For FUSE_BUF_IS_FD + * it shouldn't make much of a difference because fuse_reply_data() -> + * fuse_reply_data_uring() also has access to the payload and will + * read directly from the FD into the payload. + * It is more useful for file systems that need a buffer for decryption, + * decompression, etc. + */ + if (res == 0) { + /* This is an io-uring request - write directly to the payload */ + assert(payload_size >= size); + + buf.buf[0].mem = payload; + buf.buf[0].size = payload_size; + + res = pread(fi->fh, payload, size, off); + if (res < 0) { + fuse_reply_err(req, errno); + return; + } + + buf.buf[0].size = res; + } else { + buf.buf[0].flags = static_cast( + FUSE_BUF_IS_FD | FUSE_BUF_FD_SEEK); + buf.buf[0].fd = fi->fh; + buf.buf[0].pos = off; + } + + fuse_reply_data(req, &buf, FUSE_BUF_COPY_FLAGS); +} + +static void sfs_read(fuse_req_t req, fuse_ino_t ino, size_t size, off_t off, + fuse_file_info *fi) +{ + (void)ino; + if (fs.passthrough && !fs.direct_io) { + cerr << "ERROR: fuse_passthrough read failed." << endl; + fuse_reply_err(req, EIO); + return; + } + do_read(req, size, off, fi); +} + +static void do_write_buf(fuse_req_t req, size_t size, off_t off, + fuse_bufvec *in_buf, fuse_file_info *fi) +{ + fuse_bufvec out_buf = FUSE_BUFVEC_INIT(size); + out_buf.buf[0].flags = + static_cast(FUSE_BUF_IS_FD | FUSE_BUF_FD_SEEK); + out_buf.buf[0].fd = fi->fh; + out_buf.buf[0].pos = off; + + auto res = fuse_buf_copy(&out_buf, in_buf, FUSE_BUF_COPY_FLAGS); + if (res < 0) + fuse_reply_err(req, -res); + else + fuse_reply_write(req, (size_t)res); +} + +static void sfs_write_buf(fuse_req_t req, fuse_ino_t ino, fuse_bufvec *in_buf, + off_t off, fuse_file_info *fi) +{ + (void)ino; + if (fs.passthrough && !fs.direct_io) { + cerr << "ERROR: fuse_passthrough write failed." << endl; + fuse_reply_err(req, EIO); + return; + } + auto size{ fuse_buf_size(in_buf) }; + do_write_buf(req, size, off, in_buf, fi); +} + +static void sfs_statfs(fuse_req_t req, fuse_ino_t ino) +{ + struct statvfs stbuf; + + auto res = fstatvfs(get_fs_fd(ino), &stbuf); + if (res == -1) + fuse_reply_err(req, errno); + else + fuse_reply_statfs(req, &stbuf); +} + +static void sfs_fallocate(fuse_req_t req, fuse_ino_t ino, int mode, + off_t offset, off_t length, fuse_file_info *fi) +{ + (void)ino; + + auto err = -do_fallocate(fi->fh, mode, offset, length); + + fuse_reply_err(req, err); +} + +static void sfs_flock(fuse_req_t req, fuse_ino_t ino, fuse_file_info *fi, + int op) +{ + (void)ino; + auto res = flock(fi->fh, op); + fuse_reply_err(req, res == -1 ? errno : 0); +} + +#ifdef HAVE_SETXATTR +static void sfs_getxattr(fuse_req_t req, fuse_ino_t ino, const char *name, + size_t size) +{ + char *value = nullptr; + Inode &inode = get_inode(ino); + ssize_t ret; + int saverr; + + char procname[64]; + sprintf(procname, "/proc/self/fd/%i", inode.fd); + + if (size) { + value = new (nothrow) char[size]; + if (value == nullptr) { + saverr = ENOMEM; + goto out; + } + + ret = getxattr(procname, name, value, size); + if (ret == -1) + goto out_err; + saverr = 0; + if (ret == 0) + goto out; + + fuse_reply_buf(req, value, ret); + } else { + ret = getxattr(procname, name, nullptr, 0); + if (ret == -1) + goto out_err; + + fuse_reply_xattr(req, ret); + } +out_free: + delete[] value; + return; + +out_err: + saverr = errno; +out: + fuse_reply_err(req, saverr); + goto out_free; +} + +static void sfs_listxattr(fuse_req_t req, fuse_ino_t ino, size_t size) +{ + char *value = nullptr; + Inode &inode = get_inode(ino); + ssize_t ret; + int saverr; + + char procname[64]; + sprintf(procname, "/proc/self/fd/%i", inode.fd); + + if (size) { + value = new (nothrow) char[size]; + if (value == nullptr) { + saverr = ENOMEM; + goto out; + } + + ret = listxattr(procname, value, size); + if (ret == -1) + goto out_err; + saverr = 0; + if (ret == 0) + goto out; + + fuse_reply_buf(req, value, ret); + } else { + ret = listxattr(procname, nullptr, 0); + if (ret == -1) + goto out_err; + + fuse_reply_xattr(req, ret); + } +out_free: + delete[] value; + return; +out_err: + saverr = errno; +out: + fuse_reply_err(req, saverr); + goto out_free; +} + +static void sfs_setxattr(fuse_req_t req, fuse_ino_t ino, const char *name, + const char *value, size_t size, int flags) +{ + Inode &inode = get_inode(ino); + ssize_t ret; + int saverr; + + char procname[64]; + sprintf(procname, "/proc/self/fd/%i", inode.fd); + + ret = setxattr(procname, name, value, size, flags); + saverr = ret == -1 ? errno : 0; + + fuse_reply_err(req, saverr); +} + +static void sfs_removexattr(fuse_req_t req, fuse_ino_t ino, const char *name) +{ + char procname[64]; + Inode &inode = get_inode(ino); + ssize_t ret; + int saverr; + + sprintf(procname, "/proc/self/fd/%i", inode.fd); + ret = removexattr(procname, name); + saverr = ret == -1 ? errno : 0; + + fuse_reply_err(req, saverr); +} +#endif + +static void assign_operations(fuse_lowlevel_ops &sfs_oper) +{ + sfs_oper.init = sfs_init; + sfs_oper.lookup = sfs_lookup; + sfs_oper.mkdir = sfs_mkdir; + sfs_oper.mknod = sfs_mknod; + sfs_oper.symlink = sfs_symlink; + sfs_oper.link = sfs_link; + sfs_oper.unlink = sfs_unlink; + sfs_oper.rmdir = sfs_rmdir; + sfs_oper.rename = sfs_rename; + sfs_oper.forget = sfs_forget; + sfs_oper.forget_multi = sfs_forget_multi; + sfs_oper.getattr = sfs_getattr; + sfs_oper.setattr = sfs_setattr; + sfs_oper.readlink = sfs_readlink; + sfs_oper.opendir = sfs_opendir; + sfs_oper.readdir = sfs_readdir; + sfs_oper.readdirplus = sfs_readdirplus; + sfs_oper.releasedir = sfs_releasedir; + sfs_oper.fsyncdir = sfs_fsyncdir; + sfs_oper.create = sfs_create; +#ifdef O_TMPFILE + sfs_oper.tmpfile = sfs_tmpfile; +#endif + sfs_oper.open = sfs_open; + sfs_oper.release = sfs_release; + sfs_oper.flush = sfs_flush; + sfs_oper.fsync = sfs_fsync; + sfs_oper.read = sfs_read; + sfs_oper.write_buf = sfs_write_buf; + sfs_oper.statfs = sfs_statfs; + sfs_oper.fallocate = sfs_fallocate; + sfs_oper.flock = sfs_flock; +#ifdef HAVE_SETXATTR + sfs_oper.setxattr = sfs_setxattr; + sfs_oper.getxattr = sfs_getxattr; + sfs_oper.listxattr = sfs_listxattr; + sfs_oper.removexattr = sfs_removexattr; +#endif +} + +static void print_usage(char *prog_name) +{ + cout << "Usage: " << prog_name << " --help\n" + << " " << prog_name << " [options] \n"; +} + +static cxxopts::ParseResult parse_wrapper(cxxopts::Options &parser, int &argc, + char **&argv) +{ + try { + return parser.parse(argc, argv); + } catch (cxxopts::option_not_exists_exception &exc) { + std::cout << argv[0] << ": " << exc.what() << std::endl; + print_usage(argv[0]); + exit(2); + } +} + +static void string_split(std::string s, std::vector &out, + std::string delimiter) +{ + size_t pos_start = 0, pos_end, delim_len = delimiter.length(); + std::string token; + + while ((pos_end = s.find(delimiter, pos_start)) != std::string::npos) { + token = s.substr(pos_start, pos_end - pos_start); + pos_start = pos_end + delim_len; + out.push_back(token); + } + + out.push_back(s.substr(pos_start)); +} + +static std::string string_join(const std::vector &elems, + char delim) +{ + std::ostringstream out; + for (auto ii = elems.begin(); ii != elems.end(); ++ii) { + out << (*ii); + if (ii + 1 != elems.end()) { + out << delim; + } + } + return out.str(); +} + +static cxxopts::ParseResult parse_options(int argc, char **argv) +{ + cxxopts::Options opt_parser(argv[0]); + std::vector mount_options; + opt_parser.add_options()("debug", "Enable filesystem debug messages")( + "debug-fuse", "Enable libfuse debug messages")( + "foreground", "Run in foreground")("help", "Print help")( + "nocache", "Disable attribute all caching")( + "nosplice", "Do not use splice(2) to transfer data")( + "nopassthrough", "Do not use pass-through mode for read/write")( + "single", "Run single-threaded")( + "o", + "Mount options (see mount.fuse(5) - only use if you know what " + "you are doing)", + cxxopts::value(mount_options))( + "num-threads", "Number of libfuse worker threads", + cxxopts::value()->default_value(SFS_DEFAULT_THREADS))( + "clone-fd", "use separate fuse device fd for each thread")( + "direct-io", "enable fuse kernel internal direct-io"); + + // FIXME: Find a better way to limit the try clause to just + // opt_parser.parse() (cf. https://github.com/jarro2783/cxxopts/issues/146) + auto options = parse_wrapper(opt_parser, argc, argv); + + if (options.count("help")) { + print_usage(argv[0]); + // Strip everything before the option list from the + // default help string. + auto help = opt_parser.help(); + std::cout << std::endl + << "options:" + << help.substr(help.find("\n\n") + 1, string::npos); + std::cout << "\nFuse lowlevel options:\n"; + fuse_lowlevel_help(); + exit(0); + + } else if (argc != 3) { + std::cout << argv[0] << ": invalid number of arguments\n"; + print_usage(argv[0]); + exit(2); + } + + fs.debug = options.count("debug") != 0; + fs.debug_fuse = options.count("debug-fuse") != 0; + + fs.foreground = options.count("foreground") != 0; + if (fs.debug || fs.debug_fuse) + fs.foreground = true; + + fs.nosplice = options.count("nosplice") != 0; + fs.passthrough = options.count("nopassthrough") == 0; + fs.num_threads = options["num-threads"].as(); + fs.clone_fd = options.count("clone-fd"); + fs.direct_io = options.count("direct-io"); + + char *resolved_path = realpath(argv[1], NULL); + if (resolved_path == NULL) + warn("WARNING: realpath() failed with"); + fs.source = std::string{ resolved_path }; + free(resolved_path); + + std::vector flattened_mount_opts; + for (auto opt : mount_options) { + string_split(opt, flattened_mount_opts, ","); + } + + bool found_fsname = false; + for (auto opt : flattened_mount_opts) { + if (opt.find("fsname=") == 0) { + found_fsname = true; + continue; + } + + /* Filter out some obviously incorrect options. */ + if (opt == "fd") { + std::cout << argv[0] + << ": Unsupported mount option: " << opt + << "\n"; + print_usage(argv[0]); + exit(2); + } + } + if (!found_fsname) { + flattened_mount_opts.push_back("fsname=" + fs.source); + } + flattened_mount_opts.push_back("default_permissions"); + fs.fuse_mount_options = string_join(flattened_mount_opts, ','); + return options; +} + +static void maximize_fd_limit() +{ + struct rlimit lim {}; + auto res = getrlimit(RLIMIT_NOFILE, &lim); + if (res != 0) { + warn("WARNING: getrlimit() failed with"); + return; + } + lim.rlim_cur = lim.rlim_max; + res = setrlimit(RLIMIT_NOFILE, &lim); + if (res != 0) + warn("WARNING: setrlimit() failed with"); +} + +int main(int argc, char *argv[]) +{ + struct fuse_loop_config *loop_config = NULL; + + // Parse command line options + auto options{ parse_options(argc, argv) }; + + // We need an fd for every dentry in our the filesystem that the + // kernel knows about. This is way more than most processes need, + // so try to get rid of any resource softlimit. + maximize_fd_limit(); + + // Initialize filesystem root + fs.root.fd = -1; + fs.root.nlookup = 9999; + fs.timeout = options.count("nocache") ? 0 : 86400.0; + + struct stat stat; + auto ret = lstat(fs.source.c_str(), &stat); + if (ret == -1) + err(1, "ERROR: failed to stat source (\"%s\")", + fs.source.c_str()); + if (!S_ISDIR(stat.st_mode)) + errx(1, "ERROR: source is not a directory"); + fs.src_dev = stat.st_dev; + + fs.root.fd = open(fs.source.c_str(), O_PATH); + if (fs.root.fd == -1) + err(1, "ERROR: open(\"%s\", O_PATH)", fs.source.c_str()); + + // Initialize fuse + fuse_args args = FUSE_ARGS_INIT(0, nullptr); + if (fuse_opt_add_arg(&args, argv[0]) || fuse_opt_add_arg(&args, "-o") || + fuse_opt_add_arg(&args, fs.fuse_mount_options.c_str()) || + (fs.debug_fuse && fuse_opt_add_arg(&args, "-odebug"))) + errx(3, "ERROR: Out of memory adding arguments"); + + ret = -1; + fuse_lowlevel_ops sfs_oper{}; + assign_operations(sfs_oper); + auto se = fuse_session_new(&args, &sfs_oper, sizeof(sfs_oper), &fs); + if (se == nullptr) + goto err_out1; + + if (fuse_set_signal_handlers(se) != 0) + goto err_out2; + + if (fuse_set_fail_signal_handlers(se) != 0) + goto err_out2; + + // Don't apply umask, use modes exactly as specified + umask(0); + + // Mount and run main loop + loop_config = fuse_loop_cfg_create(); + + if (fs.num_threads != -1) + fuse_loop_cfg_set_max_threads(loop_config, fs.num_threads); + + fuse_loop_cfg_set_clone_fd(loop_config, fs.clone_fd); + + if (fuse_session_mount(se, argv[2]) != 0) + goto err_out3; + + fuse_daemonize(fs.foreground); + + if (!fs.foreground) + fuse_log_enable_syslog("passthrough-hp", LOG_PID | LOG_CONS, + LOG_DAEMON); + + if (options.count("single")) + ret = fuse_session_loop(se); + else + ret = fuse_session_loop_mt(se, loop_config); + + fuse_session_unmount(se); + +err_out3: + fuse_remove_signal_handlers(se); +err_out2: + fuse_session_destroy(se); +err_out1: + + fuse_loop_cfg_destroy(loop_config); + fuse_opt_free_args(&args); + + if (!fs.foreground) + fuse_log_close_syslog(); + + return ret ? 1 : 0; +} diff --git a/example/passthrough_ll.c b/example/passthrough_ll.c new file mode 100644 index 0000000..7dfaee3 --- /dev/null +++ b/example/passthrough_ll.c @@ -0,0 +1,1416 @@ +/* + FUSE: Filesystem in Userspace + Copyright (C) 2001-2007 Miklos Szeredi + + This program can be distributed under the terms of the GNU GPLv2. + See the file GPL2.txt. +*/ + +/** @file + * + * This file system mirrors the existing file system hierarchy of the + * system, starting at the root file system. This is implemented by + * just "passing through" all requests to the corresponding user-space + * libc functions. In contrast to passthrough.c and passthrough_fh.c, + * this implementation uses the low-level API. Its performance should + * be the least bad among the three. + * + * When writeback caching is enabled (-o writeback mount option), it + * is only possible to write to files for which the mounting user has + * read permissions. This is because the writeback cache requires the + * kernel to be able to issue read requests for all files (which the + * passthrough filesystem cannot satisfy if it can't read the file in + * the underlying filesystem). + * + * Compile with: + * + * gcc -Wall passthrough_ll.c `pkg-config fuse3 --cflags --libs` -o passthrough_ll + * + * ## Source code ## + * \include passthrough_ll.c + */ + +#define _GNU_SOURCE +#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 18) + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "passthrough_helpers.h" + +/* We are re-using pointers to our `struct lo_inode` and `struct + lo_dirp` elements as inodes. This means that we must be able to + store uintptr_t values in a fuse_ino_t variable. The following + incantation checks this condition at compile time. */ +#if defined(__GNUC__) && (__GNUC__ > 4 || __GNUC__ == 4 && __GNUC_MINOR__ >= 6) && !defined __cplusplus +_Static_assert(sizeof(fuse_ino_t) >= sizeof(uintptr_t), + "fuse_ino_t too small to hold uintptr_t values!"); +#else +struct _uintptr_to_must_hold_fuse_ino_t_dummy_struct \ + { unsigned _uintptr_to_must_hold_fuse_ino_t: + ((sizeof(fuse_ino_t) >= sizeof(uintptr_t)) ? 1 : -1); }; +#endif + +struct lo_inode { + struct lo_inode *next; /* protected by lo->mutex */ + struct lo_inode *prev; /* protected by lo->mutex */ + int fd; + ino_t ino; + dev_t dev; + uint64_t refcount; /* protected by lo->mutex */ +}; + +enum { + CACHE_NEVER, + CACHE_NORMAL, + CACHE_ALWAYS, +}; + +struct lo_data { + pthread_mutex_t mutex; + int debug; + int writeback; + int flock; + int xattr; + char *source; + double timeout; + int cache; + int timeout_set; + struct lo_inode root; /* protected by lo->mutex */ +}; + +static const struct fuse_opt lo_opts[] = { + { "writeback", + offsetof(struct lo_data, writeback), 1 }, + { "no_writeback", + offsetof(struct lo_data, writeback), 0 }, + { "source=%s", + offsetof(struct lo_data, source), 0 }, + { "flock", + offsetof(struct lo_data, flock), 1 }, + { "no_flock", + offsetof(struct lo_data, flock), 0 }, + { "xattr", + offsetof(struct lo_data, xattr), 1 }, + { "no_xattr", + offsetof(struct lo_data, xattr), 0 }, + { "timeout=%lf", + offsetof(struct lo_data, timeout), 0 }, + { "timeout=", + offsetof(struct lo_data, timeout_set), 1 }, + { "cache=never", + offsetof(struct lo_data, cache), CACHE_NEVER }, + { "cache=auto", + offsetof(struct lo_data, cache), CACHE_NORMAL }, + { "cache=always", + offsetof(struct lo_data, cache), CACHE_ALWAYS }, + + FUSE_OPT_END +}; + +static void passthrough_ll_help(void) +{ + printf( +" -o writeback Enable writeback\n" +" -o no_writeback Disable write back\n" +" -o source=/home/dir Source directory to be mounted\n" +" -o flock Enable flock\n" +" -o no_flock Disable flock\n" +" -o xattr Enable xattr\n" +" -o no_xattr Disable xattr\n" +" -o timeout=1.0 Caching timeout\n" +" -o timeout=0/1 Timeout is set\n" +" -o cache=never Disable cache\n" +" -o cache=auto Auto enable cache\n" +" -o cache=always Cache always\n"); +} + +static struct lo_data *lo_data(fuse_req_t req) +{ + return (struct lo_data *) fuse_req_userdata(req); +} + +static struct lo_inode *lo_inode(fuse_req_t req, fuse_ino_t ino) +{ + if (ino == FUSE_ROOT_ID) + return &lo_data(req)->root; + else + return (struct lo_inode *) (uintptr_t) ino; +} + +static int lo_fd(fuse_req_t req, fuse_ino_t ino) +{ + return lo_inode(req, ino)->fd; +} + +static bool lo_debug(fuse_req_t req) +{ + return lo_data(req)->debug != 0; +} + +static void lo_init(void *userdata, + struct fuse_conn_info *conn) +{ + struct lo_data *lo = (struct lo_data *)userdata; + bool has_flag; + + if (lo->writeback) { + has_flag = fuse_set_feature_flag(conn, FUSE_CAP_WRITEBACK_CACHE); + if (lo->debug && has_flag) + fuse_log(FUSE_LOG_DEBUG, + "lo_init: activating writeback\n"); + } + if (lo->flock && conn->capable & FUSE_CAP_FLOCK_LOCKS) { + has_flag = fuse_set_feature_flag(conn, FUSE_CAP_FLOCK_LOCKS); + if (lo->debug && has_flag) + fuse_log(FUSE_LOG_DEBUG, + "lo_init: activating flock locks\n"); + } + + /* Disable the receiving and processing of FUSE_INTERRUPT requests */ + conn->no_interrupt = 1; +} + +static void lo_destroy(void *userdata) +{ + struct lo_data *lo = (struct lo_data*) userdata; + + while (lo->root.next != &lo->root) { + struct lo_inode* next = lo->root.next; + lo->root.next = next->next; + close(next->fd); + free(next); + } +} + +static void lo_getattr(fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info *fi) +{ + int res; + struct stat buf; + struct lo_data *lo = lo_data(req); + int fd = fi ? fi->fh : lo_fd(req, ino); + + (void) fi; + + res = fstatat(fd, "", &buf, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW); + if (res == -1) + return (void) fuse_reply_err(req, errno); + + fuse_reply_attr(req, &buf, lo->timeout); +} + +static void lo_setattr(fuse_req_t req, fuse_ino_t ino, struct stat *attr, + int valid, struct fuse_file_info *fi) +{ + int saverr; + char procname[64]; + struct lo_inode *inode = lo_inode(req, ino); + int ifd = inode->fd; + int res; + + if (valid & FUSE_SET_ATTR_MODE) { + if (fi) { + res = fchmod(fi->fh, attr->st_mode); + } else { + sprintf(procname, "/proc/self/fd/%i", ifd); + res = chmod(procname, attr->st_mode); + } + if (res == -1) + goto out_err; + } + if (valid & (FUSE_SET_ATTR_UID | FUSE_SET_ATTR_GID)) { + uid_t uid = (valid & FUSE_SET_ATTR_UID) ? + attr->st_uid : (uid_t) -1; + gid_t gid = (valid & FUSE_SET_ATTR_GID) ? + attr->st_gid : (gid_t) -1; + + res = fchownat(ifd, "", uid, gid, + AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW); + if (res == -1) + goto out_err; + } + if (valid & FUSE_SET_ATTR_SIZE) { + if (fi) { + res = ftruncate(fi->fh, attr->st_size); + } else { + sprintf(procname, "/proc/self/fd/%i", ifd); + res = truncate(procname, attr->st_size); + } + if (res == -1) + goto out_err; + } + if (valid & (FUSE_SET_ATTR_ATIME | FUSE_SET_ATTR_MTIME)) { + struct timespec tv[2]; + + tv[0].tv_sec = 0; + tv[1].tv_sec = 0; + tv[0].tv_nsec = UTIME_OMIT; + tv[1].tv_nsec = UTIME_OMIT; + + if (valid & FUSE_SET_ATTR_ATIME_NOW) + tv[0].tv_nsec = UTIME_NOW; + else if (valid & FUSE_SET_ATTR_ATIME) + tv[0] = attr->st_atim; + + if (valid & FUSE_SET_ATTR_MTIME_NOW) + tv[1].tv_nsec = UTIME_NOW; + else if (valid & FUSE_SET_ATTR_MTIME) + tv[1] = attr->st_mtim; + + if (fi) + res = futimens(fi->fh, tv); + else { + sprintf(procname, "/proc/self/fd/%i", ifd); + res = utimensat(AT_FDCWD, procname, tv, 0); + } + if (res == -1) + goto out_err; + } + + return lo_getattr(req, ino, fi); + +out_err: + saverr = errno; + fuse_reply_err(req, saverr); +} + +static struct lo_inode *lo_find(struct lo_data *lo, struct stat *st) +{ + struct lo_inode *p; + struct lo_inode *ret = NULL; + + pthread_mutex_lock(&lo->mutex); + for (p = lo->root.next; p != &lo->root; p = p->next) { + if (p->ino == st->st_ino && p->dev == st->st_dev) { + assert(p->refcount > 0); + ret = p; + ret->refcount++; + break; + } + } + pthread_mutex_unlock(&lo->mutex); + return ret; +} + + +static struct lo_inode *create_new_inode(int fd, struct fuse_entry_param *e, struct lo_data* lo) +{ + struct lo_inode *inode = NULL; + struct lo_inode *prev, *next; + + inode = calloc(1, sizeof(struct lo_inode)); + if (!inode) + return NULL; + + inode->refcount = 1; + inode->fd = fd; + inode->ino = e->attr.st_ino; + inode->dev = e->attr.st_dev; + + pthread_mutex_lock(&lo->mutex); + prev = &lo->root; + next = prev->next; + next->prev = inode; + inode->next = next; + inode->prev = prev; + prev->next = inode; + pthread_mutex_unlock(&lo->mutex); + return inode; +} + +static int fill_entry_param_new_inode(fuse_req_t req, fuse_ino_t parent, int fd, struct fuse_entry_param *e) +{ + int res; + struct lo_data *lo = lo_data(req); + + memset(e, 0, sizeof(*e)); + e->attr_timeout = lo->timeout; + e->entry_timeout = lo->timeout; + + res = fstatat(fd, "", &e->attr, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW); + if (res == -1) + return errno; + + e->ino = (uintptr_t) create_new_inode(dup(fd), e, lo); + + if (lo_debug(req)) + fuse_log(FUSE_LOG_DEBUG, " %lli/%d -> %lli\n", + (unsigned long long) parent, fd, (unsigned long long) e->ino); + + return 0; + +} + +static int lo_do_lookup(fuse_req_t req, fuse_ino_t parent, const char *name, + struct fuse_entry_param *e) +{ + int newfd; + int res; + int saverr; + struct lo_data *lo = lo_data(req); + struct lo_inode *inode; + + memset(e, 0, sizeof(*e)); + e->attr_timeout = lo->timeout; + e->entry_timeout = lo->timeout; + + newfd = openat(lo_fd(req, parent), name, O_PATH | O_NOFOLLOW); + if (newfd == -1) + goto out_err; + + res = fstatat(newfd, "", &e->attr, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW); + if (res == -1) + goto out_err; + + inode = lo_find(lo_data(req), &e->attr); + if (inode) { + close(newfd); + newfd = -1; + } else { + inode = create_new_inode(newfd, e, lo); + if (!inode) + goto out_err; + } + e->ino = (uintptr_t) inode; + + if (lo_debug(req)) + fuse_log(FUSE_LOG_DEBUG, " %lli/%s -> %lli\n", + (unsigned long long) parent, name, (unsigned long long) e->ino); + + return 0; + +out_err: + saverr = errno; + if (newfd != -1) + close(newfd); + return saverr; +} + +static void lo_lookup(fuse_req_t req, fuse_ino_t parent, const char *name) +{ + struct fuse_entry_param e; + int err; + + if (lo_debug(req)) + fuse_log(FUSE_LOG_DEBUG, "lo_lookup(parent=%" PRIu64 ", name=%s)\n", + parent, name); + + err = lo_do_lookup(req, parent, name, &e); + if (err) + fuse_reply_err(req, err); + else + fuse_reply_entry(req, &e); +} + +static void lo_mknod_symlink(fuse_req_t req, fuse_ino_t parent, + const char *name, mode_t mode, dev_t rdev, + const char *link) +{ + int res; + int saverr; + struct lo_inode *dir = lo_inode(req, parent); + struct fuse_entry_param e; + + res = mknod_wrapper(dir->fd, name, link, mode, rdev); + + saverr = errno; + if (res == -1) + goto out; + + saverr = lo_do_lookup(req, parent, name, &e); + if (saverr) + goto out; + + if (lo_debug(req)) + fuse_log(FUSE_LOG_DEBUG, " %lli/%s -> %lli\n", + (unsigned long long) parent, name, (unsigned long long) e.ino); + + fuse_reply_entry(req, &e); + return; + +out: + fuse_reply_err(req, saverr); +} + +static void lo_mknod(fuse_req_t req, fuse_ino_t parent, + const char *name, mode_t mode, dev_t rdev) +{ + lo_mknod_symlink(req, parent, name, mode, rdev, NULL); +} + +static void lo_mkdir(fuse_req_t req, fuse_ino_t parent, const char *name, + mode_t mode) +{ + lo_mknod_symlink(req, parent, name, S_IFDIR | mode, 0, NULL); +} + +static void lo_symlink(fuse_req_t req, const char *link, + fuse_ino_t parent, const char *name) +{ + lo_mknod_symlink(req, parent, name, S_IFLNK, 0, link); +} + +static void lo_link(fuse_req_t req, fuse_ino_t ino, fuse_ino_t parent, + const char *name) +{ + int res; + struct lo_data *lo = lo_data(req); + struct lo_inode *inode = lo_inode(req, ino); + struct fuse_entry_param e; + char procname[64]; + int saverr; + + memset(&e, 0, sizeof(struct fuse_entry_param)); + e.attr_timeout = lo->timeout; + e.entry_timeout = lo->timeout; + + sprintf(procname, "/proc/self/fd/%i", inode->fd); + res = linkat(AT_FDCWD, procname, lo_fd(req, parent), name, + AT_SYMLINK_FOLLOW); + if (res == -1) + goto out_err; + + res = fstatat(inode->fd, "", &e.attr, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW); + if (res == -1) + goto out_err; + + pthread_mutex_lock(&lo->mutex); + inode->refcount++; + pthread_mutex_unlock(&lo->mutex); + e.ino = (uintptr_t) inode; + + if (lo_debug(req)) + fuse_log(FUSE_LOG_DEBUG, " %lli/%s -> %lli\n", + (unsigned long long) parent, name, + (unsigned long long) e.ino); + + fuse_reply_entry(req, &e); + return; + +out_err: + saverr = errno; + fuse_reply_err(req, saverr); +} + +static void lo_rmdir(fuse_req_t req, fuse_ino_t parent, const char *name) +{ + int res; + + res = unlinkat(lo_fd(req, parent), name, AT_REMOVEDIR); + + fuse_reply_err(req, res == -1 ? errno : 0); +} + +static void lo_rename(fuse_req_t req, fuse_ino_t parent, const char *name, + fuse_ino_t newparent, const char *newname, + unsigned int flags) +{ + int res; + + if (flags) { + fuse_reply_err(req, EINVAL); + return; + } + + res = renameat(lo_fd(req, parent), name, + lo_fd(req, newparent), newname); + + fuse_reply_err(req, res == -1 ? errno : 0); +} + +static void lo_unlink(fuse_req_t req, fuse_ino_t parent, const char *name) +{ + int res; + + res = unlinkat(lo_fd(req, parent), name, 0); + + fuse_reply_err(req, res == -1 ? errno : 0); +} + +static void unref_inode(struct lo_data *lo, struct lo_inode *inode, uint64_t n) +{ + if (!inode) + return; + + pthread_mutex_lock(&lo->mutex); + assert(inode->refcount >= n); + inode->refcount -= n; + if (!inode->refcount) { + struct lo_inode *prev, *next; + + prev = inode->prev; + next = inode->next; + next->prev = prev; + prev->next = next; + + pthread_mutex_unlock(&lo->mutex); + close(inode->fd); + free(inode); + + } else { + pthread_mutex_unlock(&lo->mutex); + } +} + +static void lo_forget_one(fuse_req_t req, fuse_ino_t ino, uint64_t nlookup) +{ + struct lo_data *lo = lo_data(req); + struct lo_inode *inode = lo_inode(req, ino); + + if (lo_debug(req)) { + fuse_log(FUSE_LOG_DEBUG, " forget %lli %lli -%lli\n", + (unsigned long long) ino, + (unsigned long long) inode->refcount, + (unsigned long long) nlookup); + } + + unref_inode(lo, inode, nlookup); +} + +static void lo_forget(fuse_req_t req, fuse_ino_t ino, uint64_t nlookup) +{ + lo_forget_one(req, ino, nlookup); + fuse_reply_none(req); +} + +static void lo_forget_multi(fuse_req_t req, size_t count, + struct fuse_forget_data *forgets) +{ + int i; + + for (i = 0; i < count; i++) + lo_forget_one(req, forgets[i].ino, forgets[i].nlookup); + fuse_reply_none(req); +} + +static void lo_readlink(fuse_req_t req, fuse_ino_t ino) +{ + char buf[PATH_MAX + 1]; + int res; + + res = readlinkat(lo_fd(req, ino), "", buf, sizeof(buf)); + if (res == -1) + return (void) fuse_reply_err(req, errno); + + if (res == sizeof(buf)) + return (void) fuse_reply_err(req, ENAMETOOLONG); + + buf[res] = '\0'; + + fuse_reply_readlink(req, buf); +} + +struct lo_dirp { + DIR *dp; + struct dirent *entry; + off_t offset; +}; + +static struct lo_dirp *lo_dirp(struct fuse_file_info *fi) +{ + return (struct lo_dirp *) (uintptr_t) fi->fh; +} + +static void lo_opendir(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi) +{ + int error = ENOMEM; + struct lo_data *lo = lo_data(req); + struct lo_dirp *d; + int fd = -1; + + d = calloc(1, sizeof(struct lo_dirp)); + if (d == NULL) + goto out_err; + + fd = openat(lo_fd(req, ino), ".", O_RDONLY); + if (fd == -1) + goto out_errno; + + d->dp = fdopendir(fd); + if (d->dp == NULL) + goto out_errno; + + d->offset = 0; + d->entry = NULL; + + fi->fh = (uintptr_t) d; + if (lo->cache != CACHE_NEVER) + fi->cache_readdir = 1; + if (lo->cache == CACHE_ALWAYS) + fi->keep_cache = 1; + fuse_reply_open(req, fi); + return; + +out_errno: + error = errno; +out_err: + if (d) { + if (fd != -1) + close(fd); + free(d); + } + fuse_reply_err(req, error); +} + +static int is_dot_or_dotdot(const char *name) +{ + return name[0] == '.' && (name[1] == '\0' || + (name[1] == '.' && name[2] == '\0')); +} + +static void lo_do_readdir(fuse_req_t req, fuse_ino_t ino, size_t size, + off_t offset, struct fuse_file_info *fi, int plus) +{ + struct lo_dirp *d = lo_dirp(fi); + char *buf; + char *p; + size_t rem = size; + int err; + + (void) ino; + + buf = calloc(1, size); + if (!buf) { + err = ENOMEM; + goto error; + } + p = buf; + + if (offset != d->offset) { + seekdir(d->dp, offset); + d->entry = NULL; + d->offset = offset; + } + while (1) { + size_t entsize; + off_t nextoff; + const char *name; + + if (!d->entry) { + errno = 0; + d->entry = readdir(d->dp); + if (!d->entry) { + if (errno) { // Error + err = errno; + goto error; + } else { // End of stream + break; + } + } + } + nextoff = d->entry->d_off; + name = d->entry->d_name; + fuse_ino_t entry_ino = 0; + if (plus) { + struct fuse_entry_param e; + if (is_dot_or_dotdot(name)) { + e = (struct fuse_entry_param) { + .attr.st_ino = d->entry->d_ino, + .attr.st_mode = d->entry->d_type << 12, + }; + } else { + err = lo_do_lookup(req, ino, name, &e); + if (err) + goto error; + entry_ino = e.ino; + } + + entsize = fuse_add_direntry_plus(req, p, rem, name, + &e, nextoff); + } else { + struct stat st = { + .st_ino = d->entry->d_ino, + .st_mode = d->entry->d_type << 12, + }; + entsize = fuse_add_direntry(req, p, rem, name, + &st, nextoff); + } + if (entsize > rem) { + if (entry_ino != 0) + lo_forget_one(req, entry_ino, 1); + break; + } + + p += entsize; + rem -= entsize; + + d->entry = NULL; + d->offset = nextoff; + } + + err = 0; +error: + // If there's an error, we can only signal it if we haven't stored + // any entries yet - otherwise we'd end up with wrong lookup + // counts for the entries that are already in the buffer. So we + // return what we've collected until that point. + if (err && rem == size) + fuse_reply_err(req, err); + else + fuse_reply_buf(req, buf, size - rem); + free(buf); +} + +static void lo_readdir(fuse_req_t req, fuse_ino_t ino, size_t size, + off_t offset, struct fuse_file_info *fi) +{ + lo_do_readdir(req, ino, size, offset, fi, 0); +} + +static void lo_readdirplus(fuse_req_t req, fuse_ino_t ino, size_t size, + off_t offset, struct fuse_file_info *fi) +{ + lo_do_readdir(req, ino, size, offset, fi, 1); +} + +static void lo_releasedir(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi) +{ + struct lo_dirp *d = lo_dirp(fi); + (void) ino; + closedir(d->dp); + free(d); + fuse_reply_err(req, 0); +} + +static void lo_tmpfile(fuse_req_t req, fuse_ino_t parent, + mode_t mode, struct fuse_file_info *fi) +{ + int fd; + struct lo_data *lo = lo_data(req); + struct fuse_entry_param e; + int err; + + if (lo_debug(req)) + fuse_log(FUSE_LOG_DEBUG, "lo_tmpfile(parent=%" PRIu64 ")\n", + parent); + + fd = openat(lo_fd(req, parent), ".", + (fi->flags | O_TMPFILE) & ~O_NOFOLLOW, mode); + if (fd == -1) + return (void) fuse_reply_err(req, errno); + + fi->fh = fd; + if (lo->cache == CACHE_NEVER) + fi->direct_io = 1; + else if (lo->cache == CACHE_ALWAYS) + fi->keep_cache = 1; + + /* parallel_direct_writes feature depends on direct_io features. + To make parallel_direct_writes valid, need set fi->direct_io + in current function. */ + fi->parallel_direct_writes = 1; + + err = fill_entry_param_new_inode(req, parent, fd, &e); + if (err) + fuse_reply_err(req, err); + else + fuse_reply_create(req, &e, fi); +} + +static void lo_create(fuse_req_t req, fuse_ino_t parent, const char *name, + mode_t mode, struct fuse_file_info *fi) +{ + int fd; + struct lo_data *lo = lo_data(req); + struct fuse_entry_param e; + int err; + + if (lo_debug(req)) + fuse_log(FUSE_LOG_DEBUG, "lo_create(parent=%" PRIu64 ", name=%s)\n", + parent, name); + + fd = openat(lo_fd(req, parent), name, + (fi->flags | O_CREAT) & ~O_NOFOLLOW, mode); + if (fd == -1) + return (void) fuse_reply_err(req, errno); + + fi->fh = fd; + if (lo->cache == CACHE_NEVER) + fi->direct_io = 1; + else if (lo->cache == CACHE_ALWAYS) + fi->keep_cache = 1; + + /* parallel_direct_writes feature depends on direct_io features. + To make parallel_direct_writes valid, need set fi->direct_io + in current function. */ + fi->parallel_direct_writes = 1; + + err = lo_do_lookup(req, parent, name, &e); + if (err) + fuse_reply_err(req, err); + else + fuse_reply_create(req, &e, fi); +} + +static void lo_fsyncdir(fuse_req_t req, fuse_ino_t ino, int datasync, + struct fuse_file_info *fi) +{ + int res; + int fd = dirfd(lo_dirp(fi)->dp); + (void) ino; + if (datasync) + res = fdatasync(fd); + else + res = fsync(fd); + fuse_reply_err(req, res == -1 ? errno : 0); +} + +static void lo_open(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi) +{ + int fd; + char buf[64]; + struct lo_data *lo = lo_data(req); + + if (lo_debug(req)) + fuse_log(FUSE_LOG_DEBUG, "lo_open(ino=%" PRIu64 ", flags=%d)\n", + ino, fi->flags); + + /* With writeback cache, kernel may send read requests even + when userspace opened write-only */ + if (lo->writeback && (fi->flags & O_ACCMODE) == O_WRONLY) { + fi->flags &= ~O_ACCMODE; + fi->flags |= O_RDWR; + } + + /* With writeback cache, O_APPEND is handled by the kernel. + This breaks atomicity (since the file may change in the + underlying filesystem, so that the kernel's idea of the + end of the file isn't accurate anymore). In this example, + we just accept that. A more rigorous filesystem may want + to return an error here */ + if (lo->writeback && (fi->flags & O_APPEND)) + fi->flags &= ~O_APPEND; + + sprintf(buf, "/proc/self/fd/%i", lo_fd(req, ino)); + fd = open(buf, fi->flags & ~O_NOFOLLOW); + if (fd == -1) + return (void) fuse_reply_err(req, errno); + + fi->fh = fd; + if (lo->cache == CACHE_NEVER) + fi->direct_io = 1; + else if (lo->cache == CACHE_ALWAYS) + fi->keep_cache = 1; + + /* Enable direct_io when open has flags O_DIRECT to enjoy the feature + parallel_direct_writes (i.e., to get a shared lock, not exclusive lock, + for writes to the same file in the kernel). */ + if (fi->flags & O_DIRECT) + fi->direct_io = 1; + + /* parallel_direct_writes feature depends on direct_io features. + To make parallel_direct_writes valid, need set fi->direct_io + in current function. */ + fi->parallel_direct_writes = 1; + + fuse_reply_open(req, fi); +} + +static void lo_release(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi) +{ + (void) ino; + + close(fi->fh); + fuse_reply_err(req, 0); +} + +static void lo_flush(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi) +{ + int res; + (void) ino; + res = close(dup(fi->fh)); + fuse_reply_err(req, res == -1 ? errno : 0); +} + +static void lo_fsync(fuse_req_t req, fuse_ino_t ino, int datasync, + struct fuse_file_info *fi) +{ + int res; + (void) ino; + if (datasync) + res = fdatasync(fi->fh); + else + res = fsync(fi->fh); + fuse_reply_err(req, res == -1 ? errno : 0); +} + +static void lo_read(fuse_req_t req, fuse_ino_t ino, size_t size, + off_t offset, struct fuse_file_info *fi) +{ + struct fuse_bufvec buf = FUSE_BUFVEC_INIT(size); + + if (lo_debug(req)) + fuse_log(FUSE_LOG_DEBUG, "lo_read(ino=%" PRIu64 ", size=%zd, " + "off=%lu)\n", ino, size, (unsigned long) offset); + + buf.buf[0].flags = FUSE_BUF_IS_FD | FUSE_BUF_FD_SEEK; + buf.buf[0].fd = fi->fh; + buf.buf[0].pos = offset; + + fuse_reply_data(req, &buf, FUSE_BUF_SPLICE_MOVE); +} + +static void lo_write_buf(fuse_req_t req, fuse_ino_t ino, + struct fuse_bufvec *in_buf, off_t off, + struct fuse_file_info *fi) +{ + (void) ino; + ssize_t res; + struct fuse_bufvec out_buf = FUSE_BUFVEC_INIT(fuse_buf_size(in_buf)); + + out_buf.buf[0].flags = FUSE_BUF_IS_FD | FUSE_BUF_FD_SEEK; + out_buf.buf[0].fd = fi->fh; + out_buf.buf[0].pos = off; + + if (lo_debug(req)) + fuse_log(FUSE_LOG_DEBUG, "lo_write(ino=%" PRIu64 ", size=%zd, off=%jd)\n", + ino, out_buf.buf[0].size, (intmax_t) off); + + res = fuse_buf_copy(&out_buf, in_buf, 0); + if(res < 0) + fuse_reply_err(req, -res); + else + fuse_reply_write(req, (size_t) res); +} + +static void lo_statfs(fuse_req_t req, fuse_ino_t ino) +{ + int res; + struct statvfs stbuf; + + res = fstatvfs(lo_fd(req, ino), &stbuf); + if (res == -1) + fuse_reply_err(req, errno); + else + fuse_reply_statfs(req, &stbuf); +} + +static void lo_fallocate(fuse_req_t req, fuse_ino_t ino, int mode, + off_t offset, off_t length, struct fuse_file_info *fi) +{ + int err; + (void) ino; + + err = -do_fallocate(fi->fh, mode, offset, length); + + fuse_reply_err(req, err); +} + +static void lo_flock(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi, + int op) +{ + int res; + (void) ino; + + res = flock(fi->fh, op); + + fuse_reply_err(req, res == -1 ? errno : 0); +} + +static void lo_getxattr(fuse_req_t req, fuse_ino_t ino, const char *name, + size_t size) +{ + char *value = NULL; + char procname[64]; + struct lo_inode *inode = lo_inode(req, ino); + ssize_t ret; + int saverr; + + saverr = ENOSYS; + if (!lo_data(req)->xattr) + goto out; + + if (lo_debug(req)) { + fuse_log(FUSE_LOG_DEBUG, "lo_getxattr(ino=%" PRIu64 ", name=%s size=%zd)\n", + ino, name, size); + } + + sprintf(procname, "/proc/self/fd/%i", inode->fd); + + if (size) { + value = malloc(size); + if (!value) + goto out_err; + + ret = getxattr(procname, name, value, size); + if (ret == -1) + goto out_err; + saverr = 0; + if (ret == 0) + goto out; + + fuse_reply_buf(req, value, ret); + } else { + ret = getxattr(procname, name, NULL, 0); + if (ret == -1) + goto out_err; + + fuse_reply_xattr(req, ret); + } +out_free: + free(value); + return; + +out_err: + saverr = errno; +out: + fuse_reply_err(req, saverr); + goto out_free; +} + +static void lo_listxattr(fuse_req_t req, fuse_ino_t ino, size_t size) +{ + char *value = NULL; + char procname[64]; + struct lo_inode *inode = lo_inode(req, ino); + ssize_t ret; + int saverr; + + saverr = ENOSYS; + if (!lo_data(req)->xattr) + goto out; + + if (lo_debug(req)) { + fuse_log(FUSE_LOG_DEBUG, "lo_listxattr(ino=%" PRIu64 ", size=%zd)\n", + ino, size); + } + + sprintf(procname, "/proc/self/fd/%i", inode->fd); + + if (size) { + value = malloc(size); + if (!value) + goto out_err; + + ret = listxattr(procname, value, size); + if (ret == -1) + goto out_err; + saverr = 0; + if (ret == 0) + goto out; + + fuse_reply_buf(req, value, ret); + } else { + ret = listxattr(procname, NULL, 0); + if (ret == -1) + goto out_err; + + fuse_reply_xattr(req, ret); + } +out_free: + free(value); + return; + +out_err: + saverr = errno; +out: + fuse_reply_err(req, saverr); + goto out_free; +} + +static void lo_setxattr(fuse_req_t req, fuse_ino_t ino, const char *name, + const char *value, size_t size, int flags) +{ + char procname[64]; + struct lo_inode *inode = lo_inode(req, ino); + ssize_t ret; + int saverr; + + saverr = ENOSYS; + if (!lo_data(req)->xattr) + goto out; + + if (lo_debug(req)) { + fuse_log(FUSE_LOG_DEBUG, "lo_setxattr(ino=%" PRIu64 ", name=%s value=%s size=%zd)\n", + ino, name, value, size); + } + + sprintf(procname, "/proc/self/fd/%i", inode->fd); + + ret = setxattr(procname, name, value, size, flags); + saverr = ret == -1 ? errno : 0; + +out: + fuse_reply_err(req, saverr); +} + +static void lo_removexattr(fuse_req_t req, fuse_ino_t ino, const char *name) +{ + char procname[64]; + struct lo_inode *inode = lo_inode(req, ino); + ssize_t ret; + int saverr; + + saverr = ENOSYS; + if (!lo_data(req)->xattr) + goto out; + + if (lo_debug(req)) { + fuse_log(FUSE_LOG_DEBUG, "lo_removexattr(ino=%" PRIu64 ", name=%s)\n", + ino, name); + } + + sprintf(procname, "/proc/self/fd/%i", inode->fd); + + ret = removexattr(procname, name); + saverr = ret == -1 ? errno : 0; + +out: + fuse_reply_err(req, saverr); +} + +#ifdef HAVE_COPY_FILE_RANGE +static void lo_copy_file_range(fuse_req_t req, fuse_ino_t ino_in, off_t off_in, + struct fuse_file_info *fi_in, + fuse_ino_t ino_out, off_t off_out, + struct fuse_file_info *fi_out, size_t len, + int flags) +{ + ssize_t res; + + if (lo_debug(req)) + fuse_log(FUSE_LOG_DEBUG, + "%s(ino=%lld fd=%lld off=%jd ino=%lld fd=%lld off=%jd, size=%zd, flags=0x%x)\n", + __func__, (unsigned long long)ino_in, + (unsigned long long)fi_in->fh, + (intmax_t) off_in, (unsigned long long)ino_out, + (unsigned long long)fi_out->fh, (intmax_t) off_out, + len, flags); + + res = copy_file_range(fi_in->fh, &off_in, fi_out->fh, &off_out, len, + flags); + if (res < 0) + fuse_reply_err(req, errno); + else + fuse_reply_write(req, res); +} +#endif + +static void lo_lseek(fuse_req_t req, fuse_ino_t ino, off_t off, int whence, + struct fuse_file_info *fi) +{ + off_t res; + + (void)ino; + res = lseek(fi->fh, off, whence); + if (res != -1) + fuse_reply_lseek(req, res); + else + fuse_reply_err(req, errno); +} + +#ifdef HAVE_STATX +static void lo_statx(fuse_req_t req, fuse_ino_t ino, int flags, int mask, + struct fuse_file_info *fi) +{ + struct lo_data *lo = lo_data(req); + struct statx buf; + int res; + int fd; + + if (fi) + fd = fi->fh; + else + fd = lo_fd(req, ino); + + res = statx(fd, "", flags | AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW, mask, &buf); + if (res == -1) + fuse_reply_err(req, errno); + else + fuse_reply_statx(req, 0, &buf, lo->timeout); +} +#endif + +static const struct fuse_lowlevel_ops lo_oper = { + .init = lo_init, + .destroy = lo_destroy, + .lookup = lo_lookup, + .mkdir = lo_mkdir, + .mknod = lo_mknod, + .symlink = lo_symlink, + .link = lo_link, + .unlink = lo_unlink, + .rmdir = lo_rmdir, + .rename = lo_rename, + .forget = lo_forget, + .forget_multi = lo_forget_multi, + .getattr = lo_getattr, + .setattr = lo_setattr, + .readlink = lo_readlink, + .opendir = lo_opendir, + .readdir = lo_readdir, + .readdirplus = lo_readdirplus, + .releasedir = lo_releasedir, + .fsyncdir = lo_fsyncdir, + .create = lo_create, + .tmpfile = lo_tmpfile, + .open = lo_open, + .release = lo_release, + .flush = lo_flush, + .fsync = lo_fsync, + .read = lo_read, + .write_buf = lo_write_buf, + .statfs = lo_statfs, + .fallocate = lo_fallocate, + .flock = lo_flock, + .getxattr = lo_getxattr, + .listxattr = lo_listxattr, + .setxattr = lo_setxattr, + .removexattr = lo_removexattr, +#ifdef HAVE_COPY_FILE_RANGE + .copy_file_range = lo_copy_file_range, +#endif + .lseek = lo_lseek, +#ifdef HAVE_STATX + .statx = lo_statx, +#endif +}; + +int main(int argc, char *argv[]) +{ + struct fuse_args args = FUSE_ARGS_INIT(argc, argv); + struct fuse_session *se; + struct fuse_cmdline_opts opts; + struct fuse_loop_config *config; + struct lo_data lo = { .debug = 0, + .writeback = 0 }; + int ret = -1; + + /* Don't mask creation mode, kernel already did that */ + umask(0); + + pthread_mutex_init(&lo.mutex, NULL); + lo.root.next = lo.root.prev = &lo.root; + lo.root.fd = -1; + lo.cache = CACHE_NORMAL; + + if (fuse_parse_cmdline(&args, &opts) != 0) + return 1; + if (opts.show_help) { + printf("usage: %s [options] \n\n", argv[0]); + fuse_cmdline_help(); + fuse_lowlevel_help(); + passthrough_ll_help(); + ret = 0; + goto err_out1; + } else if (opts.show_version) { + printf("FUSE library version %s\n", fuse_pkgversion()); + fuse_lowlevel_version(); + ret = 0; + goto err_out1; + } + + if(opts.mountpoint == NULL) { + printf("usage: %s [options] \n", argv[0]); + printf(" %s --help\n", argv[0]); + ret = 1; + goto err_out1; + } + + if (fuse_opt_parse(&args, &lo, lo_opts, NULL)== -1) + return 1; + + lo.debug = opts.debug; + lo.root.refcount = 2; + if (lo.source) { + struct stat stat; + int res; + + res = lstat(lo.source, &stat); + if (res == -1) { + fuse_log(FUSE_LOG_ERR, "failed to stat source (\"%s\"): %m\n", + lo.source); + exit(1); + } + if (!S_ISDIR(stat.st_mode)) { + fuse_log(FUSE_LOG_ERR, "source is not a directory\n"); + exit(1); + } + + } else { + lo.source = strdup("/"); + if(!lo.source) { + fuse_log(FUSE_LOG_ERR, "fuse: memory allocation failed\n"); + exit(1); + } + } + if (!lo.timeout_set) { + switch (lo.cache) { + case CACHE_NEVER: + lo.timeout = 0.0; + break; + + case CACHE_NORMAL: + lo.timeout = 1.0; + break; + + case CACHE_ALWAYS: + lo.timeout = 86400.0; + break; + } + } else if (lo.timeout < 0) { + fuse_log(FUSE_LOG_ERR, "timeout is negative (%lf)\n", + lo.timeout); + exit(1); + } + + lo.root.fd = open(lo.source, O_PATH); + if (lo.root.fd == -1) { + fuse_log(FUSE_LOG_ERR, "open(\"%s\", O_PATH): %m\n", + lo.source); + exit(1); + } + + se = fuse_session_new(&args, &lo_oper, sizeof(lo_oper), &lo); + if (se == NULL) + goto err_out1; + + if (fuse_set_signal_handlers(se) != 0) + goto err_out2; + + if (fuse_session_mount(se, opts.mountpoint) != 0) + goto err_out3; + + fuse_daemonize(opts.foreground); + + /* Block until ctrl+c or fusermount -u */ + if (opts.singlethread) + ret = fuse_session_loop(se); + else { + config = fuse_loop_cfg_create(); + fuse_loop_cfg_set_clone_fd(config, opts.clone_fd); + fuse_loop_cfg_set_max_threads(config, opts.max_threads); + ret = fuse_session_loop_mt(se, config); + fuse_loop_cfg_destroy(config); + config = NULL; + } + + fuse_session_unmount(se); +err_out3: + fuse_remove_signal_handlers(se); +err_out2: + fuse_session_destroy(se); +err_out1: + free(opts.mountpoint); + fuse_opt_free_args(&args); + + if (lo.root.fd >= 0) + close(lo.root.fd); + + free(lo.source); + return ret ? 1 : 0; +} diff --git a/example/poll.c b/example/poll.c new file mode 100644 index 0000000..cdcdd87 --- /dev/null +++ b/example/poll.c @@ -0,0 +1,304 @@ +/* + FUSE fsel: FUSE select example + Copyright (C) 2008 SUSE Linux Products GmbH + Copyright (C) 2008 Tejun Heo + + This program can be distributed under the terms of the GNU GPLv2. + See the file GPL2.txt. +*/ + +/** @file + * + * This example illustrates how to write a FUSE file system that + * supports polling for changes that don't come through the kernel. It + * can be tested with the poll_client.c program. + * + * Compile with: + * + * gcc -Wall poll.c `pkg-config fuse3 --cflags --libs` -o poll + * + * ## Source code ## + * \include poll.c + */ + +#define FUSE_USE_VERSION 31 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * fsel_open_mask is used to limit the number of opens to 1 per file. + * This is to use file index (0-F) as fh as poll support requires + * unique fh per open file. Lifting this would require proper open + * file management. + */ +static unsigned fsel_open_mask; +static const char fsel_hex_map[] = "0123456789ABCDEF"; +static struct fuse *fsel_fuse; /* needed for poll notification */ + +#define FSEL_CNT_MAX 10 /* each file can store up to 10 chars */ +#define FSEL_FILES 16 + +static pthread_mutex_t fsel_mutex; /* protects notify_mask and cnt array */ +static unsigned fsel_poll_notify_mask; /* poll notification scheduled? */ +static struct fuse_pollhandle *fsel_poll_handle[FSEL_FILES]; /* poll notify handles */ +static unsigned fsel_cnt[FSEL_FILES]; /* nbytes stored in each file */ +static _Atomic bool fsel_stop = false; +static pthread_t fsel_producer_thread; + + +static int fsel_path_index(const char *path) +{ + char ch = path[1]; + + if (strlen(path) != 2 || path[0] != '/' || !isxdigit(ch) || islower(ch)) + return -1; + return ch <= '9' ? ch - '0' : ch - 'A' + 10; +} + +static void fsel_destroy(void *private_data) +{ + (void)private_data; + + fsel_stop = true; + + pthread_join(fsel_producer_thread, NULL); +} + +static int fsel_getattr(const char *path, struct stat *stbuf, + struct fuse_file_info *fi) +{ + (void) fi; + int idx; + + memset(stbuf, 0, sizeof(struct stat)); + + if (strcmp(path, "/") == 0) { + stbuf->st_mode = S_IFDIR | 0555; + stbuf->st_nlink = 2; + return 0; + } + + idx = fsel_path_index(path); + if (idx < 0) + return -ENOENT; + + stbuf->st_mode = S_IFREG | 0444; + stbuf->st_nlink = 1; + stbuf->st_size = fsel_cnt[idx]; + return 0; +} + +static int fsel_readdir(const char *path, void *buf, fuse_fill_dir_t filler, + off_t offset, struct fuse_file_info *fi, + enum fuse_readdir_flags flags) +{ + char name[2] = { }; + int i; + + (void) offset; + (void) fi; + (void) flags; + + if (strcmp(path, "/") != 0) + return -ENOENT; + + for (i = 0; i < FSEL_FILES; i++) { + name[0] = fsel_hex_map[i]; + filler(buf, name, NULL, 0, FUSE_FILL_DIR_DEFAULTS); + } + + return 0; +} + +static int fsel_open(const char *path, struct fuse_file_info *fi) +{ + int idx = fsel_path_index(path); + + if (idx < 0) + return -ENOENT; + if ((fi->flags & O_ACCMODE) != O_RDONLY) + return -EACCES; + if (fsel_open_mask & (1 << idx)) + return -EBUSY; + fsel_open_mask |= (1 << idx); + + /* + * fsel files are nonseekable somewhat pipe-like files which + * gets filled up periodically by producer thread and consumed + * on read. Tell FUSE as such. + */ + fi->fh = idx; + fi->direct_io = 1; + fi->nonseekable = 1; + + return 0; +} + +static int fsel_release(const char *path, struct fuse_file_info *fi) +{ + int idx = fi->fh; + + (void) path; + + fsel_open_mask &= ~(1 << idx); + return 0; +} + +static int fsel_read(const char *path, char *buf, size_t size, off_t offset, + struct fuse_file_info *fi) +{ + int idx = fi->fh; + + (void) path; + (void) offset; + + pthread_mutex_lock(&fsel_mutex); + if (fsel_cnt[idx] < size) + size = fsel_cnt[idx]; + printf("READ %X transferred=%zu cnt=%u\n", idx, size, fsel_cnt[idx]); + fsel_cnt[idx] -= size; + pthread_mutex_unlock(&fsel_mutex); + + memset(buf, fsel_hex_map[idx], size); + return size; +} + +static int fsel_poll(const char *path, struct fuse_file_info *fi, + struct fuse_pollhandle *ph, unsigned *reventsp) +{ + static unsigned polled_zero; + int idx = fi->fh; + + (void) path; + + /* + * Poll notification requires pointer to struct fuse which + * can't be obtained when using fuse_main(). As notification + * happens only after poll is called, fill it here from + * fuse_context. + */ + if (!fsel_fuse) { + struct fuse_context *cxt = fuse_get_context(); + if (cxt) + fsel_fuse = cxt->fuse; + } + + pthread_mutex_lock(&fsel_mutex); + + if (ph != NULL) { + struct fuse_pollhandle *oldph = fsel_poll_handle[idx]; + + if (oldph) + fuse_pollhandle_destroy(oldph); + + fsel_poll_notify_mask |= (1 << idx); + fsel_poll_handle[idx] = ph; + } + + if (fsel_cnt[idx]) { + *reventsp |= POLLIN; + printf("POLL %X cnt=%u polled_zero=%u\n", + idx, fsel_cnt[idx], polled_zero); + polled_zero = 0; + } else + polled_zero++; + + pthread_mutex_unlock(&fsel_mutex); + return 0; +} + +static const struct fuse_operations fsel_oper = { + .destroy = fsel_destroy, + .getattr = fsel_getattr, + .readdir = fsel_readdir, + .open = fsel_open, + .release = fsel_release, + .read = fsel_read, + .poll = fsel_poll, +}; + +static void *fsel_producer(void *data) +{ + const struct timespec interval = { 0, 250000000 }; + unsigned idx = 0, nr = 1; + + (void) data; + + while (!fsel_stop) { + int i, t; + + pthread_mutex_lock(&fsel_mutex); + + /* + * This is the main producer loop which is executed + * ever 500ms. On each iteration, it fills one byte + * to 1, 2 or 4 files and sends poll notification if + * requested. + */ + for (i = 0, t = idx; i < nr; + i++, t = (t + FSEL_FILES / nr) % FSEL_FILES) { + if (fsel_cnt[t] == FSEL_CNT_MAX) + continue; + + fsel_cnt[t]++; + if (fsel_fuse && (fsel_poll_notify_mask & (1 << t))) { + struct fuse_pollhandle *ph; + + printf("NOTIFY %X\n", t); + ph = fsel_poll_handle[t]; + fuse_notify_poll(ph); + fuse_pollhandle_destroy(ph); + fsel_poll_notify_mask &= ~(1 << t); + fsel_poll_handle[t] = NULL; + } + } + + idx = (idx + 1) % FSEL_FILES; + if (idx == 0) + nr = (nr * 2) % 7; /* cycle through 1, 2 and 4 */ + + pthread_mutex_unlock(&fsel_mutex); + + nanosleep(&interval, NULL); + } + + return NULL; +} + +int main(int argc, char *argv[]) +{ + pthread_attr_t attr; + int ret; + + errno = pthread_mutex_init(&fsel_mutex, NULL); + if (errno) { + perror("pthread_mutex_init"); + return 1; + } + + errno = pthread_attr_init(&attr); + if (errno) { + perror("pthread_attr_init"); + return 1; + } + + errno = pthread_create(&fsel_producer_thread, &attr, fsel_producer, NULL); + if (errno) { + perror("pthread_create"); + return 1; + } + + ret = fuse_main(argc, argv, &fsel_oper, NULL); + + return ret; +} diff --git a/example/poll_client.c b/example/poll_client.c new file mode 100644 index 0000000..6dba3e7 --- /dev/null +++ b/example/poll_client.c @@ -0,0 +1,84 @@ +/* + FUSE fselclient: FUSE select example client + Copyright (C) 2008 SUSE Linux Products GmbH + Copyright (C) 2008 Tejun Heo + + This program can be distributed under the terms of the GNU GPLv2. + See the file GPL2.txt. +*/ + +/** @file + * + * This program tests the poll.c example file systsem. + * + * Compile with: + * + * gcc -Wall poll_client.c -o poll_client + * + * ## Source code ## + * \include poll_client.c + */ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +int main(void) +{ + static const char hex_map[] = "0123456789ABCDEF"; + const size_t fsel_files = sizeof(hex_map) - 1; + int fds[sizeof(hex_map) - 1]; + size_t i, tries; + int nfds; + + for (i = 0; i < fsel_files; i++) { + char name[] = { hex_map[i], '\0' }; + fds[i] = open(name, O_RDONLY); + if (fds[i] < 0) { + perror("open"); + return 1; + } + } + nfds = fds[fsel_files - 1] + 1; + + for (tries = 0; tries < fsel_files; tries++) { + static char buf[4096]; + fd_set rfds; + int rc; + + FD_ZERO(&rfds); + for (i = 0; i < fsel_files; i++) + FD_SET(fds[i], &rfds); + + rc = select(nfds, &rfds, NULL, NULL, NULL); + + if (rc < 0) { + perror("select"); + return 1; + } + + for (i = 0; i < fsel_files; i++) { + if (!FD_ISSET(fds[i], &rfds)) { + printf("_: "); + continue; + } + printf("%zX:", i); + rc = read(fds[i], buf, sizeof(buf)); + if (rc < 0) { + perror("read"); + return 1; + } + printf("%02d ", rc); + } + printf("\n"); + } + return 0; +} diff --git a/example/printcap.c b/example/printcap.c new file mode 100644 index 0000000..4c59582 --- /dev/null +++ b/example/printcap.c @@ -0,0 +1,136 @@ +/* + FUSE: Filesystem in Userspace + Copyright (C) 2017 Nikolaus Rath + + This program can be distributed under the terms of the GNU GPLv2. + See the file GPL2.txt. +*/ + +/** @file + * + * minimal example filesystem that prints out all capabilities + * supported by the kernel and then exits. + * + * Compile with: + * + * gcc -Wall printcap.c `pkg-config fuse3 --cflags --libs` -o printcap + * + * ## Source code ## + * \include printcap.c + */ + +#define FUSE_USE_VERSION 31 + +#include +#include +#include +#include +#include + +struct fuse_session *se; + +// Define a structure to hold capability information +struct cap_info { + uint64_t flag; + const char *name; +}; + +// Define an array of all capabilities +static const struct cap_info capabilities[] = { + {FUSE_CAP_ASYNC_READ, "FUSE_CAP_ASYNC_READ"}, + {FUSE_CAP_POSIX_LOCKS, "FUSE_CAP_POSIX_LOCKS"}, + {FUSE_CAP_ATOMIC_O_TRUNC, "FUSE_CAP_ATOMIC_O_TRUNC"}, + {FUSE_CAP_EXPORT_SUPPORT, "FUSE_CAP_EXPORT_SUPPORT"}, + {FUSE_CAP_DONT_MASK, "FUSE_CAP_DONT_MASK"}, + {FUSE_CAP_SPLICE_MOVE, "FUSE_CAP_SPLICE_MOVE"}, + {FUSE_CAP_SPLICE_READ, "FUSE_CAP_SPLICE_READ"}, + {FUSE_CAP_SPLICE_WRITE, "FUSE_CAP_SPLICE_WRITE"}, + {FUSE_CAP_FLOCK_LOCKS, "FUSE_CAP_FLOCK_LOCKS"}, + {FUSE_CAP_IOCTL_DIR, "FUSE_CAP_IOCTL_DIR"}, + {FUSE_CAP_AUTO_INVAL_DATA, "FUSE_CAP_AUTO_INVAL_DATA"}, + {FUSE_CAP_READDIRPLUS, "FUSE_CAP_READDIRPLUS"}, + {FUSE_CAP_READDIRPLUS_AUTO, "FUSE_CAP_READDIRPLUS_AUTO"}, + {FUSE_CAP_ASYNC_DIO, "FUSE_CAP_ASYNC_DIO"}, + {FUSE_CAP_WRITEBACK_CACHE, "FUSE_CAP_WRITEBACK_CACHE"}, + {FUSE_CAP_NO_OPEN_SUPPORT, "FUSE_CAP_NO_OPEN_SUPPORT"}, + {FUSE_CAP_PARALLEL_DIROPS, "FUSE_CAP_PARALLEL_DIROPS"}, + {FUSE_CAP_POSIX_ACL, "FUSE_CAP_POSIX_ACL"}, + {FUSE_CAP_CACHE_SYMLINKS, "FUSE_CAP_CACHE_SYMLINKS"}, + {FUSE_CAP_NO_OPENDIR_SUPPORT, "FUSE_CAP_NO_OPENDIR_SUPPORT"}, + {FUSE_CAP_EXPLICIT_INVAL_DATA, "FUSE_CAP_EXPLICIT_INVAL_DATA"}, + {FUSE_CAP_EXPIRE_ONLY, "FUSE_CAP_EXPIRE_ONLY"}, + {FUSE_CAP_SETXATTR_EXT, "FUSE_CAP_SETXATTR_EXT"}, + {FUSE_CAP_HANDLE_KILLPRIV, "FUSE_CAP_HANDLE_KILLPRIV"}, + {FUSE_CAP_HANDLE_KILLPRIV_V2, "FUSE_CAP_HANDLE_KILLPRIV_V2"}, + {FUSE_CAP_DIRECT_IO_ALLOW_MMAP, "FUSE_CAP_DIRECT_IO_ALLOW_MMAP"}, + {FUSE_CAP_NO_EXPORT_SUPPORT, "FUSE_CAP_NO_EXPORT_SUPPORT"}, + {FUSE_CAP_PASSTHROUGH, "FUSE_CAP_PASSTHROUGH"}, + // Add any new capabilities here + {0, NULL} // Sentinel to mark the end of the array +}; + +static void print_capabilities(struct fuse_conn_info *conn) +{ + printf("Capabilities:\n"); + for (const struct cap_info *cap = capabilities; cap->name != NULL; cap++) { + if (fuse_get_feature_flag(conn, cap->flag)) { + printf("\t%s\n", cap->name); + } + } +} + +static void pc_init(void *userdata, struct fuse_conn_info *conn) +{ + (void) userdata; + + printf("Protocol version: %d.%d\n", conn->proto_major, + conn->proto_minor); + print_capabilities(conn); + fuse_session_exit(se); +} + + +static const struct fuse_lowlevel_ops pc_oper = { + .init = pc_init, +}; + +int main(int argc, char **argv) +{ + struct fuse_args args = FUSE_ARGS_INIT(argc, argv); + char *mountpoint; + int ret = -1; + + mountpoint = strdup("/tmp/fuse_printcap_XXXXXX"); + if(mkdtemp(mountpoint) == NULL) { + perror("mkdtemp"); + return 1; + } + + printf("FUSE library version %s\n", fuse_pkgversion()); + fuse_lowlevel_version(); + + se = fuse_session_new(&args, &pc_oper, + sizeof(pc_oper), NULL); + if (se == NULL) + goto err_out1; + + if (fuse_set_signal_handlers(se) != 0) + goto err_out2; + + if (fuse_session_mount(se, mountpoint) != 0) + goto err_out3; + + ret = fuse_session_loop(se); + + fuse_session_unmount(se); +err_out3: + fuse_remove_signal_handlers(se); +err_out2: + fuse_session_destroy(se); +err_out1: + rmdir(mountpoint); + free(mountpoint); + fuse_opt_free_args(&args); + + return ret ? 1 : 0; +} diff --git a/example/usdt.bt b/example/usdt.bt new file mode 100644 index 0000000..41cfcdc --- /dev/null +++ b/example/usdt.bt @@ -0,0 +1,19 @@ +#!/usr/bin/env bpftrace + +// To run, do `sudo bpftrace usdt.bt` + +usdt:../build/lib/libfuse3.so:libfuse:request_receive +{ + printf("libfuse:request_receive hit, err=%d\n", arg0); +} + +usdt:../build/lib/libfuse3.so:libfuse:request_process +{ + printf("libfuse:request_process hit, opcode=%u, unique=%u\n", arg0, arg1); +} + +usdt:../build/lib/libfuse3.so:libfuse:request_reply +{ + printf("libfuse:request_reply hit, unique=%lu, len=%u, err=%u, reply_err=%d\n", + arg0, arg1, arg2, arg3); +} diff --git a/include/cuse_lowlevel.h b/include/cuse_lowlevel.h new file mode 100644 index 0000000..fe0b6f2 --- /dev/null +++ b/include/cuse_lowlevel.h @@ -0,0 +1,87 @@ +/* + CUSE: Character device in Userspace + Copyright (C) 2008-2009 SUSE Linux Products GmbH + Copyright (C) 2008-2009 Tejun Heo + + This program can be distributed under the terms of the GNU LGPLv2. + See the file LGPL2.txt. + + Read example/cusexmp.c for usages. +*/ + +#ifndef CUSE_LOWLEVEL_H_ +#define CUSE_LOWLEVEL_H_ + +#ifndef FUSE_USE_VERSION +#define FUSE_USE_VERSION 29 +#endif + +#include "fuse_lowlevel.h" + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define CUSE_UNRESTRICTED_IOCTL (1 << 0) /* use unrestricted ioctl */ + +struct fuse_session; + +struct cuse_info { + unsigned dev_major; + unsigned dev_minor; + unsigned dev_info_argc; + const char **dev_info_argv; + unsigned flags; +}; + +/* + * Most ops behave almost identically to the matching fuse_lowlevel + * ops except that they don't take @ino. + * + * init_done : called after initialization is complete + * read/write : always direct IO, simultaneous operations allowed + * ioctl : might be in unrestricted mode depending on ci->flags + */ +struct cuse_lowlevel_ops { + void (*init) (void *userdata, struct fuse_conn_info *conn); + void (*init_done) (void *userdata); + void (*destroy) (void *userdata); + void (*open) (fuse_req_t req, struct fuse_file_info *fi); + void (*read) (fuse_req_t req, size_t size, off_t off, + struct fuse_file_info *fi); + void (*write) (fuse_req_t req, const char *buf, size_t size, off_t off, + struct fuse_file_info *fi); + void (*flush) (fuse_req_t req, struct fuse_file_info *fi); + void (*release) (fuse_req_t req, struct fuse_file_info *fi); + void (*fsync) (fuse_req_t req, int datasync, struct fuse_file_info *fi); + void (*ioctl) (fuse_req_t req, int cmd, void *arg, + struct fuse_file_info *fi, unsigned int flags, + const void *in_buf, size_t in_bufsz, size_t out_bufsz); + void (*poll) (fuse_req_t req, struct fuse_file_info *fi, + struct fuse_pollhandle *ph); +}; + +struct fuse_session *cuse_lowlevel_new(struct fuse_args *args, + const struct cuse_info *ci, + const struct cuse_lowlevel_ops *clop, + void *userdata); + +struct fuse_session *cuse_lowlevel_setup(int argc, char *argv[], + const struct cuse_info *ci, + const struct cuse_lowlevel_ops *clop, + int *multithreaded, void *userdata); + +void cuse_lowlevel_teardown(struct fuse_session *se); + +int cuse_lowlevel_main(int argc, char *argv[], const struct cuse_info *ci, + const struct cuse_lowlevel_ops *clop, void *userdata); + +#ifdef __cplusplus +} +#endif + +#endif /* CUSE_LOWLEVEL_H_ */ diff --git a/include/fuse.h b/include/fuse.h new file mode 100644 index 0000000..9dfd79e --- /dev/null +++ b/include/fuse.h @@ -0,0 +1,1435 @@ +/* + FUSE: Filesystem in Userspace + Copyright (C) 2001-2007 Miklos Szeredi + + This program can be distributed under the terms of the GNU LGPLv2. + See the file LGPL2.txt. +*/ + +#ifndef FUSE_H_ +#define FUSE_H_ + +/** @file + * + * This file defines the library interface of FUSE + * + * IMPORTANT: you should define FUSE_USE_VERSION before including this header. + */ + +#include "fuse_common.h" + +#include +#include +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* ----------------------------------------------------------- * + * Basic FUSE API * + * ----------------------------------------------------------- */ + +/* Forward declaration */ +struct statx; + +/** Handle for a FUSE filesystem */ +struct fuse; + +/** + * Readdir flags, passed to ->readdir() + */ +enum fuse_readdir_flags { + /** + * "Plus" mode. + * + * The kernel wants to prefill the inode cache during readdir. The + * filesystem may honour this by filling in the attributes and setting + * FUSE_FILL_DIR_FLAGS for the filler function. The filesystem may also + * just ignore this flag completely. + */ + FUSE_READDIR_DEFAULTS = 0, + FUSE_READDIR_PLUS = (1 << 0) +}; + +/** + * Readdir flags, passed to fuse_fill_dir_t callback. + */ +enum fuse_fill_dir_flags { + /** + * "Plus" mode: file attributes are valid + * + * The attributes are used by the kernel to prefill the inode cache + * during a readdir. + * + * It is okay to set FUSE_FILL_DIR_PLUS if FUSE_READDIR_PLUS is not set + * and vice versa. + * + * This does not make libfuse honor the 'st_ino' field. That is + * controlled by the 'use_ino' option instead. + */ + FUSE_FILL_DIR_DEFAULTS = 0, + FUSE_FILL_DIR_PLUS = (1 << 1) +}; + +/** Function to add an entry in a readdir() operation + * + * The *off* parameter can be any non-zero value that enables the + * filesystem to identify the current point in the directory + * stream. It does not need to be the actual physical position. A + * value of zero is reserved to indicate that seeking in directories + * is not supported. + * + * @param buf the buffer passed to the readdir() operation + * @param name the file name of the directory entry + * @param stbuf file attributes, can be NULL + * @param off offset of the next entry or zero + * @param flags fill flags + * @return 1 if buffer is full, zero otherwise + */ +typedef int (*fuse_fill_dir_t) (void *buf, const char *name, + const struct stat *stbuf, off_t off, + enum fuse_fill_dir_flags flags); +/** + * Configuration of the high-level API + * + * This structure is initialized from the arguments passed to + * fuse_new(), and then passed to the file system's init() handler + * which should ensure that the configuration is compatible with the + * file system implementation. + * + * Note: this data structure is ABI sensitive, new options have to be + * appended at the end of the structure + */ +struct fuse_config { + /** + * If `set_gid` is non-zero, the st_gid attribute of each file + * is overwritten with the value of `gid`. + */ + int32_t set_gid; + uint32_t gid; + + /** + * If `set_uid` is non-zero, the st_uid attribute of each file + * is overwritten with the value of `uid`. + */ + int32_t set_uid; + uint32_t uid; + + /** + * If `set_mode` is non-zero, the any permissions bits set in + * `umask` are unset in the st_mode attribute of each file. + */ + int32_t set_mode; + uint32_t umask; + + /** + * The timeout in seconds for which name lookups will be + * cached. + */ + double entry_timeout; + + /** + * The timeout in seconds for which a negative lookup will be + * cached. This means, that if file did not exist (lookup + * returned ENOENT), the lookup will only be redone after the + * timeout, and the file/directory will be assumed to not + * exist until then. A value of zero means that negative + * lookups are not cached. + */ + double negative_timeout; + + /** + * The timeout in seconds for which file/directory attributes + * (as returned by e.g. the `getattr` handler) are cached. + */ + double attr_timeout; + + /** + * Allow requests to be interrupted + */ + int32_t intr; + + /** + * Specify which signal number to send to the filesystem when + * a request is interrupted. The default is hardcoded to + * USR1. + */ + int32_t intr_signal; + + /** + * Normally, FUSE assigns inodes to paths only for as long as + * the kernel is aware of them. With this option inodes are + * instead remembered for at least this many seconds. This + * will require more memory, but may be necessary when using + * applications that make use of inode numbers. + * + * A number of -1 means that inodes will be remembered for the + * entire life-time of the file-system process. + */ + int32_t remember; + + /** + * The default behavior is that if an open file is deleted, + * the file is renamed to a hidden file (.fuse_hiddenXXX), and + * only removed when the file is finally released. This + * relieves the filesystem implementation of having to deal + * with this problem. This option disables the hiding + * behavior, and files are removed immediately in an unlink + * operation (or in a rename operation which overwrites an + * existing file). + * + * It is recommended that you not use the hard_remove + * option. When hard_remove is set, the following libc + * functions fail on unlinked files (returning errno of + * ENOENT): read(2), write(2), fsync(2), close(2), f*xattr(2), + * ftruncate(2), fstat(2), fchmod(2), fchown(2) + */ + int32_t hard_remove; + + /** + * Honor the st_ino field in the functions getattr() and + * fill_dir(). This value is used to fill in the st_ino field + * in the stat(2), lstat(2), fstat(2) functions and the d_ino + * field in the readdir(2) function. The filesystem does not + * have to guarantee uniqueness, however some applications + * rely on this value being unique for the whole filesystem. + * + * Note that this does *not* affect the inode that libfuse + * and the kernel use internally (also called the "nodeid"). + */ + int32_t use_ino; + + /** + * If use_ino option is not given, still try to fill in the + * d_ino field in readdir(2). If the name was previously + * looked up, and is still in the cache, the inode number + * found there will be used. Otherwise it will be set to -1. + * If use_ino option is given, this option is ignored. + */ + int32_t readdir_ino; + + /** + * This option disables the use of page cache (file content cache) + * in the kernel for this filesystem. This has several affects: + * + * 1. Each read(2) or write(2) system call will initiate one + * or more read or write operations, data will not be + * cached in the kernel. + * + * 2. The return value of the read() and write() system calls + * will correspond to the return values of the read and + * write operations. This is useful for example if the + * file size is not known in advance (before reading it). + * + * Internally, enabling this option causes fuse to set the + * `direct_io` field of `struct fuse_file_info` - overwriting + * any value that was put there by the file system. + */ + int32_t direct_io; + + /** + * This option disables flushing the cache of the file + * contents on every open(2). This should only be enabled on + * filesystems where the file data is never changed + * externally (not through the mounted FUSE filesystem). Thus + * it is not suitable for network filesystems and other + * intermediate filesystems. + * + * NOTE: if this option is not specified (and neither + * direct_io) data is still cached after the open(2), so a + * read(2) system call will not always initiate a read + * operation. + * + * Internally, enabling this option causes fuse to set the + * `keep_cache` field of `struct fuse_file_info` - overwriting + * any value that was put there by the file system. + */ + int32_t kernel_cache; + + /** + * This option is an alternative to `kernel_cache`. Instead of + * unconditionally keeping cached data, the cached data is + * invalidated on open(2) if if the modification time or the + * size of the file has changed since it was last opened. + */ + int32_t auto_cache; + + /* + * The timeout in seconds for which file attributes are cached + * for the purpose of checking if auto_cache should flush the + * file data on open. + */ + int32_t ac_attr_timeout_set; + double ac_attr_timeout; + + /** + * If this option is given the file-system handlers for the + * following operations will not receive path information: + * read, write, flush, release, fallocate, fsync, readdir, + * releasedir, fsyncdir, lock, ioctl and poll. + * + * For the truncate, getattr, chmod, chown and utimens + * operations the path will be provided only if the struct + * fuse_file_info argument is NULL. + */ + int32_t nullpath_ok; + + /** + * These 3 options are used by libfuse internally and + * should not be touched. + */ + int32_t show_help; + char *modules; + int32_t debug; + + /** + * `fmask` and `dmask` function the same way as `umask`, but apply + * to files and directories separately. If non-zero, `fmask` and + * `dmask` take precedence over the `umask` setting. + */ + uint32_t fmask; + uint32_t dmask; + + /** + * By default, fuse waits for all pending writes to complete + * and calls the FLUSH operation on close(2) of every fuse fd. + * With this option, wait and FLUSH are not done for read-only + * fuse fd, similar to the behavior of NFS/SMB clients. + */ + int32_t no_rofd_flush; + + /** + * Allow parallel direct-io writes to operate on the same file. + * + * FUSE implementations which do not handle parallel writes on + * same file/region should NOT enable this option at all as it + * might lead to data inconsistencies. + * + * For the FUSE implementations which have their own mechanism + * of cache/data integrity are beneficiaries of this setting as + * it now open doors to parallel writes on the same file (without + * enabling this setting, all direct writes on the same file are + * serialized, resulting in huge data bandwidth loss). + */ + int32_t parallel_direct_writes; + + + /** + * Reserved for future use. + */ + uint32_t flags; + + /** + * Reserved for future use. + */ + uint64_t reserved[48]; +}; + + +/** + * The file system operations: + * + * Most of these should work very similarly to the well known UNIX + * file system operations. A major exception is that instead of + * returning an error in 'errno', the operation should return the + * negated error value (-errno) directly. + * + * All methods are optional, but some are essential for a useful + * filesystem (e.g. getattr). Open, flush, release, fsync, opendir, + * releasedir, fsyncdir, access, create, truncate, lock, init and + * destroy are special purpose methods, without which a full featured + * filesystem can still be implemented. + * + * In general, all methods are expected to perform any necessary + * permission checking. However, a filesystem may delegate this task + * to the kernel by passing the `default_permissions` mount option to + * `fuse_new()`. In this case, methods will only be called if + * the kernel's permission check has succeeded. + * + * Almost all operations take a path which can be of any length. + */ +struct fuse_operations { + /** Get file attributes. + * + * Similar to stat(). The 'st_dev' and 'st_blksize' fields are + * ignored. The 'st_ino' field is ignored except if the 'use_ino' + * mount option is given. In that case it is passed to userspace, + * but libfuse and the kernel will still assign a different + * inode for internal use (called the "nodeid"). + * + * `fi` will always be NULL if the file is not currently open, but + * may also be NULL if the file is open. + */ + int (*getattr) (const char *, struct stat *, struct fuse_file_info *fi); + + /** Read the target of a symbolic link + * + * The buffer should be filled with a null terminated string. The + * buffer size argument includes the space for the terminating + * null character. If the linkname is too long to fit in the + * buffer, it should be truncated. The return value should be 0 + * for success. + */ + int (*readlink) (const char *, char *, size_t); + + /** Create a file node + * + * This is called for creation of all non-directory, non-symlink + * nodes. If the filesystem defines a create() method, then for + * regular files that will be called instead. + */ + int (*mknod) (const char *, mode_t, dev_t); + + /** Create a directory + * + * Note that the mode argument may not have the type specification + * bits set, i.e. S_ISDIR(mode) can be false. To obtain the + * correct directory type bits use mode|S_IFDIR + * */ + int (*mkdir) (const char *, mode_t); + + /** Remove a file */ + int (*unlink) (const char *); + + /** Remove a directory */ + int (*rmdir) (const char *); + + /** Create a symbolic link */ + int (*symlink) (const char *, const char *); + + /** Rename a file + * + * *flags* may be `RENAME_EXCHANGE` or `RENAME_NOREPLACE`. If + * RENAME_NOREPLACE is specified, the filesystem must not + * overwrite *newname* if it exists and return an error + * instead. If `RENAME_EXCHANGE` is specified, the filesystem + * must atomically exchange the two files, i.e. both must + * exist and neither may be deleted. + */ + int (*rename) (const char *, const char *, unsigned int flags); + + /** Create a hard link to a file */ + int (*link) (const char *, const char *); + + /** Change the permission bits of a file + * + * `fi` will always be NULL if the file is not currently open, but + * may also be NULL if the file is open. + */ + int (*chmod) (const char *, mode_t, struct fuse_file_info *fi); + + /** Change the owner and group of a file + * + * `fi` will always be NULL if the file is not currently open, but + * may also be NULL if the file is open. + * + * Unless FUSE_CAP_HANDLE_KILLPRIV is disabled, this method is + * expected to reset the setuid and setgid bits. + */ + int (*chown) (const char *, uid_t, gid_t, struct fuse_file_info *fi); + + /** Change the size of a file + * + * `fi` will always be NULL if the file is not currently open, but + * may also be NULL if the file is open. + * + * Unless FUSE_CAP_HANDLE_KILLPRIV is disabled, this method is + * expected to reset the setuid and setgid bits. + */ + int (*truncate) (const char *, off_t, struct fuse_file_info *fi); + + /** Open a file + * + * Open flags are available in fi->flags. The following rules + * apply. + * + * - Creation (O_CREAT, O_EXCL, O_NOCTTY) flags will be + * filtered out / handled by the kernel. + * + * - Access modes (O_RDONLY, O_WRONLY, O_RDWR, O_EXEC, O_SEARCH) + * should be used by the filesystem to check if the operation is + * permitted. If the ``-o default_permissions`` mount option is + * given, this check is already done by the kernel before calling + * open() and may thus be omitted by the filesystem. + * + * - When writeback caching is enabled, the kernel may send + * read requests even for files opened with O_WRONLY. The + * filesystem should be prepared to handle this. + * + * - When writeback caching is disabled, the filesystem is + * expected to properly handle the O_APPEND flag and ensure + * that each write is appending to the end of the file. + * + * - When writeback caching is enabled, the kernel will + * handle O_APPEND. However, unless all changes to the file + * come through the kernel this will not work reliably. The + * filesystem should thus either ignore the O_APPEND flag + * (and let the kernel handle it), or return an error + * (indicating that reliably O_APPEND is not available). + * + * Filesystem may store an arbitrary file handle (pointer, + * index, etc) in fi->fh, and use this in other all other file + * operations (read, write, flush, release, fsync). + * + * Filesystem may also implement stateless file I/O and not store + * anything in fi->fh. + * + * There are also some flags (direct_io, keep_cache) which the + * filesystem may set in fi, to change the way the file is opened. + * See fuse_file_info structure in for more details. + * + * If this request is answered with an error code of ENOSYS + * and FUSE_CAP_NO_OPEN_SUPPORT is set in + * `fuse_conn_info.capable`, this is treated as success and + * future calls to open will also succeed without being sent + * to the filesystem process. + * + */ + int (*open) (const char *, struct fuse_file_info *); + + /** Read data from an open file + * + * Read should return exactly the number of bytes requested except + * on EOF or error, otherwise the rest of the data will be + * substituted with zeroes. An exception to this is when the + * 'direct_io' mount option is specified, in which case the return + * value of the read system call will reflect the return value of + * this operation. + */ + int (*read) (const char *, char *, size_t, off_t, + struct fuse_file_info *); + + /** Write data to an open file + * + * Write should return exactly the number of bytes requested + * except on error. An exception to this is when the 'direct_io' + * mount option is specified (see read operation). + * + * Unless FUSE_CAP_HANDLE_KILLPRIV is disabled, this method is + * expected to reset the setuid and setgid bits. + */ + int (*write) (const char *, const char *, size_t, off_t, + struct fuse_file_info *); + + /** Get file system statistics + * + * The 'f_favail', 'f_fsid' and 'f_flag' fields are ignored + */ + int (*statfs) (const char *, struct statvfs *); + + /** Possibly flush cached data + * + * BIG NOTE: This is not equivalent to fsync(). It's not a + * request to sync dirty data. + * + * Flush is called on each close() of a file descriptor, as opposed to + * release which is called on the close of the last file descriptor for + * a file. Under Linux, errors returned by flush() will be passed to + * userspace as errors from close(), so flush() is a good place to write + * back any cached dirty data. However, many applications ignore errors + * on close(), and on non-Linux systems, close() may succeed even if flush() + * returns an error. For these reasons, filesystems should not assume + * that errors returned by flush will ever be noticed or even + * delivered. + * + * NOTE: The flush() method may be called more than once for each + * open(). This happens if more than one file descriptor refers to an + * open file handle, e.g. due to dup(), dup2() or fork() calls. It is + * not possible to determine if a flush is final, so each flush should + * be treated equally. Multiple write-flush sequences are relatively + * rare, so this shouldn't be a problem. + * + * Filesystems shouldn't assume that flush will be called at any + * particular point. It may be called more times than expected, or not + * at all. + * + * [close]: http://pubs.opengroup.org/onlinepubs/9699919799/functions/close.html + */ + int (*flush) (const char *, struct fuse_file_info *); + + /** Release an open file + * + * Release is called when there are no more references to an open + * file: all file descriptors are closed and all memory mappings + * are unmapped. + * + * For every open() call there will be exactly one release() call + * with the same flags and file handle. It is possible to + * have a file opened more than once, in which case only the last + * release will mean, that no more reads/writes will happen on the + * file. The return value of release is ignored. + */ + int (*release) (const char *, struct fuse_file_info *); + + /** Synchronize file contents + * + * If the datasync parameter is non-zero, then only the user data + * should be flushed, not the meta data. + */ + int (*fsync) (const char *, int, struct fuse_file_info *); + + /** Set extended attributes */ + int (*setxattr) (const char *, const char *, const char *, size_t, int); + + /** Get extended attributes */ + int (*getxattr) (const char *, const char *, char *, size_t); + + /** List extended attributes */ + int (*listxattr) (const char *, char *, size_t); + + /** Remove extended attributes */ + int (*removexattr) (const char *, const char *); + + /** Open directory + * + * Unless the 'default_permissions' mount option is given, + * this method should check if opendir is permitted for this + * directory. Optionally opendir may also return an arbitrary + * filehandle in the fuse_file_info structure, which will be + * passed to readdir, releasedir and fsyncdir. + */ + int (*opendir) (const char *, struct fuse_file_info *); + + /** Read directory + * + * The filesystem may choose between two modes of operation: + * + * 1) The readdir implementation ignores the offset parameter, and + * passes zero to the filler function's offset. The filler + * function will not return '1' (unless an error happens), so the + * whole directory is read in a single readdir operation. + * + * 2) The readdir implementation keeps track of the offsets of the + * directory entries. It uses the offset parameter and always + * passes non-zero offset to the filler function. When the buffer + * is full (or an error happens) the filler function will return + * '1'. + * + * When FUSE_READDIR_PLUS is not set, only some parameters of the + * fill function (the fuse_fill_dir_t parameter) are actually used: + * The file type (which is part of stat::st_mode) is used. And if + * fuse_config::use_ino is set, the inode (stat::st_ino) is also + * used. The other fields are ignored when FUSE_READDIR_PLUS is not + * set. + */ + int (*readdir) (const char *, void *, fuse_fill_dir_t, off_t, + struct fuse_file_info *, enum fuse_readdir_flags); + + /** Release directory + * + * If the directory has been removed after the call to opendir, the + * path parameter will be NULL. + */ + int (*releasedir) (const char *, struct fuse_file_info *); + + /** Synchronize directory contents + * + * If the directory has been removed after the call to opendir, the + * path parameter will be NULL. + * + * If the datasync parameter is non-zero, then only the user data + * should be flushed, not the meta data + */ + int (*fsyncdir) (const char *, int, struct fuse_file_info *); + + /** + * Initialize filesystem + * + * The return value will passed in the `private_data` field of + * `struct fuse_context` to all file operations, and as a + * parameter to the destroy() method. It overrides the initial + * value provided to fuse_main() / fuse_new(). + */ + void *(*init) (struct fuse_conn_info *conn, + struct fuse_config *cfg); + + /** + * Clean up filesystem + * + * Called on filesystem exit. + */ + void (*destroy) (void *private_data); + + /** + * Check file access permissions + * + * This will be called for the access() system call. If the + * 'default_permissions' mount option is given, this method is not + * called. + * + * This method is not called under Linux kernel versions 2.4.x + */ + int (*access) (const char *, int); + + /** + * Create and open a file + * + * If the file does not exist, first create it with the specified + * mode, and then open it. + * + * If this method is not implemented or under Linux kernel + * versions earlier than 2.6.15, the mknod() and open() methods + * will be called instead. + */ + int (*create) (const char *, mode_t, struct fuse_file_info *); + + /** + * Perform POSIX file locking operation + * + * The cmd argument will be either F_GETLK, F_SETLK or F_SETLKW. + * + * For the meaning of fields in 'struct flock' see the man page + * for fcntl(2). The l_whence field will always be set to + * SEEK_SET. + * + * For checking lock ownership, the 'fuse_file_info->owner' + * argument must be used. + * + * For F_GETLK operation, the library will first check currently + * held locks, and if a conflicting lock is found it will return + * information without calling this method. This ensures, that + * for local locks the l_pid field is correctly filled in. The + * results may not be accurate in case of race conditions and in + * the presence of hard links, but it's unlikely that an + * application would rely on accurate GETLK results in these + * cases. If a conflicting lock is not found, this method will be + * called, and the filesystem may fill out l_pid by a meaningful + * value, or it may leave this field zero. + * + * For F_SETLK and F_SETLKW the l_pid field will be set to the pid + * of the process performing the locking operation. + * + * Note: if this method is not implemented, the kernel will still + * allow file locking to work locally. Hence it is only + * interesting for network filesystems and similar. + */ + int (*lock) (const char *, struct fuse_file_info *, int cmd, + struct flock *); + + /** + * Change the access and modification times of a file with + * nanosecond resolution + * + * This supersedes the old utime() interface. New applications + * should use this. + * + * `fi` will always be NULL if the file is not currently open, but + * may also be NULL if the file is open. + * + * See the utimensat(2) man page for details. + */ + int (*utimens) (const char *, const struct timespec tv[2], + struct fuse_file_info *fi); + + /** + * Map block index within file to block index within device + * + * Note: This makes sense only for block device backed filesystems + * mounted with the 'blkdev' option + */ + int (*bmap) (const char *, size_t blocksize, uint64_t *idx); + +#if FUSE_USE_VERSION < 35 + int (*ioctl) (const char *, int cmd, void *arg, + struct fuse_file_info *, unsigned int flags, void *data); +#else + /** + * Ioctl + * + * flags will have FUSE_IOCTL_COMPAT set for 32bit ioctls in + * 64bit environment. The size and direction of data is + * determined by _IOC_*() decoding of cmd. For _IOC_NONE, + * data will be NULL, for _IOC_WRITE data is out area, for + * _IOC_READ in area and if both are set in/out area. In all + * non-NULL cases, the area is of _IOC_SIZE(cmd) bytes. + * + * If flags has FUSE_IOCTL_DIR then the fuse_file_info refers to a + * directory file handle. + * + * Note : the unsigned long request submitted by the application + * is truncated to 32 bits. + */ + int (*ioctl) (const char *, unsigned int cmd, void *arg, + struct fuse_file_info *, unsigned int flags, void *data); +#endif + + /** + * Poll for IO readiness events + * + * Note: If ph is non-NULL, the client should notify + * when IO readiness events occur by calling + * fuse_notify_poll() with the specified ph. + * + * Regardless of the number of times poll with a non-NULL ph + * is received, single notification is enough to clear all. + * Notifying more times incurs overhead but doesn't harm + * correctness. + * + * The callee is responsible for destroying ph with + * fuse_pollhandle_destroy() when no longer in use. + */ + int (*poll) (const char *, struct fuse_file_info *, + struct fuse_pollhandle *ph, unsigned *reventsp); + + /** Write contents of buffer to an open file + * + * Similar to the write() method, but data is supplied in a + * generic buffer. Use fuse_buf_copy() to transfer data to + * the destination. + * + * Unless FUSE_CAP_HANDLE_KILLPRIV is disabled, this method is + * expected to reset the setuid and setgid bits. + */ + int (*write_buf) (const char *, struct fuse_bufvec *buf, off_t off, + struct fuse_file_info *); + + /** Store data from an open file in a buffer + * + * Similar to the read() method, but data is stored and + * returned in a generic buffer. + * + * No actual copying of data has to take place, the source + * file descriptor may simply be stored in the buffer for + * later data transfer. + * + * The buffer must be allocated dynamically and stored at the + * location pointed to by bufp. If the buffer contains memory + * regions, they too must be allocated using malloc(). The + * allocated memory will be freed by the caller. + */ + int (*read_buf) (const char *, struct fuse_bufvec **bufp, + size_t size, off_t off, struct fuse_file_info *); + /** + * Perform BSD file locking operation + * + * The op argument will be either LOCK_SH, LOCK_EX or LOCK_UN + * + * Nonblocking requests will be indicated by ORing LOCK_NB to + * the above operations + * + * For more information see the flock(2) manual page. + * + * Additionally fi->owner will be set to a value unique to + * this open file. This same value will be supplied to + * ->release() when the file is released. + * + * Note: if this method is not implemented, the kernel will still + * allow file locking to work locally. Hence it is only + * interesting for network filesystems and similar. + */ + int (*flock) (const char *, struct fuse_file_info *, int op); + + /** + * Allocates space for an open file + * + * This function ensures that required space is allocated for specified + * file. If this function returns success then any subsequent write + * request to specified range is guaranteed not to fail because of lack + * of space on the file system media. + */ + int (*fallocate) (const char *, int, off_t, off_t, + struct fuse_file_info *); + + /** + * Copy a range of data from one file to another + * + * Performs an optimized copy between two file descriptors without the + * additional cost of transferring data through the FUSE kernel module + * to user space (glibc) and then back into the FUSE filesystem again. + * + * In case this method is not implemented, applications are expected to + * fall back to a regular file copy. (Some glibc versions did this + * emulation automatically, but the emulation has been removed from all + * glibc release branches.) + */ + ssize_t (*copy_file_range) (const char *path_in, + struct fuse_file_info *fi_in, + off_t offset_in, const char *path_out, + struct fuse_file_info *fi_out, + off_t offset_out, size_t size, int flags); + + /** + * Find next data or hole after the specified offset + */ + off_t (*lseek) (const char *, off_t off, int whence, struct fuse_file_info *); + + /** + * Get extended file attributes. + * + * fi may be NULL. + * + * If path is NULL, then the AT_EMPTY_PATH bit in flags will be + * already set. + */ + int (*statx)(const char *path, int flags, int mask, struct statx *stxbuf, + struct fuse_file_info *fi); +}; + +/** Extra context that may be needed by some filesystems + * + * The uid, gid and pid fields are not filled in case of a writepage + * operation. + */ +struct fuse_context { + /** Pointer to the fuse object */ + struct fuse *fuse; + + /** User ID of the calling process */ + uid_t uid; + + /** Group ID of the calling process */ + gid_t gid; + + /** Process ID of the calling thread */ + pid_t pid; + + /** Private filesystem data */ + void *private_data; + + /** Umask of the calling process */ + mode_t umask; +}; + +/** + * The real main function + * + * Do not call this directly, use fuse_main() + */ +int fuse_main_real_versioned(int argc, char *argv[], + const struct fuse_operations *op, size_t op_size, + struct libfuse_version *version, void *user_data); +static inline int fuse_main_real(int argc, char *argv[], + const struct fuse_operations *op, + size_t op_size, void *user_data) +{ + struct libfuse_version version = { .major = FUSE_MAJOR_VERSION, + .minor = FUSE_MINOR_VERSION, + .hotfix = FUSE_HOTFIX_VERSION, + .padding = 0 }; + + fuse_log(FUSE_LOG_ERR, + "%s is a libfuse internal function, please use fuse_main()\n", + __func__); + + return fuse_main_real_versioned(argc, argv, op, op_size, &version, + user_data); +} + +/** + * Main function of FUSE. + * + * This is for the lazy. This is all that has to be called from the + * main() function. + * + * This function does the following: + * - parses command line options, and handles --help and + * --version + * - installs signal handlers for INT, HUP, TERM and PIPE + * - registers an exit handler to unmount the filesystem on program exit + * - creates a fuse handle + * - registers the operations + * - calls either the single-threaded or the multi-threaded event loop + * + * Most file systems will have to parse some file-system specific + * arguments before calling this function. It is recommended to do + * this with fuse_opt_parse() and a processing function that passes + * through any unknown options (this can also be achieved by just + * passing NULL as the processing function). That way, the remaining + * options can be passed directly to fuse_main(). + * + * fuse_main() accepts all options that can be passed to + * fuse_parse_cmdline(), fuse_new(), or fuse_session_new(). + * + * Option parsing skips argv[0], which is assumed to contain the + * program name. This element must always be present and is used to + * construct a basic ``usage: `` message for the --help + * output. argv[0] may also be set to the empty string. In this case + * the usage message is suppressed. This can be used by file systems + * to print their own usage line first. See hello.c for an example of + * how to do this. + * + * Note: this is currently implemented as a macro. + * + * The following error codes may be returned from fuse_main(): + * 1: Invalid option arguments + * 2: No mount point specified + * 3: FUSE setup failed + * 4: Mounting failed + * 5: Failed to daemonize (detach from session) + * 6: Failed to set up signal handlers + * 7: An error occurred during the life of the file system + * + * @param argc the argument counter passed to the main() function + * @param argv the argument vector passed to the main() function + * @param op the file system operation + * @param private_data Initial value for the `private_data` + * field of `struct fuse_context`. May be overridden by the + * `struct fuse_operations.init` handler. + * @return 0 on success, nonzero on failure + * + * Example usage, see hello.c + */ +static inline int fuse_main_fn(int argc, char *argv[], + const struct fuse_operations *op, + void *user_data) +{ + struct libfuse_version version = { + .major = FUSE_MAJOR_VERSION, + .minor = FUSE_MINOR_VERSION, + .hotfix = FUSE_HOTFIX_VERSION, + .padding = 0 + }; + + return fuse_main_real_versioned(argc, argv, op, sizeof(*(op)), &version, + user_data); +} +#define fuse_main(argc, argv, op, user_data) \ + fuse_main_fn(argc, argv, op, user_data) + +/* ----------------------------------------------------------- * + * More detailed API * + * ----------------------------------------------------------- */ + +/** + * Print available options (high- and low-level) to stdout. This is + * not an exhaustive list, but includes only those options that may be + * of interest to an end-user of a file system. + * + * The function looks at the argument vector only to determine if + * there are additional modules to be loaded (module=foo option), + * and attempts to call their help functions as well. + * + * @param args the argument vector. + */ +void fuse_lib_help(struct fuse_args *args); + +/* Do not call this directly, use fuse_new() instead */ +struct fuse *_fuse_new_30(struct fuse_args *args, + const struct fuse_operations *op, size_t op_size, + struct libfuse_version *version, void *user_data); +struct fuse *_fuse_new_31(struct fuse_args *args, + const struct fuse_operations *op, size_t op_size, + struct libfuse_version *version, void *user_data); + +/** + * Create a new FUSE filesystem. + * + * This function accepts most file-system independent mount options + * (like context, nodev, ro - see mount(8)), as well as the + * FUSE-specific mount options from mount.fuse(8). + * + * If the --help option is specified, the function writes a help text + * to stdout and returns NULL. + * + * Option parsing skips argv[0], which is assumed to contain the + * program name. This element must always be present and is used to + * construct a basic ``usage: `` message for the --help output. If + * argv[0] is set to the empty string, no usage message is included in + * the --help output. + * + * If an unknown option is passed in, an error message is written to + * stderr and the function returns NULL. + * + * @param args argument vector + * @param op the filesystem operations + * @param op_size the size of the fuse_operations structure + * @param private_data Initial value for the `private_data` + * field of `struct fuse_context`. May be overridden by the + * `struct fuse_operations.init` handler. + * @return the created FUSE handle + */ +#if FUSE_USE_VERSION == 30 +static inline struct fuse *fuse_new_fn(struct fuse_args *args, + const struct fuse_operations *op, + size_t op_size, void *user_data) +{ + struct libfuse_version version = { + .major = FUSE_MAJOR_VERSION, + .minor = FUSE_MINOR_VERSION, + .hotfix = FUSE_HOTFIX_VERSION, + .padding = 0 + }; + + return _fuse_new_30(args, op, op_size, &version, user_data); +} +#else /* FUSE_USE_VERSION */ +static inline struct fuse *fuse_new_fn(struct fuse_args *args, + const struct fuse_operations *op, + size_t op_size, void *user_data) +{ + struct libfuse_version version = { + .major = FUSE_MAJOR_VERSION, + .minor = FUSE_MINOR_VERSION, + .hotfix = FUSE_HOTFIX_VERSION, + .padding = 0 + }; + + return _fuse_new_31(args, op, op_size, &version, user_data); +} +#endif +#define fuse_new(args, op, size, data) fuse_new_fn(args, op, size, data) + +/** + * Mount a FUSE file system. + * + * @param mountpoint the mount point path + * @param f the FUSE handle + * + * @return 0 on success, -1 on failure. + **/ +int fuse_mount(struct fuse *f, const char *mountpoint); + +/** + * Unmount a FUSE file system. + * + * See fuse_session_unmount() for additional information. + * + * @param f the FUSE handle + **/ +void fuse_unmount(struct fuse *f); + +/** + * Destroy the FUSE handle. + * + * NOTE: This function does not unmount the filesystem. If this is + * needed, call fuse_unmount() before calling this function. + * + * @param f the FUSE handle + */ +void fuse_destroy(struct fuse *f); + +/** + * FUSE event loop. + * + * Requests from the kernel are processed, and the appropriate + * operations are called. + * + * For a description of the return value and the conditions when the + * event loop exits, refer to the documentation of + * fuse_session_loop(). + * + * @param f the FUSE handle + * @return see fuse_session_loop() + * + * See also: fuse_loop_mt() + */ +int fuse_loop(struct fuse *f); + +/** + * Flag session as terminated + * + * This function will cause any running event loops to exit on + * the next opportunity. + * + * @param f the FUSE handle + */ +void fuse_exit(struct fuse *f); + +#if FUSE_USE_VERSION < 32 +int fuse_loop_mt_31(struct fuse *f, int clone_fd); +#define fuse_loop_mt(f, clone_fd) fuse_loop_mt_31(f, clone_fd) +#elif FUSE_USE_VERSION < FUSE_MAKE_VERSION(3, 12) +int fuse_loop_mt_32(struct fuse *f, struct fuse_loop_config *config); +#define fuse_loop_mt(f, config) fuse_loop_mt_32(f, config) +#else +/** + * FUSE event loop with multiple threads + * + * Requests from the kernel are processed, and the appropriate + * operations are called. Request are processed in parallel by + * distributing them between multiple threads. + * + * For a description of the return value and the conditions when the + * event loop exits, refer to the documentation of + * fuse_session_loop(). + * + * Note: using fuse_loop() instead of fuse_loop_mt() means you are running in + * single-threaded mode, and that you will not have to worry about reentrancy, + * though you will have to worry about recursive lookups. In single-threaded + * mode, FUSE will wait for one callback to return before calling another. + * + * Enabling multiple threads, by using fuse_loop_mt(), will cause FUSE to make + * multiple simultaneous calls into the various callback functions given by your + * fuse_operations record. + * + * If you are using multiple threads, you can enjoy all the parallel execution + * and interactive response benefits of threads, and you get to enjoy all the + * benefits of race conditions and locking bugs, too. Ensure that any code used + * in the callback function of fuse_operations is also thread-safe. + * + * @param f the FUSE handle + * @param config loop configuration, may be NULL and defaults will be used then + * @return see fuse_session_loop() + * + * See also: fuse_loop() + */ +#if (defined(LIBFUSE_BUILT_WITH_VERSIONED_SYMBOLS)) +int fuse_loop_mt(struct fuse *f, struct fuse_loop_config *config); +#else +int fuse_loop_mt_312(struct fuse *f, struct fuse_loop_config *config); +#define fuse_loop_mt(f, config) fuse_loop_mt_312(f, config) +#endif /* LIBFUSE_BUILT_WITH_VERSIONED_SYMBOLS */ +#endif + + +/** + * Get the current context + * + * The context is only valid for the duration of a filesystem + * operation, and thus must not be stored and used later. + * + * @return the context + */ +struct fuse_context *fuse_get_context(void); + +/** + * Get the current supplementary group IDs for the current request + * + * Similar to the getgroups(2) system call, except the return value is + * always the total number of group IDs, even if it is larger than the + * specified size. + * + * The current fuse kernel module in linux (as of 2.6.30) doesn't pass + * the group list to userspace, hence this function needs to parse + * "/proc/$TID/task/$TID/status" to get the group IDs. + * + * This feature may not be supported on all operating systems. In + * such a case this function will return -ENOSYS. + * + * @param size size of given array + * @param list array of group IDs to be filled in + * @return the total number of supplementary group IDs or -errno on failure + */ +int fuse_getgroups(int size, gid_t list[]); + +/** + * Check if the current request has already been interrupted + * + * @return 1 if the request has been interrupted, 0 otherwise + */ +int fuse_interrupted(void); + +/** + * Invalidates cache for the given path. + * + * This calls fuse_lowlevel_notify_inval_inode internally. + * + * @return 0 on successful invalidation, negative error value otherwise. + * This routine may return -ENOENT to indicate that there was + * no entry to be invalidated, e.g., because the path has not + * been seen before or has been forgotten; this should not be + * considered to be an error. + */ +int fuse_invalidate_path(struct fuse *f, const char *path); + +/** + * Start the cleanup thread when using option "remember". + * + * This is done automatically by fuse_loop_mt() + * @param fuse struct fuse pointer for fuse instance + * @return 0 on success and -1 on error + */ +int fuse_start_cleanup_thread(struct fuse *fuse); + +/** + * Stop the cleanup thread when using option "remember". + * + * This is done automatically by fuse_loop_mt() + * @param fuse struct fuse pointer for fuse instance + */ +void fuse_stop_cleanup_thread(struct fuse *fuse); + +/** + * Iterate over cache removing stale entries + * use in conjunction with "-oremember" + * + * NOTE: This is already done for the standard sessions + * + * @param fuse struct fuse pointer for fuse instance + * @return the number of seconds until the next cleanup + */ +int fuse_clean_cache(struct fuse *fuse); + +/* + * Stacking API + */ + +/** + * Fuse filesystem object + * + * This is opaque object represents a filesystem layer + */ +struct fuse_fs; + +/* + * These functions call the relevant filesystem operation, and return + * the result. + * + * If the operation is not defined, they return -ENOSYS, with the + * exception of fuse_fs_open, fuse_fs_release, fuse_fs_opendir, + * fuse_fs_releasedir and fuse_fs_statfs, which return 0. + */ + +int fuse_fs_getattr(struct fuse_fs *fs, const char *path, struct stat *buf, + struct fuse_file_info *fi); +int fuse_fs_rename(struct fuse_fs *fs, const char *oldpath, + const char *newpath, unsigned int flags); +int fuse_fs_unlink(struct fuse_fs *fs, const char *path); +int fuse_fs_rmdir(struct fuse_fs *fs, const char *path); +int fuse_fs_symlink(struct fuse_fs *fs, const char *linkname, + const char *path); +int fuse_fs_link(struct fuse_fs *fs, const char *oldpath, const char *newpath); +int fuse_fs_release(struct fuse_fs *fs, const char *path, + struct fuse_file_info *fi); +int fuse_fs_open(struct fuse_fs *fs, const char *path, + struct fuse_file_info *fi); +int fuse_fs_read(struct fuse_fs *fs, const char *path, char *buf, size_t size, + off_t off, struct fuse_file_info *fi); +int fuse_fs_read_buf(struct fuse_fs *fs, const char *path, + struct fuse_bufvec **bufp, size_t size, off_t off, + struct fuse_file_info *fi); +int fuse_fs_write(struct fuse_fs *fs, const char *path, const char *buf, + size_t size, off_t off, struct fuse_file_info *fi); +int fuse_fs_write_buf(struct fuse_fs *fs, const char *path, + struct fuse_bufvec *buf, off_t off, + struct fuse_file_info *fi); +int fuse_fs_fsync(struct fuse_fs *fs, const char *path, int datasync, + struct fuse_file_info *fi); +int fuse_fs_flush(struct fuse_fs *fs, const char *path, + struct fuse_file_info *fi); +int fuse_fs_statfs(struct fuse_fs *fs, const char *path, struct statvfs *buf); +int fuse_fs_opendir(struct fuse_fs *fs, const char *path, + struct fuse_file_info *fi); +int fuse_fs_readdir(struct fuse_fs *fs, const char *path, void *buf, + fuse_fill_dir_t filler, off_t off, + struct fuse_file_info *fi, enum fuse_readdir_flags flags); +int fuse_fs_fsyncdir(struct fuse_fs *fs, const char *path, int datasync, + struct fuse_file_info *fi); +int fuse_fs_releasedir(struct fuse_fs *fs, const char *path, + struct fuse_file_info *fi); +int fuse_fs_create(struct fuse_fs *fs, const char *path, mode_t mode, + struct fuse_file_info *fi); +int fuse_fs_lock(struct fuse_fs *fs, const char *path, + struct fuse_file_info *fi, int cmd, struct flock *lock); +int fuse_fs_flock(struct fuse_fs *fs, const char *path, + struct fuse_file_info *fi, int op); +int fuse_fs_chmod(struct fuse_fs *fs, const char *path, mode_t mode, + struct fuse_file_info *fi); +int fuse_fs_chown(struct fuse_fs *fs, const char *path, uid_t uid, gid_t gid, + struct fuse_file_info *fi); +int fuse_fs_truncate(struct fuse_fs *fs, const char *path, off_t size, + struct fuse_file_info *fi); +int fuse_fs_utimens(struct fuse_fs *fs, const char *path, + const struct timespec tv[2], struct fuse_file_info *fi); +int fuse_fs_access(struct fuse_fs *fs, const char *path, int mask); +int fuse_fs_readlink(struct fuse_fs *fs, const char *path, char *buf, + size_t len); +int fuse_fs_mknod(struct fuse_fs *fs, const char *path, mode_t mode, + dev_t rdev); +int fuse_fs_mkdir(struct fuse_fs *fs, const char *path, mode_t mode); +int fuse_fs_setxattr(struct fuse_fs *fs, const char *path, const char *name, + const char *value, size_t size, int flags); +int fuse_fs_getxattr(struct fuse_fs *fs, const char *path, const char *name, + char *value, size_t size); +int fuse_fs_listxattr(struct fuse_fs *fs, const char *path, char *list, + size_t size); +int fuse_fs_removexattr(struct fuse_fs *fs, const char *path, + const char *name); +int fuse_fs_bmap(struct fuse_fs *fs, const char *path, size_t blocksize, + uint64_t *idx); +#if FUSE_USE_VERSION < 35 +int fuse_fs_ioctl(struct fuse_fs *fs, const char *path, int cmd, + void *arg, struct fuse_file_info *fi, unsigned int flags, + void *data); +#else +int fuse_fs_ioctl(struct fuse_fs *fs, const char *path, unsigned int cmd, + void *arg, struct fuse_file_info *fi, unsigned int flags, + void *data); +#endif +int fuse_fs_poll(struct fuse_fs *fs, const char *path, + struct fuse_file_info *fi, struct fuse_pollhandle *ph, + unsigned *reventsp); +int fuse_fs_fallocate(struct fuse_fs *fs, const char *path, int mode, + off_t offset, off_t length, struct fuse_file_info *fi); +ssize_t fuse_fs_copy_file_range(struct fuse_fs *fs, const char *path_in, + struct fuse_file_info *fi_in, off_t off_in, + const char *path_out, + struct fuse_file_info *fi_out, off_t off_out, + size_t len, int flags); +off_t fuse_fs_lseek(struct fuse_fs *fs, const char *path, off_t off, int whence, + struct fuse_file_info *fi); +int fuse_fs_statx(struct fuse_fs *fs, const char *path, int flags, int mask, + struct statx *stxbuf, struct fuse_file_info *fi); +void fuse_fs_init(struct fuse_fs *fs, struct fuse_conn_info *conn, + struct fuse_config *cfg); +void fuse_fs_destroy(struct fuse_fs *fs); + +int fuse_notify_poll(struct fuse_pollhandle *ph); + +/** + * Create a new fuse filesystem object + * + * This is usually called from the factory of a fuse module to create + * a new instance of a filesystem. + * + * @param op the filesystem operations + * @param op_size the size of the fuse_operations structure + * @param private_data Initial value for the `private_data` + * field of `struct fuse_context`. May be overridden by the + * `struct fuse_operations.init` handler. + * @return a new filesystem object + */ +struct fuse_fs *fuse_fs_new(const struct fuse_operations *op, size_t op_size, + void *private_data); + +/** + * Factory for creating filesystem objects + * + * The function may use and remove options from 'args' that belong + * to this module. + * + * For now the 'fs' vector always contains exactly one filesystem. + * This is the filesystem which will be below the newly created + * filesystem in the stack. + * + * @param args the command line arguments + * @param fs NULL terminated filesystem object vector + * @return the new filesystem object + */ +typedef struct fuse_fs *(*fuse_module_factory_t)(struct fuse_args *args, + struct fuse_fs *fs[]); +/** + * Register filesystem module + * + * If the "-omodules=*name*_:..." option is present, filesystem + * objects are created and pushed onto the stack with the *factory_* + * function. + * + * @param name_ the name of this filesystem module + * @param factory_ the factory function for this filesystem module + */ +#define FUSE_REGISTER_MODULE(name_, factory_) \ + fuse_module_factory_t fuse_module_ ## name_ ## _factory = factory_ + +/** Get session from fuse object */ +struct fuse_session *fuse_get_session(struct fuse *f); + +/** + * Open a FUSE file descriptor and set up the mount for the given + * mountpoint and flags. + * + * @param mountpoint reference to the mount in the file system + * @param options mount options + * @return the FUSE file descriptor or -1 upon error + */ +int fuse_open_channel(const char *mountpoint, const char *options); + +#ifdef __cplusplus +} +#endif + +#endif /* FUSE_H_ */ diff --git a/include/fuse_common.h b/include/fuse_common.h new file mode 100644 index 0000000..b693459 --- /dev/null +++ b/include/fuse_common.h @@ -0,0 +1,1169 @@ +/* FUSE: Filesystem in Userspace + Copyright (C) 2001-2007 Miklos Szeredi + + This program can be distributed under the terms of the GNU LGPLv2. + See the file LGPL2.txt. +*/ + +/** @file */ + +#if !defined(FUSE_H_) && !defined(FUSE_LOWLEVEL_H_) +#error "Never include directly; use or instead." +#endif + +#ifndef FUSE_COMMON_H_ +#define FUSE_COMMON_H_ + +#ifdef HAVE_LIBFUSE_PRIVATE_CONFIG_H +#include "fuse_config.h" +#endif + +#include "libfuse_config.h" + +#include "fuse_opt.h" +#include "fuse_log.h" +#include +#include +#include +#include + +#define FUSE_MAKE_VERSION(maj, min) ((maj) * 100 + (min)) +#define FUSE_VERSION FUSE_MAKE_VERSION(FUSE_MAJOR_VERSION, FUSE_MINOR_VERSION) + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Information about an open file. + * + * File Handles are created by the open, opendir, and create methods and closed + * by the release and releasedir methods. Multiple file handles may be + * concurrently open for the same file. Generally, a client will create one + * file handle per file descriptor, though in some cases multiple file + * descriptors can share a single file handle. + * + * Note: This data structure is ABI sensitive, new parameters have to be + * added within padding/padding2 bits and always below existing + * parameters. + */ +struct fuse_file_info { + /** Open flags. Available in open(), release() and create() */ + int32_t flags; + + /** In case of a write operation indicates if this was caused + by a delayed write from the page cache. If so, then the + context's pid, uid, and gid fields will not be valid, and + the *fh* value may not match the *fh* value that would + have been sent with the corresponding individual write + requests if write caching had been disabled. */ + uint32_t writepage : 1; + + /** Can be filled in by open/create, to use direct I/O on this file. */ + uint32_t direct_io : 1; + + /** Can be filled in by open and opendir. It signals the kernel that any + currently cached data (ie., data that the filesystem provided the + last time the file/directory was open) need not be invalidated when + the file/directory is closed. */ + uint32_t keep_cache : 1; + + /** Indicates a flush operation. Set in flush operation, also + maybe set in highlevel lock operation and lowlevel release + operation. */ + uint32_t flush : 1; + + /** Can be filled in by open, to indicate that the file is not + seekable. */ + uint32_t nonseekable : 1; + + /* Indicates that flock locks for this file should be + released. If set, lock_owner shall contain a valid value. + May only be set in ->release(). */ + uint32_t flock_release : 1; + + /** Can be filled in by opendir. It signals the kernel to + enable caching of entries returned by readdir(). Has no + effect when set in other contexts (in particular it does + nothing when set by open()). */ + uint32_t cache_readdir : 1; + + /** Can be filled in by open, to indicate that flush is not needed + on close. */ + uint32_t noflush : 1; + + /** Can be filled by open/create, to allow parallel direct writes on this + file */ + uint32_t parallel_direct_writes : 1; + + /** + * Set in open, create and write when KILLPRIV_V2 was negotiated and + * the application issuing the syscall lacks CAP_FSETID; the + * filesystem's own privileges do not matter. The filesystem then + * has to remove setuid/setgid bits. + */ + uint32_t kill_suidgid : 1; + + /** Padding. Reserved for future use*/ + uint32_t padding : 22; + uint32_t padding2 : 32; + uint32_t padding3 : 32; + + /** File handle id. May be filled in by filesystem in create, + * open, and opendir(). Available in most other file operations on the + * same file handle. */ + uint64_t fh; + + /** Lock owner id. Available in locking operations and flush */ + uint64_t lock_owner; + + /** Requested poll events. Available in ->poll. Only set on kernels + which support it. If unsupported, this field is set to zero. */ + uint32_t poll_events; + + /** Passthrough backing file id. May be filled in by filesystem in + * create and open. It is used to create a passthrough connection + * between FUSE file and backing file. */ + int32_t backing_id; + + /** struct fuse_file_info api and abi flags */ + uint64_t compat_flags; + + uint64_t reserved[2]; +}; + +/** + * Configuration parameters passed to fuse_session_loop_mt() and + * fuse_loop_mt(). + * For FUSE API versions less than 312, use a public struct + * fuse_loop_config in applications and struct fuse_loop_config_v1 + * is used in library (i.e., libfuse.so). These two structs are binary + * compatible in earlier API versions and can be linked. + * Deprecated and replaced by a newer private struct in FUSE API + * version 312 (FUSE_MAKE_VERSION(3, 12)). + */ +#if FUSE_USE_VERSION < FUSE_MAKE_VERSION(3, 12) +struct fuse_loop_config_v1; /* forward declaration */ +struct fuse_loop_config { +#else +struct fuse_loop_config_v1 { +#endif + /** + * whether to use separate device fds for each thread + * (may increase performance) + */ + int clone_fd; + + /** + * The maximum number of available worker threads before they + * start to get deleted when they become idle. If not + * specified, the default is 10. + * + * Adjusting this has performance implications; a very small number + * of threads in the pool will cause a lot of thread creation and + * deletion overhead and performance may suffer. When set to 0, a new + * thread will be created to service every operation. + */ + unsigned int max_idle_threads; +}; + + +/************************************************************************** + * Capability bits for 'fuse_conn_info.capable' and 'fuse_conn_info.want' * + **************************************************************************/ + +/** + * Indicates that the filesystem supports asynchronous read requests. + * + * If this capability is not requested/available, the kernel will + * ensure that there is at most one pending read request per + * file-handle at any time, and will attempt to order read requests by + * increasing offset. + * + * This feature is enabled by default when supported by the kernel. + */ +#define FUSE_CAP_ASYNC_READ (1UL << 0) + +/** + * Indicates that the filesystem supports "remote" locking. + * + * This feature is enabled by default when supported by the kernel, + * and if getlk() and setlk() handlers are implemented. + */ +#define FUSE_CAP_POSIX_LOCKS (1UL << 1) + +/** + * Indicates that the filesystem supports the O_TRUNC open flag. If + * disabled, and an application specifies O_TRUNC, fuse first calls + * truncate() and then open() with O_TRUNC filtered out. + * + * This feature is enabled by default when supported by the kernel. + */ +#define FUSE_CAP_ATOMIC_O_TRUNC (1UL << 3) + +/** + * Indicates that the filesystem supports lookups of "." and "..". + * + * When this flag is set, the filesystem must be prepared to receive requests + * for invalid inodes (i.e., for which a FORGET request was received or + * which have been used in a previous instance of the filesystem daemon) and + * must not reuse node-ids (even when setting generation numbers). + * + * This feature is disabled by default. + */ +#define FUSE_CAP_EXPORT_SUPPORT (1UL << 4) + +/** + * Indicates that the kernel should not apply the umask to the + * file mode on create operations. + * + * This feature is disabled by default. + */ +#define FUSE_CAP_DONT_MASK (1UL << 6) + +/** + * Indicates that libfuse should try to use splice() when writing to + * the fuse device. This may improve performance. + * + * This feature is disabled by default. + */ +#define FUSE_CAP_SPLICE_WRITE (1UL << 7) + +/** + * Indicates that libfuse should try to move pages instead of copying when + * writing to / reading from the fuse device. This may improve performance. + * + * This feature is disabled by default. + */ +#define FUSE_CAP_SPLICE_MOVE (1UL << 8) + +/** + * Indicates that libfuse should try to use splice() when reading from + * the fuse device. This may improve performance. + * + * This feature is enabled by default when supported by the kernel and + * if the filesystem implements a write_buf() handler. + */ +#define FUSE_CAP_SPLICE_READ (1UL << 9) + +/** + * If set, the calls to flock(2) will be emulated using POSIX locks and must + * then be handled by the filesystem's setlock() handler. + * + * If not set, flock(2) calls will be handled by the FUSE kernel module + * internally (so any access that does not go through the kernel cannot be taken + * into account). + * + * This feature is enabled by default when supported by the kernel and + * if the filesystem implements a flock() handler. + */ +#define FUSE_CAP_FLOCK_LOCKS (1UL << 10) + +/** + * Indicates that the filesystem supports ioctl's on directories. + * + * This feature is enabled by default when supported by the kernel. + */ +#define FUSE_CAP_IOCTL_DIR (1UL << 11) + +/** + * Traditionally, while a file is open the FUSE kernel module only + * asks the filesystem for an update of the file's attributes when a + * client attempts to read beyond EOF. This is unsuitable for + * e.g. network filesystems, where the file contents may change + * without the kernel knowing about it. + * + * If this flag is set, FUSE will check the validity of the attributes + * on every read. If the attributes are no longer valid (i.e., if the + * *attr_timeout* passed to fuse_reply_attr() or set in `struct + * fuse_entry_param` has passed), it will first issue a `getattr` + * request. If the new mtime differs from the previous value, any + * cached file *contents* will be invalidated as well. + * + * This flag should always be set when available. If all file changes + * go through the kernel, *attr_timeout* should be set to a very large + * number to avoid unnecessary getattr() calls. + * + * This feature is enabled by default when supported by the kernel. + */ +#define FUSE_CAP_AUTO_INVAL_DATA (1UL << 12) + +/** + * Indicates that the filesystem supports readdirplus. + * + * This feature is enabled by default when supported by the kernel and if the + * filesystem implements a readdirplus() handler. + */ +#define FUSE_CAP_READDIRPLUS (1UL << 13) + +/** + * Indicates that the filesystem supports adaptive readdirplus. + * + * If FUSE_CAP_READDIRPLUS is not set, this flag has no effect. + * + * If FUSE_CAP_READDIRPLUS is set and this flag is not set, the kernel + * will always issue readdirplus() requests to retrieve directory + * contents. + * + * If FUSE_CAP_READDIRPLUS is set and this flag is set, the kernel + * will issue both readdir() and readdirplus() requests, depending on + * how much information is expected to be required. + * + * As of Linux 4.20, the algorithm is as follows: when userspace + * starts to read directory entries, issue a READDIRPLUS request to + * the filesystem. If any entry attributes have been looked up by the + * time userspace requests the next batch of entries continue with + * READDIRPLUS, otherwise switch to plain READDIR. This will reasult + * in eg plain "ls" triggering READDIRPLUS first then READDIR after + * that because it doesn't do lookups. "ls -l" should result in all + * READDIRPLUS, except if dentries are already cached. + * + * This feature is enabled by default when supported by the kernel and + * if the filesystem implements both a readdirplus() and a readdir() + * handler. + */ +#define FUSE_CAP_READDIRPLUS_AUTO (1UL << 14) + +/** + * Indicates that the filesystem supports asynchronous direct I/O submission. + * + * If this capability is not requested/available, the kernel will ensure that + * there is at most one pending read and one pending write request per direct + * I/O file-handle at any time. + * + * This feature is enabled by default when supported by the kernel. + */ +#define FUSE_CAP_ASYNC_DIO (1UL << 15) + +/** + * Indicates that writeback caching should be enabled. This means that + * individual write request may be buffered and merged in the kernel + * before they are send to the filesystem. + * + * This feature is disabled by default. + */ +#define FUSE_CAP_WRITEBACK_CACHE (1UL << 16) + +/** + * Indicates support for zero-message opens. If this flag is set in + * the `capable` field of the `fuse_conn_info` structure, then the + * filesystem may return `ENOSYS` from the open() handler to indicate + * success. Further attempts to open files will be handled in the + * kernel. (If this flag is not set, returning ENOSYS will be treated + * as an error and signaled to the caller). + * + * Setting this flag in the `want` field enables this behavior automatically + * within libfuse for low level API users. If non-low level users wish to have + * this behavior you must return `ENOSYS` from the open() handler on supporting + * kernels. + */ +#define FUSE_CAP_NO_OPEN_SUPPORT (1UL << 17) + +/** + * Indicates support for parallel directory operations. If this flag + * is unset, the FUSE kernel module will ensure that lookup() and + * readdir() requests are never issued concurrently for the same + * directory. + */ +#define FUSE_CAP_PARALLEL_DIROPS (1UL << 18) + +/** + * Indicates support for POSIX ACLs. + * + * If this feature is enabled, the kernel will cache and have + * responsibility for enforcing ACLs. ACL will be stored as xattrs and + * passed to userspace, which is responsible for updating the ACLs in + * the filesystem, keeping the file mode in sync with the ACL, and + * ensuring inheritance of default ACLs when new filesystem nodes are + * created. Note that this requires that the file system is able to + * parse and interpret the xattr representation of ACLs. + * + * Enabling this feature implicitly turns on the + * ``default_permissions`` mount option (even if it was not passed to + * mount(2)). + * + * This feature is disabled by default. + */ +#define FUSE_CAP_POSIX_ACL (1UL << 19) + +/** + * Indicates that the filesystem is responsible for unsetting + * setuid and setgid bits when a file is written, truncated, or + * its owner is changed. + * + * This feature is disabled by default. + */ +#define FUSE_CAP_HANDLE_KILLPRIV (1UL << 20) + +/** + * Indicates that the filesystem is responsible for unsetting + * setuid and setgid bit and additionally cap (stored as xattr) when a + * file is written, truncated, or its owner is changed. + * Upon write/truncate suid/sgid is only killed if caller + * does not have CAP_FSETID. Additionally upon + * write/truncate sgid is killed only if file has group + * execute permission. (Same as Linux VFS behavior). + * KILLPRIV_V2 requires handling of + * - FUSE_OPEN_KILL_SUIDGID (set in struct fuse_create_in::open_flags) + * - FATTR_KILL_SUIDGID (set in struct fuse_setattr_in::valid) + * - FUSE_WRITE_KILL_SUIDGID (set in struct fuse_write_in::write_flags) + * + * This feature is disabled by default. + */ +#define FUSE_CAP_HANDLE_KILLPRIV_V2 (1UL << 21) + +/** + * Indicates that the kernel supports caching symlinks in its page cache. + * + * When this feature is enabled, symlink targets are saved in the page cache. + * You can invalidate a cached link by calling: + * `fuse_lowlevel_notify_inval_inode(se, ino, 0, 0);` + * + * This feature is disabled by default. + * If the kernel supports it (>= 4.20), you can enable this feature by + * setting this flag in the `want` field of the `fuse_conn_info` structure. + */ +#define FUSE_CAP_CACHE_SYMLINKS (1UL << 23) + +/** + * Indicates support for zero-message opendirs. If this flag is set in + * the `capable` field of the `fuse_conn_info` structure, then the filesystem + * may return `ENOSYS` from the opendir() handler to indicate success. Further + * opendir and releasedir messages will be handled in the kernel. (If this + * flag is not set, returning ENOSYS will be treated as an error and signalled + * to the caller.) + * + * Setting this flag in the `want` field enables this behavior automatically + * within libfuse for low level API users. If non-low level users with to have + * this behavior you must return `ENOSYS` from the opendir() handler on + * supporting kernels. + */ +#define FUSE_CAP_NO_OPENDIR_SUPPORT (1UL << 24) + +/** + * Indicates support for invalidating cached pages only on explicit request. + * + * If this flag is set in the `capable` field of the `fuse_conn_info` structure, + * then the FUSE kernel module supports invalidating cached pages only on + * explicit request by the filesystem through fuse_lowlevel_notify_inval_inode() + * or fuse_invalidate_path(). + * + * By setting this flag in the `want` field of the `fuse_conn_info` structure, + * the filesystem is responsible for invalidating cached pages through explicit + * requests to the kernel. + * + * Note that setting this flag does not prevent the cached pages from being + * flushed by OS itself and/or through user actions. + * + * Note that if both FUSE_CAP_EXPLICIT_INVAL_DATA and FUSE_CAP_AUTO_INVAL_DATA + * are set in the `capable` field of the `fuse_conn_info` structure then + * FUSE_CAP_AUTO_INVAL_DATA takes precedence. + * + * This feature is disabled by default. + */ +#define FUSE_CAP_EXPLICIT_INVAL_DATA (1UL << 25) + +/** + * Indicates support that dentries can be expired. + * + * Expiring dentries, instead of invalidating them, makes a difference for + * overmounted dentries, where plain invalidation would detach all submounts + * before dropping the dentry from the cache. If only expiry is set on the + * dentry, then any overmounts are left alone and until ->d_revalidate() + * is called. + * + * Note: ->d_revalidate() is not called for the case of following a submount, + * so invalidation will only be triggered for the non-overmounted case. + * The dentry could also be mounted in a different mount instance, in which case + * any submounts will still be detached. + */ +#define FUSE_CAP_EXPIRE_ONLY (1UL << 26) + +/** + * Indicates that an extended 'struct fuse_setxattr' is used by the kernel + * side - extra_flags are passed, which are used (as of now by acl) processing. + * For example FUSE_SETXATTR_ACL_KILL_SGID might be set. + */ +#define FUSE_CAP_SETXATTR_EXT (1UL << 27) + +/** + * Files opened with FUSE_DIRECT_IO do not support MAP_SHARED mmap. This restriction + * is relaxed through FUSE_CAP_DIRECT_IO_RELAX (kernel flag: FUSE_DIRECT_IO_RELAX). + * MAP_SHARED is disabled by default for FUSE_DIRECT_IO, as this flag can be used to + * ensure coherency between mount points (or network clients) and with kernel page + * cache as enforced by mmap that cannot be guaranteed anymore. + */ +#define FUSE_CAP_DIRECT_IO_ALLOW_MMAP (1UL << 28) + +/** + * Indicates support for passthrough mode access for read/write operations. + * + * If this flag is set in the `capable` field of the `fuse_conn_info` + * structure, then the FUSE kernel module supports redirecting read/write + * operations to the backing file instead of letting them to be handled + * by the FUSE daemon. + * + * This feature is disabled by default. + */ +#define FUSE_CAP_PASSTHROUGH (1UL << 29) + +/** + * Indicates that the file system cannot handle NFS export + * + * If this flag is set NFS export and name_to_handle_at + * is not going to work at all and will fail with EOPNOTSUPP. + */ +#define FUSE_CAP_NO_EXPORT_SUPPORT (1UL << 30) + +/** + * Indicates support for io-uring between fuse-server and fuse-client + */ +#define FUSE_CAP_OVER_IO_URING (1UL << 31) + +/** + * Ioctl flags + * + * FUSE_IOCTL_COMPAT: 32bit compat ioctl on 64bit machine + * FUSE_IOCTL_UNRESTRICTED: not restricted to well-formed ioctls, retry allowed + * FUSE_IOCTL_RETRY: retry with new iovecs + * FUSE_IOCTL_DIR: is a directory + * + * FUSE_IOCTL_MAX_IOV: maximum of in_iovecs + out_iovecs + */ +#define FUSE_IOCTL_COMPAT (1 << 0) +#define FUSE_IOCTL_UNRESTRICTED (1 << 1) +#define FUSE_IOCTL_RETRY (1 << 2) +#define FUSE_IOCTL_DIR (1 << 4) + +#define FUSE_IOCTL_MAX_IOV 256 + +/** + * Connection information, passed to the ->init() method + * + * Some of the elements are read-write, these can be changed to + * indicate the value requested by the filesystem. The requested + * value must usually be smaller than the indicated value. + * + * Note: The `capable` and `want` fields are limited to 32 bits for + * ABI compatibility. For full 64-bit capability support, use the + * `capable_ext` and `want_ext` fields instead. + */ +struct fuse_conn_info { + /** + * Major version of the protocol (read-only) + */ + uint32_t proto_major; + + /** + * Minor version of the protocol (read-only) + */ + uint32_t proto_minor; + + /** + * Maximum size of the write buffer + */ + uint32_t max_write; + + /** + * Maximum size of read requests. A value of zero indicates no + * limit. However, even if the filesystem does not specify a + * limit, the maximum size of read requests will still be + * limited by the kernel. + * + * NOTE: For the time being, the maximum size of read requests + * must be set both here *and* passed to fuse_session_new() + * using the ``-o max_read=`` mount option. At some point + * in the future, specifying the mount option will no longer + * be necessary. + */ + uint32_t max_read; + + /** + * Maximum readahead + */ + uint32_t max_readahead; + + /** + * Capability flags that the kernel supports (read-only) + * + * Deprecated left over for ABI compatibility, use capable_ext + */ + uint32_t capable; + + /** + * Capability flags that the filesystem wants to enable. + * + * libfuse attempts to initialize this field with + * reasonable default values before calling the init() handler. + * + * Deprecated left over for ABI compatibility. + * Use want_ext with the helper functions + * fuse_set_feature_flag() / fuse_unset_feature_flag() + */ + uint32_t want; + + /** + * Maximum number of pending "background" requests. A + * background request is any type of request for which the + * total number is not limited by other means. As of kernel + * 4.8, only two types of requests fall into this category: + * + * 1. Read-ahead requests + * 2. Asynchronous direct I/O requests + * + * Read-ahead requests are generated (if max_readahead is + * non-zero) by the kernel to preemptively fill its caches + * when it anticipates that userspace will soon read more + * data. + * + * Asynchronous direct I/O requests are generated if + * FUSE_CAP_ASYNC_DIO is enabled and userspace submits a large + * direct I/O request. In this case the kernel will internally + * split it up into multiple smaller requests and submit them + * to the filesystem concurrently. + * + * Note that the following requests are *not* background + * requests: writeback requests (limited by the kernel's + * flusher algorithm), regular (i.e., synchronous and + * buffered) userspace read/write requests (limited to one per + * thread), asynchronous read requests (Linux's io_submit(2) + * call actually blocks, so these are also limited to one per + * thread). + */ + uint32_t max_background; + + /** + * Kernel congestion threshold parameter. If the number of pending + * background requests exceeds this number, the FUSE kernel module will + * mark the filesystem as "congested". This instructs the kernel to + * expect that queued requests will take some time to complete, and to + * adjust its algorithms accordingly (e.g. by putting a waiting thread + * to sleep instead of using a busy-loop). + */ + uint32_t congestion_threshold; + + /** + * When FUSE_CAP_WRITEBACK_CACHE is enabled, the kernel is responsible + * for updating mtime and ctime when write requests are received. The + * updated values are passed to the filesystem with setattr() requests. + * However, if the filesystem does not support the full resolution of + * the kernel timestamps (nanoseconds), the mtime and ctime values used + * by kernel and filesystem will differ (and result in an apparent + * change of times after a cache flush). + * + * To prevent this problem, this variable can be used to inform the + * kernel about the timestamp granularity supported by the file-system. + * The value should be power of 10. The default is 1, i.e. full + * nano-second resolution. Filesystems supporting only second resolution + * should set this to 1000000000. + */ + uint32_t time_gran; + + /** + * When FUSE_CAP_PASSTHROUGH is enabled, this is the maximum allowed + * stacking depth of the backing files. In current kernel, the maximum + * allowed stack depth if FILESYSTEM_MAX_STACK_DEPTH (2), which includes + * the FUSE passthrough layer, so the maximum stacking depth for backing + * files is 1. + * + * The default is FUSE_BACKING_STACKED_UNDER (0), meaning that the + * backing files cannot be on a stacked filesystem, but another stacked + * filesystem can be stacked over this FUSE passthrough filesystem. + * + * Set this to FUSE_BACKING_STACKED_OVER (1) if backing files may be on + * a stacked filesystem, such as overlayfs or another FUSE passthrough. + * In this configuration, another stacked filesystem cannot be stacked + * over this FUSE passthrough filesystem. + */ +#define FUSE_BACKING_STACKED_UNDER (0) +#define FUSE_BACKING_STACKED_OVER (1) + uint32_t max_backing_stack_depth; + + /** + * Disable FUSE_INTERRUPT requests. + * + * Enable `no_interrupt` option to: + * 1) Avoid unnecessary locking operations and list operations, + * 2) Return ENOSYS for the reply of FUSE_INTERRUPT request to + * inform the kernel not to send the FUSE_INTERRUPT request. + */ + uint32_t no_interrupt : 1; + + /* reserved bits for future use */ + uint32_t padding : 31; + + /** + * Extended capability flags that the kernel supports (read-only) + * This field provides full 64-bit capability support. + */ + uint64_t capable_ext; + + /** + * Extended capability flags that the filesystem wants to enable. + * This field provides full 64-bit capability support. + * + * Don't set this field directly, but use the helper functions + * fuse_set_feature_flag() / fuse_unset_feature_flag() + * + */ + uint64_t want_ext; + + /** + * Request timeout (in seconds). If the request is not answered by + * this timeout, the connection will be aborted by the kernel. + */ + uint16_t request_timeout; + + /** + * For future use. + */ + uint16_t reserved[31]; +}; + +struct fuse_session; +struct fuse_pollhandle; +struct fuse_conn_info_opts; + +/** + * This function parses several command-line options that can be used + * to override elements of struct fuse_conn_info. The pointer returned + * by this function should be passed to the + * fuse_apply_conn_info_opts() method by the file system's init() + * handler. + * + * Before using this function, think twice if you really want these + * parameters to be adjustable from the command line. In most cases, + * they should be determined by the file system internally. + * + * The following options are recognized: + * + * -o max_write=N sets conn->max_write + * -o max_readahead=N sets conn->max_readahead + * -o max_background=N sets conn->max_background + * -o congestion_threshold=N sets conn->congestion_threshold + * -o async_read sets FUSE_CAP_ASYNC_READ in conn->want + * -o sync_read unsets FUSE_CAP_ASYNC_READ in conn->want + * -o atomic_o_trunc sets FUSE_CAP_ATOMIC_O_TRUNC in conn->want + * -o no_remote_lock Equivalent to -o no_remote_flock,no_remote_posix_lock + * -o no_remote_flock Unsets FUSE_CAP_FLOCK_LOCKS in conn->want + * -o no_remote_posix_lock Unsets FUSE_CAP_POSIX_LOCKS in conn->want + * -o [no_]splice_write (un-)sets FUSE_CAP_SPLICE_WRITE in conn->want + * -o [no_]splice_move (un-)sets FUSE_CAP_SPLICE_MOVE in conn->want + * -o [no_]splice_read (un-)sets FUSE_CAP_SPLICE_READ in conn->want + * -o [no_]auto_inval_data (un-)sets FUSE_CAP_AUTO_INVAL_DATA in conn->want + * -o readdirplus=no unsets FUSE_CAP_READDIRPLUS in conn->want + * -o readdirplus=yes sets FUSE_CAP_READDIRPLUS and unsets + * FUSE_CAP_READDIRPLUS_AUTO in conn->want + * -o readdirplus=auto sets FUSE_CAP_READDIRPLUS and + * FUSE_CAP_READDIRPLUS_AUTO in conn->want + * -o [no_]async_dio (un-)sets FUSE_CAP_ASYNC_DIO in conn->want + * -o [no_]writeback_cache (un-)sets FUSE_CAP_WRITEBACK_CACHE in conn->want + * -o time_gran=N sets conn->time_gran + * + * Known options will be removed from *args*, unknown options will be + * passed through unchanged. + * + * @param args argument vector (input+output) + * @return parsed options + **/ +struct fuse_conn_info_opts* fuse_parse_conn_info_opts(struct fuse_args *args); + +/** + * This function applies the (parsed) parameters in *opts* to the + * *conn* pointer. It may modify the following fields: wants, + * max_write, max_readahead, congestion_threshold, max_background, + * time_gran. A field is only set (or unset) if the corresponding + * option has been explicitly set. + */ +void fuse_apply_conn_info_opts(struct fuse_conn_info_opts *opts, + struct fuse_conn_info *conn); + +/** + * Go into the background + * + * @param foreground if true, stay in the foreground + * @return 0 on success, -1 on failure + */ +int fuse_daemonize(int foreground); + +/** + * Get the version of the library + * + * @return the version + */ +int fuse_version(void); + +/** + * Get the full package version string of the library + * + * @return the package version + */ +const char *fuse_pkgversion(void); + +/** + * Destroy poll handle + * + * @param ph the poll handle + */ +void fuse_pollhandle_destroy(struct fuse_pollhandle *ph); + +/* ----------------------------------------------------------- * + * Data buffer * + * ----------------------------------------------------------- */ + +/** + * Buffer flags + */ +enum fuse_buf_flags { + /** + * Buffer contains a file descriptor + * + * If this flag is set, the .fd field is valid, otherwise the + * .mem fields is valid. + */ + FUSE_BUF_IS_FD = (1 << 1), + + /** + * Seek on the file descriptor + * + * If this flag is set then the .pos field is valid and is + * used to seek to the given offset before performing + * operation on file descriptor. + */ + FUSE_BUF_FD_SEEK = (1 << 2), + + /** + * Retry operation on file descriptor + * + * If this flag is set then retry operation on file descriptor + * until .size bytes have been copied or an error or EOF is + * detected. + */ + FUSE_BUF_FD_RETRY = (1 << 3) +}; + +/** + * Buffer copy flags + */ +enum fuse_buf_copy_flags { + /** + * Don't use splice(2) + * + * Always fall back to using read and write instead of + * splice(2) to copy data from one file descriptor to another. + * + * If this flag is not set, then only fall back if splice is + * unavailable. + */ + FUSE_BUF_NO_SPLICE = (1 << 1), + + /** + * Force splice + * + * Always use splice(2) to copy data from one file descriptor + * to another. If splice is not available, return -EINVAL. + */ + FUSE_BUF_FORCE_SPLICE = (1 << 2), + + /** + * Try to move data with splice. + * + * If splice is used, try to move pages from the source to the + * destination instead of copying. See documentation of + * SPLICE_F_MOVE in splice(2) man page. + */ + FUSE_BUF_SPLICE_MOVE = (1 << 3), + + /** + * Don't block on the pipe when copying data with splice + * + * Makes the operations on the pipe non-blocking (if the pipe + * is full or empty). See SPLICE_F_NONBLOCK in the splice(2) + * man page. + */ + FUSE_BUF_SPLICE_NONBLOCK= (1 << 4) +}; + +/** + * Single data buffer + * + * Generic data buffer for I/O, extended attributes, etc... Data may + * be supplied as a memory pointer or as a file descriptor + */ +struct fuse_buf { + /** + * Size of data in bytes + */ + size_t size; + + /** + * Buffer flags + */ + enum fuse_buf_flags flags; + + /** + * Memory pointer + * + * Used unless FUSE_BUF_IS_FD flag is set. + */ + void *mem; + + /** + * File descriptor + * + * Used if FUSE_BUF_IS_FD flag is set. + */ + int fd; + + /** + * File position + * + * Used if FUSE_BUF_FD_SEEK flag is set. + */ + off_t pos; + + /** + * Size of memory pointer + * + * Used only if mem was internally allocated. + * Not used if mem was user-provided. + */ + size_t mem_size; +}; + +/** + * Data buffer vector + * + * An array of data buffers, each containing a memory pointer or a + * file descriptor. + * + * Allocate dynamically to add more than one buffer. + */ +struct fuse_bufvec { + /** + * Number of buffers in the array + */ + size_t count; + + /** + * Index of current buffer within the array + */ + size_t idx; + + /** + * Current offset within the current buffer + */ + size_t off; + + /** + * Array of buffers + */ + struct fuse_buf buf[1]; +}; + +/** + * libfuse version a file system was compiled with. Should be filled in from + * defines in 'libfuse_config.h' + */ +struct libfuse_version +{ + uint32_t major; + uint32_t minor; + uint32_t hotfix; + uint32_t padding; +}; + +/* Initialize bufvec with a single buffer of given size */ +#define FUSE_BUFVEC_INIT(size__) \ + ((struct fuse_bufvec) { \ + /* .count= */ 1, \ + /* .idx = */ 0, \ + /* .off = */ 0, \ + /* .buf = */ { /* [0] = */ { \ + /* .size = */ (size__), \ + /* .flags = */ (enum fuse_buf_flags) 0, \ + /* .mem = */ NULL, \ + /* .fd = */ -1, \ + /* .pos = */ 0, \ + /* .mem_size = */ 0, \ + } } \ + } ) + +/** + * Get total size of data in a fuse buffer vector + * + * @param bufv buffer vector + * @return size of data + */ +size_t fuse_buf_size(const struct fuse_bufvec *bufv); + +/** + * Copy data from one buffer vector to another + * + * @param dst destination buffer vector + * @param src source buffer vector + * @param flags flags controlling the copy + * @return actual number of bytes copied or -errno on error + */ +ssize_t fuse_buf_copy(struct fuse_bufvec *dst, struct fuse_bufvec *src, + enum fuse_buf_copy_flags flags); + +/* ----------------------------------------------------------- * + * Signal handling * + * ----------------------------------------------------------- */ + +/** + * Exit session on HUP, TERM and INT signals and ignore PIPE signal + * + * Stores session in a global variable. May only be called once per + * process until fuse_remove_signal_handlers() is called. + * + * Once either of the POSIX signals arrives, the signal handler calls + * fuse_session_exit(). + * + * @param se the session to exit + * @return 0 on success, -1 on failure + * + * See also: + * fuse_remove_signal_handlers() + */ +int fuse_set_signal_handlers(struct fuse_session *se); + +/** + * Print a stack backtrace diagnostic on critical signals () + * + * Stores session in a global variable. May only be called once per + * process until fuse_remove_signal_handlers() is called. + * + * Once either of the POSIX signals arrives, the signal handler calls + * fuse_session_exit(). + * + * @param se the session to exit + * @return 0 on success, -1 on failure + * + * See also: + * fuse_remove_signal_handlers() + */ +int fuse_set_fail_signal_handlers(struct fuse_session *se); + +/** + * Restore default signal handlers + * + * Resets global session. After this fuse_set_signal_handlers() may + * be called again. + * + * @param se the same session as given in fuse_set_signal_handlers() + * + * See also: + * fuse_set_signal_handlers() + */ +void fuse_remove_signal_handlers(struct fuse_session *se); + +/** + * Config operations. + * These APIs are introduced in version 312 (FUSE_MAKE_VERSION(3, 12)). + * Using them in earlier versions will result in errors. + */ +#if FUSE_USE_VERSION >= FUSE_MAKE_VERSION(3, 12) +/** + * Create and set default config for fuse_session_loop_mt and fuse_loop_mt. + * + * @return anonymous config struct + */ +struct fuse_loop_config *fuse_loop_cfg_create(void); + +/** + * Free the config data structure + */ +void fuse_loop_cfg_destroy(struct fuse_loop_config *config); + +/** + * fuse_loop_config setter to set the number of max idle threads. + */ +void fuse_loop_cfg_set_idle_threads(struct fuse_loop_config *config, + unsigned int value); + +/** + * fuse_loop_config setter to set the number of max threads. + */ +void fuse_loop_cfg_set_max_threads(struct fuse_loop_config *config, + unsigned int value); + +/** + * fuse_loop_config setter to enable the clone_fd feature + */ +void fuse_loop_cfg_set_clone_fd(struct fuse_loop_config *config, + unsigned int value); + +/** + * Convert old config to more recernt fuse_loop_config2 + * + * @param config current config2 type + * @param v1_conf older config1 type (below FUSE API 312) + */ +void fuse_loop_cfg_convert(struct fuse_loop_config *config, + struct fuse_loop_config_v1 *v1_conf); +#endif + +/** + * Set a feature flag in the want_ext field of fuse_conn_info. + * + * @param conn connection information + * @param flag feature flag to be set + * @return true if the flag was set, false if the flag is not supported + */ +bool fuse_set_feature_flag(struct fuse_conn_info *conn, uint64_t flag); + +/** + * Unset a feature flag in the want_ext field of fuse_conn_info. + * + * @param conn connection information + * @param flag feature flag to be unset + */ +void fuse_unset_feature_flag(struct fuse_conn_info *conn, uint64_t flag); + +/** + * Get the value of a feature flag in the want_ext field of fuse_conn_info. + * + * @param conn connection information + * @param flag feature flag to be checked + * @return true if the flag is set, false otherwise + */ +bool fuse_get_feature_flag(struct fuse_conn_info *conn, uint64_t flag); + +/* + * DO NOT USE: Not part of public API, for internal test use only. + * The function signature or any use of it is not guaranteeed to + * remain stable. And neither are results of what this function does. + */ +int fuse_convert_to_conn_want_ext(struct fuse_conn_info *conn); + + + +/* ----------------------------------------------------------- * + * Compatibility stuff * + * ----------------------------------------------------------- */ + +#if !defined(FUSE_USE_VERSION) || FUSE_USE_VERSION < 30 +# error only API version 30 or greater is supported +#endif + +#ifdef __cplusplus +} +#endif + + +/* + * This interface uses 64 bit off_t. + * + * On 32bit systems please add -D_FILE_OFFSET_BITS=64 to your compile flags! + */ + +#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L) +_Static_assert(sizeof(off_t) == 8, "fuse: off_t must be 64bit"); +#else +struct _fuse_off_t_must_be_64bit_dummy_struct \ + { unsigned _fuse_off_t_must_be_64bit:((sizeof(off_t) == 8) ? 1 : -1); }; +#endif + +#endif /* FUSE_COMMON_H_ */ diff --git a/include/fuse_kernel.h b/include/fuse_kernel.h new file mode 100644 index 0000000..94621f6 --- /dev/null +++ b/include/fuse_kernel.h @@ -0,0 +1,1303 @@ +/* SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-2-Clause) */ +/* + This file defines the kernel interface of FUSE + Copyright (C) 2001-2008 Miklos Szeredi + + This program can be distributed under the terms of the GNU GPL. + See the file COPYING. + + This -- and only this -- header file may also be distributed under + the terms of the BSD Licence as follows: + + Copyright (C) 2001-2007 Miklos Szeredi. All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. +*/ + +/* + * This file defines the kernel interface of FUSE + * + * Protocol changelog: + * + * 7.1: + * - add the following messages: + * FUSE_SETATTR, FUSE_SYMLINK, FUSE_MKNOD, FUSE_MKDIR, FUSE_UNLINK, + * FUSE_RMDIR, FUSE_RENAME, FUSE_LINK, FUSE_OPEN, FUSE_READ, FUSE_WRITE, + * FUSE_RELEASE, FUSE_FSYNC, FUSE_FLUSH, FUSE_SETXATTR, FUSE_GETXATTR, + * FUSE_LISTXATTR, FUSE_REMOVEXATTR, FUSE_OPENDIR, FUSE_READDIR, + * FUSE_RELEASEDIR + * - add padding to messages to accommodate 32-bit servers on 64-bit kernels + * + * 7.2: + * - add FOPEN_DIRECT_IO and FOPEN_KEEP_CACHE flags + * - add FUSE_FSYNCDIR message + * + * 7.3: + * - add FUSE_ACCESS message + * - add FUSE_CREATE message + * - add filehandle to fuse_setattr_in + * + * 7.4: + * - add frsize to fuse_kstatfs + * - clean up request size limit checking + * + * 7.5: + * - add flags and max_write to fuse_init_out + * + * 7.6: + * - add max_readahead to fuse_init_in and fuse_init_out + * + * 7.7: + * - add FUSE_INTERRUPT message + * - add POSIX file lock support + * + * 7.8: + * - add lock_owner and flags fields to fuse_release_in + * - add FUSE_BMAP message + * - add FUSE_DESTROY message + * + * 7.9: + * - new fuse_getattr_in input argument of GETATTR + * - add lk_flags in fuse_lk_in + * - add lock_owner field to fuse_setattr_in, fuse_read_in and fuse_write_in + * - add blksize field to fuse_attr + * - add file flags field to fuse_read_in and fuse_write_in + * - Add ATIME_NOW and MTIME_NOW flags to fuse_setattr_in + * + * 7.10 + * - add nonseekable open flag + * + * 7.11 + * - add IOCTL message + * - add unsolicited notification support + * - add POLL message and NOTIFY_POLL notification + * + * 7.12 + * - add umask flag to input argument of create, mknod and mkdir + * - add notification messages for invalidation of inodes and + * directory entries + * + * 7.13 + * - make max number of background requests and congestion threshold + * tunables + * + * 7.14 + * - add splice support to fuse device + * + * 7.15 + * - add store notify + * - add retrieve notify + * + * 7.16 + * - add BATCH_FORGET request + * - FUSE_IOCTL_UNRESTRICTED shall now return with array of 'struct + * fuse_ioctl_iovec' instead of ambiguous 'struct iovec' + * - add FUSE_IOCTL_32BIT flag + * + * 7.17 + * - add FUSE_FLOCK_LOCKS and FUSE_RELEASE_FLOCK_UNLOCK + * + * 7.18 + * - add FUSE_IOCTL_DIR flag + * - add FUSE_NOTIFY_DELETE + * + * 7.19 + * - add FUSE_FALLOCATE + * + * 7.20 + * - add FUSE_AUTO_INVAL_DATA + * + * 7.21 + * - add FUSE_READDIRPLUS + * - send the requested events in POLL request + * + * 7.22 + * - add FUSE_ASYNC_DIO + * + * 7.23 + * - add FUSE_WRITEBACK_CACHE + * - add time_gran to fuse_init_out + * - add reserved space to fuse_init_out + * - add FATTR_CTIME + * - add ctime and ctimensec to fuse_setattr_in + * - add FUSE_RENAME2 request + * - add FUSE_NO_OPEN_SUPPORT flag + * + * 7.24 + * - add FUSE_LSEEK for SEEK_HOLE and SEEK_DATA support + * + * 7.25 + * - add FUSE_PARALLEL_DIROPS + * + * 7.26 + * - add FUSE_HANDLE_KILLPRIV + * - add FUSE_POSIX_ACL + * + * 7.27 + * - add FUSE_ABORT_ERROR + * + * 7.28 + * - add FUSE_COPY_FILE_RANGE + * - add FOPEN_CACHE_DIR + * - add FUSE_MAX_PAGES, add max_pages to init_out + * - add FUSE_CACHE_SYMLINKS + * + * 7.29 + * - add FUSE_NO_OPENDIR_SUPPORT flag + * + * 7.30 + * - add FUSE_EXPLICIT_INVAL_DATA + * - add FUSE_IOCTL_COMPAT_X32 + * + * 7.31 + * - add FUSE_WRITE_KILL_PRIV flag + * - add FUSE_SETUPMAPPING and FUSE_REMOVEMAPPING + * - add map_alignment to fuse_init_out, add FUSE_MAP_ALIGNMENT flag + * + * 7.32 + * - add flags to fuse_attr, add FUSE_ATTR_SUBMOUNT, add FUSE_SUBMOUNTS + * + * 7.33 + * - add FUSE_HANDLE_KILLPRIV_V2, FUSE_WRITE_KILL_SUIDGID, FATTR_KILL_SUIDGID + * - add FUSE_OPEN_KILL_SUIDGID + * - extend fuse_setxattr_in, add FUSE_SETXATTR_EXT + * - add FUSE_SETXATTR_ACL_KILL_SGID + * + * 7.34 + * - add FUSE_SYNCFS + * + * 7.35 + * - add FOPEN_NOFLUSH + * + * 7.36 + * - extend fuse_init_in with reserved fields, add FUSE_INIT_EXT init flag + * - add flags2 to fuse_init_in and fuse_init_out + * - add FUSE_SECURITY_CTX init flag + * - add security context to create, mkdir, symlink, and mknod requests + * - add FUSE_HAS_INODE_DAX, FUSE_ATTR_DAX + * + * 7.37 + * - add FUSE_TMPFILE + * + * 7.38 + * - add FUSE_EXPIRE_ONLY flag to fuse_notify_inval_entry + * - add FOPEN_PARALLEL_DIRECT_WRITES + * - add total_extlen to fuse_in_header + * - add FUSE_MAX_NR_SECCTX + * - add extension header + * - add FUSE_EXT_GROUPS + * - add FUSE_CREATE_SUPP_GROUP + * - add FUSE_HAS_EXPIRE_ONLY + * + * 7.39 + * - add FUSE_DIRECT_IO_ALLOW_MMAP + * - add FUSE_STATX and related structures + * + * 7.40 + * - add max_stack_depth to fuse_init_out, add FUSE_PASSTHROUGH init flag + * - add backing_id to fuse_open_out, add FOPEN_PASSTHROUGH open flag + * - add FUSE_NO_EXPORT_SUPPORT init flag + * - add FUSE_NOTIFY_RESEND, add FUSE_HAS_RESEND init flag + * + * 7.41 + * - add FUSE_ALLOW_IDMAP + * 7.42 + * - Add FUSE_OVER_IO_URING and all other io-uring related flags and data + * structures: + * - struct fuse_uring_ent_in_out + * - struct fuse_uring_req_header + * - struct fuse_uring_cmd_req + * - FUSE_URING_IN_OUT_HEADER_SZ + * - FUSE_URING_OP_IN_OUT_SZ + * - enum fuse_uring_cmd + * + * 7.43 + * - add FUSE_REQUEST_TIMEOUT + * + * 7.44 + * - add FUSE_NOTIFY_INC_EPOCH + * + * 7.45 + * - add FUSE_COPY_FILE_RANGE_64 + * - add struct fuse_copy_file_range_out + */ + +#ifndef _LINUX_FUSE_H +#define _LINUX_FUSE_H + +#ifdef __KERNEL__ +#include +#else +#include +#endif + +/* + * Version negotiation: + * + * Both the kernel and userspace send the version they support in the + * INIT request and reply respectively. + * + * If the major versions match then both shall use the smallest + * of the two minor versions for communication. + * + * If the kernel supports a larger major version, then userspace shall + * reply with the major version it supports, ignore the rest of the + * INIT message and expect a new INIT message from the kernel with a + * matching major version. + * + * If the library supports a larger major version, then it shall fall + * back to the major protocol version sent by the kernel for + * communication and reply with that major version (and an arbitrary + * supported minor version). + */ + +/** Version number of this interface */ +#define FUSE_KERNEL_VERSION 7 + +/** Minor version number of this interface */ +#define FUSE_KERNEL_MINOR_VERSION 45 + +/** The node ID of the root inode */ +#define FUSE_ROOT_ID 1 + +/* Make sure all structures are padded to 64bit boundary, so 32bit + userspace works under 64bit kernels */ + +struct fuse_attr { + uint64_t ino; + uint64_t size; + uint64_t blocks; + uint64_t atime; + uint64_t mtime; + uint64_t ctime; + uint32_t atimensec; + uint32_t mtimensec; + uint32_t ctimensec; + uint32_t mode; + uint32_t nlink; + uint32_t uid; + uint32_t gid; + uint32_t rdev; + uint32_t blksize; + uint32_t flags; +}; + +/* + * The following structures are bit-for-bit compatible with the statx(2) ABI in + * Linux. + */ +struct fuse_sx_time { + int64_t tv_sec; + uint32_t tv_nsec; + int32_t __reserved; +}; + +struct fuse_statx { + uint32_t mask; + uint32_t blksize; + uint64_t attributes; + uint32_t nlink; + uint32_t uid; + uint32_t gid; + uint16_t mode; + uint16_t __spare0[1]; + uint64_t ino; + uint64_t size; + uint64_t blocks; + uint64_t attributes_mask; + struct fuse_sx_time atime; + struct fuse_sx_time btime; + struct fuse_sx_time ctime; + struct fuse_sx_time mtime; + uint32_t rdev_major; + uint32_t rdev_minor; + uint32_t dev_major; + uint32_t dev_minor; + uint64_t __spare2[14]; +}; + +struct fuse_kstatfs { + uint64_t blocks; + uint64_t bfree; + uint64_t bavail; + uint64_t files; + uint64_t ffree; + uint32_t bsize; + uint32_t namelen; + uint32_t frsize; + uint32_t padding; + uint32_t spare[6]; +}; + +struct fuse_file_lock { + uint64_t start; + uint64_t end; + uint32_t type; + uint32_t pid; /* tgid */ +}; + +/** + * Bitmasks for fuse_setattr_in.valid + */ +#define FATTR_MODE (1 << 0) +#define FATTR_UID (1 << 1) +#define FATTR_GID (1 << 2) +#define FATTR_SIZE (1 << 3) +#define FATTR_ATIME (1 << 4) +#define FATTR_MTIME (1 << 5) +#define FATTR_FH (1 << 6) +#define FATTR_ATIME_NOW (1 << 7) +#define FATTR_MTIME_NOW (1 << 8) +#define FATTR_LOCKOWNER (1 << 9) +#define FATTR_CTIME (1 << 10) +#define FATTR_KILL_SUIDGID (1 << 11) + +/** + * Flags returned by the OPEN request + * + * FOPEN_DIRECT_IO: bypass page cache for this open file + * FOPEN_KEEP_CACHE: don't invalidate the data cache on open + * FOPEN_NONSEEKABLE: the file is not seekable + * FOPEN_CACHE_DIR: allow caching this directory + * FOPEN_STREAM: the file is stream-like (no file position at all) + * FOPEN_NOFLUSH: don't flush data cache on close (unless FUSE_WRITEBACK_CACHE) + * FOPEN_PARALLEL_DIRECT_WRITES: Allow concurrent direct writes on the same inode + * FOPEN_PASSTHROUGH: passthrough read/write io for this open file + */ +#define FOPEN_DIRECT_IO (1 << 0) +#define FOPEN_KEEP_CACHE (1 << 1) +#define FOPEN_NONSEEKABLE (1 << 2) +#define FOPEN_CACHE_DIR (1 << 3) +#define FOPEN_STREAM (1 << 4) +#define FOPEN_NOFLUSH (1 << 5) +#define FOPEN_PARALLEL_DIRECT_WRITES (1 << 6) +#define FOPEN_PASSTHROUGH (1 << 7) + +/** + * INIT request/reply flags + * + * FUSE_ASYNC_READ: asynchronous read requests + * FUSE_POSIX_LOCKS: remote locking for POSIX file locks + * FUSE_FILE_OPS: kernel sends file handle for fstat, etc... (not yet supported) + * FUSE_ATOMIC_O_TRUNC: handles the O_TRUNC open flag in the filesystem + * FUSE_EXPORT_SUPPORT: filesystem handles lookups of "." and ".." + * FUSE_BIG_WRITES: filesystem can handle write size larger than 4kB + * FUSE_DONT_MASK: don't apply umask to file mode on create operations + * FUSE_SPLICE_WRITE: kernel supports splice write on the device + * FUSE_SPLICE_MOVE: kernel supports splice move on the device + * FUSE_SPLICE_READ: kernel supports splice read on the device + * FUSE_FLOCK_LOCKS: remote locking for BSD style file locks + * FUSE_HAS_IOCTL_DIR: kernel supports ioctl on directories + * FUSE_AUTO_INVAL_DATA: automatically invalidate cached pages + * FUSE_DO_READDIRPLUS: do READDIRPLUS (READDIR+LOOKUP in one) + * FUSE_READDIRPLUS_AUTO: adaptive readdirplus + * FUSE_ASYNC_DIO: asynchronous direct I/O submission + * FUSE_WRITEBACK_CACHE: use writeback cache for buffered writes + * FUSE_NO_OPEN_SUPPORT: kernel supports zero-message opens + * FUSE_PARALLEL_DIROPS: allow parallel lookups and readdir + * FUSE_HANDLE_KILLPRIV: fs handles killing suid/sgid/cap on write/chown/trunc + * FUSE_POSIX_ACL: filesystem supports posix acls + * FUSE_ABORT_ERROR: reading the device after abort returns ECONNABORTED + * FUSE_MAX_PAGES: init_out.max_pages contains the max number of req pages + * FUSE_CACHE_SYMLINKS: cache READLINK responses + * FUSE_NO_OPENDIR_SUPPORT: kernel supports zero-message opendir + * FUSE_EXPLICIT_INVAL_DATA: only invalidate cached pages on explicit request + * FUSE_MAP_ALIGNMENT: init_out.map_alignment contains log2(byte alignment) for + * foffset and moffset fields in struct + * fuse_setupmapping_out and fuse_removemapping_one. + * FUSE_SUBMOUNTS: kernel supports auto-mounting directory submounts + * FUSE_HANDLE_KILLPRIV_V2: fs kills suid/sgid/cap on write/chown/trunc. + * Upon write/truncate suid/sgid is only killed if caller + * does not have CAP_FSETID. Additionally upon + * write/truncate sgid is killed only if file has group + * execute permission. (Same as Linux VFS behavior). + * FUSE_SETXATTR_EXT: Server supports extended struct fuse_setxattr_in + * FUSE_INIT_EXT: extended fuse_init_in request + * FUSE_INIT_RESERVED: reserved, do not use + * FUSE_SECURITY_CTX: add security context to create, mkdir, symlink, and + * mknod + * FUSE_HAS_INODE_DAX: use per inode DAX + * FUSE_CREATE_SUPP_GROUP: add supplementary group info to create, mkdir, + * symlink and mknod (single group that matches parent) + * FUSE_HAS_EXPIRE_ONLY: kernel supports expiry-only entry invalidation + * FUSE_DIRECT_IO_ALLOW_MMAP: allow shared mmap in FOPEN_DIRECT_IO mode. + * FUSE_NO_EXPORT_SUPPORT: explicitly disable export support + * FUSE_HAS_RESEND: kernel supports resending pending requests, and the high bit + * of the request ID indicates resend requests + * FUSE_ALLOW_IDMAP: allow creation of idmapped mounts + * FUSE_OVER_IO_URING: Indicate that client supports io-uring + * FUSE_REQUEST_TIMEOUT: kernel supports timing out requests. + * init_out.request_timeout contains the timeout (in secs) + */ +#define FUSE_ASYNC_READ (1 << 0) +#define FUSE_POSIX_LOCKS (1 << 1) +#define FUSE_FILE_OPS (1 << 2) +#define FUSE_ATOMIC_O_TRUNC (1 << 3) +#define FUSE_EXPORT_SUPPORT (1 << 4) +#define FUSE_BIG_WRITES (1 << 5) +#define FUSE_DONT_MASK (1 << 6) +#define FUSE_SPLICE_WRITE (1 << 7) +#define FUSE_SPLICE_MOVE (1 << 8) +#define FUSE_SPLICE_READ (1 << 9) +#define FUSE_FLOCK_LOCKS (1 << 10) +#define FUSE_HAS_IOCTL_DIR (1 << 11) +#define FUSE_AUTO_INVAL_DATA (1 << 12) +#define FUSE_DO_READDIRPLUS (1 << 13) +#define FUSE_READDIRPLUS_AUTO (1 << 14) +#define FUSE_ASYNC_DIO (1 << 15) +#define FUSE_WRITEBACK_CACHE (1 << 16) +#define FUSE_NO_OPEN_SUPPORT (1 << 17) +#define FUSE_PARALLEL_DIROPS (1 << 18) +#define FUSE_HANDLE_KILLPRIV (1 << 19) +#define FUSE_POSIX_ACL (1 << 20) +#define FUSE_ABORT_ERROR (1 << 21) +#define FUSE_MAX_PAGES (1 << 22) +#define FUSE_CACHE_SYMLINKS (1 << 23) +#define FUSE_NO_OPENDIR_SUPPORT (1 << 24) +#define FUSE_EXPLICIT_INVAL_DATA (1 << 25) +#define FUSE_MAP_ALIGNMENT (1 << 26) +#define FUSE_SUBMOUNTS (1 << 27) +#define FUSE_HANDLE_KILLPRIV_V2 (1 << 28) +#define FUSE_SETXATTR_EXT (1 << 29) +#define FUSE_INIT_EXT (1 << 30) +#define FUSE_INIT_RESERVED (1 << 31) +/* bits 32..63 get shifted down 32 bits into the flags2 field */ +#define FUSE_SECURITY_CTX (1ULL << 32) +#define FUSE_HAS_INODE_DAX (1ULL << 33) +#define FUSE_CREATE_SUPP_GROUP (1ULL << 34) +#define FUSE_HAS_EXPIRE_ONLY (1ULL << 35) +#define FUSE_DIRECT_IO_ALLOW_MMAP (1ULL << 36) +#define FUSE_PASSTHROUGH (1ULL << 37) +#define FUSE_NO_EXPORT_SUPPORT (1ULL << 38) +#define FUSE_HAS_RESEND (1ULL << 39) +/* Obsolete alias for FUSE_DIRECT_IO_ALLOW_MMAP */ +#define FUSE_DIRECT_IO_RELAX FUSE_DIRECT_IO_ALLOW_MMAP +#define FUSE_ALLOW_IDMAP (1ULL << 40) +#define FUSE_OVER_IO_URING (1ULL << 41) +#define FUSE_REQUEST_TIMEOUT (1ULL << 42) + +/** + * CUSE INIT request/reply flags + * + * CUSE_UNRESTRICTED_IOCTL: use unrestricted ioctl + */ +#define CUSE_UNRESTRICTED_IOCTL (1 << 0) + +/** + * Release flags + */ +#define FUSE_RELEASE_FLUSH (1 << 0) +#define FUSE_RELEASE_FLOCK_UNLOCK (1 << 1) + +/** + * Getattr flags + */ +#define FUSE_GETATTR_FH (1 << 0) + +/** + * Lock flags + */ +#define FUSE_LK_FLOCK (1 << 0) + +/** + * WRITE flags + * + * FUSE_WRITE_CACHE: delayed write from page cache, file handle is guessed + * FUSE_WRITE_LOCKOWNER: lock_owner field is valid + * FUSE_WRITE_KILL_SUIDGID: kill suid and sgid bits + */ +#define FUSE_WRITE_CACHE (1 << 0) +#define FUSE_WRITE_LOCKOWNER (1 << 1) +#define FUSE_WRITE_KILL_SUIDGID (1 << 2) + +/* Obsolete alias; this flag implies killing suid/sgid only. */ +#define FUSE_WRITE_KILL_PRIV FUSE_WRITE_KILL_SUIDGID + +/** + * Read flags + */ +#define FUSE_READ_LOCKOWNER (1 << 1) + +/** + * Ioctl flags + * + * FUSE_IOCTL_COMPAT: 32bit compat ioctl on 64bit machine + * FUSE_IOCTL_UNRESTRICTED: not restricted to well-formed ioctls, retry allowed + * FUSE_IOCTL_RETRY: retry with new iovecs + * FUSE_IOCTL_32BIT: 32bit ioctl + * FUSE_IOCTL_DIR: is a directory + * FUSE_IOCTL_COMPAT_X32: x32 compat ioctl on 64bit machine (64bit time_t) + * + * FUSE_IOCTL_MAX_IOV: maximum of in_iovecs + out_iovecs + */ +#define FUSE_IOCTL_COMPAT (1 << 0) +#define FUSE_IOCTL_UNRESTRICTED (1 << 1) +#define FUSE_IOCTL_RETRY (1 << 2) +#define FUSE_IOCTL_32BIT (1 << 3) +#define FUSE_IOCTL_DIR (1 << 4) +#define FUSE_IOCTL_COMPAT_X32 (1 << 5) + +#define FUSE_IOCTL_MAX_IOV 256 + +/** + * Poll flags + * + * FUSE_POLL_SCHEDULE_NOTIFY: request poll notify + */ +#define FUSE_POLL_SCHEDULE_NOTIFY (1 << 0) + +/** + * Fsync flags + * + * FUSE_FSYNC_FDATASYNC: Sync data only, not metadata + */ +#define FUSE_FSYNC_FDATASYNC (1 << 0) + +/** + * fuse_attr flags + * + * FUSE_ATTR_SUBMOUNT: Object is a submount root + * FUSE_ATTR_DAX: Enable DAX for this file in per inode DAX mode + */ +#define FUSE_ATTR_SUBMOUNT (1 << 0) +#define FUSE_ATTR_DAX (1 << 1) + +/** + * Open flags + * FUSE_OPEN_KILL_SUIDGID: Kill suid and sgid if executable + */ +#define FUSE_OPEN_KILL_SUIDGID (1 << 0) + +/** + * setxattr flags + * FUSE_SETXATTR_ACL_KILL_SGID: Clear SGID when system.posix_acl_access is set + */ +#define FUSE_SETXATTR_ACL_KILL_SGID (1 << 0) + +/** + * notify_inval_entry flags + * FUSE_EXPIRE_ONLY + */ +#define FUSE_EXPIRE_ONLY (1 << 0) + +/** + * extension type + * FUSE_MAX_NR_SECCTX: maximum value of &fuse_secctx_header.nr_secctx + * FUSE_EXT_GROUPS: &fuse_supp_groups extension + */ +enum fuse_ext_type { + /* Types 0..31 are reserved for fuse_secctx_header */ + FUSE_MAX_NR_SECCTX = 31, + FUSE_EXT_GROUPS = 32, +}; + +enum fuse_opcode { + FUSE_LOOKUP = 1, + FUSE_FORGET = 2, /* no reply */ + FUSE_GETATTR = 3, + FUSE_SETATTR = 4, + FUSE_READLINK = 5, + FUSE_SYMLINK = 6, + FUSE_MKNOD = 8, + FUSE_MKDIR = 9, + FUSE_UNLINK = 10, + FUSE_RMDIR = 11, + FUSE_RENAME = 12, + FUSE_LINK = 13, + FUSE_OPEN = 14, + FUSE_READ = 15, + FUSE_WRITE = 16, + FUSE_STATFS = 17, + FUSE_RELEASE = 18, + FUSE_FSYNC = 20, + FUSE_SETXATTR = 21, + FUSE_GETXATTR = 22, + FUSE_LISTXATTR = 23, + FUSE_REMOVEXATTR = 24, + FUSE_FLUSH = 25, + FUSE_INIT = 26, + FUSE_OPENDIR = 27, + FUSE_READDIR = 28, + FUSE_RELEASEDIR = 29, + FUSE_FSYNCDIR = 30, + FUSE_GETLK = 31, + FUSE_SETLK = 32, + FUSE_SETLKW = 33, + FUSE_ACCESS = 34, + FUSE_CREATE = 35, + FUSE_INTERRUPT = 36, + FUSE_BMAP = 37, + FUSE_DESTROY = 38, + FUSE_IOCTL = 39, + FUSE_POLL = 40, + FUSE_NOTIFY_REPLY = 41, + FUSE_BATCH_FORGET = 42, + FUSE_FALLOCATE = 43, + FUSE_READDIRPLUS = 44, + FUSE_RENAME2 = 45, + FUSE_LSEEK = 46, + FUSE_COPY_FILE_RANGE = 47, + FUSE_SETUPMAPPING = 48, + FUSE_REMOVEMAPPING = 49, + FUSE_SYNCFS = 50, + FUSE_TMPFILE = 51, + FUSE_STATX = 52, + FUSE_COPY_FILE_RANGE_64 = 53, + + /* CUSE specific operations */ + CUSE_INIT = 4096, + + /* Reserved opcodes: helpful to detect structure endian-ness */ + CUSE_INIT_BSWAP_RESERVED = 1048576, /* CUSE_INIT << 8 */ + FUSE_INIT_BSWAP_RESERVED = 436207616, /* FUSE_INIT << 24 */ +}; + +enum fuse_notify_code { + FUSE_NOTIFY_POLL = 1, + FUSE_NOTIFY_INVAL_INODE = 2, + FUSE_NOTIFY_INVAL_ENTRY = 3, + FUSE_NOTIFY_STORE = 4, + FUSE_NOTIFY_RETRIEVE = 5, + FUSE_NOTIFY_DELETE = 6, + FUSE_NOTIFY_RESEND = 7, + FUSE_NOTIFY_INC_EPOCH = 8, + FUSE_NOTIFY_CODE_MAX, +}; + +/* The read buffer is required to be at least 8k, but may be much larger */ +#define FUSE_MIN_READ_BUFFER 8192 + +#define FUSE_COMPAT_ENTRY_OUT_SIZE 120 + +struct fuse_entry_out { + uint64_t nodeid; /* Inode ID */ + uint64_t generation; /* Inode generation: nodeid:gen must + be unique for the fs's lifetime */ + uint64_t entry_valid; /* Cache timeout for the name */ + uint64_t attr_valid; /* Cache timeout for the attributes */ + uint32_t entry_valid_nsec; + uint32_t attr_valid_nsec; + struct fuse_attr attr; +}; + +struct fuse_forget_in { + uint64_t nlookup; +}; + +struct fuse_forget_one { + uint64_t nodeid; + uint64_t nlookup; +}; + +struct fuse_batch_forget_in { + uint32_t count; + uint32_t dummy; +}; + +struct fuse_getattr_in { + uint32_t getattr_flags; + uint32_t dummy; + uint64_t fh; +}; + +#define FUSE_COMPAT_ATTR_OUT_SIZE 96 + +struct fuse_attr_out { + uint64_t attr_valid; /* Cache timeout for the attributes */ + uint32_t attr_valid_nsec; + uint32_t dummy; + struct fuse_attr attr; +}; + +struct fuse_statx_in { + uint32_t getattr_flags; + uint32_t reserved; + uint64_t fh; + uint32_t sx_flags; + uint32_t sx_mask; +}; + +struct fuse_statx_out { + uint64_t attr_valid; /* Cache timeout for the attributes */ + uint32_t attr_valid_nsec; + uint32_t flags; + uint64_t spare[2]; + struct fuse_statx stat; +}; + +#define FUSE_COMPAT_MKNOD_IN_SIZE 8 + +struct fuse_mknod_in { + uint32_t mode; + uint32_t rdev; + uint32_t umask; + uint32_t padding; +}; + +struct fuse_mkdir_in { + uint32_t mode; + uint32_t umask; +}; + +struct fuse_rename_in { + uint64_t newdir; +}; + +struct fuse_rename2_in { + uint64_t newdir; + uint32_t flags; + uint32_t padding; +}; + +struct fuse_link_in { + uint64_t oldnodeid; +}; + +struct fuse_setattr_in { + uint32_t valid; + uint32_t padding; + uint64_t fh; + uint64_t size; + uint64_t lock_owner; + uint64_t atime; + uint64_t mtime; + uint64_t ctime; + uint32_t atimensec; + uint32_t mtimensec; + uint32_t ctimensec; + uint32_t mode; + uint32_t unused4; + uint32_t uid; + uint32_t gid; + uint32_t unused5; +}; + +struct fuse_open_in { + uint32_t flags; + uint32_t open_flags; /* FUSE_OPEN_... */ +}; + +struct fuse_create_in { + uint32_t flags; + uint32_t mode; + uint32_t umask; + uint32_t open_flags; /* FUSE_OPEN_... */ +}; + +struct fuse_open_out { + uint64_t fh; + uint32_t open_flags; + int32_t backing_id; +}; + +struct fuse_release_in { + uint64_t fh; + uint32_t flags; + uint32_t release_flags; + uint64_t lock_owner; +}; + +struct fuse_flush_in { + uint64_t fh; + uint32_t unused; + uint32_t padding; + uint64_t lock_owner; +}; + +struct fuse_read_in { + uint64_t fh; + uint64_t offset; + uint32_t size; + uint32_t read_flags; + uint64_t lock_owner; + uint32_t flags; + uint32_t padding; +}; + +#define FUSE_COMPAT_WRITE_IN_SIZE 24 + +struct fuse_write_in { + uint64_t fh; + uint64_t offset; + uint32_t size; + uint32_t write_flags; + uint64_t lock_owner; + uint32_t flags; + uint32_t padding; +}; + +struct fuse_write_out { + uint32_t size; + uint32_t padding; +}; + +#define FUSE_COMPAT_STATFS_SIZE 48 + +struct fuse_statfs_out { + struct fuse_kstatfs st; +}; + +struct fuse_fsync_in { + uint64_t fh; + uint32_t fsync_flags; + uint32_t padding; +}; + +#define FUSE_COMPAT_SETXATTR_IN_SIZE 8 + +struct fuse_setxattr_in { + uint32_t size; + uint32_t flags; + uint32_t setxattr_flags; + uint32_t padding; +}; + +struct fuse_getxattr_in { + uint32_t size; + uint32_t padding; +}; + +struct fuse_getxattr_out { + uint32_t size; + uint32_t padding; +}; + +struct fuse_lk_in { + uint64_t fh; + uint64_t owner; + struct fuse_file_lock lk; + uint32_t lk_flags; + uint32_t padding; +}; + +struct fuse_lk_out { + struct fuse_file_lock lk; +}; + +struct fuse_access_in { + uint32_t mask; + uint32_t padding; +}; + +struct fuse_init_in { + uint32_t major; + uint32_t minor; + uint32_t max_readahead; + uint32_t flags; + uint32_t flags2; + uint32_t unused[11]; +}; + +#define FUSE_COMPAT_INIT_OUT_SIZE 8 +#define FUSE_COMPAT_22_INIT_OUT_SIZE 24 + +struct fuse_init_out { + uint32_t major; + uint32_t minor; + uint32_t max_readahead; + uint32_t flags; + uint16_t max_background; + uint16_t congestion_threshold; + uint32_t max_write; + uint32_t time_gran; + uint16_t max_pages; + uint16_t map_alignment; + uint32_t flags2; + uint32_t max_stack_depth; + uint16_t request_timeout; + uint16_t unused[11]; +}; + +#define CUSE_INIT_INFO_MAX 4096 + +struct cuse_init_in { + uint32_t major; + uint32_t minor; + uint32_t unused; + uint32_t flags; +}; + +struct cuse_init_out { + uint32_t major; + uint32_t minor; + uint32_t unused; + uint32_t flags; + uint32_t max_read; + uint32_t max_write; + uint32_t dev_major; /* chardev major */ + uint32_t dev_minor; /* chardev minor */ + uint32_t spare[10]; +}; + +struct fuse_interrupt_in { + uint64_t unique; +}; + +struct fuse_bmap_in { + uint64_t block; + uint32_t blocksize; + uint32_t padding; +}; + +struct fuse_bmap_out { + uint64_t block; +}; + +struct fuse_ioctl_in { + uint64_t fh; + uint32_t flags; + uint32_t cmd; + uint64_t arg; + uint32_t in_size; + uint32_t out_size; +}; + +struct fuse_ioctl_iovec { + uint64_t base; + uint64_t len; +}; + +struct fuse_ioctl_out { + int32_t result; + uint32_t flags; + uint32_t in_iovs; + uint32_t out_iovs; +}; + +struct fuse_poll_in { + uint64_t fh; + uint64_t kh; + uint32_t flags; + uint32_t events; +}; + +struct fuse_poll_out { + uint32_t revents; + uint32_t padding; +}; + +struct fuse_notify_poll_wakeup_out { + uint64_t kh; +}; + +struct fuse_fallocate_in { + uint64_t fh; + uint64_t offset; + uint64_t length; + uint32_t mode; + uint32_t padding; +}; + +/** + * FUSE request unique ID flag + * + * Indicates whether this is a resend request. The receiver should handle this + * request accordingly. + */ +#define FUSE_UNIQUE_RESEND (1ULL << 63) + +/** + * This value will be set by the kernel to + * (struct fuse_in_header).{uid,gid} fields in + * case when: + * - fuse daemon enabled FUSE_ALLOW_IDMAP + * - idmapping information is not available and uid/gid + * can not be mapped in accordance with an idmapping. + * + * Note: an idmapping information always available + * for inode creation operations like: + * FUSE_MKNOD, FUSE_SYMLINK, FUSE_MKDIR, FUSE_TMPFILE, + * FUSE_CREATE and FUSE_RENAME2 (with RENAME_WHITEOUT). + */ +#define FUSE_INVALID_UIDGID ((uint32_t)(-1)) + +struct fuse_in_header { + uint32_t len; + uint32_t opcode; + uint64_t unique; + uint64_t nodeid; + uint32_t uid; + uint32_t gid; + uint32_t pid; + uint16_t total_extlen; /* length of extensions in 8byte units */ + uint16_t padding; +}; + +struct fuse_out_header { + uint32_t len; + int32_t error; + uint64_t unique; +}; + +struct fuse_dirent { + uint64_t ino; + uint64_t off; + uint32_t namelen; + uint32_t type; + char name[]; +}; + +/* Align variable length records to 64bit boundary */ +#define FUSE_REC_ALIGN(x) \ + (((x) + sizeof(uint64_t) - 1) & ~(sizeof(uint64_t) - 1)) + +#define FUSE_NAME_OFFSET offsetof(struct fuse_dirent, name) +#define FUSE_DIRENT_ALIGN(x) FUSE_REC_ALIGN(x) +#define FUSE_DIRENT_SIZE(d) \ + FUSE_DIRENT_ALIGN(FUSE_NAME_OFFSET + (d)->namelen) + +struct fuse_direntplus { + struct fuse_entry_out entry_out; + struct fuse_dirent dirent; +}; + +#define FUSE_NAME_OFFSET_DIRENTPLUS \ + offsetof(struct fuse_direntplus, dirent.name) +#define FUSE_DIRENTPLUS_SIZE(d) \ + FUSE_DIRENT_ALIGN(FUSE_NAME_OFFSET_DIRENTPLUS + (d)->dirent.namelen) + +struct fuse_notify_inval_inode_out { + uint64_t ino; + int64_t off; + int64_t len; +}; + +struct fuse_notify_inval_entry_out { + uint64_t parent; + uint32_t namelen; + uint32_t flags; +}; + +struct fuse_notify_delete_out { + uint64_t parent; + uint64_t child; + uint32_t namelen; + uint32_t padding; +}; + +struct fuse_notify_store_out { + uint64_t nodeid; + uint64_t offset; + uint32_t size; + uint32_t padding; +}; + +struct fuse_notify_retrieve_out { + uint64_t notify_unique; + uint64_t nodeid; + uint64_t offset; + uint32_t size; + uint32_t padding; +}; + +/* Matches the size of fuse_write_in */ +struct fuse_notify_retrieve_in { + uint64_t dummy1; + uint64_t offset; + uint32_t size; + uint32_t dummy2; + uint64_t dummy3; + uint64_t dummy4; +}; + +struct fuse_backing_map { + int32_t fd; + uint32_t flags; + uint64_t padding; +}; + +/* Device ioctls: */ +#define FUSE_DEV_IOC_MAGIC 229 +#define FUSE_DEV_IOC_CLONE _IOR(FUSE_DEV_IOC_MAGIC, 0, uint32_t) +#define FUSE_DEV_IOC_BACKING_OPEN _IOW(FUSE_DEV_IOC_MAGIC, 1, \ + struct fuse_backing_map) +#define FUSE_DEV_IOC_BACKING_CLOSE _IOW(FUSE_DEV_IOC_MAGIC, 2, uint32_t) + +struct fuse_lseek_in { + uint64_t fh; + uint64_t offset; + uint32_t whence; + uint32_t padding; +}; + +struct fuse_lseek_out { + uint64_t offset; +}; + +struct fuse_copy_file_range_in { + uint64_t fh_in; + uint64_t off_in; + uint64_t nodeid_out; + uint64_t fh_out; + uint64_t off_out; + uint64_t len; + uint64_t flags; +}; + +/* For FUSE_COPY_FILE_RANGE_64 */ +struct fuse_copy_file_range_out { + uint64_t bytes_copied; +}; + +#define FUSE_SETUPMAPPING_FLAG_WRITE (1ull << 0) +#define FUSE_SETUPMAPPING_FLAG_READ (1ull << 1) +struct fuse_setupmapping_in { + /* An already open handle */ + uint64_t fh; + /* Offset into the file to start the mapping */ + uint64_t foffset; + /* Length of mapping required */ + uint64_t len; + /* Flags, FUSE_SETUPMAPPING_FLAG_* */ + uint64_t flags; + /* Offset in Memory Window */ + uint64_t moffset; +}; + +struct fuse_removemapping_in { + /* number of fuse_removemapping_one follows */ + uint32_t count; +}; + +struct fuse_removemapping_one { + /* Offset into the dax window start the unmapping */ + uint64_t moffset; + /* Length of mapping required */ + uint64_t len; +}; + +#define FUSE_REMOVEMAPPING_MAX_ENTRY \ + (PAGE_SIZE / sizeof(struct fuse_removemapping_one)) + +struct fuse_syncfs_in { + uint64_t padding; +}; + +/* + * For each security context, send fuse_secctx with size of security context + * fuse_secctx will be followed by security context name and this in turn + * will be followed by actual context label. + * fuse_secctx, name, context + */ +struct fuse_secctx { + uint32_t size; + uint32_t padding; +}; + +/* + * Contains the information about how many fuse_secctx structures are being + * sent and what's the total size of all security contexts (including + * size of fuse_secctx_header). + * + */ +struct fuse_secctx_header { + uint32_t size; + uint32_t nr_secctx; +}; + +/** + * struct fuse_ext_header - extension header + * @size: total size of this extension including this header + * @type: type of extension + * + * This is made compatible with fuse_secctx_header by using type values > + * FUSE_MAX_NR_SECCTX + */ +struct fuse_ext_header { + uint32_t size; + uint32_t type; +}; + +/** + * struct fuse_supp_groups - Supplementary group extension + * @nr_groups: number of supplementary groups + * @groups: flexible array of group IDs + */ +struct fuse_supp_groups { + uint32_t nr_groups; + uint32_t groups[]; +}; + +/** + * Size of the ring buffer header + */ +#define FUSE_URING_IN_OUT_HEADER_SZ 128 +#define FUSE_URING_OP_IN_OUT_SZ 128 + +/* Used as part of the fuse_uring_req_header */ +struct fuse_uring_ent_in_out { + uint64_t flags; + + /* + * commit ID to be used in a reply to a ring request (see also + * struct fuse_uring_cmd_req) + */ + uint64_t commit_id; + + /* size of user payload buffer */ + uint32_t payload_sz; + uint32_t padding; + + uint64_t reserved; +}; + +/** + * Header for all fuse-io-uring requests + */ +struct fuse_uring_req_header { + /* struct fuse_in_header / struct fuse_out_header */ + char in_out[FUSE_URING_IN_OUT_HEADER_SZ]; + + /* per op code header */ + char op_in[FUSE_URING_OP_IN_OUT_SZ]; + + struct fuse_uring_ent_in_out ring_ent_in_out; +}; + +/** + * sqe commands to the kernel + */ +enum fuse_uring_cmd { + FUSE_IO_URING_CMD_INVALID = 0, + + /* register the request buffer and fetch a fuse request */ + FUSE_IO_URING_CMD_REGISTER = 1, + + /* commit fuse request result and fetch next request */ + FUSE_IO_URING_CMD_COMMIT_AND_FETCH = 2, +}; + +/** + * In the 80B command area of the SQE. + */ +struct fuse_uring_cmd_req { + uint64_t flags; + + /* entry identifier for commits */ + uint64_t commit_id; + + /* queue the command is for (queue index) */ + uint16_t qid; + uint8_t padding[6]; +}; + +#endif /* _LINUX_FUSE_H */ diff --git a/include/fuse_log.h b/include/fuse_log.h new file mode 100644 index 0000000..7948bab --- /dev/null +++ b/include/fuse_log.h @@ -0,0 +1,95 @@ +/* + FUSE: Filesystem in Userspace + Copyright (C) 2019 Red Hat, Inc. + + This program can be distributed under the terms of the GNU LGPLv2. + See the file LGPL2.txt. +*/ + +#ifndef FUSE_LOG_H_ +#define FUSE_LOG_H_ + +/** @file + * + * This file defines the logging interface of FUSE + */ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Log severity level + * + * These levels correspond to syslog(2) log levels since they are widely used. + */ +enum fuse_log_level { + FUSE_LOG_EMERG, + FUSE_LOG_ALERT, + FUSE_LOG_CRIT, + FUSE_LOG_ERR, + FUSE_LOG_WARNING, + FUSE_LOG_NOTICE, + FUSE_LOG_INFO, + FUSE_LOG_DEBUG +}; + +/** + * Log message handler function. + * + * This function must be thread-safe. It may be called from any libfuse + * function, including fuse_parse_cmdline() and other functions invoked before + * a FUSE filesystem is created. + * + * Install a custom log message handler function using fuse_set_log_func(). + * + * @param level log severity level + * @param fmt sprintf-style format string including newline + * @param ap format string arguments + */ +typedef void (*fuse_log_func_t)(enum fuse_log_level level, + const char *fmt, va_list ap); + +/** + * Install a custom log handler function. + * + * Log messages are emitted by libfuse functions to report errors and debug + * information. Messages are printed to stderr by default but this can be + * overridden by installing a custom log message handler function. + * + * The log message handler function is global and affects all FUSE filesystems + * created within this process. + * + * @param func a custom log message handler function or NULL to revert to + * the default + */ +void fuse_set_log_func(fuse_log_func_t func); + +/** + * Emit a log message + * + * @param level severity level (FUSE_LOG_ERR, FUSE_LOG_DEBUG, etc) + * @param fmt sprintf-style format string including newline + */ +void fuse_log(enum fuse_log_level level, const char *fmt, ...) + __attribute__((format(printf, 2, 3))); + +/** + * Switch default log handler from stderr to syslog + * + * Passed options are according to 'man 3 openlog' + */ +void fuse_log_enable_syslog(const char *ident, int option, int facility); + +/** + * To be called at teardown to close syslog. +*/ +void fuse_log_close_syslog(void); + +#ifdef __cplusplus +} +#endif + +#endif /* FUSE_LOG_H_ */ diff --git a/include/fuse_lowlevel.h b/include/fuse_lowlevel.h new file mode 100644 index 0000000..c8009b8 --- /dev/null +++ b/include/fuse_lowlevel.h @@ -0,0 +1,2404 @@ +/* + FUSE: Filesystem in Userspace + Copyright (C) 2001-2007 Miklos Szeredi + + This program can be distributed under the terms of the GNU LGPLv2. + See the file LGPL2.txt. +*/ + +#ifndef FUSE_LOWLEVEL_H_ +#define FUSE_LOWLEVEL_H_ + +/** @file + * + * Low level API + * + * IMPORTANT: you should define FUSE_USE_VERSION before including this + * header. To use the newest API define it to 35 (recommended for any + * new application). + */ + +#ifndef FUSE_USE_VERSION +#error FUSE_USE_VERSION not defined +#endif + +#include "fuse_common.h" + +#include +#include +#include +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* ----------------------------------------------------------- * + * Miscellaneous definitions * + * ----------------------------------------------------------- */ + +/** The node ID of the root inode */ +#define FUSE_ROOT_ID 1 + +/** Inode number type */ +typedef uint64_t fuse_ino_t; + +/** Request pointer type */ +typedef struct fuse_req *fuse_req_t; + +/* Forward declaration */ +struct statx; + +/** + * Session + * + * This provides hooks for processing requests, and exiting + */ +struct fuse_session; + +/** Directory entry parameters supplied to fuse_reply_entry() */ +struct fuse_entry_param { + /** Unique inode number + * + * In lookup, zero means negative entry (from version 2.5) + * Returning ENOENT also means negative entry, but by setting zero + * ino the kernel may cache negative entries for entry_timeout + * seconds. + */ + fuse_ino_t ino; + + /** Generation number for this entry. + * + * If the file system will be exported over NFS, the + * ino/generation pairs need to be unique over the file + * system's lifetime (rather than just the mount time). So if + * the file system reuses an inode after it has been deleted, + * it must assign a new, previously unused generation number + * to the inode at the same time. + * + */ + uint64_t generation; + + /** Inode attributes. + * + * Even if attr_timeout == 0, attr must be correct. For example, + * for open(), FUSE uses attr.st_size from lookup() to determine + * how many bytes to request. If this value is not correct, + * incorrect data will be returned. + */ + struct stat attr; + + /** Validity timeout (in seconds) for inode attributes. If + attributes only change as a result of requests that come + through the kernel, this should be set to a very large + value. */ + double attr_timeout; + + /** Validity timeout (in seconds) for the name. If directory + entries are changed/deleted only as a result of requests + that come through the kernel, this should be set to a very + large value. */ + double entry_timeout; +}; + +/** + * Additional context associated with requests. + * + * Note that the reported client uid, gid and pid may be zero in some + * situations. For example, if the FUSE file system is running in a + * PID or user namespace but then accessed from outside the namespace, + * there is no valid uid/pid/gid that could be reported. + */ +struct fuse_ctx { + /** User ID of the calling process */ + uid_t uid; + + /** Group ID of the calling process */ + gid_t gid; + + /** Thread ID of the calling process */ + pid_t pid; + + /** Umask of the calling process */ + mode_t umask; +}; + +struct fuse_forget_data { + fuse_ino_t ino; + uint64_t nlookup; +}; + +struct fuse_custom_io { + ssize_t (*writev)(int fd, struct iovec *iov, int count, void *userdata); + ssize_t (*read)(int fd, void *buf, size_t buf_len, void *userdata); + ssize_t (*splice_receive)(int fdin, off_t *offin, int fdout, + off_t *offout, size_t len, + unsigned int flags, void *userdata); + ssize_t (*splice_send)(int fdin, off_t *offin, int fdout, + off_t *offout, size_t len, + unsigned int flags, void *userdata); + int (*clone_fd)(int master_fd); +}; + +/** + * Flags for fuse_lowlevel_notify_entry() + * 0 = invalidate entry + * FUSE_LL_EXPIRE_ONLY = expire entry +*/ +enum fuse_notify_entry_flags { + FUSE_LL_INVALIDATE = 0, + FUSE_LL_EXPIRE_ONLY = (1 << 0), +}; + +/* 'to_set' flags in setattr */ +#define FUSE_SET_ATTR_MODE (1 << 0) +#define FUSE_SET_ATTR_UID (1 << 1) +#define FUSE_SET_ATTR_GID (1 << 2) +#define FUSE_SET_ATTR_SIZE (1 << 3) +#define FUSE_SET_ATTR_ATIME (1 << 4) +#define FUSE_SET_ATTR_MTIME (1 << 5) +#define FUSE_SET_ATTR_ATIME_NOW (1 << 7) +#define FUSE_SET_ATTR_MTIME_NOW (1 << 8) +#define FUSE_SET_ATTR_FORCE (1 << 9) +#define FUSE_SET_ATTR_CTIME (1 << 10) +#define FUSE_SET_ATTR_KILL_SUID (1 << 11) +#define FUSE_SET_ATTR_KILL_SGID (1 << 12) +#define FUSE_SET_ATTR_FILE (1 << 13) +#define FUSE_SET_ATTR_KILL_PRIV (1 << 14) +#define FUSE_SET_ATTR_OPEN (1 << 15) +#define FUSE_SET_ATTR_TIMES_SET (1 << 16) +#define FUSE_SET_ATTR_TOUCH (1 << 17) + +/* ----------------------------------------------------------- * + * Request methods and replies * + * ----------------------------------------------------------- */ + +/** + * Low level filesystem operations + * + * Most of the methods (with the exception of init and destroy) + * receive a request handle (fuse_req_t) as their first argument. + * This handle must be passed to one of the specified reply functions. + * + * This may be done inside the method invocation, or after the call + * has returned. The request handle is valid until one of the reply + * functions is called. + * + * Other pointer arguments (name, fuse_file_info, etc) are not valid + * after the call has returned, so if they are needed later, their + * contents have to be copied. + * + * In general, all methods are expected to perform any necessary + * permission checking. However, a filesystem may delegate this task + * to the kernel by passing the `default_permissions` mount option to + * `fuse_session_new()`. In this case, methods will only be called if + * the kernel's permission check has succeeded. + * + * It is generally not really necessary to check the fuse_reply_* return + * values for errors, as any error in sending a reply indicates an + * unrecoverable problem with the kernel fuse connection, which will also + * terminate the session loop anyway. + * + * This data structure is ABI sensitive, on adding new functions these need to + * be appended at the end of the struct + */ +struct fuse_lowlevel_ops { + /** + * Initialize filesystem + * + * This function is called when libfuse establishes + * communication with the FUSE kernel module. The file system + * should use this module to inspect and/or modify the + * connection parameters provided in the `conn` structure. + * + * Note that some parameters may be overwritten by options + * passed to fuse_session_new() which take precedence over the + * values set in this handler. + * + * There's no reply to this function + * + * @param userdata the user data passed to fuse_session_new() + */ + void (*init) (void *userdata, struct fuse_conn_info *conn); + + /** + * Clean up filesystem. + * + * Called on filesystem exit. When this method is called, the + * connection to the kernel may be gone already, so that eg. calls + * to fuse_lowlevel_notify_* will fail. + * + * There's no reply to this function + * + * @param userdata the user data passed to fuse_session_new() + */ + void (*destroy) (void *userdata); + + /** + * Look up a directory entry by name and get its attributes. + * + * Valid replies: + * fuse_reply_entry + * fuse_reply_err + * + * @param req request handle + * @param parent inode number of the parent directory + * @param name the name to look up + */ + void (*lookup) (fuse_req_t req, fuse_ino_t parent, const char *name); + + /** + * Forget about an inode + * + * This function is called when the kernel removes an inode + * from its internal caches. + * + * The inode's lookup count increases by one for every call to + * fuse_reply_entry and fuse_reply_create. The nlookup parameter + * indicates by how much the lookup count should be decreased. + * + * Inodes with a non-zero lookup count may receive request from + * the kernel even after calls to unlink, rmdir or (when + * overwriting an existing file) rename. Filesystems must handle + * such requests properly and it is recommended to defer removal + * of the inode until the lookup count reaches zero. Calls to + * unlink, rmdir or rename will be followed closely by forget + * unless the file or directory is open, in which case the + * kernel issues forget only after the release or releasedir + * calls. + * + * Note that if a file system will be exported over NFS the + * inodes lifetime must extend even beyond forget. See the + * generation field in struct fuse_entry_param above. + * + * On unmount the lookup count for all inodes implicitly drops + * to zero. It is not guaranteed that the file system will + * receive corresponding forget messages for the affected + * inodes. + * + * Valid replies: + * fuse_reply_none + * + * @param req request handle + * @param ino the inode number + * @param nlookup the number of lookups to forget + */ + void (*forget) (fuse_req_t req, fuse_ino_t ino, uint64_t nlookup); + + /** + * Get file attributes. + * + * If writeback caching is enabled, the kernel may have a + * better idea of a file's length than the FUSE file system + * (eg if there has been a write that extended the file size, + * but that has not yet been passed to the filesystem. + * + * In this case, the st_size value provided by the file system + * will be ignored. + * + * Valid replies: + * fuse_reply_attr + * fuse_reply_err + * + * @param req request handle + * @param ino the inode number + * @param fi file information, or NULL + */ + void (*getattr) (fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info *fi); + + /** + * Set file attributes + * + * In the 'attr' argument only members indicated by the 'to_set' + * bitmask contain valid values. Other members contain undefined + * values. + * + * Unless FUSE_CAP_HANDLE_KILLPRIV is disabled, this method is + * expected to reset the setuid and setgid bits if the file + * size or owner is being changed. + * + * This method will not be called to update st_atime or st_ctime implicitly + * (eg. after a read() request), and only be called to implicitly update st_mtime + * if writeback caching is active. It is the filesystem's responsibility to update + * these timestamps when needed, and (if desired) to implement mount options like + * `noatime` or `relatime`. + * + * If the setattr was invoked from the ftruncate() system call + * under Linux kernel versions 2.6.15 or later, the fi->fh will + * contain the value set by the open method or will be undefined + * if the open method didn't set any value. Otherwise (not + * ftruncate call, or kernel version earlier than 2.6.15) the fi + * parameter will be NULL. + * + * Valid replies: + * fuse_reply_attr + * fuse_reply_err + * + * @param req request handle + * @param ino the inode number + * @param attr the attributes + * @param to_set bit mask of attributes which should be set + * @param fi file information, or NULL + */ + void (*setattr) (fuse_req_t req, fuse_ino_t ino, struct stat *attr, + int to_set, struct fuse_file_info *fi); + + /** + * Read symbolic link + * + * Valid replies: + * fuse_reply_readlink + * fuse_reply_err + * + * @param req request handle + * @param ino the inode number + */ + void (*readlink) (fuse_req_t req, fuse_ino_t ino); + + /** + * Create file node + * + * Create a regular file, character device, block device, fifo or + * socket node. + * + * Valid replies: + * fuse_reply_entry + * fuse_reply_err + * + * @param req request handle + * @param parent inode number of the parent directory + * @param name to create + * @param mode file type and mode with which to create the new file + * @param rdev the device number (only valid if created file is a device) + */ + void (*mknod) (fuse_req_t req, fuse_ino_t parent, const char *name, + mode_t mode, dev_t rdev); + + /** + * Create a directory + * + * Valid replies: + * fuse_reply_entry + * fuse_reply_err + * + * @param req request handle + * @param parent inode number of the parent directory + * @param name to create + * @param mode with which to create the new file + */ + void (*mkdir) (fuse_req_t req, fuse_ino_t parent, const char *name, + mode_t mode); + + /** + * Remove a file + * + * If the file's inode's lookup count is non-zero, the file + * system is expected to postpone any removal of the inode + * until the lookup count reaches zero (see description of the + * forget function). + * + * Valid replies: + * fuse_reply_err + * + * @param req request handle + * @param parent inode number of the parent directory + * @param name to remove + */ + void (*unlink) (fuse_req_t req, fuse_ino_t parent, const char *name); + + /** + * Remove a directory + * + * If the directory's inode's lookup count is non-zero, the + * file system is expected to postpone any removal of the + * inode until the lookup count reaches zero (see description + * of the forget function). + * + * Valid replies: + * fuse_reply_err + * + * @param req request handle + * @param parent inode number of the parent directory + * @param name to remove + */ + void (*rmdir) (fuse_req_t req, fuse_ino_t parent, const char *name); + + /** + * Create a symbolic link + * + * Valid replies: + * fuse_reply_entry + * fuse_reply_err + * + * @param req request handle + * @param link the contents of the symbolic link + * @param parent inode number of the parent directory + * @param name to create + */ + void (*symlink) (fuse_req_t req, const char *link, fuse_ino_t parent, + const char *name); + + /** Rename a file + * + * If the target exists it should be atomically replaced. If + * the target's inode's lookup count is non-zero, the file + * system is expected to postpone any removal of the inode + * until the lookup count reaches zero (see description of the + * forget function). + * + * If this request is answered with an error code of ENOSYS, this is + * treated as a permanent failure with error code EINVAL, i.e. all + * future rename requests will fail with EINVAL without being + * send to the filesystem process. + * + * *flags* may be `RENAME_EXCHANGE` or `RENAME_NOREPLACE`. If + * RENAME_NOREPLACE is specified, the filesystem must not + * overwrite *newname* if it exists and return an error + * instead. If `RENAME_EXCHANGE` is specified, the filesystem + * must atomically exchange the two files, i.e. both must + * exist and neither may be deleted. + * + * Valid replies: + * fuse_reply_err + * + * @param req request handle + * @param parent inode number of the old parent directory + * @param name old name + * @param newparent inode number of the new parent directory + * @param newname new name + */ + void (*rename) (fuse_req_t req, fuse_ino_t parent, const char *name, + fuse_ino_t newparent, const char *newname, + unsigned int flags); + + /** + * Create a hard link + * + * Valid replies: + * fuse_reply_entry + * fuse_reply_err + * + * @param req request handle + * @param ino the old inode number + * @param newparent inode number of the new parent directory + * @param newname new name to create + */ + void (*link) (fuse_req_t req, fuse_ino_t ino, fuse_ino_t newparent, + const char *newname); + + /** + * Open a file + * + * Open flags are available in fi->flags. The following rules + * apply. + * + * - Creation (O_CREAT, O_EXCL, O_NOCTTY) flags will be + * filtered out / handled by the kernel. + * + * - Access modes (O_RDONLY, O_WRONLY, O_RDWR) should be used + * by the filesystem to check if the operation is + * permitted. If the ``-o default_permissions`` mount + * option is given, this check is already done by the + * kernel before calling open() and may thus be omitted by + * the filesystem. + * + * - When writeback caching is enabled, the kernel may send + * read requests even for files opened with O_WRONLY. The + * filesystem should be prepared to handle this. + * + * - When writeback caching is disabled, the filesystem is + * expected to properly handle the O_APPEND flag and ensure + * that each write is appending to the end of the file. + * + * - When writeback caching is enabled, the kernel will + * handle O_APPEND. However, unless all changes to the file + * come through the kernel this will not work reliably. The + * filesystem should thus either ignore the O_APPEND flag + * (and let the kernel handle it), or return an error + * (indicating that reliably O_APPEND is not available). + * + * Filesystem may store an arbitrary file handle (pointer, + * index, etc) in fi->fh, and use this in other all other file + * operations (read, write, flush, release, fsync). + * + * Filesystem may also implement stateless file I/O and not store + * anything in fi->fh. + * + * There are also some flags (direct_io, keep_cache) which the + * filesystem may set in fi, to change the way the file is opened. + * See fuse_file_info structure in for more details. + * + * If this request is answered with an error code of ENOSYS + * and FUSE_CAP_NO_OPEN_SUPPORT is set in + * `fuse_conn_info.capable`, this is treated as success and + * future calls to open and release will also succeed without being + * sent to the filesystem process. + * + * To get this behavior without providing an opendir handler, you may + * set FUSE_CAP_NO_OPEN_SUPPORT in `fuse_conn_info.want` on supported + * kernels to automatically get the zero message open(). + * + * If this callback is not provided and FUSE_CAP_NO_OPEN_SUPPORT is not + * set in `fuse_conn_info.want` then an empty reply will be sent. + * + * Valid replies: + * fuse_reply_open + * fuse_reply_err + * + * @param req request handle + * @param ino the inode number + * @param fi file information + */ + void (*open) (fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info *fi); + + /** + * Read data + * + * Read should send exactly the number of bytes requested except + * on EOF or error, otherwise the rest of the data will be + * substituted with zeroes. An exception to this is when the file + * has been opened in 'direct_io' mode, in which case the return + * value of the read system call will reflect the return value of + * this operation. + * + * fi->fh will contain the value set by the open method, or will + * be undefined if the open method didn't set any value. + * + * Valid replies: + * fuse_reply_buf + * fuse_reply_iov + * fuse_reply_data + * fuse_reply_err + * + * @param req request handle + * @param ino the inode number + * @param size number of bytes to read + * @param off offset to read from + * @param fi file information + */ + void (*read) (fuse_req_t req, fuse_ino_t ino, size_t size, off_t off, + struct fuse_file_info *fi); + + /** + * Write data + * + * Write should return exactly the number of bytes requested + * except on error. An exception to this is when the file has + * been opened in 'direct_io' mode, in which case the return value + * of the write system call will reflect the return value of this + * operation. + * + * Unless FUSE_CAP_HANDLE_KILLPRIV is disabled, this method is + * expected to reset the setuid and setgid bits. + * + * fi->fh will contain the value set by the open method, or will + * be undefined if the open method didn't set any value. + * + * Valid replies: + * fuse_reply_write + * fuse_reply_err + * + * @param req request handle + * @param ino the inode number + * @param buf data to write + * @param size number of bytes to write + * @param off offset to write to + * @param fi file information + */ + void (*write) (fuse_req_t req, fuse_ino_t ino, const char *buf, + size_t size, off_t off, struct fuse_file_info *fi); + + /** + * Flush method + * + * This is called on each close() of the opened file. + * + * Since file descriptors can be duplicated (dup, dup2, fork), for + * one open call there may be many flush calls. + * + * Filesystems shouldn't assume that flush will always be called + * after some writes, or that if will be called at all. + * + * fi->fh will contain the value set by the open method, or will + * be undefined if the open method didn't set any value. + * + * NOTE: the name of the method is misleading, since (unlike + * fsync) the filesystem is not forced to flush pending writes. + * One reason to flush data is if the filesystem wants to return + * write errors during close. However, such use is non-portable + * because POSIX does not require [close] to wait for delayed I/O to + * complete. + * + * If the filesystem supports file locking operations (setlk, + * getlk) it should remove all locks belonging to 'fi->owner'. + * + * If this request is answered with an error code of ENOSYS, + * this is treated as success and future calls to flush() will + * succeed automatically without being send to the filesystem + * process. + * + * Valid replies: + * fuse_reply_err + * + * @param req request handle + * @param ino the inode number + * @param fi file information + * + * [close]: http://pubs.opengroup.org/onlinepubs/9699919799/functions/close.html + */ + void (*flush) (fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info *fi); + + /** + * Release an open file + * + * Release is called when there are no more references to an open + * file: all file descriptors are closed and all memory mappings + * are unmapped. + * + * For every open call there will be exactly one release call (unless + * the filesystem is force-unmounted). + * + * The filesystem may reply with an error, but error values are + * not returned to close() or munmap() which triggered the + * release. + * + * fi->fh will contain the value set by the open method, or will + * be undefined if the open method didn't set any value. + * fi->flags will contain the same flags as for open. + * + * Valid replies: + * fuse_reply_err + * + * @param req request handle + * @param ino the inode number + * @param fi file information + */ + void (*release) (fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info *fi); + + /** + * Synchronize file contents + * + * If the datasync parameter is non-zero, then only the user data + * should be flushed, not the meta data. + * + * If this request is answered with an error code of ENOSYS, + * this is treated as success and future calls to fsync() will + * succeed automatically without being send to the filesystem + * process. + * + * Valid replies: + * fuse_reply_err + * + * @param req request handle + * @param ino the inode number + * @param datasync flag indicating if only data should be flushed + * @param fi file information + */ + void (*fsync) (fuse_req_t req, fuse_ino_t ino, int datasync, + struct fuse_file_info *fi); + + /** + * Open a directory + * + * Filesystem may store an arbitrary file handle (pointer, index, + * etc) in fi->fh, and use this in other all other directory + * stream operations (readdir, releasedir, fsyncdir). + * + * If this request is answered with an error code of ENOSYS and + * FUSE_CAP_NO_OPENDIR_SUPPORT is set in `fuse_conn_info.capable`, + * this is treated as success and future calls to opendir and + * releasedir will also succeed without being sent to the filesystem + * process. In addition, the kernel will cache readdir results + * as if opendir returned FOPEN_KEEP_CACHE | FOPEN_CACHE_DIR. + * + * To get this behavior without providing an opendir handler, you may + * set FUSE_CAP_NO_OPENDIR_SUPPORT in `fuse_conn_info.want` on supported + * kernels to automatically get the zero message opendir(). + * + * If this callback is not provided and FUSE_CAP_NO_OPENDIR_SUPPORT is + * not set in `fuse_conn_info.want` then an empty reply will be sent. + * + * Valid replies: + * fuse_reply_open + * fuse_reply_err + * + * @param req request handle + * @param ino the inode number + * @param fi file information + */ + void (*opendir) (fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info *fi); + + /** + * Read directory + * + * Send a buffer filled using fuse_add_direntry(), with size not + * exceeding the requested size. Send an empty buffer on end of + * stream. + * + * fi->fh will contain the value set by the opendir method, or + * will be undefined if the opendir method didn't set any value. + * + * Returning a directory entry from readdir() does not affect + * its lookup count. + * + * If off_t is non-zero, then it will correspond to one of the off_t + * values that was previously returned by readdir() for the same + * directory handle. In this case, readdir() should skip over entries + * coming before the position defined by the off_t value. If entries + * are added or removed while the directory handle is open, the filesystem + * may still include the entries that have been removed, and may not + * report the entries that have been created. However, addition or + * removal of entries must never cause readdir() to skip over unrelated + * entries or to report them more than once. This means + * that off_t can not be a simple index that enumerates the entries + * that have been returned but must contain sufficient information to + * uniquely determine the next directory entry to return even when the + * set of entries is changing. + * + * The function does not have to report the '.' and '..' + * entries, but is allowed to do so. Note that, if readdir does + * not return '.' or '..', they will not be implicitly returned, + * and this behavior is observable by the caller. + * + * Valid replies: + * fuse_reply_buf + * fuse_reply_data + * fuse_reply_err + * + * @param req request handle + * @param ino the inode number + * @param size maximum number of bytes to send + * @param off offset to continue reading the directory stream + * @param fi file information + */ + void (*readdir) (fuse_req_t req, fuse_ino_t ino, size_t size, off_t off, + struct fuse_file_info *fi); + + /** + * Release an open directory + * + * For every opendir call there will be exactly one releasedir + * call (unless the filesystem is force-unmounted). + * + * fi->fh will contain the value set by the opendir method, or + * will be undefined if the opendir method didn't set any value. + * + * Valid replies: + * fuse_reply_err + * + * @param req request handle + * @param ino the inode number + * @param fi file information + */ + void (*releasedir) (fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info *fi); + + /** + * Synchronize directory contents + * + * If the datasync parameter is non-zero, then only the directory + * contents should be flushed, not the meta data. + * + * fi->fh will contain the value set by the opendir method, or + * will be undefined if the opendir method didn't set any value. + * + * If this request is answered with an error code of ENOSYS, + * this is treated as success and future calls to fsyncdir() will + * succeed automatically without being send to the filesystem + * process. + * + * Valid replies: + * fuse_reply_err + * + * @param req request handle + * @param ino the inode number + * @param datasync flag indicating if only data should be flushed + * @param fi file information + */ + void (*fsyncdir) (fuse_req_t req, fuse_ino_t ino, int datasync, + struct fuse_file_info *fi); + + /** + * Get file system statistics + * + * Valid replies: + * fuse_reply_statfs + * fuse_reply_err + * + * @param req request handle + * @param ino the inode number, zero means "undefined" + */ + void (*statfs) (fuse_req_t req, fuse_ino_t ino); + + /** + * Set an extended attribute + * + * If this request is answered with an error code of ENOSYS, this is + * treated as a permanent failure with error code EOPNOTSUPP, i.e. all + * future setxattr() requests will fail with EOPNOTSUPP without being + * send to the filesystem process. + * + * Valid replies: + * fuse_reply_err + */ + void (*setxattr) (fuse_req_t req, fuse_ino_t ino, const char *name, + const char *value, size_t size, int flags); + + /** + * Get an extended attribute + * + * If size is zero, the size of the value should be sent with + * fuse_reply_xattr. + * + * If the size is non-zero, and the value fits in the buffer, the + * value should be sent with fuse_reply_buf. + * + * If the size is too small for the value, the ERANGE error should + * be sent. + * + * If this request is answered with an error code of ENOSYS, this is + * treated as a permanent failure with error code EOPNOTSUPP, i.e. all + * future getxattr() requests will fail with EOPNOTSUPP without being + * send to the filesystem process. + * + * Valid replies: + * fuse_reply_buf + * fuse_reply_data + * fuse_reply_xattr + * fuse_reply_err + * + * @param req request handle + * @param ino the inode number + * @param name of the extended attribute + * @param size maximum size of the value to send + */ + void (*getxattr) (fuse_req_t req, fuse_ino_t ino, const char *name, + size_t size); + + /** + * List extended attribute names + * + * If size is zero, the total size of the attribute list should be + * sent with fuse_reply_xattr. + * + * If the size is non-zero, and the null character separated + * attribute list fits in the buffer, the list should be sent with + * fuse_reply_buf. + * + * If the size is too small for the list, the ERANGE error should + * be sent. + * + * If this request is answered with an error code of ENOSYS, this is + * treated as a permanent failure with error code EOPNOTSUPP, i.e. all + * future listxattr() requests will fail with EOPNOTSUPP without being + * send to the filesystem process. + * + * Valid replies: + * fuse_reply_buf + * fuse_reply_data + * fuse_reply_xattr + * fuse_reply_err + * + * @param req request handle + * @param ino the inode number + * @param size maximum size of the list to send + */ + void (*listxattr) (fuse_req_t req, fuse_ino_t ino, size_t size); + + /** + * Remove an extended attribute + * + * If this request is answered with an error code of ENOSYS, this is + * treated as a permanent failure with error code EOPNOTSUPP, i.e. all + * future removexattr() requests will fail with EOPNOTSUPP without being + * send to the filesystem process. + * + * Valid replies: + * fuse_reply_err + * + * @param req request handle + * @param ino the inode number + * @param name of the extended attribute + */ + void (*removexattr) (fuse_req_t req, fuse_ino_t ino, const char *name); + + /** + * Check file access permissions + * + * This will be called for the access() and chdir() system + * calls. If the 'default_permissions' mount option is given, + * this method is not called. + * + * This method is not called under Linux kernel versions 2.4.x + * + * If this request is answered with an error code of ENOSYS, this is + * treated as a permanent success, i.e. this and all future access() + * requests will succeed without being send to the filesystem process. + * + * Valid replies: + * fuse_reply_err + * + * @param req request handle + * @param ino the inode number + * @param mask requested access mode + */ + void (*access) (fuse_req_t req, fuse_ino_t ino, int mask); + + /** + * Create and open a file + * + * If the file does not exist, first create it with the specified + * mode, and then open it. + * + * See the description of the open handler for more + * information. + * + * If this method is not implemented or under Linux kernel + * versions earlier than 2.6.15, the mknod() and open() methods + * will be called instead. + * + * If this request is answered with an error code of ENOSYS, the handler + * is treated as not implemented (i.e., for this and future requests the + * mknod() and open() handlers will be called instead). + * + * Valid replies: + * fuse_reply_create + * fuse_reply_err + * + * @param req request handle + * @param parent inode number of the parent directory + * @param name to create + * @param mode file type and mode with which to create the new file + * @param fi file information + */ + void (*create) (fuse_req_t req, fuse_ino_t parent, const char *name, + mode_t mode, struct fuse_file_info *fi); + + /** + * Test for a POSIX file lock + * + * Valid replies: + * fuse_reply_lock + * fuse_reply_err + * + * @param req request handle + * @param ino the inode number + * @param fi file information + * @param lock the region/type to test + */ + void (*getlk) (fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info *fi, struct flock *lock); + + /** + * Acquire, modify or release a POSIX file lock + * + * For POSIX threads (NPTL) there's a 1-1 relation between pid and + * owner, but otherwise this is not always the case. For checking + * lock ownership, 'fi->owner' must be used. The l_pid field in + * 'struct flock' should only be used to fill in this field in + * getlk(). + * + * Note: if the locking methods are not implemented, the kernel + * will still allow file locking to work locally. Hence these are + * only interesting for network filesystems and similar. + * + * Valid replies: + * fuse_reply_err + * + * @param req request handle + * @param ino the inode number + * @param fi file information + * @param lock the region/type to set + * @param sleep locking operation may sleep + */ + void (*setlk) (fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info *fi, + struct flock *lock, int sleep); + + /** + * Map block index within file to block index within device + * + * Note: This makes sense only for block device backed filesystems + * mounted with the 'blkdev' option + * + * If this request is answered with an error code of ENOSYS, this is + * treated as a permanent failure, i.e. all future bmap() requests will + * fail with the same error code without being send to the filesystem + * process. + * + * Valid replies: + * fuse_reply_bmap + * fuse_reply_err + * + * @param req request handle + * @param ino the inode number + * @param blocksize unit of block index + * @param idx block index within file + */ + void (*bmap) (fuse_req_t req, fuse_ino_t ino, size_t blocksize, + uint64_t idx); + +#if FUSE_USE_VERSION < 35 + void (*ioctl) (fuse_req_t req, fuse_ino_t ino, int cmd, + void *arg, struct fuse_file_info *fi, unsigned flags, + const void *in_buf, size_t in_bufsz, size_t out_bufsz); +#else + /** + * Ioctl + * + * Note: For unrestricted ioctls (not allowed for FUSE + * servers), data in and out areas can be discovered by giving + * iovs and setting FUSE_IOCTL_RETRY in *flags*. For + * restricted ioctls, kernel prepares in/out data area + * according to the information encoded in cmd. + * + * Valid replies: + * fuse_reply_ioctl_retry + * fuse_reply_ioctl + * fuse_reply_ioctl_iov + * fuse_reply_err + * + * @param req request handle + * @param ino the inode number + * @param cmd ioctl command + * @param arg ioctl argument + * @param fi file information + * @param flags for FUSE_IOCTL_* flags + * @param in_buf data fetched from the caller + * @param in_bufsz number of fetched bytes + * @param out_bufsz maximum size of output data + * + * Note : the unsigned long request submitted by the application + * is truncated to 32 bits. + */ + void (*ioctl) (fuse_req_t req, fuse_ino_t ino, unsigned int cmd, + void *arg, struct fuse_file_info *fi, unsigned flags, + const void *in_buf, size_t in_bufsz, size_t out_bufsz); +#endif + + /** + * Poll for IO readiness + * + * The client should immediately respond with fuse_reply_poll(), + * setting revents appropriately according to which events are ready. + * + * Additionally, if ph is non-NULL, the client must retain it and + * notify when all future IO readiness events occur by calling + * fuse_lowlevel_notify_poll() with the specified ph. + * + * Regardless of the number of times poll with a non-NULL ph is + * received, a single notify_poll is enough to service all. (Notifying + * more times incurs overhead but doesn't harm correctness.) Any + * additional received handles can be immediately destroyed. + * + * The callee is responsible for destroying ph with + * fuse_pollhandle_destroy() when no longer in use. + * + * If this request is answered with an error code of ENOSYS, this is + * treated as success (with a kernel-defined default poll-mask) and + * future calls to poll() will succeed the same way without being send + * to the filesystem process. + * + * Valid replies: + * fuse_reply_poll + * fuse_reply_err + * + * @param req request handle + * @param ino the inode number + * @param fi file information + * @param ph poll handle to be used for notification + */ + void (*poll) (fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi, + struct fuse_pollhandle *ph); + + /** + * Write data made available in a buffer + * + * This is a more generic version of the ->write() method. If + * FUSE_CAP_SPLICE_READ is set in fuse_conn_info.want and the + * kernel supports splicing from the fuse device, then the + * data will be made available in pipe for supporting zero + * copy data transfer. + * + * buf->count is guaranteed to be one (and thus buf->idx is + * always zero). The write_buf handler must ensure that + * bufv->off is correctly updated (reflecting the number of + * bytes read from bufv->buf[0]). + * + * Unless FUSE_CAP_HANDLE_KILLPRIV is disabled, this method is + * expected to reset the setuid and setgid bits. + * + * Valid replies: + * fuse_reply_write + * fuse_reply_err + * + * @param req request handle + * @param ino the inode number + * @param bufv buffer containing the data + * @param off offset to write to + * @param fi file information + */ + void (*write_buf) (fuse_req_t req, fuse_ino_t ino, + struct fuse_bufvec *bufv, off_t off, + struct fuse_file_info *fi); + + /** + * Callback function for the retrieve request + * + * Valid replies: + * fuse_reply_none + * + * @param req request handle + * @param cookie user data supplied to fuse_lowlevel_notify_retrieve() + * @param ino the inode number supplied to fuse_lowlevel_notify_retrieve() + * @param offset the offset supplied to fuse_lowlevel_notify_retrieve() + * @param bufv the buffer containing the returned data + */ + void (*retrieve_reply) (fuse_req_t req, void *cookie, fuse_ino_t ino, + off_t offset, struct fuse_bufvec *bufv); + + /** + * Forget about multiple inodes + * + * See description of the forget function for more + * information. + * + * Valid replies: + * fuse_reply_none + * + * @param req request handle + */ + void (*forget_multi) (fuse_req_t req, size_t count, + struct fuse_forget_data *forgets); + + /** + * Acquire, modify or release a BSD file lock + * + * Note: if the locking methods are not implemented, the kernel + * will still allow file locking to work locally. Hence these are + * only interesting for network filesystems and similar. + * + * Valid replies: + * fuse_reply_err + * + * @param req request handle + * @param ino the inode number + * @param fi file information + * @param op the locking operation, see flock(2) + */ + void (*flock) (fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info *fi, int op); + + /** + * Allocate requested space. If this function returns success then + * subsequent writes to the specified range shall not fail due to the lack + * of free space on the file system storage media. + * + * If this request is answered with an error code of ENOSYS, this is + * treated as a permanent failure with error code EOPNOTSUPP, i.e. all + * future fallocate() requests will fail with EOPNOTSUPP without being + * send to the filesystem process. + * + * Valid replies: + * fuse_reply_err + * + * @param req request handle + * @param ino the inode number + * @param offset starting point for allocated region + * @param length size of allocated region + * @param mode determines the operation to be performed on the given range, + * see fallocate(2) + */ + void (*fallocate) (fuse_req_t req, fuse_ino_t ino, int mode, + off_t offset, off_t length, struct fuse_file_info *fi); + + /** + * Read directory with attributes + * + * Send a buffer filled using fuse_add_direntry_plus(), with size not + * exceeding the requested size. Send an empty buffer on end of + * stream. + * + * fi->fh will contain the value set by the opendir method, or + * will be undefined if the opendir method didn't set any value. + * + * In contrast to readdir() (which does not affect the lookup counts), + * the lookup count of every entry returned by readdirplus(), except "." + * and "..", is incremented by one. + * + * Valid replies: + * fuse_reply_buf + * fuse_reply_data + * fuse_reply_err + * + * @param req request handle + * @param ino the inode number + * @param size maximum number of bytes to send + * @param off offset to continue reading the directory stream + * @param fi file information + */ + void (*readdirplus) (fuse_req_t req, fuse_ino_t ino, size_t size, off_t off, + struct fuse_file_info *fi); + + /** + * Copy a range of data from one file to another + * + * Performs an optimized copy between two file descriptors without the + * additional cost of transferring data through the FUSE kernel module + * to user space (glibc) and then back into the FUSE filesystem again. + * + * In case this method is not implemented, glibc falls back to reading + * data from the source and writing to the destination. Effectively + * doing an inefficient copy of the data. + * + * If this request is answered with an error code of ENOSYS, this is + * treated as a permanent failure with error code EOPNOTSUPP, i.e. all + * future copy_file_range() requests will fail with EOPNOTSUPP without + * being send to the filesystem process. + * + * Valid replies: + * fuse_reply_write + * fuse_reply_err + * + * @param req request handle + * @param ino_in the inode number or the source file + * @param off_in starting point from were the data should be read + * @param fi_in file information of the source file + * @param ino_out the inode number or the destination file + * @param off_out starting point where the data should be written + * @param fi_out file information of the destination file + * @param len maximum size of the data to copy + * @param flags passed along with the copy_file_range() syscall + */ + void (*copy_file_range) (fuse_req_t req, fuse_ino_t ino_in, + off_t off_in, struct fuse_file_info *fi_in, + fuse_ino_t ino_out, off_t off_out, + struct fuse_file_info *fi_out, size_t len, + int flags); + + /** + * Find next data or hole after the specified offset + * + * If this request is answered with an error code of ENOSYS, this is + * treated as a permanent failure, i.e. all future lseek() requests will + * fail with the same error code without being send to the filesystem + * process. + * + * Valid replies: + * fuse_reply_lseek + * fuse_reply_err + * + * @param req request handle + * @param ino the inode number + * @param off offset to start search from + * @param whence either SEEK_DATA or SEEK_HOLE + * @param fi file information + */ + void (*lseek) (fuse_req_t req, fuse_ino_t ino, off_t off, int whence, + struct fuse_file_info *fi); + + /** + * Create a tempfile + * + * Tempfile means an anonymous file. It can be made into a normal file later + * by using linkat or such. + * + * If this is answered with an error ENOSYS this is treated by the kernel as + * a permanent failure and it will disable the feature and not ask again. + * + * Valid replies: + * fuse_reply_create + * fuse_reply_err + * + * @param req request handle + * @param parent inode number of the parent directory + * @param mode file type and mode with which to create the new file + * @param fi file information + */ + void (*tmpfile) (fuse_req_t req, fuse_ino_t parent, + mode_t mode, struct fuse_file_info *fi); + + /** + * Get extended file attributes. + * + * Valid replies: + * fuse_reply_statx + * fuse_reply_err + * + * @param req request handle + * @param ino the inode number + * @param flags bitmask of requested flags + * @param mask bitmask of requested fields + * @param fi file information (may be NULL) + */ + void (*statx)(fuse_req_t req, fuse_ino_t ino, int flags, int mask, + struct fuse_file_info *fi); +}; + +/** + * Reply with an error code or success. + * + * Possible requests: + * all except forget, forget_multi, retrieve_reply + * + * Wherever possible, error codes should be chosen from the list of + * documented error conditions in the corresponding system calls + * manpage. + * + * An error code of ENOSYS is sometimes treated specially. This is + * indicated in the documentation of the affected handler functions. + * + * The following requests may be answered with a zero error code: + * unlink, rmdir, rename, flush, release, fsync, fsyncdir, setxattr, + * removexattr, setlk. + * + * @param req request handle + * @param err the positive error value, or zero for success + * @return zero for success, -errno for failure to send reply + */ +int fuse_reply_err(fuse_req_t req, int err); + +/** + * Don't send reply + * + * Possible requests: + * forget + * forget_multi + * retrieve_reply + * + * @param req request handle + */ +void fuse_reply_none(fuse_req_t req); + +/** + * Reply with a directory entry + * + * Possible requests: + * lookup, mknod, mkdir, symlink, link + * + * Side effects: + * increments the lookup count on success + * + * @param req request handle + * @param e the entry parameters + * @return zero for success, -errno for failure to send reply + */ +int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e); + +/** + * Reply with a directory entry and open parameters + * + * currently the following members of 'fi' are used: + * fh, direct_io, keep_cache, cache_readdir, nonseekable, noflush, + * parallel_direct_writes + * + * Possible requests: + * create + * + * Side effects: + * increments the lookup count on success + * + * @param req request handle + * @param e the entry parameters + * @param fi file information + * @return zero for success, -errno for failure to send reply + */ +int fuse_reply_create(fuse_req_t req, const struct fuse_entry_param *e, + const struct fuse_file_info *fi); + +/** + * Reply with attributes + * + * Possible requests: + * getattr, setattr + * + * @param req request handle + * @param attr the attributes + * @param attr_timeout validity timeout (in seconds) for the attributes + * @return zero for success, -errno for failure to send reply + */ +int fuse_reply_attr(fuse_req_t req, const struct stat *attr, + double attr_timeout); + +/** + * Reply with the contents of a symbolic link + * + * Possible requests: + * readlink + * + * @param req request handle + * @param link symbolic link contents + * @return zero for success, -errno for failure to send reply + */ +int fuse_reply_readlink(fuse_req_t req, const char *link); + +/** + * Setup passthrough backing file for open reply + * + * Currently there should be only one backing id per node / backing file. + * + * Possible requests: + * open, opendir, create + * + * @param req request handle + * @param fd backing file descriptor + * @return positive backing id for success, 0 for failure + */ +int fuse_passthrough_open(fuse_req_t req, int fd); +int fuse_passthrough_close(fuse_req_t req, int backing_id); + +/** + * Reply with open parameters + * + * currently the following members of 'fi' are used: + * fh, direct_io, keep_cache, cache_readdir, nonseekable, noflush, + * parallel_direct_writes, + * + * Possible requests: + * open, opendir + * + * @param req request handle + * @param fi file information + * @return zero for success, -errno for failure to send reply + */ +int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi); + +/** + * Reply with number of bytes written + * + * Possible requests: + * write + * + * @param req request handle + * @param count the number of bytes written + * @return zero for success, -errno for failure to send reply + */ +int fuse_reply_write(fuse_req_t req, size_t count); + +/** + * Reply with data + * + * Possible requests: + * read, readdir, getxattr, listxattr + * + * @param req request handle + * @param buf buffer containing data + * @param size the size of data in bytes + * @return zero for success, -errno for failure to send reply + */ +int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size); + +/** + * Reply with data copied/moved from buffer(s) + * + * Zero copy data transfer ("splicing") will be used under + * the following circumstances: + * + * 1. FUSE_CAP_SPLICE_WRITE is set in fuse_conn_info.want, and + * 2. the kernel supports splicing from the fuse device + * (FUSE_CAP_SPLICE_WRITE is set in fuse_conn_info.capable), and + * 3. *flags* does not contain FUSE_BUF_NO_SPLICE + * 4. The amount of data that is provided in file-descriptor backed + * buffers (i.e., buffers for which bufv[n].flags == FUSE_BUF_FD) + * is at least twice the page size. + * + * In order for SPLICE_F_MOVE to be used, the following additional + * conditions have to be fulfilled: + * + * 1. FUSE_CAP_SPLICE_MOVE is set in fuse_conn_info.want, and + * 2. the kernel supports it (i.e, FUSE_CAP_SPLICE_MOVE is set in + fuse_conn_info.capable), and + * 3. *flags* contains FUSE_BUF_SPLICE_MOVE + * + * Note that, if splice is used, the data is actually spliced twice: + * once into a temporary pipe (to prepend header data), and then again + * into the kernel. If some of the provided buffers are memory-backed, + * the data in them is copied in step one and spliced in step two. + * + * The FUSE_BUF_SPLICE_FORCE_SPLICE and FUSE_BUF_SPLICE_NONBLOCK flags + * are silently ignored. + * + * Possible requests: + * read, readdir, getxattr, listxattr + * + * Side effects: + * when used to return data from a readdirplus() (but not readdir()) + * call, increments the lookup count of each returned entry by one + * on success. + * + * @param req request handle + * @param bufv buffer vector + * @param flags flags controlling the copy + * @return zero for success, -errno for failure to send reply + */ +int fuse_reply_data(fuse_req_t req, struct fuse_bufvec *bufv, + enum fuse_buf_copy_flags flags); + +/** + * Reply with data vector + * + * Possible requests: + * read, readdir, getxattr, listxattr + * + * @param req request handle + * @param iov the vector containing the data + * @param count the size of vector + * @return zero for success, -errno for failure to send reply + */ +int fuse_reply_iov(fuse_req_t req, const struct iovec *iov, int count); + +/** + * Reply with filesystem statistics + * + * Possible requests: + * statfs + * + * @param req request handle + * @param stbuf filesystem statistics + * @return zero for success, -errno for failure to send reply + */ +int fuse_reply_statfs(fuse_req_t req, const struct statvfs *stbuf); + +/** + * Reply with needed buffer size + * + * Possible requests: + * getxattr, listxattr + * + * @param req request handle + * @param count the buffer size needed in bytes + * @return zero for success, -errno for failure to send reply + */ +int fuse_reply_xattr(fuse_req_t req, size_t count); + +/** + * Reply with file lock information + * + * Possible requests: + * getlk + * + * @param req request handle + * @param lock the lock information + * @return zero for success, -errno for failure to send reply + */ +int fuse_reply_lock(fuse_req_t req, const struct flock *lock); + +/** + * Reply with block index + * + * Possible requests: + * bmap + * + * @param req request handle + * @param idx block index within device + * @return zero for success, -errno for failure to send reply + */ +int fuse_reply_bmap(fuse_req_t req, uint64_t idx); + +/* ----------------------------------------------------------- * + * Filling a buffer in readdir * + * ----------------------------------------------------------- */ + +/** + * Add a directory entry to the buffer + * + * Buffer needs to be large enough to hold the entry. If it's not, + * then the entry is not filled in but the size of the entry is still + * returned. The caller can check this by comparing the bufsize + * parameter with the returned entry size. If the entry size is + * larger than the buffer size, the operation failed. + * + * From the 'stbuf' argument the st_ino field and bits 12-15 of the + * st_mode field are used. The other fields are ignored. + * + * *off* should be any non-zero value that the filesystem can use to + * identify the current point in the directory stream. It does not + * need to be the actual physical position. A value of zero is + * reserved to mean "from the beginning", and should therefore never + * be used (the first call to fuse_add_direntry should be passed the + * offset of the second directory entry). + * + * @param req request handle + * @param buf the point where the new entry will be added to the buffer + * @param bufsize remaining size of the buffer + * @param name the name of the entry + * @param stbuf the file attributes + * @param off the offset of the next entry + * @return the space needed for the entry + */ +size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize, + const char *name, const struct stat *stbuf, + off_t off); + +/** + * Add a directory entry to the buffer with the attributes + * + * See documentation of `fuse_add_direntry()` for more details. + * + * @param req request handle + * @param buf the point where the new entry will be added to the buffer + * @param bufsize remaining size of the buffer + * @param name the name of the entry + * @param e the directory entry + * @param off the offset of the next entry + * @return the space needed for the entry + */ +size_t fuse_add_direntry_plus(fuse_req_t req, char *buf, size_t bufsize, + const char *name, + const struct fuse_entry_param *e, off_t off); + +/** + * Reply to ask for data fetch and output buffer preparation. ioctl + * will be retried with the specified input data fetched and output + * buffer prepared. + * + * Possible requests: + * ioctl + * + * @param req request handle + * @param in_iov iovec specifying data to fetch from the caller + * @param in_count number of entries in in_iov + * @param out_iov iovec specifying addresses to write output to + * @param out_count number of entries in out_iov + * @return zero for success, -errno for failure to send reply + */ +int fuse_reply_ioctl_retry(fuse_req_t req, + const struct iovec *in_iov, size_t in_count, + const struct iovec *out_iov, size_t out_count); + +/** + * Reply to finish ioctl + * + * Possible requests: + * ioctl + * + * @param req request handle + * @param result result to be passed to the caller + * @param buf buffer containing output data + * @param size length of output data + */ +int fuse_reply_ioctl(fuse_req_t req, int result, const void *buf, size_t size); + +/** + * Reply to finish ioctl with iov buffer + * + * Possible requests: + * ioctl + * + * @param req request handle + * @param result result to be passed to the caller + * @param iov the vector containing the data + * @param count the size of vector + */ +int fuse_reply_ioctl_iov(fuse_req_t req, int result, const struct iovec *iov, + int count); + +/** + * Reply with poll result event mask + * + * @param req request handle + * @param revents poll result event mask + */ +int fuse_reply_poll(fuse_req_t req, unsigned revents); + +/** + * Reply with offset + * + * Possible requests: + * lseek + * + * @param req request handle + * @param off offset of next data or hole + * @return zero for success, -errno for failure to send reply + */ +int fuse_reply_lseek(fuse_req_t req, off_t off); + +/** + * Reply with extended file attributes. + * + * Possible requests: + * statx + * + * @param req request handle + * @param flags statx flags + * @param statx the attributes + * @param attr_timeout validity timeout (in seconds) for the attributes + * @return zero for success, -errno for failure to send reply + */ +int fuse_reply_statx(fuse_req_t req, int flags, struct statx *statx, double attr_timeout); + +/* ----------------------------------------------------------- * + * Notification * + * ----------------------------------------------------------- */ + +/** + * Notify IO readiness event + * + * For more information, please read comment for poll operation. + * + * @param ph poll handle to notify IO readiness event for + */ +int fuse_lowlevel_notify_poll(struct fuse_pollhandle *ph); + +/** + * Notify to invalidate cache for an inode. + * + * Added in FUSE protocol version 7.12. If the kernel does not support + * this (or a newer) version, the function will return -ENOSYS and do + * nothing. + * + * If the filesystem has writeback caching enabled, invalidating an + * inode will first trigger a writeback of all dirty pages. The call + * will block until all writeback requests have completed and the + * inode has been invalidated. It will, however, not wait for + * completion of pending writeback requests that have been issued + * before. + * + * If there are no dirty pages, this function will never block. + * + * @param se the session object + * @param ino the inode number + * @param off the offset in the inode where to start invalidating + * or negative to invalidate attributes only + * @param len the amount of cache to invalidate or 0 for all + * @return zero for success, -errno for failure + */ +int fuse_lowlevel_notify_inval_inode(struct fuse_session *se, fuse_ino_t ino, + off_t off, off_t len); + +/** + * Notify to increment the epoch for the current + * + * Each fuse connection has an 'epoch', which is initialized during INIT. + * Caching will then be validated against the epoch value: if the current epoch + * is higher than an object being revalidated, the object is invalid. + * + * This function simply increment the current epoch value. + * + * @param se the session object + * @return zero for success, -errno for failure + */ +int fuse_lowlevel_notify_increment_epoch(struct fuse_session *se); + +/** + * Notify to invalidate parent attributes and the dentry matching parent/name + * + * To avoid a deadlock this function must not be called in the + * execution path of a related filesystem operation or within any code + * that could hold a lock that could be needed to execute such an + * operation. As of kernel 4.18, a "related operation" is a lookup(), + * symlink(), mknod(), mkdir(), unlink(), rename(), link() or create() + * request for the parent, and a setattr(), unlink(), rmdir(), + * rename(), setxattr(), removexattr(), readdir() or readdirplus() + * request for the inode itself. + * + * When called correctly, this function will never block. + * + * Added in FUSE protocol version 7.12. If the kernel does not support + * this (or a newer) version, the function will return -ENOSYS and do + * nothing. + * + * @param se the session object + * @param parent inode number + * @param name file name + * @param namelen strlen() of file name + * @return zero for success, -errno for failure + */ +int fuse_lowlevel_notify_inval_entry(struct fuse_session *se, fuse_ino_t parent, + const char *name, size_t namelen); + +/** + * Notify to expire parent attributes and the dentry matching parent/name + * + * Same restrictions apply as for fuse_lowlevel_notify_inval_entry() + * + * Compared to invalidating an entry, expiring the entry results not in a + * forceful removal of that entry from kernel cache but instead the next access + * to it forces a lookup from the filesystem. + * + * This makes a difference for overmounted dentries, where plain invalidation + * would detach all submounts before dropping the dentry from the cache. + * If only expiry is set on the dentry, then any overmounts are left alone and + * until ->d_revalidate() is called. + * + * Note: ->d_revalidate() is not called for the case of following a submount, + * so invalidation will only be triggered for the non-overmounted case. + * The dentry could also be mounted in a different mount instance, in which case + * any submounts will still be detached. + * + * Added in FUSE protocol version 7.38. If the kernel does not support + * this (or a newer) version, the function will return -ENOSYS and do nothing. + * + * @param se the session object + * @param parent inode number + * @param name file name + * @param namelen strlen() of file name + * @return zero for success, -errno for failure, -enosys if no kernel support +*/ +int fuse_lowlevel_notify_expire_entry(struct fuse_session *se, fuse_ino_t parent, + const char *name, size_t namelen); + +/** + * This function behaves like fuse_lowlevel_notify_inval_entry() with + * the following additional effect (at least as of Linux kernel 4.8): + * + * If the provided *child* inode matches the inode that is currently + * associated with the cached dentry, and if there are any inotify + * watches registered for the dentry, then the watchers are informed + * that the dentry has been deleted. + * + * To avoid a deadlock this function must not be called while + * executing a related filesystem operation or while holding a lock + * that could be needed to execute such an operation (see the + * description of fuse_lowlevel_notify_inval_entry() for more + * details). + * + * When called correctly, this function will never block. + * + * Added in FUSE protocol version 7.18. If the kernel does not support + * this (or a newer) version, the function will return -ENOSYS and do + * nothing. + * + * @param se the session object + * @param parent inode number + * @param child inode number + * @param name file name + * @param namelen strlen() of file name + * @return zero for success, -errno for failure + */ +int fuse_lowlevel_notify_delete(struct fuse_session *se, + fuse_ino_t parent, fuse_ino_t child, + const char *name, size_t namelen); + +/** + * Store data to the kernel buffers + * + * Synchronously store data in the kernel buffers belonging to the + * given inode. The stored data is marked up-to-date (no read will be + * performed against it, unless it's invalidated or evicted from the + * cache). + * + * If the stored data overflows the current file size, then the size + * is extended, similarly to a write(2) on the filesystem. + * + * If this function returns an error, then the store wasn't fully + * completed, but it may have been partially completed. + * + * Added in FUSE protocol version 7.15. If the kernel does not support + * this (or a newer) version, the function will return -ENOSYS and do + * nothing. + * + * @param se the session object + * @param ino the inode number + * @param offset the starting offset into the file to store to + * @param bufv buffer vector + * @param flags flags controlling the copy + * @return zero for success, -errno for failure + */ +int fuse_lowlevel_notify_store(struct fuse_session *se, fuse_ino_t ino, + off_t offset, struct fuse_bufvec *bufv, + enum fuse_buf_copy_flags flags); +/** + * Retrieve data from the kernel buffers + * + * Retrieve data in the kernel buffers belonging to the given inode. + * If successful then the retrieve_reply() method will be called with + * the returned data. + * + * Only present pages are returned in the retrieve reply. Retrieving + * stops when it finds a non-present page and only data prior to that + * is returned. + * + * If this function returns an error, then the retrieve will not be + * completed and no reply will be sent. + * + * This function doesn't change the dirty state of pages in the kernel + * buffer. For dirty pages the write() method will be called + * regardless of having been retrieved previously. + * + * Added in FUSE protocol version 7.15. If the kernel does not support + * this (or a newer) version, the function will return -ENOSYS and do + * nothing. + * + * @param se the session object + * @param ino the inode number + * @param size the number of bytes to retrieve + * @param offset the starting offset into the file to retrieve from + * @param cookie user data to supply to the reply callback + * @return zero for success, -errno for failure + */ +int fuse_lowlevel_notify_retrieve(struct fuse_session *se, fuse_ino_t ino, + size_t size, off_t offset, void *cookie); + + +/* ----------------------------------------------------------- * + * Utility functions * + * ----------------------------------------------------------- */ + +/** + * Get the userdata from the request + * + * @param req request handle + * @return the user data passed to fuse_session_new() + */ +void *fuse_req_userdata(fuse_req_t req); + +/** + * Get the context from the request + * + * The pointer returned by this function will only be valid for the + * request's lifetime + * + * @param req request handle + * @return the context structure + */ +const struct fuse_ctx *fuse_req_ctx(fuse_req_t req); + +/** + * Get the current supplementary group IDs for the specified request + * + * Similar to the getgroups(2) system call, except the return value is + * always the total number of group IDs, even if it is larger than the + * specified size. + * + * The current fuse kernel module in linux (as of 2.6.30) doesn't pass + * the group list to userspace, hence this function needs to parse + * "/proc/$TID/task/$TID/status" to get the group IDs. + * + * This feature may not be supported on all operating systems. In + * such a case this function will return -ENOSYS. + * + * @param req request handle + * @param size size of given array + * @param list array of group IDs to be filled in + * @return the total number of supplementary group IDs or -errno on failure + */ +int fuse_req_getgroups(fuse_req_t req, int size, gid_t list[]); + +/** + * Callback function for an interrupt + * + * @param req interrupted request + * @param data user data + */ +typedef void (*fuse_interrupt_func_t)(fuse_req_t req, void *data); + +/** + * Register/unregister callback for an interrupt + * + * If an interrupt has already happened, then the callback function is + * called from within this function, hence it's not possible for + * interrupts to be lost. + * + * @param req request handle + * @param func the callback function or NULL for unregister + * @param data user data passed to the callback function + */ +void fuse_req_interrupt_func(fuse_req_t req, fuse_interrupt_func_t func, + void *data); + +/** + * Check if a request has already been interrupted + * + * @param req request handle + * @return 1 if the request has been interrupted, 0 otherwise + */ +int fuse_req_interrupted(fuse_req_t req); + + +/* ----------------------------------------------------------- * + * Inquiry functions * + * ----------------------------------------------------------- */ + +/** + * Print low-level version information to stdout. + */ +void fuse_lowlevel_version(void); + +/** + * Print available low-level options to stdout. This is not an + * exhaustive list, but includes only those options that may be of + * interest to an end-user of a file system. + */ +void fuse_lowlevel_help(void); + +/** + * Print available options for `fuse_parse_cmdline()`. + */ +void fuse_cmdline_help(void); + +/* ----------------------------------------------------------- * + * Filesystem setup & teardown * + * ----------------------------------------------------------- */ + +/** + * Note: Any addition to this struct needs to create a compatibility symbol + * for fuse_parse_cmdline(). For ABI compatibility reasons it is also + * not possible to remove struct members. + */ +struct fuse_cmdline_opts { + int singlethread; + int foreground; + int debug; + int nodefault_subtype; + char *mountpoint; + int show_version; + int show_help; + int clone_fd; + unsigned int max_idle_threads; /* discouraged, due to thread + * destruct overhead */ + + /* Added in libfuse-3.12 */ + unsigned int max_threads; +}; + +/** + * Utility function to parse common options for simple file systems + * using the low-level API. A help text that describes the available + * options can be printed with `fuse_cmdline_help`. A single + * non-option argument is treated as the mountpoint. Multiple + * non-option arguments will result in an error. + * + * If neither -o subtype= or -o fsname= options are given, a new + * subtype option will be added and set to the basename of the program + * (the fsname will remain unset, and then defaults to "fuse"). + * + * Known options will be removed from *args*, unknown options will + * remain. + * + * @param args argument vector (input+output) + * @param opts output argument for parsed options + * @return 0 on success, -1 on failure + */ +#if (defined(LIBFUSE_BUILT_WITH_VERSIONED_SYMBOLS)) +int fuse_parse_cmdline(struct fuse_args *args, + struct fuse_cmdline_opts *opts); +#else +#if FUSE_USE_VERSION < FUSE_MAKE_VERSION(3, 12) +int fuse_parse_cmdline_30(struct fuse_args *args, + struct fuse_cmdline_opts *opts); +#define fuse_parse_cmdline(args, opts) fuse_parse_cmdline_30(args, opts) +#else +int fuse_parse_cmdline_312(struct fuse_args *args, + struct fuse_cmdline_opts *opts); +#define fuse_parse_cmdline(args, opts) fuse_parse_cmdline_312(args, opts) +#endif +#endif + +/* Do not call this directly, use fuse_session_new() instead */ +struct fuse_session * +fuse_session_new_versioned(struct fuse_args *args, + const struct fuse_lowlevel_ops *op, size_t op_size, + struct libfuse_version *version, void *userdata); + +/** + * Create a low level session. + * + * Returns a session structure suitable for passing to + * fuse_session_mount() and fuse_session_loop(). + * + * This function accepts most file-system independent mount options + * (like context, nodev, ro - see mount(8)), as well as the general + * fuse mount options listed in mount.fuse(8) (e.g. -o allow_root and + * -o default_permissions, but not ``-o use_ino``). Instead of `-o + * debug`, debugging may also enabled with `-d` or `--debug`. + * + * If not all options are known, an error message is written to stderr + * and the function returns NULL. + * + * To create a no-op session just for mounting pass op as NULL. + * + * Option parsing skips argv[0], which is assumed to contain the + * program name. To prevent accidentally passing an option in + * argv[0], this element must always be present (even if no options + * are specified). It may be set to the empty string ('\0') if no + * reasonable value can be provided. + * + * @param args argument vector + * @param op the (low-level) filesystem operations + * @param op_size sizeof(struct fuse_lowlevel_ops) + * @param version the libfuse version a file system server was compiled against + * @param userdata user data + * @return the fuse session on success, NULL on failure + **/ +static inline struct fuse_session * +fuse_session_new_fn(struct fuse_args *args, const struct fuse_lowlevel_ops *op, + size_t op_size, void *userdata) +{ + struct libfuse_version version = { + .major = FUSE_MAJOR_VERSION, + .minor = FUSE_MINOR_VERSION, + .hotfix = FUSE_HOTFIX_VERSION, + .padding = 0 + }; + + return fuse_session_new_versioned(args, op, op_size, &version, + userdata); +} +#define fuse_session_new(args, op, op_size, userdata) \ + fuse_session_new_fn(args, op, op_size, userdata) + +/* + * This should mostly not be called directly, but instead the + * fuse_session_custom_io() should be used. + */ +int fuse_session_custom_io_317(struct fuse_session *se, + const struct fuse_custom_io *io, size_t op_size, int fd); + +/** + * Set a file descriptor for the session. + * + * This function can be used if you want to have a custom communication + * interface instead of using a mountpoint. In practice, this means that instead + * of calling fuse_session_mount() and fuse_session_unmount(), one could call + * fuse_session_custom_io() where fuse_session_mount() would have otherwise been + * called. + * + * In `io`, implementations for read and writev MUST be provided. Otherwise -1 + * will be returned and `fd` will not be used. Implementations for `splice_send` + * and `splice_receive` are optional. If they are not provided splice will not + * be used for send or receive respectively. + * + * The provided file descriptor `fd` will be closed when fuse_session_destroy() + * is called. + * + * Requires a library built with -Denable-custom-io=true. The peer at the other + * end of `fd` takes the place of the kernel as the sender of requests, so it + * owes the session the validation the kernel would have done -- libfuse does + * not repeat it. A request whose length or count field exceeds the bytes that + * actually arrived makes the handlers read past the receive buffer and crash + * the filesystem process. + * + * @param se session object + * @param io Custom io to use when retrieving/sending requests/responses + * @param fd file descriptor for the session + * + * @return 0 on success + * @return -EINVAL if `io`, `io->read` or `ìo->writev` are NULL + * @return -EBADF if `fd` was smaller than 0 + * @return -errno if failed to allocate memory to store `io` + * @return -ENOTSUP if the library was built without custom io + * + **/ +#if FUSE_MAKE_VERSION(3, 17) <= FUSE_USE_VERSION +static inline int fuse_session_custom_io(struct fuse_session *se, + const struct fuse_custom_io *io, size_t op_size, int fd) +{ + return fuse_session_custom_io_317(se, io, op_size, fd); +} +#else +static inline int fuse_session_custom_io(struct fuse_session *se, + const struct fuse_custom_io *io, int fd) +{ + return fuse_session_custom_io_317(se, io, + offsetof(struct fuse_custom_io, clone_fd), fd); +} +#endif + +/** + * Mount a FUSE file system. + * + * @param mountpoint the mount point path + * @param se session object + * + * @return 0 on success, -1 on failure. + **/ +int fuse_session_mount(struct fuse_session *se, const char *mountpoint); + +/** + * Enter a single threaded, blocking event loop. + * + * When the event loop terminates because the connection to the FUSE + * kernel module has been closed, this function returns zero. This + * happens when the filesystem is unmounted regularly (by the + * filesystem owner or root running the umount(8) or fusermount(1) + * command), or if connection is explicitly severed by writing ``1`` + * to the``abort`` file in ``/sys/fs/fuse/connections/NNN``. The only + * way to distinguish between these two conditions is to check if the + * filesystem is still mounted after the session loop returns. + * + * When some error occurs during request processing, the function + * returns a negated errno(3) value. + * + * If the loop has been terminated because of a signal handler + * installed by fuse_set_signal_handlers(), this function returns the + * (positive) signal value that triggered the exit. + * + * @param se the session + * @return 0, -errno, or a signal value + */ +int fuse_session_loop(struct fuse_session *se); + +#if FUSE_USE_VERSION < 32 + int fuse_session_loop_mt_31(struct fuse_session *se, int clone_fd); + #define fuse_session_loop_mt(se, clone_fd) fuse_session_loop_mt_31(se, clone_fd) +#elif FUSE_USE_VERSION < FUSE_MAKE_VERSION(3, 12) + int fuse_session_loop_mt_32(struct fuse_session *se, struct fuse_loop_config *config); + #define fuse_session_loop_mt(se, config) fuse_session_loop_mt_32(se, config) +#else + #if (defined(LIBFUSE_BUILT_WITH_VERSIONED_SYMBOLS)) + /** + * Enter a multi-threaded event loop. + * + * For a description of the return value and the conditions when the + * event loop exits, refer to the documentation of + * fuse_session_loop(). + * + * @param se the session + * @param config session loop configuration + * @return see fuse_session_loop() + */ + int fuse_session_loop_mt(struct fuse_session *se, struct fuse_loop_config *config); + #else + int fuse_session_loop_mt_312(struct fuse_session *se, struct fuse_loop_config *config); + #define fuse_session_loop_mt(se, config) fuse_session_loop_mt_312(se, config) + #endif +#endif + +/** + * Flag a session as terminated. + * + * This will cause any running event loop to terminate on the next opportunity. + * fuse_session_loop_mt(), and fuse_session_loop() from FUSE_USE_VERSION 3.19 + * on, are woken up directly and return without waiting for a request, no + * matter which thread calls this. + * + * The deprecated fuse_session_loop() below FUSE_USE_VERSION 3.19 is not woken + * up and only notices the flag once the next request is received, which may be + * far in the future if the filesystem is not currently being used by any + * clients. Sending a signal to its thread afterwards avoids that delay - if + * fuse_set_signal_handlers() is used, SIGPIPE will cause the thread to wake-up + * but otherwise be ignored. + * + * @param se the session + */ +void fuse_session_exit(struct fuse_session *se); + +/** + * Reset the terminated flag of a session + * + * @param se the session + */ +void fuse_session_reset(struct fuse_session *se); + +/** + * Query the terminated flag of a session + * + * @param se the session + * @return 1 if exited, 0 if not exited + */ +int fuse_session_exited(struct fuse_session *se); + +/** + * Ensure that file system is unmounted. + * + * In regular operation, the file system is typically unmounted by the + * user calling umount(8) or fusermount(1), which then terminates the + * FUSE session loop. However, the session loop may also terminate as + * a result of an explicit call to fuse_session_exit() (e.g. by a + * signal handler installed by fuse_set_signal_handler()). In this + * case the filesystem remains mounted, but any attempt to access it + * will block (while the filesystem process is still running) or give + * an ESHUTDOWN error (after the filesystem process has terminated). + * + * If the communication channel with the FUSE kernel module is still + * open (i.e., if the session loop was terminated by an explicit call + * to fuse_session_exit()), this function will close it and unmount + * the filesystem. If the communication channel has been closed by the + * kernel, this method will do (almost) nothing. + * + * NOTE: The above semantics mean that if the connection to the kernel + * is terminated via the ``/sys/fs/fuse/connections/NNN/abort`` file, + * this method will *not* unmount the filesystem. + * + * @param se the session + */ +void fuse_session_unmount(struct fuse_session *se); + +/** + * Destroy a session + * + * @param se the session + */ +void fuse_session_destroy(struct fuse_session *se); + +/* ----------------------------------------------------------- * + * Custom event loop support * + * ----------------------------------------------------------- */ + +/** + * Return file descriptor for communication with kernel. + * + * The file selector can be used to integrate FUSE with a custom event + * loop. Whenever data is available for reading on the provided fd, + * the event loop should call `fuse_session_receive_buf` followed by + * `fuse_session_process_buf` to process the request. + * + * The returned file descriptor is valid until `fuse_session_unmount` + * is called. + * + * @param se the session + * @return a file descriptor + */ +int fuse_session_fd(struct fuse_session *se); + +/** + * Process a raw request supplied in a generic buffer + * + * The fuse_buf may contain a memory buffer or a pipe file descriptor. + * + * @param se the session + * @param buf the fuse_buf containing the request + */ +void fuse_session_process_buf(struct fuse_session *se, + const struct fuse_buf *buf); + +/** + * Read a raw request from the kernel into the supplied buffer. + * + * Depending on file system options, system capabilities, and request + * size the request is either read into a memory buffer or spliced + * into a temporary pipe. + * + * @param se the session + * @param buf the fuse_buf to store the request in + * @return the actual size of the raw request, or -errno on error + */ +int fuse_session_receive_buf(struct fuse_session *se, struct fuse_buf *buf); + +/** + * Check if the request is submitted through fuse-io-uring + */ +bool fuse_req_is_uring(fuse_req_t req); + +/** + * Get the payload of a request + * (for requests submitted through fuse-io-uring only) + * + * This is useful for a file system that wants to write data directly + * to the request buffer. With io-uring the req is the buffer owner + * and the file system can write directly to the buffer and avoid + * extra copying. For example useful for network file systems. + * + * @param req the request + * @param payload pointer to the payload + * @param payload_sz size of the payload + * @param mr memory registration handle, currently unused + * @return 0 on success, -errno on failure + */ +int fuse_req_get_payload(fuse_req_t req, char **payload, size_t *payload_sz, + void **mr); + +#ifdef __cplusplus +} +#endif + +#endif /* FUSE_LOWLEVEL_H_ */ diff --git a/include/fuse_mount_compat.h b/include/fuse_mount_compat.h new file mode 100644 index 0000000..be8d576 --- /dev/null +++ b/include/fuse_mount_compat.h @@ -0,0 +1,49 @@ +/* + FUSE: Filesystem in Userspace + Copyright (C) 2023 Giulio Benetti + + Logging API. + + This program can be distributed under the terms of the GNU LGPLv2. + See the file LICENSE +*/ + +#ifndef FUSE_MOUNT_COMPAT_H_ +#define FUSE_MOUNT_COMPAT_H_ + +#include + +/* Some libc don't define MS_*, so define them manually + * (values taken from https://elixir.bootlin.com/linux/v6.10/source/include/uapi/linux/mount.h#L13 on) + */ +#ifndef MS_DIRSYNC +#define MS_DIRSYNC 128 +#endif + +#ifndef MS_NOSYMFOLLOW +#define MS_NOSYMFOLLOW 256 +#endif + +#ifndef MS_REC +#define MS_REC 16384 +#endif + +#ifndef MS_PRIVATE +#define MS_PRIVATE (1<<18) +#endif + +#ifndef MS_LAZYTIME +#define MS_LAZYTIME (1<<25) +#endif + +#ifndef UMOUNT_DETACH +#define UMOUNT_DETACH 0x00000002 /* Just detach from the tree */ +#endif +#ifndef UMOUNT_NOFOLLOW +#define UMOUNT_NOFOLLOW 0x00000008 /* Don't follow symlink on umount */ +#endif +#ifndef UMOUNT_UNUSED +#define UMOUNT_UNUSED 0x80000000 /* Flag guaranteed to be unused */ +#endif + +#endif /* FUSE_MOUNT_COMPAT_H_ */ diff --git a/include/fuse_opt.h b/include/fuse_opt.h new file mode 100644 index 0000000..a9a41fe --- /dev/null +++ b/include/fuse_opt.h @@ -0,0 +1,271 @@ +/* + FUSE: Filesystem in Userspace + Copyright (C) 2001-2007 Miklos Szeredi + + This program can be distributed under the terms of the GNU LGPLv2. + See the file LGPL2.txt. +*/ + +#ifndef FUSE_OPT_H_ +#define FUSE_OPT_H_ + +/** @file + * + * This file defines the option parsing interface of FUSE + */ + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Option description + * + * This structure describes a single option, and action associated + * with it, in case it matches. + * + * More than one such match may occur, in which case the action for + * each match is executed. + * + * There are three possible actions in case of a match: + * + * i) An integer (int or unsigned) variable determined by 'offset' is + * set to 'value' + * + * ii) The processing function is called, with 'value' as the key + * + * iii) An integer (any) or string (char *) variable determined by + * 'offset' is set to the value of an option parameter + * + * 'offset' should normally be either set to + * + * - 'offsetof(struct foo, member)' actions i) and iii) + * + * - -1 action ii) + * + * The 'offsetof()' macro is defined in the header. + * + * The template determines which options match, and also have an + * effect on the action. Normally the action is either i) or ii), but + * if a format is present in the template, then action iii) is + * performed. + * + * The types of templates are: + * + * 1) "-x", "-foo", "--foo", "--foo-bar", etc. These match only + * themselves. Invalid values are "--" and anything beginning + * with "-o" + * + * 2) "foo", "foo-bar", etc. These match "-ofoo", "-ofoo-bar" or + * the relevant option in a comma separated option list + * + * 3) "bar=", "--foo=", etc. These are variations of 1) and 2) + * which have a parameter + * + * 4) "bar=%s", "--foo=%lu", etc. Same matching as above but perform + * action iii). + * + * 5) "-x ", etc. Matches either "-xparam" or "-x param" as + * two separate arguments + * + * 6) "-x %s", etc. Combination of 4) and 5) + * + * If the format is "%s", memory is allocated for the string unlike with + * scanf(). The previous value (if non-NULL) stored at the this location is + * freed. + */ +struct fuse_opt { + /** Matching template and optional parameter formatting */ + const char *templ; + + /** + * Offset of variable within 'data' parameter of fuse_opt_parse() + * or -1 + */ + unsigned long offset; + + /** + * Value to set the variable to, or to be passed as 'key' to the + * processing function. Ignored if template has a format + */ + int value; +}; + +/** + * Key option. In case of a match, the processing function will be + * called with the specified key. + */ +#define FUSE_OPT_KEY(templ, key) { templ, -1U, key } + +/** + * Last option. An array of 'struct fuse_opt' must end with a NULL + * template value + */ +#define FUSE_OPT_END { NULL, 0, 0 } + +/** + * Argument list + */ +struct fuse_args { + /** Argument count */ + int argc; + + /** Argument vector. NULL terminated */ + char **argv; + + /** Is 'argv' allocated? */ + int allocated; +}; + +/** + * Initializer for 'struct fuse_args' + */ +#define FUSE_ARGS_INIT(argc, argv) { argc, argv, 0 } + +/** + * Key value passed to the processing function if an option did not + * match any template + */ +#define FUSE_OPT_KEY_OPT -1 + +/** + * Key value passed to the processing function for all non-options + * + * Non-options are the arguments beginning with a character other than + * '-' or all arguments after the special '--' option + */ +#define FUSE_OPT_KEY_NONOPT -2 + +/** + * Special key value for options to keep + * + * Argument is not passed to processing function, but behave as if the + * processing function returned 1 + */ +#define FUSE_OPT_KEY_KEEP -3 + +/** + * Special key value for options to discard + * + * Argument is not passed to processing function, but behave as if the + * processing function returned zero + */ +#define FUSE_OPT_KEY_DISCARD -4 + +/** + * Processing function + * + * This function is called if + * - option did not match any 'struct fuse_opt' + * - argument is a non-option + * - option did match and offset was set to -1 + * + * The 'arg' parameter will always contain the whole argument or + * option including the parameter if exists. A two-argument option + * ("-x foo") is always converted to single argument option of the + * form "-xfoo" before this function is called. + * + * Options of the form '-ofoo' are passed to this function without the + * '-o' prefix. + * + * The return value of this function determines whether this argument + * is to be inserted into the output argument vector, or discarded. + * + * @param data is the user data passed to the fuse_opt_parse() function + * @param arg is the whole argument or option + * @param key determines why the processing function was called + * @param outargs the current output argument list + * @return -1 on error, 0 if arg is to be discarded, 1 if arg should be kept + */ +typedef int (*fuse_opt_proc_t)(void *data, const char *arg, int key, + struct fuse_args *outargs); + +/** + * Option parsing function + * + * If 'args' was returned from a previous call to fuse_opt_parse() or + * it was constructed from + * + * A NULL 'args' is equivalent to an empty argument vector + * + * A NULL 'opts' is equivalent to an 'opts' array containing a single + * end marker + * + * A NULL 'proc' is equivalent to a processing function always + * returning '1' + * + * @param args is the input and output argument list + * @param data is the user data + * @param opts is the option description array + * @param proc is the processing function + * @return -1 on error, 0 on success + */ +int fuse_opt_parse(struct fuse_args *args, void *data, + const struct fuse_opt opts[], fuse_opt_proc_t proc); + +/** + * Add an option to a comma separated option list + * + * @param opts is a pointer to an option list, may point to a NULL value + * @param opt is the option to add + * @return -1 on allocation error, 0 on success + */ +int fuse_opt_add_opt(char **opts, const char *opt); + +/** + * Add an option, escaping commas, to a comma separated option list + * + * @param opts is a pointer to an option list, may point to a NULL value + * @param opt is the option to add + * @return -1 on allocation error, 0 on success + */ +int fuse_opt_add_opt_escaped(char **opts, const char *opt); + +/** + * Add an argument to a NULL terminated argument vector + * + * @param args is the structure containing the current argument list + * @param arg is the new argument to add + * @return -1 on allocation error, 0 on success + */ +int fuse_opt_add_arg(struct fuse_args *args, const char *arg); + +/** + * Add an argument at the specified position in a NULL terminated + * argument vector + * + * Adds the argument to the N-th position. This is useful for adding + * options at the beginning of the array which must not come after the + * special '--' option. + * + * @param args is the structure containing the current argument list + * @param pos is the position at which to add the argument + * @param arg is the new argument to add + * @return -1 on allocation error, 0 on success + */ +int fuse_opt_insert_arg(struct fuse_args *args, int pos, const char *arg); + +/** + * Free the contents of argument list + * + * The structure itself is not freed + * + * @param args is the structure containing the argument list + */ +void fuse_opt_free_args(struct fuse_args *args); + + +/** + * Check if an option matches + * + * @param opts is the option description array + * @param opt is the option to match + * @return 1 if a match is found, 0 if not + */ +int fuse_opt_match(const struct fuse_opt opts[], const char *opt); + +#ifdef __cplusplus +} +#endif + +#endif /* FUSE_OPT_H_ */ diff --git a/include/meson.build b/include/meson.build new file mode 100644 index 0000000..bf67197 --- /dev/null +++ b/include/meson.build @@ -0,0 +1,4 @@ +libfuse_headers = [ 'fuse.h', 'fuse_common.h', 'fuse_lowlevel.h', + 'fuse_opt.h', 'cuse_lowlevel.h', 'fuse_log.h' ] + +install_headers(libfuse_headers, subdir: 'fuse3') diff --git a/lib/buffer.c b/lib/buffer.c new file mode 100644 index 0000000..2b72d95 --- /dev/null +++ b/lib/buffer.c @@ -0,0 +1,324 @@ +/* + FUSE: Filesystem in Userspace + Copyright (C) 2010 Miklos Szeredi + + Functions for dealing with `struct fuse_buf` and `struct + fuse_bufvec`. + + This program can be distributed under the terms of the GNU LGPLv2. + See the file LGPL2.txt +*/ + +#define _GNU_SOURCE + +#include "fuse_config.h" +#include "fuse_i.h" +#include "fuse_lowlevel.h" +#include +#include +#include +#include + +size_t fuse_buf_size(const struct fuse_bufvec *bufv) +{ + size_t i; + size_t size = 0; + + for (i = 0; i < bufv->count; i++) { + if (bufv->buf[i].size >= SIZE_MAX - size) + return SIZE_MAX; + + size += bufv->buf[i].size; + } + + return size; +} + +static size_t min_size(size_t s1, size_t s2) +{ + return s1 < s2 ? s1 : s2; +} + +static ssize_t fuse_buf_write(const struct fuse_buf *dst, size_t dst_off, + const struct fuse_buf *src, size_t src_off, + size_t len) +{ + ssize_t res = 0; + size_t copied = 0; + + while (len) { + if (dst->flags & FUSE_BUF_FD_SEEK) { + res = pwrite(dst->fd, (char *)src->mem + src_off, len, + dst->pos + dst_off); + } else { + res = write(dst->fd, (char *)src->mem + src_off, len); + } + if (res == -1) { + if (!copied) + return -errno; + break; + } + if (res == 0) + break; + + copied += res; + if (!(dst->flags & FUSE_BUF_FD_RETRY)) + break; + + src_off += res; + dst_off += res; + len -= res; + } + + return copied; +} + +static ssize_t fuse_buf_read(const struct fuse_buf *dst, size_t dst_off, + const struct fuse_buf *src, size_t src_off, + size_t len) +{ + ssize_t res = 0; + size_t copied = 0; + + while (len) { + if (src->flags & FUSE_BUF_FD_SEEK) { + res = pread(src->fd, (char *)dst->mem + dst_off, len, + src->pos + src_off); + } else { + res = read(src->fd, (char *)dst->mem + dst_off, len); + } + if (res == -1) { + if (!copied) + return -errno; + break; + } + if (res == 0) + break; + + copied += res; + if (!(src->flags & FUSE_BUF_FD_RETRY)) + break; + + dst_off += res; + src_off += res; + len -= res; + } + + return copied; +} + +static ssize_t fuse_buf_fd_to_fd(const struct fuse_buf *dst, size_t dst_off, + const struct fuse_buf *src, size_t src_off, + size_t len) +{ + char buf[4096]; + struct fuse_buf tmp = { + .size = sizeof(buf), + .flags = 0, + }; + ssize_t res; + size_t copied = 0; + + tmp.mem = buf; + + while (len) { + size_t this_len = min_size(tmp.size, len); + size_t read_len; + + res = fuse_buf_read(&tmp, 0, src, src_off, this_len); + if (res < 0) { + if (!copied) + return res; + break; + } + if (res == 0) + break; + + read_len = res; + res = fuse_buf_write(dst, dst_off, &tmp, 0, read_len); + if (res < 0) { + if (!copied) + return res; + break; + } + if (res == 0) + break; + + copied += res; + + if (res < this_len) + break; + + dst_off += res; + src_off += res; + len -= res; + } + + return copied; +} + +#ifdef HAVE_SPLICE +static ssize_t fuse_buf_splice(const struct fuse_buf *dst, size_t dst_off, + const struct fuse_buf *src, size_t src_off, + size_t len, enum fuse_buf_copy_flags flags) +{ + int splice_flags = 0; + off_t *srcpos = NULL; + off_t *dstpos = NULL; + off_t srcpos_val; + off_t dstpos_val; + ssize_t res; + size_t copied = 0; + + if (flags & FUSE_BUF_SPLICE_MOVE) + splice_flags |= SPLICE_F_MOVE; + if (flags & FUSE_BUF_SPLICE_NONBLOCK) + splice_flags |= SPLICE_F_NONBLOCK; + + if (src->flags & FUSE_BUF_FD_SEEK) { + srcpos_val = src->pos + src_off; + srcpos = &srcpos_val; + } + if (dst->flags & FUSE_BUF_FD_SEEK) { + dstpos_val = dst->pos + dst_off; + dstpos = &dstpos_val; + } + + while (len) { + res = splice(src->fd, srcpos, dst->fd, dstpos, len, + splice_flags); + if (res == -1) { + if (copied) + break; + + if (errno != EINVAL || (flags & FUSE_BUF_FORCE_SPLICE)) + return -errno; + + /* Maybe splice is not supported for this combination */ + return fuse_buf_fd_to_fd(dst, dst_off, src, src_off, + len); + } + if (res == 0) + break; + + copied += res; + if (!(src->flags & FUSE_BUF_FD_RETRY) && + !(dst->flags & FUSE_BUF_FD_RETRY)) { + break; + } + + len -= res; + } + + return copied; +} +#else +static ssize_t fuse_buf_splice(const struct fuse_buf *dst, size_t dst_off, + const struct fuse_buf *src, size_t src_off, + size_t len, enum fuse_buf_copy_flags flags) +{ + (void) flags; + + return fuse_buf_fd_to_fd(dst, dst_off, src, src_off, len); +} +#endif + + +static ssize_t fuse_buf_copy_one(const struct fuse_buf *dst, size_t dst_off, + const struct fuse_buf *src, size_t src_off, + size_t len, enum fuse_buf_copy_flags flags) +{ + int src_is_fd = src->flags & FUSE_BUF_IS_FD; + int dst_is_fd = dst->flags & FUSE_BUF_IS_FD; + + if (!src_is_fd && !dst_is_fd) { + char *dstmem = (char *)dst->mem + dst_off; + char *srcmem = (char *)src->mem + src_off; + + if (dstmem != srcmem) { + if (dstmem + len <= srcmem || srcmem + len <= dstmem) + memcpy(dstmem, srcmem, len); + else + memmove(dstmem, srcmem, len); + } + + return len; + } else if (!src_is_fd) { + return fuse_buf_write(dst, dst_off, src, src_off, len); + } else if (!dst_is_fd) { + return fuse_buf_read(dst, dst_off, src, src_off, len); + } else if (flags & FUSE_BUF_NO_SPLICE) { + return fuse_buf_fd_to_fd(dst, dst_off, src, src_off, len); + } else { + return fuse_buf_splice(dst, dst_off, src, src_off, len, flags); + } +} + +static const struct fuse_buf *fuse_bufvec_current(struct fuse_bufvec *bufv) +{ + if (bufv->idx < bufv->count) + return &bufv->buf[bufv->idx]; + else + return NULL; +} + +static int fuse_bufvec_advance(struct fuse_bufvec *bufv, size_t len) +{ + const struct fuse_buf *buf = fuse_bufvec_current(bufv); + + if (!buf) + return 0; + + bufv->off += len; + assert(bufv->off <= buf->size); + if (bufv->off == buf->size) { + assert(bufv->idx < bufv->count); + bufv->idx++; + if (bufv->idx == bufv->count) + return 0; + bufv->off = 0; + } + return 1; +} + +ssize_t fuse_buf_copy(struct fuse_bufvec *dstv, struct fuse_bufvec *srcv, + enum fuse_buf_copy_flags flags) +{ + size_t copied = 0; + + if (dstv == srcv) + return fuse_buf_size(dstv); + + for (;;) { + const struct fuse_buf *src = fuse_bufvec_current(srcv); + const struct fuse_buf *dst = fuse_bufvec_current(dstv); + size_t src_len; + size_t dst_len; + size_t len; + ssize_t res; + + if (src == NULL || dst == NULL) + break; + + src_len = src->size - srcv->off; + dst_len = dst->size - dstv->off; + len = min_size(src_len, dst_len); + + res = fuse_buf_copy_one(dst, dstv->off, src, srcv->off, len, flags); + if (res < 0) { + if (!copied) + return res; + break; + } + copied += res; + + if (!fuse_bufvec_advance(srcv, res) || + !fuse_bufvec_advance(dstv, res)) + break; + + if (res < len) + break; + } + + return copied; +} diff --git a/lib/compat.c b/lib/compat.c new file mode 100644 index 0000000..5fac415 --- /dev/null +++ b/lib/compat.c @@ -0,0 +1,86 @@ +/* + FUSE: Filesystem in Userspace + Copyright (C) 2001-2007 Miklos Szeredi + + Helper functions to create (simple) standalone programs. With the + aid of these functions it should be possible to create full FUSE + file system by implementing nothing but the request handlers. + + This program can be distributed under the terms of the GNU LGPLv2. + See the file LGPL2.txt. +*/ + +/* Description: + This file has compatibility symbols for platforms that do not + support version symboling +*/ + +#include "libfuse_config.h" + +#include +#include + +struct fuse_args; +struct fuse_cmdline_opts; +struct fuse_cmdline_opts; +struct fuse_session; +struct fuse_custom_io; +struct fuse_operations; +struct fuse_lowlevel_ops; + +/** + * Compatibility ABI symbol for systems that do not support version symboling + */ +#if (!defined(LIBFUSE_BUILT_WITH_VERSIONED_SYMBOLS)) +/* With current libfuse fuse_parse_cmdline is a macro pointing to the + * versioned function. Here in this file we need to provide the ABI symbol + * and the redirecting macro is conflicting. + */ +#ifdef fuse_parse_cmdline +#undef fuse_parse_cmdline +#endif +int fuse_parse_cmdline_30(struct fuse_args *args, + struct fuse_cmdline_opts *opts); +int fuse_parse_cmdline(struct fuse_args *args, + struct fuse_cmdline_opts *opts); +int fuse_parse_cmdline(struct fuse_args *args, + struct fuse_cmdline_opts *opts) +{ + return fuse_parse_cmdline_30(args, opts); +} + +int fuse_session_custom_io_30(struct fuse_session *se, + const struct fuse_custom_io *io, int fd); +int fuse_session_custom_io(struct fuse_session *se, + const struct fuse_custom_io *io, int fd); +int fuse_session_custom_io(struct fuse_session *se, + const struct fuse_custom_io *io, int fd) + +{ + return fuse_session_custom_io_30(se, io, fd); +} + +#endif /* LIBFUSE_BUILT_WITH_VERSIONED_SYMBOLS */ + +int fuse_main_real_30(int argc, char *argv[], const struct fuse_operations *op, + size_t op_size, void *user_data); +int fuse_main_real(int argc, char *argv[], const struct fuse_operations *op, + size_t op_size, void *user_data); +int fuse_main_real(int argc, char *argv[], const struct fuse_operations *op, + size_t op_size, void *user_data) +{ + return fuse_main_real_30(argc, argv, op, op_size, user_data); +} + +struct fuse_session *fuse_session_new_30(struct fuse_args *args, + const struct fuse_lowlevel_ops *op, + size_t op_size, void *userdata); +struct fuse_session *fuse_session_new(struct fuse_args *args, + const struct fuse_lowlevel_ops *op, + size_t op_size, void *userdata); +struct fuse_session *fuse_session_new(struct fuse_args *args, + const struct fuse_lowlevel_ops *op, + size_t op_size, void *userdata) +{ + return fuse_session_new_30(args, op, op_size, userdata); +} diff --git a/lib/cuse_lowlevel.c b/lib/cuse_lowlevel.c new file mode 100644 index 0000000..0bc2e66 --- /dev/null +++ b/lib/cuse_lowlevel.c @@ -0,0 +1,375 @@ +/* + CUSE: Character device in Userspace + Copyright (C) 2008 SUSE Linux Products GmbH + Copyright (C) 2008 Tejun Heo + + This program can be distributed under the terms of the GNU LGPLv2. + See the file LGPL2.txt. +*/ + +#include "fuse_config.h" +#include "cuse_lowlevel.h" +#include "fuse_kernel.h" +#include "fuse_i.h" +#include "fuse_opt.h" + +#include +#include +#include +#include +#include +#include + +struct cuse_data { + struct cuse_lowlevel_ops clop; + unsigned max_read; + unsigned dev_major; + unsigned dev_minor; + unsigned flags; + unsigned dev_info_len; + char dev_info[]; +}; + +static struct cuse_lowlevel_ops *req_clop(fuse_req_t req) +{ + return &req->se->cuse_data->clop; +} + +static void cuse_fll_open(fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info *fi) +{ + (void)ino; + req_clop(req)->open(req, fi); +} + +static void cuse_fll_read(fuse_req_t req, fuse_ino_t ino, size_t size, + off_t off, struct fuse_file_info *fi) +{ + (void)ino; + req_clop(req)->read(req, size, off, fi); +} + +static void cuse_fll_write(fuse_req_t req, fuse_ino_t ino, const char *buf, + size_t size, off_t off, struct fuse_file_info *fi) +{ + (void)ino; + req_clop(req)->write(req, buf, size, off, fi); +} + +static void cuse_fll_flush(fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info *fi) +{ + (void)ino; + req_clop(req)->flush(req, fi); +} + +static void cuse_fll_release(fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info *fi) +{ + (void)ino; + req_clop(req)->release(req, fi); +} + +static void cuse_fll_fsync(fuse_req_t req, fuse_ino_t ino, int datasync, + struct fuse_file_info *fi) +{ + (void)ino; + req_clop(req)->fsync(req, datasync, fi); +} + +static void cuse_fll_ioctl(fuse_req_t req, fuse_ino_t ino, unsigned int cmd, void *arg, + struct fuse_file_info *fi, unsigned int flags, + const void *in_buf, size_t in_bufsz, size_t out_bufsz) +{ + (void)ino; + req_clop(req)->ioctl(req, cmd, arg, fi, flags, in_buf, in_bufsz, + out_bufsz); +} + +static void cuse_fll_poll(fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info *fi, struct fuse_pollhandle *ph) +{ + (void)ino; + req_clop(req)->poll(req, fi, ph); +} + +static size_t cuse_pack_info(int argc, const char **argv, char *buf) +{ + size_t size = 0; + int i; + + for (i = 0; i < argc; i++) { + size_t len; + + len = strlen(argv[i]) + 1; + size += len; + if (buf) { + memcpy(buf, argv[i], len); + buf += len; + } + } + + return size; +} + +static struct cuse_data *cuse_prep_data(const struct cuse_info *ci, + const struct cuse_lowlevel_ops *clop) +{ + struct cuse_data *cd; + size_t dev_info_len; + + dev_info_len = cuse_pack_info(ci->dev_info_argc, ci->dev_info_argv, + NULL); + + if (dev_info_len > CUSE_INIT_INFO_MAX) { + fuse_log(FUSE_LOG_ERR, "cuse: dev_info (%zu) too large, limit=%u\n", + dev_info_len, CUSE_INIT_INFO_MAX); + return NULL; + } + + cd = calloc(1, sizeof(*cd) + dev_info_len); + if (!cd) { + fuse_log(FUSE_LOG_ERR, "cuse: failed to allocate cuse_data\n"); + return NULL; + } + + memcpy(&cd->clop, clop, sizeof(cd->clop)); + cd->max_read = 131072; + cd->dev_major = ci->dev_major; + cd->dev_minor = ci->dev_minor; + cd->dev_info_len = dev_info_len; + cd->flags = ci->flags; + cuse_pack_info(ci->dev_info_argc, ci->dev_info_argv, cd->dev_info); + + return cd; +} + +struct fuse_session *cuse_lowlevel_new(struct fuse_args *args, + const struct cuse_info *ci, + const struct cuse_lowlevel_ops *clop, + void *userdata) +{ + struct fuse_lowlevel_ops lop; + struct cuse_data *cd; + struct fuse_session *se; + + cd = cuse_prep_data(ci, clop); + if (!cd) + return NULL; + + memset(&lop, 0, sizeof(lop)); + lop.init = clop->init; + lop.destroy = clop->destroy; + lop.open = clop->open ? cuse_fll_open : NULL; + lop.read = clop->read ? cuse_fll_read : NULL; + lop.write = clop->write ? cuse_fll_write : NULL; + lop.flush = clop->flush ? cuse_fll_flush : NULL; + lop.release = clop->release ? cuse_fll_release : NULL; + lop.fsync = clop->fsync ? cuse_fll_fsync : NULL; + lop.ioctl = clop->ioctl ? cuse_fll_ioctl : NULL; + lop.poll = clop->poll ? cuse_fll_poll : NULL; + + se = fuse_session_new(args, &lop, sizeof(lop), userdata); + if (!se) { + free(cd); + return NULL; + } + se->cuse_data = cd; + + return se; +} + +static int cuse_reply_init(fuse_req_t req, struct cuse_init_out *arg, + char *dev_info, unsigned dev_info_len) +{ + struct iovec iov[3]; + + iov[1].iov_base = arg; + iov[1].iov_len = sizeof(struct cuse_init_out); + iov[2].iov_base = dev_info; + iov[2].iov_len = dev_info_len; + + return fuse_send_reply_iov_nofree(req, 0, iov, 3); +} + +void _cuse_lowlevel_init(fuse_req_t req, const fuse_ino_t nodeid, + const void *op_in, const void *req_payload) +{ + const struct fuse_init_in *arg = op_in; + (void)req_payload; + struct cuse_init_out outarg; + struct fuse_session *se = req->se; + struct cuse_data *cd = se->cuse_data; + size_t bufsize = se->bufsize; + struct cuse_lowlevel_ops *clop = req_clop(req); + + (void) nodeid; + if (se->debug) { + fuse_log(FUSE_LOG_DEBUG, "CUSE_INIT: %u.%u\n", arg->major, arg->minor); + fuse_log(FUSE_LOG_DEBUG, "flags=0x%08x\n", arg->flags); + } + se->conn.proto_major = arg->major; + se->conn.proto_minor = arg->minor; + + /* XXX This is not right.*/ + se->conn.capable_ext = 0; + se->conn.want_ext = 0; + + if (arg->major < 7) { + fuse_log(FUSE_LOG_ERR, "cuse: unsupported protocol version: %u.%u\n", + arg->major, arg->minor); + fuse_reply_err(req, EPROTO); + return; + } + + if (bufsize < FUSE_MIN_READ_BUFFER) { + fuse_log(FUSE_LOG_ERR, "cuse: warning: buffer size too small: %zu\n", + bufsize); + bufsize = FUSE_MIN_READ_BUFFER; + } + + bufsize -= 4096; + if (bufsize < se->conn.max_write) + se->conn.max_write = bufsize; + + se->got_init = 1; + if (se->op.init) + se->op.init(se->userdata, &se->conn); + + memset(&outarg, 0, sizeof(outarg)); + outarg.major = FUSE_KERNEL_VERSION; + outarg.minor = FUSE_KERNEL_MINOR_VERSION; + outarg.flags = cd->flags; + outarg.max_read = cd->max_read; + outarg.max_write = se->conn.max_write; + outarg.dev_major = cd->dev_major; + outarg.dev_minor = cd->dev_minor; + + if (se->debug) { + fuse_log(FUSE_LOG_DEBUG, " CUSE_INIT: %u.%u\n", + outarg.major, outarg.minor); + fuse_log(FUSE_LOG_DEBUG, " flags=0x%08x\n", outarg.flags); + fuse_log(FUSE_LOG_DEBUG, " max_read=0x%08x\n", outarg.max_read); + fuse_log(FUSE_LOG_DEBUG, " max_write=0x%08x\n", outarg.max_write); + fuse_log(FUSE_LOG_DEBUG, " dev_major=%u\n", outarg.dev_major); + fuse_log(FUSE_LOG_DEBUG, " dev_minor=%u\n", outarg.dev_minor); + fuse_log(FUSE_LOG_DEBUG, " dev_info: %.*s\n", cd->dev_info_len, + cd->dev_info); + } + + cuse_reply_init(req, &outarg, cd->dev_info, cd->dev_info_len); + + if (clop->init_done) + clop->init_done(se->userdata); + + fuse_free_req(req); +} + +void cuse_lowlevel_init(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) +{ + _cuse_lowlevel_init(req, nodeid, inarg, NULL); +} + +struct fuse_session *cuse_lowlevel_setup(int argc, char *argv[], + const struct cuse_info *ci, + const struct cuse_lowlevel_ops *clop, + int *multithreaded, void *userdata) +{ + const char *devname = "/dev/cuse"; + static const struct fuse_opt kill_subtype_opts[] = { + FUSE_OPT_KEY("subtype=", FUSE_OPT_KEY_DISCARD), + FUSE_OPT_END + }; + struct fuse_args args = FUSE_ARGS_INIT(argc, argv); + struct fuse_session *se; + struct fuse_cmdline_opts opts; + int fd; + int res; + + if (fuse_parse_cmdline(&args, &opts) == -1) + return NULL; + *multithreaded = !opts.singlethread; + + /* Remove subtype= option */ + res = fuse_opt_parse(&args, NULL, kill_subtype_opts, NULL); + if (res == -1) + goto out1; + + /* + * Make sure file descriptors 0, 1 and 2 are open, otherwise chaos + * would ensue. + */ + do { + fd = open("/dev/null", O_RDWR); + if (fd > 2) + close(fd); + } while (fd >= 0 && fd <= 2); + + se = cuse_lowlevel_new(&args, ci, clop, userdata); + if (se == NULL) + goto out1; + + fd = open(devname, O_RDWR); + if (fd == -1) { + if (errno == ENODEV || errno == ENOENT) + fuse_log(FUSE_LOG_ERR, "cuse: device not found, try 'modprobe cuse' first\n"); + else + fuse_log(FUSE_LOG_ERR, "cuse: failed to open %s: %s\n", + devname, strerror(errno)); + goto err_se; + } + se->fd = fd; + + res = fuse_set_signal_handlers(se); + if (res == -1) + goto err_se; + + res = fuse_daemonize(opts.foreground); + if (res == -1) + goto err_sig; + + fuse_opt_free_args(&args); + return se; + +err_sig: + fuse_remove_signal_handlers(se); +err_se: + fuse_session_destroy(se); +out1: + free(opts.mountpoint); + fuse_opt_free_args(&args); + return NULL; +} + +void cuse_lowlevel_teardown(struct fuse_session *se) +{ + fuse_remove_signal_handlers(se); + fuse_session_destroy(se); +} + +int cuse_lowlevel_main(int argc, char *argv[], const struct cuse_info *ci, + const struct cuse_lowlevel_ops *clop, void *userdata) +{ + struct fuse_session *se; + int multithreaded; + int res; + + se = cuse_lowlevel_setup(argc, argv, ci, clop, &multithreaded, + userdata); + if (se == NULL) + return 1; + + if (multithreaded) { + struct fuse_loop_config *config = fuse_loop_cfg_create(); + res = fuse_session_loop_mt(se, config); + fuse_loop_cfg_destroy(config); + } + else + res = fuse_session_loop(se); + + cuse_lowlevel_teardown(se); + if (res == -1) + return 1; + + return 0; +} diff --git a/lib/fuse.c b/lib/fuse.c new file mode 100644 index 0000000..a000981 --- /dev/null +++ b/lib/fuse.c @@ -0,0 +1,5277 @@ +/* + FUSE: Filesystem in Userspace + Copyright (C) 2001-2007 Miklos Szeredi + + Implementation of the high-level FUSE API on top of the low-level + API. + + This program can be distributed under the terms of the GNU LGPLv2. + See the file LGPL2.txt +*/ + +#define _GNU_SOURCE +#include "fuse.h" +#include + +#include "fuse_config.h" +#include "fuse_i.h" +#include "fuse_lowlevel.h" +#include "fuse_opt.h" +#include "fuse_misc.h" +#include "fuse_kernel.h" +#include "util.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define FUSE_NODE_SLAB 1 + +#ifndef MAP_ANONYMOUS +#undef FUSE_NODE_SLAB +#endif + +#ifndef RENAME_EXCHANGE +#define RENAME_EXCHANGE (1 << 1) /* Exchange source and dest */ +#endif + +#define FUSE_DEFAULT_INTR_SIGNAL SIGUSR1 + +#define FUSE_UNKNOWN_INO 0xffffffff +#define OFFSET_MAX 0x7fffffffffffffffLL + +#define NODE_TABLE_MIN_SIZE 8192 + +struct fuse_fs { + struct fuse_operations op; + void *user_data; + int debug; +}; + +struct fusemod_so { + void *handle; + int ctr; +}; + +struct lock_queue_element { + struct lock_queue_element *next; + pthread_cond_t cond; + fuse_ino_t nodeid1; + const char *name1; + char **path1; + struct node **wnode1; + fuse_ino_t nodeid2; + const char *name2; + char **path2; + struct node **wnode2; + int err; + bool done : 1; +}; + +struct node_table { + struct node **array; + size_t use; + size_t size; + size_t split; +}; + +#define list_entry(ptr, type, member) \ + container_of(ptr, type, member) + +struct list_head { + struct list_head *next; + struct list_head *prev; +}; + +struct node_slab { + struct list_head list; /* must be the first member */ + struct list_head freelist; + int used; +}; + +struct fuse { + struct fuse_session *se; + struct node_table name_table; + struct node_table id_table; + struct list_head lru_table; + fuse_ino_t ctr; + unsigned int generation; + unsigned int hidectr; + pthread_mutex_t lock; + struct fuse_config conf; + int intr_installed; + struct fuse_fs *fs; + struct lock_queue_element *lockq; + int pagesize; + struct list_head partial_slabs; + struct list_head full_slabs; + pthread_t prune_thread; +}; + +struct lock { + int type; + off_t start; + off_t end; + pid_t pid; + uint64_t owner; + struct lock *next; +}; + +struct node { + struct node *name_next; + struct node *id_next; + fuse_ino_t nodeid; + unsigned int generation; + int refctr; + struct node *parent; + char *name; + uint64_t nlookup; + int open_count; + struct timespec stat_updated; + struct timespec mtime; + off_t size; + struct lock *locks; + unsigned int is_hidden : 1; + unsigned int cache_valid : 1; + int treelock; + char inline_name[32]; +}; + +#define TREELOCK_WRITE -1 +#define TREELOCK_WAIT_OFFSET INT_MIN + +struct node_lru { + struct node node; + struct list_head lru; + struct timespec forget_time; +}; + +struct fuse_direntry { + struct stat stat; + enum fuse_fill_dir_flags flags; + char *name; + struct fuse_direntry *next; +}; + +struct fuse_dh { + pthread_mutex_t lock; + struct fuse *fuse; + fuse_req_t req; + char *contents; + struct fuse_direntry *first; + struct fuse_direntry **last; + unsigned len; + unsigned size; + unsigned needlen; + int filled; + uint64_t fh; + int error; + fuse_ino_t nodeid; +}; + +struct fuse_context_i { + struct fuse_context ctx; + fuse_req_t req; +}; + +/* Defined by FUSE_REGISTER_MODULE() in lib/modules/subdir.c and iconv.c. */ +extern fuse_module_factory_t fuse_module_subdir_factory; +#ifdef HAVE_ICONV +extern fuse_module_factory_t fuse_module_iconv_factory; +#endif + +static pthread_key_t fuse_context_key; +static pthread_mutex_t fuse_context_lock = PTHREAD_MUTEX_INITIALIZER; +static int fuse_context_ref; +static struct fuse_module *fuse_modules = NULL; + +static int fuse_register_module(const char *name, + fuse_module_factory_t factory, + struct fusemod_so *so) +{ + struct fuse_module *mod; + + mod = calloc(1, sizeof(struct fuse_module)); + if (!mod) { + fuse_log(FUSE_LOG_ERR, "fuse: failed to allocate module\n"); + return -1; + } + mod->name = strdup(name); + if (!mod->name) { + fuse_log(FUSE_LOG_ERR, "fuse: failed to allocate module name\n"); + free(mod); + return -1; + } + mod->factory = factory; + mod->ctr = 0; + mod->so = so; + if (mod->so) + mod->so->ctr++; + mod->next = fuse_modules; + fuse_modules = mod; + + return 0; +} + +static void fuse_unregister_module(struct fuse_module *m) +{ + struct fuse_module **mp; + for (mp = &fuse_modules; *mp; mp = &(*mp)->next) { + if (*mp == m) { + *mp = (*mp)->next; + break; + } + } + free(m->name); + free(m); +} + +static int fuse_load_so_module(const char *module) +{ + int ret = -1; + char *tmp; + struct fusemod_so *so; + fuse_module_factory_t *factory; + + tmp = malloc(strlen(module) + 64); + if (!tmp) { + fuse_log(FUSE_LOG_ERR, "fuse: memory allocation failed\n"); + return -1; + } + sprintf(tmp, "libfusemod_%s.so", module); + so = calloc(1, sizeof(struct fusemod_so)); + if (!so) { + fuse_log(FUSE_LOG_ERR, "fuse: failed to allocate module so\n"); + goto out; + } + + so->handle = dlopen(tmp, RTLD_NOW); + if (so->handle == NULL) { + fuse_log(FUSE_LOG_ERR, "fuse: dlopen(%s) failed: %s\n", + tmp, dlerror()); + goto out_free_so; + } + + sprintf(tmp, "fuse_module_%s_factory", module); + factory = (fuse_module_factory_t*)dlsym(so->handle, tmp); + if (factory == NULL) { + fuse_log(FUSE_LOG_ERR, "fuse: symbol <%s> not found in module: %s\n", + tmp, dlerror()); + goto out_dlclose; + } + ret = fuse_register_module(module, *factory, so); + if (ret) + goto out_dlclose; + +out: + free(tmp); + return ret; + +out_dlclose: + dlclose(so->handle); +out_free_so: + free(so); + goto out; +} + +static struct fuse_module *fuse_find_module(const char *module) +{ + struct fuse_module *m; + for (m = fuse_modules; m; m = m->next) { + if (strcmp(module, m->name) == 0) { + m->ctr++; + break; + } + } + return m; +} + +static struct fuse_module *fuse_get_module(const char *module) +{ + struct fuse_module *m; + + pthread_mutex_lock(&fuse_context_lock); + m = fuse_find_module(module); + if (!m) { + int err = fuse_load_so_module(module); + if (!err) + m = fuse_find_module(module); + } + pthread_mutex_unlock(&fuse_context_lock); + return m; +} + +static void fuse_put_module(struct fuse_module *m) +{ + pthread_mutex_lock(&fuse_context_lock); + if (m->so) + assert(m->ctr > 0); + /* Builtin modules may already have m->ctr == 0 */ + if (m->ctr > 0) + m->ctr--; + if (!m->ctr && m->so) { + struct fusemod_so *so = m->so; + assert(so->ctr > 0); + so->ctr--; + if (!so->ctr) { + struct fuse_module **mp; + for (mp = &fuse_modules; *mp;) { + if ((*mp)->so == so) + fuse_unregister_module(*mp); + else + mp = &(*mp)->next; + } + dlclose(so->handle); + free(so); + } + } else if (!m->ctr) { + fuse_unregister_module(m); + } + pthread_mutex_unlock(&fuse_context_lock); +} + +static void init_list_head(struct list_head *list) +{ + list->next = list; + list->prev = list; +} + +static int list_empty(const struct list_head *head) +{ + return head->next == head; +} + +static void list_add(struct list_head *new, struct list_head *prev, + struct list_head *next) +{ + next->prev = new; + new->next = next; + new->prev = prev; + prev->next = new; +} + +static inline void list_add_head(struct list_head *new, struct list_head *head) +{ + list_add(new, head, head->next); +} + +static inline void list_add_tail(struct list_head *new, struct list_head *head) +{ + list_add(new, head->prev, head); +} + +static inline void list_del(struct list_head *entry) +{ + struct list_head *prev = entry->prev; + struct list_head *next = entry->next; + + next->prev = prev; + prev->next = next; +} + +static inline int lru_enabled(struct fuse *f) +{ + return f->conf.remember > 0; +} + +static struct node_lru *node_lru(struct node *node) +{ + return (struct node_lru *) node; +} + +static size_t get_node_size(struct fuse *f) +{ + if (lru_enabled(f)) + return sizeof(struct node_lru); + else + return sizeof(struct node); +} + +#ifdef FUSE_NODE_SLAB +static struct node_slab *list_to_slab(struct list_head *head) +{ + return (struct node_slab *) head; +} + +static struct node_slab *node_to_slab(struct fuse *f, struct node *node) +{ + return (struct node_slab *) (((uintptr_t) node) & ~((uintptr_t) f->pagesize - 1)); +} + +static int alloc_slab(struct fuse *f) +{ + void *mem; + struct node_slab *slab; + char *start; + size_t num; + size_t i; + size_t node_size = get_node_size(f); + + mem = mmap(NULL, f->pagesize, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + + if (mem == MAP_FAILED) + return -1; + + slab = mem; + init_list_head(&slab->freelist); + slab->used = 0; + num = (f->pagesize - sizeof(struct node_slab)) / node_size; + + start = (char *) mem + f->pagesize - num * node_size; + for (i = 0; i < num; i++) { + struct list_head *n; + + n = (struct list_head *) (start + i * node_size); + list_add_tail(n, &slab->freelist); + } + list_add_tail(&slab->list, &f->partial_slabs); + + return 0; +} + +static struct node *alloc_node(struct fuse *f) +{ + struct node_slab *slab; + struct list_head *node; + + if (list_empty(&f->partial_slabs)) { + int res = alloc_slab(f); + if (res != 0) + return NULL; + } + slab = list_to_slab(f->partial_slabs.next); + slab->used++; + node = slab->freelist.next; + list_del(node); + if (list_empty(&slab->freelist)) { + list_del(&slab->list); + list_add_tail(&slab->list, &f->full_slabs); + } + memset(node, 0, sizeof(struct node)); + + return (struct node *) node; +} + +static void free_slab(struct fuse *f, struct node_slab *slab) +{ + int res; + + list_del(&slab->list); + res = munmap(slab, f->pagesize); + if (res == -1) + fuse_log(FUSE_LOG_WARNING, "fuse warning: munmap(%p) failed\n", + slab); +} + +static void free_node_mem(struct fuse *f, struct node *node) +{ + struct node_slab *slab = node_to_slab(f, node); + struct list_head *n = (struct list_head *) node; + + slab->used--; + if (slab->used) { + if (list_empty(&slab->freelist)) { + list_del(&slab->list); + list_add_tail(&slab->list, &f->partial_slabs); + } + list_add_head(n, &slab->freelist); + } else { + free_slab(f, slab); + } +} +#else +static struct node *alloc_node(struct fuse *f) +{ + return (struct node *) calloc(1, get_node_size(f)); +} + +static void free_node_mem(struct fuse *f, struct node *node) +{ + (void) f; + free(node); +} +#endif + +static size_t id_hash(struct fuse *f, fuse_ino_t ino) +{ + uint64_t hash = ((uint32_t) ino * 2654435761U) % f->id_table.size; + uint64_t oldhash = hash % (f->id_table.size / 2); + + if (oldhash >= f->id_table.split) + return oldhash; + else + return hash; +} + +static struct node *get_node_nocheck(struct fuse *f, fuse_ino_t nodeid) +{ + size_t hash = id_hash(f, nodeid); + struct node *node; + + for (node = f->id_table.array[hash]; node != NULL; node = node->id_next) + if (node->nodeid == nodeid) + return node; + + return NULL; +} + +static struct node *get_node(struct fuse *f, fuse_ino_t nodeid) +{ + struct node *node = get_node_nocheck(f, nodeid); + if (!node) { + fuse_log(FUSE_LOG_ERR, "fuse internal error: node %llu not found\n", + (unsigned long long) nodeid); + abort(); + } + return node; +} + +static void curr_time(struct timespec *now); +static double diff_timespec(const struct timespec *t1, + const struct timespec *t2); + +static void remove_node_lru(struct node *node) +{ + struct node_lru *lnode = node_lru(node); + list_del(&lnode->lru); + init_list_head(&lnode->lru); +} + +static void set_forget_time(struct fuse *f, struct node *node) +{ + struct node_lru *lnode = node_lru(node); + + list_del(&lnode->lru); + list_add_tail(&lnode->lru, &f->lru_table); + curr_time(&lnode->forget_time); +} + +static void free_node(struct fuse *f, struct node *node) +{ + if (node->name != node->inline_name) + free(node->name); + free_node_mem(f, node); +} + +static void node_table_reduce(struct node_table *t) +{ + size_t newsize = t->size / 2; + void *newarray; + + if (newsize < NODE_TABLE_MIN_SIZE) + return; + + newarray = realloc(t->array, sizeof(struct node *) * newsize); + if (newarray != NULL) + t->array = newarray; + + t->size = newsize; + t->split = t->size / 2; +} + +static void remerge_id(struct fuse *f) +{ + struct node_table *t = &f->id_table; + int iter; + + if (t->split == 0) + node_table_reduce(t); + + for (iter = 8; t->split > 0 && iter; iter--) { + struct node **upper; + + t->split--; + upper = &t->array[t->split + t->size / 2]; + if (*upper) { + struct node **nodep; + + for (nodep = &t->array[t->split]; *nodep; + nodep = &(*nodep)->id_next); + + *nodep = *upper; + *upper = NULL; + break; + } + } +} + +static void unhash_id(struct fuse *f, struct node *node) +{ + struct node **nodep = &f->id_table.array[id_hash(f, node->nodeid)]; + + for (; *nodep != NULL; nodep = &(*nodep)->id_next) + if (*nodep == node) { + *nodep = node->id_next; + f->id_table.use--; + + if(f->id_table.use < f->id_table.size / 4) + remerge_id(f); + return; + } +} + +static int node_table_resize(struct node_table *t) +{ + size_t newsize = t->size * 2; + void *newarray; + + newarray = realloc(t->array, sizeof(struct node *) * newsize); + if (newarray == NULL) + return -1; + + t->array = newarray; + memset(t->array + t->size, 0, t->size * sizeof(struct node *)); + t->size = newsize; + t->split = 0; + + return 0; +} + +static void rehash_id(struct fuse *f) +{ + struct node_table *t = &f->id_table; + struct node **nodep; + struct node **next; + size_t hash; + + if (t->split == t->size / 2) + return; + + hash = t->split; + t->split++; + for (nodep = &t->array[hash]; *nodep != NULL; nodep = next) { + struct node *node = *nodep; + size_t newhash = id_hash(f, node->nodeid); + + if (newhash != hash) { + next = nodep; + *nodep = node->id_next; + node->id_next = t->array[newhash]; + t->array[newhash] = node; + } else { + next = &node->id_next; + } + } + if (t->split == t->size / 2) + node_table_resize(t); +} + +static void hash_id(struct fuse *f, struct node *node) +{ + size_t hash = id_hash(f, node->nodeid); + node->id_next = f->id_table.array[hash]; + f->id_table.array[hash] = node; + f->id_table.use++; + + if (f->id_table.use >= f->id_table.size / 2) + rehash_id(f); +} + +static size_t name_hash(struct fuse *f, fuse_ino_t parent, + const char *name) +{ + uint64_t hash = parent; + uint64_t oldhash; + + for (; *name; name++) + hash = hash * 31 + (unsigned char) *name; + + hash %= f->name_table.size; + oldhash = hash % (f->name_table.size / 2); + if (oldhash >= f->name_table.split) + return oldhash; + else + return hash; +} + +static void unref_node(struct fuse *f, struct node *node); + +static void remerge_name(struct fuse *f) +{ + struct node_table *t = &f->name_table; + int iter; + + if (t->split == 0) + node_table_reduce(t); + + for (iter = 8; t->split > 0 && iter; iter--) { + struct node **upper; + + t->split--; + upper = &t->array[t->split + t->size / 2]; + if (*upper) { + struct node **nodep; + + for (nodep = &t->array[t->split]; *nodep; + nodep = &(*nodep)->name_next); + + *nodep = *upper; + *upper = NULL; + break; + } + } +} + +static void unhash_name(struct fuse *f, struct node *node) +{ + if (node->name) { + size_t hash = name_hash(f, node->parent->nodeid, node->name); + struct node **nodep = &f->name_table.array[hash]; + + for (; *nodep != NULL; nodep = &(*nodep)->name_next) + if (*nodep == node) { + *nodep = node->name_next; + node->name_next = NULL; + unref_node(f, node->parent); + if (node->name != node->inline_name) + free(node->name); + node->name = NULL; + node->parent = NULL; + f->name_table.use--; + + if (f->name_table.use < f->name_table.size / 4) + remerge_name(f); + return; + } + fuse_log(FUSE_LOG_ERR, + "fuse internal error: unable to unhash node: %llu\n", + (unsigned long long) node->nodeid); + abort(); + } +} + +static void rehash_name(struct fuse *f) +{ + struct node_table *t = &f->name_table; + struct node **nodep; + struct node **next; + size_t hash; + + if (t->split == t->size / 2) + return; + + hash = t->split; + t->split++; + for (nodep = &t->array[hash]; *nodep != NULL; nodep = next) { + struct node *node = *nodep; + size_t newhash = name_hash(f, node->parent->nodeid, node->name); + + if (newhash != hash) { + next = nodep; + *nodep = node->name_next; + node->name_next = t->array[newhash]; + t->array[newhash] = node; + } else { + next = &node->name_next; + } + } + if (t->split == t->size / 2) + node_table_resize(t); +} + +static int hash_name(struct fuse *f, struct node *node, fuse_ino_t parentid, + const char *name) +{ + size_t hash = name_hash(f, parentid, name); + struct node *parent = get_node(f, parentid); + if (strlen(name) < sizeof(node->inline_name)) { + strcpy(node->inline_name, name); + node->name = node->inline_name; + } else { + node->name = strdup(name); + if (node->name == NULL) + return -1; + } + + parent->refctr ++; + node->parent = parent; + node->name_next = f->name_table.array[hash]; + f->name_table.array[hash] = node; + f->name_table.use++; + + if (f->name_table.use >= f->name_table.size / 2) + rehash_name(f); + + return 0; +} + +static void delete_node(struct fuse *f, struct node *node) +{ + if (f->conf.debug) + fuse_log(FUSE_LOG_DEBUG, "DELETE: %llu\n", + (unsigned long long) node->nodeid); + + assert(node->treelock == 0); + unhash_name(f, node); + if (lru_enabled(f)) + remove_node_lru(node); + unhash_id(f, node); + free_node(f, node); +} + +static void unref_node(struct fuse *f, struct node *node) +{ + assert(node->refctr > 0); + node->refctr --; + if (!node->refctr) + delete_node(f, node); +} + +static fuse_ino_t next_id(struct fuse *f) +{ + do { + f->ctr = (f->ctr + 1) & 0xffffffff; + if (!f->ctr) + f->generation ++; + } while (f->ctr == 0 || f->ctr == FUSE_UNKNOWN_INO || + get_node_nocheck(f, f->ctr) != NULL); + return f->ctr; +} + +static struct node *lookup_node(struct fuse *f, fuse_ino_t parent, + const char *name) +{ + size_t hash = name_hash(f, parent, name); + struct node *node; + + for (node = f->name_table.array[hash]; node != NULL; node = node->name_next) + if (node->parent->nodeid == parent && + strcmp(node->name, name) == 0) + return node; + + return NULL; +} + +static void inc_nlookup(struct node *node) +{ + if (!node->nlookup) + node->refctr++; + node->nlookup++; +} + +static struct node *find_node(struct fuse *f, fuse_ino_t parent, + const char *name) +{ + struct node *node; + + pthread_mutex_lock(&f->lock); + if (!name) + node = get_node(f, parent); + else + node = lookup_node(f, parent, name); + if (node == NULL) { + node = alloc_node(f); + if (node == NULL) + goto out_err; + + node->nodeid = next_id(f); + node->generation = f->generation; + if (f->conf.remember) + inc_nlookup(node); + + if (hash_name(f, node, parent, name) == -1) { + free_node(f, node); + node = NULL; + goto out_err; + } + hash_id(f, node); + if (lru_enabled(f)) { + struct node_lru *lnode = node_lru(node); + init_list_head(&lnode->lru); + } + } else if (lru_enabled(f) && node->nlookup == 1) { + remove_node_lru(node); + } + inc_nlookup(node); +out_err: + pthread_mutex_unlock(&f->lock); + return node; +} + +static int lookup_path_in_cache(struct fuse *f, + const char *path, fuse_ino_t *inop) +{ + char *tmp = strdup(path); + if (!tmp) + return -ENOMEM; + + pthread_mutex_lock(&f->lock); + fuse_ino_t ino = FUSE_ROOT_ID; + + int err = 0; + char *save_ptr; + char *path_element = strtok_r(tmp, "/", &save_ptr); + while (path_element != NULL) { + struct node *node = lookup_node(f, ino, path_element); + if (node == NULL) { + err = -ENOENT; + break; + } + ino = node->nodeid; + path_element = strtok_r(NULL, "/", &save_ptr); + } + pthread_mutex_unlock(&f->lock); + free(tmp); + + if (!err) + *inop = ino; + return err; +} + +static char *add_name(char **buf, unsigned *bufsize, char *s, const char *name) +{ + size_t len = strlen(name); + + if (s - len <= *buf) { + unsigned pathlen = *bufsize - (s - *buf); + unsigned newbufsize = *bufsize; + char *newbuf; + + while (newbufsize < pathlen + len + 1) { + if (newbufsize >= 0x80000000) + newbufsize = 0xffffffff; + else + newbufsize *= 2; + } + + newbuf = realloc(*buf, newbufsize); + if (newbuf == NULL) + return NULL; + + *buf = newbuf; + s = newbuf + newbufsize - pathlen; + memmove(s, newbuf + *bufsize - pathlen, pathlen); + *bufsize = newbufsize; + } + s -= len; + memcpy(s, name, len); + s--; + *s = '/'; + + return s; +} + +static void unlock_path(struct fuse *f, fuse_ino_t nodeid, struct node *wnode, + struct node *end) +{ + struct node *node; + + if (wnode) { + assert(wnode->treelock == TREELOCK_WRITE); + wnode->treelock = 0; + } + + for (node = get_node(f, nodeid); + node != end && node->nodeid != FUSE_ROOT_ID; node = node->parent) { + assert(node->treelock != 0); + assert(node->treelock != TREELOCK_WAIT_OFFSET); + assert(node->treelock != TREELOCK_WRITE); + node->treelock--; + if (node->treelock == TREELOCK_WAIT_OFFSET) + node->treelock = 0; + } +} + +static int try_get_path(struct fuse *f, fuse_ino_t nodeid, const char *name, + char **path, struct node **wnodep, bool need_lock) +{ + unsigned bufsize = 256; + char *buf; + char *s; + struct node *node; + struct node *wnode = NULL; + int err; + + *path = NULL; + + err = -ENOMEM; + buf = malloc(bufsize); + if (buf == NULL) + goto out_err; + + s = buf + bufsize - 1; + *s = '\0'; + + if (name != NULL) { + s = add_name(&buf, &bufsize, s, name); + err = -ENOMEM; + if (s == NULL) + goto out_free; + } + + if (wnodep) { + assert(need_lock); + assert(name != NULL); + wnode = lookup_node(f, nodeid, name); + if (wnode) { + if (wnode->treelock != 0) { + if (wnode->treelock > 0) + wnode->treelock += TREELOCK_WAIT_OFFSET; + err = -EAGAIN; + goto out_free; + } + wnode->treelock = TREELOCK_WRITE; + } + } + + for (node = get_node(f, nodeid); node->nodeid != FUSE_ROOT_ID; + node = node->parent) { + err = -ESTALE; + if (node->name == NULL || node->parent == NULL) + goto out_unlock; + + err = -ENOMEM; + s = add_name(&buf, &bufsize, s, node->name); + if (s == NULL) + goto out_unlock; + + if (need_lock) { + err = -EAGAIN; + if (node->treelock < 0) + goto out_unlock; + + node->treelock++; + } + } + + if (s[0]) + memmove(buf, s, bufsize - (s - buf)); + else + strcpy(buf, "/"); + + *path = buf; + if (wnodep) + *wnodep = wnode; + + return 0; + + out_unlock: + if (need_lock) + unlock_path(f, nodeid, wnode, node); + out_free: + free(buf); + + out_err: + return err; +} + +static int try_get_path2(struct fuse *f, fuse_ino_t nodeid1, const char *name1, + fuse_ino_t nodeid2, const char *name2, + char **path1, char **path2, + struct node **wnode1, struct node **wnode2) +{ + int err; + + /* FIXME: locking two paths needs deadlock checking */ + err = try_get_path(f, nodeid1, name1, path1, wnode1, true); + if (!err) { + err = try_get_path(f, nodeid2, name2, path2, wnode2, true); + if (err) { + struct node *wn1 = wnode1 ? *wnode1 : NULL; + + unlock_path(f, nodeid1, wn1, NULL); + free(*path1); + } + } + return err; +} + +static void queue_element_wakeup(struct fuse *f, struct lock_queue_element *qe) +{ + int err; + + if (!qe->path1) { + /* Just waiting for it to be unlocked */ + if (get_node(f, qe->nodeid1)->treelock == 0) + pthread_cond_signal(&qe->cond); + + return; + } + + if (qe->done) + return; // Don't try to double-lock the element + + if (!qe->path2) { + err = try_get_path(f, qe->nodeid1, qe->name1, qe->path1, + qe->wnode1, true); + } else { + err = try_get_path2(f, qe->nodeid1, qe->name1, qe->nodeid2, + qe->name2, qe->path1, qe->path2, qe->wnode1, + qe->wnode2); + } + + if (err == -EAGAIN) + return; /* keep trying */ + + qe->err = err; + qe->done = true; + pthread_cond_signal(&qe->cond); +} + +static void wake_up_queued(struct fuse *f) +{ + struct lock_queue_element *qe; + + for (qe = f->lockq; qe != NULL; qe = qe->next) + queue_element_wakeup(f, qe); +} + +static void debug_path(struct fuse *f, const char *msg, fuse_ino_t nodeid, + const char *name, bool wr) +{ + if (f->conf.debug) { + struct node *wnode = NULL; + + if (wr) + wnode = lookup_node(f, nodeid, name); + + if (wnode) { + fuse_log(FUSE_LOG_DEBUG, "%s %llu (w)\n", + msg, (unsigned long long) wnode->nodeid); + } else { + fuse_log(FUSE_LOG_DEBUG, "%s %llu\n", + msg, (unsigned long long) nodeid); + } + } +} + +static void queue_path(struct fuse *f, struct lock_queue_element *qe) +{ + struct lock_queue_element **qp; + + qe->done = false; + pthread_cond_init(&qe->cond, NULL); + qe->next = NULL; + for (qp = &f->lockq; *qp != NULL; qp = &(*qp)->next); + *qp = qe; +} + +static void dequeue_path(struct fuse *f, struct lock_queue_element *qe) +{ + struct lock_queue_element **qp; + + pthread_cond_destroy(&qe->cond); + for (qp = &f->lockq; *qp != qe; qp = &(*qp)->next); + *qp = qe->next; +} + +static int wait_path(struct fuse *f, struct lock_queue_element *qe) +{ + queue_path(f, qe); + + do { + pthread_cond_wait(&qe->cond, &f->lock); + } while (!qe->done); + + dequeue_path(f, qe); + + return qe->err; +} + +static int get_path_common(struct fuse *f, fuse_ino_t nodeid, const char *name, + char **path, struct node **wnode) +{ + int err; + + pthread_mutex_lock(&f->lock); + err = try_get_path(f, nodeid, name, path, wnode, true); + if (err == -EAGAIN) { + struct lock_queue_element qe = { + .nodeid1 = nodeid, + .name1 = name, + .path1 = path, + .wnode1 = wnode, + }; + debug_path(f, "QUEUE PATH", nodeid, name, !!wnode); + err = wait_path(f, &qe); + debug_path(f, "DEQUEUE PATH", nodeid, name, !!wnode); + } + pthread_mutex_unlock(&f->lock); + + return err; +} + +static int get_path(struct fuse *f, fuse_ino_t nodeid, char **path) +{ + return get_path_common(f, nodeid, NULL, path, NULL); +} + +static int get_path_nullok(struct fuse *f, fuse_ino_t nodeid, char **path) +{ + int err = 0; + + if (f->conf.nullpath_ok) { + *path = NULL; + } else { + err = get_path_common(f, nodeid, NULL, path, NULL); + if (err == -ESTALE) + err = 0; + } + + return err; +} + +static int get_path_name(struct fuse *f, fuse_ino_t nodeid, const char *name, + char **path) +{ + return get_path_common(f, nodeid, name, path, NULL); +} + +static int get_path_wrlock(struct fuse *f, fuse_ino_t nodeid, const char *name, + char **path, struct node **wnode) +{ + return get_path_common(f, nodeid, name, path, wnode); +} + +#if defined(__FreeBSD__) +#define CHECK_DIR_LOOP +#endif + +#if defined(CHECK_DIR_LOOP) +static int check_dir_loop(struct fuse *f, + fuse_ino_t nodeid1, const char *name1, + fuse_ino_t nodeid2, const char *name2) +{ + struct node *node, *node1, *node2; + fuse_ino_t id1, id2; + + node1 = lookup_node(f, nodeid1, name1); + id1 = node1 ? node1->nodeid : nodeid1; + + node2 = lookup_node(f, nodeid2, name2); + id2 = node2 ? node2->nodeid : nodeid2; + + for (node = get_node(f, id2); node->nodeid != FUSE_ROOT_ID; + node = node->parent) { + if (node->name == NULL || node->parent == NULL) + break; + + if (node->nodeid != id2 && node->nodeid == id1) + return -EINVAL; + } + + if (node2) + { + for (node = get_node(f, id1); node->nodeid != FUSE_ROOT_ID; + node = node->parent) { + if (node->name == NULL || node->parent == NULL) + break; + + if (node->nodeid != id1 && node->nodeid == id2) + return -ENOTEMPTY; + } + } + + return 0; +} +#endif + +static int get_path2(struct fuse *f, fuse_ino_t nodeid1, const char *name1, + fuse_ino_t nodeid2, const char *name2, + char **path1, char **path2, + struct node **wnode1, struct node **wnode2) +{ + int err; + + pthread_mutex_lock(&f->lock); + +#if defined(CHECK_DIR_LOOP) + if (name1) + { + // called during rename; perform dir loop check + err = check_dir_loop(f, nodeid1, name1, nodeid2, name2); + if (err) + goto out_unlock; + } +#endif + + err = try_get_path2(f, nodeid1, name1, nodeid2, name2, + path1, path2, wnode1, wnode2); + if (err == -EAGAIN) { + struct lock_queue_element qe = { + .nodeid1 = nodeid1, + .name1 = name1, + .path1 = path1, + .wnode1 = wnode1, + .nodeid2 = nodeid2, + .name2 = name2, + .path2 = path2, + .wnode2 = wnode2, + }; + + debug_path(f, "QUEUE PATH1", nodeid1, name1, !!wnode1); + debug_path(f, " PATH2", nodeid2, name2, !!wnode2); + err = wait_path(f, &qe); + debug_path(f, "DEQUEUE PATH1", nodeid1, name1, !!wnode1); + debug_path(f, " PATH2", nodeid2, name2, !!wnode2); + } + +#if defined(CHECK_DIR_LOOP) +out_unlock: +#endif + pthread_mutex_unlock(&f->lock); + + return err; +} + +static void free_path_wrlock(struct fuse *f, fuse_ino_t nodeid, + struct node *wnode, char *path) +{ + pthread_mutex_lock(&f->lock); + unlock_path(f, nodeid, wnode, NULL); + if (f->lockq) + wake_up_queued(f); + pthread_mutex_unlock(&f->lock); + free(path); +} + +static void free_path(struct fuse *f, fuse_ino_t nodeid, char *path) +{ + if (path) + free_path_wrlock(f, nodeid, NULL, path); +} + +static void free_path2(struct fuse *f, fuse_ino_t nodeid1, fuse_ino_t nodeid2, + struct node *wnode1, struct node *wnode2, + char *path1, char *path2) +{ + pthread_mutex_lock(&f->lock); + unlock_path(f, nodeid1, wnode1, NULL); + unlock_path(f, nodeid2, wnode2, NULL); + wake_up_queued(f); + pthread_mutex_unlock(&f->lock); + free(path1); + free(path2); +} + +static void forget_node(struct fuse *f, fuse_ino_t nodeid, uint64_t nlookup) +{ + struct node *node; + if (nodeid == FUSE_ROOT_ID) + return; + pthread_mutex_lock(&f->lock); + node = get_node(f, nodeid); + + /* + * Node may still be locked due to interrupt idiocy in open, + * create and opendir + */ + while (node->nlookup == nlookup && node->treelock) { + struct lock_queue_element qe = { + .nodeid1 = nodeid, + }; + + debug_path(f, "QUEUE PATH (forget)", nodeid, NULL, false); + queue_path(f, &qe); + + do { + pthread_cond_wait(&qe.cond, &f->lock); + } while (node->nlookup == nlookup && node->treelock); + + dequeue_path(f, &qe); + debug_path(f, "DEQUEUE_PATH (forget)", nodeid, NULL, false); + } + + assert(node->nlookup >= nlookup); + node->nlookup -= nlookup; + if (!node->nlookup) { + unref_node(f, node); + } else if (lru_enabled(f) && node->nlookup == 1) { + set_forget_time(f, node); + } + pthread_mutex_unlock(&f->lock); +} + +static void unlink_node(struct fuse *f, struct node *node) +{ + if (f->conf.remember) { + assert(node->nlookup > 1); + node->nlookup--; + } + unhash_name(f, node); +} + +static void remove_node(struct fuse *f, fuse_ino_t dir, const char *name) +{ + struct node *node; + + pthread_mutex_lock(&f->lock); + node = lookup_node(f, dir, name); + if (node != NULL) + unlink_node(f, node); + pthread_mutex_unlock(&f->lock); +} + +static int rename_node(struct fuse *f, fuse_ino_t olddir, const char *oldname, + fuse_ino_t newdir, const char *newname, int hide) +{ + struct node *node; + struct node *newnode; + int err = 0; + + pthread_mutex_lock(&f->lock); + node = lookup_node(f, olddir, oldname); + newnode = lookup_node(f, newdir, newname); + if (node == NULL) + goto out; + + if (newnode != NULL) { + if (hide) { + fuse_log(FUSE_LOG_ERR, "fuse: hidden file got created during hiding\n"); + err = -EBUSY; + goto out; + } + unlink_node(f, newnode); + } + + unhash_name(f, node); + if (hash_name(f, node, newdir, newname) == -1) { + err = -ENOMEM; + goto out; + } + + if (hide) + node->is_hidden = 1; + +out: + pthread_mutex_unlock(&f->lock); + return err; +} + +static int exchange_node(struct fuse *f, fuse_ino_t olddir, const char *oldname, + fuse_ino_t newdir, const char *newname) +{ + struct node *oldnode; + struct node *newnode; + int err; + + pthread_mutex_lock(&f->lock); + oldnode = lookup_node(f, olddir, oldname); + newnode = lookup_node(f, newdir, newname); + + if (oldnode) + unhash_name(f, oldnode); + if (newnode) + unhash_name(f, newnode); + + err = -ENOMEM; + if (oldnode) { + if (hash_name(f, oldnode, newdir, newname) == -1) + goto out; + } + if (newnode) { + if (hash_name(f, newnode, olddir, oldname) == -1) + goto out; + } + err = 0; +out: + pthread_mutex_unlock(&f->lock); + return err; +} + +static void set_stat(struct fuse *f, fuse_ino_t nodeid, struct stat *stbuf) +{ + if (!f->conf.use_ino) + stbuf->st_ino = nodeid; + if (f->conf.set_mode) { + if (f->conf.dmask && S_ISDIR(stbuf->st_mode)) + stbuf->st_mode = (stbuf->st_mode & S_IFMT) | + (0777 & ~f->conf.dmask); + else if (f->conf.fmask) + stbuf->st_mode = (stbuf->st_mode & S_IFMT) | + (0777 & ~f->conf.fmask); + else + stbuf->st_mode = (stbuf->st_mode & S_IFMT) | + (0777 & ~f->conf.umask); + } + if (f->conf.set_uid) + stbuf->st_uid = f->conf.uid; + if (f->conf.set_gid) + stbuf->st_gid = f->conf.gid; +} + +#ifdef HAVE_STATX +static void set_statx(struct fuse *f, fuse_ino_t nodeid, struct statx *stxbuf) +{ + if (!f->conf.use_ino) + stxbuf->stx_ino = nodeid; + if (f->conf.set_mode) { + if (f->conf.dmask && S_ISDIR(stxbuf->stx_mode)) + stxbuf->stx_mode = (stxbuf->stx_mode & S_IFMT) | + (0777 & ~f->conf.dmask); + else if (f->conf.fmask) + stxbuf->stx_mode = (stxbuf->stx_mode & S_IFMT) | + (0777 & ~f->conf.fmask); + else + stxbuf->stx_mode = (stxbuf->stx_mode & S_IFMT) | + (0777 & ~f->conf.umask); + } + if (f->conf.set_uid) + stxbuf->stx_uid = f->conf.uid; + if (f->conf.set_gid) + stxbuf->stx_gid = f->conf.gid; +} +#endif + +static struct fuse *req_fuse(fuse_req_t req) +{ + return (struct fuse *) fuse_req_userdata(req); +} + +static void fuse_intr_sighandler(int sig) +{ + (void) sig; + /* Nothing to do */ +} + +struct fuse_intr_data { + pthread_t id; + pthread_cond_t cond; + int finished; +}; + +static void fuse_interrupt(fuse_req_t req, void *d_) +{ + struct fuse_intr_data *d = d_; + struct fuse *f = req_fuse(req); + + if (d->id == pthread_self()) + return; + + pthread_mutex_lock(&f->lock); + while (!d->finished) { + struct timeval now; + struct timespec timeout; + + pthread_kill(d->id, f->conf.intr_signal); + gettimeofday(&now, NULL); + timeout.tv_sec = now.tv_sec + 1; + timeout.tv_nsec = now.tv_usec * 1000; + pthread_cond_timedwait(&d->cond, &f->lock, &timeout); + } + pthread_mutex_unlock(&f->lock); +} + +static void fuse_do_finish_interrupt(struct fuse *f, fuse_req_t req, + struct fuse_intr_data *d) +{ + pthread_mutex_lock(&f->lock); + d->finished = 1; + pthread_cond_broadcast(&d->cond); + pthread_mutex_unlock(&f->lock); + fuse_req_interrupt_func(req, NULL, NULL); + pthread_cond_destroy(&d->cond); +} + +static void fuse_do_prepare_interrupt(fuse_req_t req, struct fuse_intr_data *d) +{ + d->id = pthread_self(); + pthread_cond_init(&d->cond, NULL); + d->finished = 0; + fuse_req_interrupt_func(req, fuse_interrupt, d); +} + +static inline void fuse_finish_interrupt(struct fuse *f, fuse_req_t req, + struct fuse_intr_data *d) +{ + if (f->conf.intr) + fuse_do_finish_interrupt(f, req, d); +} + +static inline void fuse_prepare_interrupt(struct fuse *f, fuse_req_t req, + struct fuse_intr_data *d) +{ + if (f->conf.intr) + fuse_do_prepare_interrupt(req, d); +} + +static const char* file_info_string(struct fuse_file_info *fi, + char* buf, size_t len) +{ + if(fi == NULL) + return "NULL"; + snprintf(buf, len, "%llu", (unsigned long long) fi->fh); + return buf; +} + +int fuse_fs_getattr(struct fuse_fs *fs, const char *path, struct stat *buf, + struct fuse_file_info *fi) +{ + fuse_get_context()->private_data = fs->user_data; + if (!fs->op.getattr) + return -ENOSYS; + + if (fs->debug) { + char buf[10]; + + fuse_log(FUSE_LOG_DEBUG, "getattr[%s] %s\n", + file_info_string(fi, buf, sizeof(buf)), + path); + } + return fs->op.getattr(path, buf, fi); +} + +int fuse_fs_rename(struct fuse_fs *fs, const char *oldpath, + const char *newpath, unsigned int flags) +{ + fuse_get_context()->private_data = fs->user_data; + if (!fs->op.rename) + return -ENOSYS; + if (fs->debug) + fuse_log(FUSE_LOG_DEBUG, "rename %s %s 0x%x\n", oldpath, newpath, + flags); + + return fs->op.rename(oldpath, newpath, flags); +} + +int fuse_fs_unlink(struct fuse_fs *fs, const char *path) +{ + fuse_get_context()->private_data = fs->user_data; + if (!fs->op.unlink) + return -ENOSYS; + if (fs->debug) + fuse_log(FUSE_LOG_DEBUG, "unlink %s\n", path); + + return fs->op.unlink(path); +} + +int fuse_fs_rmdir(struct fuse_fs *fs, const char *path) +{ + fuse_get_context()->private_data = fs->user_data; + if (!fs->op.rmdir) + return -ENOSYS; + if (fs->debug) + fuse_log(FUSE_LOG_DEBUG, "rmdir %s\n", path); + + return fs->op.rmdir(path); +} + +int fuse_fs_symlink(struct fuse_fs *fs, const char *linkname, const char *path) +{ + fuse_get_context()->private_data = fs->user_data; + if (!fs->op.symlink) + return -ENOSYS; + if (fs->debug) + fuse_log(FUSE_LOG_DEBUG, "symlink %s %s\n", linkname, path); + + return fs->op.symlink(linkname, path); +} + +int fuse_fs_link(struct fuse_fs *fs, const char *oldpath, const char *newpath) +{ + fuse_get_context()->private_data = fs->user_data; + if (!fs->op.link) + return -ENOSYS; + if (fs->debug) + fuse_log(FUSE_LOG_DEBUG, "link %s %s\n", oldpath, newpath); + + return fs->op.link(oldpath, newpath); +} + +int fuse_fs_release(struct fuse_fs *fs, const char *path, + struct fuse_file_info *fi) +{ + fuse_get_context()->private_data = fs->user_data; + if (!fs->op.release) + return 0; + + if (fs->debug) + fuse_log(FUSE_LOG_DEBUG, "release%s[%llu] flags: 0x%x\n", + fi->flush ? "+flush" : "", + (unsigned long long) fi->fh, fi->flags); + + return fs->op.release(path, fi); +} + +int fuse_fs_opendir(struct fuse_fs *fs, const char *path, + struct fuse_file_info *fi) +{ + int err; + + fuse_get_context()->private_data = fs->user_data; + if (!fs->op.opendir) + return 0; + + if (fs->debug) + fuse_log(FUSE_LOG_DEBUG, "opendir flags: 0x%x %s\n", fi->flags, + path); + + err = fs->op.opendir(path, fi); + + if (fs->debug && !err) + fuse_log(FUSE_LOG_DEBUG, " opendir[%llu] flags: 0x%x %s\n", + (unsigned long long) fi->fh, fi->flags, path); + + return err; +} + +int fuse_fs_open(struct fuse_fs *fs, const char *path, + struct fuse_file_info *fi) +{ + int err; + + fuse_get_context()->private_data = fs->user_data; + if (!fs->op.open) + return 0; + + if (fs->debug) + fuse_log(FUSE_LOG_DEBUG, "open flags: 0x%x %s\n", fi->flags, + path); + + err = fs->op.open(path, fi); + + if (fs->debug && !err) + fuse_log(FUSE_LOG_DEBUG, " open[%llu] flags: 0x%x %s\n", + (unsigned long long) fi->fh, fi->flags, path); + + return err; +} + +static void fuse_free_buf(struct fuse_bufvec *buf) +{ + if (buf != NULL) { + size_t i; + + for (i = 0; i < buf->count; i++) + if (!(buf->buf[i].flags & FUSE_BUF_IS_FD)) + free(buf->buf[i].mem); + free(buf); + } +} + +int fuse_fs_read_buf(struct fuse_fs *fs, const char *path, + struct fuse_bufvec **bufp, size_t size, off_t off, + struct fuse_file_info *fi) +{ + int res; + + fuse_get_context()->private_data = fs->user_data; + if (!fs->op.read && !fs->op.read_buf) + return -ENOSYS; + + if (fs->debug) + fuse_log(FUSE_LOG_DEBUG, + "read[%llu] %zu bytes from %llu flags: 0x%x\n", + (unsigned long long) fi->fh, + size, (unsigned long long) off, fi->flags); + + if (fs->op.read_buf) { + res = fs->op.read_buf(path, bufp, size, off, fi); + } else { + struct fuse_bufvec *buf; + void *mem; + + buf = malloc(sizeof(struct fuse_bufvec)); + if (buf == NULL) + return -ENOMEM; + + mem = malloc(size); + if (mem == NULL) { + free(buf); + return -ENOMEM; + } + *buf = FUSE_BUFVEC_INIT(size); + buf->buf[0].mem = mem; + *bufp = buf; + + res = fs->op.read(path, mem, size, off, fi); + if (res >= 0) + buf->buf[0].size = res; + } + + if (fs->debug && res >= 0) + fuse_log(FUSE_LOG_DEBUG, " read[%llu] %zu bytes from %llu\n", + (unsigned long long) fi->fh, + fuse_buf_size(*bufp), + (unsigned long long) off); + if (res >= 0 && fuse_buf_size(*bufp) > size) + fuse_log(FUSE_LOG_ERR, "fuse: read too many bytes\n"); + + if (res < 0) + return res; + + return 0; +} + +int fuse_fs_read(struct fuse_fs *fs, const char *path, char *mem, size_t size, + off_t off, struct fuse_file_info *fi) +{ + int res; + + fuse_get_context()->private_data = fs->user_data; + if (!fs->op.read && !fs->op.read_buf) + return -ENOSYS; + + if (fs->debug) + fuse_log(FUSE_LOG_DEBUG, + "read[%llu] %zu bytes from %llu flags: 0x%x\n", + (unsigned long long) fi->fh, + size, (unsigned long long) off, fi->flags); + + if (fs->op.read_buf) { + struct fuse_bufvec *buf = NULL; + + res = fs->op.read_buf(path, &buf, size, off, fi); + if (res == 0) { + struct fuse_bufvec dst = FUSE_BUFVEC_INIT(size); + + dst.buf[0].mem = mem; + res = fuse_buf_copy(&dst, buf, 0); + } + fuse_free_buf(buf); + } else { + res = fs->op.read(path, mem, size, off, fi); + } + + if (fs->debug && res >= 0) + fuse_log(FUSE_LOG_DEBUG, " read[%llu] %u bytes from %llu\n", + (unsigned long long) fi->fh, + res, + (unsigned long long) off); + if (res >= 0 && res > (int) size) + fuse_log(FUSE_LOG_ERR, "fuse: read too many bytes\n"); + + return res; +} + +int fuse_fs_write_buf(struct fuse_fs *fs, const char *path, + struct fuse_bufvec *buf, off_t off, + struct fuse_file_info *fi) +{ + int res; + size_t size; + + fuse_get_context()->private_data = fs->user_data; + if (!fs->op.write_buf && !fs->op.write) + return -ENOSYS; + + size = fuse_buf_size(buf); + assert(buf->idx == 0 && buf->off == 0); + if (fs->debug) + fuse_log(FUSE_LOG_DEBUG, + "write%s[%llu] %zu bytes to %llu flags: 0x%x\n", + fi->writepage ? "page" : "", + (unsigned long long) fi->fh, + size, + (unsigned long long) off, + fi->flags); + + if (fs->op.write_buf) { + res = fs->op.write_buf(path, buf, off, fi); + } else { + void *mem = NULL; + struct fuse_buf *flatbuf; + struct fuse_bufvec tmp = FUSE_BUFVEC_INIT(size); + + if (buf->count == 1 && + !(buf->buf[0].flags & FUSE_BUF_IS_FD)) { + flatbuf = &buf->buf[0]; + } else { + res = -ENOMEM; + mem = malloc(size); + if (mem == NULL) + goto out; + + tmp.buf[0].mem = mem; + res = fuse_buf_copy(&tmp, buf, 0); + if (res <= 0) + goto out_free; + + tmp.buf[0].size = res; + flatbuf = &tmp.buf[0]; + } + + res = fs->op.write(path, flatbuf->mem, flatbuf->size, + off, fi); +out_free: + free(mem); + } +out: + if (fs->debug && res >= 0) + fuse_log(FUSE_LOG_DEBUG, " write%s[%llu] %u bytes to %llu\n", + fi->writepage ? "page" : "", + (unsigned long long) fi->fh, res, + (unsigned long long) off); + if (res > (int) size) + fuse_log(FUSE_LOG_ERR, "fuse: wrote too many bytes\n"); + + return res; +} + +int fuse_fs_write(struct fuse_fs *fs, const char *path, const char *mem, + size_t size, off_t off, struct fuse_file_info *fi) +{ + struct fuse_bufvec bufv = FUSE_BUFVEC_INIT(size); + + bufv.buf[0].mem = (void *) mem; + + return fuse_fs_write_buf(fs, path, &bufv, off, fi); +} + +int fuse_fs_fsync(struct fuse_fs *fs, const char *path, int datasync, + struct fuse_file_info *fi) +{ + fuse_get_context()->private_data = fs->user_data; + if (!fs->op.fsync) + return -ENOSYS; + + if (fs->debug) + fuse_log(FUSE_LOG_DEBUG, "fsync[%llu] datasync: %i\n", + (unsigned long long) fi->fh, datasync); + + return fs->op.fsync(path, datasync, fi); +} + +int fuse_fs_fsyncdir(struct fuse_fs *fs, const char *path, int datasync, + struct fuse_file_info *fi) +{ + fuse_get_context()->private_data = fs->user_data; + if (!fs->op.fsyncdir) + return -ENOSYS; + + if (fs->debug) + fuse_log(FUSE_LOG_DEBUG, "fsyncdir[%llu] datasync: %i\n", + (unsigned long long) fi->fh, datasync); + + return fs->op.fsyncdir(path, datasync, fi); +} + +int fuse_fs_flush(struct fuse_fs *fs, const char *path, + struct fuse_file_info *fi) +{ + fuse_get_context()->private_data = fs->user_data; + if (!fs->op.flush) + return -ENOSYS; + if (fs->debug) + fuse_log(FUSE_LOG_DEBUG, "flush[%llu]\n", + (unsigned long long) fi->fh); + + return fs->op.flush(path, fi); +} + +int fuse_fs_statfs(struct fuse_fs *fs, const char *path, struct statvfs *buf) +{ + fuse_get_context()->private_data = fs->user_data; + if (fs->op.statfs) { + if (fs->debug) + fuse_log(FUSE_LOG_DEBUG, "statfs %s\n", path); + + return fs->op.statfs(path, buf); + } else { + buf->f_namemax = 255; + buf->f_bsize = 512; + return 0; + } +} + +int fuse_fs_releasedir(struct fuse_fs *fs, const char *path, + struct fuse_file_info *fi) +{ + fuse_get_context()->private_data = fs->user_data; + if (!fs->op.releasedir) + return 0; + + if (fs->debug) + fuse_log(FUSE_LOG_DEBUG, "releasedir[%llu] flags: 0x%x\n", + (unsigned long long) fi->fh, fi->flags); + + return fs->op.releasedir(path, fi); +} + +int fuse_fs_readdir(struct fuse_fs *fs, const char *path, void *buf, + fuse_fill_dir_t filler, off_t off, + struct fuse_file_info *fi, + enum fuse_readdir_flags flags) +{ + fuse_get_context()->private_data = fs->user_data; + if (!fs->op.readdir) + return -ENOSYS; + if (fs->debug) { + fuse_log(FUSE_LOG_DEBUG, "readdir%s[%llu] from %llu\n", + (flags & FUSE_READDIR_PLUS) ? "plus" : "", + (unsigned long long) fi->fh, + (unsigned long long) off); + } + + return fs->op.readdir(path, buf, filler, off, fi, flags); +} + +int fuse_fs_create(struct fuse_fs *fs, const char *path, mode_t mode, + struct fuse_file_info *fi) +{ + int err; + + fuse_get_context()->private_data = fs->user_data; + if (!fs->op.create) + return -ENOSYS; + + if (fs->debug) + fuse_log(FUSE_LOG_DEBUG, + "create flags: 0x%x %s 0%o umask=0%03o\n", + fi->flags, path, mode, + fuse_get_context()->umask); + + err = fs->op.create(path, mode, fi); + + if (fs->debug && !err) + fuse_log(FUSE_LOG_DEBUG, " create[%llu] flags: 0x%x %s\n", + (unsigned long long) fi->fh, fi->flags, path); + + return err; +} + +int fuse_fs_lock(struct fuse_fs *fs, const char *path, + struct fuse_file_info *fi, int cmd, struct flock *lock) +{ + fuse_get_context()->private_data = fs->user_data; + if (!fs->op.lock) + return -ENOSYS; + + if (fs->debug) + fuse_log(FUSE_LOG_DEBUG, "lock[%llu] %s %s start: %llu len: %llu pid: %llu\n", + (unsigned long long) fi->fh, + (cmd == F_GETLK ? "F_GETLK" : + (cmd == F_SETLK ? "F_SETLK" : + (cmd == F_SETLKW ? "F_SETLKW" : "???"))), + (lock->l_type == F_RDLCK ? "F_RDLCK" : + (lock->l_type == F_WRLCK ? "F_WRLCK" : + (lock->l_type == F_UNLCK ? "F_UNLCK" : + "???"))), + (unsigned long long) lock->l_start, + (unsigned long long) lock->l_len, + (unsigned long long) lock->l_pid); + + return fs->op.lock(path, fi, cmd, lock); +} + +int fuse_fs_flock(struct fuse_fs *fs, const char *path, + struct fuse_file_info *fi, int op) +{ + fuse_get_context()->private_data = fs->user_data; + if (!fs->op.flock) + return -ENOSYS; + + if (fs->debug) { + int xop = op & ~LOCK_NB; + + fuse_log(FUSE_LOG_DEBUG, "lock[%llu] %s%s\n", + (unsigned long long) fi->fh, + xop == LOCK_SH ? "LOCK_SH" : + (xop == LOCK_EX ? "LOCK_EX" : + (xop == LOCK_UN ? "LOCK_UN" : "???")), + (op & LOCK_NB) ? "|LOCK_NB" : ""); + } + return fs->op.flock(path, fi, op); +} + +int fuse_fs_chown(struct fuse_fs *fs, const char *path, uid_t uid, + gid_t gid, struct fuse_file_info *fi) +{ + fuse_get_context()->private_data = fs->user_data; + if (!fs->op.chown) + return -ENOSYS; + if (fs->debug) { + char buf[10]; + + fuse_log(FUSE_LOG_DEBUG, "chown[%s] %s %lu %lu\n", + file_info_string(fi, buf, sizeof(buf)), + path, (unsigned long) uid, (unsigned long) gid); + } + return fs->op.chown(path, uid, gid, fi); +} + +int fuse_fs_truncate(struct fuse_fs *fs, const char *path, off_t size, + struct fuse_file_info *fi) +{ + fuse_get_context()->private_data = fs->user_data; + if (!fs->op.truncate) + return -ENOSYS; + if (fs->debug) { + char buf[10]; + + fuse_log(FUSE_LOG_DEBUG, "truncate[%s] %llu\n", + file_info_string(fi, buf, sizeof(buf)), + (unsigned long long) size); + } + return fs->op.truncate(path, size, fi); +} + +int fuse_fs_utimens(struct fuse_fs *fs, const char *path, + const struct timespec tv[2], struct fuse_file_info *fi) +{ + fuse_get_context()->private_data = fs->user_data; + if (!fs->op.utimens) + return -ENOSYS; + if (fs->debug) { + char buf[10]; + + fuse_log(FUSE_LOG_DEBUG, "utimens[%s] %s %jd.%09ld %jd.%09ld\n", + file_info_string(fi, buf, sizeof(buf)), + path, (intmax_t)tv[0].tv_sec, tv[0].tv_nsec, + (intmax_t)tv[1].tv_sec, tv[1].tv_nsec); + } + return fs->op.utimens(path, tv, fi); +} + +int fuse_fs_access(struct fuse_fs *fs, const char *path, int mask) +{ + fuse_get_context()->private_data = fs->user_data; + if (!fs->op.access) + return -ENOSYS; + if (fs->debug) + fuse_log(FUSE_LOG_DEBUG, "access %s 0%o\n", path, mask); + + return fs->op.access(path, mask); +} + +int fuse_fs_readlink(struct fuse_fs *fs, const char *path, char *buf, + size_t len) +{ + fuse_get_context()->private_data = fs->user_data; + if (!fs->op.readlink) + return -ENOSYS; + if (fs->debug) + fuse_log(FUSE_LOG_DEBUG, "readlink %s %lu\n", path, + (unsigned long) len); + + return fs->op.readlink(path, buf, len); +} + +int fuse_fs_mknod(struct fuse_fs *fs, const char *path, mode_t mode, + dev_t rdev) +{ + fuse_get_context()->private_data = fs->user_data; + if (!fs->op.mknod) + return -ENOSYS; + if (fs->debug) + fuse_log(FUSE_LOG_DEBUG, "mknod %s 0%o 0x%llx umask=0%03o\n", + path, mode, (unsigned long long) rdev, + fuse_get_context()->umask); + + return fs->op.mknod(path, mode, rdev); +} + +int fuse_fs_mkdir(struct fuse_fs *fs, const char *path, mode_t mode) +{ + fuse_get_context()->private_data = fs->user_data; + if (!fs->op.mkdir) + return -ENOSYS; + if (fs->debug) + fuse_log(FUSE_LOG_DEBUG, "mkdir %s 0%o umask=0%03o\n", + path, mode, fuse_get_context()->umask); + + return fs->op.mkdir(path, mode); +} + +int fuse_fs_setxattr(struct fuse_fs *fs, const char *path, const char *name, + const char *value, size_t size, int flags) +{ + fuse_get_context()->private_data = fs->user_data; + if (!fs->op.setxattr) + return -ENOSYS; + if (fs->debug) + fuse_log(FUSE_LOG_DEBUG, "setxattr %s %s %lu 0x%x\n", + path, name, (unsigned long) size, flags); + + return fs->op.setxattr(path, name, value, size, flags); +} + +int fuse_fs_getxattr(struct fuse_fs *fs, const char *path, const char *name, + char *value, size_t size) +{ + fuse_get_context()->private_data = fs->user_data; + if (!fs->op.getxattr) + return -ENOSYS; + if (fs->debug) + fuse_log(FUSE_LOG_DEBUG, "getxattr %s %s %lu\n", + path, name, (unsigned long) size); + + return fs->op.getxattr(path, name, value, size); +} + +int fuse_fs_listxattr(struct fuse_fs *fs, const char *path, char *list, + size_t size) +{ + fuse_get_context()->private_data = fs->user_data; + if (!fs->op.listxattr) + return -ENOSYS; + if (fs->debug) + fuse_log(FUSE_LOG_DEBUG, "listxattr %s %lu\n", + path, (unsigned long) size); + + return fs->op.listxattr(path, list, size); +} + +int fuse_fs_bmap(struct fuse_fs *fs, const char *path, size_t blocksize, + uint64_t *idx) +{ + fuse_get_context()->private_data = fs->user_data; + if (!fs->op.bmap) + return -ENOSYS; + if (fs->debug) + fuse_log(FUSE_LOG_DEBUG, "bmap %s blocksize: %lu index: %llu\n", + path, (unsigned long) blocksize, + (unsigned long long) *idx); + + return fs->op.bmap(path, blocksize, idx); +} + +int fuse_fs_removexattr(struct fuse_fs *fs, const char *path, const char *name) +{ + fuse_get_context()->private_data = fs->user_data; + if (!fs->op.removexattr) + return -ENOSYS; + if (fs->debug) + fuse_log(FUSE_LOG_DEBUG, "removexattr %s %s\n", path, name); + + return fs->op.removexattr(path, name); +} + +int fuse_fs_ioctl(struct fuse_fs *fs, const char *path, unsigned int cmd, + void *arg, struct fuse_file_info *fi, unsigned int flags, + void *data) +{ + fuse_get_context()->private_data = fs->user_data; + if (!fs->op.ioctl) + return -ENOSYS; + if (fs->debug) + fuse_log(FUSE_LOG_DEBUG, "ioctl[%llu] 0x%x flags: 0x%x\n", + (unsigned long long) fi->fh, cmd, flags); + + return fs->op.ioctl(path, cmd, arg, fi, flags, data); +} + +int fuse_fs_poll(struct fuse_fs *fs, const char *path, + struct fuse_file_info *fi, struct fuse_pollhandle *ph, + unsigned *reventsp) +{ + int res; + + fuse_get_context()->private_data = fs->user_data; + + if (!fs->op.poll) { + fuse_pollhandle_destroy(ph); + return -ENOSYS; + } + + if (fs->debug) + fuse_log(FUSE_LOG_DEBUG, "poll[%llu] ph: %p, events 0x%x\n", + (unsigned long long) fi->fh, ph, + fi->poll_events); + + res = fs->op.poll(path, fi, ph, reventsp); + + if (fs->debug && !res) + fuse_log(FUSE_LOG_DEBUG, " poll[%llu] revents: 0x%x\n", + (unsigned long long) fi->fh, *reventsp); + + return res; +} + +int fuse_fs_fallocate(struct fuse_fs *fs, const char *path, int mode, + off_t offset, off_t length, struct fuse_file_info *fi) +{ + fuse_get_context()->private_data = fs->user_data; + if (!fs->op.fallocate) + return -ENOSYS; + if (fs->debug) + fuse_log(FUSE_LOG_DEBUG, "fallocate %s mode %x, offset: %llu, length: %llu\n", + path, + mode, + (unsigned long long) offset, + (unsigned long long) length); + + return fs->op.fallocate(path, mode, offset, length, fi); +} + +ssize_t fuse_fs_copy_file_range(struct fuse_fs *fs, const char *path_in, + struct fuse_file_info *fi_in, off_t off_in, + const char *path_out, + struct fuse_file_info *fi_out, off_t off_out, + size_t len, int flags) +{ + fuse_get_context()->private_data = fs->user_data; + if (!fs->op.copy_file_range) + return -ENOSYS; + if (fs->debug) + fuse_log(FUSE_LOG_DEBUG, "copy_file_range from %s:%llu to " + "%s:%llu, length: %llu\n", + path_in, + (unsigned long long) off_in, + path_out, + (unsigned long long) off_out, + (unsigned long long) len); + + return fs->op.copy_file_range(path_in, fi_in, off_in, path_out, + fi_out, off_out, len, flags); +} + +off_t fuse_fs_lseek(struct fuse_fs *fs, const char *path, off_t off, int whence, + struct fuse_file_info *fi) +{ + fuse_get_context()->private_data = fs->user_data; + if (!fs->op.lseek) + return -ENOSYS; + if (fs->debug) { + char buf[10]; + + fuse_log(FUSE_LOG_DEBUG, "lseek[%s] %llu %d\n", + file_info_string(fi, buf, sizeof(buf)), + (unsigned long long) off, whence); + } + return fs->op.lseek(path, off, whence, fi); +} + +#ifdef HAVE_STATX +int fuse_fs_statx(struct fuse_fs *fs, const char *path, int flags, int mask, + struct statx *stxbuf, struct fuse_file_info *fi) +{ + fuse_get_context()->private_data = fs->user_data; + if (fs->op.statx) { + if (fs->debug) { + char buf[10]; + + fuse_log(FUSE_LOG_DEBUG, "statx[%s] %s %d %d\n", + file_info_string(fi, buf, sizeof(buf)), path, + flags, mask); + } + return fs->op.statx(path, flags, mask, stxbuf, fi); + } + + return -ENOSYS; +} +#else +int fuse_fs_statx(struct fuse_fs *fs, const char *path, int flags, int mask, + struct statx *stxbuf, struct fuse_file_info *fi) +{ + (void)fs; + (void)path; + (void)flags; + (void)mask; + (void)stxbuf; + (void)fi; + + return -ENOSYS; +} +#endif + +static int is_open(struct fuse *f, fuse_ino_t dir, const char *name) +{ + struct node *node; + int isopen = 0; + pthread_mutex_lock(&f->lock); + node = lookup_node(f, dir, name); + if (node && node->open_count > 0) + isopen = 1; + pthread_mutex_unlock(&f->lock); + return isopen; +} + +static char *hidden_name(struct fuse *f, fuse_ino_t dir, const char *oldname, + char *newname, size_t bufsize) +{ + struct stat buf; + struct node *node; + struct node *newnode; + char *newpath; + int res; + int failctr = 10; + + do { + pthread_mutex_lock(&f->lock); + node = lookup_node(f, dir, oldname); + if (node == NULL) { + pthread_mutex_unlock(&f->lock); + return NULL; + } + do { + f->hidectr ++; + snprintf(newname, bufsize, ".fuse_hidden%08x%08x", + (unsigned int) node->nodeid, f->hidectr); + newnode = lookup_node(f, dir, newname); + } while(newnode); + + res = try_get_path(f, dir, newname, &newpath, NULL, false); + pthread_mutex_unlock(&f->lock); + if (res) + break; + + memset(&buf, 0, sizeof(buf)); + res = fuse_fs_getattr(f->fs, newpath, &buf, NULL); + if (res == -ENOENT) + break; + free(newpath); + newpath = NULL; + } while(res == 0 && --failctr); + + return newpath; +} + +static int hide_node(struct fuse *f, const char *oldpath, + fuse_ino_t dir, const char *oldname) +{ + char newname[64]; + char *newpath; + int err = -EBUSY; + + newpath = hidden_name(f, dir, oldname, newname, sizeof(newname)); + if (newpath) { + err = fuse_fs_rename(f->fs, oldpath, newpath, 0); + if (!err) + err = rename_node(f, dir, oldname, dir, newname, 1); + free(newpath); + } + return err; +} + +static int mtime_eq(const struct stat *stbuf, const struct timespec *ts) +{ + return stbuf->st_mtime == ts->tv_sec && + ST_MTIM_NSEC(stbuf) == ts->tv_nsec; +} + +#ifndef CLOCK_MONOTONIC +#define CLOCK_MONOTONIC CLOCK_REALTIME +#endif + +static void curr_time(struct timespec *now) +{ + static clockid_t clockid = CLOCK_MONOTONIC; + int res = clock_gettime(clockid, now); + if (res == -1 && errno == EINVAL) { + clockid = CLOCK_REALTIME; + res = clock_gettime(clockid, now); + } + if (res == -1) { + perror("fuse: clock_gettime"); + abort(); + } +} + +static void update_stat(struct node *node, const struct stat *stbuf) +{ + if (node->cache_valid && (!mtime_eq(stbuf, &node->mtime) || + stbuf->st_size != node->size)) + node->cache_valid = 0; + node->mtime.tv_sec = stbuf->st_mtime; + node->mtime.tv_nsec = ST_MTIM_NSEC(stbuf); + node->size = stbuf->st_size; + curr_time(&node->stat_updated); +} + +static int do_lookup(struct fuse *f, fuse_ino_t nodeid, const char *name, + struct fuse_entry_param *e) +{ + struct node *node; + + node = find_node(f, nodeid, name); + if (node == NULL) + return -ENOMEM; + + e->ino = node->nodeid; + e->generation = node->generation; + e->entry_timeout = f->conf.entry_timeout; + e->attr_timeout = f->conf.attr_timeout; + if (f->conf.auto_cache) { + pthread_mutex_lock(&f->lock); + update_stat(node, &e->attr); + pthread_mutex_unlock(&f->lock); + } + set_stat(f, e->ino, &e->attr); + return 0; +} + +static int lookup_path(struct fuse *f, fuse_ino_t nodeid, + const char *name, const char *path, + struct fuse_entry_param *e, struct fuse_file_info *fi) +{ + int res; + + memset(e, 0, sizeof(struct fuse_entry_param)); + res = fuse_fs_getattr(f->fs, path, &e->attr, fi); + if (res == 0) { + res = do_lookup(f, nodeid, name, e); + if (res == 0 && f->conf.debug) { + fuse_log(FUSE_LOG_DEBUG, " NODEID: %llu\n", + (unsigned long long) e->ino); + } + } + return res; +} + +static struct fuse_context_i *fuse_get_context_internal(void) +{ + return (struct fuse_context_i *) pthread_getspecific(fuse_context_key); +} + +static struct fuse_context_i *fuse_create_context(struct fuse *f) +{ + struct fuse_context_i *c = fuse_get_context_internal(); + if (c == NULL) { + c = (struct fuse_context_i *) + calloc(1, sizeof(struct fuse_context_i)); + if (c == NULL) { + /* This is hard to deal with properly, so just + abort. If memory is so low that the + context cannot be allocated, there's not + much hope for the filesystem anyway */ + fuse_log(FUSE_LOG_ERR, "fuse: failed to allocate thread specific data\n"); + abort(); + } + if (pthread_setspecific(fuse_context_key, c) != 0) { + fuse_log(FUSE_LOG_ERR, "fuse: failed to set thread specific data\n"); + free(c); + abort(); + } + } else { + memset(c, 0, sizeof(*c)); + } + c->ctx.fuse = f; + + return c; +} + +static void fuse_freecontext(void *data) +{ + free(data); +} + +static int fuse_create_context_key(void) +{ + int err = 0; + pthread_mutex_lock(&fuse_context_lock); + if (!fuse_context_ref) { + err = pthread_key_create(&fuse_context_key, fuse_freecontext); + if (err) { + fuse_log(FUSE_LOG_ERR, "fuse: failed to create thread specific key: %s\n", + strerror(err)); + pthread_mutex_unlock(&fuse_context_lock); + return -1; + } + } + fuse_context_ref++; + pthread_mutex_unlock(&fuse_context_lock); + return 0; +} + +static void fuse_delete_context_key(void) +{ + pthread_mutex_lock(&fuse_context_lock); + fuse_context_ref--; + if (!fuse_context_ref) { + free(pthread_getspecific(fuse_context_key)); + pthread_key_delete(fuse_context_key); + } + pthread_mutex_unlock(&fuse_context_lock); +} + +static struct fuse *req_fuse_prepare(fuse_req_t req) +{ + struct fuse_context_i *c = fuse_create_context(req_fuse(req)); + const struct fuse_ctx *ctx = fuse_req_ctx(req); + c->req = req; + c->ctx.uid = ctx->uid; + c->ctx.gid = ctx->gid; + c->ctx.pid = ctx->pid; + c->ctx.umask = ctx->umask; + return c->ctx.fuse; +} + +static inline void reply_err(fuse_req_t req, int err) +{ + /* fuse_reply_err() uses non-negated errno values */ + fuse_reply_err(req, -err); +} + +static void reply_entry(fuse_req_t req, const struct fuse_entry_param *e, + int err) +{ + if (!err) { + struct fuse *f = req_fuse(req); + if (fuse_reply_entry(req, e) == -ENOENT) { + /* Skip forget for negative result */ + if (e->ino != 0) + forget_node(f, e->ino, 1); + } + } else + reply_err(req, err); +} + +void fuse_fs_init(struct fuse_fs *fs, struct fuse_conn_info *conn, + struct fuse_config *cfg) +{ + fuse_get_context()->private_data = fs->user_data; + if (!fs->op.write_buf) + fuse_unset_feature_flag(conn, FUSE_CAP_SPLICE_READ); + if (!fs->op.lock) + fuse_unset_feature_flag(conn, FUSE_CAP_POSIX_LOCKS); + if (!fs->op.flock) + fuse_unset_feature_flag(conn, FUSE_CAP_FLOCK_LOCKS); + if (fs->op.init) + fs->user_data = fs->op.init(conn, cfg); +} + +static int fuse_init_intr_signal(int signum, int *installed); + +static void fuse_lib_init(void *data, struct fuse_conn_info *conn) +{ + struct fuse *f = (struct fuse *) data; + + fuse_create_context(f); + fuse_set_feature_flag(conn, FUSE_CAP_EXPORT_SUPPORT); + fuse_fs_init(f->fs, conn, &f->conf); + + if (f->conf.intr) { + if (fuse_init_intr_signal(f->conf.intr_signal, + &f->intr_installed) == -1) + fuse_log(FUSE_LOG_ERR, "fuse: failed to init interrupt signal\n"); + } else { + /* Disable the receiving and processing of FUSE_INTERRUPT requests */ + conn->no_interrupt = 1; + } +} + +void fuse_fs_destroy(struct fuse_fs *fs) +{ + fuse_get_context()->private_data = fs->user_data; + if (fs->op.destroy) + fs->op.destroy(fs->user_data); +} + +static void fuse_lib_destroy(void *data) +{ + struct fuse *f = (struct fuse *) data; + + fuse_create_context(f); + fuse_fs_destroy(f->fs); +} + +static void fuse_lib_lookup(fuse_req_t req, fuse_ino_t parent, + const char *name) +{ + struct fuse *f = req_fuse_prepare(req); + struct fuse_entry_param e = { .ino = 0 }; /* invalid ino */ + char *path; + int err; + struct node *dot = NULL; + + if (name[0] == '.') { + int len = strlen(name); + + if (len == 1 || (name[1] == '.' && len == 2)) { + pthread_mutex_lock(&f->lock); + if (len == 1) { + if (f->conf.debug) + fuse_log(FUSE_LOG_DEBUG, "LOOKUP-DOT\n"); + dot = get_node_nocheck(f, parent); + if (dot == NULL) { + pthread_mutex_unlock(&f->lock); + reply_entry(req, &e, -ESTALE); + return; + } + dot->refctr++; + } else { + if (f->conf.debug) + fuse_log(FUSE_LOG_DEBUG, "LOOKUP-DOTDOT\n"); + parent = get_node(f, parent)->parent->nodeid; + } + pthread_mutex_unlock(&f->lock); + name = NULL; + } + } + + err = get_path_name(f, parent, name, &path); + if (!err) { + struct fuse_intr_data d; + if (f->conf.debug) + fuse_log(FUSE_LOG_DEBUG, "LOOKUP %s\n", path); + fuse_prepare_interrupt(f, req, &d); + err = lookup_path(f, parent, name, path, &e, NULL); + if (err == -ENOENT && f->conf.negative_timeout != 0.0) { + e.ino = 0; + e.entry_timeout = f->conf.negative_timeout; + err = 0; + } + fuse_finish_interrupt(f, req, &d); + free_path(f, parent, path); + } + if (dot) { + pthread_mutex_lock(&f->lock); + unref_node(f, dot); + pthread_mutex_unlock(&f->lock); + } + reply_entry(req, &e, err); +} + +static void do_forget(struct fuse *f, fuse_ino_t ino, uint64_t nlookup) +{ + if (f->conf.debug) + fuse_log(FUSE_LOG_DEBUG, "FORGET %llu/%llu\n", (unsigned long long)ino, + (unsigned long long) nlookup); + forget_node(f, ino, nlookup); +} + +static void fuse_lib_forget(fuse_req_t req, fuse_ino_t ino, uint64_t nlookup) +{ + do_forget(req_fuse(req), ino, nlookup); + fuse_reply_none(req); +} + +static void fuse_lib_forget_multi(fuse_req_t req, size_t count, + struct fuse_forget_data *forgets) +{ + struct fuse *f = req_fuse(req); + size_t i; + + for (i = 0; i < count; i++) + do_forget(f, forgets[i].ino, forgets[i].nlookup); + + fuse_reply_none(req); +} + + +static void fuse_lib_getattr(fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info *fi) +{ + struct fuse *f = req_fuse_prepare(req); + struct stat buf; + char *path; + int err; + + memset(&buf, 0, sizeof(buf)); + + if (fi != NULL) + err = get_path_nullok(f, ino, &path); + else + err = get_path(f, ino, &path); + if (!err) { + struct fuse_intr_data d; + fuse_prepare_interrupt(f, req, &d); + err = fuse_fs_getattr(f->fs, path, &buf, fi); + fuse_finish_interrupt(f, req, &d); + free_path(f, ino, path); + } + if (!err) { + struct node *node; + + pthread_mutex_lock(&f->lock); + node = get_node(f, ino); + if (node->is_hidden && buf.st_nlink > 0) + buf.st_nlink--; + if (f->conf.auto_cache) + update_stat(node, &buf); + pthread_mutex_unlock(&f->lock); + set_stat(f, ino, &buf); + fuse_reply_attr(req, &buf, f->conf.attr_timeout); + } else + reply_err(req, err); +} + +int fuse_fs_chmod(struct fuse_fs *fs, const char *path, mode_t mode, + struct fuse_file_info *fi) +{ + fuse_get_context()->private_data = fs->user_data; + if (!fs->op.chmod) + return -ENOSYS; + + if (fs->debug) { + char buf[10]; + + fuse_log(FUSE_LOG_DEBUG, "chmod[%s] %s %llo\n", + file_info_string(fi, buf, sizeof(buf)), + path, (unsigned long long) mode); + } + return fs->op.chmod(path, mode, fi); +} + +static void fuse_lib_setattr(fuse_req_t req, fuse_ino_t ino, struct stat *attr, + int valid, struct fuse_file_info *fi) +{ + struct fuse *f = req_fuse_prepare(req); + struct stat buf; + char *path; + int err; + + memset(&buf, 0, sizeof(buf)); + if (fi != NULL) + err = get_path_nullok(f, ino, &path); + else + err = get_path(f, ino, &path); + if (!err) { + struct fuse_intr_data d; + fuse_prepare_interrupt(f, req, &d); + err = 0; + if (!err && (valid & FUSE_SET_ATTR_MODE)) + err = fuse_fs_chmod(f->fs, path, attr->st_mode, fi); + if (!err && (valid & (FUSE_SET_ATTR_UID | FUSE_SET_ATTR_GID))) { + uid_t uid = (valid & FUSE_SET_ATTR_UID) ? + attr->st_uid : (uid_t) -1; + gid_t gid = (valid & FUSE_SET_ATTR_GID) ? + attr->st_gid : (gid_t) -1; + err = fuse_fs_chown(f->fs, path, uid, gid, fi); + } + if (!err && (valid & FUSE_SET_ATTR_SIZE)) { + err = fuse_fs_truncate(f->fs, path, + attr->st_size, fi); + } +#ifdef HAVE_UTIMENSAT + if (!err && + (valid & (FUSE_SET_ATTR_ATIME | FUSE_SET_ATTR_MTIME))) { + struct timespec tv[2]; + + tv[0].tv_sec = 0; + tv[1].tv_sec = 0; + tv[0].tv_nsec = UTIME_OMIT; + tv[1].tv_nsec = UTIME_OMIT; + + if (valid & FUSE_SET_ATTR_ATIME_NOW) + tv[0].tv_nsec = UTIME_NOW; + else if (valid & FUSE_SET_ATTR_ATIME) + tv[0] = attr->st_atim; + + if (valid & FUSE_SET_ATTR_MTIME_NOW) + tv[1].tv_nsec = UTIME_NOW; + else if (valid & FUSE_SET_ATTR_MTIME) + tv[1] = attr->st_mtim; + + err = fuse_fs_utimens(f->fs, path, tv, fi); + } else +#endif + if (!err && + (valid & (FUSE_SET_ATTR_ATIME | FUSE_SET_ATTR_MTIME)) == + (FUSE_SET_ATTR_ATIME | FUSE_SET_ATTR_MTIME)) { + struct timespec tv[2]; + tv[0].tv_sec = attr->st_atime; + tv[0].tv_nsec = ST_ATIM_NSEC(attr); + tv[1].tv_sec = attr->st_mtime; + tv[1].tv_nsec = ST_MTIM_NSEC(attr); + err = fuse_fs_utimens(f->fs, path, tv, fi); + } + if (!err) { + err = fuse_fs_getattr(f->fs, path, &buf, fi); + } + fuse_finish_interrupt(f, req, &d); + free_path(f, ino, path); + } + if (!err) { + if (f->conf.auto_cache) { + pthread_mutex_lock(&f->lock); + update_stat(get_node(f, ino), &buf); + pthread_mutex_unlock(&f->lock); + } + set_stat(f, ino, &buf); + fuse_reply_attr(req, &buf, f->conf.attr_timeout); + } else + reply_err(req, err); +} + +static void fuse_lib_access(fuse_req_t req, fuse_ino_t ino, int mask) +{ + struct fuse *f = req_fuse_prepare(req); + char *path; + int err; + + err = get_path(f, ino, &path); + if (!err) { + struct fuse_intr_data d; + + fuse_prepare_interrupt(f, req, &d); + err = fuse_fs_access(f->fs, path, mask); + fuse_finish_interrupt(f, req, &d); + free_path(f, ino, path); + } + reply_err(req, err); +} + +static void fuse_lib_readlink(fuse_req_t req, fuse_ino_t ino) +{ + struct fuse *f = req_fuse_prepare(req); + char linkname[PATH_MAX + 1]; + char *path; + int err; + + err = get_path(f, ino, &path); + if (!err) { + struct fuse_intr_data d; + fuse_prepare_interrupt(f, req, &d); + err = fuse_fs_readlink(f->fs, path, linkname, sizeof(linkname)); + fuse_finish_interrupt(f, req, &d); + free_path(f, ino, path); + } + if (!err) { + linkname[PATH_MAX] = '\0'; + fuse_reply_readlink(req, linkname); + } else + reply_err(req, err); +} + +static void fuse_lib_mknod(fuse_req_t req, fuse_ino_t parent, const char *name, + mode_t mode, dev_t rdev) +{ + struct fuse *f = req_fuse_prepare(req); + struct fuse_entry_param e; + char *path; + int err; + + err = get_path_name(f, parent, name, &path); + if (!err) { + struct fuse_intr_data d; + + fuse_prepare_interrupt(f, req, &d); + err = -ENOSYS; + if (S_ISREG(mode)) { + struct fuse_file_info fi; + + memset(&fi, 0, sizeof(fi)); + fi.flags = O_CREAT | O_EXCL | O_WRONLY; + err = fuse_fs_create(f->fs, path, mode, &fi); + if (!err) { + err = lookup_path(f, parent, name, path, &e, + &fi); + fuse_fs_release(f->fs, path, &fi); + } + } + if (err == -ENOSYS) { + err = fuse_fs_mknod(f->fs, path, mode, rdev); + if (!err) + err = lookup_path(f, parent, name, path, &e, + NULL); + } + fuse_finish_interrupt(f, req, &d); + free_path(f, parent, path); + } + reply_entry(req, &e, err); +} + +static void fuse_lib_mkdir(fuse_req_t req, fuse_ino_t parent, const char *name, + mode_t mode) +{ + struct fuse *f = req_fuse_prepare(req); + struct fuse_entry_param e; + char *path; + int err; + + err = get_path_name(f, parent, name, &path); + if (!err) { + struct fuse_intr_data d; + + fuse_prepare_interrupt(f, req, &d); + err = fuse_fs_mkdir(f->fs, path, mode); + if (!err) + err = lookup_path(f, parent, name, path, &e, NULL); + fuse_finish_interrupt(f, req, &d); + free_path(f, parent, path); + } + reply_entry(req, &e, err); +} + +static void fuse_lib_unlink(fuse_req_t req, fuse_ino_t parent, + const char *name) +{ + struct fuse *f = req_fuse_prepare(req); + struct node *wnode; + char *path; + int err; + + err = get_path_wrlock(f, parent, name, &path, &wnode); + if (!err) { + struct fuse_intr_data d; + + fuse_prepare_interrupt(f, req, &d); + if (!f->conf.hard_remove && is_open(f, parent, name)) { + err = hide_node(f, path, parent, name); + if (!err) { + /* we have hidden the node so now check again under a lock in case it is not used any more */ + if (!is_open(f, parent, wnode->name)) { + char *unlinkpath; + + /* get the hidden file path, to unlink it */ + if (try_get_path(f, wnode->nodeid, NULL, &unlinkpath, NULL, false) == 0) { + err = fuse_fs_unlink(f->fs, unlinkpath); + if (!err) + remove_node(f, parent, wnode->name); + free(unlinkpath); + } + } + } + } else { + err = fuse_fs_unlink(f->fs, path); + if (!err) + remove_node(f, parent, name); + } + fuse_finish_interrupt(f, req, &d); + free_path_wrlock(f, parent, wnode, path); + } + reply_err(req, err); +} + +static void fuse_lib_rmdir(fuse_req_t req, fuse_ino_t parent, const char *name) +{ + struct fuse *f = req_fuse_prepare(req); + struct node *wnode; + char *path; + int err; + + err = get_path_wrlock(f, parent, name, &path, &wnode); + if (!err) { + struct fuse_intr_data d; + + fuse_prepare_interrupt(f, req, &d); + err = fuse_fs_rmdir(f->fs, path); + fuse_finish_interrupt(f, req, &d); + if (!err) + remove_node(f, parent, name); + free_path_wrlock(f, parent, wnode, path); + } + reply_err(req, err); +} + +static void fuse_lib_symlink(fuse_req_t req, const char *linkname, + fuse_ino_t parent, const char *name) +{ + struct fuse *f = req_fuse_prepare(req); + struct fuse_entry_param e; + char *path; + int err; + + err = get_path_name(f, parent, name, &path); + if (!err) { + struct fuse_intr_data d; + + fuse_prepare_interrupt(f, req, &d); + err = fuse_fs_symlink(f->fs, linkname, path); + if (!err) + err = lookup_path(f, parent, name, path, &e, NULL); + fuse_finish_interrupt(f, req, &d); + free_path(f, parent, path); + } + reply_entry(req, &e, err); +} + +static void fuse_lib_rename(fuse_req_t req, fuse_ino_t olddir, + const char *oldname, fuse_ino_t newdir, + const char *newname, unsigned int flags) +{ + struct fuse *f = req_fuse_prepare(req); + char *oldpath; + char *newpath; + struct node *wnode1; + struct node *wnode2; + int err; + + err = get_path2(f, olddir, oldname, newdir, newname, + &oldpath, &newpath, &wnode1, &wnode2); + if (!err) { + struct fuse_intr_data d; + err = 0; + fuse_prepare_interrupt(f, req, &d); + if (!f->conf.hard_remove && !(flags & RENAME_EXCHANGE) && + is_open(f, newdir, newname)) + err = hide_node(f, newpath, newdir, newname); + if (!err) { + err = fuse_fs_rename(f->fs, oldpath, newpath, flags); + if (!err) { + if (flags & RENAME_EXCHANGE) { + err = exchange_node(f, olddir, oldname, + newdir, newname); + } else { + err = rename_node(f, olddir, oldname, + newdir, newname, 0); + } + } + } + fuse_finish_interrupt(f, req, &d); + free_path2(f, olddir, newdir, wnode1, wnode2, oldpath, newpath); + } + reply_err(req, err); +} + +static void fuse_lib_link(fuse_req_t req, fuse_ino_t ino, fuse_ino_t newparent, + const char *newname) +{ + struct fuse *f = req_fuse_prepare(req); + struct fuse_entry_param e; + char *oldpath; + char *newpath; + int err; + + err = get_path2(f, ino, NULL, newparent, newname, + &oldpath, &newpath, NULL, NULL); + if (!err) { + struct fuse_intr_data d; + + fuse_prepare_interrupt(f, req, &d); + err = fuse_fs_link(f->fs, oldpath, newpath); + if (!err) + err = lookup_path(f, newparent, newname, newpath, + &e, NULL); + fuse_finish_interrupt(f, req, &d); + free_path2(f, ino, newparent, NULL, NULL, oldpath, newpath); + } + reply_entry(req, &e, err); +} + +static void fuse_do_release(struct fuse *f, fuse_ino_t ino, const char *path, + struct fuse_file_info *fi) +{ + struct node *node; + int unlink_hidden = 0; + + fuse_fs_release(f->fs, path, fi); + + pthread_mutex_lock(&f->lock); + node = get_node(f, ino); + assert(node->open_count > 0); + --node->open_count; + if (node->is_hidden && !node->open_count) { + unlink_hidden = 1; + node->is_hidden = 0; + } + pthread_mutex_unlock(&f->lock); + + if(unlink_hidden) { + if (path) { + fuse_fs_unlink(f->fs, path); + } else if (f->conf.nullpath_ok) { + char *unlinkpath; + + if (get_path(f, ino, &unlinkpath) == 0) + fuse_fs_unlink(f->fs, unlinkpath); + + free_path(f, ino, unlinkpath); + } + } +} + +static void fuse_lib_create(fuse_req_t req, fuse_ino_t parent, + const char *name, mode_t mode, + struct fuse_file_info *fi) +{ + struct fuse *f = req_fuse_prepare(req); + struct fuse_intr_data d; + struct fuse_entry_param e; + char *path; + int err; + + err = get_path_name(f, parent, name, &path); + if (!err) { + fuse_prepare_interrupt(f, req, &d); + err = fuse_fs_create(f->fs, path, mode, fi); + if (!err) { + err = lookup_path(f, parent, name, path, &e, fi); + if (err) + fuse_fs_release(f->fs, path, fi); + else if (!S_ISREG(e.attr.st_mode)) { + err = -EIO; + fuse_fs_release(f->fs, path, fi); + forget_node(f, e.ino, 1); + } else { + if (f->conf.direct_io) + fi->direct_io = 1; + if (f->conf.kernel_cache) + fi->keep_cache = 1; + if (fi->direct_io && + f->conf.parallel_direct_writes) + fi->parallel_direct_writes = 1; + } + } + fuse_finish_interrupt(f, req, &d); + } + if (!err) { + pthread_mutex_lock(&f->lock); + get_node(f, e.ino)->open_count++; + pthread_mutex_unlock(&f->lock); + if (fuse_reply_create(req, &e, fi) == -ENOENT) { + /* The open syscall was interrupted, so it + must be cancelled */ + fuse_do_release(f, e.ino, path, fi); + forget_node(f, e.ino, 1); + } + } else { + reply_err(req, err); + } + + free_path(f, parent, path); +} + +static double diff_timespec(const struct timespec *t1, + const struct timespec *t2) +{ + return (t1->tv_sec - t2->tv_sec) + + ((double) t1->tv_nsec - (double) t2->tv_nsec) / 1000000000.0; +} + +static void open_auto_cache(struct fuse *f, fuse_ino_t ino, const char *path, + struct fuse_file_info *fi) +{ + struct node *node; + + pthread_mutex_lock(&f->lock); + node = get_node(f, ino); + if (node->cache_valid) { + struct timespec now; + + curr_time(&now); + if (diff_timespec(&now, &node->stat_updated) > + f->conf.ac_attr_timeout) { + struct stat stbuf; + int err; + pthread_mutex_unlock(&f->lock); + err = fuse_fs_getattr(f->fs, path, &stbuf, fi); + pthread_mutex_lock(&f->lock); + if (!err) + update_stat(node, &stbuf); + else + node->cache_valid = 0; + } + } + if (node->cache_valid) + fi->keep_cache = 1; + + node->cache_valid = 1; + pthread_mutex_unlock(&f->lock); +} + +static void fuse_lib_open(fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info *fi) +{ + struct fuse *f = req_fuse_prepare(req); + struct fuse_intr_data d; + char *path; + int err; + + err = get_path(f, ino, &path); + if (!err) { + fuse_prepare_interrupt(f, req, &d); + err = fuse_fs_open(f->fs, path, fi); + if (!err) { + if (f->conf.direct_io) + fi->direct_io = 1; + if (f->conf.kernel_cache) + fi->keep_cache = 1; + + if (f->conf.auto_cache) + open_auto_cache(f, ino, path, fi); + + if (f->conf.no_rofd_flush && + (fi->flags & O_ACCMODE) == O_RDONLY) + fi->noflush = 1; + + if (fi->direct_io && f->conf.parallel_direct_writes) + fi->parallel_direct_writes = 1; + + } + fuse_finish_interrupt(f, req, &d); + } + if (!err) { + pthread_mutex_lock(&f->lock); + get_node(f, ino)->open_count++; + pthread_mutex_unlock(&f->lock); + if (fuse_reply_open(req, fi) == -ENOENT) { + /* The open syscall was interrupted, so it + must be cancelled */ + fuse_do_release(f, ino, path, fi); + } + } else + reply_err(req, err); + + free_path(f, ino, path); +} + +static void fuse_lib_read(fuse_req_t req, fuse_ino_t ino, size_t size, + off_t off, struct fuse_file_info *fi) +{ + struct fuse *f = req_fuse_prepare(req); + struct fuse_bufvec *buf = NULL; + char *path; + int res; + + res = get_path_nullok(f, ino, &path); + if (res == 0) { + struct fuse_intr_data d; + + fuse_prepare_interrupt(f, req, &d); + res = fuse_fs_read_buf(f->fs, path, &buf, size, off, fi); + fuse_finish_interrupt(f, req, &d); + free_path(f, ino, path); + } + + if (res == 0) + fuse_reply_data(req, buf, FUSE_BUF_SPLICE_MOVE); + else + reply_err(req, res); + + fuse_free_buf(buf); +} + +static void fuse_lib_write_buf(fuse_req_t req, fuse_ino_t ino, + struct fuse_bufvec *buf, off_t off, + struct fuse_file_info *fi) +{ + struct fuse *f = req_fuse_prepare(req); + char *path; + int res; + + res = get_path_nullok(f, ino, &path); + if (res == 0) { + struct fuse_intr_data d; + + fuse_prepare_interrupt(f, req, &d); + res = fuse_fs_write_buf(f->fs, path, buf, off, fi); + fuse_finish_interrupt(f, req, &d); + free_path(f, ino, path); + } + + if (res >= 0) + fuse_reply_write(req, res); + else + reply_err(req, res); +} + +static void fuse_lib_fsync(fuse_req_t req, fuse_ino_t ino, int datasync, + struct fuse_file_info *fi) +{ + struct fuse *f = req_fuse_prepare(req); + char *path; + int err; + + err = get_path_nullok(f, ino, &path); + if (!err) { + struct fuse_intr_data d; + + fuse_prepare_interrupt(f, req, &d); + err = fuse_fs_fsync(f->fs, path, datasync, fi); + fuse_finish_interrupt(f, req, &d); + free_path(f, ino, path); + } + reply_err(req, err); +} + +static struct fuse_dh *get_dirhandle(const struct fuse_file_info *llfi, + struct fuse_file_info *fi) +{ + struct fuse_dh *dh = (struct fuse_dh *) (uintptr_t) llfi->fh; + memset(fi, 0, sizeof(struct fuse_file_info)); + fi->fh = dh->fh; + return dh; +} + +static void fuse_lib_opendir(fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info *llfi) +{ + struct fuse *f = req_fuse_prepare(req); + struct fuse_intr_data d; + struct fuse_dh *dh; + struct fuse_file_info fi; + char *path; + int err; + + dh = (struct fuse_dh *) malloc(sizeof(struct fuse_dh)); + if (dh == NULL) { + reply_err(req, -ENOMEM); + return; + } + memset(dh, 0, sizeof(struct fuse_dh)); + dh->fuse = f; + dh->contents = NULL; + dh->first = NULL; + dh->len = 0; + dh->filled = 0; + dh->nodeid = ino; + pthread_mutex_init(&dh->lock, NULL); + + llfi->fh = (uintptr_t) dh; + + memset(&fi, 0, sizeof(fi)); + fi.flags = llfi->flags; + + err = get_path(f, ino, &path); + if (!err) { + fuse_prepare_interrupt(f, req, &d); + err = fuse_fs_opendir(f->fs, path, &fi); + fuse_finish_interrupt(f, req, &d); + dh->fh = fi.fh; + llfi->cache_readdir = fi.cache_readdir; + llfi->keep_cache = fi.keep_cache; + } + if (!err) { + if (fuse_reply_open(req, llfi) == -ENOENT) { + /* The opendir syscall was interrupted, so it + must be cancelled */ + fuse_fs_releasedir(f->fs, path, &fi); + pthread_mutex_destroy(&dh->lock); + free(dh); + } + } else { + reply_err(req, err); + pthread_mutex_destroy(&dh->lock); + free(dh); + } + free_path(f, ino, path); +} + +static int extend_contents(struct fuse_dh *dh, unsigned minsize) +{ + if (minsize > dh->size) { + char *newptr; + unsigned newsize = dh->size; + if (!newsize) + newsize = 1024; + while (newsize < minsize) { + if (newsize >= 0x80000000) + newsize = 0xffffffff; + else + newsize *= 2; + } + + newptr = (char *) realloc(dh->contents, newsize); + if (!newptr) { + dh->error = -ENOMEM; + return -1; + } + dh->contents = newptr; + dh->size = newsize; + } + return 0; +} + +static int fuse_add_direntry_to_dh(struct fuse_dh *dh, const char *name, + struct stat *st, enum fuse_fill_dir_flags flags) +{ + struct fuse_direntry *de; + + de = malloc(sizeof(struct fuse_direntry)); + if (!de) { + dh->error = -ENOMEM; + return -1; + } + de->name = strdup(name); + if (!de->name) { + dh->error = -ENOMEM; + free(de); + return -1; + } + de->flags = flags; + de->stat = *st; + de->next = NULL; + + *dh->last = de; + dh->last = &de->next; + + return 0; +} + +static fuse_ino_t lookup_nodeid(struct fuse *f, fuse_ino_t parent, + const char *name) +{ + struct node *node; + fuse_ino_t res = FUSE_UNKNOWN_INO; + + pthread_mutex_lock(&f->lock); + node = lookup_node(f, parent, name); + if (node) + res = node->nodeid; + pthread_mutex_unlock(&f->lock); + + return res; +} + +static int fill_dir(void *dh_, const char *name, const struct stat *statp, + off_t off, enum fuse_fill_dir_flags flags) +{ + struct fuse_dh *dh = (struct fuse_dh *) dh_; + struct stat stbuf; + + if ((flags & ~FUSE_FILL_DIR_PLUS) != 0) { + dh->error = -EIO; + return 1; + } + + if (statp) + stbuf = *statp; + else { + memset(&stbuf, 0, sizeof(stbuf)); + stbuf.st_ino = FUSE_UNKNOWN_INO; + } + + if (!dh->fuse->conf.use_ino) { + stbuf.st_ino = FUSE_UNKNOWN_INO; + if (dh->fuse->conf.readdir_ino) { + stbuf.st_ino = (ino_t) + lookup_nodeid(dh->fuse, dh->nodeid, name); + } + } + + if (off) { + size_t newlen; + + if (dh->filled) { + dh->error = -EIO; + return 1; + } + + if (dh->first) { + dh->error = -EIO; + return 1; + } + + if (extend_contents(dh, dh->needlen) == -1) + return 1; + + newlen = dh->len + + fuse_add_direntry(dh->req, dh->contents + dh->len, + dh->needlen - dh->len, name, + &stbuf, off); + if (newlen > dh->needlen) + return 1; + + dh->len = newlen; + } else { + dh->filled = 1; + + if (fuse_add_direntry_to_dh(dh, name, &stbuf, flags) == -1) + return 1; + } + return 0; +} + +static int is_dot_or_dotdot(const char *name) +{ + return name[0] == '.' && (name[1] == '\0' || + (name[1] == '.' && name[2] == '\0')); +} + +static int fill_dir_plus(void *dh_, const char *name, const struct stat *statp, + off_t off, enum fuse_fill_dir_flags flags) +{ + struct fuse_dh *dh = (struct fuse_dh *) dh_; + struct fuse_entry_param e = { + /* ino=0 tells the kernel to ignore readdirplus stat info */ + .ino = 0, + }; + struct fuse *f = dh->fuse; + int res; + + if ((flags & ~FUSE_FILL_DIR_PLUS) != 0) { + dh->error = -EIO; + return 1; + } + + if (statp && (flags & FUSE_FILL_DIR_PLUS)) { + e.attr = *statp; + } + + e.attr.st_ino = FUSE_UNKNOWN_INO; + if (statp) { + e.attr.st_mode = statp->st_mode; + if (f->conf.use_ino) + e.attr.st_ino = statp->st_ino; + } + if (!f->conf.use_ino && f->conf.readdir_ino) { + e.attr.st_ino = (ino_t) + lookup_nodeid(f, dh->nodeid, name); + } + + if (off) { + size_t newlen; + + if (dh->filled) { + dh->error = -EIO; + return 1; + } + + if (dh->first) { + dh->error = -EIO; + return 1; + } + if (extend_contents(dh, dh->needlen) == -1) + return 1; + + if (statp && (flags & FUSE_FILL_DIR_PLUS)) { + if (!is_dot_or_dotdot(name)) { + res = do_lookup(f, dh->nodeid, name, &e); + if (res) { + dh->error = res; + return 1; + } + } + } + + newlen = dh->len + + fuse_add_direntry_plus(dh->req, dh->contents + dh->len, + dh->needlen - dh->len, name, + &e, off); + if (newlen > dh->needlen) + return 1; + dh->len = newlen; + } else { + dh->filled = 1; + + if (fuse_add_direntry_to_dh(dh, name, &e.attr, flags) == -1) + return 1; + } + + return 0; +} + +static void free_direntries(struct fuse_direntry *de) +{ + while (de) { + struct fuse_direntry *next = de->next; + free(de->name); + free(de); + de = next; + } +} + +static int readdir_fill(struct fuse *f, fuse_req_t req, fuse_ino_t ino, + size_t size, off_t off, struct fuse_dh *dh, + struct fuse_file_info *fi, + enum fuse_readdir_flags flags) +{ + char *path; + int err; + + if (f->fs->op.readdir) + err = get_path_nullok(f, ino, &path); + else + err = get_path(f, ino, &path); + if (!err) { + struct fuse_intr_data d; + fuse_fill_dir_t filler = fill_dir; + + if (flags & FUSE_READDIR_PLUS) + filler = fill_dir_plus; + + free_direntries(dh->first); + dh->first = NULL; + dh->last = &dh->first; + dh->len = 0; + dh->error = 0; + dh->needlen = size; + dh->filled = 0; + dh->req = req; + fuse_prepare_interrupt(f, req, &d); + err = fuse_fs_readdir(f->fs, path, dh, filler, off, fi, flags); + fuse_finish_interrupt(f, req, &d); + dh->req = NULL; + if (!err) + err = dh->error; + if (err) + dh->filled = 0; + free_path(f, ino, path); + } + return err; +} + +static int readdir_fill_from_list(fuse_req_t req, struct fuse_dh *dh, + off_t off, enum fuse_readdir_flags flags) +{ + off_t pos; + struct fuse_direntry *de = dh->first; + int res; + + dh->len = 0; + + if (extend_contents(dh, dh->needlen) == -1) + return dh->error; + + for (pos = 0; pos < off; pos++) { + if (!de) + break; + + de = de->next; + } + while (de) { + char *p = dh->contents + dh->len; + unsigned rem = dh->needlen - dh->len; + unsigned thislen; + unsigned newlen; + pos++; + + if (flags & FUSE_READDIR_PLUS) { + struct fuse_entry_param e = { + .ino = 0, + .attr = de->stat, + }; + + if (de->flags & FUSE_FILL_DIR_PLUS && + !is_dot_or_dotdot(de->name)) { + res = do_lookup(dh->fuse, dh->nodeid, + de->name, &e); + if (res) { + dh->error = res; + return 1; + } + } + + thislen = fuse_add_direntry_plus(req, p, rem, + de->name, &e, pos); + } else { + thislen = fuse_add_direntry(req, p, rem, + de->name, &de->stat, pos); + } + newlen = dh->len + thislen; + if (newlen > dh->needlen) + break; + dh->len = newlen; + de = de->next; + } + return 0; +} + +static void fuse_readdir_common(fuse_req_t req, fuse_ino_t ino, size_t size, + off_t off, struct fuse_file_info *llfi, + enum fuse_readdir_flags flags) +{ + struct fuse *f = req_fuse_prepare(req); + struct fuse_file_info fi; + struct fuse_dh *dh = get_dirhandle(llfi, &fi); + int err; + + pthread_mutex_lock(&dh->lock); + /* According to SUS, directory contents need to be refreshed on + rewinddir() */ + if (!off) + dh->filled = 0; + + if (!dh->filled) { + err = readdir_fill(f, req, ino, size, off, dh, &fi, flags); + if (err) { + reply_err(req, err); + goto out; + } + } + if (dh->filled) { + dh->needlen = size; + err = readdir_fill_from_list(req, dh, off, flags); + if (err) { + reply_err(req, err); + goto out; + } + } + fuse_reply_buf(req, dh->contents, dh->len); +out: + pthread_mutex_unlock(&dh->lock); +} + +static void fuse_lib_readdir(fuse_req_t req, fuse_ino_t ino, size_t size, + off_t off, struct fuse_file_info *llfi) +{ + fuse_readdir_common(req, ino, size, off, llfi, 0); +} + +static void fuse_lib_readdirplus(fuse_req_t req, fuse_ino_t ino, size_t size, + off_t off, struct fuse_file_info *llfi) +{ + fuse_readdir_common(req, ino, size, off, llfi, FUSE_READDIR_PLUS); +} + +static void fuse_lib_releasedir(fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info *llfi) +{ + struct fuse *f = req_fuse_prepare(req); + struct fuse_intr_data d; + struct fuse_file_info fi; + struct fuse_dh *dh = get_dirhandle(llfi, &fi); + char *path; + + get_path_nullok(f, ino, &path); + + fuse_prepare_interrupt(f, req, &d); + fuse_fs_releasedir(f->fs, path, &fi); + fuse_finish_interrupt(f, req, &d); + free_path(f, ino, path); + + pthread_mutex_lock(&dh->lock); + pthread_mutex_unlock(&dh->lock); + pthread_mutex_destroy(&dh->lock); + free_direntries(dh->first); + free(dh->contents); + free(dh); + reply_err(req, 0); +} + +static void fuse_lib_fsyncdir(fuse_req_t req, fuse_ino_t ino, int datasync, + struct fuse_file_info *llfi) +{ + struct fuse *f = req_fuse_prepare(req); + struct fuse_file_info fi; + char *path; + int err; + + get_dirhandle(llfi, &fi); + + err = get_path_nullok(f, ino, &path); + if (!err) { + struct fuse_intr_data d; + fuse_prepare_interrupt(f, req, &d); + err = fuse_fs_fsyncdir(f->fs, path, datasync, &fi); + fuse_finish_interrupt(f, req, &d); + free_path(f, ino, path); + } + reply_err(req, err); +} + +static void fuse_lib_statfs(fuse_req_t req, fuse_ino_t ino) +{ + struct fuse *f = req_fuse_prepare(req); + struct statvfs buf; + char *path = NULL; + int err = 0; + + memset(&buf, 0, sizeof(buf)); + if (ino) + err = get_path(f, ino, &path); + + if (!err) { + struct fuse_intr_data d; + fuse_prepare_interrupt(f, req, &d); + err = fuse_fs_statfs(f->fs, path ? path : "/", &buf); + fuse_finish_interrupt(f, req, &d); + free_path(f, ino, path); + } + + if (!err) + fuse_reply_statfs(req, &buf); + else + reply_err(req, err); +} + +static void fuse_lib_setxattr(fuse_req_t req, fuse_ino_t ino, const char *name, + const char *value, size_t size, int flags) +{ + struct fuse *f = req_fuse_prepare(req); + char *path; + int err; + + err = get_path(f, ino, &path); + if (!err) { + struct fuse_intr_data d; + fuse_prepare_interrupt(f, req, &d); + err = fuse_fs_setxattr(f->fs, path, name, value, size, flags); + fuse_finish_interrupt(f, req, &d); + free_path(f, ino, path); + } + reply_err(req, err); +} + +static int common_getxattr(struct fuse *f, fuse_req_t req, fuse_ino_t ino, + const char *name, char *value, size_t size) +{ + int err; + char *path; + + err = get_path(f, ino, &path); + if (!err) { + struct fuse_intr_data d; + fuse_prepare_interrupt(f, req, &d); + err = fuse_fs_getxattr(f->fs, path, name, value, size); + fuse_finish_interrupt(f, req, &d); + free_path(f, ino, path); + } + return err; +} + +static void fuse_lib_getxattr(fuse_req_t req, fuse_ino_t ino, const char *name, + size_t size) +{ + struct fuse *f = req_fuse_prepare(req); + int res; + + if (size) { + char *value = (char *) malloc(size); + if (value == NULL) { + reply_err(req, -ENOMEM); + return; + } + res = common_getxattr(f, req, ino, name, value, size); + if (res > 0) + fuse_reply_buf(req, value, res); + else + reply_err(req, res); + free(value); + } else { + res = common_getxattr(f, req, ino, name, NULL, 0); + if (res >= 0) + fuse_reply_xattr(req, res); + else + reply_err(req, res); + } +} + +static int common_listxattr(struct fuse *f, fuse_req_t req, fuse_ino_t ino, + char *list, size_t size) +{ + char *path; + int err; + + err = get_path(f, ino, &path); + if (!err) { + struct fuse_intr_data d; + fuse_prepare_interrupt(f, req, &d); + err = fuse_fs_listxattr(f->fs, path, list, size); + fuse_finish_interrupt(f, req, &d); + free_path(f, ino, path); + } + return err; +} + +static void fuse_lib_listxattr(fuse_req_t req, fuse_ino_t ino, size_t size) +{ + struct fuse *f = req_fuse_prepare(req); + int res; + + if (size) { + char *list = (char *) malloc(size); + if (list == NULL) { + reply_err(req, -ENOMEM); + return; + } + res = common_listxattr(f, req, ino, list, size); + if (res > 0) + fuse_reply_buf(req, list, res); + else + reply_err(req, res); + free(list); + } else { + res = common_listxattr(f, req, ino, NULL, 0); + if (res >= 0) + fuse_reply_xattr(req, res); + else + reply_err(req, res); + } +} + +static void fuse_lib_removexattr(fuse_req_t req, fuse_ino_t ino, + const char *name) +{ + struct fuse *f = req_fuse_prepare(req); + char *path; + int err; + + err = get_path(f, ino, &path); + if (!err) { + struct fuse_intr_data d; + fuse_prepare_interrupt(f, req, &d); + err = fuse_fs_removexattr(f->fs, path, name); + fuse_finish_interrupt(f, req, &d); + free_path(f, ino, path); + } + reply_err(req, err); +} + +static struct lock *locks_conflict(struct node *node, const struct lock *lock) +{ + struct lock *l; + + for (l = node->locks; l; l = l->next) + if (l->owner != lock->owner && + lock->start <= l->end && l->start <= lock->end && + (l->type == F_WRLCK || lock->type == F_WRLCK)) + break; + + return l; +} + +static void delete_lock(struct lock **lockp) +{ + struct lock *l = *lockp; + *lockp = l->next; + free(l); +} + +static void insert_lock(struct lock **pos, struct lock *lock) +{ + lock->next = *pos; + *pos = lock; +} + +static int locks_insert(struct node *node, struct lock *lock) +{ + struct lock **lp; + struct lock *newl1 = NULL; + struct lock *newl2 = NULL; + + if (lock->type != F_UNLCK || lock->start != 0 || + lock->end != OFFSET_MAX) { + newl1 = malloc(sizeof(struct lock)); + newl2 = malloc(sizeof(struct lock)); + + if (!newl1 || !newl2) { + free(newl1); + free(newl2); + return -ENOLCK; + } + } + + for (lp = &node->locks; *lp;) { + struct lock *l = *lp; + if (l->owner != lock->owner) + goto skip; + + if (lock->type == l->type) { + if (l->end < lock->start - 1) + goto skip; + if (lock->end < l->start - 1) + break; + if (l->start <= lock->start && lock->end <= l->end) + goto out; + if (l->start < lock->start) + lock->start = l->start; + if (lock->end < l->end) + lock->end = l->end; + goto delete; + } else { + if (l->end < lock->start) + goto skip; + if (lock->end < l->start) + break; + if (lock->start <= l->start && l->end <= lock->end) + goto delete; + if (l->end <= lock->end) { + l->end = lock->start - 1; + goto skip; + } + if (lock->start <= l->start) { + l->start = lock->end + 1; + break; + } + *newl2 = *l; + newl2->start = lock->end + 1; + l->end = lock->start - 1; + insert_lock(&l->next, newl2); + newl2 = NULL; + } + skip: + lp = &l->next; + continue; + + delete: + delete_lock(lp); + } + if (lock->type != F_UNLCK) { + *newl1 = *lock; + insert_lock(lp, newl1); + newl1 = NULL; + } +out: + free(newl1); + free(newl2); + return 0; +} + +static void flock_to_lock(struct flock *flock, struct lock *lock) +{ + memset(lock, 0, sizeof(struct lock)); + lock->type = flock->l_type; + lock->start = flock->l_start; + lock->end = + flock->l_len ? flock->l_start + flock->l_len - 1 : OFFSET_MAX; + lock->pid = flock->l_pid; +} + +static void lock_to_flock(struct lock *lock, struct flock *flock) +{ + flock->l_type = lock->type; + flock->l_start = lock->start; + flock->l_len = + (lock->end == OFFSET_MAX) ? 0 : lock->end - lock->start + 1; + flock->l_pid = lock->pid; +} + +static int fuse_flush_common(struct fuse *f, fuse_req_t req, fuse_ino_t ino, + const char *path, struct fuse_file_info *fi) +{ + struct fuse_intr_data d; + struct flock lock; + struct lock l; + int err; + int errlock; + + fuse_prepare_interrupt(f, req, &d); + memset(&lock, 0, sizeof(lock)); + lock.l_type = F_UNLCK; + lock.l_whence = SEEK_SET; + err = fuse_fs_flush(f->fs, path, fi); + errlock = fuse_fs_lock(f->fs, path, fi, F_SETLK, &lock); + fuse_finish_interrupt(f, req, &d); + + if (errlock != -ENOSYS) { + flock_to_lock(&lock, &l); + l.owner = fi->lock_owner; + pthread_mutex_lock(&f->lock); + locks_insert(get_node(f, ino), &l); + pthread_mutex_unlock(&f->lock); + + /* if op.lock() is defined FLUSH is needed regardless + of op.flush() */ + if (err == -ENOSYS) + err = 0; + } + return err; +} + +static void fuse_lib_release(fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info *fi) +{ + struct fuse *f = req_fuse_prepare(req); + struct fuse_intr_data d; + char *path; + int err = 0; + + get_path_nullok(f, ino, &path); + if (fi->flush) { + err = fuse_flush_common(f, req, ino, path, fi); + if (err == -ENOSYS) + err = 0; + } + + fuse_prepare_interrupt(f, req, &d); + fuse_do_release(f, ino, path, fi); + fuse_finish_interrupt(f, req, &d); + free_path(f, ino, path); + + reply_err(req, err); +} + +static void fuse_lib_flush(fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info *fi) +{ + struct fuse *f = req_fuse_prepare(req); + char *path; + int err; + + get_path_nullok(f, ino, &path); + err = fuse_flush_common(f, req, ino, path, fi); + free_path(f, ino, path); + + reply_err(req, err); +} + +static int fuse_lock_common(fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info *fi, struct flock *lock, + int cmd) +{ + struct fuse *f = req_fuse_prepare(req); + char *path; + int err; + + err = get_path_nullok(f, ino, &path); + if (!err) { + struct fuse_intr_data d; + fuse_prepare_interrupt(f, req, &d); + err = fuse_fs_lock(f->fs, path, fi, cmd, lock); + fuse_finish_interrupt(f, req, &d); + free_path(f, ino, path); + } + return err; +} + +static void fuse_lib_getlk(fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info *fi, struct flock *lock) +{ + int err; + struct lock l; + struct lock *conflict; + struct fuse *f = req_fuse(req); + + flock_to_lock(lock, &l); + l.owner = fi->lock_owner; + pthread_mutex_lock(&f->lock); + conflict = locks_conflict(get_node(f, ino), &l); + if (conflict) + lock_to_flock(conflict, lock); + pthread_mutex_unlock(&f->lock); + if (!conflict) + err = fuse_lock_common(req, ino, fi, lock, F_GETLK); + else + err = 0; + + if (!err) + fuse_reply_lock(req, lock); + else + reply_err(req, err); +} + +static void fuse_lib_setlk(fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info *fi, struct flock *lock, + int sleep) +{ + int err = fuse_lock_common(req, ino, fi, lock, + sleep ? F_SETLKW : F_SETLK); + if (!err) { + struct fuse *f = req_fuse(req); + struct lock l; + flock_to_lock(lock, &l); + l.owner = fi->lock_owner; + pthread_mutex_lock(&f->lock); + locks_insert(get_node(f, ino), &l); + pthread_mutex_unlock(&f->lock); + } + reply_err(req, err); +} + +static void fuse_lib_flock(fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info *fi, int op) +{ + struct fuse *f = req_fuse_prepare(req); + char *path; + int err; + + err = get_path_nullok(f, ino, &path); + if (err == 0) { + struct fuse_intr_data d; + fuse_prepare_interrupt(f, req, &d); + err = fuse_fs_flock(f->fs, path, fi, op); + fuse_finish_interrupt(f, req, &d); + free_path(f, ino, path); + } + reply_err(req, err); +} + +static void fuse_lib_bmap(fuse_req_t req, fuse_ino_t ino, size_t blocksize, + uint64_t idx) +{ + struct fuse *f = req_fuse_prepare(req); + struct fuse_intr_data d; + char *path; + int err; + + err = get_path(f, ino, &path); + if (!err) { + fuse_prepare_interrupt(f, req, &d); + err = fuse_fs_bmap(f->fs, path, blocksize, &idx); + fuse_finish_interrupt(f, req, &d); + free_path(f, ino, path); + } + if (!err) + fuse_reply_bmap(req, idx); + else + reply_err(req, err); +} + +static void fuse_lib_ioctl(fuse_req_t req, fuse_ino_t ino, unsigned int cmd, + void *arg, struct fuse_file_info *llfi, + unsigned int flags, const void *in_buf, + size_t in_bufsz, size_t out_bufsz) +{ + struct fuse *f = req_fuse_prepare(req); + struct fuse_intr_data d; + struct fuse_file_info fi; + char *path, *out_buf = NULL; + int err; + + err = -EPERM; + if (flags & FUSE_IOCTL_UNRESTRICTED) + goto err; + + if (flags & FUSE_IOCTL_DIR) + get_dirhandle(llfi, &fi); + else + fi = *llfi; + + if (out_bufsz) { + err = -ENOMEM; + out_buf = malloc(out_bufsz); + if (!out_buf) + goto err; + } + + assert(!in_bufsz || !out_bufsz || in_bufsz == out_bufsz); + if (out_buf && in_bufsz) + memcpy(out_buf, in_buf, in_bufsz); + + err = get_path_nullok(f, ino, &path); + if (err) + goto err; + + fuse_prepare_interrupt(f, req, &d); + + err = fuse_fs_ioctl(f->fs, path, cmd, arg, &fi, flags, + out_buf ? out_buf : (void *)in_buf); + + fuse_finish_interrupt(f, req, &d); + free_path(f, ino, path); + + if (err < 0) + goto err; + fuse_reply_ioctl(req, err, out_buf, out_bufsz); + goto out; +err: + reply_err(req, err); +out: + free(out_buf); +} + +static void fuse_lib_poll(fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info *fi, struct fuse_pollhandle *ph) +{ + struct fuse *f = req_fuse_prepare(req); + struct fuse_intr_data d; + char *path; + int err; + unsigned revents = 0; + + err = get_path_nullok(f, ino, &path); + if (!err) { + fuse_prepare_interrupt(f, req, &d); + err = fuse_fs_poll(f->fs, path, fi, ph, &revents); + fuse_finish_interrupt(f, req, &d); + free_path(f, ino, path); + } else { + fuse_pollhandle_destroy(ph); + } + if (!err) + fuse_reply_poll(req, revents); + else + reply_err(req, err); +} + +static void fuse_lib_fallocate(fuse_req_t req, fuse_ino_t ino, int mode, + off_t offset, off_t length, struct fuse_file_info *fi) +{ + struct fuse *f = req_fuse_prepare(req); + struct fuse_intr_data d; + char *path; + int err; + + err = get_path_nullok(f, ino, &path); + if (!err) { + fuse_prepare_interrupt(f, req, &d); + err = fuse_fs_fallocate(f->fs, path, mode, offset, length, fi); + fuse_finish_interrupt(f, req, &d); + free_path(f, ino, path); + } + reply_err(req, err); +} + +static void fuse_lib_copy_file_range(fuse_req_t req, fuse_ino_t nodeid_in, + off_t off_in, struct fuse_file_info *fi_in, + fuse_ino_t nodeid_out, off_t off_out, + struct fuse_file_info *fi_out, size_t len, + int flags) +{ + struct fuse *f = req_fuse_prepare(req); + struct fuse_intr_data d; + char *path_in, *path_out; + int err; + ssize_t res; + + err = get_path_nullok(f, nodeid_in, &path_in); + if (err) { + reply_err(req, err); + return; + } + + err = get_path_nullok(f, nodeid_out, &path_out); + if (err) { + free_path(f, nodeid_in, path_in); + reply_err(req, err); + return; + } + + fuse_prepare_interrupt(f, req, &d); + res = fuse_fs_copy_file_range(f->fs, path_in, fi_in, off_in, path_out, + fi_out, off_out, len, flags); + fuse_finish_interrupt(f, req, &d); + + if (res >= 0) + fuse_reply_write(req, res); + else + reply_err(req, res); + + free_path(f, nodeid_in, path_in); + free_path(f, nodeid_out, path_out); +} + +static void fuse_lib_lseek(fuse_req_t req, fuse_ino_t ino, off_t off, int whence, + struct fuse_file_info *fi) +{ + struct fuse *f = req_fuse_prepare(req); + struct fuse_intr_data d; + char *path; + int err; + off_t res; + + err = get_path(f, ino, &path); + if (err) { + reply_err(req, err); + return; + } + + fuse_prepare_interrupt(f, req, &d); + res = fuse_fs_lseek(f->fs, path, off, whence, fi); + fuse_finish_interrupt(f, req, &d); + free_path(f, ino, path); + if (res >= 0) + fuse_reply_lseek(req, res); + else + reply_err(req, res); +} + +#ifdef HAVE_STATX +static void fuse_lib_statx(fuse_req_t req, fuse_ino_t ino, int flags, int mask, + struct fuse_file_info *fi) +{ + struct fuse *f = req_fuse_prepare(req); + struct statx stxbuf; + char *path; + int err; + + memset(&stxbuf, 0, sizeof(stxbuf)); + + if (fi != NULL) + err = get_path_nullok(f, ino, &path); + else + err = get_path(f, ino, &path); + + if (!err) { + struct fuse_intr_data d; + + if (!path) + flags |= AT_EMPTY_PATH; + fuse_prepare_interrupt(f, req, &d); + err = fuse_fs_statx(f->fs, path, flags, mask, &stxbuf, fi); + fuse_finish_interrupt(f, req, &d); + free_path(f, ino, path); + } + if (!err) { + struct node *node; + + pthread_mutex_lock(&f->lock); + node = get_node(f, ino); + if (node->is_hidden && stxbuf.stx_nlink > 0) + stxbuf.stx_nlink--; + if (f->conf.auto_cache) { + struct stat stbuf; + + stbuf.st_mtime = stxbuf.stx_mtime.tv_sec; + ST_MTIM_NSEC_SET(&stbuf, stxbuf.stx_mtime.tv_nsec); + stbuf.st_size = stxbuf.stx_size; + update_stat(node, &stbuf); + } + pthread_mutex_unlock(&f->lock); + set_statx(f, ino, &stxbuf); + fuse_reply_statx(req, 0, &stxbuf, f->conf.attr_timeout); + } else + reply_err(req, err); +} +#endif + +static int clean_delay(struct fuse *f) +{ + /* + * This is calculating the delay between clean runs. To + * reduce the number of cleans we are doing them 10 times + * within the remember window. + */ + int min_sleep = 60; + int max_sleep = 3600; + int sleep_time = f->conf.remember / 10; + + if (sleep_time > max_sleep) + return max_sleep; + if (sleep_time < min_sleep) + return min_sleep; + return sleep_time; +} + +int fuse_clean_cache(struct fuse *f) +{ + struct node_lru *lnode; + struct list_head *curr, *next; + struct node *node; + struct timespec now; + + pthread_mutex_lock(&f->lock); + + curr_time(&now); + + for (curr = f->lru_table.next; curr != &f->lru_table; curr = next) { + double age; + + next = curr->next; + lnode = list_entry(curr, struct node_lru, lru); + node = &lnode->node; + + age = diff_timespec(&now, &lnode->forget_time); + if (age <= f->conf.remember) + break; + + assert(node->nlookup == 1); + + /* Don't forget active directories */ + if (node->refctr > 1) + continue; + + node->nlookup = 0; + unhash_name(f, node); + unref_node(f, node); + } + pthread_mutex_unlock(&f->lock); + + return clean_delay(f); +} + +static struct fuse_lowlevel_ops fuse_path_ops = { + .init = fuse_lib_init, + .destroy = fuse_lib_destroy, + .lookup = fuse_lib_lookup, + .forget = fuse_lib_forget, + .forget_multi = fuse_lib_forget_multi, + .getattr = fuse_lib_getattr, + .setattr = fuse_lib_setattr, + .access = fuse_lib_access, + .readlink = fuse_lib_readlink, + .mknod = fuse_lib_mknod, + .mkdir = fuse_lib_mkdir, + .unlink = fuse_lib_unlink, + .rmdir = fuse_lib_rmdir, + .symlink = fuse_lib_symlink, + .rename = fuse_lib_rename, + .link = fuse_lib_link, + .create = fuse_lib_create, + .open = fuse_lib_open, + .read = fuse_lib_read, + .write_buf = fuse_lib_write_buf, + .flush = fuse_lib_flush, + .release = fuse_lib_release, + .fsync = fuse_lib_fsync, + .opendir = fuse_lib_opendir, + .readdir = fuse_lib_readdir, + .readdirplus = fuse_lib_readdirplus, + .releasedir = fuse_lib_releasedir, + .fsyncdir = fuse_lib_fsyncdir, + .statfs = fuse_lib_statfs, + .setxattr = fuse_lib_setxattr, + .getxattr = fuse_lib_getxattr, + .listxattr = fuse_lib_listxattr, + .removexattr = fuse_lib_removexattr, + .getlk = fuse_lib_getlk, + .setlk = fuse_lib_setlk, + .flock = fuse_lib_flock, + .bmap = fuse_lib_bmap, + .ioctl = fuse_lib_ioctl, + .poll = fuse_lib_poll, + .fallocate = fuse_lib_fallocate, + .copy_file_range = fuse_lib_copy_file_range, + .lseek = fuse_lib_lseek, +#ifdef HAVE_STATX + .statx = fuse_lib_statx, +#endif +}; + +int fuse_notify_poll(struct fuse_pollhandle *ph) +{ + return fuse_lowlevel_notify_poll(ph); +} + +struct fuse_session *fuse_get_session(struct fuse *f) +{ + return f->se; +} + +static int fuse_session_loop_remember(struct fuse *f) +{ + struct fuse_session *se = f->se; + int res = 0; + struct timespec now; + time_t next_clean; + struct pollfd fds = { + .fd = se->fd, + .events = POLLIN + }; + struct fuse_buf fbuf = { + .mem = NULL, + }; + + curr_time(&now); + next_clean = now.tv_sec; + while (!fuse_session_exited(se)) { + unsigned timeout; + + curr_time(&now); + if (now.tv_sec < next_clean) + timeout = next_clean - now.tv_sec; + else + timeout = 0; + + res = poll(&fds, 1, timeout * 1000); + if (res == -1) { + if (errno == EINTR) + continue; + else + break; + } else if (res > 0) { + res = fuse_session_receive_buf_internal(se, &fbuf, + NULL); + if (res == -EINTR) + continue; + if (res <= 0) + break; + + fuse_session_process_buf_internal(se, &fbuf, NULL); + } else { + timeout = fuse_clean_cache(f); + curr_time(&now); + next_clean = now.tv_sec + timeout; + } + } + + fuse_buf_free(&fbuf); + return res < 0 ? -1 : 0; +} + +int fuse_loop(struct fuse *f) +{ + if (!f) + return -1; + + if (lru_enabled(f)) + return fuse_session_loop_remember(f); + + return fuse_session_loop(f->se); +} + +FUSE_SYMVER("fuse_loop_mt_312", "fuse_loop_mt@@FUSE_3.12") +int fuse_loop_mt_312(struct fuse *f, struct fuse_loop_config *config) +{ + if (f == NULL) + return -1; + + int res = fuse_start_cleanup_thread(f); + if (res) + return -1; + + res = fuse_session_loop_mt_312(fuse_get_session(f), config); + fuse_stop_cleanup_thread(f); + return res; +} + +int fuse_loop_mt_32(struct fuse *f, struct fuse_loop_config_v1 *config_v1); +FUSE_SYMVER("fuse_loop_mt_32", "fuse_loop_mt@FUSE_3.2") +int fuse_loop_mt_32(struct fuse *f, struct fuse_loop_config_v1 *config_v1) +{ + struct fuse_loop_config *config = fuse_loop_cfg_create(); + if (config == NULL) + return ENOMEM; + + fuse_loop_cfg_convert(config, config_v1); + + int res = fuse_loop_mt_312(f, config); + + fuse_loop_cfg_destroy(config); + + return res; +} + +int fuse_loop_mt_31(struct fuse *f, int clone_fd); +FUSE_SYMVER("fuse_loop_mt_31", "fuse_loop_mt@FUSE_3.0") +int fuse_loop_mt_31(struct fuse *f, int clone_fd) +{ + int err; + struct fuse_loop_config *config = fuse_loop_cfg_create(); + + if (config == NULL) + return ENOMEM; + + fuse_loop_cfg_set_clone_fd(config, clone_fd); + + err = fuse_loop_mt_312(f, config); + + fuse_loop_cfg_destroy(config); + + return err; +} + +void fuse_exit(struct fuse *f) +{ + fuse_session_exit(f->se); +} + +struct fuse_context *fuse_get_context(void) +{ + struct fuse_context_i *c = fuse_get_context_internal(); + + if (c) + return &c->ctx; + else + return NULL; +} + +int fuse_getgroups(int size, gid_t list[]) +{ + struct fuse_context_i *c = fuse_get_context_internal(); + if (!c) + return -EINVAL; + + return fuse_req_getgroups(c->req, size, list); +} + +int fuse_interrupted(void) +{ + struct fuse_context_i *c = fuse_get_context_internal(); + + if (c) + return fuse_req_interrupted(c->req); + else + return 0; +} + +int fuse_invalidate_path(struct fuse *f, const char *path) { + fuse_ino_t ino; + int err = lookup_path_in_cache(f, path, &ino); + if (err) { + return err; + } + + return fuse_lowlevel_notify_inval_inode(f->se, ino, 0, 0); +} + +#define FUSE_LIB_OPT(t, p, v) { t, offsetof(struct fuse_config, p), v } + +static const struct fuse_opt fuse_lib_opts[] = { + FUSE_OPT_KEY("debug", FUSE_OPT_KEY_KEEP), + FUSE_OPT_KEY("-d", FUSE_OPT_KEY_KEEP), + FUSE_LIB_OPT("debug", debug, 1), + FUSE_LIB_OPT("-d", debug, 1), + FUSE_LIB_OPT("kernel_cache", kernel_cache, 1), + FUSE_LIB_OPT("auto_cache", auto_cache, 1), + FUSE_LIB_OPT("noauto_cache", auto_cache, 0), + FUSE_LIB_OPT("no_rofd_flush", no_rofd_flush, 1), + FUSE_LIB_OPT("umask=", set_mode, 1), + FUSE_LIB_OPT("umask=%o", umask, 0), + FUSE_LIB_OPT("fmask=", set_mode, 1), + FUSE_LIB_OPT("fmask=%o", fmask, 0), + FUSE_LIB_OPT("dmask=", set_mode, 1), + FUSE_LIB_OPT("dmask=%o", dmask, 0), + FUSE_LIB_OPT("uid=", set_uid, 1), + FUSE_LIB_OPT("uid=%d", uid, 0), + FUSE_LIB_OPT("gid=", set_gid, 1), + FUSE_LIB_OPT("gid=%d", gid, 0), + FUSE_LIB_OPT("entry_timeout=%lf", entry_timeout, 0), + FUSE_LIB_OPT("attr_timeout=%lf", attr_timeout, 0), + FUSE_LIB_OPT("ac_attr_timeout=%lf", ac_attr_timeout, 0), + FUSE_LIB_OPT("ac_attr_timeout=", ac_attr_timeout_set, 1), + FUSE_LIB_OPT("negative_timeout=%lf", negative_timeout, 0), + FUSE_LIB_OPT("noforget", remember, -1), + FUSE_LIB_OPT("remember=%u", remember, 0), + FUSE_LIB_OPT("modules=%s", modules, 0), + FUSE_LIB_OPT("parallel_direct_write=%d", parallel_direct_writes, 0), + FUSE_OPT_END +}; + +static int fuse_lib_opt_proc(void *data, const char *arg, int key, + struct fuse_args *outargs) +{ + (void) arg; (void) outargs; (void) data; (void) key; + + /* Pass through unknown options */ + return 1; +} + + +static const struct fuse_opt fuse_help_opts[] = { + FUSE_LIB_OPT("modules=%s", modules, 1), + FUSE_OPT_KEY("modules=%s", FUSE_OPT_KEY_KEEP), + FUSE_OPT_END +}; + +static void print_module_help(const char *name, + fuse_module_factory_t *fac) +{ + struct fuse_args a = FUSE_ARGS_INIT(0, NULL); + if (fuse_opt_add_arg(&a, "") == -1 || + fuse_opt_add_arg(&a, "-h") == -1) { + fuse_opt_free_args(&a); + return; + } + printf("\nOptions for %s module:\n", name); + (*fac)(&a, NULL); + fuse_opt_free_args(&a); +} + +void fuse_lib_help(struct fuse_args *args) +{ + /* These are not all options, but only the ones that + may be of interest to an end-user */ + printf( +" -o kernel_cache cache files in kernel\n" +" -o [no]auto_cache enable caching based on modification times (off)\n" +" -o no_rofd_flush disable flushing of read-only fd on close (off)\n" +" -o umask=M set file permissions (octal)\n" +" -o fmask=M set file permissions (octal)\n" +" -o dmask=M set dir permissions (octal)\n" +" -o uid=N set file owner\n" +" -o gid=N set file group\n" +" -o entry_timeout=T cache timeout for names (1.0s)\n" +" -o negative_timeout=T cache timeout for deleted names (0.0s)\n" +" -o attr_timeout=T cache timeout for attributes (1.0s)\n" +" -o ac_attr_timeout=T auto cache timeout for attributes (attr_timeout)\n" +" -o noforget never forget cached inodes\n" +" -o remember=T remember cached inodes for T seconds (0s)\n" +" -o modules=M1[:M2...] names of modules to push onto filesystem stack\n"); + + + /* Print low-level help */ + fuse_lowlevel_help(); + + /* Print help for builtin modules */ + print_module_help("subdir", &fuse_module_subdir_factory); +#ifdef HAVE_ICONV + print_module_help("iconv", &fuse_module_iconv_factory); +#endif + + /* Parse command line options in case we need to + activate more modules */ + struct fuse_config conf = { .modules = NULL }; + if (fuse_opt_parse(args, &conf, fuse_help_opts, + fuse_lib_opt_proc) == -1 + || !conf.modules) + return; + + char *module; + char *next; + struct fuse_module *m; + + // Iterate over all modules + for (module = conf.modules; module; module = next) { + char *p; + for (p = module; *p && *p != ':'; p++); + next = *p ? p + 1 : NULL; + *p = '\0'; + + m = fuse_get_module(module); + if (m) + print_module_help(module, &m->factory); + } +} + +static int fuse_init_intr_signal(int signum, int *installed) +{ + struct sigaction old_sa; + + if (sigaction(signum, NULL, &old_sa) == -1) { + perror("fuse: cannot get old signal handler"); + return -1; + } + + if (old_sa.sa_handler == SIG_DFL) { + struct sigaction sa; + + memset(&sa, 0, sizeof(struct sigaction)); + sa.sa_handler = fuse_intr_sighandler; + sigemptyset(&sa.sa_mask); + + if (sigaction(signum, &sa, NULL) == -1) { + perror("fuse: cannot set interrupt signal handler"); + return -1; + } + *installed = 1; + } + return 0; +} + +static void fuse_restore_intr_signal(int signum) +{ + struct sigaction sa; + + memset(&sa, 0, sizeof(struct sigaction)); + sa.sa_handler = SIG_DFL; + sigaction(signum, &sa, NULL); +} + + +static int fuse_push_module(struct fuse *f, const char *module, + struct fuse_args *args) +{ + struct fuse_fs *fs[2] = { f->fs, NULL }; + struct fuse_fs *newfs; + struct fuse_module *m = fuse_get_module(module); + + if (!m) + return -1; + + newfs = m->factory(args, fs); + if (!newfs) { + fuse_put_module(m); + return -1; + } + f->fs = newfs; + return 0; +} + +struct fuse_fs *fuse_fs_new(const struct fuse_operations *op, size_t op_size, + void *user_data) +{ + struct fuse_fs *fs; + + if (sizeof(struct fuse_operations) < op_size) { + fuse_log(FUSE_LOG_ERR, "fuse: warning: library too old, some operations may not not work\n"); + op_size = sizeof(struct fuse_operations); + } + + fs = (struct fuse_fs *) calloc(1, sizeof(struct fuse_fs)); + if (!fs) { + fuse_log(FUSE_LOG_ERR, "fuse: failed to allocate fuse_fs object\n"); + return NULL; + } + + fs->user_data = user_data; + if (op) + memcpy(&fs->op, op, op_size); + return fs; +} + +static int node_table_init(struct node_table *t) +{ + t->size = NODE_TABLE_MIN_SIZE; + t->array = (struct node **) calloc(1, sizeof(struct node *) * t->size); + if (t->array == NULL) { + fuse_log(FUSE_LOG_ERR, "fuse: memory allocation failed\n"); + return -1; + } + t->use = 0; + t->split = 0; + + return 0; +} + +static void *fuse_prune_nodes(void *fuse) +{ + struct fuse *f = fuse; + int sleep_time; + + fuse_set_thread_name("fuse_prune_nodes"); + + while(1) { + sleep_time = fuse_clean_cache(f); + sleep(sleep_time); + } + return NULL; +} + +int fuse_start_cleanup_thread(struct fuse *f) +{ + if (lru_enabled(f)) + return fuse_start_thread(&f->prune_thread, fuse_prune_nodes, f); + + return 0; +} + +void fuse_stop_cleanup_thread(struct fuse *f) +{ + if (lru_enabled(f)) { + pthread_mutex_lock(&f->lock); + pthread_cancel(f->prune_thread); + pthread_mutex_unlock(&f->lock); + pthread_join(f->prune_thread, NULL); + } +} + +/* + * Not supposed to be called directly, but supposed to be called + * through the fuse_new macro + */ +struct fuse *_fuse_new_31(struct fuse_args *args, + const struct fuse_operations *op, size_t op_size, + struct libfuse_version *version, void *user_data) +{ + struct fuse *f; + struct node *root; + struct fuse_fs *fs; + struct fuse_lowlevel_ops llop = fuse_path_ops; + + f = (struct fuse *) calloc(1, sizeof(struct fuse)); + if (f == NULL) { + fuse_log(FUSE_LOG_ERR, "fuse: failed to allocate fuse object\n"); + goto out; + } + + f->conf.entry_timeout = 1.0; + f->conf.attr_timeout = 1.0; + f->conf.negative_timeout = 0.0; + f->conf.intr_signal = FUSE_DEFAULT_INTR_SIGNAL; + + /* Parse options */ + if (fuse_opt_parse(args, &f->conf, fuse_lib_opts, + fuse_lib_opt_proc) == -1) + goto out_free; + + pthread_mutex_lock(&fuse_context_lock); + static int builtin_modules_registered = 0; + /* Have the builtin modules already been registered? */ + if (builtin_modules_registered == 0) { + /* If not, register them. */ + fuse_register_module("subdir", fuse_module_subdir_factory, NULL); +#ifdef HAVE_ICONV + fuse_register_module("iconv", fuse_module_iconv_factory, NULL); +#endif + builtin_modules_registered= 1; + } + pthread_mutex_unlock(&fuse_context_lock); + + if (fuse_create_context_key() == -1) + goto out_free; + + fs = fuse_fs_new(op, op_size, user_data); + if (!fs) + goto out_delete_context_key; + + f->fs = fs; + + /* Oh f**k, this is ugly! */ + if (!fs->op.lock) { + llop.getlk = NULL; + llop.setlk = NULL; + } + + f->pagesize = getpagesize(); + init_list_head(&f->partial_slabs); + init_list_head(&f->full_slabs); + init_list_head(&f->lru_table); + + if (f->conf.modules) { + char *module; + char *next; + + for (module = f->conf.modules; module; module = next) { + char *p; + for (p = module; *p && *p != ':'; p++); + next = *p ? p + 1 : NULL; + *p = '\0'; + if (module[0] && + fuse_push_module(f, module, args) == -1) + goto out_free_fs; + } + } + + if (!f->conf.ac_attr_timeout_set) + f->conf.ac_attr_timeout = f->conf.attr_timeout; + +#if defined(__FreeBSD__) || defined(__NetBSD__) + /* + * In FreeBSD, we always use these settings as inode numbers + * are needed to make getcwd(3) work. + */ + f->conf.readdir_ino = 1; +#endif + + /* not declared globally, to restrict usage of this function */ + struct fuse_session *fuse_session_new_versioned( + struct fuse_args *args, const struct fuse_lowlevel_ops *op, + size_t op_size, struct libfuse_version *version, + void *userdata); + f->se = fuse_session_new_versioned(args, &llop, sizeof(llop), version, + f); + if (f->se == NULL) + goto out_free_fs; + + /* Trace topmost layer by default */ + f->fs->debug = f->conf.debug; + f->ctr = 0; + f->generation = 0; + if (node_table_init(&f->name_table) == -1) + goto out_free_session; + + if (node_table_init(&f->id_table) == -1) + goto out_free_name_table; + + pthread_mutex_init(&f->lock, NULL); + + root = alloc_node(f); + if (root == NULL) { + fuse_log(FUSE_LOG_ERR, "fuse: memory allocation failed\n"); + goto out_free_id_table; + } + if (lru_enabled(f)) { + struct node_lru *lnode = node_lru(root); + init_list_head(&lnode->lru); + } + + strcpy(root->inline_name, "/"); + root->name = root->inline_name; + root->parent = NULL; + root->nodeid = FUSE_ROOT_ID; + inc_nlookup(root); + hash_id(f, root); + + return f; + +out_free_id_table: + free(f->id_table.array); +out_free_name_table: + free(f->name_table.array); +out_free_session: + fuse_session_destroy(f->se); +out_free_fs: + free(f->fs); + free(f->conf.modules); +out_delete_context_key: + fuse_delete_context_key(); +out_free: + free(f); +out: + return NULL; +} + +/* Emulates 3.0-style fuse_new(), which processes --help */ +FUSE_SYMVER("_fuse_new_30", "_fuse_new@FUSE_3.0") +struct fuse *_fuse_new_30(struct fuse_args *args, + const struct fuse_operations *op, + size_t op_size, + struct libfuse_version *version, + void *user_data) +{ + struct fuse_config conf = {0}; + + const struct fuse_opt opts[] = { + FUSE_LIB_OPT("-h", show_help, 1), + FUSE_LIB_OPT("--help", show_help, 1), + FUSE_OPT_END + }; + + if (fuse_opt_parse(args, &conf, opts, + fuse_lib_opt_proc) == -1) + return NULL; + + if (conf.show_help) { + fuse_lib_help(args); + return NULL; + } else + return _fuse_new_31(args, op, op_size, version, user_data); +} + +/* ABI compat version */ +struct fuse *fuse_new_31(struct fuse_args *args, const struct fuse_operations *op, + size_t op_size, void *user_data); +FUSE_SYMVER("fuse_new_31", "fuse_new@FUSE_3.1") +struct fuse *fuse_new_31(struct fuse_args *args, + const struct fuse_operations *op, + size_t op_size, void *user_data) +{ + /* unknown version */ + struct libfuse_version version = { 0 }; + + return _fuse_new_31(args, op, op_size, &version, user_data); +} + +/* + * ABI compat version + * Emulates 3.0-style fuse_new(), which processes --help + */ +struct fuse *fuse_new_30(struct fuse_args *args, const struct fuse_operations *op, + size_t op_size, void *user_data); +FUSE_SYMVER("fuse_new_30", "fuse_new@FUSE_3.0") +struct fuse *fuse_new_30(struct fuse_args *args, + const struct fuse_operations *op, + size_t op_size, void *user_data) +{ + struct fuse_config conf = {0}; + + const struct fuse_opt opts[] = { + FUSE_LIB_OPT("-h", show_help, 1), + FUSE_LIB_OPT("--help", show_help, 1), + FUSE_OPT_END + }; + + if (fuse_opt_parse(args, &conf, opts, + fuse_lib_opt_proc) == -1) + return NULL; + + if (conf.show_help) { + fuse_lib_help(args); + return NULL; + } else + return fuse_new_31(args, op, op_size, user_data); +} + + +void fuse_destroy(struct fuse *f) +{ + size_t i; + + if (f->conf.intr && f->intr_installed) + fuse_restore_intr_signal(f->conf.intr_signal); + + if (f->fs) { + fuse_create_context(f); + + for (i = 0; i < f->id_table.size; i++) { + struct node *node; + + for (node = f->id_table.array[i]; node != NULL; + node = node->id_next) { + if (node->is_hidden) { + char *path; + if (try_get_path(f, node->nodeid, NULL, &path, NULL, false) == 0) { + fuse_fs_unlink(f->fs, path); + free(path); + } + } + } + } + } + for (i = 0; i < f->id_table.size; i++) { + struct node *node; + struct node *next; + + for (node = f->id_table.array[i]; node != NULL; node = next) { + next = node->id_next; + free_node(f, node); + f->id_table.use--; + } + } + assert(list_empty(&f->partial_slabs)); + assert(list_empty(&f->full_slabs)); + + while (fuse_modules) { + fuse_put_module(fuse_modules); + } + free(f->id_table.array); + free(f->name_table.array); + pthread_mutex_destroy(&f->lock); + fuse_session_destroy(f->se); + free(f->fs); + free(f->conf.modules); + free(f); + fuse_delete_context_key(); +} + +int fuse_mount(struct fuse *f, const char *mountpoint) { + return fuse_session_mount(fuse_get_session(f), mountpoint); +} + + +void fuse_unmount(struct fuse *f) { + fuse_session_unmount(fuse_get_session(f)); +} + +int fuse_version(void) +{ + return FUSE_VERSION; +} + +const char *fuse_pkgversion(void) +{ + return PACKAGE_VERSION; +} diff --git a/lib/fuse_i.h b/lib/fuse_i.h new file mode 100644 index 0000000..fa8d26b --- /dev/null +++ b/lib/fuse_i.h @@ -0,0 +1,266 @@ +/* + FUSE: Filesystem in Userspace + Copyright (C) 2001-2007 Miklos Szeredi + + This program can be distributed under the terms of the GNU LGPLv2. + See the file LGPL2.txt +*/ + +#ifndef LIB_FUSE_I_H_ +#define LIB_FUSE_I_H_ + +#include "fuse.h" +#include "fuse_lowlevel.h" +#include "util.h" + +#include +#include +#include +#include +#include +#include + +#define MIN(a, b) \ +({ \ + typeof(a) _a = (a); \ + typeof(b) _b = (b); \ + _a < _b ? _a : _b; \ +}) + +struct mount_opts; +struct fuse_ring_pool; + +struct fuse_req { + struct fuse_session *se; + uint64_t unique; + _Atomic int ref_cnt; + pthread_mutex_t lock; + struct fuse_ctx ctx; + struct fuse_chan *ch; + int interrupted; + struct { + unsigned int ioctl_64bit : 1; + unsigned int is_uring : 1; + unsigned int is_copy_file_range_64 : 1; + } flags; + union { + struct { + uint64_t unique; + } i; + struct { + fuse_interrupt_func_t func; + void *data; + } ni; + } u; + struct fuse_req *next; + struct fuse_req *prev; +}; + +struct fuse_notify_req { + uint64_t unique; + void (*reply)(struct fuse_notify_req *, fuse_req_t, fuse_ino_t, + const void *, const struct fuse_buf *); + struct fuse_notify_req *next; + struct fuse_notify_req *prev; +}; + +struct fuse_session_uring { + bool enable; + unsigned int q_depth; + struct fuse_ring_pool *pool; +}; + +struct fuse_session { + _Atomic(char *)mountpoint; + int fd; + struct fuse_custom_io *io; + struct mount_opts *mo; + int debug; + int deny_others; + struct fuse_lowlevel_ops op; + int got_init; + struct cuse_data *cuse_data; + void *userdata; + uid_t owner; + struct fuse_conn_info conn; + struct fuse_req list; + struct fuse_req interrupts; + pthread_mutex_t lock; + int got_destroy; + pthread_key_t pipe_key; + int broken_splice_nonblock; + uint64_t notify_ctr; + struct fuse_notify_req notify_list; + _Atomic size_t bufsize; + int error; + + /* + * This is useful if any kind of ABI incompatibility is found at + * a later version, to 'fix' it at run time. + */ + struct libfuse_version version; + + /* thread synchronization */ + _Atomic bool mt_exited; + pthread_mutex_t mt_lock; + sem_t mt_finish; + + /* true if reading requests from /dev/fuse are handled internally */ + bool buf_reallocable; + + /* io_uring */ + struct fuse_session_uring uring; + + /* + * conn->want and conn_want_ext options set by libfuse , needed + * to correctly convert want to want_ext + */ + uint32_t conn_want; + uint64_t conn_want_ext; +}; + +struct fuse_chan { + pthread_mutex_t lock; + int ctr; + int fd; +}; + +/** + * Filesystem module + * + * Filesystem modules are registered with the FUSE_REGISTER_MODULE() + * macro. + * + */ +struct fuse_module { + char *name; + fuse_module_factory_t factory; + struct fuse_module *next; + struct fusemod_so *so; + int ctr; +}; + +/** + * Configuration parameters passed to fuse_session_loop_mt() and + * fuse_loop_mt(). + * + * Internal API to avoid exposing the plain data structure and + * causing compat issues after adding or removing struct members. + * + */ +#if FUSE_USE_VERSION >= FUSE_MAKE_VERSION(3, 12) +struct fuse_loop_config +{ + /* verififier that a correct struct was was passed. This is especially + * needed, as versions below (3, 12) were using a public struct + * (now called fuse_loop_config_v1), which was hard to extend with + * additional parameters, without risking that file system implementations + * would not have noticed and might either pass uninitialized members + * or even too small structs. + * fuse_loop_config_v1 has clone_fd at this offset, which should be either 0 + * or 1. v2 or even higher version just need to set a value here + * which not conflicting and very unlikely as having been set by + * file system implementation. + */ + int version_id; + + /** + * whether to use separate device fds for each thread + * (may increase performance) + */ + int clone_fd; + /** + * The maximum number of available worker threads before they + * start to get deleted when they become idle. If not + * specified, the default is -1. + * + * Adjusting this has performance implications; a very small number + * of threads in the pool will cause a lot of thread creation and + * deletion overhead and performance may suffer. When set to 0, a new + * thread will be created to service every operation. + * The special value of -1 means that this parameter is disabled. + */ + int max_idle_threads; + + /** + * max number of threads taking and processing kernel requests + * + * As of now threads are created dynamically + */ + unsigned int max_threads; +}; +#endif + +/* ----------------------------------------------------------- * + * Channel interface (when using -o clone_fd) * + * ----------------------------------------------------------- */ + +/** + * Obtain counted reference to the channel + * + * @param ch the channel + * @return the channel + */ +struct fuse_chan *fuse_chan_get(struct fuse_chan *ch); + +/** + * Drop counted reference to a channel + * + * @param ch the channel + */ +void fuse_chan_put(struct fuse_chan *ch); + +struct mount_opts *parse_mount_opts(struct fuse_args *args); +void destroy_mount_opts(struct mount_opts *mo); +void fuse_mount_version(void); +unsigned get_max_read(struct mount_opts *o); +void fuse_kern_unmount(const char *mountpoint, int fd); +int fuse_kern_mount(const char *mountpoint, struct mount_opts *mo); + +int fuse_send_reply_iov_nofree(fuse_req_t req, int error, struct iovec *iov, + int count); +void fuse_free_req(fuse_req_t req); +void list_init_req(struct fuse_req *req); + +void _cuse_lowlevel_init(fuse_req_t req, const fuse_ino_t nodeid, + const void *req_header, const void *req_payload); +void cuse_lowlevel_init(fuse_req_t req, fuse_ino_t nodeide, const void *inarg); + +int fuse_start_thread(pthread_t *thread_id, void *(*func)(void *), void *arg); + +void fuse_buf_free(struct fuse_buf *buf); + +int fuse_session_receive_buf_internal(struct fuse_session *se, + struct fuse_buf *buf, + struct fuse_chan *ch); +void fuse_session_process_buf_internal(struct fuse_session *se, + const struct fuse_buf *buf, + struct fuse_chan *ch); + +struct fuse *fuse_new_31(struct fuse_args *args, const struct fuse_operations *op, + size_t op_size, void *private_data); +int fuse_loop_mt_312(struct fuse *f, struct fuse_loop_config *config); +int fuse_session_loop_mt_312(struct fuse_session *se, struct fuse_loop_config *config); + +/** + * Internal verifier for the given config. + * + * @return negative standard error code or 0 on success + */ +int fuse_loop_cfg_verify(struct fuse_loop_config *config); + + +/* + * This can be changed dynamically on recent kernels through the + * /proc/sys/fs/fuse/max_pages_limit interface. + * + * Older kernels will always use the default value. + */ +#define FUSE_DEFAULT_MAX_PAGES_LIMIT 256 +#define FUSE_DEFAULT_MAX_PAGES_PER_REQ 32 + +/* room needed in buffer to accommodate header */ +#define FUSE_BUFFER_HEADER_SIZE 0x1000 + + +#endif /* LIB_FUSE_I_H_*/ diff --git a/lib/fuse_log.c b/lib/fuse_log.c new file mode 100644 index 0000000..66dcf8a --- /dev/null +++ b/lib/fuse_log.c @@ -0,0 +1,59 @@ +/* + FUSE: Filesystem in Userspace + Copyright (C) 2019 Red Hat, Inc. + + Logging API. + + This program can be distributed under the terms of the GNU LGPLv2. + See the file LGPL2.txt +*/ + +#include "fuse_log.h" + +#include +#include +#include +#include + +#define MAX_SYSLOG_LINE_LEN 512 + +static bool to_syslog = false; + +static void default_log_func(enum fuse_log_level level, const char *fmt, va_list ap) +{ + if (to_syslog) + vsyslog(level, fmt, ap); + else + vfprintf(stderr, fmt, ap); +} + +static fuse_log_func_t log_func = default_log_func; + +void fuse_set_log_func(fuse_log_func_t func) +{ + if (!func) + func = default_log_func; + + log_func = func; +} + +void fuse_log(enum fuse_log_level level, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + log_func(level, fmt, ap); + va_end(ap); +} + +void fuse_log_enable_syslog(const char *ident, int option, int facility) +{ + to_syslog = true; + + openlog(ident, option, facility); +} + +void fuse_log_close_syslog(void) +{ + closelog(); +} diff --git a/lib/fuse_loop.c b/lib/fuse_loop.c new file mode 100644 index 0000000..1ff075c --- /dev/null +++ b/lib/fuse_loop.c @@ -0,0 +1,48 @@ +/* + FUSE: Filesystem in Userspace + Copyright (C) 2001-2007 Miklos Szeredi + + Implementation of the single-threaded FUSE session loop. + + This program can be distributed under the terms of the GNU LGPLv2. + See the file LGPL2.txt +*/ + +#include "fuse_config.h" +#include "fuse_lowlevel.h" +#include "fuse_i.h" +#include "fuse_uring_i.h" +#include +#include +#include + +int fuse_session_loop(struct fuse_session *se) +{ + int res = 0; + struct fuse_buf fbuf = { + .mem = NULL, + }; + + while (!fuse_session_exited(se)) { + res = fuse_session_receive_buf_internal(se, &fbuf, NULL); + + if (res == -EINTR) + continue; + if (res <= 0) + break; + + fuse_session_process_buf(se, &fbuf); + } + + fuse_buf_free(&fbuf); + if(res > 0) + /* No error, just the length of the most recently read + request */ + res = 0; + if(se->error != 0) + res = se->error; + + if (se->uring.pool) + fuse_uring_stop(se); + return res; +} diff --git a/lib/fuse_loop_mt.c b/lib/fuse_loop_mt.c new file mode 100644 index 0000000..7bdb1c7 --- /dev/null +++ b/lib/fuse_loop_mt.c @@ -0,0 +1,538 @@ +/* + FUSE: Filesystem in Userspace + Copyright (C) 2001-2007 Miklos Szeredi + + Implementation of the multi-threaded FUSE session loop. + + This program can be distributed under the terms of the GNU LGPLv2. + See the file LGPL2.txt. +*/ + +#define _GNU_SOURCE + +#include "fuse_config.h" +#include "fuse_lowlevel.h" +#include "fuse_misc.h" +#include "fuse_kernel.h" +#include "fuse_i.h" +#include "fuse_uring_i.h" +#include "util.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Environment var controlling the thread stack size */ +#define ENVNAME_THREAD_STACK "FUSE_THREAD_STACK" + +#define FUSE_LOOP_MT_V2_IDENTIFIER INT_MAX - 2 +#define FUSE_LOOP_MT_DEF_CLONE_FD 0 +#define FUSE_LOOP_MT_DEF_MAX_THREADS 10 +#define FUSE_LOOP_MT_DEF_IDLE_THREADS -1 /* thread destruction is disabled + * by default */ + +/* an arbitrary large value that cannot be valid */ +#define FUSE_LOOP_MT_MAX_THREADS (100U * 1000) + +struct fuse_worker { + struct fuse_worker *prev; + struct fuse_worker *next; + pthread_t thread_id; + + // We need to include fuse_buf so that we can properly free + // it when a thread is terminated by pthread_cancel(). + struct fuse_buf fbuf; + struct fuse_chan *ch; + struct fuse_mt *mt; +}; + +/* synchronization via se->mt_lock */ +struct fuse_mt { + int numworker; + int numavail; + struct fuse_session *se; + struct fuse_worker main; + int error; + int clone_fd; + int max_idle; + int max_threads; +}; + +static struct fuse_chan *fuse_chan_new(int fd) +{ + struct fuse_chan *ch = (struct fuse_chan *) malloc(sizeof(*ch)); + if (ch == NULL) { + fuse_log(FUSE_LOG_ERR, "fuse: failed to allocate channel\n"); + return NULL; + } + + memset(ch, 0, sizeof(*ch)); + ch->fd = fd; + ch->ctr = 1; + pthread_mutex_init(&ch->lock, NULL); + + return ch; +} + +struct fuse_chan *fuse_chan_get(struct fuse_chan *ch) +{ + assert(ch->ctr > 0); + pthread_mutex_lock(&ch->lock); + ch->ctr++; + pthread_mutex_unlock(&ch->lock); + + return ch; +} + +void fuse_chan_put(struct fuse_chan *ch) +{ + if (ch == NULL) + return; + pthread_mutex_lock(&ch->lock); + ch->ctr--; + if (!ch->ctr) { + pthread_mutex_unlock(&ch->lock); + close(ch->fd); + pthread_mutex_destroy(&ch->lock); + free(ch); + } else + pthread_mutex_unlock(&ch->lock); +} + +static void list_add_worker(struct fuse_worker *w, struct fuse_worker *next) +{ + struct fuse_worker *prev = next->prev; + w->next = next; + w->prev = prev; + prev->next = w; + next->prev = w; +} + +static void list_del_worker(struct fuse_worker *w) +{ + struct fuse_worker *prev = w->prev; + struct fuse_worker *next = w->next; + prev->next = next; + next->prev = prev; +} + +static int fuse_loop_start_thread(struct fuse_mt *mt); + +static void *fuse_do_work(void *data) +{ + struct fuse_worker *w = (struct fuse_worker *) data; + struct fuse_mt *mt = w->mt; + struct fuse_session *se = mt->se; + + fuse_set_thread_name("fuse_worker"); + + while (!fuse_session_exited(se)) { + int isforget = 0; + int res; + + pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL); + res = fuse_session_receive_buf_internal(se, &w->fbuf, w->ch); + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL); + if (res == -EINTR) + continue; + if (res <= 0) { + if (res < 0) { + fuse_session_exit(se); + mt->error = res; + } + break; + } + + pthread_mutex_lock(&se->mt_lock); + if (fuse_session_exited(se)) { + pthread_mutex_unlock(&se->mt_lock); + return NULL; + } + + /* + * This disgusting hack is needed so that zillions of threads + * are not created on a burst of FORGET messages + */ + if (!(w->fbuf.flags & FUSE_BUF_IS_FD)) { + struct fuse_in_header *in = w->fbuf.mem; + + if (in->opcode == FUSE_FORGET || + in->opcode == FUSE_BATCH_FORGET) + isforget = 1; + } + + if (!isforget) + mt->numavail--; + if (mt->numavail == 0 && mt->numworker < mt->max_threads && + likely(se->got_init)) + fuse_loop_start_thread(mt); + pthread_mutex_unlock(&se->mt_lock); + + fuse_session_process_buf_internal(se, &w->fbuf, w->ch); + + pthread_mutex_lock(&se->mt_lock); + if (!isforget) + mt->numavail++; + + /* creating and destroying threads is rather expensive - and there is + * not much gain from destroying existing threads. It is therefore + * discouraged to set max_idle to anything else than -1. If there + * is indeed a good reason to destruct threads it should be done + * delayed, a moving average might be useful for that. + */ + if (mt->max_idle != -1 && mt->numavail > mt->max_idle && mt->numworker > 1) { + if (fuse_session_exited(se)) { + pthread_mutex_unlock(&se->mt_lock); + return NULL; + } + list_del_worker(w); + mt->numavail--; + mt->numworker--; + pthread_mutex_unlock(&se->mt_lock); + + pthread_detach(w->thread_id); + fuse_buf_free(&w->fbuf); + fuse_chan_put(w->ch); + free(w); + return NULL; + } + pthread_mutex_unlock(&se->mt_lock); + } + + sem_post(&se->mt_finish); + return NULL; +} + +int fuse_start_thread(pthread_t *thread_id, void *(*func)(void *), void *arg) +{ + sigset_t oldset; + sigset_t newset; + int res; + pthread_attr_t attr; + char *stack_size; + + /* Override default stack size + * XXX: This should ideally be a parameter option. It is rather + * well hidden here. + */ + pthread_attr_init(&attr); + stack_size = getenv(ENVNAME_THREAD_STACK); + if (stack_size) { + long size; + + res = libfuse_strtol(stack_size, &size); + if (res) + fuse_log(FUSE_LOG_ERR, "fuse: invalid stack size: %s\n", + stack_size); + else if (pthread_attr_setstacksize(&attr, size)) + fuse_log(FUSE_LOG_ERR, "fuse: could not set stack size: %ld\n", + size); + } + + /* Disallow signal reception in worker threads */ + sigemptyset(&newset); + sigaddset(&newset, SIGTERM); + sigaddset(&newset, SIGINT); + sigaddset(&newset, SIGHUP); + sigaddset(&newset, SIGQUIT); + pthread_sigmask(SIG_BLOCK, &newset, &oldset); + res = pthread_create(thread_id, &attr, func, arg); + pthread_sigmask(SIG_SETMASK, &oldset, NULL); + pthread_attr_destroy(&attr); + if (res != 0) { + fuse_log(FUSE_LOG_ERR, "fuse: error creating thread: %s\n", + strerror(res)); + return -1; + } + + return 0; +} + +static int fuse_clone_chan_fd_default(struct fuse_session *se) +{ + int res; + int clonefd; + uint32_t masterfd; + const char *devname = "/dev/fuse"; + +#ifndef O_CLOEXEC +#define O_CLOEXEC 0 +#endif + clonefd = open(devname, O_RDWR | O_CLOEXEC); + if (clonefd == -1) { + fuse_log(FUSE_LOG_ERR, "fuse: failed to open %s: %s\n", devname, + strerror(errno)); + return -1; + } + if (!O_CLOEXEC) { + res = fcntl(clonefd, F_SETFD, FD_CLOEXEC); + if (res == -1) { + fuse_log(FUSE_LOG_ERR, "fuse: failed to set CLOEXEC: %s\n", + strerror(errno)); + close(clonefd); + return -1; + } + } + + masterfd = se->fd; + res = ioctl(clonefd, FUSE_DEV_IOC_CLONE, &masterfd); + if (res == -1) { + fuse_log(FUSE_LOG_ERR, "fuse: failed to clone device fd: %s\n", + strerror(errno)); + close(clonefd); + return -1; + } + return clonefd; +} + +static struct fuse_chan *fuse_clone_chan(struct fuse_mt *mt) +{ + int clonefd; + struct fuse_session *se = mt->se; + struct fuse_chan *newch; + + if (se->io != NULL) { + if (se->io->clone_fd != NULL) + clonefd = se->io->clone_fd(se->fd); + else + return NULL; + } else { + clonefd = fuse_clone_chan_fd_default(se); + } + if (clonefd < 0) + return NULL; + + newch = fuse_chan_new(clonefd); + if (newch == NULL) + close(clonefd); + + return newch; +} + +static int fuse_loop_start_thread(struct fuse_mt *mt) +{ + int res; + + struct fuse_worker *w = malloc(sizeof(struct fuse_worker)); + if (!w) { + fuse_log(FUSE_LOG_ERR, "fuse: failed to allocate worker structure\n"); + return -1; + } + memset(w, 0, sizeof(struct fuse_worker)); + w->fbuf.mem = NULL; + w->mt = mt; + + w->ch = NULL; + if (mt->clone_fd) { + w->ch = fuse_clone_chan(mt); + if(!w->ch) { + /* Don't attempt this again */ + fuse_log(FUSE_LOG_ERR, "fuse: trying to continue " + "without -o clone_fd.\n"); + mt->clone_fd = 0; + } + } + + res = fuse_start_thread(&w->thread_id, fuse_do_work, w); + if (res == -1) { + fuse_chan_put(w->ch); + free(w); + return -1; + } + list_add_worker(w, &mt->main); + mt->numavail ++; + mt->numworker ++; + + return 0; +} + +static void fuse_join_worker(struct fuse_mt *mt, struct fuse_worker *w) +{ + pthread_join(w->thread_id, NULL); + pthread_mutex_lock(&mt->se->mt_lock); + list_del_worker(w); + pthread_mutex_unlock(&mt->se->mt_lock); + fuse_buf_free(&w->fbuf); + fuse_chan_put(w->ch); + free(w); +} + +int fuse_session_loop_mt_312(struct fuse_session *se, struct fuse_loop_config *config); +FUSE_SYMVER("fuse_session_loop_mt_312", "fuse_session_loop_mt@@FUSE_3.12") +int fuse_session_loop_mt_312(struct fuse_session *se, struct fuse_loop_config *config) +{ +int err; + struct fuse_mt mt; + struct fuse_worker *w; + int created_config = 0; + + if (config) { + err = fuse_loop_cfg_verify(config); + if (err) + return err; + } else { + /* The caller does not care about parameters - use the default */ + config = fuse_loop_cfg_create(); + if (!config) + return -ENOMEM; + created_config = 1; + } + + + memset(&mt, 0, sizeof(struct fuse_mt)); + mt.se = se; + mt.clone_fd = config->clone_fd; + mt.error = 0; + mt.numworker = 0; + mt.numavail = 0; + mt.max_idle = config->max_idle_threads; + mt.max_threads = config->max_threads; + mt.main.thread_id = pthread_self(); + mt.main.prev = mt.main.next = &mt.main; + + pthread_mutex_lock(&se->mt_lock); + err = fuse_loop_start_thread(&mt); + pthread_mutex_unlock(&se->mt_lock); + if (!err) { + while (!fuse_session_exited(se)) + sem_wait(&se->mt_finish); + if (se->debug) + fuse_log(FUSE_LOG_DEBUG, + "fuse: session exited, terminating workers\n"); + + pthread_mutex_lock(&se->mt_lock); + for (w = mt.main.next; w != &mt.main; w = w->next) + pthread_cancel(w->thread_id); + pthread_mutex_unlock(&se->mt_lock); + + while (mt.main.next != &mt.main) + fuse_join_worker(&mt, mt.main.next); + + err = mt.error; + + if (se->uring.pool) + fuse_uring_stop(se); + } + + if(se->error != 0) + err = se->error; + + + if (created_config) { + fuse_loop_cfg_destroy(config); + config = NULL; + } + + return err; +} + +int fuse_session_loop_mt_32(struct fuse_session *se, struct fuse_loop_config_v1 *config_v1); +FUSE_SYMVER("fuse_session_loop_mt_32", "fuse_session_loop_mt@FUSE_3.2") +int fuse_session_loop_mt_32(struct fuse_session *se, struct fuse_loop_config_v1 *config_v1) +{ + int err; + struct fuse_loop_config *config = NULL; + + if (config_v1 != NULL) { + /* convert the given v1 config */ + config = fuse_loop_cfg_create(); + if (config == NULL) + return ENOMEM; + + fuse_loop_cfg_convert(config, config_v1); + } + + err = fuse_session_loop_mt_312(se, config); + + fuse_loop_cfg_destroy(config); + + return err; +} + + +int fuse_session_loop_mt_31(struct fuse_session *se, int clone_fd); +FUSE_SYMVER("fuse_session_loop_mt_31", "fuse_session_loop_mt@FUSE_3.0") +int fuse_session_loop_mt_31(struct fuse_session *se, int clone_fd) +{ + int err; + struct fuse_loop_config *config = fuse_loop_cfg_create(); + if (!config) + return -ENOMEM; + if (clone_fd > 0) + fuse_loop_cfg_set_clone_fd(config, clone_fd); + err = fuse_session_loop_mt_312(se, config); + + fuse_loop_cfg_destroy(config); + + return err; +} + +struct fuse_loop_config *fuse_loop_cfg_create(void) +{ + struct fuse_loop_config *config = calloc(1, sizeof(*config)); + if (config == NULL) + return NULL; + + config->version_id = FUSE_LOOP_MT_V2_IDENTIFIER; + config->max_idle_threads = FUSE_LOOP_MT_DEF_IDLE_THREADS; + config->max_threads = FUSE_LOOP_MT_DEF_MAX_THREADS; + config->clone_fd = FUSE_LOOP_MT_DEF_CLONE_FD; + + return config; +} + +void fuse_loop_cfg_destroy(struct fuse_loop_config *config) +{ + free(config); +} + +int fuse_loop_cfg_verify(struct fuse_loop_config *config) +{ + if (config->version_id != FUSE_LOOP_MT_V2_IDENTIFIER) + return -EINVAL; + + return 0; +} + +void fuse_loop_cfg_convert(struct fuse_loop_config *config, + struct fuse_loop_config_v1 *v1_conf) +{ + fuse_loop_cfg_set_idle_threads(config, v1_conf->max_idle_threads); + + fuse_loop_cfg_set_clone_fd(config, v1_conf->clone_fd); +} + +void fuse_loop_cfg_set_idle_threads(struct fuse_loop_config *config, + unsigned int value) +{ + if (value > FUSE_LOOP_MT_MAX_THREADS) { + if (value != UINT_MAX) + fuse_log(FUSE_LOG_ERR, + "Ignoring invalid max threads value " + "%u > max (%u).\n", value, + FUSE_LOOP_MT_MAX_THREADS); + return; + } + config->max_idle_threads = value; +} + +void fuse_loop_cfg_set_max_threads(struct fuse_loop_config *config, + unsigned int value) +{ + config->max_threads = value; +} + +void fuse_loop_cfg_set_clone_fd(struct fuse_loop_config *config, + unsigned int value) +{ + config->clone_fd = value; +} + diff --git a/lib/fuse_lowlevel.c b/lib/fuse_lowlevel.c new file mode 100644 index 0000000..00c2815 --- /dev/null +++ b/lib/fuse_lowlevel.c @@ -0,0 +1,4531 @@ +/* + FUSE: Filesystem in Userspace + Copyright (C) 2001-2007 Miklos Szeredi + + Implementation of (most of) the low-level FUSE API. The session loop + functions are implemented in separate files. + + This program can be distributed under the terms of the GNU LGPLv2. + See the file LGPL2.txt +*/ + +#define _GNU_SOURCE + +#include "fuse_config.h" +#include "fuse_i.h" +#include "fuse_kernel.h" +#include "fuse_opt.h" +#include "fuse_misc.h" +#include "mount_util.h" +#include "util.h" +#include "fuse_uring_i.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef USDT_ENABLED +#include "usdt.h" +#endif + +#ifndef F_LINUX_SPECIFIC_BASE +#define F_LINUX_SPECIFIC_BASE 1024 +#endif +#ifndef F_SETPIPE_SZ +#define F_SETPIPE_SZ (F_LINUX_SPECIFIC_BASE + 7) +#endif + +#ifndef O_CLOEXEC +#define O_CLOEXEC 0 +#endif + +#define PARAM(inarg) (((char *)(inarg)) + sizeof(*(inarg))) +#define OFFSET_MAX 0x7fffffffffffffffLL + +struct fuse_pollhandle { + uint64_t kh; + struct fuse_session *se; +}; + +static size_t pagesize; + +static __attribute__((constructor)) void fuse_ll_init_pagesize(void) +{ + pagesize = getpagesize(); +} + +#ifdef USDT_ENABLED +/* tracepoints */ +static void trace_request_receive(int err) +{ + USDT(libfuse, request_receive, err); +} + +static void trace_request_process(unsigned int opcode, unsigned int unique) +{ + USDT(libfuse, request_process, opcode, unique); +} + +static void trace_request_reply(uint64_t unique, unsigned int len, + int error, int reply_err) +{ + USDT(libfuse, request_reply, unique, len, error, reply_err); +} +#else +static void trace_request_receive(int err) +{ + (void)err; +} + +static void trace_request_process(unsigned int opcode, unsigned int unique) +{ + (void)opcode; + (void)unique; +} + +static void trace_request_reply(uint64_t unique, unsigned int len, + int error, int reply_err) +{ + (void)unique; + (void)len; + (void)error; + (void)reply_err; +} +#endif + +static void convert_stat(const struct stat *stbuf, struct fuse_attr *attr) +{ + attr->ino = stbuf->st_ino; + attr->mode = stbuf->st_mode; + attr->nlink = stbuf->st_nlink; + attr->uid = stbuf->st_uid; + attr->gid = stbuf->st_gid; + attr->rdev = stbuf->st_rdev; + attr->size = stbuf->st_size; + attr->blksize = stbuf->st_blksize; + attr->blocks = stbuf->st_blocks; + attr->atime = stbuf->st_atime; + attr->mtime = stbuf->st_mtime; + attr->ctime = stbuf->st_ctime; + attr->atimensec = ST_ATIM_NSEC(stbuf); + attr->mtimensec = ST_MTIM_NSEC(stbuf); + attr->ctimensec = ST_CTIM_NSEC(stbuf); +} + +static void convert_attr(const struct fuse_setattr_in *attr, struct stat *stbuf) +{ + stbuf->st_mode = attr->mode; + stbuf->st_uid = attr->uid; + stbuf->st_gid = attr->gid; + stbuf->st_size = attr->size; + stbuf->st_atime = attr->atime; + stbuf->st_mtime = attr->mtime; + stbuf->st_ctime = attr->ctime; + ST_ATIM_NSEC_SET(stbuf, attr->atimensec); + ST_MTIM_NSEC_SET(stbuf, attr->mtimensec); + ST_CTIM_NSEC_SET(stbuf, attr->ctimensec); +} + +static size_t iov_length(const struct iovec *iov, size_t count) +{ + size_t seg; + size_t ret = 0; + + for (seg = 0; seg < count; seg++) + ret += iov[seg].iov_len; + return ret; +} + +void list_init_req(struct fuse_req *req) +{ + req->next = req; + req->prev = req; +} + +static void list_del_req(struct fuse_req *req) +{ + struct fuse_req *prev = req->prev; + struct fuse_req *next = req->next; + prev->next = next; + next->prev = prev; +} + +static void list_add_req(struct fuse_req *req, struct fuse_req *next) +{ + struct fuse_req *prev = next->prev; + req->next = next; + req->prev = prev; + prev->next = req; + next->prev = req; +} + +static void destroy_req(fuse_req_t req) +{ + if (req->flags.is_uring) { + fuse_log(FUSE_LOG_ERR, "Refusing to destruct uring req\n"); + return; + } + assert(req->ch == NULL); + pthread_mutex_destroy(&req->lock); + free(req); +} + +void fuse_free_req(fuse_req_t req) +{ + int ctr; + struct fuse_session *se = req->se; + + /* XXX: for now no support for interrupts with io-uring + * It actually might work already, though. But then would add + * a lock across ring queues. + */ + if (se->conn.no_interrupt || req->flags.is_uring) { + ctr = --req->ref_cnt; + fuse_chan_put(req->ch); + req->ch = NULL; + } else { + pthread_mutex_lock(&se->lock); + req->u.ni.func = NULL; + req->u.ni.data = NULL; + list_del_req(req); + ctr = --req->ref_cnt; + fuse_chan_put(req->ch); + req->ch = NULL; + pthread_mutex_unlock(&se->lock); + } + if (!ctr) + destroy_req(req); +} + +static struct fuse_req *fuse_ll_alloc_req(struct fuse_session *se) +{ + struct fuse_req *req; + + req = (struct fuse_req *) calloc(1, sizeof(struct fuse_req)); + if (req == NULL) { + fuse_log(FUSE_LOG_ERR, "fuse: failed to allocate request\n"); + } else { + req->se = se; + req->ref_cnt = 1; + list_init_req(req); + pthread_mutex_init(&req->lock, NULL); + } + + return req; +} + +/* + * Send data to fuse-kernel using an fd of the fuse device. + */ +static int fuse_write_msg_dev(struct fuse_session *se, struct fuse_chan *ch, + struct iovec *iov, int count) +{ + ssize_t res; + int err; + + if (se->io != NULL) + + /* se->io->writev is never NULL if se->io is not NULL as + * specified by fuse_session_custom_io() + */ + res = se->io->writev(ch ? ch->fd : se->fd, iov, count, + se->userdata); + else + res = writev(ch ? ch->fd : se->fd, iov, count); + + if (res == -1) { + /* ENOENT means the operation was interrupted */ + err = errno; + if (!fuse_session_exited(se) && err != ENOENT) + perror("fuse: writing device"); + return -err; + } + + return 0; +} + +static int fuse_send_msg(struct fuse_session *se, struct fuse_chan *ch, + struct iovec *iov, int count, fuse_req_t req) +{ + struct fuse_out_header *out = iov[0].iov_base; + int err; + bool is_uring = req && req->flags.is_uring ? true : false; + + if (!is_uring) + assert(se != NULL); + out->len = iov_length(iov, count); + + if (se->debug) { + if (out->unique == 0) { + fuse_log(FUSE_LOG_DEBUG, "NOTIFY: code=%d length=%u\n", + out->error, out->len); + } else if (out->error) { + fuse_log(FUSE_LOG_DEBUG, + " unique: %llu, error: %i (%s), outsize: %i\n", + (unsigned long long) out->unique, out->error, + strerror(-out->error), out->len); + } else { + fuse_log(FUSE_LOG_DEBUG, + " unique: %llu, success, outsize: %i\n", + (unsigned long long) out->unique, out->len); + } + } + + if (is_uring) + err = fuse_send_msg_uring(req, iov, count); + else + err = fuse_write_msg_dev(se, ch, iov, count); + + trace_request_reply(out->unique, out->len, out->error, err); + return err; +} + +int fuse_send_reply_iov_nofree(fuse_req_t req, int error, struct iovec *iov, + int count) +{ + struct fuse_out_header out; + +#if __GLIBC__ >= 2 && __GLIBC_MINOR__ >= 32 + const char *str = strerrordesc_np(error * -1); + if ((str == NULL && error != 0) || error > 0) { +#else + if (error <= -1000 || error > 0) { +#endif + fuse_log(FUSE_LOG_ERR, "fuse: bad error value: %i\n", error); + error = -ERANGE; + } + + out.unique = req->unique; + out.error = error; + + iov[0].iov_base = &out; + iov[0].iov_len = sizeof(struct fuse_out_header); + + return fuse_send_msg(req->se, req->ch, iov, count, req); +} + +static int send_reply_iov(fuse_req_t req, int error, struct iovec *iov, + int count) +{ + int res; + + res = fuse_send_reply_iov_nofree(req, error, iov, count); + fuse_free_req(req); + return res; +} + +static int send_reply(fuse_req_t req, int error, const void *arg, + size_t argsize) +{ + if (req->flags.is_uring) + return send_reply_uring(req, error, arg, argsize); + + struct iovec iov[2]; + int count = 1; + if (argsize) { + iov[1].iov_base = (void *) arg; + iov[1].iov_len = argsize; + count++; + } + return send_reply_iov(req, error, iov, count); +} + +int fuse_reply_iov(fuse_req_t req, const struct iovec *iov, int count) +{ + int res; + struct iovec *padded_iov; + + padded_iov = malloc((count + 1) * sizeof(struct iovec)); + if (padded_iov == NULL) + return fuse_reply_err(req, ENOMEM); + + memcpy(padded_iov + 1, iov, count * sizeof(struct iovec)); + count++; + + res = send_reply_iov(req, 0, padded_iov, count); + free(padded_iov); + + return res; +} + + +/* `buf` is allowed to be empty so that the proper size may be + allocated by the caller */ +size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize, + const char *name, const struct stat *stbuf, off_t off) +{ + (void)req; + size_t namelen; + size_t entlen; + size_t entlen_padded; + struct fuse_dirent *dirent; + + namelen = strlen(name); + entlen = FUSE_NAME_OFFSET + namelen; + entlen_padded = FUSE_DIRENT_ALIGN(entlen); + + if ((buf == NULL) || (entlen_padded > bufsize)) + return entlen_padded; + + dirent = (struct fuse_dirent*) buf; + dirent->ino = stbuf->st_ino; + dirent->off = off; + dirent->namelen = namelen; + dirent->type = (stbuf->st_mode & S_IFMT) >> 12; + memcpy(dirent->name, name, namelen); + memset(dirent->name + namelen, 0, entlen_padded - entlen); + + return entlen_padded; +} + +static void convert_statfs(const struct statvfs *stbuf, + struct fuse_kstatfs *kstatfs) +{ + kstatfs->bsize = stbuf->f_bsize; + kstatfs->frsize = stbuf->f_frsize; + kstatfs->blocks = stbuf->f_blocks; + kstatfs->bfree = stbuf->f_bfree; + kstatfs->bavail = stbuf->f_bavail; + kstatfs->files = stbuf->f_files; + kstatfs->ffree = stbuf->f_ffree; + kstatfs->namelen = stbuf->f_namemax; +} + +static int send_reply_ok(fuse_req_t req, const void *arg, size_t argsize) +{ + return send_reply(req, 0, arg, argsize); +} + +int fuse_reply_err(fuse_req_t req, int err) +{ + return send_reply(req, -err, NULL, 0); +} + +void fuse_reply_none(fuse_req_t req) +{ + if (req->flags.is_uring) { + send_reply_uring(req, 0, NULL, 0); + return; + } + fuse_free_req(req); +} + +static unsigned long calc_timeout_sec(double t) +{ + if (t > (double) ULONG_MAX) + return ULONG_MAX; + else if (t < 0.0) + return 0; + else + return (unsigned long) t; +} + +static unsigned int calc_timeout_nsec(double t) +{ + double f = t - (double) calc_timeout_sec(t); + if (f < 0.0) + return 0; + else if (f >= 0.999999999) + return 999999999; + else + return (unsigned int) (f * 1.0e9); +} + +static void fill_entry(struct fuse_entry_out *arg, + const struct fuse_entry_param *e) +{ + arg->nodeid = e->ino; + arg->generation = e->generation; + arg->entry_valid = calc_timeout_sec(e->entry_timeout); + arg->entry_valid_nsec = calc_timeout_nsec(e->entry_timeout); + arg->attr_valid = calc_timeout_sec(e->attr_timeout); + arg->attr_valid_nsec = calc_timeout_nsec(e->attr_timeout); + convert_stat(&e->attr, &arg->attr); +} + +/* `buf` is allowed to be empty so that the proper size may be + allocated by the caller */ +size_t fuse_add_direntry_plus(fuse_req_t req, char *buf, size_t bufsize, + const char *name, + const struct fuse_entry_param *e, off_t off) +{ + (void)req; + size_t namelen; + size_t entlen; + size_t entlen_padded; + + namelen = strlen(name); + entlen = FUSE_NAME_OFFSET_DIRENTPLUS + namelen; + entlen_padded = FUSE_DIRENT_ALIGN(entlen); + if ((buf == NULL) || (entlen_padded > bufsize)) + return entlen_padded; + + struct fuse_direntplus *dp = (struct fuse_direntplus *) buf; + memset(&dp->entry_out, 0, sizeof(dp->entry_out)); + fill_entry(&dp->entry_out, e); + + struct fuse_dirent *dirent = &dp->dirent; + dirent->ino = e->attr.st_ino; + dirent->off = off; + dirent->namelen = namelen; + dirent->type = (e->attr.st_mode & S_IFMT) >> 12; + memcpy(dirent->name, name, namelen); + memset(dirent->name + namelen, 0, entlen_padded - entlen); + + return entlen_padded; +} + +static void fill_open(struct fuse_open_out *arg, + const struct fuse_file_info *f) +{ + arg->fh = f->fh; + if (f->backing_id > 0) { + arg->backing_id = f->backing_id; + arg->open_flags |= FOPEN_PASSTHROUGH; + } + if (f->direct_io) + arg->open_flags |= FOPEN_DIRECT_IO; + if (f->keep_cache) + arg->open_flags |= FOPEN_KEEP_CACHE; + if (f->cache_readdir) + arg->open_flags |= FOPEN_CACHE_DIR; + if (f->nonseekable) + arg->open_flags |= FOPEN_NONSEEKABLE; + if (f->noflush) + arg->open_flags |= FOPEN_NOFLUSH; + if (f->parallel_direct_writes) + arg->open_flags |= FOPEN_PARALLEL_DIRECT_WRITES; +} + +int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e) +{ + struct fuse_entry_out arg; + size_t size = req->se->conn.proto_minor < 9 ? + FUSE_COMPAT_ENTRY_OUT_SIZE : sizeof(arg); + + /* before ABI 7.4 e->ino == 0 was invalid, only ENOENT meant + negative entry */ + if (!e->ino && req->se->conn.proto_minor < 4) + return fuse_reply_err(req, ENOENT); + + memset(&arg, 0, sizeof(arg)); + fill_entry(&arg, e); + return send_reply_ok(req, &arg, size); +} + +int fuse_reply_create(fuse_req_t req, const struct fuse_entry_param *e, + const struct fuse_file_info *f) +{ + alignas(uint64_t) char buf[sizeof(struct fuse_entry_out) + sizeof(struct fuse_open_out)]; + size_t entrysize = req->se->conn.proto_minor < 9 ? + FUSE_COMPAT_ENTRY_OUT_SIZE : sizeof(struct fuse_entry_out); + struct fuse_entry_out *earg = (struct fuse_entry_out *) buf; + struct fuse_open_out *oarg = (struct fuse_open_out *) (buf + entrysize); + + memset(buf, 0, sizeof(buf)); + fill_entry(earg, e); + fill_open(oarg, f); + return send_reply_ok(req, buf, + entrysize + sizeof(struct fuse_open_out)); +} + +int fuse_reply_attr(fuse_req_t req, const struct stat *attr, + double attr_timeout) +{ + struct fuse_attr_out arg; + size_t size = req->se->conn.proto_minor < 9 ? + FUSE_COMPAT_ATTR_OUT_SIZE : sizeof(arg); + + memset(&arg, 0, sizeof(arg)); + arg.attr_valid = calc_timeout_sec(attr_timeout); + arg.attr_valid_nsec = calc_timeout_nsec(attr_timeout); + convert_stat(attr, &arg.attr); + + return send_reply_ok(req, &arg, size); +} + +int fuse_reply_readlink(fuse_req_t req, const char *linkname) +{ + return send_reply_ok(req, linkname, strlen(linkname)); +} + +int fuse_passthrough_open(fuse_req_t req, int fd) +{ + struct fuse_backing_map map = { .fd = fd }; + int ret; + + ret = ioctl(req->se->fd, FUSE_DEV_IOC_BACKING_OPEN, &map); + if (ret <= 0) { + fuse_log(FUSE_LOG_ERR, "fuse: passthrough_open: %s\n", strerror(errno)); + return 0; + } + + return ret; +} + +int fuse_passthrough_close(fuse_req_t req, int backing_id) +{ + int ret; + + ret = ioctl(req->se->fd, FUSE_DEV_IOC_BACKING_CLOSE, &backing_id); + if (ret < 0) + fuse_log(FUSE_LOG_ERR, "fuse: passthrough_close: %s\n", strerror(errno)); + + return ret; +} + +int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *f) +{ + struct fuse_open_out arg; + + memset(&arg, 0, sizeof(arg)); + fill_open(&arg, f); + return send_reply_ok(req, &arg, sizeof(arg)); +} + +static int do_fuse_reply_write(fuse_req_t req, size_t count) +{ + struct fuse_write_out arg; + + memset(&arg, 0, sizeof(arg)); + arg.size = count; + + return send_reply_ok(req, &arg, sizeof(arg)); +} + +static int do_fuse_reply_copy(fuse_req_t req, size_t count) +{ + struct fuse_copy_file_range_out arg; + + memset(&arg, 0, sizeof(arg)); + arg.bytes_copied = count; + + return send_reply_ok(req, &arg, sizeof(arg)); +} + +int fuse_reply_write(fuse_req_t req, size_t count) +{ + /* + * This function is also used by FUSE_COPY_FILE_RANGE and its 64-bit + * variant. + */ + if (req->flags.is_copy_file_range_64) + return do_fuse_reply_copy(req, count); + else + return do_fuse_reply_write(req, count); +} + +int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size) +{ + return send_reply_ok(req, buf, size); +} + +static int fuse_send_data_iov_fallback(struct fuse_session *se, + struct fuse_chan *ch, + struct iovec *iov, int iov_count, + struct fuse_bufvec *buf, + size_t len, fuse_req_t req) +{ + struct fuse_bufvec mem_buf = FUSE_BUFVEC_INIT(len); + void *mbuf; + int res; + + /* Optimize common case */ + if (buf->count == 1 && buf->idx == 0 && buf->off == 0 && + !(buf->buf[0].flags & FUSE_BUF_IS_FD)) { + /* FIXME: also avoid memory copy if there are multiple buffers + but none of them contain an fd */ + + iov[iov_count].iov_base = buf->buf[0].mem; + iov[iov_count].iov_len = len; + iov_count++; + return fuse_send_msg(se, ch, iov, iov_count, req); + } + + res = posix_memalign(&mbuf, pagesize, len); + if (res != 0) + return res; + + mem_buf.buf[0].mem = mbuf; + res = fuse_buf_copy(&mem_buf, buf, 0); + if (res < 0) { + free(mbuf); + return -res; + } + len = res; + + iov[iov_count].iov_base = mbuf; + iov[iov_count].iov_len = len; + iov_count++; + res = fuse_send_msg(se, ch, iov, iov_count, req); + free(mbuf); + + return res; +} + +struct fuse_ll_pipe { + size_t size; + int can_grow; + int pipe[2]; +}; + +static void fuse_ll_pipe_free(struct fuse_ll_pipe *llp) +{ + close(llp->pipe[0]); + close(llp->pipe[1]); + free(llp); +} + +#ifdef HAVE_SPLICE +#if !defined(HAVE_PIPE2) || !defined(O_CLOEXEC) +static int fuse_pipe(int fds[2]) +{ + int rv = pipe(fds); + + if (rv == -1) + return rv; + + if (fcntl(fds[0], F_SETFL, O_NONBLOCK) == -1 || + fcntl(fds[1], F_SETFL, O_NONBLOCK) == -1 || + fcntl(fds[0], F_SETFD, FD_CLOEXEC) == -1 || + fcntl(fds[1], F_SETFD, FD_CLOEXEC) == -1) { + close(fds[0]); + close(fds[1]); + rv = -1; + } + return rv; +} +#else +static int fuse_pipe(int fds[2]) +{ + return pipe2(fds, O_CLOEXEC | O_NONBLOCK); +} +#endif + +static struct fuse_ll_pipe *fuse_ll_get_pipe(struct fuse_session *se) +{ + struct fuse_ll_pipe *llp = pthread_getspecific(se->pipe_key); + if (llp == NULL) { + int res; + + llp = malloc(sizeof(struct fuse_ll_pipe)); + if (llp == NULL) + return NULL; + + res = fuse_pipe(llp->pipe); + if (res == -1) { + free(llp); + return NULL; + } + + /* + *the default size is 16 pages on linux + */ + llp->size = pagesize * 16; + llp->can_grow = 1; + + pthread_setspecific(se->pipe_key, llp); + } + + return llp; +} +#endif + +static void fuse_ll_clear_pipe(struct fuse_session *se) +{ + struct fuse_ll_pipe *llp = pthread_getspecific(se->pipe_key); + if (llp) { + pthread_setspecific(se->pipe_key, NULL); + fuse_ll_pipe_free(llp); + } +} + +#if defined(HAVE_SPLICE) && defined(HAVE_VMSPLICE) +static int read_back(int fd, char *buf, size_t len) +{ + int res; + + res = read(fd, buf, len); + if (res == -1) { + fuse_log(FUSE_LOG_ERR, + "fuse: internal error: failed to read back from pipe: %s\n", + strerror(errno)); + return -EIO; + } + if (res != len) { + fuse_log(FUSE_LOG_ERR, + "fuse: internal error: short read back from pipe: %i from %zd\n", + res, len); + return -EIO; + } + return 0; +} + +static int grow_pipe_to_max(int pipefd) +{ + int res; + long max; + long maxfd; + char buf[32]; + + maxfd = open("/proc/sys/fs/pipe-max-size", O_RDONLY | O_CLOEXEC); + if (maxfd < 0) + return -errno; + + res = read(maxfd, buf, sizeof(buf) - 1); + if (res < 0) { + int saved_errno; + + saved_errno = errno; + close(maxfd); + return -saved_errno; + } + close(maxfd); + buf[res] = '\0'; + + res = libfuse_strtol(buf, &max); + if (res) + return res; + res = fcntl(pipefd, F_SETPIPE_SZ, max); + if (res < 0) + return -errno; + return max; +} + +static int fuse_send_data_iov(struct fuse_session *se, struct fuse_chan *ch, + struct iovec *iov, int iov_count, + struct fuse_bufvec *buf, unsigned int flags, + fuse_req_t req) +{ + int res; + size_t len = fuse_buf_size(buf); + struct fuse_out_header *out = iov[0].iov_base; + struct fuse_ll_pipe *llp; + int splice_flags; + size_t pipesize; + size_t total_buf_size; + size_t idx; + size_t headerlen; + struct fuse_bufvec pipe_buf = FUSE_BUFVEC_INIT(len); + + if (se->broken_splice_nonblock) + goto fallback; + + if (flags & FUSE_BUF_NO_SPLICE) + goto fallback; + + total_buf_size = 0; + for (idx = buf->idx; idx < buf->count; idx++) { + total_buf_size += buf->buf[idx].size; + if (idx == buf->idx) + total_buf_size -= buf->off; + } + if (total_buf_size < 2 * pagesize) + goto fallback; + + if (se->conn.proto_minor < 14 || + !(se->conn.want_ext & FUSE_CAP_SPLICE_WRITE)) + goto fallback; + + llp = fuse_ll_get_pipe(se); + if (llp == NULL) + goto fallback; + + + headerlen = iov_length(iov, iov_count); + + out->len = headerlen + len; + + /* + * Heuristic for the required pipe size, does not work if the + * source contains less than page size fragments + */ + pipesize = pagesize * (iov_count + buf->count + 1) + out->len; + + if (llp->size < pipesize) { + if (llp->can_grow) { + res = fcntl(llp->pipe[0], F_SETPIPE_SZ, pipesize); + if (res == -1) { + res = grow_pipe_to_max(llp->pipe[0]); + if (res > 0) + llp->size = res; + llp->can_grow = 0; + goto fallback; + } + llp->size = res; + } + if (llp->size < pipesize) + goto fallback; + } + + + res = vmsplice(llp->pipe[1], iov, iov_count, SPLICE_F_NONBLOCK); + if (res == -1) + goto fallback; + + if (res != headerlen) { + res = -EIO; + fuse_log(FUSE_LOG_ERR, "fuse: short vmsplice to pipe: %u/%zu\n", res, + headerlen); + goto clear_pipe; + } + + pipe_buf.buf[0].flags = FUSE_BUF_IS_FD; + pipe_buf.buf[0].fd = llp->pipe[1]; + + res = fuse_buf_copy(&pipe_buf, buf, + FUSE_BUF_FORCE_SPLICE | FUSE_BUF_SPLICE_NONBLOCK); + if (res < 0) { + if (res == -EAGAIN || res == -EINVAL) { + /* + * Should only get EAGAIN on kernels with + * broken SPLICE_F_NONBLOCK support (<= + * 2.6.35) where this error or a short read is + * returned even if the pipe itself is not + * full + * + * EINVAL might mean that splice can't handle + * this combination of input and output. + */ + if (res == -EAGAIN) + se->broken_splice_nonblock = 1; + + pthread_setspecific(se->pipe_key, NULL); + fuse_ll_pipe_free(llp); + goto fallback; + } + res = -res; + goto clear_pipe; + } + + if (res != 0 && res < len) { + struct fuse_bufvec mem_buf = FUSE_BUFVEC_INIT(len); + void *mbuf; + size_t now_len = res; + /* + * For regular files a short count is either + * 1) due to EOF, or + * 2) because of broken SPLICE_F_NONBLOCK (see above) + * + * For other inputs it's possible that we overflowed + * the pipe because of small buffer fragments. + */ + + res = posix_memalign(&mbuf, pagesize, len); + if (res != 0) + goto clear_pipe; + + mem_buf.buf[0].mem = mbuf; + mem_buf.off = now_len; + res = fuse_buf_copy(&mem_buf, buf, 0); + if (res > 0) { + char *tmpbuf; + size_t extra_len = res; + /* + * Trickiest case: got more data. Need to get + * back the data from the pipe and then fall + * back to regular write. + */ + tmpbuf = malloc(headerlen); + if (tmpbuf == NULL) { + free(mbuf); + res = ENOMEM; + goto clear_pipe; + } + res = read_back(llp->pipe[0], tmpbuf, headerlen); + free(tmpbuf); + if (res != 0) { + free(mbuf); + goto clear_pipe; + } + res = read_back(llp->pipe[0], mbuf, now_len); + if (res != 0) { + free(mbuf); + goto clear_pipe; + } + len = now_len + extra_len; + iov[iov_count].iov_base = mbuf; + iov[iov_count].iov_len = len; + iov_count++; + res = fuse_send_msg(se, ch, iov, iov_count, req); + free(mbuf); + return res; + } + free(mbuf); + res = now_len; + } + len = res; + out->len = headerlen + len; + + if (se->debug) { + fuse_log(FUSE_LOG_DEBUG, + " unique: %llu, success, outsize: %i (splice)\n", + (unsigned long long) out->unique, out->len); + } + + splice_flags = 0; + if ((flags & FUSE_BUF_SPLICE_MOVE) && + (se->conn.want_ext & FUSE_CAP_SPLICE_MOVE)) + splice_flags |= SPLICE_F_MOVE; + + if (se->io != NULL && se->io->splice_send != NULL) { + res = se->io->splice_send(llp->pipe[0], NULL, + ch ? ch->fd : se->fd, NULL, out->len, + splice_flags, se->userdata); + } else { + res = splice(llp->pipe[0], NULL, ch ? ch->fd : se->fd, NULL, + out->len, splice_flags); + } + if (res == -1) { + res = -errno; + perror("fuse: splice from pipe"); + goto clear_pipe; + } + if (res != out->len) { + res = -EIO; + fuse_log(FUSE_LOG_ERR, "fuse: short splice from pipe: %u/%u\n", + res, out->len); + goto clear_pipe; + } + return 0; + +clear_pipe: + fuse_ll_clear_pipe(se); + return res; + +fallback: + return fuse_send_data_iov_fallback(se, ch, iov, iov_count, buf, len, req); +} +#else +static int fuse_send_data_iov(struct fuse_session *se, struct fuse_chan *ch, + struct iovec *iov, int iov_count, + struct fuse_bufvec *req_data, unsigned int flags, + fuse_req_t req) +{ + size_t len = fuse_buf_size(req_data); + (void) flags; + + return fuse_send_data_iov_fallback(se, ch, iov, iov_count, req_data, len, req); +} +#endif + +int fuse_reply_data(fuse_req_t req, struct fuse_bufvec *bufv, + enum fuse_buf_copy_flags flags) +{ + struct iovec iov[2]; + struct fuse_out_header out; + int res; + + if (req->flags.is_uring) + return fuse_reply_data_uring(req, bufv, flags); + + iov[0].iov_base = &out; + iov[0].iov_len = sizeof(struct fuse_out_header); + + out.unique = req->unique; + out.error = 0; + + res = fuse_send_data_iov(req->se, req->ch, iov, 1, bufv, flags, req); + if (res <= 0) { + fuse_free_req(req); + return res; + } else { + return fuse_reply_err(req, res); + } +} + +int fuse_reply_statfs(fuse_req_t req, const struct statvfs *stbuf) +{ + struct fuse_statfs_out arg; + size_t size = req->se->conn.proto_minor < 4 ? + FUSE_COMPAT_STATFS_SIZE : sizeof(arg); + + memset(&arg, 0, sizeof(arg)); + convert_statfs(stbuf, &arg.st); + + return send_reply_ok(req, &arg, size); +} + +int fuse_reply_xattr(fuse_req_t req, size_t count) +{ + struct fuse_getxattr_out arg; + + memset(&arg, 0, sizeof(arg)); + arg.size = count; + + return send_reply_ok(req, &arg, sizeof(arg)); +} + +int fuse_reply_lock(fuse_req_t req, const struct flock *lock) +{ + struct fuse_lk_out arg; + + memset(&arg, 0, sizeof(arg)); + arg.lk.type = lock->l_type; + if (lock->l_type != F_UNLCK) { + arg.lk.start = lock->l_start; + if (lock->l_len == 0) + arg.lk.end = OFFSET_MAX; + else + arg.lk.end = lock->l_start + lock->l_len - 1; + } + arg.lk.pid = lock->l_pid; + return send_reply_ok(req, &arg, sizeof(arg)); +} + +int fuse_reply_bmap(fuse_req_t req, uint64_t idx) +{ + struct fuse_bmap_out arg; + + memset(&arg, 0, sizeof(arg)); + arg.block = idx; + + return send_reply_ok(req, &arg, sizeof(arg)); +} + +static struct fuse_ioctl_iovec *fuse_ioctl_iovec_copy(const struct iovec *iov, + size_t count) +{ + struct fuse_ioctl_iovec *fiov; + size_t i; + + fiov = malloc(sizeof(fiov[0]) * count); + if (!fiov) + return NULL; + + for (i = 0; i < count; i++) { + fiov[i].base = (uintptr_t) iov[i].iov_base; + fiov[i].len = iov[i].iov_len; + } + + return fiov; +} + +int fuse_reply_ioctl_retry(fuse_req_t req, + const struct iovec *in_iov, size_t in_count, + const struct iovec *out_iov, size_t out_count) +{ + struct fuse_ioctl_out arg; + struct fuse_ioctl_iovec *in_fiov = NULL; + struct fuse_ioctl_iovec *out_fiov = NULL; + struct iovec iov[4]; + size_t count = 1; + int res; + + memset(&arg, 0, sizeof(arg)); + arg.flags |= FUSE_IOCTL_RETRY; + arg.in_iovs = in_count; + arg.out_iovs = out_count; + iov[count].iov_base = &arg; + iov[count].iov_len = sizeof(arg); + count++; + + if (req->se->conn.proto_minor < 16) { + if (in_count) { + iov[count].iov_base = (void *)in_iov; + iov[count].iov_len = sizeof(in_iov[0]) * in_count; + count++; + } + + if (out_count) { + iov[count].iov_base = (void *)out_iov; + iov[count].iov_len = sizeof(out_iov[0]) * out_count; + count++; + } + } else { + /* Can't handle non-compat 64bit ioctls on 32bit */ + if (sizeof(void *) == 4 && req->flags.ioctl_64bit) { + res = fuse_reply_err(req, EINVAL); + goto out; + } + + if (in_count) { + in_fiov = fuse_ioctl_iovec_copy(in_iov, in_count); + if (!in_fiov) + goto enomem; + + iov[count].iov_base = (void *)in_fiov; + iov[count].iov_len = sizeof(in_fiov[0]) * in_count; + count++; + } + if (out_count) { + out_fiov = fuse_ioctl_iovec_copy(out_iov, out_count); + if (!out_fiov) + goto enomem; + + iov[count].iov_base = (void *)out_fiov; + iov[count].iov_len = sizeof(out_fiov[0]) * out_count; + count++; + } + } + + res = send_reply_iov(req, 0, iov, count); +out: + free(in_fiov); + free(out_fiov); + + return res; + +enomem: + res = fuse_reply_err(req, ENOMEM); + goto out; +} + +int fuse_reply_ioctl(fuse_req_t req, int result, const void *buf, size_t size) +{ + struct fuse_ioctl_out arg; + struct iovec iov[3]; + size_t count = 1; + + memset(&arg, 0, sizeof(arg)); + arg.result = result; + iov[count].iov_base = &arg; + iov[count].iov_len = sizeof(arg); + count++; + + if (size) { + iov[count].iov_base = (char *) buf; + iov[count].iov_len = size; + count++; + } + + return send_reply_iov(req, 0, iov, count); +} + +int fuse_reply_ioctl_iov(fuse_req_t req, int result, const struct iovec *iov, + int count) +{ + struct iovec *padded_iov; + struct fuse_ioctl_out arg; + int res; + + padded_iov = malloc((count + 2) * sizeof(struct iovec)); + if (padded_iov == NULL) + return fuse_reply_err(req, ENOMEM); + + memset(&arg, 0, sizeof(arg)); + arg.result = result; + padded_iov[1].iov_base = &arg; + padded_iov[1].iov_len = sizeof(arg); + + memcpy(&padded_iov[2], iov, count * sizeof(struct iovec)); + + res = send_reply_iov(req, 0, padded_iov, count + 2); + free(padded_iov); + + return res; +} + +int fuse_reply_poll(fuse_req_t req, unsigned revents) +{ + struct fuse_poll_out arg; + + memset(&arg, 0, sizeof(arg)); + arg.revents = revents; + + return send_reply_ok(req, &arg, sizeof(arg)); +} + +int fuse_reply_lseek(fuse_req_t req, off_t off) +{ + struct fuse_lseek_out arg; + + memset(&arg, 0, sizeof(arg)); + arg.offset = off; + + return send_reply_ok(req, &arg, sizeof(arg)); +} + +#ifdef HAVE_STATX +int fuse_reply_statx(fuse_req_t req, int flags, struct statx *statx, + double attr_timeout) +{ + struct fuse_statx_out arg; + + memset(&arg, 0, sizeof(arg)); + arg.flags = flags; + arg.attr_valid = calc_timeout_sec(attr_timeout); + arg.attr_valid_nsec = calc_timeout_nsec(attr_timeout); + memcpy(&arg.stat, statx, sizeof(arg.stat)); + + return send_reply_ok(req, &arg, sizeof(arg)); +} +#else +int fuse_reply_statx(fuse_req_t req, int flags, struct statx *statx, + double attr_timeout) +{ + (void)req; + (void)flags; + (void)statx; + (void)attr_timeout; + + return -ENOSYS; +} +#endif + +static void _do_lookup(fuse_req_t req, const fuse_ino_t nodeid, + const void *op_in, const void *in_payload) +{ + (void)op_in; + + char *name = (char *)in_payload; + + if (req->se->op.lookup) + req->se->op.lookup(req, nodeid, name); + else + fuse_reply_err(req, ENOSYS); +} + +static void do_lookup(fuse_req_t req, const fuse_ino_t nodeid, + const void *inarg) +{ + _do_lookup(req, nodeid, NULL, inarg); +} + +static void _do_forget(fuse_req_t req, const fuse_ino_t nodeid, + const void *op_in, const void *in_payload) +{ + (void)in_payload; + + struct fuse_forget_in *arg = (struct fuse_forget_in *)op_in; + + if (req->se->op.forget) + req->se->op.forget(req, nodeid, arg->nlookup); + else + fuse_reply_none(req); +} + +static void do_forget(fuse_req_t req, const fuse_ino_t nodeid, + const void *inarg) +{ + _do_forget(req, nodeid, inarg, NULL); +} + +static void _do_batch_forget(fuse_req_t req, const fuse_ino_t nodeid, + const void *op_in, const void *in_payload) +{ + (void)nodeid; + unsigned int i; + + const struct fuse_batch_forget_in *arg = op_in; + const struct fuse_forget_one *forgets = in_payload; + + if (req->se->op.forget_multi) { + req->se->op.forget_multi(req, arg->count, + (struct fuse_forget_data *)in_payload); + } else if (req->se->op.forget) { + for (i = 0; i < arg->count; i++) { + const struct fuse_forget_one *forget = &forgets[i]; + struct fuse_req *dummy_req; + + dummy_req = fuse_ll_alloc_req(req->se); + if (dummy_req == NULL) + break; + + dummy_req->unique = req->unique; + dummy_req->ctx = req->ctx; + dummy_req->ch = NULL; + + req->se->op.forget(dummy_req, forget->nodeid, + forget->nlookup); + } + fuse_reply_none(req); + } else { + fuse_reply_none(req); + } +} + +static void do_batch_forget(fuse_req_t req, const fuse_ino_t nodeid, + const void *inarg) +{ + struct fuse_batch_forget_in *arg = (void *)inarg; + struct fuse_forget_one *param = (void *)PARAM(arg); + + _do_batch_forget(req, nodeid, inarg, param); +} + +static void _do_getattr(fuse_req_t req, const fuse_ino_t nodeid, + const void *op_in, const void *in_payload) +{ + struct fuse_getattr_in *arg = (struct fuse_getattr_in *)op_in; + (void)in_payload; + + struct fuse_file_info *fip = NULL; + struct fuse_file_info fi; + + if (req->se->conn.proto_minor >= 9) { + if (arg->getattr_flags & FUSE_GETATTR_FH) { + memset(&fi, 0, sizeof(fi)); + fi.fh = arg->fh; + fip = &fi; + } + } + + if (req->se->op.getattr) + req->se->op.getattr(req, nodeid, fip); + else + fuse_reply_err(req, ENOSYS); +} + +static void do_getattr(fuse_req_t req, const fuse_ino_t nodeid, + const void *inarg) +{ + _do_getattr(req, nodeid, inarg, NULL); +} + +static void _do_setattr(fuse_req_t req, const fuse_ino_t nodeid, + const void *op_in, const void *in_payload) +{ + (void)in_payload; + const struct fuse_setattr_in *arg = op_in; + uint32_t valid = arg->valid; + + if (req->se->op.setattr) { + struct fuse_file_info *fi = NULL; + struct fuse_file_info fi_store; + struct stat stbuf; + memset(&stbuf, 0, sizeof(stbuf)); + convert_attr(arg, &stbuf); + if (arg->valid & FATTR_FH) { + valid &= ~FATTR_FH; + memset(&fi_store, 0, sizeof(fi_store)); + fi = &fi_store; + fi->fh = arg->fh; + } + valid &= FUSE_SET_ATTR_MODE | FUSE_SET_ATTR_UID | + FUSE_SET_ATTR_GID | FUSE_SET_ATTR_SIZE | + FUSE_SET_ATTR_ATIME | FUSE_SET_ATTR_MTIME | + FUSE_SET_ATTR_KILL_SUID | FUSE_SET_ATTR_KILL_SGID | + FUSE_SET_ATTR_ATIME_NOW | FUSE_SET_ATTR_MTIME_NOW | + FUSE_SET_ATTR_CTIME; + + req->se->op.setattr(req, nodeid, &stbuf, valid, fi); + } else + fuse_reply_err(req, ENOSYS); +} + +static void do_setattr(fuse_req_t req, const fuse_ino_t nodeid, + const void *inarg) +{ + _do_setattr(req, nodeid, inarg, NULL); +} + +static void _do_access(fuse_req_t req, const fuse_ino_t nodeid, + const void *op_in, const void *in_payload) +{ + (void)in_payload; + const struct fuse_access_in *arg = op_in; + + if (req->se->op.access) + req->se->op.access(req, nodeid, arg->mask); + else + fuse_reply_err(req, ENOSYS); +} + +static void do_access(fuse_req_t req, const fuse_ino_t nodeid, + const void *inarg) +{ + _do_access(req, nodeid, inarg, NULL); +} + +static void _do_readlink(fuse_req_t req, const fuse_ino_t nodeid, + const void *op_in, const void *in_payload) +{ + (void)op_in; + (void)in_payload; + + if (req->se->op.readlink) + req->se->op.readlink(req, nodeid); + else + fuse_reply_err(req, ENOSYS); +} + +static void do_readlink(fuse_req_t req, const fuse_ino_t nodeid, + const void *inarg) +{ + _do_readlink(req, nodeid, inarg, NULL); +} + +static void _do_mknod(fuse_req_t req, const fuse_ino_t nodeid, + const void *op_in, const void *in_payload) +{ + const struct fuse_mknod_in *arg = (struct fuse_mknod_in *)op_in; + const char *name = in_payload; + + if (req->se->conn.proto_minor >= 12) + req->ctx.umask = arg->umask; + + if (req->se->op.mknod) + req->se->op.mknod(req, nodeid, name, arg->mode, arg->rdev); + else + fuse_reply_err(req, ENOSYS); +} + +static void do_mknod(fuse_req_t req, const fuse_ino_t nodeid, const void *inarg) +{ + struct fuse_mknod_in *arg = (struct fuse_mknod_in *)inarg; + char *name = PARAM(arg); + + if (req->se->conn.proto_minor < 12) + name = (char *)inarg + FUSE_COMPAT_MKNOD_IN_SIZE; + + _do_mknod(req, nodeid, inarg, name); +} + +static void _do_mkdir(fuse_req_t req, const fuse_ino_t nodeid, + const void *op_in, const void *in_payload) +{ + const char *name = in_payload; + const struct fuse_mkdir_in *arg = op_in; + + if (req->se->conn.proto_minor >= 12) + req->ctx.umask = arg->umask; + + if (req->se->op.mkdir) + req->se->op.mkdir(req, nodeid, name, arg->mode); + else + fuse_reply_err(req, ENOSYS); +} + +static void do_mkdir(fuse_req_t req, const fuse_ino_t nodeid, const void *inarg) +{ + const struct fuse_mkdir_in *arg = inarg; + const char *name = PARAM(arg); + + _do_mkdir(req, nodeid, inarg, name); +} + +static void _do_unlink(fuse_req_t req, const fuse_ino_t nodeid, + const void *op_in, const void *in_payload) +{ + (void)op_in; + const char *name = in_payload; + + if (req->se->op.unlink) + req->se->op.unlink(req, nodeid, name); + else + fuse_reply_err(req, ENOSYS); +} + +static void do_unlink(fuse_req_t req, const fuse_ino_t nodeid, + const void *inarg) +{ + _do_unlink(req, nodeid, NULL, inarg); +} + +static void _do_rmdir(fuse_req_t req, const fuse_ino_t nodeid, + const void *op_in, const void *in_payload) +{ + (void)op_in; + const char *name = in_payload; + + if (req->se->op.rmdir) + req->se->op.rmdir(req, nodeid, name); + else + fuse_reply_err(req, ENOSYS); +} + +static void do_rmdir(fuse_req_t req, const fuse_ino_t nodeid, const void *inarg) +{ + _do_rmdir(req, nodeid, NULL, inarg); +} + +static void _do_symlink(fuse_req_t req, const fuse_ino_t nodeid, + const void *op_in, const void *in_payload) +{ + (void)op_in; + const char *name = (char *)in_payload; + const char *linkname = name + strlen(name) + 1; + + if (req->se->op.symlink) + req->se->op.symlink(req, linkname, nodeid, name); + else + fuse_reply_err(req, ENOSYS); +} + +static void do_symlink(fuse_req_t req, const fuse_ino_t nodeid, + const void *inarg) +{ + _do_symlink(req, nodeid, NULL, inarg); +} + +static void _do_rename(fuse_req_t req, const fuse_ino_t nodeid, + const void *op_in, const void *in_payload) +{ + const struct fuse_rename_in *arg = (struct fuse_rename_in *)op_in; + const char *oldname = in_payload; + const char *newname = oldname + strlen(oldname) + 1; + + if (req->se->op.rename) + req->se->op.rename(req, nodeid, oldname, arg->newdir, newname, + 0); + else + fuse_reply_err(req, ENOSYS); +} + +static void do_rename(fuse_req_t req, const fuse_ino_t nodeid, + const void *inarg) +{ + const struct fuse_rename_in *arg = inarg; + const void *payload = PARAM(arg); + + _do_rename(req, nodeid, arg, payload); +} + +static void _do_rename2(fuse_req_t req, const fuse_ino_t nodeid, + const void *op_in, const void *in_payload) +{ + const struct fuse_rename2_in *arg = op_in; + const char *oldname = in_payload; + const char *newname = oldname + strlen(oldname) + 1; + + if (req->se->op.rename) + req->se->op.rename(req, nodeid, oldname, arg->newdir, newname, + arg->flags); + else + fuse_reply_err(req, ENOSYS); +} + +static void do_rename2(fuse_req_t req, const fuse_ino_t nodeid, + const void *inarg) +{ + const struct fuse_rename2_in *arg = inarg; + const void *payload = PARAM(arg); + + _do_rename2(req, nodeid, arg, payload); +} + +static void _do_tmpfile(fuse_req_t req, fuse_ino_t nodeid, const void *op_in, + const void *in_payload) +{ + (void)in_payload; + const struct fuse_create_in *arg = op_in; + + if (req->se->op.tmpfile) { + struct fuse_file_info fi; + + memset(&fi, 0, sizeof(fi)); + fi.flags = arg->flags; + + if (req->se->conn.proto_minor >= 12) + req->ctx.umask = arg->umask; + + req->se->op.tmpfile(req, nodeid, arg->mode, &fi); + } else + fuse_reply_err(req, ENOSYS); +} + +static void do_tmpfile(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) +{ + struct fuse_create_in *arg = (struct fuse_create_in *) inarg; + + _do_tmpfile(req, nodeid, arg, NULL); +} + +static void _do_link(fuse_req_t req, const fuse_ino_t nodeid, const void *op_in, + const void *in_payload) +{ + struct fuse_link_in *arg = (struct fuse_link_in *)op_in; + + if (req->se->op.link) + req->se->op.link(req, arg->oldnodeid, nodeid, in_payload); + else + fuse_reply_err(req, ENOSYS); +} + +static void do_link(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) +{ + const struct fuse_link_in *arg = inarg; + const void *name = PARAM(arg); + + _do_link(req, nodeid, inarg, name); +} + +static void _do_create(fuse_req_t req, const fuse_ino_t nodeid, + const void *op_in, const void *in_payload) +{ + const struct fuse_create_in *arg = op_in; + const char *name = in_payload; + + if (req->se->op.create) { + struct fuse_file_info fi; + + memset(&fi, 0, sizeof(fi)); + fi.flags = arg->flags; + + if (req->se->conn.proto_minor >= 12) + req->ctx.umask = arg->umask; + + if (req->se->conn.want_ext & FUSE_CAP_HANDLE_KILLPRIV_V2) + fi.kill_suidgid = + (arg->open_flags & FUSE_OPEN_KILL_SUIDGID) != 0; + + req->se->op.create(req, nodeid, name, arg->mode, &fi); + } else { + fuse_reply_err(req, ENOSYS); + } +} + +static void do_create(fuse_req_t req, const fuse_ino_t nodeid, + const void *inarg) +{ + const struct fuse_create_in *arg = (struct fuse_create_in *)inarg; + void *payload = PARAM(arg); + + if (req->se->conn.proto_minor < 12) + payload = (char *)inarg + sizeof(struct fuse_open_in); + + _do_create(req, nodeid, arg, payload); +} + +static void _do_open(fuse_req_t req, const fuse_ino_t nodeid, const void *op_in, + const void *in_payload) +{ + (void)in_payload; + struct fuse_open_in *arg = (struct fuse_open_in *)op_in; + struct fuse_file_info fi; + + memset(&fi, 0, sizeof(fi)); + fi.flags = arg->flags; + + if (req->se->conn.want_ext & FUSE_CAP_HANDLE_KILLPRIV_V2) + fi.kill_suidgid = + (arg->open_flags & FUSE_OPEN_KILL_SUIDGID) != 0; + + if (req->se->op.open) + req->se->op.open(req, nodeid, &fi); + else if (req->se->conn.want_ext & FUSE_CAP_NO_OPEN_SUPPORT) + fuse_reply_err(req, ENOSYS); + else + fuse_reply_open(req, &fi); +} + +static void do_open(fuse_req_t req, const fuse_ino_t nodeid, const void *inarg) +{ + _do_open(req, nodeid, inarg, NULL); +} + +static void _do_read(fuse_req_t req, const fuse_ino_t nodeid, const void *op_in, + const void *in_payload) +{ + (void)in_payload; + struct fuse_read_in *arg = (struct fuse_read_in *)op_in; + + if (req->se->op.read) { + struct fuse_file_info fi; + + memset(&fi, 0, sizeof(fi)); + fi.fh = arg->fh; + if (req->se->conn.proto_minor >= 9) { + fi.lock_owner = arg->lock_owner; + fi.flags = arg->flags; + } + req->se->op.read(req, nodeid, arg->size, arg->offset, &fi); + } else + fuse_reply_err(req, ENOSYS); +} + +static void do_read(fuse_req_t req, const fuse_ino_t nodeid, const void *inarg) +{ + _do_read(req, nodeid, inarg, NULL); +} + +static void _do_write(fuse_req_t req, const fuse_ino_t nodeid, + const void *op_in, const void *in_payload) +{ + struct fuse_write_in *arg = (struct fuse_write_in *)op_in; + const char *buf = in_payload; + struct fuse_file_info fi; + + memset(&fi, 0, sizeof(fi)); + fi.fh = arg->fh; + fi.writepage = (arg->write_flags & FUSE_WRITE_CACHE) != 0; + + if (req->se->conn.want_ext & FUSE_CAP_HANDLE_KILLPRIV_V2) + fi.kill_suidgid = + (arg->write_flags & FUSE_WRITE_KILL_SUIDGID) != 0; + + if (req->se->conn.proto_minor >= 9) { + fi.lock_owner = arg->lock_owner; + fi.flags = arg->flags; + } + + if (req->se->op.write) + req->se->op.write(req, nodeid, buf, arg->size, arg->offset, + &fi); + else + fuse_reply_err(req, ENOSYS); +} + +static void do_write(fuse_req_t req, const fuse_ino_t nodeid, const void *inarg) +{ + struct fuse_write_in *arg = (struct fuse_write_in *)inarg; + const void *payload; + + if (req->se->conn.proto_minor < 9) + payload = ((char *)arg) + FUSE_COMPAT_WRITE_IN_SIZE; + else + payload = PARAM(arg); + + _do_write(req, nodeid, arg, payload); +} + +static void _do_write_buf(fuse_req_t req, const fuse_ino_t nodeid, + const void *op_in, struct fuse_bufvec *bufv) +{ + struct fuse_session *se = req->se; + struct fuse_write_in *arg = (struct fuse_write_in *)op_in; + struct fuse_file_info fi; + + memset(&fi, 0, sizeof(fi)); + fi.fh = arg->fh; + fi.writepage = arg->write_flags & FUSE_WRITE_CACHE; + + if (se->conn.want_ext & FUSE_CAP_HANDLE_KILLPRIV_V2) + fi.kill_suidgid = + (arg->write_flags & FUSE_WRITE_KILL_SUIDGID) != 0; + + if (se->conn.proto_minor >= 9) { + fi.lock_owner = arg->lock_owner; + fi.flags = arg->flags; + } + + se->op.write_buf(req, nodeid, bufv, arg->offset, &fi); +} + +static void do_write_buf(fuse_req_t req, const fuse_ino_t nodeid, + const void *inarg, const struct fuse_buf *ibuf) +{ + struct fuse_session *se = req->se; + struct fuse_bufvec bufv = { + .buf[0] = *ibuf, + .count = 1, + }; + struct fuse_write_in *arg = (struct fuse_write_in *)inarg; + + if (se->conn.proto_minor < 9) { + bufv.buf[0].mem = ((char *)arg) + FUSE_COMPAT_WRITE_IN_SIZE; + bufv.buf[0].size -= sizeof(struct fuse_in_header) + + FUSE_COMPAT_WRITE_IN_SIZE; + assert(!(bufv.buf[0].flags & FUSE_BUF_IS_FD)); + } else { + if (!(bufv.buf[0].flags & FUSE_BUF_IS_FD)) + bufv.buf[0].mem = PARAM(arg); + + bufv.buf[0].size -= sizeof(struct fuse_in_header) + + sizeof(struct fuse_write_in); + } + if (bufv.buf[0].size < arg->size) { + fuse_log(FUSE_LOG_ERR, + "fuse: %s: buffer size too small\n", __func__); + fuse_reply_err(req, EIO); + goto out; + } + bufv.buf[0].size = arg->size; + + _do_write_buf(req, nodeid, inarg, &bufv); + +out: + /* Need to reset the pipe if ->write_buf() didn't consume all data */ + if ((ibuf->flags & FUSE_BUF_IS_FD) && bufv.idx < bufv.count) + fuse_ll_clear_pipe(se); +} + +static void _do_flush(fuse_req_t req, const fuse_ino_t nodeid, + const void *op_in, const void *in_payload) +{ + (void)in_payload; + struct fuse_flush_in *arg = (struct fuse_flush_in *)op_in; + struct fuse_file_info fi; + + memset(&fi, 0, sizeof(fi)); + fi.fh = arg->fh; + fi.flush = 1; + if (req->se->conn.proto_minor >= 7) + fi.lock_owner = arg->lock_owner; + + if (req->se->op.flush) + req->se->op.flush(req, nodeid, &fi); + else + fuse_reply_err(req, ENOSYS); +} + +static void do_flush(fuse_req_t req, const fuse_ino_t nodeid, const void *inarg) +{ + _do_flush(req, nodeid, inarg, NULL); +} + +static void _do_release(fuse_req_t req, const fuse_ino_t nodeid, + const void *op_in, const void *in_payload) +{ + (void)in_payload; + const struct fuse_release_in *arg = op_in; + struct fuse_file_info fi; + + memset(&fi, 0, sizeof(fi)); + fi.flags = arg->flags; + fi.fh = arg->fh; + if (req->se->conn.proto_minor >= 8) { + fi.flush = (arg->release_flags & FUSE_RELEASE_FLUSH) ? 1 : 0; + fi.lock_owner = arg->lock_owner; + } + if (arg->release_flags & FUSE_RELEASE_FLOCK_UNLOCK) { + fi.flock_release = 1; + fi.lock_owner = arg->lock_owner; + } + + if (req->se->op.release) + req->se->op.release(req, nodeid, &fi); + else + fuse_reply_err(req, 0); +} + +static void do_release(fuse_req_t req, const fuse_ino_t nodeid, + const void *inarg) +{ + _do_release(req, nodeid, inarg, NULL); +} + +static void _do_fsync(fuse_req_t req, const fuse_ino_t nodeid, + const void *op_in, const void *in_payload) +{ + (void)in_payload; + const struct fuse_fsync_in *arg = op_in; + struct fuse_file_info fi; + int datasync = arg->fsync_flags & 1; + + memset(&fi, 0, sizeof(fi)); + fi.fh = arg->fh; + + if (req->se->op.fsync) + req->se->op.fsync(req, nodeid, datasync, &fi); + else + fuse_reply_err(req, ENOSYS); +} + +static void do_fsync(fuse_req_t req, const fuse_ino_t nodeid, const void *inarg) +{ + _do_fsync(req, nodeid, inarg, NULL); +} + +static void _do_opendir(fuse_req_t req, const fuse_ino_t nodeid, + const void *op_in, const void *in_payload) +{ + (void)in_payload; + const struct fuse_open_in *arg = op_in; + struct fuse_file_info fi; + + memset(&fi, 0, sizeof(fi)); + fi.flags = arg->flags; + /* XXX: fuse_open_in::open_flags */ + + if (req->se->op.opendir) + req->se->op.opendir(req, nodeid, &fi); + else if (req->se->conn.want_ext & FUSE_CAP_NO_OPENDIR_SUPPORT) + fuse_reply_err(req, ENOSYS); + else + fuse_reply_open(req, &fi); +} + +static void do_opendir(fuse_req_t req, const fuse_ino_t nodeid, + const void *inarg) +{ + _do_opendir(req, nodeid, inarg, NULL); +} + +static void _do_readdir(fuse_req_t req, const fuse_ino_t nodeid, + const void *op_in, const void *in_payload) +{ + (void)in_payload; + struct fuse_read_in *arg = (struct fuse_read_in *)op_in; + struct fuse_file_info fi; + + memset(&fi, 0, sizeof(fi)); + fi.fh = arg->fh; + + if (req->se->op.readdir) + req->se->op.readdir(req, nodeid, arg->size, arg->offset, &fi); + else + fuse_reply_err(req, ENOSYS); +} + +static void do_readdir(fuse_req_t req, const fuse_ino_t nodeid, + const void *inarg) +{ + _do_readdir(req, nodeid, inarg, NULL); +} + +static void _do_readdirplus(fuse_req_t req, const fuse_ino_t nodeid, + const void *op_in, const void *in_payload) +{ + (void)in_payload; + struct fuse_read_in *arg = (struct fuse_read_in *)op_in; + struct fuse_file_info fi; + + memset(&fi, 0, sizeof(fi)); + fi.fh = arg->fh; + + if (req->se->op.readdirplus) + req->se->op.readdirplus(req, nodeid, arg->size, arg->offset, &fi); + else + fuse_reply_err(req, ENOSYS); +} + +static void do_readdirplus(fuse_req_t req, const fuse_ino_t nodeid, + const void *inarg) +{ + _do_readdirplus(req, nodeid, inarg, NULL); +} + +static void _do_releasedir(fuse_req_t req, const fuse_ino_t nodeid, + const void *op_in, const void *in_payload) +{ + (void)in_payload; + struct fuse_release_in *arg = (struct fuse_release_in *)op_in; + struct fuse_file_info fi; + + memset(&fi, 0, sizeof(fi)); + fi.flags = arg->flags; + fi.fh = arg->fh; + + if (req->se->op.releasedir) + req->se->op.releasedir(req, nodeid, &fi); + else + fuse_reply_err(req, 0); +} + +static void do_releasedir(fuse_req_t req, const fuse_ino_t nodeid, + const void *inarg) +{ + _do_releasedir(req, nodeid, inarg, NULL); +} + +static void _do_fsyncdir(fuse_req_t req, const fuse_ino_t nodeid, + const void *op_in, const void *in_payload) +{ + (void)in_payload; + struct fuse_fsync_in *arg = (struct fuse_fsync_in *)op_in; + struct fuse_file_info fi; + int datasync = arg->fsync_flags & 1; + + memset(&fi, 0, sizeof(fi)); + fi.fh = arg->fh; + + if (req->se->op.fsyncdir) + req->se->op.fsyncdir(req, nodeid, datasync, &fi); + else + fuse_reply_err(req, ENOSYS); +} +static void do_fsyncdir(fuse_req_t req, const fuse_ino_t nodeid, + const void *inarg) +{ + _do_fsyncdir(req, nodeid, inarg, NULL); +} + +static void _do_statfs(fuse_req_t req, const fuse_ino_t nodeid, + const void *op_in, const void *in_payload) +{ + (void) nodeid; + (void)op_in; + (void)in_payload; + + if (req->se->op.statfs) + req->se->op.statfs(req, nodeid); + else { + struct statvfs buf = { + .f_namemax = 255, + .f_bsize = 512, + }; + fuse_reply_statfs(req, &buf); + } +} +static void do_statfs(fuse_req_t req, const fuse_ino_t nodeid, + const void *inarg) +{ + _do_statfs(req, nodeid, inarg, NULL); +} + +static void _do_setxattr(fuse_req_t req, const fuse_ino_t nodeid, + const void *op_in, const void *in_payload) +{ + struct fuse_setxattr_in *arg = (struct fuse_setxattr_in *)op_in; + const char *name = in_payload; + const char *value = name + strlen(name) + 1; + + /* XXX:The API should be extended to support extra_flags/setxattr_flags */ + + if (req->se->op.setxattr) + req->se->op.setxattr(req, nodeid, name, value, arg->size, + arg->flags); + else + fuse_reply_err(req, ENOSYS); +} +static void do_setxattr(fuse_req_t req, const fuse_ino_t nodeid, + const void *inarg) +{ + struct fuse_session *se = req->se; + unsigned int xattr_ext = !!(se->conn.want & FUSE_CAP_SETXATTR_EXT); + const struct fuse_setxattr_in *arg = inarg; + char *payload = xattr_ext ? PARAM(arg) : + (char *)arg + FUSE_COMPAT_SETXATTR_IN_SIZE; + + _do_setxattr(req, nodeid, arg, payload); +} + +static void _do_getxattr(fuse_req_t req, const fuse_ino_t nodeid, + const void *op_in, const void *in_payload) +{ + const struct fuse_getxattr_in *arg = op_in; + + if (req->se->op.getxattr) + req->se->op.getxattr(req, nodeid, in_payload, arg->size); + else + fuse_reply_err(req, ENOSYS); +} + +static void do_getxattr(fuse_req_t req, const fuse_ino_t nodeid, + const void *inarg) +{ + const struct fuse_getxattr_in *arg = inarg; + const void *payload = PARAM(arg); + + _do_getxattr(req, nodeid, arg, payload); +} + +static void _do_listxattr(fuse_req_t req, const fuse_ino_t nodeid, + const void *inarg, const void *in_payload) +{ + (void)in_payload; + const struct fuse_getxattr_in *arg = inarg; + + if (req->se->op.listxattr) + req->se->op.listxattr(req, nodeid, arg->size); + else + fuse_reply_err(req, ENOSYS); +} +static void do_listxattr(fuse_req_t req, const fuse_ino_t nodeid, + const void *inarg) +{ + _do_listxattr(req, nodeid, inarg, NULL); +} + +static void _do_removexattr(fuse_req_t req, const fuse_ino_t nodeid, + const void *inarg, const void *in_payload) +{ + (void)inarg; + const char *name = in_payload; + + if (req->se->op.removexattr) + req->se->op.removexattr(req, nodeid, name); + else + fuse_reply_err(req, ENOSYS); +} +static void do_removexattr(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) +{ + _do_removexattr(req, nodeid, NULL, inarg); +} + +static void convert_fuse_file_lock(const struct fuse_file_lock *fl, + struct flock *flock) +{ + memset(flock, 0, sizeof(struct flock)); + flock->l_type = fl->type; + flock->l_whence = SEEK_SET; + flock->l_start = fl->start; + if (fl->end == OFFSET_MAX) + flock->l_len = 0; + else + flock->l_len = fl->end - fl->start + 1; + flock->l_pid = fl->pid; +} + +static void _do_getlk(fuse_req_t req, const fuse_ino_t nodeid, + const void *op_in, const void *in_payload) +{ + (void)in_payload; + const struct fuse_lk_in *arg = op_in; + struct fuse_file_info fi; + struct flock flock; + + memset(&fi, 0, sizeof(fi)); + fi.fh = arg->fh; + fi.lock_owner = arg->owner; + + convert_fuse_file_lock(&arg->lk, &flock); + if (req->se->op.getlk) + req->se->op.getlk(req, nodeid, &fi, &flock); + else + fuse_reply_err(req, ENOSYS); +} +static void do_getlk(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) +{ + _do_getlk(req, nodeid, inarg, NULL); +} + +static void do_setlk_common(fuse_req_t req, const fuse_ino_t nodeid, + const void *op_in, int sleep) +{ + const struct fuse_lk_in *arg = op_in; + struct fuse_file_info fi; + struct flock flock; + + memset(&fi, 0, sizeof(fi)); + fi.fh = arg->fh; + fi.lock_owner = arg->owner; + + if (arg->lk_flags & FUSE_LK_FLOCK) { + int op = 0; + + switch (arg->lk.type) { + case F_RDLCK: + op = LOCK_SH; + break; + case F_WRLCK: + op = LOCK_EX; + break; + case F_UNLCK: + op = LOCK_UN; + break; + } + if (!sleep) + op |= LOCK_NB; + + if (req->se->op.flock) + req->se->op.flock(req, nodeid, &fi, op); + else + fuse_reply_err(req, ENOSYS); + } else { + convert_fuse_file_lock(&arg->lk, &flock); + if (req->se->op.setlk) + req->se->op.setlk(req, nodeid, &fi, &flock, sleep); + else + fuse_reply_err(req, ENOSYS); + } +} + +static void _do_setlk(fuse_req_t req, const fuse_ino_t nodeid, + const void *op_in, const void *in_payload) +{ + (void)in_payload; + do_setlk_common(req, nodeid, op_in, 0); +} + +static void do_setlk(fuse_req_t req, const fuse_ino_t nodeid, const void *inarg) +{ + _do_setlk(req, nodeid, inarg, NULL); +} + +static void _do_setlkw(fuse_req_t req, const fuse_ino_t nodeid, + const void *op_in, const void *in_payload) +{ + (void)in_payload; + do_setlk_common(req, nodeid, op_in, 1); +} +static void do_setlkw(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) +{ + _do_setlkw(req, nodeid, inarg, NULL); +} + +static int find_interrupted(struct fuse_session *se, struct fuse_req *req) +{ + struct fuse_req *curr; + + for (curr = se->list.next; curr != &se->list; curr = curr->next) { + if (curr->unique == req->u.i.unique) { + fuse_interrupt_func_t func; + void *data; + + curr->ref_cnt++; + pthread_mutex_unlock(&se->lock); + + /* Ugh, ugly locking */ + pthread_mutex_lock(&curr->lock); + pthread_mutex_lock(&se->lock); + curr->interrupted = 1; + func = curr->u.ni.func; + data = curr->u.ni.data; + pthread_mutex_unlock(&se->lock); + if (func) + func(curr, data); + pthread_mutex_unlock(&curr->lock); + + pthread_mutex_lock(&se->lock); + curr->ref_cnt--; + if (!curr->ref_cnt) { + destroy_req(curr); + } + + return 1; + } + } + for (curr = se->interrupts.next; curr != &se->interrupts; + curr = curr->next) { + if (curr->u.i.unique == req->u.i.unique) + return 1; + } + return 0; +} + +static void _do_interrupt(fuse_req_t req, const fuse_ino_t nodeid, + const void *op_in, const void *in_payload) +{ + (void)in_payload; + const struct fuse_interrupt_in *arg = op_in; + struct fuse_session *se = req->se; + + (void) nodeid; + if (se->debug) + fuse_log(FUSE_LOG_DEBUG, "INTERRUPT: %llu\n", + (unsigned long long) arg->unique); + + req->u.i.unique = arg->unique; + + pthread_mutex_lock(&se->lock); + if (find_interrupted(se, req)) { + fuse_chan_put(req->ch); + req->ch = NULL; + destroy_req(req); + } else + list_add_req(req, &se->interrupts); + pthread_mutex_unlock(&se->lock); +} +static void do_interrupt(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) +{ + _do_interrupt(req, nodeid, inarg, NULL); +} + +static struct fuse_req *check_interrupt(struct fuse_session *se, + struct fuse_req *req) +{ + struct fuse_req *curr; + + for (curr = se->interrupts.next; curr != &se->interrupts; + curr = curr->next) { + if (curr->u.i.unique == req->unique) { + req->interrupted = 1; + list_del_req(curr); + fuse_chan_put(curr->ch); + curr->ch = NULL; + destroy_req(curr); + return NULL; + } + } + curr = se->interrupts.next; + if (curr != &se->interrupts) { + list_del_req(curr); + list_init_req(curr); + return curr; + } else + return NULL; +} + +static void _do_bmap(fuse_req_t req, const fuse_ino_t nodeid, const void *op_in, + const void *in_payload) +{ + (void)in_payload; + const struct fuse_bmap_in *arg = op_in; + + if (req->se->op.bmap) + req->se->op.bmap(req, nodeid, arg->blocksize, arg->block); + else + fuse_reply_err(req, ENOSYS); +} +static void do_bmap(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) +{ + _do_bmap(req, nodeid, inarg, NULL); +} + +static void _do_ioctl(fuse_req_t req, const fuse_ino_t nodeid, + const void *op_in, const void *in_payload) +{ + struct fuse_ioctl_in *arg = (struct fuse_ioctl_in *)op_in; + unsigned int flags = arg->flags; + const void *in_buf = in_payload; + struct fuse_file_info fi; + + if (flags & FUSE_IOCTL_DIR && + !(req->se->conn.want_ext & FUSE_CAP_IOCTL_DIR)) { + fuse_reply_err(req, ENOTTY); + return; + } + + memset(&fi, 0, sizeof(fi)); + fi.fh = arg->fh; + + if (sizeof(void *) == 4 && req->se->conn.proto_minor >= 16 && + !(flags & FUSE_IOCTL_32BIT)) { + req->flags.ioctl_64bit = 1; + } + + if (req->se->op.ioctl) + req->se->op.ioctl(req, nodeid, arg->cmd, + (void *)(uintptr_t)arg->arg, &fi, flags, + in_buf, arg->in_size, arg->out_size); + else + fuse_reply_err(req, ENOSYS); +} +static void do_ioctl(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) +{ + const struct fuse_ioctl_in *arg = inarg; + void *in_buf = arg->in_size ? PARAM(arg) : NULL; + + _do_ioctl(req, nodeid, arg, in_buf); +} + +void fuse_pollhandle_destroy(struct fuse_pollhandle *ph) +{ + free(ph); +} + +static void _do_poll(fuse_req_t req, const fuse_ino_t nodeid, const void *op_in, + const void *in_payload) +{ + (void)in_payload; + struct fuse_poll_in *arg = (struct fuse_poll_in *)op_in; + struct fuse_file_info fi; + + memset(&fi, 0, sizeof(fi)); + fi.fh = arg->fh; + fi.poll_events = arg->events; + + if (req->se->op.poll) { + struct fuse_pollhandle *ph = NULL; + + if (arg->flags & FUSE_POLL_SCHEDULE_NOTIFY) { + ph = malloc(sizeof(struct fuse_pollhandle)); + if (ph == NULL) { + fuse_reply_err(req, ENOMEM); + return; + } + ph->kh = arg->kh; + ph->se = req->se; + } + + req->se->op.poll(req, nodeid, &fi, ph); + } else { + fuse_reply_err(req, ENOSYS); + } +} + +static void do_poll(fuse_req_t req, const fuse_ino_t nodeid, const void *inarg) +{ + _do_poll(req, nodeid, inarg, NULL); +} + +static void _do_fallocate(fuse_req_t req, const fuse_ino_t nodeid, + const void *op_in, const void *in_payload) +{ + (void)in_payload; + const struct fuse_fallocate_in *arg = op_in; + struct fuse_file_info fi; + + memset(&fi, 0, sizeof(fi)); + fi.fh = arg->fh; + + if (req->se->op.fallocate) + req->se->op.fallocate(req, nodeid, arg->mode, arg->offset, + arg->length, &fi); + else + fuse_reply_err(req, ENOSYS); +} + +static void do_fallocate(fuse_req_t req, const fuse_ino_t nodeid, + const void *inarg) +{ + _do_fallocate(req, nodeid, inarg, NULL); +} + +static void copy_file_range_common(fuse_req_t req, const fuse_ino_t nodeid_in, + const struct fuse_copy_file_range_in *arg) +{ + struct fuse_file_info fi_in, fi_out; + + memset(&fi_in, 0, sizeof(fi_in)); + fi_in.fh = arg->fh_in; + + memset(&fi_out, 0, sizeof(fi_out)); + fi_out.fh = arg->fh_out; + + if (req->se->op.copy_file_range) + req->se->op.copy_file_range(req, nodeid_in, arg->off_in, &fi_in, + arg->nodeid_out, arg->off_out, + &fi_out, arg->len, arg->flags); + else + fuse_reply_err(req, ENOSYS); +} + +static void _do_copy_file_range(fuse_req_t req, const fuse_ino_t nodeid_in, + const void *op_in, const void *in_payload) +{ + const struct fuse_copy_file_range_in *arg = op_in; + struct fuse_copy_file_range_in arg_tmp; + + (void) in_payload; + /* fuse_write_out can only handle 32bit copy size */ + if (arg->len > 0xfffff000) { + arg_tmp = *arg; + arg_tmp.len = 0xfffff000; + arg = &arg_tmp; + } + copy_file_range_common(req, nodeid_in, arg); +} + +static void do_copy_file_range(fuse_req_t req, const fuse_ino_t nodeid_in, + const void *inarg) +{ + _do_copy_file_range(req, nodeid_in, inarg, NULL); +} + +static void _do_copy_file_range_64(fuse_req_t req, const fuse_ino_t nodeid_in, + const void *op_in, const void *in_payload) +{ + (void) in_payload; + req->flags.is_copy_file_range_64 = 1; + /* Limit size on 32bit userspace to avoid conversion overflow */ + if (sizeof(size_t) == 4) + _do_copy_file_range(req, nodeid_in, op_in, NULL); + else + copy_file_range_common(req, nodeid_in, op_in); +} + +static void do_copy_file_range_64(fuse_req_t req, const fuse_ino_t nodeid_in, + const void *inarg) +{ + _do_copy_file_range_64(req, nodeid_in, inarg, NULL); +} + +/* + * Note that the uint64_t offset in struct fuse_lseek_in is derived from + * linux kernel loff_t and is therefore signed. + */ +static void _do_lseek(fuse_req_t req, const fuse_ino_t nodeid, + const void *op_in, const void *in_payload) +{ + (void)in_payload; + const struct fuse_lseek_in *arg = op_in; + struct fuse_file_info fi; + + memset(&fi, 0, sizeof(fi)); + fi.fh = arg->fh; + + if (req->se->op.lseek) + req->se->op.lseek(req, nodeid, arg->offset, arg->whence, &fi); + else + fuse_reply_err(req, ENOSYS); +} + +static void do_lseek(fuse_req_t req, const fuse_ino_t nodeid, const void *inarg) +{ + _do_lseek(req, nodeid, inarg, NULL); +} + +#ifdef HAVE_STATX +static void _do_statx(fuse_req_t req, const fuse_ino_t nodeid, + const void *op_in, const void *in_payload) +{ + (void)in_payload; + const struct fuse_statx_in *arg = op_in; + struct fuse_file_info *fip = NULL; + struct fuse_file_info fi; + + if (arg->getattr_flags & FUSE_GETATTR_FH) { + memset(&fi, 0, sizeof(fi)); + fi.fh = arg->fh; + fip = &fi; + } + + if (req->se->op.statx) + req->se->op.statx(req, nodeid, arg->sx_flags, arg->sx_mask, fip); + else + fuse_reply_err(req, ENOSYS); +} +#else +static void _do_statx(fuse_req_t req, const fuse_ino_t nodeid, + const void *op_in, const void *in_payload) +{ + (void)in_payload; + (void)nodeid; + (void)op_in; + fuse_reply_err(req, ENOSYS); +} +#endif + +static void do_statx(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) +{ + _do_statx(req, nodeid, inarg, NULL); +} + +static bool want_flags_valid(uint64_t capable, uint64_t want) +{ + uint64_t unknown_flags = want & (~capable); + if (unknown_flags != 0) { + fuse_log(FUSE_LOG_ERR, + "fuse: unknown connection 'want' flags: 0x%08llx\n", + (unsigned long long)unknown_flags); + return false; + } + return true; +} + +/** + * Get the wanted capability flags, converting from old format if necessary + */ +int fuse_convert_to_conn_want_ext(struct fuse_conn_info *conn) +{ + struct fuse_session *se = container_of(conn, struct fuse_session, conn); + + /* + * Convert want to want_ext if necessary. + * For the high level interface this function might be called + * twice, once from the high level interface and once from the + * low level interface. Both, with different want_ext_default and + * want_default values. In order to suppress a failure for the + * second call, we check if the lower 32 bits of want_ext are + * already set to the value of want. + */ + if (conn->want != se->conn_want && + fuse_lower_32_bits(conn->want_ext) != conn->want) { + if (conn->want_ext != se->conn_want_ext) { + fuse_log(FUSE_LOG_ERR, + "%s: Both conn->want_ext and conn->want are set.\n" + "want=%x want_ext=%llx, se->want=%x se->want_ext=%llx\n", + __func__, conn->want, + (unsigned long long)conn->want_ext, + se->conn_want, + (unsigned long long)se->conn_want_ext); + return -EINVAL; + } + + /* high bits from want_ext, low bits from want */ + conn->want_ext = fuse_higher_32_bits(conn->want_ext) | + conn->want; + } + + /* ensure there won't be a second conversion */ + conn->want = fuse_lower_32_bits(conn->want_ext); + + return 0; +} + +bool fuse_set_feature_flag(struct fuse_conn_info *conn, + uint64_t flag) +{ + struct fuse_session *se = container_of(conn, struct fuse_session, conn); + + if (conn->capable_ext & flag) { + conn->want_ext |= flag; + se->conn_want_ext |= flag; + conn->want |= flag; + se->conn_want |= flag; + return true; + } + return false; +} + +void fuse_unset_feature_flag(struct fuse_conn_info *conn, + uint64_t flag) +{ + struct fuse_session *se = container_of(conn, struct fuse_session, conn); + + conn->want_ext &= ~flag; + se->conn_want_ext &= ~flag; + conn->want &= ~flag; + se->conn_want &= ~flag; +} + +bool fuse_get_feature_flag(struct fuse_conn_info *conn, + uint64_t flag) +{ + return conn->capable_ext & flag ? true : false; +} + +/* Prevent bogus data races (bogus since "init" is called before + * multi-threading becomes relevant */ +static __attribute__((no_sanitize("thread"))) void +_do_init(fuse_req_t req, const fuse_ino_t nodeid, const void *op_in, + const void *in_payload) +{ + (void)in_payload; + const struct fuse_init_in *arg = op_in; + struct fuse_init_out outarg; + struct fuse_session *se = req->se; + size_t bufsize = se->bufsize; + size_t outargsize = sizeof(outarg); + uint64_t inargflags = 0; + uint64_t outargflags = 0; + bool buf_reallocable = se->buf_reallocable; + (void) nodeid; + bool enable_io_uring = false; + + if (se->debug) { + fuse_log(FUSE_LOG_DEBUG, "INIT: %u.%u\n", arg->major, arg->minor); + if (arg->major == 7 && arg->minor >= 6) { + fuse_log(FUSE_LOG_DEBUG, "flags=0x%08x\n", arg->flags); + fuse_log(FUSE_LOG_DEBUG, "max_readahead=0x%08x\n", + arg->max_readahead); + } + } + se->conn.proto_major = arg->major; + se->conn.proto_minor = arg->minor; + se->conn.capable_ext = 0; + se->conn.want_ext = 0; + + memset(&outarg, 0, sizeof(outarg)); + outarg.major = FUSE_KERNEL_VERSION; + outarg.minor = FUSE_KERNEL_MINOR_VERSION; + + if (arg->major < 7) { + fuse_log(FUSE_LOG_ERR, "fuse: unsupported protocol version: %u.%u\n", + arg->major, arg->minor); + fuse_reply_err(req, EPROTO); + return; + } + + if (arg->major > 7) { + /* Wait for a second INIT request with a 7.X version */ + send_reply_ok(req, &outarg, sizeof(outarg)); + return; + } + + if (arg->minor >= 6) { + if (arg->max_readahead < se->conn.max_readahead) + se->conn.max_readahead = arg->max_readahead; + inargflags = arg->flags; + if (inargflags & FUSE_INIT_EXT) + inargflags = inargflags | (uint64_t) arg->flags2 << 32; + if (inargflags & FUSE_ASYNC_READ) + se->conn.capable_ext |= FUSE_CAP_ASYNC_READ; + if (inargflags & FUSE_POSIX_LOCKS) + se->conn.capable_ext |= FUSE_CAP_POSIX_LOCKS; + if (inargflags & FUSE_ATOMIC_O_TRUNC) + se->conn.capable_ext |= FUSE_CAP_ATOMIC_O_TRUNC; + if (inargflags & FUSE_EXPORT_SUPPORT) + se->conn.capable_ext |= FUSE_CAP_EXPORT_SUPPORT; + if (inargflags & FUSE_DONT_MASK) + se->conn.capable_ext |= FUSE_CAP_DONT_MASK; + if (inargflags & FUSE_FLOCK_LOCKS) + se->conn.capable_ext |= FUSE_CAP_FLOCK_LOCKS; + if (inargflags & FUSE_AUTO_INVAL_DATA) + se->conn.capable_ext |= FUSE_CAP_AUTO_INVAL_DATA; + if (inargflags & FUSE_DO_READDIRPLUS) + se->conn.capable_ext |= FUSE_CAP_READDIRPLUS; + if (inargflags & FUSE_READDIRPLUS_AUTO) + se->conn.capable_ext |= FUSE_CAP_READDIRPLUS_AUTO; + if (inargflags & FUSE_ASYNC_DIO) + se->conn.capable_ext |= FUSE_CAP_ASYNC_DIO; + if (inargflags & FUSE_WRITEBACK_CACHE) + se->conn.capable_ext |= FUSE_CAP_WRITEBACK_CACHE; + if (inargflags & FUSE_NO_OPEN_SUPPORT) + se->conn.capable_ext |= FUSE_CAP_NO_OPEN_SUPPORT; + if (inargflags & FUSE_PARALLEL_DIROPS) + se->conn.capable_ext |= FUSE_CAP_PARALLEL_DIROPS; + if (inargflags & FUSE_POSIX_ACL) + se->conn.capable_ext |= FUSE_CAP_POSIX_ACL; + if (inargflags & FUSE_HANDLE_KILLPRIV) + se->conn.capable_ext |= FUSE_CAP_HANDLE_KILLPRIV; + if (inargflags & FUSE_HANDLE_KILLPRIV_V2) + se->conn.capable_ext |= FUSE_CAP_HANDLE_KILLPRIV_V2; + if (inargflags & FUSE_CACHE_SYMLINKS) + se->conn.capable_ext |= FUSE_CAP_CACHE_SYMLINKS; + if (inargflags & FUSE_NO_OPENDIR_SUPPORT) + se->conn.capable_ext |= FUSE_CAP_NO_OPENDIR_SUPPORT; + if (inargflags & FUSE_EXPLICIT_INVAL_DATA) + se->conn.capable_ext |= FUSE_CAP_EXPLICIT_INVAL_DATA; + if (inargflags & FUSE_SETXATTR_EXT) + se->conn.capable_ext |= FUSE_CAP_SETXATTR_EXT; + if (!(inargflags & FUSE_MAX_PAGES)) { + size_t max_bufsize = + FUSE_DEFAULT_MAX_PAGES_PER_REQ * getpagesize() + + FUSE_BUFFER_HEADER_SIZE; + if (bufsize > max_bufsize) { + bufsize = max_bufsize; + } + buf_reallocable = false; + } + if (inargflags & FUSE_DIRECT_IO_ALLOW_MMAP) + se->conn.capable_ext |= FUSE_CAP_DIRECT_IO_ALLOW_MMAP; + if (arg->minor >= 38 || (inargflags & FUSE_HAS_EXPIRE_ONLY)) + se->conn.capable_ext |= FUSE_CAP_EXPIRE_ONLY; + if (inargflags & FUSE_PASSTHROUGH) + se->conn.capable_ext |= FUSE_CAP_PASSTHROUGH; + if (inargflags & FUSE_NO_EXPORT_SUPPORT) + se->conn.capable_ext |= FUSE_CAP_NO_EXPORT_SUPPORT; + if (inargflags & FUSE_OVER_IO_URING) + se->conn.capable_ext |= FUSE_CAP_OVER_IO_URING; + + } else { + se->conn.max_readahead = 0; + } + + if (se->conn.proto_minor >= 14) { +#ifdef HAVE_SPLICE +#ifdef HAVE_VMSPLICE + if ((se->io == NULL) || (se->io->splice_send != NULL)) { + se->conn.capable_ext |= FUSE_CAP_SPLICE_WRITE | + FUSE_CAP_SPLICE_MOVE; + } +#endif + if ((se->io == NULL) || (se->io->splice_receive != NULL)) { + se->conn.capable_ext |= FUSE_CAP_SPLICE_READ; + } +#endif + } + if (se->conn.proto_minor >= 18) + se->conn.capable_ext |= FUSE_CAP_IOCTL_DIR; + + /* Default settings for modern filesystems. + * + * Most of these capabilities were disabled by default in + * libfuse2 for backwards compatibility reasons. In libfuse3, + * we can finally enable them by default (as long as they're + * supported by the kernel). + */ +#define LL_SET_DEFAULT(cond, cap) \ + if ((cond)) \ + fuse_set_feature_flag(&se->conn, cap) + + LL_SET_DEFAULT(1, FUSE_CAP_ASYNC_READ); + LL_SET_DEFAULT(1, FUSE_CAP_AUTO_INVAL_DATA); + LL_SET_DEFAULT(1, FUSE_CAP_ASYNC_DIO); + LL_SET_DEFAULT(1, FUSE_CAP_IOCTL_DIR); + LL_SET_DEFAULT(1, FUSE_CAP_ATOMIC_O_TRUNC); + LL_SET_DEFAULT(se->op.write_buf, FUSE_CAP_SPLICE_READ); + LL_SET_DEFAULT(se->op.getlk && se->op.setlk, + FUSE_CAP_POSIX_LOCKS); + LL_SET_DEFAULT(se->op.flock, FUSE_CAP_FLOCK_LOCKS); + LL_SET_DEFAULT(se->op.readdirplus, FUSE_CAP_READDIRPLUS); + LL_SET_DEFAULT(se->op.readdirplus && se->op.readdir, + FUSE_CAP_READDIRPLUS_AUTO); + LL_SET_DEFAULT(1, FUSE_CAP_OVER_IO_URING); + + /* This could safely become default, but libfuse needs an API extension + * to support it + * LL_SET_DEFAULT(1, FUSE_CAP_SETXATTR_EXT); + */ + + se->conn.time_gran = 1; + + if (se->op.init) { + // Apply the first 32 bits of capable_ext to capable + se->conn.capable = fuse_lower_32_bits(se->conn.capable_ext); + + se->op.init(se->userdata, &se->conn); + + /* + * se->conn.want is 32-bit value and deprecated in favour of + * se->conn.want_ext + * Userspace might still use conn.want - we need to convert it + */ + fuse_convert_to_conn_want_ext(&se->conn); + } + + if (!want_flags_valid(se->conn.capable_ext, se->conn.want_ext)) { + fuse_reply_err(req, EPROTO); + se->error = -EPROTO; + fuse_session_exit(se); + return; + } + + unsigned max_read_mo = get_max_read(se->mo); + if (se->conn.max_read != max_read_mo) { + fuse_log(FUSE_LOG_ERR, "fuse: error: init() and fuse_session_new() " + "requested different maximum read size (%u vs %u)\n", + se->conn.max_read, max_read_mo); + fuse_reply_err(req, EPROTO); + se->error = -EPROTO; + fuse_session_exit(se); + return; + } + + if (bufsize < FUSE_MIN_READ_BUFFER) { + fuse_log(FUSE_LOG_ERR, + "fuse: warning: buffer size too small: %zu\n", + bufsize); + bufsize = FUSE_MIN_READ_BUFFER; + } + + if (buf_reallocable) + bufsize = UINT_MAX; + se->conn.max_write = MIN(se->conn.max_write, bufsize - FUSE_BUFFER_HEADER_SIZE); + se->bufsize = se->conn.max_write + FUSE_BUFFER_HEADER_SIZE; + + if (arg->flags & FUSE_MAX_PAGES) { + outarg.flags |= FUSE_MAX_PAGES; + outarg.max_pages = (se->conn.max_write - 1) / getpagesize() + 1; + } + outargflags = outarg.flags; + /* Always enable big writes, this is superseded + by the max_write option */ + outargflags |= FUSE_BIG_WRITES; + + if (se->conn.want_ext & FUSE_CAP_ASYNC_READ) + outargflags |= FUSE_ASYNC_READ; + if (se->conn.want_ext & FUSE_CAP_POSIX_LOCKS) + outargflags |= FUSE_POSIX_LOCKS; + if (se->conn.want_ext & FUSE_CAP_ATOMIC_O_TRUNC) + outargflags |= FUSE_ATOMIC_O_TRUNC; + if (se->conn.want_ext & FUSE_CAP_EXPORT_SUPPORT) + outargflags |= FUSE_EXPORT_SUPPORT; + if (se->conn.want_ext & FUSE_CAP_DONT_MASK) + outargflags |= FUSE_DONT_MASK; + if (se->conn.want_ext & FUSE_CAP_FLOCK_LOCKS) + outargflags |= FUSE_FLOCK_LOCKS; + if (se->conn.want_ext & FUSE_CAP_AUTO_INVAL_DATA) + outargflags |= FUSE_AUTO_INVAL_DATA; + if (se->conn.want_ext & FUSE_CAP_READDIRPLUS) + outargflags |= FUSE_DO_READDIRPLUS; + if (se->conn.want_ext & FUSE_CAP_READDIRPLUS_AUTO) + outargflags |= FUSE_READDIRPLUS_AUTO; + if (se->conn.want_ext & FUSE_CAP_ASYNC_DIO) + outargflags |= FUSE_ASYNC_DIO; + if (se->conn.want_ext & FUSE_CAP_WRITEBACK_CACHE) + outargflags |= FUSE_WRITEBACK_CACHE; + if (se->conn.want_ext & FUSE_CAP_PARALLEL_DIROPS) + outargflags |= FUSE_PARALLEL_DIROPS; + if (se->conn.want_ext & FUSE_CAP_POSIX_ACL) + outargflags |= FUSE_POSIX_ACL; + if (se->conn.want_ext & FUSE_CAP_HANDLE_KILLPRIV) + outargflags |= FUSE_HANDLE_KILLPRIV; + if (se->conn.want_ext & FUSE_CAP_HANDLE_KILLPRIV_V2) + outargflags |= FUSE_HANDLE_KILLPRIV_V2; + if (se->conn.want_ext & FUSE_CAP_CACHE_SYMLINKS) + outargflags |= FUSE_CACHE_SYMLINKS; + if (se->conn.want_ext & FUSE_CAP_EXPLICIT_INVAL_DATA) + outargflags |= FUSE_EXPLICIT_INVAL_DATA; + if (se->conn.want_ext & FUSE_CAP_SETXATTR_EXT) + outargflags |= FUSE_SETXATTR_EXT; + if (se->conn.want_ext & FUSE_CAP_DIRECT_IO_ALLOW_MMAP) + outargflags |= FUSE_DIRECT_IO_ALLOW_MMAP; + if (se->conn.want_ext & FUSE_CAP_PASSTHROUGH) { + outargflags |= FUSE_PASSTHROUGH; + /* + * outarg.max_stack_depth includes the fuse stack layer, + * so it is one more than max_backing_stack_depth. + */ + outarg.max_stack_depth = se->conn.max_backing_stack_depth + 1; + } + if (se->conn.want_ext & FUSE_CAP_NO_EXPORT_SUPPORT) + outargflags |= FUSE_NO_EXPORT_SUPPORT; + if (se->uring.enable && se->conn.want_ext & FUSE_CAP_OVER_IO_URING) { + outargflags |= FUSE_OVER_IO_URING; + enable_io_uring = true; + } + + if ((inargflags & FUSE_REQUEST_TIMEOUT) && se->conn.request_timeout) { + outargflags |= FUSE_REQUEST_TIMEOUT; + outarg.request_timeout = se->conn.request_timeout; + } + + outarg.max_readahead = se->conn.max_readahead; + outarg.max_write = se->conn.max_write; + if (se->conn.proto_minor >= 13) { + if (se->conn.max_background >= (1 << 16)) + se->conn.max_background = (1 << 16) - 1; + if (se->conn.congestion_threshold > se->conn.max_background) + se->conn.congestion_threshold = se->conn.max_background; + if (!se->conn.congestion_threshold) { + se->conn.congestion_threshold = + se->conn.max_background * 3 / 4; + } + + outarg.max_background = se->conn.max_background; + outarg.congestion_threshold = se->conn.congestion_threshold; + } + if (se->conn.proto_minor >= 23) + outarg.time_gran = se->conn.time_gran; + + if (se->debug) { + fuse_log(FUSE_LOG_DEBUG, " INIT: %u.%u\n", outarg.major, outarg.minor); + fuse_log(FUSE_LOG_DEBUG, " flags=0x%08x\n", outarg.flags); + fuse_log(FUSE_LOG_DEBUG, " max_readahead=0x%08x\n", + outarg.max_readahead); + fuse_log(FUSE_LOG_DEBUG, " max_write=0x%08x\n", outarg.max_write); + fuse_log(FUSE_LOG_DEBUG, " max_background=%i\n", + outarg.max_background); + fuse_log(FUSE_LOG_DEBUG, " congestion_threshold=%i\n", + outarg.congestion_threshold); + fuse_log(FUSE_LOG_DEBUG, " time_gran=%u\n", + outarg.time_gran); + if (se->conn.want_ext & FUSE_CAP_PASSTHROUGH) + fuse_log(FUSE_LOG_DEBUG, " max_stack_depth=%u\n", + outarg.max_stack_depth); + } + if (arg->minor < 5) + outargsize = FUSE_COMPAT_INIT_OUT_SIZE; + else if (arg->minor < 23) + outargsize = FUSE_COMPAT_22_INIT_OUT_SIZE; + + /* XXX: Add an option to make non-available io-uring fatal */ + if (enable_io_uring) { + int ring_rc = fuse_uring_start(se); + + if (ring_rc != 0) { + fuse_log(FUSE_LOG_INFO, + "fuse: failed to start io-uring: %s\n", + strerror(ring_rc)); + outargflags &= ~FUSE_OVER_IO_URING; + enable_io_uring = false; + } + } + + if (inargflags & FUSE_INIT_EXT) { + outargflags |= FUSE_INIT_EXT; + outarg.flags2 = outargflags >> 32; + } + outarg.flags = outargflags; + + /* + * Has to be set before replying, as new kernel requests might + * immediately arrive and got_init is used for op-code sanity. + * Especially with external handlers, where we have no control + * over the thread scheduling. + */ + se->got_init = 1; + send_reply_ok(req, &outarg, outargsize); + if (enable_io_uring) + fuse_uring_wake_ring_threads(se); +} + +static __attribute__((no_sanitize("thread"))) void +do_init(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) +{ + _do_init(req, nodeid, inarg, NULL); +} + +static void _do_destroy(fuse_req_t req, const fuse_ino_t nodeid, + const void *op_in, const void *in_payload) +{ + struct fuse_session *se = req->se; + char *mountpoint; + + (void) nodeid; + (void)op_in; + (void)in_payload; + + mountpoint = atomic_exchange(&se->mountpoint, NULL); + free(mountpoint); + + se->got_destroy = 1; + se->got_init = 0; + if (se->op.destroy) + se->op.destroy(se->userdata); + + send_reply_ok(req, NULL, 0); +} + +static void do_destroy(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) +{ + _do_destroy(req, nodeid, inarg, NULL); +} + +static void list_del_nreq(struct fuse_notify_req *nreq) +{ + struct fuse_notify_req *prev = nreq->prev; + struct fuse_notify_req *next = nreq->next; + prev->next = next; + next->prev = prev; +} + +static void list_add_nreq(struct fuse_notify_req *nreq, + struct fuse_notify_req *next) +{ + struct fuse_notify_req *prev = next->prev; + nreq->next = next; + nreq->prev = prev; + prev->next = nreq; + next->prev = nreq; +} + +static void list_init_nreq(struct fuse_notify_req *nreq) +{ + nreq->next = nreq; + nreq->prev = nreq; +} + +static void _do_notify_reply(fuse_req_t req, fuse_ino_t nodeid, + const void *inarg, const struct fuse_buf *buf) +{ + struct fuse_session *se = req->se; + struct fuse_notify_req *nreq; + struct fuse_notify_req *head; + + pthread_mutex_lock(&se->lock); + head = &se->notify_list; + for (nreq = head->next; nreq != head; nreq = nreq->next) { + if (nreq->unique == req->unique) { + list_del_nreq(nreq); + break; + } + } + pthread_mutex_unlock(&se->lock); + + if (nreq != head) + nreq->reply(nreq, req, nodeid, inarg, buf); +} + +static void do_notify_reply(fuse_req_t req, fuse_ino_t nodeid, + const void *inarg, const struct fuse_buf *ibuf) +{ + struct fuse_buf buf = *ibuf; + + buf.size -= sizeof(struct fuse_in_header); + _do_notify_reply(req, nodeid, inarg, &buf); +} + +static int send_notify_iov(struct fuse_session *se, int notify_code, + struct iovec *iov, int count) +{ + struct fuse_out_header out; + struct fuse_req *req = NULL; + + if (!se->got_init) + return -ENOTCONN; + + out.unique = 0; + out.error = notify_code; + iov[0].iov_base = &out; + iov[0].iov_len = sizeof(struct fuse_out_header); + + return fuse_send_msg(se, NULL, iov, count, req); +} + +int fuse_lowlevel_notify_poll(struct fuse_pollhandle *ph) +{ + if (ph != NULL) { + struct fuse_notify_poll_wakeup_out outarg; + struct iovec iov[2]; + + outarg.kh = ph->kh; + + iov[1].iov_base = &outarg; + iov[1].iov_len = sizeof(outarg); + + return send_notify_iov(ph->se, FUSE_NOTIFY_POLL, iov, 2); + } else { + return 0; + } +} + +int fuse_lowlevel_notify_inval_inode(struct fuse_session *se, fuse_ino_t ino, + off_t off, off_t len) +{ + struct fuse_notify_inval_inode_out outarg; + struct iovec iov[2]; + + if (!se) + return -EINVAL; + + if (se->conn.proto_minor < 12) + return -ENOSYS; + + outarg.ino = ino; + outarg.off = off; + outarg.len = len; + + iov[1].iov_base = &outarg; + iov[1].iov_len = sizeof(outarg); + + return send_notify_iov(se, FUSE_NOTIFY_INVAL_INODE, iov, 2); +} + +int fuse_lowlevel_notify_increment_epoch(struct fuse_session *se) +{ + struct iovec iov[1]; + + if (!se) + return -EINVAL; + + if (se->conn.proto_minor < 44) + return -ENOSYS; + + return send_notify_iov(se, FUSE_NOTIFY_INC_EPOCH, iov, 1); +} + +/** + * Notify parent attributes and the dentry matching parent/name + * + * Underlying base function for fuse_lowlevel_notify_inval_entry() and + * fuse_lowlevel_notify_expire_entry(). + * + * @warning + * Only checks if fuse_lowlevel_notify_inval_entry() is supported by + * the kernel. All other flags will fall back to + * fuse_lowlevel_notify_inval_entry() if not supported! + * DO THE PROPER CHECKS IN THE DERIVED FUNCTION! + * + * @param se the session object + * @param parent inode number + * @param name file name + * @param namelen strlen() of file name + * @param flags flags to control if the entry should be expired or invalidated + * @return zero for success, -errno for failure +*/ +static int fuse_lowlevel_notify_entry(struct fuse_session *se, fuse_ino_t parent, + const char *name, size_t namelen, + enum fuse_notify_entry_flags flags) +{ + struct fuse_notify_inval_entry_out outarg; + struct iovec iov[3]; + + if (!se) + return -EINVAL; + + if (se->conn.proto_minor < 12) + return -ENOSYS; + + outarg.parent = parent; + outarg.namelen = namelen; + outarg.flags = 0; + if (flags & FUSE_LL_EXPIRE_ONLY) + outarg.flags |= FUSE_EXPIRE_ONLY; + + iov[1].iov_base = &outarg; + iov[1].iov_len = sizeof(outarg); + iov[2].iov_base = (void *)name; + iov[2].iov_len = namelen + 1; + + return send_notify_iov(se, FUSE_NOTIFY_INVAL_ENTRY, iov, 3); +} + +int fuse_lowlevel_notify_inval_entry(struct fuse_session *se, fuse_ino_t parent, + const char *name, size_t namelen) +{ + return fuse_lowlevel_notify_entry(se, parent, name, namelen, FUSE_LL_INVALIDATE); +} + +int fuse_lowlevel_notify_expire_entry(struct fuse_session *se, fuse_ino_t parent, + const char *name, size_t namelen) +{ + if (!se) + return -EINVAL; + + if (!(se->conn.capable_ext & FUSE_CAP_EXPIRE_ONLY)) + return -ENOSYS; + + return fuse_lowlevel_notify_entry(se, parent, name, namelen, FUSE_LL_EXPIRE_ONLY); +} + + +int fuse_lowlevel_notify_delete(struct fuse_session *se, + fuse_ino_t parent, fuse_ino_t child, + const char *name, size_t namelen) +{ + struct fuse_notify_delete_out outarg; + struct iovec iov[3]; + + if (!se) + return -EINVAL; + + if (se->conn.proto_minor < 18) + return -ENOSYS; + + outarg.parent = parent; + outarg.child = child; + outarg.namelen = namelen; + outarg.padding = 0; + + iov[1].iov_base = &outarg; + iov[1].iov_len = sizeof(outarg); + iov[2].iov_base = (void *)name; + iov[2].iov_len = namelen + 1; + + return send_notify_iov(se, FUSE_NOTIFY_DELETE, iov, 3); +} + +int fuse_lowlevel_notify_store(struct fuse_session *se, fuse_ino_t ino, + off_t offset, struct fuse_bufvec *bufv, + enum fuse_buf_copy_flags flags) +{ + struct fuse_out_header out; + struct fuse_notify_store_out outarg; + struct iovec iov[3]; + size_t size = fuse_buf_size(bufv); + int res; + struct fuse_req *req = NULL; + + if (!se) + return -EINVAL; + + if (se->conn.proto_minor < 15) + return -ENOSYS; + + out.unique = 0; + out.error = FUSE_NOTIFY_STORE; + + outarg.nodeid = ino; + outarg.offset = offset; + outarg.size = size; + outarg.padding = 0; + + iov[0].iov_base = &out; + iov[0].iov_len = sizeof(out); + iov[1].iov_base = &outarg; + iov[1].iov_len = sizeof(outarg); + + res = fuse_send_data_iov(se, NULL, iov, 2, bufv, flags, req); + if (res > 0) + res = -res; + + return res; +} + +struct fuse_retrieve_req { + struct fuse_notify_req nreq; + void *cookie; +}; + +static void fuse_ll_retrieve_reply(struct fuse_notify_req *nreq, + fuse_req_t req, fuse_ino_t ino, + const void *inarg, + const struct fuse_buf *ibuf) +{ + struct fuse_session *se = req->se; + struct fuse_retrieve_req *rreq = + container_of(nreq, struct fuse_retrieve_req, nreq); + const struct fuse_notify_retrieve_in *arg = inarg; + struct fuse_bufvec bufv = { + .buf[0] = *ibuf, + .count = 1, + }; + + if (!(bufv.buf[0].flags & FUSE_BUF_IS_FD)) + bufv.buf[0].mem = PARAM(arg); + + bufv.buf[0].size -= sizeof(struct fuse_notify_retrieve_in); + + if (bufv.buf[0].size < arg->size) { + fuse_log(FUSE_LOG_ERR, "fuse: retrieve reply: buffer size too small\n"); + fuse_reply_none(req); + goto out; + } + bufv.buf[0].size = arg->size; + + if (se->op.retrieve_reply) { + se->op.retrieve_reply(req, rreq->cookie, ino, + arg->offset, &bufv); + } else { + fuse_reply_none(req); + } +out: + free(rreq); + if ((ibuf->flags & FUSE_BUF_IS_FD) && bufv.idx < bufv.count) + fuse_ll_clear_pipe(se); +} + +int fuse_lowlevel_notify_retrieve(struct fuse_session *se, fuse_ino_t ino, + size_t size, off_t offset, void *cookie) +{ + struct fuse_notify_retrieve_out outarg; + struct iovec iov[2]; + struct fuse_retrieve_req *rreq; + int err; + + if (!se) + return -EINVAL; + + if (se->conn.proto_minor < 15) + return -ENOSYS; + + rreq = malloc(sizeof(*rreq)); + if (rreq == NULL) + return -ENOMEM; + + pthread_mutex_lock(&se->lock); + rreq->cookie = cookie; + rreq->nreq.unique = se->notify_ctr++; + rreq->nreq.reply = fuse_ll_retrieve_reply; + list_add_nreq(&rreq->nreq, &se->notify_list); + pthread_mutex_unlock(&se->lock); + + outarg.notify_unique = rreq->nreq.unique; + outarg.nodeid = ino; + outarg.offset = offset; + outarg.size = size; + outarg.padding = 0; + + iov[1].iov_base = &outarg; + iov[1].iov_len = sizeof(outarg); + + err = send_notify_iov(se, FUSE_NOTIFY_RETRIEVE, iov, 2); + if (err) { + pthread_mutex_lock(&se->lock); + list_del_nreq(&rreq->nreq); + pthread_mutex_unlock(&se->lock); + free(rreq); + } + + return err; +} + +void *fuse_req_userdata(fuse_req_t req) +{ + return req->se->userdata; +} + +const struct fuse_ctx *fuse_req_ctx(fuse_req_t req) +{ + return &req->ctx; +} + +void fuse_req_interrupt_func(fuse_req_t req, fuse_interrupt_func_t func, + void *data) +{ + pthread_mutex_lock(&req->lock); + pthread_mutex_lock(&req->se->lock); + req->u.ni.func = func; + req->u.ni.data = data; + pthread_mutex_unlock(&req->se->lock); + if (req->interrupted && func) + func(req, data); + pthread_mutex_unlock(&req->lock); +} + +int fuse_req_interrupted(fuse_req_t req) +{ + int interrupted; + + pthread_mutex_lock(&req->se->lock); + interrupted = req->interrupted; + pthread_mutex_unlock(&req->se->lock); + + return interrupted; +} + +bool fuse_req_is_uring(fuse_req_t req) +{ + return req->flags.is_uring; +} + +#ifndef HAVE_URING +int fuse_req_get_payload(fuse_req_t req, char **payload, size_t *payload_sz, + void **mr) +{ + (void)req; + (void)payload; + (void)payload_sz; + (void)mr; + return -ENOTSUP; +} +#endif + +static struct { + void (*func)(fuse_req_t req, const fuse_ino_t node, const void *arg); + const char *name; +} fuse_ll_ops[] = { + [FUSE_LOOKUP] = { do_lookup, "LOOKUP" }, + [FUSE_FORGET] = { do_forget, "FORGET" }, + [FUSE_GETATTR] = { do_getattr, "GETATTR" }, + [FUSE_SETATTR] = { do_setattr, "SETATTR" }, + [FUSE_READLINK] = { do_readlink, "READLINK" }, + [FUSE_SYMLINK] = { do_symlink, "SYMLINK" }, + [FUSE_MKNOD] = { do_mknod, "MKNOD" }, + [FUSE_MKDIR] = { do_mkdir, "MKDIR" }, + [FUSE_UNLINK] = { do_unlink, "UNLINK" }, + [FUSE_RMDIR] = { do_rmdir, "RMDIR" }, + [FUSE_RENAME] = { do_rename, "RENAME" }, + [FUSE_LINK] = { do_link, "LINK" }, + [FUSE_OPEN] = { do_open, "OPEN" }, + [FUSE_READ] = { do_read, "READ" }, + [FUSE_WRITE] = { do_write, "WRITE" }, + [FUSE_STATFS] = { do_statfs, "STATFS" }, + [FUSE_RELEASE] = { do_release, "RELEASE" }, + [FUSE_FSYNC] = { do_fsync, "FSYNC" }, + [FUSE_SETXATTR] = { do_setxattr, "SETXATTR" }, + [FUSE_GETXATTR] = { do_getxattr, "GETXATTR" }, + [FUSE_LISTXATTR] = { do_listxattr, "LISTXATTR" }, + [FUSE_REMOVEXATTR] = { do_removexattr, "REMOVEXATTR" }, + [FUSE_FLUSH] = { do_flush, "FLUSH" }, + [FUSE_INIT] = { do_init, "INIT" }, + [FUSE_OPENDIR] = { do_opendir, "OPENDIR" }, + [FUSE_READDIR] = { do_readdir, "READDIR" }, + [FUSE_RELEASEDIR] = { do_releasedir, "RELEASEDIR" }, + [FUSE_FSYNCDIR] = { do_fsyncdir, "FSYNCDIR" }, + [FUSE_GETLK] = { do_getlk, "GETLK" }, + [FUSE_SETLK] = { do_setlk, "SETLK" }, + [FUSE_SETLKW] = { do_setlkw, "SETLKW" }, + [FUSE_ACCESS] = { do_access, "ACCESS" }, + [FUSE_CREATE] = { do_create, "CREATE" }, + [FUSE_TMPFILE] = { do_tmpfile, "TMPFILE" }, + [FUSE_INTERRUPT] = { do_interrupt, "INTERRUPT" }, + [FUSE_BMAP] = { do_bmap, "BMAP" }, + [FUSE_IOCTL] = { do_ioctl, "IOCTL" }, + [FUSE_POLL] = { do_poll, "POLL" }, + [FUSE_FALLOCATE] = { do_fallocate, "FALLOCATE" }, + [FUSE_DESTROY] = { do_destroy, "DESTROY" }, + [FUSE_NOTIFY_REPLY] = { (void *) 1, "NOTIFY_REPLY" }, + [FUSE_BATCH_FORGET] = { do_batch_forget, "BATCH_FORGET" }, + [FUSE_READDIRPLUS] = { do_readdirplus, "READDIRPLUS"}, + [FUSE_RENAME2] = { do_rename2, "RENAME2" }, + [FUSE_COPY_FILE_RANGE] = { do_copy_file_range, "COPY_FILE_RANGE" }, + [FUSE_COPY_FILE_RANGE_64] = { do_copy_file_range_64, "COPY_FILE_RANGE_64" }, + [FUSE_LSEEK] = { do_lseek, "LSEEK" }, + [FUSE_STATX] = { do_statx, "STATX" }, + [CUSE_INIT] = { cuse_lowlevel_init, "CUSE_INIT" }, +}; + +static struct { + void (*func)(fuse_req_t req, const fuse_ino_t ino, const void *op_in, + const void *op_payload); + const char *name; +} fuse_ll_ops2[] __attribute__((unused)) = { + [FUSE_LOOKUP] = { _do_lookup, "LOOKUP" }, + [FUSE_FORGET] = { _do_forget, "FORGET" }, + [FUSE_GETATTR] = { _do_getattr, "GETATTR" }, + [FUSE_SETATTR] = { _do_setattr, "SETATTR" }, + [FUSE_READLINK] = { _do_readlink, "READLINK" }, + [FUSE_SYMLINK] = { _do_symlink, "SYMLINK" }, + [FUSE_MKNOD] = { _do_mknod, "MKNOD" }, + [FUSE_MKDIR] = { _do_mkdir, "MKDIR" }, + [FUSE_UNLINK] = { _do_unlink, "UNLINK" }, + [FUSE_RMDIR] = { _do_rmdir, "RMDIR" }, + [FUSE_RENAME] = { _do_rename, "RENAME" }, + [FUSE_LINK] = { _do_link, "LINK" }, + [FUSE_OPEN] = { _do_open, "OPEN" }, + [FUSE_READ] = { _do_read, "READ" }, + [FUSE_WRITE] = { _do_write, "WRITE" }, + [FUSE_STATFS] = { _do_statfs, "STATFS" }, + [FUSE_RELEASE] = { _do_release, "RELEASE" }, + [FUSE_FSYNC] = { _do_fsync, "FSYNC" }, + [FUSE_SETXATTR] = { _do_setxattr, "SETXATTR" }, + [FUSE_GETXATTR] = { _do_getxattr, "GETXATTR" }, + [FUSE_LISTXATTR] = { _do_listxattr, "LISTXATTR" }, + [FUSE_REMOVEXATTR] = { _do_removexattr, "REMOVEXATTR" }, + [FUSE_FLUSH] = { _do_flush, "FLUSH" }, + [FUSE_INIT] = { _do_init, "INIT" }, + [FUSE_OPENDIR] = { _do_opendir, "OPENDIR" }, + [FUSE_READDIR] = { _do_readdir, "READDIR" }, + [FUSE_RELEASEDIR] = { _do_releasedir, "RELEASEDIR" }, + [FUSE_FSYNCDIR] = { _do_fsyncdir, "FSYNCDIR" }, + [FUSE_GETLK] = { _do_getlk, "GETLK" }, + [FUSE_SETLK] = { _do_setlk, "SETLK" }, + [FUSE_SETLKW] = { _do_setlkw, "SETLKW" }, + [FUSE_ACCESS] = { _do_access, "ACCESS" }, + [FUSE_CREATE] = { _do_create, "CREATE" }, + [FUSE_TMPFILE] = { _do_tmpfile, "TMPFILE" }, + [FUSE_INTERRUPT] = { _do_interrupt, "INTERRUPT" }, + [FUSE_BMAP] = { _do_bmap, "BMAP" }, + [FUSE_IOCTL] = { _do_ioctl, "IOCTL" }, + [FUSE_POLL] = { _do_poll, "POLL" }, + [FUSE_FALLOCATE] = { _do_fallocate, "FALLOCATE" }, + [FUSE_DESTROY] = { _do_destroy, "DESTROY" }, + [FUSE_NOTIFY_REPLY] = { (void *)1, "NOTIFY_REPLY" }, + [FUSE_BATCH_FORGET] = { _do_batch_forget, "BATCH_FORGET" }, + [FUSE_READDIRPLUS] = { _do_readdirplus, "READDIRPLUS" }, + [FUSE_RENAME2] = { _do_rename2, "RENAME2" }, + [FUSE_COPY_FILE_RANGE] = { _do_copy_file_range, "COPY_FILE_RANGE" }, + [FUSE_COPY_FILE_RANGE_64] = { _do_copy_file_range_64, "COPY_FILE_RANGE_64" }, + [FUSE_LSEEK] = { _do_lseek, "LSEEK" }, + [FUSE_STATX] = { _do_statx, "STATX" }, + [CUSE_INIT] = { _cuse_lowlevel_init, "CUSE_INIT" }, +}; + +/* + * For ABI compatibility we cannot allow higher values than CUSE_INIT. + * Without ABI compatibility we could use the size of the array. + * #define FUSE_MAXOP (sizeof(fuse_ll_ops) / sizeof(fuse_ll_ops[0])) + */ +#define FUSE_MAXOP (CUSE_INIT + 1) + + +/** + * + * @return 0 if sanity is ok, error otherwise + */ +static inline int +fuse_req_opcode_sanity_ok(struct fuse_session *se, enum fuse_opcode in_op) +{ + int err = EIO; + + if (!se->got_init) { + enum fuse_opcode expected; + + expected = se->cuse_data ? CUSE_INIT : FUSE_INIT; + if (in_op != expected) + return err; + } else if (in_op == FUSE_INIT || in_op == CUSE_INIT) + return err; + + return 0; +} + +static inline void +fuse_session_in2req(struct fuse_req *req, struct fuse_in_header *in) +{ + req->unique = in->unique; + req->ctx.uid = in->uid; + req->ctx.gid = in->gid; + req->ctx.pid = in->pid; +} + +/** + * Implement -o allow_root + */ +static inline int +fuse_req_check_allow_root(struct fuse_session *se, enum fuse_opcode in_op, + uid_t in_uid) +{ + int err = EACCES; + + if (se->deny_others && in_uid != se->owner && in_uid != 0 && + in_op != FUSE_INIT && in_op != FUSE_READ && + in_op != FUSE_WRITE && in_op != FUSE_FSYNC && + in_op != FUSE_RELEASE && in_op != FUSE_READDIR && + in_op != FUSE_FSYNCDIR && in_op != FUSE_RELEASEDIR && + in_op != FUSE_NOTIFY_REPLY && + in_op != FUSE_READDIRPLUS) + return err; + + return 0; +} + +static const char *opname(enum fuse_opcode opcode) +{ + if (opcode >= FUSE_MAXOP || !fuse_ll_ops[opcode].name) + return "???"; + else + return fuse_ll_ops[opcode].name; +} + +static int fuse_ll_copy_from_pipe(struct fuse_bufvec *dst, + struct fuse_bufvec *src) +{ + ssize_t res = fuse_buf_copy(dst, src, 0); + if (res < 0) { + fuse_log(FUSE_LOG_ERR, "fuse: copy from pipe: %s\n", strerror(-res)); + return res; + } + if ((size_t)res < fuse_buf_size(dst)) { + fuse_log(FUSE_LOG_ERR, "fuse: copy from pipe: short read\n"); + return -1; + } + return 0; +} + +void fuse_session_process_buf(struct fuse_session *se, + const struct fuse_buf *buf) +{ + fuse_session_process_buf_internal(se, buf, NULL); +} + +/* libfuse internal handler */ +void fuse_session_process_buf_internal(struct fuse_session *se, + const struct fuse_buf *buf, struct fuse_chan *ch) +{ + const size_t write_header_size = sizeof(struct fuse_in_header) + + sizeof(struct fuse_write_in); + struct fuse_bufvec bufv = { .buf[0] = *buf, .count = 1 }; + struct fuse_bufvec tmpbuf = FUSE_BUFVEC_INIT(write_header_size); + struct fuse_in_header *in; + const void *inarg; + struct fuse_req *req; + void *mbuf = NULL; + int err; + int res; + + if (buf->flags & FUSE_BUF_IS_FD) { + if (buf->size < tmpbuf.buf[0].size) + tmpbuf.buf[0].size = buf->size; + + mbuf = malloc(tmpbuf.buf[0].size); + if (mbuf == NULL) { + fuse_log(FUSE_LOG_ERR, "fuse: failed to allocate header\n"); + goto clear_pipe; + } + tmpbuf.buf[0].mem = mbuf; + + res = fuse_ll_copy_from_pipe(&tmpbuf, &bufv); + if (res < 0) + goto clear_pipe; + + in = mbuf; + } else { + in = buf->mem; + } + + trace_request_process(in->opcode, in->unique); + + if (se->debug) { + fuse_log(FUSE_LOG_DEBUG, + "dev unique: %llu, opcode: %s (%i), nodeid: %llu, insize: %zu, pid: %u\n", + (unsigned long long) in->unique, + opname((enum fuse_opcode) in->opcode), in->opcode, + (unsigned long long) in->nodeid, buf->size, in->pid); + } + + req = fuse_ll_alloc_req(se); + if (req == NULL) { + struct fuse_out_header out = { + .unique = in->unique, + .error = -ENOMEM, + }; + struct iovec iov = { + .iov_base = &out, + .iov_len = sizeof(struct fuse_out_header), + }; + + fuse_send_msg(se, ch, &iov, 1, NULL); + goto clear_pipe; + } + + fuse_session_in2req(req, in); + req->ch = ch ? fuse_chan_get(ch) : NULL; + + err = fuse_req_opcode_sanity_ok(se, in->opcode); + if (err) + goto reply_err; + + err = fuse_req_check_allow_root(se, in->opcode, in->uid); + if (err) + goto reply_err; + + err = ENOSYS; + if (in->opcode >= FUSE_MAXOP || !fuse_ll_ops[in->opcode].func) + goto reply_err; + /* Do not process interrupt request */ + if (se->conn.no_interrupt && in->opcode == FUSE_INTERRUPT) { + if (se->debug) + fuse_log(FUSE_LOG_DEBUG, "FUSE_INTERRUPT: reply to kernel to disable interrupt\n"); + goto reply_err; + } + if (!se->conn.no_interrupt && in->opcode != FUSE_INTERRUPT) { + struct fuse_req *intr; + pthread_mutex_lock(&se->lock); + intr = check_interrupt(se, req); + list_add_req(req, &se->list); + pthread_mutex_unlock(&se->lock); + if (intr) + fuse_reply_err(intr, EAGAIN); + } + + if ((buf->flags & FUSE_BUF_IS_FD) && write_header_size < buf->size && + (in->opcode != FUSE_WRITE || !se->op.write_buf) && + in->opcode != FUSE_NOTIFY_REPLY) { + void *newmbuf; + + err = ENOMEM; + newmbuf = realloc(mbuf, buf->size); + if (newmbuf == NULL) + goto reply_err; + mbuf = newmbuf; + + tmpbuf = FUSE_BUFVEC_INIT(buf->size - write_header_size); + tmpbuf.buf[0].mem = (char *)mbuf + write_header_size; + + res = fuse_ll_copy_from_pipe(&tmpbuf, &bufv); + err = -res; + if (res < 0) + goto reply_err; + + in = mbuf; + } + + inarg = (void *) &in[1]; + if (in->opcode == FUSE_WRITE && se->op.write_buf) + do_write_buf(req, in->nodeid, inarg, buf); + else if (in->opcode == FUSE_NOTIFY_REPLY) + do_notify_reply(req, in->nodeid, inarg, buf); + else + fuse_ll_ops[in->opcode].func(req, in->nodeid, inarg); + +out_free: + free(mbuf); + return; + +reply_err: + fuse_reply_err(req, err); +clear_pipe: + if (buf->flags & FUSE_BUF_IS_FD) + fuse_ll_clear_pipe(se); + goto out_free; +} + +void fuse_session_process_uring_cqe(struct fuse_session *se, + struct fuse_req *req, + struct fuse_in_header *in, void *op_in, + void *op_payload, size_t payload_len) +{ + int err; + + fuse_session_in2req(req, in); + + err = fuse_req_opcode_sanity_ok(se, in->opcode); + if (err) + goto reply_err; + + err = fuse_req_check_allow_root(se, in->opcode, in->uid); + if (err) + goto reply_err; + + err = ENOSYS; + if (in->opcode >= FUSE_MAXOP || !fuse_ll_ops2[in->opcode].func) + goto reply_err; + + if (se->debug) { + fuse_log( + FUSE_LOG_DEBUG, + "cqe unique: %llu, opcode: %s (%i), nodeid: %llu, insize: %zu, pid: %u\n", + (unsigned long long)in->unique, + opname((enum fuse_opcode)in->opcode), in->opcode, + (unsigned long long)in->nodeid, payload_len, in->pid); + } + + if (in->opcode == FUSE_WRITE && se->op.write_buf) { + struct fuse_bufvec bufv = { + .buf[0] = { .size = payload_len, + .flags = 0, + .mem = op_payload }, + .count = 1, + }; + _do_write_buf(req, in->nodeid, op_in, &bufv); + } else if (in->opcode == FUSE_NOTIFY_REPLY) { + struct fuse_buf buf = { .size = payload_len, + .mem = op_payload }; + _do_notify_reply(req, in->nodeid, op_payload, &buf); + } else { + fuse_ll_ops2[in->opcode].func(req, in->nodeid, op_in, + op_payload); + } + + return; + +reply_err: + fuse_reply_err(req, err); +} + +#define LL_OPTION(n,o,v) \ + { n, offsetof(struct fuse_session, o), v } + +static const struct fuse_opt fuse_ll_opts[] = { + LL_OPTION("debug", debug, 1), + LL_OPTION("-d", debug, 1), + LL_OPTION("--debug", debug, 1), + LL_OPTION("allow_root", deny_others, 1), + LL_OPTION("io_uring", uring.enable, 1), + LL_OPTION("io_uring_q_depth=%u", uring.q_depth, -1), + FUSE_OPT_END +}; + +void fuse_lowlevel_version(void) +{ + printf("using FUSE kernel interface version %i.%i\n", + FUSE_KERNEL_VERSION, FUSE_KERNEL_MINOR_VERSION); + fuse_mount_version(); +} + +void fuse_lowlevel_help(void) +{ + /* These are not all options, but the ones that are + potentially of interest to an end-user */ + printf( +" -o allow_other allow access by all users\n" +" -o allow_root allow access by root\n" +" -o auto_unmount auto unmount on process termination\n" +" -o io_uring enable io-uring\n" +" -o io_uring_q_depth= io-uring queue depth\n" +); +} + +void fuse_session_destroy(struct fuse_session *se) +{ + struct fuse_ll_pipe *llp; + + if (se->got_init && !se->got_destroy) { + if (se->op.destroy) + se->op.destroy(se->userdata); + } + llp = pthread_getspecific(se->pipe_key); + if (llp != NULL) + fuse_ll_pipe_free(llp); + pthread_key_delete(se->pipe_key); + sem_destroy(&se->mt_finish); + pthread_mutex_destroy(&se->mt_lock); + pthread_mutex_destroy(&se->lock); + free(se->cuse_data); + if (se->fd != -1) + close(se->fd); + if (se->io != NULL) + free(se->io); + destroy_mount_opts(se->mo); + free(atomic_exchange(&se->mountpoint, NULL)); + free(se); +} + + +static void fuse_ll_pipe_destructor(void *data) +{ + struct fuse_ll_pipe *llp = data; + fuse_ll_pipe_free(llp); +} + +void fuse_buf_free(struct fuse_buf *buf) +{ + if (buf->mem == NULL) + return; + + size_t write_header_sz = + sizeof(struct fuse_in_header) + sizeof(struct fuse_write_in); + + char *ptr = (char *)buf->mem - pagesize + write_header_sz; + free(ptr); + buf->mem = NULL; +} + +/* + * This is used to allocate buffers that hold fuse requests + */ +static void *buf_alloc(size_t size, bool internal) +{ + /* + * For libfuse internal caller add in alignment. That cannot be done + * for an external caller, as it is not guaranteed that the external + * caller frees the raw pointer. + */ + if (internal) { + size_t write_header_sz = sizeof(struct fuse_in_header) + + sizeof(struct fuse_write_in); + size_t new_size = ROUND_UP(size + write_header_sz, pagesize); + + char *buf = aligned_alloc(pagesize, new_size); + if (buf == NULL) + return NULL; + + buf += pagesize - write_header_sz; + + return buf; + } else { + return malloc(size); + } +} + +/* + *@param internal true if called from libfuse internal code + */ +static int _fuse_session_receive_buf(struct fuse_session *se, + struct fuse_buf *buf, struct fuse_chan *ch, + bool internal) +{ + int err; + ssize_t res; + size_t bufsize; +#ifdef HAVE_SPLICE + struct fuse_ll_pipe *llp; + struct fuse_buf tmpbuf; + +pipe_retry: + bufsize = se->bufsize; + + if (se->conn.proto_minor < 14 || + !(se->conn.want_ext & FUSE_CAP_SPLICE_READ)) + goto fallback; + + llp = fuse_ll_get_pipe(se); + if (llp == NULL) + goto fallback; + + if (llp->size < bufsize) { + if (llp->can_grow) { + res = fcntl(llp->pipe[0], F_SETPIPE_SZ, bufsize); + if (res == -1) { + llp->can_grow = 0; + res = grow_pipe_to_max(llp->pipe[0]); + if (res > 0) + llp->size = res; + fuse_ll_clear_pipe(se); + goto fallback; + } + llp->size = res; + } + if (llp->size < bufsize) { + fuse_ll_clear_pipe(se); + goto fallback; + } + } + + if (se->io != NULL && se->io->splice_receive != NULL) { + res = se->io->splice_receive(ch ? ch->fd : se->fd, NULL, + llp->pipe[1], NULL, bufsize, 0, + se->userdata); + } else { + res = splice(ch ? ch->fd : se->fd, NULL, llp->pipe[1], NULL, + bufsize, 0); + } + err = errno; + trace_request_receive(err); + + if (fuse_session_exited(se)) + return 0; + + if (res == -1) { + if (err == ENODEV) { + /* Filesystem was unmounted, or connection was aborted + via /sys/fs/fuse/connections */ + fuse_session_exit(se); + return 0; + } + + /* FUSE_INIT might have increased the required bufsize */ + if (err == EINVAL && bufsize < se->bufsize) { + fuse_ll_clear_pipe(se); + goto pipe_retry; + } + + if (err != EINTR && err != EAGAIN) + perror("fuse: splice from device"); + return -err; + } + + if (res < sizeof(struct fuse_in_header)) { + fuse_log(FUSE_LOG_ERR, "short splice from fuse device\n"); + return -EIO; + } + + tmpbuf = (struct fuse_buf){ + .size = res, + .flags = FUSE_BUF_IS_FD, + .fd = llp->pipe[0], + }; + + /* + * Don't bother with zero copy for small requests. + * fuse_loop_mt() needs to check for FORGET so this more than + * just an optimization. + */ + if (res < sizeof(struct fuse_in_header) + sizeof(struct fuse_write_in) + + pagesize) { + struct fuse_bufvec src = { .buf[0] = tmpbuf, .count = 1 }; + struct fuse_bufvec dst = { .count = 1 }; + + if (!buf->mem) { + buf->mem = buf_alloc(bufsize, internal); + if (!buf->mem) { + fuse_log( + FUSE_LOG_ERR, + "fuse: failed to allocate read buffer\n"); + return -ENOMEM; + } + buf->mem_size = bufsize; + } + buf->size = bufsize; + buf->flags = 0; + dst.buf[0] = *buf; + + res = fuse_buf_copy(&dst, &src, 0); + if (res < 0) { + fuse_log(FUSE_LOG_ERR, "fuse: copy from pipe: %s\n", + strerror(-res)); + fuse_ll_clear_pipe(se); + return res; + } + if (res < tmpbuf.size) { + fuse_log(FUSE_LOG_ERR, + "fuse: copy from pipe: short read\n"); + fuse_ll_clear_pipe(se); + return -EIO; + } + assert(res == tmpbuf.size); + + } else { + /* Don't overwrite buf->mem, as that would cause a leak */ + buf->fd = tmpbuf.fd; + buf->flags = tmpbuf.flags; + } + buf->size = tmpbuf.size; + + return res; + +fallback: +#endif + bufsize = internal ? buf->mem_size : se->bufsize; + if (!buf->mem) { + bufsize = se->bufsize; /* might have changed */ + buf->mem = buf_alloc(bufsize, internal); + if (!buf->mem) { + fuse_log(FUSE_LOG_ERR, + "fuse: failed to allocate read buffer\n"); + return -ENOMEM; + } + + if (internal) + buf->mem_size = bufsize; + } + +restart: + if (se->io != NULL) { + /* se->io->read is never NULL if se->io is not NULL as + specified by fuse_session_custom_io()*/ + res = se->io->read(ch ? ch->fd : se->fd, buf->mem, bufsize, + se->userdata); + } else { + res = read(ch ? ch->fd : se->fd, buf->mem, bufsize); + } + err = errno; + trace_request_receive(err); + + if (fuse_session_exited(se)) + return 0; + if (res == -1) { + if (err == EINVAL && internal && se->bufsize > bufsize) { + /* FUSE_INIT might have increased the required bufsize */ + bufsize = se->bufsize; + void *newbuf = buf_alloc(bufsize, internal); + if (!newbuf) { + fuse_log( + FUSE_LOG_ERR, + "fuse: failed to (re)allocate read buffer\n"); + return -ENOMEM; + } + fuse_buf_free(buf); + buf->mem = newbuf; + buf->mem_size = bufsize; + goto restart; + } + + /* ENOENT means the operation was interrupted, it's safe + to restart */ + if (err == ENOENT) + goto restart; + + if (err == ENODEV) { + /* Filesystem was unmounted, or connection was aborted + via /sys/fs/fuse/connections */ + fuse_session_exit(se); + return 0; + } + /* Errors occurring during normal operation: EINTR (read + interrupted), EAGAIN (nonblocking I/O), ENODEV (filesystem + umounted) */ + if (err != EINTR && err != EAGAIN) + perror("fuse: reading device"); + return -err; + } + if ((size_t)res < sizeof(struct fuse_in_header)) { + fuse_log(FUSE_LOG_ERR, "short read on fuse device\n"); + return -EIO; + } + + buf->size = res; + + return res; +} + +int fuse_session_receive_buf(struct fuse_session *se, struct fuse_buf *buf) +{ + return _fuse_session_receive_buf(se, buf, NULL, false); +} + +/* libfuse internal handler */ +int fuse_session_receive_buf_internal(struct fuse_session *se, + struct fuse_buf *buf, + struct fuse_chan *ch) +{ + /* + * if run internally thread buffers are from libfuse - we can + * reallocate them + */ + if (unlikely(!se->got_init) && !se->buf_reallocable) + se->buf_reallocable = true; + + return _fuse_session_receive_buf(se, buf, ch, true); +} + +struct fuse_session * +fuse_session_new_versioned(struct fuse_args *args, + const struct fuse_lowlevel_ops *op, size_t op_size, + struct libfuse_version *version, void *userdata) +{ + int err; + struct fuse_session *se; + struct mount_opts *mo; + + if (op == NULL || op_size == 0) { + fuse_log(FUSE_LOG_ERR, + "fuse: warning: empty op list passed to fuse_session_new()\n"); + return NULL; + } + + if (version == NULL) { + fuse_log(FUSE_LOG_ERR, "fuse: warning: version not passed to fuse_session_new()\n"); + return NULL; + } + + if (sizeof(struct fuse_lowlevel_ops) < op_size) { + fuse_log(FUSE_LOG_ERR, "fuse: warning: library too old, some operations may not work\n"); + op_size = sizeof(struct fuse_lowlevel_ops); + } + + if (args == NULL || args->argc == 0) { + fuse_log(FUSE_LOG_ERR, "fuse: empty argv passed to fuse_session_new().\n"); + return NULL; + } + + se = (struct fuse_session *) calloc(1, sizeof(struct fuse_session)); + if (se == NULL) { + fuse_log(FUSE_LOG_ERR, "fuse: failed to allocate fuse object\n"); + goto out1; + } + se->fd = -1; + se->conn.max_write = FUSE_DEFAULT_MAX_PAGES_LIMIT * getpagesize(); + se->bufsize = se->conn.max_write + FUSE_BUFFER_HEADER_SIZE; + se->conn.max_readahead = UINT_MAX; + + /* + * Allow overriding with env, mostly to avoid the need to modify + * all tests. I.e. to test with and without io-uring being enabled. + */ + se->uring.enable = getenv("FUSE_URING_ENABLE") ? + atoi(getenv("FUSE_URING_ENABLE")) : + SESSION_DEF_URING_ENABLE; + se->uring.q_depth = getenv("FUSE_URING_QUEUE_DEPTH") ? + atoi(getenv("FUSE_URING_QUEUE_DEPTH")) : + SESSION_DEF_URING_Q_DEPTH; + + /* Parse options */ + if(fuse_opt_parse(args, se, fuse_ll_opts, NULL) == -1) + goto out2; + if(se->deny_others) { + /* Allowing access only by root is done by instructing + * kernel to allow access by everyone, and then restricting + * access to root and mountpoint owner in libfuse. + */ + // We may be adding the option a second time, but + // that doesn't hurt. + if(fuse_opt_add_arg(args, "-oallow_other") == -1) + goto out2; + } + mo = parse_mount_opts(args); + if (mo == NULL) + goto out3; + + if(args->argc == 1 && + args->argv[0][0] == '-') { + fuse_log(FUSE_LOG_ERR, "fuse: warning: argv[0] looks like an option, but " + "will be ignored\n"); + } else if (args->argc != 1) { + int i; + fuse_log(FUSE_LOG_ERR, "fuse: unknown option(s): `"); + for(i = 1; i < args->argc-1; i++) + fuse_log(FUSE_LOG_ERR, "%s ", args->argv[i]); + fuse_log(FUSE_LOG_ERR, "%s'\n", args->argv[i]); + goto out4; + } + + if (se->debug) + fuse_log(FUSE_LOG_DEBUG, "FUSE library version: %s\n", PACKAGE_VERSION); + + list_init_req(&se->list); + list_init_req(&se->interrupts); + list_init_nreq(&se->notify_list); + se->notify_ctr = 1; + pthread_mutex_init(&se->lock, NULL); + sem_init(&se->mt_finish, 0, 0); + pthread_mutex_init(&se->mt_lock, NULL); + + err = pthread_key_create(&se->pipe_key, fuse_ll_pipe_destructor); + if (err) { + fuse_log(FUSE_LOG_ERR, "fuse: failed to create thread specific key: %s\n", + strerror(err)); + goto out5; + } + + memcpy(&se->op, op, op_size); + se->owner = getuid(); + se->userdata = userdata; + + se->mo = mo; + + /* Fuse server application should pass the version it was compiled + * against and pass it. If a libfuse version accidentally introduces an + * ABI incompatibility, it might be possible to 'fix' that at run time, + * by checking the version numbers. + */ + se->version = *version; + + return se; + +out5: + sem_destroy(&se->mt_finish); + pthread_mutex_destroy(&se->mt_lock); + pthread_mutex_destroy(&se->lock); +out4: + fuse_opt_free_args(args); +out3: + if (mo != NULL) + destroy_mount_opts(mo); +out2: + free(se); +out1: + return NULL; +} + +struct fuse_session *fuse_session_new_30(struct fuse_args *args, + const struct fuse_lowlevel_ops *op, + size_t op_size, void *userdata); +struct fuse_session *fuse_session_new_30(struct fuse_args *args, + const struct fuse_lowlevel_ops *op, + size_t op_size, + void *userdata) +{ + struct fuse_lowlevel_ops null_ops = { 0 }; + + /* unknown version */ + struct libfuse_version version = { 0 }; + + /* + * This function is the ABI interface function from fuse_session_new in + * compat.c. External libraries like "fuser" might call fuse_session_new() + * with NULL ops and then pass that session to fuse_session_mount(). + * The actual FUSE operations are handled in their own library. + */ + if (op == NULL) { + op = &null_ops; + op_size = sizeof(null_ops); + } + + return fuse_session_new_versioned(args, op, op_size, &version, + userdata); +} + +FUSE_SYMVER("fuse_session_custom_io_317", "fuse_session_custom_io@@FUSE_3.17") +int fuse_session_custom_io_317(struct fuse_session *se, + const struct fuse_custom_io *io, size_t op_size, int fd) +{ +#ifndef HAVE_CUSTOM_IO + (void)se; + (void)io; + (void)op_size; + (void)fd; + fuse_log(FUSE_LOG_ERR, "fuse: custom io is not enabled in this build\n"); + return -ENOTSUP; +#else + if (sizeof(struct fuse_custom_io) < op_size) { + fuse_log(FUSE_LOG_ERR, "fuse: warning: library too old, some operations may not work\n"); + op_size = sizeof(struct fuse_custom_io); + } + + if (fd < 0) { + fuse_log(FUSE_LOG_ERR, "Invalid file descriptor value %d passed to " + "fuse_session_custom_io()\n", fd); + return -EBADF; + } + if (io == NULL) { + fuse_log(FUSE_LOG_ERR, "No custom IO passed to " + "fuse_session_custom_io()\n"); + return -EINVAL; + } else if (io->read == NULL || io->writev == NULL) { + /* If the user provides their own file descriptor, we can't + guarantee that the default behavior of the io operations made + in libfuse will function properly. Therefore, we enforce the + user to implement these io operations when using custom io. */ + fuse_log(FUSE_LOG_ERR, "io passed to fuse_session_custom_io() must " + "implement both io->read() and io->writev\n"); + return -EINVAL; + } + + se->io = calloc(1, sizeof(struct fuse_custom_io)); + if (se->io == NULL) { + fuse_log(FUSE_LOG_ERR, "Failed to allocate memory for custom io. " + "Error: %s\n", strerror(errno)); + return -errno; + } + + se->fd = fd; + memcpy(se->io, io, op_size); + return 0; +#endif +} + +int fuse_session_custom_io_30(struct fuse_session *se, + const struct fuse_custom_io *io, int fd); +FUSE_SYMVER("fuse_session_custom_io_30", "fuse_session_custom_io@FUSE_3.0") +int fuse_session_custom_io_30(struct fuse_session *se, + const struct fuse_custom_io *io, int fd) +{ + return fuse_session_custom_io_317(se, io, + offsetof(struct fuse_custom_io, clone_fd), fd); +} + +int fuse_session_mount(struct fuse_session *se, const char *_mountpoint) +{ + int fd; + char *mountpoint; + + if (_mountpoint == NULL) { + fuse_log(FUSE_LOG_ERR, "Invalid null-ptr mountpoint!\n"); + return -1; + } + + mountpoint = strdup(_mountpoint); + if (mountpoint == NULL) { + fuse_log(FUSE_LOG_ERR, "Failed to allocate memory for mountpoint. Error: %s\n", + strerror(errno)); + return -1; + } + + /* + * Make sure file descriptors 0, 1 and 2 are open, otherwise chaos + * would ensue. + */ + do { + fd = open("/dev/null", O_RDWR); + if (fd > 2) + close(fd); + } while (fd >= 0 && fd <= 2); + + /* + * To allow FUSE daemons to run without privileges, the caller may open + * /dev/fuse before launching the file system and pass on the file + * descriptor by specifying /dev/fd/N as the mount point. Note that the + * parent process takes care of performing the mount in this case. + */ + fd = fuse_mnt_parse_fuse_fd(mountpoint); + if (fd != -1) { + if (fcntl(fd, F_GETFD) == -1) { + fuse_log(FUSE_LOG_ERR, + "fuse: Invalid file descriptor /dev/fd/%u\n", + fd); + goto error_out; + } + goto out; + } + + /* Open channel */ + fd = fuse_kern_mount(mountpoint, se->mo); + if (fd == -1) + goto error_out; + +out: + se->fd = fd; + se->mountpoint = mountpoint; + + return 0; + +error_out: + free(mountpoint); + return -1; +} + +int fuse_session_fd(struct fuse_session *se) +{ + return se->fd; +} + +void fuse_session_unmount(struct fuse_session *se) +{ + if (se->mountpoint != NULL) { + char *mountpoint = atomic_exchange(&se->mountpoint, NULL); + + fuse_kern_unmount(mountpoint, se->fd); + se->fd = -1; + free(mountpoint); + } +} + +#ifdef linux +int fuse_req_getgroups(fuse_req_t req, int size, gid_t list[]) +{ + char *buf; + size_t bufsize = 1024; + char path[128]; + int ret; + int fd; + unsigned long pid = req->ctx.pid; + char *s; + + sprintf(path, "/proc/%lu/task/%lu/status", pid, pid); + +retry: + buf = malloc(bufsize); + if (buf == NULL) + return -ENOMEM; + + ret = -EIO; + fd = open(path, O_RDONLY); + if (fd == -1) + goto out_free; + + ret = read(fd, buf, bufsize); + close(fd); + if (ret < 0) { + ret = -EIO; + goto out_free; + } + + if ((size_t)ret == bufsize) { + free(buf); + bufsize *= 4; + goto retry; + } + + buf[ret] = '\0'; + ret = -EIO; + s = strstr(buf, "\nGroups:"); + if (s == NULL) + goto out_free; + + s += 8; + ret = 0; + while (1) { + char *end; + unsigned long val = strtoul(s, &end, 0); + if (end == s) + break; + + s = end; + if (ret < size) + list[ret] = val; + ret++; + } + +out_free: + free(buf); + return ret; +} +#else /* linux */ +/* + * This is currently not implemented on other than Linux... + */ +int fuse_req_getgroups(fuse_req_t req, int size, gid_t list[]) +{ + (void) req; (void) size; (void) list; + return -ENOSYS; +} +#endif + +/* Prevent spurious data race warning - we don't care + * about races for this flag */ +__attribute__((no_sanitize_thread)) +void fuse_session_exit(struct fuse_session *se) +{ + atomic_store_explicit(&se->mt_exited, 1, memory_order_relaxed); + sem_post(&se->mt_finish); +} + +__attribute__((no_sanitize_thread)) +void fuse_session_reset(struct fuse_session *se) +{ + se->mt_exited = false; + se->error = 0; +} + +__attribute__((no_sanitize_thread)) +int fuse_session_exited(struct fuse_session *se) +{ + bool exited = + atomic_load_explicit(&se->mt_exited, memory_order_relaxed); + + return exited ? 1 : 0; +} diff --git a/lib/fuse_misc.h b/lib/fuse_misc.h new file mode 100644 index 0000000..1452593 --- /dev/null +++ b/lib/fuse_misc.h @@ -0,0 +1,51 @@ +/* + FUSE: Filesystem in Userspace + Copyright (C) 2001-2007 Miklos Szeredi + + This program can be distributed under the terms of the GNU LGPLv2. + See the file LGPL2.txt +*/ + +#include + +/* + Versioned symbols cannot be used in some cases because it + - not supported on MacOSX (in MachO binary format) + + Note: "@@" denotes the default symbol, "@" is binary a compat version. + +*/ +#ifdef LIBFUSE_BUILT_WITH_VERSIONED_SYMBOLS +# if HAVE_SYMVER_ATTRIBUTE +# define FUSE_SYMVER(sym1, sym2) __attribute__ ((symver (sym2))) +# else +# define FUSE_SYMVER(sym1, sym2) __asm__("\t.symver " sym1 "," sym2); +# endif +#else +#define FUSE_SYMVER(sym1, sym2) +#endif + +#ifdef HAVE_STRUCT_STAT_ST_ATIM +/* Linux */ +#define ST_ATIM_NSEC(stbuf) ((stbuf)->st_atim.tv_nsec) +#define ST_CTIM_NSEC(stbuf) ((stbuf)->st_ctim.tv_nsec) +#define ST_MTIM_NSEC(stbuf) ((stbuf)->st_mtim.tv_nsec) +#define ST_ATIM_NSEC_SET(stbuf, val) (stbuf)->st_atim.tv_nsec = (val) +#define ST_CTIM_NSEC_SET(stbuf, val) (stbuf)->st_ctim.tv_nsec = (val) +#define ST_MTIM_NSEC_SET(stbuf, val) (stbuf)->st_mtim.tv_nsec = (val) +#elif defined(HAVE_STRUCT_STAT_ST_ATIMESPEC) +/* FreeBSD */ +#define ST_ATIM_NSEC(stbuf) ((stbuf)->st_atimespec.tv_nsec) +#define ST_CTIM_NSEC(stbuf) ((stbuf)->st_ctimespec.tv_nsec) +#define ST_MTIM_NSEC(stbuf) ((stbuf)->st_mtimespec.tv_nsec) +#define ST_ATIM_NSEC_SET(stbuf, val) (stbuf)->st_atimespec.tv_nsec = (val) +#define ST_CTIM_NSEC_SET(stbuf, val) (stbuf)->st_ctimespec.tv_nsec = (val) +#define ST_MTIM_NSEC_SET(stbuf, val) (stbuf)->st_mtimespec.tv_nsec = (val) +#else +#define ST_ATIM_NSEC(stbuf) 0 +#define ST_CTIM_NSEC(stbuf) 0 +#define ST_MTIM_NSEC(stbuf) 0 +#define ST_ATIM_NSEC_SET(stbuf, val) do { } while (0) +#define ST_CTIM_NSEC_SET(stbuf, val) do { } while (0) +#define ST_MTIM_NSEC_SET(stbuf, val) do { } while (0) +#endif diff --git a/lib/fuse_opt.c b/lib/fuse_opt.c new file mode 100644 index 0000000..046e577 --- /dev/null +++ b/lib/fuse_opt.c @@ -0,0 +1,423 @@ +/* + FUSE: Filesystem in Userspace + Copyright (C) 2001-2007 Miklos Szeredi + + Implementation of option parsing routines (dealing with `struct + fuse_args`). + + This program can be distributed under the terms of the GNU LGPLv2. + See the file LGPL2.txt +*/ + +#include "fuse_config.h" +#include "fuse_i.h" +#include "fuse_opt.h" +#include "fuse_misc.h" + +#include +#include +#include +#include + +struct fuse_opt_context { + void *data; + const struct fuse_opt *opt; + fuse_opt_proc_t proc; + int argctr; + int argc; + char **argv; + struct fuse_args outargs; + char *opts; + int nonopt; +}; + +void fuse_opt_free_args(struct fuse_args *args) +{ + if (args) { + if (args->argv && args->allocated) { + int i; + for (i = 0; i < args->argc; i++) + free(args->argv[i]); + free(args->argv); + } + args->argc = 0; + args->argv = NULL; + args->allocated = 0; + } +} + +static int alloc_failed(void) +{ + fuse_log(FUSE_LOG_ERR, "fuse: memory allocation failed\n"); + return -1; +} + +int fuse_opt_add_arg(struct fuse_args *args, const char *arg) +{ + char **newargv; + char *newarg; + + assert(!args->argv || args->allocated); + + newarg = strdup(arg); + if (!newarg) + return alloc_failed(); + + newargv = realloc(args->argv, (args->argc + 2) * sizeof(char *)); + if (!newargv) { + free(newarg); + return alloc_failed(); + } + + args->argv = newargv; + args->allocated = 1; + args->argv[args->argc++] = newarg; + args->argv[args->argc] = NULL; + return 0; +} + +static int fuse_opt_insert_arg_common(struct fuse_args *args, int pos, + const char *arg) +{ + assert(pos <= args->argc); + if (fuse_opt_add_arg(args, arg) == -1) + return -1; + + if (pos != args->argc - 1) { + char *newarg = args->argv[args->argc - 1]; + memmove(&args->argv[pos + 1], &args->argv[pos], + sizeof(char *) * (args->argc - pos - 1)); + args->argv[pos] = newarg; + } + return 0; +} + +int fuse_opt_insert_arg(struct fuse_args *args, int pos, const char *arg) +{ + return fuse_opt_insert_arg_common(args, pos, arg); +} + +static int next_arg(struct fuse_opt_context *ctx, const char *opt) +{ + if (ctx->argctr + 1 >= ctx->argc) { + fuse_log(FUSE_LOG_ERR, "fuse: missing argument after `%s'\n", opt); + return -1; + } + ctx->argctr++; + return 0; +} + +static int add_arg(struct fuse_opt_context *ctx, const char *arg) +{ + return fuse_opt_add_arg(&ctx->outargs, arg); +} + +static int add_opt_common(char **opts, const char *opt, int esc) +{ + unsigned oldlen = *opts ? strlen(*opts) : 0; + char *d = realloc(*opts, oldlen + 1 + strlen(opt) * 2 + 1); + + if (!d) + return alloc_failed(); + + *opts = d; + if (oldlen) { + d += oldlen; + *d++ = ','; + } + + for (; *opt; opt++) { + if (esc && (*opt == ',' || *opt == '\\')) + *d++ = '\\'; + *d++ = *opt; + } + *d = '\0'; + + return 0; +} + +int fuse_opt_add_opt(char **opts, const char *opt) +{ + return add_opt_common(opts, opt, 0); +} + +int fuse_opt_add_opt_escaped(char **opts, const char *opt) +{ + return add_opt_common(opts, opt, 1); +} + +static int add_opt(struct fuse_opt_context *ctx, const char *opt) +{ + return add_opt_common(&ctx->opts, opt, 1); +} + +static int call_proc(struct fuse_opt_context *ctx, const char *arg, int key, + int iso) +{ + if (key == FUSE_OPT_KEY_DISCARD) + return 0; + + if (key != FUSE_OPT_KEY_KEEP && ctx->proc) { + int res = ctx->proc(ctx->data, arg, key, &ctx->outargs); + if (res == -1 || !res) + return res; + } + if (iso) + return add_opt(ctx, arg); + else + return add_arg(ctx, arg); +} + +static int match_template(const char *t, const char *arg, unsigned *sepp) +{ + int arglen = strlen(arg); + const char *sep = strchr(t, '='); + sep = sep ? sep : strchr(t, ' '); + if (sep && (!sep[1] || sep[1] == '%')) { + int tlen = sep - t; + if (sep[0] == '=') + tlen ++; + if (arglen >= tlen && strncmp(arg, t, tlen) == 0) { + *sepp = sep - t; + return 1; + } + } + if (strcmp(t, arg) == 0) { + *sepp = 0; + return 1; + } + return 0; +} + +static const struct fuse_opt *find_opt(const struct fuse_opt *opt, + const char *arg, unsigned *sepp) +{ + for (; opt && opt->templ; opt++) + if (match_template(opt->templ, arg, sepp)) + return opt; + return NULL; +} + +int fuse_opt_match(const struct fuse_opt *opts, const char *opt) +{ + unsigned dummy; + return find_opt(opts, opt, &dummy) ? 1 : 0; +} + +static int process_opt_param(void *var, const char *format, const char *param, + const char *arg) +{ + assert(format[0] == '%'); + if (format[1] == 's') { + char **s = var; + char *copy = strdup(param); + if (!copy) + return alloc_failed(); + + free(*s); + *s = copy; + } else { + if (sscanf(param, format, var) != 1) { + fuse_log(FUSE_LOG_ERR, "fuse: invalid parameter in option `%s'\n", arg); + return -1; + } + } + return 0; +} + +static int process_opt(struct fuse_opt_context *ctx, + const struct fuse_opt *opt, unsigned sep, + const char *arg, int iso) +{ + if (opt->offset == -1U) { + if (call_proc(ctx, arg, opt->value, iso) == -1) + return -1; + } else { + void *var = (char *)ctx->data + opt->offset; + if (sep && opt->templ[sep + 1]) { + const char *param = arg + sep; + if (opt->templ[sep] == '=') + param ++; + if (process_opt_param(var, opt->templ + sep + 1, + param, arg) == -1) + return -1; + } else + *(int *)var = opt->value; + } + return 0; +} + +static int process_opt_sep_arg(struct fuse_opt_context *ctx, + const struct fuse_opt *opt, unsigned sep, + const char *arg, int iso) +{ + int res; + char *newarg; + char *param; + + if (next_arg(ctx, arg) == -1) + return -1; + + param = ctx->argv[ctx->argctr]; + newarg = malloc(sep + strlen(param) + 1); + if (!newarg) + return alloc_failed(); + + memcpy(newarg, arg, sep); + strcpy(newarg + sep, param); + res = process_opt(ctx, opt, sep, newarg, iso); + free(newarg); + + return res; +} + +static int process_gopt(struct fuse_opt_context *ctx, const char *arg, int iso) +{ + unsigned sep; + const struct fuse_opt *opt = find_opt(ctx->opt, arg, &sep); + if (opt) { + for (; opt; opt = find_opt(opt + 1, arg, &sep)) { + int res; + if (sep && opt->templ[sep] == ' ' && !arg[sep]) + res = process_opt_sep_arg(ctx, opt, sep, arg, + iso); + else + res = process_opt(ctx, opt, sep, arg, iso); + if (res == -1) + return -1; + } + return 0; + } else + return call_proc(ctx, arg, FUSE_OPT_KEY_OPT, iso); +} + +static int process_real_option_group(struct fuse_opt_context *ctx, char *opts) +{ + char *s = opts; + char *d = s; + int end = 0; + + while (!end) { + if (*s == '\0') + end = 1; + if (*s == ',' || end) { + int res; + + *d = '\0'; + res = process_gopt(ctx, opts, 1); + if (res == -1) + return -1; + d = opts; + } else { + if (s[0] == '\\' && s[1] != '\0') { + s++; + if (s[0] >= '0' && s[0] <= '3' && + s[1] >= '0' && s[1] <= '7' && + s[2] >= '0' && s[2] <= '7') { + *d++ = (s[0] - '0') * 0100 + + (s[1] - '0') * 0010 + + (s[2] - '0'); + s += 2; + } else { + *d++ = *s; + } + } else { + *d++ = *s; + } + } + s++; + } + + return 0; +} + +static int process_option_group(struct fuse_opt_context *ctx, const char *opts) +{ + int res; + char *copy = strdup(opts); + + if (!copy) { + fuse_log(FUSE_LOG_ERR, "fuse: memory allocation failed\n"); + return -1; + } + res = process_real_option_group(ctx, copy); + free(copy); + return res; +} + +static int process_one(struct fuse_opt_context *ctx, const char *arg) +{ + if (ctx->nonopt || arg[0] != '-') + return call_proc(ctx, arg, FUSE_OPT_KEY_NONOPT, 0); + else if (arg[1] == 'o') { + if (arg[2]) + return process_option_group(ctx, arg + 2); + else { + if (next_arg(ctx, arg) == -1) + return -1; + + return process_option_group(ctx, + ctx->argv[ctx->argctr]); + } + } else if (arg[1] == '-' && !arg[2]) { + if (add_arg(ctx, arg) == -1) + return -1; + ctx->nonopt = ctx->outargs.argc; + return 0; + } else + return process_gopt(ctx, arg, 0); +} + +static int opt_parse(struct fuse_opt_context *ctx) +{ + if (ctx->argc) { + if (add_arg(ctx, ctx->argv[0]) == -1) + return -1; + } + + for (ctx->argctr = 1; ctx->argctr < ctx->argc; ctx->argctr++) + if (process_one(ctx, ctx->argv[ctx->argctr]) == -1) + return -1; + + if (ctx->opts) { + if (fuse_opt_insert_arg(&ctx->outargs, 1, "-o") == -1 || + fuse_opt_insert_arg(&ctx->outargs, 2, ctx->opts) == -1) + return -1; + } + + /* If option separator ("--") is the last argument, remove it */ + if (ctx->nonopt && ctx->nonopt == ctx->outargs.argc && + strcmp(ctx->outargs.argv[ctx->outargs.argc - 1], "--") == 0) { + free(ctx->outargs.argv[ctx->outargs.argc - 1]); + ctx->outargs.argv[--ctx->outargs.argc] = NULL; + } + + return 0; +} + +int fuse_opt_parse(struct fuse_args *args, void *data, + const struct fuse_opt opts[], fuse_opt_proc_t proc) +{ + int res; + struct fuse_opt_context ctx = { + .data = data, + .opt = opts, + .proc = proc, + }; + + if (!args || !args->argv || !args->argc) + return 0; + + ctx.argc = args->argc; + ctx.argv = args->argv; + + res = opt_parse(&ctx); + if (res != -1) { + struct fuse_args tmp = *args; + *args = ctx.outargs; + ctx.outargs = tmp; + } + free(ctx.opts); + fuse_opt_free_args(&ctx.outargs); + return res; +} diff --git a/lib/fuse_signals.c b/lib/fuse_signals.c new file mode 100644 index 0000000..03b0d8f --- /dev/null +++ b/lib/fuse_signals.c @@ -0,0 +1,216 @@ +/* + FUSE: Filesystem in Userspace + Copyright (C) 2001-2007 Miklos Szeredi + + Utility functions for setting signal handlers. + + This program can be distributed under the terms of the GNU LGPLv2. + See the file LGPL2.txt +*/ + +#include "fuse_config.h" +#include "fuse_lowlevel.h" +#include "fuse_i.h" + +#include +#include +#include +#include +#include + +#ifdef HAVE_BACKTRACE +#include +#endif + +/* + * Must not handle SIGCANCEL, as that is used to wake up threads from + * syscalls reading requests from /dev/fuse + */ +static int teardown_sigs[] = { SIGHUP, SIGINT, SIGTERM }; + +static int ignore_sigs[] = { SIGPIPE}; +static int fail_sigs[] = { SIGILL, SIGTRAP, SIGABRT, SIGBUS, SIGFPE, SIGSEGV }; +static struct fuse_session *fuse_instance; + +#ifdef HAVE_BACKTRACE +#define BT_STACK_SZ (1024 * 1024) +static void *backtrace_buffer[BT_STACK_SZ]; +#endif + +static void dump_stack(void) +{ +#ifdef HAVE_BACKTRACE + char **strings; + + int nptrs = backtrace(backtrace_buffer, BT_STACK_SZ); + strings = backtrace_symbols(backtrace_buffer, nptrs); + + if (strings == NULL) { + fuse_log(FUSE_LOG_ERR, "Failed to get backtrace symbols: %s\n", + strerror(errno)); + return; + } + + for (int idx = 0; idx < nptrs; idx++) + fuse_log(FUSE_LOG_ERR, "%s\n", strings[idx]); + + free(strings); +#endif +} + +static void exit_handler(int sig) +{ + if (fuse_instance == NULL) { + fuse_log(FUSE_LOG_ERR, "fuse_instance is NULL\n"); + return; + } + + if (fuse_instance->debug) + fuse_log(FUSE_LOG_ERR, "exit_handler called with sig %d\n", + sig); + + fuse_session_exit(fuse_instance); + + if (sig < 0) { + fuse_log(FUSE_LOG_ERR, + "assertion error: signal value <= 0\n"); + dump_stack(); + abort(); + fuse_instance->error = sig; + } + + fuse_instance->error = sig; +} + +static void exit_backtrace(int sig) +{ + if (fuse_instance == NULL) + return; + + fuse_session_exit(fuse_instance); + + fuse_remove_signal_handlers(fuse_instance); + fuse_log(FUSE_LOG_ERR, "Got signal: %d\n", sig); + dump_stack(); + abort(); +} + + +static void do_nothing(int sig) +{ + (void) sig; +} + +static int set_one_signal_handler(int sig, void (*handler)(int), int remove) +{ + struct sigaction sa; + struct sigaction old_sa; + + memset(&sa, 0, sizeof(struct sigaction)); + sa.sa_handler = remove ? SIG_DFL : handler; + sigemptyset(&(sa.sa_mask)); + sa.sa_flags = 0; + + if (sigaction(sig, NULL, &old_sa) == -1) { + perror("fuse: cannot get old signal handler"); + return -1; + } + + if (old_sa.sa_handler == (remove ? handler : SIG_DFL) && + sigaction(sig, &sa, NULL) == -1) { + perror("fuse: cannot set signal handler"); + return -1; + } + return 0; +} + +static int _fuse_set_signal_handlers(int signals[], int nr_signals, + void (*handler)(int)) +{ + for (int idx = 0; idx < nr_signals; idx++) { + int signal = signals[idx]; + + /* + * If we used SIG_IGN instead of the do_nothing function, + * then we would be unable to tell if we set SIG_IGN (and + * thus should reset to SIG_DFL in fuse_remove_signal_handlers) + * or if it was already set to SIG_IGN (and should be left + * untouched. + */ + if (set_one_signal_handler(signal, handler, 0) == -1) { + fuse_log(FUSE_LOG_ERR, + "Failed to install signal handler for sig %d\n", + signal); + return -1; + } + } + + return 0; +} + +int fuse_set_signal_handlers(struct fuse_session *se) +{ + size_t nr_signals; + int rc; + + nr_signals = sizeof(teardown_sigs) / sizeof(teardown_sigs[0]); + rc = _fuse_set_signal_handlers(teardown_sigs, nr_signals, exit_handler); + if (rc < 0) + return rc; + + nr_signals = sizeof(ignore_sigs) / sizeof(ignore_sigs[0]); + rc = _fuse_set_signal_handlers(ignore_sigs, nr_signals, do_nothing); + if (rc < 0) + return rc; + + /* + * needs to be set independently if already set, as some applications + * may have multiple sessions and might rely on traditional behavior + * that the last session is used. + */ + fuse_instance = se; + + return 0; +} + +int fuse_set_fail_signal_handlers(struct fuse_session *se) +{ + size_t nr_signals = sizeof(fail_sigs) / sizeof(fail_sigs[0]); + + int rc = _fuse_set_signal_handlers(fail_sigs, nr_signals, + exit_backtrace); + if (rc < 0) + return rc; + + /* See fuse_set_signal_handlers, why set unconditionally */ + fuse_instance = se; + + return 0; +} + +static void _fuse_remove_signal_handlers(int signals[], int nr_signals, + void (*handler)(int)) +{ + for (int idx = 0; idx < nr_signals; idx++) + set_one_signal_handler(signals[idx], handler, 1); +} + +void fuse_remove_signal_handlers(struct fuse_session *se) +{ + size_t nr_signals; + + if (fuse_instance != se) + fuse_log(FUSE_LOG_ERR, + "fuse: fuse_remove_signal_handlers: unknown session\n"); + else + fuse_instance = NULL; + + nr_signals = sizeof(teardown_sigs) / sizeof(teardown_sigs[0]); + _fuse_remove_signal_handlers(teardown_sigs, nr_signals, exit_handler); + + nr_signals = sizeof(ignore_sigs) / sizeof(ignore_sigs[0]); + _fuse_remove_signal_handlers(ignore_sigs, nr_signals, do_nothing); + + nr_signals = sizeof(fail_sigs) / sizeof(fail_sigs[0]); + _fuse_remove_signal_handlers(fail_sigs, nr_signals, exit_backtrace); +} diff --git a/lib/fuse_uring.c b/lib/fuse_uring.c new file mode 100644 index 0000000..8268fc8 --- /dev/null +++ b/lib/fuse_uring.c @@ -0,0 +1,959 @@ +/* + * FUSE: Filesystem in Userspace + * Copyright (C) 2025 Bernd Schubert + * + * Implementation of (most of) FUSE-over-io-uring. + * + * This program can be distributed under the terms of the GNU LGPLv2. + * See the file LGPL2.txt + */ + +#define _GNU_SOURCE + +#include "fuse_i.h" +#include "fuse_kernel.h" +#include "fuse_uring_i.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Size of command data area in SQE when IORING_SETUP_SQE128 is used */ +#define FUSE_URING_MAX_SQE128_CMD_DATA 80 + +struct fuse_ring_ent { + struct fuse_ring_queue *ring_queue; /* back pointer */ + struct fuse_req req; + + struct fuse_uring_req_header *req_header; + void *op_payload; + size_t req_payload_sz; + + /* commit id of a fuse request */ + uint64_t req_commit_id; + + enum fuse_uring_cmd last_cmd; + + /* header and payload */ + struct iovec iov[2]; +}; + +struct fuse_ring_queue { + /* back pointer */ + struct fuse_ring_pool *ring_pool; + int qid; + int numa_node; + pthread_t tid; + int eventfd; + size_t req_header_sz; + struct io_uring ring; + + pthread_mutex_t ring_lock; + bool cqe_processing; + + /* size depends on queue depth */ + struct fuse_ring_ent ent[]; +}; + +/** + * Main fuse_ring structure, holds all fuse-ring data + */ +struct fuse_ring_pool { + struct fuse_session *se; + + /* number of queues */ + size_t nr_queues; + + /* number of per queue entries */ + size_t queue_depth; + + /* max payload size for fuse requests*/ + size_t max_req_payload_sz; + + /* size of a single queue */ + size_t queue_mem_size; + + unsigned int started_threads; + unsigned int failed_threads; + + /* Avoid sending queue entries before FUSE_INIT reply*/ + sem_t init_sem; + + pthread_cond_t thread_start_cond; + pthread_mutex_t thread_start_mutex; + + /* pointer to the first queue */ + struct fuse_ring_queue *queues; +}; + +static size_t +fuse_ring_queue_size(const size_t q_depth) +{ + const size_t req_size = sizeof(struct fuse_ring_ent) * q_depth; + + return sizeof(struct fuse_ring_queue) + req_size; +} + +static struct fuse_ring_queue * +fuse_uring_get_queue(struct fuse_ring_pool *fuse_ring, int qid) +{ + void *ptr = + ((char *)fuse_ring->queues) + (qid * fuse_ring->queue_mem_size); + + return ptr; +} + +/** + * return a pointer to the 80B area + */ +static void *fuse_uring_get_sqe_cmd(struct io_uring_sqe *sqe) +{ + return (void *)&sqe->cmd[0]; +} + +static void fuse_uring_sqe_set_req_data(struct fuse_uring_cmd_req *req, + const unsigned int qid, + const uint64_t commit_id) +{ + req->qid = qid; + req->commit_id = commit_id; + req->flags = 0; +} + +static void +fuse_uring_sqe_prepare(struct io_uring_sqe *sqe, struct fuse_ring_ent *req, + __u32 cmd_op) +{ + /* These fields should be written once, never change */ + sqe->opcode = IORING_OP_URING_CMD; + + /* + * IOSQE_FIXED_FILE: fd is the index to the fd *array* + * given to io_uring_register_files() + */ + sqe->flags = IOSQE_FIXED_FILE; + sqe->fd = 0; + + sqe->rw_flags = 0; + sqe->ioprio = 0; + sqe->off = 0; + + io_uring_sqe_set_data(sqe, req); + + sqe->cmd_op = cmd_op; + sqe->__pad1 = 0; +} + +static int fuse_uring_commit_sqe(struct fuse_ring_pool *ring_pool, + struct fuse_ring_queue *queue, + struct fuse_ring_ent *ring_ent) +{ + bool locked = false; + struct fuse_session *se = ring_pool->se; + struct fuse_uring_req_header *rrh = ring_ent->req_header; + struct fuse_out_header *out = (struct fuse_out_header *)&rrh->in_out; + struct fuse_uring_ent_in_out *ent_in_out = + (struct fuse_uring_ent_in_out *)&rrh->ring_ent_in_out; + struct io_uring_sqe *sqe; + + if (pthread_self() != queue->tid) { + pthread_mutex_lock(&queue->ring_lock); + locked = true; + } + + sqe = io_uring_get_sqe(&queue->ring); + + if (sqe == NULL) { + /* This is an impossible condition, unless there is a bug. + * The kernel sent back an SQEs, which is assigned to a request. + * There is no way to get out of SQEs, as the number of + * SQEs matches the number tof requests. + */ + + se->error = -EIO; + fuse_log(FUSE_LOG_ERR, "Failed to get a ring SQEs\n"); + + return -EIO; + } + + ring_ent->last_cmd = FUSE_IO_URING_CMD_COMMIT_AND_FETCH; + fuse_uring_sqe_prepare(sqe, ring_ent, ring_ent->last_cmd); + fuse_uring_sqe_set_req_data(fuse_uring_get_sqe_cmd(sqe), queue->qid, + ring_ent->req_commit_id); + + if (se->debug) { + fuse_log(FUSE_LOG_DEBUG, " unique: %" PRIu64 ", result=%d\n", + out->unique, ent_in_out->payload_sz); + } + + if (!queue->cqe_processing) + io_uring_submit(&queue->ring); + + if (locked) + pthread_mutex_unlock(&queue->ring_lock); + + return 0; +} + +int fuse_req_get_payload(fuse_req_t req, char **payload, size_t *payload_sz, + void **mr) +{ + struct fuse_ring_ent *ring_ent; + + /* Not possible without io-uring interface */ + if (!req->flags.is_uring) + return -EINVAL; + + ring_ent = container_of(req, struct fuse_ring_ent, req); + + *payload = ring_ent->op_payload; + *payload_sz = ring_ent->req_payload_sz; + + /* + * For now unused, but will be used later when the application can + * allocate the buffers itself and register them for rdma. + */ + if (mr) + *mr = NULL; + + return 0; +} + +int send_reply_uring(fuse_req_t req, int error, const void *arg, size_t argsize) +{ + int res; + struct fuse_ring_ent *ring_ent = + container_of(req, struct fuse_ring_ent, req); + struct fuse_uring_req_header *rrh = ring_ent->req_header; + struct fuse_out_header *out = (struct fuse_out_header *)&rrh->in_out; + struct fuse_uring_ent_in_out *ent_in_out = + (struct fuse_uring_ent_in_out *)&rrh->ring_ent_in_out; + + struct fuse_ring_queue *queue = ring_ent->ring_queue; + struct fuse_ring_pool *ring_pool = queue->ring_pool; + size_t max_payload_sz = ring_pool->max_req_payload_sz; + + if (argsize > max_payload_sz) { + fuse_log(FUSE_LOG_ERR, "argsize %zu exceeds buffer size %zu", + argsize, max_payload_sz); + error = -EINVAL; + } else if (argsize) { + if (arg != ring_ent->op_payload) + memcpy(ring_ent->op_payload, arg, argsize); + } + ent_in_out->payload_sz = argsize; + + out->error = error; + out->unique = req->unique; + + res = fuse_uring_commit_sqe(ring_pool, queue, ring_ent); + + fuse_free_req(req); + + return res; +} + +int fuse_reply_data_uring(fuse_req_t req, struct fuse_bufvec *bufv, + enum fuse_buf_copy_flags flags) +{ + struct fuse_ring_ent *ring_ent = + container_of(req, struct fuse_ring_ent, req); + + struct fuse_ring_queue *queue = ring_ent->ring_queue; + struct fuse_ring_pool *ring_pool = queue->ring_pool; + struct fuse_uring_req_header *rrh = ring_ent->req_header; + struct fuse_out_header *out = (struct fuse_out_header *)&rrh->in_out; + struct fuse_uring_ent_in_out *ent_in_out = + (struct fuse_uring_ent_in_out *)&rrh->ring_ent_in_out; + size_t max_payload_sz = ring_ent->req_payload_sz; + struct fuse_bufvec dest_vec = FUSE_BUFVEC_INIT(max_payload_sz); + int res; + + dest_vec.buf[0].mem = ring_ent->op_payload; + dest_vec.buf[0].size = max_payload_sz; + + res = fuse_buf_copy(&dest_vec, bufv, flags); + + out->error = res < 0 ? res : 0; + out->unique = req->unique; + + ent_in_out->payload_sz = res > 0 ? res : 0; + + res = fuse_uring_commit_sqe(ring_pool, queue, ring_ent); + + fuse_free_req(req); + + return res; +} + +/** + * Copy the iov into the ring buffer and submit and commit/fetch sqe + */ +int fuse_send_msg_uring(fuse_req_t req, struct iovec *iov, int count) +{ + struct fuse_ring_ent *ring_ent = + container_of(req, struct fuse_ring_ent, req); + + struct fuse_ring_queue *queue = ring_ent->ring_queue; + struct fuse_ring_pool *ring_pool = queue->ring_pool; + struct fuse_uring_req_header *rrh = ring_ent->req_header; + struct fuse_out_header *out = (struct fuse_out_header *)&rrh->in_out; + struct fuse_uring_ent_in_out *ent_in_out = + (struct fuse_uring_ent_in_out *)&rrh->ring_ent_in_out; + size_t max_buf = ring_pool->max_req_payload_sz; + size_t len = 0; + int res = 0; + + /* copy iov into the payload, idx=0 is the header section */ + for (int idx = 1; idx < count; idx++) { + struct iovec *cur = &iov[idx]; + + if (len + cur->iov_len > max_buf) { + fuse_log(FUSE_LOG_ERR, + "iov[%d] exceeds buffer size %zu", + idx, max_buf); + res = -EINVAL; /* Gracefully handle this? */ + break; + } + + memcpy(ring_ent->op_payload + len, cur->iov_base, cur->iov_len); + len += cur->iov_len; + } + + ent_in_out->payload_sz = len; + + out->error = res; + out->unique = req->unique; + out->len = len; + + return fuse_uring_commit_sqe(ring_pool, queue, ring_ent); +} + +static int fuse_queue_setup_io_uring(struct io_uring *ring, size_t qid, + size_t depth, int fd, int evfd) +{ + int rc; + struct io_uring_params params = {0}; + int files[2] = { fd, evfd }; + + depth += 1; /* for the eventfd poll SQE */ + + params.flags = IORING_SETUP_SQE128; + + /* Replies are batched and flushed in one io_uring_enter; don't let a + * single failing commit SQE stall submission of the rest of the batch. + */ + params.flags |= IORING_SETUP_SUBMIT_ALL; + + /* Avoid cq overflow */ + params.flags |= IORING_SETUP_CQSIZE; + params.cq_entries = depth * 2; + + /* These flags should help to increase performance, but actually + * make it a bit slower - reason should get investigated. + */ + if (0) { + /* Has the main slow down effect */ + params.flags |= IORING_SETUP_SINGLE_ISSUER; + + // params.flags |= IORING_SETUP_DEFER_TASKRUN; + params.flags |= IORING_SETUP_TASKRUN_FLAG; + + /* Second main effect to make it slower */ + params.flags |= IORING_SETUP_COOP_TASKRUN; + } + + rc = io_uring_queue_init_params(depth, ring, ¶ms); + if (rc != 0) { + fuse_log(FUSE_LOG_ERR, "Failed to setup qid %zu: %d (%s)\n", + qid, rc, strerror(-rc)); + return rc; + } + + rc = io_uring_register_files(ring, files, 1); + if (rc != 0) { + rc = -errno; + fuse_log(FUSE_LOG_ERR, + "Failed to register files for ring idx %zu: %s", + qid, strerror(errno)); + return rc; + } + + return 0; +} + +static void fuse_session_destruct_uring(struct fuse_ring_pool *fuse_ring) +{ + for (size_t qid = 0; qid < fuse_ring->nr_queues; qid++) { + struct fuse_ring_queue *queue = + fuse_uring_get_queue(fuse_ring, qid); + + if (queue->tid != 0) { + uint64_t value = 1ULL; + int rc; + + rc = write(queue->eventfd, &value, sizeof(value)); + if (rc != sizeof(value)) + fprintf(stderr, + "Wrote to eventfd=%d err=%s: rc=%d\n", + queue->eventfd, strerror(errno), rc); + pthread_cancel(queue->tid); + pthread_join(queue->tid, NULL); + queue->tid = 0; + } + + if (queue->eventfd >= 0) { + close(queue->eventfd); + queue->eventfd = -1; + } + + if (queue->ring.ring_fd != -1) + io_uring_queue_exit(&queue->ring); + + for (size_t idx = 0; idx < fuse_ring->queue_depth; idx++) { + struct fuse_ring_ent *ent = &queue->ent[idx]; + + numa_free(ent->op_payload, ent->req_payload_sz); + numa_free(ent->req_header, queue->req_header_sz); + } + + pthread_mutex_destroy(&queue->ring_lock); + } + + free(fuse_ring->queues); + pthread_cond_destroy(&fuse_ring->thread_start_cond); + pthread_mutex_destroy(&fuse_ring->thread_start_mutex); + free(fuse_ring); +} + +static int fuse_uring_register_ent(struct fuse_ring_queue *queue, + struct fuse_ring_ent *ent) +{ + struct io_uring_sqe *sqe; + + sqe = io_uring_get_sqe(&queue->ring); + if (sqe == NULL) { + /* + * All SQEs are idle here - no good reason this + * could fail + */ + fuse_log(FUSE_LOG_ERR, "Failed to get all ring SQEs"); + return -EIO; + } + + ent->last_cmd = FUSE_IO_URING_CMD_REGISTER; + fuse_uring_sqe_prepare(sqe, ent, ent->last_cmd); + + /* only needed for fetch */ + ent->iov[0].iov_base = ent->req_header; + ent->iov[0].iov_len = queue->req_header_sz; + + ent->iov[1].iov_base = ent->op_payload; + ent->iov[1].iov_len = ent->req_payload_sz; + + sqe->addr = (uint64_t)(ent->iov); + sqe->len = 2; + + /* this is a fetch, kernel does not read commit id */ + fuse_uring_sqe_set_req_data(fuse_uring_get_sqe_cmd(sqe), queue->qid, 0); + + return 0; + +} + +static int fuse_uring_register_queue(struct fuse_ring_queue *queue) +{ + struct fuse_ring_pool *ring_pool = queue->ring_pool; + unsigned int sq_ready; + struct io_uring_sqe *sqe; + int res; + + for (size_t idx = 0; idx < ring_pool->queue_depth; idx++) { + struct fuse_ring_ent *ent = &queue->ent[idx]; + + res = fuse_uring_register_ent(queue, ent); + if (res != 0) + return res; + } + + sq_ready = io_uring_sq_ready(&queue->ring); + if (sq_ready != ring_pool->queue_depth) { + fuse_log(FUSE_LOG_ERR, + "SQE ready mismatch, expected %zu got %u\n", + ring_pool->queue_depth, sq_ready); + return -EINVAL; + } + + /* Poll SQE for the eventfd to wake up on teardown */ + sqe = io_uring_get_sqe(&queue->ring); + if (sqe == NULL) { + fuse_log(FUSE_LOG_ERR, "Failed to get eventfd SQE"); + return -EIO; + } + + io_uring_prep_poll_add(sqe, queue->eventfd, POLLIN); + io_uring_sqe_set_data(sqe, (void *)(uintptr_t)queue->eventfd); + + /* Only preparation until here, no submission yet */ + + return 0; +} + +static struct fuse_ring_pool *fuse_create_ring(struct fuse_session *se) +{ + struct fuse_ring_pool *fuse_ring = NULL; + const size_t nr_queues = get_nprocs_conf(); + size_t payload_sz = se->bufsize - FUSE_BUFFER_HEADER_SIZE; + size_t queue_sz; + + if (se->debug) + fuse_log(FUSE_LOG_DEBUG, "starting io-uring q-depth=%d\n", + se->uring.q_depth); + + fuse_ring = calloc(1, sizeof(*fuse_ring)); + if (fuse_ring == NULL) { + fuse_log(FUSE_LOG_ERR, "Allocating the ring failed\n"); + goto err; + } + + queue_sz = fuse_ring_queue_size(se->uring.q_depth); + fuse_ring->queues = calloc(1, queue_sz * nr_queues); + if (fuse_ring->queues == NULL) { + fuse_log(FUSE_LOG_ERR, "Allocating the queues failed\n"); + goto err; + } + + fuse_ring->se = se; + fuse_ring->nr_queues = nr_queues; + fuse_ring->queue_depth = se->uring.q_depth; + fuse_ring->max_req_payload_sz = payload_sz; + fuse_ring->queue_mem_size = queue_sz; + + /* + * very basic queue initialization, that cannot fail and will + * allow easy cleanup if something (like mmap) fails in the middle + * below + */ + for (size_t qid = 0; qid < nr_queues; qid++) { + struct fuse_ring_queue *queue = + fuse_uring_get_queue(fuse_ring, qid); + + queue->ring.ring_fd = -1; + queue->numa_node = numa_node_of_cpu(qid); + queue->qid = qid; + queue->ring_pool = fuse_ring; + queue->eventfd = -1; + pthread_mutex_init(&queue->ring_lock, NULL); + } + + pthread_cond_init(&fuse_ring->thread_start_cond, NULL); + pthread_mutex_init(&fuse_ring->thread_start_mutex, NULL); + sem_init(&fuse_ring->init_sem, 0, 0); + + return fuse_ring; + +err: + if (fuse_ring) + fuse_session_destruct_uring(fuse_ring); + + return NULL; +} + +static void fuse_uring_resubmit(struct fuse_ring_queue *queue, + struct fuse_ring_ent *ent) +{ + struct io_uring_sqe *sqe; + + sqe = io_uring_get_sqe(&queue->ring); + if (sqe == NULL) { + /* This is an impossible condition, unless there is a bug. + * The kernel sent back an SQEs, which is assigned to a request. + * There is no way to get out of SQEs, as the number of + * SQEs matches the number tof requests. + */ + + queue->ring_pool->se->error = -EIO; + fuse_log(FUSE_LOG_ERR, "Failed to get a ring SQEs\n"); + + return; + } + + fuse_uring_sqe_prepare(sqe, ent, ent->last_cmd); + + switch (ent->last_cmd) { + case FUSE_IO_URING_CMD_REGISTER: + sqe->addr = (uint64_t)(ent->iov); + sqe->len = 2; + fuse_uring_sqe_set_req_data(fuse_uring_get_sqe_cmd(sqe), + queue->qid, 0); + break; + case FUSE_IO_URING_CMD_COMMIT_AND_FETCH: + fuse_uring_sqe_set_req_data(fuse_uring_get_sqe_cmd(sqe), + queue->qid, ent->req_commit_id); + break; + default: + fuse_log(FUSE_LOG_ERR, "Unknown command type: %d\n", + ent->last_cmd); + queue->ring_pool->se->error = -EINVAL; + break; + } + + /* caller submits */ +} + +static void fuse_uring_handle_cqe(struct fuse_ring_queue *queue, + struct io_uring_cqe *cqe) +{ + struct fuse_ring_ent *ent = io_uring_cqe_get_data(cqe); + + if (!ent) { + fuse_log(FUSE_LOG_ERR, + "cqe=%p io_uring_cqe_get_data returned NULL\n", cqe); + return; + } + + struct fuse_req *req = &ent->req; + struct fuse_ring_pool *fuse_ring = queue->ring_pool; + struct fuse_uring_req_header *rrh = ent->req_header; + + struct fuse_in_header *in = (struct fuse_in_header *)&rrh->in_out; + struct fuse_uring_ent_in_out *ent_in_out = &rrh->ring_ent_in_out; + + ent->req_commit_id = ent_in_out->commit_id; + if (unlikely(ent->req_commit_id == 0)) { + /* + * If this happens kernel will not find the response - it will + * be stuck forever - better to abort immediately. + */ + fuse_log(FUSE_LOG_ERR, "Received invalid commit_id=0\n"); + abort(); + } + + memset(&req->flags, 0, sizeof(req->flags)); + memset(&req->u, 0, sizeof(req->u)); + req->flags.is_uring = 1; + req->ref_cnt++; + req->ch = NULL; /* not needed for uring */ + req->interrupted = 0; + list_init_req(req); + + fuse_session_process_uring_cqe(fuse_ring->se, req, in, &rrh->op_in, + ent->op_payload, ent_in_out->payload_sz); +} + +static int fuse_uring_queue_handle_cqes(struct fuse_ring_queue *queue) +{ + struct fuse_ring_pool *ring_pool = queue->ring_pool; + struct fuse_session *se = ring_pool->se; + size_t num_completed = 0; + struct io_uring_cqe *cqe; + unsigned int head; + struct fuse_ring_ent *ent; + int ret = 0; + + io_uring_for_each_cqe(&queue->ring, head, cqe) { + int err = 0; + + num_completed++; + + err = cqe->res; + if (unlikely(err != 0)) { + if (err > 0 && ((uintptr_t)io_uring_cqe_get_data(cqe) == + (unsigned int)queue->eventfd)) { + /* teardown from eventfd */ + return -ENOTCONN; + } + + + switch (err) { + case -EAGAIN: + fallthrough; + case -EINTR: + ent = io_uring_cqe_get_data(cqe); + fuse_uring_resubmit(queue, ent); + continue; + default: + break; + } + + /* -ENOTCONN is ok on umount */ + if (err != -ENOTCONN) { + se->error = cqe->res; + + /* return first error */ + if (ret == 0) + ret = err; + } + + } else { + fuse_uring_handle_cqe(queue, cqe); + } + } + + if (num_completed) + io_uring_cq_advance(&queue->ring, num_completed); + + return ret == 0 ? 0 : num_completed; +} + +/** + * In per-core-queue configuration we have thread per core - the thread + * to that core + */ +static void fuse_uring_set_thread_core(int qid) +{ + cpu_set_t mask; + int rc; + + CPU_ZERO(&mask); + CPU_SET(qid, &mask); + rc = sched_setaffinity(0, sizeof(cpu_set_t), &mask); + if (rc != 0) + fuse_log(FUSE_LOG_ERR, "Failed to bind qid=%d to its core: %s\n", + qid, strerror(errno)); + + if (0) { + const int policy = SCHED_IDLE; + const struct sched_param param = { + .sched_priority = sched_get_priority_min(policy), + }; + + /* Set the lowest possible priority, so that the application + * submitting requests is not moved away from the current core. + */ + rc = sched_setscheduler(0, policy, ¶m); + if (rc != 0) + fuse_log(FUSE_LOG_ERR, "Failed to set scheduler: %s\n", + strerror(errno)); + } +} + +/* + * @return negative error code or io-uring file descriptor + */ +static int fuse_uring_init_queue(struct fuse_ring_queue *queue) +{ + struct fuse_ring_pool *ring = queue->ring_pool; + struct fuse_session *se = ring->se; + int res; + size_t page_sz = sysconf(_SC_PAGESIZE); + + queue->eventfd = eventfd(0, EFD_CLOEXEC); + if (queue->eventfd < 0) { + res = -errno; + fuse_log(FUSE_LOG_ERR, + "Failed to create eventfd for qid %d: %s\n", + queue->qid, strerror(errno)); + return res; + } + + res = fuse_queue_setup_io_uring(&queue->ring, queue->qid, + ring->queue_depth, se->fd, + queue->eventfd); + if (res != 0) { + fuse_log(FUSE_LOG_ERR, "qid=%d io_uring init failed\n", + queue->qid); + return res; + } + + queue->req_header_sz = ROUND_UP(sizeof(struct fuse_uring_req_header), + page_sz); + + for (size_t idx = 0; idx < ring->queue_depth; idx++) { + struct fuse_ring_ent *ring_ent = &queue->ent[idx]; + struct fuse_req *req = &ring_ent->req; + + ring_ent->ring_queue = queue; + + /* + * Also allocate the header to have it page aligned, which + * is a requirement for page pinning + */ + ring_ent->req_header = + numa_alloc_local(queue->req_header_sz); + if (!ring_ent->req_header) + return -ENOMEM; + ring_ent->req_payload_sz = ring->max_req_payload_sz; + + ring_ent->op_payload = + numa_alloc_local(ring_ent->req_payload_sz); + if (!ring_ent->op_payload) + return -ENOMEM; + + req->se = se; + pthread_mutex_init(&req->lock, NULL); + req->flags.is_uring = 1; + req->ref_cnt = 1; /* extra ref to avoid destruction */ + list_init_req(req); + } + + res = fuse_uring_register_queue(queue); + if (res != 0) { + fuse_log( + FUSE_LOG_ERR, + "Grave fuse-uring error on preparing SQEs, aborting\n"); + se->error = -EIO; + fuse_session_exit(se); + return res; + } + + return queue->ring.ring_fd; +} + +static void *fuse_uring_thread(void *arg) +{ + struct fuse_ring_queue *queue = arg; + struct fuse_ring_pool *ring_pool = queue->ring_pool; + struct fuse_session *se = ring_pool->se; + int err; + char thread_name[16] = { 0 }; + + snprintf(thread_name, 16, "fuse-ring-%d", queue->qid); + thread_name[15] = '\0'; + fuse_set_thread_name(thread_name); + + fuse_uring_set_thread_core(queue->qid); + + err = fuse_uring_init_queue(queue); + pthread_mutex_lock(&ring_pool->thread_start_mutex); + if (err < 0) + ring_pool->failed_threads++; + ring_pool->started_threads++; + pthread_cond_broadcast(&ring_pool->thread_start_cond); + pthread_mutex_unlock(&ring_pool->thread_start_mutex); + + if (err < 0) { + fuse_log(FUSE_LOG_ERR, "qid=%d queue setup failed\n", + queue->qid); + goto err_non_fatal; + } + + sem_wait(&ring_pool->init_sem); + + /* Not using fuse_session_exited(se), as that cannot be inlined */ + while (!atomic_load_explicit(&se->mt_exited, memory_order_relaxed)) { + io_uring_submit_and_wait(&queue->ring, 1); + + pthread_mutex_lock(&queue->ring_lock); + queue->cqe_processing = true; + err = fuse_uring_queue_handle_cqes(queue); + queue->cqe_processing = false; + pthread_mutex_unlock(&queue->ring_lock); + if (err < 0) + goto err; + } + + return NULL; + +err: + fuse_session_exit(se); +err_non_fatal: + return NULL; +} + +static int fuse_uring_start_ring_threads(struct fuse_ring_pool *ring) +{ + int rc = 0; + + for (size_t qid = 0; qid < ring->nr_queues; qid++) { + struct fuse_ring_queue *queue = fuse_uring_get_queue(ring, qid); + + rc = pthread_create(&queue->tid, NULL, fuse_uring_thread, queue); + if (rc != 0) + break; + } + + return rc; +} + +static int fuse_uring_sanity_check(struct fuse_session *se) +{ + if (se->uring.q_depth == 0) { + fuse_log(FUSE_LOG_ERR, "io-uring queue depth must be > 0\n"); + return -EINVAL; + } + + _Static_assert(sizeof(struct fuse_uring_cmd_req) <= + FUSE_URING_MAX_SQE128_CMD_DATA, + "SQE128_CMD_DATA has 80B cmd data"); + + return 0; +} + +int fuse_uring_start(struct fuse_session *se) +{ + int err = 0; + struct fuse_ring_pool *fuse_ring; + + fuse_uring_sanity_check(se); + + fuse_ring = fuse_create_ring(se); + if (fuse_ring == NULL) { + err = -EADDRNOTAVAIL; + goto err; + } + + se->uring.pool = fuse_ring; + + /* Hold off threads from send fuse ring entries (SQEs) */ + sem_init(&fuse_ring->init_sem, 0, 0); + pthread_cond_init(&fuse_ring->thread_start_cond, NULL); + pthread_mutex_init(&fuse_ring->thread_start_mutex, NULL); + + err = fuse_uring_start_ring_threads(fuse_ring); + if (err) + goto err; + + /* + * Wait for all threads to start or to fail + */ + pthread_mutex_lock(&fuse_ring->thread_start_mutex); + while (fuse_ring->started_threads < fuse_ring->nr_queues) + pthread_cond_wait(&fuse_ring->thread_start_cond, + &fuse_ring->thread_start_mutex); + + if (fuse_ring->failed_threads != 0) + err = -EADDRNOTAVAIL; + pthread_mutex_unlock(&fuse_ring->thread_start_mutex); + +err: + if (err) { + /* Note all threads need to have been started */ + if (fuse_ring) + fuse_session_destruct_uring(fuse_ring); + se->uring.pool = NULL; + } + return err; +} + +int fuse_uring_stop(struct fuse_session *se) +{ + struct fuse_ring_pool *ring = se->uring.pool; + + if (ring == NULL) + return 0; + + fuse_session_destruct_uring(ring); + + return 0; +} + +void fuse_uring_wake_ring_threads(struct fuse_session *se) +{ + struct fuse_ring_pool *ring = se->uring.pool; + + /* Wake up the threads to let them send SQEs */ + for (size_t qid = 0; qid < ring->nr_queues; qid++) + sem_post(&ring->init_sem); +} diff --git a/lib/fuse_uring_i.h b/lib/fuse_uring_i.h new file mode 100644 index 0000000..c1da73a --- /dev/null +++ b/lib/fuse_uring_i.h @@ -0,0 +1,86 @@ +/* + * FUSE: Filesystem in Userspace + * Copyright (C) 2025 Bernd Schubert + * This program can be distributed under the terms of the GNU LGPLv2. + * See the file LGPL2.txt + */ + +#ifndef FUSE_URING_I_H_ +#define FUSE_URING_I_H_ + +#include "fuse_config.h" +#include "fuse_lowlevel.h" +#include "fuse_kernel.h" + +#ifndef HAVE_URING +#include "util.h" +#endif + +#include // IWYU pragma: keep + +/* io-uring defaults */ +#define SESSION_DEF_URING_ENABLE (0) +#define SESSION_DEF_URING_Q_DEPTH (8) + +void fuse_session_process_uring_cqe(struct fuse_session *se, + struct fuse_req *req, + struct fuse_in_header *in, void *in_header, + void *in_payload, size_t payload_len); + +#ifdef HAVE_URING + +struct fuse_in_header; + +int fuse_uring_start(struct fuse_session *se); +void fuse_uring_wake_ring_threads(struct fuse_session *se); +int fuse_uring_stop(struct fuse_session *se); +int send_reply_uring(fuse_req_t req, int error, const void *arg, + size_t argsize); + +int fuse_reply_data_uring(fuse_req_t req, struct fuse_bufvec *bufv, + enum fuse_buf_copy_flags flags); +int fuse_send_msg_uring(fuse_req_t req, struct iovec *iov, int count); + +#else // HAVE_URING + +static inline int fuse_uring_start(struct fuse_session *se FUSE_VAR_UNUSED) +{ + return -ENOTSUP; +} + +static inline void +fuse_uring_wake_ring_threads(struct fuse_session *se FUSE_VAR_UNUSED) +{ +} + +static inline int fuse_uring_stop(struct fuse_session *se FUSE_VAR_UNUSED) +{ + return -ENOTSUP; +} + +static inline int send_reply_uring(fuse_req_t req FUSE_VAR_UNUSED, + int error FUSE_VAR_UNUSED, + const void *arg FUSE_VAR_UNUSED, + size_t argsize FUSE_VAR_UNUSED) +{ + return -ENOTSUP; +} + +static inline int +fuse_reply_data_uring(fuse_req_t req FUSE_VAR_UNUSED, + struct fuse_bufvec *bufv FUSE_VAR_UNUSED, + enum fuse_buf_copy_flags flags FUSE_VAR_UNUSED) +{ + return -ENOTSUP; +} + +static inline int fuse_send_msg_uring(fuse_req_t req FUSE_VAR_UNUSED, + struct iovec *iov FUSE_VAR_UNUSED, + int count FUSE_VAR_UNUSED) +{ + return -ENOTSUP; +} + +#endif // HAVE_URING + +#endif // FUSE_URING_I_H_ diff --git a/lib/fuse_versionscript b/lib/fuse_versionscript new file mode 100644 index 0000000..b58360f --- /dev/null +++ b/lib/fuse_versionscript @@ -0,0 +1,227 @@ +FUSE_3.0 { + global: + fuse_destroy; + fuse_exit; + fuse_loop; + fuse_loop_mt; + fuse_reply_attr; + fuse_reply_buf; + fuse_reply_entry; + fuse_reply_err; + fuse_reply_none; + fuse_reply_readlink; + fuse_reply_write; + fuse_reply_xattr; + fuse_req_userdata; + fuse_session_destroy; + fuse_session_exit; + fuse_session_exited; + fuse_session_loop; + fuse_session_loop_mt; + fuse_session_reset; + fuse_session_fd; + fuse_opt_parse; + fuse_opt_add_opt; + fuse_opt_add_arg; + fuse_opt_free_args; + fuse_opt_match; + fuse_parse_cmdline; + fuse_remove_signal_handlers; + fuse_reply_create; + fuse_reply_open; + fuse_reply_statfs; + fuse_set_signal_handlers; + fuse_add_direntry; + fuse_add_direntry_plus; + fuse_daemonize; + fuse_get_session; + fuse_interrupted; + fuse_session_new; + fuse_main_real; + fuse_mount; + fuse_session_custom_io; + fuse_session_mount; + fuse_new; + fuse_opt_insert_arg; + fuse_reply_lock; + fuse_req_interrupt_func; + fuse_req_interrupted; + fuse_unmount; + fuse_session_unmount; + fuse_fs_access; + fuse_fs_bmap; + fuse_fs_chmod; + fuse_fs_chown; + fuse_fs_create; + fuse_fs_destroy; + fuse_fs_flush; + fuse_fs_fsync; + fuse_fs_fsyncdir; + fuse_fs_getattr; + fuse_fs_getxattr; + fuse_fs_init; + fuse_fs_link; + fuse_fs_listxattr; + fuse_fs_lock; + fuse_fs_mkdir; + fuse_fs_mknod; + fuse_fs_new; + fuse_fs_open; + fuse_fs_opendir; + fuse_fs_read; + fuse_fs_readdir; + fuse_fs_readlink; + fuse_fs_release; + fuse_fs_releasedir; + fuse_fs_removexattr; + fuse_fs_rename; + fuse_fs_rmdir; + fuse_fs_setxattr; + fuse_fs_statfs; + fuse_fs_symlink; + fuse_fs_truncate; + fuse_fs_unlink; + fuse_fs_utimens; + fuse_fs_write; + fuse_reply_iov; + fuse_version; + fuse_pkgversion; + fuse_reply_bmap; + cuse_lowlevel_new; + cuse_lowlevel_main; + cuse_lowlevel_setup; + cuse_lowlevel_teardown; + fuse_fs_ioctl; + fuse_fs_poll; + fuse_get_context; + fuse_getgroups; + fuse_lowlevel_notify_inval_entry; + fuse_lowlevel_notify_inval_inode; + fuse_lowlevel_notify_poll; + fuse_notify_poll; + fuse_opt_add_opt_escaped; + fuse_pollhandle_destroy; + fuse_reply_ioctl; + fuse_reply_ioctl_iov; + fuse_reply_ioctl_retry; + fuse_reply_poll; + fuse_req_ctx; + fuse_req_getgroups; + fuse_buf_copy; + fuse_buf_size; + fuse_fs_read_buf; + fuse_fs_write_buf; + fuse_lowlevel_notify_retrieve; + fuse_lowlevel_notify_store; + fuse_reply_data; + fuse_session_process_buf; + fuse_session_receive_buf; + fuse_start_cleanup_thread; + fuse_stop_cleanup_thread; + fuse_clean_cache; + fuse_lowlevel_notify_delete; + fuse_fs_flock; + fuse_fs_fallocate; + fuse_lowlevel_help; + fuse_lowlevel_version; + fuse_cmdline_help; + fuse_apply_conn_info_opts; + fuse_parse_conn_info_opts; + fuse_fs_lseek; + fuse_reply_lseek; + + local: + *; +}; + +FUSE_3.1 { + global: + fuse_lib_help; + fuse_invalidate_path; + fuse_new_30; + fuse_new_31; + fuse_new; +} FUSE_3.0; + +FUSE_3.2 { + global: + fuse_session_loop_mt; + fuse_session_loop_mt_31; + fuse_session_loop_mt_32; + fuse_loop_mt; + fuse_loop_mt_31; +} FUSE_3.1; + +FUSE_3.3 { + global: + fuse_open_channel; +} FUSE_3.2; + +FUSE_3.4 { + global: + fuse_fs_copy_file_range; +} FUSE_3.3; + +FUSE_3.7 { + global: + fuse_set_log_func; + fuse_log; +} FUSE_3.4; + +FUSE_3.12 { + global: + fuse_session_loop_mt; + fuse_session_loop_mt_312; + fuse_loop_mt; + fuse_loop_mt_32; + fuse_loop_mt_312; + fuse_loop_cfg_create; + fuse_loop_cfg_destroy; + fuse_loop_cfg_set_idle_threads; + fuse_loop_cfg_set_max_threads; + fuse_loop_cfg_set_clone_fd; + fuse_loop_cfg_convert; + fuse_parse_cmdline; + fuse_parse_cmdline_30; + fuse_parse_cmdline_312; + fuse_lowlevel_notify_expire_entry; +} FUSE_3.4; + +FUSE_3.17 { + global: + fuse_main_real_versioned; + fuse_session_new_versioned; + _fuse_new_30; + _fuse_new_31; + fuse_passthrough_open; + fuse_passthrough_close; + fuse_session_custom_io_30; + fuse_session_custom_io_317; + fuse_set_fail_signal_handlers; + fuse_log_enable_syslog; + fuse_log_close_syslog; +} FUSE_3.12; + +FUSE_3.17.3 { + global: + fuse_set_feature_flag; + fuse_unset_feature_flag; + fuse_get_feature_flag; + + # Not part of public API, for internal testing only + fuse_convert_to_conn_want_ext; +} FUSE_3.17; + +FUSE_3.18 { + global: + fuse_req_is_uring; + fuse_req_get_payload; + fuse_lowlevel_notify_increment_epoch; + + fuse_reply_statx; + fuse_fs_statx; +} FUSE_3.17; + +# Local Variables: +# indent-tabs-mode: t +# End: diff --git a/lib/helper.c b/lib/helper.c new file mode 100644 index 0000000..357a0b6 --- /dev/null +++ b/lib/helper.c @@ -0,0 +1,508 @@ +/* + FUSE: Filesystem in Userspace + Copyright (C) 2001-2007 Miklos Szeredi + + Helper functions to create (simple) standalone programs. With the + aid of these functions it should be possible to create full FUSE + file system by implementing nothing but the request handlers. + + This program can be distributed under the terms of the GNU LGPLv2. + See the file LGPL2.txt. +*/ + +#include "fuse_config.h" +#include "fuse_i.h" +#include "fuse_misc.h" +#include "fuse_opt.h" +#include "fuse_lowlevel.h" +#include "mount_util.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#define FUSE_HELPER_OPT(t, p) \ + { t, offsetof(struct fuse_cmdline_opts, p), 1 } + +static const struct fuse_opt fuse_helper_opts[] = { + FUSE_HELPER_OPT("-h", show_help), + FUSE_HELPER_OPT("--help", show_help), + FUSE_HELPER_OPT("-V", show_version), + FUSE_HELPER_OPT("--version", show_version), + FUSE_HELPER_OPT("-d", debug), + FUSE_HELPER_OPT("debug", debug), + FUSE_HELPER_OPT("-d", foreground), + FUSE_HELPER_OPT("debug", foreground), + FUSE_OPT_KEY("-d", FUSE_OPT_KEY_KEEP), + FUSE_OPT_KEY("debug", FUSE_OPT_KEY_KEEP), + FUSE_HELPER_OPT("-f", foreground), + FUSE_HELPER_OPT("-s", singlethread), + FUSE_HELPER_OPT("fsname=", nodefault_subtype), + FUSE_OPT_KEY("fsname=", FUSE_OPT_KEY_KEEP), +#ifndef __FreeBSD__ + FUSE_HELPER_OPT("subtype=", nodefault_subtype), + FUSE_OPT_KEY("subtype=", FUSE_OPT_KEY_KEEP), +#endif + FUSE_HELPER_OPT("clone_fd", clone_fd), + FUSE_HELPER_OPT("max_idle_threads=%u", max_idle_threads), + FUSE_HELPER_OPT("max_threads=%u", max_threads), + FUSE_OPT_END +}; + +struct fuse_conn_info_opts { + int atomic_o_trunc; + int no_remote_posix_lock; + int no_remote_flock; + int splice_write; + int splice_move; + int splice_read; + int no_splice_write; + int no_splice_move; + int no_splice_read; + int auto_inval_data; + int no_auto_inval_data; + int no_readdirplus; + int no_readdirplus_auto; + int async_dio; + int no_async_dio; + int writeback_cache; + int no_writeback_cache; + int async_read; + int sync_read; + unsigned max_write; + unsigned max_readahead; + unsigned max_background; + unsigned congestion_threshold; + unsigned time_gran; + int set_max_write; + int set_max_readahead; + int set_max_background; + int set_congestion_threshold; + int set_time_gran; +}; + +#define CONN_OPTION(t, p, v) \ + { t, offsetof(struct fuse_conn_info_opts, p), v } +static const struct fuse_opt conn_info_opt_spec[] = { + CONN_OPTION("max_write=%u", max_write, 0), + CONN_OPTION("max_write=", set_max_write, 1), + CONN_OPTION("max_readahead=%u", max_readahead, 0), + CONN_OPTION("max_readahead=", set_max_readahead, 1), + CONN_OPTION("max_background=%u", max_background, 0), + CONN_OPTION("max_background=", set_max_background, 1), + CONN_OPTION("congestion_threshold=%u", congestion_threshold, 0), + CONN_OPTION("congestion_threshold=", set_congestion_threshold, 1), + CONN_OPTION("sync_read", sync_read, 1), + CONN_OPTION("async_read", async_read, 1), + CONN_OPTION("atomic_o_trunc", atomic_o_trunc, 1), + CONN_OPTION("no_remote_lock", no_remote_posix_lock, 1), + CONN_OPTION("no_remote_lock", no_remote_flock, 1), + CONN_OPTION("no_remote_flock", no_remote_flock, 1), + CONN_OPTION("no_remote_posix_lock", no_remote_posix_lock, 1), + CONN_OPTION("splice_write", splice_write, 1), + CONN_OPTION("no_splice_write", no_splice_write, 1), + CONN_OPTION("splice_move", splice_move, 1), + CONN_OPTION("no_splice_move", no_splice_move, 1), + CONN_OPTION("splice_read", splice_read, 1), + CONN_OPTION("no_splice_read", no_splice_read, 1), + CONN_OPTION("auto_inval_data", auto_inval_data, 1), + CONN_OPTION("no_auto_inval_data", no_auto_inval_data, 1), + CONN_OPTION("readdirplus=no", no_readdirplus, 1), + CONN_OPTION("readdirplus=yes", no_readdirplus, 0), + CONN_OPTION("readdirplus=yes", no_readdirplus_auto, 1), + CONN_OPTION("readdirplus=auto", no_readdirplus, 0), + CONN_OPTION("readdirplus=auto", no_readdirplus_auto, 0), + CONN_OPTION("async_dio", async_dio, 1), + CONN_OPTION("no_async_dio", no_async_dio, 1), + CONN_OPTION("writeback_cache", writeback_cache, 1), + CONN_OPTION("no_writeback_cache", no_writeback_cache, 1), + CONN_OPTION("time_gran=%u", time_gran, 0), + CONN_OPTION("time_gran=", set_time_gran, 1), + FUSE_OPT_END +}; + + +void fuse_cmdline_help(void) +{ + printf(" -h --help print help\n" + " -V --version print version\n" + " -d -o debug enable debug output (implies -f)\n" + " -f foreground operation\n" + " -s disable multi-threaded operation\n" + " -o clone_fd use separate fuse device fd for each thread\n" + " (may improve performance)\n" + " -o max_idle_threads the maximum number of idle worker threads\n" + " allowed (default: -1)\n" + " -o max_threads the maximum number of worker threads\n" + " allowed (default: 10)\n"); +} + +static int fuse_helper_opt_proc(void *data, const char *arg, int key, + struct fuse_args *outargs) +{ + (void) outargs; + struct fuse_cmdline_opts *opts = data; + + switch (key) { + case FUSE_OPT_KEY_NONOPT: + if (!opts->mountpoint) { + if (fuse_mnt_parse_fuse_fd(arg) != -1) { + return fuse_opt_add_opt(&opts->mountpoint, arg); + } + + char mountpoint[PATH_MAX] = ""; + if (realpath(arg, mountpoint) == NULL) { + fuse_log(FUSE_LOG_ERR, + "fuse: bad mount point `%s': %s\n", + arg, strerror(errno)); + return -1; + } + return fuse_opt_add_opt(&opts->mountpoint, mountpoint); + } else { + fuse_log(FUSE_LOG_ERR, "fuse: invalid argument `%s'\n", arg); + return -1; + } + + default: + /* Pass through unknown options */ + return 1; + } +} + +/* Under FreeBSD, there is no subtype option so this + function actually sets the fsname */ +static int add_default_subtype(const char *progname, struct fuse_args *args) +{ + int res; + char *subtype_opt; + + const char *basename = strrchr(progname, '/'); + if (basename == NULL) + basename = progname; + else if (basename[1] != '\0') + basename++; + + subtype_opt = (char *) malloc(strlen(basename) + 64); + if (subtype_opt == NULL) { + fuse_log(FUSE_LOG_ERR, "fuse: memory allocation failed\n"); + return -1; + } +#ifdef __FreeBSD__ + sprintf(subtype_opt, "-ofsname=%s", basename); +#else + sprintf(subtype_opt, "-osubtype=%s", basename); +#endif + res = fuse_opt_add_arg(args, subtype_opt); + free(subtype_opt); + return res; +} + +int fuse_parse_cmdline_312(struct fuse_args *args, + struct fuse_cmdline_opts *opts); +FUSE_SYMVER("fuse_parse_cmdline_312", "fuse_parse_cmdline@@FUSE_3.12") +int fuse_parse_cmdline_312(struct fuse_args *args, + struct fuse_cmdline_opts *opts) +{ + memset(opts, 0, sizeof(struct fuse_cmdline_opts)); + + opts->max_idle_threads = UINT_MAX; /* new default in fuse version 3.12 */ + opts->max_threads = 10; + + if (fuse_opt_parse(args, opts, fuse_helper_opts, + fuse_helper_opt_proc) == -1) + return -1; + + /* *Linux*: if neither -o subtype nor -o fsname are specified, + set subtype to program's basename. + *FreeBSD*: if fsname is not specified, set to program's + basename. */ + if (!opts->nodefault_subtype) + if (add_default_subtype(args->argv[0], args) == -1) + return -1; + + return 0; +} + +/** + * struct fuse_cmdline_opts got extended in libfuse-3.12 + */ +int fuse_parse_cmdline_30(struct fuse_args *args, + struct fuse_cmdline_opts *opts); +FUSE_SYMVER("fuse_parse_cmdline_30", "fuse_parse_cmdline@FUSE_3.0") +int fuse_parse_cmdline_30(struct fuse_args *args, + struct fuse_cmdline_opts *out_opts) +{ + struct fuse_cmdline_opts opts; + + int rc = fuse_parse_cmdline_312(args, &opts); + if (rc == 0) { + /* copy up to the size of the old pre 3.12 struct */ + memcpy(out_opts, &opts, + offsetof(struct fuse_cmdline_opts, max_idle_threads) + + sizeof(opts.max_idle_threads)); + } + + return rc; +} + +int fuse_daemonize(int foreground) +{ + if (!foreground) { + int nullfd; + int waiter[2]; + char completed; + + if (pipe(waiter)) { + perror("fuse_daemonize: pipe"); + return -1; + } + + /* + * demonize current process by forking it and killing the + * parent. This makes current process as a child of 'init'. + */ + switch(fork()) { + case -1: + perror("fuse_daemonize: fork"); + close(waiter[0]); + close(waiter[1]); + return -1; + case 0: + /* child */ + close(waiter[0]); + waiter[0] = -1; + break; + default: + /* parent */ + (void) read(waiter[0], &completed, sizeof(completed)); + close(waiter[0]); + close(waiter[1]); + _exit(0); + } + + if (setsid() == -1) { + perror("fuse_daemonize: setsid"); + close(waiter[1]); + return -1; + } + + (void) chdir("/"); + + nullfd = open("/dev/null", O_RDWR, 0); + if (nullfd != -1) { + (void) dup2(nullfd, 0); + (void) dup2(nullfd, 1); + (void) dup2(nullfd, 2); + if (nullfd > 2) + close(nullfd); + } + + /* Propagate completion of daemon initialization */ + completed = 1; + (void) write(waiter[1], &completed, sizeof(completed)); + close(waiter[1]); + waiter[1] = -1; + } else { + (void)chdir("/"); + } + + return 0; +} + +int fuse_main_real_versioned(int argc, char *argv[], + const struct fuse_operations *op, size_t op_size, + struct libfuse_version *version, void *user_data) +{ + struct fuse_args args = FUSE_ARGS_INIT(argc, argv); + struct fuse *fuse; + struct fuse_cmdline_opts opts; + int res; + struct fuse_loop_config *loop_config = NULL; + + if (fuse_parse_cmdline(&args, &opts) != 0) + return 1; + + if (opts.show_version) { + printf("FUSE library version %s\n", PACKAGE_VERSION); + fuse_lowlevel_version(); + res = 0; + goto out1; + } + + if (opts.show_help) { + if(args.argv[0][0] != '\0') + printf("usage: %s [options] \n\n", + args.argv[0]); + printf("FUSE options:\n"); + fuse_cmdline_help(); + fuse_lib_help(&args); + res = 0; + goto out1; + } + + if (!opts.show_help && + !opts.mountpoint) { + fuse_log(FUSE_LOG_ERR, "error: no mountpoint specified\n"); + res = 2; + goto out1; + } + + struct fuse *_fuse_new_31(struct fuse_args *args, + const struct fuse_operations *op, size_t op_size, + struct libfuse_version *version, + void *user_data); + fuse = _fuse_new_31(&args, op, op_size, version, user_data); + if (fuse == NULL) { + res = 3; + goto out1; + } + + if (fuse_mount(fuse,opts.mountpoint) != 0) { + res = 4; + goto out2; + } + + if (fuse_daemonize(opts.foreground) != 0) { + res = 5; + goto out3; + } + + struct fuse_session *se = fuse_get_session(fuse); + if (fuse_set_signal_handlers(se) != 0) { + res = 6; + goto out3; + } + + if (opts.singlethread) + res = fuse_loop(fuse); + else { + loop_config = fuse_loop_cfg_create(); + if (loop_config == NULL) { + res = 7; + goto out3; + } + + fuse_loop_cfg_set_clone_fd(loop_config, opts.clone_fd); + + fuse_loop_cfg_set_idle_threads(loop_config, opts.max_idle_threads); + fuse_loop_cfg_set_max_threads(loop_config, opts.max_threads); + res = fuse_loop_mt(fuse, loop_config); + } + if (res) + res = 8; + + fuse_remove_signal_handlers(se); +out3: + fuse_unmount(fuse); +out2: + fuse_destroy(fuse); +out1: + fuse_loop_cfg_destroy(loop_config); + free(opts.mountpoint); + fuse_opt_free_args(&args); + return res; +} + +/* Not symboled, as not part of the official API */ +int fuse_main_real_30(int argc, char *argv[], const struct fuse_operations *op, + size_t op_size, void *user_data); +int fuse_main_real_30(int argc, char *argv[], const struct fuse_operations *op, + size_t op_size, void *user_data) +{ + struct libfuse_version version = { 0 }; + return fuse_main_real_versioned(argc, argv, op, op_size, &version, + user_data); +} + +void fuse_apply_conn_info_opts(struct fuse_conn_info_opts *opts, + struct fuse_conn_info *conn) +{ + if(opts->set_max_write) + conn->max_write = opts->max_write; + if(opts->set_max_background) + conn->max_background = opts->max_background; + if(opts->set_congestion_threshold) + conn->congestion_threshold = opts->congestion_threshold; + if(opts->set_time_gran) + conn->time_gran = opts->time_gran; + if(opts->set_max_readahead) + conn->max_readahead = opts->max_readahead; + +#define LL_ENABLE(cond, cap) \ + do { \ + if (cond) \ + fuse_set_feature_flag(conn, cap); \ + } while (0) + +#define LL_DISABLE(cond, cap) \ + do { \ + if (cond) \ + fuse_unset_feature_flag(conn, cap); \ + } while (0) + + LL_ENABLE(opts->splice_read, FUSE_CAP_SPLICE_READ); + LL_DISABLE(opts->no_splice_read, FUSE_CAP_SPLICE_READ); + + LL_ENABLE(opts->splice_write, FUSE_CAP_SPLICE_WRITE); + LL_DISABLE(opts->no_splice_write, FUSE_CAP_SPLICE_WRITE); + + LL_ENABLE(opts->splice_move, FUSE_CAP_SPLICE_MOVE); + LL_DISABLE(opts->no_splice_move, FUSE_CAP_SPLICE_MOVE); + + LL_ENABLE(opts->auto_inval_data, FUSE_CAP_AUTO_INVAL_DATA); + LL_DISABLE(opts->no_auto_inval_data, FUSE_CAP_AUTO_INVAL_DATA); + + LL_DISABLE(opts->no_readdirplus, FUSE_CAP_READDIRPLUS); + LL_DISABLE(opts->no_readdirplus_auto, FUSE_CAP_READDIRPLUS_AUTO); + + LL_ENABLE(opts->async_dio, FUSE_CAP_ASYNC_DIO); + LL_DISABLE(opts->no_async_dio, FUSE_CAP_ASYNC_DIO); + + LL_ENABLE(opts->writeback_cache, FUSE_CAP_WRITEBACK_CACHE); + LL_DISABLE(opts->no_writeback_cache, FUSE_CAP_WRITEBACK_CACHE); + + LL_ENABLE(opts->async_read, FUSE_CAP_ASYNC_READ); + LL_DISABLE(opts->sync_read, FUSE_CAP_ASYNC_READ); + + LL_DISABLE(opts->no_remote_posix_lock, FUSE_CAP_POSIX_LOCKS); + LL_DISABLE(opts->no_remote_flock, FUSE_CAP_FLOCK_LOCKS); +} + +struct fuse_conn_info_opts* fuse_parse_conn_info_opts(struct fuse_args *args) +{ + struct fuse_conn_info_opts *opts; + + opts = calloc(1, sizeof(struct fuse_conn_info_opts)); + if(opts == NULL) { + fuse_log(FUSE_LOG_ERR, "calloc failed\n"); + return NULL; + } + if(fuse_opt_parse(args, opts, conn_info_opt_spec, NULL) == -1) { + free(opts); + return NULL; + } + return opts; +} + +int fuse_open_channel(const char *mountpoint, const char* options) +{ + struct mount_opts *opts = NULL; + int fd = -1; + const char *argv[] = { "", "-o", options }; + int argc = sizeof(argv) / sizeof(argv[0]); + struct fuse_args args = FUSE_ARGS_INIT(argc, (char**) argv); + + opts = parse_mount_opts(&args); + if (opts == NULL) + return -1; + + fd = fuse_kern_mount(mountpoint, opts); + destroy_mount_opts(opts); + + return fd; +} diff --git a/lib/meson.build b/lib/meson.build new file mode 100644 index 0000000..fcd9574 --- /dev/null +++ b/lib/meson.build @@ -0,0 +1,66 @@ +libfuse_sources = ['fuse.c', 'fuse_i.h', 'fuse_loop.c', 'fuse_loop_mt.c', + 'fuse_lowlevel.c', 'fuse_misc.h', 'fuse_opt.c', + 'fuse_signals.c', 'buffer.c', 'cuse_lowlevel.c', + 'helper.c', 'modules/subdir.c', 'mount_util.c', + 'fuse_log.c', 'compat.c', 'util.c', 'util.h' ] + +if host_machine.system().startswith('linux') + libfuse_sources += [ 'mount.c' ] +else + libfuse_sources += [ 'mount_bsd.c' ] +endif + +deps = [ thread_dep ] +if private_cfg.get('HAVE_ICONV') + libfuse_sources += [ 'modules/iconv.c' ] + libiconv = cc.find_library('iconv', required: false) + if libiconv.found() + deps += [ libiconv ] + endif +endif + +if private_cfg.get('HAVE_URING', false) + libfuse_sources += [ 'fuse_uring.c' ] + deps += [ dependency('liburing') ] + deps += [ dependency('numa') ] +endif + + + +libdl = cc.find_library('dl', required: false) +if libdl.found() + deps += [ libdl ] +endif + +if host_machine.system().startswith('netbsd') + deps += [ cc.find_library('perfuse'), + cc.find_library('puffs') ] +else + # Required for clock_gettime before glibc 2.17 + deps += cc.find_library('rt') +endif + +fusermount_path = join_paths(get_option('prefix'), get_option('bindir')) +libfuse = library('fuse3', + libfuse_sources, + version: base_version, + soversion: '4', + include_directories: include_dirs, + dependencies: deps, + install: true, + link_depends: 'fuse_versionscript', + c_args: [ '-DFUSE_USE_VERSION=317', + '-DFUSERMOUNT_DIR="@0@"'.format(fusermount_path) ], + link_args: ['-Wl,--version-script,' + meson.current_source_dir() + + '/fuse_versionscript' ]) + +pkg = import('pkgconfig') +pkg.generate(libraries: [ libfuse, '-lpthread' ], + libraries_private: '-ldl', + version: meson.project_version(), + name: 'fuse3', + description: 'Filesystem in Userspace', + subdirs: 'fuse3') + +libfuse_dep = declare_dependency(include_directories: include_dirs, + link_with: libfuse, dependencies: deps) diff --git a/lib/modules/iconv.c b/lib/modules/iconv.c new file mode 100644 index 0000000..5c055a7 --- /dev/null +++ b/lib/modules/iconv.c @@ -0,0 +1,756 @@ +/* + fuse iconv module: file name charset conversion + Copyright (C) 2007 Miklos Szeredi + + This program can be distributed under the terms of the GNU LGPLv2. + See the file LGPL2.txt +*/ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct iconv { + struct fuse_fs *next; + pthread_mutex_t lock; + char *from_code; + char *to_code; + iconv_t tofs; + iconv_t fromfs; +}; + +struct iconv_dh { + struct iconv *ic; + void *prev_buf; + fuse_fill_dir_t prev_filler; +}; + +static struct iconv *iconv_get(void) +{ + return fuse_get_context()->private_data; +} + +static int iconv_convpath(struct iconv *ic, const char *path, char **newpathp, + int fromfs) +{ + size_t pathlen; + size_t newpathlen; + char *newpath; + size_t plen; + char *p; + size_t res; + int err; + + if (path == NULL) { + *newpathp = NULL; + return 0; + } + + pathlen = strlen(path); + newpathlen = pathlen * 4; + newpath = malloc(newpathlen + 1); + if (!newpath) + return -ENOMEM; + + plen = newpathlen; + p = newpath; + pthread_mutex_lock(&ic->lock); + do { + res = iconv(fromfs ? ic->fromfs : ic->tofs, (char **) &path, + &pathlen, &p, &plen); + if (res == (size_t) -1) { + char *tmp; + size_t inc; + + err = -EILSEQ; + if (errno != E2BIG) + goto err; + + inc = (pathlen + 1) * 4; + newpathlen += inc; + int dp = p - newpath; + tmp = realloc(newpath, newpathlen + 1); + err = -ENOMEM; + if (!tmp) + goto err; + + p = tmp + dp; + plen += inc; + newpath = tmp; + } + } while (res == (size_t) -1); + pthread_mutex_unlock(&ic->lock); + *p = '\0'; + *newpathp = newpath; + return 0; + +err: + iconv(fromfs ? ic->fromfs : ic->tofs, NULL, NULL, NULL, NULL); + pthread_mutex_unlock(&ic->lock); + free(newpath); + return err; +} + +static int iconv_getattr(const char *path, struct stat *stbuf, + struct fuse_file_info *fi) +{ + struct iconv *ic = iconv_get(); + char *newpath; + int err = iconv_convpath(ic, path, &newpath, 0); + if (!err) { + err = fuse_fs_getattr(ic->next, newpath, stbuf, fi); + free(newpath); + } + return err; +} + +static int iconv_access(const char *path, int mask) +{ + struct iconv *ic = iconv_get(); + char *newpath; + int err = iconv_convpath(ic, path, &newpath, 0); + if (!err) { + err = fuse_fs_access(ic->next, newpath, mask); + free(newpath); + } + return err; +} + +static int iconv_readlink(const char *path, char *buf, size_t size) +{ + struct iconv *ic = iconv_get(); + char *newpath; + int err = iconv_convpath(ic, path, &newpath, 0); + if (!err) { + err = fuse_fs_readlink(ic->next, newpath, buf, size); + if (!err) { + char *newlink; + err = iconv_convpath(ic, buf, &newlink, 1); + if (!err) { + strncpy(buf, newlink, size - 1); + buf[size - 1] = '\0'; + free(newlink); + } + } + free(newpath); + } + return err; +} + +static int iconv_opendir(const char *path, struct fuse_file_info *fi) +{ + struct iconv *ic = iconv_get(); + char *newpath; + int err = iconv_convpath(ic, path, &newpath, 0); + if (!err) { + err = fuse_fs_opendir(ic->next, newpath, fi); + free(newpath); + } + return err; +} + +static int iconv_dir_fill(void *buf, const char *name, + const struct stat *stbuf, off_t off, + enum fuse_fill_dir_flags flags) +{ + struct iconv_dh *dh = buf; + char *newname; + int res = 0; + if (iconv_convpath(dh->ic, name, &newname, 1) == 0) { + res = dh->prev_filler(dh->prev_buf, newname, stbuf, off, flags); + free(newname); + } + return res; +} + +static int iconv_readdir(const char *path, void *buf, fuse_fill_dir_t filler, + off_t offset, struct fuse_file_info *fi, + enum fuse_readdir_flags flags) +{ + struct iconv *ic = iconv_get(); + char *newpath; + int err = iconv_convpath(ic, path, &newpath, 0); + if (!err) { + struct iconv_dh dh; + dh.ic = ic; + dh.prev_buf = buf; + dh.prev_filler = filler; + err = fuse_fs_readdir(ic->next, newpath, &dh, iconv_dir_fill, + offset, fi, flags); + free(newpath); + } + return err; +} + +static int iconv_releasedir(const char *path, struct fuse_file_info *fi) +{ + struct iconv *ic = iconv_get(); + char *newpath; + int err = iconv_convpath(ic, path, &newpath, 0); + if (!err) { + err = fuse_fs_releasedir(ic->next, newpath, fi); + free(newpath); + } + return err; +} + +static int iconv_mknod(const char *path, mode_t mode, dev_t rdev) +{ + struct iconv *ic = iconv_get(); + char *newpath; + int err = iconv_convpath(ic, path, &newpath, 0); + if (!err) { + err = fuse_fs_mknod(ic->next, newpath, mode, rdev); + free(newpath); + } + return err; +} + +static int iconv_mkdir(const char *path, mode_t mode) +{ + struct iconv *ic = iconv_get(); + char *newpath; + int err = iconv_convpath(ic, path, &newpath, 0); + if (!err) { + err = fuse_fs_mkdir(ic->next, newpath, mode); + free(newpath); + } + return err; +} + +static int iconv_unlink(const char *path) +{ + struct iconv *ic = iconv_get(); + char *newpath; + int err = iconv_convpath(ic, path, &newpath, 0); + if (!err) { + err = fuse_fs_unlink(ic->next, newpath); + free(newpath); + } + return err; +} + +static int iconv_rmdir(const char *path) +{ + struct iconv *ic = iconv_get(); + char *newpath; + int err = iconv_convpath(ic, path, &newpath, 0); + if (!err) { + err = fuse_fs_rmdir(ic->next, newpath); + free(newpath); + } + return err; +} + +static int iconv_symlink(const char *from, const char *to) +{ + struct iconv *ic = iconv_get(); + char *newfrom; + char *newto; + int err = iconv_convpath(ic, from, &newfrom, 0); + if (!err) { + err = iconv_convpath(ic, to, &newto, 0); + if (!err) { + err = fuse_fs_symlink(ic->next, newfrom, newto); + free(newto); + } + free(newfrom); + } + return err; +} + +static int iconv_rename(const char *from, const char *to, unsigned int flags) +{ + struct iconv *ic = iconv_get(); + char *newfrom; + char *newto; + int err = iconv_convpath(ic, from, &newfrom, 0); + if (!err) { + err = iconv_convpath(ic, to, &newto, 0); + if (!err) { + err = fuse_fs_rename(ic->next, newfrom, newto, flags); + free(newto); + } + free(newfrom); + } + return err; +} + +static int iconv_link(const char *from, const char *to) +{ + struct iconv *ic = iconv_get(); + char *newfrom; + char *newto; + int err = iconv_convpath(ic, from, &newfrom, 0); + if (!err) { + err = iconv_convpath(ic, to, &newto, 0); + if (!err) { + err = fuse_fs_link(ic->next, newfrom, newto); + free(newto); + } + free(newfrom); + } + return err; +} + +static int iconv_chmod(const char *path, mode_t mode, + struct fuse_file_info *fi) +{ + struct iconv *ic = iconv_get(); + char *newpath; + int err = iconv_convpath(ic, path, &newpath, 0); + if (!err) { + err = fuse_fs_chmod(ic->next, newpath, mode, fi); + free(newpath); + } + return err; +} + +static int iconv_chown(const char *path, uid_t uid, gid_t gid, + struct fuse_file_info *fi) +{ + struct iconv *ic = iconv_get(); + char *newpath; + int err = iconv_convpath(ic, path, &newpath, 0); + if (!err) { + err = fuse_fs_chown(ic->next, newpath, uid, gid, fi); + free(newpath); + } + return err; +} + +static int iconv_truncate(const char *path, off_t size, + struct fuse_file_info *fi) +{ + struct iconv *ic = iconv_get(); + char *newpath; + int err = iconv_convpath(ic, path, &newpath, 0); + if (!err) { + err = fuse_fs_truncate(ic->next, newpath, size, fi); + free(newpath); + } + return err; +} + +static int iconv_utimens(const char *path, const struct timespec ts[2], + struct fuse_file_info *fi) +{ + struct iconv *ic = iconv_get(); + char *newpath; + int err = iconv_convpath(ic, path, &newpath, 0); + if (!err) { + err = fuse_fs_utimens(ic->next, newpath, ts, fi); + free(newpath); + } + return err; +} + +static int iconv_create(const char *path, mode_t mode, + struct fuse_file_info *fi) +{ + struct iconv *ic = iconv_get(); + char *newpath; + int err = iconv_convpath(ic, path, &newpath, 0); + if (!err) { + err = fuse_fs_create(ic->next, newpath, mode, fi); + free(newpath); + } + return err; +} + +static int iconv_open_file(const char *path, struct fuse_file_info *fi) +{ + struct iconv *ic = iconv_get(); + char *newpath; + int err = iconv_convpath(ic, path, &newpath, 0); + if (!err) { + err = fuse_fs_open(ic->next, newpath, fi); + free(newpath); + } + return err; +} + +static int iconv_read_buf(const char *path, struct fuse_bufvec **bufp, + size_t size, off_t offset, struct fuse_file_info *fi) +{ + struct iconv *ic = iconv_get(); + char *newpath; + int err = iconv_convpath(ic, path, &newpath, 0); + if (!err) { + err = fuse_fs_read_buf(ic->next, newpath, bufp, size, offset, fi); + free(newpath); + } + return err; +} + +static int iconv_write_buf(const char *path, struct fuse_bufvec *buf, + off_t offset, struct fuse_file_info *fi) +{ + struct iconv *ic = iconv_get(); + char *newpath; + int err = iconv_convpath(ic, path, &newpath, 0); + if (!err) { + err = fuse_fs_write_buf(ic->next, newpath, buf, offset, fi); + free(newpath); + } + return err; +} + +static int iconv_statfs(const char *path, struct statvfs *stbuf) +{ + struct iconv *ic = iconv_get(); + char *newpath; + int err = iconv_convpath(ic, path, &newpath, 0); + if (!err) { + err = fuse_fs_statfs(ic->next, newpath, stbuf); + free(newpath); + } + return err; +} + +static int iconv_flush(const char *path, struct fuse_file_info *fi) +{ + struct iconv *ic = iconv_get(); + char *newpath; + int err = iconv_convpath(ic, path, &newpath, 0); + if (!err) { + err = fuse_fs_flush(ic->next, newpath, fi); + free(newpath); + } + return err; +} + +static int iconv_release(const char *path, struct fuse_file_info *fi) +{ + struct iconv *ic = iconv_get(); + char *newpath; + int err = iconv_convpath(ic, path, &newpath, 0); + if (!err) { + err = fuse_fs_release(ic->next, newpath, fi); + free(newpath); + } + return err; +} + +static int iconv_fsync(const char *path, int isdatasync, + struct fuse_file_info *fi) +{ + struct iconv *ic = iconv_get(); + char *newpath; + int err = iconv_convpath(ic, path, &newpath, 0); + if (!err) { + err = fuse_fs_fsync(ic->next, newpath, isdatasync, fi); + free(newpath); + } + return err; +} + +static int iconv_fsyncdir(const char *path, int isdatasync, + struct fuse_file_info *fi) +{ + struct iconv *ic = iconv_get(); + char *newpath; + int err = iconv_convpath(ic, path, &newpath, 0); + if (!err) { + err = fuse_fs_fsyncdir(ic->next, newpath, isdatasync, fi); + free(newpath); + } + return err; +} + +static int iconv_setxattr(const char *path, const char *name, + const char *value, size_t size, int flags) +{ + struct iconv *ic = iconv_get(); + char *newpath; + int err = iconv_convpath(ic, path, &newpath, 0); + if (!err) { + err = fuse_fs_setxattr(ic->next, newpath, name, value, size, + flags); + free(newpath); + } + return err; +} + +static int iconv_getxattr(const char *path, const char *name, char *value, + size_t size) +{ + struct iconv *ic = iconv_get(); + char *newpath; + int err = iconv_convpath(ic, path, &newpath, 0); + if (!err) { + err = fuse_fs_getxattr(ic->next, newpath, name, value, size); + free(newpath); + } + return err; +} + +static int iconv_listxattr(const char *path, char *list, size_t size) +{ + struct iconv *ic = iconv_get(); + char *newpath; + int err = iconv_convpath(ic, path, &newpath, 0); + if (!err) { + err = fuse_fs_listxattr(ic->next, newpath, list, size); + free(newpath); + } + return err; +} + +static int iconv_removexattr(const char *path, const char *name) +{ + struct iconv *ic = iconv_get(); + char *newpath; + int err = iconv_convpath(ic, path, &newpath, 0); + if (!err) { + err = fuse_fs_removexattr(ic->next, newpath, name); + free(newpath); + } + return err; +} + +static int iconv_lock(const char *path, struct fuse_file_info *fi, int cmd, + struct flock *lock) +{ + struct iconv *ic = iconv_get(); + char *newpath; + int err = iconv_convpath(ic, path, &newpath, 0); + if (!err) { + err = fuse_fs_lock(ic->next, newpath, fi, cmd, lock); + free(newpath); + } + return err; +} + +static int iconv_flock(const char *path, struct fuse_file_info *fi, int op) +{ + struct iconv *ic = iconv_get(); + char *newpath; + int err = iconv_convpath(ic, path, &newpath, 0); + if (!err) { + err = fuse_fs_flock(ic->next, newpath, fi, op); + free(newpath); + } + return err; +} + +static int iconv_bmap(const char *path, size_t blocksize, uint64_t *idx) +{ + struct iconv *ic = iconv_get(); + char *newpath; + int err = iconv_convpath(ic, path, &newpath, 0); + if (!err) { + err = fuse_fs_bmap(ic->next, newpath, blocksize, idx); + free(newpath); + } + return err; +} + +static off_t iconv_lseek(const char *path, off_t off, int whence, + struct fuse_file_info *fi) +{ + struct iconv *ic = iconv_get(); + char *newpath; + int res = iconv_convpath(ic, path, &newpath, 0); + if (!res) { + res = fuse_fs_lseek(ic->next, newpath, off, whence, fi); + free(newpath); + } + return res; +} + +#ifdef HAVE_STATX +static int iconv_statx(const char *path, int flags, int mask, struct statx *stxbuf, + struct fuse_file_info *fi) +{ + struct iconv *ic = iconv_get(); + char *newpath; + int res = iconv_convpath(ic, path, &newpath, 0); + + if (!res) { + res = fuse_fs_statx(ic->next, newpath, flags, mask, stxbuf, fi); + free(newpath); + } + return res; +} +#endif + +static void *iconv_init(struct fuse_conn_info *conn, + struct fuse_config *cfg) +{ + struct iconv *ic = iconv_get(); + fuse_fs_init(ic->next, conn, cfg); + /* Don't touch cfg->nullpath_ok, we can work with + either */ + return ic; +} + +static void iconv_destroy(void *data) +{ + struct iconv *ic = data; + fuse_fs_destroy(ic->next); + iconv_close(ic->tofs); + iconv_close(ic->fromfs); + pthread_mutex_destroy(&ic->lock); + free(ic->from_code); + free(ic->to_code); + free(ic); +} + +static const struct fuse_operations iconv_oper = { + .destroy = iconv_destroy, + .init = iconv_init, + .getattr = iconv_getattr, + .access = iconv_access, + .readlink = iconv_readlink, + .opendir = iconv_opendir, + .readdir = iconv_readdir, + .releasedir = iconv_releasedir, + .mknod = iconv_mknod, + .mkdir = iconv_mkdir, + .symlink = iconv_symlink, + .unlink = iconv_unlink, + .rmdir = iconv_rmdir, + .rename = iconv_rename, + .link = iconv_link, + .chmod = iconv_chmod, + .chown = iconv_chown, + .truncate = iconv_truncate, + .utimens = iconv_utimens, + .create = iconv_create, + .open = iconv_open_file, + .read_buf = iconv_read_buf, + .write_buf = iconv_write_buf, + .statfs = iconv_statfs, + .flush = iconv_flush, + .release = iconv_release, + .fsync = iconv_fsync, + .fsyncdir = iconv_fsyncdir, + .setxattr = iconv_setxattr, + .getxattr = iconv_getxattr, + .listxattr = iconv_listxattr, + .removexattr = iconv_removexattr, + .lock = iconv_lock, + .flock = iconv_flock, + .bmap = iconv_bmap, + .lseek = iconv_lseek, +#ifdef HAVE_STATX + .statx = iconv_statx, +#endif +}; + +static const struct fuse_opt iconv_opts[] = { + FUSE_OPT_KEY("-h", 0), + FUSE_OPT_KEY("--help", 0), + { "from_code=%s", offsetof(struct iconv, from_code), 0 }, + { "to_code=%s", offsetof(struct iconv, to_code), 1 }, + FUSE_OPT_END +}; + +static void iconv_help(void) +{ + char *charmap; + const char *old = setlocale(LC_CTYPE, ""); + + charmap = strdup(nl_langinfo(CODESET)); + if (old) + setlocale(LC_CTYPE, old); + else + perror("setlocale"); + + printf( +" -o from_code=CHARSET original encoding of file names (default: UTF-8)\n" +" -o to_code=CHARSET new encoding of the file names (default: %s)\n", + charmap); + free(charmap); +} + +static int iconv_opt_proc(void *data, const char *arg, int key, + struct fuse_args *outargs) +{ + (void) data; (void) arg; (void) outargs; + + if (!key) { + iconv_help(); + return -1; + } + + return 1; +} + +static struct fuse_fs *iconv_new(struct fuse_args *args, + struct fuse_fs *next[]) +{ + struct fuse_fs *fs; + struct iconv *ic; + const char *old = NULL; + const char *from; + const char *to; + + ic = calloc(1, sizeof(struct iconv)); + if (ic == NULL) { + fuse_log(FUSE_LOG_ERR, "fuse-iconv: memory allocation failed\n"); + return NULL; + } + + if (fuse_opt_parse(args, ic, iconv_opts, iconv_opt_proc) == -1) + goto out_free; + + if (!next[0] || next[1]) { + fuse_log(FUSE_LOG_ERR, "fuse-iconv: exactly one next filesystem required\n"); + goto out_free; + } + + from = ic->from_code ? ic->from_code : "UTF-8"; + to = ic->to_code ? ic->to_code : ""; + /* FIXME: detect charset equivalence? */ + if (!to[0]) + old = setlocale(LC_CTYPE, ""); + ic->tofs = iconv_open(from, to); + if (ic->tofs == (iconv_t) -1) { + fuse_log(FUSE_LOG_ERR, "fuse-iconv: cannot convert from %s to %s\n", + to, from); + goto out_free; + } + ic->fromfs = iconv_open(to, from); + if (ic->fromfs == (iconv_t) -1) { + fuse_log(FUSE_LOG_ERR, "fuse-iconv: cannot convert from %s to %s\n", + from, to); + goto out_iconv_close_to; + } + if (old) { + setlocale(LC_CTYPE, old); + old = NULL; + } + + ic->next = next[0]; + fs = fuse_fs_new(&iconv_oper, sizeof(iconv_oper), ic); + if (!fs) + goto out_iconv_close_from; + + return fs; + +out_iconv_close_from: + iconv_close(ic->fromfs); +out_iconv_close_to: + iconv_close(ic->tofs); +out_free: + free(ic->from_code); + free(ic->to_code); + free(ic); + if (old) { + setlocale(LC_CTYPE, old); + } + return NULL; +} + +FUSE_REGISTER_MODULE(iconv, iconv_new); diff --git a/lib/modules/subdir.c b/lib/modules/subdir.c new file mode 100644 index 0000000..67c4697 --- /dev/null +++ b/lib/modules/subdir.c @@ -0,0 +1,708 @@ +/* + fuse subdir module: offset paths with a base directory + Copyright (C) 2007 Miklos Szeredi + + This program can be distributed under the terms of the GNU LGPLv2. + See the file LGPL2.txt +*/ + +#include + +#include +#include +#include +#include +#include +#include + +struct subdir { + char *base; + size_t baselen; + int rellinks; + struct fuse_fs *next; +}; + +static struct subdir *subdir_get(void) +{ + return fuse_get_context()->private_data; +} + +static int subdir_addpath(struct subdir *d, const char *path, char **newpathp) +{ + char *newpath = NULL; + + if (path != NULL) { + unsigned newlen = d->baselen + strlen(path); + + newpath = malloc(newlen + 2); + if (!newpath) + return -ENOMEM; + + if (path[0] == '/') + path++; + strcpy(newpath, d->base); + strcpy(newpath + d->baselen, path); + if (!newpath[0]) + strcpy(newpath, "."); + } + *newpathp = newpath; + + return 0; +} + +static int subdir_getattr(const char *path, struct stat *stbuf, + struct fuse_file_info *fi) +{ + struct subdir *d = subdir_get(); + char *newpath; + int err = subdir_addpath(d, path, &newpath); + if (!err) { + err = fuse_fs_getattr(d->next, newpath, stbuf, fi); + free(newpath); + } + return err; +} + +static int subdir_access(const char *path, int mask) +{ + struct subdir *d = subdir_get(); + char *newpath; + int err = subdir_addpath(d, path, &newpath); + if (!err) { + err = fuse_fs_access(d->next, newpath, mask); + free(newpath); + } + return err; +} + + +static int count_components(const char *p) +{ + int ctr; + + for (; *p == '/'; p++); + for (ctr = 0; *p; ctr++) { + for (; *p && *p != '/'; p++); + for (; *p == '/'; p++); + } + return ctr; +} + +static void strip_common(const char **sp, const char **tp) +{ + const char *s = *sp; + const char *t = *tp; + do { + for (; *s == '/'; s++); + for (; *t == '/'; t++); + *tp = t; + *sp = s; + for (; *s == *t && *s && *s != '/'; s++, t++); + } while ((*s == *t && *s) || (!*s && *t == '/') || (*s == '/' && !*t)); +} + +static void transform_symlink(struct subdir *d, const char *path, + char *buf, size_t size) +{ + const char *l = buf; + size_t llen; + char *s; + int dotdots; + int i; + + if (l[0] != '/' || d->base[0] != '/') + return; + + strip_common(&l, &path); + if (l - buf < (long) d->baselen) + return; + + dotdots = count_components(path); + if (!dotdots) + return; + dotdots--; + + llen = strlen(l); + if (dotdots * 3 + llen + 2 > size) + return; + + s = buf + dotdots * 3; + if (llen) + memmove(s, l, llen + 1); + else if (!dotdots) + strcpy(s, "."); + else + *s = '\0'; + + for (s = buf, i = 0; i < dotdots; i++, s += 3) + memcpy(s, "../", 3); +} + + +static int subdir_readlink(const char *path, char *buf, size_t size) +{ + struct subdir *d = subdir_get(); + char *newpath; + int err = subdir_addpath(d, path, &newpath); + if (!err) { + err = fuse_fs_readlink(d->next, newpath, buf, size); + if (!err && d->rellinks) + transform_symlink(d, newpath, buf, size); + free(newpath); + } + return err; +} + +static int subdir_opendir(const char *path, struct fuse_file_info *fi) +{ + struct subdir *d = subdir_get(); + char *newpath; + int err = subdir_addpath(d, path, &newpath); + if (!err) { + err = fuse_fs_opendir(d->next, newpath, fi); + free(newpath); + } + return err; +} + +static int subdir_readdir(const char *path, void *buf, + fuse_fill_dir_t filler, off_t offset, + struct fuse_file_info *fi, + enum fuse_readdir_flags flags) +{ + struct subdir *d = subdir_get(); + char *newpath; + int err = subdir_addpath(d, path, &newpath); + if (!err) { + err = fuse_fs_readdir(d->next, newpath, buf, filler, offset, + fi, flags); + free(newpath); + } + return err; +} + +static int subdir_releasedir(const char *path, struct fuse_file_info *fi) +{ + struct subdir *d = subdir_get(); + char *newpath; + int err = subdir_addpath(d, path, &newpath); + if (!err) { + err = fuse_fs_releasedir(d->next, newpath, fi); + free(newpath); + } + return err; +} + +static int subdir_mknod(const char *path, mode_t mode, dev_t rdev) +{ + struct subdir *d = subdir_get(); + char *newpath; + int err = subdir_addpath(d, path, &newpath); + if (!err) { + err = fuse_fs_mknod(d->next, newpath, mode, rdev); + free(newpath); + } + return err; +} + +static int subdir_mkdir(const char *path, mode_t mode) +{ + struct subdir *d = subdir_get(); + char *newpath; + int err = subdir_addpath(d, path, &newpath); + if (!err) { + err = fuse_fs_mkdir(d->next, newpath, mode); + free(newpath); + } + return err; +} + +static int subdir_unlink(const char *path) +{ + struct subdir *d = subdir_get(); + char *newpath; + int err = subdir_addpath(d, path, &newpath); + if (!err) { + err = fuse_fs_unlink(d->next, newpath); + free(newpath); + } + return err; +} + +static int subdir_rmdir(const char *path) +{ + struct subdir *d = subdir_get(); + char *newpath; + int err = subdir_addpath(d, path, &newpath); + if (!err) { + err = fuse_fs_rmdir(d->next, newpath); + free(newpath); + } + return err; +} + +static int subdir_symlink(const char *from, const char *path) +{ + struct subdir *d = subdir_get(); + char *newpath; + int err = subdir_addpath(d, path, &newpath); + if (!err) { + err = fuse_fs_symlink(d->next, from, newpath); + free(newpath); + } + return err; +} + +static int subdir_rename(const char *from, const char *to, unsigned int flags) +{ + struct subdir *d = subdir_get(); + char *newfrom; + char *newto; + int err = subdir_addpath(d, from, &newfrom); + if (!err) { + err = subdir_addpath(d, to, &newto); + if (!err) { + err = fuse_fs_rename(d->next, newfrom, newto, flags); + free(newto); + } + free(newfrom); + } + return err; +} + +static int subdir_link(const char *from, const char *to) +{ + struct subdir *d = subdir_get(); + char *newfrom; + char *newto; + int err = subdir_addpath(d, from, &newfrom); + if (!err) { + err = subdir_addpath(d, to, &newto); + if (!err) { + err = fuse_fs_link(d->next, newfrom, newto); + free(newto); + } + free(newfrom); + } + return err; +} + +static int subdir_chmod(const char *path, mode_t mode, + struct fuse_file_info *fi) +{ + struct subdir *d = subdir_get(); + char *newpath; + int err = subdir_addpath(d, path, &newpath); + if (!err) { + err = fuse_fs_chmod(d->next, newpath, mode, fi); + free(newpath); + } + return err; +} + +static int subdir_chown(const char *path, uid_t uid, gid_t gid, + struct fuse_file_info *fi) +{ + struct subdir *d = subdir_get(); + char *newpath; + int err = subdir_addpath(d, path, &newpath); + if (!err) { + err = fuse_fs_chown(d->next, newpath, uid, gid, fi); + free(newpath); + } + return err; +} + +static int subdir_truncate(const char *path, off_t size, + struct fuse_file_info *fi) +{ + struct subdir *d = subdir_get(); + char *newpath; + int err = subdir_addpath(d, path, &newpath); + if (!err) { + err = fuse_fs_truncate(d->next, newpath, size, fi); + free(newpath); + } + return err; +} + +static int subdir_utimens(const char *path, const struct timespec ts[2], + struct fuse_file_info *fi) +{ + struct subdir *d = subdir_get(); + char *newpath; + int err = subdir_addpath(d, path, &newpath); + if (!err) { + err = fuse_fs_utimens(d->next, newpath, ts, fi); + free(newpath); + } + return err; +} + +static int subdir_create(const char *path, mode_t mode, + struct fuse_file_info *fi) +{ + struct subdir *d = subdir_get(); + char *newpath; + int err = subdir_addpath(d, path, &newpath); + if (!err) { + err = fuse_fs_create(d->next, newpath, mode, fi); + free(newpath); + } + return err; +} + +static int subdir_open(const char *path, struct fuse_file_info *fi) +{ + struct subdir *d = subdir_get(); + char *newpath; + int err = subdir_addpath(d, path, &newpath); + if (!err) { + err = fuse_fs_open(d->next, newpath, fi); + free(newpath); + } + return err; +} + +static int subdir_read_buf(const char *path, struct fuse_bufvec **bufp, + size_t size, off_t offset, struct fuse_file_info *fi) +{ + struct subdir *d = subdir_get(); + char *newpath; + int err = subdir_addpath(d, path, &newpath); + if (!err) { + err = fuse_fs_read_buf(d->next, newpath, bufp, size, offset, fi); + free(newpath); + } + return err; +} + +static int subdir_write_buf(const char *path, struct fuse_bufvec *buf, + off_t offset, struct fuse_file_info *fi) +{ + struct subdir *d = subdir_get(); + char *newpath; + int err = subdir_addpath(d, path, &newpath); + if (!err) { + err = fuse_fs_write_buf(d->next, newpath, buf, offset, fi); + free(newpath); + } + return err; +} + +static int subdir_statfs(const char *path, struct statvfs *stbuf) +{ + struct subdir *d = subdir_get(); + char *newpath; + int err = subdir_addpath(d, path, &newpath); + if (!err) { + err = fuse_fs_statfs(d->next, newpath, stbuf); + free(newpath); + } + return err; +} + +static int subdir_flush(const char *path, struct fuse_file_info *fi) +{ + struct subdir *d = subdir_get(); + char *newpath; + int err = subdir_addpath(d, path, &newpath); + if (!err) { + err = fuse_fs_flush(d->next, newpath, fi); + free(newpath); + } + return err; +} + +static int subdir_release(const char *path, struct fuse_file_info *fi) +{ + struct subdir *d = subdir_get(); + char *newpath; + int err = subdir_addpath(d, path, &newpath); + if (!err) { + err = fuse_fs_release(d->next, newpath, fi); + free(newpath); + } + return err; +} + +static int subdir_fsync(const char *path, int isdatasync, + struct fuse_file_info *fi) +{ + struct subdir *d = subdir_get(); + char *newpath; + int err = subdir_addpath(d, path, &newpath); + if (!err) { + err = fuse_fs_fsync(d->next, newpath, isdatasync, fi); + free(newpath); + } + return err; +} + +static int subdir_fsyncdir(const char *path, int isdatasync, + struct fuse_file_info *fi) +{ + struct subdir *d = subdir_get(); + char *newpath; + int err = subdir_addpath(d, path, &newpath); + if (!err) { + err = fuse_fs_fsyncdir(d->next, newpath, isdatasync, fi); + free(newpath); + } + return err; +} + +static int subdir_setxattr(const char *path, const char *name, + const char *value, size_t size, int flags) +{ + struct subdir *d = subdir_get(); + char *newpath; + int err = subdir_addpath(d, path, &newpath); + if (!err) { + err = fuse_fs_setxattr(d->next, newpath, name, value, size, + flags); + free(newpath); + } + return err; +} + +static int subdir_getxattr(const char *path, const char *name, char *value, + size_t size) +{ + struct subdir *d = subdir_get(); + char *newpath; + int err = subdir_addpath(d, path, &newpath); + if (!err) { + err = fuse_fs_getxattr(d->next, newpath, name, value, size); + free(newpath); + } + return err; +} + +static int subdir_listxattr(const char *path, char *list, size_t size) +{ + struct subdir *d = subdir_get(); + char *newpath; + int err = subdir_addpath(d, path, &newpath); + if (!err) { + err = fuse_fs_listxattr(d->next, newpath, list, size); + free(newpath); + } + return err; +} + +static int subdir_removexattr(const char *path, const char *name) +{ + struct subdir *d = subdir_get(); + char *newpath; + int err = subdir_addpath(d, path, &newpath); + if (!err) { + err = fuse_fs_removexattr(d->next, newpath, name); + free(newpath); + } + return err; +} + +static int subdir_lock(const char *path, struct fuse_file_info *fi, int cmd, + struct flock *lock) +{ + struct subdir *d = subdir_get(); + char *newpath; + int err = subdir_addpath(d, path, &newpath); + if (!err) { + err = fuse_fs_lock(d->next, newpath, fi, cmd, lock); + free(newpath); + } + return err; +} + +static int subdir_flock(const char *path, struct fuse_file_info *fi, int op) +{ + struct subdir *d = subdir_get(); + char *newpath; + int err = subdir_addpath(d, path, &newpath); + if (!err) { + err = fuse_fs_flock(d->next, newpath, fi, op); + free(newpath); + } + return err; +} + +static int subdir_bmap(const char *path, size_t blocksize, uint64_t *idx) +{ + struct subdir *d = subdir_get(); + char *newpath; + int err = subdir_addpath(d, path, &newpath); + if (!err) { + err = fuse_fs_bmap(d->next, newpath, blocksize, idx); + free(newpath); + } + return err; +} + +static off_t subdir_lseek(const char *path, off_t off, int whence, + struct fuse_file_info *fi) +{ + struct subdir *ic = subdir_get(); + char *newpath; + int res = subdir_addpath(ic, path, &newpath); + if (!res) { + res = fuse_fs_lseek(ic->next, newpath, off, whence, fi); + free(newpath); + } + return res; +} + +#ifdef HAVE_STATX +static int subdir_statx(const char *path, int flags, int mask, struct statx *stxbuf, + struct fuse_file_info *fi) +{ + struct subdir *ic = subdir_get(); + char *newpath; + int res = subdir_addpath(ic, path, &newpath); + + if (!res) { + res = fuse_fs_statx(ic->next, newpath, flags, mask, stxbuf, fi); + free(newpath); + } + return res; +} +#endif + +static void *subdir_init(struct fuse_conn_info *conn, + struct fuse_config *cfg) +{ + struct subdir *d = subdir_get(); + fuse_fs_init(d->next, conn, cfg); + /* Don't touch cfg->nullpath_ok, we can work with + either */ + return d; +} + +static void subdir_destroy(void *data) +{ + struct subdir *d = data; + fuse_fs_destroy(d->next); + free(d->base); + free(d); +} + +static const struct fuse_operations subdir_oper = { + .destroy = subdir_destroy, + .init = subdir_init, + .getattr = subdir_getattr, + .access = subdir_access, + .readlink = subdir_readlink, + .opendir = subdir_opendir, + .readdir = subdir_readdir, + .releasedir = subdir_releasedir, + .mknod = subdir_mknod, + .mkdir = subdir_mkdir, + .symlink = subdir_symlink, + .unlink = subdir_unlink, + .rmdir = subdir_rmdir, + .rename = subdir_rename, + .link = subdir_link, + .chmod = subdir_chmod, + .chown = subdir_chown, + .truncate = subdir_truncate, + .utimens = subdir_utimens, + .create = subdir_create, + .open = subdir_open, + .read_buf = subdir_read_buf, + .write_buf = subdir_write_buf, + .statfs = subdir_statfs, + .flush = subdir_flush, + .release = subdir_release, + .fsync = subdir_fsync, + .fsyncdir = subdir_fsyncdir, + .setxattr = subdir_setxattr, + .getxattr = subdir_getxattr, + .listxattr = subdir_listxattr, + .removexattr = subdir_removexattr, + .lock = subdir_lock, + .flock = subdir_flock, + .bmap = subdir_bmap, + .lseek = subdir_lseek, +#ifdef HAVE_STATX + .statx = subdir_statx, +#endif +}; + +static const struct fuse_opt subdir_opts[] = { + FUSE_OPT_KEY("-h", 0), + FUSE_OPT_KEY("--help", 0), + { "subdir=%s", offsetof(struct subdir, base), 0 }, + { "rellinks", offsetof(struct subdir, rellinks), 1 }, + { "norellinks", offsetof(struct subdir, rellinks), 0 }, + FUSE_OPT_END +}; + +static void subdir_help(void) +{ + printf( +" -o subdir=DIR prepend this directory to all paths (mandatory)\n" +" -o [no]rellinks transform absolute symlinks to relative\n"); +} + +static int subdir_opt_proc(void *data, const char *arg, int key, + struct fuse_args *outargs) +{ + (void) data; (void) arg; (void) outargs; + + if (!key) { + subdir_help(); + return -1; + } + + return 1; +} + +static struct fuse_fs *subdir_new(struct fuse_args *args, + struct fuse_fs *next[]) +{ + struct fuse_fs *fs; + struct subdir *d; + + d = calloc(1, sizeof(struct subdir)); + if (d == NULL) { + fuse_log(FUSE_LOG_ERR, "fuse-subdir: memory allocation failed\n"); + return NULL; + } + + if (fuse_opt_parse(args, d, subdir_opts, subdir_opt_proc) == -1) + goto out_free; + + if (!next[0] || next[1]) { + fuse_log(FUSE_LOG_ERR, "fuse-subdir: exactly one next filesystem required\n"); + goto out_free; + } + + if (!d->base) { + fuse_log(FUSE_LOG_ERR, "fuse-subdir: missing 'subdir' option\n"); + goto out_free; + } + + if (d->base[0] && d->base[strlen(d->base)-1] != '/') { + char *tmp = realloc(d->base, strlen(d->base) + 2); + if (!tmp) { + fuse_log(FUSE_LOG_ERR, "fuse-subdir: memory allocation failed\n"); + goto out_free; + } + d->base = tmp; + strcat(d->base, "/"); + } + d->baselen = strlen(d->base); + d->next = next[0]; + fs = fuse_fs_new(&subdir_oper, sizeof(subdir_oper), d); + if (!fs) + goto out_free; + return fs; + +out_free: + free(d->base); + free(d); + return NULL; +} + +FUSE_REGISTER_MODULE(subdir, subdir_new); diff --git a/lib/mount.c b/lib/mount.c new file mode 100644 index 0000000..20f6b1c --- /dev/null +++ b/lib/mount.c @@ -0,0 +1,730 @@ +/* + FUSE: Filesystem in Userspace + Copyright (C) 2001-2007 Miklos Szeredi + + Architecture specific file system mounting (Linux). + + This program can be distributed under the terms of the GNU LGPLv2. + See the file LGPL2.txt. +*/ + +/* For environ */ +#define _GNU_SOURCE + +#include "fuse_config.h" +#include "fuse_i.h" +#include "fuse_misc.h" +#include "fuse_opt.h" +#include "mount_util.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "fuse_mount_compat.h" + +#ifdef __NetBSD__ +#include + +#define MS_RDONLY MNT_RDONLY +#define MS_NOSUID MNT_NOSUID +#define MS_NODEV MNT_NODEV +#define MS_NOEXEC MNT_NOEXEC +#define MS_SYNCHRONOUS MNT_SYNCHRONOUS +#define MS_NOATIME MNT_NOATIME +#define MS_NOSYMFOLLOW MNT_NOSYMFOLLOW + +#define umount2(mnt, flags) unmount(mnt, (flags == 2) ? MNT_FORCE : 0) +#endif + +#define FUSERMOUNT_PROG "fusermount3" +#define FUSE_COMMFD_ENV "_FUSE_COMMFD" +#define FUSE_COMMFD2_ENV "_FUSE_COMMFD2" +#define FUSE_KERN_DEVICE_ENV "FUSE_KERN_DEVICE" + +#ifndef MS_DIRSYNC +#define MS_DIRSYNC 128 +#endif + +enum { + KEY_KERN_FLAG, + KEY_KERN_OPT, + KEY_FUSERMOUNT_OPT, + KEY_SUBTYPE_OPT, + KEY_MTAB_OPT, + KEY_ALLOW_OTHER, + KEY_RO, +}; + +struct mount_opts { + int allow_other; + int flags; + int auto_unmount; + int blkdev; + char *fsname; + char *subtype; + char *subtype_opt; + char *mtab_opts; + char *fusermount_opts; + char *kernel_opts; + unsigned max_read; +}; + +#define FUSE_MOUNT_OPT(t, p) { t, offsetof(struct mount_opts, p), 1 } + +static const struct fuse_opt fuse_mount_opts[] = { + FUSE_MOUNT_OPT("allow_other", allow_other), + FUSE_MOUNT_OPT("blkdev", blkdev), + FUSE_MOUNT_OPT("auto_unmount", auto_unmount), + FUSE_MOUNT_OPT("fsname=%s", fsname), + FUSE_MOUNT_OPT("max_read=%u", max_read), + FUSE_MOUNT_OPT("subtype=%s", subtype), + FUSE_OPT_KEY("allow_other", KEY_KERN_OPT), + FUSE_OPT_KEY("auto_unmount", KEY_FUSERMOUNT_OPT), + FUSE_OPT_KEY("blkdev", KEY_FUSERMOUNT_OPT), + FUSE_OPT_KEY("fsname=", KEY_FUSERMOUNT_OPT), + FUSE_OPT_KEY("subtype=", KEY_SUBTYPE_OPT), + FUSE_OPT_KEY("blksize=", KEY_KERN_OPT), + FUSE_OPT_KEY("default_permissions", KEY_KERN_OPT), + FUSE_OPT_KEY("context=", KEY_KERN_OPT), + FUSE_OPT_KEY("fscontext=", KEY_KERN_OPT), + FUSE_OPT_KEY("defcontext=", KEY_KERN_OPT), + FUSE_OPT_KEY("rootcontext=", KEY_KERN_OPT), + FUSE_OPT_KEY("max_read=", KEY_KERN_OPT), + FUSE_OPT_KEY("user=", KEY_MTAB_OPT), + FUSE_OPT_KEY("-n", KEY_MTAB_OPT), + FUSE_OPT_KEY("-r", KEY_RO), + FUSE_OPT_KEY("ro", KEY_KERN_FLAG), + FUSE_OPT_KEY("rw", KEY_KERN_FLAG), + FUSE_OPT_KEY("suid", KEY_KERN_FLAG), + FUSE_OPT_KEY("nosuid", KEY_KERN_FLAG), + FUSE_OPT_KEY("dev", KEY_KERN_FLAG), + FUSE_OPT_KEY("nodev", KEY_KERN_FLAG), + FUSE_OPT_KEY("exec", KEY_KERN_FLAG), + FUSE_OPT_KEY("noexec", KEY_KERN_FLAG), + FUSE_OPT_KEY("async", KEY_KERN_FLAG), + FUSE_OPT_KEY("sync", KEY_KERN_FLAG), + FUSE_OPT_KEY("dirsync", KEY_KERN_FLAG), + FUSE_OPT_KEY("noatime", KEY_KERN_FLAG), + FUSE_OPT_KEY("nodiratime", KEY_KERN_FLAG), + FUSE_OPT_KEY("nostrictatime", KEY_KERN_FLAG), + FUSE_OPT_KEY("symfollow", KEY_KERN_FLAG), + FUSE_OPT_KEY("nosymfollow", KEY_KERN_FLAG), + FUSE_OPT_END +}; + +/* + * Running fusermount by calling 'posix_spawn' + * + * @param out_pid might be NULL + */ +static int fusermount_posix_spawn(posix_spawn_file_actions_t *action, + char const * const argv[], pid_t *out_pid) +{ + const char *full_path = FUSERMOUNT_DIR "/" FUSERMOUNT_PROG; + pid_t pid; + + /* See man 7 environ for the global environ pointer */ + + /* first try the install path */ + int status = posix_spawn(&pid, full_path, action, NULL, + (char * const *) argv, environ); + if (status != 0) { + /* if that fails, try a system install */ + status = posix_spawnp(&pid, FUSERMOUNT_PROG, action, NULL, + (char * const *) argv, environ); + } + + if (status != 0) { + fuse_log(FUSE_LOG_ERR, "Failed to call '%s': %s\n", + FUSERMOUNT_PROG, strerror(status)); + return -status; + } + + if (out_pid) + *out_pid = pid; + else + waitpid(pid, NULL, 0); /* FIXME: check exit code and return error if any */ + + return 0; +} + +void fuse_mount_version(void) +{ + char const *const argv[] = {FUSERMOUNT_PROG, "--version", NULL}; + int status = fusermount_posix_spawn(NULL, argv, NULL); + + if(status != 0) + fuse_log(FUSE_LOG_ERR, "Running '%s --version' failed", + FUSERMOUNT_PROG); +} + +struct mount_flags { + const char *opt; + unsigned long flag; + int on; +}; + +static const struct mount_flags mount_flags[] = { + {"rw", MS_RDONLY, 0}, + {"ro", MS_RDONLY, 1}, + {"suid", MS_NOSUID, 0}, + {"nosuid", MS_NOSUID, 1}, + {"dev", MS_NODEV, 0}, + {"nodev", MS_NODEV, 1}, + {"exec", MS_NOEXEC, 0}, + {"noexec", MS_NOEXEC, 1}, + {"async", MS_SYNCHRONOUS, 0}, + {"sync", MS_SYNCHRONOUS, 1}, + {"noatime", MS_NOATIME, 1}, + {"nodiratime", MS_NODIRATIME, 1}, + {"norelatime", MS_RELATIME, 0}, + {"nostrictatime", MS_STRICTATIME, 0}, + {"symfollow", MS_NOSYMFOLLOW, 0}, + {"nosymfollow", MS_NOSYMFOLLOW, 1}, +#ifndef __NetBSD__ + {"dirsync", MS_DIRSYNC, 1}, +#endif + {NULL, 0, 0} +}; + +unsigned get_max_read(struct mount_opts *o) +{ + return o->max_read; +} + +static void set_mount_flag(const char *s, int *flags) +{ + int i; + + for (i = 0; mount_flags[i].opt != NULL; i++) { + const char *opt = mount_flags[i].opt; + if (strcmp(opt, s) == 0) { + if (mount_flags[i].on) + *flags |= mount_flags[i].flag; + else + *flags &= ~mount_flags[i].flag; + return; + } + } + fuse_log(FUSE_LOG_ERR, "fuse: internal error, can't find mount flag\n"); + abort(); +} + +static int fuse_mount_opt_proc(void *data, const char *arg, int key, + struct fuse_args *outargs) +{ + (void) outargs; + struct mount_opts *mo = data; + + switch (key) { + case KEY_RO: + arg = "ro"; + /* fall through */ + case KEY_KERN_FLAG: + set_mount_flag(arg, &mo->flags); + return 0; + + case KEY_KERN_OPT: + return fuse_opt_add_opt(&mo->kernel_opts, arg); + + case KEY_FUSERMOUNT_OPT: + return fuse_opt_add_opt_escaped(&mo->fusermount_opts, arg); + + case KEY_SUBTYPE_OPT: + return fuse_opt_add_opt(&mo->subtype_opt, arg); + + case KEY_MTAB_OPT: + return fuse_opt_add_opt(&mo->mtab_opts, arg); + + /* Third party options like 'x-gvfs-notrash' */ + case FUSE_OPT_KEY_OPT: + return (strncmp("x-", arg, 2) == 0) ? + fuse_opt_add_opt(&mo->mtab_opts, arg) : + 1; + } + + /* Pass through unknown options */ + return 1; +} + +/* return value: + * >= 0 => fd + * -1 => error + */ +static int receive_fd(int fd) +{ + struct msghdr msg; + struct iovec iov; + char buf[1]; + int rv; + size_t ccmsg[CMSG_SPACE(sizeof(int)) / sizeof(size_t)]; + struct cmsghdr *cmsg; + + iov.iov_base = buf; + iov.iov_len = 1; + + memset(&msg, 0, sizeof(msg)); + msg.msg_name = 0; + msg.msg_namelen = 0; + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + /* old BSD implementations should use msg_accrights instead of + * msg_control; the interface is different. */ + msg.msg_control = ccmsg; + msg.msg_controllen = sizeof(ccmsg); + + while(((rv = recvmsg(fd, &msg, 0)) == -1) && errno == EINTR); + if (rv == -1) { + fuse_log(FUSE_LOG_ERR, "recvmsg failed: %s", strerror(errno)); + return -1; + } + if(!rv) { + /* EOF */ + return -1; + } + + cmsg = CMSG_FIRSTHDR(&msg); + if (!cmsg) { + fuse_log(FUSE_LOG_ERR, "error retrieving control message header\n"); + return -1; + } + if (cmsg->cmsg_type != SCM_RIGHTS) { + fuse_log(FUSE_LOG_ERR, "got control message of unknown type %d\n", + cmsg->cmsg_type); + return -1; + } + return *(int*)CMSG_DATA(cmsg); +} + +void fuse_kern_unmount(const char *mountpoint, int fd) +{ + int res; + + if (fd != -1) { + struct pollfd pfd; + + pfd.fd = fd; + pfd.events = 0; + res = poll(&pfd, 1, 0); + + /* Need to close file descriptor, otherwise synchronous umount + would recurse into filesystem, and deadlock. + + Caller expects fuse_kern_unmount to close the fd, so close it + anyway. */ + close(fd); + + /* If file poll returns POLLERR on the device file descriptor, + then the filesystem is already unmounted or the connection + was severed via /sys/fs/fuse/connections/NNN/abort */ + if (res == 1 && (pfd.revents & POLLERR)) + return; + } + + if (geteuid() == 0) { + fuse_mnt_umount("fuse", mountpoint, mountpoint, 1); + return; + } + + res = umount2(mountpoint, MNT_DETACH | UMOUNT_NOFOLLOW); + if (res == 0) + return; + + char const * const argv[] = + { FUSERMOUNT_PROG, "--unmount", "--quiet", "--lazy", + "--", mountpoint, NULL }; + int status = fusermount_posix_spawn(NULL, argv, NULL); + if(status != 0) { + fuse_log(FUSE_LOG_ERR, "Spawning %s to unmount failed: %s", + FUSERMOUNT_PROG, strerror(-status)); + return; + } +} + +static int setup_auto_unmount(const char *mountpoint, int quiet) +{ + int fds[2]; + pid_t pid; + int res; + + if (!mountpoint) { + fuse_log(FUSE_LOG_ERR, "fuse: missing mountpoint parameter\n"); + return -1; + } + + res = socketpair(PF_UNIX, SOCK_STREAM, 0, fds); + if(res == -1) { + fuse_log(FUSE_LOG_ERR, "Setting up auto-unmount socketpair() failed: %s\n", + strerror(errno)); + return -1; + } + + char arg_fd_entry[30]; + snprintf(arg_fd_entry, sizeof(arg_fd_entry), "%i", fds[0]); + setenv(FUSE_COMMFD_ENV, arg_fd_entry, 1); + /* + * This helps to identify the FD hold by parent process. + * In auto-unmount case, parent process can close this FD explicitly to do unmount. + * The FD[1] can be got via getenv(FUSE_COMMFD2_ENV). + * One potential use case is to satisfy FD-Leak checks. + */ + snprintf(arg_fd_entry, sizeof(arg_fd_entry), "%i", fds[1]); + setenv(FUSE_COMMFD2_ENV, arg_fd_entry, 1); + + char const *const argv[] = { + FUSERMOUNT_PROG, + "--auto-unmount", + "--", + mountpoint, + NULL, + }; + + // TODO: add error handling for all manipulations of action. + posix_spawn_file_actions_t action; + posix_spawn_file_actions_init(&action); + + if (quiet) { + posix_spawn_file_actions_addopen(&action, STDOUT_FILENO, "/dev/null", O_WRONLY, 0); + posix_spawn_file_actions_addopen(&action, STDERR_FILENO, "/dev/null", O_WRONLY, 0); + } + posix_spawn_file_actions_addclose(&action, fds[1]); + + /* + * auto-umount runs in the background - it is not waiting for the + * process + */ + int status = fusermount_posix_spawn(&action, argv, &pid); + + posix_spawn_file_actions_destroy(&action); + + if(status != 0) { + close(fds[0]); + close(fds[1]); + fuse_log(FUSE_LOG_ERR, "fuse: Setting up auto-unmount failed (spawn): %s", + strerror(-status)); + return -1; + } + // passed to child now, so can close here. + close(fds[0]); + + // Now fusermount3 will only exit when fds[1] closes automatically when our + // process exits. + return 0; + // Note: fds[1] is leakend and doesn't get FD_CLOEXEC +} + +static int fuse_mount_fusermount(const char *mountpoint, struct mount_opts *mo, + const char *opts, int quiet) +{ + int fds[2]; + pid_t pid; + int res; + + if (!mountpoint) { + fuse_log(FUSE_LOG_ERR, "fuse: missing mountpoint parameter\n"); + return -1; + } + + res = socketpair(PF_UNIX, SOCK_STREAM, 0, fds); + if(res == -1) { + fuse_log(FUSE_LOG_ERR, "Running %s: socketpair() failed: %s\n", + FUSERMOUNT_PROG, strerror(errno)); + return -1; + } + + char arg_fd_entry[30]; + snprintf(arg_fd_entry, sizeof(arg_fd_entry), "%i", fds[0]); + setenv(FUSE_COMMFD_ENV, arg_fd_entry, 1); + /* + * This helps to identify the FD hold by parent process. + * In auto-unmount case, parent process can close this FD explicitly to do unmount. + * The FD[1] can be got via getenv(FUSE_COMMFD2_ENV). + * One potential use case is to satisfy FD-Leak checks. + */ + snprintf(arg_fd_entry, sizeof(arg_fd_entry), "%i", fds[1]); + setenv(FUSE_COMMFD2_ENV, arg_fd_entry, 1); + + char const *const argv[] = { + FUSERMOUNT_PROG, + "-o", opts ? opts : "", + "--", + mountpoint, + NULL, + }; + + + posix_spawn_file_actions_t action; + posix_spawn_file_actions_init(&action); + + if (quiet) { + posix_spawn_file_actions_addopen(&action, STDOUT_FILENO, "/dev/null", O_WRONLY, 0); + posix_spawn_file_actions_addopen(&action, STDERR_FILENO, "/dev/null", O_WRONLY, 0); + } + posix_spawn_file_actions_addclose(&action, fds[1]); + + int status = fusermount_posix_spawn(&action, argv, &pid); + + posix_spawn_file_actions_destroy(&action); + + if(status != 0) { + close(fds[0]); + close(fds[1]); + fuse_log(FUSE_LOG_ERR, "posix_spawn(p)() for %s failed: %s", + FUSERMOUNT_PROG, strerror(-status)); + return -1; + } + + // passed to child now, so can close here. + close(fds[0]); + + int fd = receive_fd(fds[1]); + + if (!mo->auto_unmount) { + /* with auto_unmount option fusermount3 will not exit until + this socket is closed */ + close(fds[1]); + waitpid(pid, NULL, 0); /* bury zombie */ + } + + if (fd >= 0) + fcntl(fd, F_SETFD, FD_CLOEXEC); + + return fd; +} + +#ifndef O_CLOEXEC +#define O_CLOEXEC 0 +#endif + +static int fuse_mount_sys(const char *mnt, struct mount_opts *mo, + const char *mnt_opts) +{ + char tmp[128]; + const char *devname = getenv(FUSE_KERN_DEVICE_ENV) ?: "/dev/fuse"; + char *source = NULL; + char *type = NULL; + struct stat stbuf; + int fd; + int res; + + if (!mnt) { + fuse_log(FUSE_LOG_ERR, "fuse: missing mountpoint parameter\n"); + return -1; + } + + res = stat(mnt, &stbuf); + if (res == -1) { + fuse_log(FUSE_LOG_ERR, "fuse: failed to access mountpoint %s: %s\n", + mnt, strerror(errno)); + return -1; + } + + fd = open(devname, O_RDWR | O_CLOEXEC); + if (fd == -1) { + if (errno == ENODEV || errno == ENOENT) + fuse_log(FUSE_LOG_ERR, + "fuse: device %s not found. Kernel module not loaded?\n", + devname); + else + fuse_log(FUSE_LOG_ERR, "fuse: failed to open %s: %s\n", + devname, strerror(errno)); + return -1; + } + if (!O_CLOEXEC) + fcntl(fd, F_SETFD, FD_CLOEXEC); + + snprintf(tmp, sizeof(tmp), "fd=%i,rootmode=%o,user_id=%u,group_id=%u", + fd, stbuf.st_mode & S_IFMT, getuid(), getgid()); + + res = fuse_opt_add_opt(&mo->kernel_opts, tmp); + if (res == -1) + goto out_close; + + source = malloc((mo->fsname ? strlen(mo->fsname) : 0) + + (mo->subtype ? strlen(mo->subtype) : 0) + + strlen(devname) + 32); + + type = malloc((mo->subtype ? strlen(mo->subtype) : 0) + 32); + if (!type || !source) { + fuse_log(FUSE_LOG_ERR, "fuse: failed to allocate memory\n"); + goto out_close; + } + + strcpy(type, mo->blkdev ? "fuseblk" : "fuse"); + if (mo->subtype) { + strcat(type, "."); + strcat(type, mo->subtype); + } + strcpy(source, + mo->fsname ? mo->fsname : (mo->subtype ? mo->subtype : devname)); + + res = mount(source, mnt, type, mo->flags, mo->kernel_opts); + if (res == -1 && errno == ENODEV && mo->subtype) { + /* Probably missing subtype support */ + strcpy(type, mo->blkdev ? "fuseblk" : "fuse"); + if (mo->fsname) { + if (!mo->blkdev) + sprintf(source, "%s#%s", mo->subtype, + mo->fsname); + } else { + strcpy(source, type); + } + res = mount(source, mnt, type, mo->flags, mo->kernel_opts); + } + if (res == -1) { + /* + * Maybe kernel doesn't support unprivileged mounts, in this + * case try falling back to fusermount3 + */ + if (errno == EPERM) { + res = -2; + } else { + int errno_save = errno; + if (mo->blkdev && errno == ENODEV && + !fuse_mnt_check_fuseblk()) + fuse_log(FUSE_LOG_ERR, + "fuse: 'fuseblk' support missing\n"); + else + fuse_log(FUSE_LOG_ERR, "fuse: mount failed: %s\n", + strerror(errno_save)); + } + + goto out_close; + } + +#ifndef IGNORE_MTAB + if (geteuid() == 0) { + char *newmnt = fuse_mnt_resolve_path("fuse", mnt); + res = -1; + if (!newmnt) + goto out_umount; + + res = fuse_mnt_add_mount("fuse", source, newmnt, type, + mnt_opts); + free(newmnt); + if (res == -1) + goto out_umount; + } +#endif /* IGNORE_MTAB */ + free(type); + free(source); + + return fd; + +out_umount: + umount2(mnt, MNT_DETACH | UMOUNT_NOFOLLOW); /* lazy umount */ +out_close: + free(type); + free(source); + close(fd); + return res; +} + +static int get_mnt_flag_opts(char **mnt_optsp, int flags) +{ + int i; + + if (!(flags & MS_RDONLY) && fuse_opt_add_opt(mnt_optsp, "rw") == -1) + return -1; + + for (i = 0; mount_flags[i].opt != NULL; i++) { + if (mount_flags[i].on && (flags & mount_flags[i].flag) && + fuse_opt_add_opt(mnt_optsp, mount_flags[i].opt) == -1) + return -1; + } + return 0; +} + +struct mount_opts *parse_mount_opts(struct fuse_args *args) +{ + struct mount_opts *mo; + + mo = (struct mount_opts*) malloc(sizeof(struct mount_opts)); + if (mo == NULL) + return NULL; + + memset(mo, 0, sizeof(struct mount_opts)); + mo->flags = MS_NOSUID | MS_NODEV; + + if (args && + fuse_opt_parse(args, mo, fuse_mount_opts, fuse_mount_opt_proc) == -1) + goto err_out; + + return mo; + +err_out: + destroy_mount_opts(mo); + return NULL; +} + +void destroy_mount_opts(struct mount_opts *mo) +{ + free(mo->fsname); + free(mo->subtype); + free(mo->fusermount_opts); + free(mo->subtype_opt); + free(mo->kernel_opts); + free(mo->mtab_opts); + free(mo); +} + + +int fuse_kern_mount(const char *mountpoint, struct mount_opts *mo) +{ + int res = -1; + char *mnt_opts = NULL; + + res = -1; + if (get_mnt_flag_opts(&mnt_opts, mo->flags) == -1) + goto out; + if (mo->kernel_opts && fuse_opt_add_opt(&mnt_opts, mo->kernel_opts) == -1) + goto out; + if (mo->mtab_opts && fuse_opt_add_opt(&mnt_opts, mo->mtab_opts) == -1) + goto out; + + res = fuse_mount_sys(mountpoint, mo, mnt_opts); + if (res >= 0 && mo->auto_unmount) { + if(0 > setup_auto_unmount(mountpoint, 0)) { + // Something went wrong, let's umount like in fuse_mount_sys. + umount2(mountpoint, + MNT_DETACH | UMOUNT_NOFOLLOW); /* lazy umount */ + res = -1; + } + } else if (res == -2) { + if (mo->fusermount_opts && + fuse_opt_add_opt(&mnt_opts, mo->fusermount_opts) == -1) + goto out; + + if (mo->subtype) { + char *tmp_opts = NULL; + + res = -1; + if (fuse_opt_add_opt(&tmp_opts, mnt_opts) == -1 || + fuse_opt_add_opt(&tmp_opts, mo->subtype_opt) == -1) { + free(tmp_opts); + goto out; + } + + res = fuse_mount_fusermount(mountpoint, mo, tmp_opts, 1); + free(tmp_opts); + if (res == -1) + res = fuse_mount_fusermount(mountpoint, mo, + mnt_opts, 0); + } else { + res = fuse_mount_fusermount(mountpoint, mo, mnt_opts, 0); + } + } +out: + free(mnt_opts); + return res; +} diff --git a/lib/mount_bsd.c b/lib/mount_bsd.c new file mode 100644 index 0000000..c12ab32 --- /dev/null +++ b/lib/mount_bsd.c @@ -0,0 +1,269 @@ +/* + FUSE: Filesystem in Userspace + Copyright (C) 2005-2008 Csaba Henk + + Architecture specific file system mounting (FreeBSD). + + This program can be distributed under the terms of the GNU LGPLv2. + See the file LGPL2.txt. +*/ + +#include "fuse_config.h" +#include "fuse_i.h" +#include "fuse_misc.h" +#include "fuse_opt.h" +#include "util.h" + +#include +#include "fuse_mount_compat.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#define FUSERMOUNT_PROG "mount_fusefs" +#define FUSE_DEV_TRUNK "/dev/fuse" + +enum { + KEY_RO, + KEY_KERN +}; + +struct mount_opts { + int allow_other; + char *kernel_opts; + unsigned max_read; +}; + +#define FUSE_DUAL_OPT_KEY(templ, key) \ + FUSE_OPT_KEY(templ, key), FUSE_OPT_KEY("no" templ, key) + +static const struct fuse_opt fuse_mount_opts[] = { + { "allow_other", offsetof(struct mount_opts, allow_other), 1 }, + { "max_read=%u", offsetof(struct mount_opts, max_read), 1 }, + FUSE_OPT_KEY("-r", KEY_RO), + /* standard FreeBSD mount options */ + FUSE_DUAL_OPT_KEY("dev", KEY_KERN), + FUSE_DUAL_OPT_KEY("async", KEY_KERN), + FUSE_DUAL_OPT_KEY("atime", KEY_KERN), + FUSE_DUAL_OPT_KEY("dev", KEY_KERN), + FUSE_DUAL_OPT_KEY("exec", KEY_KERN), + FUSE_DUAL_OPT_KEY("suid", KEY_KERN), + FUSE_DUAL_OPT_KEY("symfollow", KEY_KERN), + FUSE_DUAL_OPT_KEY("rdonly", KEY_KERN), + FUSE_DUAL_OPT_KEY("sync", KEY_KERN), + FUSE_DUAL_OPT_KEY("union", KEY_KERN), + FUSE_DUAL_OPT_KEY("userquota", KEY_KERN), + FUSE_DUAL_OPT_KEY("groupquota", KEY_KERN), + FUSE_DUAL_OPT_KEY("clusterr", KEY_KERN), + FUSE_DUAL_OPT_KEY("clusterw", KEY_KERN), + FUSE_DUAL_OPT_KEY("suiddir", KEY_KERN), + FUSE_DUAL_OPT_KEY("snapshot", KEY_KERN), + FUSE_DUAL_OPT_KEY("multilabel", KEY_KERN), + FUSE_DUAL_OPT_KEY("acls", KEY_KERN), + FUSE_DUAL_OPT_KEY("force", KEY_KERN), + FUSE_DUAL_OPT_KEY("update", KEY_KERN), + FUSE_DUAL_OPT_KEY("ro", KEY_KERN), + FUSE_DUAL_OPT_KEY("rw", KEY_KERN), + FUSE_DUAL_OPT_KEY("auto", KEY_KERN), + FUSE_DUAL_OPT_KEY("automounted", KEY_KERN), + /* options supported under both Linux and FBSD */ + FUSE_DUAL_OPT_KEY("allow_other", KEY_KERN), + FUSE_DUAL_OPT_KEY("default_permissions",KEY_KERN), + FUSE_OPT_KEY("max_read=", KEY_KERN), + FUSE_OPT_KEY("subtype=", KEY_KERN), + /* FBSD FUSE specific mount options */ + FUSE_DUAL_OPT_KEY("private", KEY_KERN), + FUSE_DUAL_OPT_KEY("neglect_shares", KEY_KERN), + FUSE_DUAL_OPT_KEY("push_symlinks_in", KEY_KERN), +#if __FreeBSD_version >= 1200519 + FUSE_DUAL_OPT_KEY("intr", KEY_KERN), +#endif + /* stock FBSD mountopt parsing routine lets anything be negated... */ + /* + * Linux specific mount options, but let just the mount util + * handle them + */ + FUSE_OPT_KEY("fsname=", KEY_KERN), + FUSE_OPT_END +}; + +void fuse_mount_version(void) +{ + system(FUSERMOUNT_PROG " --version"); +} + +unsigned get_max_read(struct mount_opts *o) +{ + return o->max_read; +} + +static int fuse_mount_opt_proc(void *data, const char *arg, int key, + struct fuse_args *outargs) +{ + (void) outargs; + struct mount_opts *mo = data; + + switch (key) { + case KEY_RO: + arg = "ro"; + /* fall through */ + + case KEY_KERN: + return fuse_opt_add_opt(&mo->kernel_opts, arg); + } + + /* Pass through unknown options */ + return 1; +} + +void fuse_kern_unmount(const char *mountpoint, int fd) +{ + if (close(fd) < 0) + fuse_log(FUSE_LOG_ERR, "closing FD %d failed: %s", fd, strerror(errno)); + if (unmount(mountpoint, MNT_FORCE) < 0) + fuse_log(FUSE_LOG_ERR, "unmounting %s failed: %s", + mountpoint, strerror(errno)); +} + +static int fuse_mount_core(const char *mountpoint, const char *opts) +{ + const char *mountprog = FUSERMOUNT_PROG; + long fd; + char *fdnam, *dev; + pid_t pid, cpid; + int status; + int err; + + fdnam = getenv("FUSE_DEV_FD"); + + if (fdnam) { + err = libfuse_strtol(fdnam, &fd); + if (err || fd < 0) { + fuse_log(FUSE_LOG_ERR, "invalid value given in FUSE_DEV_FD\n"); + return -1; + } + + goto mount; + } + + dev = getenv("FUSE_DEV_NAME"); + + if (! dev) + dev = (char *)FUSE_DEV_TRUNK; + + if ((fd = open(dev, O_RDWR)) < 0) { + perror("fuse: failed to open fuse device"); + return -1; + } + +mount: + if (getenv("FUSE_NO_MOUNT") || ! mountpoint) + goto out; + + pid = fork(); + cpid = pid; + + if (pid == -1) { + perror("fuse: fork() failed"); + close(fd); + return -1; + } + + if (pid == 0) { + pid = fork(); + + if (pid == -1) { + perror("fuse: fork() failed"); + close(fd); + _exit(EXIT_FAILURE); + } + + if (pid == 0) { + const char *argv[32]; + int a = 0; + int ret = -1; + + if (! fdnam) + { + ret = asprintf(&fdnam, "%ld", fd); + if(ret == -1) + { + perror("fuse: failed to assemble mount arguments"); + close(fd); + _exit(EXIT_FAILURE); + } + } + + argv[a++] = mountprog; + if (opts) { + argv[a++] = "-o"; + argv[a++] = opts; + } + argv[a++] = fdnam; + argv[a++] = mountpoint; + argv[a++] = NULL; + execvp(mountprog, (char **) argv); + perror("fuse: failed to exec mount program"); + free(fdnam); + _exit(EXIT_FAILURE); + } + + waitpid(pid, &status, 0); + if (!WIFEXITED(status)) + _exit(EXIT_FAILURE); + _exit(WEXITSTATUS(status)); + } + + if (waitpid(cpid, &status, 0) == -1 || WEXITSTATUS(status) != 0) { + perror("fuse: failed to mount file system"); + if (close(fd) < 0) + perror("fuse: closing FD"); + return -1; + } + +out: + return fd; +} + +struct mount_opts *parse_mount_opts(struct fuse_args *args) +{ + struct mount_opts *mo; + + mo = (struct mount_opts*) malloc(sizeof(struct mount_opts)); + if (mo == NULL) + return NULL; + + memset(mo, 0, sizeof(struct mount_opts)); + + if (args && + fuse_opt_parse(args, mo, fuse_mount_opts, fuse_mount_opt_proc) == -1) + goto err_out; + + return mo; + +err_out: + destroy_mount_opts(mo); + return NULL; +} + +void destroy_mount_opts(struct mount_opts *mo) +{ + free(mo->kernel_opts); + free(mo); +} + +int fuse_kern_mount(const char *mountpoint, struct mount_opts *mo) +{ + /* mount util should not try to spawn the daemon */ + setenv("MOUNT_FUSEFS_SAFE", "1", 1); + /* to notify the mount util it's called from lib */ + setenv("MOUNT_FUSEFS_CALL_BY_LIB", "1", 1); + + return fuse_mount_core(mountpoint, mo->kernel_opts); +} diff --git a/lib/mount_util.c b/lib/mount_util.c new file mode 100644 index 0000000..9a02745 --- /dev/null +++ b/lib/mount_util.c @@ -0,0 +1,413 @@ +/* + FUSE: Filesystem in Userspace + Copyright (C) 2001-2007 Miklos Szeredi + + Architecture-independent mounting code. + + This program can be distributed under the terms of the GNU LGPLv2. + See the file LGPL2.txt. +*/ + +#include "fuse_config.h" +#include "mount_util.h" +#include "fuse_log.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if !defined( __NetBSD__) && !defined(__FreeBSD__) && !defined(__DragonFly__) && !defined(__ANDROID__) +#include +#else +#define IGNORE_MTAB +#endif +#include +#include + +#include "fuse_mount_compat.h" + +#include + +#if defined(__NetBSD__) || defined(__FreeBSD__) || defined(__DragonFly__) || defined(__FreeBSD_kernel__) +#define umount2(mnt, flags) unmount(mnt, ((flags) == 2) ? MNT_FORCE : 0) +#endif + +#ifdef IGNORE_MTAB +#define mtab_needs_update(mnt) 0 +#else +static int mtab_needs_update(const char *mnt) +{ + int res; + struct stat stbuf; + + /* If mtab is within new mount, don't touch it */ + if (strncmp(mnt, _PATH_MOUNTED, strlen(mnt)) == 0 && + _PATH_MOUNTED[strlen(mnt)] == '/') + return 0; + + /* + * Skip mtab update if /etc/mtab: + * + * - doesn't exist, + * - is on a read-only filesystem. + */ + res = lstat(_PATH_MOUNTED, &stbuf); + if (res == -1) { + if (errno == ENOENT) + return 0; + } else { + uid_t ruid; + int err; + + ruid = getuid(); + if (ruid != 0) + setreuid(0, -1); + + res = access(_PATH_MOUNTED, W_OK); + err = (res == -1) ? errno : 0; + if (ruid != 0) + setreuid(ruid, -1); + + if (err == EROFS) + return 0; + + res = access("/run/mount/utab", F_OK); + if (res == -1) + return 0; + } + + return 1; +} +#endif /* IGNORE_MTAB */ + +/* + * These values become command line arguments of /bin/mount and /bin/umount. + * BusyBox mount(8) parses an argument starting with '-' as an option even + * behind an end-of-options marker ("--"); NULL would end the argument list + * early. + * + * @return 1 if @value is unsafe as a /bin/mount or /bin/umount argument. + */ +static int unsafe_operand(const char *value) +{ + return value == NULL || value[0] == '-'; +} + +static int add_mount(const char *progname, const char *fsname, + const char *mnt, const char *type, const char *opts) +{ + int res; + int status; + sigset_t blockmask; + sigset_t oldmask; + + if (unsafe_operand(fsname) || unsafe_operand(mnt) || + unsafe_operand(type) || unsafe_operand(opts)) { + fuse_log(FUSE_LOG_DEBUG, + "%s: option-like mount argument, skipping mtab update\n", + progname); + return 0; + } + + sigemptyset(&blockmask); + sigaddset(&blockmask, SIGCHLD); + res = sigprocmask(SIG_BLOCK, &blockmask, &oldmask); + if (res == -1) { + fprintf(stderr, "%s: sigprocmask: %s\n", progname, strerror(errno)); + return -1; + } + + res = fork(); + if (res == -1) { + fprintf(stderr, "%s: fork: %s\n", progname, strerror(errno)); + goto out_restore; + } + if (res == 0) { + char *env = NULL; + + sigprocmask(SIG_SETMASK, &oldmask, NULL); + + if(setuid(geteuid()) == -1) { + fprintf(stderr, "%s: setuid: %s\n", progname, strerror(errno)); + res = -1; + goto out_restore; + } + + /* + * fsname comes from -ofsname=, so it can start with '-'. The + * setuid() above raises the real uid to 0, so mount(8) is not + * in restricted mode either. Terminate the options with "--" + * to keep it from parsing the operands as further options. + */ + execle("/bin/mount", "/bin/mount", "--no-canonicalize", "-i", + "-f", "-t", type, "-o", opts, "--", fsname, mnt, NULL, + &env); + fprintf(stderr, "%s: failed to execute /bin/mount: %s\n", + progname, strerror(errno)); + exit(1); + } + res = waitpid(res, &status, 0); + if (res == -1) + fprintf(stderr, "%s: waitpid: %s\n", progname, strerror(errno)); + + if (status != 0) + res = -1; + + out_restore: + sigprocmask(SIG_SETMASK, &oldmask, NULL); + + return res; +} + +int fuse_mnt_add_mount(const char *progname, const char *fsname, + const char *mnt, const char *type, const char *opts) +{ + if (!mtab_needs_update(mnt)) + return 0; + + return add_mount(progname, fsname, mnt, type, opts); +} + +static int exec_umount(const char *progname, const char *rel_mnt, int lazy) +{ + int res; + int status; + sigset_t blockmask; + sigset_t oldmask; + + sigemptyset(&blockmask); + sigaddset(&blockmask, SIGCHLD); + res = sigprocmask(SIG_BLOCK, &blockmask, &oldmask); + if (res == -1) { + fprintf(stderr, "%s: sigprocmask: %s\n", progname, strerror(errno)); + return -1; + } + + res = fork(); + if (res == -1) { + fprintf(stderr, "%s: fork: %s\n", progname, strerror(errno)); + goto out_restore; + } + if (res == 0) { + char *env = NULL; + + sigprocmask(SIG_SETMASK, &oldmask, NULL); + + if(setuid(geteuid()) == -1) { + fprintf(stderr, "%s: setuid: %s\n", progname, strerror(errno)); + res = -1; + goto out_restore; + } + + if (lazy) { + execle("/bin/umount", "/bin/umount", "-i", "-l", + "--", rel_mnt, NULL, &env); + } else { + execle("/bin/umount", "/bin/umount", "-i", + "--", rel_mnt, NULL, &env); + } + fprintf(stderr, "%s: failed to execute /bin/umount: %s\n", + progname, strerror(errno)); + exit(1); + } + res = waitpid(res, &status, 0); + if (res == -1) + fprintf(stderr, "%s: waitpid: %s\n", progname, strerror(errno)); + + if (status != 0) { + res = -1; + } + + out_restore: + sigprocmask(SIG_SETMASK, &oldmask, NULL); + return res; + +} + +int fuse_mnt_umount(const char *progname, const char *abs_mnt, + const char *rel_mnt, int lazy) +{ + int res; + + /* + * umount(8) may read an option-like rel_mnt as an option: unmount here + * instead and leave the mtab/utab record behind + */ + if (!mtab_needs_update(abs_mnt) || unsafe_operand(rel_mnt)) { + res = umount2(rel_mnt, lazy ? 2 : 0); + if (res == -1) + fprintf(stderr, "%s: failed to unmount %s: %s\n", + progname, abs_mnt, strerror(errno)); + return res; + } + + return exec_umount(progname, rel_mnt, lazy); +} + +static int remove_mount(const char *progname, const char *mnt) +{ + int res; + int status; + sigset_t blockmask; + sigset_t oldmask; + + if (unsafe_operand(mnt)) + return 0; + + sigemptyset(&blockmask); + sigaddset(&blockmask, SIGCHLD); + res = sigprocmask(SIG_BLOCK, &blockmask, &oldmask); + if (res == -1) { + fprintf(stderr, "%s: sigprocmask: %s\n", progname, strerror(errno)); + return -1; + } + + res = fork(); + if (res == -1) { + fprintf(stderr, "%s: fork: %s\n", progname, strerror(errno)); + goto out_restore; + } + if (res == 0) { + char *env = NULL; + + sigprocmask(SIG_SETMASK, &oldmask, NULL); + + if(setuid(geteuid()) == -1) { + fprintf(stderr, "%s: setuid: %s\n", progname, strerror(errno)); + res = -1; + goto out_restore; + } + + execle("/bin/umount", "/bin/umount", "--no-canonicalize", "-i", + "--fake", "--", mnt, NULL, &env); + fprintf(stderr, "%s: failed to execute /bin/umount: %s\n", + progname, strerror(errno)); + exit(1); + } + res = waitpid(res, &status, 0); + if (res == -1) + fprintf(stderr, "%s: waitpid: %s\n", progname, strerror(errno)); + + if (status != 0) + res = -1; + + out_restore: + sigprocmask(SIG_SETMASK, &oldmask, NULL); + return res; +} + +int fuse_mnt_remove_mount(const char *progname, const char *mnt) +{ + if (!mtab_needs_update(mnt)) + return 0; + + return remove_mount(progname, mnt); +} + +char *fuse_mnt_resolve_path(const char *progname, const char *orig) +{ + char buf[PATH_MAX]; + char *copy; + char *dst; + char *end; + char *lastcomp; + const char *toresolv; + + if (!orig[0]) { + fprintf(stderr, "%s: invalid mountpoint '%s'\n", progname, + orig); + return NULL; + } + + copy = strdup(orig); + if (copy == NULL) { + fprintf(stderr, "%s: failed to allocate memory\n", progname); + return NULL; + } + + toresolv = copy; + lastcomp = NULL; + for (end = copy + strlen(copy) - 1; end > copy && *end == '/'; end --); + if (end[0] != '/') { + char *tmp; + end[1] = '\0'; + tmp = strrchr(copy, '/'); + if (tmp == NULL) { + lastcomp = copy; + toresolv = "."; + } else { + lastcomp = tmp + 1; + if (tmp == copy) + toresolv = "/"; + } + if (strcmp(lastcomp, ".") == 0 || strcmp(lastcomp, "..") == 0) { + lastcomp = NULL; + toresolv = copy; + } + else if (tmp) + tmp[0] = '\0'; + } + if (realpath(toresolv, buf) == NULL) { + fprintf(stderr, "%s: bad mount point %s: %s\n", progname, orig, + strerror(errno)); + free(copy); + return NULL; + } + if (lastcomp == NULL) + dst = strdup(buf); + else { + dst = (char *) malloc(strlen(buf) + 1 + strlen(lastcomp) + 1); + if (dst) { + unsigned buflen = strlen(buf); + if (buflen && buf[buflen-1] == '/') + sprintf(dst, "%s%s", buf, lastcomp); + else + sprintf(dst, "%s/%s", buf, lastcomp); + } + } + free(copy); + if (dst == NULL) + fprintf(stderr, "%s: failed to allocate memory\n", progname); + return dst; +} + +int fuse_mnt_check_fuseblk(void) +{ + char buf[256]; + FILE *f = fopen("/proc/filesystems", "r"); + if (!f) + return 1; + + while (fgets(buf, sizeof(buf), f)) + if (strstr(buf, "fuseblk\n")) { + fclose(f); + return 1; + } + + fclose(f); + return 0; +} + +int fuse_mnt_parse_fuse_fd(const char *mountpoint) +{ + int fd = -1; + int len = 0; + + if (mountpoint == NULL) { + fprintf(stderr, "Invalid null-ptr mount-point!\n"); + return -1; + } + + if (sscanf(mountpoint, "/dev/fd/%u%n", &fd, &len) == 1 && + len == strlen(mountpoint)) { + return fd; + } + + return -1; +} diff --git a/lib/mount_util.h b/lib/mount_util.h new file mode 100644 index 0000000..9cb9077 --- /dev/null +++ b/lib/mount_util.h @@ -0,0 +1,18 @@ +/* + FUSE: Filesystem in Userspace + Copyright (C) 2001-2007 Miklos Szeredi + + This program can be distributed under the terms of the GNU LGPLv2. + See the file LGPL2.txt. +*/ + +#include + +int fuse_mnt_add_mount(const char *progname, const char *fsname, + const char *mnt, const char *type, const char *opts); +int fuse_mnt_remove_mount(const char *progname, const char *mnt); +int fuse_mnt_umount(const char *progname, const char *abs_mnt, + const char *rel_mnt, int lazy); +char *fuse_mnt_resolve_path(const char *progname, const char *orig); +int fuse_mnt_check_fuseblk(void); +int fuse_mnt_parse_fuse_fd(const char *mountpoint); diff --git a/lib/usdt.h b/lib/usdt.h new file mode 100644 index 0000000..6a0bc42 --- /dev/null +++ b/lib/usdt.h @@ -0,0 +1,540 @@ +/* + * Copied from https://github.com/libbpf/usdt/ + */ + +// SPDX-License-Identifier: BSD-2-Clause +/* + * This single-header library defines a collection of variadic macros for + * defining and triggering USDTs (User Statically-Defined Tracepoints): + * + * - For USDTs without associated semaphore: + * USDT(group, name, args...) + * + * - For USDTs with implicit (transparent to the user) semaphore: + * USDT_WITH_SEMA(group, name, args...) + * USDT_IS_ACTIVE(group, name) + * + * - For USDTs with explicit (user-defined and provided) semaphore: + * USDT_WITH_EXPLICIT_SEMA(sema, group, name, args...) + * USDT_SEMA_IS_ACTIVE(sema) + * + * all of which emit a NOP instruction into the instruction stream, and so + * have *zero* overhead for the surrounding code. USDTs are identified by + * a combination of `group` and `name` identifiers, which is used by external + * tracing tooling (tracers) for identifying exact USDTs of interest. + * + * USDTs can have an associated (2-byte) activity counter (USDT semaphore), + * automatically maintained by Linux kernel whenever any correctly written + * BPF-based tracer is attached to the USDT. This USDT semaphore can be used + * to check whether there is a need to do any extra data collection and + * processing for a given USDT (if necessary), and otherwise avoid extra work + * for a common case of USDT not being traced ("active"). + * + * See documentation for USDT_WITH_SEMA()/USDT_IS_ACTIVE() or + * USDT_WITH_EXPLICIT_SEMA()/USDT_SEMA_IS_ACTIVE() APIs below for details on + * working with USDTs with implicitly or explicitly associated + * USDT semaphores, respectively. + * + * There is also some additional data recorded into an auxiliary note + * section. The data in the note section describes the operands, in terms of + * size and location, used by tracing tooling to know where to find USDT + * arguments. Each location is encoded as an assembler operand string. + * Tracing tools (bpftrace and BPF-based tracers, systemtap, etc) insert + * breakpoints on top of the nop, and decode the location operand-strings, + * like an assembler, to find the values being passed. + * + * The operand strings are selected by the compiler for each operand. + * They are constrained by inline-assembler codes.The default is: + * + * #define USDT_ARG_CONSTRAINT nor + * + * This is a good default if the operands tend to be integral and + * moderate in number (smaller than number of registers). In other + * cases, the compiler may report "'asm' requires impossible reload" or + * similar. In this case, consider simplifying the macro call (fewer + * and simpler operands), reduce optimization, or override the default + * constraints string via: + * + * #define USDT_ARG_CONSTRAINT g + * #include + * + * For some historical description of USDT v3 format (the one used by this + * library and generally recognized and assumed by BPF-based tracing tools) + * see [0]. The more formal specification can be found at [1]. Additional + * argument constraints information can be found at [2]. + * + * Original SystemTap's sys/sdt.h implementation ([3]) was used as a base for + * this USDT library implementation. Current implementation differs *a lot* in + * terms of exposed user API and general usability, which was the main goal + * and focus of the reimplementation work. Nevertheless, underlying recorded + * USDT definitions are fully binary compatible and any USDT-based tooling + * should work equally well with USDTs defined by either SystemTap's or this + * library's USDT implementation. + * + * [0] https://ecos.sourceware.org/ml/systemtap/2010-q3/msg00145.html + * [1] https://sourceware.org/systemtap/wiki/UserSpaceProbeImplementation + * [2] https://gcc.gnu.org/onlinedocs/gcc/Constraints.html + * [3] https://sourceware.org/git/?p=systemtap.git;a=blob;f=includes/sys/sdt.h + */ +#ifndef __USDT_H +#define __USDT_H + +/* + * Changelog: + * + * 0.1.0 + * ----- + * - Initial release + */ +#define USDT_MAJOR_VERSION 0 +#define USDT_MINOR_VERSION 1 +#define USDT_PATCH_VERSION 0 + +/* C++20 and C23 added __VA_OPT__ as a standard replacement for non-standard `##__VA_ARGS__` extension */ +#if (defined(__STDC_VERSION__) && __STDC_VERSION__ > 201710L) || (defined(__cplusplus) && __cplusplus > 201703L) +#define __usdt_va_opt 1 +#define __usdt_va_args(...) __VA_OPT__(,) __VA_ARGS__ +#else +#define __usdt_va_args(...) , ##__VA_ARGS__ +#endif + +/* + * Trigger USDT with `group`:`name` identifier and pass through `args` as its + * arguments. Zero arguments are acceptable as well. No USDT semaphore is + * associated with this USDT. + * + * Such "semaphoreless" USDTs are commonly used when there is no extra data + * collection or processing needed to collect and prepare USDT arguments and + * they are just available in the surrounding code. USDT() macro will just + * record their locations in CPU registers or in memory for tracing tooling to + * be able to access them, if necessary. + */ +#ifdef __usdt_va_opt +#define USDT(group, name, ...) \ + __usdt_probe(group, name, __usdt_sema_none, 0 __VA_OPT__(,) __VA_ARGS__) +#else +#define USDT(group, name, ...) \ + __usdt_probe(group, name, __usdt_sema_none, 0, ##__VA_ARGS__) +#endif + +/* + * Trigger USDT with `group`:`name` identifier and pass through `args` as its + * arguments. Zero arguments are acceptable as well. USDT also get an + * implicitly-defined associated USDT semaphore, which will be "activated" by + * tracing tooling and can be used to check whether USDT is being actively + * observed. + * + * USDTs with semaphore are commonly used when there is a need to perform + * additional data collection and processing to prepare USDT arguments, which + * otherwise might not be necessary for the rest of application logic. In such + * case, USDT semaphore can be used to avoid unnecessary extra work. If USDT + * is not traced (which is presumed to be a common situation), the associated + * USDT semaphore is "inactive", and so there is no need to waste resources to + * prepare USDT arguments. Use USDT_IS_ACTIVE(group, name) to check whether + * USDT is "active". + * + * N.B. There is an inherent (albeit short) gap between checking whether USDT + * is active and triggering corresponding USDT, in which external tracer can + * be attached to an USDT and activate USDT semaphore after the activity check. + * If such a race occurs, tracers might miss one USDT execution. Tracers are + * expected to accommodate such possibility and this is expected to not be + * a problem for applications and tracers. + * + * N.B. Implicit USDT semaphore defined by USDT_WITH_SEMA() is contained + * within a single executable or shared library and is not shared outside + * them. I.e., if you use USDT_WITH_SEMA() with the same USDT group and name + * identifier across executable and shared library, it will work and won't + * conflict, per se, but will define independent USDT semaphores, one for each + * shared library/executable in which USDT_WITH_SEMA(group, name) is used. + * That is, if you attach to this USDT in one shared library (or executable), + * then only USDT semaphore within that shared library (or executable) will be + * updated by the kernel, while other libraries (or executable) will not see + * activated USDT semaphore. In short, it's best to use unique USDT group:name + * identifiers across different shared libraries (and, equivalently, between + * executable and shared library). This is advanced consideration and is + * rarely (if ever) seen in practice, but just to avoid surprises this is + * called out here. (Static libraries become a part of final executable, once + * linked by linker, so the above considerations don't apply to them.) + */ +#ifdef __usdt_va_opt +#define USDT_WITH_SEMA(group, name, ...) \ + __usdt_probe(group, name, \ + __usdt_sema_implicit, __usdt_sema_name(group, name) \ + __VA_OPT__(,) __VA_ARGS__) +#else +#define USDT_WITH_SEMA(group, name, ...) \ + __usdt_probe(group, name, \ + __usdt_sema_implicit, __usdt_sema_name(group, name), \ + ##__VA_ARGS__) +#endif + +struct usdt_sema { volatile unsigned short active; }; + +/* + * Check if USDT with `group`:`name` identifier is "active" (i.e., whether it + * is attached to by external tracing tooling and is actively observed). + * + * This macro can be used to decide whether any additional and potentially + * expensive data collection or processing should be done to pass extra + * information into the given USDT. It is assumed that USDT is triggered with + * USDT_WITH_SEMA() macro which will implicitly define associated USDT + * semaphore. (If one needs more control over USDT semaphore, see + * USDT_DEFINE_SEMA() and USDT_WITH_EXPLICIT_SEMA() macros below.) + * + * N.B. Such checks are necessarily racy and speculative. Between checking + * whether USDT is active and triggering the USDT itself, tracer can be + * detached with no notification. This race should be extremely rare and worst + * case should result in one-time wasted extra data collection and processing. + */ +#define USDT_IS_ACTIVE(group, name) ({ \ + extern struct usdt_sema __usdt_sema_name(group, name) \ + __usdt_asm_name(__usdt_sema_name(group, name)); \ + __usdt_sema_implicit(__usdt_sema_name(group, name)); \ + __usdt_sema_name(group, name).active > 0; \ +}) + +/* + * APIs for working with user-defined explicit USDT semaphores. + * + * This is a less commonly used advanced API for use cases in which user needs + * an explicit control over (potentially shared across multiple USDTs) USDT + * semaphore instance. This can be used when there is a group of logically + * related USDTs that all need extra data collection and processing whenever + * any of a family of related USDTs are "activated" (i.e., traced). In such + * a case, all such related USDTs will be associated with the same shared USDT + * semaphore defined with USDT_DEFINE_SEMA() and the USDTs themselves will be + * triggered with USDT_WITH_EXPLICIT_SEMA() macros, taking an explicit extra + * USDT semaphore identifier as an extra parameter. + */ + +/** + * Underlying C global variable name for user-defined USDT semaphore with + * `sema` identifier. Could be useful for debugging, but normally shouldn't be + * used explicitly. + */ +#define USDT_SEMA(sema) __usdt_sema_##sema + +/* + * Define storage for user-defined USDT semaphore `sema`. + * + * Should be used only once in non-header source file to let compiler allocate + * space for the semaphore variable. Just like with any other global variable. + * + * This macro can be used anywhere where global variable declaration is + * allowed. Just like with global variable definitions, there should be only + * one definition of user-defined USDT semaphore with given `sema` identifier, + * otherwise compiler or linker will complain about duplicate variable + * definition. + * + * For C++, it is allowed to use USDT_DEFINE_SEMA() both in global namespace + * and inside namespaces (including nested namespaces). Just make sure that + * USDT_DECLARE_SEMA() is placed within the namespace where this semaphore is + * referenced, or any of its parent namespaces, so the C++ language-level + * identifier is visible to the code that needs to reference the semaphore. + * At the lowest layer, USDT semaphores have global naming and visibility + * (they have a corresponding `__usdt_sema_` symbol, which can be linked + * against from C or C++ code, if necessary). To keep it simple, putting + * USDT_DECLARE_SEMA() declarations into global namespaces is the simplest + * no-brainer solution. All these aspects are irrelevant for plain C, because + * C doesn't have namespaces and everything is always in the global namespace. + * + * N.B. Due to USDT metadata being recorded in non-allocatable ELF note + * section, it has limitations when it comes to relocations, which, in + * practice, means that it's not possible to correctly share USDT semaphores + * between main executable and shared libraries, or even between multiple + * shared libraries. USDT semaphore has to be contained to individual shared + * library or executable to avoid unpleasant surprises with half-working USDT + * semaphores. We enforce this by marking semaphore ELF symbols as having + * a hidden visibility. This is quite an advanced use case and consideration + * and for most users this should have no consequences whatsoever. + */ +#define USDT_DEFINE_SEMA(sema) \ + struct usdt_sema __usdt_sema_sec USDT_SEMA(sema) \ + __usdt_asm_name(USDT_SEMA(sema)) \ + __attribute__((visibility("hidden"))) = { 0 } + +/* + * Declare extern reference to user-defined USDT semaphore `sema`. + * + * Refers to a variable defined in another compilation unit by + * USDT_DEFINE_SEMA() and allows to use the same USDT semaphore across + * multiple compilation units (i.e., .c and .cpp files). + * + * See USDT_DEFINE_SEMA() notes above for C++ language usage peculiarities. + */ +#define USDT_DECLARE_SEMA(sema) \ + extern struct usdt_sema USDT_SEMA(sema) __usdt_asm_name(USDT_SEMA(sema)) + +/* + * Check if user-defined USDT semaphore `sema` is "active" (i.e., whether it + * is attached to by external tracing tooling and is actively observed). + * + * This macro can be used to decide whether any additional and potentially + * expensive data collection or processing should be done to pass extra + * information into USDT(s) associated with USDT semaphore `sema`. + * + * N.B. Such checks are necessarily racy. Between checking the state of USDT + * semaphore and triggering associated USDT(s), the active tracer might attach + * or detach. This race should be extremely rare and worst case should result + * in one-time missed USDT event or wasted extra data collection and + * processing. USDT-using tracers should be written with this in mind and is + * not a concern of the application defining USDTs with associated semaphore. + */ +#define USDT_SEMA_IS_ACTIVE(sema) (USDT_SEMA(sema).active > 0) + +/* + * Invoke USDT specified by `group` and `name` identifiers and associate + * explicitly user-defined semaphore `sema` with it. Pass through `args` as + * USDT arguments. `args` are optional and zero arguments are acceptable. + * + * Semaphore is defined with the help of USDT_DEFINE_SEMA() macro and can be + * checked whether active with USDT_SEMA_IS_ACTIVE(). + */ +#ifdef __usdt_va_opt +#define USDT_WITH_EXPLICIT_SEMA(sema, group, name, ...) \ + __usdt_probe(group, name, __usdt_sema_explicit, USDT_SEMA(sema), ##__VA_ARGS__) +#else +#define USDT_WITH_EXPLICIT_SEMA(sema, group, name, ...) \ + __usdt_probe(group, name, __usdt_sema_explicit, USDT_SEMA(sema) __VA_OPT__(,) __VA_ARGS__) +#endif + +/* + * Adjustable implementation aspects + */ +#ifndef USDT_ARG_CONSTRAINT +#if defined __powerpc__ +#define USDT_ARG_CONSTRAINT nZr +#elif defined __arm__ +#define USDT_ARG_CONSTRAINT g +#elif defined __loongarch__ +#define USDT_ARG_CONSTRAINT nmr +#else +#define USDT_ARG_CONSTRAINT nor +#endif +#endif /* USDT_ARG_CONSTRAINT */ + +#ifndef USDT_NOP +#if defined(__ia64__) || defined(__s390__) || defined(__s390x__) +#define USDT_NOP nop 0 +#else +#define USDT_NOP nop +#endif +#endif /* USDT_NOP */ + +/* + * Implementation details + */ +/* USDT name for implicitly-defined USDT semaphore, derived from group:name */ +#define __usdt_sema_name(group, name) __usdt_sema_##group##__##name +/* ELF section into which USDT semaphores are put */ +#define __usdt_sema_sec __attribute__((section(".probes"))) + +#define __usdt_concat(a, b) a ## b +#define __usdt_apply(fn, n) __usdt_concat(fn, n) + +#ifndef __usdt_nth +#define __usdt_nth(_, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, N, ...) N +#endif + +#ifndef __usdt_narg +#ifdef __usdt_va_opt +#define __usdt_narg(...) __usdt_nth(_ __VA_OPT__(,) __VA_ARGS__, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) +#else +#define __usdt_narg(...) __usdt_nth(_, ##__VA_ARGS__, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) +#endif +#endif /* __usdt_narg */ + +#define __usdt_hash # +#define __usdt_str_(x) #x +#define __usdt_str(x) __usdt_str_(x) + +#ifndef __usdt_asm_name +#define __usdt_asm_name(name) __asm__(__usdt_str(name)) +#endif + +#define __usdt_asm1(a) __usdt_str(a) "\n" +#define __usdt_asm2(a,b) __usdt_str(a) "," __usdt_str(b) "\n" +#define __usdt_asm3(a,b,c) __usdt_str(a) "," __usdt_str(b) "," __usdt_str(c) "\n" +#define __usdt_asm5(a,b,c,d,e) __usdt_str(a) "," __usdt_str(b) "," __usdt_str(c) "," \ + __usdt_str(d) "," __usdt_str(e) "\n" + +#ifdef __LP64__ +#define __usdt_asm_addr .8byte +#else +#define __usdt_asm_addr .4byte +#endif + +#define __usdt_asm_strz_(x) __usdt_asm1(.asciz #x) +#define __usdt_asm_strz(x) __usdt_asm_strz_(x) +#define __usdt_asm_str_(x) __usdt_asm1(.ascii #x) +#define __usdt_asm_str(x) __usdt_asm_str_(x) + +/* "semaphoreless" USDT case */ +#ifndef __usdt_sema_none +#define __usdt_sema_none(sema) +#endif + +/* implicitly defined __usdt_sema__group__name semaphore (using weak symbols) */ +#ifndef __usdt_sema_implicit +#define __usdt_sema_implicit(sema) \ + __asm__ __volatile__ ( \ + __usdt_asm1(.ifndef sema) \ + __usdt_asm3( .pushsection .probes, "aw", "progbits") \ + __usdt_asm1( .weak sema) \ + __usdt_asm1( .hidden sema) \ + __usdt_asm1( .align 2) \ + __usdt_asm1(sema:) \ + __usdt_asm1( .zero 2) \ + __usdt_asm2( .type sema, @object) \ + __usdt_asm2( .size sema, 2) \ + __usdt_asm1( .popsection) \ + __usdt_asm1(.endif) \ + ); +#endif + +/* externally defined semaphore using USDT_DEFINE_SEMA() and passed explicitly by user */ +#ifndef __usdt_sema_explicit +#define __usdt_sema_explicit(sema) \ + __asm__ __volatile__ ("" :: "m" (sema)); +#endif + +/* main USDT definition (nop and .note.stapsdt metadata) */ +#define __usdt_probe(group, name, sema_def, sema, ...) do { \ + sema_def(sema) \ + __asm__ __volatile__ ( \ + __usdt_asm1(990: USDT_NOP) \ + __usdt_asm3( .pushsection .note.stapsdt, "", "note") \ + __usdt_asm1( .balign 4) \ + __usdt_asm3( .4byte 992f-991f,994f-993f,3) \ + __usdt_asm1(991: .asciz "stapsdt") \ + __usdt_asm1(992: .balign 4) \ + __usdt_asm1(993: __usdt_asm_addr 990b) \ + __usdt_asm1( __usdt_asm_addr _.stapsdt.base) \ + __usdt_asm1( __usdt_asm_addr sema) \ + __usdt_asm_strz(group) \ + __usdt_asm_strz(name) \ + __usdt_asm_args(__VA_ARGS__) \ + __usdt_asm1( .ascii "\0") \ + __usdt_asm1(994: .balign 4) \ + __usdt_asm1( .popsection) \ + __usdt_asm1(.ifndef _.stapsdt.base) \ + __usdt_asm5( .pushsection .stapsdt.base,"aG","progbits",.stapsdt.base,comdat)\ + __usdt_asm1( .weak _.stapsdt.base) \ + __usdt_asm1( .hidden _.stapsdt.base) \ + __usdt_asm1(_.stapsdt.base:) \ + __usdt_asm1( .space 1) \ + __usdt_asm2( .size _.stapsdt.base, 1) \ + __usdt_asm1( .popsection) \ + __usdt_asm1(.endif) \ + :: __usdt_asm_ops(__VA_ARGS__) \ + ); \ +} while (0) + +/* + * NB: gdb PR24541 highlighted an unspecified corner of the sdt.h + * operand note format. + * + * The named register may be a longer or shorter (!) alias for the + * storage where the value in question is found. For example, on + * i386, 64-bit value may be put in register pairs, and a register + * name stored would identify just one of them. Previously, gcc was + * asked to emit the %w[id] (16-bit alias of some registers holding + * operands), even when a wider 32-bit value was used. + * + * Bottom line: the byte-width given before the @ sign governs. If + * there is a mismatch between that width and that of the named + * register, then a sys/sdt.h note consumer may need to employ + * architecture-specific heuristics to figure out where the compiler + * has actually put the complete value. + */ +#if defined(__powerpc__) || defined(__powerpc64__) +#define __usdt_argref(id) %I[id]%[id] +#elif defined(__i386__) +#define __usdt_argref(id) %k[id] /* gcc.gnu.org/PR80115 sourceware.org/PR24541 */ +#else +#define __usdt_argref(id) %[id] +#endif + +#define __usdt_asm_arg(n) __usdt_asm_str(%c[__usdt_asz##n]) \ + __usdt_asm1(.ascii "@") \ + __usdt_asm_str(__usdt_argref(__usdt_aval##n)) + +#define __usdt_asm_args0 /* no arguments */ +#define __usdt_asm_args1 __usdt_asm_arg(1) +#define __usdt_asm_args2 __usdt_asm_args1 __usdt_asm1(.ascii " ") __usdt_asm_arg(2) +#define __usdt_asm_args3 __usdt_asm_args2 __usdt_asm1(.ascii " ") __usdt_asm_arg(3) +#define __usdt_asm_args4 __usdt_asm_args3 __usdt_asm1(.ascii " ") __usdt_asm_arg(4) +#define __usdt_asm_args5 __usdt_asm_args4 __usdt_asm1(.ascii " ") __usdt_asm_arg(5) +#define __usdt_asm_args6 __usdt_asm_args5 __usdt_asm1(.ascii " ") __usdt_asm_arg(6) +#define __usdt_asm_args7 __usdt_asm_args6 __usdt_asm1(.ascii " ") __usdt_asm_arg(7) +#define __usdt_asm_args8 __usdt_asm_args7 __usdt_asm1(.ascii " ") __usdt_asm_arg(8) +#define __usdt_asm_args9 __usdt_asm_args8 __usdt_asm1(.ascii " ") __usdt_asm_arg(9) +#define __usdt_asm_args10 __usdt_asm_args9 __usdt_asm1(.ascii " ") __usdt_asm_arg(10) +#define __usdt_asm_args11 __usdt_asm_args10 __usdt_asm1(.ascii " ") __usdt_asm_arg(11) +#define __usdt_asm_args12 __usdt_asm_args11 __usdt_asm1(.ascii " ") __usdt_asm_arg(12) +#define __usdt_asm_args(...) __usdt_apply(__usdt_asm_args, __usdt_narg(__VA_ARGS__)) + +#define __usdt_is_arr(x) (__builtin_classify_type(x) == 14 || __builtin_classify_type(x) == 5) +#define __usdt_arg_size(x) (__usdt_is_arr(x) ? sizeof(void *) : sizeof(x)) + +/* + * We can't use __builtin_choose_expr() in C++, so fall back to table-based + * signedness determination for known types, utilizing templates magic. + */ +#ifdef __cplusplus + +#define __usdt_is_signed(x) (!__usdt_is_arr(x) && __usdt_t<__typeof(x)>::is_signed) + +#include + +template struct __usdt_t { static const bool is_signed = false; }; +template struct __usdt_t : public __usdt_t {}; +template struct __usdt_t : public __usdt_t {}; + +#define __usdt_def_signed(T) \ +template<> struct __usdt_t { static const bool is_signed = true; }; \ +template<> struct __usdt_t { static const bool is_signed = true; }; \ +template<> struct __usdt_t { static const bool is_signed = true; }; \ +template<> struct __usdt_t { static const bool is_signed = true; } +#define __usdt_maybe_signed(T) \ +template<> struct __usdt_t { static const bool is_signed = (T)-1 < (T)1; }; \ +template<> struct __usdt_t { static const bool is_signed = (T)-1 < (T)1; }; \ +template<> struct __usdt_t { static const bool is_signed = (T)-1 < (T)1; }; \ +template<> struct __usdt_t { static const bool is_signed = (T)-1 < (T)1; } + +__usdt_def_signed(signed char); +__usdt_def_signed(short); +__usdt_def_signed(int); +__usdt_def_signed(long); +__usdt_def_signed(long long); +__usdt_maybe_signed(char); +__usdt_maybe_signed(wchar_t); + +#else /* !__cplusplus */ + +#define __usdt_is_inttype(x) (__builtin_classify_type(x) >= 1 && __builtin_classify_type(x) <= 4) +#define __usdt_inttype(x) __typeof(__builtin_choose_expr(__usdt_is_inttype(x), (x), 0U)) +#define __usdt_is_signed(x) ((__usdt_inttype(x))-1 < (__usdt_inttype(x))1) + +#endif /* __cplusplus */ + +#define __usdt_asm_op(n, x) \ + [__usdt_asz##n] "n" ((__usdt_is_signed(x) ? (int)-1 : 1) * (int)__usdt_arg_size(x)), \ + [__usdt_aval##n] __usdt_str(USDT_ARG_CONSTRAINT)(x) + +#define __usdt_asm_ops0() [__usdt_dummy] "g" (0) +#define __usdt_asm_ops1(x) __usdt_asm_op(1, x) +#define __usdt_asm_ops2(a,x) __usdt_asm_ops1(a), __usdt_asm_op(2, x) +#define __usdt_asm_ops3(a,b,x) __usdt_asm_ops2(a,b), __usdt_asm_op(3, x) +#define __usdt_asm_ops4(a,b,c,x) __usdt_asm_ops3(a,b,c), __usdt_asm_op(4, x) +#define __usdt_asm_ops5(a,b,c,d,x) __usdt_asm_ops4(a,b,c,d), __usdt_asm_op(5, x) +#define __usdt_asm_ops6(a,b,c,d,e,x) __usdt_asm_ops5(a,b,c,d,e), __usdt_asm_op(6, x) +#define __usdt_asm_ops7(a,b,c,d,e,f,x) __usdt_asm_ops6(a,b,c,d,e,f), __usdt_asm_op(7, x) +#define __usdt_asm_ops8(a,b,c,d,e,f,g,x) __usdt_asm_ops7(a,b,c,d,e,f,g), __usdt_asm_op(8, x) +#define __usdt_asm_ops9(a,b,c,d,e,f,g,h,x) __usdt_asm_ops8(a,b,c,d,e,f,g,h), __usdt_asm_op(9, x) +#define __usdt_asm_ops10(a,b,c,d,e,f,g,h,i,x) __usdt_asm_ops9(a,b,c,d,e,f,g,h,i), __usdt_asm_op(10, x) +#define __usdt_asm_ops11(a,b,c,d,e,f,g,h,i,j,x) __usdt_asm_ops10(a,b,c,d,e,f,g,h,i,j), __usdt_asm_op(11, x) +#define __usdt_asm_ops12(a,b,c,d,e,f,g,h,i,j,k,x) __usdt_asm_ops11(a,b,c,d,e,f,g,h,i,j,k), __usdt_asm_op(12, x) +#define __usdt_asm_ops(...) __usdt_apply(__usdt_asm_ops, __usdt_narg(__VA_ARGS__))(__VA_ARGS__) + +#endif /* __USDT_H */ diff --git a/lib/util.c b/lib/util.c new file mode 100644 index 0000000..2914f1c --- /dev/null +++ b/lib/util.c @@ -0,0 +1,53 @@ + +#include "fuse_config.h" + +#ifdef HAVE_PTHREAD_SETNAME_NP +#define _GNU_SOURCE +#include +#endif + +#include +#include +#include + +#ifndef FUSE_USE_VERSION +#define FUSE_USE_VERSION (FUSE_MAKE_VERSION(3, 18)) +#endif + +#include "util.h" +#include "fuse_log.h" +#include "fuse_lowlevel.h" +#include + +int libfuse_strtol(const char *str, long *res) +{ + char *endptr; + int base = 10; + long val; + + errno = 0; + + if (!str) + return -EINVAL; + + val = strtol(str, &endptr, base); + + if (errno) + return -errno; + + if (endptr == str || *endptr != '\0') + return -EINVAL; + + *res = val; + return 0; +} + +void fuse_set_thread_name(const char *name) +{ +#ifdef HAVE_PTHREAD_SETNAME_NP + pthread_setname_np(pthread_self(), name); +#else + (void)name; +#endif +} + diff --git a/lib/util.h b/lib/util.h new file mode 100644 index 0000000..107a2bf --- /dev/null +++ b/lib/util.h @@ -0,0 +1,49 @@ +#ifndef FUSE_UTIL_H_ +#define FUSE_UTIL_H_ + +#include +#include + +#define ROUND_UP(val, round_to) (((val) + (round_to - 1)) & ~(round_to - 1)) + +#define likely(x) __builtin_expect(!!(x), 1) +#define unlikely(x) __builtin_expect(!!(x), 0) + +struct fuse_conn_info; + +int libfuse_strtol(const char *str, long *res); +void fuse_set_thread_name(const char *name); + +/** + * Return the low bits of a number + */ +static inline uint32_t fuse_lower_32_bits(uint64_t nr) +{ + return (uint32_t)(nr & 0xffffffff); +} + +/** + * Return the high bits of a number + */ +static inline uint64_t fuse_higher_32_bits(uint64_t nr) +{ + return nr & ~0xffffffffULL; +} + +#ifndef FUSE_VAR_UNUSED +#define FUSE_VAR_UNUSED __attribute__((__unused__)) +#endif + +#define container_of(ptr, type, member) \ + ({ \ + unsigned long __mptr = (unsigned long)(ptr); \ + ((type *)(__mptr - offsetof(type, member))); \ + }) + +#if __has_attribute(__fallthrough__) +#define fallthrough __attribute__((__fallthrough__)) +#else +#define fallthrough do {} while (0) +#endif + +#endif /* FUSE_UTIL_H_ */ diff --git a/make_release_tarball.sh b/make_release_tarball.sh new file mode 100755 index 0000000..bd97125 --- /dev/null +++ b/make_release_tarball.sh @@ -0,0 +1,38 @@ +#!/bin/sh -x +# +# Create tarball from Git tag, removing and adding +# some files. +# + +set -e + +if [ -z "$1" ]; then + TAG="$(git tag --list 'fuse-3*' --sort=-creatordate | head -1)" +else + TAG="$1" +fi +PREV_TAG="$(git tag --list 'fuse-3*' --sort=-creatordate --merged "${TAG}^"| head -1)" +MAJOR_REV=${TAG%.*} + +echo "Creating release tarball for ${TAG}..." + +git checkout -q "${TAG}" +doxygen doc/Doxyfile + +mkdir "${TAG}" + +git archive --format=tar "${TAG}" | tar -x "--directory=${TAG}" +find "${TAG}" -name .gitignore -delete +rm -r "${TAG}/make_release_tarball.sh" \ + "${TAG}/.github" \ + "${TAG}/.cirrus.yml" +cp -a doc/html "${TAG}/doc/" +tar -czf "${TAG}.tar.gz" "${TAG}/" + +signify-openbsd -S -s signify/$MAJOR_REV.sec -m $TAG.tar.gz +signify-openbsd -V -m ${TAG}.tar.gz -p signify/$MAJOR_REV.pub + + +echo "Contributors from ${PREV_TAG} to ${TAG}:" +git log --pretty="format:%an <%aE>" "${PREV_TAG}..${TAG}" | sort -u + diff --git a/meson.build b/meson.build new file mode 100644 index 0000000..1e6cbb7 --- /dev/null +++ b/meson.build @@ -0,0 +1,342 @@ +project('libfuse3', ['c'], + version: '3.18.3', + meson_version: '>= 0.60.0', + default_options: [ + 'buildtype=debugoptimized', + 'c_std=gnu11', + 'cpp_std=c++20', + 'warning_level=2', + ]) + +# Would be better to create the version string +# from integers, i.e. concatenating strings instead +# of splitting a string, but 'project' needs to be +# the first meson.build keyword... + +# split version into base and rc +version_parts = meson.project_version().split('-') +base_version = version_parts[0] + +version_list = base_version.split('.') +FUSE_MAJOR_VERSION = version_list[0] +FUSE_MINOR_VERSION = version_list[1] +FUSE_HOTFIX_VERSION = version_list[2] +FUSE_RC_VERSION = version_parts.length() > 1 ? version_parts[1] : '' + +platform = host_machine.system() +if platform == 'darwin' + error('libfuse does not support OS-X.\n' + + 'Take a look at http://osxfuse.github.io/ or the more recent\n' + + 'https://www.fuse-t.org/ instead') +elif platform == 'cygwin' or platform == 'windows' + error('libfuse does not support Windows.\n' + + 'Take a look at http://www.secfs.net/winfsp/ instead') +endif + +cc = meson.get_compiler('c') + +# +# Feature detection, the resulting config file is installed +# with the package. +# Note: Symbols need to be care fully named, to avoid conflicts +# with applications linking to libfuse and including +# this config. +# +public_cfg = configuration_data() + +public_cfg.set('FUSE_MAJOR_VERSION', FUSE_MAJOR_VERSION) +public_cfg.set('FUSE_MINOR_VERSION', FUSE_MINOR_VERSION) +public_cfg.set('FUSE_HOTFIX_VERSION', FUSE_HOTFIX_VERSION) +public_cfg.set('FUSE_RC_VERSION', FUSE_RC_VERSION) + +# Default includes when checking for presence of functions and +# struct members +include_default = ''' +#include +#include +#include +#include +#include +#include +#include +#include /* For pthread_setname_np */ +''' +args_default = [ '-D_GNU_SOURCE' ] + +# +# Feature detection, only available at libfuse compilation time, +# but not for application linking to libfuse. +# +private_cfg = configuration_data() +private_cfg.set_quoted('PACKAGE_VERSION', meson.project_version()) + +# Test for presence of some functions +test_funcs = [ 'fork', 'fstatat', 'openat', 'readlinkat', 'pipe2', + 'splice', 'vmsplice', 'posix_fallocate', 'fdatasync', + 'utimensat', 'copy_file_range', 'fallocate', 'fspacectl' ] +foreach func : test_funcs + private_cfg.set('HAVE_' + func.to_upper(), + cc.has_function(func, prefix: include_default, args: args_default)) +endforeach + +# Special case checks that need custom code +special_funcs = { + 'pthread_setname_np': ''' + #include + int main(void) { + pthread_t thread = pthread_self(); + pthread_setname_np(thread, "test"); + return 0; + } + ''', + 'close_range': ''' + #include + #include + #ifdef linux + #include + #endif + int main(void) { + unsigned int flags = CLOSE_RANGE_UNSHARE; + return close_range(3, ~0U, flags); + } + ''', + 'listmount': ''' + #include + #include + #include + #include + + int main(int argc, char *argv[]) { + struct mnt_id_req req = { + .size = sizeof(struct mnt_id_req), + .mnt_id = LSMT_ROOT, + }; + uint64_t mnt_ids[1]; + + int n = syscall(SYS_listmount, &req, &mnt_ids, 1, 0); + if (n == -1) { + return -1; + } + } + ''' +} + +foreach name, code : special_funcs + private_cfg.set('HAVE_' + name.to_upper(), + cc.links(code, args: args_default, + name: name + ' check')) +endforeach + +# Headers checks +test_headers = [ 'sys/xattr.h', 'linux/limits.h' ] +foreach header : test_headers + private_cfg.set('HAVE_' + header.underscorify().to_upper(), + cc.has_header(header)) +endforeach + +# Regular function checks +private_cfg.set('HAVE_SETXATTR', + cc.has_function('setxattr', prefix: '#include ')) +private_cfg.set('HAVE_ICONV', + cc.has_function('iconv', prefix: '#include ')) +private_cfg.set('HAVE_BACKTRACE', + cc.has_function('backtrace', prefix: '#include ')) +private_cfg.set('HAVE_STATX', + cc.has_function('statx', prefix : '#define _GNU_SOURCE\n#include ')) + +# Struct member checks +private_cfg.set('HAVE_STRUCT_STAT_ST_ATIM', + cc.has_member('struct stat', 'st_atim', + prefix: include_default + '#include ', + args: args_default)) +private_cfg.set('HAVE_STRUCT_STAT_ST_ATIMESPEC', + cc.has_member('struct stat', 'st_atimespec', + prefix: include_default + '#include ', + args: args_default)) + +private_cfg.set('USDT_ENABLED', get_option('enable-usdt')) + +# Check for liburing with SQE128 support +code = ''' +#include +#include +int main(void) { + struct io_uring ring; + int ret = io_uring_queue_init(1, &ring, 0); +#ifndef IORING_SETUP_SQE128 +#error "No SQE128 support" +#endif + return ret; +}''' + +liburing = dependency('liburing', required: false) +libnuma = dependency('numa', required: false) + +if get_option('enable-io-uring') and liburing.found() and libnuma.found() + if cc.links(code, + name: 'liburing linking and SQE128 support', + dependencies: [liburing]) + private_cfg.set('HAVE_URING', true) + endif +endif + +# A custom io peer might not be a kernel and can forge requests libfuse parses +# without bounds checks. Off unless a build asks for it. +private_cfg.set('HAVE_CUSTOM_IO', get_option('enable-custom-io')) +if get_option('enable-custom-io') + warning('enable-custom-io lets a peer that might not be a kernel send ' + + 'requests that libfuse parses without bounds checks, so a ' + + 'forged request can crash or corrupt the filesystem process - ' + + 'see doc/README.custom-io.') +endif + +# +# Compiler configuration +# +add_project_arguments('-D_REENTRANT', '-DHAVE_LIBFUSE_PRIVATE_CONFIG_H', '-Wno-sign-compare', '-D_FILE_OFFSET_BITS=64', + '-Wstrict-prototypes', '-Wmissing-declarations', '-Wwrite-strings', + '-fno-strict-aliasing', language: 'c') +add_project_arguments('-D_REENTRANT', '-DHAVE_LIBFUSE_PRIVATE_CONFIG_H', '-D_GNU_SOURCE', '-D_FILE_OFFSET_BITS=64', + '-Wno-sign-compare', '-Wmissing-declarations', + '-Wwrite-strings', '-fno-strict-aliasing', language: 'cpp') + +# Some (stupid) GCC versions warn about unused return values even when they are +# casted to void. This makes -Wunused-result pretty useless, since there is no +# way to suppress the warning when we really *want* to ignore the value. +code = ''' +__attribute__((warn_unused_result)) int get_4() { + return 4; +} +int main(void) { + (void) get_4(); + return 0; +}''' +if not cc.compiles(code, args: [ '-O0', '-Werror=unused-result' ]) + message('Compiler warns about unused result even when casting to void') + add_project_arguments('-Wno-unused-result', language: 'c') +endif + +# It is hard to detect if the libc supports versioned symbols. Only gnu-libc +# seems to provide that, but then glibc is the main target for libfuse, so +# enable it by default +versioned_symbols = 1 + +# This is an attempt to detect if another libc is used. +code = ''' +int main(void) { +#if (defined(__UCLIBC__) || defined(__APPLE__)) +#error /* libc does not have versioned symbols */ +#endif + return 0; +}''' +if not cc.compiles(code, args: [ '-O0' ]) + versioned_symbols = 0 +endif + +# The detection can be overridden, which is useful for other (above unhandled) +# libcs and also especially useful for testing +if get_option('disable-libc-symbol-version') + versioned_symbols = 0 +endif + +if versioned_symbols == 1 + message('Enabling versioned libc symbols') + public_cfg.set('LIBFUSE_BUILT_WITH_VERSIONED_SYMBOLS', 1) + + # gcc-10 and newer support the symver attribute which we need to use if we + # want to support LTO + # recent clang and gcc both support __has_attribute (and if they are too old + # to have __has_attribute, then they are too old to support symver) + # other compilers might not have __has_attribute, but in those cases + # it is safe for this check to fail and for us to fallback to the old _asm_ + # method for symver. Anyway the attributes not supported by __has_attribute() + # unfortunately return true giving a false positive. So let's try to build + # using __attribute__ ((symver )) and see the result. + code = ''' + __attribute__ ((symver ("test@TEST"))) + void foo(void) { + } + + int main(void) { + return 0; + }''' + if cc.compiles(code, args: [ '-O0', '-c', '-Werror']) + message('Compiler supports symver attribute') + add_project_arguments('-DHAVE_SYMVER_ATTRIBUTE', language: 'c') + else + message('Compiler does not support symver attribute') + endif +else + message('Disabling versioned libc symbols') +endif + +# Older versions of musl libc don't unescape entries in /etc/mtab +# Try to detect this behaviour, and work around, if necessary. +detect_getmntent_needs_unescape = ''' +#define _GNU_SOURCE +#include +#include +#include +#include + +#define dir_space_tab "dir\\040space\\011tab" + +int main() +{ + const char *fake_mtab = "name " dir_space_tab " type opts 0 0\n"; + FILE *f = fmemopen((void *)fake_mtab, strlen(fake_mtab) + 1, "r"); + struct mntent *entp = getmntent(f); + fclose(f); + if(NULL == entp) + exit(EXIT_FAILURE); + if (0 == strcmp(entp->mnt_dir, dir_space_tab)) + printf("needs escaping\n"); + else + printf("no need to escape\n"); +} +''' + +if not meson.is_cross_build() + result = cc.run(detect_getmntent_needs_unescape) + if result.compiled() and result.returncode() == 0 and result.stdout().strip() == 'needs escaping' + message('getmntent does not unescape') + add_project_arguments('-DGETMNTENT_NEEDS_UNESCAPING', language: 'c') + endif +endif + +# Write private test results into fuse_config.h (stored in build directory) +configure_file(output: 'fuse_config.h', configuration : private_cfg) + +# Write the test results, installed with the package, +# symbols need to be properly prefixed to avoid +# symbol (define) conflicts +configure_file(output: 'libfuse_config.h', + configuration : public_cfg, + install: true, install_dir: join_paths(get_option('includedir'), 'fuse3')) + +# '.' will refer to current build directory, which contains config.h +include_dirs = include_directories('include', 'lib', '.') + +# Common dependencies +thread_dep = dependency('threads') + +# +# Read build files from sub-directories +# +subdirs = [ 'lib', 'include'] +if get_option('utils') and not platform.endswith('bsd') and platform != 'dragonfly' + subdirs += [ 'util', 'doc' ] +endif + +if get_option('examples') + subdirs += 'example' +endif + +if get_option('tests') + subdirs += 'test' +endif + +foreach n : subdirs + subdir(n) +endforeach + diff --git a/meson_options.txt b/meson_options.txt new file mode 100644 index 0000000..969686e --- /dev/null +++ b/meson_options.txt @@ -0,0 +1,32 @@ +option('disable-mtab', type : 'boolean', value : false, + description: 'Disable and ignore usage of /etc/mtab') + +option('udevrulesdir', type : 'string', value : '', + description: 'Where to install udev rules (if empty, query pkg-config(1))') + +option('initscriptdir', type : 'string', value : '/etc/init.d/', + description: 'Init script installation location (if empty, disable init script installation)') + +option('utils', type : 'boolean', value : true, + description: 'Whether or not to build and install helper programs') + +option('examples', type : 'boolean', value : true, + description: 'Whether or not to build example programs') + +option('useroot', type : 'boolean', value : true, + description: 'Set owner and setuid bits on installed files') + +option('tests', type : 'boolean', value : true, + description: 'Compile the test files') + +option('disable-libc-symbol-version', type : 'boolean', value : false, + description: 'Disable versioned symbols through libc') + +option('enable-usdt', type : 'boolean', value : false, + description: 'Enable user statically defined tracepoints for extra observability') + +option('enable-io-uring', type: 'boolean', value: true, + description: 'Enable fuse-over-io-uring support') + +option('enable-custom-io', type: 'boolean', value: false, + description: 'Enable fuse_session_custom_io(), which serves the protocol over a caller-provided fd instead of /dev/fuse. The peer might not be a kernel and can forge requests that libfuse parses without bounds checks, crashing or corrupting the filesystem process - see doc/README.custom-io') diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..1471ce5 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,6 @@ +# Packages required for building and testing libfuse only, +# no python code is required when using libfuse. +# Build packages: +meson +ninja +# Test packages: the suite itself is standard library only. diff --git a/signify/fuse-3.15.pub b/signify/fuse-3.15.pub new file mode 100644 index 0000000..b8c688c --- /dev/null +++ b/signify/fuse-3.15.pub @@ -0,0 +1,2 @@ +untrusted comment: signify public key +RWRsNRJIAGSHE0C6WuVmusQlvudAPlmOOkCw1LbuOLuu+25DuAmPCpDf diff --git a/signify/fuse-3.16.pub b/signify/fuse-3.16.pub new file mode 100644 index 0000000..13fdcb3 --- /dev/null +++ b/signify/fuse-3.16.pub @@ -0,0 +1,2 @@ +untrusted comment: signify public key +RWQtnc3WSoYwHGAdfvtTTVX8RsAXrNwMb8xqVwlY8lYY2Fxn2QUDiPYK diff --git a/signify/fuse-3.17.pub b/signify/fuse-3.17.pub new file mode 100644 index 0000000..02525e1 --- /dev/null +++ b/signify/fuse-3.17.pub @@ -0,0 +1,2 @@ +untrusted comment: signify public key +RWQqzcI/bjQ4/nouPgwUbs0WorZncrBxJHmiCb2N+GrMs9L6YAcGSFY/ diff --git a/signify/fuse-3.18.pub b/signify/fuse-3.18.pub new file mode 100644 index 0000000..eeee6bf --- /dev/null +++ b/signify/fuse-3.18.pub @@ -0,0 +1,2 @@ +untrusted comment: signify public key +RWS6gMnNrKp/zRSYWv13J+KwXE26vCUsbC/hVZmjQ8PA3xjixGLjodz3 diff --git a/signify/fuse-3.19.pub b/signify/fuse-3.19.pub new file mode 100644 index 0000000..317f8f7 --- /dev/null +++ b/signify/fuse-3.19.pub @@ -0,0 +1,2 @@ +untrusted comment: signify public key +RWR4cEcMGJhD3Dnd3NOeJck3WiuVt9A7mrkq+nQYwrwwmMdDDAan/YiU diff --git a/test/cases/README.md b/test/cases/README.md new file mode 100644 index 0000000..3c0d020 --- /dev/null +++ b/test/cases/README.md @@ -0,0 +1,421 @@ +# Writing a libfuse test + +## The runner + +`test/run-tests.py` is the suite. A plain Python program with no test framework +under it — run it directly; `meson test` only wraps it. Per run it + +- discovers every executable `cases/**/*.sh`, then narrows by `-t`, `-g`, `-X` + and `test/exclude`; +- runs `printcap` once, so every script sees the same `$FUSE_CAPS`; +- gives each test a private work directory and its own cgroup; +- launches the script as `bash -e`, cwd its own log directory, and enforces + `# TIMEOUT:` as a wall-clock bound; +- kills that cgroup afterwards, pass or fail, so a leaked daemon cannot wedge + the next test; +- turns the exit status plus what it collected — cores, suspicious log lines — + into PASS/SKIP/FAIL, prints the counts and the slowest five, and exits + non-zero if anything failed. + +`-j` tests run at once, so nothing may depend on a global name. + +One run, end to end — `->` calls or execs, `=>` spawns and waits, `..>` writes a +file, `[ ]` a process of its own: + +```text +meson test -C build (two entries: fuse-tests, fuse-tests-io-uring) + -> test/run-tests.py --build-dir + -> discover() cases/**/*.sh + cases/unit/ markers + -> TestRunner.run_all() + -> _run_batch(serial group, jobs=1) + -> _run_batch(rest, jobs=N) + -> run_one(spec) + -> prepare() mnt/ src/ tmp/ logs/ + env contract + -> write_repro() ..> logs/repro.sh + => [ /bin/sh -c LAUNCHER ] + ..> /cgroup.procs (self-move) + -> ulimit -S -c "$(ulimit -H -c)" + -> exec bash -e cases/.sh + -> . cases/lib/common.sh + -> fuse_mount_at + => [ fs daemon ] ..> logs/fs-N-*.out + -> fuse_wait_mount + -> _fuse_assert_uring + -> _check -> python3 cases/lib/checks.py + -> fuse_umount -> fusermount3 -u + -> _stream() ..> logs/script.out, logs/timestamps.out + -- timeout? -> dump_stacks() ..> ps.txt kstack.N.txt gdbstack.N.txt + -> _kill() ..> /cgroup.kill + -> collect_cores() -> gdb ..> backtrace...txt + -> scan_logs() suspicious words, minus logs/.allow + -> classify() PASS | FAIL | SKIP(77) + -> report() / discard_workdir() + -> summarise() +``` + +## What a test is + +An executable `*.sh` anywhere under `test/cases/`. Its **name** is the path +below `cases/` without the extension, so `cases/examples/hello-direct.sh` is +`examples/hello-direct`. + +| exit code | meaning | +|---------------|------------------------------------| +| 0 | PASS | +| 77 | SKIP — the script called `_notrun` | +| anything else | FAIL | + +Nothing registers a test anywhere. Dropping the file in and making it +executable is the whole step; `test/run-tests.py` discovers it. + +`test/cases/unit/` is the exception to all of the above. A C test that needs +neither a mount nor a gate has no script there — only a file named after the +binary, which the runner execs from `$FUSE_TEST_BIN_DIR` with no shell in +between. It carries no exec bit, and its content is a header block or nothing +at all. + +## The skeleton + +```sh +#!/usr/bin/env bash +# GROUP: examples quick + +. "$TEST_LIB/common.sh" + +_require_cap FUSE_CAP_WRITEBACK_CACHE # skip (77) if unsupported + +fuse_mount passthrough_ll -o writeback,source="$TEST_SRC" +_check fuse_test_open_write "$TEST_SRC" "$TEST_MNT" +fuse_umount +``` + +`#!/usr/bin/env bash`, not `#!/bin/bash`: FreeBSD has no bash in base, and +`common.sh` needs bash for the `FUSE_FS_*` arrays and `$SECONDS`. + +A case that sources a body from `lib/` gets `common.sh` with it and does not +name it itself. Source it directly only when there is no body, or when the case +gates before one — a gate has to run before the body, which is the whole test. + +## The environment you get + +The runner exports all of this into every script: + +```sh +TEST_NAME=examples/hello-ll-direct # discovery name +TEST_SCRIPT=/…/test/cases/examples/hello-ll-direct.sh +TEST_DIR=/…/test # source tree, never the build dir +TEST_LIB=/…/test/cases/lib # what tests source from +TEST_WORKDIR=/…/run/examples/hello-ll-direct +TEST_MNT=$TEST_WORKDIR/mnt +TEST_SRC=$TEST_WORKDIR/src +TEST_TMP=$TEST_WORKDIR/tmp +TEST_LOGDIR=$TEST_WORKDIR/logs # also the cwd of your script +BUILD_DIR=/…/build # meson build root +FUSE_EXAMPLE_DIR=$BUILD_DIR/example +FUSE_TEST_BIN_DIR=$BUILD_DIR/test +FUSE_UTIL_DIR=$BUILD_DIR/util +FUSE_CAPS="FUSE_CAP_ASYNC_READ FUSE_CAP_POSIX_LOCKS …" # printcap, run once +FUSE_UID=1000 # effective uid +FUSE_OS=Linux # platform.system() +PATH=$FUSE_UTIL_DIR:$FUSE_EXAMPLE_DIR:$PATH +``` + +`$TEST_MNT`, `$TEST_SRC` and `$TEST_TMP` already exist and are yours alone, so +a test never invents a temporary directory or worries about colliding with a +concurrent test. `$FUSE_OS` is exported so no script shells out to `uname -s`. + +## Header directives + +Only two exist. Anything else in the leading comment block is an ordinary +comment. + +```sh +# TIMEOUT: 180 # seconds; the default is 60 +# GROUP: examples serial +``` + +`# GROUP:` names are used by `run-tests.py -g`. + +## Errexit is on + +The runner runs your script as `bash -e`, so any command that fails and is not +checked ends the test there. Nothing to add to the script. What it means when +writing one is that a command you *expect* to fail must say so — `cmd || true`, +or an `if`, or `_assert_errno`. + +The same applies to a test you write as a bare condition: `[ -n "$x" ] && cmd` +returns non-zero when `$x` is empty and ends the test. Write +`[ -z "$x" ] || cmd` instead. + +Run a test by hand the same way (`bash -e cases/…`, or the `repro.sh` the +runner writes) or it will behave differently from CI. + +## Skipping + +> Gate on a feature, never on a version. `_require_cap FUSE_CAP_X` asks the +> running kernel what it supports; `_require_binary`, `_require_root` and +> `_require_prog` ask about the environment. There is deliberately no +> `_require_kernel_version`: a version comparison is wrong the moment the fix +> is backported. If a feature has no capability flag, grep the filesystem's own +> log for whatever it prints when the kernel refuses +> (`_require_fs_marker_absent`), or let the test fail on old kernels and have +> those targets exclude it by name via `test/exclude` or `-X`. +> +> A *platform* is the one exception, because it is not a version: +> `/proc/self/mountinfo` will not be backported to FreeBSD. `_require_linux +> ` states which Linux-only facility the test needs. Put it in the shared +> body when a whole family is affected, not in each caller. + +```sh +_fail # exit 1 with a message +_notrun # exit 77; the runner records the reason +_require_root # / _require_nonroot +_is_linux # true/false, for a conditional section +_require_linux # skip off Linux, naming the facility +_require_cap FUSE_CAP_NAME # skip unless printcap reported it +_require_binary example/null # skip unless $BUILD_DIR/ is executable +_require_prog losetup # skip unless it is on $PATH +_require_fuse_device # the kernel module, without the setuid check +_require_not_32bit_on_64 +_require_reachable_without_caps +_require_fs_marker_absent [fs-index] +``` + +Sourcing `common.sh` also gates the case on FUSE being usable at all — the +module loaded, and a setuid `fusermount3` when you are not root — because +every case that mounts needs both. A case that never mounts says so *before* +sourcing, and runs anyway: + +```sh +_fuse_no_mount_needed=1 +. "$TEST_LIB/common.sh" +``` + +## Mounting + +```sh +fuse_mount [args...] # $TEST_MNT, foreground (-f) +fuse_mount_at [args...] # explicit mountpoint, no -f +fuse_mount_helper [args...] # via mount.fuse3 +fuse_umount [idx] # and require the daemon to exit 0 +fuse_umount_lazy [idx] # the give-up path; the trap uses it +fuse_wait_mount [predicate] +fuse_fs_pid [idx] / fuse_fs_log [idx] +``` + +A filesystem name is resolved under `$FUSE_EXAMPLE_DIR`, or under +`$FUSE_TEST_BIN_DIR` when it is prefixed `test/` (so `test/hello` is the +`hello` built in `test/`). Each mount gets an index, and every one of them is +unmounted and killed by an EXIT trap, so an aborting test never leaks a mount. +Register your own cleanup with `_at_exit ` rather than `trap`, or you +will replace that trap. + +### Daemons run in the foreground + +`fuse_mount` passes `-f`, and a case that launches a filesystem itself is +expected to do the same. The runner owns the process: the daemon has to stay +in the test's cgroup for the timeout to reach it, and its stderr has to stay +attached to `logs/fs-N-*.out`. + +That leaves the backgrounded path — `fuse_daemonize_early_start()`, the parent +waiting for the mount, `stdout`/`stderr` closed once FUSE_INIT is answered — +covered only by the `mount.fuse3` cases, which is where synchronous FUSE_INIT +is reached as well. Covering it properly needs foreground and background +variants of the existing cases; that has not been done. + +## Assertions + +> Shell owns mounting, sequencing and skipping. It does not own checks. +> `_assert_eq`, `_assert_ne`, `_assert_file_eq` and `_wait_for` exist because +> shell expresses them exactly. Everything else is a `checks.py` subcommand — +> and if what you need is not there, **add a subcommand rather than +> approximating it in shell**. An `errno` compared against `strerror` text, a +> timestamp rounded by `touch -d`, an option set split by `awk`: each is a +> weaker test than the Python one line of `checks.py` would have been. + +A check is a subcommand of `test/cases/lib/checks.py`, not a command and not a +shell function. `_check` runs it: + +```sh +_check fuse_test_open_write "$TEST_SRC" "$TEST_MNT" +# runs: python3 test/cases/lib/checks.py fuse_test_open_write +``` + +Every name is prefixed `fuse_test_` so a reader never has to wonder whether +`unlink` in a test script means coreutils or a check. The list below is all +of them; if what you need is missing, add it to `checks.py`. + +```sh +# file and directory operations +fuse_test_unlink [--src ] # create, unlink, require ENOENT +fuse_test_mkdir # directory create +fuse_test_rmdir [--src ] # directory remove +fuse_test_symlink # symlink and read it back +fuse_test_create # O_CREAT, then mode/nlink/size +fuse_test_link # hard link, nlink, unlink +fuse_test_chown # chown uid then gid separately +fuse_test_utimens [--ns-tol N] # nanosecond timestamps +fuse_test_xattr # set/get/remove +fuse_test_statvfs # statvfs succeeds + +# reading and writing +fuse_test_open_read # write via src, read through the mount +fuse_test_open_write # create via src, write through the mount +fuse_test_append # O_APPEND writes +fuse_test_seek # lseek writes +fuse_test_open_unlink # write to an unlinked fd +fuse_test_truncate_path # grow and shrink by path +fuse_test_truncate_fd # grow and shrink by fd + +# listing and comparison +fuse_test_listdir # print the entries +fuse_test_listdir_first # print the first entry +fuse_test_assert_listdir ... # exactly these entries +fuse_test_readdir [--inode-check M] # listing and inodes +fuse_test_readdir_big [--inode-check M] # 500 entries +fuse_test_passthrough [--inode-check M] # both sides agree + +# predicates and errno +fuse_test_size # print st_size +fuse_test_ismount # is it a mountpoint +fuse_test_isbigger # is it bigger than that +fuse_test_expect_errno [args...] # require exactly that errno + +# mount state +fuse_test_mountinfo # one /proc/self/mountinfo field +fuse_test_assert_mount_opt ... # present in mount_options +fuse_test_refute_mount_opt ... # absent from mount_options +fuse_test_assert_super_opt ... # present in super_options +fuse_test_assert_super_opt_prefix

    ... # e.g. user_id= +fuse_test_assert_fstype ... # any one is acceptable +fuse_test_assert_source ... # any one is acceptable +fuse_test_fusermount_rejects # fusermount3 refuses to mount it + +# the remainder +fuse_test_printcap_caps # every FUSE_CAP_* is in the header +fuse_test_reachable_without_caps # mode-bit walk up the tree +fuse_test_elf_class # 32 or 64, from e_ident +fuse_test_null_roundtrip # read zeros, then write +fuse_test_cuse_roundtrip # read/write/offset exchange +fuse_test_uds_init # a FUSE INIT handshake +``` + +`--inode-check M` is `exact` (the default) or `nonzero`. `` for +`fuse_test_expect_errno` is one of `fuse_test_open_rw`, `fuse_test_open_ro` +or `fuse_test_stat`. `` for `fuse_test_fusermount_rejects` is +`not_dir_or_regular`, `no_write_access` or `no_such_file`. + +`common.sh` wraps the common ones in shell functions, which keep the +underscore-prefixed naming shell uses elsewhere: `_assert_errno`, +`_assert_listdir`, `_assert_xattr_roundtrip`, `mountinfo_field`, +`_assert_mount_opt`, `_refute_mount_opt`, `_assert_super_opt`, +`_assert_super_opt_prefix`, `_assert_fstype`, `_assert_source`. + +## Adding a variant of an existing test + +A parametrized case becomes a real file, so it gets its own duration, log +directory and name in the summary. The variant sets a few variables and sources +a shared body from `lib/`: + +```sh +#!/usr/bin/env bash +# cases/examples/hello-ll-mount-fuse-clone_fd.sh +# GROUP: examples quick + +FS_NAME=hello_ll # which example to mount +FS_OPTS=clone_fd # -o option list, empty for none +LAUNCH=mount_fuse # direct | mount_fuse | mount_fuse_dropcaps + +. "$TEST_LIB/hello.sh" +``` + +The shared body carries the gates for the whole family — one +`_require_linux "the clone_fd mount option"` in `lib/hello.sh` covers every +`clone_fd` variant — so a new caller inherits them. + +## Suspicious output fails a test + +Anything a daemon writes matching `error`, `warning`, `fatal`, `crash`, +`abort`, `exception`, `traceback`, `fault` or `uninitialised` fails the test +even when it exited 0. If that is a false positive for your test, exempt the +exact line: + +```sh +fuse_allow_output '^ \d\d \[[^]]+ message: .No error: 0.\]' +``` + +A core dump fails the test the same way. A test that deliberately crashes +something opts out per executable with `fuse_allow_core `. + +## When a test fails + +``` +$TEST_WORKDIR/logs/script.out what the test printed +$TEST_WORKDIR/logs/fs-0-*.out what the filesystem daemon printed +$TEST_WORKDIR/logs/fs-0-*.status its exit status +$TEST_WORKDIR/logs/core.. core, one per crashed process +$TEST_WORKDIR/logs/backtrace...txt its gdb backtrace +$TEST_WORKDIR/logs/repro.sh re-run this one test, same env, by hand +``` + +On a CI failure this whole tree is downloadable from the job's `test-logs` +artifact, minus the raw cores — the backtraces the runner already resolved are +what is worth keeping. + +A test's directory is removed as soon as it passes or skips, and the run +directory itself when nothing failed at all — so what is left afterwards is +exactly what is worth looking at (and uploading). `--keep-logs` keeps +everything anyway. + +## Running them + +`--build-dir` is the only option a run normally needs; it defaults to `.`. + +```sh +./test/run-tests.py --build-dir build # everything +./test/run-tests.py -t 'examples/hello-*' # fnmatch on the name, repeatable +./test/run-tests.py -g quick # only this GROUP, repeatable +./test/run-tests.py -X misc/hello-uds # skip one, repeatable +./test/run-tests.py -l # list the selection, run nothing +./test/run-tests.py -j1 -v # serially, output live +./test/run-tests.py -j8 # default is cpu_count // 2 +./test/run-tests.py --repeat 20 -t misc/x # chase a flake +./test/run-tests.py --timeout 300 # override every # TIMEOUT: +./test/run-tests.py --keep-logs # keep passing tests' output too +``` + +`-t` and `-g` narrow; `-X` and `test/exclude` remove from what is left. + +A `-t` pattern may also be the path to a case, so it can be tab-completed: +`test/cases/examples/cuse.sh` and `examples/cuse` name the same test, from any +directory. A directory selects everything under it, by path or by bare name — +`-t examples` and `-t test/cases/examples` are the same selection. A pattern +that matches no test is an error rather than an empty run. + +Where the output lands: + +```sh +--run-dir DIR # this directory, verbatim; so does $FUSE_TEST_RUN_DIR +--print-base-dir # print where a run would land, and exit +--persist-base-dir DIR # remember DIR in ~/.config/libfuse/tests.conf +``` + +Without `--run-dir`, a run gets a `run---` leaf under the +base, and the leaf is deleted when nothing failed. + +Unprivileged, without a setuid `fusermount3`, everything that mounts skips +itself. `--setuid-helpers` makes the build tree's helpers setuid root (with +`sudo`) before the run; it is a no-op once they are. + +`test/exclude` holds one test name per line for a host that cannot support a +test; `-X ` adds to it for one run. + +`TEST_WITH_VALGRIND=1` traces every daemon and scales the timeouts to match. + +## What breaks under -j + +A test that binds a fixed path, names a device node globally, or takes the +first free loop device cannot run concurrently with a copy of itself and must +declare `# GROUP: serial`. The three existing cases are `misc/hello-uds` +(`hello_ll_uds` hard-codes a socket path in /tmp), `examples/cuse` (it names a +device node globally) and `mount/blkdev-fsname-*` (`losetup -f` races). diff --git a/test/cases/ctests/api-30.sh b/test/cases/ctests/api-30.sh new file mode 100755 index 0000000..dae29c7 --- /dev/null +++ b/test/cases/ctests/api-30.sh @@ -0,0 +1,7 @@ +#!/usr/bin/env bash +# GROUP: ctests +# Serve a filesystem built against the FUSE_USE_VERSION 30 API. + +. "$TEST_LIB/common.sh" + +"$FUSE_TEST_BIN_DIR/test_api_30" "$TEST_MNT" diff --git a/test/cases/ctests/signals.sh b/test/cases/ctests/signals.sh new file mode 100755 index 0000000..d2cd1dd --- /dev/null +++ b/test/cases/ctests/signals.sh @@ -0,0 +1,7 @@ +#!/usr/bin/env bash +# GROUP: ctests +# Signal handling, issue #1182. + +. "$TEST_LIB/common.sh" + +"$FUSE_TEST_BIN_DIR/test_signals" diff --git a/test/cases/ctests/write-cache-writeback.sh b/test/cases/ctests/write-cache-writeback.sh new file mode 100755 index 0000000..ba3b3b8 --- /dev/null +++ b/test/cases/ctests/write-cache-writeback.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash +# GROUP: ctests + +. "$TEST_LIB/common.sh" + +_require_cap FUSE_CAP_WRITEBACK_CACHE + +"$FUSE_TEST_BIN_DIR/test_write_cache" "$TEST_MNT" -owriteback_cache diff --git a/test/cases/ctests/write-cache.sh b/test/cases/ctests/write-cache.sh new file mode 100755 index 0000000..20efa8e --- /dev/null +++ b/test/cases/ctests/write-cache.sh @@ -0,0 +1,11 @@ +#!/usr/bin/env bash +# GROUP: ctests + +. "$TEST_LIB/common.sh" + +_require_cap FUSE_CAP_WRITEBACK_CACHE + +# --delay_ms tests that close(rofd) does not block waiting for pending writes, +# which needs a390ccb316be ("fuse: add FOPEN_NOFLUSH"). The suite assumes a +# current kernel; an older target excludes this test by name. +"$FUSE_TEST_BIN_DIR/test_write_cache" "$TEST_MNT" --delay_ms=200 diff --git a/test/cases/examples/cuse.sh b/test/cases/examples/cuse.sh new file mode 100755 index 0000000..0f5e462 --- /dev/null +++ b/test/cases/examples/cuse.sh @@ -0,0 +1,29 @@ +#!/usr/bin/env bash +# GROUP: examples serial + +. "$TEST_LIB/common.sh" + +# example/cuse is built on FreeBSD too, so an unbuilt binary is not a proxy +# for "cannot run": CUSE is a Linux FUSE protocol feature. +_require_linux CUSE +_require_root +_require_binary example/cuse +_require_not_32bit_on_64 "$FUSE_EXAMPLE_DIR/cuse" + +# The device node is named globally, hence GROUP serial. +devname=cuse-test-$$ +devpath=/dev/$devname + +# cuse takes no mountpoint: it names its device with --name, and the test +# ends by terminating it rather than unmounting anything. +log=$TEST_LOGDIR/fs-0-cuse.out +"$FUSE_EXAMPLE_DIR/cuse" -f --name="$devname" >"$log" 2>&1 & +cuse_pid=$! +_at_exit "kill $cuse_pid 2>/dev/null" + +_wait_for 30 "[ -e '$devpath' ]" || { + cat "$log" >&2 + _fail "cuse did not create $devpath" +} + +_check fuse_test_cuse_roundtrip "$devpath" "$FUSE_EXAMPLE_DIR/cuse_client" diff --git a/test/cases/examples/dev-auto-unmount-nonroot.sh b/test/cases/examples/dev-auto-unmount-nonroot.sh new file mode 100755 index 0000000..1593764 --- /dev/null +++ b/test/cases/examples/dev-auto-unmount-nonroot.sh @@ -0,0 +1,15 @@ +#!/usr/bin/env bash +# GROUP: examples + +. "$TEST_LIB/common.sh" + +# Root can mount with dev and auto_unmount; a non-root user cannot use the +# device nodes it exposes. Split in two so the summary says which half ran. +_require_nonroot +_require_binary example/passthrough_ll + +fuse_mount passthrough_ll -o source=/dev,dev,auto_unmount >/dev/null + +_assert_errno EACCES fuse_test_open_ro "$TEST_MNT/null" + +fuse_umount diff --git a/test/cases/examples/dev-auto-unmount-root.sh b/test/cases/examples/dev-auto-unmount-root.sh new file mode 100755 index 0000000..fcf29dc --- /dev/null +++ b/test/cases/examples/dev-auto-unmount-root.sh @@ -0,0 +1,15 @@ +#!/usr/bin/env bash +# GROUP: examples + +. "$TEST_LIB/common.sh" + +# Root can mount with dev and auto_unmount; a non-root user cannot use the +# device nodes it exposes. Split in two so the summary says which half ran. +_require_root +_require_binary example/passthrough_ll + +fuse_mount passthrough_ll -o source=/dev,dev,auto_unmount >/dev/null + +cat "$TEST_MNT/null" >/dev/null + +fuse_umount diff --git a/test/cases/examples/hello-direct-clone_fd.sh b/test/cases/examples/hello-direct-clone_fd.sh new file mode 100755 index 0000000..ae6da37 --- /dev/null +++ b/test/cases/examples/hello-direct-clone_fd.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash +# GROUP: examples quick + +FS_NAME=hello +FS_OPTS=clone_fd +LAUNCH=direct + +. "$TEST_LIB/hello.sh" diff --git a/test/cases/examples/hello-direct.sh b/test/cases/examples/hello-direct.sh new file mode 100755 index 0000000..9b36214 --- /dev/null +++ b/test/cases/examples/hello-direct.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash +# GROUP: examples quick + +FS_NAME=hello +FS_OPTS= +LAUNCH=direct + +. "$TEST_LIB/hello.sh" diff --git a/test/cases/examples/hello-dropcaps-clone_fd.sh b/test/cases/examples/hello-dropcaps-clone_fd.sh new file mode 100755 index 0000000..f791962 --- /dev/null +++ b/test/cases/examples/hello-dropcaps-clone_fd.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash +# GROUP: examples quick + +FS_NAME=hello +FS_OPTS=clone_fd +LAUNCH=mount_fuse_dropcaps + +. "$TEST_LIB/hello.sh" diff --git a/test/cases/examples/hello-dropcaps.sh b/test/cases/examples/hello-dropcaps.sh new file mode 100755 index 0000000..72dec47 --- /dev/null +++ b/test/cases/examples/hello-dropcaps.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash +# GROUP: examples quick + +FS_NAME=hello +FS_OPTS= +LAUNCH=mount_fuse_dropcaps + +. "$TEST_LIB/hello.sh" diff --git a/test/cases/examples/hello-ll-direct-clone_fd.sh b/test/cases/examples/hello-ll-direct-clone_fd.sh new file mode 100755 index 0000000..dadf5f6 --- /dev/null +++ b/test/cases/examples/hello-ll-direct-clone_fd.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash +# GROUP: examples quick + +FS_NAME=hello_ll +FS_OPTS=clone_fd +LAUNCH=direct + +. "$TEST_LIB/hello.sh" diff --git a/test/cases/examples/hello-ll-direct.sh b/test/cases/examples/hello-ll-direct.sh new file mode 100755 index 0000000..9e1b7f7 --- /dev/null +++ b/test/cases/examples/hello-ll-direct.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash +# GROUP: examples quick + +FS_NAME=hello_ll +FS_OPTS= +LAUNCH=direct + +. "$TEST_LIB/hello.sh" diff --git a/test/cases/examples/hello-ll-dropcaps-clone_fd.sh b/test/cases/examples/hello-ll-dropcaps-clone_fd.sh new file mode 100755 index 0000000..37f93e1 --- /dev/null +++ b/test/cases/examples/hello-ll-dropcaps-clone_fd.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash +# GROUP: examples quick + +FS_NAME=hello_ll +FS_OPTS=clone_fd +LAUNCH=mount_fuse_dropcaps + +. "$TEST_LIB/hello.sh" diff --git a/test/cases/examples/hello-ll-dropcaps.sh b/test/cases/examples/hello-ll-dropcaps.sh new file mode 100755 index 0000000..c03529f --- /dev/null +++ b/test/cases/examples/hello-ll-dropcaps.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash +# GROUP: examples quick + +FS_NAME=hello_ll +FS_OPTS= +LAUNCH=mount_fuse_dropcaps + +. "$TEST_LIB/hello.sh" diff --git a/test/cases/examples/hello-ll-mount-fuse-clone_fd.sh b/test/cases/examples/hello-ll-mount-fuse-clone_fd.sh new file mode 100755 index 0000000..381ea24 --- /dev/null +++ b/test/cases/examples/hello-ll-mount-fuse-clone_fd.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash +# GROUP: examples quick + +FS_NAME=hello_ll +FS_OPTS=clone_fd +LAUNCH=mount_fuse + +. "$TEST_LIB/hello.sh" diff --git a/test/cases/examples/hello-ll-mount-fuse.sh b/test/cases/examples/hello-ll-mount-fuse.sh new file mode 100755 index 0000000..97feb38 --- /dev/null +++ b/test/cases/examples/hello-ll-mount-fuse.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash +# GROUP: examples quick + +FS_NAME=hello_ll +FS_OPTS= +LAUNCH=mount_fuse + +. "$TEST_LIB/hello.sh" diff --git a/test/cases/examples/hello-mount-fuse-clone_fd.sh b/test/cases/examples/hello-mount-fuse-clone_fd.sh new file mode 100755 index 0000000..4e41e57 --- /dev/null +++ b/test/cases/examples/hello-mount-fuse-clone_fd.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash +# GROUP: examples quick + +FS_NAME=hello +FS_OPTS=clone_fd +LAUNCH=mount_fuse + +. "$TEST_LIB/hello.sh" diff --git a/test/cases/examples/hello-mount-fuse.sh b/test/cases/examples/hello-mount-fuse.sh new file mode 100755 index 0000000..b71468d --- /dev/null +++ b/test/cases/examples/hello-mount-fuse.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash +# GROUP: examples quick + +FS_NAME=hello +FS_OPTS= +LAUNCH=mount_fuse + +. "$TEST_LIB/hello.sh" diff --git a/test/cases/examples/ioctl-ll.sh b/test/cases/examples/ioctl-ll.sh new file mode 100755 index 0000000..b2761ed --- /dev/null +++ b/test/cases/examples/ioctl-ll.sh @@ -0,0 +1,24 @@ +#!/usr/bin/env bash +# GROUP: examples + +. "$TEST_LIB/common.sh" + +_require_binary example/ioctl_ll +_require_not_32bit_on_64 "$FUSE_EXAMPLE_DIR/ioctl_ll" + +fuse_mount ioctl_ll >/dev/null + +testfile=$TEST_MNT/fioc +client=$FUSE_EXAMPLE_DIR/ioctl_ll_client + +# Restricted ioctls only. The unrestricted FIOC_READ/FIOC_WRITE work with +# CUSE rather than a regular FUSE mount, and are covered by examples/cuse. +_assert_eq "$("$client" get_size "$testfile")" 0 "initial size" +printf 'foobar' >"$testfile" +_assert_eq "$("$client" get_size "$testfile")" 6 "size after write" + +"$client" set_size "$testfile" 3 +printf 'foo' >"$TEST_TMP/expected" +_assert_file_eq "$testfile" "$TEST_TMP/expected" + +fuse_umount diff --git a/test/cases/examples/ioctl.sh b/test/cases/examples/ioctl.sh new file mode 100755 index 0000000..fd7ab9d --- /dev/null +++ b/test/cases/examples/ioctl.sh @@ -0,0 +1,23 @@ +#!/usr/bin/env bash +# GROUP: examples + +. "$TEST_LIB/common.sh" + +_require_binary example/ioctl +_require_not_32bit_on_64 "$FUSE_EXAMPLE_DIR/ioctl" + +fuse_mount ioctl >/dev/null + +testfile=$TEST_MNT/fioc +client=$FUSE_EXAMPLE_DIR/ioctl_client + +_assert_eq "$("$client" "$testfile")" 0 "initial size" +printf 'foobar' >"$testfile" +_assert_eq "$("$client" "$testfile")" 6 "size after write" + +# With a size argument the client truncates instead of reporting. +"$client" "$testfile" 3 +printf 'foo' >"$TEST_TMP/expected" +_assert_file_eq "$testfile" "$TEST_TMP/expected" + +fuse_umount diff --git a/test/cases/examples/null.sh b/test/cases/examples/null.sh new file mode 100755 index 0000000..69beac0 --- /dev/null +++ b/test/cases/examples/null.sh @@ -0,0 +1,24 @@ +#!/usr/bin/env bash +# GROUP: examples + +. "$TEST_LIB/common.sh" + +_require_binary example/null + +# The null filesystem replaces a regular file rather than a directory, so +# there is no mountpoint to poll for; it announces itself by the size it +# reports growing past what the placeholder holds. +mnt_file=$TEST_TMP/file +printf 'dummy' >"$mnt_file" + +null_is_up() +{ + python3 "$TEST_LIB/checks.py" fuse_test_isbigger "$1" 4000 +} + +FUSE_WAIT_PREDICATE=null_is_up +fuse_mount_at "$mnt_file" null -f >/dev/null + +_check fuse_test_null_roundtrip "$mnt_file" + +fuse_umount diff --git a/test/cases/examples/passthrough-base-debug.sh b/test/cases/examples/passthrough-base-debug.sh new file mode 100755 index 0000000..86bfa62 --- /dev/null +++ b/test/cases/examples/passthrough-base-debug.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env bash +# GROUP: examples passthrough +# 500-entry readdir plus test_syscalls over FUSE; the default 60s is +# not enough on a loaded runner, and -d makes it slower again. +# TIMEOUT: 300 + +FS_NAME=passthrough +FS_ARGS="-f -o entry_timeout=0,negative_timeout=0,attr_timeout=0,ac_attr_timeout=0 -d" +INODE_CHECK=exact +PT_MIRROR=1 +PT_SRC_VISIBLE=1 + +. "$TEST_LIB/passthrough.sh" diff --git a/test/cases/examples/passthrough-base.sh b/test/cases/examples/passthrough-base.sh new file mode 100755 index 0000000..38b2d68 --- /dev/null +++ b/test/cases/examples/passthrough-base.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env bash +# GROUP: examples passthrough +# 500-entry readdir plus test_syscalls over FUSE; the default 60s is +# not enough on a loaded runner, and -d makes it slower again. +# TIMEOUT: 300 + +FS_NAME=passthrough +FS_ARGS="-f -o entry_timeout=0,negative_timeout=0,attr_timeout=0,ac_attr_timeout=0" +INODE_CHECK=exact +PT_MIRROR=1 +PT_SRC_VISIBLE=1 + +. "$TEST_LIB/passthrough.sh" diff --git a/test/cases/examples/passthrough-fh-debug.sh b/test/cases/examples/passthrough-fh-debug.sh new file mode 100755 index 0000000..4ba7145 --- /dev/null +++ b/test/cases/examples/passthrough-fh-debug.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env bash +# GROUP: examples passthrough +# 500-entry readdir plus test_syscalls over FUSE; the default 60s is +# not enough on a loaded runner, and -d makes it slower again. +# TIMEOUT: 300 + +FS_NAME=passthrough_fh +FS_ARGS="-f -o entry_timeout=0,negative_timeout=0,attr_timeout=0,ac_attr_timeout=0 -d" +INODE_CHECK=exact +PT_MIRROR=1 +PT_SRC_VISIBLE=1 + +. "$TEST_LIB/passthrough.sh" diff --git a/test/cases/examples/passthrough-fh.sh b/test/cases/examples/passthrough-fh.sh new file mode 100755 index 0000000..ea66433 --- /dev/null +++ b/test/cases/examples/passthrough-fh.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env bash +# GROUP: examples passthrough +# 500-entry readdir plus test_syscalls over FUSE; the default 60s is +# not enough on a loaded runner, and -d makes it slower again. +# TIMEOUT: 300 + +FS_NAME=passthrough_fh +FS_ARGS="-f -o entry_timeout=0,negative_timeout=0,attr_timeout=0,ac_attr_timeout=0" +INODE_CHECK=exact +PT_MIRROR=1 +PT_SRC_VISIBLE=1 + +. "$TEST_LIB/passthrough.sh" diff --git a/test/cases/examples/passthrough-hp-nocache.sh b/test/cases/examples/passthrough-hp-nocache.sh new file mode 100755 index 0000000..48a7dee --- /dev/null +++ b/test/cases/examples/passthrough-hp-nocache.sh @@ -0,0 +1,19 @@ +#!/usr/bin/env bash +# GROUP: examples passthrough +# 500-entry readdir plus test_syscalls over FUSE; the default 60s is +# not enough on a loaded runner, and -d makes it slower again. +# TIMEOUT: 300 + +FS_NAME=passthrough_hp +FS_ARGS="--foreground --nocache" +PT_POSITIONAL="$TEST_SRC" +INODE_CHECK=exact +PT_MIRROR=0 +# With the cache on the mount is allowed a stale view of the +# source, so nothing may be compared against it. +PT_SRC_VISIBLE=1 +# The unlinked-testfiles check needs "fuse: fix illegal access to +# inode with reused nodeid"; the suite assumes a current kernel. +SYSCALL_ARGS=-u + +. "$TEST_LIB/passthrough.sh" diff --git a/test/cases/examples/passthrough-hp.sh b/test/cases/examples/passthrough-hp.sh new file mode 100755 index 0000000..3375e89 --- /dev/null +++ b/test/cases/examples/passthrough-hp.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash +# GROUP: examples passthrough +# 500-entry readdir plus test_syscalls over FUSE; the default 60s is +# not enough on a loaded runner, and -d makes it slower again. +# TIMEOUT: 300 + +FS_NAME=passthrough_hp +FS_ARGS="--foreground" +PT_POSITIONAL="$TEST_SRC" +INODE_CHECK=exact +PT_MIRROR=0 +# With the cache on the mount is allowed a stale view of the +# source, so nothing may be compared against it. +PT_SRC_VISIBLE=0 + +. "$TEST_LIB/passthrough.sh" diff --git a/test/cases/examples/passthrough-ll-debug.sh b/test/cases/examples/passthrough-ll-debug.sh new file mode 100755 index 0000000..42259e2 --- /dev/null +++ b/test/cases/examples/passthrough-ll-debug.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env bash +# GROUP: examples passthrough +# 500-entry readdir plus test_syscalls over FUSE; the default 60s is +# not enough on a loaded runner, and -d makes it slower again. +# TIMEOUT: 300 + +FS_NAME=passthrough_ll +FS_ARGS="-f -o timeout=0 -d" +INODE_CHECK=exact +PT_MIRROR=1 +PT_SRC_VISIBLE=1 + +. "$TEST_LIB/passthrough.sh" diff --git a/test/cases/examples/passthrough-ll-writeback-debug.sh b/test/cases/examples/passthrough-ll-writeback-debug.sh new file mode 100755 index 0000000..bf1490a --- /dev/null +++ b/test/cases/examples/passthrough-ll-writeback-debug.sh @@ -0,0 +1,17 @@ +#!/usr/bin/env bash +# GROUP: examples passthrough +# 500-entry readdir plus test_syscalls over FUSE; the default 60s is +# not enough on a loaded runner, and -d makes it slower again. +# TIMEOUT: 300 + +FS_NAME=passthrough_ll +FS_ARGS="-f -o timeout=0 -o writeback -d" +INODE_CHECK=exact +PT_MIRROR=1 +PT_SRC_VISIBLE=1 +# With writeback caching the kernel opens files for reading even +# when userspace asked for O_WRONLY, which fails unless the +# filesystem process has special permission. +SYSCALL_ARGS=-53 + +. "$TEST_LIB/passthrough.sh" diff --git a/test/cases/examples/passthrough-ll-writeback.sh b/test/cases/examples/passthrough-ll-writeback.sh new file mode 100755 index 0000000..d57fecb --- /dev/null +++ b/test/cases/examples/passthrough-ll-writeback.sh @@ -0,0 +1,17 @@ +#!/usr/bin/env bash +# GROUP: examples passthrough +# 500-entry readdir plus test_syscalls over FUSE; the default 60s is +# not enough on a loaded runner, and -d makes it slower again. +# TIMEOUT: 300 + +FS_NAME=passthrough_ll +FS_ARGS="-f -o timeout=0 -o writeback" +INODE_CHECK=exact +PT_MIRROR=1 +PT_SRC_VISIBLE=1 +# With writeback caching the kernel opens files for reading even +# when userspace asked for O_WRONLY, which fails unless the +# filesystem process has special permission. +SYSCALL_ARGS=-53 + +. "$TEST_LIB/passthrough.sh" diff --git a/test/cases/examples/passthrough-ll.sh b/test/cases/examples/passthrough-ll.sh new file mode 100755 index 0000000..994175a --- /dev/null +++ b/test/cases/examples/passthrough-ll.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env bash +# GROUP: examples passthrough +# 500-entry readdir plus test_syscalls over FUSE; the default 60s is +# not enough on a loaded runner, and -d makes it slower again. +# TIMEOUT: 300 + +FS_NAME=passthrough_ll +FS_ARGS="-f -o timeout=0" +INODE_CHECK=exact +PT_MIRROR=1 +PT_SRC_VISIBLE=1 + +. "$TEST_LIB/passthrough.sh" diff --git a/test/cases/examples/passthrough-plus-debug.sh b/test/cases/examples/passthrough-plus-debug.sh new file mode 100755 index 0000000..0e0fe6a --- /dev/null +++ b/test/cases/examples/passthrough-plus-debug.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env bash +# GROUP: examples passthrough +# 500-entry readdir plus test_syscalls over FUSE; the default 60s is +# not enough on a loaded runner, and -d makes it slower again. +# TIMEOUT: 300 + +FS_NAME=passthrough +FS_ARGS="-f --plus -o entry_timeout=0,negative_timeout=0,attr_timeout=0,ac_attr_timeout=0 -d" +INODE_CHECK=exact +PT_MIRROR=1 +PT_SRC_VISIBLE=1 + +. "$TEST_LIB/passthrough.sh" diff --git a/test/cases/examples/passthrough-plus.sh b/test/cases/examples/passthrough-plus.sh new file mode 100755 index 0000000..3ab2aad --- /dev/null +++ b/test/cases/examples/passthrough-plus.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env bash +# GROUP: examples passthrough +# 500-entry readdir plus test_syscalls over FUSE; the default 60s is +# not enough on a loaded runner, and -d makes it slower again. +# TIMEOUT: 300 + +FS_NAME=passthrough +FS_ARGS="-f --plus -o entry_timeout=0,negative_timeout=0,attr_timeout=0,ac_attr_timeout=0" +INODE_CHECK=exact +PT_MIRROR=1 +PT_SRC_VISIBLE=1 + +. "$TEST_LIB/passthrough.sh" diff --git a/test/cases/examples/passthrough-zero-ino-debug.sh b/test/cases/examples/passthrough-zero-ino-debug.sh new file mode 100755 index 0000000..7cd6287 --- /dev/null +++ b/test/cases/examples/passthrough-zero-ino-debug.sh @@ -0,0 +1,14 @@ +#!/usr/bin/env bash +# GROUP: examples passthrough +# 500-entry readdir plus test_syscalls over FUSE; the default 60s is +# not enough on a loaded runner, and -d makes it slower again. +# TIMEOUT: 300 + +FS_NAME=passthrough +FS_ARGS="-f --plus --readdir-zero-inodes -o entry_timeout=0,negative_timeout=0," +FS_ARGS+="attr_timeout=0,ac_attr_timeout=0 -d" +INODE_CHECK=nonzero +PT_MIRROR=1 +PT_SRC_VISIBLE=1 + +. "$TEST_LIB/passthrough.sh" diff --git a/test/cases/examples/passthrough-zero-ino.sh b/test/cases/examples/passthrough-zero-ino.sh new file mode 100755 index 0000000..f8358e4 --- /dev/null +++ b/test/cases/examples/passthrough-zero-ino.sh @@ -0,0 +1,14 @@ +#!/usr/bin/env bash +# GROUP: examples passthrough +# 500-entry readdir plus test_syscalls over FUSE; the default 60s is +# not enough on a loaded runner, and -d makes it slower again. +# TIMEOUT: 300 + +FS_NAME=passthrough +FS_ARGS="-f --plus --readdir-zero-inodes -o entry_timeout=0,negative_timeout=0," +FS_ARGS+="attr_timeout=0,ac_attr_timeout=0" +INODE_CHECK=nonzero +PT_MIRROR=1 +PT_SRC_VISIBLE=1 + +. "$TEST_LIB/passthrough.sh" diff --git a/test/cases/examples/poll.sh b/test/cases/examples/poll.sh new file mode 100755 index 0000000..de517e7 --- /dev/null +++ b/test/cases/examples/poll.sh @@ -0,0 +1,11 @@ +#!/usr/bin/env bash +# GROUP: examples + +. "$TEST_LIB/common.sh" + +fuse_mount poll >/dev/null + +# poll_client looks at files in its own working directory. +(cd "$TEST_MNT" && "$FUSE_EXAMPLE_DIR/poll_client") + +fuse_umount diff --git a/test/cases/examples/release-unlink-race.sh b/test/cases/examples/release-unlink-race.sh new file mode 100755 index 0000000..bfdfc12 --- /dev/null +++ b/test/cases/examples/release-unlink-race.sh @@ -0,0 +1,24 @@ +#!/usr/bin/env bash +# GROUP: examples +# Issue #746: RELEASE and UNLINK sent back to back must not race, which is +# only triggered when nullpath_ok is set. + +. "$TEST_LIB/common.sh" + +fuse_mount test/release_unlink_race >/dev/null + +# The filesystem mirrors the real tree, so the mount's view of a directory +# is that directory prefixed with the mountpoint. +race_dir=$TEST_TMP/race +mkdir "$race_dir" + +racefile=$TEST_MNT$race_dir/racefile +: >"$racefile" +rm "$racefile" + +# Slow CI pipelines need this for the unlink to finish processing. +sleep 3 + +_assert_listdir "$race_dir" + +fuse_umount diff --git a/test/cases/examples/test-hello-direct-clone_fd.sh b/test/cases/examples/test-hello-direct-clone_fd.sh new file mode 100755 index 0000000..c419195 --- /dev/null +++ b/test/cases/examples/test-hello-direct-clone_fd.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash +# GROUP: examples quick + +FS_NAME=test/hello +FS_OPTS=clone_fd +LAUNCH=direct + +. "$TEST_LIB/hello.sh" diff --git a/test/cases/examples/test-hello-direct.sh b/test/cases/examples/test-hello-direct.sh new file mode 100755 index 0000000..a588e13 --- /dev/null +++ b/test/cases/examples/test-hello-direct.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash +# GROUP: examples quick + +FS_NAME=test/hello +FS_OPTS= +LAUNCH=direct + +. "$TEST_LIB/hello.sh" diff --git a/test/cases/examples/test-hello-dropcaps-clone_fd.sh b/test/cases/examples/test-hello-dropcaps-clone_fd.sh new file mode 100755 index 0000000..40647d0 --- /dev/null +++ b/test/cases/examples/test-hello-dropcaps-clone_fd.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash +# GROUP: examples quick + +FS_NAME=test/hello +FS_OPTS=clone_fd +LAUNCH=mount_fuse_dropcaps + +. "$TEST_LIB/hello.sh" diff --git a/test/cases/examples/test-hello-dropcaps.sh b/test/cases/examples/test-hello-dropcaps.sh new file mode 100755 index 0000000..da92180 --- /dev/null +++ b/test/cases/examples/test-hello-dropcaps.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash +# GROUP: examples quick + +FS_NAME=test/hello +FS_OPTS= +LAUNCH=mount_fuse_dropcaps + +. "$TEST_LIB/hello.sh" diff --git a/test/cases/examples/test-hello-mount-fuse-clone_fd.sh b/test/cases/examples/test-hello-mount-fuse-clone_fd.sh new file mode 100755 index 0000000..c8e80eb --- /dev/null +++ b/test/cases/examples/test-hello-mount-fuse-clone_fd.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash +# GROUP: examples quick + +FS_NAME=test/hello +FS_OPTS=clone_fd +LAUNCH=mount_fuse + +. "$TEST_LIB/hello.sh" diff --git a/test/cases/examples/test-hello-mount-fuse.sh b/test/cases/examples/test-hello-mount-fuse.sh new file mode 100755 index 0000000..f02d166 --- /dev/null +++ b/test/cases/examples/test-hello-mount-fuse.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash +# GROUP: examples quick + +FS_NAME=test/hello +FS_OPTS= +LAUNCH=mount_fuse + +. "$TEST_LIB/hello.sh" diff --git a/test/cases/lib/checks.py b/test/cases/lib/checks.py new file mode 100755 index 0000000..53c8bc5 --- /dev/null +++ b/test/cases/lib/checks.py @@ -0,0 +1,1016 @@ +#!/usr/bin/env python3 +"""Everything the shell tests check that shell cannot express exactly. + +Shell is the dispatch unit, not the implementation language: a test calls this +module like any other command and it exits 0 on success, non-zero with a +diagnostic on stderr on failure. One subcommand per check, each named +fuse_test_* so a test script cannot mistake it for a system command. + +Re-expressing these in coreutils would silently weaken them -- an errno becomes +a strerror string match, an ftruncate on a held fd has no shell equivalent, a +nanosecond utimensat becomes whatever `touch -d` rounds to, and a +/proc/self/mountinfo parser becomes an awk script that gets the separator field +or the \\040 escapes subtly wrong. +""" + +import argparse +import errno +import filecmp +import os +import re +import shutil +import stat +import struct +import subprocess +import sys +import time +from enum import Enum +from os.path import join as pjoin +from tempfile import NamedTemporaryFile + + +class CheckFailed(Exception): + """An assertion inside a subcommand did not hold; message goes to stderr.""" + + +class InodeCheck(Enum): + EXACT = 1 + NONZERO = 2 + + +TEST_FILE = os.path.abspath(__file__) + +with open(TEST_FILE, 'rb') as _fh: + TEST_DATA = _fh.read() + + +def name_generator(): + """A file name no other invocation in this test will produce. + + Each check is its own process, so the pid goes into the name -- a plain + counter would make the second subcommand collide with the files the first + one left behind. + """ + name_generator.counter += 1 + return 'testfile_%d_%d' % (os.getpid(), name_generator.counter) + + +name_generator.counter = 0 + + +def _require(condition, message): + if not condition: + raise CheckFailed(message) + + +def readdir_inode(path): + """Sorted " " lines, from the test/readdir_inode helper.""" + binary = pjoin(os.environ['FUSE_TEST_BIN_DIR'], 'readdir_inode') + with subprocess.Popen([binary, path], stdout=subprocess.PIPE, + universal_newlines=True) as proc: + lines = proc.communicate()[0].splitlines() + lines.sort() + return lines + + +def check_inode(inode_check, actual, expected): + if inode_check == InodeCheck.EXACT: + _require(expected == actual, + 'inode mismatch: got %d, want %d' % (actual, expected)) + elif inode_check == InodeCheck.NONZERO: + _require(actual != 0, 'inode is zero') + + +def _inode_check(name): + return InodeCheck.EXACT if name == 'exact' else InodeCheck.NONZERO + + +class _open_fd: + """os.open() as a context manager.""" + + def __init__(self, name, flags): + self.fd = os.open(name, flags) + + def __enter__(self): + return self.fd + + def __exit__(self, *_exc): + os.close(self.fd) + + +def os_create(name): + os.close(os.open(name, os.O_CREAT | os.O_RDWR)) + + +def expect_enoent(path): + """Require that stat(path) fails with ENOENT.""" + try: + os.stat(path) + except OSError as exc: + _require(exc.errno == errno.ENOENT, + '%s: expected ENOENT, got %s' % (path, exc.strerror)) + return + raise CheckFailed('%s: expected ENOENT, but it exists' % path) + + +# ----------------------------------------------------------- POSIX operations + +def cmd_unlink(mnt_dir, src_dir=None): + name = name_generator() + fullname = mnt_dir + "/" + name + srcname = fullname + if src_dir is not None: + srcname = pjoin(src_dir, name) + with open(srcname, 'wb') as fh: + fh.write(b'hello') + _require(name in os.listdir(mnt_dir), '%s did not appear' % name) + os.unlink(fullname) + expect_enoent(fullname) + _require(name not in os.listdir(mnt_dir), '%s still listed' % name) + + +def cmd_mkdir(mnt_dir): + dirname = name_generator() + fullname = mnt_dir + "/" + dirname + os.mkdir(fullname) + fstat = os.stat(fullname) + _require(stat.S_ISDIR(fstat.st_mode), '%s is not a directory' % fullname) + _require(os.listdir(fullname) == [], '%s is not empty' % fullname) + # Some filesystems (e.g. BTRFS) don't track st_nlink for directories + _require(fstat.st_nlink in (1, 2), 'st_nlink is %d' % fstat.st_nlink) + _require(dirname in os.listdir(mnt_dir), '%s did not appear' % dirname) + + +def cmd_rmdir(mnt_dir, src_dir=None): + name = name_generator() + fullname = mnt_dir + "/" + name + srcname = fullname + if src_dir is not None: + srcname = pjoin(src_dir, name) + os.mkdir(srcname) + _require(name in os.listdir(mnt_dir), '%s did not appear' % name) + os.rmdir(fullname) + expect_enoent(fullname) + _require(name not in os.listdir(mnt_dir), '%s still listed' % name) + + +def cmd_symlink(mnt_dir): + linkname = name_generator() + fullname = mnt_dir + "/" + linkname + os.symlink("/imaginary/dest", fullname) + fstat = os.lstat(fullname) + _require(stat.S_ISLNK(fstat.st_mode), '%s is not a symlink' % fullname) + _require(os.readlink(fullname) == "/imaginary/dest", + 'wrong link target: %s' % os.readlink(fullname)) + _require(fstat.st_nlink == 1, 'st_nlink is %d' % fstat.st_nlink) + _require(linkname in os.listdir(mnt_dir), '%s did not appear' % linkname) + + +def cmd_create(mnt_dir): + name = name_generator() + fullname = pjoin(mnt_dir, name) + expect_enoent(fullname) + _require(name not in os.listdir(mnt_dir), '%s already listed' % name) + + os.close(os.open(fullname, os.O_CREAT | os.O_RDWR)) + + _require(name in os.listdir(mnt_dir), '%s did not appear' % name) + fstat = os.lstat(fullname) + _require(stat.S_ISREG(fstat.st_mode), '%s is not a regular file' % fullname) + _require(fstat.st_nlink == 1, 'st_nlink is %d' % fstat.st_nlink) + _require(fstat.st_size == 0, 'st_size is %d' % fstat.st_size) + + +def cmd_chown(mnt_dir): + filename = pjoin(mnt_dir, name_generator()) + os.mkdir(filename) + fstat = os.lstat(filename) + uid = fstat.st_uid + gid = fstat.st_gid + + uid_new = uid + 1 + os.chown(filename, uid_new, -1) + fstat = os.lstat(filename) + _require(fstat.st_uid == uid_new, 'uid is %d' % fstat.st_uid) + _require(fstat.st_gid == gid, 'gid changed to %d' % fstat.st_gid) + + gid_new = gid + 1 + os.chown(filename, -1, gid_new) + fstat = os.lstat(filename) + _require(fstat.st_uid == uid_new, 'uid changed to %d' % fstat.st_uid) + _require(fstat.st_gid == gid_new, 'gid is %d' % fstat.st_gid) + + +def cmd_open_read(src_dir, mnt_dir): + name = name_generator() + with open(pjoin(src_dir, name), 'wb') as fh_out, \ + open(TEST_FILE, 'rb') as fh_in: + shutil.copyfileobj(fh_in, fh_out) + + _require(filecmp.cmp(pjoin(mnt_dir, name), TEST_FILE, False), + '%s differs from %s' % (pjoin(mnt_dir, name), TEST_FILE)) + + +def cmd_open_write(src_dir, mnt_dir): + name = name_generator() + os_create(pjoin(src_dir, name)) + fullname = pjoin(mnt_dir, name) + with open(fullname, 'wb') as fh_out, \ + open(TEST_FILE, 'rb') as fh_in: + shutil.copyfileobj(fh_in, fh_out) + + _require(filecmp.cmp(fullname, TEST_FILE, False), + '%s differs from %s' % (fullname, TEST_FILE)) + + +def cmd_append(src_dir, mnt_dir): + name = name_generator() + os_create(pjoin(src_dir, name)) + fullname = pjoin(mnt_dir, name) + with _open_fd(fullname, os.O_WRONLY) as fd: + os.write(fd, b'foo\n') + with _open_fd(fullname, os.O_WRONLY | os.O_APPEND) as fd: + os.write(fd, b'bar\n') + + with open(fullname, 'rb') as fh: + got = fh.read() + _require(got == b'foo\nbar\n', 'appended file holds %r' % got) + + +def cmd_seek(src_dir, mnt_dir): + name = name_generator() + os_create(pjoin(src_dir, name)) + fullname = pjoin(mnt_dir, name) + with _open_fd(fullname, os.O_WRONLY) as fd: + os.lseek(fd, 1, os.SEEK_SET) + os.write(fd, b'foobar\n') + with _open_fd(fullname, os.O_WRONLY) as fd: + os.lseek(fd, 4, os.SEEK_SET) + os.write(fd, b'com') + + with open(fullname, 'rb') as fh: + got = fh.read() + _require(got == b'\0foocom\n', 'file holds %r' % got) + + +def cmd_open_unlink(mnt_dir): + name = name_generator() + data1 = b'foo' + data2 = b'bar' + fullname = pjoin(mnt_dir, name) + with open(fullname, 'wb+', buffering=0) as fh: + fh.write(data1) + os.unlink(fullname) + expect_enoent(fullname) + _require(name not in os.listdir(mnt_dir), '%s still listed' % name) + fh.write(data2) + fh.seek(0) + got = fh.read() + _require(got == data1 + data2, 'unlinked file holds %r' % got) + + +def cmd_statvfs(mnt_dir): + os.statvfs(mnt_dir) + + +def cmd_link(mnt_dir): + name1 = pjoin(mnt_dir, name_generator()) + name2 = pjoin(mnt_dir, name_generator()) + shutil.copyfile(TEST_FILE, name1) + _require(filecmp.cmp(name1, TEST_FILE, False), + '%s differs from %s' % (name1, TEST_FILE)) + + fstat1 = os.lstat(name1) + _require(fstat1.st_nlink == 1, 'st_nlink is %d' % fstat1.st_nlink) + + os.link(name1, name2) + + fstat1 = os.lstat(name1) + fstat2 = os.lstat(name2) + _require(fstat1 == fstat2, 'stat of the two links differs') + _require(fstat1.st_nlink == 2, 'st_nlink is %d' % fstat1.st_nlink) + _require(os.path.basename(name2) in os.listdir(mnt_dir), + '%s did not appear' % name2) + _require(filecmp.cmp(name1, name2, False), '%s and %s differ' + % (name1, name2)) + + # Since RELEASE requests are asynchronous, it is possible that libfuse + # still considers the file to be open at this point and (since + # -o hard_remove is not used) renames it instead of deleting it. + os.unlink(name2) + + _require(os.path.basename(name2) not in os.listdir(mnt_dir), + '%s still listed' % name2) + expect_enoent(name2) + + # See above, we may have to wait until RELEASE has been received before + # the st_nlink value is correct. + maxwait = time.time() + 2 + fstat1 = os.lstat(name1) + while fstat1.st_nlink == 2 and time.time() < maxwait: + fstat1 = os.lstat(name1) + time.sleep(0.1) + _require(fstat1.st_nlink == 1, 'st_nlink is %d' % fstat1.st_nlink) + + os.unlink(name1) + + +def cmd_readdir(src_dir, mnt_dir, inode_check='exact'): + inode_check = _inode_check(inode_check) + newdir = name_generator() + + src_newdir = pjoin(src_dir, newdir) + mnt_newdir = pjoin(mnt_dir, newdir) + file_ = src_newdir + "/" + name_generator() + subdir = src_newdir + "/" + name_generator() + subfile = subdir + "/" + name_generator() + + os.mkdir(src_newdir) + shutil.copyfile(TEST_FILE, file_) + os.mkdir(subdir) + shutil.copyfile(TEST_FILE, subfile) + + listdir_is = sorted(os.listdir(mnt_newdir)) + listdir_should = sorted([os.path.basename(file_), + os.path.basename(subdir)]) + _require(listdir_is == listdir_should, + 'readdir returned %r, want %r' % (listdir_is, listdir_should)) + + inodes_is = readdir_inode(mnt_newdir) + if inode_check == InodeCheck.EXACT: + inodes_should = readdir_inode(src_newdir) + _require(inodes_is == inodes_should, + 'inodes %r, want %r' % (inodes_is, inodes_should)) + else: + _require(all(int(line.split()[0]) != 0 for line in inodes_is), + 'a readdir inode is zero: %r' % inodes_is) + + os.unlink(file_) + os.unlink(subfile) + os.rmdir(subdir) + os.rmdir(src_newdir) + + +def cmd_readdir_big(src_dir, mnt_dir, inode_check='exact'): + inode_check = _inode_check(inode_check) + # Add enough entries so that readdir needs to be called multiple times. + fnames = [] + for i in range(500): + fname = ('A rather long filename to make sure that we ' + 'fill up the buffer - ' * 3) + str(i) + with open(pjoin(src_dir, fname), 'w') as fh: + fh.write('File %d' % i) + fnames.append(fname) + + listdir_is = sorted(os.listdir(mnt_dir)) + listdir_should = sorted(os.listdir(src_dir)) + _require(listdir_is == listdir_should, + 'mount lists %d entries, source lists %d' + % (len(listdir_is), len(listdir_should))) + + inodes_is = readdir_inode(mnt_dir) + if inode_check == InodeCheck.EXACT: + inodes_should = readdir_inode(src_dir) + _require(inodes_is == inodes_should, 'readdir inodes differ') + else: + _require(all(int(line.split()[0]) != 0 for line in inodes_is), + 'a readdir inode is zero') + + for fname in fnames: + stat_src = os.stat(pjoin(src_dir, fname)) + stat_mnt = os.stat(pjoin(mnt_dir, fname)) + check_inode(inode_check, stat_mnt.st_ino, stat_src.st_ino) + _require(stat_src.st_mtime == stat_mnt.st_mtime, 'st_mtime differs') + _require(stat_src.st_ctime == stat_mnt.st_ctime, 'st_ctime differs') + _require(stat_src.st_size == stat_mnt.st_size, 'st_size differs') + os.unlink(pjoin(src_dir, fname)) + + +def cmd_truncate_path(mnt_dir): + _require(len(TEST_DATA) > 1024, 'test data is too short') + + filename = pjoin(mnt_dir, name_generator()) + with open(filename, 'wb') as fh: + fh.write(TEST_DATA) + + size = os.stat(filename).st_size + _require(size == len(TEST_DATA), 'st_size is %d' % size) + + # Add zeros at the end + os.truncate(filename, size + 1024) + _require(os.stat(filename).st_size == size + 1024, 'grow failed') + with open(filename, 'rb') as fh: + _require(fh.read(size) == TEST_DATA, 'data changed on grow') + _require(fh.read(1025) == b'\0' * 1024, 'grow did not zero-fill') + + # Truncate data + os.truncate(filename, size - 1024) + _require(os.stat(filename).st_size == size - 1024, 'shrink failed') + with open(filename, 'rb') as fh: + _require(fh.read(size) == TEST_DATA[:size - 1024], + 'data changed on shrink') + + os.unlink(filename) + + +def cmd_truncate_fd(mnt_dir): + _require(len(TEST_DATA) > 1024, 'test data is too short') + with NamedTemporaryFile('w+b', 0, dir=mnt_dir) as fh: + fd = fh.fileno() + fh.write(TEST_DATA) + size = os.fstat(fd).st_size + _require(size == len(TEST_DATA), 'st_size is %d' % size) + + # Add zeros at the end + os.ftruncate(fd, size + 1024) + _require(os.fstat(fd).st_size == size + 1024, 'grow failed') + fh.seek(0) + _require(fh.read(size) == TEST_DATA, 'data changed on grow') + _require(fh.read(1025) == b'\0' * 1024, 'grow did not zero-fill') + + # Truncate data + os.ftruncate(fd, size - 1024) + _require(os.fstat(fd).st_size == size - 1024, 'shrink failed') + fh.seek(0) + _require(fh.read(size) == TEST_DATA[:size - 1024], + 'data changed on shrink') + + +def cmd_utimens(mnt_dir, ns_tol=0): + ns_tol = int(ns_tol) + filename = pjoin(mnt_dir, name_generator()) + os.mkdir(filename) + fstat = os.lstat(filename) + + atime = fstat.st_atime + 42.28 + mtime = fstat.st_mtime - 42.23 + atime_ns = fstat.st_atime_ns + int(42.28 * 1e9) + mtime_ns = fstat.st_mtime_ns - int(42.23 * 1e9) + os.utime(filename, None, ns=(atime_ns, mtime_ns)) + + fstat = os.lstat(filename) + + _require(abs(fstat.st_atime - atime) < 1, 'st_atime off by too much') + _require(abs(fstat.st_mtime - mtime) < 1, 'st_mtime off by too much') + _require(abs(fstat.st_atime_ns - atime_ns) <= ns_tol, + 'st_atime_ns off by %d' % abs(fstat.st_atime_ns - atime_ns)) + _require(abs(fstat.st_mtime_ns - mtime_ns) <= ns_tol, + 'st_mtime_ns off by %d' % abs(fstat.st_mtime_ns - mtime_ns)) + + +def cmd_passthrough(src_dir, mnt_dir, inode_check='exact'): + inode_check = _inode_check(inode_check) + name = name_generator() + src_name = pjoin(src_dir, name) + mnt_name = pjoin(mnt_dir, name) + + # First: write to the source directory + _require(name not in os.listdir(src_dir), '%s already in source' % name) + _require(name not in os.listdir(mnt_dir), '%s already in mount' % name) + with open(src_name, 'w') as fh: + fh.write('Hello, world') + + start_time = time.time() + while time.time() - start_time < 10: + if name in os.listdir(mnt_dir): + break + time.sleep(0.1) + else: + raise CheckFailed('%s did not appear in the mount within 10s' % name) + + _require(name in os.listdir(src_dir), '%s vanished from source' % name) + + src_stat = os.stat(src_name) + mnt_stat = os.stat(mnt_name) + _require(src_stat.st_mode == mnt_stat.st_mode, 'st_mode differs') + check_inode(inode_check, mnt_stat.st_ino, src_stat.st_ino) + _require(src_stat.st_size == mnt_stat.st_size, 'st_size differs') + _require(src_stat.st_mtime == mnt_stat.st_mtime, 'st_mtime differs') + + # Second: write to the mount directory + name = name_generator() + src_name = pjoin(src_dir, name) + mnt_name = pjoin(mnt_dir, name) + _require(name not in os.listdir(src_dir), '%s already in source' % name) + _require(name not in os.listdir(mnt_dir), '%s already in mount' % name) + with open(mnt_name, 'w') as fh: + fh.write('Hello, world') + _require(name in os.listdir(src_dir), '%s did not reach the source' % name) + _require(name in os.listdir(mnt_dir), '%s not in the mount' % name) + + src_stat = os.stat(src_name) + mnt_stat = os.stat(mnt_name) + _require(src_stat.st_mode == mnt_stat.st_mode, 'st_mode differs') + check_inode(inode_check, mnt_stat.st_ino, src_stat.st_ino) + _require(src_stat.st_size == mnt_stat.st_size, 'st_size differs') + _require(abs(src_stat.st_mtime - mnt_stat.st_mtime) < 0.01, + 'st_mtime differs') + + +def cmd_xattr(path): + os.setxattr(path, b'hello_ll_setxattr_name', b'hello_ll_setxattr_value') + got = os.getxattr(path, b'hello_ll_getxattr_name') + _require(got == b'hello_ll_getxattr_value', 'getxattr returned %r' % got) + os.removexattr(path, b'hello_ll_removexattr_name') + + +# ------------------------------------------------------- generic assertions + +# Operations expect-errno can run. One line each; add one rather than +# approximating the check in shell. +OPS = { + 'fuse_test_open_rw': lambda path: os.close(os.open(path, os.O_RDWR)), + 'fuse_test_open_ro': lambda path: os.close(os.open(path, os.O_RDONLY)), + 'fuse_test_stat': os.stat, +} + + +def cmd_expect_errno(want, op, args): + """Run one filesystem operation and require it to fail with *want*. + + Comparing OSError.errno keeps the check exact -- matching strerror text + would be locale-dependent and would conflate distinct errnos. + """ + code = getattr(errno, want, None) + if code is None: + raise CheckFailed('unknown errno name %s' % want) + handler = OPS.get(op) + if handler is None: + raise CheckFailed('unknown operation %s (have: %s)' + % (op, ' '.join(sorted(OPS)))) + try: + handler(*args) + except OSError as exc: + if exc.errno != code: + raise CheckFailed('%s %s: expected %s, got %s' + % (op, ' '.join(args), want, + errno.errorcode.get(exc.errno, exc.errno))) + return + raise CheckFailed('%s %s: expected %s, but it succeeded' + % (op, ' '.join(args), want)) + + +def cmd_assert_listdir(directory, names): + got = sorted(os.listdir(directory)) + want = sorted(names) + _require(got == want, '%s lists %r, want %r' % (directory, got, want)) + + +def cmd_listdir(directory): + for name in sorted(os.listdir(directory)): + print(name) + + +def cmd_listdir_first(directory): + """Print the first entry of *directory*. + + A subcommand rather than `listdir | head -1` because pipefail is off, so a + failing listdir in a pipeline would hand the caller an empty name instead + of an error. + """ + names = sorted(os.listdir(directory)) + _require(names, '%s is empty' % directory) + print(names[0]) + + +def cmd_size(path): + print(os.path.getsize(path)) + + +def cmd_ismount(path): + """Exit 0 when *path* is a mountpoint. + + os.path.ismount() is portable; mountpoint(1) is util-linux only. + """ + _require(os.path.ismount(path), '%s is not a mountpoint' % path) + + +def cmd_isbigger(path, size): + """Exit 0 when *path* is larger than *size* bytes. + + The mount predicate for a filesystem with no mountpoint of its own: the + null filesystem is mounted over a regular file. + """ + _require(os.stat(path).st_size > int(size), + '%s is not larger than %s bytes' % (path, size)) + + +# --------------------------------------------------------------- mount state + +def parse_mountinfo(mnt_dir): + """Return the /proc/self/mountinfo entry for *mnt_dir*, or None. + + Parses the line for the mountpoint and returns a dict with keys: + 'mountpoint' - the mount point path (str) + 'fstype' - filesystem type as the kernel sees it, + e.g. 'fuse' or 'fuse.' (str) + 'source' - mount source field, e.g. 'hello' or + '#' fallback form (str) + 'mount_options' - per-mount options/attrs (set of str) + 'super_options' - superblock options from the filesystem (set of str) + + These fields are exactly what /proc/self/mountinfo exposes; they capture + the post-mount state that differs between the legacy mount(2) path and the + new fsopen/fsconfig/fsmount path. + """ + target = os.path.realpath(mnt_dir) + with open('/proc/self/mountinfo') as fh: + for line in fh: + parts = line.rstrip('\n').split(' ') + try: + sep = parts.index('-') + except ValueError: + continue + if len(parts) < sep + 4 or sep < 6: + continue + mountpoint = parts[4].replace('\\040', ' ') + if mountpoint != target: + continue + return { + 'mountpoint': mountpoint, + 'fstype': parts[sep + 1], + 'source': parts[sep + 2].replace('\\040', ' '), + 'mount_options': set(parts[5].split(',')), + 'super_options': set(parts[sep + 3].split(',')), + } + return None + + +def _mountinfo(mnt_dir): + info = parse_mountinfo(mnt_dir) + if info is None: + raise CheckFailed('%s not found in /proc/self/mountinfo' % mnt_dir) + return info + + +def cmd_mountinfo(mnt_dir, field): + """Echo one field of the mountinfo line. Empty output means not mounted.""" + info = parse_mountinfo(mnt_dir) + if info is None: + return + _require(field in info, + 'no such mountinfo field: %s (have %s)' + % (field, ' '.join(sorted(info)))) + value = info[field] + print(','.join(sorted(value)) if isinstance(value, set) else value) + + +def cmd_assert_mount_opt(mnt_dir, opts): + info = _mountinfo(mnt_dir) + for opt in opts: + _require(opt in info['mount_options'], + '%r missing from mount_options=%r' + % (opt, sorted(info['mount_options']))) + + +def cmd_refute_mount_opt(mnt_dir, opts): + info = _mountinfo(mnt_dir) + for opt in opts: + _require(opt not in info['mount_options'], + 'unexpected %r in mount_options=%r' + % (opt, sorted(info['mount_options']))) + + +def cmd_assert_super_opt(mnt_dir, opts): + info = _mountinfo(mnt_dir) + for opt in opts: + _require(opt in info['super_options'], + '%r missing from super_options=%r' + % (opt, sorted(info['super_options']))) + + +def cmd_assert_super_opt_prefix(mnt_dir, prefixes): + info = _mountinfo(mnt_dir) + for prefix in prefixes: + _require(any(o.startswith(prefix) for o in info['super_options']), + 'no super option starts with %r: %r' + % (prefix, sorted(info['super_options']))) + + +def cmd_assert_fstype(mnt_dir, expected): + """The fstype must be one of *expected*; several are legitimate.""" + info = _mountinfo(mnt_dir) + _require(info['fstype'] in expected, + 'unexpected fstype %r (expected one of %r)' + % (info['fstype'], expected)) + + +def cmd_assert_source(mnt_dir, expected): + """The source must be one of *expected*. + + Several are accepted because the ENODEV fallback legitimately produces + either 'myfsname' or 'mysub#myfsname'. + """ + info = _mountinfo(mnt_dir) + _require(info['source'] in expected, + 'unexpected source %r (expected one of %r)' + % (info['source'], expected)) + + +# ------------------------------------------------------------- odds and ends + +def cmd_printcap_caps(src_root): + """Verify that the FUSE_CAP_* name table printcap uses includes every + flag from fuse_common.h.""" + header_path = pjoin(src_root, 'include', 'fuse_common.h') + printcap_path = pjoin(src_root, 'lib', 'fuse_cap_names_i.h') + if not os.path.exists(header_path): + raise CheckFailed('%s not found' % header_path) + + header_caps = set() + with open(header_path) as fh: + for line in fh: + match = re.match(r'#define\s+(FUSE_CAP_\w+)\s+', line) + if match: + header_caps.add(match.group(1)) + + printcap_caps = set() + with open(printcap_path) as fh: + for line in fh: + if re.match(r'^\s*//', line): # commented out + continue + match = re.search(r'\{\s*(FUSE_CAP_\w+),\s*"', line) + if match: + printcap_caps.add(match.group(1)) + + missing = header_caps - printcap_caps + extra = printcap_caps - header_caps + if missing or extra: + msg = ['%d caps in %s, %d in %s' + % (len(header_caps), header_path, + len(printcap_caps), printcap_path)] + if missing: + msg.append('missing in %s: %s' % (printcap_path, sorted(missing))) + if extra: + msg.append('extra in %s: %s' % (printcap_path, sorted(extra))) + raise CheckFailed('\n'.join(msg)) + + +def cmd_reachable_without_caps(path): + """Exit 0 when *path* is reachable by a process without CAP_DAC_OVERRIDE. + + mount.fuse3 execs the fs binary only after dropping all capabilities; the + process keeps uid/gid 0 but loses CAP_DAC_OVERRIDE, so the binary and every + directory on the way to it must be executable for it by plain permission + bits (a user-owned mode-0700 home directory is not). + """ + def executable_without_caps(st): + if st.st_uid == 0: + return st.st_mode & stat.S_IXUSR + if st.st_gid == 0: + return st.st_mode & stat.S_IXGRP + return st.st_mode & stat.S_IXOTH + + path = os.path.realpath(path) + while True: + if not executable_without_caps(os.stat(path)): + raise CheckFailed('%s is not reachable by the capability-less ' + 'file system process' % path) + if path == '/': + return + path = os.path.dirname(path) + + +def cmd_elf_class(path): + """Print the ELF class of *path*, 32 or 64. + + Reads e_ident[EI_CLASS] directly rather than grepping file(1) output for + the string 'ELF 32-bit'. + """ + with open(path, 'rb') as fh: + ident = fh.read(5) + if ident[:4] != b'\x7fELF': + raise CheckFailed('%s is not an ELF binary' % path) + print({1: 32, 2: 64}.get(ident[4], 0)) + + +def cmd_null_roundtrip(path): + """Read zeros from the null filesystem, then write to it.""" + with open(path, 'rb') as fh: + got = fh.read(382) + _require(got == b'\0' * 382, 'read %r rather than zeros' % got[:32]) + with open(path, 'wb') as fh: + fh.write(b'whatever') + + +def cmd_cuse_roundtrip(devpath, client): + """The binary read/write/offset exchange test_cuse performed.""" + output = subprocess.check_output([client, devpath, 's']) + _require(output == b'0\n', 'initial size is %r' % output) + + data = b'some test data' + off = 5 + proc = subprocess.Popen([client, devpath, 'w', str(len(data)), str(off)], + stdin=subprocess.PIPE) + proc.stdin.write(data) + proc.stdin.close() + ret = proc.wait(timeout=10) + _require(ret == 0, 'write client exited with %d' % ret) + + want = str(off + len(data)).encode() + b'\n' + output = subprocess.check_output([client, devpath, 's']) + _require(output == want, 'size is %r, want %r' % (output, want)) + + out = subprocess.check_output( + [client, devpath, 'r', str(off + len(data) + 2), '0']) + _require(out == (b'\0' * off) + data, 'read back %r' % out) + + +# ------------------------------------------------------ FUSE INIT over AF_UNIX + +FUSE_OP_INIT = 26 +FUSE_MAJOR_VERSION = 7 +FUSE_MINOR_VERSION = 38 + +_fuse_in_header_fmt = ' for the named reason. + + fusermount3 reaches its mountpoint checks only once _FUSE_COMMFD names a + socket it could hand the /dev/fuse fd back over, so a real socketpair goes + in even though the mount is never meant to get that far. + """ + import socket + + wanted = _FUSERMOUNT_REJECTIONS.get(reason) + if wanted is None: + raise CheckFailed('unknown rejection reason %r' % reason) + + binary = pjoin(os.environ['FUSE_UTIL_DIR'], 'fusermount3') + env = dict(os.environ) + parent, child = socket.socketpair() + try: + env['_FUSE_COMMFD'] = str(child.fileno()) + proc = subprocess.run([binary, '-o', 'rw', mountpoint], env=env, + pass_fds=(child.fileno(),), + stderr=subprocess.PIPE, + universal_newlines=True) + finally: + parent.close() + child.close() + + _require(proc.returncode != 0, + 'fusermount3 accepted mountpoint %s' % mountpoint) + _require(wanted in proc.stderr, + 'mountpoint %s: expected %r on stderr, got: %s' + % (mountpoint, wanted, proc.stderr.strip())) + _require(not os.path.ismount(mountpoint), + '%s is a mountpoint after a refused mount' % mountpoint) + + +# ------------------------------------------------------------------ dispatch + +_INODE_CHECK = {'default': 'exact', 'choices': ('exact', 'nonzero')} + + +def build_parser(): + """One subparser per check. Each records how to call its function, so + adding a check is one line here and one function above.""" + parser = argparse.ArgumentParser(description=__doc__) + sub = parser.add_subparsers(dest='cmd', required=True) + + def add(name, func, *positional, **options): + cmd = sub.add_parser(name) + for arg in positional: + if isinstance(arg, tuple): + cmd.add_argument(arg[0], **arg[1]) + else: + cmd.add_argument(arg) + for flag, kwargs in options.items(): + cmd.add_argument('--' + flag.replace('_', '-'), dest=flag, + **kwargs) + names = [a[0] if isinstance(a, tuple) else a for a in positional] + names += list(options) + cmd.set_defaults(call=lambda args: func(*[getattr(args, n) + for n in names])) + return cmd + + add('fuse_test_unlink', cmd_unlink, 'mnt', src={'default': None}) + add('fuse_test_mkdir', cmd_mkdir, 'mnt') + add('fuse_test_rmdir', cmd_rmdir, 'mnt', src={'default': None}) + add('fuse_test_symlink', cmd_symlink, 'mnt') + add('fuse_test_create', cmd_create, 'mnt') + add('fuse_test_chown', cmd_chown, 'mnt') + add('fuse_test_open_read', cmd_open_read, 'src', 'mnt') + add('fuse_test_open_write', cmd_open_write, 'src', 'mnt') + add('fuse_test_append', cmd_append, 'src', 'mnt') + add('fuse_test_seek', cmd_seek, 'src', 'mnt') + add('fuse_test_open_unlink', cmd_open_unlink, 'mnt') + add('fuse_test_statvfs', cmd_statvfs, 'mnt') + add('fuse_test_link', cmd_link, 'mnt') + add('fuse_test_readdir', cmd_readdir, 'src', 'mnt', + inode_check=_INODE_CHECK) + add('fuse_test_readdir_big', cmd_readdir_big, 'src', 'mnt', + inode_check=_INODE_CHECK) + add('fuse_test_truncate_path', cmd_truncate_path, 'mnt') + add('fuse_test_truncate_fd', cmd_truncate_fd, 'mnt') + add('fuse_test_utimens', cmd_utimens, 'mnt', ns_tol={'default': 0}) + add('fuse_test_passthrough', cmd_passthrough, 'src', 'mnt', + inode_check=_INODE_CHECK) + add('fuse_test_xattr', cmd_xattr, 'path') + + add('fuse_test_expect_errno', cmd_expect_errno, 'want', 'op', + ('args', {'nargs': '*'})) + add('fuse_test_assert_listdir', cmd_assert_listdir, 'dir', + ('names', {'nargs': '*'})) + add('fuse_test_listdir', cmd_listdir, 'dir') + add('fuse_test_listdir_first', cmd_listdir_first, 'dir') + add('fuse_test_size', cmd_size, 'path') + add('fuse_test_ismount', cmd_ismount, 'path') + add('fuse_test_isbigger', cmd_isbigger, 'path', 'size') + + add('fuse_test_mountinfo', cmd_mountinfo, 'mnt', 'field') + for name, func in (('fuse_test_assert_mount_opt', cmd_assert_mount_opt), + ('fuse_test_refute_mount_opt', cmd_refute_mount_opt), + ('fuse_test_assert_super_opt', cmd_assert_super_opt), + ('fuse_test_assert_super_opt_prefix', + cmd_assert_super_opt_prefix), + ('fuse_test_assert_fstype', cmd_assert_fstype), + ('fuse_test_assert_source', cmd_assert_source)): + add(name, func, 'mnt', ('values', {'nargs': '+'})) + add('fuse_test_fusermount_rejects', cmd_fusermount_rejects, + 'mountpoint', 'reason') + + add('fuse_test_printcap_caps', cmd_printcap_caps, 'src_root') + add('fuse_test_reachable_without_caps', cmd_reachable_without_caps, 'path') + add('fuse_test_elf_class', cmd_elf_class, 'path') + add('fuse_test_null_roundtrip', cmd_null_roundtrip, 'path') + add('fuse_test_cuse_roundtrip', cmd_cuse_roundtrip, 'devpath', 'client') + add('fuse_test_uds_init', cmd_uds_init, 'sockpath') + return parser + + +def main(argv): + args = build_parser().parse_args(argv) + try: + args.call(args) + # struct.error is what a daemon replying with a short or overlong packet + # raises, which is a check failing rather than this script breaking. + except (CheckFailed, OSError, subprocess.SubprocessError, + struct.error) as exc: + print('checks.py %s: %s' % (args.cmd, exc), file=sys.stderr) + return 1 + return 0 + + +if __name__ == '__main__': + sys.exit(main(sys.argv[1:])) diff --git a/test/cases/lib/common.sh b/test/cases/lib/common.sh new file mode 100644 index 0000000..4516620 --- /dev/null +++ b/test/cases/lib/common.sh @@ -0,0 +1,561 @@ +# common.sh - helper library for the libfuse shell tests. +# +# Sourced by each lib/ body, and by a case that has none or that gates before +# sourcing one. Provides the FUSE verbs (mount, umount, assertions, skip +# protocol); it knows nothing about scheduling, which belongs to run-tests.py. +# +# Scripts run under errexit, imposed once by the runner (`bash -e`), so an +# unchecked command that fails ends the test instead of letting the rest of it +# run against a broken state. Two consequences show up below: a status that is +# examined must be captured in the same command, and the cleanup trap has to +# swallow each of its own failures. +# +# Nothing here is sourced, probed or defaulted: the runner puts the whole +# contract in the environment before exec'ing the script, and the variables +# below are read as given. A case is therefore not runnable by hand; replay +# logs/repro.sh, which restores the environment first. +# +# TEST_NAME the name the runner reports this test under +# TEST_LIB this directory, where checks.py is found +# TEST_MNT empty mount point, one per test +# TEST_LOGDIR where daemon logs and status files are written +# BUILD_DIR build tree root; _require_binary resolves against it +# FUSE_EXAMPLE_DIR $BUILD_DIR/example +# FUSE_TEST_BIN_DIR $BUILD_DIR/test +# FUSE_UTIL_DIR $BUILD_DIR/util, holding fusermount3 and mount.fuse3 +# FUSE_CAPS capability names printcap reported, space separated +# FUSE_UID effective uid +# FUSE_OS uname -s +# FUSE_URING_ENABLE 1 when the run expects the daemons to use io_uring +# FUSE_VALGRIND command prefix, empty unless the run is under valgrind +# +# The runner exports more for the cases themselves (TEST_SRC, TEST_TMP, +# TEST_WORKDIR); those are not read here. A case may set FUSE_WAIT_PREDICATE +# before mounting, to replace the readiness check fuse_mount waits on, and +# _fuse_no_mount_needed before sourcing this file, when it never mounts. + +# A case that gates before sourcing a lib/ body reaches this file twice, and +# the second pass must not reset the mount bookkeeping the first one is +# already tracking a mount in. +[ -z "${_FUSE_COMMON_SH:-}" ] || return 0 +_FUSE_COMMON_SH=1 + +# --------------------------------------------------------------- result protocol + +# _fail +# Print msg to stderr with the test name, then exit 1 (FAIL). +# +# A test whose subject only reports an unsupported kernel once it has tried to +# use it cannot gate on that up front. $FAIL_CALLBACK is its last look, called +# with the message; _notrun from there turns the failure into a skip. +_fail() +{ + if [ -n "${FAIL_CALLBACK:-}" ]; then + local callback=$FAIL_CALLBACK + FAIL_CALLBACK= # a failing callback must not recurse + "$callback" "$@" + fi + echo "FAIL: ${TEST_NAME}: $*" >&2 + exit 1 +} + +# _notrun +# Print reason to stderr, then exit 77 (SKIP). The runner records the reason. +# stderr, because fuse_mount echoes the mount index on stdout and its callers +# redirect that away; a skip raised inside it would go with it. +_notrun() +{ + echo "SKIP: $*" >&2 + exit 77 +} + +# _require_root +# _notrun unless the effective uid is 0. +_require_root() +{ + [ "$FUSE_UID" = 0 ] || _notrun "needs root" +} + +# _require_nonroot +# _notrun unless the effective uid is not 0. +_require_nonroot() +{ + [ "$FUSE_UID" != 0 ] || _notrun "needs to run as non-root" +} + +# _is_linux +# True on Linux. For conditional sections inside a shared body; a whole script +# that cannot run elsewhere calls _require_linux instead. +_is_linux() +{ + [ "$FUSE_OS" = Linux ] +} + +# _require_linux +# _notrun unless $FUSE_OS is Linux. names the Linux-only facility, so +# the summary says which one rather than just "not linux". +_require_linux() +{ + _is_linux || _notrun "$1 is Linux-only" +} + +# _require_cap +# _notrun unless the name appears in $FUSE_CAPS. +_require_cap() +{ + local cap=$1 + + case " $FUSE_CAPS " in + *" $cap "*) return 0 ;; + esac + _notrun "kernel does not offer $cap" +} + +# _require_fuse_device +# _notrun unless the kernel side is there. A case that reaches fusermount3 but +# never completes a mount needs this much and no more. +_require_fuse_device() +{ + [ -e /dev/fuse ] || + _notrun "the FUSE kernel module does not seem to be loaded" +} + +# _require_fuse +# _notrun unless this user can mount a FUSE filesystem at all: the kernel side +# has to be there, and an unprivileged user needs a setuid fusermount3. It runs +# when a case sources this file, so a build tree whose fusermount3 was never +# chmod 4755'd reports skips rather than a wall of mount failures. +_require_fuse() +{ + local fusermount + + # BSD has no fusermount3 at all; vfs.usermount decides there. + _is_linux || return 0 + _require_fuse_device + [ "$FUSE_UID" != 0 ] || return 0 + fusermount=$(command -v fusermount3) || + _notrun "no fusermount3 on \$PATH" + [ -u "$fusermount" ] || + _notrun "$fusermount is not setuid, and we are not root" +} + +# _require_binary +# _notrun unless the file exists and is executable. +_require_binary() +{ + [ -x "$BUILD_DIR/$1" ] || _notrun "$1 not built" +} + +# _require_prog +# _notrun unless `command -v ` succeeds. +_require_prog() +{ + command -v "$1" >/dev/null 2>&1 || _notrun "$1 not installed" +} + +# _require_not_32bit_on_64 +# _notrun for a 32-bit ELF binary on an x86_64 host. +_require_not_32bit_on_64() +{ + local class + + class=$(_check fuse_test_elf_class "$1") + if [ "$class" = 32 ] && [ "$(uname -m)" = x86_64 ]; then + _notrun "$1 is a 32-bit binary on a 64-bit host" + fi +} + +# _require_reachable_without_caps +# _notrun unless the fs binary and every directory above it are executable by +# plain permission bits. mount.fuse3 -o drop_privileges execs the fs after +# dropping CAP_DAC_OVERRIDE, so a mode-0700 home makes it unreachable. +_require_reachable_without_caps() +{ + python3 "$TEST_LIB/checks.py" fuse_test_reachable_without_caps \ + "$(_fuse_fs_binary "$1")" || + _notrun "$1 is not reachable without CAP_DAC_OVERRIDE" +} + +# _require_fs_marker_absent [fs-index] +# _notrun if the mounted daemon's log matched . Used for runtime +# "not supported by kernel" markers. +# +# The daemon may not have written the marker yet when the test looks; the +# examples emit it during the first update interval, so poll briefly. +_require_fs_marker_absent() +{ + local regex=$1 idx=${2:-0} deadline=$((SECONDS + 3)) + + while [ $SECONDS -lt $deadline ]; do + _skip_if_fs_marker "$regex" "$idx" + sleep 0.2 + done +} + +# _skip_if_fs_marker [fs-index] +# _notrun if the daemon's log already matches , without waiting. For a +# marker whose arrival the caller has already waited for, polling again would +# only add delay. +_skip_if_fs_marker() +{ + local regex=$1 idx=${2:-0} + + if grep -qE -- "$regex" "$(fuse_fs_log "$idx")"; then + _notrun "filesystem reported: $regex" + fi +} + +# _wait_fs_marker [fs-index] +# _fail unless the daemon's log comes to match . For progress a daemon +# reports itself: waiting for the line rather than for a duration lets a slow +# machine take longer instead of failing, and lets a fast one stop early. +_wait_fs_marker() +{ + local regex=$1 idx=${2:-0} deadline=$((SECONDS + 10)) + + while [ $SECONDS -lt $deadline ]; do + if grep -qE -- "$regex" "$(fuse_fs_log "$idx")"; then + return 0 + fi + sleep 0.1 + done + _fail "filesystem did not report: $regex" +} + +# ------------------------------------------------------------------- assertions + +# _assert_eq [msg] +_assert_eq() +{ + [ "$1" = "$2" ] || _fail "${3:-assertion failed}: got '$1', want '$2'" +} + +# _assert_ne [msg] +_assert_ne() +{ + [ "$1" != "$2" ] || _fail "${3:-assertion failed}: both are '$1'" +} + +# _assert_file_eq +_assert_file_eq() +{ + cmp -s "$1" "$2" || _fail "$1 and $2 differ" +} + +# _wait_for +# Poll until cmd succeeds. Returns non-zero when the deadline passes, so the +# caller decides whether that is a failure or a skip. +_wait_for() +{ + # Separate statements: one `local` expands all its words before it + # assigns any of them, so a shared one computes the deadline from an + # unset secs and the loop ends before it has tried once. + local secs=$1 cmd=$2 + local deadline=$((SECONDS + secs)) + + while [ $SECONDS -lt $deadline ]; do + if eval "$cmd"; then + return 0 + fi + sleep 0.1 + done + return 1 +} + +# Everything shell cannot express exactly goes to checks.py: an exact errno, an +# fd held across an operation, nanosecond timestamps, directory listings, +# /proc/self/mountinfo fields. Add a subcommand there rather than approximating +# a check here. + +# _check [args...] +# Run the Python check helper. Its stdout/stderr already carry the diagnostic, +# so just propagate the failure. +_check() +{ + # Silent on success otherwise, so a step that hangs is invisible. + # stderr: callers capture stdout for the check's value. + echo "check: $*" >&2 + python3 "$TEST_LIB/checks.py" "$@" || _fail "checks.py $1 failed" +} + +# _assert_errno [args...] +# op is a checks.py operation name (fuse_test_open_rw, fuse_test_open_ro, +# fuse_test_stat). The errno is compared against OSError.errno, never against +# locale-dependent strerror text. +_assert_errno() +{ + _check fuse_test_expect_errno "$@" +} + +_assert_listdir() { _check fuse_test_assert_listdir "$@"; } +_assert_xattr_roundtrip() { _check fuse_test_xattr "$@"; } # set/get/remove + +# mountinfo_field +# Echo one field of the /proc/self/mountinfo line for . Empty output means +# the mountpoint is not mounted. +mountinfo_field() +{ + _check fuse_test_mountinfo "$1" "$2" +} + +_assert_mount_opt() { _check fuse_test_assert_mount_opt "$@"; } +_refute_mount_opt() { _check fuse_test_refute_mount_opt "$@"; } +_assert_super_opt() { _check fuse_test_assert_super_opt "$@"; } +_assert_super_opt_prefix() { _check fuse_test_assert_super_opt_prefix "$@"; } +_assert_fstype() { _check fuse_test_assert_fstype "$@"; } +_assert_source() { _check fuse_test_assert_source "$@"; } + +# ---------------------------------------------------------------- cleanup hooks + +_FUSE_ATEXIT=() + +# _at_exit +# Register a command to run when the script exits, newest first. Registering is +# what installs the EXIT trap, so a test never has to write its own and cannot +# clobber the mount unwinding. +_at_exit() +{ + _FUSE_ATEXIT+=("$*") + trap _fuse_cleanup_all EXIT +} + +# _fuse_cleanup_all +# Unwind every mount still registered, newest first, then run the _at_exit +# commands. Every step swallows its own failure: under errexit one stuck umount +# would otherwise end the trap and leak the remaining mounts, which is exactly +# what the trap exists to prevent. +_fuse_cleanup_all() +{ + local idx + + for ((idx = _fuse_fs_count - 1; idx >= 0; idx--)); do + fuse_umount_lazy "$idx" || true + done + for ((idx = ${#_FUSE_ATEXIT[@]} - 1; idx >= 0; idx--)); do + eval "${_FUSE_ATEXIT[$idx]}" || true + done +} + +# ------------------------------------------------------------------ mount verbs + +_fuse_fs_count=0 +FUSE_FS_PID=() +FUSE_FS_LOG=() +FUSE_FS_MNT=() +FUSE_FS_NAME=() + +# _fuse_fs_binary +# Echo the build-tree path of : $FUSE_TEST_BIN_DIR when the name is +# prefixed "test/", $FUSE_EXAMPLE_DIR otherwise. +_fuse_fs_binary() +{ + case $1 in + test/*) echo "$FUSE_TEST_BIN_DIR/${1#test/}" ;; + *) echo "$FUSE_EXAMPLE_DIR/$1" ;; + esac +} + +# fuse_mount_at [args...] +# Launch the filesystem in the foreground on with its stdout+stderr +# redirected to logs/fs--.out. Waits for the mountpoint to +# appear. Registers an EXIT trap that lazily unmounts and kills the daemon, so +# an aborting test never leaks a mount. +# Sets FUSE_FS_PID[idx] and FUSE_FS_LOG[idx]; echoes idx. +fuse_mount_at() +{ + local mnt=$1 fs=$2; shift 2 + local idx=$_fuse_fs_count + local bin log + + bin=$(_fuse_fs_binary "$fs") + [ -x "$bin" ] || _notrun "$fs not built" + + # Each daemon gets its own log so a multi-mount test stays readable. + log=$TEST_LOGDIR/fs-$idx-$(basename "$fs").out + "$bin" "$@" "$mnt" >"$log" 2>&1 & + FUSE_FS_PID[$idx]=$! + FUSE_FS_LOG[$idx]=$log + FUSE_FS_MNT[$idx]=$mnt + FUSE_FS_NAME[$idx]=$fs + _fuse_fs_count=$((idx + 1)) + + trap _fuse_cleanup_all EXIT + fuse_wait_mount "$mnt" "${FUSE_WAIT_PREDICATE:-}" || { + _fuse_dump_fs_log $idx + _fail "$fs did not mount on $mnt" + } + echo $idx +} + +# fuse_mount [args...] +# fuse_mount_at on $TEST_MNT, with -f so the daemon stays in the foreground. +fuse_mount() +{ + local fs=$1; shift + + fuse_mount_at "$TEST_MNT" "$fs" -f "$@" +} + +# fuse_mount_helper [args...] +# Mount via $FUSE_UTIL_DIR/mount.fuse3 instead of exec'ing the fs directly. +# +# mount.fuse3 execs the filesystem type through /bin/sh, so an example resolves +# via $PATH but a binary in test/ has to be named by its absolute path. +fuse_mount_helper() +{ + local fs=$1; shift + local idx=$_fuse_fs_count + local spec=$fs log + + case $fs in + test/*) spec=$(_fuse_fs_binary "$fs") ;; + esac + + log=$TEST_LOGDIR/fs-$idx-$(basename "$fs").out + "$FUSE_UTIL_DIR/mount.fuse3" "$spec" "$TEST_MNT" "$@" >"$log" 2>&1 & + FUSE_FS_PID[$idx]=$! + FUSE_FS_LOG[$idx]=$log + FUSE_FS_MNT[$idx]=$TEST_MNT + FUSE_FS_NAME[$idx]=$fs + _fuse_fs_count=$((idx + 1)) + + trap _fuse_cleanup_all EXIT + fuse_wait_mount "$TEST_MNT" || { + _fuse_dump_fs_log $idx + _fail "mount.fuse3 $fs did not mount on $TEST_MNT" + } + echo $idx +} + +# fuse_wait_mount [predicate] +# Poll until `checks.py fuse_test_ismount ` succeeds, or until the +# caller-supplied predicate (a shell command taking ) succeeds. Fails if +# the daemon exits first. +fuse_wait_mount() +{ + local path=$1 predicate=${2:-} + local idx=$((_fuse_fs_count - 1)) + local deadline=$((SECONDS + 30)) + local test_cmd + + if [ -n "$predicate" ]; then + test_cmd="$predicate '$path'" + else + test_cmd="python3 '$TEST_LIB/checks.py' fuse_test_ismount '$path'" + fi + + while [ $SECONDS -lt $deadline ]; do + if eval "$test_cmd" >/dev/null 2>&1; then + return 0 + fi + if ! kill -0 "${FUSE_FS_PID[$idx]}" 2>/dev/null; then + # The daemon may have exited *after* completing the + # mount (mount.fuse3 does), so look once more. + eval "$test_cmd" >/dev/null 2>&1 && return 0 + return 1 + fi + sleep 0.1 + done + return 1 +} + +# fuse_umount [idx] +# fusermount3 -u on Linux, umount(8) on BSD (no util/ is built there), then +# wait for the daemon; _fail unless it exits 0. Writes +# logs/fs--.status. +fuse_umount() +{ + local idx=${1:-0} + local mnt=${FUSE_FS_MNT[$idx]} pid=${FUSE_FS_PID[$idx]} + local rc + + if _is_linux; then + "$FUSE_UTIL_DIR/fusermount3" -u "$mnt" || + _fail "fusermount3 -u $mnt failed" + else + umount "$mnt" || _fail "umount $mnt failed" + fi + # The daemon exits on its own once the kernel drops the connection; a + # daemon that does not is a bug the test must report, not paper over. + _wait_for 30 "! kill -0 $pid 2>/dev/null" || + _fail "${FUSE_FS_NAME[$idx]} still running 30s after umount" + # One command, not `wait; rc=$?`: under errexit a non-zero wait would + # abort the script before its status could be reported. + rc=0 + wait "$pid" || rc=$? + # Reaped: the number is free for any process to be assigned next, so + # nothing may signal it again. + FUSE_FS_PID[$idx]= + echo "$rc" >"$TEST_LOGDIR/fs-$idx-$(basename "${FUSE_FS_NAME[$idx]}").status" + [ $rc -eq 0 ] || { + _fuse_dump_fs_log $idx + _fail "${FUSE_FS_NAME[$idx]} exited with $rc" + } +} + +# fuse_umount_lazy [idx] +# fusermount3 -z -u (Linux) or umount -f (BSD), + SIGTERM + SIGKILL, ignoring +# errors. The trap path. +fuse_umount_lazy() +{ + local idx=${1:-0} + local mnt=${FUSE_FS_MNT[$idx]} pid=${FUSE_FS_PID[$idx]} + + # The trap runs over every index, fuse_umount'ed ones included. + [ -n "$pid" ] || return 0 + if _is_linux; then + "$FUSE_UTIL_DIR/fusermount3" -z -u "$mnt" >/dev/null 2>&1 || true + else + umount -f "$mnt" >/dev/null 2>&1 || true + fi + kill "$pid" 2>/dev/null || true + _wait_for 1 "! kill -0 $pid 2>/dev/null" || kill -9 "$pid" 2>/dev/null || true + wait "$pid" 2>/dev/null || true + FUSE_FS_PID[$idx]= +} + +fuse_fs_pid() +{ + echo "${FUSE_FS_PID[${1:-0}]}" +} + +fuse_fs_log() +{ + echo "${FUSE_FS_LOG[${1:-0}]}" +} + +# _fuse_dump_fs_log +# Copy that daemon's log to stderr before failing. The log file survives in +# logs/, but a failure line with the daemon's own last words beside it is what +# makes a CI log readable without downloading anything. +_fuse_dump_fs_log() +{ + local idx=${1:-0} + + echo "--- ${FUSE_FS_NAME[$idx]} log (${FUSE_FS_LOG[$idx]}) ---" >&2 + cat "${FUSE_FS_LOG[$idx]}" >&2 || true + echo "--- end log ---" >&2 +} + +# --------------------------------------------------------------- output scanning + +# fuse_allow_output +# Append a false-positive pattern to logs/.allow, one per line. The runner +# strips every match before scanning for suspicious words. +fuse_allow_output() +{ + printf '%s\n' "$1" >>"$TEST_LOGDIR/.allow" +} + +# fuse_allow_core +# Record that a core from is expected in this test. +fuse_allow_core() +{ + printf '%s\n' "$1" >>"$TEST_LOGDIR/.allow-core" +} + +# ----------------------------------------------------------------- the fuse gate +# Nearly every case reaches /dev/fuse, through the mount verbs or through a +# binary that mounts itself, so this is applied on source rather than left to +# each case to remember. The few that never mount say so instead. +[ -n "${_fuse_no_mount_needed:-}" ] || _require_fuse diff --git a/test/cases/lib/hello.sh b/test/cases/lib/hello.sh new file mode 100644 index 0000000..64362ea --- /dev/null +++ b/test/cases/lib/hello.sh @@ -0,0 +1,63 @@ +# lib/hello.sh - body for the hello / hello_ll / test/hello cases. +# +# Caller sets FS_NAME, FS_OPTS and LAUNCH before sourcing: +# FS_NAME hello | hello_ll | test/hello +# FS_OPTS -o option list, empty for none +# LAUNCH direct | mount_fuse | mount_fuse_dropcaps +# +# The platform gates live here rather than in the 18 callers: three of them +# apply to whole columns of the matrix, and a caller that forgot one would +# fail on BSD instead of skipping. + +. "$TEST_LIB/common.sh" + +# clone_fd needs the /dev/fuse clone device, which is Linux-only. +case $FS_OPTS in +*clone_fd*) _require_linux "the clone_fd mount option" ;; +esac + +# hello_ll supports single-threading only. +FS_EXTRA= +[ "$FS_NAME" != hello_ll ] || FS_EXTRA=-s + +case $LAUNCH in +direct) + fuse_mount "$FS_NAME" $FS_EXTRA ${FS_OPTS:+-o "$FS_OPTS"} >/dev/null + ;; +mount_fuse) + # util/ is not built on BSD, so there is no mount.fuse3 to invoke. + _require_linux "mount.fuse3" + fuse_mount_helper "$FS_NAME" ${FS_OPTS:+-o "$FS_OPTS"} >/dev/null + ;; +mount_fuse_dropcaps) + _require_linux "mount.fuse3" + _require_root + # mount.fuse3 execs the fs only after dropping all capabilities, so + # every directory on the way to the binary must be traversable by + # plain permission bits - a mode-0700 home is not. + _require_reachable_without_caps "$FS_NAME" + fuse_mount_helper "$FS_NAME" \ + -o "${FS_OPTS:+$FS_OPTS,}drop_privileges" >/dev/null + ;; +*) + _fail "unknown LAUNCH '$LAUNCH'" + ;; +esac + +_assert_listdir "$TEST_MNT" hello +# Compared against a file rather than against "$(cat ...)": command +# substitution eats the trailing newline, so the shell form would not notice +# if the example stopped writing one. +printf 'Hello World!\n' >"$TEST_TMP/expected" +_assert_file_eq "$TEST_MNT/hello" "$TEST_TMP/expected" +_assert_errno EACCES fuse_test_open_rw "$TEST_MNT/hello" +_assert_errno ENOENT fuse_test_open_rw "$TEST_MNT/hello-does-not-exist" + +# os.setxattr/getxattr/removexattr, which tst_xattr() used, exist only on Linux +# in CPython; FreeBSD's equivalent is extattr(2), a different API. +if [ "$FS_NAME" = hello_ll ] && _is_linux; then + _assert_xattr_roundtrip "$TEST_MNT" + _assert_xattr_roundtrip "$TEST_MNT/hello" +fi + +fuse_umount diff --git a/test/cases/lib/mountinfo.sh b/test/cases/lib/mountinfo.sh new file mode 100644 index 0000000..1e8f86a --- /dev/null +++ b/test/cases/lib/mountinfo.sh @@ -0,0 +1,31 @@ +# lib/mountinfo.sh - body for the /proc/self/mountinfo cases. +# +# Caller sets FS_NAME (hello or hello_ll), optionally MOUNT_OPTS, and defines +# mountinfo_assert(). It may also define mountinfo_setup(), which runs before +# the mount and may extend MOUNT_OPTS. +# +# These tests assert what the kernel recorded for a mount, which is what +# differs between the legacy mount(2) path and the fsopen/fsconfig/fsmount +# path: an option dropped on one of them shows up here and nowhere else. + +. "$TEST_LIB/common.sh" + +# Every assertion below is about what /proc/self/mountinfo reports, and +# getmntinfo(3) exposes neither the subtype nor the mount/super option split +# these tests exist to check. +_require_linux "/proc/self/mountinfo" + +# hello_ll supports single-threading only. +FS_EXTRA= +[ "$FS_NAME" != hello_ll ] || FS_EXTRA=-s + +if declare -F mountinfo_setup >/dev/null; then + mountinfo_setup +fi + +fuse_mount "$FS_NAME" $FS_EXTRA ${MOUNT_OPTS:+-o "$MOUNT_OPTS"} >/dev/null + +# While it is still mounted: the mountinfo line disappears with the mount. +mountinfo_assert + +fuse_umount diff --git a/test/cases/lib/notify.sh b/test/cases/lib/notify.sh new file mode 100644 index 0000000..30fc3d0 --- /dev/null +++ b/test/cases/lib/notify.sh @@ -0,0 +1,112 @@ +# lib/notify.sh - body for the notification / invalidation cases. +# +# Caller sets before sourcing: +# FS_NAME example to mount +# NOTIFY 1 to let the filesystem notify the kernel, 0 for --no-notify +# NOTIFY_MODE content | size | entry | prune +# FS_ARGS extra arguments, optional +# MARKER_ABSENT regex the daemon prints when the kernel refuses, optional +# +# Every mode has the same shape: mount, look at the filesystem, wait for the +# filesystem to have moved on, look again. With notification on the kernel must +# have seen the change; with it off the kernel's cache must still be serving +# what it saw first. + +. "$TEST_LIB/common.sh" + +notify_args="--update-interval=1" +[ "$NOTIFY" = 1 ] || notify_args="$notify_args --no-notify" + +# What notify_prune prints once it has issued a prune the kernel accepted, and +# once one has taken effect and the contents are new. The prune mode waits for +# these rather than for a duration. +prune_sent="prune sent" +prune_done="prune complete" + +# $FAIL_CALLBACK, for the marker the daemon may still be about to write. +_notify_recheck_marker() +{ + _require_fs_marker_absent "$MARKER_ABSENT" +} + +fuse_mount "$FS_NAME" $notify_args ${FS_ARGS:-} >/dev/null + +if [ -n "${MARKER_ABSENT:-}" ]; then + # Not gated here: nothing has been looked up yet, so no example can + # have tried to notify, and none of them reports on kernel support + # before it has. The prune mode gates on the report itself below; for + # the rest $FAIL_CALLBACK is what catches it. + FAIL_CALLBACK=_notify_recheck_marker +fi + +case $NOTIFY_MODE in +content) + # The file holds a timestamp the filesystem rewrites; notification is + # what makes the kernel re-read it. + read1=$(cat "$TEST_MNT/current_time") + sleep 2 + read2=$(cat "$TEST_MNT/current_time") + if [ "$NOTIFY" = 1 ]; then + _assert_ne "$read1" "$read2" "content did not change" + else + _assert_eq "$read1" "$read2" "cached content changed" + fi + ;; +size) + size=$(_check fuse_test_size "$TEST_MNT/growing") + sleep 2 + new_size=$(_check fuse_test_size "$TEST_MNT/growing") + if [ "$NOTIFY" = 1 ]; then + [ "$new_size" -gt "$size" ] || + _fail "growing file stayed at $size bytes" + else + _assert_eq "$new_size" "$size" "cached size changed" + fi + ;; +entry) + fname=$(_check fuse_test_listdir_first "$TEST_MNT") + if [ ! -e "$TEST_MNT/$fname" ]; then + # We hit a race and issued readdir just before the name changed. + fname=$(_check fuse_test_listdir_first "$TEST_MNT") + [ -e "$TEST_MNT/$fname" ] || + _fail "$fname vanished twice in a row" + fi + + sleep 2 + if [ "$NOTIFY" != 1 ]; then + # Without notification the entry stays visible until the + # kernel's own --timeout expires. + [ -e "$TEST_MNT/$fname" ] || + _fail "$fname expired before the entry timeout" + sleep 5 + fi + _assert_errno ENOENT fuse_test_stat "$TEST_MNT/$fname" + ;; +prune) + fname=$(_check fuse_test_listdir_first "$TEST_MNT") + content=$(cat "$TEST_MNT/$fname") + if [ "$NOTIFY" = 1 ]; then + # The read above is this file's first lookup, which is what lets + # the daemon prune at all. From here it reports exactly one of + # the two, so the kernel's answer is decided rather than guessed. + _wait_fs_marker "$prune_sent|$MARKER_ABSENT" + _skip_if_fs_marker "$MARKER_ABSENT" + # A prune did go out, so nothing below can be blamed on the + # kernel not having the notification at all. + _wait_fs_marker "$prune_done" + new_content=$(cat "$TEST_MNT/$fname") + _assert_ne "$content" "$new_content" "content did not change" + else + # Nothing is pruned, so there is no progress to wait for; the + # point is that the cache keeps serving the first read. + sleep 2 + new_content=$(cat "$TEST_MNT/$fname") + _assert_eq "$content" "$new_content" "cached content changed" + fi + ;; +*) + _fail "unknown NOTIFY_MODE '$NOTIFY_MODE'" + ;; +esac + +fuse_umount diff --git a/test/cases/lib/passthrough.sh b/test/cases/lib/passthrough.sh new file mode 100644 index 0000000..6455b44 --- /dev/null +++ b/test/cases/lib/passthrough.sh @@ -0,0 +1,82 @@ +# lib/passthrough.sh - body for the passthrough* cases. +# +# Caller sets before sourcing: +# FS_NAME example to mount +# FS_ARGS its arguments, without the mountpoint +# INODE_CHECK exact | nonzero +# PT_MIRROR 1 when the example mirrors the whole tree, so the work +# directory is $TEST_MNT$TEST_SRC rather than $TEST_MNT +# PT_SRC_VISIBLE 1 when a change made directly in $TEST_SRC is expected to +# show up under the mount straight away +# PT_POSITIONAL extra trailing arguments before the mountpoint, optional +# SYSCALL_ARGS extra test_syscalls arguments, optional +# +# PT_SRC_VISIBLE is one flag covering three decisions the Python made +# separately, because they are the same question: with attribute caching on, +# passthrough_hp's mount must not be compared against a source directory it is +# allowed to have a stale view of. + +. "$TEST_LIB/common.sh" + +fuse_mount_at "$TEST_MNT" "$FS_NAME" $FS_ARGS ${PT_POSITIONAL:-} >/dev/null + +if [ "$PT_MIRROR" = 1 ]; then + work_dir=$TEST_MNT$TEST_SRC +else + work_dir=$TEST_MNT +fi + +# test_syscalls prints "No error" under FreeBSD. +fuse_allow_output "^ [0-9][0-9] \[[^]]+ message: 'No error: 0'\]" + +_check fuse_test_statvfs "$work_dir" +_check fuse_test_readdir "$TEST_SRC" "$work_dir" --inode-check "$INODE_CHECK" +_check fuse_test_readdir_big "$TEST_SRC" "$work_dir" --inode-check "$INODE_CHECK" +_check fuse_test_open_read "$TEST_SRC" "$work_dir" +_check fuse_test_open_write "$TEST_SRC" "$work_dir" +_check fuse_test_create "$work_dir" +[ "$PT_SRC_VISIBLE" != 1 ] || + _check fuse_test_passthrough "$TEST_SRC" "$work_dir" --inode-check "$INODE_CHECK" +_check fuse_test_append "$TEST_SRC" "$work_dir" +_check fuse_test_seek "$TEST_SRC" "$work_dir" +_check fuse_test_mkdir "$work_dir" +if [ "$PT_SRC_VISIBLE" = 1 ]; then + _check fuse_test_rmdir "$work_dir" --src "$TEST_SRC" + _check fuse_test_unlink "$work_dir" --src "$TEST_SRC" +else + # With the cache on, no operation may go through $TEST_SRC: the + # cache would become stale. + _check fuse_test_rmdir "$work_dir" + _check fuse_test_unlink "$work_dir" +fi +_check fuse_test_symlink "$work_dir" +[ "$FUSE_UID" -ne 0 ] || _check fuse_test_chown "$work_dir" +# The underlying fs may not have full nanosecond resolution. +_check fuse_test_utimens "$work_dir" --ns-tol 1000 +[ "$INODE_CHECK" != exact ] || _check fuse_test_link "$work_dir" +_check fuse_test_truncate_path "$work_dir" +_check fuse_test_truncate_fd "$work_dir" +_check fuse_test_open_unlink "$work_dir" + +# test_syscalls assumes a change in the source directory is reflected in the +# mountpoint immediately, so it runs only where that holds. It is an extra +# layer on top of the checks above, not a substitute for them. +if [ "$PT_SRC_VISIBLE" = 1 ]; then + # Its socket test binds under the work directory, and sun_path holds + # 108 bytes -- $TEST_MNT$TEST_SRC alone is longer than that. Hand it a + # short symlink instead; a symlinked prefix resolves the same for every + # syscall it makes. /tmp literally, since shortness is the point. + # + # A bare link rather than a directory to put one in: _at_exit does not + # run when the runner's timeout kills the test, and a dangling link is + # the smallest thing to leak outside the work directory. ln without -f, + # so a name mktemp -u handed out twice fails here instead of silently + # stealing another run's link. + short_link=$(mktemp -u /tmp/fuse-pt.XXXXXX) + _at_exit "rm -f '$short_link'" + ln -s "$work_dir" "$short_link" + "$FUSE_TEST_BIN_DIR/test_syscalls" "$short_link" ":$TEST_SRC" \ + ${SYSCALL_ARGS:-} || _fail "test_syscalls failed" +fi + +fuse_umount diff --git a/test/cases/misc/hello-uds.sh b/test/cases/misc/hello-uds.sh new file mode 100755 index 0000000..95f3715 --- /dev/null +++ b/test/cases/misc/hello-uds.sh @@ -0,0 +1,26 @@ +#!/usr/bin/env bash +# GROUP: misc + +# The protocol goes over a socket here, not /dev/fuse. +_fuse_no_mount_needed=1 +. "$TEST_LIB/common.sh" + +_require_binary example/hello_ll_uds + +# Relative, hence short: sun_path holds 108 bytes, which the run directory +# alone can exhaust. The daemon inherits this script's cwd ($TEST_LOGDIR) and +# never chdirs, and checks.py runs in it too, so a bare name is the same +# socket from both ends. A name of its own per test also keeps a second run, +# or the root pass beside the non-root one, out of the way. +sockpath=hello_ll.sock + +# It never calls fuse_mount, so none of the mount verbs apply: it serves the +# FUSE protocol over an AF_UNIX socket through fuse_session_custom_io(). +"$FUSE_EXAMPLE_DIR/hello_ll_uds" --socket="$sockpath" \ + >"$TEST_LOGDIR/fs-0-hello_ll_uds.out" 2>&1 & +uds_pid=$! +_at_exit "kill $uds_pid 2>/dev/null" + +_wait_for 10 "[ -S '$sockpath' ]" || _fail "$sockpath never appeared" + +_check fuse_test_uds_init "$sockpath" diff --git a/test/cases/misc/printcap.sh b/test/cases/misc/printcap.sh new file mode 100755 index 0000000..6bec9c6 --- /dev/null +++ b/test/cases/misc/printcap.sh @@ -0,0 +1,11 @@ +#!/usr/bin/env bash +# GROUP: misc quick + +. "$TEST_LIB/common.sh" + +_require_binary example/printcap + +# The runner already ran printcap once to build $FUSE_CAPS; this is the case +# that fails rather than silently reporting no capabilities when it breaks. +"$FUSE_EXAMPLE_DIR/printcap" +[ -n "$FUSE_CAPS" ] || _fail "printcap reported no capabilities" diff --git a/test/cases/mount/attrs-default-hello-ll.sh b/test/cases/mount/attrs-default-hello-ll.sh new file mode 100755 index 0000000..fd75965 --- /dev/null +++ b/test/cases/mount/attrs-default-hello-ll.sh @@ -0,0 +1,12 @@ +#!/usr/bin/env bash +# GROUP: mount + +FS_NAME=hello_ll + +mountinfo_assert() +{ + _assert_mount_opt "$TEST_MNT" rw nosuid nodev + _refute_mount_opt "$TEST_MNT" ro +} + +. "$TEST_LIB/mountinfo.sh" diff --git a/test/cases/mount/attrs-default-hello.sh b/test/cases/mount/attrs-default-hello.sh new file mode 100755 index 0000000..72c0b4f --- /dev/null +++ b/test/cases/mount/attrs-default-hello.sh @@ -0,0 +1,12 @@ +#!/usr/bin/env bash +# GROUP: mount + +FS_NAME=hello + +mountinfo_assert() +{ + _assert_mount_opt "$TEST_MNT" rw nosuid nodev + _refute_mount_opt "$TEST_MNT" ro +} + +. "$TEST_LIB/mountinfo.sh" diff --git a/test/cases/mount/attrs-nodev-hello-ll.sh b/test/cases/mount/attrs-nodev-hello-ll.sh new file mode 100755 index 0000000..73d5a0a --- /dev/null +++ b/test/cases/mount/attrs-nodev-hello-ll.sh @@ -0,0 +1,12 @@ +#!/usr/bin/env bash +# GROUP: mount + +FS_NAME=hello_ll +MOUNT_OPTS=nodev + +mountinfo_assert() +{ + _assert_mount_opt "$TEST_MNT" nodev +} + +. "$TEST_LIB/mountinfo.sh" diff --git a/test/cases/mount/attrs-nodev-hello.sh b/test/cases/mount/attrs-nodev-hello.sh new file mode 100755 index 0000000..1b13cbd --- /dev/null +++ b/test/cases/mount/attrs-nodev-hello.sh @@ -0,0 +1,12 @@ +#!/usr/bin/env bash +# GROUP: mount + +FS_NAME=hello +MOUNT_OPTS=nodev + +mountinfo_assert() +{ + _assert_mount_opt "$TEST_MNT" nodev +} + +. "$TEST_LIB/mountinfo.sh" diff --git a/test/cases/mount/attrs-nosuid-hello-ll.sh b/test/cases/mount/attrs-nosuid-hello-ll.sh new file mode 100755 index 0000000..294a220 --- /dev/null +++ b/test/cases/mount/attrs-nosuid-hello-ll.sh @@ -0,0 +1,12 @@ +#!/usr/bin/env bash +# GROUP: mount + +FS_NAME=hello_ll +MOUNT_OPTS=nosuid + +mountinfo_assert() +{ + _assert_mount_opt "$TEST_MNT" nosuid +} + +. "$TEST_LIB/mountinfo.sh" diff --git a/test/cases/mount/attrs-nosuid-hello.sh b/test/cases/mount/attrs-nosuid-hello.sh new file mode 100755 index 0000000..61cabf8 --- /dev/null +++ b/test/cases/mount/attrs-nosuid-hello.sh @@ -0,0 +1,12 @@ +#!/usr/bin/env bash +# GROUP: mount + +FS_NAME=hello +MOUNT_OPTS=nosuid + +mountinfo_assert() +{ + _assert_mount_opt "$TEST_MNT" nosuid +} + +. "$TEST_LIB/mountinfo.sh" diff --git a/test/cases/mount/attrs-ro-hello-ll.sh b/test/cases/mount/attrs-ro-hello-ll.sh new file mode 100755 index 0000000..1b59097 --- /dev/null +++ b/test/cases/mount/attrs-ro-hello-ll.sh @@ -0,0 +1,14 @@ +#!/usr/bin/env bash +# GROUP: mount + +FS_NAME=hello_ll +MOUNT_OPTS=ro + +mountinfo_assert() +{ + _assert_mount_opt "$TEST_MNT" ro + _refute_mount_opt "$TEST_MNT" rw + _assert_super_opt "$TEST_MNT" ro +} + +. "$TEST_LIB/mountinfo.sh" diff --git a/test/cases/mount/attrs-ro-hello.sh b/test/cases/mount/attrs-ro-hello.sh new file mode 100755 index 0000000..4990e60 --- /dev/null +++ b/test/cases/mount/attrs-ro-hello.sh @@ -0,0 +1,14 @@ +#!/usr/bin/env bash +# GROUP: mount + +FS_NAME=hello +MOUNT_OPTS=ro + +mountinfo_assert() +{ + _assert_mount_opt "$TEST_MNT" ro + _refute_mount_opt "$TEST_MNT" rw + _assert_super_opt "$TEST_MNT" ro +} + +. "$TEST_LIB/mountinfo.sh" diff --git a/test/cases/mount/attrs-root-dev-hello-ll.sh b/test/cases/mount/attrs-root-dev-hello-ll.sh new file mode 100755 index 0000000..48afdb6 --- /dev/null +++ b/test/cases/mount/attrs-root-dev-hello-ll.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash +# GROUP: mount + +. "$TEST_LIB/common.sh" + +_require_root + +FS_NAME=hello_ll +MOUNT_OPTS=dev + +mountinfo_assert() +{ + _refute_mount_opt "$TEST_MNT" nodev +} + +. "$TEST_LIB/mountinfo.sh" diff --git a/test/cases/mount/attrs-root-dev-hello.sh b/test/cases/mount/attrs-root-dev-hello.sh new file mode 100755 index 0000000..e61deae --- /dev/null +++ b/test/cases/mount/attrs-root-dev-hello.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash +# GROUP: mount + +. "$TEST_LIB/common.sh" + +_require_root + +FS_NAME=hello +MOUNT_OPTS=dev + +mountinfo_assert() +{ + _refute_mount_opt "$TEST_MNT" nodev +} + +. "$TEST_LIB/mountinfo.sh" diff --git a/test/cases/mount/attrs-root-suid-hello-ll.sh b/test/cases/mount/attrs-root-suid-hello-ll.sh new file mode 100755 index 0000000..cc62828 --- /dev/null +++ b/test/cases/mount/attrs-root-suid-hello-ll.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash +# GROUP: mount + +. "$TEST_LIB/common.sh" + +_require_root + +FS_NAME=hello_ll +MOUNT_OPTS=suid + +mountinfo_assert() +{ + _refute_mount_opt "$TEST_MNT" nosuid +} + +. "$TEST_LIB/mountinfo.sh" diff --git a/test/cases/mount/attrs-root-suid-hello.sh b/test/cases/mount/attrs-root-suid-hello.sh new file mode 100755 index 0000000..3cec3d7 --- /dev/null +++ b/test/cases/mount/attrs-root-suid-hello.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash +# GROUP: mount + +. "$TEST_LIB/common.sh" + +_require_root + +FS_NAME=hello +MOUNT_OPTS=suid + +mountinfo_assert() +{ + _refute_mount_opt "$TEST_MNT" nosuid +} + +. "$TEST_LIB/mountinfo.sh" diff --git a/test/cases/mount/blkdev-fsname-hello-ll.sh b/test/cases/mount/blkdev-fsname-hello-ll.sh new file mode 100755 index 0000000..d21b943 --- /dev/null +++ b/test/cases/mount/blkdev-fsname-hello-ll.sh @@ -0,0 +1,28 @@ +#!/usr/bin/env bash +# GROUP: mount serial + +. "$TEST_LIB/common.sh" + +_require_root +_require_prog losetup + +FS_NAME=hello_ll + +mountinfo_setup() +{ + loop_file=$TEST_TMP/loop.img + dd if=/dev/zero of="$loop_file" bs=1M count=1 status=none + loop_dev=$(losetup -f --show "$loop_file") + _at_exit "losetup -d '$loop_dev'" + MOUNT_OPTS="blkdev,fsname=$loop_dev,subtype=mysub" +} + +mountinfo_assert() +{ + # fstype carries the subtype only where the kernel took it. + _assert_fstype "$TEST_MNT" fuseblk.mysub fuseblk + # The source must be the device name, not 'fuseblk'. + _assert_source "$TEST_MNT" "$loop_dev" +} + +. "$TEST_LIB/mountinfo.sh" diff --git a/test/cases/mount/blkdev-fsname-hello.sh b/test/cases/mount/blkdev-fsname-hello.sh new file mode 100755 index 0000000..d4fc047 --- /dev/null +++ b/test/cases/mount/blkdev-fsname-hello.sh @@ -0,0 +1,28 @@ +#!/usr/bin/env bash +# GROUP: mount serial + +. "$TEST_LIB/common.sh" + +_require_root +_require_prog losetup + +FS_NAME=hello + +mountinfo_setup() +{ + loop_file=$TEST_TMP/loop.img + dd if=/dev/zero of="$loop_file" bs=1M count=1 status=none + loop_dev=$(losetup -f --show "$loop_file") + _at_exit "losetup -d '$loop_dev'" + MOUNT_OPTS="blkdev,fsname=$loop_dev,subtype=mysub" +} + +mountinfo_assert() +{ + # fstype carries the subtype only where the kernel took it. + _assert_fstype "$TEST_MNT" fuseblk.mysub fuseblk + # The source must be the device name, not 'fuseblk'. + _assert_source "$TEST_MNT" "$loop_dev" +} + +. "$TEST_LIB/mountinfo.sh" diff --git a/test/cases/mount/mountinfo-baseline-hello-ll.sh b/test/cases/mount/mountinfo-baseline-hello-ll.sh new file mode 100755 index 0000000..79660e2 --- /dev/null +++ b/test/cases/mount/mountinfo-baseline-hello-ll.sh @@ -0,0 +1,12 @@ +#!/usr/bin/env bash +# GROUP: mount + +FS_NAME=hello_ll + +mountinfo_assert() +{ + _assert_fstype "$TEST_MNT" fuse.hello_ll fuse + _assert_super_opt_prefix "$TEST_MNT" user_id= group_id= +} + +. "$TEST_LIB/mountinfo.sh" diff --git a/test/cases/mount/mountinfo-baseline-hello.sh b/test/cases/mount/mountinfo-baseline-hello.sh new file mode 100755 index 0000000..9419f7b --- /dev/null +++ b/test/cases/mount/mountinfo-baseline-hello.sh @@ -0,0 +1,12 @@ +#!/usr/bin/env bash +# GROUP: mount + +FS_NAME=hello + +mountinfo_assert() +{ + _assert_fstype "$TEST_MNT" fuse.hello fuse + _assert_super_opt_prefix "$TEST_MNT" user_id= group_id= +} + +. "$TEST_LIB/mountinfo.sh" diff --git a/test/cases/mount/mountinfo-fsname-hello-ll.sh b/test/cases/mount/mountinfo-fsname-hello-ll.sh new file mode 100755 index 0000000..2701462 --- /dev/null +++ b/test/cases/mount/mountinfo-fsname-hello-ll.sh @@ -0,0 +1,12 @@ +#!/usr/bin/env bash +# GROUP: mount + +FS_NAME=hello_ll +MOUNT_OPTS=fsname=myfsname + +mountinfo_assert() +{ + _assert_source "$TEST_MNT" myfsname +} + +. "$TEST_LIB/mountinfo.sh" diff --git a/test/cases/mount/mountinfo-fsname-hello.sh b/test/cases/mount/mountinfo-fsname-hello.sh new file mode 100755 index 0000000..342bf49 --- /dev/null +++ b/test/cases/mount/mountinfo-fsname-hello.sh @@ -0,0 +1,12 @@ +#!/usr/bin/env bash +# GROUP: mount + +FS_NAME=hello +MOUNT_OPTS=fsname=myfsname + +mountinfo_assert() +{ + _assert_source "$TEST_MNT" myfsname +} + +. "$TEST_LIB/mountinfo.sh" diff --git a/test/cases/mount/mountinfo-subtype-fsname-hello-ll.sh b/test/cases/mount/mountinfo-subtype-fsname-hello-ll.sh new file mode 100755 index 0000000..359adb6 --- /dev/null +++ b/test/cases/mount/mountinfo-subtype-fsname-hello-ll.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env bash +# GROUP: mount + +FS_NAME=hello_ll +MOUNT_OPTS=subtype=mysub,fsname=myfsname + +mountinfo_assert() +{ + _assert_fstype "$TEST_MNT" fuse.mysub + _assert_source "$TEST_MNT" myfsname mysub#myfsname +} + +. "$TEST_LIB/mountinfo.sh" diff --git a/test/cases/mount/mountinfo-subtype-fsname-hello.sh b/test/cases/mount/mountinfo-subtype-fsname-hello.sh new file mode 100755 index 0000000..2e22b90 --- /dev/null +++ b/test/cases/mount/mountinfo-subtype-fsname-hello.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env bash +# GROUP: mount + +FS_NAME=hello +MOUNT_OPTS=subtype=mysub,fsname=myfsname + +mountinfo_assert() +{ + _assert_fstype "$TEST_MNT" fuse.mysub + _assert_source "$TEST_MNT" myfsname mysub#myfsname +} + +. "$TEST_LIB/mountinfo.sh" diff --git a/test/cases/mount/mountinfo-subtype-hello-ll.sh b/test/cases/mount/mountinfo-subtype-hello-ll.sh new file mode 100755 index 0000000..b7d8a2f --- /dev/null +++ b/test/cases/mount/mountinfo-subtype-hello-ll.sh @@ -0,0 +1,12 @@ +#!/usr/bin/env bash +# GROUP: mount + +FS_NAME=hello_ll +MOUNT_OPTS=subtype=mysub + +mountinfo_assert() +{ + _assert_fstype "$TEST_MNT" fuse.mysub +} + +. "$TEST_LIB/mountinfo.sh" diff --git a/test/cases/mount/mountinfo-subtype-hello.sh b/test/cases/mount/mountinfo-subtype-hello.sh new file mode 100755 index 0000000..5d9e7fd --- /dev/null +++ b/test/cases/mount/mountinfo-subtype-hello.sh @@ -0,0 +1,12 @@ +#!/usr/bin/env bash +# GROUP: mount + +FS_NAME=hello +MOUNT_OPTS=subtype=mysub + +mountinfo_assert() +{ + _assert_fstype "$TEST_MNT" fuse.mysub +} + +. "$TEST_LIB/mountinfo.sh" diff --git a/test/cases/mount/mountpoint-symlink-parent.sh b/test/cases/mount/mountpoint-symlink-parent.sh new file mode 100755 index 0000000..11479f4 --- /dev/null +++ b/test/cases/mount/mountpoint-symlink-parent.sh @@ -0,0 +1,25 @@ +#!/usr/bin/env bash +# GROUP: mount +# +# Only the last component of a mountpoint is refused as a symlink, so a +# mountpoint reached through a symlinked parent still mounts. Guards against a +# resolution rule that rejects a symlink anywhere in the path and so breaks +# every user whose mountpoint sits below a symlinked home. + +. "$TEST_LIB/common.sh" + +# Root mounts with mount(2) and reaches fusermount3 only on EPERM, so the +# resolution rule under test is not on the path a root run takes. +_require_nonroot + +mkdir -p "$TEST_TMP/real/mnt" +ln -s "$TEST_TMP/real" "$TEST_TMP/link" + +fuse_mount_at "$TEST_TMP/link/mnt" hello -f >/dev/null + +# Named through the real path, not through the symlink: what is being checked +# is that the mount landed on the inode the symlink resolved to. +_check fuse_test_ismount "$TEST_TMP/real/mnt" +_assert_listdir "$TEST_TMP/real/mnt" hello + +fuse_umount diff --git a/test/cases/mount/mountpoint-validation.sh b/test/cases/mount/mountpoint-validation.sh new file mode 100755 index 0000000..7111cba --- /dev/null +++ b/test/cases/mount/mountpoint-validation.sh @@ -0,0 +1,33 @@ +#!/usr/bin/env bash +# GROUP: mount quick +# +# fusermount3 refuses a mountpoint it cannot validate. Every case here is +# rejected before mount(2) is reached, so this needs the kernel module but +# not a setuid fusermount3. + +_fuse_no_mount_needed=1 +. "$TEST_LIB/common.sh" + +_require_linux "fusermount3" +# Root skips the mountpoint checks entirely and follows a symlinked mountpoint. +_require_nonroot +_require_fuse_device +_require_binary util/fusermount3 + +: >"$TEST_TMP/plain" +ln -s "$TEST_SRC" "$TEST_TMP/link-dir" +ln -s "$TEST_TMP/plain" "$TEST_TMP/link-reg" +ln -s "$TEST_TMP/gone" "$TEST_TMP/link-dangling" +mkfifo "$TEST_TMP/fifo" +mkdir "$TEST_TMP/nowrite" +chmod 500 "$TEST_TMP/nowrite" + +# O_NOFOLLOW makes fstat() see the final symlink itself, so the target type +# cannot make a symlink acceptable as a mountpoint. +_check fuse_test_fusermount_rejects "$TEST_TMP/link-dir" not_dir_or_regular +_check fuse_test_fusermount_rejects "$TEST_TMP/link-reg" not_dir_or_regular +_check fuse_test_fusermount_rejects "$TEST_TMP/link-dangling" not_dir_or_regular + +_check fuse_test_fusermount_rejects "$TEST_TMP/fifo" not_dir_or_regular +_check fuse_test_fusermount_rejects "$TEST_TMP/nowrite" no_write_access +_check fuse_test_fusermount_rejects "$TEST_TMP/missing" no_such_file diff --git a/test/cases/notify/file-size-no-notify.sh b/test/cases/notify/file-size-no-notify.sh new file mode 100755 index 0000000..103b8d2 --- /dev/null +++ b/test/cases/notify/file-size-no-notify.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash +# GROUP: notify + +FS_NAME=invalidate_path +NOTIFY=0 +NOTIFY_MODE=size + +. "$TEST_LIB/notify.sh" diff --git a/test/cases/notify/file-size.sh b/test/cases/notify/file-size.sh new file mode 100755 index 0000000..86d1c75 --- /dev/null +++ b/test/cases/notify/file-size.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash +# GROUP: notify + +FS_NAME=invalidate_path +NOTIFY=1 +NOTIFY_MODE=size + +. "$TEST_LIB/notify.sh" diff --git a/test/cases/notify/inval-entry-expire-no-notify.sh b/test/cases/notify/inval-entry-expire-no-notify.sh new file mode 100755 index 0000000..b13c353 --- /dev/null +++ b/test/cases/notify/inval-entry-expire-no-notify.sh @@ -0,0 +1,15 @@ +#!/usr/bin/env bash +# GROUP: notify + +. "$TEST_LIB/common.sh" + +# The kernel has to support expiring an entry rather than +# invalidating it; printcap reports whether it does. +_require_cap FUSE_CAP_EXPIRE_ONLY + +FS_NAME=notify_inval_entry +NOTIFY=0 +NOTIFY_MODE=entry +FS_ARGS="--timeout=5 --only-expire" + +. "$TEST_LIB/notify.sh" diff --git a/test/cases/notify/inval-entry-expire.sh b/test/cases/notify/inval-entry-expire.sh new file mode 100755 index 0000000..91b63d6 --- /dev/null +++ b/test/cases/notify/inval-entry-expire.sh @@ -0,0 +1,15 @@ +#!/usr/bin/env bash +# GROUP: notify + +. "$TEST_LIB/common.sh" + +# The kernel has to support expiring an entry rather than +# invalidating it; printcap reports whether it does. +_require_cap FUSE_CAP_EXPIRE_ONLY + +FS_NAME=notify_inval_entry +NOTIFY=1 +NOTIFY_MODE=entry +FS_ARGS="--timeout=5 --only-expire" + +. "$TEST_LIB/notify.sh" diff --git a/test/cases/notify/inval-entry-inc-epoch-no-notify.sh b/test/cases/notify/inval-entry-inc-epoch-no-notify.sh new file mode 100755 index 0000000..9f7852a --- /dev/null +++ b/test/cases/notify/inval-entry-inc-epoch-no-notify.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env bash +# GROUP: notify + +FS_NAME=notify_inval_entry +NOTIFY=0 +NOTIFY_MODE=entry +FS_ARGS="--timeout=5 --inc-epoch" +# There is no capability flag for the epoch counter, so the gate +# is what notify_inval_entry itself prints when the kernel +# refuses the request. +MARKER_ABSENT="not supported by kernel" + +. "$TEST_LIB/notify.sh" diff --git a/test/cases/notify/inval-entry-inc-epoch.sh b/test/cases/notify/inval-entry-inc-epoch.sh new file mode 100755 index 0000000..5fcfb06 --- /dev/null +++ b/test/cases/notify/inval-entry-inc-epoch.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env bash +# GROUP: notify + +FS_NAME=notify_inval_entry +NOTIFY=1 +NOTIFY_MODE=entry +FS_ARGS="--timeout=5 --inc-epoch" +# There is no capability flag for the epoch counter, so the gate +# is what notify_inval_entry itself prints when the kernel +# refuses the request. +MARKER_ABSENT="not supported by kernel" + +. "$TEST_LIB/notify.sh" diff --git a/test/cases/notify/inval-entry-invalidate-no-notify.sh b/test/cases/notify/inval-entry-invalidate-no-notify.sh new file mode 100755 index 0000000..702950a --- /dev/null +++ b/test/cases/notify/inval-entry-invalidate-no-notify.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash +# GROUP: notify + +FS_NAME=notify_inval_entry +NOTIFY=0 +NOTIFY_MODE=entry +FS_ARGS="--timeout=5" + +. "$TEST_LIB/notify.sh" diff --git a/test/cases/notify/inval-entry-invalidate.sh b/test/cases/notify/inval-entry-invalidate.sh new file mode 100755 index 0000000..d81f75a --- /dev/null +++ b/test/cases/notify/inval-entry-invalidate.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash +# GROUP: notify + +FS_NAME=notify_inval_entry +NOTIFY=1 +NOTIFY_MODE=entry +FS_ARGS="--timeout=5" + +. "$TEST_LIB/notify.sh" diff --git a/test/cases/notify/inval-inode-no-notify.sh b/test/cases/notify/inval-inode-no-notify.sh new file mode 100755 index 0000000..ae7aac1 --- /dev/null +++ b/test/cases/notify/inval-inode-no-notify.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash +# GROUP: notify + +FS_NAME=notify_inval_inode +NOTIFY=0 +NOTIFY_MODE=content + +. "$TEST_LIB/notify.sh" diff --git a/test/cases/notify/inval-inode.sh b/test/cases/notify/inval-inode.sh new file mode 100755 index 0000000..f52b43f --- /dev/null +++ b/test/cases/notify/inval-inode.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash +# GROUP: notify + +FS_NAME=notify_inval_inode +NOTIFY=1 +NOTIFY_MODE=content + +. "$TEST_LIB/notify.sh" diff --git a/test/cases/notify/invalidate-path-no-notify.sh b/test/cases/notify/invalidate-path-no-notify.sh new file mode 100755 index 0000000..fb66a85 --- /dev/null +++ b/test/cases/notify/invalidate-path-no-notify.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash +# GROUP: notify + +FS_NAME=invalidate_path +NOTIFY=0 +NOTIFY_MODE=content + +. "$TEST_LIB/notify.sh" diff --git a/test/cases/notify/invalidate-path.sh b/test/cases/notify/invalidate-path.sh new file mode 100755 index 0000000..5f2c7c1 --- /dev/null +++ b/test/cases/notify/invalidate-path.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash +# GROUP: notify + +FS_NAME=invalidate_path +NOTIFY=1 +NOTIFY_MODE=content + +. "$TEST_LIB/notify.sh" diff --git a/test/cases/notify/prune-no-notify.sh b/test/cases/notify/prune-no-notify.sh new file mode 100755 index 0000000..84b7434 --- /dev/null +++ b/test/cases/notify/prune-no-notify.sh @@ -0,0 +1,12 @@ +#!/usr/bin/env bash +# GROUP: notify + +FS_NAME=notify_prune +NOTIFY=0 +NOTIFY_MODE=prune +# There is no capability flag for FUSE_NOTIFY_PRUNE, so the gate +# is what notify_prune itself prints when the kernel refuses the +# request. +MARKER_ABSENT="not supported by kernel" + +. "$TEST_LIB/notify.sh" diff --git a/test/cases/notify/prune.sh b/test/cases/notify/prune.sh new file mode 100755 index 0000000..4e26090 --- /dev/null +++ b/test/cases/notify/prune.sh @@ -0,0 +1,12 @@ +#!/usr/bin/env bash +# GROUP: notify + +FS_NAME=notify_prune +NOTIFY=1 +NOTIFY_MODE=prune +# There is no capability flag for FUSE_NOTIFY_PRUNE, so the gate +# is what notify_prune itself prints when the kernel refuses the +# request. +MARKER_ABSENT="not supported by kernel" + +. "$TEST_LIB/notify.sh" diff --git a/test/cases/notify/store-retrieve-no-notify.sh b/test/cases/notify/store-retrieve-no-notify.sh new file mode 100755 index 0000000..8fbf288 --- /dev/null +++ b/test/cases/notify/store-retrieve-no-notify.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash +# GROUP: notify + +FS_NAME=notify_store_retrieve +NOTIFY=0 +NOTIFY_MODE=content + +. "$TEST_LIB/notify.sh" diff --git a/test/cases/notify/store-retrieve.sh b/test/cases/notify/store-retrieve.sh new file mode 100755 index 0000000..328402e --- /dev/null +++ b/test/cases/notify/store-retrieve.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash +# GROUP: notify + +FS_NAME=notify_store_retrieve +NOTIFY=1 +NOTIFY_MODE=content + +. "$TEST_LIB/notify.sh" diff --git a/test/cases/unit/test_abi b/test/cases/unit/test_abi new file mode 100644 index 0000000..7dafe0e --- /dev/null +++ b/test/cases/unit/test_abi @@ -0,0 +1 @@ +# GROUP: unit quick diff --git a/test/ci-build.sh b/test/ci-build.sh new file mode 100755 index 0000000..3006795 --- /dev/null +++ b/test/ci-build.sh @@ -0,0 +1,214 @@ +#!/bin/bash +# +# Build one libfuse configuration and run the test suite against it. +# +# One invocation is one configuration. The workflow's build matrix supplies +# the arguments for each of them, and CI runs them as parallel matrix jobs. + +set -e + +usage() +{ + cat >&2 <&2; exit 1; } +} + +NAME= +CC_BIN=cc +CXX_BIN= +SANITIZE=0 +VALGRIND=0 +ROOT=0 +IO_URING=0 +MESON_OPTS=() +cli_work_dir= + +while [ $# -gt 0 ]; do + case $1 in + --name) need_arg "$@"; NAME=$2; shift 2 ;; + --cc) need_arg "$@"; CC_BIN=$2; shift 2 ;; + --cxx) need_arg "$@"; CXX_BIN=$2; shift 2 ;; + --meson-opt) need_arg "$@"; MESON_OPTS+=("$2"); shift 2 ;; + --work-dir) need_arg "$@"; cli_work_dir=$2; shift 2 ;; + --sanitize) SANITIZE=1; shift ;; + --valgrind) VALGRIND=1; shift ;; + --root) ROOT=1; shift ;; + --io-uring) IO_URING=1; shift ;; + *) usage ;; + esac +done +[ -n "${NAME}" ] || usage + +# Make sure binaries can be accessed when invoked by root. +umask 0022 + +SOURCE_DIR="$(readlink -f "$(dirname "$0")/..")" + +# Where this run's builds and logs go. --work-dir is used verbatim so CI can +# predict the path it later uploads; otherwise take the base from run-tests.py +# (persisted config, else /var/tmp/fuse-tests) and append a dedicated leaf. +# Asking the runner keeps the precedence in one place instead of parsing an +# INI in bash. +WORK_DIR="${cli_work_dir:-}" +if [ -z "${WORK_DIR}" ]; then + WORK_DIR="$(python3 "${SOURCE_DIR}/test/run-tests.py" --print-base-dir)" + WORK_DIR="${WORK_DIR}/ci-${USER:-unknown}-$(date +%y%m%d%H%M%S)" +fi +WORK_DIR="$(readlink -f "${WORK_DIR}")" # resolve before the cd below + +# Named after the configuration, so several of them share one work directory +# without colliding and the logs of a whole run stay together. +BUILD_DIR="${WORK_DIR}/build-${NAME}" +PREFIX_DIR="${WORK_DIR}/install-${NAME}" +RUN_DIR="${WORK_DIR}/run" + +# A stale tree from a previous failed run would poison this one. The install +# prefix is owned by root from the last `ninja install`. +rm -rf "${BUILD_DIR}" +sudo rm -rf "${PREFIX_DIR}" +mkdir -p "${BUILD_DIR}" "${PREFIX_DIR}" "${RUN_DIR}" +# There are tests that run as root but without CAP_DAC_OVERRIDE. To allow those +# to launch built binaries, every directory on the way in must be traversable +# by plain permission bits. +chmod 0755 "${WORK_DIR}" "${BUILD_DIR}" "${PREFIX_DIR}" "${RUN_DIR}" +cd "${BUILD_DIR}" +echo "Building ${NAME} in ${BUILD_DIR}" + +echo "=== System ===" +uname -a +lsb_release -a 2>/dev/null || cat /etc/os-release 2>/dev/null || true +lscpu || true +echo "===============" + +export CC="${CC_BIN}" +if [ -n "${CXX_BIN}" ]; then + export CXX="${CXX_BIN}" +else + unset CXX +fi + +# Exported rather than assigned: run-tests.py reads it from the environment of +# the process meson spawns. +if [ "${VALGRIND}" = 1 ]; then + export TEST_WITH_VALGRIND=true +else + export TEST_WITH_VALGRIND=false +fi + +cp -v "${SOURCE_DIR}/test/lsan_suppress.txt" . +export LSAN_OPTIONS="suppressions=${BUILD_DIR}/lsan_suppress.txt" +export ASAN_OPTIONS="detect_leaks=1" +export UBSAN_OPTIONS=halt_on_error=1 # not the default + +echo "=== Environment ===" +echo "Configuration: ${NAME}" +echo "CC: ${CC}" +echo "CXX: ${CXX-}" +echo "Sanitize: ${SANITIZE}" +echo "LSAN_OPTIONS: ${LSAN_OPTIONS}" +echo "ASAN_OPTIONS: ${ASAN_OPTIONS}" +echo "UBSAN_OPTIONS: ${UBSAN_OPTIONS}" +echo "Valgrind: ${TEST_WITH_VALGRIND}" +echo "Root: ${ROOT}" +echo "IO-uring: ${IO_URING}" +echo "===================" + +meson setup -Dprefix="${PREFIX_DIR}" -Dwerror=true "${MESON_OPTS[@]}" \ + "${SOURCE_DIR}" || { cat meson-logs/meson-log.txt; false; } + +if [ "${SANITIZE}" = 1 ]; then + meson configure -Db_sanitize=address,undefined + # b_lundef=false is required to work around a clang bug, cf. + # https://groups.google.com/forum/#!topic/mesonbuild/tgEdAXIIdC4 + meson configure -Db_lundef=false + # Reconfigure so the build actually uses them. + meson setup --reconfigure "${SOURCE_DIR}" +fi + +meson configure --no-pager # what was actually configured +ninja +sudo env PATH="$PATH" ninja install + +# libfuse will first try the install path and then system defaults. +sudo chmod 4755 "${PREFIX_DIR}/bin/fusermount3" +if [ -x "${PREFIX_DIR}/sbin/fuservicemount3" ]; then + sudo chmod 4755 "${PREFIX_DIR}/sbin/fuservicemount3" +fi + +restore_io_uring() +{ + echo "${FUSE_URING_WAS}" | sudo tee "$1" >/dev/null + [ -z "${IO_URING_DISABLED_WAS}" ] || + sudo sysctl -q -w "kernel.io_uring_disabled=${IO_URING_DISABLED_WAS}" +} + +if [ "${IO_URING}" = 1 ]; then + # The kernel parameter is global and defaults to off, and this script is + # also run by hand, so put back whatever the machine had. Failing to enable + # it is fatal: run-tests.py skips its io-uring invocation where the kernel + # does not offer the transport, which is right for a developer machine and + # wrong here - a job that silently stops testing io-uring is what this + # exists to prevent. + param=/sys/module/fuse/parameters/enable_uring + sysctl_param=/proc/sys/kernel/io_uring_disabled + FUSE_URING_WAS="$(cat "${param}")" || + { echo "this kernel has no fuse io-uring support"; exit 1; } + # Non-zero denies the daemons io_uring_setup(), which drops them back to + # /dev/fuse. Empty before 6.6, where nothing restricts ring creation. + IO_URING_DISABLED_WAS="$(cat "${sysctl_param}" 2>/dev/null || true)" + # Armed before either write, so a failure between them still restores. + trap "restore_io_uring ${param}" EXIT + echo Y | sudo tee "${param}" >/dev/null + [ -z "${IO_URING_DISABLED_WAS}" ] || + sudo sysctl -q -w kernel.io_uring_disabled=0 +fi + +RUN_TESTS_OPTS=(--build-dir .) +[ "${IO_URING}" = 1 ] && RUN_TESTS_OPTS+=(--io-uring) + +if [ "${ROOT}" = 1 ]; then + SUDO=(sudo) +else + SUDO=() + RUN_TESTS_OPTS+=(--setuid-helpers) +fi + +# sudo resets the environment, so PATH and FUSE_TEST_RUN_DIR are passed +# through env rather than shell-prefix assignments, which sudo would +# otherwise drop; harmless when SUDO is empty. Calling run-tests.py directly, +# never through `meson test`, keeps its per-test results in the job log +# instead of only printing output on failure. +rc=0 +"${SUDO[@]}" env PATH="$PATH" FUSE_TEST_RUN_DIR="${RUN_DIR}/${NAME}" \ + timeout 1800 python3 "${SOURCE_DIR}/test/run-tests.py" \ + "${RUN_TESTS_OPTS[@]}" || rc=$? + +if [ "${ROOT}" = 1 ]; then + # upload-artifact has to read what root wrote -- before the failure is + # propagated, or one unreadable file fails the whole upload of the very + # run whose logs are wanted. + sudo chown -R "$(id -u):$(id -g)" "${RUN_DIR}" +fi +[ "${rc}" = 0 ] || exit "${rc}" + +# Only reached when everything above passed, because of set -e: a failed run +# has to stay inspectable. The logs are kept either way, and are the product. +rm -rf "${BUILD_DIR}" +sudo rm -rf "${PREFIX_DIR}" diff --git a/test/ci/doxygen.sh b/test/ci/doxygen.sh new file mode 100755 index 0000000..3c103cf --- /dev/null +++ b/test/ci/doxygen.sh @@ -0,0 +1,8 @@ +#!/bin/bash +# The API documentation build. Not a build configuration: it needs no compiler +# of its own and none of them has to have run first. + +set -e + +cd "$(readlink -f "$(dirname "$0")/../..")" +doxygen doc/Doxyfile diff --git a/test/ci/prepare-runner.sh b/test/ci/prepare-runner.sh new file mode 100755 index 0000000..92dda11 --- /dev/null +++ b/test/ci/prepare-runner.sh @@ -0,0 +1,48 @@ +#!/bin/bash +# +# Host tuning a CI runner needs and a developer machine must not get. +# +# Cores must land in the test's own log directory, which run-tests.py makes the +# cwd of every test. ubuntu-latest ships apport, so core_pattern is a pipe and +# every core disappears into a collector the job then throws away. %p keeps two +# crashing processes in one test from overwriting each other, and %e is how the +# runner finds the binary to hand gdb. +# +# Deliberately not part of ci-build.sh: that script is also run by hand, and a +# build script has no business rewriting a global kernel tunable -- or, below, +# the permission bits on directories outside the tree it builds -- on someone's +# workstation. Locally the runner's own warning is enough, and it still fetches +# cores through coredumpctl. +# +# Usage: prepare-runner.sh + +set -e + +# Before the writes below, so a mistyped invocation leaves the host alone. +# Absolute, because the ancestor walk further down ends at / and `dirname` on +# a relative path stops at "." instead -- which is a loop, not a walk. +WORK_DIR="$(readlink -f "${1:?usage: prepare-runner.sh }")" +SOURCE_DIR="$(readlink -f "$(dirname "$0")/../..")" + +sudo sysctl -w kernel.core_pattern=core.%e.%p +sudo sysctl -w kernel.core_uses_pid=0 + +# There are tests that run as root but without CAP_DAC_OVERRIDE. To allow those +# to launch built binaries, every ancestor of the work directory must be +# traversable by plain permission bits; a 0750 home is the usual culprit. This +# script does not create the work directory itself -- ci-build.sh chmods that +# and the directories below it -- only the ancestors above it. +dir="$(dirname "${WORK_DIR}")" +while [ "${dir}" != / ]; do + if [ -z "$(find "${dir}" -maxdepth 0 -perm -001)" ]; then + echo "making ${dir} traversable for the capability-less tests" + chmod o+x "${dir}" 2>/dev/null || sudo chmod o+x "${dir}" + fi + dir="$(dirname "${dir}")" +done +# Assert it rather than trust the loop: a test that skips itself here leaves a +# green job that stopped exercising drop_privileges, which is the one outcome +# CI must not produce. +python3 "${SOURCE_DIR}/test/cases/lib/checks.py" fuse_test_reachable_without_caps \ + "$(dirname "${WORK_DIR}")" || + { echo "use --work-dir to build somewhere reachable"; exit 1; } diff --git a/test/hello.c b/test/hello.c new file mode 100644 index 0000000..cf57668 --- /dev/null +++ b/test/hello.c @@ -0,0 +1,180 @@ +/* + * FUSE: Filesystem in Userspace + * Copyright (C) 2001-2007 Miklos Szeredi + * + * This program can be distributed under the terms of the GNU GPLv2. + * See the file GPL2.txt. + */ + +/** @file + * + * minimal example filesystem using high-level API + * + * Compile with: + * + * gcc -Wall hello.c `pkg-config fuse3 --cflags --libs` -o hello + * + * ## Source code ## + * \include hello.c + */ + +#define FUSE_USE_VERSION 31 + +#include +#include +#include +#include +#include +#include +#include + +/* + * Command line options + * + * We can't set default values for the char* fields here because + * fuse_opt_parse would attempt to free() them when the user specifies + * different values on the command line. + */ +static struct options { + const char *filename; + const char *contents; + int show_help; +} options; + +#define OPTION(t, p) { t, offsetof(struct options, p), 1 } +static const struct fuse_opt option_spec[] = { + OPTION("--name=%s", filename), OPTION("--contents=%s", contents), + OPTION("-h", show_help), OPTION("--help", show_help), FUSE_OPT_END +}; + +static void *hello_init(struct fuse_conn_info *conn, struct fuse_config *cfg) +{ + (void)conn; + cfg->kernel_cache = 1; + + /* Test setting flags the old way */ + conn->want = FUSE_CAP_ASYNC_READ; + conn->want &= ~FUSE_CAP_ASYNC_READ; + + return NULL; +} + +static int hello_getattr(const char *path, struct stat *stbuf, + struct fuse_file_info *fi) +{ + (void)fi; + int res = 0; + + memset(stbuf, 0, sizeof(struct stat)); + if (strcmp(path, "/") == 0) { + stbuf->st_mode = S_IFDIR | 0755; + stbuf->st_nlink = 2; + } else if (strcmp(path + 1, options.filename) == 0) { + stbuf->st_mode = S_IFREG | 0444; + stbuf->st_nlink = 1; + stbuf->st_size = strlen(options.contents); + } else + res = -ENOENT; + + return res; +} + +static int hello_readdir(const char *path, void *buf, fuse_fill_dir_t filler, + off_t offset, struct fuse_file_info *fi, + enum fuse_readdir_flags flags) +{ + (void)offset; + (void)fi; + (void)flags; + + if (strcmp(path, "/") != 0) + return -ENOENT; + + filler(buf, ".", NULL, 0, FUSE_FILL_DIR_DEFAULTS); + filler(buf, "..", NULL, 0, FUSE_FILL_DIR_DEFAULTS); + filler(buf, options.filename, NULL, 0, FUSE_FILL_DIR_DEFAULTS); + + return 0; +} + +static int hello_open(const char *path, struct fuse_file_info *fi) +{ + if (strcmp(path + 1, options.filename) != 0) + return -ENOENT; + + if ((fi->flags & O_ACCMODE) != O_RDONLY) + return -EACCES; + + return 0; +} + +static int hello_read(const char *path, char *buf, size_t size, off_t offset, + struct fuse_file_info *fi) +{ + size_t len; + (void)fi; + if (strcmp(path + 1, options.filename) != 0) + return -ENOENT; + + len = strlen(options.contents); + if (offset < len) { + if (offset + size > len) + size = len - offset; + memcpy(buf, options.contents + offset, size); + } else + size = 0; + + return size; +} + +static const struct fuse_operations hello_oper = { + .init = hello_init, + .getattr = hello_getattr, + .readdir = hello_readdir, + .open = hello_open, + .read = hello_read, +}; + +static void show_help(const char *progname) +{ + printf("usage: %s [options] \n\n", progname); + printf("File-system specific options:\n" + " --name= Name of the \"hello\" file\n" + " (default: \"hello\")\n" + " --contents= Contents \"hello\" file\n" + " (default \"Hello, World!\\n\")\n" + "\n"); +} + +int main(int argc, char *argv[]) +{ + int ret; + struct fuse_args args = FUSE_ARGS_INIT(argc, argv); + + /* Set defaults -- we have to use strdup so that + * fuse_opt_parse can free the defaults if other + * values are specified + */ + options.filename = strdup("hello"); + options.contents = strdup("Hello World!\n"); + + /* Parse options */ + if (fuse_opt_parse(&args, &options, option_spec, NULL) == -1) + return 1; + + /* When --help is specified, first print our own file-system + * specific help text, then signal fuse_main to show + * additional help (by adding `--help` to the options again) + * without usage: line (by setting argv[0] to the empty + * string) + */ + if (options.show_help) { + show_help(argv[0]); + assert(fuse_opt_add_arg(&args, "--help") == 0); + args.argv[0][0] = '\0'; + } + + ret = fuse_main(args.argc, args.argv, &hello_oper, NULL); + fuse_opt_free_args(&args); + return ret; +} diff --git a/test/lsan_suppress.txt b/test/lsan_suppress.txt new file mode 100644 index 0000000..44703fc --- /dev/null +++ b/test/lsan_suppress.txt @@ -0,0 +1 @@ +# Suppression file for address sanitizer. diff --git a/test/meson.build b/test/meson.build new file mode 100644 index 0000000..c34b620 --- /dev/null +++ b/test/meson.build @@ -0,0 +1,61 @@ +# Compile helper programs +td = [] +foreach prog: [ 'test_write_cache', 'test_setattr', 'test_api_30', 'hello' ] + td += executable(prog, prog + '.c', + include_directories: include_dirs, + link_with: [ libfuse ], + dependencies: thread_dep, + install: false) +endforeach +td += executable('test_syscalls', 'test_syscalls.c', + include_directories: include_dirs, + install: false) +td += executable('readdir_inode', 'readdir_inode.c', + include_directories: include_dirs, + install: false) +td += executable('release_unlink_race', 'release_unlink_race.c', + dependencies: [ libfuse_dep ], + install: false) +td += executable('test_want_conversion', 'test_want_conversion.c', + dependencies: [ libfuse_dep ], + install: false) +td += executable('test_signals', 'test_signals.c', + dependencies: [ libfuse_dep, thread_dep ], + install: false) +td += executable('test_abi', 'test_abi.c', + dependencies: [ libfuse_dep ], + install: false) + +if meson.is_subproject() + # Skipped rather than run: the tests mount filesystems, which is not + # something a parent project's `meson test` should do. Exit 77 is how a + # test tells meson SKIP. python3 is optional here because nothing else + # in this branch needs it, and a parent must still be able to configure. + python3 = find_program('python3', required: false) + if python3.found() + test('libfuse is a subproject, skipping tests', python3, + args: ['-c', + 'import sys; print("run them with test/run-tests.py ' + + '--build-dir ", file=sys.stderr); sys.exit(77)']) + endif +else + # The same definition works both as a regular user and under sudo: + # invoke 'meson test -C build' for the unprivileged subset, or + # 'sudo -E meson test -C build' to also exercise the tests that require + # root (they pick the uid up at runtime). + # + # Nothing is copied into the build tree: the runner, + # its library and the test scripts are read from the source dir, and + # the only build artefacts a test needs are the binaries, which it + # reaches through --build-dir. That also means no custom_target and no + # rebuild after editing a test. + # + # No --run-dir: it defaults under /var/tmp/fuse-tests, and CI redirects + # it with $FUSE_TEST_RUN_DIR so the logs outlive the build tree. + runner = find_program('run-tests.py') + test('fuse-tests', runner, + args: ['--build-dir', meson.project_build_root()], + depends: td, + timeout: 1800, + is_parallel: false) # the runner does its own -j +endif diff --git a/test/readdir_inode.c b/test/readdir_inode.c new file mode 100644 index 0000000..99f95ff --- /dev/null +++ b/test/readdir_inode.c @@ -0,0 +1,57 @@ +/* + * Prints each directory entry, its inode and d_type as returned by 'readdir'. + * Skips '.' and '..' because readdir is not required to return them and + * some of our examples don't. However if they are returned, their d_type + * should be valid. + */ + +#include +#include +#include +#include +#include + +int main(int argc, char* argv[]) +{ + DIR* dirp; + struct dirent* dent; + + if (argc != 2) { + fprintf(stderr, "Usage: readdir_inode dir\n"); + return 1; + } + + dirp = opendir(argv[1]); + if (dirp == NULL) { + perror("failed to open directory"); + return 2; + } + + errno = 0; + dent = readdir(dirp); + while (dent != NULL) { + if (strcmp(dent->d_name, ".") != 0 && strcmp(dent->d_name, "..") != 0) { + printf("%llu %d %s\n", (unsigned long long)dent->d_ino, + (int)dent->d_type, dent->d_name); + if ((long long)dent->d_ino < 0) + fprintf(stderr,"%s : bad d_ino %llu\n", + dent->d_name, (unsigned long long)dent->d_ino); + if ((dent->d_type < 1) || (dent->d_type > 15)) + fprintf(stderr,"%s : bad d_type %d\n", + dent->d_name, (int)dent->d_type); + } else { + if (dent->d_type != DT_DIR) + fprintf(stderr,"%s : bad d_type %d\n", + dent->d_name, (int)dent->d_type); + } + dent = readdir(dirp); + } + if (errno != 0) { + perror("failed to read directory entry"); + return 3; + } + + closedir(dirp); + + return 0; +} diff --git a/test/release_unlink_race.c b/test/release_unlink_race.c new file mode 100644 index 0000000..f7b7b81 --- /dev/null +++ b/test/release_unlink_race.c @@ -0,0 +1,111 @@ +/* + This program can be distributed under the terms of the GNU GPLv2. + See the file GPL2.txt. +*/ + +#define FUSE_USE_VERSION 31 + +#define _GNU_SOURCE + +#include + +#include +#include +#include +#include + +static void *xmp_init(struct fuse_conn_info *conn, + struct fuse_config *cfg) +{ + (void) conn; + + cfg->use_ino = 1; + cfg->nullpath_ok = 1; + cfg->entry_timeout = 0; + cfg->attr_timeout = 0; + cfg->negative_timeout = 0; + + return NULL; +} + +static int xmp_getattr(const char *path, struct stat *stbuf, + struct fuse_file_info *fi) +{ + int res; + + (void) path; + + if(fi) + res = fstat(fi->fh, stbuf); + else + res = lstat(path, stbuf); + if (res == -1) + return -errno; + + return 0; +} + +static int xmp_unlink(const char *path) +{ + int res; + + res = unlink(path); + if (res == -1) + return -errno; + + return 0; +} + +static int xmp_rename(const char *from, const char *to, unsigned int flags) +{ + int res; + + if (flags) + return -EINVAL; + + if(!getenv("RELEASEUNLINKRACE_DELAY_DISABLE")) usleep(100000); + + res = rename(from, to); + if (res == -1) + return -errno; + + return 0; +} + +static int xmp_create(const char *path, mode_t mode, struct fuse_file_info *fi) +{ + int fd; + + fd = open(path, fi->flags, mode); + if (fd == -1) + return -errno; + + fi->fh = fd; + return 0; +} + +static int xmp_release(const char *path, struct fuse_file_info *fi) +{ + (void) path; + + if(!getenv("RELEASEUNLINKRACE_DELAY_DISABLE")) usleep(100000); + + close(fi->fh); + + return 0; +} + +static const struct fuse_operations xmp_oper = { + .init = xmp_init, + .getattr = xmp_getattr, + .unlink = xmp_unlink, + .rename = xmp_rename, + .create = xmp_create, + .release = xmp_release, +}; + +int main(int argc, char *argv[]) +{ + umask(0); + return fuse_main(argc, argv, &xmp_oper, NULL); +} diff --git a/test/run-tests.py b/test/run-tests.py new file mode 100755 index 0000000..d536c0c --- /dev/null +++ b/test/run-tests.py @@ -0,0 +1,1648 @@ +#!/usr/bin/env python3 +"""Run the libfuse shell test suite. + +Every executable cases/**/*.sh outside cases/lib is a test, as is every entry +in a directory is_shell_exception_dir() names, where the file name is a +build-tree binary to run instead. A test's name is the path below cases/ +without the extension. Exit code 0 is a pass, 77 a self-declared skip and +anything else a failure. + +The runner knows nothing about FUSE: it owns discovery, working directories, +containment, timeouts, parallelism, logs, core dumps and the summary. The FUSE +verbs live in cases/lib/common.sh. +""" + +import argparse +import ctypes +import fnmatch +import os +import platform +import re +import resource +import shutil +import signal +import stat +import subprocess +import sys +import threading +import time +from concurrent.futures import ThreadPoolExecutor, as_completed +from dataclasses import dataclass, field, replace +from pathlib import Path + +# Everything the suite reads lives beside this file in the source tree; the +# build directory supplies binaries and nothing else, so a test edited in the +# source tree runs without a rebuild. +TEST_DIR = Path(__file__).resolve().parent +CASES_DIR = TEST_DIR / 'cases' +# What the cases source, beside them rather than above them; discovery skips it. +LIB_DIR = CASES_DIR / 'lib' +EXCLUDE_FILE = TEST_DIR / 'exclude' + +DEFAULT_TIMEOUT = 60.0 # seconds; per-script "# TIMEOUT:" overrides +SKIP_EXIT_CODE = 77 # automake convention +LAUNCH_FAILED_EXIT_CODE = 125 # launcher could not establish containment +LAUNCH_FAILED_MARKER = 'run-tests: containment failed' + +# util/install_helper.sh installs exactly these setuid; nothing else in util/ +# needs the bit. +SETUID_HELPERS = ('fusermount3', 'fuservicemount3') + +GDB_TIMEOUT = 120.0 # gdb can wedge on a huge core or absent symbols +GDB_TOP_FRAMES = 12 # frames inlined into the summary + +# A task wedged in an uninterruptible FUSE request does not die on SIGKILL, so +# waiting for one without a bound hangs the run itself. +REAP_TIMEOUT = 30.0 # seconds to wait for a killed test to be gone + +# A daemon under valgrind is an order of magnitude slower, so every timeout is +# scaled by one constant rather than each script carrying a second number for a +# mode most runs do not use. +VALGRIND_TIMEOUT_FACTOR = 10 +# --error-exitcode is what makes a finding fail the test: the daemon exits 99 +# and fuse_umount's existing status check catches it. Leak checking stays off; +# turning it on is a separate decision with its own noise budget. +VALGRIND_PREFIX = 'valgrind -q --error-exitcode=99 --' +# ==PID== is valgrind's standard output, --PID-- its warnings; the report is +# not the verdict, the daemon's exit status is. +_VALGRIND_LINE_RE = re.compile(r'^(?:==|--)[0-9]+(?:==|--) .*$', re.MULTILINE) + +# Test output must land somewhere predictable, so this is the default base for +# everything the suite writes. /var/tmp rather than /tmp because it survives a +# reboot and no tmpfiles cleaner walks it mid-run, and a fixed name rather than +# a mktemp directory because a path you cannot predict is a path you cannot +# look in after CI hands you a red build. +DEFAULT_BASE_DIR = Path('/var/tmp/fuse-tests') + +# Tests declaring this group cannot run concurrently with a copy of themselves: +# they bind a fixed path, name a device node globally, or take the first free +# loop device. They run first, with one job, before the rest fan out. +SERIAL_GROUP = 'serial' + +_CONFIG_SECTION = 'tests' +_CONFIG_BASE_DIR_KEY = 'base_dir' + +IS_LINUX = platform.system() == 'Linux' + +# FreeBSD's default kern.corefile is "%N.core"; CI sets "core.%N.%P" to get the +# shape Linux's "core.%e.%p" produces. Glob those plus the bare "core" and +# "core.%p" a stock Linux box writes, so an unconfigured developer machine +# still yields a core. +CORE_GLOBS = ('core', 'core.*', '*.core') + +# A daemon writing any of these to its stderr fails the test even when it +# exited 0. +SUSPICIOUS_WORDS = ('exception', 'error', 'warning', 'fatal', 'traceback', + 'fault', 'crash(?:ed)?', 'abort(?:ed)?', + 'uninitiali[zs]ed') + +# libfuse logs this at FUSE_LOG_INFO and then carries on over /dev/fuse, so an +# io-uring run that nobody checks passes green while testing the transport it +# was meant to replace. +IO_URING_FALLBACK = 'failed to start io-uring' +IO_URING_CAP = 'FUSE_CAP_OVER_IO_URING' +# Every session states its transport on its own stderr, which is a log the +# suite keeps either way. off:custom_io is the only refusal a daemon cannot +# help: no /dev/fuse fd of its own, so no ring to issue against. +IO_URING_STATE_KEY = 'FUSE_INIT: io_uring=' +IO_URING_STATES_OK = ('FUSE_INIT: io_uring=on', + 'FUSE_INIT: io_uring=off:custom_io') +# What -Dsync-init=always/never leave in fuse_config.h; auto writes neither. +SYNC_INIT_ENABLED = '#define FUSE_SYNC_INIT_DEFAULT FUSE_SYNC_INIT_ENABLED' +SYNC_INIT_DISABLED = '#define FUSE_SYNC_INIT_DEFAULT FUSE_SYNC_INIT_DISABLED' +FUSE_URING_PARAM = Path('/sys/module/fuse/parameters/enable_uring') +# glibc has no io_uring_setup() wrapper, so this goes through syscall(2), the +# way liburing does it. 425 on every architecture but alpha. +IO_URING_SETUP = 425 +IO_URING_PARAMS_SIZE = 120 # sizeof(struct io_uring_params) + +# FUSE debug messages "unique: X, error: -Y (...), outsize: Z" contain the word +# "error" but only report a request's return code. +_FUSE_DEBUG_RE = re.compile( + r'^.*unique: \d+, error: -\d+ \(.*\), outsize: \d+.*$', re.MULTILINE) + +_HEADER_RE = re.compile(r'^#\s*(TIMEOUT|GROUP):\s*(.+?)\s*$') + +# Two things must happen in the child before the test runs: it must enter its +# cgroup leaf and raise its soft core limit. Neither can be done with a +# preexec_fn -- run_all() is threaded, and Python between fork() and exec() can +# deadlock inside Popen(), where no timeout is watching yet. Only builtins run +# here, so nothing forks before the leaf is joined and a daemon the script +# spawns cannot escape it. +# +# `ulimit -H -c` rather than a limit computed in Python: ulimit counts 512-byte +# blocks in dash and 1024-byte blocks in bash, and RLIM_INFINITY is -1 in +# CPython, which every shell parses as an option. Only the soft limit is raised; +# raising a finite hard limit needs privilege. +# +# The command to exec is passed in rather than built here: the runner decides +# whether a test is a script or a binary, and both the spawn and repro.sh must +# name the same one. +LAUNCHER = f''' +[ -z "$1" ] || printf '%s\\n' "$$" >"$1" || {{ + echo "{LAUNCH_FAILED_MARKER}: cgroup" >&2; exit {LAUNCH_FAILED_EXIT_CODE} +}} +ulimit -S -c "$(ulimit -H -c)" || {{ + echo "{LAUNCH_FAILED_MARKER}: core limit" >&2 + exit {LAUNCH_FAILED_EXIT_CODE} +}} +shift +exec "$@" +''' + + +@dataclass(frozen=True) +class TestSpec: + """One discovered test and the two facts parsed from its header.""" + + name: str # "examples/hello-ll-direct" -- path under cases/, no .sh + script: Path # absolute path to the script, or to the binary marker + timeout: float # seconds; DEFAULT_TIMEOUT unless "# TIMEOUT:" overrides + groups: frozenset # from "# GROUP:"; used by -g/--group selection + binary: str = '' # marker: the build-tree binary to run; "" for a script + + +@dataclass +class CoreDump: + """One core file found in a test's log directory.""" + + path: Path + executable: str # %e from the file name, e.g. "passthrough_ll" + pid: int # %p from the file name + binary: Path | None = None # resolved build-tree binary + # logs/backtrace...txt -- deliberately not sharing the "core." + # prefix, so CI can upload backtraces while excluding the huge cores with a + # single "!**/core.*" pattern. + backtrace: Path | None = None # None if gdb is absent + top_frames: str = '' # first GDB_TOP_FRAMES frames + + +@dataclass +class TestResult: + """Outcome of one test script run.""" + + name: str + status: str # "PASS" | "FAIL" | "SKIP" + duration: float # wall clock seconds, monotonic + exit_code: int # script exit status; negative == killed by signal + reason: str # skip reason, kill reason, or "" on a clean pass + workdir: Path + cores: list = field(default_factory=list) # list[CoreDump], backtraced + suspicious: str = '' # first offending log line found by scan_logs() + + +def parse_spec(script: Path, cases_dir: Path) -> TestSpec: + """Read the leading comment block of *script* for TIMEOUT and GROUP.""" + timeout, groups = DEFAULT_TIMEOUT, set() + with script.open() as fh: + for line in fh: + if not line.startswith('#'): + if line.strip(): # first non-comment line ends the header + break + continue + hit = _HEADER_RE.match(line) + if not hit: + continue + if hit.group(1) == 'TIMEOUT': + timeout = float(hit.group(2)) + else: + groups.update(hit.group(2).split()) + return TestSpec(name=str(script.relative_to(cases_dir).with_suffix('')), + script=script, timeout=timeout, groups=frozenset(groups), + # discover() collects nothing else without a suffix + binary='' if script.suffix else script.name) + + +def read_core_pattern() -> str: + """The kernel's core-file pattern, Linux and FreeBSD spelling alike.""" + if IS_LINUX: + try: + return Path('/proc/sys/kernel/core_pattern').read_text().strip() + except OSError: + return '' + proc = subprocess.run(['sysctl', '-n', 'kern.corefile'], + capture_output=True, text=True, check=False) + return proc.stdout.strip() + + +def io_uring_setup_error() -> str: + """The errno text when io_uring_setup() is refused, else "". + + Asked as the user the daemons will run as, so it answers for them. + """ + libc = ctypes.CDLL(None, use_errno=True) + libc.syscall.restype = ctypes.c_long + ring = libc.syscall(IO_URING_SETUP, 1, + ctypes.create_string_buffer(IO_URING_PARAMS_SIZE)) + if ring < 0: + return os.strerror(ctypes.get_errno()) + os.close(ring) + return '' + + +def raise_nofile_limit() -> None: + """Raise RLIMIT_NOFILE to the hard limit; -j multiplies fd pressure.""" + soft, hard = resource.getrlimit(resource.RLIMIT_NOFILE) + if soft >= hard: + return + try: + resource.setrlimit(resource.RLIMIT_NOFILE, (hard, hard)) + except (ValueError, OSError): + return + if hard != resource.RLIM_INFINITY and hard < 1024: + print(f'note: RLIMIT_NOFILE hard limit is only {hard}; ' + 'reduce -j if tests fail with EMFILE') + + +def setuid_helpers(build_dir: Path) -> None: + """chown root:root + chmod 4755 the build tree's mount helpers. + + Opt-in because it needs sudo and mutates the build tree; without it an + unprivileged run skips every test that mounts. + """ + sudo = [] if os.geteuid() == 0 else ['sudo'] + for name in SETUID_HELPERS: + helper = build_dir / 'util' / name + if not helper.exists(): # fuservicemount3 is not always built + continue + info = helper.stat() + if info.st_uid == 0 and info.st_mode & stat.S_ISUID: + continue + for cmd in (['chown', 'root:root', str(helper)], + ['chmod', '4755', str(helper)]): + if subprocess.run(sudo + cmd, check=False).returncode: + sys.exit(f'{" ".join(sudo + cmd)} failed') + + +class CgroupManager: + """A cgroup-v2 tree for the run, one leaf per test. + + The launcher puts itself into its leaf before it execs the test, so each + descendant the script spawns -- the fs daemon, fusermount3, anything a + helper backgrounds -- inherits it. Writing "1" to the leaf's cgroup.kill + reaps the whole subtree atomically, which is the only reliable way to stop + a wedged FUSE daemon that has already been reparented to init. + + cgroup-v2 is Linux-only, and FreeBSD does not mount /proc at all by + default, so every method here is disabled off Linux and the runner falls + back to killpg(). + """ + + def __init__(self, root: Path | None): + self.root = root + self._counter = 0 + self._lock = threading.Lock() + + @classmethod + def create(cls, tag: str) -> "CgroupManager": + """Create the run root under the delegated base; disabled on failure.""" + base = cls.delegated_base() + if base is None: + if IS_LINUX: + print('note: no writable delegated cgroup; containing tests ' + 'with killpg() instead of cgroup.kill') + else: + print(f'note: cgroups are Linux-only; containing tests with ' + f'killpg() on {platform.system()}') + return cls(None) + root = base / f'fuse-tests-{tag}' + try: + root.mkdir(exist_ok=True) + except OSError as exc: + print(f'note: cannot create {root} ({exc}); containing tests with ' + 'killpg() instead of cgroup.kill') + return cls(None) + return cls(root) + + @staticmethod + def delegated_base() -> Path | None: + """Parent of this process's own cgroup, if it is writable.""" + if not IS_LINUX: + return None + try: + for line in Path('/proc/self/cgroup').read_text().splitlines(): + if not line.startswith('0::'): + continue + own = Path('/sys/fs/cgroup') / line[3:].lstrip('/') + base = own.parent + if os.access(base, os.W_OK): + return base + except OSError: + pass + return None + + def new_leaf(self) -> Path | None: + """mkdir one leaf; None when disabled.""" + if self.root is None: + return None + with self._lock: + self._counter += 1 + leaf = self.root / f'test-{self._counter}' + try: + leaf.mkdir(exist_ok=True) + except OSError: + return None + return leaf + + def procs_path(self, leaf: Path | None) -> str: + """leaf/cgroup.procs for the launcher to write itself into, or "". + + The runner never writes it: a process moving *itself* needs write + access to the destination cgroup.procs alone, while moving another + process also needs it on the common ancestor -- so the launcher works + under strictly more delegation setups than the runner would. + """ + return '' if leaf is None else str(leaf / 'cgroup.procs') + + def kill_leaf(self, leaf: Path | None) -> None: + """echo 1 > leaf/cgroup.kill, then rmdir. No-op when disabled.""" + if leaf is None: + return + try: + (leaf / 'cgroup.kill').write_text('1\n') + except OSError: + pass + # The kill is asynchronous; a leaf with a process still in it cannot be + # removed, so give it a moment rather than leaking the directory. + for _ in range(50): + try: + leaf.rmdir() + return + except OSError: + time.sleep(0.1) + + def cleanup(self) -> None: + """Kill and remove every remaining leaf, then the root.""" + if self.root is None: + return + for leaf in sorted(self.root.glob('test-*')): + self.kill_leaf(leaf) + try: + self.root.rmdir() + except OSError: + pass + + +REEXEC_SENTINEL = 'FUSE_TESTS_UNDER_SCOPE' # set on the re-exec'd child + + +def reexec_under_user_scope_if_needed() -> None: + """Re-exec under a systemd user scope so cgroup leaves have a writable + parent. Returns unchanged when already delegated or when systemd-run is + unavailable; replaces this process otherwise.""" + if not IS_LINUX or os.environ.get(REEXEC_SENTINEL): + return + if CgroupManager.delegated_base() is not None: + return + systemd_run = shutil.which('systemd-run') + if systemd_run is None: + return + os.environ[REEXEC_SENTINEL] = '1' + argv = [systemd_run, '--user', '--scope', '--quiet', + sys.executable, os.path.abspath(__file__)] + sys.argv[1:] + try: + os.execv(systemd_run, argv) + except OSError: + return + + +def config_path() -> Path: + """Path to the persisted runner config.""" + return Path.home() / '.config' / 'libfuse' / 'tests.conf' + + +def read_configured_base_dir() -> str | None: + """Return the persisted base_dir, or None if unset or unreadable.""" + import configparser + + parser = configparser.ConfigParser() + try: + parser.read(config_path()) + except (OSError, configparser.Error): + return None + return parser.get(_CONFIG_SECTION, _CONFIG_BASE_DIR_KEY, fallback=None) + + +def write_configured_base_dir(base_dir: str) -> None: + """Persist base_dir, preserving any other keys already in the file.""" + import configparser + + parser = configparser.ConfigParser() + path = config_path() + try: + parser.read(path) + except (OSError, configparser.Error): + pass + if not parser.has_section(_CONFIG_SECTION): + parser.add_section(_CONFIG_SECTION) + parser.set(_CONFIG_SECTION, _CONFIG_BASE_DIR_KEY, base_dir) + path.parent.mkdir(parents=True, exist_ok=True) + with path.open('w') as fh: + parser.write(fh) + + +def resolve_base_dir() -> Path: + """The base a per-run leaf is appended to.""" + return Path(read_configured_base_dir() or str(DEFAULT_BASE_DIR)) + + +def resolve_run_dir(args: argparse.Namespace) -> Path: + """Pick the run directory: --run-dir, $FUSE_TEST_RUN_DIR, config, default. + + --run-dir and $FUSE_TEST_RUN_DIR are used verbatim, because a caller that + names a directory means that directory: CI has to hand the path to a later + upload step, and `meson test` cannot pass arguments to a registered test, + so the env var is the only channel it has. + + The persisted config value and DEFAULT_BASE_DIR are *bases*: a + run--- leaf is appended so two runs on one machine + -- or two users sharing /var/tmp -- cannot land in the same directory. + """ + if args.run_dir: + return Path(args.run_dir) + env_run_dir = os.environ.get('FUSE_TEST_RUN_DIR') + if env_run_dir: + return Path(env_run_dir) + return resolve_base_dir() / run_leaf_name(args) + + +def make_run_dir(run_dir: Path) -> bool: + """mkdir the run directory, and say whether this call created it. + + Which matters at the end: a directory the runner made is its own to + delete, one it merely found is not. + """ + try: + run_dir.mkdir(parents=True) + except FileExistsError: + return False + return True + + +def resolve_valgrind() -> str: + """The command prefix every daemon launch gets, or "" when off. + + Resolved once here and exported, so the policy lives in one place and no + test re-implements the no/false/0 spellings. + """ + value = os.environ.get('TEST_WITH_VALGRIND', 'no').lower().strip() + return '' if value in ('no', 'false', '0') else VALGRIND_PREFIX + + +def run_leaf_name(args: argparse.Namespace) -> str: + """The per-run leaf appended to a base directory. + + The timestamp is second-resolution, so the pid is what keeps two runs + started in the same second apart: a shared leaf is a shared work + directory, log and pid file for every test, and the first run to finish + clean deletes them all. + + The io-uring invocation gets its own suffix, so which transport a kept + directory holds is visible without opening it. A verbatim --run-dir stays + verbatim; the caller chose that path. + """ + user = os.environ.get('USER') or os.environ.get('LOGNAME') or 'unknown' + mode = '-iouring' if getattr(args, 'io_uring', False) else '' + return f'run-{user}-{time.strftime("%y%m%d%H%M%S")}-{os.getpid()}{mode}' + + +def as_test_name(pattern: str, cases_dir: Path) -> str: + """A -t pattern as a discovery name, so shell completion can supply it: + "test/cases/examples/cuse.sh" and "examples/cuse" name the same test. + + Resolved as a path rather than matched as text, against the cwd first so a + completed path works from wherever it was completed, then against cases/ so + a bare "examples" means the same thing from every directory. A directory + selects what is under it. Anything that is not a path into cases/ is + returned as given and stays an fnmatch pattern. + """ + for base in (Path.cwd(), cases_dir): + resolved = (base / pattern).resolve() + if not resolved.is_relative_to(cases_dir): + continue + rel = resolved.relative_to(cases_dir) + if resolved.is_dir(): + return str(rel / '*') + return str(rel.with_suffix('')) if rel.suffix == '.sh' else str(rel) + return pattern + + +def is_shell_exception_dir(directory: Path) -> bool: + """True when the cases/**/*.sh rule does not apply inside *directory*. + + The runner decides what a file is by where it sits, so the exceptions are + listed once here. Today every one of them holds binary markers. + """ + return directory.name in ('unit',) + + +def shell_scripts(cases_dir: Path) -> list: + """Executable cases/**/*.sh, outside cases/lib and the shell-exception + directories.""" + return [path for path in cases_dir.rglob('*.sh') + if LIB_DIR not in path.parents + and not is_shell_exception_dir(path.parent) + and os.access(path, os.X_OK)] + + +def binary_markers(cases_dir: Path) -> list: + """

    / -- one marker per C test that needs no script. + + A binary name has no suffix, so a suffixed entry is malformed and is said + so rather than skipped. Only .md is allowed, for documentation. + """ + markers = [] + for directory in sorted(cases_dir.iterdir()): + if not directory.is_dir() or not is_shell_exception_dir(directory): + continue + for path in sorted(directory.iterdir()): + if path.name.startswith('.') or path.suffix == '.md': + continue + if not path.is_file() or path.suffix: + sys.exit(f'{path}: not a binary name; ' + f'{directory} holds markers only') + markers.append(path) + return markers + + +def discover(cases_dir: Path, patterns: list, groups: set, + exclude: set) -> list: + """Executable cases/**/*.sh plus the binary markers, filtered by fnmatch + patterns, GROUP membership, and the exclude set (CLI -X plus + test/exclude).""" + patterns = [as_test_name(p, cases_dir) for p in patterns] + names, specs = [], [] + for script in sorted([*shell_scripts(cases_dir), + *binary_markers(cases_dir)]): + spec = parse_spec(script, cases_dir) + names.append(spec.name) + if spec.name in exclude: + continue + if patterns and not any(fnmatch.fnmatch(spec.name, p) + for p in patterns): + continue + if groups and not (groups & spec.groups): + continue + specs.append(spec) + # Checked against every name rather than against the selection, so that -g + # or -X narrowing the result to nothing is not reported as a bad pattern. + for pattern in patterns: + if not any(fnmatch.fnmatch(name, pattern) for name in names): + sys.exit(f'{pattern}: matches no test') + return specs + + +class TestRunner: + """Runs TestSpecs, one worker thread each, and reports.""" + + def __init__(self, build_dir: Path, run_dir: Path, run_dir_created: bool, + jobs: int, keep_logs: bool, verbose: bool, + io_uring: bool = False, io_uring_depth: int | None = None): + self.build_dir = build_dir + self.run_dir = run_dir + self.run_dir_created = run_dir_created + self.jobs = jobs + self.keep_logs = keep_logs + self.verbose = verbose + self.io_uring = io_uring + self.io_uring_depth = io_uring_depth + self.valgrind = resolve_valgrind() + self.core_pattern = read_core_pattern() + self.fuse_caps = self.read_fuse_caps() + self.sync_init = self.read_sync_init() + self._cgroup = CgroupManager.create(str(os.getpid())) + self._stop = threading.Event() + self._print_lock = threading.Lock() + self._gdb = shutil.which('gdb') + # Overwritten per run_all() call; set here so run_one() always has a + # reference even if it is ever driven without run_all(). + self._run_started = time.monotonic() + self.report_environment() + + # ---------------------------------------------------------------- startup + + def build_path(self, base: str | None = None) -> str: + """$PATH with the build tree's util/ and example/ ahead of *base*. + + util/ first for anything that spawns the mount helper by name: an + installed one older than the build tree makes printcap emit + "unrecognized option '--sync-init'", and a tree whose helper is not + installed system-wide at all would find no helper whatsoever. + """ + if base is None: + base = os.environ.get('PATH', '') + return os.pathsep.join([str(self.build_dir / 'util'), + str(self.build_dir / 'example'), base]) + + def read_fuse_caps(self) -> frozenset: + """The FUSE_CAP_* names printcap reports, read once per run. + + printcap lives in example/, not util/, so this works the same way on + every platform. It mounts to negotiate, so it needs the same $PATH a + test gets rather than the ambient one -- otherwise it reaches for a + mount helper that this build may not have installed anywhere, and + every _require_cap test silently skips. + """ + printcap = self.build_dir / 'example' / 'printcap' + if not os.access(printcap, os.X_OK): + print(f'note: {printcap} not built; no capability is available ' + 'and every _require_cap test will skip') + return frozenset() + proc = subprocess.run([str(printcap)], capture_output=True, text=True, + timeout=30, check=False, + env=dict(os.environ, PATH=self.build_path())) + if proc.returncode != 0: + print(f'note: printcap failed ({proc.returncode}); ' + 'every _require_cap test will skip') + return frozenset() + return frozenset(line.strip() for line in proc.stdout.splitlines() + if line.startswith('\t')) + + def read_sync_init(self) -> str: + """Which -Dsync-init the library was built with: auto, always, never. + + A property of the build, not of the run, so it is read where the + io-uring preflight reads HAVE_URING rather than selected on the + command line. + """ + try: + config = (self.build_dir / 'fuse_config.h').read_text() + except OSError: + return 'unknown' + for define, mode in ((SYNC_INIT_ENABLED, 'always'), + (SYNC_INIT_DISABLED, 'never')): + if define in config: + return mode + return 'auto' + + def preflight_io_uring(self) -> str: + """Return "" when the io-uring transport can actually be exercised, + else the reason the whole invocation skips with exit 77. + + Checked in this order, so the message names the first thing that is + actually wrong. + """ + if not IS_LINUX: + return f'fuse-io-uring is Linux-only, this is {platform.system()}' + try: + config = (self.build_dir / 'fuse_config.h').read_text() + except OSError: + return f'{self.build_dir}/fuse_config.h is unreadable' + if 'HAVE_URING' not in config: + return 'the library was built with -Denable-io-uring=false' + if IO_URING_CAP in self.fuse_caps: + return '' + # printcap reports capable_ext, so the capability's absence *is* the + # kernel's answer. The module parameter only tells the two reasons + # apart. + try: + enabled = FUSE_URING_PARAM.read_text().strip() + except OSError: + return 'this kernel has no fuse io-uring support' + if enabled in ('N', '0'): + return ('io-uring is disabled in the fuse module\n' + f' echo Y | sudo tee {FUSE_URING_PARAM}') + return f'the kernel did not offer {IO_URING_CAP}' + + def report_environment(self) -> None: + """Say what will silently degrade before any test runs.""" + if self.core_pattern.startswith('|'): + print('note: core_pattern is piped to systemd-coredump; cores ' + 'will be fetched via coredumpctl. For in-workdir cores run:') + print(' sudo sysctl -w kernel.core_pattern=core.%e.%p') + if self._gdb is None: + print('note: gdb not found; cores will be kept but not backtraced') + + # ------------------------------------------------------------ preparation + + def prepare(self, spec: TestSpec): + """Create the workdir tree, build the env dict, allocate the cgroup + leaf. Returns (workdir, env, leaf).""" + workdir = self.run_dir / spec.name + logs = workdir / 'logs' + for sub in ('mnt', 'src', 'tmp', 'logs'): + (workdir / sub).mkdir(parents=True, exist_ok=True) + + util_dir = self.build_dir / 'util' + example_dir = self.build_dir / 'example' + env = dict(os.environ) + env.update({ + 'TEST_NAME': spec.name, + 'TEST_SCRIPT': str(spec.script), + 'TEST_DIR': str(TEST_DIR), + 'TEST_LIB': str(LIB_DIR), + 'TEST_WORKDIR': str(workdir), + 'TEST_MNT': str(workdir / 'mnt'), + 'TEST_SRC': str(workdir / 'src'), + 'TEST_TMP': str(workdir / 'tmp'), + 'TEST_LOGDIR': str(logs), + 'BUILD_DIR': str(self.build_dir), + 'FUSE_EXAMPLE_DIR': str(example_dir), + 'FUSE_TEST_BIN_DIR': str(self.build_dir / 'test'), + 'FUSE_UTIL_DIR': str(util_dir), + 'FUSE_CAPS': ' '.join(sorted(self.fuse_caps)), + 'FUSE_UID': str(os.geteuid()), + 'FUSE_OS': platform.system(), + # Exported as 0 rather than left unset: fuse_session_new() reads it + # from the environment, so a value in the developer's shell would + # otherwise change what the default run tests without saying so. + 'FUSE_URING_ENABLE': '1' if self.io_uring else '0', + # Asked of every session, not only the ones a mount verb started, + # so the transport a test never launched through the shell -- a + # self-mounting C test, a daemon the script backgrounded itself -- + # is on the record too. It lands in that daemon's log, because + # none of them redirects fuse_log() anywhere else. + 'FUSE_INIT_STATUS': '1', + 'FUSE_VALGRIND': self.valgrind, + }) + if self.io_uring_depth is not None: + env['FUSE_URING_QUEUE_DEPTH'] = str(self.io_uring_depth) + env['PATH'] = self.build_path(env.get('PATH', '')) + return workdir, env, self._cgroup.new_leaf() + + def child_argv(self, spec: TestSpec) -> list: + """The command the launcher execs for *spec*. + + bash -e for a script: errexit is the runner's rule, not something 88 + files have to remember. A marker names a binary, which the launcher + execs itself; there is no shell in between to impose errexit on, and + none is wanted -- the binary's exit status is already the verdict. + """ + if spec.binary: + return [str(self.build_dir / 'test' / spec.binary)] + return ['bash', '-e', str(spec.script)] + + def write_repro(self, spec: TestSpec, workdir: Path, env: dict) -> None: + """Write logs/repro.sh: the env deltas, the cd, and the command. + + It execs what the launcher execs, so the reproducer cannot pass where + the run failed. + """ + import shlex + + lines = ['#!/bin/sh', + '# Re-run this one test by hand, with the environment the', + '# runner built for it.', + ''] + for key, value in sorted(env.items()): + if os.environ.get(key) == value and key != 'PATH': + continue + lines.append(f'export {key}={shlex.quote(value)}') + lines += ['', + f'cd {shlex.quote(str(workdir / "logs"))}', + 'exec ' + shlex.join(self.child_argv(spec)), + ''] + repro = workdir / 'logs' / 'repro.sh' + repro.write_text('\n'.join(lines)) + repro.chmod(0o755) + + # -------------------------------------------------------------- execution + + def _stream(self, proc: subprocess.Popen, logs: Path, name: str, + started: float) -> None: + """Copy the script's merged output to logs/script.out until EOF, and + a copy prefixed with elapsed time to logs/timestamps.out. + + A timed-out test's script.out has no way to tell "stalled right + after the last line" from "was still grinding right up to the + kill" -- the CI step that prints it afterwards timestamps when it + printed, not when the test produced it. timestamps.out is named to + pick up the same "*.out" glob so CI shows it for free. + """ + with (logs / 'script.out').open('wb') as out, \ + (logs / 'timestamps.out').open('wb') as timestamps: + for line in proc.stdout: + out.write(line) + out.flush() + elapsed = time.monotonic() - started + timestamps.write(f'+{elapsed:8.3f}s '.encode() + line) + timestamps.flush() + if self.verbose: + text = line.decode('utf8', errors='replace').rstrip('\n') + with self._print_lock: + print(f'[{name}] {text}') + proc.stdout.close() + + def _kill(self, proc: subprocess.Popen, leaf: Path | None) -> None: + """Stop a timed-out or interrupted test: cgroup.kill on the leaf when + there is one, else killpg() on the session the script leads.""" + if leaf is not None: + try: + (leaf / 'cgroup.kill').write_text('1\n') + return + except OSError: + pass + for sig in (signal.SIGTERM, signal.SIGKILL): + try: + os.killpg(proc.pid, sig) + except OSError: + return + try: + proc.wait(timeout=5.0) + return + except subprocess.TimeoutExpired: + continue + + def dump_stacks(self, proc: subprocess.Popen, leaf: Path | None, + logs: Path) -> None: + """On a timeout, before _kill() tears the containment down, write a + snapshot of the whole process table, plus every thread's kernel stack + and (when gdb is available) user backtrace for every process still in + the containment. + + A thread blocked in an uninterruptible syscall never answers + PTRACE_ATTACH, so the kernel stack is taken first -- it needs no + attach and is often the only trace such a thread ever yields. The + process table is taken before either, while nothing has stopped a + thread yet. + """ + self._dump_process_table(logs) + for pid in self._containment_pids(proc, leaf): + self._dump_kernel_stacks(pid, logs) + if self._gdb is not None: + self._dump_gdb_backtrace(pid, logs) + + @staticmethod + def _dump_process_table(logs: Path) -> None: + """ps auxwww, so a hang that involves a process outside the test's own + containment -- a daemon an earlier test leaked, say -- is still + visible in the report. + """ + result = subprocess.run(['ps', 'auxwww'], capture_output=True, + text=True, check=False) + (logs / 'ps.txt').write_text(result.stdout + result.stderr) + + @staticmethod + def _proc_identity(pid: int) -> str: + """A " (): " header line for a dump. + + Which pid was the daemon and which the client blocked on it is the + first question asked of a stack dump, and a bare pid answers it for + nobody reading the report minutes or days later. + """ + try: + comm = Path(f'/proc/{pid}/comm').read_text().strip() + except OSError: + comm = '?' + try: + raw = Path(f'/proc/{pid}/cmdline').read_bytes() + cmdline = raw.decode('utf8', errors='replace').replace('\0', ' ') + except OSError: + cmdline = '' + # An argument may hold a newline; a header that is not one line + # breaks every reader that greps the dump for its pid. + return f'=== pid {pid} ({comm}): {" ".join(cmdline.split())} ===' + + @staticmethod + def _as_text(data) -> str: + """TimeoutExpired carries its partial output undecoded even when the + run was in text mode.""" + if data is None: + return '' + return data if isinstance(data, str) \ + else data.decode('utf8', errors='replace') + + @staticmethod + def _uninterruptible_tids(pid: int) -> list: + """Threads of *pid* in uninterruptible sleep, D in /proc. + + Such a thread never answers PTRACE_ATTACH, so gdb waits on it until + GDB_TIMEOUT kills it. That is per pid, and a wedged filesystem leaves + several -- the client blocked on it and the daemon's own threads -- so + the dump can outlast the timeout that asked for it by minutes. + """ + wedged = [] + try: + tids = sorted(int(tid) for tid in os.listdir(f'/proc/{pid}/task')) + except OSError: + return wedged + for tid in tids: + try: + status = Path(f'/proc/{pid}/task/{tid}/status').read_text() + except OSError: + continue # exited between listdir() and here + for line in status.splitlines(): + if line.startswith('State:'): + if line.split()[1] == 'D': + wedged.append(tid) + break + return wedged + + @staticmethod + def _containment_pids(proc: subprocess.Popen, leaf: Path | None) -> list: + """Every pid sharing the test's cgroup leaf, or just proc.pid when + cgroups are disabled.""" + if leaf is not None: + try: + pids = [int(line) for line in + (leaf / 'cgroup.procs').read_text().split()] + if pids: + return pids + except OSError: + pass + return [proc.pid] + + @staticmethod + def _dump_kernel_stacks(pid: int, logs: Path) -> None: + """sudo cat /proc//task/*/stack for every thread of *pid*. + + /proc//task//stack is gated on CAP_SYS_ADMIN unconditionally + -- unlike ptrace, ownership and Yama's exceptions are not enough -- + to keep it from leaking kernel addresses. sudo is what gets it on a + non-root run; -n so a runner without passwordless sudo fails the read + immediately instead of blocking on a password prompt nothing answers. + """ + task_dir = Path(f'/proc/{pid}/task') + try: + tids = sorted(int(tid) for tid in os.listdir(task_dir)) + except OSError: + return + lines = [TestRunner._proc_identity(pid)] + for tid in tids: + stack_path = task_dir / str(tid) / 'stack' + result = subprocess.run(['sudo', '-n', 'cat', str(stack_path)], + capture_output=True, text=True, + check=False) + stack = result.stdout if result.returncode == 0 else \ + f'(unreadable: {result.stderr.strip() or result.returncode})\n' + lines.append(f'=== tid {tid} ===\n{stack}') + (logs / f'kstack.{pid}.txt').write_text('\n'.join(lines)) + + def _dump_gdb_backtrace(self, pid: int, logs: Path) -> None: + """sudo gdb -p backtraces, written beside the kernel stacks. + + Bare "bt" for every thread first and "bt full" only after it: the + locals are worth having, but they bury the frame list they belong to + under pages of struct dumps, and the frame list is what is read first. + + sudo, not a Yama prctl exception: the wedged pid is as likely to be a + daemon the test forked after launch as the test's own exec-chain, and + an exception only covers the specific task that calls it, not + children it forks afterwards. Root's CAP_SYS_PTRACE reaches either + one uniformly. Best-effort: a thread ptrace cannot stop, or a runner + without passwordless sudo, just leaves gdb's or sudo's own error text + in the file. + + Skipped entirely where a thread is already in uninterruptible sleep: + the attach cannot complete, so the whole GDB_TIMEOUT buys one line + saying gdb was killed, and the kernel stack taken just before is what + such a thread yields instead. + """ + wedged = self._uninterruptible_tids(pid) + if wedged: + tids = ' '.join(str(tid) for tid in wedged) + (logs / f'gdbstack.{pid}.txt').write_text( + f'{self._proc_identity(pid)}\n' + f'(skipped: tid {tids} in uninterruptible sleep, so the ' + f'attach cannot complete -- see kstack.{pid}.txt)\n') + return + argv = ['sudo', '-n', self._gdb, '--batch', '--nx', + '-ex', 'set pagination off', + '-ex', 'thread apply all bt', + '-ex', 'set print pretty on', + '-ex', 'thread apply all bt full', + '-p', str(pid)] + try: + proc = subprocess.run(argv, capture_output=True, text=True, + timeout=GDB_TIMEOUT, check=False) + out = proc.stdout + proc.stderr + except subprocess.TimeoutExpired as expired: + # A pid gdb cannot finish with is the interesting one often + # enough that whatever it did print has to be kept. + out = (self._as_text(expired.stdout) + + self._as_text(expired.stderr) + + f'\n(gdb killed after {GDB_TIMEOUT:.0f}s)\n') + (logs / f'gdbstack.{pid}.txt').write_text( + f'{self._proc_identity(pid)}\n{out}') + + def _log_started(self, name: str) -> None: + """A "test X is now running" line, timestamped against the whole + run's own start rather than the test's -- so under -j the console + log (which CI keeps regardless of pass/fail) shows what was in + flight and since when, not only what has finished. + """ + elapsed = time.monotonic() - self._run_started + with self._print_lock: + print(f'+{elapsed:8.3f}s START {name}') + sys.stdout.flush() + + def run_one(self, spec: TestSpec) -> TestResult: + """Spawn the test, enforce spec.timeout, collect the result.""" + workdir, env, leaf = self.prepare(spec) + self.write_repro(spec, workdir, env) + logs = workdir / 'logs' + timeout = self.effective_timeout(spec) + started = time.monotonic() + self._log_started(spec.name) + + proc = subprocess.Popen( + ['/bin/sh', '-c', LAUNCHER, 'run-tests', + self._cgroup.procs_path(leaf), *self.child_argv(spec)], + env=env, cwd=str(logs), + stdout=subprocess.PIPE, stderr=subprocess.STDOUT, + start_new_session=True) + # exec keeps the pid, so this is still the script and still the group + # leader that "kill -- -$(cat pid)" targets. + (workdir / 'pid').write_text(f'{proc.pid}\n') + if leaf is not None: + (workdir / 'cgroup').write_text(f'{leaf}\n') + + # _stream blocks until EOF, so read in a thread and wait with a + # deadline here; that is what makes the timeout a hard wall-clock bound. + reader = threading.Thread(target=self._stream, + args=(proc, logs, spec.name, started), + daemon=True) + reader.start() + reader.join(timeout=timeout) + reason = '' + if reader.is_alive(): + reason = f'timeout after {timeout:.0f}s' + self.dump_stacks(proc, leaf, logs) + self._kill(proc, leaf) + reader.join(timeout=5.0) + try: + proc.wait(timeout=REAP_TIMEOUT) + except subprocess.TimeoutExpired: + # Report it and carry on: the run is worth more than this one + # process, and everything worth reading is already on disk. + unkillable = f'unkillable after {REAP_TIMEOUT:.0f}s' + reason = f'{reason}, {unkillable}' if reason else unkillable + # Always reap the subtree: a passing script may still have leaked a + # daemon, and that would wedge the next test on the same mountpoint. + self._cgroup.kill_leaf(leaf) + + duration = time.monotonic() - started + cores = self.collect_cores(workdir, leaf) + suspicious = self.scan_logs(workdir) if proc.returncode == 0 else '' + result = self.classify(spec, workdir, proc.returncode, duration, + reason, cores, suspicious) + (workdir / 'status').write_text( + f'{result.status} {result.duration:.3f} {result.reason}\n'.rstrip() + + '\n') + return result + + def effective_timeout(self, spec: TestSpec) -> float: + """The wall-clock bound actually enforced for *spec*.""" + if self.valgrind: + return spec.timeout * VALGRIND_TIMEOUT_FACTOR + return spec.timeout + + def classify(self, spec: TestSpec, workdir: Path, code: int, + duration: float, reason: str, cores: list, + suspicious: str) -> TestResult: + """Turn an exit status plus the collected evidence into a verdict.""" + unexpected = [c for c in cores + if not self.core_is_allowed(workdir, c)] + transport = self.wrong_transport(workdir) + if reason: # killed by the runner + status = 'FAIL' + elif code == SKIP_EXIT_CODE: + status, reason = 'SKIP', self.skip_reason(workdir) + elif code == LAUNCH_FAILED_EXIT_CODE and self.launch_failed(workdir): + status, reason = 'FAIL', 'could not establish containment' + elif code != 0: + status = 'FAIL' + reason = (f'killed by signal {-code}' if code < 0 + else f'exit {code}') + elif unexpected: + core = unexpected[0] + status = 'FAIL' + reason = f'core: {core.executable} (pid {core.pid})' + elif suspicious: + status, reason = 'FAIL', f'suspicious output: {suspicious}' + elif transport: + status, reason = 'FAIL', transport + else: + status = 'PASS' + return TestResult(name=spec.name, status=status, duration=duration, + exit_code=code, reason=reason, workdir=workdir, + cores=unexpected, suspicious=suspicious) + + @staticmethod + def skip_reason(workdir: Path) -> str: + """The reason _notrun printed, taken from the script's own output.""" + try: + text = (workdir / 'logs' / 'script.out').read_text(errors='replace') + except OSError: + return '' + for line in reversed(text.splitlines()): + if line.startswith('SKIP: '): + return line[len('SKIP: '):] + return '' + + def wrong_transport(self, workdir: Path) -> str: + """Under --io-uring, the first session that did not get a ring, or "". + + A test that started no session reports nothing and so has nothing to + prove. off:custom_io is the one refusal a daemon cannot avoid: the + ring is issued against the session's own /dev/fuse fd, which a + custom-io session does not have. + + timestamps.out holds the same lines behind an elapsed-time prefix, so + matching on the start of the line reports each session once. + """ + if not self.io_uring: + return '' + for out in sorted((workdir / 'logs').glob('*.out')): + try: + text = out.read_text(errors='replace') + except OSError: + continue + for line in text.splitlines(): + if (line.startswith(IO_URING_STATE_KEY) + and line not in IO_URING_STATES_OK): + return f'{out.name}: {line}' + return '' + + @staticmethod + def launch_failed(workdir: Path) -> bool: + """True when exit 125 came from the launcher, not from the test.""" + try: + text = (workdir / 'logs' / 'script.out').read_text(errors='replace') + except OSError: + return False + return LAUNCH_FAILED_MARKER in text + + # ----------------------------------------------------------- log scanning + + def scan_logs(self, workdir: Path) -> str: + """Strip logs/.allow patterns, then look for suspicious words in every + logs/*.out. Returns the first offending line, or "".""" + logs = workdir / 'logs' + allow = [] + try: + for line in (logs / '.allow').read_text().splitlines(): + if line.strip(): + allow.append(re.compile(line, re.MULTILINE)) + except OSError: + pass + words = [re.compile(r'\b{}\b'.format(w), re.IGNORECASE | re.MULTILINE) + for w in self.suspicious_words()] + for out in sorted(logs.glob('*.out')): + try: + buf = out.read_text(errors='replace') + except OSError: + continue + for pattern in allow: + buf = pattern.sub('', buf) + buf = _FUSE_DEBUG_RE.sub('', buf) + buf = self.strip_extra(buf) + for word in words: + hit = word.search(buf) + if hit: + line_start = buf.rfind('\n', 0, hit.start()) + 1 + line_end = buf.find('\n', hit.end()) + if line_end == -1: + line_end = len(buf) + return f'{out.name}: {buf[line_start:line_end].strip()}' + return '' + + def suspicious_words(self) -> tuple: + """The word list scan_logs() looks for. + + Under --io-uring it gains the message libfuse prints before falling + back to /dev/fuse. The status file keeps only the last session of a + binary that runs several, so the log stays the backstop for the + earlier ones. + """ + if self.io_uring: + return SUSPICIOUS_WORDS + (re.escape(IO_URING_FALLBACK),) + return SUSPICIOUS_WORDS + + def strip_extra(self, buf: str) -> str: + """Drop the noise a mode adds to every daemon's log. + + Valgrind's own lines go before the word scan. Built in rather than a + per-test fuse_allow_output, because it applies to every test whenever + valgrind is on. + """ + if self.valgrind: + return _VALGRIND_LINE_RE.sub('', buf) + return buf + + # ---------------------------------------------------------- core handling + + @staticmethod + def core_is_allowed(workdir: Path, core: CoreDump) -> bool: + """True when the test declared a core from this executable expected.""" + try: + names = (workdir / 'logs' / '.allow-core').read_text().split() + except OSError: + return False + return core.executable in names + + def resolve_binary(self, executable: str) -> Path | None: + """Map a core's %e back to the build-tree binary that produced it. + + %e is a bare basename, so search the three directories the suite runs + binaries from. An unrecognised name (a system tool, or %e truncated at + 16 chars) yields None; gdb still produces a usable backtrace from the + core alone, just without symbol names for the main executable. + """ + for directory in (self.build_dir / 'example', + self.build_dir / 'test', + self.build_dir / 'util'): + candidate = directory / executable + if candidate.is_file(): + return candidate + return None + + def collect_cores(self, workdir: Path, leaf: Path | None) -> list: + """Every core matching CORE_GLOBS in logs/, backtraced. + + When core_pattern is a pipe, nothing lands in logs/ at all. The + fallback then asks the journal for cores recorded against *this test's* + cgroup -- COREDUMP_CGROUP, which is exactly the leaf written to + $TEST_WORKDIR/cgroup. A time-window query is deliberately not used: + under -j the test intervals overlap, so one crash would be handed to + every worker whose window contains it and fail unrelated tests. + + With no leaf to match on -- cgroups disabled, or not Linux -- the + fallback yields nothing and the startup note is the only signal. CI + sets a relative pattern on both platforms, so this costs no CI + coverage. + """ + logs = workdir / 'logs' + found = [] + for glob in CORE_GLOBS: + for path in sorted(logs.glob(glob)): + found.append(self._core_from_name(path)) + if not found and self.core_pattern.startswith('|'): + found = self._cores_from_journal(logs, leaf) + for core in found: + core.binary = self.resolve_binary(core.executable) + self.backtrace(core) + return found + + @staticmethod + def _core_from_name(path: Path) -> CoreDump: + """Parse a core file name into the %e and %p the pattern encoded. + + core.. is what CI configures and .core is FreeBSD's + default, but a developer machine often has a bare "core" or "core." + pattern, which names no executable at all. Every shape still yields a + CoreDump -- the executable is only used to find symbols for gdb. + """ + parts = path.name.split('.') + if parts[0] == 'core': + if len(parts) >= 3 and parts[-1].isdigit(): + return CoreDump(path=path, executable='.'.join(parts[1:-1]), + pid=int(parts[-1])) + if len(parts) == 2 and parts[1].isdigit(): + return CoreDump(path=path, executable='unknown', + pid=int(parts[1])) + if len(parts) == 1: + return CoreDump(path=path, executable='unknown', pid=0) + if path.name.endswith('.core'): + return CoreDump(path=path, executable=path.name[:-len('.core')], + pid=0) + return CoreDump(path=path, executable=path.name, pid=0) + + def _cores_from_journal(self, logs: Path, leaf: Path | None) -> list: + """Cores systemd-coredump captured for this test's cgroup.""" + if leaf is None or not IS_LINUX or shutil.which('coredumpctl') is None: + return [] + import json + + proc = subprocess.run( + ['journalctl', '-t', 'systemd-coredump', + f'COREDUMP_CGROUP={self._cgroup_relative(leaf)}', '-o', 'json'], + capture_output=True, text=True, check=False) + cores = [] + for line in proc.stdout.splitlines(): + try: + entry = json.loads(line) + except ValueError: + continue + pid = int(entry.get('COREDUMP_PID', 0)) + exe = os.path.basename(entry.get('COREDUMP_COMM', '') or '') + if not pid: + continue + path = logs / f'core.{exe}.{pid}' + dumped = subprocess.run( + ['coredumpctl', 'dump', str(pid), '--output', str(path)], + capture_output=True, check=False) + if dumped.returncode == 0 and path.exists(): + cores.append(CoreDump(path=path, executable=exe, pid=pid)) + return cores + + @staticmethod + def _cgroup_relative(leaf: Path) -> str: + """The leaf path as the journal records it (without the mount point).""" + return '/' + str(leaf.relative_to('/sys/fs/cgroup')) + + def backtrace(self, core: CoreDump) -> None: + """Run gdb on *core*, write the full backtrace beside it as + backtrace...txt, and keep the top frames for the summary. + + "thread apply all bt full" rather than a plain bt: a FUSE daemon is + multi-threaded and the crashing thread is rarely the only interesting + one. --nx so a developer's ~/.gdbinit cannot change the output. + """ + if self._gdb is None: + return + argv = [self._gdb, '--batch', '--nx', + '-ex', 'set pagination off', + '-ex', 'set print pretty on', + '-ex', 'thread apply all bt full'] + argv += ['--', str(core.binary)] if core.binary else [] + argv += [str(core.path)] + try: + proc = subprocess.run(argv, capture_output=True, text=True, + timeout=GDB_TIMEOUT, check=False) + except subprocess.TimeoutExpired: + return + out = core.path.parent / f'backtrace.{core.executable}.{core.pid}.txt' + out.write_text(proc.stdout + proc.stderr) + core.backtrace = out + frames = [line for line in proc.stdout.splitlines() + if line.startswith('#')] + core.top_frames = '\n'.join(frames[:GDB_TOP_FRAMES]) + if len(frames) > GDB_TOP_FRAMES: + core.top_frames += (f'\n ... {len(frames) - GDB_TOP_FRAMES} ' + f'more frames, full: {out}') + + # ------------------------------------------------------------- scheduling + + def run_all(self, specs: list) -> int: + """Run the serial group first with one job, then fan the rest out. + Returns 0 when nothing failed.""" + self._run_started = time.monotonic() + serial = [s for s in specs if SERIAL_GROUP in s.groups] + parallel = [s for s in specs if SERIAL_GROUP not in s.groups] + results = self._run_batch(serial, 1) + \ + self._run_batch(parallel, self.jobs) + return self.summarise(results, time.monotonic() - self._run_started) + + def _run_batch(self, specs: list, jobs: int) -> list: + """ThreadPoolExecutor over specs; print a live line per finished test.""" + results = [] + if not specs: + return results + # as_completed() rather than map(): map yields in submission order, so + # a finished test would be reported -- and its workdir freed -- only + # once every test submitted before it has landed. + with ThreadPoolExecutor(max_workers=jobs) as pool: + futures = [pool.submit(self._run_guarded, spec) for spec in specs] + for future in as_completed(futures): + result = future.result() + if result is not None: + results.append(result) + self.report(result) + self.discard_workdir(result) + return results + + def _run_guarded(self, spec: TestSpec): + """run_one(), unless SIGINT already asked the run to stop.""" + if self._stop.is_set(): + return None + return self.run_one(spec) + + def report(self, result: TestResult) -> None: + """One line per finished test, plus where to look when it failed.""" + elapsed = time.monotonic() - self._run_started + with self._print_lock: + line = (f'+{elapsed:8.3f}s {result.status:<5} {result.name:<50} ' + f'{result.duration:6.2f}s') + if result.reason: + line += f' {result.reason}' + print(line) + if result.status == 'FAIL': + print(f' logs: {result.workdir / "logs"}') + sys.stdout.flush() + + def discard_workdir(self, result: TestResult) -> None: + """Drop a finished test's directory unless it failed. + + Only a failure is ever read, and deleting as each test lands keeps the + run's disk use flat instead of holding all of them to the end. + """ + if self.keep_logs or result.status == 'FAIL': + return + shutil.rmtree(result.workdir, ignore_errors=True) + + def discard_run_dir(self, results: list) -> None: + """Drop the run directory after a clean run, but only what this run + made. + + --run-dir is used verbatim, so it can name a directory that was + already there holding anything at all; rmtree on that is data loss, + not cleanup. discard_workdir() has taken each test's own directory + by now, so all that is left of this run are the empty parents the + test names needed. + """ + if self.run_dir_created: + shutil.rmtree(self.run_dir, ignore_errors=True) + return + for result in results: + directory = result.workdir.parent + while directory != self.run_dir: + try: + directory.rmdir() # empty only; never recursive + except OSError: + break + directory = directory.parent + + def mode_note(self) -> str: + """Extra text for the summary header naming a non-default mode. + + The io-uring invocation selects nothing, so its counts must match the + default run's; without the label the two are indistinguishable in a CI + log. A -Dsync-init build is invisible for the same reason. + """ + note = ', io-uring' if self.io_uring else '' + if self.sync_init != 'auto': + note += f', sync-init={self.sync_init}' + return note + + def summarise(self, results: list, elapsed: float) -> int: + """Print the counts, the slowest tests and every failure.""" + passed = [r for r in results if r.status == 'PASS'] + failed = [r for r in results if r.status == 'FAIL'] + skipped = [r for r in results if r.status == 'SKIP'] + print() + print(f'{len(results)} tests: {len(passed)} passed, {len(failed)} ' + f'failed, {len(skipped)} skipped in {elapsed:.1f}s ' + f'(-j{self.jobs}{self.mode_note()})') + + slowest = sorted(results, key=lambda r: r.duration, reverse=True)[:5] + if slowest: + print() + print('slowest:') + for result in slowest: + print(f' {result.duration:5.1f}s {result.name}') + + if failed: + print() + print('failed:') + for result in failed: + print(f' {result.name} {result.reason}') + print(f' logs: {result.workdir / "logs" / "script.out"}') + for core in result.cores: + if core.top_frames: + for frame in core.top_frames.splitlines(): + print(f' {frame}') + elif core.backtrace is None: + print(f' core kept, not backtraced: {core.path}') + print(f' repro: sh {result.workdir / "logs" / "repro.sh"}') + + if not failed and not self.keep_logs: + self.discard_run_dir(results) + else: + print() + print(f'test output kept in {self.run_dir}') + return 1 if failed else 0 + + def stop(self) -> None: + """Ask the workers to stop launching new tests.""" + self._stop.set() + + def stopping(self) -> bool: + """True once stop() has been asked for.""" + return self._stop.is_set() + + def cleanup(self) -> None: + self._cgroup.cleanup() + + +def read_exclude_file() -> set: + """One test name per line; blank lines and #-comments ignored, missing + file is fine.""" + try: + text = EXCLUDE_FILE.read_text() + except OSError: + return set() + names = set() + for line in text.splitlines(): + line = line.split('#', 1)[0].strip() + if line: + names.add(line) + return names + + +def parse_args(argv: list) -> argparse.Namespace: + parser = argparse.ArgumentParser( + description='Run the libfuse shell test suite.') + parser.add_argument('-j', '--jobs', type=int, + default=max(1, (os.cpu_count() or 2) // 2), + help='parallel tests (default: cpu_count // 2)') + parser.add_argument('-t', '--test', action='append', default=[], + metavar='PATTERN', + help='fnmatch against the test name, or the path to ' + 'a case; repeatable') + parser.add_argument('-g', '--group', action='append', default=[], + help='only tests declaring "# GROUP: "') + parser.add_argument('-X', '--exclude', action='append', default=[], + metavar='NAME', + help='skip this test; repeatable, adds to test/exclude') + parser.add_argument('--build-dir', default='.', + help='meson build root (default: .)') + parser.add_argument('--setuid-helpers', action='store_true', + help="chown root:root + chmod 4755 the build tree's " + 'fusermount3 (needs sudo); without it an ' + 'unprivileged run skips every mounting test') + parser.add_argument('--run-dir', default=None, + help='where to write per-test output, verbatim') + parser.add_argument('--persist-base-dir', default=None, metavar='DIR', + help='write DIR to ~/.config/libfuse/tests.conf ' + 'and exit') + parser.add_argument('--print-base-dir', action='store_true', + help='print the resolved base directory and exit') + parser.add_argument('--timeout', type=float, default=None, + metavar='SECS', help="override every script's timeout") + parser.add_argument('--io-uring', action='store_true', + help='run every test over fuse-io-uring; exits 77 ' + 'when the kernel or the build does not offer it') + parser.add_argument('--io-uring-queue-depth', type=int, default=None, + metavar='N', + help="export FUSE_URING_QUEUE_DEPTH; default is " + "libfuse's 8") + parser.add_argument('-l', '--list', action='store_true', + help='print the selected tests and exit') + parser.add_argument('-v', '--verbose', action='store_true', + help="stream each script's output live") + parser.add_argument('--keep-logs', action='store_true', + help="keep every test's output, not just the failures'") + parser.add_argument('--repeat', type=int, default=1, metavar='N', + help='run the selection N times') + return parser.parse_args(argv) + + +def main(argv: list) -> int: + args = parse_args(argv) + + if args.persist_base_dir: + write_configured_base_dir(args.persist_base_dir) + return 0 + if args.print_base_dir: + print(resolve_base_dir()) + return 0 + + build_dir = Path(args.build_dir).resolve() + exclude = read_exclude_file() | set(args.exclude) + + if args.list: + for spec in discover(CASES_DIR, args.test, set(args.group), exclude): + print(spec.name) + return 0 + + if args.setuid_helpers: + setuid_helpers(build_dir) + + reexec_under_user_scope_if_needed() + raise_nofile_limit() + + run_dir = resolve_run_dir(args) + runner = TestRunner(build_dir=build_dir, run_dir=run_dir, + run_dir_created=make_run_dir(run_dir), jobs=args.jobs, + keep_logs=args.keep_logs, verbose=args.verbose, + io_uring=args.io_uring, + io_uring_depth=args.io_uring_queue_depth) + if args.io_uring: + # A missing precondition skips the whole invocation rather than + # failing it, so a plain `meson test` on a machine that never enabled + # the module parameter reports one SKIP instead of 88 failures. CI does + # not rely on that: ci-build.sh enables the parameter itself. + reason = runner.preflight_io_uring() + if reason: + print(f'SKIP: {reason}') + runner.cleanup() + return SKIP_EXIT_CODE + # The capability says the kernel offers the transport, not that this + # user may open a ring. Asked here rather than skipped on, because a + # run that then fails every ring check is the honest report. + refused = io_uring_setup_error() + if refused: + print(f'warning: io_uring_setup() failed with {refused}; the ' + 'daemons will fall back to /dev/fuse') + print(' sudo sysctl -w kernel.io_uring_disabled=0') + specs = discover(CASES_DIR, args.test, set(args.group), exclude) + if args.timeout is not None: + specs = [replace(s, timeout=args.timeout) for s in specs] + + def on_signal(signum, frame): + # The first one lets the tests already running finish, which is + # usually what an interrupt means. Asking twice means the test in + # flight is the problem, so take the containment down with us -- + # otherwise the run sits out its full timeout before it can react. + if runner.stopping(): + print('\ninterrupted again: killing the tests still running') + runner.cleanup() + os._exit(128 + signum) + print('\ninterrupted: waiting for the running tests to finish') + runner.stop() + signal.signal(signal.SIGINT, on_signal) + signal.signal(signal.SIGTERM, on_signal) + + print(f'running {len(specs)} tests from {CASES_DIR} in {run_dir}') + rc = 0 + try: + for _ in range(args.repeat): + rc |= runner.run_all(specs) + finally: + runner.cleanup() + return rc + + +if __name__ == '__main__': + sys.exit(main(sys.argv[1:])) diff --git a/test/stracedecode.c b/test/stracedecode.c new file mode 100644 index 0000000..01bf523 --- /dev/null +++ b/test/stracedecode.c @@ -0,0 +1,199 @@ +#include +#include +#include "fuse_kernel.h" + +static struct { + const char *name; +} fuse_ll_ops[] = { + [FUSE_LOOKUP] = { "LOOKUP" }, + [FUSE_FORGET] = { "FORGET" }, + [FUSE_GETATTR] = { "GETATTR" }, + [FUSE_SETATTR] = { "SETATTR" }, + [FUSE_READLINK] = { "READLINK" }, + [FUSE_SYMLINK] = { "SYMLINK" }, + [FUSE_MKNOD] = { "MKNOD" }, + [FUSE_MKDIR] = { "MKDIR" }, + [FUSE_UNLINK] = { "UNLINK" }, + [FUSE_RMDIR] = { "RMDIR" }, + [FUSE_RENAME] = { "RENAME" }, + [FUSE_LINK] = { "LINK" }, + [FUSE_OPEN] = { "OPEN" }, + [FUSE_READ] = { "READ" }, + [FUSE_WRITE] = { "WRITE" }, + [FUSE_STATFS] = { "STATFS" }, + [FUSE_RELEASE] = { "RELEASE" }, + [FUSE_FSYNC] = { "FSYNC" }, + [FUSE_SETXATTR] = { "SETXATTR" }, + [FUSE_GETXATTR] = { "GETXATTR" }, + [FUSE_LISTXATTR] = { "LISTXATTR" }, + [FUSE_REMOVEXATTR] = { "REMOVEXATTR" }, + [FUSE_FLUSH] = { "FLUSH" }, + [FUSE_INIT] = { "INIT" }, + [FUSE_OPENDIR] = { "OPENDIR" }, + [FUSE_READDIR] = { "READDIR" }, + [FUSE_RELEASEDIR] = { "RELEASEDIR" }, + [FUSE_FSYNCDIR] = { "FSYNCDIR" }, + [FUSE_GETLK] = { "GETLK" }, + [FUSE_SETLK] = { "SETLK" }, + [FUSE_SETLKW] = { "SETLKW" }, + [FUSE_ACCESS] = { "ACCESS" }, + [FUSE_CREATE] = { "CREATE" }, + [FUSE_TMPFILE] = { "TMPFILE" }, + [FUSE_INTERRUPT] = { "INTERRUPT" }, + [FUSE_BMAP] = { "BMAP" }, + [FUSE_DESTROY] = { "DESTROY" }, + [FUSE_READDIRPLUS] = { "READDIRPLUS" }, +}; + +#define FUSE_MAXOP (sizeof(fuse_ll_ops) / sizeof(fuse_ll_ops[0])) + +static const char *opname(enum fuse_opcode opcode) +{ + if (opcode >= FUSE_MAXOP || !fuse_ll_ops[opcode].name) + return "???"; + else + return fuse_ll_ops[opcode].name; +} + + +static void process_buf(int dir, char *buf, int len) +{ + static unsigned long long prevuniq = -1; + static int prevopcode; + + if (!dir) { + struct fuse_in_header *in = (struct fuse_in_header *) buf; + buf += sizeof(struct fuse_in_header); + + printf("unique: %llu, opcode: %s (%i), nodeid: %lu, len: %i, insize: %i\n", + (unsigned long long) in->unique, + opname((enum fuse_opcode) in->opcode), in->opcode, + (unsigned long) in->nodeid, in->len, len); + + switch (in->opcode) { + case FUSE_READ: { + struct fuse_read_in *arg = (struct fuse_read_in *) buf; + printf("-READ fh:%llu off:%llu siz:%u rfl:%u own:%llu fl:%u\n", + arg->fh, arg->offset, arg->size, arg->read_flags, + arg->lock_owner, arg->flags); + break; + } + case FUSE_WRITE: { + struct fuse_write_in *arg = (struct fuse_write_in *) buf; + printf("-WRITE fh:%llu off:%llu siz:%u wfl:%u own:%llu fl:%u\n", + arg->fh, arg->offset, arg->size, arg->write_flags, + arg->lock_owner, arg->flags); + break; + } + } + prevuniq = in->unique; + prevopcode = in->opcode; + } else { + struct fuse_out_header *out = (struct fuse_out_header *) buf; + buf += sizeof(struct fuse_out_header); + + printf(" unique: %llu, error: %i (%s), len: %i, outsize: %i\n", + (unsigned long long) out->unique, out->error, + strerror(-out->error), out->len, len); + + if (out->unique == prevuniq) { + switch (prevopcode) { + case FUSE_GETATTR: { + struct fuse_attr_out *arg = (struct fuse_attr_out *) buf; + printf("+ATTR v:%llu.%09u i:%llu s:%llu b:%llu\n", + arg->attr_valid, arg->attr_valid_nsec, + arg->attr.ino, arg->attr.size, arg->attr.blocks); + break; + } + case FUSE_LOOKUP: { + struct fuse_entry_out *arg = (struct fuse_entry_out *) buf; + printf("+ENTRY nodeid:%llu v:%llu.%09u i:%llu s:%llu b:%llu\n", + arg->nodeid, arg->attr_valid, arg->attr_valid_nsec, + arg->attr.ino, arg->attr.size, arg->attr.blocks); + break; + } + } + } + } + +} + +int main(void) +{ + FILE *in = stdin; + while (1) { + int dir; + int res; + char buf[1048576]; + unsigned len = 0; + + memset(buf, 0, sizeof(buf)); + while (1) { + char str[32]; + + res = fscanf(in, "%30s", str); + if (res != 1 && feof(in)) + return 0; + + if (res == 0) + continue; + + if (strncmp(str, "read(", 5) == 0) { + dir = 0; + break; + } else if (strncmp(str, "writev(", 7) == 0) { + dir = 1; + break; + } + } + + while (1) { + int c = getc(in); + if (c == '"') { + while (1) { + int val; + + c = getc(in); + if (c == EOF) { + fprintf(stderr, "eof in string\n"); + break; + } + if (c == '\n') { + fprintf(stderr, "eol in string\n"); + break; + } + if (c == '"') + break; + if (c != '\\') { + val = c; + } else { + c = getc(in); + switch (c) { + case 'n': val = '\n'; break; + case 'r': val = '\r'; break; + case 't': val = '\t'; break; + case '"': val = '"'; break; + case '\\': val = '\\'; break; + case 'x': + res = scanf("%x", &val); + if (res != 1) { + fprintf(stderr, "parse error\n"); + continue; + } + break; + default: + fprintf(stderr, "unknown sequence: '\\%c'\n", c); + continue; + } + } + buf[len++] = val; + } + } + if (c == '\n') + break; + } + process_buf(dir, buf, len); + memset(buf, 0, len); + len = 0; + } +} diff --git a/test/test_abi.c b/test/test_abi.c new file mode 100644 index 0000000..99daa09 --- /dev/null +++ b/test/test_abi.c @@ -0,0 +1,18 @@ +#define FUSE_USE_VERSION 30 + +#include "fuse.h" + +#include +#include + +int main(void) +{ + if (sizeof(struct fuse_file_info) != 64) { + fprintf(stderr, "struct fuse_file_info size mismatch\n"); + exit(1); + } + if (sizeof(struct fuse_conn_info) != 128) { + fprintf(stderr, "struct fuse_conn_info size mismatch\n"); + exit(1); + } +} diff --git a/test/test_api_30.c b/test/test_api_30.c new file mode 100644 index 0000000..2b9d232 --- /dev/null +++ b/test/test_api_30.c @@ -0,0 +1,256 @@ +/* + * FUSE: Filesystem in Userspace + * + * Minimal low level filesystem built against the FUSE_USE_VERSION 30 API, + * so that the old API keeps being compiled and exercised. + * + * This program can be distributed under the terms of the GNU GPLv2. + * See the file GPL2.txt. + */ + +#define FUSE_USE_VERSION 30 + +/* the high level header has to keep building at this version as well */ +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef __linux__ +#include +#else +#include +#endif + +#define FILE_INO 2 + +static const char file_name[] = "hello"; +static const char file_contents[] = "Hello World!\n"; + +static int tfs_stat(fuse_ino_t ino, struct stat *stbuf) +{ + stbuf->st_ino = ino; + switch (ino) { + case FUSE_ROOT_ID: + stbuf->st_mode = S_IFDIR | 0755; + stbuf->st_nlink = 2; + break; + + case FILE_INO: + stbuf->st_mode = S_IFREG | 0444; + stbuf->st_nlink = 1; + stbuf->st_size = strlen(file_contents); + break; + + default: + return -1; + } + return 0; +} + +static void tfs_lookup(fuse_req_t req, fuse_ino_t parent, const char *name) +{ + struct fuse_entry_param e; + + if (parent != FUSE_ROOT_ID || strcmp(name, file_name) != 0) { + fuse_reply_err(req, ENOENT); + return; + } + + memset(&e, 0, sizeof(e)); + e.ino = FILE_INO; + e.attr_timeout = 1.0; + e.entry_timeout = 1.0; + tfs_stat(e.ino, &e.attr); + + fuse_reply_entry(req, &e); +} + +static void tfs_getattr(fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info *fi) +{ + struct stat stbuf; + + (void) fi; + + memset(&stbuf, 0, sizeof(stbuf)); + if (tfs_stat(ino, &stbuf) == -1) + fuse_reply_err(req, ENOENT); + else + fuse_reply_attr(req, &stbuf, 1.0); +} + +struct dirbuf { + char *p; + size_t size; +}; + +static void dirbuf_add(fuse_req_t req, struct dirbuf *b, const char *name, + fuse_ino_t ino) +{ + struct stat stbuf; + size_t oldsize = b->size; + + b->size += fuse_add_direntry(req, NULL, 0, name, NULL, 0); + b->p = realloc(b->p, b->size); + assert(b->p != NULL); + + memset(&stbuf, 0, sizeof(stbuf)); + stbuf.st_ino = ino; + fuse_add_direntry(req, b->p + oldsize, b->size - oldsize, name, &stbuf, + b->size); +} + +static int reply_buf_limited(fuse_req_t req, const char *buf, size_t bufsize, + off_t off, size_t maxsize) +{ + size_t offset = (size_t) off; + + if (off < 0 || offset >= bufsize) + return fuse_reply_buf(req, NULL, 0); + + if (bufsize - offset < maxsize) + maxsize = bufsize - offset; + + return fuse_reply_buf(req, buf + offset, maxsize); +} + +static void tfs_readdir(fuse_req_t req, fuse_ino_t ino, size_t size, + off_t off, struct fuse_file_info *fi) +{ + struct dirbuf b; + + (void) fi; + + if (ino != FUSE_ROOT_ID) { + fuse_reply_err(req, ENOTDIR); + return; + } + + memset(&b, 0, sizeof(b)); + dirbuf_add(req, &b, ".", FUSE_ROOT_ID); + dirbuf_add(req, &b, "..", FUSE_ROOT_ID); + dirbuf_add(req, &b, file_name, FILE_INO); + reply_buf_limited(req, b.p, b.size, off, size); + free(b.p); +} + +static void tfs_open(fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info *fi) +{ + if (ino != FILE_INO) + fuse_reply_err(req, EISDIR); + else if ((fi->flags & O_ACCMODE) != O_RDONLY) + fuse_reply_err(req, EACCES); + else + fuse_reply_open(req, fi); +} + +static void tfs_read(fuse_req_t req, fuse_ino_t ino, size_t size, off_t off, + struct fuse_file_info *fi) +{ + (void) fi; + + assert(ino == FILE_INO); + reply_buf_limited(req, file_contents, strlen(file_contents), off, size); +} + +static const struct fuse_lowlevel_ops tfs_oper = { + .lookup = tfs_lookup, + .getattr = tfs_getattr, + .readdir = tfs_readdir, + .open = tfs_open, + .read = tfs_read, +}; + +static void *run_fs(void *data) +{ + struct fuse_session *se = (struct fuse_session *)data; + + assert(fuse_session_loop(se) == 0); + return NULL; +} + +static void check_readdir(const char *mountpoint) +{ + DIR *dir = opendir(mountpoint); + const struct dirent *ent; + int found = 0; + + assert(dir != NULL); + while ((ent = readdir(dir)) != NULL) { + if (strcmp(ent->d_name, file_name) == 0) + found = 1; + } + assert(closedir(dir) == 0); + assert(found == 1); +} + +static void check_read(const char *mountpoint) +{ + char path[PATH_MAX]; + char buf[sizeof(file_contents)]; + int fd; + ssize_t len; + + assert(snprintf(path, PATH_MAX, "%s/%s", mountpoint, file_name) > 0); + fd = open(path, O_RDONLY); + if (fd == -1) { + perror(path); + assert(0); + } + + len = read(fd, buf, sizeof(buf)); + assert(len == (ssize_t) strlen(file_contents)); + assert(memcmp(buf, file_contents, len) == 0); + assert(close(fd) == 0); +} + +int main(int argc, char *argv[]) +{ + struct fuse_args args = FUSE_ARGS_INIT(argc, argv); + struct fuse_session *se; + struct fuse_cmdline_opts fuse_opts; + pthread_t fs_thread; + + assert(fuse_parse_cmdline(&args, &fuse_opts) == 0); +#ifndef __FreeBSD__ + assert(fuse_opt_add_arg(&args, "-oauto_unmount") == 0); +#endif + se = fuse_session_new(&args, &tfs_oper, sizeof(tfs_oper), NULL); + assert(se != NULL); + assert(fuse_set_signal_handlers(se) == 0); + assert(fuse_session_mount(se, fuse_opts.mountpoint) == 0); + + assert(pthread_create(&fs_thread, NULL, run_fs, (void *)se) == 0); + + check_readdir(fuse_opts.mountpoint); + check_read(fuse_opts.mountpoint); + + /* + * fuse_session_exit() does not interrupt read(), so nudge this thread + * with the ignored signal to let the loop free its receive buffer. + */ + fuse_session_exit(se); + assert(pthread_kill(fs_thread, SIGPIPE) == 0); + assert(pthread_join(fs_thread, NULL) == 0); + + fuse_session_unmount(se); + fuse_remove_signal_handlers(se); + fuse_session_destroy(se); + free(fuse_opts.mountpoint); + fuse_opt_free_args(&args); + + printf("Test completed successfully.\n"); + return 0; +} diff --git a/test/test_setattr.c b/test/test_setattr.c new file mode 100644 index 0000000..ed0e93d --- /dev/null +++ b/test/test_setattr.c @@ -0,0 +1,194 @@ +/* + FUSE: Filesystem in Userspace + Copyright (C) 2016 Nikolaus Rath + + This program can be distributed under the terms of the GNU GPLv2. + See the file GPL2.txt. +*/ + + +#define FUSE_USE_VERSION 30 + +/* Not really needed - just to test build with FUSE_USE_VERSION == 30 */ +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef __linux__ +#include +#else +#include +#endif + +#define FILE_INO 2 +#define FILE_NAME "truncate_me" + +static int got_fh; +static mode_t file_mode = S_IFREG | 0644; + +static int tfs_stat(fuse_ino_t ino, struct stat *stbuf) { + stbuf->st_ino = ino; + if (ino == FUSE_ROOT_ID) { + stbuf->st_mode = S_IFDIR | 0755; + stbuf->st_nlink = 1; + } + + else if (ino == FILE_INO) { + stbuf->st_mode = file_mode; + stbuf->st_nlink = 1; + stbuf->st_size = 0; + } + + else + return -1; + + return 0; +} + +static void tfs_lookup(fuse_req_t req, fuse_ino_t parent, + const char *name) { + struct fuse_entry_param e; + memset(&e, 0, sizeof(e)); + + if (parent != FUSE_ROOT_ID) + goto err_out; + else if (strcmp(name, FILE_NAME) == 0) + e.ino = FILE_INO; + else + goto err_out; + + if (tfs_stat(e.ino, &e.attr) != 0) + goto err_out; + fuse_reply_entry(req, &e); + return; + +err_out: + fuse_reply_err(req, ENOENT); +} + +static void tfs_getattr(fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info *fi) { + struct stat stbuf; + + (void) fi; + + memset(&stbuf, 0, sizeof(stbuf)); + if (tfs_stat(ino, &stbuf) != 0) + fuse_reply_err(req, ENOENT); + else + fuse_reply_attr(req, &stbuf, 5); +} + +static void tfs_open(fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info *fi) { + if (ino == FUSE_ROOT_ID) + fuse_reply_err(req, EISDIR); + else { + assert(ino == FILE_INO); + fi->fh = FILE_INO; + fuse_reply_open(req, fi); + } +} + +static void tfs_setattr (fuse_req_t req, fuse_ino_t ino, struct stat *attr, + int to_set, struct fuse_file_info *fi) { + if(ino != FILE_INO || + !(to_set & FUSE_SET_ATTR_MODE)) { + fuse_reply_err(req, EINVAL); + return; + } + + if(fi == NULL) + fprintf(stderr, "setattr with fi == NULL\n"); + else if (fi->fh != FILE_INO) + fprintf(stderr, "setattr with wrong fi->fh\n"); + else { + fprintf(stderr, "setattr ok\n"); + got_fh = 1; + file_mode = attr->st_mode; + } + + tfs_getattr(req, ino, fi); +} + +static struct fuse_lowlevel_ops tfs_oper = { + .lookup = tfs_lookup, + .getattr = tfs_getattr, + .open = tfs_open, + .setattr = tfs_setattr, +}; + +static void* run_fs(void *data) { + struct fuse_session *se = (struct fuse_session*) data; + assert(fuse_session_loop(se) == 0); + return NULL; +} + +static void test_fs(char *mountpoint) { + char fname[PATH_MAX]; + int fd; + + assert(snprintf(fname, PATH_MAX, "%s/" FILE_NAME, + mountpoint) > 0); + fd = open(fname, O_WRONLY); + if (fd == -1) { + perror(fname); + assert(0); + } + + assert(fchmod(fd, 0600) == 0); + close(fd); +} + +int main(int argc, char *argv[]) { + struct fuse_args args = FUSE_ARGS_INIT(argc, argv); + struct fuse_session *se; + struct fuse_cmdline_opts fuse_opts; + pthread_t fs_thread; + + assert(fuse_parse_cmdline(&args, &fuse_opts) == 0); +#ifndef __FreeBSD__ + assert(fuse_opt_add_arg(&args, "-oauto_unmount") == 0); +#endif + se = fuse_session_new(&args, &tfs_oper, + sizeof(tfs_oper), NULL); + assert (se != NULL); + assert(fuse_set_signal_handlers(se) == 0); + assert(fuse_session_mount(se, fuse_opts.mountpoint) == 0); + + /* Start file-system thread */ + assert(pthread_create(&fs_thread, NULL, run_fs, (void *)se) == 0); + + /* Do test */ + test_fs(fuse_opts.mountpoint); + + /* Stop file system */ + assert(pthread_cancel(fs_thread) == 0); + + fuse_session_unmount(se); + assert(got_fh == 1); + fuse_remove_signal_handlers(se); + fuse_session_destroy(se); + + printf("Test completed successfully.\n"); + return 0; +} + + +/** + * Local Variables: + * mode: c + * indent-tabs-mode: nil + * c-basic-offset: 4 + * End: + */ diff --git a/test/test_signals.c b/test/test_signals.c new file mode 100644 index 0000000..3ce7fbd --- /dev/null +++ b/test/test_signals.c @@ -0,0 +1,202 @@ +/* + * FUSE: Filesystem in Userspace + * Copyright (C) 2025 Bernd Schubert + * + * Test for signal handling in libfuse. + * + * This program can be distributed under the terms of the GNU LGPLv2. + * See the file GPL2.txt + */ + +#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 17) + +#include "fuse_config.h" +#include "fuse_lowlevel.h" +#include "fuse_i.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static void test_ll_lookup(fuse_req_t req, fuse_ino_t parent, const char *name) +{ + (void)parent; + (void)name; + /* Simulate slow lookup to test signal interruption */ + sleep(2); + fuse_reply_err(req, ENOENT); +} + +static void test_ll_getattr(fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info *fi) +{ + (void)ino; + (void)fi; + /* Simulate slow getattr to test signal interruption */ + sleep(2); + fuse_reply_err(req, ENOENT); +} + +static const struct fuse_lowlevel_ops test_ll_ops = { + .lookup = test_ll_lookup, + .getattr = test_ll_getattr, +}; + +static void *signal_sender_thread(void *arg) +{ + (void)arg; + + usleep(2 * 1000 * 1000); + + /* Send SIGTERM to the process */ + kill(getpid(), SIGTERM); + return NULL; +} + +static void fork_child(void) +{ + struct fuse_args args = FUSE_ARGS_INIT(0, NULL); + struct fuse_session *se; + struct fuse_loop_config *loop_config; + pthread_t sig_thread; + char *mountpoint = NULL; + int ret = -1; + + /* Add the program name to arg[0] */ + if (fuse_opt_add_arg(&args, "test_signals")) { + fprintf(stderr, "Failed to add argument\n"); + goto out_free_mountpoint; + } + + /* Add debug flag to see more output */ + fuse_opt_add_arg(&args, "-d"); + + /* Create temporary mount point */ + mountpoint = strdup("/tmp/fuse_test_XXXXXX"); + if (!mountpoint || !mkdtemp(mountpoint)) { + fprintf(stderr, "Failed to create temp dir\n"); + goto out_free_args; + } + + /* Create session */ + se = fuse_session_new(&args, &test_ll_ops, sizeof(test_ll_ops), NULL); + if (!se) { + fprintf(stderr, "Failed to create FUSE session\n"); + goto out_free_mountpoint; + } + + /* Mount filesystem */ + if (fuse_session_mount(se, mountpoint)) { + fprintf(stderr, "Failed to mount FUSE filesystem\n"); + goto out_destroy_session; + } + + /* Create loop config */ + loop_config = fuse_loop_cfg_create(); + if (!loop_config) { + fprintf(stderr, "Failed to create loop config\n"); + goto out_unmount; + } + fuse_loop_cfg_set_clone_fd(loop_config, 0); + fuse_loop_cfg_set_max_threads(loop_config, 2); + + /* Set up signal handlers */ + if (fuse_set_signal_handlers(se)) { + fprintf(stderr, "Failed to set up signal handlers\n"); + goto out_destroy_config; + } + + /* Create thread that will send signals */ + if (pthread_create(&sig_thread, NULL, signal_sender_thread, NULL)) { + fprintf(stderr, "Failed to create signal sender thread\n"); + goto out_remove_handlers; + } + + /* Enter FUSE loop */ + ret = fuse_session_loop_mt_312(se, loop_config); + + printf("Debug: fuse_session_loop_mt_312 returned %d\n", ret); + printf("Debug: session exited state: %d\n", fuse_session_exited(se)); + printf("Debug: session status: %d\n", se->error); + + /* Check exit status before cleanup */ + int clean_exit = (fuse_session_exited(se) && se->error == SIGTERM); + + /* Clean up */ + pthread_join(sig_thread, NULL); + fuse_remove_signal_handlers(se); + fuse_session_unmount(se); + fuse_session_destroy(se); + fuse_loop_cfg_destroy(loop_config); + rmdir(mountpoint); + free(mountpoint); + fuse_opt_free_args(&args); + + /* Use saved exit status */ + if (clean_exit) { + printf("Debug: Clean shutdown via SIGTERM\n"); + exit(0); + } + printf("Debug: Exiting with status %d\n", ret != 0); + exit(ret != 0); + +out_remove_handlers: + fuse_remove_signal_handlers(se); +out_destroy_config: + fuse_loop_cfg_destroy(loop_config); +out_unmount: + fuse_session_unmount(se); +out_destroy_session: + fuse_session_destroy(se); +out_free_mountpoint: + rmdir(mountpoint); + free(mountpoint); +out_free_args: + fuse_opt_free_args(&args); + exit(1); +} + +static void run_test_in_child(void) +{ + pid_t child; + int status; + + child = fork(); + if (child == -1) { + perror("fork"); + exit(1); + } + + if (child == 0) + fork_child(); + + /* In parent process */ + if (waitpid(child, &status, 0) == -1) { + perror("waitpid"); + exit(1); + } + + /* Check if child exited due to SIGTERM - this is expected */ + if (WIFSIGNALED(status) && WTERMSIG(status) == SIGTERM) { + printf("Child process terminated by SIGTERM as expected\n"); + exit(0); + } + + /* For any other type of exit, maintain existing behavior */ + exit(WIFEXITED(status) ? WEXITSTATUS(status) : 1); +} + +int main(void) +{ + printf("Testing SIGTERM handling in libfuse\n"); + run_test_in_child(); + printf("SIGTERM handling test passed\n"); + return 0; +} diff --git a/test/test_syscalls.c b/test/test_syscalls.c new file mode 100644 index 0000000..115eab8 --- /dev/null +++ b/test/test_syscalls.c @@ -0,0 +1,2273 @@ +#define _GNU_SOURCE +#include "fuse_config.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef ALLPERMS +# define ALLPERMS (S_ISUID|S_ISGID|S_ISVTX|S_IRWXU|S_IRWXG|S_IRWXO)/* 07777 */ +#endif + + +static const char *basepath; +static const char *basepath_r; +static char testfile[1024]; +static char testfile2[1024]; +static char testdir[1024]; +static char testdir2[1024]; +static char testsock[1024]; +static char subfile[1280]; + +static char testfile_r[1024]; +static char testfile2_r[1024]; +static char testdir_r[1024]; +static char testdir2_r[1024]; +static char subfile_r[1280]; + +static char testname[256]; +static char testdata[] = "abcdefghijklmnopqrstuvwxyz"; +static char testdata2[] = "1234567890-=qwertyuiop[]\asdfghjkl;'zxcvbnm,./"; +static const char *testdir_files[] = { "f1", "f2", NULL}; +static long seekdir_offsets[4]; +static char zerodata[4096]; +static int testdatalen = sizeof(testdata) - 1; +static int testdata2len = sizeof(testdata2) - 1; +static unsigned int testnum = 0; +static unsigned int select_test = 0; +static unsigned int skip_test = 0; +static unsigned int unlinked_test = 0; + +#define MAX_ENTRIES 1024 +#define MAX_TESTS 100 + +static struct test { + int fd; + struct stat stat; +} tests[MAX_TESTS]; + +static void test_perror(const char *func, const char *msg) +{ + fprintf(stderr, "%s %s() - %s: %s\n", testname, func, msg, + strerror(errno)); +} + +static void test_error(const char *func, const char *msg, ...) + __attribute__ ((format (printf, 2, 3))); + +static void __start_test(const char *fmt, ...) + __attribute__ ((format (printf, 1, 2))); + +static void test_error(const char *func, const char *msg, ...) +{ + va_list ap; + fprintf(stderr, "%s %s() - ", testname, func); + va_start(ap, msg); + vfprintf(stderr, msg, ap); + va_end(ap); + fprintf(stderr, "\n"); +} + +static int is_dot_or_dotdot(const char *name) { + return name[0] == '.' && + (name[1] == '\0' || (name[1] == '.' && name[2] == '\0')); +} + +/* + * Seconds since the first line this process printed. The origin is taken + * lazily so it needs no init call and cannot precede the first test. + */ +static double test_elapsed(void) +{ + static struct timespec start; + struct timespec now; + + clock_gettime(CLOCK_MONOTONIC, &now); + if (start.tv_sec == 0 && start.tv_nsec == 0) + start = now; + return (now.tv_sec - start.tv_sec) + + (now.tv_nsec - start.tv_nsec) / 1e9; +} + +static void success(void) +{ + fprintf(stderr, "+%8.3fs %s OK\n", test_elapsed(), testname); +} + +#define this_test (&tests[testnum-1]) +#define next_test (&tests[testnum]) + +static void __start_test(const char *fmt, ...) +{ + unsigned int n; + va_list ap; + n = sprintf(testname, "%3i [", testnum); + va_start(ap, fmt); + n += vsprintf(testname + n, fmt, ap); + va_end(ap); + sprintf(testname + n, "]"); + // Use dedicated testfile per test + sprintf(testfile, "%s/testfile.%d", basepath, testnum); + sprintf(testfile_r, "%s/testfile.%d", basepath_r, testnum); + if (testnum > MAX_TESTS) { + fprintf(stderr, "%s - too many tests\n", testname); + exit(1); + } + /* A test that never finishes prints nothing otherwise, leaving the log + * ending at the last test that passed. stderr, so a SIGKILL cannot + * swallow it. + */ + fprintf(stderr, "+%8.3fs %s START\n", test_elapsed(), testname); + this_test->fd = -1; +} + +#define start_test(msg, args...) { \ + testnum++; \ + if ((select_test && testnum != select_test) || \ + (testnum == skip_test)) { \ + return 0; \ + } \ + __start_test(msg, ##args); \ +} + +#define PERROR(msg) test_perror(__FUNCTION__, msg) +#define ERROR(msg, args...) test_error(__FUNCTION__, msg, ##args) + +#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0])) + +static int st_check_size(struct stat *st, int len) +{ + if (st->st_size != len) { + ERROR("length %u instead of %u", (int) st->st_size, + (int) len); + return -1; + } + return 0; +} + +static int check_size(const char *path, int len) +{ + struct stat stbuf; + int res = stat(path, &stbuf); + if (res == -1) { + PERROR("stat"); + return -1; + } + return st_check_size(&stbuf, len); +} + +static int check_testfile_size(const char *path, int len) +{ + this_test->stat.st_size = len; + return check_size(path, len); +} + +static int st_check_type(struct stat *st, mode_t type) +{ + if ((st->st_mode & S_IFMT) != type) { + ERROR("type 0%o instead of 0%o", st->st_mode & S_IFMT, type); + return -1; + } + return 0; +} + +static int check_type(const char *path, mode_t type) +{ + struct stat stbuf; + int res = lstat(path, &stbuf); + if (res == -1) { + PERROR("lstat"); + return -1; + } + return st_check_type(&stbuf, type); +} + +static int st_check_mode(struct stat *st, mode_t mode) +{ + if ((st->st_mode & ALLPERMS) != mode) { + ERROR("mode 0%o instead of 0%o", st->st_mode & ALLPERMS, + mode); + return -1; + } + return 0; +} + +static int check_mode(const char *path, mode_t mode) +{ + struct stat stbuf; + int res = lstat(path, &stbuf); + if (res == -1) { + PERROR("lstat"); + return -1; + } + return st_check_mode(&stbuf, mode); +} + +static int check_testfile_mode(const char *path, mode_t mode) +{ + this_test->stat.st_mode &= ~ALLPERMS; + this_test->stat.st_mode |= mode; + return check_mode(path, mode); +} + +static int check_times(const char *path, time_t atime, time_t mtime) +{ + int err = 0; + struct stat stbuf; + int res = lstat(path, &stbuf); + if (res == -1) { + PERROR("lstat"); + return -1; + } + if (stbuf.st_atime != atime) { + ERROR("atime %li instead of %li", stbuf.st_atime, atime); + err--; + } + if (stbuf.st_mtime != mtime) { + ERROR("mtime %li instead of %li", stbuf.st_mtime, mtime); + err--; + } + if (err) + return -1; + + return 0; +} + +#if 0 +static int fcheck_times(int fd, time_t atime, time_t mtime) +{ + int err = 0; + struct stat stbuf; + int res = fstat(fd, &stbuf); + if (res == -1) { + PERROR("fstat"); + return -1; + } + if (stbuf.st_atime != atime) { + ERROR("atime %li instead of %li", stbuf.st_atime, atime); + err--; + } + if (stbuf.st_mtime != mtime) { + ERROR("mtime %li instead of %li", stbuf.st_mtime, mtime); + err--; + } + if (err) + return -1; + + return 0; +} +#endif + +static int st_check_nlink(struct stat *st, nlink_t nlink) +{ + if (st->st_nlink != nlink) { + ERROR("nlink %li instead of %li", (long) st->st_nlink, + (long) nlink); + return -1; + } + return 0; +} + +static int check_nlink(const char *path, nlink_t nlink) +{ + struct stat stbuf; + int res = lstat(path, &stbuf); + if (res == -1) { + PERROR("lstat"); + return -1; + } + return st_check_nlink(&stbuf, nlink); +} + +static int fcheck_stat(int fd, int flags, struct stat *st) +{ + struct stat stbuf; + int res = fstat(fd, &stbuf); + if (res == -1) { + if (flags & O_PATH) { + // With O_PATH fd, the server does not have to keep + // the inode alive so FUSE inode may be stale or bad + if (errno == ESTALE || errno == EIO || + errno == ENOENT || errno == EBADF) + return 0; + } + PERROR("fstat"); + return -1; + } + + int err = 0; + err += st_check_type(&stbuf, st->st_mode & S_IFMT); + err += st_check_mode(&stbuf, st->st_mode & ALLPERMS); + err += st_check_size(&stbuf, st->st_size); + err += st_check_nlink(&stbuf, st->st_nlink); + + return err; +} + +static int check_nonexist(const char *path) +{ + struct stat stbuf; + int res = lstat(path, &stbuf); + if (res == 0) { + ERROR("file should not exist"); + return -1; + } + if (errno != ENOENT) { + ERROR("file should not exist: %s", strerror(errno)); + return -1; + } + return 0; +} + +static int check_buffer(const char *buf, const char *data, unsigned len) +{ + if (memcmp(buf, data, len) != 0) { + ERROR("data mismatch"); + return -1; + } + return 0; +} + +static int check_data(const char *path, const char *data, int offset, + unsigned len) +{ + char buf[4096]; + int res; + int fd = open(path, O_RDONLY); + if (fd == -1) { + PERROR("open"); + return -1; + } + if (lseek(fd, offset, SEEK_SET) == (off_t) -1) { + PERROR("lseek"); + close(fd); + return -1; + } + while (len) { + int rdlen = len < sizeof(buf) ? len : sizeof(buf); + res = read(fd, buf, rdlen); + if (res == -1) { + PERROR("read"); + close(fd); + return -1; + } + if (res != rdlen) { + ERROR("short read: %u instead of %u", res, rdlen); + close(fd); + return -1; + } + if (check_buffer(buf, data, rdlen) != 0) { + close(fd); + return -1; + } + data += rdlen; + len -= rdlen; + } + res = close(fd); + if (res == -1) { + PERROR("close"); + return -1; + } + return 0; +} + +static int fcheck_data(int fd, const char *data, int offset, + unsigned len) +{ + char buf[4096]; + int res; + if (lseek(fd, offset, SEEK_SET) == (off_t) -1) { + PERROR("lseek"); + return -1; + } + while (len) { + int rdlen = len < sizeof(buf) ? len : sizeof(buf); + res = read(fd, buf, rdlen); + if (res == -1) { + PERROR("read"); + return -1; + } + if (res != rdlen) { + ERROR("short read: %u instead of %u", res, rdlen); + return -1; + } + if (check_buffer(buf, data, rdlen) != 0) { + return -1; + } + data += rdlen; + len -= rdlen; + } + return 0; +} + +static int check_dir_contents(const char *path, const char **contents) +{ + int i; + int res; + int err = 0; + int found[MAX_ENTRIES]; + const char *cont[MAX_ENTRIES]; + DIR *dp; + + for (i = 0; contents[i]; i++) { + assert(i < MAX_ENTRIES - 3); + found[i] = 0; + cont[i] = contents[i]; + } + cont[i] = NULL; + + dp = opendir(path); + if (dp == NULL) { + PERROR("opendir"); + return -1; + } + memset(found, 0, sizeof(found)); + while(1) { + struct dirent *de; + errno = 0; + de = readdir(dp); + if (de == NULL) { + if (errno) { + PERROR("readdir"); + closedir(dp); + return -1; + } + break; + } + if (is_dot_or_dotdot(de->d_name)) + continue; + for (i = 0; cont[i] != NULL; i++) { + assert(i < MAX_ENTRIES); + if (strcmp(cont[i], de->d_name) == 0) { + if (found[i]) { + ERROR("duplicate entry <%s>", + de->d_name); + err--; + } else + found[i] = 1; + break; + } + } + if (!cont[i]) { + ERROR("unexpected entry <%s>", de->d_name); + err --; + } + } + for (i = 0; cont[i] != NULL; i++) { + if (!found[i]) { + ERROR("missing entry <%s>", cont[i]); + err--; + } + } + res = closedir(dp); + if (res == -1) { + PERROR("closedir"); + return -1; + } + if (err) + return -1; + + return 0; +} + +static int create_file(const char *path, const char *data, int len) +{ + int res; + int fd; + + unlink(path); + fd = creat(path, 0644); + if (fd == -1) { + PERROR("creat"); + return -1; + } + if (len) { + res = write(fd, data, len); + if (res == -1) { + PERROR("write"); + close(fd); + return -1; + } + if (res != len) { + ERROR("write is short: %u instead of %u", res, len); + close(fd); + return -1; + } + } + res = close(fd); + if (res == -1) { + PERROR("close"); + return -1; + } + res = check_type(path, S_IFREG); + if (res == -1) + return -1; + res = check_mode(path, 0644); + if (res == -1) + return -1; + res = check_nlink(path, 1); + if (res == -1) + return -1; + res = check_size(path, len); + if (res == -1) + return -1; + + if (len) { + res = check_data(path, data, 0, len); + if (res == -1) + return -1; + } + + return 0; +} + +static int create_path_fd(const char *path, const char *data, int len) +{ + int path_fd; + int res; + + res = create_file(path, data, len); + if (res == -1) + return -1; + + path_fd = open(path, O_PATH); + if (path_fd == -1) + PERROR("open(O_PATH)"); + + return path_fd; +} + +// Can be called once per test +static int create_testfile(const char *path, const char *data, int len) +{ + struct test *t = this_test; + struct stat *st = &t->stat; + int res, fd; + + if (t->fd > 0) { + ERROR("testfile already created"); + return -1; + } + + fd = create_path_fd(path, data, len); + if (fd == -1) + return -1; + + t->fd = fd; + + res = fstat(fd, st); + if (res == -1) { + PERROR("fstat"); + return -1; + } + + return 0; +} + +static int check_unlinked_testfile(int fd) +{ + struct stat *st = &this_test->stat; + + st->st_nlink = 0; + return fcheck_stat(fd, O_PATH, st); +} + +// Check recorded testfiles after all tests completed +static int check_unlinked_testfiles(void) +{ + int fd; + int res, err = 0; + int num = testnum; + + if (!unlinked_test) + return 0; + + testnum = 0; + while (testnum < num) { + fd = next_test->fd; + start_test("check_unlinked_testfile"); + if (fd == -1) + continue; + + err += check_unlinked_testfile(fd); + res = close(fd); + if (res == -1) { + PERROR("close(test_fd)"); + err--; + } + } + + if (err) { + fprintf(stderr, "%i unlinked testfile checks failed\n", -err); + return 1; + } + + return err; +} + +static int cleanup_dir(const char *path, const char **dir_files, int quiet) +{ + int i; + int err = 0; + + for (i = 0; dir_files[i]; i++) { + int res; + char fpath[1280]; + sprintf(fpath, "%s/%s", path, dir_files[i]); + res = unlink(fpath); + if (res == -1 && !quiet) { + PERROR("unlink"); + err --; + } + } + if (err) + return -1; + + return 0; +} + +static int create_dir(const char *path, const char **dir_files) +{ + int res; + int i; + + rmdir(path); + res = mkdir(path, 0755); + if (res == -1) { + PERROR("mkdir"); + return -1; + } + res = check_type(path, S_IFDIR); + if (res == -1) + return -1; + res = check_mode(path, 0755); + if (res == -1) + return -1; + + for (i = 0; dir_files[i]; i++) { + char fpath[1280]; + sprintf(fpath, "%s/%s", path, dir_files[i]); + res = create_file(fpath, "", 0); + if (res == -1) { + cleanup_dir(path, dir_files, 1); + return -1; + } + } + res = check_dir_contents(path, dir_files); + if (res == -1) { + cleanup_dir(path, dir_files, 1); + return -1; + } + + return 0; +} + +static int test_truncate(int len) +{ + const char *data = testdata; + int datalen = testdatalen; + int res; + + start_test("truncate(%u)", (int) len); + res = create_testfile(testfile, data, datalen); + if (res == -1) + return -1; + + res = truncate(testfile, len); + if (res == -1) { + PERROR("truncate"); + return -1; + } + res = check_testfile_size(testfile, len); + if (res == -1) + return -1; + + if (len > 0) { + if (len <= datalen) { + res = check_data(testfile, data, 0, len); + if (res == -1) + return -1; + } else { + res = check_data(testfile, data, 0, datalen); + if (res == -1) + return -1; + res = check_data(testfile, zerodata, datalen, + len - datalen); + if (res == -1) + return -1; + } + } + res = unlink(testfile); + if (res == -1) { + PERROR("unlink"); + return -1; + } + res = check_nonexist(testfile); + if (res == -1) + return -1; + + success(); + return 0; +} + +static int test_ftruncate(int len, int mode) +{ + const char *data = testdata; + int datalen = testdatalen; + int res; + int fd; + + start_test("ftruncate(%u) mode: 0%03o", len, mode); + res = create_testfile(testfile, data, datalen); + if (res == -1) + return -1; + + fd = open(testfile, O_WRONLY); + if (fd == -1) { + PERROR("open"); + return -1; + } + + res = fchmod(fd, mode); + if (res == -1) { + PERROR("fchmod"); + close(fd); + return -1; + } + res = check_testfile_mode(testfile, mode); + if (res == -1) { + close(fd); + return -1; + } + res = ftruncate(fd, len); + if (res == -1) { + PERROR("ftruncate"); + close(fd); + return -1; + } + close(fd); + res = check_testfile_size(testfile, len); + if (res == -1) + return -1; + + if (len > 0) { + if (len <= datalen) { + res = check_data(testfile, data, 0, len); + if (res == -1) + return -1; + } else { + res = check_data(testfile, data, 0, datalen); + if (res == -1) + return -1; + res = check_data(testfile, zerodata, datalen, + len - datalen); + if (res == -1) + return -1; + } + } + res = unlink(testfile); + if (res == -1) { + PERROR("unlink"); + return -1; + } + res = check_nonexist(testfile); + if (res == -1) + return -1; + + success(); + return 0; +} + +static int test_seekdir(void) +{ + int i; + int res; + DIR *dp; + struct dirent *de = NULL; + + start_test("seekdir"); + res = create_dir(testdir, testdir_files); + if (res == -1) + return res; + + dp = opendir(testdir); + if (dp == NULL) { + PERROR("opendir"); + return -1; + } + + /* Remember dir offsets */ + for (i = 0; i < ARRAY_SIZE(seekdir_offsets); i++) { + seekdir_offsets[i] = telldir(dp); + errno = 0; + de = readdir(dp); + if (de == NULL) { + if (errno) { + PERROR("readdir"); + goto fail; + } + break; + } + } + + /* Walk until the end of directory */ + while (de) + de = readdir(dp); + + /* Start from the last valid dir offset and seek backwards */ + for (i--; i >= 0; i--) { + seekdir(dp, seekdir_offsets[i]); + de = readdir(dp); + if (de == NULL) { + ERROR("Unexpected end of directory after seekdir()"); + goto fail; + } + } + + closedir(dp); + res = cleanup_dir(testdir, testdir_files, 0); + if (!res) + success(); + return res; +fail: + closedir(dp); + cleanup_dir(testdir, testdir_files, 1); + return -1; +} + +#ifdef HAVE_COPY_FILE_RANGE +static int test_copy_file_range(void) +{ + const char *data = testdata; + int datalen = testdatalen; + int err = 0; + int res; + int fd_in, fd_out; + off_t pos_in = 0, pos_out = 0; + + start_test("copy_file_range"); + unlink(testfile); + fd_in = open(testfile, O_CREAT | O_RDWR, 0644); + if (fd_in == -1) { + PERROR("creat"); + return -1; + } + res = write(fd_in, data, datalen); + if (res == -1) { + PERROR("write"); + close(fd_in); + return -1; + } + if (res != datalen) { + ERROR("write is short: %u instead of %u", res, datalen); + close(fd_in); + return -1; + } + + unlink(testfile2); + fd_out = creat(testfile2, 0644); + if (fd_out == -1) { + PERROR("creat"); + close(fd_in); + return -1; + } + res = copy_file_range(fd_in, &pos_in, fd_out, &pos_out, datalen, 0); + if (res == -1) { + PERROR("copy_file_range"); + close(fd_in); + close(fd_out); + return -1; + } + if (res != datalen) { + ERROR("copy is short: %u instead of %u", res, datalen); + close(fd_in); + close(fd_out); + return -1; + } + + res = close(fd_in); + if (res == -1) { + PERROR("close"); + close(fd_out); + return -1; + } + res = close(fd_out); + if (res == -1) { + PERROR("close"); + return -1; + } + + err = check_data(testfile2, data, 0, datalen); + + res = unlink(testfile); + if (res == -1) { + PERROR("unlink"); + return -1; + } + res = check_nonexist(testfile); + if (res == -1) + return -1; + if (err) + return -1; + + res = unlink(testfile2); + if (res == -1) { + PERROR("unlink"); + return -1; + } + res = check_nonexist(testfile2); + if (res == -1) + return -1; + if (err) + return -1; + + success(); + return 0; +} +#else +static int test_copy_file_range(void) +{ + return 0; +} +#endif + +#ifdef HAVE_STATX +static int test_statx(void) +{ + struct statx sb; + char msg[] = "hi"; + size_t msg_size = sizeof(msg); + struct timespec tp; + int res; + + memset(&sb, 0, sizeof(sb)); + unlink(testfile); + + start_test("statx"); + + res = create_testfile(testfile, msg, msg_size); + if (res == -1) + return -1; + + res = statx(-1, testfile, AT_EMPTY_PATH, + STATX_BASIC_STATS | STATX_BTIME, &sb); + if (res == -1) + return -1; + + if (sb.stx_size != msg_size) + return -1; + + clock_gettime(CLOCK_REALTIME, &tp); + + if (sb.stx_btime.tv_sec > tp.tv_sec) + return -1; + + if (sb.stx_btime.tv_sec == tp.tv_sec && + sb.stx_btime.tv_nsec >= tp.tv_nsec) + return -1; + + unlink(testfile); + + success(); + return 0; +} +#else +static int test_statx(void) +{ + return 0; +} +#endif + +static int test_utime(void) +{ + struct utimbuf utm; + time_t atime = 987631200; + time_t mtime = 123116400; + int res; + + start_test("utime"); + res = create_testfile(testfile, NULL, 0); + if (res == -1) + return -1; + + utm.actime = atime; + utm.modtime = mtime; + res = utime(testfile, &utm); + if (res == -1) { + PERROR("utime"); + return -1; + } + res = check_times(testfile, atime, mtime); + if (res == -1) { + return -1; + } + res = unlink(testfile); + if (res == -1) { + PERROR("unlink"); + return -1; + } + res = check_nonexist(testfile); + if (res == -1) + return -1; + + success(); + return 0; +} + +static int test_create(void) +{ + const char *data = testdata; + int datalen = testdatalen; + int err = 0; + int res; + int fd; + + start_test("create"); + unlink(testfile); + fd = creat(testfile, 0644); + if (fd == -1) { + PERROR("creat"); + return -1; + } + res = write(fd, data, datalen); + if (res == -1) { + PERROR("write"); + close(fd); + return -1; + } + if (res != datalen) { + ERROR("write is short: %u instead of %u", res, datalen); + close(fd); + return -1; + } + res = close(fd); + if (res == -1) { + PERROR("close"); + return -1; + } + res = check_type(testfile, S_IFREG); + if (res == -1) + return -1; + err += check_mode(testfile, 0644); + err += check_nlink(testfile, 1); + err += check_size(testfile, datalen); + err += check_data(testfile, data, 0, datalen); + res = unlink(testfile); + if (res == -1) { + PERROR("unlink"); + return -1; + } + res = check_nonexist(testfile); + if (res == -1) + return -1; + if (err) + return -1; + + success(); + return 0; +} + +static int test_create_unlink(void) +{ + const char *data = testdata; + int datalen = testdatalen; + int err = 0; + int res; + int fd; + + start_test("create+unlink"); + unlink(testfile); + fd = open(testfile, O_CREAT | O_RDWR | O_TRUNC, 0644); + if (fd == -1) { + PERROR("creat"); + return -1; + } + res = unlink(testfile); + if (res == -1) { + PERROR("unlink"); + close(fd); + return -1; + } + res = check_nonexist(testfile); + if (res == -1) { + close(fd); + return -1; + } + res = write(fd, data, datalen); + if (res == -1) { + PERROR("write"); + close(fd); + return -1; + } + if (res != datalen) { + ERROR("write is short: %u instead of %u", res, datalen); + close(fd); + return -1; + } + struct stat st = { + .st_mode = S_IFREG | 0644, + .st_size = datalen, + }; + err = fcheck_stat(fd, O_RDWR, &st); + err += fcheck_data(fd, data, 0, datalen); + res = close(fd); + if (res == -1) { + PERROR("close"); + err--; + } + if (err) + return -1; + + success(); + return 0; +} + +static int test_mknod(void) +{ + int err = 0; + int res; + + start_test("mknod"); + unlink(testfile); + res = mknod(testfile, 0644, 0); + if (res == -1) { + PERROR("mknod"); + return -1; + } + res = check_type(testfile, S_IFREG); + if (res == -1) + return -1; + err += check_mode(testfile, 0644); + err += check_nlink(testfile, 1); + err += check_size(testfile, 0); + res = unlink(testfile); + if (res == -1) { + PERROR("unlink"); + return -1; + } + res = check_nonexist(testfile); + if (res == -1) + return -1; + if (err) + return -1; + + success(); + return 0; +} + +#define test_open(exist, flags, mode) do_test_open(exist, flags, #flags, mode) + +static int do_test_open(int exist, int flags, const char *flags_str, int mode) +{ + char buf[4096]; + const char *data = testdata; + int datalen = testdatalen; + unsigned currlen = 0; + int err = 0; + int res; + int fd; + off_t off; + + start_test("open(%s, %s, 0%03o)", exist ? "+" : "-", flags_str, mode); + unlink(testfile); + if (exist) { + res = create_file(testfile_r, testdata2, testdata2len); + if (res == -1) + return -1; + + currlen = testdata2len; + } + + fd = open(testfile, flags, mode); + if ((flags & O_CREAT) && (flags & O_EXCL) && exist) { + if (fd != -1) { + ERROR("open should have failed"); + close(fd); + return -1; + } else if (errno == EEXIST) + goto succ; + } + if (!(flags & O_CREAT) && !exist) { + if (fd != -1) { + ERROR("open should have failed"); + close(fd); + return -1; + } else if (errno == ENOENT) + goto succ; + } + if (fd == -1) { + PERROR("open"); + return -1; + } + + if (flags & O_TRUNC) + currlen = 0; + + err += check_type(testfile, S_IFREG); + if (exist) + err += check_mode(testfile, 0644); + else + err += check_mode(testfile, mode); + err += check_nlink(testfile, 1); + err += check_size(testfile, currlen); + if (exist && !(flags & O_TRUNC) && (mode & S_IRUSR)) + err += check_data(testfile, testdata2, 0, testdata2len); + + res = write(fd, data, datalen); + if ((flags & O_ACCMODE) != O_RDONLY) { + if (res == -1) { + PERROR("write"); + err --; + } else if (res != datalen) { + ERROR("write is short: %u instead of %u", res, datalen); + err --; + } else { + if (datalen > (int) currlen) + currlen = datalen; + + err += check_size(testfile, currlen); + + if (mode & S_IRUSR) { + err += check_data(testfile, data, 0, datalen); + if (exist && !(flags & O_TRUNC) && + testdata2len > datalen) + err += check_data(testfile, + testdata2 + datalen, + datalen, + testdata2len - datalen); + } + } + } else { + if (res != -1) { + ERROR("write should have failed"); + err --; + } else if (errno != EBADF) { + PERROR("write"); + err --; + } + } + off = lseek(fd, SEEK_SET, 0); + if (off == (off_t) -1) { + PERROR("lseek"); + err--; + } else if (off != 0) { + ERROR("offset should have returned 0"); + err --; + } + res = read(fd, buf, sizeof(buf)); + if ((flags & O_ACCMODE) != O_WRONLY) { + if (res == -1) { + PERROR("read"); + err--; + } else { + int readsize = + currlen < sizeof(buf) ? currlen : sizeof(buf); + if (res != readsize) { + ERROR("read is short: %i instead of %u", + res, readsize); + err--; + } else { + if ((flags & O_ACCMODE) != O_RDONLY) { + err += check_buffer(buf, data, datalen); + if (exist && !(flags & O_TRUNC) && + testdata2len > datalen) + err += check_buffer(buf + datalen, + testdata2 + datalen, + testdata2len - datalen); + } else if (exist) + err += check_buffer(buf, testdata2, + testdata2len); + } + } + } else { + if (res != -1) { + ERROR("read should have failed"); + err --; + } else if (errno != EBADF) { + PERROR("read"); + err --; + } + } + + res = close(fd); + if (res == -1) { + PERROR("close"); + return -1; + } + res = unlink(testfile); + if (res == -1) { + PERROR("unlink"); + return -1; + } + res = check_nonexist(testfile); + if (res == -1) + return -1; + res = check_nonexist(testfile_r); + if (res == -1) + return -1; + if (err) + return -1; + +succ: + success(); + return 0; +} + +#define test_open_acc(flags, mode, err) \ + do_test_open_acc(flags, #flags, mode, err) + +static int do_test_open_acc(int flags, const char *flags_str, int mode, int err) +{ + const char *data = testdata; + int datalen = testdatalen; + int res; + int fd; + + start_test("open_acc(%s) mode: 0%03o message: '%s'", flags_str, mode, + strerror(err)); + unlink(testfile); + res = create_testfile(testfile, data, datalen); + if (res == -1) + return -1; + + res = chmod(testfile, mode); + if (res == -1) { + PERROR("chmod"); + return -1; + } + + res = check_testfile_mode(testfile, mode); + if (res == -1) + return -1; + + fd = open(testfile, flags); + if (fd == -1) { + if (err != errno) { + PERROR("open"); + return -1; + } + } else { + if (err) { + ERROR("open should have failed"); + close(fd); + return -1; + } + close(fd); + } + + res = unlink(testfile); + if (res == -1) { + PERROR("unlink"); + return -1; + } + res = check_nonexist(testfile); + if (res == -1) + return -1; + res = check_nonexist(testfile_r); + if (res == -1) + return -1; + + success(); + return 0; +} + +static int test_symlink(void) +{ + char buf[1024]; + const char *data = testdata; + int datalen = testdatalen; + int linklen = strlen(testfile); + int err = 0; + int res; + + start_test("symlink"); + res = create_testfile(testfile, data, datalen); + if (res == -1) + return -1; + + unlink(testfile2); + res = symlink(testfile, testfile2); + if (res == -1) { + PERROR("symlink"); + return -1; + } + res = check_type(testfile2, S_IFLNK); + if (res == -1) + return -1; + err += check_mode(testfile2, 0777); + err += check_nlink(testfile2, 1); + res = readlink(testfile2, buf, sizeof(buf)); + if (res == -1) { + PERROR("readlink"); + err--; + } + if (res != linklen) { + ERROR("short readlink: %u instead of %u", res, linklen); + err--; + } + if (memcmp(buf, testfile, linklen) != 0) { + ERROR("link mismatch"); + err--; + } + err += check_size(testfile2, datalen); + err += check_data(testfile2, data, 0, datalen); + res = unlink(testfile2); + if (res == -1) { + PERROR("unlink"); + return -1; + } + res = check_nonexist(testfile2); + if (res == -1) + return -1; + if (err) + return -1; + + res = unlink(testfile); + if (res == -1) { + PERROR("unlink"); + return -1; + } + res = check_nonexist(testfile); + if (res == -1) + return -1; + + success(); + return 0; +} + +static int test_link(void) +{ + const char *data = testdata; + int datalen = testdatalen; + int err = 0; + int res; + + start_test("link"); + res = create_testfile(testfile, data, datalen); + if (res == -1) + return -1; + + unlink(testfile2); + res = link(testfile, testfile2); + if (res == -1) { + PERROR("link"); + return -1; + } + res = check_type(testfile2, S_IFREG); + if (res == -1) + return -1; + err += check_mode(testfile2, 0644); + err += check_nlink(testfile2, 2); + err += check_size(testfile2, datalen); + err += check_data(testfile2, data, 0, datalen); + res = unlink(testfile); + if (res == -1) { + PERROR("unlink"); + return -1; + } + res = check_nonexist(testfile); + if (res == -1) + return -1; + + err += check_nlink(testfile2, 1); + res = unlink(testfile2); + if (res == -1) { + PERROR("unlink"); + return -1; + } + res = check_nonexist(testfile2); + if (res == -1) + return -1; + if (err) + return -1; + + success(); + return 0; +} + +static int test_link2(void) +{ + const char *data = testdata; + int datalen = testdatalen; + int err = 0; + int res; + + start_test("link-unlink-link"); + res = create_testfile(testfile, data, datalen); + if (res == -1) + return -1; + + unlink(testfile2); + res = link(testfile, testfile2); + if (res == -1) { + PERROR("link"); + return -1; + } + res = unlink(testfile); + if (res == -1) { + PERROR("unlink"); + return -1; + } + res = check_nonexist(testfile); + if (res == -1) + return -1; + res = link(testfile2, testfile); + if (res == -1) { + PERROR("link"); + } + res = check_type(testfile, S_IFREG); + if (res == -1) + return -1; + err += check_mode(testfile, 0644); + err += check_nlink(testfile, 2); + err += check_size(testfile, datalen); + err += check_data(testfile, data, 0, datalen); + + res = unlink(testfile2); + if (res == -1) { + PERROR("unlink"); + return -1; + } + err += check_nlink(testfile, 1); + res = unlink(testfile); + if (res == -1) { + PERROR("unlink"); + return -1; + } + res = check_nonexist(testfile); + if (res == -1) + return -1; + if (err) + return -1; + + success(); + return 0; +} + +static int test_rename_file(void) +{ + const char *data = testdata; + int datalen = testdatalen; + int err = 0; + int res; + + start_test("rename file"); + res = create_testfile(testfile, data, datalen); + if (res == -1) + return -1; + + unlink(testfile2); + res = rename(testfile, testfile2); + if (res == -1) { + PERROR("rename"); + return -1; + } + res = check_nonexist(testfile); + if (res == -1) + return -1; + res = check_type(testfile2, S_IFREG); + if (res == -1) + return -1; + err += check_mode(testfile2, 0644); + err += check_nlink(testfile2, 1); + err += check_size(testfile2, datalen); + err += check_data(testfile2, data, 0, datalen); + res = unlink(testfile2); + if (res == -1) { + PERROR("unlink"); + return -1; + } + res = check_nonexist(testfile2); + if (res == -1) + return -1; + if (err) + return -1; + + success(); + return 0; +} + +static int test_rename_dir(void) +{ + int err = 0; + int res; + + start_test("rename dir"); + res = create_dir(testdir, testdir_files); + if (res == -1) + return -1; + + rmdir(testdir2); + res = rename(testdir, testdir2); + if (res == -1) { + PERROR("rename"); + cleanup_dir(testdir, testdir_files, 1); + return -1; + } + res = check_nonexist(testdir); + if (res == -1) { + cleanup_dir(testdir, testdir_files, 1); + return -1; + } + res = check_type(testdir2, S_IFDIR); + if (res == -1) { + cleanup_dir(testdir2, testdir_files, 1); + return -1; + } + err += check_mode(testdir2, 0755); + err += check_dir_contents(testdir2, testdir_files); + err += cleanup_dir(testdir2, testdir_files, 0); + res = rmdir(testdir2); + if (res == -1) { + PERROR("rmdir"); + return -1; + } + res = check_nonexist(testdir2); + if (res == -1) + return -1; + if (err) + return -1; + + success(); + return 0; +} + +static int test_rename_dir_loop(void) +{ +#define PATH(p) (snprintf(path, sizeof path, "%s/%s", testdir, p), path) +#define PATH2(p) (snprintf(path2, sizeof path2, "%s/%s", testdir, p), path2) + + char path[1280], path2[1280]; + int err = 0; + int res; + + start_test("rename dir loop"); + + res = create_dir(testdir, testdir_files); + if (res == -1) + return -1; + + res = mkdir(PATH("a"), 0755); + if (res == -1) { + PERROR("mkdir"); + goto fail; + } + + res = rename(PATH("a"), PATH2("a")); + if (res == -1) { + PERROR("rename"); + goto fail; + } + + errno = 0; + res = rename(PATH("a"), PATH2("a/b")); + if (res == 0 || errno != EINVAL) { + PERROR("rename"); + goto fail; + } + + res = mkdir(PATH("a/b"), 0755); + if (res == -1) { + PERROR("mkdir"); + goto fail; + } + + res = mkdir(PATH("a/b/c"), 0755); + if (res == -1) { + PERROR("mkdir"); + goto fail; + } + + errno = 0; + res = rename(PATH("a"), PATH2("a/b/c")); + if (res == 0 || errno != EINVAL) { + PERROR("rename"); + goto fail; + } + + errno = 0; + res = rename(PATH("a"), PATH2("a/b/c/a")); + if (res == 0 || errno != EINVAL) { + PERROR("rename"); + goto fail; + } + + errno = 0; + res = rename(PATH("a/b/c"), PATH2("a")); + if (res == 0 || errno != ENOTEMPTY) { + PERROR("rename"); + goto fail; + } + + res = open(PATH("a/foo"), O_CREAT, 0644); + if (res == -1) { + PERROR("open"); + goto fail; + } + close(res); + + res = rename(PATH("a/foo"), PATH2("a/bar")); + if (res == -1) { + PERROR("rename"); + goto fail; + } + + res = rename(PATH("a/bar"), PATH2("a/foo")); + if (res == -1) { + PERROR("rename"); + goto fail; + } + + res = rename(PATH("a/foo"), PATH2("a/b/bar")); + if (res == -1) { + PERROR("rename"); + goto fail; + } + + res = rename(PATH("a/b/bar"), PATH2("a/foo")); + if (res == -1) { + PERROR("rename"); + goto fail; + } + + res = rename(PATH("a/foo"), PATH2("a/b/c/bar")); + if (res == -1) { + PERROR("rename"); + goto fail; + } + + res = rename(PATH("a/b/c/bar"), PATH2("a/foo")); + if (res == -1) { + PERROR("rename"); + goto fail; + } + + res = open(PATH("a/bar"), O_CREAT, 0644); + if (res == -1) { + PERROR("open"); + goto fail; + } + close(res); + + res = rename(PATH("a/foo"), PATH2("a/bar")); + if (res == -1) { + PERROR("rename"); + goto fail; + } + + unlink(PATH("a/bar")); + + res = rename(PATH("a/b"), PATH2("a/d")); + if (res == -1) { + PERROR("rename"); + goto fail; + } + + res = rename(PATH("a/d"), PATH2("a/b")); + if (res == -1) { + PERROR("rename"); + goto fail; + } + + res = mkdir(PATH("a/d"), 0755); + if (res == -1) { + PERROR("mkdir"); + goto fail; + } + + res = rename(PATH("a/b"), PATH2("a/d")); + if (res == -1) { + PERROR("rename"); + goto fail; + } + + res = rename(PATH("a/d"), PATH2("a/b")); + if (res == -1) { + PERROR("rename"); + goto fail; + } + + res = mkdir(PATH("a/d"), 0755); + if (res == -1) { + PERROR("mkdir"); + goto fail; + } + + res = mkdir(PATH("a/d/e"), 0755); + if (res == -1) { + PERROR("mkdir"); + goto fail; + } + + errno = 0; + res = rename(PATH("a/b"), PATH2("a/d")); + if (res == 0 || (errno != ENOTEMPTY && errno != EEXIST)) { + PERROR("rename"); + goto fail; + } + + rmdir(PATH("a/d/e")); + rmdir(PATH("a/d")); + + rmdir(PATH("a/b/c")); + rmdir(PATH("a/b")); + rmdir(PATH("a")); + + err += cleanup_dir(testdir, testdir_files, 0); + res = rmdir(testdir); + if (res == -1) { + PERROR("rmdir"); + goto fail; + } + res = check_nonexist(testdir); + if (res == -1) + return -1; + if (err) + return -1; + + success(); + return 0; + +fail: + unlink(PATH("a/bar")); + + rmdir(PATH("a/d/e")); + rmdir(PATH("a/d")); + + rmdir(PATH("a/b/c")); + rmdir(PATH("a/b")); + rmdir(PATH("a")); + + cleanup_dir(testdir, testdir_files, 1); + rmdir(testdir); + + return -1; + +#undef PATH2 +#undef PATH +} + +static int test_mkfifo(void) +{ + int res; + int err = 0; + + start_test("mkfifo"); + unlink(testfile); + res = mkfifo(testfile, 0644); + if (res == -1) { + PERROR("mkfifo"); + return -1; + } + res = check_type(testfile, S_IFIFO); + if (res == -1) + return -1; + err += check_mode(testfile, 0644); + err += check_nlink(testfile, 1); + res = unlink(testfile); + if (res == -1) { + PERROR("unlink"); + return -1; + } + res = check_nonexist(testfile); + if (res == -1) + return -1; + if (err) + return -1; + + success(); + return 0; +} + +static int test_mkdir(void) +{ + int res; + int err = 0; + const char *dir_contents[] = {NULL}; + + start_test("mkdir"); + rmdir(testdir); + res = mkdir(testdir, 0755); + if (res == -1) { + PERROR("mkdir"); + return -1; + } + res = check_type(testdir, S_IFDIR); + if (res == -1) + return -1; + err += check_mode(testdir, 0755); + /* Some file systems (like btrfs) don't track link + count for directories */ + //err += check_nlink(testdir, 2); + err += check_dir_contents(testdir, dir_contents); + res = rmdir(testdir); + if (res == -1) { + PERROR("rmdir"); + return -1; + } + res = check_nonexist(testdir); + if (res == -1) + return -1; + if (err) + return -1; + + success(); + return 0; +} + +static int test_socket(void) +{ + struct sockaddr_un su; + int fd; + int res; + int err = 0; + const size_t test_sock_len = strlen(testsock) + 1; + + start_test("socket"); + if (test_sock_len > sizeof(su.sun_path)) { + fprintf(stderr, "Need to shorten mount point by %zu chars\n", + strlen(testsock) + 1 - sizeof(su.sun_path)); + return -1; + } + unlink(testsock); + fd = socket(AF_UNIX, SOCK_STREAM, 0); + if (fd < 0) { + PERROR("socket"); + return -1; + } + su.sun_family = AF_UNIX; + + strncpy(su.sun_path, testsock, test_sock_len); + su.sun_path[sizeof(su.sun_path) - 1] = '\0'; + res = bind(fd, (struct sockaddr*)&su, sizeof(su)); + if (res == -1) { + PERROR("bind"); + return -1; + } + + res = check_type(testsock, S_IFSOCK); + if (res == -1) { + close(fd); + return -1; + } + err += check_nlink(testsock, 1); + close(fd); + res = unlink(testsock); + if (res == -1) { + PERROR("unlink"); + return -1; + } + res = check_nonexist(testsock); + if (res == -1) + return -1; + if (err) + return -1; + + success(); + return 0; +} + +#define test_create_ro_dir(flags) \ + do_test_create_ro_dir(flags, #flags) + +static int do_test_create_ro_dir(int flags, const char *flags_str) +{ + int res; + int err = 0; + int fd; + + start_test("open(%s) in read-only directory", flags_str); + rmdir(testdir); + res = mkdir(testdir, 0555); + if (res == -1) { + PERROR("mkdir"); + return -1; + } + fd = open(subfile, flags, 0644); + if (fd != -1) { + close(fd); + unlink(subfile); + ERROR("open should have failed"); + err--; + } else { + res = check_nonexist(subfile); + if (res == -1) + err--; + } + unlink(subfile); + res = rmdir(testdir); + if (res == -1) { + PERROR("rmdir"); + return -1; + } + res = check_nonexist(testdir); + if (res == -1) + return -1; + if (err) + return -1; + + success(); + return 0; +} + +#ifndef __FreeBSD__ +/* this tests open with O_TMPFILE + note that this will only work with the fuse low level api + you will get ENOTSUP with the high level api */ +static int test_create_tmpfile(void) +{ + rmdir(testdir); + int res = mkdir(testdir, 0777); + if (res) + return -1; + + start_test("create tmpfile"); + + int fd = open(testdir, O_TMPFILE | O_RDWR, S_IRUSR | S_IWUSR); + if(fd == -1) { + if (errno == ENOTSUP) { + /* don't bother if we're working on an old kernel + or on the high level API */ + return 0; + } + + PERROR("open O_TMPFILE | O_RDWR"); + return -1; + } + close(fd); + + fd = open(testdir, O_TMPFILE | O_WRONLY | O_EXCL, S_IRUSR | S_IWUSR); + if(fd == -1){ + PERROR("open with O_TMPFILE | O_WRONLY | O_EXCL"); + return -1; + }; + close(fd); + + fd = open(testdir, O_TMPFILE | O_RDONLY, S_IRUSR); + if (fd != -1) { + ERROR("open with O_TMPFILE | O_RDONLY succeeded"); + return -1; + } + + success(); + return 0; +} + +static int test_create_and_link_tmpfile(void) +{ + /* skip this test for now since the github runner will fail in the linkat call below */ + return 0; + + rmdir(testdir); + unlink(testfile); + + int res = mkdir(testdir, 0777); + if (res) + return -1; + + start_test("create and link tmpfile"); + + int fd = open(testdir, O_TMPFILE | O_RDWR | O_EXCL, S_IRUSR | S_IWUSR); + if(fd == -1) { + if (errno == ENOTSUP) { + /* don't bother if we're working on an old kernel + or on the high level API */ + return 0; + } + PERROR("open with O_TMPFILE | O_RDWR | O_EXCL"); + return -1; + } + + if (!linkat(fd, "", AT_FDCWD, testfile, AT_EMPTY_PATH)) { + ERROR("linkat succeeded on a tmpfile opened with O_EXCL"); + return -1; + } + close(fd); + + fd = open(testdir, O_TMPFILE | O_RDWR, S_IRUSR | S_IWUSR); + if(fd == -1) { + PERROR("open O_TMPFILE"); + return -1; + } + + if (check_nonexist(testfile)) { + return -1; + } + + if (linkat(fd, "", AT_FDCWD, testfile, AT_EMPTY_PATH)) { + PERROR("linkat tempfile"); + return -1; + } + close(fd); + + if (check_nlink(testfile, 1)) { + return -1; + } + unlink(testfile); + + success(); + return 0; +} +#endif + +int main(int argc, char *argv[]) +{ + int err = 0; + int a; + int is_root; + + /* Piped stdout is fully buffered, so a killed test loses whatever it + * had printed about what it was doing. + */ + setvbuf(stdout, NULL, _IOLBF, 0); + + umask(0); + if (argc < 2 || argc > 4) { + fprintf(stderr, "usage: %s testdir [:realdir] [[-]test#] [-u]\n", argv[0]); + return 1; + } + basepath = argv[1]; + basepath_r = basepath; + for (a = 2; a < argc; a++) { + char *endptr; + char *arg = argv[a]; + if (arg[0] == ':') { + basepath_r = arg + 1; + } else { + if (arg[0] == '-') { + arg++; + if (arg[0] == 'u') { + unlinked_test = 1; + endptr = arg + 1; + } else { + skip_test = strtoul(arg, &endptr, 10); + } + } else { + select_test = strtoul(arg, &endptr, 10); + } + if (arg[0] == '\0' || *endptr != '\0') { + fprintf(stderr, "invalid option: '%s'\n", argv[a]); + return 1; + } + } + } + assert(strlen(basepath) < 512); + assert(strlen(basepath_r) < 512); + if (basepath[0] != '/') { + fprintf(stderr, "testdir must be an absolute path\n"); + return 1; + } + + sprintf(testfile, "%s/testfile", basepath); + sprintf(testfile2, "%s/testfile2", basepath); + sprintf(testdir, "%s/testdir", basepath); + sprintf(testdir2, "%s/testdir2", basepath); + sprintf(subfile, "%s/subfile", testdir2); + sprintf(testsock, "%s/testsock", basepath); + + sprintf(testfile_r, "%s/testfile", basepath_r); + sprintf(testfile2_r, "%s/testfile2", basepath_r); + sprintf(testdir_r, "%s/testdir", basepath_r); + sprintf(testdir2_r, "%s/testdir2", basepath_r); + sprintf(subfile_r, "%s/subfile", testdir2_r); + + is_root = (geteuid() == 0); + + err += test_create(); + err += test_create_unlink(); + err += test_symlink(); + err += test_link(); + err += test_link2(); + err += test_mknod(); + err += test_mkfifo(); + err += test_mkdir(); + err += test_rename_file(); + err += test_rename_dir(); + err += test_rename_dir_loop(); + err += test_seekdir(); + err += test_socket(); + err += test_utime(); + err += test_truncate(0); + err += test_truncate(testdatalen / 2); + err += test_truncate(testdatalen); + err += test_truncate(testdatalen + 100); + err += test_ftruncate(0, 0600); + err += test_ftruncate(testdatalen / 2, 0600); + err += test_ftruncate(testdatalen, 0600); + err += test_ftruncate(testdatalen + 100, 0600); + err += test_ftruncate(0, 0400); + err += test_ftruncate(0, 0200); + err += test_ftruncate(0, 0000); + err += test_open(0, O_RDONLY, 0); + err += test_open(1, O_RDONLY, 0); + err += test_open(1, O_RDWR, 0); + err += test_open(1, O_WRONLY, 0); + err += test_open(0, O_RDWR | O_CREAT, 0600); + err += test_open(1, O_RDWR | O_CREAT, 0600); + err += test_open(0, O_RDWR | O_CREAT | O_TRUNC, 0600); + err += test_open(1, O_RDWR | O_CREAT | O_TRUNC, 0600); + err += test_open(0, O_RDONLY | O_CREAT, 0600); + err += test_open(0, O_RDONLY | O_CREAT, 0400); + err += test_open(0, O_RDONLY | O_CREAT, 0200); + err += test_open(0, O_RDONLY | O_CREAT, 0000); + err += test_open(0, O_WRONLY | O_CREAT, 0600); + err += test_open(0, O_WRONLY | O_CREAT, 0400); + err += test_open(0, O_WRONLY | O_CREAT, 0200); + err += test_open(0, O_WRONLY | O_CREAT, 0000); + err += test_open(0, O_RDWR | O_CREAT, 0400); + err += test_open(0, O_RDWR | O_CREAT, 0200); + err += test_open(0, O_RDWR | O_CREAT, 0000); + err += test_open(0, O_RDWR | O_CREAT | O_EXCL, 0600); + err += test_open(1, O_RDWR | O_CREAT | O_EXCL, 0600); + err += test_open(0, O_RDWR | O_CREAT | O_EXCL, 0000); + err += test_open(1, O_RDWR | O_CREAT | O_EXCL, 0000); + err += test_open_acc(O_RDONLY, 0600, 0); + err += test_open_acc(O_WRONLY, 0600, 0); + err += test_open_acc(O_RDWR, 0600, 0); + err += test_open_acc(O_RDONLY, 0400, 0); + err += test_open_acc(O_WRONLY, 0200, 0); + if(!is_root) { + err += test_open_acc(O_RDONLY | O_TRUNC, 0400, EACCES); + err += test_open_acc(O_WRONLY, 0400, EACCES); + err += test_open_acc(O_RDWR, 0400, EACCES); + err += test_open_acc(O_RDONLY, 0200, EACCES); + err += test_open_acc(O_RDWR, 0200, EACCES); + err += test_open_acc(O_RDONLY, 0000, EACCES); + err += test_open_acc(O_WRONLY, 0000, EACCES); + err += test_open_acc(O_RDWR, 0000, EACCES); + } + err += test_create_ro_dir(O_CREAT); + err += test_create_ro_dir(O_CREAT | O_EXCL); + err += test_create_ro_dir(O_CREAT | O_WRONLY); + err += test_create_ro_dir(O_CREAT | O_TRUNC); + err += test_copy_file_range(); + err += test_statx(); +#ifndef __FreeBSD__ + err += test_create_tmpfile(); + err += test_create_and_link_tmpfile(); +#endif + + unlink(testfile2); + unlink(testsock); + rmdir(testdir); + rmdir(testdir2); + + if (err) { + fprintf(stderr, "%i tests failed\n", -err); + return 1; + } + + return check_unlinked_testfiles(); +} diff --git a/test/test_want_conversion.c b/test/test_want_conversion.c new file mode 100644 index 0000000..db731ed --- /dev/null +++ b/test/test_want_conversion.c @@ -0,0 +1,181 @@ +#define FUSE_USE_VERSION FUSE_MAKE_VERSION(3, 17) + +#include "util.h" +#include "fuse_i.h" +#include "fuse_lowlevel.h" +#include +#include +#include +#include +#include + +static void print_conn_info(const char *prefix, struct fuse_conn_info *conn) +{ + struct fuse_session *se = container_of(conn, struct fuse_session, conn); + + printf("%s: want=0x%" PRIx32 " want_ext=0x%" PRIx64 + " want_default=0x%" PRIx32 " want_ext_default=0x%" PRIx64 "\n", + prefix, conn->want, conn->want_ext, se->conn_want, + se->conn_want_ext); +} + +static void application_init_old_style(struct fuse_conn_info *conn) +{ + /* Simulate application init the old style */ + conn->want |= FUSE_CAP_ASYNC_READ; + conn->want &= ~FUSE_CAP_SPLICE_READ; + + /* + * Also use new style API, as that might happen through + * fuse_apply_conn_info_opts() + */ + fuse_set_feature_flag(conn, FUSE_CAP_IOCTL_DIR); +} + +static void application_init_new_style(struct fuse_conn_info *conn) +{ + /* Simulate application init the new style */ + fuse_set_feature_flag(conn, FUSE_CAP_ASYNC_READ); + fuse_set_feature_flag(conn, FUSE_CAP_IOCTL_DIR); + fuse_unset_feature_flag(conn, FUSE_CAP_SPLICE_READ); +} + +static void test_fuse_fs_init(struct fuse_conn_info *conn, bool new_style) +{ + /* High-level init */ + fuse_set_feature_flag(conn, FUSE_CAP_EXPORT_SUPPORT); + + if (new_style) + application_init_new_style(conn); + else + application_init_old_style(conn); +} + +static void test_do_init(struct fuse_conn_info *conn, bool new_style) +{ + /* Initial setup */ + conn->capable_ext = FUSE_CAP_SPLICE_READ | FUSE_CAP_SPLICE_WRITE | + FUSE_CAP_SPLICE_MOVE | FUSE_CAP_POSIX_LOCKS | + FUSE_CAP_FLOCK_LOCKS | FUSE_CAP_EXPORT_SUPPORT | + FUSE_CAP_ASYNC_READ | FUSE_CAP_IOCTL_DIR; + conn->capable = fuse_lower_32_bits(conn->capable_ext); + + fuse_set_feature_flag(conn, FUSE_CAP_SPLICE_READ | + FUSE_CAP_SPLICE_WRITE | + FUSE_CAP_SPLICE_MOVE); + + print_conn_info("Initial state", conn); + + int rc; + + test_fuse_fs_init(conn, new_style); + print_conn_info("After init", conn); + + rc = fuse_convert_to_conn_want_ext(conn); + assert(rc == 0); + + /* Verify all expected flags are set */ + assert(!(conn->want_ext & FUSE_CAP_SPLICE_READ)); + assert(conn->want_ext & FUSE_CAP_SPLICE_WRITE); + assert(conn->want_ext & FUSE_CAP_SPLICE_MOVE); + assert(conn->want_ext & FUSE_CAP_EXPORT_SUPPORT); + assert(conn->want_ext & FUSE_CAP_ASYNC_READ); + assert(conn->want_ext & FUSE_CAP_IOCTL_DIR); + + /* Verify no other flags are set */ + assert(conn->want_ext == + (FUSE_CAP_SPLICE_WRITE | FUSE_CAP_SPLICE_MOVE | + FUSE_CAP_EXPORT_SUPPORT | FUSE_CAP_ASYNC_READ | + FUSE_CAP_IOCTL_DIR)); + + print_conn_info("After init", conn); +} + +static void test_want_conversion_basic(void) +{ + const struct fuse_lowlevel_ops ops = { 0 }; + struct fuse_args args = FUSE_ARGS_INIT(0, NULL); + struct fuse_session *se; + struct fuse_conn_info *conn; + + /* Add the program name to arg[0] */ + if (fuse_opt_add_arg(&args, "test_signals")) { + fprintf(stderr, "Failed to add argument\n"); + errx(1, "Failed to add argument"); + } + + + se = fuse_session_new(&args, &ops, sizeof(ops), NULL); + assert(se); + conn = &se->conn; + printf("\nTesting basic want conversion, old style:\n"); + test_do_init(conn, false); + fuse_session_destroy(se); + + se = fuse_session_new(&args, &ops, sizeof(ops), NULL); + assert(se); + conn = &se->conn; + printf("\nTesting basic want conversion, new style:\n"); + test_do_init(conn, true); + print_conn_info("After init", conn); + fuse_session_destroy(se); + + fuse_opt_free_args(&args); + +} + +static void test_want_conversion_conflict(void) +{ + struct fuse_conn_info conn = { 0 }; + int rc; + + printf("\nTesting want conversion conflict:\n"); + + /* Test conflicting values */ + /* Initialize like fuse_lowlevel.c does */ + conn.capable_ext = FUSE_CAP_SPLICE_READ | FUSE_CAP_SPLICE_WRITE | + FUSE_CAP_SPLICE_MOVE | FUSE_CAP_POSIX_LOCKS | + FUSE_CAP_FLOCK_LOCKS; + conn.capable = fuse_lower_32_bits(conn.capable_ext); + conn.want_ext = conn.capable_ext; + conn.want = fuse_lower_32_bits(conn.want_ext); + print_conn_info("Test conflict initial", &conn); + + /* Simulate application init modifying capabilities */ + conn.want_ext |= FUSE_CAP_ATOMIC_O_TRUNC; /* Add new capability */ + conn.want &= ~FUSE_CAP_SPLICE_READ; /* Remove a capability */ + + rc = fuse_convert_to_conn_want_ext(&conn); + assert(rc == -EINVAL); + print_conn_info("Test conflict after", &conn); + + printf("Want conversion conflict test passed\n"); +} + +static void test_want_conversion_high_bits(void) +{ + struct fuse_conn_info conn = { 0 }; + int rc; + + printf("\nTesting want conversion high bits preservation:\n"); + + /* Test high bits preservation */ + conn.want_ext = (1ULL << 33) | FUSE_CAP_ASYNC_READ; + conn.want = fuse_lower_32_bits(conn.want_ext); + print_conn_info("Test high bits initial", &conn); + + rc = fuse_convert_to_conn_want_ext(&conn); + assert(rc == 0); + assert(conn.want_ext == ((1ULL << 33) | FUSE_CAP_ASYNC_READ)); + print_conn_info("Test high bits after", &conn); + + printf("Want conversion high bits test passed\n"); +} + +int main(void) +{ + test_want_conversion_basic(); + test_want_conversion_conflict(); + test_want_conversion_high_bits(); + return 0; +} diff --git a/test/test_write_cache.c b/test/test_write_cache.c new file mode 100644 index 0000000..00db5a6 --- /dev/null +++ b/test/test_write_cache.c @@ -0,0 +1,323 @@ +/* + FUSE: Filesystem in Userspace + Copyright (C) 2016 Nikolaus Rath + + This program can be distributed under the terms of the GNU GPLv2. + See the file GPL2.txt. +*/ + +#define FUSE_USE_VERSION 30 + +/* Not really needed - just to test build with FUSE_USE_VERSION == 30 */ +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef __linux__ +#include +#else +#include +#endif + +#define FILE_INO 2 +#define FILE_NAME "write_me" + +/* Command line parsing */ +struct options { + int writeback; + int data_size; + int delay_ms; +} options = { + .writeback = 0, + .data_size = 2048, + .delay_ms = 0, +}; + +#define WRITE_SYSCALLS 64 + +#define OPTION(t, p) { t, offsetof(struct options, p), 1 } +static const struct fuse_opt option_spec[] = { + OPTION("writeback_cache", writeback), + OPTION("--data-size=%d", data_size), OPTION("--delay_ms=%d", delay_ms), + FUSE_OPT_END +}; +static int got_write; +static atomic_int write_cnt; + +pthread_cond_t cond = PTHREAD_COND_INITIALIZER; +pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; +static int write_start, write_done; + +static void tfs_init(void *userdata, struct fuse_conn_info *conn) +{ + (void)userdata; + + if (options.writeback) { + assert(fuse_get_feature_flag(conn, FUSE_CAP_WRITEBACK_CACHE)); + fuse_set_feature_flag(conn, FUSE_CAP_WRITEBACK_CACHE); + } +} + +static int tfs_stat(fuse_ino_t ino, struct stat *stbuf) +{ + stbuf->st_ino = ino; + if (ino == FUSE_ROOT_ID) { + stbuf->st_mode = S_IFDIR | 0755; + stbuf->st_nlink = 1; + } + + else if (ino == FILE_INO) { + stbuf->st_mode = S_IFREG | 0222; + stbuf->st_nlink = 1; + stbuf->st_size = 0; + } + + else + return -1; + + return 0; +} + +static void tfs_lookup(fuse_req_t req, fuse_ino_t parent, const char *name) +{ + struct fuse_entry_param e; + + memset(&e, 0, sizeof(e)); + + if (parent != FUSE_ROOT_ID) + goto err_out; + else if (strcmp(name, FILE_NAME) == 0) + e.ino = FILE_INO; + else + goto err_out; + + if (tfs_stat(e.ino, &e.attr) != 0) + goto err_out; + fuse_reply_entry(req, &e); + return; + +err_out: + fuse_reply_err(req, ENOENT); +} + +static void tfs_getattr(fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info *fi) +{ + struct stat stbuf; + + (void)fi; + + memset(&stbuf, 0, sizeof(stbuf)); + if (tfs_stat(ino, &stbuf) != 0) + fuse_reply_err(req, ENOENT); + else + fuse_reply_attr(req, &stbuf, 5); +} + +static void tfs_open(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi) +{ + if (ino == FUSE_ROOT_ID) + fuse_reply_err(req, EISDIR); + else { + assert(ino == FILE_INO); + /* Test close(rofd) does not block waiting for pending writes */ + fi->noflush = !options.writeback && options.delay_ms && + (fi->flags & O_ACCMODE) == O_RDONLY; + fuse_reply_open(req, fi); + } +} + +static void tfs_write(fuse_req_t req, fuse_ino_t ino, const char *buf, + size_t size, off_t off, struct fuse_file_info *fi) +{ + (void)fi; + (void)buf; + (void)off; + size_t expected; + + assert(ino == FILE_INO); + expected = options.data_size; + if (options.writeback) + expected *= 2; + + write_cnt++; + + if (size != expected && !options.writeback) + fprintf(stderr, "ERROR: Expected %zd bytes, got %zd\n!", + expected, size); + else + got_write = 1; + + /* Simulate waiting for pending writes */ + if (options.delay_ms) { + pthread_mutex_lock(&lock); + write_start = 1; + pthread_cond_signal(&cond); + pthread_mutex_unlock(&lock); + + usleep(options.delay_ms * 1000); + + pthread_mutex_lock(&lock); + write_done = 1; + pthread_cond_signal(&cond); + pthread_mutex_unlock(&lock); + } + + fuse_reply_write(req, size); +} + +static struct fuse_lowlevel_ops tfs_oper = { + .init = tfs_init, + .lookup = tfs_lookup, + .getattr = tfs_getattr, + .open = tfs_open, + .write = tfs_write, +}; + +static void *close_rofd(void *data) +{ + int rofd = (int)(long)data; + + /* Wait for first write to start */ + pthread_mutex_lock(&lock); + while (!write_start && !write_done) + pthread_cond_wait(&cond, &lock); + pthread_mutex_unlock(&lock); + + close(rofd); + printf("rofd closed. write_start: %d write_done: %d\n", write_start, + write_done); + + /* First write should not have been completed */ + if (write_done) + fprintf(stderr, "ERROR: close(rofd) blocked on write!\n"); + + return NULL; +} + +static void *run_fs(void *data) +{ + struct fuse_session *se = (struct fuse_session *)data; + + assert(fuse_session_loop(se) == 0); + return NULL; +} + +static void test_fs(char *mountpoint) +{ + char fname[PATH_MAX]; + char *buf; + const size_t iosize = options.data_size; + const size_t dsize = options.data_size * WRITE_SYSCALLS; + int fd, rofd; + pthread_t rofd_thread; + off_t off = 0; + + buf = malloc(dsize); + assert(buf != NULL); + assert((fd = open("/dev/urandom", O_RDONLY)) != -1); + assert(read(fd, buf, dsize) == dsize); + close(fd); + + assert(snprintf(fname, PATH_MAX, "%s/" FILE_NAME, mountpoint) > 0); + fd = open(fname, O_WRONLY); + if (fd == -1) { + perror(fname); + assert(0); + } + + if (options.delay_ms) { + /* Verify that close(rofd) does not block waiting for pending writes */ + rofd = open(fname, O_RDONLY); + assert(pthread_create(&rofd_thread, NULL, close_rofd, + (void *)(long)rofd) == 0); + /* Give close_rofd time to start */ + usleep(options.delay_ms * 1000); + } + + for (int cnt = 0; cnt < WRITE_SYSCALLS; cnt++) { + assert(pwrite(fd, buf + off, iosize, off) == iosize); + off += iosize; + assert(off <= dsize); + } + free(buf); + close(fd); + + if (options.delay_ms) { + printf("rwfd closed. write_start: %d write_done: %d\n", + write_start, write_done); + assert(pthread_join(rofd_thread, NULL) == 0); + } +} + +int main(int argc, char *argv[]) +{ + struct fuse_args args = FUSE_ARGS_INIT(argc, argv); + struct fuse_session *se; + struct fuse_cmdline_opts fuse_opts; + pthread_t fs_thread; + + assert(fuse_opt_parse(&args, &options, option_spec, NULL) == 0); + assert(fuse_parse_cmdline(&args, &fuse_opts) == 0); +#ifndef __FreeBSD__ + assert(fuse_opt_add_arg(&args, "-oauto_unmount") == 0); +#endif + se = fuse_session_new(&args, &tfs_oper, sizeof(tfs_oper), NULL); + fuse_opt_free_args(&args); + assert(se != NULL); + assert(fuse_set_signal_handlers(se) == 0); + assert(fuse_session_mount(se, fuse_opts.mountpoint) == 0); + + /* Start file-system thread */ + assert(pthread_create(&fs_thread, NULL, run_fs, (void *)se) == 0); + + /* Write test data */ + test_fs(fuse_opts.mountpoint); + free(fuse_opts.mountpoint); + + /* Stop file system */ + fuse_session_exit(se); + fuse_session_unmount(se); + assert(pthread_join(fs_thread, NULL) == 0); + + assert(got_write == 1); + + /* + * when writeback cache is enabled, kernel side can merge requests, but + * memory pressure, system 'sync' might trigger data flushes before - flush + * might happen in between write syscalls - merging subpage writes into + * a single page and pages into large fuse requests might or might not work. + * Though we can expect that that at least some (but maybe all) write + * system calls can be merged. + */ + if (options.writeback) + assert(write_cnt < WRITE_SYSCALLS); + else + assert(write_cnt == WRITE_SYSCALLS); + + fuse_remove_signal_handlers(se); + fuse_session_destroy(se); + + printf("Test completed successfully.\n"); + return 0; +} + +/** + * Local Variables: + * mode: c + * indent-tabs-mode: nil + * c-basic-offset: 4 + * End: + */ diff --git a/tsan_suppressions.txt b/tsan_suppressions.txt new file mode 100644 index 0000000..3470d15 --- /dev/null +++ b/tsan_suppressions.txt @@ -0,0 +1,5 @@ +# Use with +# TSAN_OPTIONS="suppressions=tsan_suppressions.txt" ./myprogram + +# False positive +race:pthread_setcancelstate diff --git a/util/fuse.conf b/util/fuse.conf new file mode 100644 index 0000000..ab048e0 --- /dev/null +++ b/util/fuse.conf @@ -0,0 +1,17 @@ +# The file /etc/fuse.conf allows for the following parameters: +# +# user_allow_other - Using the allow_other mount option works fine as root, but +# in order to have it work as a regular user, you need to set user_allow_other +# in /etc/fuse.conf as well. This option allows non-root users to use the +# allow_other option. You need allow_other if you want users other than the +# owner of a mounted fuse to access it. This option must appear on a line by +# itself. There is no value; just the presence of the option activates it. + +#user_allow_other + + +# mount_max = n - this option sets the maximum number of mounts. +# It must be typed exactly as shown (with a single space before and after the +# equals sign). + +#mount_max = 1000 diff --git a/util/fusermount.c b/util/fusermount.c new file mode 100644 index 0000000..11b912b --- /dev/null +++ b/util/fusermount.c @@ -0,0 +1,1818 @@ +/* + FUSE: Filesystem in Userspace + Copyright (C) 2001-2007 Miklos Szeredi + + This program can be distributed under the terms of the GNU GPLv2. + See the file GPL2.txt. +*/ +/* This program does the mounting and unmounting of FUSE filesystems */ + +#define _GNU_SOURCE /* for clone,strchrnul and close_range */ +#include "fuse_config.h" +#include "mount_util.h" +#include "util.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "fuse_mount_compat.h" + +#include +#include +#include +#include +#include +#include + +#if defined HAVE_CLOSE_RANGE && defined linux +#include +#endif + +#if defined HAVE_LISTMOUNT +#include +#include +#include +#endif + +#define FUSE_COMMFD_ENV "_FUSE_COMMFD" +#define FUSE_KERN_DEVICE_ENV "FUSE_KERN_DEVICE" + +#define FUSE_DEV "/dev/fuse" + +static const char *progname; + +static int user_allow_other = 0; +static int mount_max = 1000; + +static int auto_unmount = 0; + +#ifdef GETMNTENT_NEEDS_UNESCAPING +// Older versions of musl libc don't unescape entries in /etc/mtab + +// unescapes octal sequences like \040 in-place +// That's ok, because unescaping can not extend the length of the string. +static void unescape(char *buf) { + char *src = buf; + char *dest = buf; + while (1) { + char *next_src = strchrnul(src, '\\'); + int offset = next_src - src; + memmove(dest, src, offset); + src = next_src; + dest += offset; + + if(*src == '\0') { + *dest = *src; + return; + } + src++; + + if('0' <= src[0] && src[0] < '2' && + '0' <= src[1] && src[1] < '8' && + '0' <= src[2] && src[2] < '8') { + *dest++ = (src[0] - '0') << 6 + | (src[1] - '0') << 3 + | (src[2] - '0') << 0; + src += 3; + } else if (src[0] == '\\') { + *dest++ = '\\'; + src += 1; + } else { + *dest++ = '\\'; + } + } +} + +static struct mntent *GETMNTENT(FILE *stream) +{ + struct mntent *entp = getmntent(stream); + if(entp != NULL) { + unescape(entp->mnt_fsname); + unescape(entp->mnt_dir); + unescape(entp->mnt_type); + unescape(entp->mnt_opts); + } + return entp; +} +#else +#define GETMNTENT getmntent +#endif // GETMNTENT_NEEDS_UNESCAPING + +/* + * Take a ',' separated option string and extract "x-" options + */ +static int extract_x_options(const char *original, char **non_x_opts, + char **x_opts) +{ + size_t orig_len; + const char *opt, *opt_end; + + orig_len = strlen(original) + 1; + + *non_x_opts = calloc(1, orig_len); + *x_opts = calloc(1, orig_len); + + size_t non_x_opts_len = orig_len; + size_t x_opts_len = orig_len; + + if (*non_x_opts == NULL || *x_opts == NULL) { + fprintf(stderr, "%s: Failed to allocate %zuB.\n", + __func__, orig_len); + return -ENOMEM; + } + + for (opt = original; opt < original + orig_len; opt = opt_end + 1) { + char *opt_buf; + + opt_end = strchr(opt, ','); + if (opt_end == NULL) + opt_end = original + orig_len; + + size_t opt_len = opt_end - opt; + size_t opt_len_left = orig_len - (opt - original); + size_t buf_len; + bool is_x_opts; + + if (strncmp(opt, "x-", MIN(2, opt_len_left)) == 0) { + buf_len = x_opts_len; + is_x_opts = true; + opt_buf = *x_opts; + } else { + buf_len = non_x_opts_len; + is_x_opts = false; + opt_buf = *non_x_opts; + } + + if (buf_len < orig_len) { + strncat(opt_buf, ",", 2); + buf_len -= 1; + } + + /* omits ',' */ + if ((ssize_t)(buf_len - opt_len) < 0) { + /* This would be a bug */ + fprintf(stderr, "%s: no buf space left in copy, orig='%s'\n", + __func__, original); + return -EIO; + } + + strncat(opt_buf, opt, opt_end - opt); + buf_len -= opt_len; + + if (is_x_opts) + x_opts_len = buf_len; + else + non_x_opts_len = buf_len; + } + + return 0; +} + +static const char *get_user_name(void) +{ + struct passwd *pw = getpwuid(getuid()); + if (pw != NULL && pw->pw_name != NULL) + return pw->pw_name; + else { + fprintf(stderr, "%s: could not determine username\n", progname); + return NULL; + } +} + +static uid_t oldfsuid; +static gid_t oldfsgid; + +static void drop_privs(void) +{ + if (getuid() != 0) { + oldfsuid = setfsuid(getuid()); + oldfsgid = setfsgid(getgid()); + } +} + +static void restore_privs(void) +{ + if (getuid() != 0) { + setfsuid(oldfsuid); + setfsgid(oldfsgid); + } +} + +#ifndef IGNORE_MTAB +/* + * Make sure that /etc/mtab is checked and updated atomically + */ +static int lock_umount(void) +{ + const char *mtab_lock = _PATH_MOUNTED ".fuselock"; + int mtablock; + int res; + struct stat mtab_stat; + + /* /etc/mtab could be a symlink to /proc/mounts */ + if (lstat(_PATH_MOUNTED, &mtab_stat) == 0 && S_ISLNK(mtab_stat.st_mode)) + return -1; + + mtablock = open(mtab_lock, O_RDWR | O_CREAT, 0600); + if (mtablock == -1) { + fprintf(stderr, "%s: unable to open fuse lock file: %s\n", + progname, strerror(errno)); + return -1; + } + res = lockf(mtablock, F_LOCK, 0); + if (res < 0) { + fprintf(stderr, "%s: error getting lock: %s\n", progname, + strerror(errno)); + close(mtablock); + return -1; + } + + return mtablock; +} + +static void unlock_umount(int mtablock) +{ + if (mtablock >= 0) { + int res; + + res = lockf(mtablock, F_ULOCK, 0); + if (res < 0) { + fprintf(stderr, "%s: error releasing lock: %s\n", + progname, strerror(errno)); + } + close(mtablock); + } +} + +static int add_mount(const char *source, const char *mnt, const char *type, + const char *opts) +{ + return fuse_mnt_add_mount(progname, source, mnt, type, opts); +} + +static int may_unmount(const char *mnt, int quiet) +{ + struct mntent *entp; + FILE *fp; + const char *user = NULL; + char uidstr[32]; + unsigned uidlen = 0; + int found; + const char *mtab = _PATH_MOUNTED; + + user = get_user_name(); + if (user == NULL) + return -1; + + fp = setmntent(mtab, "r"); + if (fp == NULL) { + fprintf(stderr, "%s: failed to open %s: %s\n", progname, mtab, + strerror(errno)); + return -1; + } + + uidlen = sprintf(uidstr, "%u", getuid()); + + found = 0; + while ((entp = GETMNTENT(fp)) != NULL) { + if (!found && strcmp(entp->mnt_dir, mnt) == 0 && + (strcmp(entp->mnt_type, "fuse") == 0 || + strcmp(entp->mnt_type, "fuseblk") == 0 || + strncmp(entp->mnt_type, "fuse.", 5) == 0 || + strncmp(entp->mnt_type, "fuseblk.", 8) == 0)) { + char *p = strstr(entp->mnt_opts, "user="); + if (p && + (p == entp->mnt_opts || *(p-1) == ',') && + strcmp(p + 5, user) == 0) { + found = 1; + break; + } + /* /etc/mtab is a link pointing to + /proc/mounts: */ + else if ((p = + strstr(entp->mnt_opts, "user_id=")) && + (p == entp->mnt_opts || + *(p-1) == ',') && + strncmp(p + 8, uidstr, uidlen) == 0 && + (*(p+8+uidlen) == ',' || + *(p+8+uidlen) == '\0')) { + found = 1; + break; + } + } + } + endmntent(fp); + + if (!found) { + if (!quiet) + fprintf(stderr, + "%s: entry for %s not found in %s\n", + progname, mnt, mtab); + return -1; + } + + return 0; +} +#endif + +/* + * Check whether the file specified in "fusermount3 -u" is really a + * mountpoint and not a symlink. This is necessary otherwise the user + * could move the mountpoint away and replace it with a symlink + * pointing to an arbitrary mount, thereby tricking fusermount3 into + * unmounting that (umount(2) will follow symlinks). + * + * This is the child process running in a separate mount namespace, so + * we don't mess with the global namespace and if the process is + * killed for any reason, mounts are automatically cleaned up. + * + * First make sure nothing is propagated back into the parent + * namespace by marking all mounts "private". + * + * Then bind mount parent onto a stable base where the user can't move + * it around. + * + * Finally check /proc/mounts for an entry matching the requested + * mountpoint. If it's found then we are OK, and the user can't move + * it around within the parent directory as rename() will return + * EBUSY. Be careful to ignore any mounts that existed before the + * bind. + */ +static int check_is_mount_child(void *p) +{ + const char **a = p; + const char *last = a[0]; + const char *mnt = a[1]; + const char *type = a[2]; + int res; + const char *procmounts = "/proc/mounts"; + int found; + FILE *fp; + struct mntent *entp; + int count; + + res = mount("", "/", "", MS_PRIVATE | MS_REC, NULL); + if (res == -1) { + fprintf(stderr, "%s: failed to mark mounts private: %s\n", + progname, strerror(errno)); + return 1; + } + + fp = setmntent(procmounts, "r"); + if (fp == NULL) { + fprintf(stderr, "%s: failed to open %s: %s\n", progname, + procmounts, strerror(errno)); + return 1; + } + + count = 0; + while (GETMNTENT(fp) != NULL) + count++; + endmntent(fp); + + fp = setmntent(procmounts, "r"); + if (fp == NULL) { + fprintf(stderr, "%s: failed to open %s: %s\n", progname, + procmounts, strerror(errno)); + return 1; + } + + res = mount(".", "/", "", MS_BIND | MS_REC, NULL); + if (res == -1) { + fprintf(stderr, "%s: failed to bind parent to /: %s\n", + progname, strerror(errno)); + return 1; + } + + found = 0; + while ((entp = GETMNTENT(fp)) != NULL) { + if (count > 0) { + count--; + continue; + } + if (entp->mnt_dir[0] == '/' && + strcmp(entp->mnt_dir + 1, last) == 0 && + (!type || strcmp(entp->mnt_type, type) == 0)) { + found = 1; + break; + } + } + endmntent(fp); + + if (!found) { + fprintf(stderr, "%s: %s not mounted\n", progname, mnt); + return 1; + } + + return 0; +} + +static pid_t clone_newns(void *a) +{ + char buf[131072]; + char *stack = buf + (sizeof(buf) / 2 - ((size_t) buf & 15)); + +#ifdef __ia64__ + extern int __clone2(int (*fn)(void *), + void *child_stack_base, size_t stack_size, + int flags, void *arg, pid_t *ptid, + void *tls, pid_t *ctid); + + return __clone2(check_is_mount_child, stack, sizeof(buf) / 2, + CLONE_NEWNS, a, NULL, NULL, NULL); +#else + return clone(check_is_mount_child, stack, CLONE_NEWNS, a); +#endif +} + +static int check_is_mount(const char *last, const char *mnt, const char *type) +{ + pid_t pid, p; + int status; + const char *a[3] = { last, mnt, type }; + + pid = clone_newns((void *) a); + if (pid == (pid_t) -1) { + fprintf(stderr, "%s: failed to clone namespace: %s\n", + progname, strerror(errno)); + return -1; + } + p = waitpid(pid, &status, __WCLONE); + if (p == (pid_t) -1) { + fprintf(stderr, "%s: waitpid failed: %s\n", + progname, strerror(errno)); + return -1; + } + if (!WIFEXITED(status)) { + fprintf(stderr, "%s: child terminated abnormally (status %i)\n", + progname, status); + return -1; + } + if (WEXITSTATUS(status) != 0) + return -1; + + return 0; +} + +static int chdir_to_parent(char *copy, const char **lastp) +{ + char *tmp; + const char *parent; + char buf[65536]; + int res; + + tmp = strrchr(copy, '/'); + if (tmp == NULL || tmp[1] == '\0') { + fprintf(stderr, "%s: internal error: invalid abs path: <%s>\n", + progname, copy); + return -1; + } + if (tmp != copy) { + *tmp = '\0'; + parent = copy; + *lastp = tmp + 1; + } else if (tmp[1] != '\0') { + *lastp = tmp + 1; + parent = "/"; + } else { + *lastp = "."; + parent = "/"; + } + + res = chdir(parent); + if (res == -1) { + fprintf(stderr, "%s: failed to chdir to %s: %s\n", + progname, parent, strerror(errno)); + return -1; + } + + if (getcwd(buf, sizeof(buf)) == NULL) { + fprintf(stderr, "%s: failed to obtain current directory: %s\n", + progname, strerror(errno)); + return -1; + } + if (strcmp(buf, parent) != 0) { + fprintf(stderr, "%s: mountpoint moved (%s -> %s)\n", progname, + parent, buf); + return -1; + + } + + return 0; +} + +#ifndef IGNORE_MTAB +static int unmount_fuse_locked(const char *mnt, int quiet, int lazy) +{ + int res; + char *copy; + const char *last; + int umount_flags = (lazy ? UMOUNT_DETACH : 0) | UMOUNT_NOFOLLOW; + + if (getuid() != 0) { + res = may_unmount(mnt, quiet); + if (res == -1) + return -1; + } + + copy = strdup(mnt); + if (copy == NULL) { + fprintf(stderr, "%s: failed to allocate memory\n", progname); + return -1; + } + + drop_privs(); + res = chdir_to_parent(copy, &last); + if (res == -1) { + restore_privs(); + goto out; + } + + res = umount2(last, umount_flags); + restore_privs(); + if (res == -1 && !quiet) { + fprintf(stderr, "%s: failed to unmount %s: %s\n", + progname, mnt, strerror(errno)); + } + +out: + free(copy); + if (res == -1) + return -1; + + res = chdir("/"); + if (res == -1) { + fprintf(stderr, "%s: failed to chdir to '/'\n", progname); + return -1; + } + + return fuse_mnt_remove_mount(progname, mnt); +} + +static int unmount_fuse(const char *mnt, int quiet, int lazy) +{ + int res; + int mtablock = lock_umount(); + + res = unmount_fuse_locked(mnt, quiet, lazy); + unlock_umount(mtablock); + + return res; +} + +static int count_fuse_fs_mtab(void) +{ + struct mntent *entp; + int count = 0; + const char *mtab = _PATH_MOUNTED; + FILE *fp = setmntent(mtab, "r"); + if (fp == NULL) { + fprintf(stderr, "%s: failed to open %s: %s\n", progname, mtab, + strerror(errno)); + return -1; + } + while ((entp = GETMNTENT(fp)) != NULL) { + if (strcmp(entp->mnt_type, "fuse") == 0 || + strncmp(entp->mnt_type, "fuse.", 5) == 0) + count ++; + } + endmntent(fp); + return count; +} + +#ifdef HAVE_LISTMOUNT +static int count_fuse_fs_ls_mnt(void) +{ + #define SMBUF_SIZE 1024 + #define MNT_ID_LEN 128 + + int fuse_count = 0; + int n_mounts = 0; + int ret = 0; + uint64_t mnt_ids[MNT_ID_LEN]; + unsigned char smbuf[SMBUF_SIZE]; + struct mnt_id_req req = { + .size = sizeof(struct mnt_id_req), + }; + struct statmount *sm; + + for (;;) { + req.mnt_id = LSMT_ROOT; + + n_mounts = syscall(SYS_listmount, &req, &mnt_ids, MNT_ID_LEN, 0); + if (n_mounts == -1) { + if (errno != ENOSYS) { + fprintf(stderr, "%s: failed to list mounts: %s\n", progname, + strerror(errno)); + } + return -1; + } + + for (int i = 0; i < n_mounts; i++) { + req.mnt_id = mnt_ids[i]; + req.param = STATMOUNT_FS_TYPE; + ret = syscall(SYS_statmount, &req, &smbuf, SMBUF_SIZE, 0); + if (ret) { + if (errno == ENOENT) + continue; + + fprintf(stderr, "%s: failed to stat mount %lld: %s\n", progname, + req.mnt_id, strerror(errno)); + return -1; + } + + sm = (struct statmount *)smbuf; + if (sm->mask & STATMOUNT_FS_TYPE && + strcmp(&sm->str[sm->fs_type], "fuse") == 0) + fuse_count++; + } + + if (n_mounts < MNT_ID_LEN) + break; + req.param = mnt_ids[MNT_ID_LEN - 1]; + } + return fuse_count; +} + +static int count_fuse_fs(void) +{ + int count = count_fuse_fs_ls_mnt(); + + return count >= 0 ? count : count_fuse_fs_mtab(); +} +#else +static int count_fuse_fs(void) +{ + return count_fuse_fs_mtab(); +} +#endif + +#else /* IGNORE_MTAB */ +static int count_fuse_fs(void) +{ + return 0; +} + +static int add_mount(const char *source, const char *mnt, const char *type, + const char *opts) +{ + (void) source; + (void) mnt; + (void) type; + (void) opts; + return 0; +} + +static int unmount_fuse(const char *mnt, int quiet, int lazy) +{ + (void) quiet; + return fuse_mnt_umount(progname, mnt, mnt, lazy); +} +#endif /* IGNORE_MTAB */ + +static void strip_line(char *line) +{ + char *s = strchr(line, '#'); + size_t len; + + if (s != NULL) + s[0] = '\0'; + /* + * Count down rather than walk a pointer back: an empty or all-blank + * line would form line - 1, which is undefined behaviour even though + * the store that follows lands inside the buffer. + */ + len = strlen(line); + while (len > 0 && isspace((unsigned char) line[len - 1])) + len--; + line[len] = '\0'; + for (s = line; isspace((unsigned char) *s); s++); + if (s != line) + memmove(line, s, strlen(s)+1); +} + +static void parse_line(char *line, int linenum) +{ + int tmp; + if (strcmp(line, "user_allow_other") == 0) + user_allow_other = 1; + else if (sscanf(line, "mount_max = %i", &tmp) == 1) { + if (tmp < -1) + fprintf(stderr, + "%s: invalid mount_max = %i in %s at line %i\n", + progname, tmp, FUSE_CONF, linenum); + else + mount_max = tmp; + } + else if(line[0]) + fprintf(stderr, + "%s: unknown parameter in %s at line %i: '%s'\n", + progname, FUSE_CONF, linenum, line); +} + +static void read_conf(void) +{ + FILE *fp = fopen(FUSE_CONF, "r"); + if (fp != NULL) { + int linenum = 1; + char line[256]; + int isnewline = 1; + while (fgets(line, sizeof(line), fp) != NULL) { + if (isnewline) { + if (line[strlen(line)-1] == '\n') { + strip_line(line); + parse_line(line, linenum); + } else { + isnewline = 0; + } + } else if(line[strlen(line)-1] == '\n') { + fprintf(stderr, "%s: reading %s: line %i too long\n", progname, FUSE_CONF, linenum); + + isnewline = 1; + } + if (isnewline) + linenum ++; + } + if (!isnewline) { + fprintf(stderr, "%s: reading %s: missing newline at end of file\n", progname, FUSE_CONF); + + } + if (ferror(fp)) { + fprintf(stderr, "%s: reading %s: read failed\n", progname, FUSE_CONF); + exit(1); + } + fclose(fp); + } else if (errno != ENOENT) { + bool fatal = (errno != EACCES && errno != ELOOP && + errno != ENAMETOOLONG && errno != ENOTDIR && + errno != EOVERFLOW); + fprintf(stderr, "%s: failed to open %s: %s\n", + progname, FUSE_CONF, strerror(errno)); + if (fatal) + exit(1); + } +} + +static int begins_with(const char *s, const char *beg) +{ + if (strncmp(s, beg, strlen(beg)) == 0) + return 1; + else + return 0; +} + +struct mount_flags { + const char *opt; + unsigned long flag; + int on; + int safe; +}; + +static struct mount_flags mount_flags[] = { + {"rw", MS_RDONLY, 0, 1}, + {"ro", MS_RDONLY, 1, 1}, + {"suid", MS_NOSUID, 0, 0}, + {"nosuid", MS_NOSUID, 1, 1}, + {"dev", MS_NODEV, 0, 0}, + {"nodev", MS_NODEV, 1, 1}, + {"exec", MS_NOEXEC, 0, 1}, + {"noexec", MS_NOEXEC, 1, 1}, + {"async", MS_SYNCHRONOUS, 0, 1}, + {"sync", MS_SYNCHRONOUS, 1, 1}, + {"atime", MS_NOATIME, 0, 1}, + {"noatime", MS_NOATIME, 1, 1}, + {"diratime", MS_NODIRATIME, 0, 1}, + {"nodiratime", MS_NODIRATIME, 1, 1}, + {"lazytime", MS_LAZYTIME, 1, 1}, + {"nolazytime", MS_LAZYTIME, 0, 1}, + {"relatime", MS_RELATIME, 1, 1}, + {"norelatime", MS_RELATIME, 0, 1}, + {"strictatime", MS_STRICTATIME, 1, 1}, + {"nostrictatime", MS_STRICTATIME, 0, 1}, + {"dirsync", MS_DIRSYNC, 1, 1}, + {"symfollow", MS_NOSYMFOLLOW, 0, 1}, + {"nosymfollow", MS_NOSYMFOLLOW, 1, 1}, + {NULL, 0, 0, 0} +}; + +static int find_mount_flag(const char *s, unsigned len, int *on, int *flag) +{ + int i; + + for (i = 0; mount_flags[i].opt != NULL; i++) { + const char *opt = mount_flags[i].opt; + if (strlen(opt) == len && strncmp(opt, s, len) == 0) { + *on = mount_flags[i].on; + *flag = mount_flags[i].flag; + if (!mount_flags[i].safe && getuid() != 0) { + *flag = 0; + fprintf(stderr, + "%s: unsafe option %s ignored\n", + progname, opt); + } + return 1; + } + } + return 0; +} + +static int add_option(char **optsp, const char *opt, unsigned expand) +{ + char *newopts; + if (*optsp == NULL) + newopts = strdup(opt); + else { + unsigned oldsize = strlen(*optsp); + unsigned newsize = oldsize + 1 + strlen(opt) + expand + 1; + newopts = (char *) realloc(*optsp, newsize); + if (newopts) + sprintf(newopts + oldsize, ",%s", opt); + } + if (newopts == NULL) { + fprintf(stderr, "%s: failed to allocate memory\n", progname); + return -1; + } + *optsp = newopts; + return 0; +} + +static int get_mnt_opts(int flags, char *opts, char **mnt_optsp) +{ + int i; + int l; + + if (!(flags & MS_RDONLY) && add_option(mnt_optsp, "rw", 0) == -1) + return -1; + + for (i = 0; mount_flags[i].opt != NULL; i++) { + if (mount_flags[i].on && (flags & mount_flags[i].flag) && + add_option(mnt_optsp, mount_flags[i].opt, 0) == -1) + return -1; + } + + if (add_option(mnt_optsp, opts, 0) == -1) + return -1; + /* remove comma from end of opts*/ + l = strlen(*mnt_optsp); + if (l > 0 && (*mnt_optsp)[l-1] == ',') + (*mnt_optsp)[l-1] = '\0'; + if (getuid() != 0) { + const char *user = get_user_name(); + if (user == NULL) + return -1; + + if (add_option(mnt_optsp, "user=", strlen(user)) == -1) + return -1; + strcat(*mnt_optsp, user); + } + return 0; +} + +static int opt_eq(const char *s, unsigned len, const char *opt) +{ + if(strlen(opt) == len && strncmp(s, opt, len) == 0) + return 1; + else + return 0; +} + +static int get_string_opt(const char *s, unsigned len, const char *opt, + char **val) +{ + int i; + unsigned opt_len = strlen(opt); + char *d; + + if (*val) + free(*val); + *val = (char *) malloc(len - opt_len + 1); + if (!*val) { + fprintf(stderr, "%s: failed to allocate memory\n", progname); + return 0; + } + + d = *val; + s += opt_len; + len -= opt_len; + for (i = 0; i < len; i++) { + if (s[i] == '\\' && i + 1 < len) + i++; + *d++ = s[i]; + } + *d = '\0'; + return 1; +} + +/* The kernel silently truncates the "data" argument to PAGE_SIZE-1 characters. + * This can be dangerous if it e.g. truncates the option "group_id=1000" to + * "group_id=1". + * This wrapper detects this case and bails out with an error. + */ +static int mount_notrunc(const char *source, const char *target, + const char *filesystemtype, unsigned long mountflags, + const char *data) { + if (strlen(data) > sysconf(_SC_PAGESIZE) - 1) { + fprintf(stderr, "%s: mount options too long\n", progname); + errno = EINVAL; + return -1; + } + return mount(source, target, filesystemtype, mountflags, data); +} + + +static int do_mount(const char *mnt, const char **typep, mode_t rootmode, + int fd, const char *opts, const char *dev, char **sourcep, + char **mnt_optsp) +{ + int res; + int flags = MS_NOSUID | MS_NODEV; + char *optbuf; + char *mnt_opts = NULL; + const char *s; + char *d; + char *fsname = NULL; + char *subtype = NULL; + char *source = NULL; + char *type = NULL; + int blkdev = 0; + + optbuf = (char *) malloc(strlen(opts) + 128); + if (!optbuf) { + fprintf(stderr, "%s: failed to allocate memory\n", progname); + return -1; + } + + for (s = opts, d = optbuf; *s;) { + unsigned len; + const char *fsname_str = "fsname="; + const char *subtype_str = "subtype="; + bool escape_ok = begins_with(s, fsname_str) || + begins_with(s, subtype_str); + for (len = 0; s[len]; len++) { + if (escape_ok && s[len] == '\\' && s[len + 1]) + len++; + else if (s[len] == ',') + break; + } + if (begins_with(s, fsname_str)) { + if (!get_string_opt(s, len, fsname_str, &fsname)) + goto err; + } else if (begins_with(s, subtype_str)) { + if (!get_string_opt(s, len, subtype_str, &subtype)) + goto err; + } else if (opt_eq(s, len, "blkdev")) { + if (getuid() != 0) { + fprintf(stderr, + "%s: option blkdev is privileged\n", + progname); + goto err; + } + blkdev = 1; + } else if (opt_eq(s, len, "auto_unmount")) { + auto_unmount = 1; + } else if (!opt_eq(s, len, "nonempty") && + !begins_with(s, "fd=") && + !begins_with(s, "rootmode=") && + !begins_with(s, "user_id=") && + !begins_with(s, "group_id=")) { + int on; + int flag; + int skip_option = 0; + if (opt_eq(s, len, "large_read")) { + struct utsname utsname; + unsigned kmaj, kmin; + res = uname(&utsname); + if (res == 0 && + sscanf(utsname.release, "%u.%u", + &kmaj, &kmin) == 2 && + (kmaj > 2 || (kmaj == 2 && kmin > 4))) { + fprintf(stderr, "%s: note: 'large_read' mount option is deprecated for %i.%i kernels\n", progname, kmaj, kmin); + skip_option = 1; + } + } + if (getuid() != 0 && !user_allow_other && + (opt_eq(s, len, "allow_other") || + opt_eq(s, len, "allow_root"))) { + fprintf(stderr, "%s: option %.*s only allowed if 'user_allow_other' is set in %s\n", progname, len, s, FUSE_CONF); + goto err; + } + if (!skip_option) { + if (find_mount_flag(s, len, &on, &flag)) { + if (on) + flags |= flag; + else + flags &= ~flag; + } else if (opt_eq(s, len, "default_permissions") || + opt_eq(s, len, "allow_other") || + begins_with(s, "max_read=") || + begins_with(s, "blksize=")) { + memcpy(d, s, len); + d += len; + *d++ = ','; + } else { + fprintf(stderr, "%s: unknown option '%.*s'\n", progname, len, s); + exit(1); + } + } + } + s += len; + if (*s) + s++; + } + *d = '\0'; + res = get_mnt_opts(flags, optbuf, &mnt_opts); + if (res == -1) + goto err; + + sprintf(d, "fd=%i,rootmode=%o,user_id=%u,group_id=%u", + fd, rootmode, getuid(), getgid()); + + source = malloc((fsname ? strlen(fsname) : 0) + + (subtype ? strlen(subtype) : 0) + strlen(dev) + 32); + + type = malloc((subtype ? strlen(subtype) : 0) + 32); + if (!type || !source) { + fprintf(stderr, "%s: failed to allocate memory\n", progname); + goto err; + } + + if (subtype) + sprintf(type, "%s.%s", blkdev ? "fuseblk" : "fuse", subtype); + else + strcpy(type, blkdev ? "fuseblk" : "fuse"); + + if (fsname) + strcpy(source, fsname); + else + strcpy(source, subtype ? subtype : dev); + + res = mount_notrunc(source, mnt, type, flags, optbuf); + if (res == -1 && errno == ENODEV && subtype) { + /* Probably missing subtype support */ + strcpy(type, blkdev ? "fuseblk" : "fuse"); + if (fsname) { + if (!blkdev) + sprintf(source, "%s#%s", subtype, fsname); + } else { + strcpy(source, type); + } + + res = mount_notrunc(source, mnt, type, flags, optbuf); + } + if (res == -1 && errno == EINVAL) { + /* It could be an old version not supporting group_id */ + sprintf(d, "fd=%i,rootmode=%o,user_id=%u", + fd, rootmode, getuid()); + res = mount_notrunc(source, mnt, type, flags, optbuf); + } + if (res == -1) { + int errno_save = errno; + if (blkdev && errno == ENODEV && !fuse_mnt_check_fuseblk()) + fprintf(stderr, "%s: 'fuseblk' support missing\n", + progname); + else + fprintf(stderr, "%s: mount failed: %s\n", progname, + strerror(errno_save)); + goto err; + } + *sourcep = source; + *typep = type; + *mnt_optsp = mnt_opts; + free(fsname); + free(optbuf); + + return 0; + +err: + free(fsname); + free(subtype); + free(source); + free(type); + free(mnt_opts); + free(optbuf); + return -1; +} + +/* + * Resolve the caller-supplied mountpoint exactly once and hand back a + * descriptor for the inode it named. O_NOFOLLOW refuses a symlink outright for + * an unprivileged caller; root keeps following them, as it always has. + */ +static int pin_mountpoint(const char *mnt, bool is_root, struct stat *stbuf) +{ + int open_flags = O_PATH | O_CLOEXEC; + int fd; + + if (!is_root) + open_flags |= O_NOFOLLOW; + + fd = open(mnt, open_flags); + if (fd == -1) { + fprintf(stderr, "%s: failed to access mountpoint %s: %s\n", + progname, mnt, strerror(errno)); + return -1; + } + + if (fstat(fd, stbuf) == -1) { + fprintf(stderr, "%s: failed to access mountpoint %s: %s\n", + progname, mnt, strerror(errno)); + close(fd); + return -1; + } + + return fd; +} + +static int check_perm(const char **mntp, struct stat *stbuf, int *mountpoint_fd) +{ + int res; + int fd; + const char *mnt = *mntp; + const bool is_root = getuid() == 0; + struct statfs fs_buf; + size_t i; + + fd = pin_mountpoint(mnt, is_root, stbuf); + if (fd == -1) + return -1; + + /* No permission checking is done for root */ + if (is_root) { + *mountpoint_fd = fd; + return 0; + } + + if (S_ISDIR(stbuf->st_mode)) { + res = fchdir(fd); + if (res == -1) { + fprintf(stderr, + "%s: failed to chdir to mountpoint: %s\n", + progname, strerror(errno)); + goto out_close; + } + /* + * The pinned directory is the CWD, so "." names it without + * going through the caller-supplied path a second time. + */ + *mntp = "."; + + if ((stbuf->st_mode & S_ISVTX) && stbuf->st_uid != getuid()) { + fprintf(stderr, "%s: mountpoint %s not owned by user\n", + progname, mnt); + res = -1; + goto out_close; + } + + res = access(*mntp, W_OK); + if (res == -1) { + fprintf(stderr, "%s: user has no write access to mountpoint %s\n", + progname, mnt); + goto out_close; + } + } else if (S_ISREG(stbuf->st_mode)) { + static char procfile[256]; + int wfd; + + snprintf(procfile, sizeof(procfile), "/proc/self/fd/%i", fd); + + /* + * Reopening the pinned inode through its magic link both tests + * write access and yields the descriptor mount(2) is pointed + * at, so the caller's path is never resolved again. + */ + wfd = open(procfile, O_WRONLY); + if (wfd == -1) { + fprintf(stderr, "%s: failed to open %s: %s\n", + progname, mnt, strerror(errno)); + res = -1; + goto out_close; + } + close(fd); + fd = wfd; + + snprintf(procfile, sizeof(procfile), "/proc/self/fd/%i", fd); + *mntp = procfile; + } else { + fprintf(stderr, + "%s: mountpoint %s is not a directory or a regular file\n", + progname, mnt); + res = -1; + goto out_close; + } + + /* Do not permit mounting over anything in procfs - it has a couple + * places to which we have "write access" without being supposed to be + * able to just put anything we want there. + * Luckily, without allow_other, we can't get other users to actually + * use any fake information we try to put there anyway. + * Use a whitelist to be safe. */ + if (statfs(*mntp, &fs_buf)) { + fprintf(stderr, "%s: failed to access mountpoint %s: %s\n", + progname, mnt, strerror(errno)); + res = -1; + goto out_close; + } + + /* Define permitted filesystems for the mount target. This was + * originally the same list as used by the ecryptfs mount helper + * (https://bazaar.launchpad.net/~ecryptfs/ecryptfs/trunk/view/head:/src/utils/mount.ecryptfs_private.c#L225) + * but got expanded as we found more filesystems that needed to be + * overlaid. */ + typeof(fs_buf.f_type) f_type_whitelist[] = { + 0x61756673 /* AUFS_SUPER_MAGIC */, + 0x00000187 /* AUTOFS_SUPER_MAGIC */, + 0xCA451A4E /* BCACHEFS_STATFS_MAGIC */, + 0x9123683E /* BTRFS_SUPER_MAGIC */, + 0x00C36400 /* CEPH_SUPER_MAGIC */, + 0xFF534D42 /* CIFS_MAGIC_NUMBER */, + 0x0000F15F /* ECRYPTFS_SUPER_MAGIC */, + 0X2011BAB0 /* EXFAT_SUPER_MAGIC */, + 0x0000EF53 /* EXT[234]_SUPER_MAGIC */, + 0xF2F52010 /* F2FS_SUPER_MAGIC */, + 0x65735546 /* FUSE_SUPER_MAGIC */, + 0x01161970 /* GFS2_MAGIC */, + 0x47504653 /* GPFS_SUPER_MAGIC */, + 0x0000482b /* HFSPLUS_SUPER_MAGIC */, + 0x000072B6 /* JFFS2_SUPER_MAGIC */, + 0x3153464A /* JFS_SUPER_MAGIC */, + 0x0BD00BD0 /* LL_SUPER_MAGIC */, + 0X00004D44 /* MSDOS_SUPER_MAGIC */, + 0x0000564C /* NCP_SUPER_MAGIC */, + 0x00006969 /* NFS_SUPER_MAGIC */, + 0x00003434 /* NILFS_SUPER_MAGIC */, + 0x5346544E /* NTFS_SB_MAGIC */, + 0x7366746E /* NTFS3_SUPER_MAGIC */, + 0x5346414f /* OPENAFS_SUPER_MAGIC */, + 0x794C7630 /* OVERLAYFS_SUPER_MAGIC */, + 0xAAD7AAEA /* PANFS_SUPER_MAGIC */, + 0x52654973 /* REISERFS_SUPER_MAGIC */, + 0xFE534D42 /* SMB2_SUPER_MAGIC */, + 0x73717368 /* SQUASHFS_MAGIC */, + 0x01021994 /* TMPFS_MAGIC */, + 0x24051905 /* UBIFS_SUPER_MAGIC */, + 0x18031977 /* WEKAFS_SUPER_MAGIC */, +#if __SIZEOF_LONG__ > 4 + 0x736675005346544e /* UFSD */, +#endif + 0x58465342 /* XFS_SB_MAGIC */, + 0x2FC12FC1 /* ZFS_SUPER_MAGIC */, + 0x858458f6 /* RAMFS_MAGIC */, + }; + for (i = 0; i < sizeof(f_type_whitelist)/sizeof(f_type_whitelist[0]); i++) { + if (f_type_whitelist[i] == fs_buf.f_type) { + *mountpoint_fd = fd; + return 0; + } + } + + fprintf(stderr, "%s: mounting over filesystem type %#010lx is forbidden\n", + progname, (unsigned long)fs_buf.f_type); + res = -1; + +out_close: + close(fd); + return res; +} + +static int open_fuse_device(const char *dev) +{ + int fd; + + drop_privs(); + fd = open(dev, O_RDWR); + if (fd == -1) { + if (errno == ENODEV || errno == ENOENT)/* check for ENOENT too, for the udev case */ + fprintf(stderr, + "%s: fuse device %s not found. Kernel module not loaded?\n", + progname, dev); + else + fprintf(stderr, + "%s: failed to open %s: %s\n", progname, dev, strerror(errno)); + } + restore_privs(); + return fd; +} + +static int mount_fuse(const char *mnt, const char *opts, const char **type) +{ + int res; + int fd; + const char *dev = getenv(FUSE_KERN_DEVICE_ENV) ?: FUSE_DEV; + struct stat stbuf; + char *source = NULL; + char *mnt_opts = NULL; + const char *real_mnt = mnt; + int mountpoint_fd = -1; + char *do_mount_opts = NULL; + char *x_opts = NULL; + + fd = open_fuse_device(dev); + if (fd == -1) + return -1; + + drop_privs(); + read_conf(); + + if (getuid() != 0 && mount_max != -1) { + int mount_count = count_fuse_fs(); + if (mount_count >= mount_max) { + fprintf(stderr, "%s: too many FUSE filesystems mounted; mount_max=N can be set in %s\n", progname, FUSE_CONF); + goto fail_close_fd; + } + } + + // Extract any options starting with "x-" + res= extract_x_options(opts, &do_mount_opts, &x_opts); + if (res) + goto fail_close_fd; + + res = check_perm(&real_mnt, &stbuf, &mountpoint_fd); + restore_privs(); + if (res != -1) + res = do_mount(real_mnt, type, stbuf.st_mode & S_IFMT, + fd, do_mount_opts, dev, &source, &mnt_opts); + + if (mountpoint_fd != -1) + close(mountpoint_fd); + + if (res == -1) + goto fail_close_fd; + + res = chdir("/"); + if (res == -1) { + fprintf(stderr, "%s: failed to chdir to '/'\n", progname); + goto fail_close_fd; + } + + if (geteuid() == 0) { + if (x_opts && strlen(x_opts) > 0) { + /* + * Add back the options starting with "x-" to opts from + * do_mount. +2 for ',' and '\0' + */ + size_t mnt_opts_len = strlen(mnt_opts); + size_t x_mnt_opts_len = mnt_opts_len+ + strlen(x_opts) + 2; + char *x_mnt_opts = calloc(1, x_mnt_opts_len); + + if (mnt_opts_len) { + strcpy(x_mnt_opts, mnt_opts); + strncat(x_mnt_opts, ",", 2); + } + + strncat(x_mnt_opts, x_opts, + x_mnt_opts_len - mnt_opts_len - 2); + + free(mnt_opts); + mnt_opts = x_mnt_opts; + } + + res = add_mount(source, mnt, *type, mnt_opts); + if (res == -1) { + /* Can't clean up mount in a non-racy way */ + goto fail_close_fd; + } + } + +out_free: + free(source); + free(mnt_opts); + free(x_opts); + free(do_mount_opts); + + return fd; + +fail_close_fd: + close(fd); + fd = -1; + goto out_free; +} + +static int send_fd(int sock_fd, int fd) +{ + int retval; + struct msghdr msg; + struct cmsghdr *p_cmsg; + struct iovec vec; + size_t cmsgbuf[CMSG_SPACE(sizeof(fd)) / sizeof(size_t)]; + int *p_fds; + char sendchar = 0; + + msg.msg_control = cmsgbuf; + msg.msg_controllen = sizeof(cmsgbuf); + p_cmsg = CMSG_FIRSTHDR(&msg); + p_cmsg->cmsg_level = SOL_SOCKET; + p_cmsg->cmsg_type = SCM_RIGHTS; + p_cmsg->cmsg_len = CMSG_LEN(sizeof(fd)); + p_fds = (int *) CMSG_DATA(p_cmsg); + *p_fds = fd; + msg.msg_controllen = p_cmsg->cmsg_len; + msg.msg_name = NULL; + msg.msg_namelen = 0; + msg.msg_iov = &vec; + msg.msg_iovlen = 1; + msg.msg_flags = 0; + /* "To pass file descriptors or credentials you need to send/read at + * least one byte" (man 7 unix) */ + vec.iov_base = &sendchar; + vec.iov_len = sizeof(sendchar); + while ((retval = sendmsg(sock_fd, &msg, 0)) == -1 && errno == EINTR); + if (retval != 1) { + perror("sending file descriptor"); + return -1; + } + return 0; +} + +/* Helper for should_auto_unmount + * + * Try opening `mnt` with uid and gid of the calling process. + */ +static int check_ENOTCONN_as_owner(const char *mnt) +{ + int pid = fork(); + if(pid == -1) { + perror("fuse: recheck_ENOTCONN_as_owner can't fork"); + _exit(EXIT_FAILURE); + } else if(pid == 0) { + uid_t uid = getuid(); + gid_t gid = getgid(); + if(setresgid(gid, gid, gid) == -1) { + perror("fuse: can't set resgid"); + _exit(EXIT_FAILURE); + } + if(setresuid(uid, uid, uid) == -1) { + perror("fuse: can't set resuid"); + _exit(EXIT_FAILURE); + } + + int fd = open(mnt, O_RDONLY); + if (fd == -1 && (errno == ENOTCONN || errno == ECONNABORTED)) + _exit(EXIT_SUCCESS); + else + _exit(EXIT_FAILURE); + } else { + int status; + int res = waitpid(pid, &status, 0); + if (res == -1) { + perror("fuse: waiting for child failed"); + _exit(EXIT_FAILURE); + } + return WIFEXITED(status) && WEXITSTATUS(status) == EXIT_SUCCESS; + } +} + +/* The parent fuse process has died: decide whether to auto_unmount. + * + * In the normal case (umount or fusermount -u), the filesystem + * has already been unmounted. If we simply unmount again we can + * cause problems with stacked mounts (e.g. autofs). + * + * So we unmount here only in abnormal case where fuse process has + * died without unmount happening. To detect this, we first look in + * the mount table to make sure the mountpoint is still mounted and + * has proper type. If so, we then see if opening the mount dir is + * returning 'Transport endpoint is not connected'. + * + * The order of these is important, because if autofs is in use, + * opening the dir to check for ENOTCONN will cause a new mount + * in the normal case where filesystem has been unmounted cleanly. + */ +static int should_auto_unmount(const char *mnt, const char *type) +{ + char *copy; + const char *last; + int result = 0; + + copy = strdup(mnt); + if (copy == NULL) { + fprintf(stderr, "%s: failed to allocate memory\n", progname); + return 0; + } + + if (chdir_to_parent(copy, &last) == -1) + goto out; + if (check_is_mount(last, mnt, type) == -1) + goto out; + + result = check_ENOTCONN_as_owner(mnt); +out: + free(copy); + return result; +} + +static void usage(void) +{ + printf("%s: [options] mountpoint\n" + "Options:\n" + " -h print help\n" + " -V print version\n" + " -o opt[,opt...] mount options\n" + " -u unmount\n" + " -q quiet\n" + " -z lazy unmount\n", + progname); + exit(1); +} + +static void show_version(void) +{ + printf("fusermount3 version: %s\n", PACKAGE_VERSION); + exit(0); +} + +static void close_range_loop(int min_fd, int max_fd, int cfd) +{ + for (int fd = min_fd; fd <= max_fd; fd++) + if (fd != cfd) + close(fd); +} + +/* + * Close all inherited fds that are not needed + * Ideally these wouldn't come up at all, applications should better + * use FD_CLOEXEC / O_CLOEXEC + */ +static int close_inherited_fds(int cfd) +{ + int rc = -1; + int nullfd; + + /* We can't even report an error */ + if (cfd <= STDERR_FILENO) + return -EINVAL; + +#ifdef HAVE_CLOSE_RANGE + if (cfd < STDERR_FILENO + 2) { + close_range_loop(STDERR_FILENO + 1, cfd - 1, cfd); + } else { + rc = close_range(STDERR_FILENO + 1, cfd - 1, 0); + if (rc < 0) + goto fallback; + } + + /* Close high range */ + rc = close_range(cfd + 1, ~0U, 0); +#else + goto fallback; /* make use of fallback to avoid compiler warnings */ +#endif + +fallback: + if (rc < 0) { + int max_fd = sysconf(_SC_OPEN_MAX) - 1; + + close_range_loop(STDERR_FILENO + 1, max_fd, cfd); + } + + nullfd = open("/dev/null", O_RDWR); + if (nullfd < 0) { + perror("fusermount: cannot open /dev/null"); + return -errno; + } + + /* Redirect stdin, stdout, stderr to /dev/null */ + dup2(nullfd, STDIN_FILENO); + dup2(nullfd, STDOUT_FILENO); + dup2(nullfd, STDERR_FILENO); + if (nullfd > STDERR_FILENO) + close(nullfd); + + return 0; +} + +int main(int argc, char *argv[]) +{ + sigset_t sigset; + int ch; + int fd; + int res; + char *origmnt; + char *mnt; + static int unmount = 0; + static int lazy = 0; + static int quiet = 0; + char *commfd = NULL; + long cfd; + const char *opts = ""; + const char *type = NULL; + int setup_auto_unmount_only = 0; + + static const struct option long_opts[] = { + {"unmount", no_argument, NULL, 'u'}, + {"lazy", no_argument, NULL, 'z'}, + {"quiet", no_argument, NULL, 'q'}, + {"help", no_argument, NULL, 'h'}, + {"version", no_argument, NULL, 'V'}, + {"options", required_argument, NULL, 'o'}, + // Note: auto-unmount and comm-fd don't have short versions. + // They'ne meant for internal use by mount.c + {"auto-unmount", no_argument, NULL, 'U'}, + {"comm-fd", required_argument, NULL, 'c'}, + {0, 0, 0, 0}}; + + progname = strdup(argc > 0 ? argv[0] : "fusermount"); + if (progname == NULL) { + fprintf(stderr, "%s: failed to allocate memory\n", argv[0]); + exit(1); + } + + while ((ch = getopt_long(argc, argv, "hVo:uzq", long_opts, + NULL)) != -1) { + switch (ch) { + case 'h': + usage(); + break; + + case 'V': + show_version(); + break; + + case 'o': + opts = optarg; + break; + + case 'u': + unmount = 1; + break; + case 'U': + unmount = 1; + auto_unmount = 1; + setup_auto_unmount_only = 1; + break; + case 'c': + commfd = optarg; + break; + case 'z': + lazy = 1; + break; + + case 'q': + quiet = 1; + break; + + default: + exit(1); + } + } + + if (lazy && !unmount) { + fprintf(stderr, "%s: -z can only be used with -u\n", progname); + exit(1); + } + + if (optind >= argc) { + fprintf(stderr, "%s: missing mountpoint argument\n", progname); + exit(1); + } else if (argc > optind + 1) { + fprintf(stderr, "%s: extra arguments after the mountpoint\n", + progname); + exit(1); + } + + origmnt = argv[optind]; + + drop_privs(); + mnt = fuse_mnt_resolve_path(progname, origmnt); + if (mnt != NULL) { + res = chdir("/"); + if (res == -1) { + fprintf(stderr, "%s: failed to chdir to '/'\n", progname); + goto err_out; + } + } + restore_privs(); + if (mnt == NULL) + exit(1); + + umask(033); + if (!setup_auto_unmount_only && unmount) + goto do_unmount; + + if(commfd == NULL) + commfd = getenv(FUSE_COMMFD_ENV); + if (commfd == NULL) { + fprintf(stderr, "%s: old style mounting not supported\n", + progname); + goto err_out; + } + + res = libfuse_strtol(commfd, &cfd); + if (res) { + fprintf(stderr, + "%s: invalid _FUSE_COMMFD: %s\n", + progname, commfd); + goto err_out; + + } + + { + struct stat statbuf; + if (fstat(cfd, &statbuf) == -1) { + fprintf(stderr, + "%s: fstat of comm fd %li failed: %s\n", + progname, cfd, strerror(errno)); + goto err_out; + } + if(!S_ISSOCK(statbuf.st_mode)) { + fprintf(stderr, + "%s: file descriptor %li is not a socket, can't send fuse fd\n", + progname, cfd); + goto err_out; + } + } + + if (setup_auto_unmount_only) + goto wait_for_auto_unmount; + + fd = mount_fuse(mnt, opts, &type); + if (fd == -1) + goto err_out; + + res = send_fd(cfd, fd); + if (res != 0) { + unmount_fuse(mnt, 1, 1); /* lazy umount */ + goto err_out; + } + close(fd); + + if (!auto_unmount) { + free(mnt); + free((void*) type); + return 0; + } + +wait_for_auto_unmount: + /* Become a daemon and wait for the parent to exit or die. + ie For the control socket to get closed. + Btw, we don't want to use daemon() function here because + it forks and messes with the file descriptors. */ + + res = close_inherited_fds(cfd); + if (res < 0) + exit(EXIT_FAILURE); + + setsid(); + res = chdir("/"); + if (res == -1) { + fprintf(stderr, "%s: failed to chdir to '/'\n", progname); + goto err_out; + } + + sigfillset(&sigset); + sigprocmask(SIG_BLOCK, &sigset, NULL); + + lazy = 1; + quiet = 1; + + while (1) { + unsigned char buf[16]; + int n = recv(cfd, buf, sizeof(buf), 0); + if (!n) + break; + + if (n < 0) { + if (errno == EINTR) + continue; + break; + } + } + + if (!should_auto_unmount(mnt, type)) { + goto success_out; + } + +do_unmount: + if (geteuid() == 0) + res = unmount_fuse(mnt, quiet, lazy); + else { + res = umount2(mnt, (lazy ? UMOUNT_DETACH : 0) | UMOUNT_NOFOLLOW); + if (res == -1 && !quiet) + fprintf(stderr, + "%s: failed to unmount %s: %s\n", + progname, mnt, strerror(errno)); + } + if (res == -1) + goto err_out; + +success_out: + free((void*) type); + free(mnt); + return 0; + +err_out: + free((void*) type); + free(mnt); + exit(1); +} diff --git a/util/init_script b/util/init_script new file mode 100755 index 0000000..a4b8e7b --- /dev/null +++ b/util/init_script @@ -0,0 +1,92 @@ +#!/bin/sh +### BEGIN INIT INFO +# Provides: fuse +# Required-Start: +# Should-Start: udev +# Required-Stop: +# Default-Start: S +# Default-Stop: +# Short-Description: Start and stop fuse. +# Description: Load the fuse module and mount the fuse control +# filesystem. +### END INIT INFO + +set -e + +PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin +MOUNTPOINT=/sys/fs/fuse/connections + +# Gracefully exit if the package has been removed. +which fusermount3 &>/dev/null || exit 5 + +# Define LSB log_* functions. +. /lib/lsb/init-functions + +case "$1" in + start|restart|force-reload) + if ! grep -qw fuse /proc/filesystems; then + echo -n "Loading fuse module" + if ! modprobe fuse >/dev/null 2>&1; then + echo " failed!" + exit 1 + else + echo "." + fi + else + echo "Fuse filesystem already available." + fi + if grep -qw fusectl /proc/filesystems && \ + ! grep -qw $MOUNTPOINT /proc/mounts; then + echo -n "Mounting fuse control filesystem" + if ! mount -t fusectl fusectl $MOUNTPOINT >/dev/null 2>&1; then + echo " failed!" + exit 1 + else + echo "." + fi + else + echo "Fuse control filesystem already available." + fi + ;; + stop) + if ! grep -qw fuse /proc/filesystems; then + echo "Fuse filesystem not loaded." + exit 7 + fi + if grep -qw $MOUNTPOINT /proc/mounts; then + echo -n "Unmounting fuse control filesystem" + if ! umount $MOUNTPOINT >/dev/null 2>&1; then + echo " failed!" + else + echo "." + fi + else + echo "Fuse control filesystem not mounted." + fi + if grep -qw "^fuse" /proc/modules; then + echo -n "Unloading fuse module" + if ! rmmod fuse >/dev/null 2>&1; then + echo " failed!" + else + echo "." + fi + else + echo "Fuse module not loaded." + fi + ;; + status) + echo -n "Checking fuse filesystem" + if ! grep -qw fuse /proc/filesystems; then + echo " not available." + exit 3 + else + echo " ok." + fi + ;; + *) + echo "Usage: $0 {start|stop|restart|force-reload|status}" + exit 1 + ;; +esac + +exit 0 diff --git a/util/install_helper.sh b/util/install_helper.sh new file mode 100755 index 0000000..76f2b47 --- /dev/null +++ b/util/install_helper.sh @@ -0,0 +1,55 @@ +#!/bin/sh +# +# Don't call this script. It is used internally by the Meson +# build system. Thank you for your cooperation. +# + +set -e + +sysconfdir="$1" +bindir="$2" +udevrulesdir="$3" +useroot="$4" +initscriptdir="$5" + +# Both sysconfdir and bindir are absolute paths (since they are joined +# with --prefix in meson.build), but need to be interpreted relative +# to DESTDIR (if specified). + +if [ -z "${DESTDIR}" ]; then + # Prevent warnings about uninitialized variable + DESTDIR="" +else + # Get rid of duplicate slash + DESTDIR="${DESTDIR%/}" +fi + +install -D -m 644 "${MESON_SOURCE_ROOT}/util/fuse.conf" \ + "${DESTDIR}${sysconfdir}/fuse.conf" + +if $useroot; then + chown root:root "${DESTDIR}${bindir}/fusermount3" + chmod u+s "${DESTDIR}${bindir}/fusermount3" + + if test ! -e "${DESTDIR}/dev/fuse"; then + mkdir -p "${DESTDIR}/dev" + mknod "${DESTDIR}/dev/fuse" -m 0666 c 10 229 + fi +fi + +if [ "${udevrulesdir}" != "" ]; then + install -D -m 644 "${MESON_SOURCE_ROOT}/util/udev.rules" \ + "${DESTDIR}${udevrulesdir}/99-fuse3.rules" +fi + +if [ "$initscriptdir" != "" ]; then + install -D -m 755 "${MESON_SOURCE_ROOT}/util/init_script" \ + "${DESTDIR}${initscriptdir}/fuse3" + + if test -x /usr/sbin/update-rc.d && test -z "${DESTDIR}"; then + /usr/sbin/update-rc.d fuse3 start 34 S . start 41 0 6 . || /bin/true + else + echo "== FURTHER ACTION REQUIRED ==" + echo "Make sure that your init system will start the ${DESTDIR}${initscriptdir}/init.d/fuse3 init script" + fi +fi diff --git a/util/meson.build b/util/meson.build new file mode 100644 index 0000000..6678a8b --- /dev/null +++ b/util/meson.build @@ -0,0 +1,35 @@ +fuseconf_path = join_paths(get_option('prefix'), get_option('sysconfdir'), 'fuse.conf') + +executable('fusermount3', ['fusermount.c', '../lib/mount_util.c', + '../lib/fuse_log.c', '../lib/util.c'], + include_directories: include_dirs, + install: true, + install_dir: get_option('bindir'), + c_args: '-DFUSE_CONF="@0@"'.format(fuseconf_path)) + +executable('mount.fuse3', ['mount.fuse.c'], + include_directories: include_dirs, + link_with: [ libfuse ], + install: true, + install_dir: get_option('sbindir'), + c_args: '-DFUSE_USE_VERSION=317') + + +udevrulesdir = get_option('udevrulesdir') +if udevrulesdir == '' + udev = dependency('udev', required: false) + if udev.found() + udevrulesdir = join_paths(udev.get_variable(pkgconfig: 'udevdir'), 'rules.d') + endif +endif + +if udevrulesdir == '' + warning('could not determine udevdir, udev.rules will not be installed') +endif + +meson.add_install_script('install_helper.sh', + join_paths(get_option('prefix'), get_option('sysconfdir')), + join_paths(get_option('prefix'), get_option('bindir')), + udevrulesdir, + '@0@'.format(get_option('useroot')), + get_option('initscriptdir')) diff --git a/util/mount.fuse.c b/util/mount.fuse.c new file mode 100644 index 0000000..7dfff1b --- /dev/null +++ b/util/mount.fuse.c @@ -0,0 +1,454 @@ +/* + FUSE: Filesystem in Userspace + Copyright (C) 2001-2007 Miklos Szeredi + + This program can be distributed under the terms of the GNU GPLv2. + See the file GPL2.txt. +*/ + +#include "fuse_config.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef linux +#include +#include +#include +#include +/* for 2.6 kernels */ +#if !defined(SECBIT_KEEP_CAPS) && defined(SECURE_KEEP_CAPS) +#define SECBIT_KEEP_CAPS (issecure_mask(SECURE_KEEP_CAPS)) +#endif +#if !defined(SECBIT_KEEP_CAPS_LOCKED) && defined(SECURE_KEEP_CAPS_LOCKED) +#define SECBIT_KEEP_CAPS_LOCKED (issecure_mask(SECURE_KEEP_CAPS_LOCKED)) +#endif +#if !defined(SECBIT_NO_SETUID_FIXUP) && defined(SECURE_NO_SETUID_FIXUP) +#define SECBIT_NO_SETUID_FIXUP (issecure_mask(SECURE_NO_SETUID_FIXUP)) +#endif +#if !defined(SECBIT_NO_SETUID_FIXUP_LOCKED) && defined(SECURE_NO_SETUID_FIXUP_LOCKED) +#define SECBIT_NO_SETUID_FIXUP_LOCKED (issecure_mask(SECURE_NO_SETUID_FIXUP_LOCKED)) +#endif +#if !defined(SECBIT_NOROOT) && defined(SECURE_NOROOT) +#define SECBIT_NOROOT (issecure_mask(SECURE_NOROOT)) +#endif +#if !defined(SECBIT_NOROOT_LOCKED) && defined(SECURE_NOROOT_LOCKED) +#define SECBIT_NOROOT_LOCKED (issecure_mask(SECURE_NOROOT_LOCKED)) +#endif +#endif +/* linux < 3.5 */ +#ifndef PR_SET_NO_NEW_PRIVS +#define PR_SET_NO_NEW_PRIVS 38 +#endif + +#include "fuse.h" + +static char *progname; + +static char *xstrdup(const char *s) +{ + char *t = strdup(s); + if (!t) { + fprintf(stderr, "%s: failed to allocate memory\n", progname); + exit(1); + } + return t; +} + +static void *xrealloc(void *oldptr, size_t size) +{ + void *ptr = realloc(oldptr, size); + if (!ptr) { + fprintf(stderr, "%s: failed to allocate memory\n", progname); + exit(1); + } + return ptr; +} + +static void add_arg(char **cmdp, const char *opt) +{ + size_t optlen = strlen(opt); + size_t cmdlen = *cmdp ? strlen(*cmdp) : 0; + if (optlen >= (SIZE_MAX - cmdlen - 4)/4) { + fprintf(stderr, "%s: argument too long\n", progname); + exit(1); + } + char *cmd = xrealloc(*cmdp, cmdlen + optlen * 4 + 4); + char *s; + s = cmd + cmdlen; + if (*cmdp) + *s++ = ' '; + + *s++ = '\''; + for (; *opt; opt++) { + if (*opt == '\'') { + *s++ = '\''; + *s++ = '\\'; + *s++ = '\''; + *s++ = '\''; + } else + *s++ = *opt; + } + *s++ = '\''; + *s = '\0'; + *cmdp = cmd; +} + +static char *add_option(const char *opt, char *options) +{ + int oldlen = options ? strlen(options) : 0; + + options = xrealloc(options, oldlen + 1 + strlen(opt) + 1); + if (!oldlen) + strcpy(options, opt); + else { + strcat(options, ","); + strcat(options, opt); + } + return options; +} + +static int prepare_fuse_fd(const char *mountpoint, const char* subtype, + const char *options) +{ + int fuse_fd = -1; + int flags = -1; + int subtype_len = strlen(subtype) + 9; + char* options_copy = xrealloc(NULL, subtype_len); + + snprintf(options_copy, subtype_len, "subtype=%s", subtype); + options_copy = add_option(options, options_copy); + fuse_fd = fuse_open_channel(mountpoint, options_copy); + if (fuse_fd == -1) { + exit(1); + } + + flags = fcntl(fuse_fd, F_GETFD); + if (flags == -1 || fcntl(fuse_fd, F_SETFD, flags & ~FD_CLOEXEC) == -1) { + fprintf(stderr, "%s: Failed to clear CLOEXEC: %s\n", + progname, strerror(errno)); + exit(1); + } + + return fuse_fd; +} + +#ifdef linux +static uint64_t get_capabilities(void) +{ + /* + * This invokes the capset syscall directly to avoid the libcap + * dependency, which isn't really justified just for this. + */ + struct __user_cap_header_struct header = { + .version = _LINUX_CAPABILITY_VERSION_3, + .pid = 0, + }; + struct __user_cap_data_struct data[2]; + memset(data, 0, sizeof(data)); + if (syscall(SYS_capget, &header, data) == -1) { + fprintf(stderr, "%s: Failed to get capabilities: %s\n", + progname, strerror(errno)); + exit(1); + } + + return data[0].effective | ((uint64_t) data[1].effective << 32); +} + +static void set_capabilities(uint64_t caps) +{ + /* + * This invokes the capset syscall directly to avoid the libcap + * dependency, which isn't really justified just for this. + */ + struct __user_cap_header_struct header = { + .version = _LINUX_CAPABILITY_VERSION_3, + .pid = 0, + }; + struct __user_cap_data_struct data[2]; + memset(data, 0, sizeof(data)); + data[0].effective = data[0].permitted = caps; + data[1].effective = data[1].permitted = caps >> 32; + if (syscall(SYS_capset, &header, data) == -1) { + fprintf(stderr, "%s: Failed to set capabilities: %s\n", + progname, strerror(errno)); + exit(1); + } +} + +static void drop_and_lock_capabilities(void) +{ + /* Set and lock securebits. */ + if (prctl(PR_SET_SECUREBITS, + SECBIT_KEEP_CAPS_LOCKED | + SECBIT_NO_SETUID_FIXUP | + SECBIT_NO_SETUID_FIXUP_LOCKED | + SECBIT_NOROOT | + SECBIT_NOROOT_LOCKED) == -1) { + fprintf(stderr, "%s: Failed to set securebits %s\n", + progname, strerror(errno)); + exit(1); + } + + /* Clear the capability bounding set. */ + int cap; + for (cap = 0; ; cap++) { + int cap_status = prctl(PR_CAPBSET_READ, cap); + if (cap_status == 0) { + continue; + } + if (cap_status == -1 && errno == EINVAL) { + break; + } + + if (cap_status != 1) { + fprintf(stderr, + "%s: Failed to get capability %u: %s\n", + progname, cap, strerror(errno)); + exit(1); + } + if (prctl(PR_CAPBSET_DROP, cap) == -1) { + fprintf(stderr, + "%s: Failed to drop capability %u: %s\n", + progname, cap, strerror(errno)); + } + } + + /* Drop capabilities. */ + set_capabilities(0); + + /* Prevent re-acquisition of privileges. */ + if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) == -1) { + fprintf(stderr, "%s: Failed to set no_new_privs: %s\n", + progname, strerror(errno)); + exit(1); + } +} +#endif + +int main(int argc, char *argv[]) +{ + char *type = NULL; + char *source; + char *dup_source = NULL; + const char *mountpoint; + char *basename; + char *options = NULL; + char *command = NULL; + char *setuid_name = NULL; + int i; + int dev = 1; + int suid = 1; + int pass_fuse_fd = 0; + int fuse_fd = 0; + int drop_privileges = 0; + char *dev_fd_mountpoint = NULL; + + progname = argv[0]; + basename = strrchr(argv[0], '/'); + if (basename) + basename++; + else + basename = argv[0]; + + if (strncmp(basename, "mount.fuse.", 11) == 0) + type = basename + 11; + if (strncmp(basename, "mount.fuseblk.", 14) == 0) + type = basename + 14; + + if (type && !type[0]) + type = NULL; + + if (argc < 3) { + fprintf(stderr, + "usage: %s %s destination [-t type] [-o opt[,opts...]]\n", + progname, type ? "source" : "type#[source]"); + exit(1); + } + + source = argv[1]; + if (!source[0]) + source = NULL; + + mountpoint = argv[2]; + + for (i = 3; i < argc; i++) { + if (strcmp(argv[i], "-v") == 0) { + continue; + } else if (strcmp(argv[i], "-t") == 0) { + i++; + + if (i == argc) { + fprintf(stderr, + "%s: missing argument to option '-t'\n", + progname); + exit(1); + } + type = argv[i]; + if (strncmp(type, "fuse.", 5) == 0) + type += 5; + else if (strncmp(type, "fuseblk.", 8) == 0) + type += 8; + + if (!type[0]) { + fprintf(stderr, + "%s: empty type given as argument to option '-t'\n", + progname); + exit(1); + } + } else if (strcmp(argv[i], "-o") == 0) { + char *opts; + char *opt; + i++; + if (i == argc) + break; + + opts = xstrdup(argv[i]); + opt = strtok(opts, ","); + while (opt) { + int j; + int ignore = 0; + const char *ignore_opts[] = { "", + "user", + "nofail", + "nouser", + "users", + "auto", + "noauto", + "_netdev", + NULL}; + if (strncmp(opt, "setuid=", 7) == 0) { + setuid_name = xstrdup(opt + 7); + ignore = 1; + } else if (strcmp(opt, + "drop_privileges") == 0) { + pass_fuse_fd = 1; + drop_privileges = 1; + ignore = 1; + } + for (j = 0; ignore_opts[j]; j++) + if (strcmp(opt, ignore_opts[j]) == 0) + ignore = 1; + + if (!ignore) { + if (strcmp(opt, "nodev") == 0) + dev = 0; + else if (strcmp(opt, "nosuid") == 0) + suid = 0; + + options = add_option(opt, options); + } + opt = strtok(NULL, ","); + } + free(opts); + } + } + + if (drop_privileges) { + uint64_t required_caps = CAP_TO_MASK(CAP_SETPCAP) | + CAP_TO_MASK(CAP_SYS_ADMIN); + if ((get_capabilities() & required_caps) != required_caps) { + fprintf(stderr, "%s: drop_privileges was requested, which launches the FUSE file system fully unprivileged. In order to do so %s must be run with privileges, please invoke with CAP_SYS_ADMIN and CAP_SETPCAP (e.g. as root).\n", + progname, progname); + exit(1); + } + } + + if (dev) + options = add_option("dev", options); + if (suid) + options = add_option("suid", options); + + if (!type) { + if (source) { + dup_source = xstrdup(source); + type = dup_source; + source = strchr(type, '#'); + if (source) + *source++ = '\0'; + if (!type[0]) { + fprintf(stderr, "%s: empty filesystem type\n", + progname); + exit(1); + } + } else { + fprintf(stderr, "%s: empty source\n", progname); + exit(1); + } + } + + if (setuid_name && setuid_name[0]) { +#ifdef linux + if (drop_privileges) { + /* + * Make securebits more permissive before calling + * setuid(). Specifically, if SECBIT_KEEP_CAPS and + * SECBIT_NO_SETUID_FIXUP weren't set, setuid() would + * have the side effect of dropping all capabilities, + * and we need to retain CAP_SETPCAP in order to drop + * all privileges before exec(). + */ + if (prctl(PR_SET_SECUREBITS, + SECBIT_KEEP_CAPS | + SECBIT_NO_SETUID_FIXUP) == -1) { + fprintf(stderr, + "%s: Failed to set securebits %s\n", + progname, strerror(errno)); + exit(1); + } + } +#endif + + struct passwd *pwd = getpwnam(setuid_name); + if (!pwd || setgid(pwd->pw_gid) == -1 || setuid(pwd->pw_uid) == -1) { + fprintf(stderr, "%s: Failed to setuid to %s: %s\n", + progname, setuid_name, strerror(errno)); + exit(1); + } + } else if (!getenv("HOME")) { + /* Hack to make filesystems work in the boot environment */ + setenv("HOME", "/root", 0); + } + + if (pass_fuse_fd) { + fuse_fd = prepare_fuse_fd(mountpoint, type, options); + dev_fd_mountpoint = xrealloc(NULL, 20); + snprintf(dev_fd_mountpoint, 20, "/dev/fd/%u", fuse_fd); + mountpoint = dev_fd_mountpoint; + } + +#ifdef linux + if (drop_privileges) { + drop_and_lock_capabilities(); + } +#endif + add_arg(&command, type); + if (source) + add_arg(&command, source); + add_arg(&command, mountpoint); + if (options) { + add_arg(&command, "-o"); + add_arg(&command, options); + } + + free(options); + free(dev_fd_mountpoint); + free(dup_source); + free(setuid_name); + + execl("/bin/sh", "/bin/sh", "-c", command, NULL); + fprintf(stderr, "%s: failed to execute /bin/sh: %s\n", progname, + strerror(errno)); + + if (pass_fuse_fd) + close(fuse_fd); + free(command); + return 1; +} diff --git a/util/parse-backtrace.sh b/util/parse-backtrace.sh new file mode 100755 index 0000000..3db96f8 --- /dev/null +++ b/util/parse-backtrace.sh @@ -0,0 +1,117 @@ +#!/bin/bash + +tmpfile=`mktemp backtrace.XXX` + +PROGRAM_PATH='' +TRACE_TYPE=glibc + +print_help() +{ + echo "Usage: " + echo " " `basename $0`" [-s] [-p ] -t <\"full trace\">" + echo "Options: " + echo " -t '' - The entire trace should be put into quotes" + echo " for this option" + echo " -p path/to/filename - optional path to the binary, typically" + echo " autodetected" + echo + exit 1 +} + +if [ -z "$1" -o "$1" = "-h" -o "$1" = "--help" ]; then + rm -f $tmpfile + print_help +fi + +while getopts "hf:t:p:" opt; do + case $opt in + h) + print_help + ;; + f) + PROGRAM_PATH="$OPTARG" + ;; + t) + TRACE="$OPTARG" + ;; + p) + PROGRAM_PATH="$OPTARG" + ;; + *) + print_help + ;; + esac +done + + +# use addr2line +parse_glibc_trace() +{ + local trace="$1" + local filename="$2" + local tmpname="" + local symbol="" + + IFS=$'\n' + for line in ${trace}; do + + #echo "Line: '$line" + + # remove C2 A0 (non breaking space, as inserted by windows) + line=$(echo $line | sed 's/\xC2\xA0/ /g') + + line=`echo $line | egrep "\[" | egrep "\]"` + [ -n "$line" ] || continue + + # cut off additional syslog part - beginning of line to ':' + line=$(echo $line line | sed -e 's/.*://') + + # parse lines like + # /usr/lib/libfuse3.so.3(+0x1c0ef) [0x7fca6061c0ef] + + filename=$(echo $line | awk '{print $1}' | sed -e 's/(.*$//') + if [ -z "${filename}" ]; then + echo "Failed to get filename path for line: \"$line\"" + return + fi + + if [[ $filename != /* ]]; then + if [ -n "${PROGRAM_PATH}" ]; then + filename="${PROGRAM_PATH}" + else + tmpname="$(which $filename)" + if [ $? -ne 0 ]; then + echo "Failed to get path for '$filename'" + continue + fi + filename="${tmpname}" + fi + fi + + # for plain glibc backtrace_symbols the symbol is also in column1, + # within the brackets () + symbol=$(echo $line | awk '{print $1}' | sed -e 's/^.*(//' | sed -e 's/).*//') + if [ -z "${symbol}" ]; then + echo "Failed to get symbol for line: \"$line\"" + continue + fi + + addr2line -a -p -s -C -f -i -e ${filename} ${symbol} + done +} + +if [ -z "$TRACE" ]; then + echo "Missing backtrace option!" + echo + print_help +fi + + +# For now only glibc backtrace_symbols traces are supported +if [ $TRACE_TYPE = "glibc" ]; then + parse_glibc_trace "$TRACE" "${PROGRAM_PATH}" +else + echo "Unknown tracetype: '${TRACE_TYPE}'" +fi + +rm -f $tmpfile diff --git a/util/udev.rules b/util/udev.rules new file mode 100644 index 0000000..9585111 --- /dev/null +++ b/util/udev.rules @@ -0,0 +1 @@ +KERNEL=="fuse", MODE="0666" diff --git a/xfstests/README.md b/xfstests/README.md new file mode 100644 index 0000000..deda553 --- /dev/null +++ b/xfstests/README.md @@ -0,0 +1,18 @@ +To test FUSE with xfstests¹: + +1. copy the `mount.fuse.passthrough` file into + `/sbin` and edit the `PASSTHROUGH_PATH`, `SCRATCH_SOURCE` and `TEST_SOURCE` variables as needed. + +2. Make sure that the `SCRATCH_SOURCE` and `TEST_SOURCE` directories +exist. + +3. Copy `local.config` into your xfstests directory + +Tests can then be run with e.g.: + +```sh +# make +# sudo ./check -fuse -b +``` + +¹https://git.kernel.org/pub/scm/fs/xfs/xfstests-dev.git/about/ diff --git a/xfstests/local.config b/xfstests/local.config new file mode 100644 index 0000000..c34ebb8 --- /dev/null +++ b/xfstests/local.config @@ -0,0 +1,21 @@ +export TEST_DEV=source:/mnt/src/test +export TEST_DIR=/mnt/test + +export SCRATCH_DEV=source:/mnt/src/scratch +export SCRATCH_MNT=/mnt/scratch + +export FSTYP=fuse +export FUSE_SUBTYP=.passthrough +export MOUNT_OPTIONS="" +export TEST_FS_MOUNT_OPTS="" + +# extra binary options, such as '--direct-io' or '--nopassthrough', etc +export EXTRA_BIN_OPTIONS="" + +# If PASSTHROUGH_PATH is unset, the mount helper is going to look +# for the binary '${FUSE_SUBTYP}', though omitting the leading dot '.'. +# Example: +# with FUSE_SUBTYP=".passthrough", the mount helper is called +# 'mount.fuse.passthrough' and that would try to run +# 'passthrough'. +# export PASSTHROUGH_PATH=/example/passthrough_hp diff --git a/xfstests/mount.fuse.passthrough b/xfstests/mount.fuse.passthrough new file mode 100755 index 0000000..94c45e5 --- /dev/null +++ b/xfstests/mount.fuse.passthrough @@ -0,0 +1,40 @@ +#!/bin/bash + +ulimit -n 1048576 + +dev="$1" +shift +mnt="$1" +shift +# -o +shift +mntopts="$1" +shift + +# source can be provided as NFS style device (e.g. TEST_DEV=source:/${TEST_SOURCE}) +# and/or it can already be inside mount options (passthrough_ll style) +if ( echo "$mntopts" | grep -q "source=" ) ; then + # Don't pass source as position argument + source= +elif [[ "$dev" == "source:"* ]]; then + source="${dev#"source:"}" +else + >&2 echo "passthrough source is undefined, aborting!" +fi + +if ( echo "$mntopts" | grep -q remount ) ; then + exec mount -i "$dev" "$mnt" -o "$mntopts" +fi + +# set default to SUBTYPE (extracted from this script name) +# example: +# Copy or link this script to /sbin/mount.fuse.passthrough_hp +# If xfstests local.config does not set PASSTHROUGH_PATH, +# PASSTHROUGH_PATH will be set to 'passthrough_hp' and exec below +# will look that up from $PATH + +[ -n "$PASSTHROUGH_PATH" ] || PASSTHROUGH_PATH=${0#*mount.fuse.} + +#echo "EXTRA_BIN_OPTIONS='${EXTRA_BIN_OPTIONS}'" + +exec "$PASSTHROUGH_PATH" ${EXTRA_BIN_OPTIONS} -o fsname=$dev,allow_other,dev $source "$mnt" -o "$mntopts" "$@"