mergerfs-2.33.5/0000755000000000000000000000000014225522122012111 5ustar rootrootmergerfs-2.33.5/debian/0000755000000000000000000000000014225522122013333 5ustar rootrootmergerfs-2.33.5/debian/docs0000644000000000000000000000001214225522122014177 0ustar rootrootREADME.md mergerfs-2.33.5/debian/mergerfs.postinst0000755000000000000000000000016114225522122016753 0ustar rootroot#!/bin/sh set -e case "${1}" in configure) chmod 4755 /usr/bin/mergerfs-fusermount ;; esac mergerfs-2.33.5/debian/rules0000755000000000000000000000032514225522122014413 0ustar rootroot#!/usr/bin/make -f # -*- makefile -*- # Uncomment this to turn on verbose mode. export DH_VERBOSE=1 %: dh $@ --parallel override_dh_auto_install: $(MAKE) DESTDIR=$(CURDIR)/debian/mergerfs PREFIX=/usr install mergerfs-2.33.5/debian/control0000644000000000000000000000106214225522122014735 0ustar rootrootSource: mergerfs Section: utils Priority: optional Maintainer: Antonio SJ Musumeci Build-Depends: debhelper (>= 8.0.0) Standards-Version: 3.9.4 Homepage: http://github.com/trapexit/mergerfs Package: mergerfs Architecture: any Depends: ${shlibs:Depends}, ${misc:Depends}, fuse [linux-any] | fuse4bsd [kfreebsd-any] Description: another FUSE union filesystem mergerfs is a union filesystem geared towards simplifying storage and management of files across numerous commodity storage devices. It is similar to mhddfs, unionfs, and aufs. mergerfs-2.33.5/debian/compat0000644000000000000000000000000214225522122014531 0ustar rootroot9 mergerfs-2.33.5/debian/copyright0000644000000000000000000000203514225522122015266 0ustar rootrootFormat: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ Upstream-Name: mergerfs-1.1.0 Source: http://github.com/trapexit/mergerfs Files: * Copyright: 2014 Antonio SJ Musumeci License: ISC Files: debian/* Copyright: 2014 Antonio SJ Musumeci License: ISC License: ISC Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. . THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. n mergerfs-2.33.5/VERSION0000644000000000000000000000000614225522122013155 0ustar rootroot2.33.5mergerfs-2.33.5/ChangeLog0000644000000000000000000011670514225522123013676 0ustar rootrootmergerfs (2.33.5~debian-buster) buster; urgency=medium * 14c2ff9 Return ENOENT when dotdot for root node requested -- trapexit Tue, 12 Apr 2022 23:18:57 -0400 mergerfs (2.33.4~debian-buster) buster; urgency=medium * b95ff8b Lock less often during logging * 3f060f4 Change ENOENT to ESTALE for looking up paths to handle rename race conditions * c9a9358 Fix query of attr during symlink * 9ca10b2 Rework node slab garbage collection to limit blocking work threads * e3ab739 Update bug_report.md * 58803f4 Update README.md -- trapexit Sun, 20 Mar 2022 22:45:07 -0400 mergerfs (2.33.3~debian-buster) buster; urgency=medium * c43b464 Call FUSE getattr rather than syscall for extra logic -- trapexit Sat, 18 Dec 2021 17:21:19 -0500 mergerfs (2.33.2~debian-buster) buster; urgency=medium * e256c88 Get attrs for link, not target -- trapexit Thu, 16 Dec 2021 19:35:18 -0500 mergerfs (2.33.1~debian-buster) buster; urgency=medium * ee8532c Ensure server handling of locks is disabled -- trapexit Sat, 27 Nov 2021 00:02:55 -0500 mergerfs (2.33.0~debian-buster) buster; urgency=medium * 68b3026 Remove embedded name in node struct * 18dead4 Add new debug printing routines * 68719da Remove ioctl runtime section, use getfattr instead of xattr * c384509 Remove subscribestar from sponsor platforms * 5f737cb Add option to log node memory usage metrics * 930dad3 Reduce struct node size * 8150957 Fix regression from remember_node refactor * c929781 Add malloc_trim configure test * 313aa2a Update ghc::filesystem and nonstd::optional * 4ea0de3 Rework dirents buffer management * 6b5c484 Major rework of memory allocation using fixed mem pools * f598d3b Add details on dropping caches before benchmarks * 122f9c1 Add new cirrus-ci targets * 5263a65 Remove usage printing regression in mount_bsd * 8def16a Fix infinite loop in mfs action policy * 43a6d66 Major cleanup of libfuse to remove unneeded features * df721eb Update docs with Chia wallet address * 54e8500 Add more clarity in what to provide when submitting bug reports or questions * 1aaf742 Update 'features' section of readme * b4c031d tweak bug report template language * 61c2187 Remove config ioctl calls * 7013ff9 Update nonstd::optional to v3.4.0 * 3ce0dc5 Update ghc::filesystem to v1.5.8 * 098f353 pfrd: fix mod by zero error when all branches are filtered * c06db9c pfrd: fix mod by zero error when all branches are filtered * 80f3099 properly initialize fuse_buf in worker loop * bcd5fde properly initialize fuse_buf in worker loop * c40f42e mention bind-propagation and add link to hotio * 44efbaa Update README.md * 9573a05 Update bug_report.md * 495afef README: update strace args and add question on snapraid * 58b4eb9 Fix typo * 9a90609 remove travis-ci * 8adebc9 new features: follow-symlinks, rename-exdev, link-exdev * ed0c1db fix parsing of relative branch paths * d337574 fix parsing of relative branch paths * 6a9f7e6 readme: update support section * 3bc189c README: update support section * dfb544f README: add warning/clearity about caching script and hardlinks and cow * 538467b config: rework global config, remove rwlock, make branches RCU like * 3808e26 Update FUNDING.yml * e9e17ba README: misc updates and tweaks -- trapexit Mon, 1 Nov 2021 22:46:09 -0400 mergerfs (2.32.6~debian-buster) buster; urgency=medium * c06db9c pfrd: fix mod by zero error when all branches are filtered -- Antonio SJ Musumeci Fri, 25 Jun 2021 09:15:44 -0400 mergerfs (2.32.5~debian-buster) buster; urgency=medium * bcd5fde properly initialize fuse_buf in worker loop -- Antonio SJ Musumeci Sun, 20 Jun 2021 17:11:58 -0400 mergerfs (2.32.4~debian-buster) buster; urgency=medium * ed0c1db fix parsing of relative branch paths -- Antonio SJ Musumeci Thu, 11 Feb 2021 19:49:50 -0500 mergerfs (2.32.3~debian-buster) buster; urgency=medium * f6e3767 reset dentry buffer when rewind'ed -- Antonio SJ Musumeci Tue, 9 Feb 2021 18:09:28 -0500 mergerfs (2.32.2~debian-buster) buster; urgency=medium * f50812a dirname should not return an empty string * 5e08357 update cirrus-ci builds * 44a9891 msp policies stopped before root path -- trapexit Fri, 18 Dec 2020 17:03:43 -0500 mergerfs (2.32.1~debian-buster) buster; urgency=medium * 3900543 fix segv: zero out data structures -- trapexit Sun, 13 Dec 2020 16:56:55 -0500 mergerfs (2.32.0~debian-buster) buster; urgency=medium * 7edd3c6 config: fix invalid error check when parsing config file * 1b26f49 general cleanup, slight memory reduction * c3fffef msp policies: used wrong path to check existance * 7e583e3 use relative option for rsync -- trapexit Sat, 28 Nov 2020 21:49:51 -0500 mergerfs (2.31.0~debian-buster) buster; urgency=medium * 8990e24 README.md: add details on per branch minfreespace * f6e37c5 README.md: add details about error handling * 0484442 wyhash: use safety mode 1 * 5a31843 rework some function error handling * 610c75f properly return const ref from tofrom string wrapper * 73e8ed7 Delete issue_template.md * 0b1af2b Update bug_report.md * 767039c option_parser: return 0 when requesting help or version * fc34539 branches: add per branch minfreespace w/ original value as default * 27bd39f README.md: fix typos * 6311df7 optionally use lchmod depending on if on Linux or not (BSD) * 15fb751 README.md: add note indicating only tagged releases are supported * 0468440 add {,ep,msp}pfrd policies * 8675fce add fedora builds to cirrus-ci * 7fd629e add #warning to make it more obvious what versions of functions are used * 2fe20b8 clean up and separate out fs_* files * 2696079 break fs.hpp up into separate files * ec15872 cleanup function signatures and definitions * 6cc6524 change category to enum class * 7e17310 libfuse cleanup: add more header include guards * f488deb libfuse/Makefile: support CXXFLAGS and LDFLAGS passed via environment * 678626e Makefile: support CXXFLAGS and LDFLAGS passed via environment * 0bc6711 libfuse/Makefile: support $AR * a925fbe libfuse cleanup: extern cplusplus cleanup * dc1b698 libfuse cleanup: remove single threaded * 3c761b7 libfuse cleanup: remove libfuse API compatibility * 3bfdd78 libfuse cleanup: remove cuse * 5f12fb6 libfuse cleanup: remove unnecessary files * f9b831e libfuse cleanup: reindent * badf7e1 README.md: add XMR address * 3b38262 README.md: update support section with Open Collective and LBC * 0371b04 change from fasthash64 to wyhash * 1fe32e9 fix: add fakeroot needed for building -- trapexit Tue, 29 Sep 2020 18:51:30 -0400 mergerfs (2.30.0~debian-buster) buster; urgency=medium * 30d13b7 inodecalc: add 32bit versions of hashs * 5989d41 readdir: use getdents64 for compatibility with ARM64 * 1d2a1ba README.md: move kernel bugs to wiki * e639f3d README.md: update faq regarding Plex and page caching * 173193d Update issue templates * 0709b2e add missing options to usage * 15a0aed cleanup: move some config data structures to separate files * 8afe72d README: change back to listing fuse.mergerfs for fstab fstype * a93bd9f freebsd: misc cleanups to get freebsd compiling * 139e61e nfsopenhack: remove empty file check * 93218a3 NFS open/creat hack * 3ec137c policy: add "most shared path" policies * 3a67384 Fix typos * 468d420 Removed duplicated include * 08d267f moveonenospc: enhance the feature to allow using a policy * c4a85f5 readdir: add dirent index array * c099064 remove unnecessary libfuse flags nopath, nullpath_ok, and utime_omit_ok * 6289956 temporary fix for short readdirs on NFS * d699a97 fix rpm spec and chmod in deb * 8ed3a1f debian: fix installing of fresh deb * b4397f7 add 'inodecalc' option to allow selection of inode calculation algo * dbdd3e2 additional readdir refactor cleanup * afb07b1 add Cirrus-CI * 54c41c4 rework config management * a0c1c1a Travis-CI: add installing of deb package * add588f README: add terminology section and tweak some things * eb78c51 README: update support section * c6bce81 fix install location and setuid setting in deb pkgs * aad2257 README.md: add human readable versions of some errno references * eff15c7 README.md: request users update to latest version before they submit report * 7c37a69 add basic setup section * 698c414 fix getdent name length calculation * 0888ee1 README: update a number of sections and fix typos * 8ba1aba README: add note regarding space calculations * 3a46ec9 fix filename hashing error * 9b2634a fix name length calculation for musc * 62873d2 use getdents64 on linux * d119807 restructure readdir, add readdir_plus -- Antonio SJ Musumeci Mon, 3 Aug 2020 19:42:06 -0400 mergerfs (2.29.0~debian-buster) buster; urgency=medium * 5ce428c rework makefiles + install mount tools * a646fe0 change inode conversion algo to reduce collision * 576ff36 add cache.writeback to xattrs * bf4b390 update fuse_kernel.h * 4b9f3de add ctime support * 903d39f add writeback caching * 9952c58 add links to ZFS comparison * 66f0d9e add faq entry on files ending up on 1 branch * 0f2f78c python 2.6 fix * 489ab23 make git2debcl work on Python 2 & 3 * 3897852 add README segment on benchmarking * f4b8efc more FAQs * 4625a3c fix typos and update FAQ regarding policy preference * 322fa57 update docs: openvz kernel bug and new support links * 5eb3c8c add github sponsor and subscribestar to funding * 5d82756 Create FUNDING.yml -- trapexit Sat, 22 Feb 2020 17:00:55 -0500 mergerfs (2.28.3~debian-buster) buster; urgency=medium * 4d82ed9 fix short writes on >2GB files when cloning file * 08e1bef improve nodeid generation * 58ab7f7 Fix typo * 7a84555 update travis config to include bionic -- trapexit Mon, 14 Oct 2019 23:34:36 -0400 mergerfs (2.28.2~debian-buster) buster; urgency=medium * 0fffabf only return 1 branch for rand/eprand policies * 50ad648 initialize mutex to fix lockup * 752a159 add UPGRADE section * f77d1d9 Force symlink creation (don't error if it already exists) * e6a6ab9 update how it works * 38919ad Update README.md * 86bda61 fix some grammar and typos * 4c4c27a set uid & gid when calling ioctl -- trapexit Sat, 7 Sep 2019 17:27:56 -0400 mergerfs (2.28.1~debian-buster) buster; urgency=medium * 5ca928e accept old arguments for backwards compatibility -- trapexit Mon, 10 Jun 2019 20:24:36 -0400 mergerfs (2.28.0~debian-buster) buster; urgency=medium * 7cbd88a allow setting of 'max_pages' (via 'fuse_msg_size') * 8cb7195 add copy_file_range support * 529a953 add file caching across opens and runtime control * ddf6a2f make async_read optional again * 2323c16 add readdir caching * 1baa706 add symlink caching -- trapexit Mon, 3 Jun 2019 21:16:53 -0400 mergerfs (2.27.1~debian-buster) buster; urgency=medium * 61cded5 fix for unlink race condition -- trapexit Tue, 21 May 2019 23:09:13 -0400 mergerfs (2.27.0~debian-buster) buster; urgency=medium * 229d851 fix mount.mergerfs generation * 5f22211 ensure parallel dirops is enabled if capable * 2b019b8 ensure async_aio is enabled if capable * 3a66a68 ensure marking open files renamed over as hidden * 80d56ac add support for POSIX ACLs * 1ca7052 remove 'remote' flock support -- trapexit Mon, 20 May 2019 08:18:21 -0400 mergerfs (2.26.2~debian-buster) buster; urgency=medium * c21aa34 fix renaming over open unlinked file * 810871d update support section of README -- trapexit Thu, 16 May 2019 14:49:38 -0400 mergerfs (2.26.1~debian-buster) buster; urgency=medium * e052446 fix setting of fsname -- trapexit Sun, 12 May 2019 14:30:37 -0400 mergerfs (2.26.0~debian-buster) buster; urgency=medium * 5883020 tweak docs * 374580a create integration tests * 825fcf7 cleanup and rework build system * 3288d83 add reference to 32bit mmap kernel bug * df0d055 fix outarg size calculation to accomidate newer fuse_kernel.h on older platforms * 6ecc618 remove .fuse_hidden file creation * 2a075ea fall back to other file copy methods in clonefile * b69819e ioctl: don't set outbufsz when not needed * 61d764d makefile: remove superclean dependency from tarball * b0b265b parallel deb building * 1be9900 rework makefiles for better manage parallel builds * 38037fe replace {attr,entry,negative}_timeout references * 2af0dcd fix building of deb pkg * 0918dfd make attr, entry, negative_entry cache timeouts runtime configurable * 95c0cc7 replace libfuse's autoconf with makefile * 1d48dba add discord server to docs * 7bf607b fix FS_IOC_{GET,SET}{FLAGS,VERSION} ioctl calls * 940c323 misc fixes for FreeBSD * 5ab7d2d remove O_LARGEFILE * 65d3bfe remove libfuse modules * 9d9ee7b general code cleanup * c5b2415 remove `defaults`, hard code atomic_o_trunc, big_writes, and default_permissions * f4a06ca fix <> escaping in argument list * 5be7e00 add statfs cache * 7a057da add policy cache for 'open' * a13b822 add mention of noforget argument for NFS exports * 9fd3b96 make ioctl on directories use open policy * a57c680 clarify descriptions regarding funcitons and those without policies * 3a12134 add more info on different caching techniques * 87b2795 set direct_io per open/create, now runtime configurable * 7eefb58 misc doc updates * 0b5a0d1 update README regarding valid ENOSPC errors * aa56e12 mention mv quirk in known issues * 10f7f8b optimize link_cow eligibility check * dfa1c1a check minfreespace on newest policy create * 4096940 misc README updates * 6de8e44 general cleanup of makefile, add static & lto building * 6ae6846 fix building on alpine w/ musl -- trapexit Fri, 10 May 2019 12:03:22 -0400 mergerfs (2.25.1~debian-buster) buster; urgency=medium * 7d9458f change xattr setting notsup to nosys * 43b676a fix indexing of truncate targets -- trapexit Mon, 19 Nov 2018 23:32:07 -0500 mergerfs (2.25.0~debian-buster) buster; urgency=medium * c46134c fix building on platforms without O_PATH * 3631ab7 optimize readdir file dedup * 680f819 add ability to change statfs behavior * 7524e57 rename NW (no write) to NC (no create) * b55ebba add tagging branches RW/RO/NW * 8eacb00 misc updates to docs * 9e0ab1f misc cleanups * 8a48b74 policy return cleanup * 85026d5 add FICLONE and copy_file_range to clonefile * 1885a82 remove libattr dependency * 65f482e add ability to turn on/off xattr support at runtime * 8d1a156 fix building on certain platforms * 93f7d7d add link_cow feature * 9afefef keep literal when glob fails * 340f3c8 add fuse to debian pkg dependency * f856336 add security_capability option * 057eafe add comment regarding tar error * 587ab62 tweak docs, add FAQ regarding vendoring of libfuse * 756d4ff Makefile: option for building with system libfuse * ec6adaa options: move "-o threads=" help text to libfuse/ * fc52f89 add FAQ entry on hard links * e987ff4 Makefile: don't touch LDFLAGS * 39d5ab8 add details regarding use_ino * c005463 change examples to use /mnt rather than /tmp -- trapexit Tue, 6 Nov 2018 08:15:02 -0500 mergerfs (2.24.2~debian-buster) buster; urgency=medium * 73e8867 fix version.hpp creation, again -- trapexit Sat, 24 Mar 2018 06:53:35 -0700 mergerfs (2.24.1~debian-buster) buster; urgency=medium * 24e690b fix versioning with tarball building -- trapexit Fri, 16 Mar 2018 23:27:04 -0400 mergerfs (2.24.0~debian-buster) buster; urgency=medium * e521833 bump change date on readme and rebuild man * 737d941 bump builtin libfuse version * 9830e29 fix version generation * cfe9c28 ignore clonepath metadata errors. fixes #470 * f48c16d stop clonepath at base directory. fixes #467 * 906702d add hard_remove details and comment on unionfs * b4a003c Remove duplicate word in README * b48054f add XRP to support list * d8f05ac add BCH, ETH, & LTC donation addresses * 7d93d59 add explination in FAQ about what mergerfs is / is not * 7207e7f faq answer on epmfs * f72bb1e update FAQ and misc tweaks * bd14f33 add support info and how mergerfs works section * 43d50ca Create issue_template.md -- trapexit Fri, 9 Mar 2018 21:14:44 -0500 mergerfs (2.23.1~debian-buster) buster; urgency=medium * 1d6d227 call 32bit versions of set/geteuid on 32bit platforms * 821d1b6 rebuild manpage * ebe737e fixup makefile * 9104f25 misc updates to docs * c1d85ef expand support section of readme * cc1f07e add info on creating a caching behavior -- trapexit Tue, 17 Oct 2017 22:23:27 -0400 mergerfs (2.23.0~debian-buster) buster; urgency=medium * 75ed37a add setting of thread pool size * 8043829 fixed threads * 0708110 improve khash performance * a7b126c use pragma once in headers * a2bddec add 'ignore path preserving on rename' feature * aea2b40 use temp files (then rename) when moving files for moveonenospc -- Antonio SJ Musumeci Mon, 3 Jul 2017 00:02:35 -0400 mergerfs (2.22.1~debian-buster) buster; urgency=medium * bfd410d use fusepath from fileinfo. closes #417 * 7296d3d add time.h to fix compiling on some platforms -- Antonio SJ Musumeci Sat, 24 Jun 2017 17:04:37 -0400 mergerfs (2.22.0~debian-buster) buster; urgency=medium * de0985c add libfuse 2.9.7 to repo and build against libfuse.a * 87c2f2f add nullrw feature to facilitate benchmarking -- Antonio SJ Musumeci Fri, 2 Jun 2017 16:14:54 -0400 mergerfs (2.21.0~debian-buster) buster; urgency=medium * 6a7675f symlinkify: file -> symlink-to-original-file after timeout * 8ba3a08 make dropcacheonclose runtime configurable * ccaa458 better handle incomplete reads/writes in copying files * 162b99e enable nopath and nullpath_ok * f15437c tweak movefile behavior * 5f3aa6e add more travis build targets * 2fbeb67 hide fs::fadvise as it's not used directly * 8b976ab support older libfuse without utime_omit_ok flag * 1a1fa06 fadvise cleanup * 617195d enable utime_omit_ok flag * be3eb7e work around getgrouplist signature difference on osx * 0600734 handle 32bit and 64bit inode recalculation * 9d0798d restructure fadvise * e2acffe restructure fallocate abstraction * 42d454a abstract futimesat * 0b2bf17 abstract access to highres atime/mtime * e20d566 use correct integer types * 1539aca use compiler's preprocessor rather than cpp explicitly * 9d799ff setup travis-ci * c043ef9 make fs::attr return ENOTSUP on EINVAL #381 * 215f129 explicitly define path preservation, better explain move issues * bb4ec91 fix incorrect section header syntax -- Antonio SJ Musumeci Fri, 5 May 2017 00:47:24 -0400 mergerfs (2.20.0~debian-buster) buster; urgency=medium * cf2cb54 add info on inodes running out to faq on filled drives * 94ebccc try to clarify how path preserving policies work and other tweaks to docs * 4e7e74d update docs to include dropcacheonclose and warn about directory mtime * 6aa62d0 add option to drop file caches before closing files * 492d895 check metadata on chown/chmod errors when cloning -- Antonio SJ Musumeci Mon, 13 Mar 2017 13:43:22 -0400 mergerfs (2.19.0~debian-buster) buster; urgency=medium * 9cc9bb9 misc document updates * 16e7c72 update documentation, focus on explaining double caching & direct_io * a60d815 add ifndefs to all headers * e93c946 limit need to explicitly call .c_str() * 7b4e1ea remove clone command * 726b88e restructure error calculation * d67d5de check for system.posix_acl_default before setting umask * 1aa76a5 use different read and write functions when using direct_io -- Antonio SJ Musumeci Tue, 31 Jan 2017 19:24:48 -0500 mergerfs (2.18.0~debian-buster) buster; urgency=medium * 67b48fc compute inode in readdir * b1459c6 only remove src/version.hpp if git repo and git available * c8fa51c support setting of inodes (using use_ino option) * 822204f replace std::set with klib's khash to increase readdir performance * 5f7a168 note that mergerfs should be run as root * 35075bb return clonepath errors -- Antonio SJ Musumeci Fri, 16 Dec 2016 11:16:59 -0500 mergerfs (2.17.0~debian-buster) buster; urgency=medium * 05d81db update manpage * 3c5351a ignore filesystems which return zeros for statfs. closes #335 * 3d2283f clang cpp doesn't like grep exiting early * 192bb9c remove usage of -D from install * 157dae0 define O_LARGEFILE and O_NOATIME if needed * 00c814d consolidate and simplify utime * 6d6fb45 check if fdatasync is available and return ENOSYS if not * 897f20b minor correction of spelling mistake * d0b6cd1 further abstraction of system calls * 1dc7bff wrap most posix filesystem functions * 8f594e1 add flock * cd90193 add some more explination to the FAQ * cd71af8 add mergerfs.ctl and scorch to tooling section * 3fb7f89 add EDQUOT to errors which trigger moveonenospc -- Antonio SJ Musumeci Tue, 15 Nov 2016 23:35:06 -0500 mergerfs (2.16.1~debian-buster) buster; urgency=medium * dfa8269 update manpage * d9a7906 use SYS_setgroup32 syscall if available. closes #319 * b1f2e94 add information about page cache kernel panic bug -- Antonio SJ Musumeci Mon, 19 Sep 2016 17:07:42 -0400 mergerfs (2.16.0~debian-buster) buster; urgency=medium * a8cd9b7 recreate manpage * 7e423cd small tweaks to build on Debian kFreeBSD * 0395e7c fix futimes version of utimes wrapper * 9392317 fix #define typo * 1513c92 abstract posix_fadvise * 158dda9 Fix minor typo * 1a698e5 rename include cpp files to have icpp extension * 2ee6b4f include sys/types.h to pick up ssize_t * 709dda5 support systems without ENODATA -- Antonio SJ Musumeci Wed, 14 Sep 2016 08:50:51 -0400 mergerfs (2.15.0~debian-buster) buster; urgency=medium * 49474f0 make futimes crossplatform * 34d38cb split sendfile wrapper into separate files * 192a9d5 make fs_attr compile on unsupported platforms * 40574bd use dynamic buffer for realpath * 064fd55 bump FUSE_USE_VERSION to 29 * 45f757d add osx version of fallocate * 0fceb8e add epall and eprand policies * 7634eb1 replace nonstandard eaccess with POSIX.1-2008 faccessat * 47f184a fix typo and clarify feature -- Antonio SJ Musumeci Sun, 7 Aug 2016 14:46:43 -0400 mergerfs (2.14.0~debian-buster) buster; urgency=medium * a93ab6c add existing path first found policy. closes #289 * 43cbd9c move size calculations to use uint64_t. fixes #287 * 9f36ead add license title * 30cdfa6 reiterate path preserving policies -- Antonio SJ Musumeci Mon, 11 Jul 2016 20:50:32 -0400 mergerfs (2.13.1~debian-buster) buster; urgency=medium * 3cb2045 update man page * 6dc6fde update info on mmap bug and when it was fixed * cb35a37 rework fallocate logic * 23b8e45 fix ioctl on directories -- Antonio SJ Musumeci Thu, 19 May 2016 17:30:07 -0400 mergerfs (2.13.0~debian-buster) buster; urgency=medium * 3a50344 update man page * be6341e create eplus (existing path, least used space) policy. closes #273 * f7d3e8b create lus (least used space) policy. closes #273 * 74ed1b0 faq update on direct writes * d0414d7 clean up information regarding fstab * ef8d8f3 add fsname to readme * cd15b7a further tweak language regarding policies wrt create category * 8643d35 make policy descriptions more explicit * 1cfe1c3 tweak doc language * dbb13ef add details of mmap cache bug * 90ca14a add dual mount suggestion to mmap problem * cfaf812 add details regarding rtorrent, mmap, and direct_io * 0c6c69e update readme to include mergerfs.dedup * 309cfee change tar.gz build url -- Antonio SJ Musumeci Sat, 7 May 2016 15:19:34 -0400 mergerfs (2.12.3~debian-buster) buster; urgency=medium * 382ca87 tweak man page creation * 070ed08 properly check errors of xattr. closes #255 -- Antonio SJ Musumeci Thu, 10 Mar 2016 18:43:12 -0500 mergerfs (2.12.2~debian-buster) buster; urgency=medium * 6492fda update precompiled man page * 2061211 fix rename failing on non-path preserving policies * 4928944 clarify that rename uses the create policy to make decisions -- Antonio SJ Musumeci Sun, 6 Mar 2016 02:23:47 -0500 mergerfs (2.12.1~debian-buster) buster; urgency=medium * 12cf57d re-add minfreespace check to epmfs policy * 59b08a5 change tooling names and add guide link -- Antonio SJ Musumeci Fri, 4 Mar 2016 13:56:15 -0500 mergerfs (2.12.0~debian-buster) buster; urgency=medium * f4a4cc5 fix building of platform specific deb packages * ef4c583 fix printing of versions with no changes * 4ecf3c5 clearly separate usage of statvfs from stat for file existance * 779143f add minfreespace checks to policy ff's create and remove fwfs * 4d7148c update readme with minfreespace and readonly details * 14886a2 add readonly and minfreespace filters to all policy for creates. closes #236 * 9819cf6 fix clonepath being called on wrong source * c56b488 fix creation of changelog * e593927 normalize error handling in rename policy * 7c85cd9 ff policy tweaks * 5cf3bb7 override standard libfuse version flag * 25a0399 minor tweaks to filesystem utility functions * 792c9b9 use stat/2 rather than statvfs/2 to find file drive -- Antonio SJ Musumeci Tue, 1 Mar 2016 00:19:59 -0500 mergerfs (2.11.0~debian-buster) buster; urgency=medium * a698a8a update / tweak readme * d4ec341 remove unnecessary policies * 5813d1e ignore drives mounted as readonly from create policies. closes #224 * a4e60d7 add eplfs info to readme -- Antonio SJ Musumeci Sun, 21 Feb 2016 18:31:02 -0500 mergerfs (2.10.0~debian-buster) buster; urgency=medium * f3e75a0 use stat.st_dev to uniquely identify mounts for statfs. closes #213 * b3248a8 simplify policies * 7bf1ca4 add existing path, least free space policy. closes #216 * 6086620 use references to srcmounts rather than copies * 3a51ceb add some "why mergerfs instead of X" sections to the FAQ * 681f3d7 add section to readme about trash directories * 0bac344 add information on fixing libfuse crashes * 840f9b8 remove sbin from rpm spec file -- Antonio SJ Musumeci Mon, 15 Feb 2016 22:57:35 -0500 mergerfs (2.9.1~debian-buster) buster; urgency=medium * f779f82 fix statvfs drive dedup. closes #209 -- Antonio SJ Musumeci Fri, 22 Jan 2016 10:16:01 -0500 mergerfs (2.9.0~debian-buster) buster; urgency=medium * 3e20adb remove clone test tool * e285bde update the precompiled man page * 853769b general tweaks to readme * ea32575 make symlink function like mknod/mkdir. closes #204 * 1d1694b fix indexing of mknod targets. closes #202 * 93397ea remove incorrect warning about race condition with mkdir/mknod * 25265f4 dedup based on full statvfs struct rather than fsid. closes #183 * de776b7 remove tooling from repo. closes #198 * 62f8fc5 have link act similar to rename * 242af77 move from MIT to ISC license. closes #194 * 4c77ac4 all action functions return success should at least one succeed. closes #189 * b811522 update man page * 8a5c524 replace srcpoints with srcmounts * a3e6a03 rework rename algo to minimize likelihood of EXDEV being returned. closes #187 * eb6d1a1 change to using template for policy class * 55c4a32 add artifacts to gitignore * 51ae7d1 change make to work with non-bash shells * c731b70 fix building without xattr * 9e24796 add mergerfs pid to xattrs -- Antonio SJ Musumeci Thu, 21 Jan 2016 14:55:15 -0500 mergerfs (2.8.0~debian-buster) buster; urgency=medium * 5e880bd use SYS_setgroups rather than setgroups * 8a651b0 add support section to readme and manpage * 93cedab fix misc issues flagged by clang scan-build * fd4ce1b include distro and codename in deb package versions -- Antonio SJ Musumeci Thu, 29 Oct 2015 23:38:45 -0400 mergerfs (2.7.0~debian-buster) buster; urgency=medium * f3a6876 remove pandoc from build requirements * 30c29c9 remove manpage from root directory * 8ed11a0 if pandoc doesn't exist copy premade version * 46c8361 offer prebuilt manpage for platforms without easy access to pandoc * 02df441 update info on Samba client EXDEV issue * fe79609 fix typo * 873c4a9 add link to gvfs-fuse patch * 4df9b2e add documentation on SMB client EXDEV / EIO issue * 1c7de2d fix minor integer casting issues * a10de2a update build dependencies for Fedora * 3d7d2cf add sbin directory to rpm spec * 5489952 enhance git2debcl to work with older python releases * 068fbc0 set rpm dependency to fuse * 5a76c41 create mount.mergerfs symlink. closes #149 * 58446f9 misc fixes to compile on older platforms * 40f569b rewrite gid cache system * f6d396c audit (and fix) file permissions and ownership. closes #148 * 53e3284 remove usage of UINT32_MAX macro * 09ffc8c provide usage text and version info. closes #146 -- Antonio SJ Musumeci Sun, 18 Oct 2015 00:15:28 -0400 mergerfs (2.6.0~debian-buster) buster; urgency=medium * c289daf swap deb and unsigned-deb make tagets. closes #140 * 5808ab7 move on enospc when writing feature. closes #141 * 4e5578d make note about escaping glob tokens more explicit * 4b375fa include rpm-build in Fedora dependencies * 9542e63 include link to release page in readme * de583b7 clean up options listing * 8bf0f75 create summary feature section -- Antonio SJ Musumeci Sat, 26 Sep 2015 14:14:04 -0400 mergerfs (2.5.0~debian-buster) buster; urgency=medium * 5850fbe bump README date * a960a7e cleanup controlfile manipulation * e0cf972 include default_permissions in default arguments * f4e3f28 change README regarding setgroups cache and new rwlock ugid fallback * 08c0c2d fix typo in README. closes #134 * 3163258 make changing credentials opportunistic + per thread setgroups cache * b194272 add notes on permissions and errors to readme * c131310 include known issues section to readme -- Antonio SJ Musumeci Mon, 14 Sep 2015 22:45:45 -0400 mergerfs (2.4.0~debian-buster) buster; urgency=medium * ce93529 realpath'ize all source mount points. closes #117 * 2d89947 fix non-suffixed setxattr of user.mergerfs.minfreespace * e98b801 remove version.hpp on clean * b22528b add user.mergerfs.version xattr * e377d54 add user.mergerfs.policies xattr * 08d07b7 add building of rpm * 305f190 add basic instructions for building on Fedora * 8178bf5 refactor and simplify getxattr for user.mergerfs.\* -- Antonio SJ Musumeci Sun, 6 Sep 2015 18:25:21 -0400 mergerfs (2.3.0~debian-buster) buster; urgency=medium * aea5e44 use correct variable for finding version * bc77b0f add minfreespace check to epmfs create policy * 1f1e481 rework rename * 7a93198 forgot to add einval to policy * bbc75f6 create errno policies for simulating errors. closes #107 * 126df0f fix epmfs failing to pick the existing path. closes #102 * ab68ac0 enhance deb building * e73ea33 format README better for man pages -- Antonio SJ Musumeci Wed, 2 Sep 2015 08:02:26 -0400 mergerfs (2.2.0~debian-buster) buster; urgency=medium * 9519251 update README regarding options * 8767db9 remove unused variable * 267f2d2 move requesting of FUSE flags to init from cli args * f130d07 config get and struct naming cleanup * 52d8029 passthrough ioctl args without processing. closes #90 * c60d038 use gte rather than gt for mtime comparisons. fixes #91 -- Antonio SJ Musumeci Wed, 5 Aug 2015 12:30:14 -0400 mergerfs (2.1.2~debian-buster) buster; urgency=medium * 80b2c35 add creation of full path for open * 983fa91 change fuse functions to use the fuse namespace * e5359eb remove unused readdir function * f00cd14 use pthread_getugid_np instead of gete{u,g}id on OSX. fixes #84 -- Antonio SJ Musumeci Thu, 16 Jul 2015 16:58:14 -0400 mergerfs (2.1.1~debian-buster) buster; urgency=medium * 4d60538 ignore ENOTSUP errors when cloning paths. fixes #82 -- Antonio SJ Musumeci Mon, 13 Jul 2015 12:21:48 -0400 mergerfs (2.1.0~debian-buster) buster; urgency=medium * aafc1e9 add str to size_t conversion code * b3109ac add minfreespace to xattr interface * d079856 add info on lfs and fwfs policies and minfreespace option * c101430 rework category -> fuse function table * b2cd791 stop auto calculating and storing fullpath in policies * 0c60701 create different policies based on category of use * 51b6d3f add category to policies so as to distinguish between creates and searches * 6ca4389 separate policies into individual modules * 3c8f122 move policy function type from fs to policy * 2bd4456 move Path object to separate file * 8e5b796 create lfs policy. closes #73 * 58167c3 first w/ free space policy. closes #72 * ccb22c1 create minfreespace option. closes #71 -- Antonio SJ Musumeci Sun, 5 Jul 2015 21:03:34 -0400 mergerfs (2.0.1~debian-buster) buster; urgency=medium * ad7ce48 fix readme typos and misc formatting * fe0f442 add Tips and FAQ section to readme * 1879c9c update readme with defaults option info * 45b73e5 fix calling of lgetxattr. closes #68 -- Antonio SJ Musumeci Fri, 5 Jun 2015 20:13:24 -0400 mergerfs (2.0.0~debian-buster) buster; urgency=medium * 5fb2775 attempt to set priority to -10 on startup. closes #65 * 4b204b8 restrict who can setxattr the pseudo file. closes #64 * 33c837a provide 'defaults' option. closes #58 * 951b22b set FUSE subtype to 'mergerfs'. closes #59 * 74c334c add default policies for categories and description of `all` * 08366a3 match cli options to xattrs * 91671d7 remove FileInfo and keep only file descriptor * c022741 revert removal of 'all' policy and relevant behavior. closes #54 * 12f393a per FUSE function policies. closes #52, #53 -- Antonio SJ Musumeci Tue, 17 Mar 2015 20:40:35 -0400 mergerfs (1.7.1~debian-buster) buster; urgency=medium * 283a2b2 Try RLIMIT_INFINITY first, then cur = max, then loop and try to increase till error. closes #50 * 1a1c9db close file after getting ioc flags. closes #48 -- Antonio SJ Musumeci Tue, 17 Feb 2015 11:48:55 -0500 mergerfs (1.7.0~debian-buster) buster; urgency=medium * d30cae2 add user.mergerfs.allpaths and user.mergerfs.relpath to getxattr * 2e95c6e merge action and search category * b411c63 Remove 'all' policy and simplify logic * 1f90203 use standard platform macros. closes #43 * c2cbb93 elevate privileges when calling clonepath. closes #41 * 6276ce9 handle EEXIST while cloning a path. closes #40 * d1f3bd8 add clonepath tool * 031b87f slight refactoring * 5dd0729 remove longest common prefix from fsname. closes #38 -- Antonio SJ Musumeci Sat, 7 Feb 2015 18:49:36 -0500 mergerfs (1.6.0~debian-buster) buster; urgency=medium * 6c3ff01 pass const strings by reference. closes #33 * d7ede20 provide stat to readdir filler. closes #32 * 613b996 support RHEL6. closes #29 -- Antonio SJ Musumeci Mon, 10 Nov 2014 21:11:17 -0500 mergerfs (1.5.1~debian-buster) buster; urgency=medium * 47522a2 exclude merges in the debian changelog. closes #28 -- Antonio SJ Musumeci Mon, 29 Sep 2014 19:30:12 -0400 mergerfs (1.5.0~debian-buster) buster; urgency=medium * 075d62d add support for ioctl on directories. closes #27 -- Antonio SJ Musumeci Fri, 26 Sep 2014 18:33:51 -0400 mergerfs (1.4.1~debian-buster) buster; urgency=medium * cfe7609 find functions now return errors. closes #24 * 8f35406 use lstat to confirm file existance instead of eaccess. closes #25 * 4ea1adb use f_frsize rather than f_bsize for mfs calculations. closes #26 * 15f8849 add custom git log to debian changelog script -- Antonio SJ Musumeci Sat, 23 Aug 2014 11:44:40 -0400 mergerfs (1.4.0~debian-buster) buster; urgency=medium * 7e9ccd0 support runtime setting of srcmounts. closes #12 -- Antonio SJ Musumeci Fri, 8 Aug 2014 10:20:27 -0400 mergerfs (1.3.0~debian-buster) buster; urgency=medium * 992e05e update docs for xattr features * b82db46 getxattr for user.mergerfs.{base,full}path returns the source paths. closes #23 * 7b0d703 only allow manipulation of runtime settings via xattrs. closes #22 -- Antonio SJ Musumeci Thu, 31 Jul 2014 17:27:43 -0400 mergerfs (1.2.4~debian-buster) buster; urgency=medium * 45cec2d use int instead of long for ioctl. fixes #21 -- Antonio SJ Musumeci Tue, 22 Jul 2014 07:07:25 -0400 mergerfs (1.2.3~debian-buster) buster; urgency=medium * 2295714 on ENOENT try cloning the dest path to source drive. closes #20 -- Antonio SJ Musumeci Mon, 7 Jul 2014 21:54:38 -0400 mergerfs (1.2.2~debian-buster) buster; urgency=medium * ccb0ac1 generate the controlfile data on the fly. closes #19 * 15a0416 don't flush if it's the .mergerfs pseudo file. closes #18 * ec38a9c fix rename'ing to local device -- Antonio SJ Musumeci Wed, 25 Jun 2014 15:17:26 -0400 mergerfs (1.2.1~debian-buster) buster; urgency=medium * 3b6c748 use geteuid syscall as well -- Antonio SJ Musumeci Mon, 23 Jun 2014 12:17:09 -0400 mergerfs (1.2.0~debian-buster) buster; urgency=medium * f385f02 add -O2 to CFLAGS * 0e12d79 platform specific code to deal with sete{u,g}id. closes #17 * 7164535 add flush callback. closes #16 * dd66607 add {read,write}_buf functions. closes #15 * 0f8fe47 add man file via markdown -> man conversion with pandoc. closes #14 * 45a2e09 reverse source and destination mount points to match fstab requirements. closes #13 * 645c823 source mount paths can contain globing. closes #10 * 1835826 fsname set to list of src mounts. closes #9 * 4a0bc4a add debian package building. closes #11 * e2e0359 fix free space calculations. closes #8 * 655436f further Makefile enhancements -- Antonio SJ Musumeci Wed, 18 Jun 2014 10:40:54 -0400 mergerfs (1.1.0~debian-buster) buster; urgency=medium * a7d9a9b enhance Makefile * 36887e4 when readdir's filler returns non-zero return ENOMEM. closes #7 * 652a299 add fgetattr. closes #5 * aab90b0 rework policy code * 345d0bb use eaccess to determine permissions for ffwp. closes #2 * 4c7095c remove stat'ing of files in readdir. closes #3 -- Antonio SJ Musumeci Wed, 28 May 2014 21:39:07 -0400 mergerfs (1.0.2~debian-buster) buster; urgency=medium * bbc4b59 fs::make_path should check for forward slashes, add if missing * 16fe0cf remove statfs policy * 29ed2bc add FS_IOC_{GET}VERSION to ioctl * 243a193 use long instead of int to limit possibility of overflow in switch, closes #1 * 97ce6f5 use {get,list,set}xattr to modify runtime * 428dd8c update build instructions in readme -- Antonio SJ Musumeci Tue, 27 May 2014 07:17:32 -0400 mergerfs (1.0.1~debian-buster) buster; urgency=medium * 7f640c4 fix building without libattr -- Antonio SJ Musumeci Mon, 19 May 2014 09:34:31 -0400 mergerfs-2.33.5/.gitignore0000644000000000000000000000036414225522122014104 0ustar rootroot# Compiled Object files *.slo *.lo *.o # Compiled Dynamic libraries *.so *.dylib # Compiled Static libraries *.lai *.la *.a # build artifacts mergerfs obj/ src/version.hpp # Debian files debian/files debian/changelog # RPM files rpmbuild/ mergerfs-2.33.5/mergerfs.spec0000644000000000000000000000254114225522122014601 0ustar rootrootName: mergerfs Version: __VERSION__ Release: 1%{?dist} Summary: A featureful FUSE based union filesystem Group: Applications/System License: ISC URL: https://github.com/trapexit/mergerfs Source: mergerfs-%{version}.tar.gz BuildRequires: gcc-c++ # rpmbuild driven by the Makefile uses git to generate a version number BuildRequires: git Requires: fuse %global debug_package %{nil} %prep %setup -q %description mergerfs is a union filesystem geared towards simplifying storage and management of files across numerous commodity storage devices. It is similar to mhddfs, unionfs, and aufs. %build make %{?_smp_mflags} %install make install PREFIX=%{_prefix} DESTDIR=%{buildroot} %files /usr/bin/mergerfs /usr/bin/mergerfs-fusermount /sbin/mount.mergerfs %doc %{_mandir}/* %changelog * Fri Apr 26 2019 Antonio SJ Musumeci - Update description * Mon Jan 25 2016 Antonio SJ Musumeci - Remove sbin files * Sat Sep 05 2015 Antonio SJ Musumeci - Include PREFIX to install * Mon Dec 29 2014 Joe Lawrence - Tweak rpmbuild to archive current git HEAD into a tarball, then (re)build in the rpmbuild directory -- more complicated but seemingly better suited to generate source and debug rpms. * Fri Jun 20 2014 Joe Lawrence - Initial rpm spec file. mergerfs-2.33.5/tools/0000755000000000000000000000000014225522122013251 5ustar rootrootmergerfs-2.33.5/tools/git2debcl0000755000000000000000000000674514225522122015052 0ustar rootroot#!/usr/bin/env python # Copyright (c) 2016, Antonio SJ Musumeci # Permission to use, copy, modify, and/or distribute this software for any # purpose with or without fee is hereby granted, provided that the above # copyright notice and this permission notice appear in all copies. # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. import sys import subprocess import argparse def call(args): return subprocess.Popen(args,stdout=subprocess.PIPE).communicate()[0].decode() def git_tags(): args = ["git", "tag", '-l'] tags = call(args) tags = [[int(X) for X in tag.split(".")] for tag in tags.split()] tags.sort() tags.reverse() tags = [".".join([str(X) for X in tag]) for tag in tags] return tags def git_log(fromtag,totag): args = ['git','log','--no-merges','--oneline',fromtag+'...'+totag] return call(args).strip().split('\n') def git_author_and_time(tag): args = ['git','log','-1','--format=-- %an <%ae> %cD',tag] return call(args).strip() def git_version(): args = ['git','describe','--always','--tags','--dirty'] return call(args).strip() def guess_distro(): try: args = ['lsb_release','-i','-s'] return call(args).strip().lower() except: return 'unknown' def guess_codename(): try: args = ['lsb_release','-c','-s'] return call(args).strip().lower() except: return 'unknown' def main(): parser = argparse.ArgumentParser(description='Generated debian/changelog from git log') parser.add_argument('--name',type=str,help='Name of package',required=True) parser.add_argument('--version',type=str,help='Place in git history to include upto',default='::guess::') parser.add_argument('--distro',type=str,help='Distribution name',default='::guess::') parser.add_argument('--codename',type=str,help='Distribution codename',default='::guess::') parser.add_argument('--urgency',type=str,help='Urgency',default='medium') args = parser.parse_args() if args.distro == '::guess::': args.distro = guess_distro() if args.codename == '::guess::': args.codename = guess_codename() versuffix = "~"+args.distro+"-"+args.codename if args.version == '::guess::': args.version = git_version() tags = git_tags() if args.version in tags: idx = tags.index(args.version) tags = tags[idx:] tags = list(zip(tags,tags)) else: tags = list(zip(tags,tags)) tags.insert(0,(args.version,'HEAD')) for i in range(0,len(tags)): tags[i] = (tags[i][0] + versuffix,tags[i][1]) tag = tags[0] for prev in tags[1:]: lines = git_log(tag[1],prev[1]) if lines == ['']: tag = prev continue print('{0} ({1}) {2}; urgency={3}\n'.format(args.name,tag[0],args.codename,args.urgency)) for line in lines: print(" * " + line) authorandtime = git_author_and_time(tag[1]) print(' {0}\n'.format(authorandtime)) tag = prev if __name__ == "__main__": main() mergerfs-2.33.5/tools/update-version0000755000000000000000000000061614225522122016147 0ustar rootroot#!/bin/sh VERSION=$(git describe --always --tags --dirty) if [ $? -ne 0 ]; then VERSION=$(cat VERSION) fi if [ "${VERSION}" = "" ]; then VERSION="unknown" fi echo -n "${VERSION}" > VERSION grep -q \"${VERSION}\" src/version.hpp RV=$? if [ $RV -ne 0 ]; then echo "#pragma once" > src/version.hpp echo "static const char MERGERFS_VERSION[] = \"${VERSION}\";" >> src/version.hpp fi mergerfs-2.33.5/tools/cppfind0000755000000000000000000000032714225522122014624 0ustar rootroot#!/bin/sh CXX="${CXX:-c++}" FUSE_CFLAGS="-Ilibfuse/include -D_FILE_OFFSET_BITS=64 -DFUSE_USE_VERSION=29" echo "#include \"fuse.h\"" | ${CXX} -E ${FUSE_CFLAGS} - | grep "${1}" > /dev/null [ "$?" != "0" ]; echo $? mergerfs-2.33.5/tools/install-build-pkgs0000755000000000000000000000253114225522122016705 0ustar rootroot#!/bin/sh if [ -e /usr/bin/apt-get ]; then export DEBIAN_FRONTEND=noninteractive apt-get -qy update apt-get -qy --no-install-suggests --no-install-recommends \ install \ ca-certificates \ build-essential \ git \ g++ \ debhelper \ automake \ fakeroot \ libtool \ lsb-release apt-get -qy --no-install-suggests --no-install-recommends install python apt-get -qy --no-install-suggests --no-install-recommends install python3 elif [ -e /usr/bin/dnf ]; then dnf -y update dnf -y install \ git rpm-build gcc-c++ make which python3 automake \ libtool gettext-devel elif [ -e /usr/bin/yum ]; then yum -y update yum -y install \ git rpm-build gcc-c++ make which \ python python-argparse \ automake libtool gettext-devel elif [ -e /sbin/apk ]; then apk add \ abuild git gcc g++ make autoconf \ automake libtool gettext-dev linux-headers elif [ -e /usr/sbin/pkg ]; then pkg install \ git gmake gcc autoconf automake libtool \ gettext-tools fi if [ ! -e /usr/bin/python ]; then if [ -e /usr/bin/python3 ]; then ln -s /usr/bin/python3 /usr/bin/python elif [ -e /usr/bin/python2 ]; then ln -s /usr/bin/python2 /usr/bin/python fi fi mergerfs-2.33.5/.cirrus.yml0000644000000000000000000001557714225522122014240 0ustar rootrootfreebsd_task: name: "freebsd:11.4" freebsd_instance: image_family: freebsd-11-4 env: ASSUME_ALWAYS_YES: yes script: - tools/install-build-pkgs - gmake -j4 freebsd_task: name: "freebsd:12.1" freebsd_instance: image_family: freebsd-12-1 env: ASSUME_ALWAYS_YES: yes script: - tools/install-build-pkgs - gmake -j4 freebsd_task: name: "freebsd:12.2" freebsd_instance: image_family: freebsd-12-2 env: ASSUME_ALWAYS_YES: yes script: - tools/install-build-pkgs - gmake -j4 freebsd_task: name: "freebsd:13.0" freebsd_instance: image_family: freebsd-13-0 env: ASSUME_ALWAYS_YES: yes script: - tools/install-build-pkgs - gmake -j4 #macos_task: # osx_instance: # image: catalina-base # script: # - tools/install-build-pkgs # - gmake -j4 linux_task: name: "alpine:3.11" container: image: alpine:3.11 cpu: 4 memory: 2G timeout_in: 10m script: - tools/install-build-pkgs - make STATIC=1 LTO=1 linux_task: name: "alpine:3.12" container: image: alpine:3.12 cpu: 4 memory: 2G timeout_in: 10m script: - tools/install-build-pkgs - make STATIC=1 LTO=1 linux_task: name: "alpine:3.13" container: image: alpine:3.13 cpu: 4 memory: 2G timeout_in: 10m script: - tools/install-build-pkgs - make STATIC=1 LTO=1 linux_task: name: "alpine:3.14" container: image: alpine:3.14 cpu: 4 memory: 2G timeout_in: 10m script: - tools/install-build-pkgs - make STATIC=1 LTO=1 linux_task: name: "alpine:latest" container: image: alpine:latest cpu: 4 memory: 2G timeout_in: 10m script: - tools/install-build-pkgs - make STATIC=1 LTO=1 linux_task: name: "alpine-arm:latest" arm_container: image: alpine:latest cpu: 4 memory: 2G timeout_in: 10m script: - tools/install-build-pkgs - make STATIC=1 LTO=1 linux_task: name: "centos:7" container: image: centos:7 cpu: 4 memory: 2G timeout_in: 10m script: - tools/install-build-pkgs - make - make rpm linux_task: name: "centos:8" container: image: centos:8 cpu: 4 memory: 2G timeout_in: 10m script: - tools/install-build-pkgs - make - make rpm linux_task: name: "fedora:30" container: image: fedora:30 cpu: 4 memory: 2G timeout_in: 10m script: - tools/install-build-pkgs - make - make rpm linux_task: name: "fedora:31" container: image: fedora:31 cpu: 4 memory: 2G timeout_in: 10m script: - tools/install-build-pkgs - make - make rpm linux_task: name: "fedora:32" container: image: fedora:32 cpu: 4 memory: 2G timeout_in: 10m script: - tools/install-build-pkgs - make - make rpm linux_task: name: "fedora:33" container: image: fedora:33 cpu: 4 memory: 2G timeout_in: 10m script: - tools/install-build-pkgs - make - make rpm linux_task: name: "fedora:34" container: image: fedora:34 cpu: 4 memory: 2G timeout_in: 10m script: - tools/install-build-pkgs - make - make rpm linux_task: name: "fedora:latest" container: image: fedora:latest cpu: 4 memory: 2G timeout_in: 10m script: - tools/install-build-pkgs - make - make rpm linux_task: name: "fedora-arm:latest" arm_container: image: fedora:latest cpu: 4 memory: 2G timeout_in: 10m script: - tools/install-build-pkgs - make - make rpm linux_task: name: "ubuntu:14.04" container: image: ubuntu:14.04 cpu: 4 memory: 2G timeout_in: 10m script: - tools/install-build-pkgs - git fetch - make deb - apt-get -y install fuse - dpkg -i ../*.deb - mergerfs -v || true linux_task: name: "ubuntu:16.04" container: image: ubuntu:16.04 cpu: 4 memory: 2G timeout_in: 10m script: - tools/install-build-pkgs - git fetch - make deb - apt-get -y install fuse - dpkg -i ../*.deb - mergerfs -v || true linux_task: name: "ubuntu:18.04" container: image: ubuntu:18.04 cpu: 4 memory: 2G timeout_in: 10m script: - tools/install-build-pkgs - git fetch - make deb - apt-get -y install fuse - dpkg -i ../*.deb - mergerfs -v || true linux_task: name: "ubuntu:20.04" container: image: ubuntu:20.04 cpu: 4 memory: 2G timeout_in: 10m script: - tools/install-build-pkgs - git fetch - make deb - apt-get -y install fuse - dpkg -i ../*.deb - mergerfs -v || true linux_task: name: "ubuntu:latest" container: image: ubuntu:latest cpu: 4 memory: 2G timeout_in: 10m script: - tools/install-build-pkgs - git fetch - make deb - apt-get -y install fuse - dpkg -i ../*.deb - mergerfs -v || true linux_task: name: "ubuntu-arm:latest" arm_container: image: ubuntu:latest cpu: 4 memory: 2G timeout_in: 10m script: - tools/install-build-pkgs - git fetch - make deb - apt-get -y install fuse - dpkg -i ../*.deb - mergerfs -v || true linux_task: name: "debian:7" container: image: debian:8 cpu: 4 memory: 2G timeout_in: 10m script: - tools/install-build-pkgs - git fetch - make deb - apt-get -y install fuse - dpkg -i ../*.deb - mergerfs -v || true linux_task: name: "debian:8" container: image: debian:8 cpu: 4 memory: 2G timeout_in: 10m script: - tools/install-build-pkgs - git fetch - make deb - apt-get -y install fuse - dpkg -i ../*.deb - mergerfs -v || true linux_task: name: "debian:9" container: image: debian:9 cpu: 4 memory: 2G timeout_in: 10m script: - tools/install-build-pkgs - git fetch - make deb - apt-get -y install fuse - dpkg -i ../*.deb - mergerfs -v || true linux_task: name: "debian:10" container: image: debian:10 cpu: 4 memory: 2G timeout_in: 10m script: - tools/install-build-pkgs - git fetch - make deb - apt-get -y install fuse - dpkg -i ../*.deb - mergerfs -v || true linux_task: name: "debian:11" container: image: debian:11 cpu: 4 memory: 2G timeout_in: 10m script: - tools/install-build-pkgs - git fetch - make deb - apt-get -y install fuse - dpkg -i ../*.deb - mergerfs -v || true linux_task: name: "debian:latest" container: image: debian:latest cpu: 4 memory: 2G timeout_in: 10m script: - tools/install-build-pkgs - git fetch - make deb - apt-get -y install fuse - dpkg -i ../*.deb - mergerfs -v || true linux_task: name: "debian-arm:latest" arm_container: image: debian:latest cpu: 4 memory: 2G timeout_in: 10m script: - tools/install-build-pkgs - git fetch - make deb - apt-get -y install fuse - dpkg -i ../*.deb - mergerfs -v || true mergerfs-2.33.5/man/0000755000000000000000000000000014225522122012664 5ustar rootrootmergerfs-2.33.5/man/mergerfs.10000644000000000000000000033142714225522122014572 0ustar rootroot.\"t .\" Automatically generated by Pandoc 1.19.2.4 .\" .TH "mergerfs" "1" "2021\-05\-29" "mergerfs user manual" "" .hy .SH NAME .PP mergerfs \- a featureful union filesystem .SH SYNOPSIS .PP mergerfs \-o .SH DESCRIPTION .PP \f[B]mergerfs\f[] is a union filesystem geared towards simplifying storage and management of files across numerous commodity storage devices. It is similar to \f[B]mhddfs\f[], \f[B]unionfs\f[], and \f[B]aufs\f[]. .SH FEATURES .IP \[bu] 2 Configurable behaviors / file placement .IP \[bu] 2 Ability to add or remove filesystems at will .IP \[bu] 2 Resistance to individual filesystem failure .IP \[bu] 2 Support for extended attributes (xattrs) .IP \[bu] 2 Support for file attributes (chattr) .IP \[bu] 2 Runtime configurable (via xattrs) .IP \[bu] 2 Works with heterogeneous filesystem types .IP \[bu] 2 Moving of file when filesystem runs out of space while writing .IP \[bu] 2 Ignore read\-only filesystems when creating files .IP \[bu] 2 Turn read\-only files into symlinks to underlying file .IP \[bu] 2 Hard link copy\-on\-write / CoW .IP \[bu] 2 Support for POSIX ACLs .IP \[bu] 2 Misc other things .SH HOW IT WORKS .PP mergerfs logically merges multiple paths together. Think a union of sets. The file/s or directory/s acted on or presented through mergerfs are based on the policy chosen for that particular action. Read more about policies below. .IP .nf \f[C] A\ \ \ \ \ \ \ \ \ +\ \ \ \ \ \ B\ \ \ \ \ \ \ \ =\ \ \ \ \ \ \ C /disk1\ \ \ \ \ \ \ \ \ \ \ /disk2\ \ \ \ \ \ \ \ \ \ \ /merged |\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ |\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ | +\-\-\ /dir1\ \ \ \ \ \ \ \ +\-\-\ /dir1\ \ \ \ \ \ \ \ +\-\-\ /dir1 |\ \ \ |\ \ \ \ \ \ \ \ \ \ \ \ |\ \ \ |\ \ \ \ \ \ \ \ \ \ \ \ |\ \ \ | |\ \ \ +\-\-\ file1\ \ \ \ |\ \ \ +\-\-\ file2\ \ \ \ |\ \ \ +\-\-\ file1 |\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ |\ \ \ +\-\-\ file3\ \ \ \ |\ \ \ +\-\-\ file2 +\-\-\ /dir2\ \ \ \ \ \ \ \ |\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ |\ \ \ +\-\-\ file3 |\ \ \ |\ \ \ \ \ \ \ \ \ \ \ \ +\-\-\ /dir3\ \ \ \ \ \ \ \ | |\ \ \ +\-\-\ file4\ \ \ \ \ \ \ \ |\ \ \ \ \ \ \ \ \ \ \ \ +\-\-\ /dir2 |\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ +\-\-\ file5\ \ \ |\ \ \ | +\-\-\ file6\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ |\ \ \ +\-\-\ file4 \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ | \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ +\-\-\ /dir3 \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ |\ \ \ | \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ |\ \ \ +\-\-\ file5 \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ | \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ +\-\-\ file6 \f[] .fi .PP mergerfs does \f[B]NOT\f[] support the copy\-on\-write (CoW) or whiteout behaviors found in \f[B]aufs\f[] and \f[B]overlayfs\f[]. You can \f[B]not\f[] mount a read\-only filesystem and write to it. However, mergerfs will ignore read\-only drives when creating new files so you can mix read\-write and read\-only drives. It also does \f[B]NOT\f[] split data across drives. It is not RAID0 / striping. It is simply a union of other filesystems. .SH TERMINOLOGY .IP \[bu] 2 branch: A base path used in the pool. .IP \[bu] 2 pool: The mergerfs mount. The union of the branches. .IP \[bu] 2 relative path: The path in the pool relative to the branch and mount. .IP \[bu] 2 function: A filesystem call (open, unlink, create, getattr, rmdir, etc.) .IP \[bu] 2 category: A collection of functions based on basic behavior (action, create, search). .IP \[bu] 2 policy: The algorithm used to select a file when performing a function. .IP \[bu] 2 path preservation: Aspect of some policies which includes checking the path for which a file would be created. .SH BASIC SETUP .PP If you don\[aq]t already know that you have a special use case then just start with one of the following option sets. .SS You need \f[C]mmap\f[] (used by rtorrent and many sqlite3 base software) .PP \f[C]allow_other,use_ino,cache.files=partial,dropcacheonclose=true,category.create=mfs\f[] .SS You don\[aq]t need \f[C]mmap\f[] .PP \f[C]allow_other,use_ino,cache.files=off,dropcacheonclose=true,category.create=mfs\f[] .PP See the mergerfs wiki for real world deployments (https://github.com/trapexit/mergerfs/wiki/Real-World-Deployments) for comparisons / ideas. .SH OPTIONS .PP These options are the same regardless you use them with the \f[C]mergerfs\f[] commandline program, used in fstab, or in a config file. .SS mount options .IP \[bu] 2 \f[B]config\f[]: Path to a config file. Same arguments as below in key=val / ini style format. .IP \[bu] 2 \f[B]branches\f[]: Colon delimited list of branches. .IP \[bu] 2 \f[B]allow_other\f[]: A libfuse option which allows users besides the one which ran mergerfs to see the filesystem. This is required for most use\-cases. .IP \[bu] 2 \f[B]minfreespace=SIZE\f[]: The minimum space value used for creation policies. Can be overridden by branch specific option. Understands \[aq]K\[aq], \[aq]M\[aq], and \[aq]G\[aq] to represent kilobyte, megabyte, and gigabyte respectively. (default: 4G) .IP \[bu] 2 \f[B]moveonenospc=BOOL|POLICY\f[]: When enabled if a \f[B]write\f[] fails with \f[B]ENOSPC\f[] (no space left on device) or \f[B]EDQUOT\f[] (disk quota exceeded) the policy selected will run to find a new location for the file. An attempt to move the file to that branch will occur (keeping all metadata possible) and if successful the original is unlinked and the write retried. (default: false, true = mfs) .IP \[bu] 2 \f[B]use_ino\f[]: Causes mergerfs to supply file/directory inodes rather than libfuse. While not a default it is recommended it be enabled so that linked files share the same inode value. .IP \[bu] 2 \f[B]inodecalc=passthrough|path\-hash|devino\-hash|hybrid\-hash\f[]: Selects the inode calculation algorithm. (default: hybrid\-hash) .IP \[bu] 2 \f[B]dropcacheonclose=BOOL\f[]: When a file is requested to be closed call \f[C]posix_fadvise\f[] on it first to instruct the kernel that we no longer need the data and it can drop its cache. Recommended when \f[B]cache.files=partial|full|auto\-full\f[] to limit double caching. (default: false) .IP \[bu] 2 \f[B]symlinkify=BOOL\f[]: When enabled and a file is not writable and its mtime or ctime is older than \f[B]symlinkify_timeout\f[] files will be reported as symlinks to the original files. Please read more below before using. (default: false) .IP \[bu] 2 \f[B]symlinkify_timeout=UINT\f[]: Time to wait, in seconds, to activate the \f[B]symlinkify\f[] behavior. (default: 3600) .IP \[bu] 2 \f[B]nullrw=BOOL\f[]: Turns reads and writes into no\-ops. The request will succeed but do nothing. Useful for benchmarking mergerfs. (default: false) .IP \[bu] 2 \f[B]ignorepponrename=BOOL\f[]: Ignore path preserving on rename. Typically rename and link act differently depending on the policy of \f[C]create\f[] (read below). Enabling this will cause rename and link to always use the non\-path preserving behavior. This means files, when renamed or linked, will stay on the same drive. (default: false) .IP \[bu] 2 \f[B]security_capability=BOOL\f[]: If false return ENOATTR when xattr security.capability is queried. (default: true) .IP \[bu] 2 \f[B]xattr=passthrough|noattr|nosys\f[]: Runtime control of xattrs. Default is to passthrough xattr requests. \[aq]noattr\[aq] will short circuit as if nothing exists. \[aq]nosys\[aq] will respond with ENOSYS as if xattrs are not supported or disabled. (default: passthrough) .IP \[bu] 2 \f[B]link_cow=BOOL\f[]: When enabled if a regular file is opened which has a link count > 1 it will copy the file to a temporary file and rename over the original. Breaking the link and providing a basic copy\-on\-write function similar to cow\-shell. (default: false) .IP \[bu] 2 \f[B]statfs=base|full\f[]: Controls how statfs works. \[aq]base\[aq] means it will always use all branches in statfs calculations. \[aq]full\[aq] is in effect path preserving and only includes drives where the path exists. (default: base) .IP \[bu] 2 \f[B]statfs_ignore=none|ro|nc\f[]: \[aq]ro\[aq] will cause statfs calculations to ignore available space for branches mounted or tagged as \[aq]read\-only\[aq] or \[aq]no create\[aq]. \[aq]nc\[aq] will ignore available space for branches tagged as \[aq]no create\[aq]. (default: none) .IP \[bu] 2 \f[B]nfsopenhack=off|git|all\f[]: A workaround for exporting mergerfs over NFS where there are issues with creating files for write while setting the mode to read\-only. (default: off) .IP \[bu] 2 \f[B]follow\-symlinks=never|directory|regular|all\f[]: Turns symlinks into what they point to. (default: never) .IP \[bu] 2 \f[B]link\-exdev=passthrough|rel\-symlink|abs\-base\-symlink|abs\-pool\-symlink\f[]: When a link fails with EXDEV optionally create a symlink to the file instead. .IP \[bu] 2 \f[B]rename\-exdev=passthrough|rel\-symlink|abs\-symlink\f[]: When a rename fails with EXDEV optionally move the file to a special directory and symlink to it. .IP \[bu] 2 \f[B]posix_acl=BOOL\f[]: Enable POSIX ACL support (if supported by kernel and underlying filesystem). (default: false) .IP \[bu] 2 \f[B]async_read=BOOL\f[]: Perform reads asynchronously. If disabled or unavailable the kernel will ensure there is at most one pending read request per file handle and will attempt to order requests by offset. (default: true) .IP \[bu] 2 \f[B]fuse_msg_size=UINT\f[]: Set the max number of pages per FUSE message. Only available on Linux >= 4.20 and ignored otherwise. (min: 1; max: 256; default: 256) .IP \[bu] 2 \f[B]threads=INT\f[]: Number of threads to use in multithreaded mode. When set to zero it will attempt to discover and use the number of logical cores. If the lookup fails it will fall back to using 4. If the thread count is set negative it will look up the number of cores then divide by the absolute value. ie. threads=\-2 on an 8 core machine will result in 8 / 2 = 4 threads. There will always be at least 1 thread. NOTE: higher number of threads increases parallelism but usually decreases throughput. (default: 0) .IP \[bu] 2 \f[B]fsname=STR\f[]: Sets the name of the filesystem as seen in \f[B]mount\f[], \f[B]df\f[], etc. Defaults to a list of the source paths concatenated together with the longest common prefix removed. .IP \[bu] 2 \f[B]func.FUNC=POLICY\f[]: Sets the specific FUSE function\[aq]s policy. See below for the list of value types. Example: \f[B]func.getattr=newest\f[] .IP \[bu] 2 \f[B]category.action=POLICY\f[]: Sets policy of all FUSE functions in the action category. (default: epall) .IP \[bu] 2 \f[B]category.create=POLICY\f[]: Sets policy of all FUSE functions in the create category. (default: epmfs) .IP \[bu] 2 \f[B]category.search=POLICY\f[]: Sets policy of all FUSE functions in the search category. (default: ff) .IP \[bu] 2 \f[B]cache.open=UINT\f[]: \[aq]open\[aq] policy cache timeout in seconds. (default: 0) .IP \[bu] 2 \f[B]cache.statfs=UINT\f[]: \[aq]statfs\[aq] cache timeout in seconds. (default: 0) .IP \[bu] 2 \f[B]cache.attr=UINT\f[]: File attribute cache timeout in seconds. (default: 1) .IP \[bu] 2 \f[B]cache.entry=UINT\f[]: File name lookup cache timeout in seconds. (default: 1) .IP \[bu] 2 \f[B]cache.negative_entry=UINT\f[]: Negative file name lookup cache timeout in seconds. (default: 0) .IP \[bu] 2 \f[B]cache.files=libfuse|off|partial|full|auto\-full\f[]: File page caching mode (default: libfuse) .IP \[bu] 2 \f[B]cache.writeback=BOOL\f[]: Enable kernel writeback caching (default: false) .IP \[bu] 2 \f[B]cache.symlinks=BOOL\f[]: Cache symlinks (if supported by kernel) (default: false) .IP \[bu] 2 \f[B]cache.readdir=BOOL\f[]: Cache readdir (if supported by kernel) (default: false) .IP \[bu] 2 \f[B]direct_io\f[]: deprecated \- Bypass page cache. Use \f[C]cache.files=off\f[] instead. (default: false) .IP \[bu] 2 \f[B]kernel_cache\f[]: deprecated \- Do not invalidate data cache on file open. Use \f[C]cache.files=full\f[] instead. (default: false) .IP \[bu] 2 \f[B]auto_cache\f[]: deprecated \- Invalidate data cache if file mtime or size change. Use \f[C]cache.files=auto\-full\f[] instead. (default: false) .IP \[bu] 2 \f[B]async_read\f[]: deprecated \- Perform reads asynchronously. Use \f[C]async_read=true\f[] instead. .IP \[bu] 2 \f[B]sync_read\f[]: deprecated \- Perform reads synchronously. Use \f[C]async_read=false\f[] instead. .PP \f[B]NOTE:\f[] Options are evaluated in the order listed so if the options are \f[B]func.rmdir=rand,category.action=ff\f[] the \f[B]action\f[] category setting will override the \f[B]rmdir\f[] setting. .SS Value Types .IP \[bu] 2 BOOL = \[aq]true\[aq] | \[aq]false\[aq] .IP \[bu] 2 INT = [MIN_INT,MAX_INT] .IP \[bu] 2 UINT = [0,MAX_INT] .IP \[bu] 2 SIZE = \[aq]NNM\[aq]; NN = INT, M = \[aq]K\[aq] | \[aq]M\[aq] | \[aq]G\[aq] | \[aq]T\[aq] .IP \[bu] 2 STR = string .IP \[bu] 2 FUNC = filesystem function .IP \[bu] 2 CATEGORY = function category .IP \[bu] 2 POLICY = mergerfs function policy .SS branches .PP The \[aq]branches\[aq] argument is a colon (\[aq]:\[aq]) delimited list of paths to be pooled together. It does not matter if the paths are on the same or different drives nor does it matter the filesystem (within reason). Used and available space will not be duplicated for paths on the same device and any features which aren\[aq]t supported by the underlying filesystem (such as file attributes or extended attributes) will return the appropriate errors. .PP Branches currently have two options which can be set. A type which impacts whether or not the branch is included in a policy calculation and a individual minfreespace value. The values are set by prepending an \f[C]=\f[] at the end of a branch designation and using commas as delimiters. Example: /mnt/drive=RW,1234 .SS branch type .IP \[bu] 2 RW: (read/write) \- Default behavior. Will be eligible in all policy categories. .IP \[bu] 2 RO: (read\-only) \- Will be excluded from \f[C]create\f[] and \f[C]action\f[] policies. Same as a read\-only mounted filesystem would be (though faster to process). .IP \[bu] 2 NC: (no\-create) \- Will be excluded from \f[C]create\f[] policies. You can\[aq]t create on that branch but you can change or delete. .SS minfreespace .PP Same purpose as the global option but specific to the branch. If not set the global value is used. .SS globbing .PP To make it easier to include multiple branches mergerfs supports globbing (http://linux.die.net/man/7/glob). \f[B]The globbing tokens MUST be escaped when using via the shell else the shell itself will apply the glob itself.\f[] .IP .nf \f[C] #\ mergerfs\ \-o\ allow_other,use_ino\ /mnt/disk\\*:/mnt/cdrom\ /media/drives \f[] .fi .PP The above line will use all mount points in /mnt prefixed with \f[B]disk\f[] and the \f[B]cdrom\f[]. .PP To have the pool mounted at boot or otherwise accessible from related tools use \f[B]/etc/fstab\f[]. .IP .nf \f[C] #\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ /mnt/disk*:/mnt/cdrom\ \ /mnt/pool\ \ \ \ \ \ fuse.mergerfs\ \ allow_other,use_ino\ \ \ 0\ \ \ \ \ \ \ 0 \f[] .fi .PP \f[B]NOTE:\f[] the globbing is done at mount or when updated using the runtime API. If a new directory is added matching the glob after the fact it will not be automatically included. .PP \f[B]NOTE:\f[] for mounting via \f[B]fstab\f[] to work you must have \f[B]mount.fuse\f[] installed. For Ubuntu/Debian it is included in the \f[B]fuse\f[] package. .SS inodecalc .PP Inodes (st_ino) are unique identifiers within a filesystem. Each mounted filesystem has device ID (st_dev) as well and together they can uniquely identify a file on the whole of the system. Entries on the same device with the same inode are in fact references to the same underlying file. It is a many to one relationship between names and an inode. Directories, however, do not have multiple links on most systems due to the complexity they add. .PP FUSE allows the server (mergerfs) to set inode values but not device IDs. Creating an inode value is somewhat complex in mergerfs\[aq] case as files aren\[aq]t really in its control. If a policy changes what directory or file is to be selected or something changes out of band it becomes unclear what value should be used. Most software does not to care what the values are but those that do often break if a value changes unexpectedly. The tool \f[C]find\f[] will abort a directory walk if it sees a directory inode change. NFS will return stale handle errors if the inode changes out of band. File dedup tools will usually leverage device ids and inodes as a shortcut in searching for duplicate files and would resort to full file comparisons should it find different inode values. .PP mergerfs offers multiple ways to calculate the inode in hopes of covering different usecases. .IP \[bu] 2 passthrough: Passes through the underlying inode value. Mostly intended for testing as using this does not address any of the problems mentioned above and could confuse file deduplication software as inodes from different filesystems can be the same. .IP \[bu] 2 path\-hash: Hashes the relative path of the entry in question. The underlying file\[aq]s values are completely ignored. This means the inode value will always be the same for that file path. This is useful when using NFS and you make changes out of band such as copy data between branches. This also means that entries that do point to the same file will not be recognizable via inodes. That \f[B]does not\f[] mean hard links don\[aq]t work. They will. .IP \[bu] 2 path\-hash32: 32bit version of path\-hash. .IP \[bu] 2 devino\-hash: Hashes the device id and inode of the underlying entry. This won\[aq]t prevent issues with NFS should the policy pick a different file or files move out of band but will present the same inode for underlying files that do too. .IP \[bu] 2 devino\-hash32: 32bit version of devino\-hash. .IP \[bu] 2 hybrid\-hash: Performs \f[C]path\-hash\f[] on directories and \f[C]devino\-hash\f[] on other file types. Since directories can\[aq]t have hard links the static value won\[aq]t make a difference and the files will get values useful for finding duplicates. Probably the best to use if not using NFS. As such it is the default. .IP \[bu] 2 hybrid\-hash32: 32bit version of hybrid\-hash. .PP 32bit versions are provided as there is some software which does not handle 64bit inodes well. .PP While there is a risk of hash collision in tests of a couple million entries there were zero collisions. Unlike a typical filesystem FUSE filesystems can reuse inodes and not refer to the same entry. The internal identifier used to reference a file in FUSE is different from the inode value presented. The former is the \f[C]nodeid\f[] and is actually a tuple of 2 64bit values: \f[C]nodeid\f[] and \f[C]generation\f[]. This tuple is not client facing. The inode that is presented to the client is passed through the kernel uninterpreted. .PP From FUSE docs regarding \f[C]use_ino\f[]: .IP .nf \f[C] 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"). \f[] .fi .PP In the future the \f[C]use_ino\f[] option will probably be removed as this feature should replace the original libfuse inode calculation strategy. Currently you still need to use \f[C]use_ino\f[] in order to enable \f[C]inodecalc\f[]. .SS fuse_msg_size .PP FUSE applications communicate with the kernel over a special character device: \f[C]/dev/fuse\f[]. A large portion of the overhead associated with FUSE is the cost of going back and forth from user space and kernel space over that device. Generally speaking the fewer trips needed the better the performance will be. Reducing the number of trips can be done a number of ways. Kernel level caching and increasing message sizes being two significant ones. When it comes to reads and writes if the message size is doubled the number of trips are approximately halved. .PP In Linux 4.20 a new feature was added allowing the negotiation of the max message size. Since the size is in multiples of pages (https://en.wikipedia.org/wiki/Page_(computer_memory)) the feature is called \f[C]max_pages\f[]. There is a maximum \f[C]max_pages\f[] value of 256 (1MiB) and minimum of 1 (4KiB). The default used by Linux >=4.20, and hardcoded value used before 4.20, is 32 (128KiB). In mergerfs its referred to as \f[C]fuse_msg_size\f[] to make it clear what it impacts and provide some abstraction. .PP Since there should be no downsides to increasing \f[C]fuse_msg_size\f[] / \f[C]max_pages\f[], outside a minor bump in RAM usage due to larger message buffers, mergerfs defaults the value to 256. On kernels before 4.20 the value has no effect. The reason the value is configurable is to enable experimentation and benchmarking. See the BENCHMARKING section for examples. .SS follow\-symlinks .PP This feature, when enabled, will cause symlinks to be interpreted by mergerfs as their target (depending on the mode). .PP When there is a getattr/stat request for a file mergerfs will check if the file is a symlink and depending on the \f[C]follow\-symlinks\f[] setting will replace the information about the symlink with that of that which it points to. .PP When unlink\[aq]ing or rmdir\[aq]ing the followed symlink it will remove the symlink itself and not that which it points to. .IP \[bu] 2 never: Behave as normal. Symlinks are treated as such. .IP \[bu] 2 directory: Resolve symlinks only which point to directories. .IP \[bu] 2 regular: Resolve symlinks only which point to regular files. .IP \[bu] 2 all: Resolve all symlinks to that which they point to. .PP Symlinks which do not point to anything are left as is. .PP WARNING: This feature works but there might be edge cases yet found. If you find any odd behaviors please file a ticket on github (https://github.com/trapexit/mergerfs/issues). .SS link\-exdev .PP If using path preservation and a \f[C]link\f[] fails with EXDEV make a call to \f[C]symlink\f[] where the \f[C]target\f[] is the \f[C]oldlink\f[] and the \f[C]linkpath\f[] is the \f[C]newpath\f[]. The \f[C]target\f[] value is determined by the value of \f[C]link\-exdev\f[]. .IP \[bu] 2 passthrough: Return EXDEV as normal. .IP \[bu] 2 rel\-symlink: A relative path from the \f[C]newpath\f[]. .IP \[bu] 2 abs\-base\-symlink: A absolute value using the underlying branch. .IP \[bu] 2 abs\-pool\-symlink: A absolute value using the mergerfs mount point. .PP NOTE: It is possible that some applications check the file they link. In those cases it is possible it will error or complain. .SS rename\-exdev .PP If using path preservation and a \f[C]rename\f[] fails with EXDEV: .IP "1." 3 Move file from \f[B]/branch/a/b/c\f[] to \f[B]/branch/.mergerfs_rename_exdev/a/b/c\f[]. .IP "2." 3 symlink the rename\[aq]s \f[C]newpath\f[] to the moved file. .PP The \f[C]target\f[] value is determined by the value of \f[C]rename\-exdev\f[]. .IP \[bu] 2 passthrough: Return EXDEV as normal. .IP \[bu] 2 rel\-symlink: A relative path from the \f[C]newpath\f[]. .IP \[bu] 2 abs\-symlink: A absolute value using the mergerfs mount point. .PP NOTE: It is possible that some applications check the file they rename. In those cases it is possible it will error or complain. .PP NOTE: The reason \f[C]abs\-symlink\f[] is not split into two like \f[C]link\-exdev\f[] is due to the complexities in managing absolute base symlinks when multiple \f[C]oldpaths\f[] exist. .SS symlinkify .PP Due to the levels of indirection introduced by mergerfs and the underlying technology FUSE there can be varying levels of performance degradation. This feature will turn non\-directories which are not writable into symlinks to the original file found by the \f[C]readlink\f[] policy after the mtime and ctime are older than the timeout. .PP \f[B]WARNING:\f[] The current implementation has a known issue in which if the file is open and being used when the file is converted to a symlink then the application which has that file open will receive an error when using it. This is unlikely to occur in practice but is something to keep in mind. .PP \f[B]WARNING:\f[] Some backup solutions, such as CrashPlan, do not backup the target of a symlink. If using this feature it will be necessary to point any backup software to the original drives or configure the software to follow symlinks if such an option is available. Alternatively create two mounts. One for backup and one for general consumption. .SS nullrw .PP Due to how FUSE works there is an overhead to all requests made to a FUSE filesystem that wouldn\[aq]t exist for an in kernel one. Meaning that even a simple passthrough will have some slowdown. However, generally the overhead is minimal in comparison to the cost of the underlying I/O. By disabling the underlying I/O we can test the theoretical performance boundaries. .PP By enabling \f[C]nullrw\f[] mergerfs will work as it always does \f[B]except\f[] that all reads and writes will be no\-ops. A write will succeed (the size of the write will be returned as if it were successful) but mergerfs does nothing with the data it was given. Similarly a read will return the size requested but won\[aq]t touch the buffer. .PP See the BENCHMARKING section for suggestions on how to test. .SS xattr .PP Runtime extended attribute support can be managed via the \f[C]xattr\f[] option. By default it will passthrough any xattr calls. Given xattr support is rarely used and can have significant performance implications mergerfs allows it to be disabled at runtime. The performance problems mostly comes when file caching is enabled. The kernel will send a \f[C]getxattr\f[] for \f[C]security.capability\f[] \f[I]before every single write\f[]. It doesn\[aq]t cache the responses to any \f[C]getxattr\f[]. This might be addressed in the future but for now mergerfs can really only offer the following workarounds. .PP \f[C]noattr\f[] will cause mergerfs to short circuit all xattr calls and return ENOATTR where appropriate. mergerfs still gets all the requests but they will not be forwarded on to the underlying filesystems. The runtime control will still function in this mode. .PP \f[C]nosys\f[] will cause mergerfs to return ENOSYS for any xattr call. The difference with \f[C]noattr\f[] is that the kernel will cache this fact and itself short circuit future calls. This is more efficient than \f[C]noattr\f[] but will cause mergerfs\[aq] runtime control via the hidden file to stop working. .SS nfsopenhack .PP NFS is not fully POSIX compliant and historically certain behaviors, such as opening files with O_EXCL, are not or not well supported. When mergerfs (or any FUSE filesystem) is exported over NFS some of these issues come up due to how NFS and FUSE interact. .PP This hack addresses the issue where the creation of a file with a read\-only mode but with a read/write or write only flag. Normally this is perfectly valid but NFS chops the one open call into multiple calls. Exactly how it is translated depends on the configuration and versions of the NFS server and clients but it results in a permission error because a normal user is not allowed to open a read\-only file as writable. .PP Even though it\[aq]s a more niche situation this hack breaks normal security and behavior and as such is \f[C]off\f[] by default. If set to \f[C]git\f[] it will only perform the hack when the path in question includes \f[C]/.git/\f[]. \f[C]all\f[] will result it it applying anytime a readonly file which is empty is opened for writing. .SH FUNCTIONS, CATEGORIES and POLICIES .PP The POSIX filesystem API is made up of a number of functions. \f[B]creat\f[], \f[B]stat\f[], \f[B]chown\f[], etc. For ease of configuration in mergerfs most of the core functions are grouped into 3 categories: \f[B]action\f[], \f[B]create\f[], and \f[B]search\f[]. These functions and categories can be assigned a policy which dictates which branch is chosen when performing that function. .PP Some functions, listed in the category \f[C]N/A\f[] below, can not be assigned the normal policies. These functions work with file handles, rather than file paths, which were created by \f[C]open\f[] or \f[C]create\f[]. That said many times the current FUSE kernel driver will not always provide the file handle when a client calls \f[C]fgetattr\f[], \f[C]fchown\f[], \f[C]fchmod\f[], \f[C]futimens\f[], \f[C]ftruncate\f[], etc. This means it will call the regular, path based, versions. \f[C]readdir\f[] has no real need for a policy given the purpose is merely to return a list of entries in a directory. \f[C]statfs\f[]\[aq]s behavior can be modified via other options. .PP When using policies which are based on a branch\[aq]s available space the base path provided is used. Not the full path to the file in question. Meaning that mounts in the branch won\[aq]t be considered in the space calculations. The reason is that it doesn\[aq]t really work for non\-path preserving policies and can lead to non\-obvious behaviors. .PP NOTE: While any policy can be assigned to a function or category though some may not be very useful in practice. For instance: \f[B]rand\f[] (random) may be useful for file creation (create) but could lead to very odd behavior if used for \f[C]chmod\f[] if there were more than one copy of the file. .SS Functions and their Category classifications .PP .TS tab(@); lw(7.9n) lw(62.1n). T{ Category T}@T{ FUSE Functions T} _ T{ action T}@T{ chmod, chown, link, removexattr, rename, rmdir, setxattr, truncate, unlink, utimens T} T{ create T}@T{ create, mkdir, mknod, symlink T} T{ search T}@T{ access, getattr, getxattr, ioctl (directories), listxattr, open, readlink T} T{ N/A T}@T{ fchmod, fchown, futimens, ftruncate, fallocate, fgetattr, fsync, ioctl (files), read, readdir, release, statfs, write, copy_file_range T} .TE .PP In cases where something may be searched for (such as a path to clone) \f[B]getattr\f[] will usually be used. .SS Policies .PP A policy is the algorithm used to choose a branch or branches for a function to work on. Think of them as ways to filter and sort branches. .PP Any function in the \f[C]create\f[] category will clone the relative path if needed. Some other functions (\f[C]rename\f[],\f[C]link\f[],\f[C]ioctl\f[]) have special requirements or behaviors which you can read more about below. .SS Filtering .PP Policies basically search branches and create a list of files / paths for functions to work on. The policy is responsible for filtering and sorting the branches. Filters include \f[B]minfreespace\f[], whether or not a branch is mounted read\-only, and the branch tagging (RO,NC,RW). These filters are applied across all policies unless otherwise noted. .IP \[bu] 2 No \f[B]search\f[] function policies filter. .IP \[bu] 2 All \f[B]action\f[] function policies filter out branches which are mounted \f[B]read\-only\f[] or tagged as \f[B]RO (read\-only)\f[]. .IP \[bu] 2 All \f[B]create\f[] function policies filter out branches which are mounted \f[B]read\-only\f[], tagged \f[B]RO (read\-only)\f[] or \f[B]NC (no create)\f[], or has available space less than \f[C]minfreespace\f[]. .PP Policies may have their own additional filtering such as those that require existing paths to be present. .PP If all branches are filtered an error will be returned. Typically \f[B]EROFS\f[] (read\-only filesystem) or \f[B]ENOSPC\f[] (no space left on device) depending on the most recent reason for filtering a branch. \f[B]ENOENT\f[] will be returned if no elegible branch is found. .SS Path Preservation .PP Policies, as described below, are of two basic types. \f[C]path\ preserving\f[] and \f[C]non\-path\ preserving\f[]. .PP All policies which start with \f[C]ep\f[] (\f[B]epff\f[], \f[B]eplfs\f[], \f[B]eplus\f[], \f[B]epmfs\f[], \f[B]eprand\f[]) are \f[C]path\ preserving\f[]. \f[C]ep\f[] stands for \f[C]existing\ path\f[]. .PP A path preserving policy will only consider drives where the relative path being accessed already exists. .PP When using non\-path preserving policies paths will be cloned to target drives as necessary. .PP With the \f[C]msp\f[] or \f[C]most\ shared\ path\f[] policies they are defined as \f[C]path\ preserving\f[] for the purpose of controlling \f[C]link\f[] and \f[C]rename\f[]\[aq]s behaviors since \f[C]ignorepponrename\f[] is available to disable that behavior. In mergerfs v3.0 the path preserving behavior of rename and link will likely be separated from the policy all together. .SS Policy descriptions .PP A policy\[aq]s behavior differs, as mentioned above, based on the function it is used with. Sometimes it really might not make sense to even offer certain policies because they are literally the same as others but it makes things a bit more uniform. In mergerfs 3.0 this might change. .PP .TS tab(@); lw(16.6n) lw(53.4n). T{ Policy T}@T{ Description T} _ T{ all T}@T{ Search: Same as \f[B]epall\f[]. Action: Same as \f[B]epall\f[]. Create: for \f[B]mkdir\f[], \f[B]mknod\f[], and \f[B]symlink\f[] it will apply to all branches. \f[B]create\f[] works like \f[B]ff\f[]. T} T{ epall (existing path, all) T}@T{ Search: Same as \f[B]epff\f[] (but more expensive because it doesn\[aq]t stop after finding a valid branch). Action: apply to all found. Create: for \f[B]mkdir\f[], \f[B]mknod\f[], and \f[B]symlink\f[] it will apply to all found. \f[B]create\f[] works like \f[B]epff\f[] (but more expensive because it doesn\[aq]t stop after finding a valid branch). T} T{ epff (existing path, first found) T}@T{ Given the order of the branches, as defined at mount time or configured at runtime, act on the first one found where the relative path exists. T} T{ eplfs (existing path, least free space) T}@T{ Of all the branches on which the relative path exists choose the drive with the least free space. T} T{ eplus (existing path, least used space) T}@T{ Of all the branches on which the relative path exists choose the drive with the least used space. T} T{ epmfs (existing path, most free space) T}@T{ Of all the branches on which the relative path exists choose the drive with the most free space. T} T{ eppfrd (existing path, percentage free random distribution) T}@T{ Like \f[B]pfrd\f[] but limited to existing paths. T} T{ eprand (existing path, random) T}@T{ Calls \f[B]epall\f[] and then randomizes. Returns 1. T} T{ erofs T}@T{ Exclusively return \f[B]\-1\f[] with \f[B]errno\f[] set to \f[B]EROFS\f[] (read\-only filesystem). T} T{ ff (first found) T}@T{ Search: Same as \f[B]epff\f[]. Action: Same as \f[B]epff\f[]. Create: Given the order of the drives, as defined at mount time or configured at runtime, act on the first one found. T} T{ lfs (least free space) T}@T{ Search: Same as \f[B]eplfs\f[]. Action: Same as \f[B]eplfs\f[]. Create: Pick the drive with the least available free space. T} T{ lus (least used space) T}@T{ Search: Same as \f[B]eplus\f[]. Action: Same as \f[B]eplus\f[]. Create: Pick the drive with the least used space. T} T{ mfs (most free space) T}@T{ Search: Same as \f[B]epmfs\f[]. Action: Same as \f[B]epmfs\f[]. Create: Pick the drive with the most available free space. T} T{ msplfs (most shared path, least free space) T}@T{ Search: Same as \f[B]eplfs\f[]. Action: Same as \f[B]eplfs\f[]. Create: like \f[B]eplfs\f[] but walk back the path if it fails to find a branch at that level. T} T{ msplus (most shared path, least used space) T}@T{ Search: Same as \f[B]eplus\f[]. Action: Same as \f[B]eplus\f[]. Create: like \f[B]eplus\f[] but walk back the path if it fails to find a branch at that level. T} T{ mspmfs (most shared path, most free space) T}@T{ Search: Same as \f[B]epmfs\f[]. Action: Same as \f[B]epmfs\f[]. Create: like \f[B]epmfs\f[] but walk back the path if it fails to find a branch at that level. T} T{ msppfrd (most shared path, percentage free random distribution) T}@T{ Search: Same as \f[B]eppfrd\f[]. Action: Same as \f[B]eppfrd\f[]. Create: Like \f[B]eppfrd\f[] but will walk back the path if it fails to find a branch at that level. T} T{ newest T}@T{ Pick the file / directory with the largest mtime. T} T{ pfrd (percentage free random distribution) T}@T{ Search: Same as \f[B]eppfrd\f[]. Action: Same as \f[B]eppfrd\f[]. Create: Chooses a branch at random with the likelihood of selection based on a branch\[aq]s available space relative to the total. T} T{ rand (random) T}@T{ Calls \f[B]all\f[] and then randomizes. Returns 1. T} .TE .PP \f[B]NOTE:\f[] If you are using an underlying filesystem that reserves blocks such as ext2, ext3, or ext4 be aware that mergerfs respects the reservation by using \f[C]f_bavail\f[] (number of free blocks for unprivileged users) rather than \f[C]f_bfree\f[] (number of free blocks) in policy calculations. \f[B]df\f[] does NOT use \f[C]f_bavail\f[], it uses \f[C]f_bfree\f[], so direct comparisons between \f[B]df\f[] output and mergerfs\[aq] policies is not appropriate. .SS Defaults .PP .TS tab(@); l l. T{ Category T}@T{ Policy T} _ T{ action T}@T{ epall T} T{ create T}@T{ epmfs T} T{ search T}@T{ ff T} .TE .SS ioctl .PP When \f[C]ioctl\f[] is used with an open file then it will use the file handle which was created at the original \f[C]open\f[] call. However, when using \f[C]ioctl\f[] with a directory mergerfs will use the \f[C]open\f[] policy to find the directory to act on. .SS rename & link .PP \f[B]NOTE:\f[] If you\[aq]re receiving errors from software when files are moved / renamed / linked then you should consider changing the create policy to one which is \f[B]not\f[] path preserving, enabling \f[C]ignorepponrename\f[], or contacting the author of the offending software and requesting that \f[C]EXDEV\f[] (cross device / improper link) be properly handled. .PP \f[C]rename\f[] and \f[C]link\f[] are tricky functions in a union filesystem. \f[C]rename\f[] only works within a single filesystem or device. If a rename can\[aq]t be done atomically due to the source and destination paths existing on different mount points it will return \f[B]\-1\f[] with \f[B]errno = EXDEV\f[] (cross device / improper link). So if a \f[C]rename\f[]\[aq]s source and target are on different drives within the pool it creates an issue. .PP Originally mergerfs would return EXDEV whenever a rename was requested which was cross directory in any way. This made the code simple and was technically compliant with POSIX requirements. However, many applications fail to handle EXDEV at all and treat it as a normal error or otherwise handle it poorly. Such apps include: gvfsd\-fuse v1.20.3 and prior, Finder / CIFS/SMB client in Apple OSX 10.9+, NZBGet, Samba\[aq]s recycling bin feature. .PP As a result a compromise was made in order to get most software to work while still obeying mergerfs\[aq] policies. Below is the basic logic. .IP \[bu] 2 If using a \f[B]create\f[] policy which tries to preserve directory paths (epff,eplfs,eplus,epmfs) .IP \[bu] 2 Using the \f[B]rename\f[] policy get the list of files to rename .IP \[bu] 2 For each file attempt rename: .RS 2 .IP \[bu] 2 If failure with ENOENT (no such file or directory) run \f[B]create\f[] policy .IP \[bu] 2 If create policy returns the same drive as currently evaluating then clone the path .IP \[bu] 2 Re\-attempt rename .RE .IP \[bu] 2 If \f[B]any\f[] of the renames succeed the higher level rename is considered a success .IP \[bu] 2 If \f[B]no\f[] renames succeed the first error encountered will be returned .IP \[bu] 2 On success: .RS 2 .IP \[bu] 2 Remove the target from all drives with no source file .IP \[bu] 2 Remove the source from all drives which failed to rename .RE .IP \[bu] 2 If using a \f[B]create\f[] policy which does \f[B]not\f[] try to preserve directory paths .IP \[bu] 2 Using the \f[B]rename\f[] policy get the list of files to rename .IP \[bu] 2 Using the \f[B]getattr\f[] policy get the target path .IP \[bu] 2 For each file attempt rename: .RS 2 .IP \[bu] 2 If the source drive != target drive: .IP \[bu] 2 Clone target path from target drive to source drive .IP \[bu] 2 Rename .RE .IP \[bu] 2 If \f[B]any\f[] of the renames succeed the higher level rename is considered a success .IP \[bu] 2 If \f[B]no\f[] renames succeed the first error encountered will be returned .IP \[bu] 2 On success: .RS 2 .IP \[bu] 2 Remove the target from all drives with no source file .IP \[bu] 2 Remove the source from all drives which failed to rename .RE .PP The the removals are subject to normal entitlement checks. .PP The above behavior will help minimize the likelihood of EXDEV being returned but it will still be possible. .PP \f[B]link\f[] uses the same strategy but without the removals. .SS readdir .PP readdir (http://linux.die.net/man/3/readdir) is different from all other filesystem functions. While it could have its own set of policies to tweak its behavior at this time it provides a simple union of files and directories found. Remember that any action or information queried about these files and directories come from the respective function. For instance: an \f[B]ls\f[] is a \f[B]readdir\f[] and for each file/directory returned \f[B]getattr\f[] is called. Meaning the policy of \f[B]getattr\f[] is responsible for choosing the file/directory which is the source of the metadata you see in an \f[B]ls\f[]. .SS statfs / statvfs .PP statvfs (http://linux.die.net/man/2/statvfs) normalizes the source drives based on the fragment size and sums the number of adjusted blocks and inodes. This means you will see the combined space of all sources. Total, used, and free. The sources however are dedupped based on the drive so multiple sources on the same drive will not result in double counting its space. Filesystems mounted further down the tree of the branch will not be included when checking the mount\[aq]s stats. .PP The options \f[C]statfs\f[] and \f[C]statfs_ignore\f[] can be used to modify \f[C]statfs\f[] behavior. .SH ERROR HANDLING .PP POSIX filesystem functions offer a single return code meaning that there is some complication regarding the handling of multiple branches as mergerfs does. It tries to handle errors in a way that would generally return meaningful values for that particular function. .SS chmod, chown, removexattr, setxattr, truncate, utimens .IP "1)" 3 if no error: return 0 (success) .IP "2)" 3 if no successes: return first error .IP "3)" 3 if one of the files acted on was the same as the related search function: return its value .IP "4)" 3 return 0 (success) .PP While doing this increases the complexity and cost of error handling, particularly step 3, this provides probably the most reasonable return value. .SS unlink, rmdir .IP "1)" 3 if no errors: return 0 (success) .IP "2)" 3 return first error .PP Older version of mergerfs would return success if any success occurred but for unlink and rmdir there are downstream assumptions that, while not impossible to occur, can confuse some software. .SS others .PP For search functions there is always a single thing acted on and as such whatever return value that comes from the single function call is returned. .PP For create functions \f[C]mkdir\f[], \f[C]mknod\f[], and \f[C]symlink\f[] which don\[aq]t return a file descriptor and therefore can have \f[C]all\f[] or \f[C]epall\f[] policies it will return success if any of the calls succeed and an error otherwise. .SH BUILD / UPDATE .PP \f[B]NOTE:\f[] Prebuilt packages can be found at and recommended for most users: https://github.com/trapexit/mergerfs/releases \f[B]NOTE:\f[] Only tagged releases are supported. \f[C]master\f[] and other branches should be considered works in progress. .PP First get the code from github (https://github.com/trapexit/mergerfs). .IP .nf \f[C] $\ git\ clone\ https://github.com/trapexit/mergerfs.git $\ #\ or $\ wget\ https://github.com/trapexit/mergerfs/releases/download//mergerfs\-.tar.gz \f[] .fi .SS Debian / Ubuntu .IP .nf \f[C] $\ cd\ mergerfs $\ sudo\ tools/install\-build\-pkgs $\ make\ deb $\ sudo\ dpkg\ \-i\ ../mergerfs__.deb \f[] .fi .SS RHEL / CentOS /Fedora .IP .nf \f[C] $\ su\ \- #\ cd\ mergerfs #\ tools/install\-build\-pkgs #\ make\ rpm #\ rpm\ \-i\ rpmbuild/RPMS//mergerfs\-..rpm \f[] .fi .SS Generically .PP Have git, g++, make, python installed. .IP .nf \f[C] $\ cd\ mergerfs $\ make $\ sudo\ make\ install \f[] .fi .SS Build options .IP .nf \f[C] $\ make\ help usage:\ make make\ USE_XATTR=0\ \ \ \ \ \ \-\ build\ program\ without\ xattrs\ functionality make\ STATIC=1\ \ \ \ \ \ \ \ \ \-\ build\ static\ binary make\ LTO=1\ \ \ \ \ \ \ \ \ \ \ \ \-\ build\ with\ link\ time\ optimization \f[] .fi .SH UPGRADE .PP mergerfs can be upgraded live by mounting on top of the previous instance. Simply install the new version of mergerfs and follow the instructions below. .PP Add \f[C]nonempty\f[] to your mergerfs option list and call mergerfs again or if using \f[C]/etc/fstab\f[] call for it to mount again. Existing open files and such will continue to work fine though they won\[aq]t see runtime changes since any such change would be the new mount. If you plan on changing settings with the new mount you should / could apply those before mounting the new version. .IP .nf \f[C] $\ sudo\ mount\ /mnt/mergerfs $\ mount\ |\ grep\ mergerfs media\ on\ /mnt/mergerfs\ type\ fuse.mergerfs\ (rw,relatime,user_id=0,group_id=0,default_permissions,allow_other) media\ on\ /mnt/mergerfs\ type\ fuse.mergerfs\ (rw,relatime,user_id=0,group_id=0,default_permissions,allow_other) \f[] .fi .PP A problem with this approach is that the underlying instance will continue to run even if the software using it stop or are restarted. To work around this you can use a "lazy umount". Before mounting over top the mount point with the new instance of mergerfs issue: \f[C]umount\ \-l\ \f[]. .SH RUNTIME CONFIG .SS ioctl .PP The original runtime config API was via xattr calls. This however became an issue when needing to disable xattr. While slightly less convenient ioctl does not have the same problems and will be the main API going forward. .PP The keys are the same as the command line option arguments as well as the config file. .SS requests / commands .PP All commands take a 4096 byte char buffer. .IP \[bu] 2 read keys: get a nul \[aq]\[aq] delimited list of option keys .IP \[bu] 2 _IOWR(0xDF,0,char[4096]) = 0xD000DF00 .IP \[bu] 2 on success ioctl return value is the total length .IP \[bu] 2 read value: get an option value .IP \[bu] 2 _IOWR(0xDF,1,char[4096]) = 0xD000DF01 .IP \[bu] 2 the key is passed in via the char buffer as a nul \[aq]\[aq] terminated string .IP \[bu] 2 on success ioctl return value is the total length .IP \[bu] 2 write value: set an option value .IP \[bu] 2 _IOW(0xDF,2,char[4096]) = 0x5000DF02 .IP \[bu] 2 the key and value is passed in via the char buffer as a nul \[aq]\[aq] terminated string in the format of \f[C]key=value\f[] .IP \[bu] 2 on success ioctl return value is 0 .IP \[bu] 2 file info: get mergerfs metadata info for a file .IP \[bu] 2 _IOWR(0xDF,3,char[4096]) = 0xD000DF03 .IP \[bu] 2 the key is passed in via the char buffer as a nul \[aq]\[aq] terminated string .IP \[bu] 2 on success the ioctl return value is the total length .IP \[bu] 2 keys: .RS 2 .IP \[bu] 2 basepath: the base mount point for the file according to the getattr policy .IP \[bu] 2 relpath: the relative path of the file from the mount point .IP \[bu] 2 fullpath: the full path of the underlying file according to the getattr policy .IP \[bu] 2 allpaths: a NUL \[aq]\[aq] delimited list of full paths to all files found .RE .SS .mergerfs pseudo file (deprecated) .PP NOTE: this interface will be removed in mergerfs 3.0 .IP .nf \f[C] /.mergerfs \f[] .fi .PP There is a pseudo file available at the mount point which allows for the runtime modification of certain \f[B]mergerfs\f[] options. The file will not show up in \f[B]readdir\f[] but can be \f[B]stat\f[]\[aq]ed and manipulated via {list,get,set}xattrs (http://linux.die.net/man/2/listxattr) calls. .PP Any changes made at runtime are \f[B]not\f[] persisted. If you wish for values to persist they must be included as options wherever you configure the mounting of mergerfs (/etc/fstab). .SS Keys .PP Use \f[C]xattr\ \-l\ /mountpoint/.mergerfs\f[] to see all supported keys. Some are informational and therefore read\-only. \f[C]setxattr\f[] will return EINVAL (invalid argument) on read\-only keys. .SS Values .PP Same as the command line. .SS user.mergerfs.branches .PP \f[B]NOTE:\f[] formerly \f[C]user.mergerfs.srcmounts\f[] but said key is still supported. .PP Used to query or modify the list of branches. When modifying there are several shortcuts to easy manipulation of the list. .PP .TS tab(@); l l. T{ Value T}@T{ Description T} _ T{ [list] T}@T{ set T} T{ +<[list] T}@T{ prepend T} T{ +>[list] T}@T{ append T} T{ \-[list] T}@T{ remove all values provided T} T{ \-< T}@T{ remove first in list T} T{ \-> T}@T{ remove last in list T} .TE .PP \f[C]xattr\ \-w\ user.mergerfs.branches\ + userspace round trips there are kernel side caches for file entries and attributes. The entry cache limits the \f[C]lookup\f[] calls to mergerfs which ask if a file exists. The attribute cache limits the need to make \f[C]getattr\f[] calls to mergerfs which provide file attributes (mode, size, type, etc.). As with the page cache these should not be used if the underlying filesystems are being manipulated at the same time as it could lead to odd behavior or data corruption. The options for setting these are \f[C]cache.entry\f[] and \f[C]cache.negative_entry\f[] for the entry cache and \f[C]cache.attr\f[] for the attributes cache. \f[C]cache.negative_entry\f[] refers to the timeout for negative responses to lookups (non\-existent files). .SS writeback caching .PP When \f[C]cache.files\f[] is enabled the default is for it to perform writethrough caching. This behavior won\[aq]t help improve performance as each write still goes one for one through the filesystem. By enabling the FUSE writeback cache small writes may be aggregated by the kernel and then sent to mergerfs as one larger request. This can greatly improve the throughput for apps which write to files inefficiently. The amount the kernel can aggregate is limited by the size of a FUSE message. Read the \f[C]fuse_msg_size\f[] section for more details. .PP There is a small side effect as a result of enabling writeback caching. Underlying files won\[aq]t ever be opened with O_APPEND or O_WRONLY. The former because the kernel then manages append mode and the latter because the kernel may request file data from mergerfs to populate the write cache. The O_APPEND change means that if a file is changed outside of mergerfs it could lead to corruption as the kernel won\[aq]t know the end of the file has changed. That said any time you use caching you should keep from using the same file outside of mergerfs at the same time. .PP Note that if an application is properly sizing writes then writeback caching will have little or no effect. It will only help with writes of sizes below the FUSE message size (128K on older kernels, 1M on newer). .SS policy caching .PP Policies are run every time a function (with a policy as mentioned above) is called. These policies can be expensive depending on mergerfs\[aq] setup and client usage patterns. Generally we wouldn\[aq]t want to cache policy results because it may result in stale responses if the underlying drives are used directly. .PP The \f[C]open\f[] policy cache will cache the result of an \f[C]open\f[] policy for a particular input for \f[C]cache.open\f[] seconds or until the file is unlinked. Each file close (release) will randomly chose to clean up the cache of expired entries. .PP This cache is really only useful in cases where you have a large number of branches and \f[C]open\f[] is called on the same files repeatedly (like \f[B]Transmission\f[] which opens and closes a file on every read/write presumably to keep file handle usage low). .SS statfs caching .PP Of the syscalls used by mergerfs in policies the \f[C]statfs\f[] / \f[C]statvfs\f[] call is perhaps the most expensive. It\[aq]s used to find out the available space of a drive and whether it is mounted read\-only. Depending on the setup and usage pattern these queries can be relatively costly. When \f[C]cache.statfs\f[] is enabled all calls to \f[C]statfs\f[] by a policy will be cached for the number of seconds its set to. .PP Example: If the create policy is \f[C]mfs\f[] and the timeout is 60 then for that 60 seconds the same drive will be returned as the target for creates because the available space won\[aq]t be updated for that time. .SS symlink caching .PP As of version 4.20 Linux supports symlink caching. Significant performance increases can be had in workloads which use a lot of symlinks. Setting \f[C]cache.symlinks=true\f[] will result in requesting symlink caching from the kernel only if supported. As a result its safe to enable it on systems prior to 4.20. That said it is disabled by default for now. You can see if caching is enabled by querying the xattr \f[C]user.mergerfs.cache.symlinks\f[] but given it must be requested at startup you can not change it at runtime. .SS readdir caching .PP As of version 4.20 Linux supports readdir caching. This can have a significant impact on directory traversal. Especially when combined with entry (\f[C]cache.entry\f[]) and attribute (\f[C]cache.attr\f[]) caching. Setting \f[C]cache.readdir=true\f[] will result in requesting readdir caching from the kernel on each \f[C]opendir\f[]. If the kernel doesn\[aq]t support readdir caching setting the option to \f[C]true\f[] has no effect. This option is configurable at runtime via xattr \f[C]user.mergerfs.cache.readdir\f[]. .SS tiered caching .PP Some storage technologies support what some call "tiered" caching. The placing of usually smaller, faster storage as a transparent cache to larger, slower storage. NVMe, SSD, Optane in front of traditional HDDs for instance. .PP MergerFS does not natively support any sort of tiered caching. Most users have no use for such a feature and its inclusion would complicate the code. However, there are a few situations where a cache drive could help with a typical mergerfs setup. .IP "1." 3 Fast network, slow drives, many readers: You\[aq]ve a 10+Gbps network with many readers and your regular drives can\[aq]t keep up. .IP "2." 3 Fast network, slow drives, small\[aq]ish bursty writes: You have a 10+Gbps network and wish to transfer amounts of data less than your cache drive but wish to do so quickly. .PP With #1 its arguable if you should be using mergerfs at all. RAID would probably be the better solution. If you\[aq]re going to use mergerfs there are other tactics that may help: spreading the data across drives (see the mergerfs.dup tool) and setting \f[C]func.open=rand\f[], using \f[C]symlinkify\f[], or using dm\-cache or a similar technology to add tiered cache to the underlying device. .PP With #2 one could use dm\-cache as well but there is another solution which requires only mergerfs and a cronjob. .IP "1." 3 Create 2 mergerfs pools. One which includes just the slow drives and one which has both the fast drives (SSD,NVME,etc.) and slow drives. .IP "2." 3 The \[aq]cache\[aq] pool should have the cache drives listed first. .IP "3." 3 The best \f[C]create\f[] policies to use for the \[aq]cache\[aq] pool would probably be \f[C]ff\f[], \f[C]epff\f[], \f[C]lfs\f[], or \f[C]eplfs\f[]. The latter two under the assumption that the cache drive(s) are far smaller than the backing drives. If using path preserving policies remember that you\[aq]ll need to manually create the core directories of those paths you wish to be cached. Be sure the permissions are in sync. Use \f[C]mergerfs.fsck\f[] to check / correct them. You could also tag the slow drives as \f[C]=NC\f[] though that\[aq]d mean if the cache drives fill you\[aq]d get "out of space" errors. .IP "4." 3 Enable \f[C]moveonenospc\f[] and set \f[C]minfreespace\f[] appropriately. To make sure there is enough room on the "slow" pool you might want to set \f[C]minfreespace\f[] to at least as large as the size of the largest cache drive if not larger. This way in the worst case the whole of the cache drive(s) can be moved to the other drives. .IP "5." 3 Set your programs to use the cache pool. .IP "6." 3 Save one of the below scripts or create you\[aq]re own. .IP "7." 3 Use \f[C]cron\f[] (as root) to schedule the command at whatever frequency is appropriate for your workflow. .SS time based expiring .PP Move files from cache to backing pool based only on the last time the file was accessed. Replace \f[C]\-atime\f[] with \f[C]\-amin\f[] if you want minutes rather than days. May want to use the \f[C]fadvise\f[] / \f[C]\-\-drop\-cache\f[] version of rsync or run rsync with the tool "nocache". .PP \f[I]NOTE:\f[] The arguments to these scripts include the cache \f[B]drive\f[]. Not the pool with the cache drive. You could have data loss if the source is the cache pool. .IP .nf \f[C] #!/bin/bash if\ [\ $#\ !=\ 3\ ];\ then \ \ echo\ "usage:\ $0\ \ \ " \ \ exit\ 1 fi CACHE="${1}" BACKING="${2}" N=${3} find\ "${CACHE}"\ \-type\ f\ \-atime\ +${N}\ \-printf\ \[aq]%P\\n\[aq]\ |\ \\ \ \ rsync\ \-\-files\-from=\-\ \-axqHAXWES\ \-\-preallocate\ \-\-remove\-source\-files\ "${CACHE}/"\ "${BACKING}/" \f[] .fi .SS percentage full expiring .PP Move the oldest file from the cache to the backing pool. Continue till below percentage threshold. .PP \f[I]NOTE:\f[] The arguments to these scripts include the cache \f[B]drive\f[]. Not the pool with the cache drive. You could have data loss if the source is the cache pool. .IP .nf \f[C] #!/bin/bash if\ [\ $#\ !=\ 3\ ];\ then \ \ echo\ "usage:\ $0\ \ \ " \ \ exit\ 1 fi CACHE="${1}" BACKING="${2}" PERCENTAGE=${3} set\ \-o\ errexit while\ [\ $(df\ \-\-output=pcent\ "${CACHE}"\ |\ grep\ \-v\ Use\ |\ cut\ \-d\[aq]%\[aq]\ \-f1)\ \-gt\ ${PERCENTAGE}\ ] do \ \ \ \ FILE=$(find\ "${CACHE}"\ \-type\ f\ \-printf\ \[aq]%A\@\ %P\\n\[aq]\ |\ \\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ sort\ |\ \\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ head\ \-n\ 1\ |\ \\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ cut\ \-d\[aq]\ \[aq]\ \-f2\-) \ \ \ \ test\ \-n\ "${FILE}" \ \ \ \ rsync\ \-axqHAXWESR\ \-\-preallocate\ \-\-remove\-source\-files\ "${CACHE}/./${FILE}"\ "${BACKING}/" done \f[] .fi .SH PERFORMANCE .PP mergerfs is at its core just a proxy and therefore its theoretical max performance is that of the underlying devices. However, given it is a FUSE filesystem working from userspace there is an increase in overhead relative to kernel based solutions. That said the performance can match the theoretical max but it depends greatly on the system\[aq]s configuration. Especially when adding network filesystems into the mix there are many variables which can impact performance. Drive speeds and latency, network speeds and latency, general concurrency, read/write sizes, etc. Unfortunately, given the number of variables it has been difficult to find a single set of settings which provide optimal performance. If you\[aq]re having performance issues please look over the suggestions below (including the benchmarking section.) .PP NOTE: be sure to read about these features before changing them to understand what behaviors it may impact .IP \[bu] 2 enable (or disable) \f[C]splice_move\f[], \f[C]splice_read\f[], and \f[C]splice_write\f[] .IP \[bu] 2 disable \f[C]security_capability\f[] and/or \f[C]xattr\f[] .IP \[bu] 2 increase cache timeouts \f[C]cache.attr\f[], \f[C]cache.entry\f[], \f[C]cache.negative_entry\f[] .IP \[bu] 2 enable (or disable) page caching (\f[C]cache.files\f[]) .IP \[bu] 2 enable \f[C]cache.writeback\f[] .IP \[bu] 2 enable \f[C]cache.open\f[] .IP \[bu] 2 enable \f[C]cache.statfs\f[] .IP \[bu] 2 enable \f[C]cache.symlinks\f[] .IP \[bu] 2 enable \f[C]cache.readdir\f[] .IP \[bu] 2 change the number of worker threads .IP \[bu] 2 disable \f[C]posix_acl\f[] .IP \[bu] 2 disable \f[C]async_read\f[] .IP \[bu] 2 test theoretical performance using \f[C]nullrw\f[] or mounting a ram disk .IP \[bu] 2 use \f[C]symlinkify\f[] if your data is largely static and read\-only .IP \[bu] 2 use tiered cache drives .IP \[bu] 2 use LVM and LVM cache to place a SSD in front of your HDDs .PP If you come across a setting that significantly impacts performance please contact trapexit so he may investigate further. .SH BENCHMARKING .PP Filesystems are complicated. They do many things and many of those are interconnected. Additionally, the OS, drivers, hardware, etc. all can impact performance. Therefore, when benchmarking, it is \f[B]necessary\f[] that the test focus as narrowly as possible. .PP For most throughput is the key benchmark. To test throughput \f[C]dd\f[] is useful but \f[B]must\f[] be used with the correct settings in order to ensure the filesystem or device is actually being tested. The OS can and will cache data. Without forcing synchronous reads and writes and/or disabling caching the values returned will not be representative of the device\[aq]s true performance. .PP When benchmarking through mergerfs ensure you only use 1 branch to remove any possibility of the policies complicating the situation. Benchmark the underlying filesystem first and then mount mergerfs over it and test again. If you\[aq]re experience speeds below your expectation you will need to narrow down precisely which component is leading to the slowdown. Preferably test the following in the order listed (but not combined). .IP "1." 3 Enable \f[C]nullrw\f[] mode with \f[C]nullrw=true\f[]. This will effectively make reads and writes no\-ops. Removing the underlying device / filesystem from the equation. This will give us the top theoretical speeds. .IP "2." 3 Mount mergerfs over \f[C]tmpfs\f[]. \f[C]tmpfs\f[] is a RAM disk. Extremely high speed and very low latency. This is a more realistic best case scenario. Example: \f[C]mount\ \-t\ tmpfs\ \-o\ size=2G\ tmpfs\ /tmp/tmpfs\f[] .IP "3." 3 Mount mergerfs over a local drive. NVMe, SSD, HDD, etc. If you have more than one I\[aq]d suggest testing each of them as drives and/or controllers (their drivers) could impact performance. .IP "4." 3 Finally, if you intend to use mergerfs with a network filesystem, either as the source of data or to combine with another through mergerfs, test each of those alone as above. .PP Once you find the component which has the performance issue you can do further testing with different options to see if they impact performance. For reads and writes the most relevant would be: \f[C]cache.files\f[], \f[C]async_read\f[], \f[C]splice_move\f[], \f[C]splice_read\f[], \f[C]splice_write\f[]. Less likely but relevant when using NFS or with certain filesystems would be \f[C]security_capability\f[], \f[C]xattr\f[], and \f[C]posix_acl\f[]. If you find a specific system, drive, filesystem, controller, etc. that performs poorly contact trapexit so he may investigate further. .PP Sometimes the problem is really the application accessing or writing data through mergerfs. Some software use small buffer sizes which can lead to more requests and therefore greater overhead. You can test this out yourself by replace \f[C]bs=1M\f[] in the examples below with \f[C]ibs\f[] or \f[C]obs\f[] and using a size of \f[C]512\f[] instead of \f[C]1M\f[]. In one example test using \f[C]nullrw\f[] the write speed dropped from 4.9GB/s to 69.7MB/s when moving from \f[C]1M\f[] to \f[C]512\f[]. Similar results were had when testing reads. Small writes overhead may be improved by leveraging a write cache but in casual tests little gain was found. More tests will need to be done before this feature would become available. If you have an app that appears slow with mergerfs it could be due to this. Contact trapexit so he may investigate further. .SS write benchmark .IP .nf \f[C] $\ dd\ if=/dev/zero\ of=/mnt/mergerfs/1GB.file\ bs=1M\ count=1024\ oflag=nocache\ conv=fdatasync\ status=progress \f[] .fi .SS read benchmark .IP .nf \f[C] $\ dd\ if=/mnt/mergerfs/1GB.file\ of=/dev/null\ bs=1M\ count=1024\ iflag=nocache\ conv=fdatasync\ status=progress \f[] .fi .SS other benchmarks .PP If you are attempting to benchmark other behaviors you must ensure you clear kernel caches before runs. In fact it would be a good deal to run before the read and write benchmarks as well just in case. .IP .nf \f[C] sync echo\ 3\ |\ sudo\ tee\ /proc/sys/vm/drop_caches \f[] .fi .SH TIPS / NOTES .IP \[bu] 2 This document is very literal and thorough. Unless there is a bug things work as described. If a suspected feature isn\[aq]t mentioned it doesn\[aq]t exist. If certain libfuse arguments aren\[aq]t listed they probably shouldn\[aq]t be used. .IP \[bu] 2 Ensure you\[aq]re using the latest version. Few distros have the latest version. .IP \[bu] 2 \f[B]use_ino\f[] will only work when used with mergerfs 2.18.0 and above. .IP \[bu] 2 Run mergerfs as \f[C]root\f[] (with \f[B]allow_other\f[]) unless you\[aq]re merging paths which are owned exclusively and fully by the same user otherwise strange permission issues may arise. mergerfs is designed and intended to be run as \f[C]root\f[]. .IP \[bu] 2 If you don\[aq]t see some directories and files you expect, policies seem to skip branches, you get strange permission errors, etc. be sure the underlying filesystems\[aq] permissions are all the same. Use \f[C]mergerfs.fsck\f[] to audit the drive for out of sync permissions. .IP \[bu] 2 If you still have permission issues be sure you are using POSIX ACL compliant filesystems. mergerfs doesn\[aq]t generally make exceptions for FAT, NTFS, or other non\-POSIX filesystem. .IP \[bu] 2 Do \f[B]not\f[] use \f[C]cache.files=off\f[] if you expect applications (such as rtorrent) to use mmap (http://linux.die.net/man/2/mmap) files. Shared mmap is not currently supported in FUSE w/ page caching disabled. Enabling \f[C]dropcacheonclose\f[] is recommended when \f[C]cache.files=partial|full|auto\-full\f[]. .IP \[bu] 2 Kodi (http://kodi.tv), Plex (http://plex.tv), Subsonic (http://subsonic.org), etc. can use directory mtime (http://linux.die.net/man/2/stat) to more efficiently determine whether to scan for new content rather than simply performing a full scan. If using the default \f[B]getattr\f[] policy of \f[B]ff\f[] it\[aq]s possible those programs will miss an update on account of it returning the first directory found\[aq]s \f[B]stat\f[] info and its a later directory on another mount which had the \f[B]mtime\f[] recently updated. To fix this you will want to set \f[B]func.getattr=newest\f[]. Remember though that this is just \f[B]stat\f[]. If the file is later \f[B]open\f[]\[aq]ed or \f[B]unlink\f[]\[aq]ed and the policy is different for those then a completely different file or directory could be acted on. .IP \[bu] 2 Some policies mixed with some functions may result in strange behaviors. Not that some of these behaviors and race conditions couldn\[aq]t happen outside \f[B]mergerfs\f[] but that they are far more likely to occur on account of the attempt to merge together multiple sources of data which could be out of sync due to the different policies. .IP \[bu] 2 For consistency its generally best to set \f[B]category\f[] wide policies rather than individual \f[B]func\f[]\[aq]s. This will help limit the confusion of tools such as rsync (http://linux.die.net/man/1/rsync). However, the flexibility is there if needed. .SH KNOWN ISSUES / BUGS .SS kernel issues & bugs .PP .SS directory mtime is not being updated .PP Remember that the default policy for \f[C]getattr\f[] is \f[C]ff\f[]. The information for the first directory found will be returned. If it wasn\[aq]t the directory which had been updated then it will appear outdated. .PP The reason this is the default is because any other policy would be more expensive and for many applications it is unnecessary. To always return the directory with the most recent mtime or a faked value based on all found would require a scan of all drives. .PP If you always want the directory information from the one with the most recent mtime then use the \f[C]newest\f[] policy for \f[C]getattr\f[]. .SS \[aq]mv /mnt/pool/foo /mnt/disk1/foo\[aq] removes \[aq]foo\[aq] .PP This is not a bug. .PP Run in verbose mode to better understand what\[aq]s happening: .IP .nf \f[C] $\ mv\ \-v\ /mnt/pool/foo\ /mnt/disk1/foo copied\ \[aq]/mnt/pool/foo\[aq]\ \->\ \[aq]/mnt/disk1/foo\[aq] removed\ \[aq]/mnt/pool/foo\[aq] $\ ls\ /mnt/pool/foo ls:\ cannot\ access\ \[aq]/mnt/pool/foo\[aq]:\ No\ such\ file\ or\ directory \f[] .fi .PP \f[C]mv\f[], when working across devices, is copying the source to target and then removing the source. Since the source \f[B]is\f[] the target in this case, depending on the unlink policy, it will remove the just copied file and other files across the branches. .PP If you want to move files to one drive just copy them there and use mergerfs.dedup to clean up the old paths or manually remove them from the branches directly. .SS cached memory appears greater than it should be .PP Use \f[C]cache.files=off\f[] and/or \f[C]dropcacheonclose=true\f[]. See the section on page caching. .SS NFS clients returning ESTALE / Stale file handle .PP NFS does not like out of band changes. That is especially true of inode values. .PP Be sure to use the following options: .IP \[bu] 2 noforget .IP \[bu] 2 use_ino .IP \[bu] 2 inodecalc=path\-hash .SS rtorrent fails with ENODEV (No such device) .PP Be sure to set \f[C]cache.files=partial|full|auto\-full\f[] or turn off \f[C]direct_io\f[]. rtorrent and some other applications use mmap (http://linux.die.net/man/2/mmap) to read and write to files and offer no fallback to traditional methods. FUSE does not currently support mmap while using \f[C]direct_io\f[]. There may be a performance penalty on writes with \f[C]direct_io\f[] off as well as the problem of double caching but it\[aq]s the only way to get such applications to work. If the performance loss is too high for other apps you can mount mergerfs twice. Once with \f[C]direct_io\f[] enabled and one without it. Be sure to set \f[C]dropcacheonclose=true\f[] if not using \f[C]direct_io\f[]. .SS Plex doesn\[aq]t work with mergerfs .PP It does. If you\[aq]re trying to put Plex\[aq]s config / metadata / database on mergerfs you can\[aq]t set \f[C]cache.files=off\f[] because Plex is using sqlite3 with mmap enabled. Shared mmap is not supported by Linux\[aq]s FUSE implementation when page caching is disabled. To fix this place the data elsewhere (preferable) or enable \f[C]cache.files\f[] (with \f[C]dropcacheonclose=true\f[]). Sqlite3 does not need mmap but the developer needs to fall back to standard IO if mmap fails. .PP If the issue is that scanning doesn\[aq]t seem to pick up media then be sure to set \f[C]func.getattr=newest\f[] though generally a full scan will pick up all media anyway. .SS When a program tries to move or rename a file it fails .PP Please read the section above regarding rename & link (#rename--link). .PP The problem is that many applications do not properly handle \f[C]EXDEV\f[] errors which \f[C]rename\f[] and \f[C]link\f[] may return even though they are perfectly valid situations which do not indicate actual drive or OS errors. The error will only be returned by mergerfs if using a path preserving policy as described in the policy section above. If you do not care about path preservation simply change the mergerfs policy to the non\-path preserving version. For example: \f[C]\-o\ category.create=mfs\f[] .PP Ideally the offending software would be fixed and it is recommended that if you run into this problem you contact the software\[aq]s author and request proper handling of \f[C]EXDEV\f[] errors. .SS my 32bit software has problems .PP Some software have problems with 64bit inode values. The symptoms can include EOVERFLOW errors when trying to list files. You can address this by setting \f[C]inodecalc\f[] to one of the 32bit based algos as described in the relevant section. .SS Samba: Moving files / directories fails .PP Workaround: Copy the file/directory and then remove the original rather than move. .PP This isn\[aq]t an issue with Samba but some SMB clients. GVFS\-fuse v1.20.3 and prior (found in Ubuntu 14.04 among others) failed to handle certain error codes correctly. Particularly \f[B]STATUS_NOT_SAME_DEVICE\f[] which comes from the \f[B]EXDEV\f[] which is returned by \f[B]rename\f[] when the call is crossing mount points. When a program gets an \f[B]EXDEV\f[] it needs to explicitly take an alternate action to accomplish its goal. In the case of \f[B]mv\f[] or similar it tries \f[B]rename\f[] and on \f[B]EXDEV\f[] falls back to a manual copying of data between the two locations and unlinking the source. In these older versions of GVFS\-fuse if it received \f[B]EXDEV\f[] it would translate that into \f[B]EIO\f[]. This would cause \f[B]mv\f[] or most any application attempting to move files around on that SMB share to fail with a IO error. .PP GVFS\-fuse v1.22.0 (https://bugzilla.gnome.org/show_bug.cgi?id=734568) and above fixed this issue but a large number of systems use the older release. On Ubuntu the version can be checked by issuing \f[C]apt\-cache\ showpkg\ gvfs\-fuse\f[]. Most distros released in 2015 seem to have the updated release and will work fine but older systems may not. Upgrading gvfs\-fuse or the distro in general will address the problem. .PP In Apple\[aq]s MacOSX 10.9 they replaced Samba (client and server) with their own product. It appears their new client does not handle \f[B]EXDEV\f[] either and responds similar to older release of gvfs on Linux. .SS Trashing files occasionally fails .PP This is the same issue as with Samba. \f[C]rename\f[] returns \f[C]EXDEV\f[] (in our case that will really only happen with path preserving policies like \f[C]epmfs\f[]) and the software doesn\[aq]t handle the situation well. This is unfortunately a common failure of software which moves files around. The standard indicates that an implementation \f[C]MAY\f[] choose to support non\-user home directory trashing of files (which is a \f[C]MUST\f[]). The implementation \f[C]MAY\f[] also support "top directory trashes" which many probably do. .PP To create a \f[C]$topdir/.Trash\f[] directory as defined in the standard use the mergerfs\-tools (https://github.com/trapexit/mergerfs-tools) tool \f[C]mergerfs.mktrash\f[]. .SS tar: Directory renamed before its status could be extracted .PP Make sure to use the \f[C]use_ino\f[] option. .SS Supplemental user groups .PP Due to the overhead of getgroups/setgroups (http://linux.die.net/man/2/setgroups) mergerfs utilizes a cache. This cache is opportunistic and per thread. Each thread will query the supplemental groups for a user when that particular thread needs to change credentials and will keep that data for the lifetime of the thread. This means that if a user is added to a group it may not be picked up without the restart of mergerfs. However, since the high level FUSE API\[aq]s (at least the standard version) thread pool dynamically grows and shrinks it\[aq]s possible that over time a thread will be killed and later a new thread with no cache will start and query the new data. .PP The gid cache uses fixed storage to simplify the design and be compatible with older systems which may not have C++11 compilers. There is enough storage for 256 users\[aq] supplemental groups. Each user is allowed up to 32 supplemental groups. Linux >= 2.6.3 allows up to 65535 groups per user but most other *nixs allow far less. NFS allowing only 16. The system does handle overflow gracefully. If the user has more than 32 supplemental groups only the first 32 will be used. If more than 256 users are using the system when an uncached user is found it will evict an existing user\[aq]s cache at random. So long as there aren\[aq]t more than 256 active users this should be fine. If either value is too low for your needs you will have to modify \f[C]gidcache.hpp\f[] to increase the values. Note that doing so will increase the memory needed by each thread. .PP While not a bug some users have found when using containers that supplemental groups defined inside the container don\[aq]t work properly with regard to permissions. This is expected as mergerfs lives outside the container and therefore is querying the host\[aq]s group database. There might be a hack to work around this (make mergerfs read the /etc/group file in the container) but it is not yet implemented and would be limited to Linux and the /etc/group DB. Preferably users would mount in the host group file into the containers or use a standard shared user & groups technology like NIS or LDAP. .SS mergerfs or libfuse crashing .PP First... always upgrade to the latest version unless told otherwise. .PP If using mergerfs below 2.22.0: .PP If suddenly the mergerfs mount point disappears and \f[C]Transport\ endpoint\ is\ not\ connected\f[] is returned when attempting to perform actions within the mount directory \f[B]and\f[] the version of libfuse (use \f[C]mergerfs\ \-v\f[] to find the version) is older than \f[C]2.9.4\f[] its likely due to a bug in libfuse. Affected versions of libfuse can be found in Debian Wheezy, Ubuntu Precise and others. .PP In order to fix this please install newer versions of libfuse. If using a Debian based distro (Debian,Ubuntu,Mint) you can likely just install newer versions of libfuse (https://packages.debian.org/unstable/libfuse2) and fuse (https://packages.debian.org/unstable/fuse) from the repo of a newer release. .PP If using mergerfs at or above 2.22.0: .PP First upgrade if possible, check the known bugs section, and contact trapexit. .SS mergerfs appears to be crashing or exiting .PP There seems to be an issue with Linux version \f[C]4.9.0\f[] and above in which an invalid message appears to be transmitted to libfuse (used by mergerfs) causing it to exit. No messages will be printed in any logs as it\[aq]s not a proper crash. Debugging of the issue is still ongoing and can be followed via the fuse\-devel thread (https://sourceforge.net/p/fuse/mailman/message/35662577). .SS rm: fts_read failed: No such file or directory .PP Please update. This is only happened to mergerfs versions at or below v2.25.x and will not occur in more recent versions. .SH FAQ .SS How well does mergerfs scale? Is it "production ready?" .PP Users have reported running mergerfs on everything from a Raspberry Pi to dual socket Xeon systems with >20 cores. I\[aq]m aware of at least a few companies which use mergerfs in production. Open Media Vault (https://www.openmediavault.org) includes mergerfs as its sole solution for pooling drives. The author of mergerfs had it running for over 300 days managing 16+ drives with reasonably heavy 24/7 read and write usage. Stopping only after the machine\[aq]s power supply died. .PP Most serious issues (crashes or data corruption) have been due to kernel bugs (https://github.com/trapexit/mergerfs/wiki/Kernel-Issues-&-Bugs). All of which are fixed in stable releases. .SS Can mergerfs be used with drives which already have data / are in use? .PP Yes. MergerFS is a proxy and does \f[B]NOT\f[] interfere with the normal form or function of the drives / mounts / paths it manages. .PP MergerFS is \f[B]not\f[] a traditional filesystem. MergerFS is \f[B]not\f[] RAID. It does \f[B]not\f[] manipulate the data that passes through it. It does \f[B]not\f[] shard data across drives. It merely shards some \f[B]behavior\f[] and aggregates others. .SS Can mergerfs be removed without affecting the data? .PP See the previous question\[aq]s answer. .SS What policies should I use? .PP Unless you\[aq]re doing something more niche the average user is probably best off using \f[C]mfs\f[] for \f[C]category.create\f[]. It will spread files out across your branches based on available space. Use \f[C]mspmfs\f[] if you want to try to colocate the data a bit more. You may want to use \f[C]lus\f[] if you prefer a slightly different distribution of data if you have a mix of smaller and larger drives. Generally though \f[C]mfs\f[], \f[C]lus\f[], or even \f[C]rand\f[] are good for the general use case. If you are starting with an imbalanced pool you can use the tool \f[B]mergerfs.balance\f[] to redistribute files across the pool. .PP If you really wish to try to colocate files based on directory you can set \f[C]func.create\f[] to \f[C]epmfs\f[] or similar and \f[C]func.mkdir\f[] to \f[C]rand\f[] or \f[C]eprand\f[] depending on if you just want to colocate generally or on specific branches. Either way the \f[I]need\f[] to colocate is rare. For instance: if you wish to remove the drive regularly and want the data to predictably be on that drive or if you don\[aq]t use backup at all and don\[aq]t wish to replace that data piecemeal. In which case using path preservation can help but will require some manual attention. Colocating after the fact can be accomplished using the \f[B]mergerfs.consolidate\f[] tool. If you don\[aq]t need strict colocation which the \f[C]ep\f[] policies provide then you can use the \f[C]msp\f[] based policies which will walk back the path till finding a branch that works. .PP Ultimately there is no correct answer. It is a preference or based on some particular need. mergerfs is very easy to test and experiment with. I suggest creating a test setup and experimenting to get a sense of what you want. .PP The reason \f[C]mfs\f[] is not the default \f[C]category.create\f[] policy is historical. When/if a 3.X gets released it will be changed to minimize confusion people often have with path preserving policies. .SS What settings should I use? .PP Depends on what features you want. Generally speaking there are no "wrong" settings. All settings are performance or feature related. The best bet is to read over the available options and choose what fits your situation. If something isn\[aq]t clear from the documentation please reach out and the documentation will be improved. .PP That said, for the average person, the following should be fine: .PP \f[C]\-o\ use_ino,cache.files=off,dropcacheonclose=true,allow_other,category.create=mfs\f[] .SS Why are all my files ending up on 1 drive?! .PP Did you start with empty drives? Did you explicitly configure a \f[C]category.create\f[] policy? Are you using an \f[C]existing\ path\f[] / \f[C]path\ preserving\f[] policy? .PP The default create policy is \f[C]epmfs\f[]. That is a path preserving algorithm. With such a policy for \f[C]mkdir\f[] and \f[C]create\f[] with a set of empty drives it will select only 1 drive when the first directory is created. Anything, files or directories, created in that first directory will be placed on the same branch because it is preserving paths. .PP This catches a lot of new users off guard but changing the default would break the setup for many existing users. If you do not care about path preservation and wish your files to be spread across all your drives change to \f[C]mfs\f[] or similar policy as described above. If you do want path preservation you\[aq]ll need to perform the manual act of creating paths on the drives you want the data to land on before transferring your data. Setting \f[C]func.mkdir=epall\f[] can simplify managing path preservation for \f[C]create\f[]. Or use \f[C]func.mkdir=rand\f[] if you\[aq]re interested in just grouping together directory content by drive. .SS Do hardlinks work? .PP Yes. You need to use \f[C]use_ino\f[] to support proper reporting of inodes but they work regardless. See also the option \f[C]inodecalc\f[]. .PP What mergerfs does not do is fake hard links across branches. Read the section "rename & link" for how it works. .PP Remember that hardlinks will NOT work across devices. That includes between the original filesystem and a mergerfs pool, between two separate pools of the same underlying filesystems, or bind mounts of paths within the mergerfs pool. The latter is common when using Docker or Podman. Multiple volumes (bind mounts) to the same underlying filesystem are considered different devices. There is no way to link between them. You should mount in the highest directory in the mergerfs pool that includes all the paths you need if you want links to work. .SS Can I use mergerfs without SnapRAID? SnapRAID without mergerfs? .PP Yes. They are completely unreleated pieces of software. .SS Can mergerfs run via Docker, Podman, Kubernetes, etc. .PP Yes. With Docker you\[aq]ll need to include \f[C]\-\-cap\-add=SYS_ADMIN\ \-\-device=/dev/fuse\ \-\-security\-opt=apparmor:unconfined\f[] or similar with other container runtimes. You should also be running it as root or given sufficient caps to change user and group identity as well as have root like filesystem permissions. .PP Keep in mind that you \f[B]MUST\f[] consider identity when using containers. For example: supplemental groups will be picked up from the container unless you properly manage users and groups by sharing relevant /etc files or by using some other means to share identity across containers. Similarly if you use "rootless" containers and user namespaces to do uid/gid translations you \f[B]MUST\f[] consider that while managing shared files. .PP Also, as mentioned by hotio (https://hotio.dev/containers/mergerfs), with Docker you should probably be mounting with \f[C]bind\-propagation\f[] set to \f[C]slave\f[]. .SS Does mergerfs support CoW / copy\-on\-write / writes to read\-only filesystems? .PP Not in the sense of a filesystem like BTRFS or ZFS nor in the overlayfs or aufs sense. It does offer a cow\-shell (http://manpages.ubuntu.com/manpages/bionic/man1/cow-shell.1.html) like hard link breaking (copy to temp file then rename over original) which can be useful when wanting to save space by hardlinking duplicate files but wish to treat each name as if it were a unique and separate file. .PP If you want to write to a read\-only filesystem you should look at overlayfs. You can always include the overlayfs mount into a mergerfs pool. .SS Why can\[aq]t I see my files / directories? .PP It\[aq]s almost always a permissions issue. Unlike mhddfs and unionfs\-fuse, which runs as root and attempts to access content as such, mergerfs always changes its credentials to that of the caller. This means that if the user does not have access to a file or directory than neither will mergerfs. However, because mergerfs is creating a union of paths it may be able to read some files and directories on one drive but not another resulting in an incomplete set. .PP Whenever you run into a split permission issue (seeing some but not all files) try using mergerfs.fsck (https://github.com/trapexit/mergerfs-tools) tool to check for and fix the mismatch. If you aren\[aq]t seeing anything at all be sure that the basic permissions are correct. The user and group values are correct and that directories have their executable bit set. A common mistake by users new to Linux is to \f[C]chmod\ \-R\ 644\f[] when they should have \f[C]chmod\ \-R\ u=rwX,go=rX\f[]. .PP If using a network filesystem such as NFS, SMB, CIFS (Samba) be sure to pay close attention to anything regarding permissioning and users. Root squashing and user translation for instance has bitten a few mergerfs users. Some of these also affect the use of mergerfs from container platforms such as Docker. .SS Is my OS\[aq]s libfuse needed for mergerfs to work? .PP No. Normally \f[C]mount.fuse\f[] is needed to get mergerfs (or any FUSE filesystem to mount using the \f[C]mount\f[] command but in vendoring the libfuse library the \f[C]mount.fuse\f[] app has been renamed to \f[C]mount.mergerfs\f[] meaning the filesystem type in \f[C]fstab\f[] can simply be \f[C]mergerfs\f[]. That said there should be no harm in having it installed and continuing to using \f[C]fuse.mergerfs\f[] as the type in \f[C]/etc/fstab\f[]. .PP If \f[C]mergerfs\f[] doesn\[aq]t work as a type it could be due to how the \f[C]mount.mergerfs\f[] tool was installed. Must be in \f[C]/sbin/\f[] with proper permissions. .SS Why was libfuse embedded into mergerfs? .IP "1." 3 A significant number of users use mergerfs on distros with old versions of libfuse which have serious bugs. Requiring updated versions of libfuse on those distros isn\[aq]t practical (no package offered, user inexperience, etc.). The only practical way to provide a stable runtime on those systems was to "vendor" / embed the library into the project. .IP "2." 3 mergerfs was written to use the high level API. There are a number of limitations in the HLAPI that make certain features difficult or impossible to implement. While some of these features could be patched into newer versions of libfuse without breaking the public API some of them would require hacky code to provide backwards compatibility. While it may still be worth working with upstream to address these issues in future versions, since the library needs to be vendored for stability and compatibility reasons it is preferable / easier to modify the API. Longer term the plan is to rewrite mergerfs to use the low level API. .SS Why did support for system libfuse get removed? .PP See above first. .PP If/when mergerfs is rewritten to use the low\-level API then it\[aq]ll be plausible to support system libfuse but till then it\[aq]s simply too much work to manage the differences across the versions. .SS Why use mergerfs over mhddfs? .PP mhddfs is no longer maintained and has some known stability and security issues (see below). MergerFS provides a superset of mhddfs\[aq] features and should offer the same or maybe better performance. .PP Below is an example of mhddfs and mergerfs setup to work similarly. .PP \f[C]mhddfs\ \-o\ mlimit=4G,allow_other\ /mnt/drive1,/mnt/drive2\ /mnt/pool\f[] .PP \f[C]mergerfs\ \-o\ minfreespace=4G,allow_other,category.create=ff\ /mnt/drive1:/mnt/drive2\ /mnt/pool\f[] .SS Why use mergerfs over aufs? .PP aufs is mostly abandoned and no longer available in many distros. .PP While aufs can offer better peak performance mergerfs provides more configurability and is generally easier to use. mergerfs however does not offer the overlay / copy\-on\-write (CoW) features which aufs and overlayfs have. .SS Why use mergerfs over unionfs? .PP UnionFS is more like aufs than mergerfs in that it offers overlay / CoW features. If you\[aq]re just looking to create a union of drives and want flexibility in file/directory placement then mergerfs offers that whereas unionfs is more for overlaying RW filesystems over RO ones. .SS Why use mergerfs over overlayfs? .PP Same reasons as with unionfs. .SS Why use mergerfs over LVM/ZFS/BTRFS/RAID0 drive concatenation / striping? .PP With simple JBOD / drive concatenation / stripping / RAID0 a single drive failure will result in full pool failure. mergerfs performs a similar function without the possibility of catastrophic failure and the difficulties in recovery. Drives may fail, however, all other data will continue to be accessible. .PP When combined with something like SnapRaid (http://www.snapraid.it) and/or an offsite backup solution you can have the flexibility of JBOD without the single point of failure. .SS Why use mergerfs over ZFS? .PP MergerFS is not intended to be a replacement for ZFS. MergerFS is intended to provide flexible pooling of arbitrary drives (local or remote), of arbitrary sizes, and arbitrary filesystems. For \f[C]write\ once,\ read\ many\f[] usecases such as bulk media storage. Where data integrity and backup is managed in other ways. In that situation ZFS can introduce a number of costs and limitations as described here (http://louwrentius.com/the-hidden-cost-of-using-zfs-for-your-home-nas.html), here (https://markmcb.com/2020/01/07/five-years-of-btrfs/), and here (https://utcc.utoronto.ca/~cks/space/blog/solaris/ZFSWhyNoRealReshaping). .SS Why use mergerfs over UnRAID? .PP UnRAID is a full OS and its storage layer, as I understand, is proprietary and closed source. Users who have experience with both have said they prefer the flexibility offered by mergerfs and for some the fact it is free and open source is important. .PP There are a number of UnRAID users who use mergerfs as well though I\[aq]m not entirely familiar with the use case. .SS What should mergerfs NOT be used for? .IP \[bu] 2 databases: Even if the database stored data in separate files (mergerfs wouldn\[aq]t offer much otherwise) the higher latency of the indirection will kill performance. If it is a lightly used SQLITE database then it may be fine but you\[aq]ll need to test. .IP \[bu] 2 VM images: For the same reasons as databases. VM images are accessed very aggressively and mergerfs will introduce too much latency (if it works at all). .IP \[bu] 2 As replacement for RAID: mergerfs is just for pooling branches. If you need that kind of device performance aggregation or high availability you should stick with RAID. .SS Can drives be written to directly? Outside of mergerfs while pooled? .PP Yes, however it\[aq]s not recommended to use the same file from within the pool and from without at the same time (particularly writing). Especially if using caching of any kind (cache.files, cache.entry, cache.attr, cache.negative_entry, cache.symlinks, cache.readdir, etc.) as there could be a conflict between cached data and not. .SS Why do I get an "out of space" / "no space left on device" / ENOSPC error even though there appears to be lots of space available? .PP First make sure you\[aq]ve read the sections above about policies, path preservation, branch filtering, and the options \f[B]minfreespace\f[], \f[B]moveonenospc\f[], \f[B]statfs\f[], and \f[B]statfs_ignore\f[]. .PP mergerfs is simply presenting a union of the content within multiple branches. The reported free space is an aggregate of space available within the pool (behavior modified by \f[B]statfs\f[] and \f[B]statfs_ignore\f[]). It does not represent a contiguous space. In the same way that read\-only filesystems, those with quotas, or reserved space report the full theoretical space available. .PP Due to path preservation, branch tagging, read\-only status, and \f[B]minfreespace\f[] settings it is perfectly valid that \f[C]ENOSPC\f[] / "out of space" / "no space left on device" be returned. It is doing what was asked of it: filtering possible branches due to those settings. Only one error can be returned and if one of the reasons for filtering a branch was \f[B]minfreespace\f[] then it will be returned as such. \f[B]moveonenospc\f[] is only relevant to writing a file which is too large for the drive its currently on. .PP It is also possible that the filesystem selected has run out of inodes. Use \f[C]df\ \-i\f[] to list the total and available inodes per filesystem. .PP If you don\[aq]t care about path preservation then simply change the \f[C]create\f[] policy to one which isn\[aq]t. \f[C]mfs\f[] is probably what most are looking for. The reason it\[aq]s not default is because it was originally set to \f[C]epmfs\f[] and changing it now would change people\[aq]s setup. Such a setting change will likely occur in mergerfs 3. .SS Why does the total available space in mergerfs not equal outside? .PP Are you using ext2/3/4? With reserve for root? mergerfs uses available space for statfs calculations. If you\[aq]ve reserved space for root then it won\[aq]t show up. .PP You can remove the reserve by running: \f[C]tune2fs\ \-m\ 0\ \f[] .SS Can mergerfs mounts be exported over NFS? .PP Yes, however if you do anything which may changes files out of band (including for example using the \f[C]newest\f[] policy) it will result in "stale file handle" errors unless properly setup. .PP Be sure to use the following options: .IP \[bu] 2 noforget .IP \[bu] 2 use_ino .IP \[bu] 2 inodecalc=path\-hash .SS Can mergerfs mounts be exported over Samba / SMB? .PP Yes. While some users have reported problems it appears to always be related to how Samba is setup in relation to permissions. .SS Can mergerfs mounts be used over SSHFS? .PP Yes. .SS I notice massive slowdowns of writes when enabling cache.files. .PP When file caching is enabled in any form (\f[C]cache.files!=off\f[] or \f[C]direct_io=false\f[]) it will issue \f[C]getxattr\f[] requests for \f[C]security.capability\f[] prior to \f[I]every single write\f[]. This will usually result in a performance degradation, especially when using a network filesystem (such as NFS or CIFS/SMB/Samba.) Unfortunately at this moment the kernel is not caching the response. .PP To work around this situation mergerfs offers a few solutions. .IP "1." 3 Set \f[C]security_capability=false\f[]. It will short circuit any call and return \f[C]ENOATTR\f[]. This still means though that mergerfs will receive the request before every write but at least it doesn\[aq]t get passed through to the underlying filesystem. .IP "2." 3 Set \f[C]xattr=noattr\f[]. Same as above but applies to \f[I]all\f[] calls to getxattr. Not just \f[C]security.capability\f[]. This will not be cached by the kernel either but mergerfs\[aq] runtime config system will still function. .IP "3." 3 Set \f[C]xattr=nosys\f[]. Results in mergerfs returning \f[C]ENOSYS\f[] which \f[I]will\f[] be cached by the kernel. No future xattr calls will be forwarded to mergerfs. The downside is that also means the xattr based config and query functionality won\[aq]t work either. .IP "4." 3 Disable file caching. If you aren\[aq]t using applications which use \f[C]mmap\f[] it\[aq]s probably simpler to just disable it all together. The kernel won\[aq]t send the requests when caching is disabled. .SS What are these .fuse_hidden files? .PP Please upgrade. mergerfs >= 2.26.0 will not have these temporary files. See the notes on \f[C]unlink\f[]. .SS It\[aq]s mentioned that there are some security issues with mhddfs. What are they? How does mergerfs address them? .PP mhddfs (https://github.com/trapexit/mhddfs) manages running as \f[B]root\f[] by calling getuid() (https://github.com/trapexit/mhddfs/blob/cae96e6251dd91e2bdc24800b4a18a74044f6672/src/main.c#L319) and if it returns \f[B]0\f[] then it will chown (http://linux.die.net/man/1/chown) the file. Not only is that a race condition but it doesn\[aq]t handle other situations. Rather than attempting to simulate POSIX ACL behavior the proper way to manage this is to use seteuid (http://linux.die.net/man/2/seteuid) and setegid (http://linux.die.net/man/2/setegid), in effect becoming the user making the original call, and perform the action as them. This is what mergerfs does and why mergerfs should always run as root. .PP In Linux setreuid syscalls apply only to the thread. GLIBC hides this away by using realtime signals to inform all threads to change credentials. Taking after \f[B]Samba\f[], mergerfs uses \f[B]syscall(SYS_setreuid,...)\f[] to set the callers credentials for that thread only. Jumping back to \f[B]root\f[] as necessary should escalated privileges be needed (for instance: to clone paths between drives). .PP For non\-Linux systems mergerfs uses a read\-write lock and changes credentials only when necessary. If multiple threads are to be user X then only the first one will need to change the processes credentials. So long as the other threads need to be user X they will take a readlock allowing multiple threads to share the credentials. Once a request comes in to run as user Y that thread will attempt a write lock and change to Y\[aq]s credentials when it can. If the ability to give writers priority is supported then that flag will be used so threads trying to change credentials don\[aq]t starve. This isn\[aq]t the best solution but should work reasonably well assuming there are few users. .SH SUPPORT .PP Filesystems are complex and difficult to debug. mergerfs, while being just a proxy of sorts, is also very difficult to debug given the large number of possible settings it can have itself and the massive number of environments it can run in. When reporting on a suspected issue \f[B]please, please\f[] include as much of the below information as possible otherwise it will be difficult or impossible to diagnose. Also please make sure to read all of the above documentation as it includes nearly every known system or user issue previously encountered. .PP \f[B]Please make sure you are using the latest release (https://github.com/trapexit/mergerfs/releases) or have tried it in comparison. Old versions, which are often included in distros like Debian and Ubuntu, are not ever going to be updated and your bug may have been addressed already.\f[] .SS Information to include in bug reports .IP \[bu] 2 Version of mergerfs: \f[C]mergerfs\ \-V\f[] .IP \[bu] 2 mergerfs settings / arguments: from fstab, systemd unit, command line, etc. .IP \[bu] 2 Version of the OS: \f[C]uname\ \-a\f[] .IP \[bu] 2 List of branches, their filesystem types, sizes (before and after issue): \f[C]df\ \-h\f[] .IP \[bu] 2 \f[B]All\f[] information about the relevant branches and paths: permissions, ownership, etc. .IP \[bu] 2 \f[B]All\f[] information about the client app making the requests: version, uid/gid .IP \[bu] 2 Runtime environment: mostly are things running inside containers or not .IP \[bu] 2 A \f[C]strace\f[] of the app having problems: .IP \[bu] 2 \f[C]strace\ \-fvTtt\ \-s\ 256\ \-o\ /tmp/app.strace.txt\ \f[] .IP \[bu] 2 A \f[C]strace\f[] of mergerfs while the program is trying to do whatever it\[aq]s failing to do: .IP \[bu] 2 \f[C]strace\ \-fvTtt\ \-s\ 256\ \-p\ \ \-o\ /tmp/mergerfs.strace.txt\f[] .IP \[bu] 2 \f[B]Precise\f[] directions on replicating the issue. Do not leave \f[B]anything\f[] out. .IP \[bu] 2 Try to recreate the problem in the simplest way using standard programs: ln, mv, cp, ls, dd, etc. .SS Contact / Issue submission .IP \[bu] 2 github.com: https://github.com/trapexit/mergerfs/issues .IP \[bu] 2 email: trapexit\@spawn.link .IP \[bu] 2 discord: https://discord.gg/MpAr69V .IP \[bu] 2 twitter: https://twitter.com/_trapexit .IP \[bu] 2 reddit: https://www.reddit.com/user/trapexit .SS Support development .PP This software is free to use and released under a very liberal license (ISC). That said if you like this software and would like to support its development donations are welcome. .PP Crypto is fine in whatever protocol you prefer. My preferences for fiat would be GitHub Sponsors or PayPal though feel free to use any platform listed below. .IP \[bu] 2 GitHub Sponsors: https://github.com/sponsors/trapexit .IP \[bu] 2 PayPal: https://paypal.me/trapexit .IP \[bu] 2 Patreon: https://www.patreon.com/trapexit .IP \[bu] 2 BuyMeACoffee: https://buymeacoff.ee/trapexit .IP \[bu] 2 Ko\-Fi: https://ko\-fi.com/trapexit .IP \[bu] 2 Open Collective: https://opencollective.com/trapexit .IP \[bu] 2 Bitcoin (BTC): bc1qu537hqlnmn2wawx9n7nws0dlkz55h0cd93ny28 .IP \[bu] 2 Bitcoin Cash (BCH): bitcoincash:qqp0vh9v44us74gaggwjfv9y54zfjmmd7srlqxa3xt .IP \[bu] 2 Bitcoin SV (BSV): 1FkFuxRtt3f8LbkpeUKRZq7gKJFzGSGgZV .IP \[bu] 2 Bitcoin Gold (BTG): AaPuJgJeohPjkB3LxJM6NKGnaHoRJ8ieT3 .IP \[bu] 2 Litecoin (LTC): MJQzsHBdNnkyGqCFdcAdHYKugicBmfAXfQ .IP \[bu] 2 Dogecoin (DOGE): DLJNLVe28vZ4SMQSxDJLBQBv57rGtUoWFh .IP \[bu] 2 Ethereum (ETH): 0xB8d6d55c0319aacC327860d13f891427caEede7a .IP \[bu] 2 Any ERC20 Token: 0xB8d6d55c0319aacC327860d13f891427caEede7a .IP \[bu] 2 Ethereum Classic (ETC): 0x2B6054428e69a1201B6555f7a2aEc0Fba01EAD9F .IP \[bu] 2 Harmony (ONE): one1hrtd2hqrrx4vcvncvrgnlzg5yl9wahn66lq6rw (0xB8d6d55c0319aacC327860d13f891427caEede7a) .IP \[bu] 2 Monero (XMR): 45BBZMrJwPSaFwSoqLVNEggWR2BJJsXxz7bNz8FXnnFo3GyhVJFSCrCFSS7zYwDa9r1TmFmGMxQ2HTntuc11yZ9q1LeCE8f .IP \[bu] 2 Dash (DASH): XvsFrohu8tbjA4E8p7xsc86E2ADxLHGXHL .IP \[bu] 2 Chia (XCH): xch18l7e2q34jtuyzkq0jg8vp7yrtnfwzly30w3yyhkk96um2ur4yqcq6p2een .IP \[bu] 2 LBRY Credits (LBC): bFusyoZPkSuzM2Pr8mcthgvkymaosJZt5r .IP \[bu] 2 Ripple (XRP): r9f6aoxaGD8aymxqH89Ke1PCUPkNiFdZZC .IP \[bu] 2 Tezos (XTZ): tz1ZxerkbbALsuU9XGV9K9fFpuLWnKAGfc1C .IP \[bu] 2 Zcash (ZEC): t1Zo1GGn2T3GrhKvgdtnTsTnWu6tCPaCaHG .IP \[bu] 2 DigiByte (DGB): Sb8r1qTrryY9Sp4YkTE1eeKEGVzgArnE5N .IP \[bu] 2 Namecoin (NMC): NDzb9FkoptGu5QbgetCkodJqo2zE1cTwyb .IP \[bu] 2 Vertcoin (VTC): 3PYdhokAGXJwWrwHRoTywxG4iUDk6EHjKe .IP \[bu] 2 Other crypto currencies: contact me for address .SH LINKS .IP \[bu] 2 https://spawn.link .IP \[bu] 2 https://github.com/trapexit/mergerfs .IP \[bu] 2 https://github.com/trapexit/mergerfs/wiki .IP \[bu] 2 https://github.com/trapexit/mergerfs\-tools .IP \[bu] 2 https://github.com/trapexit/scorch .IP \[bu] 2 https://github.com/trapexit/bbf .IP \[bu] 2 https://github.com/trapexit/backup\-and\-recovery\-howtos .SH AUTHORS Antonio SJ Musumeci . mergerfs-2.33.5/.github/0000755000000000000000000000000014225522122013451 5ustar rootrootmergerfs-2.33.5/.github/FUNDING.yml0000644000000000000000000000030614225522122015265 0ustar rootroot# These are supported funding model platforms github: [trapexit] patreon: trapexit custom: ['https://paypal.me/trapexit','https://buymeacoff.ee/trapexit'] ko_fi: trapexit open_collective: trapexit mergerfs-2.33.5/.github/ISSUE_TEMPLATE/0000755000000000000000000000000014225522122015634 5ustar rootrootmergerfs-2.33.5/.github/ISSUE_TEMPLATE/feature_request.md0000644000000000000000000000110214225522122021353 0ustar rootroot--- name: Feature request about: Suggest an idea for this project title: '' labels: feature, investigating assignees: '' --- **Is your feature request related to a problem? Please describe.** A clear and concise description of what the problem is. **Describe the solution you'd like** A clear and concise description of what you want to happen. **Describe alternatives you've considered** A clear and concise description of any alternative solutions or features you've considered. **Additional context** Add any other context or screenshots about the feature request here. mergerfs-2.33.5/.github/ISSUE_TEMPLATE/question.md0000644000000000000000000000032014225522122020020 0ustar rootroot--- name: Question about: When you're looking for information title: '' labels: investigating, question assignees: '' --- You can also ask questions on Discord for realtime help: https://discord.gg/MpAr69V mergerfs-2.33.5/.github/ISSUE_TEMPLATE/bug_report.md0000644000000000000000000000256614225522122020337 0ustar rootroot--- name: Bug report about: For the reporting of behavior not as described title: '' labels: bug, investigating assignees: '' --- **Describe the bug** A clear and concise description of the unexpected behavior. Please be sure to use latest release of mergerfs to ensure the issue still exists. The master branch is **not** to be considered production ready. Feel free to file bug reports but do so indicating clearly that you are testing unreleased code. **To Reproduce** Steps to reproduce the behavior. List **all** steps to reproduce. **All** settings. Please simplify the reproduction as much as possible. - Unless it is dependenat on multiple branches, use a single branch - Use standard tooling if possible (touch,truncate,rm,rmdir,ln,etc.) **Expected behavior** A clear and concise description of the expected behavior. **System information:** - OS, kernel version: `uname -a` - mergerfs version: `mergerfs -V` - mergerfs settings - List of drives, filesystems, & sizes: - `df -h` - `lsblk -f` - A strace of the application having a problem: - `strace -fvTtt -s 256 -o /tmp/app.strace.txt ` - `strace -fvTtt -s 256 -o /tmp/app.strace.txt -p ` - strace of mergerfs while app tried to do it's thing: - `strace -fvTtt -s 256 -p -o /tmp/mergerfs.strace.txt` **Additional context** Add any other context about the problem here. mergerfs-2.33.5/LICENSE0000644000000000000000000000144314225522122013120 0ustar rootroot/* ISC License Copyright (c) 2021, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ mergerfs-2.33.5/libfuse/0000755000000000000000000000000014225522122013542 5ustar rootrootmergerfs-2.33.5/libfuse/ChangeLog0000644000000000000000000032307014225522122015321 0ustar rootrootFUSE 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. * Initilaize 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) ================================== 2013-06-20 Miklos Szeredi * libfuse: fix multiple close of device fd. Reported by Dan Greenfield 2013-03-19 Miklos Szeredi * libfuse: fix thread cancel race. Exiting a worker my race with cancelling that same worker. This caused a segmenation fault. Reported and tested by Anatol Pomozov 2013-02-04 Miklos Szeredi * libfuse: fix crash in unlock_path(). Patch by Ratna Manoj * libfuse: fix the 'remember' option. The lru list was not initialized for the "/" path. This resulted in remove_node_lru() crashing on LOOKUP-DOTDOT. Patch by Madan Valluri * libfuse: configure: detect new util-linux * libfuse: Use AC_CONFIG_HEADERS instead of AM_CONFIG_HEADER. Patch by Anatol Pomozov * libfuse: rename ./configure.in to ./configure.ac. Patch by Anatol Pomozov 2012-10-01 Miklos Szeredi * Released 2.9.2 2012-10-01 Miklos Szeredi * Fix deadlock in libfuse. Running "svn update" on a fuse filesystem could deadlock because of a bug in the way the paths are locked. Reported by Kazuaki Anami 2012-08-23 Miklos Szeredi * Fix missing config.h in buffer.c. Reported by Matthew Gabeler-Lee 2012-08-14 Miklos Szeredi * Not unhashing the name in forget (commit on 2011-12-09) broke the forget logic in a subtle way, resulting in "fuse internal error: node NNN not found" and causing the filesystem daemon to abort. Fix by incrementing the node refcount if nlookup goes from zero to one. Reported by Kyle Lippincott 2012-08-13 Miklos Szeredi * Fix linking against GNU libiconv. Patch by Natanael Copa 2012-07-19 Miklos Szeredi * Released 2.9.1 2012-07-19 Miklos Szeredi * Fix crash caused by freeing a stack address. Reported by Itay Perl 2012-07-04 Miklos Szeredi * Fix install of mount.fuse from out-of-tree build. Patch by Olivier Blin * Fix build with automake >= 1.12.1. Patch by Olivier Blin 2012-04-24 Miklos Szeredi * Add fallocate operation. Only works on linux kernels 3.5 or later. Patch by Anatol Pomozov 2012-05-16 Miklos Szeredi * Linking to a library that uses threads requires the application to be linked with -pthreads otherwise some pthread functions will be linked to stubs in glibc. So move -pthread from Libs.private to Libs in fuse.pc. Reported by Werner Fink * Fix the compile command in the examples. Reported by Luciano Dalle Ore 2012-04-20 Miklos Szeredi * Released 2.9.0 2012-04-20 Miklos Szeredi * Add missing fuse_fs_flock to fuse_versionscript 2012-04-10 Miklos Szeredi * Check protocol version before sending notifications and return -ENOSYS if a particular notification is not supported. * Add 'flag_utime_omit_ok' flag to fuse_operations. If the filesystem sets this flag then ->utimens() will receive UTIME_OMIT and UTIME_NOW values as specified in utimensat(2). 2012-01-27 Miklos Szeredi * Interpret octal escape codes in options. Requested by Jan Engelhardt 2012-01-26 Miklos Szeredi * Add man pages for fusermount, mount.fuse and ulockmgr_server. Lifted from the Debian package. The man pages were written by Daniel Baumann and Bastien Roucaries 2012-01-13 Miklos Szeredi * Disable symbol versions on MacOSX. Patch by Anatol Pomozov 2012-01-02 Miklos Szeredi * Remove unnecessary mutex unlock at the end of multithreaded event loop. 2011-12-09 Miklos Szeredi * Fix hang in wait_on_path(). Reported by Ville Silventoinen * Don't unhash name in FORGET. This resulted in ENOENT being returned for unlinked but still open files if the kernel sent a FORGET request for the parent directory. * Free request in fuse_reply_data(). 2011-12-08 Miklos Szeredi * Fix build if FUSE_NODE_SLAB is not defined. Patch by Emmanuel Dreyfus * Check for availability of utimensat() function. Patch by Emmanuel Dreyfus 2011-12-07 Miklos Szeredi * Add fuse_lowlevel_notify_delete() which tells the kernel that a file or directory is deleted. Patch by John Muir 2011-12-06 Miklos Szeredi * Update retrieve_reply() method 2011-12-05 Miklos Szeredi * Low level API: lock argument of fuse_reply_lock should have a 'const' qualifier. Reported by Shachar Sharon * Add support for ioctl on directories. Reported by Antonio SJ Musumeci 2011-10-13 Miklos Szeredi * Reply to request with ENOMEM in case of failure to allocate request structure. Otherwise the task issuing the request will just freeze up until the filesystem daemon is killed. Reported by Stephan Kulow 2011-09-23 Miklos Szeredi * Replace daemon() function with fork(). Patch by Anatol Pomozov 2011-08-26 Miklos Szeredi * If configured with --disable-mtab then don't call mount(8) from libfuse to update the mtab. Reported by: James Sierp 2011-08-24 Miklos Szeredi * Use LRU list for cleaning up the cache if the "remember=T" option was given. Patch by therealneworld@gmail.com 2011-07-06 Miklos Szeredi * Add ->flock() operation to low and high level interfaces. This fixes problems with emulating flock() with POSIX locking. Reported by Sebastian Pipping. As with lock/setlk/getlk most filesystems don't need to implement this, as the kernel takes care of file locking. The only reason to implement locking operations is for network filesystems which want file locking to work between clients. 2011-07-02 Sebastian Pipping * Make xmp_utimens of examples "fusexmp" and "fusexmp_fh" not follow symlinks as other layers do that already. 2011-06-02 Miklos Szeredi * Add "remember" option. This works similar to "noforget" except that eventually the node will be allowed to expire from the cache. Patch by therealneworld@gmail.com 2011-05-27 Miklos Szeredi * Check if splice/vmsplice are supported 2011-05-26 Miklos Szeredi * Remove -lrt -ldl from fuse.pc for dynamic linking since libfuse.so is already linked with these libraries. Reported by: Nikolaus Rath 2011-05-20 Miklos Szeredi * Cleaner build output. Patch by Reuben Hawkins 2011-05-19 Miklos Szeredi * Disable splice by default, add "splice_read", "splice_write" and "splice_move" options. Keep the "no_splice_*" variants, which can disable splice even if the filesystem explicitly enables it. 2011-04-15 Max Krasnyansky * Added support for "auto_unmount" option which unmounts the filesystem automatically on process exit (or crash). 2011-03-30 Miklos Szeredi * Patches by Laszlo Papp fixing various issues found by the Coverity checker 2011-03-11 Miklos Szeredi * In case of failure to add to /etc/mtab don't umount. Reported by Marc Deslauriers 2011-02-02 Miklos Szeredi * libfuse: In fuse_session_loop_mt() don't pause when exiting the worker threads. The pause() was added in 2.2.1 to prevent segfault on pthread_cancel() on an exited, detached thread. Now worker threads are not detached and pthread_cancel() should work fine even after the thread exited. Reported by Boris Protopopov 2011-01-31 Miklos Szeredi * fusermount: chdir to / before performing mount/umount * fusermount: only allow mount and umount if util-linux supports --no-canonicalize 2010-12-16 Miklos Szeredi * Highlevel lib: allow hash tables to shrink * Highlevel lib: add slab allocation for node cache. This will allow the memory used by the filesystem to grow and shrink depending on how many inodes are currently cached. 2010-12-13 Miklos Szeredi * Highlevel lib: use dynamically resized hash table for looking up by name and node ID. 2010-12-07 Miklos Szeredi * Allow batching of forget requests. This allows forget requests to be processed faster and doesn't require a modification to fuse filesystems. Reported by Terje Malmedal * Add ->forget_multi() operation to the lowlevel API. The filesystem may implement this to process multiple forget requests in one call * Fix the ambiguity of ioctl ABI on the kernel/userspace boundary for 32bit vs. 64bit userspace 2010-11-10 Miklos Szeredi * Add new write_buf() method to the highlevel API. Similarly to the lowlevel write_buf() method, this allows implementing zero copy writes. * Add a new read_buf() method to the highlevel API. This allows returning a generic buffer from the read method, which in turn allows zero copy reads. * In fusexmp_fh implement the ->read_buf() and ->write_buf() methods. Leave the ->read() and ->write() implementations for reference, even though they are not necessary. 2010-11-08 Miklos Szeredi * Fix check for read-only fs in mtab update * Open /dev/null for write instead of read for redirecting stdout and stderr * If umount(8) supports --fake and --no-canonicalize (util-linux-ng version 2.18 or later), and umount(2) supports the UMOUNT_NOFOLLOW flag (linux kernel version 2.6.35 or later) then, "fusermount -u" will call the umount(2) system call and use "umount --fake ..." to update /etc/mtab * Added --disable-legacy-umount option to configure. This disables the runtime checking of umount(8) version. When built with this option then "fusermount -u" will fail if umount(8) doesn't support the --fake and --no-canonicalize options. * Fix fuse_buf_copy() if already at the end of the buffers * Add new ->write_buf() method to low level interface. This allows passig a generic buffer, either containing a memory buffer or a file descriptor. This allows implementing zero copy writes. * Add fuse_session_receive_buf() and fuse_session_process_buf() which may be used in event loop implementations to replace fuse_chan_recv() and fuse_session_process() respectively. * Remove unnecessary restoring of current working directory in "fusermount -u" * Add ctx->pid to debug output * Fix st_nlink value in high level lib if file is unlinked but still open * libfuse: add store request. Request data to be stored in the kernel buffers for a given inode. * libfuse: add retrieve request. Retrieve data stored in the kernel buffers for a given inode. 2010-10-14 Miklos Szeredi * Use LTLIBICONV when linking libfuse. This fixes building against uclibc + libiconv. Patch by Natanael Copa 2010-10-05 Miklos Szeredi * Add missing argument check in ulockmgr.c to prevent calling ulockmgr_server with illegal arguments. This would cause an ever growing list of ulockmgr_server processes with an endless list of open files which finally exceeds the open file handle limit. Patch by Markus Ammer 2010-09-28 Miklos Szeredi * Fix ambiguous symbol version for fuse_chan_new. fuse_versionscript included fuse_chan_new in both FUSE_2.4 and FUSE_2.6. Remove the FUSE_2.4, which is invalid. 2010-09-28 Miklos Szeredi * Fix option escaping for fusermount. If the "fsname=" option contained a comma then the option parser in fusermount was confused (Novell bugzilla #641480). Fix by escaping commas when passing them over to fusermount. Reported by Jan Engelhardt 2010-08-27 Miklos Szeredi * Add NetBSD support. Patch from Emmanuel Dreyfus 2010-07-12 Miklos Szeredi * libfuse: add buffer interface. Add a generic buffer interface for use with I/O. Buffer vectors are supplied and each buffer in the vector may be a memory pointer or a file descriptor. * The fuse_reply_fd() interface is converted to using buffers. 2010-06-23 Miklos Szeredi * Make the number of max background requests and congestion threshold tunable. New options are "max_background" and "congestion_threshold". Only effective on linux kernel versions 2.6.32 or greater. Patch by Csaba Henk 2010-06-17 Miklos Szeredi * Add fuse_reply_fd() reply function to the low level interface. On linux version 2.6.35 or greater this will use splice() to move data directly from a file descriptor to the fuse device without needing to go though a userspace buffer. With the FUSE_REPLY_FD_MOVE flag the kernel will attempt to move the data directly into the filesystem's cache. On earlier kernels it will fall back to an intermediate buffer. The options "no_splice_write" and "no_splice_move" can be used to disable splicing and moving respectively. 2010-06-15 Miklos Szeredi * Fix out-of-source build. Patch by Jörg Faschingbauer * Add a "nopath" option and flag, indicating that path argument need not be calculated for the following operations: read, write, flush, release, fsync, readdir, releasedir, fsyncdir, ftruncate, fgetattr, lock, ioctl and poll. 2010-05-10 Miklos Szeredi * Remove "chmod root" from install of fusermount. Reported by Lucas C. Villa Real 2010-04-26 Miklos Szeredi * Released 2.8.4 2010-04-26 Miklos Szeredi * Fix checking for symlinks in umount from /tmp. Reported by Al Viro * Fix umounting if /tmp is a symlink. Reported by Franco Broi 2010-02-18 Miklos Szeredi * Fix definition of FUSE_OPT_END for C++. Reported by Tim Bruylants 2010-02-03 Miklos Szeredi * Fix stack alignment for clone() 2010-02-01 Miklos Szeredi * Released 2.8.3 2010-02-01 Miklos Szeredi * Using "--no-canonicalize" with umount(8) conflicts with the race fix, sinceit assumes the supplied path is absolute, while the race fix relies on the path being relative to the current directory. Reported by Tom Rindborg 2010-01-26 Miklos Szeredi * Released 2.8.2 2010-01-21 Miklos Szeredi * Fix race if two "fusermount -u" instances are run in parallel. Reported by Dan Rosenberg * Make sure that the path to be unmounted doesn't refer to a symlink 2010-01-14 Miklos Szeredi * Fix compile error on FreeBSD. Patch by Jay Sullivan 2009-12-17 Miklos Szeredi * Use '--no-canonicalize' option of mount(8) (available in util-linux-ng version 2.17 or greater) to avoid calling readling(2) on the newly mounted filesystem before the mount procedure is finished. This has caused a deadlock if "audit" was enabled in the kernel. Also use '--no-canonicalize' for umount to avoid touching the mounted filesystem. 2009-09-11 Miklos Szeredi * Released 2.8.1 2009-08-25 Miklos Szeredi * Fix missing versioned symbol fuse_get_context@FUSE_2.2 2009-08-18 Miklos Szeredi * Released 2.8.0 2009-08-18 Miklos Szeredi * Add missing fuse_session_data to versionscript * Make sure all global symbols are prefixed with "fuse_" or "cuse_" 2009-07-16 Miklos Szeredi * Clarify how the protocol version should be negotiated between kernel and userspace. Notably libfuse didn't correctly handle the case when the supported major versions didn't match * Add missing pthread link for libulockmgr. Patch by Petr Salinger 2009-07-02 Miklos Szeredi * The context is extended with a 'umask' field. The umask is sent for mknod, mkdir and create requests by linux kernel version 2.6.31 or later, otherwise the umask is set to zero. Also introduce a new feature flag: FUSE_CAP_DONT_MASK. If the kernel supports this feature, then this flag will be set in conn->capable in the ->init() method. If the filesystem sets this flag in in conn->want, then the create modes will not be masked. * Add low level interfaces for lookup cache and attribute invalidation. This feature is available in linux kernels 2.6.31 or later. Patch by John Muir * Kernel interface version is now 7.12 * fusermount: Do not silently ignore command line arguments. Patch by Sebastian Harl 2009-06-19 Miklos Szeredi * Released 2.8.0-pre3 2009-06-19 Miklos Szeredi * Add fuse_getgroups (high level lib) and fuse_req_getgroups (low level lib) functions to query the supplementary group IDs for the current request. Currently this is implemented on Linux by reading from the /proc filesystem. 2009-06-18 Miklos Szeredi * Add "noforget" option to high level lib to prevent ESTALE errors on NFS exported filesystems. This result in paths being cached forever, resulting in ever growing memory usage. Use with care. * Add "no_remote_lock" option to disable remote file locking even if the filesystem implements it. With this option locking primitives (flock, lockf, fcntl(F_SETLK)) will still work, but will ignore remotely locked files. * CUSE patches from Tejun Heo: * Unrestricted ioctl support left some debris. Clean them up: o No reason to pass around pointer to flags. Pass flags directly. o Clean up comment and prototype parameter names. o fuse_lib_ioctl() didn't reply when get_path() failed. Fix it. o Remove unused variables {in|out}_iov from fuse_lib_ioctl(). * Add fuse_reply_ioctl_iov() * Move fuse_session, fuse_req and fuse_ll definitions to fuse_i.h and make send_reply_iov() and fuse_setup_common() global (also in fuse_i.h). These will be used by CUSE support. * Restructure fuse_ll_process() * Implement libfuse side of CUSE support. CUSE uses subset of FUSE operations as dir operations don't make sense for CUSE where one instance implements single character device. CUSE support comes with its own cuse_lowevel_ops and related initialization and helper functions. Except for initialization, it usage is basically identical to FUSE. This patch also adds example/cusexmp.c which can create a character device with name and device number specified on command line. The created device itself is pretty boring. It's a bit bucket supporting read, write and access via ioctl. 2009-06-16 Miklos Szeredi * Add missing fuse_reply_bmap to versionscript. Debian Bug#531329. Reported by Goswin Brederlow 2009-05-27 Miklos Szeredi * Don't call forget_node() if the lookup was negative and write() for the reply returned ENOENT. Reported by John Haxby 2009-05-25 Miklos Szeredi * Add FUSE_CAP_EXPORT_SUPPORT to fuse_common.h 2009-05-08 Miklos Szeredi * Fix missing newlines in some printfs * Fix 'make install-strip'. Reported by Dominick Layfield 2009-01-05 Miklos Szeredi * Released 2.8.0-pre2 2008-12-08 Miklos Szeredi * Implement poll support. Patch by Tejun Heo * Add missing setattr flags to . * Only pass valid flags to ->setattr(). 2008-12-05 Miklos Szeredi * Implement ioctl support. On high level interface only "restricted" ioctls are supported (which are defined with the _IO(), _IOR(), _IOW() or _IOWR() macros). Unrestricted ioctls will only be allwed to CUSE (Character Device in Userspace) servers. Patch by Tejun Heo 2008-11-28 Miklos Szeredi * If open sets fi->nonseekable, libfuse will tell the kernel that the file is not seekable. Patch by Tejun Heo 2008-11-19 Miklos Szeredi * lowlevel lib: fix deadlock if fuse_reply_* is called from the interrupt handling function. Reported by Tero Marttila 2008-10-16 Miklos Szeredi * Allow commas in options to be escaped with a backslash * Add new function: fuse_opt_add_opt_escaped() * Add missing fuse_reply_bmap() to the version script 2008-10-14 Miklos Szeredi * Pass current file flags to read and write operations 2008-07-24 Miklos Szeredi * Clean up debug output in highlevel lib 2008-07-10 Miklos Szeredi * Released 2.8.0-pre1 2008-06-27 Miklos Szeredi * Fix handling of (no)suid and (no)dev options if filesystem is mounted from /etc/fstab or via mount(8). Reported by Jan Ondrej. * Skip calling mount(8) if /etc/mtab doesn't exist or if it's on a read-only filesystem. This works around issues with certain mount implementations. Reported by Szabolcs Szakacsits. 2008-06-16 Miklos Szeredi * Remove fuse kernel module sources. Linux 2.6.27 will support NFS exporting. 2008-06-10 Miklos Szeredi * Fix theoretical infinite loops in libfuse. Reported by Szabolcs Szakacsits * Fix missing include for PATH_MAX. Reported by Szabolcs Szakacsits 2008-05-23 Miklos Szeredi * Fix mounting over symlink. Reported by Szabolcs Szakacsits 2008-05-09 Miklos Szeredi * Don't allow bigger than 4kB writes by default on 2.6.26 and later kernels, so that filesystems not expecting this are not broken on a kernel upgrade. Provide a 'big_writes' mount option to enable this feature. In future API revisions this may become the default. 2008-04-09 Miklos Szeredi * Update warning message for missing newline at end of fuse.conf * Update debug message for successful operation to not include the string "error:" 2008-04-08 Miklos Szeredi * Update error message for missing mountpoint parameter. Reported by Allen Pulsifer 2008-04-04 Miklos Szeredi * Print library version information to debug output * Highlevel lib: don't limit paths to 4095 characters 2008-03-25 Miklos Szeredi * Fix memory leaks on mount. Patch by Szabolcs Szakacsits 2008-03-19 Miklos Szeredi * Fix missing pthread_mutex_destroy in error path of fuse_lib_opendir(). Patch by Szabolcs Szakacsits 2008-03-07 Miklos Szeredi * Add queuing on contention to per-node lock algorithm, to avoid starvation. * Only enable cancelation when reading a request, otherwise cancellation could happen with a mutex held, which could hang the process on umount 2008-02-08 Miklos Szeredi * Block SIGCHLD when executing mount and umount * fusexmp_fh: avoid unnecessary seeking in readdir * Update kernel interface to 7.9: * Support receiving file handle from kernel in GETATTR request * Allow operations with a NULL path argument, if the filesystem supports it * Add support atomic open(O_TRUNC) * Support the st_blksize field in struct stat * If the "FUSE_THREAD_STACK" environment is set, initialize the stack size of threads by this value. Patch by Florin Malita * Add per-node locking, instead of a global tree lock to protect the path from changing during operations. Original patch by Rodrigo Castro 2008-02-03 Csaba Henk * lib/mount_bsd.c: - string formatting fixes - exit if mounting has failed (in FreeBSD a mount failure is not critical per se, as the daemon still could be mounted externally, but waiting for such an event is more confusing than fruitful) - ditch the kvm(8) stuff and simply use forced unmount which just won't block - prettify option specifications - add "-onosync_unmount" kernel option 2008-01-07 Csaba Henk * lib/mount_bsd.c: - refine device closing in a race-free way - add support for "-osubtype" on FreeBSD * makeconf.sh: make it work under FreeBSD 2008-01-03 Csaba Henk * lib/mount_bsd.c: close device before unmount (cf. lib/mount.c rev. 1.43) and fix some warnings 2007-12-23 Miklos Szeredi * Fix './configure --disable-static'. Patch from Ismail Dönmez 2007-12-17 Miklos Szeredi * Released 2.7.2 2007-12-12 Miklos Szeredi * Fix kernel module compile for 2.6.24 * Invalidate attributes of parent directory after create(), since the modification time changes. Invalidate attributes on rename, since some filesystems may update st_ctime. Reported by Szabolcs Szakacsits * Fix NFS exporting to handle 64bit node IDs * Disable old symbol versions if __UCLIBC__ is defined. If a symbol in a library has multiple versions, the runtime linker in uClibc seems to randomly choose between them. * Remove erroneous 'fuse_opt_insert_arg@FUSE_2_5' from fuse_version_script. fuse_opt_free_args() was added in fuse-2.6. * Close fuse device file descriptor before calling umount(), preventing a deadlock when umount is synchronous. Reported by Szabolcs Szakacsits 2007-11-12 Miklos Szeredi * 'fusermount -u' did not umount the filesystem if /etc/mtab was a symlink. This bug was introduced in 2.7.1 by "Don't call /bin/[u]mount if /etc/mtab is a symlink". Found by robertsong. 2007-10-16 Miklos Szeredi * Released 2.7.1 2007-10-16 Miklos Szeredi * Clarify licence version to be "LGPLv2" for the library * kernel fixes: * After mount set nlink attribute for the root inode to 1 * Fix wake up of task waiting for a reserved request * Fix allowing setattr, listxattr and statfs for other users 2007-09-18 Miklos Szeredi * Add missing context initialization in fuse_fs_chmod(). Bug found by "iohead" * Fix kernel module compilation for 2.6.23. Based on patch by Marian Marinov 2007-09-04 Philippe Elie * lib/fuse_lowlevel.c: fix a fuse_req leak in do_forget() 2007-07-31 Miklos Szeredi * Work around hotplug issue, that it calls filesystem with file descriptors 0, 1 and 2 not open. Tracked down by Leif Johnson 2007-07-25 Miklos Szeredi * Don't call /bin/[u]mount if /etc/mtab is a symlink. Reported by Tomas M * Also don't touch /etc/mtab if it is within the mounted filesystem. Suggested by Jeffrey Law 2007-07-12 Miklos Szeredi * Reset args->argc in fuse_opt_free_args(). Patch by Lucas C. Villa Real 2007-07-02 Miklos Szeredi * Released 2.7.0 2007-07-02 Miklos Szeredi * Accept a NULL "op" for fuse_main(), etc. This is useful if filesystem is only invoking fuse to print a help message, or version. Fixes RedHat bugzilla #217343 2007-06-22 Miklos Szeredi * lib: fix locking when loading a filesystem module 2007-06-21 Miklos Szeredi * Add fs subtype support to mount.fuse 2007-06-20 Miklos Szeredi * Add fs subtype support to libfuse and fusermount 2007-06-19 Miklos Szeredi * kernel: sync with mainline (2.6.22) 2007-06-18 Miklos Szeredi * Send debug output to stderr instead of stdout. Patch by Jan Engelhardt 2007-06-03 Miklos Szeredi * libulockmgr: Work around a kernel bug in recv(), causing it to sometimes return zero even if data was available on the socket. 2007-05-29 Miklos Szeredi * lib: optimization: store parent pointer in node instead of parent id 2007-05-25 Miklos Szeredi * lib: don't create new thread for each FORGET request. FORGET messages sometimes caused so many threads to be created, that process virtual memory space ran out. Reported by Chris AtLee 2007-05-24 Miklos Szeredi * lib: fix memory leak on thread creation failure in multithreaded event loop. Found by Chris AtLee 2007-05-23 Miklos Szeredi * lowlevel lib: add fuse_reply_iov function, which is similar to fuse_reply_buf, but accepts a vector of buffers. Patch by Roger Willcocks 2007-05-21 Miklos Szeredi * Fix Oops or error if a regular file is created with mknod(2) on a fuse filesystem. Kernels 2.6.18 onward are affected. Thanks to J. Cameijo Cerdeira for the report 2007-05-11 Csaba Henk * libfuse: fix return value of fuse_loop()/fuse_loop_mt(). Error reported by Csaba Henk, fix by Miklos Szeredi * libfuse: fix unlock in flush * libfuse: do unlocking on RELEASE+FLUSH 2007-05-03 Miklos Szeredi * Released 2.7.0-rc1 2007-05-02 Miklos Szeredi * kernel: sync with mainline: * Use invalidate_mapping_pages() if available * Fix BUG when invalid file type is supplied in mount. Patch by Timo Savola 2007-04-27 Miklos Szeredi * libfuse: call umount(8) directly instead of fusermount if possible * Clean up init script, make it LSB compliant 2007-04-26 Miklos Szeredi * In multithreaded loop, use a semaphore instead of SIGHUP to wake up the main thread on umount. This is more elegant, and works even if signals are blocked. 2007-04-25 Miklos Szeredi * Improve mounting support in libfuse: - check non-empty mountpoint - only fall back to fusermount when necessary 2007-04-23 Miklos Szeredi * Don't chdir to "/" in foreground mode, it causes more trouble than it's worth 2007-04-18 Miklos Szeredi * Replace utils/mount.fuse "sh" script with a "C" program 2007-04-15 Miklos Szeredi * Add -lulockmgr to compilation comment in fusexmp_fh.c 2007-04-05 Miklos Szeredi * Check for iconv. Patch by Csaba Henk * Add direct umounting * Use "fusectl" as the device for the fusectl filesystem. Debian Bug#417945. Reported by Laurent Bonnaud 2007-04-01 Csaba Henk * Fix some FreeBSD related macros. 2007-03-30 Miklos Szeredi * Add support for direct mounting by libfuse. Fall back on calling fusermount if it doesn't work 2007-03-14 Miklos Szeredi * Released 2.7.0-pre1 2007-03-05 Miklos Szeredi * Correctly handle O_APPEND in direct IO mode. Reported by Greg Bruno * mount.fuse should use /bin/bash. Debian Bug#413403. Reported by Thomas Weinbrenner 2007-02-26 Miklos Szeredi * Fix detection of installed fuse in init script. Reported and fix suggested by Davide Canova 2007-02-05 Miklos Szeredi * Fix 2.6.9 RHEL kernels, which have compatibility mutex.h, but don't define mutex_destroy(), bummer. Patch from Phil Schwan 2007-02-04 Miklos Szeredi * Compile fuseblk for kernels which don't have an option to turn off the block layer (CONFIG_BLOCK). Reported by Szakacsits Szabolcs 2007-02-03 Miklos Szeredi * Add filesystem stacking support to high level API. Filesystem modules can be built into libfuse or loaded from shared object (.so) files * Add 'subdir' and 'iconv' built in modules * lib/fuse.c: Fix locking for the reply code in create and open 2007-02-02 Miklos Szeredi * kernel: make it compile on "strange" kernels which have emulated mutexes via but no i_mutex. Reported by Tomasz Mateja 2007-01-28 Miklos Szeredi * kernel: fix BUG in control filesystem if it is umounted and mounted again, while some fuse filesystems are present. Bugreport from Florent Mertens * kernel: sync with mainline, support 2.6.20 2007-01-22 Miklos Szeredi * lib/Makefile.am: actually link libfuse against libfuse_libs 2007-01-19 Miklos Szeredi * Build fix for 2.6.16 vanila and 2.6.15 FC5 kernels. Patch from Ian Abbott 2007-01-18 Miklos Szeredi * Fix abort in fuse_new() compatibility API for opts == NULL case. Novell bugzilla #233870. Patch from Takashi Iwai. 2007-01-13 Miklos Szeredi * Fix option parsing in mount.fuse. Patch from Jens M. Noedler 2007-01-02 Miklos Szeredi * Fix unaligned access in file desctriptor passing in libfuse, fusermount and ulockmgr. Debian bug ID: 404904. Reported and tested by Sebastian Fontius 2006-12-16 Miklos Szeredi * kernel: don't keep unreferenced inodes in the icache. 2006-12-15 Miklos Szeredi * fusermount: Fix detection of fuseblk. Reported by Szakacsits Szabolcs * lib: Fix use after free in fuse_flush(). Reported by Ron Lindman 2006-12-10 Miklos Szeredi * mount.fuse: add "setuid=USER" option which does a "su - USER" for the filesystem * fusermount: use "/bin/mount -f" to add entry to /etc/mtab, and "/bin/umount" to remove entry from /etc/mtab. This gets rid of the ugly code dealing with mtab, as well as a possible race between fusermount and mount trying to modify /etc/mtab at the same time * Fix "buffer size too small: 4" warning for users of the fuse_loop_mt_proc() function. 2006-12-04 Miklos Szeredi * Fix warnings with gcc-4.1 on 64bit archs. Report from Harshavardhana * Add extra warning options, and fix resulting warnings * Really fix fuse_teardown problem 2006-12-02 Miklos Szeredi * Add -lrt to fuse.pc (if needed) to fix static linking against libfuse. Reported by Szakacsits Szabolcs 2006-12-01 Miklos Szeredi * Released 2.6.1 2006-11-30 Miklos Szeredi * Fix API version 21 and 22 compatibility for fuse_teardown. Reported by Bgs 2006-11-29 Miklos Szeredi * fusermount: Print a more helpful message in case the kernel doesn't support the 'fuseblk' filesystem type. This has been biting ntfs-3g users. Reported by Yura Pakhuchiy * kernel: fix build problem for "make -C ...". Reported by Stephen Bryant 2006-11-19 Miklos Szeredi * Fix bug in certain error paths of lookup routines. The request object was reused for sending FORGET, which is illegal. This bug could cause an Oops in linux-2.6.18 or in fuse-2.6.0, and might silently corrupt memory in earlier versions. Report and test program by Russ Cox 2006-11-11 Miklos Szeredi * Print an error if an incompatible kernel interface version is detected in INIT. This will only show if filesystem is started with -d or -f * Fix order of fuse_destroy()/fuse_unmount() in error cleanup of fuse_setup_common(). Reported by Szakacsits Szabolcs 2006-11-06 Miklos Szeredi * Fix recursive locking in fuse_create(). Thanks to Takuya Ishibashi for the bug report 2006-10-28 Miklos Szeredi * Fix automake problem. Patch from Nix 2006-10-26 Miklos Szeredi * Fix mount.fuse to use /bin/sh instead of /bin/bash, which is not always available on embedded systems. Patch from Paul Smith * Fix util/Makefile.am, so that failure to run update-rc.d or device creation doesn't cause make to fail. Reported by Paul Smith 2006-10-21 Miklos Szeredi * Released 2.6.0 2006-10-18 Miklos Szeredi * fusermount: don't try to create a lock file if /etc/mtab is a symlink. Report and patch from Alexei Sheplyakov (debian bug #393693) 2006-10-17 Miklos Szeredi * Minor changes, sync with mainline tree 2006-10-16 Miklos Szeredi * Released 2.6.0-rc3 2006-10-15 Miklos Szeredi * kernel: cleanups 2006-10-13 Miklos Szeredi * kernel: Fix compilation on patched 2.6.18 (fc6) and 2.6.19. Report from David Shaw * lib: Fix lost error on renaming a file. Report from David Shaw * lib: Fix lost error on hiding open files (renaming to .fuse_hiddenXXXX) * kernel: Fix a rare hang on SMP/32bit on heavy filesystem activity. The cause of the bug was that some calls to i_size_write() were not protected by a lock, and hence i_size_seqcount could become corrupted. This caused subsequent calls to i_size_read() to spin forever. This is a long standing bug was probably introduced in version 2.2, and thought to be related to NFS exporting (it's not). It was reported by various people, but Dana Henriksen has finally helped me to track it down, so big thanks to him * kernel: Protect against truncation of a swapfile 2006-10-10 Miklos Szeredi * kernel: Check for signature of super_operations->umount_begin(). Ubuntu kernel 2.6.17 seems to use the new signature found in 2.6.18. Thanks to Florent Mertens for the report 2006-10-08 Miklos Szeredi * Make sure inode numers wrap around at 2^32. This is needed on dual 64bit/32bit architectures, because 32bit applications using the non-largefile interface would otherwise break (EOVERFLOW error would be returned by the stat() system call family) * ulockmgr: handle the case, when a locking operation fails because no more file desctriptors are available in ulockmgr_server. Also work around a Linux kernel bug (known to exist for all Linux kernel versions <= 2.6.18) which may cause sent file descriptors to be lost in the above case * ulockmgr: optimize file descriptor use * restore needed cpp flags to util/Makefile.am * Install udev rules as 99-fuse.rules instead of 60-fuse.rules * Minor clean up of udev rules * Add a synchronous DESTROY message to kernel interface. This is invoked from umount, when the final instance of the filesystem is released. It is only sent for filesystems mounted with the 'blkdev' option for security reasons. * If the DESTROY message is received, call the filesystem's ->destroy() method. In this case it's not called from session destruction as it would be otherwise. 2006-10-01 Miklos Szeredi * Released 2.6.0-rc2 2006-10-01 Miklos Szeredi * Add support for FLUSH+RELEASE operation for FreeBSD. Original patch by Csaba Henk * Add init script to insert fuse module and mount the control filesystem. The script is installed as /etc/init.d/fuse and on debian based systems (where update-rc.d is available) symlinks from /etc/rc*.d/ are also installed. * Include '#define FUSE_USE_VERSION=XX' into examples so they become more self contained. 2006-09-30 Miklos Szeredi * API changes: * Move lock_owner from a separate argument into fuse_file_info * Add a flag to fuse_file_info indicating (1) a highlevel lock operation (unlock all) was initiated by a flush, (2) a lowlevel release operation should perform a flush as well. * fusermount: revert modprobe change (2006-08-18) since it doesn't work reliably with udev * Add support for block device backed filesystems. This mode is selected with the 'blkdev' option, which is privileged. * Add support for the bmap (FIBMAP ioctl) operation on block device backed filesystems. This allows swapon and lilo to work on such filesystems. * kernel changes: * Drop support for kernels earlier than 2.6.9. Kernel module from previous (2.5.x) release can be used with library from this release * In fuse_dentry_revalidate() use dget_parent() instead of dereferencing d_parent, since there's no protection against parent changing and going away * Protect nlookup from concurrent updates * In lookup if a directory alias exists but is unused, then get rid of it, otherwise return -EBUSY. * In mkdir if a directory alias exists, return success, but leave dentry negative. In reality this could happen if a remote rename immediately followed the mkdir. * Don't BUG in fuse_iget() if multiple retries are needed to get a good inode. This could happen if several lookups are racing for the same inode. 2006-09-29 Miklos Szeredi * Fix compilation on 2.6.9. Report from Troy Ayers 2006-09-27 Miklos Szeredi * Fix Oops in fuse_readpages(). Reported by David Shaw 2006-09-24 Csaba Henk * Add support for nanosec times on FreeBSD * Fix FreeBSD compatibility issues 2006-09-23 Miklos Szeredi * Fix one more compatibility bug. Thanks to Ricardo Correia * Fix utimens compilation with uClibc. Patch from Jamie Guinan 2006-09-22 Miklos Szeredi * Fixed several compatibility bugs in low level interface. Reported by Ricardo Correia * Add workaround for ARM caching bug 2006-09-16 Miklos Szeredi * Rename new utimes() method to more logical utimens() 2006-09-14 Miklos Szeredi * Fuse tried to unlink already unlinked hidden files. Bug reported by Milan Svoboda 2006-09-10 Miklos Szeredi * Released 2.6.0-rc1 2006-09-10 Miklos Szeredi * kernel: Fix unlock on close for kernels < 2.6.18 * Add ulockmgr library & server. This can be used for handling file locking requests either directly from libfuse or over a network, etc. This first version is not optimized and the number of file descriptors it uses may get out of hand 2006-09-07 Miklos Szeredi * lib: Add interrupt support to high level library, which may be enabled with the 'intr' mount option. * When an operation is interrupted the thread handling that operation will receive SIGUSR1 (or other signal specified with the 'intr_signal=N' option). The library installs a no-op signal handler for this signal, unless there's already a handler installed. * The filesystem may query interrupt status (regardless of 'intr') with the fuse_interrupted() function. * mount.fuse: initialize $HOME if not set. Report from Sven Goldt 2006-09-03 Miklos Szeredi * lib: Multithreaded loop now allows unlimited number of threads. This is needed for locking operations which may block indefinitely. Also the kernel now doesn't limit the number of outstanding requests so the library shouldn't do so either. 2006-09-01 Miklos Szeredi * Fix recursive lock bug in interrupt handling * Add utimes() method to highlevel interface, which supports setting times with nanosecond resolution 2006-08-18 Miklos Szeredi * kernel: fix page leak if fuse_readpages() failed in it's initialization. Bug found and original patch from Alexander Zarochentsev * For linux kernels >=2.6.18 (2.6.19 if using the fuse module from the kernel tree) the statfs method will receive the path within the filesystem on which the stat(v)fs syscall was called * fusermount: try to modprobe fuse module if invoked by root and unable to open device. This is needed with udev, since the device node will be created only when the module is inserted, hence module autoloading won't work. Reported by Szakacsits Szabolcs 2006-07-30 Miklos Szeredi * fusermount: if selinux is active, restore the original file's security context in unmount_rename(). Redhat bugzilla id 188561. Patch from Yves Perrenoud * Add POSIX file locking operation to high level library * Initialize context for unlink of hidden files on umount. Bug reported by Tim Stoakes 2006-07-14 Miklos Szeredi * Multiple release() calls can race with each other, resulting in the hidden file being deleted before the last release finishes. Bug found and patch tested by Mark Huijgen 2006-07-05 Miklos Szeredi * fusermount: if /dev/fuse doesn't exist, suggest modprobing fuse; this makes sense on systems using udev. Reported by Szakacsits Szabolcs 2006-06-29 Miklos Szeredi * Released 2.6.0-pre3 2006-06-29 Miklos Szeredi * Support in kernel module for file locking and interruption. The same functionality is available in official kernels >= 2.6.18 2006-06-28 Miklos Szeredi * Add POSIX file locking support * Add request interruption 2006-06-06 Miklos Szeredi * Add missing pthread_rwlock_destroy(). Patch from Remy Blank 2006-06-05 Remy Blank * lib: canonicalize mount point in fuse_helper_opt_proc() so that unmounting succeeds even if mount point was relative. 2006-06-04 Csaba Henk * lib: fix emergency umount in helper.c when malloc fails. (The way it was done would end up in a segfault.) 2006-06-01 Csaba Henk * lib: adjust threading related compiler flags. Switch to "-pthread" from "-lpthread" as that's the preferred one on several platforms. Consulted with Terrence Cole and Miklos Szeredi 2006-05-08 Miklos Szeredi * lib: search fusermount in installation directory (bindir) as well as in PATH. 2006-05-03 Miklos Szeredi * lib: fix compilation if CLOCK_MONOTONIC is not defined. Reported by Christian Magnusson 2006-04-23 Csaba Henk * lib: make FreeBSD mount routine recognize if kernel features backgrounded init and if it does, run the mount util in foreground (similarly to Linux) 2006-04-21 Miklos Szeredi * kernel: fix fput deadlock fix, the lockless solution could lead to "VFS: busy inodes after umount..." * kernel: fix race between checking and setting file->private_data for the device. Found by Al Viro 2006-04-11 Miklos Szeredi * kernel: remove request pool, instead allocate requests on demand. Account the number of background requests, and if they go over a limit, block the allocation of new requests. * kernel: fix deadlock if backgrounded request holds the last reference to the super block * kernel: don't use fuse_reset_request() during direct I/O 2006-04-06 Csaba Henk * lib: Let FreeBSD mount option parsing routine recognize "no" prefixes for FUSE specific options as well 2006-04-01 Miklos Szeredi * lib: Add missing rwlock initialization. Patch by Ryan Bradetich 2006-03-17 Miklos Szeredi * API changes: * fuse_main(), fuse_setup() and fuse_new() have an additionl user_data parameter * fuse_mount() returns a 'struct fuse_chan' pointer instead of a file descriptor * fuse_unmount() receives a 'struct fuse_chan' pointer. It destroys the given channel * fuse_teardown() no longer has a file descriptor parameter * new exported functions: fuse_session_remove_chan(), fuse_get_session(), fuse_daemonize() * fuse_chan_recv() may now return a new channel which will be used to send the reply 2006-03-16 Miklos Szeredi * Released 2.6.0-pre2 2006-03-16 Miklos Szeredi * Don't unmount if already unmounted. This fixes a problem seen in the following situation: Lazy unmount a busy filesystem; Mount a new one in top; When the first finally unmounts, the second also unmounts. Reported by Franco Broi 2006-03-15 Miklos Szeredi * lowlevel lib: use indirect function calls instead of a switch/case construct. Besides increased efficiency it helps maintainability & readability too. Patch from Florin Malita 2006-03-13 Miklos Szeredi * kernel: replace global spinlock with a per-connection spinlock 2006-03-10 Miklos Szeredi * Fix source compatibility breakage for fuse_unmount(). Report from Yura Pakhuchiy 2006-03-02 Miklos Szeredi * Fix O_ASYNC handling in fuse_dev_release(). From Jeff Dike 2006-03-01 Miklos Szeredi * Add O_ASYNC and O_NONBLOCK support to FUSE device. Patch by Jeff Dike * Renamed fuse_chan_receive() to fuse_chan_recv() and changed interface to return -errno in case of error. 2006-03-01 Csaba Henk * libfuse: pass device file descriptor to fuse_unmount(), rewrite FreeBSD implementation so that it uses libc (sysctl backed) instead of an embdedded script (kmem backed). Adjust the control flow of hello_ll so that device doesn't get closed before unmount attempt. 2006-02-25 Miklos Szeredi * Lowlevel lib: return all-zero statvfs data if filesystem doesn't implement method. This is needed on FreeBSD, and nicer on Linux too. Highlevel lib already did this. Reported by Csaba Henk * Fix negative entry handling. There was a bug, that negative lookups with timeouts (nodeid == 0) returned -EIO. 2006-02-23 Miklos Szeredi * Fix race between RELEASE and UNLINK, which might leave .fuse_hidden* files around 2006-02-21 Miklos Szeredi * fusexmp_fh: implement flush() method and call close() on the open file descriptor. This is needed if used on an NFS filesystem, which buffers data until file is closed. Franco Broi spotted the situation when 'cp -p' failed to set the modification time because of this. 2006-02-20 Miklos Szeredi * Released 2.6.0-pre1 2006-02-19 Miklos Szeredi * libfuse: fix use-after-free bug in interruptred reply_entry(). Patch from John Muir * libfuse: fix wrong symbol versioning for fuse_mount. Debian bug ID: 352631. Found by Stéphane Rosi 2006-02-17 Miklos Szeredi * Lowlevel lib: Unify fuse_dirent_size() and fuse_add_dirent() into a single function fuse_add_direntry(). This cleans up the interface and makes it possible to do stacking. 2006-02-16 Miklos Szeredi * Fix rare race betweeen abort and release caused by failed iget() in fuse_create_open(). * Add 'ac_attr_timeout' option e.g. for filesystems which do their own attribute caching. 2006-02-15 Miklos Szeredi * Work around FreeBSD runtime linker "feature" which binds an old version of a symbol to internal references if the symbol has more than one version. This resulted in infinite recursion in fuse_lowlevel_new_compat25(). 2006-02-10 Csaba Henk * Refine clock_gettime() querying so that linker options shall be set as it's appropriate for the target platform. 2006-02-09 Miklos Szeredi * Fix udev rule syntax. Reported by Nix 2006-02-08 Miklos Szeredi * In some cases udev rule seems to be ineffective when installed as 40-fuse.rules but work as 60-fuse.rules. Reported by John Hunt 2006-02-03 Miklos Szeredi * Fix compilation when build directory is different from source directory. Reported by Frédéric L. W. Meunier 2006-02-02 Miklos Szeredi * Fix even bigger bug introduced in fix for request_end() on 2006-01-14. Reported by Gal Rosen 2006-01-30 Miklos Szeredi * highlevel-lib: add 'auto_cache' option. This caches file data based on modification time and size 2006-01-20 Miklos Szeredi * Sanitize storage type and help message in mount_bsd.c. Patch from Csaba Henk * fuse_opt: add new helper constants FUSE_OPT_KEY_KEEP and FUSE_OPT_KEY_DISCARD * Add options 'max_readahead', 'sync_read' and 'async_read' * Kernel ABI version 7.6: * Negotiate the 'max_readahead' value and 'async_read' flags with userspace in the INIT method * Add connection info to ->init() methods to both lowlevel and highlevel API * Fall back to synchronous read() behavior if either library or userspace filesystem is using the old interface version. This is needed so non-updated filesystems won't be confused by the different read() behavior 2006-01-19 Miklos Szeredi * lib: if "fsname=" option was given, pass it to fusermount * fuse_opt: add new fuse_opt_insert_arg() function, which is needed by filesystems to implement some argument manipulations correctly * fuse_opt: fix memory leak in handling "--" option 2006-01-18 Miklos Szeredi * kernel: fix detection of case when fuse is not configured into the kernel either as module or built-in * fuse_opt.h: fix incompatibility with C++ compilers by renaming 'template' structure member to 'templ'. Reported by Takashi Iwai * fuse.h: fix compatibility bugs. Patch by Yura Pakhuchiy * kernel: support version 2.6.16 (i_sem -> i_mutex) 2006-01-16 Miklos Szeredi * Added (again) asynchronous readpages support * Each connection now shows up under /sys/fs/fuse/connections * Connection attributes exported to sysfs: 'waiting' number of waiting requests; 'abort' abort the connection * Connection may be aborted through either the sysfs interface or with 'umount -f mountpoint' 2006-01-14 Miklos Szeredi * Released 2.5.0 2006-01-14 Miklos Szeredi * kernel: fix a couple of bugs * Order of request_end() and fuse_copy_finish() was wrong. Posthumous note: Franco Broi managed to exploit this, though it seemed quite impossible * request_end() used request pointer after decrementing refcount * Clearing ->connected or ->mounted connection flags could race with setting other bitfields not protected with a lock 2006-01-10 Miklos Szeredi * kernel: add necessary compile flags for 2.4.X/x86_64. Report from Sean Ziegeler 2006-01-09 Miklos Szeredi * Released 2.5.0-pre2 2006-01-09 Miklos Szeredi * Applied patch from Csaba Henk, to update mount_bsd to new fuse_mount() semantics * Ignore auto,noauto,... options in mount.fuse. Reported by Frank Steiner and Don Taber * fusermount: add 'dirsync' mount option 2006-01-07 Miklos Szeredi * Improved help reporting and added version reporting to library 2006-01-06 Miklos Szeredi * Change working directory to "/" even if running in the foreground. Patch from Jonathan Brandmeyer * Changed lots of functions to use 'struct fuse_args' instead of separate argc and argv * Added fuse_parse_cmdline(), fuse_set_signal_handlers() and fuse_remove_signal_handlers() functions, so that it's now pretty easy to get all the functionality of fuse_main() with a filesystem using the lowlevel API. 2006-01-02 Miklos Szeredi * mount.fuse: the 'user' option should be ignored. Report and solution from Mattd. * mount.fuse: export PATH in the right place. Report and patch from Hannes Schweizer 2005-12-16 Miklos Szeredi * Clean up the option parsing interface slightly, by creating an "argument list" structure, that contains the argument vector and count 2005-12-15 Miklos Szeredi * fusermount: check if /mnt/mtab is a symlink and don't modify it in that case * kernel: simplify request size limiting. INIT only contains maximum write size, maximum path component size remains fixed at 1024 bytes, and maximum xattr size depends on read buffer. 2005-12-14 Miklos Szeredi * Fix readdir() failure on x86_64, of 32bit programs compiled without largefile support. Bug report and help from Anthony Kolasny * If lookup returns invalid mode, return -EIO instead of creating a regular file * Add current output argument vector to option processing function 2005-12-12 Miklos Szeredi * Fix stale code in ifdef FreeBSD. Patch from Csaba Henk 2005-12-09 Miklos Szeredi * Released 2.5.0-pre1 2005-12-09 Miklos Szeredi * libfuse: added option parsing interface, defined in . 2005-12-07 Miklos Szeredi * Return EIO for file operations (read, write, fsync, flush) on open files whose inode has become "bad". Inodes will be marked "bad" if their type changes. Bug report by Csaba Henk 2005-12-06 Miklos Szeredi * Use bigger request buffer size. write() did not work on archs with > 4k page size, Bug report by Mark Haney * ABI version 7.5: * Extend INIT reply with data size limits 2005-12-02 Miklos Szeredi * Fix memory leak in fuse_read_cmd()/fuse_process_cmd(). Bug reported by Vincenzo Ciancia * Handle exit-by-umount in fuse_read_cmd() 2005-11-29 Miklos Szeredi * Check if '-msoft-float' option is supported by compiler when configuring for a 2.4.x kernel. Bug report by Mark Haney * In multithreaded loop send a TERM signal to the main thread if one of the other threads exit. Needed on FreeBSD for a clean exit on umount. Should not cause any harm on Linux either 2005-11-28 Miklos Szeredi * Fix bug in 32-bit file handle compatibility 2005-11-27 Miklos Szeredi * Block TERM, INT, HUP and QUIT signals in all but the main thread. According to POSIX it's not specified which thread will receive these signals. * Kernel changes: * Check for directory aliasing on mkdir, not just on lookup * Check for special node ID values in create+open operation * Sync with -mm: readv, writev, aio_read and aio_write methods added to file operations * Cleanups: lookup code, page offset calculation * ABI stepped to 7.4, changes: * frsize member added to fuse_kstatfs structure * added support for negative entry caching: on lowlevel API if fuse_entry_param::ino is set to zero in reply to a lookup request, the kernel will cache the dentry for the specified amount of time. * libfuse: added 'negative_timeout' option: specifies how much negative entries should be cached. Default is zero, to be compatible with prior versions 2005-11-22 Miklos Szeredi * Add detection of mainline FUSE code in running kernel 2005-11-21 Miklos Szeredi * Don't use async cancelation in multithreaded loop. This makes it more portable to systems where read() is not async cancel safe. Report from Andriy Gapon 2005-11-20 Miklos Szeredi * Warn if API version 11 compatibility is requested 2005-11-17 Miklos Szeredi * More FreeBSD merge * fusermount: don't allow mountpoints with '\n', '\t', or '\\' in them, because it corrupts /etc/mtab. Found by Thomas Biege CVE-2005-3531 * libfuse: don't use system() to invoke 'fusermount -u ...' because it breaks mountpoints with spaces in them into multiple arguments 2005-11-16 Miklos Szeredi * Merge library part of FreeBSD port. Patch by Csaba Henk 2005-11-11 Miklos Szeredi * Use 64bit type for file handle, so the full range supported by the kernel interface is available to applications 2005-11-10 Miklos Szeredi * Moved mountpoint argument checking from fuse_parse_cmdline() to fuse_mount() in preparation to FreeBSD merge. 2005-11-08 Miklos Szeredi * Remove unneeded close() from fuse_teardown(). Spotted by Csaba Henk. 2005-11-07 Miklos Szeredi * Make the statfs change backwards compatible. 2005-11-06 Miklos Szeredi * Change ->statfs() method to use 'struct statvfs' instead of 'struct statfs'. This makes the API more portable since statvfs() is defined by POSIX. 2005-10-28 Miklos Szeredi * Add fgetattr() method, which currently will only be called after a successful call to a create() method. 2005-10-26 Miklos Szeredi * Change kernel ABI version to 7.3 * Add ACCESS operation. This is called from the access() system call if 'default_permissions' mount option is not given, and is not called on kernels 2.4.* * Add atomic CREATE+OPEN operation. This will only work with 2.6.15 (presumably) or later Linux kernels. * Add ftruncate() method. This will only work with 2.6.15 (presumably) or later Linux kernels. * Fix kernel module compile if kernel source and build directories differ. Report and initial patch by John Eastman 2005-10-18 Miklos Szeredi * lib: optimize buffer reallocation in fill_dir. 2005-10-17 Miklos Szeredi * Released 2.4.1 2005-10-14 Miklos Szeredi * libfuse: add debug for write result (by Shaun Jackman) and warnings for too large read/write result 2005-10-11 Miklos Szeredi * Spelling fixes, thanks to Ioannis Barkas 2005-10-10 Miklos Szeredi * fuse_common.h: use extern "C". Thanks to Valient Gough for the patch 2005-10-07 Miklos Szeredi * highlevel-lib: init() and destroy() methods didn't have an initialized fuse_context. Bug reported by Tim Stoakes 2005-10-04 Miklos Szeredi * Released 2.4.0 2005-10-03 Miklos Szeredi * Add documentation to fuse_lowlevel.h * API cleanups: * Remove definitions of unused FATTR_CTIME / FUSE_SET_ATTR_CTIME * Move fuse_mount() and fuse_unmount() to fuse_common.h * Change the return type of fuse_reply_none() from int to void. 2005-09-30 Miklos Szeredi * kernel: NFS exporting leaked dentries. Bug found and fixed by Akshat Aranya. 2005-09-29 Miklos Szeredi * fusermount: fix error message, when unable to open /dev/fuse. Report by Balázs Pozsár 2005-09-28 Miklos Szeredi * UClibc fixes from Christian Magnusson 2005-09-27 Miklos Szeredi * Added NAME="%k" to util/udev.rules. Fix by Mattias Wadman. 2005-09-26 Miklos Szeredi * Released 2.4.0-rc1 2005-09-26 Miklos Szeredi * fusermount: allow user umount in the case when /etc/mtab is a symlink to /proc/mounts. Reported by Balázs Pozsár. 2005-09-23 Miklos Szeredi * Check for special node ID values in lookup and creation 2005-09-22 Miklos Szeredi * Slight optimization in returning EINVAL error in case of an open with O_DIRECT flag. 2005-09-20 Miklos Szeredi * Remove '--enable-auto-modprobe' configure flag. Module auto-loading is now handled by the kernel. 2005-09-15 Miklos Szeredi * Install UDEV rule file, so /dev/fuse is created with mode 0666. Help from Jens M. Noedler. 2005-09-14 Miklos Szeredi * Add memory cleanup on thread exit 2005-09-13 Miklos Szeredi * Set umask to zero in fusexmp and fusexmp_fh, so that files/directories are created with the requested mode. 2005-09-12 Miklos Szeredi * Don't ignore read error in multithreaded loop 2005-09-08 Miklos Szeredi * Released 2.4.0-pre2 2005-09-08 Miklos Szeredi * Revert lock and access operations. Postpone these until 2.5. 2005-09-02 Miklos Szeredi * Fix compile warning on 2.6.13 and later * Fix compilation on old kernels 2005-08-19 Miklos Szeredi * lib: always refresh directory contents after rewinddir() to conform to SUS. Bug found by John Muir. 2005-08-15 Miklos Szeredi * Released 2.4.0-pre1 2005-08-14 Miklos Szeredi * lib: cleaned up (or messed up, depending on your POV) the low level library API. Hopefully this is close to the final form. 2005-08-05 Miklos Szeredi * fusermount: don't allow empty mountpoint argument, which defeats automatic umounting in fuse_main(). Bugreport by Václav Jůza 2005-08-03 Miklos Szeredi * fix warnings in fuse.h and fuse_lowlevel.h if -Wshadow compiler option is used (Paul Alfille). 2005-08-02 Miklos Szeredi * highlevel-lib: added mount options "attr_timeout" and "entry_timeout". These options control the length of time file attributes and entries (names) are cached. Both default to 1.0 second. * kernel: correctly handle zero timeout for attributes and entries 2005-08-01 Miklos Szeredi * Added missing symbols to versionscript (Joshua J. Berry) * kernel: implement two flags, open can set: 'direct_io' and 'keep_cache'. These correspond exactly to mount options 'direct_io' and 'kernel_cache', but allow a per-open setting. * Move 'direct_io' and 'kernel_cache' mount option handling to userspace. For both mount options, if the option is given, then the respective open flag is set, otherwise the open flag is left unmodified (so the filesystem can set it). * lib (highlevel): make open method optional 2005-07-28 Miklos Szeredi * kernel: invalidate attributes for read/readdir/readlink operations * kernel: detect newer UML kernels 2005-07-26 Miklos Szeredi * Make the installation path of fuse.ko and mount.fuse configurable through INSTALL_MOD_PATH and MOUNT_FUSE_PATH environment variables. Requirement and help from Csaba Henk. 2005-07-22 Miklos Szeredi * Fix bug, that causes filesystem requests to hang when unique request counter becomes negative. This happens after 2,147,483,648 operations, so most people won't care. Thanks to Franco Broi for the report and testing. 2005-07-21 Miklos Szeredi * Don't change mtime/ctime/atime to local time on read/write. Bug reported by Ben Grimm * Install fuse_common.h and fuse_lowlevel.h. Report by Christian Magnusson * fusermount: use getopt_long() for option parsing. It allows the use of '--' to stop argument scanning, so fusermount can now operate on directories whose names begin with a '-'. Patch by Adam Connell 2005-07-15 Miklos Szeredi * fusermount: add '-v', '--version' and '--help' options * add inode based API 2005-07-12 Miklos Szeredi * lib: don't block signals in worker threads. Problem noticed by Usarin Heininga 2005-07-07 Miklos Szeredi * lib: don't allow both 'allow_other' and 'allow_root' options to be given 2005-07-06 Miklos Szeredi * fusermount: check if mountpoint is empty (only '.' and '..' for directories, and size = 0 for regular files). If "nonempty" option is given, omit this check. This is useful, so users don't accidentally hide data (e.g. from backup programs). Thanks to Frank van Maarseveen for pointing this out. * kernel: check if mandatory mount options ('fd', 'rootmode', 'user_id', 'group_id') are all given * lib: simplify 'readdir_ino' handling * lib: add mount options 'umask=M', 'uid=N', 'gid=N' 2005-07-03 Miklos Szeredi * kernel: clean up 'direct_io' code 2005-06-28 Miklos Szeredi * Add 'mount.fuse' written by Petr Klima * '/dev/fuse' is created by 'make install' if does not yet exist 2005-06-20 Miklos Szeredi * Fix UCLIBC compile error. Patch by Christian Magnusson 2005-06-08 Miklos Szeredi * Enable the auto-loading of the module via access to the corresponding device file. Patch by Takashi Iwai. * Allow mounting a regular file (over a regular file) for unprivleged users. * Do not create temporary device file. Require "/dev/fuse" to exist, and be readable/writable by the mounting user. 2005-06-02 Miklos Szeredi * Released 2.3.0 2005-06-02 Miklos Szeredi * Fix serious information leak: if the filesystem returns a short byte count to a read request, and there are non-zero number of pages which are not filled at all, these pages will not be zeroed. Hence the user can read out previous memory contents. Found by Sven Tantau. 2005-05-27 Miklos Szeredi * Add "readdir_ino" mount option, which tries to fill in the d_ino field in struct dirent. This mount option is ignored if "use_ino" is used. It helps some programs (e.g. 'pwd' used over NFS from a non-Linux OS). Patch by David Shaw. 2005-05-12 Miklos Szeredi * Released 2.3-rc1 2005-05-12 Miklos Szeredi * File save in krusader and other editors doesn't work with sshfs, because open() is interrupted by a periodic signal, and open() restarts forever, without any progress. This could just be fixed in open(), but the problem is more generic: if signals are received more often than the filesystem can get the request to userspace, it will never finish. This is probably only a theoretical problem, nevertheless I'm removing the possibility to interrupt requests with anything other than SIGKILL, even before being sent to userspace. Bugreport by Eduard Czimbalmos. 2005-05-09 Miklos Szeredi * libfuse: add "tree_lock" rwlock, that is locked for write in rename, unlink and rmdir, and locked for read in all other operations. This should fix the rename/release race reported by Valient Gough and others. The solution is very coarse, a finer grained locking scheme could be implemented, but it would be much more complex. Let's see whether this is good enough. 2005-05-09 Miklos Szeredi * Released 2.3-pre7 2005-05-08 Miklos Szeredi * Better fix for out of order FORGET messages. Now the LOOKUP/FORGET messages are balanced exactly (one FORGET can balance many lookups), so the order no longer matters. This changes the kernel ABI slightly, but the library remains backward compatible. 2005-05-06 Miklos Szeredi * Fix abort for out of order FORGET messages. Again. Spotted by Franco Broi again. Sorry :) 2005-04-29 Miklos Szeredi * Released 2.3-pre6 2005-04-29 Miklos Szeredi * Make fusermount work with fuse kernel modules not yet supporting the "group_id" option (added for the purpose of stricter permission checking). 2005-04-28 Miklos Szeredi * Check for hard-linked directories in lookup. This could cause problems in the VFS, which assumes that such objects never exist. * Make checking of permission for other users more strict. Now the same privilege is required for the mount owner as for ptrace on the process performing the filesystem operation. 2005-04-23 Miklos Szeredi * Released 2.3-pre5 2005-04-22 Miklos Szeredi * Add -msoft-float to kernel module compile flags for 2.4.X. This is needed on certain architectures. Report from Chris Kirby * Fix buggy behavior of open(..., O_CREAT|O_EXCL) if interrupted. Reported by David Shaw * Remove "allow_root" option from kernel module, and implement it's functionality in the library * Fix Oops caused by premature release of fuse_conn. Clean up related code, to be more readable * Sendfile should not use page cache if "direct_io" mount option is given 2005-04-08 Miklos Szeredi * Fix Oops in case of nfs export. Spotted by David Shaw * Fix another Oops in case of write over nfs with direct_io turned on. Again spotted by David Shaw 2005-04-07 Miklos Szeredi * Released 2.3-pre4 2005-04-07 Miklos Szeredi * lib: finalized new readdir() interface, which now supersedes the getdir() method. 2005-04-03 Miklos Szeredi * Released 2.3-pre3 2005-04-03 Miklos Szeredi * Implement backward compatibility with version 5 kernel ABI 2005-04-01 Miklos Szeredi * Released 2.3-pre2 2005-04-01 Miklos Szeredi * kernel: fix dirent offset handling * lib: add readdir and releasedir methods * lib: use fh field of fuse_file_info in opendir, readdir, releasedir and fsyncdir methods * lib: check kernel API version and bail out of it's old. This will be properly fixed in the next release 2005-03-31 Miklos Szeredi * Released 2.3-pre1 2005-03-31 Miklos Szeredi * kernel API: add padding to structures, so 64bit and 32bit compiler will return the same size * kernel API: add offset field to fuse_dirent. This will allow more sophisticated readdir interface for userspace * kernel API: change major number to 6 * kernel: fix warnings on 64bit archs * kernel: in case of API version mismatch, return ECONNREFUSED 2005-03-24 Miklos Szeredi * kernel: trivial cleanups 2005-03-21 Miklos Szeredi * Add fsyncdir() operation 2005-03-19 Miklos Szeredi * kernel: add locking to background list (fixes previous fix) 2005-03-18 Miklos Szeredi * kernel: fix bug which could cause leave busy inodes after unmount, and Oops. 2005-03-08 Miklos Szeredi * examples: add -lpthread to link flags to work around valgrind quirk * lib: don't exit threads, so cancelation doesn't cause segfault 2005-03-04 Miklos Szeredi * kernel: fix nasty bug which could cause an Oops under certain situations. Found by Magnus Johansson 2005-02-28 Miklos Szeredi * libfuse: added opendir() method. This can be used in case permission checking in getdir() is too late. Thanks to Usarin Heininga for pointing out this deficiency * libfuse: added init() and destroy() methods to fuse_operations * kernel: llseek() method for files and directories made explicit * kernel: fixed inode leak in NFS export in case of nodeid wrapping 2005-02-15 Miklos Szeredi * libfuse: clean up some unitialized memory found with valgrind * Add -lpthread to Libs in fuse.pc. Valgrind seems to need an explicitly linked libpthread for applications 2005-02-10 Miklos Szeredi * fusermount: set umask, otherwise /etc/mtab will have unpredictable permission. Spotted by Jindrich Kolorenc * fusermount: set owner and group of /etc/mtab to original values on unmount * libfuse: add 'use_ino' option to help. Patch by Valient Gough 2005-02-07 Miklos Szeredi * Cleaned up directory reading (temporary file is not used) 2005-02-02 Miklos Szeredi * Released 2.2 2005-02-02 Miklos Szeredi * Fix possible race when operation is interrupted 2005-01-28 Miklos Szeredi * Fix compilation on 2.6.7 2005-01-26 Miklos Szeredi * Released 2.2-pre6 2005-01-26 Miklos Szeredi * Fix bug in link() operation which caused the wrong path to be passed as the first argument. Found by Anton Altaparmakov 2005-01-21 Miklos Szeredi * LIB: fix double reply in readdir operation * fusermount: fix uid checking bug. Patch by Adam Connell * KERNEL: fix compile on various RedHat patched 2.4 kernels. Patch by Keshava Gowda 2005-01-20 Miklos Szeredi * KERNEL: provide correct llseek semantics for fuse device (fixes a bug on Progeny 2.4.20 kernel). Reported by Valient Gough 2005-01-20 Miklos Szeredi * Released 2.2-pre5 (matches kernel 2.6.11-rc1-mm2) 2005-01-18 Miklos Szeredi * KERNEL ABI: remove GETDIR operation, and add OPENDIR, READDIR and RELEASEDIR. This ends the ugly hack of passing a file descriptor to the kernel, and actually makes the code simpler. 2005-01-17 Miklos Szeredi * Released 2.2-pre4 2005-01-17 Miklos Szeredi * fusermount: remove capability setting, which was the cause of problems for some users. It seems that FS related capabilities are removed by setfsuid(), so this isn't even needed. 2005-01-15 Miklos Szeredi * fix compilation on 2.4 kernels (reported by Valient Gough) * fix failure to unmount bug (found by David Shaw) * fusermount: improve parsing of /etc/fuse.conf 2005-01-13 Miklos Szeredi * Remove 'mount_max' and 'user_allow_other' module options. These are now checked by fusermount, and can be set in /etc/fuse.conf * KERNEL: change check for fsid == 0 to capable(CAP_DAC_OVERRIDE) 2005-01-11 Miklos Szeredi * KERNEL: fix possible inode allocation problem, where sizeof(struct inode) is not aligned (found by Mike Waychison) * KERNEL: use new follow_link/put_link methods * KERNEL: cosmetic fixes 2005-01-10 Miklos Szeredi * Released 2.2-pre3 2005-01-10 Miklos Szeredi * Add missing code that was accidently left out 2005-01-09 Miklos Szeredi * Released 2.2-pre2 2005-01-09 Miklos Szeredi * Change "uid" mount option to "user_id" to avoid confusion with a mount option "uid" commonly used by many filesystems 2005-01-09 Miklos Szeredi * Released 2.2-pre1 2005-01-09 Miklos Szeredi * If FUSE is configured in the kernel, don't build it by default 2005-01-07 Miklos Szeredi * Compile fix by Christian Magnusson 2005-01-05 Miklos Szeredi * Fix compilation for 2.6.{0-5} kernels 2005-01-04 Miklos Szeredi * KERNEL: if request is interrupted, still keep reference to used inode(s) and file, so that FORGET and RELEASE are not sent until userspace finishes the request. * remove /{sys,proc}/fs/fuse/version, and instead add an INIT request with the same information, which is more flexible, simpler, works on embedded systems. 2004-12-16 Miklos Szeredi * KERNEL ABI: update interface to make it independent of type sizes. This will help on 64 bit architectures which can run legacy 32 bit applications. * KERNEL ABI: add "len" field to request headers. This will allow sending/receiving requests in multiple chunks. * KERNEL: handle file type change more intelligently * LIB: "-o debug" option should disable backgrounding (fix by Fabien Reygrobellet) 2004-12-13 Miklos Szeredi * KERNEL: invalidate dentry/attributes if interrupted request could leave filesystem in an unknown state. 2004-12-12 Miklos Szeredi * KERNEL: lots of cleanups related to avoiding possible deadlocks. These will cause some regressions, but stability is considered more important. If any of these features turns out to be important, it can be readded with the deadlock problems addressed. * Make all requests interruptible (only with SIGKILL currently). This can be used to break any deadlock produced by the userspace filesystem accessing it's own exported files. The RELEASE request is special, because if it's interrupted before sending it to userspace it is still sent, but the reply is not awaited. * If request is interrupted before being sent to userspace, and if it hasn't yet got any side effects, it is always restarted, regardless of the SA_RESTART flag. This makes these interruptions transparent to the process. * Remove shared-writable mmap support, which was prone to an out-of-memory deadlock situation * Remove INVALIDATE userspace initiated request * Make readpages() synchronous. Asynchronous requests are deadlock prone, since they cannot be interrupted. * Add readv/writev support to fuse device operations * Remove some printks, which userspace FS can use for a DoS against syslog * Remove 'large_read' mount option from 2.6 in kernel, check it in fusermount instead * LIB: improve compatibility with a fuse.h header installed in ${prefix}/include which in turn includes the real header. * LIB: improve compatibility by defining fuse_main() (which is now not used), so old configure scripts find it. 2004-12-10 Miklos Szeredi * When mounting on a subdirectory of / don't duplicate slashes at the beggining of path (spotted by David Shaw) 2004-12-09 Miklos Szeredi * Fix bug causing garbage in mount options (spotted by David Shaw) 2004-12-07 Miklos Szeredi * Add 'writepage' flag to 'fuse_file_info'. * More comments in fuse.h * Get rid of double underscores 2004-12-04 Miklos Szeredi * Add -D_FILE_OFFSET_BITS=64 to cflags provided by pkg-config * helper.c: add -ho option, which only displays the options not the usage header. This can be used by filesystems which have their own options. 2004-12-03 Miklos Szeredi * Add source compatibility to 2.1 and 1.1 APIs. To select betwen versions simply define FUSE_USE_VERSION to 22, 21 or 11 before including the fuse header * Add binary compatibility to 2.1 version of library with symbol versioning 2004-12-03 Miklos Szeredi * Released 2.1 2004-12-01 Miklos Szeredi * kernel: clean up writing functions * kernel: no allocation on write in direct_io mode * move linux/fuse.h to fuse_kernel.h 2004-11-30 Miklos Szeredi * kernel: clean up reading functions 2004-11-29 Miklos Szeredi * kernel: make readpage() uninterruptible * kernel: check readonly filesystem flag in fuse_permission * lib: don't die if version file not found and new style device exists * lib: add '-r' option, which is short for '-o ro' * fusermount: simplify device opening * kernel: when direct_io is turend on, copy data directly to destination without itermediate buffer. More efficient and safer, since no allocation is done. * fusermount: fix warning if fuse module is not loaded * kernel: use /dev/fuse on 2.4 too 2004-11-26 Miklos Szeredi * libfuse API change: open, read, write, flush, fsync and release are passed a 'struct fuse_file_info' pointer containing the open flags (open and release), and the file handle. Verion changed to 3.0. 2004-11-23 Miklos Szeredi * More cleanups in the kernel * The 10,229 charater device number has been assigned for FUSE * Version file checking fix (reported by Christian Magnusson) * fusermount: opening the fuse device now doesn't need /sys. * Optimize reading by controlling the maximum readahead based on the 'max_read' mount option * fixes for UCLIBC (Christian Magnusson) 2004-11-19 Miklos Szeredi * Cleaned up kernel in preparation for merge into mainline: * Use /sys/fs/fuse/version instead of /proc/fs/fuse/version * Use real device (/dev/fuse) instead of /proc/fs/fuse/dev * __user annotations for sparse * allocate individual pages instead of kmalloc in fuse_readdir, fuse_read and fuse_write. * Fix NFS export in case "use_ino" mount option is given * Make libfuse and fusermount compatible with future versions * fusermount: properly add mount options to /etc/mtab 2004-11-15 Miklos Szeredi * fusermount: do not resolve last component of mountpoint on if it is '.' or '..'. This new path resolvation is now done on mount as well as unmount. This enables relative paths to work on unmount. * fusermount: parse common mount options like "ro", "rw", etc... * Allow module params to be changed through sysfs 2004-11-14 Miklos Szeredi * Released 2.1-pre1 2004-11-14 Miklos Szeredi * Fix bug in fuse_readpages() causing Oops in certain situations. Bug found by Vincenzo Ciancia. * Fix compilation with kernels versions > 2.6.9. 2004-11-11 Miklos Szeredi * Check kernel interface version in fusermount to prevent strangeness in case of mismatch. * No need to allocate fuse_conn until actual mount happens * Fix potential race between umount and fuse_invalidate * Check superblock of proc file in addition to inode number * Fix race between request_send_noreply() and fuse_dev_release() 2004-11-10 Miklos Szeredi * Separate configure for the kernel directory * Don't allow write to return more than 'count' * Extend kernel interface for future use 2004-11-09 Miklos Szeredi * Fix 'makeconf.sh' to use autoreconf if available 2004-11-08 Miklos Szeredi * Add ino argument to 'fuse_dirfil_t'. NOTE: This breaks source compatibility with earlier versions. To compile earier versions just add '-DFUSE_DIRFIL_COMPAT' compile flag or fix the source. Do not use the "use_ino" mount flag with filesystems compiled with FUSE_DIRFIL_COMPAT. * Add pkg-config support. To compile a FUSE based filesystem you can do "gcc -Wall `pkg-config --cflags --libs fuse` myfs.c -o myfs" or similar. Note, that the PKG_CONFIG_PATH environment variable usually needs to be set to "/usr/local/lib/pkgconfig". * fuse.h is now installed in ${prefix}/include/fuse/ 2004-11-02 Miklos Szeredi * Added "use_ino" mount option. This enables the filesystems to set the st_ino field on files 2004-11-01 Miklos Szeredi * Fix compile problems with ancient (<=2.4.18) kernels (reported by Jeremy Smith) * Add "allow_root" mount option. Patch by Yaroslav Rastrigin * Clear the 'exited' flag when mail loop is finished 2004-10-28 Miklos Szeredi * Make xattr functions work under 2.6 (bug found by Vincenzo Ciancia) 2004-10-26 Miklos Szeredi * Reset request in fuse_flush() (bugreport by David Shaw) 2004-10-21 Miklos Szeredi * fuse_main() now does not exit on error, rather it returns an error code * Exported __fuse_setup() and __fuse_teardown() functions, which make it easier to implement a custom event loop. * Use daemon() call to background the filesystem after mounting. This function closes the standard input, output and error and changes the current working directory to "/". 2004-10-14 Miklos Szeredi * Released 1.9 2004-10-09 Miklos Szeredi * Don't allow fuse_flush() to be interrupted (bug found by David Shaw) 2004-09-27 Miklos Szeredi * Add PID to fuse_context. Patch by Steven James * Change file handle type to 'unsigned long' in kernel interface 2004-09-22 Miklos Szeredi * A slight API change: fuse_get_context() doesn't need the "fuse" pointer, but the returned context contains it instead. The fuse_get() function is not needed anymore, so it's removed. * Fix mounting and umounting FUSE filesystem under another FUSE filesystem by non-root (bug spotted by Valient Gough) 2004-09-21 Miklos Szeredi * Fix deadlock in case of memory allocation failure. Patch by Christian Magnusson 2004-09-16 Miklos Szeredi * Check memory allocation failures in libfuse 2004-09-14 Miklos Szeredi * Check temporary file creation failure in do_getdir(). Bug spotted by Terje Oseberg 2004-09-13 Miklos Szeredi * Allow "large_read" option for 2.6 kernels but warn of deprecation * Make requests non-interruptible so race with FORGET is avoided. This is only a temporary solution * Support compiling FUSE kernel module on 2.4.x UML kernels 2004-09-09 Miklos Szeredi * Fix bug in case two FORGETs for the same node are executed in the wrong order. Bug spotted and endured for months by Franco Broi, and logfile for solution provided by Terje Oseberg 2004-09-01 Miklos Szeredi * Add -D_REENTRANT to the compile flags * Add documentation of fuse internals by Terje Oseberg 2004-08-16 Miklos Szeredi * Change release method to be non-interruptible. Fixes bug causing missing release() call when program which has opened files is killed (reported by Franco Broi and David Shaw) 2004-07-29 Miklos Szeredi * Add fuse_invalidate() to library API 2004-07-26 Miklos Szeredi * Check permissions in setattr if 'default_permissions' flag is set. Bug spotted by Damjan Lango 2004-07-24 Miklos Szeredi * 'large_read' mount option removed for 2.6 kernels, since the default (dynamic read size) is better * Extend kernel API with file handles. A file handle is returned by open, and passed to read, write, flush, fsync and release. This is currently only used for debug output in the library. * Security changes: * Change the current directory to the mountpoint before checking the permissions and mount filesystem on "." * By default don't modprobe the fuse module for non-root. The old behavior can be restored with the '--enable-auto-modprobe' flag of ./configure * By default don't allow shared writable mappings for non-root. The old behavior can be restored with the 'user_mmap=1' module parameter 2004-07-23 Miklos Szeredi * Clean up mount option passing to fusermount and to fuse_new() BEWARE: this changes the userspace API slightly, and the command line usage of programs using fuse_main() 2004-07-20 Miklos Szeredi * Optimize reading under 2.6 kernels by issuing multiple page asynchronous read requests 2004-07-18 Miklos Szeredi * Only use redirty_page_for_writepage() for kernels >= 2.6.6 2004-07-16 Miklos Szeredi * Separate directory entry and inode attribute validity timer * New write semaphore to stop page writeback during truncate * Fsync now waits for all writes to complete before sending the request * Optimization: if a page is completely written by fuse_commit_write(), clear the dirty flag and set the uptodate flag for that page * Some memory cleanup at exit 2004-07-13 Miklos Szeredi * Add FUSE_HARD_REMOVE flag, and '-i' option to fuse main, which disable the "hide if open" behavior of unlink/rename. * If temporary buffer allocation fails in raw read, fall back to a smaller buffer 2004-07-12 Miklos Szeredi * Fix bug in do_open() in libfuse: open count was incremented after the reply is sent so it could race with unlink/forget and cause an abort. 2004-07-08 Miklos Szeredi * When performing create or remove operation, refresh the parent's attributes on next revalidate, as i_nlink (and maybe size/time) could be inacurate. * Use redirty_page_for_writepage() in fuse_writepage() for skipped pages (2.6 only) * Set set_page_dirty address space operation (2.6 only) 2004-07-06 Miklos Szeredi * Minor fix in read: print debug info even if read size is zero 2004-07-04 Miklos Szeredi * Fix race between truncate and writepage (fsx-linux now runs without error) 2004-07-02 Miklos Szeredi * Fix kernel hang on mkfifo under 2.4 kernels (spotted and patch by Mattias Wadman) * Added option for direct read/write (-r) * Fix revalidate time setting for newly created inodes * Remove uid==0 check for '-x' option in fusermount (kernel checks this) * fuse_main() only installs handlers for signals (out of INT, HUP, TERM, PIPE), for which no handler has yet been installed * Add module option 'user_allow_other' which if set to non-zero will allow non root user to specify the 'allow_other' mount option ('-x' option of fusermount) * Fix deadlock between page writeback completion and truncate (bug found by Valient Gough with the fsx-linux utility) 2004-07-01 Miklos Szeredi * Change passing fuse include dir to 2.6 kernel make system more robust (fixes compile problems seen on SuSE 9.1 with updated 2.6 kernel) 2004-06-30 Miklos Szeredi * Acquire inode->i_sem before open and release methods to prevent concurrent rename or unlink operations. * Make __fuse_read_cmd() read only one command. This allows multiplexing the fuse file descriptor with other event sources using select() or poll() (patch by Jeff Harris) * Export 'exited' flag with __fuse_exited() (patch by Jeff Harris) 2004-06-27 Miklos Szeredi * Fix file offset wrap around at 4G when doing large reads 2004-06-24 Miklos Szeredi * Fix memory leak in open (Valient Gough) 2004-06-24 Miklos Szeredi * Add "close after delete" support to libfuse (patch by Valient Gough) * Cancel all worker threads before exit in multithreaded mode 2004-06-23 Miklos Szeredi * Fix locking bugs * Don't send reply to RELEASE * Work with newer libtool (1.5a) * Check for st_atim member of struct stat 2004-06-22 Miklos Szeredi * No request allocation needed on inode and file release 2004-06-21 Miklos Szeredi * Fix possible inode leak in userspace in case of unfinished lookup/mknod/mkdir/symlink/link operation. 2004-06-20 Miklos Szeredi * Fix some races and cleanups in fuse_read_super() 2004-06-19 Miklos Szeredi * Requests are allocated at open time 2004-06-03 Miklos Szeredi * Build shared library as well as static (using libtool) * Change FUSE_MINOR_VERSION from 1 to 0. I know it's illegal but there has not been a release with the previous minor number, and I hope nobody is using it for anything. * Change fuse_main(), so that default behavior is to go into background if mount is successful. '-f' and '-d' options disable backgrounding. This fixes the "Why does my FUSE daemon hang?" newbie complaint. * Cache ENOSYS (function not implemented) errors on *xattr, flush and fsync * Don't call getdir method from open() only from first readdir(). Open is sometimes just used to store the current directory (e.g. find) 2004-05-18 Miklos Szeredi * Added flush() call 2004-05-04 Miklos Szeredi * Extended attributes support for 2.4 (patch by Cody Pisto) 2004-04-20 Miklos Szeredi * Fixed parser with modversions (Mattias Wadman) 2004-04-19 Miklos Szeredi * Added mount option parser to 2.4 build 2004-04-13 Miklos Szeredi * Replaced binary mount data with text options * Show FUSE specific mount options in /proc/mounts * Check in fuse.h whether _FILE_OFFSET_BITS is set to 64 2004-04-09 Miklos Szeredi * Check some limits so userspace won't get too big requests 2004-04-05 Miklos Szeredi * Kill compile warning * Upgraded user-mount patch for 2.6.5 2004-04-02 Miklos Szeredi * Add detection of user-mode-linux to configure 2004-03-31 Miklos Szeredi * fixed zero size case for getxattr and listxattr 2004-03-30 Miklos Szeredi * new fusermount flag '-z': lazy unmount, default is not lazy * Extended attributes operations added (getxattr, setxattr, listxattr, removexattr) 2004-03-25 Miklos Szeredi * If filesystem doesn't define a statfs operation, then an all-zero default statfs is returned instead of ENOSYS 2004-03-24 Miklos Szeredi * Add FS_BINARY_MOUNTDATA filesystem flag for kernels > 2.6.4 2004-03-09 Miklos Szeredi * Fix for uClinux (Christian Magnusson) 2004-03-02 Miklos Szeredi * fuse_main() adds "-n progname" to the fusermount command line * More kernel interface changes: * Lookup/getattr return cache timeout values 2004-02-25 Miklos Szeredi * Clean up option parsing in fuse_main() * Added fuse_get() function which returns the fuse object created by fuse_main() 2004-02-20 Miklos Szeredi * removed old way of mounting (fusermount mountpoint program) * more kernel interface changes: * added nanosecond precision to file times * removed interface version from mount data * added /proc/fs/fuse/version which contains MAJOR.MINOR 2004-02-19 Miklos Szeredi * statfs library API changed to match other methods. Since this is not backward compatible FUSE_MAJOR_VERSION is changed to 2 * kernel interface changes follow: * statfs changed to 64 bits, added 'bavail' field * add generation number to lookup result * optimized mknod/mkdir/symlink/link (no separate lookup is needed) * rdev size increased to 32 bits for mknod * kernel interface version changed to 3.1 2004-02-18 Miklos Szeredi * user-mount upgraded for 2.6.3 kernel 2004-02-17 Miklos Szeredi * Added user-mount.2.6.2-rc3.patch * Add FS_SAFE flag to fuse filesystem * fusermount should allow (un)mounting for non-root even if not suid-root 2004-02-12 Miklos Szeredi * Remove MS_PERMISSION mount flag (that means something else now) 2004-02-10 Miklos Szeredi * Added check for i_size_read/write functions to configure.in (patch by Valient Gough) 2004-02-06 Miklos Szeredi * Fixed writing >= 2G files * Check file size on open (with generic_file_open()) * Readpage calls flush_dcache_page() after storing data * Use i_size_read/write for accessing inode->i_size * Make loopback mount of a fuse file work 2004-02-04 Miklos Szeredi * Released 1.1 2004-01-29 Miklos Szeredi * Properly check if the inode exists in fuse_invalidate 2004-01-27 Miklos Szeredi * Added -q option for fusermount * fuse_unmount() now uses -q option of fusermount, so no error is printed if the cause of the program exit is that the filesystem has already been unmounted * Fix i_nlink correctness after rmdir/unlink 2004-01-26 Miklos Szeredi * Released 1.1-pre2 2004-01-26 Miklos Szeredi * Fix typo (thanks Marcos Dione) * Compile fixes for 2.4 kernels 2004-01-23 Miklos Szeredi * Fix CONFIG_MODVERSIONS compile on 2.6 2004-01-22 Miklos Szeredi * Write all pending data before a RELEASE operation * Suppress 'Bad file descriptor' warning on exit * Replaced fusermount option '-d xxx' with '-n xxx' so it doesn't get confused with '-d' of fuse_main() (sorry about this change) * New fusermount option '-l' which enables big reads. Big reads are now disabled by default. * fuse_main() can accept fusermount arguments after a '--' 2004-01-19 Miklos Szeredi * Support for exporting filesystem over NFS (see README.NFS) 2004-01-14 Miklos Szeredi * Support non-blocking writepage on 2.6. This makes FUSE behave much more nicely in low-memory situations * Fix 32-bit dev handling in getattr and mknod for 2.6 kernels. (Note: the mknod method does not yet use 32bit device number) 2004-01-13 Miklos Szeredi * Code cleanups 2004-01-07 Miklos Szeredi * Released 1.1-pre1 2004-01-06 Miklos Szeredi * Integrated 2.6 kernel support patch by Michael Grigoriev * Improvements and cleanups for 2.6 kernels 2004-01-05 Miklos Szeredi * Added -d option to fusermount 2003-12-15 Miklos Szeredi * Added major+minor version to library API, and minor version to kernel API 2003-12-13 David McNab * Implemented fsync support in examples/example.py * Implemented 'fsync' and 'statfs' methods in python interface 2003-12-12 Miklos Szeredi * Make it compile on 2.4.19. * Add fsync operation (write file failed on xemacs & vi) 2003-12-12 David McNab * Added distutils support to the python module, as per standard python development practice 2003-12-11 Miklos Szeredi * Add file locking for mount/unmount (based on patch by Valient Gough) 2003-12-11 David McNab * Python filesystem - was broken with python2.3, now fixed: - changed PyTuple_* calls to PySequence_*, because os.lstat is no longer returning a pure tuple - changed PyInt_Check() calls to also call PyLong_Check, to cover for cases (eg os.lstat) where longs are returned - Added support for file 'release' handling, which IMO is essential since this signals to a FS that writes to a file are complete (and therefore the file can now be disposed of meaningfully at the python filesystem's discretion) - Added '__init__' handler to base Fuse class, which allows your Python class to know the mountpoint and mount args, as attributes myfs.mountpoint, myfs.optlist, myfs.optdict * General: - added 'mount.fuse' script (in util/ dir), which is meant to be symlinked from /sbin, and which allows FUSE filesystems to be mounted with the 'mount' command, and listed in fstab; also, mount arguments get passed to your filesystem 2003-11-04 Miklos Szeredi * Fix kernel version detection (again). Bugreport by Peter Levart 2003-11-03 Miklos Szeredi * Applied read combining patch by Michael Grigoriev (tested by Valient Gough and Vincent Wagelaar) 2003-10-22 Miklos Szeredi * Mtab handling fix in fusermount by "Valient Gough" (SF patch #766443) 2003-10-13 Miklos Szeredi * Error code fixes in kernel module 2003-10-04 Miklos Szeredi * kernel version detection fix * fusermount now uses "lazy" umount option * fusermount can use modprobe with module-init-tools 2003-09-08 Miklos Szeredi * Integrated caching patch by Michael Grigoriev * Added "Filesystems" file with descriptions of projects using FUSE * Added patch by Michael Grigoriev to allow compliation of FUSE kernel module for 2.6 kernels 2003-06-02 Miklos Szeredi * And another spec-file fix by Achim Settelmeier 2003-05-26 Miklos Szeredi * Spec-file fix by Achim Settelmeier 2003-03-10 Miklos Szeredi * Fix umount oops (found by Samuli Kärkkäinen) 2003-03-05 Miklos Szeredi * Merge of fuse_redhat.spec and fuse.spec by Achim Settelmeier 2003-03-04 Miklos Szeredi * Updated fuse.spec file (Achim Settelmeier) 2003-02-19 Miklos Szeredi * Version 1.0 released 2003-02-12 Miklos Szeredi * SuSE compilation fix by Juan-Mariano de Goyeneche 2002-12-10 Miklos Szeredi * The release() VFS call is now exported to the FUSE interface 2002-12-05 Miklos Szeredi * 64 bit file offset fixes in the fuse kernel module * Added function 'fuse_exit()' which can be used to exit the main loop 2002-12-03 Miklos Szeredi * Added _FILE_OFFSET_BITS=64 define to fuse.h. Note, that this is an incompatible interface change. 2002-10-28 Miklos Szeredi * Portablility fix (bug reported by C. Chris Erway) 2002-10-25 Miklos Szeredi * Use Mark Glines' fd passing method for default operation instead of old reexec 2002-10-22 Miklos Szeredi * fix "Stale NFS file handle" bug caused by changes in 2.4.19 2002-10-22 Miklos Szeredi * fix incompatiblity with Red Hat kernels, with help from Nathan Thompson-Amato. 2002-04-18 Mark Glines * added an alternative to fuse_mount(), called fuse_mount_ioslave(), which does not need to reexec the FUSE program. * added a small helper util needed by fuse_mount_ioslave(). 2002-03-16 Mark Glines * use struct fuse_statfs everywhere possible to avoid problems with the headerfiles changing struct statfs member sizes 2002-03-01 Miklos Szeredi * Another RPM spec file for RedHat >= 7 by Ian Pilcher 2002-01-14 Miklos Szeredi * RPM support by Achim Settelmeier 2002-01-09 Miklos Szeredi * Version 0.95 released 2002-01-09 Miklos Szeredi * Revaidate all path components not just the last, this means a very small performance penalty for being more up-to-date. 2002-01-08 Miklos Szeredi * Update and fix python interface 2002-01-07 Mark Glines * Added statfs() support to kernel, lib, examples, and perl! 2001-12-26 Miklos Szeredi * Better cross compilation support * Ported to Compaq IPAQ 2001-12-20 Miklos Szeredi * Added function fuse_get_context() to library API (inspired by patch from Matt Ryan) * Added flags to fusermount and to kernel interface to control permission checking * Integrated fuse_set_operations() into fuse_new() 2001-12-08 Miklos Szeredi * Applied header protection + extern "C" patch by Roland Bauerschmidt 2001-12-02 Miklos Szeredi * Added perl bindings by Mark Glines 2001-11-21 Miklos Szeredi * Cleaned up way of mounting simple filesystems. * fuse_main() helper function added 2001-11-18 Miklos Szeredi * Optimized read/write operations, so that minimal copying of data is done 2001-11-14 Miklos Szeredi * Python bindings by Jeff Epler added 2001-11-13 Miklos Szeredi * Fixed vfsmount reference leak in fuse_follow_link * FS blocksize is set to PAGE_CACHE_SIZE, blksize attribute from userspace is ignored 2001-11-09 Miklos Szeredi * Started ChangeLog mergerfs-2.33.5/libfuse/ecfd/0000755000000000000000000000000014225522122014443 5ustar rootrootmergerfs-2.33.5/libfuse/ecfd/build0000755000000000000000000000106114225522122015466 0ustar rootroot#!/bin/sh PWD=$(pwd) BASEPATH="${PWD}/${0%/*}" CC=${CC:-cc} OUTPUT="/dev/null" echo "#ifndef CONFIG_H_INCLUDED" echo "#define CONFIG_H_INCLUDED" echo IFS= for file in "${BASEPATH}/tests/"*.c do binary="${file%.c}" basename=$(basename ${binary}) ${CC} -o "${binary}" "${file}" 1>&2 if [ $? -eq 0 ]; then STDOUT=$(${binary}) if [ "${STDOUT}" != "" ]; then echo "#define ${basename} ${STDOUT}" else echo "#define ${basename}" fi rm -f "${binary}" fi done echo echo "#endif" mergerfs-2.33.5/libfuse/ecfd/LICENSE0000644000000000000000000000144314225522122015452 0ustar rootroot/* ISC License Copyright (c) 2018, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ mergerfs-2.33.5/libfuse/ecfd/README.md0000644000000000000000000000160014225522122015717 0ustar rootroot# ecfd: embeddable C feature discovery This is a simple script and set of tests to help with what would traditionally be the `configure` step of most C/C++. It is intended to be simple to be embedded into a project and only depends on the C compiler and /bin/sh. ## USAGE First copy `ecfd` into your project. Next simply call the build script which will run the tests and output a `config.` style file to `stdout`. Compile errors will output to `stderr` which can be used to understand why they aren't working. ``` $ ./ecfd/build > include/config.h ``` ## TESTS Each test is in a single module. The build script will try to compile each module and on success will execute the program. If anything is printed to `stdout` it will be appended to the `#define` printed to the build script's `stdout`. If the compilation fails nothing will be printed. Look at the tests directory for examples. mergerfs-2.33.5/libfuse/ecfd/tests/0000755000000000000000000000000014225522122015605 5ustar rootrootmergerfs-2.33.5/libfuse/ecfd/tests/HAVE_STRUCT_STAT_ST_ATIM.c0000644000000000000000000000024214225522122021651 0ustar rootroot#include #include #include int main(int argc, char *argv[]) { struct stat st; (void)st.st_atim; return 0; } mergerfs-2.33.5/libfuse/ecfd/tests/HAVE_SPLICE.c0000644000000000000000000000016114225522122017531 0ustar rootroot#define _GNU_SOURCE #include int main(int argc, char *argv[]) { (void)splice; return 0; } mergerfs-2.33.5/libfuse/ecfd/tests/static_assert.h0000644000000000000000000000013314225522122020623 0ustar rootroot#pragma once #define STATIC_ASSERT(condition) \ ((void)sizeof(char[1 - 2*!(condition)])) mergerfs-2.33.5/libfuse/ecfd/tests/HAVE_MALLOC_TRIM.c0000644000000000000000000000006614225522122020360 0ustar rootroot#include int main() { malloc_trim(0); } mergerfs-2.33.5/libfuse/ecfd/tests/HAVE_UTIMENSAT.c0000644000000000000000000000016614225522122020130 0ustar rootroot#include #include int main(int argc, char *argv[]) { (void)utimensat; return 0; } mergerfs-2.33.5/libfuse/ecfd/tests/HAVE_VMSPLICE.c0000644000000000000000000000021014225522122017767 0ustar rootroot#define _GNU_SOURCE #include #include int main(int argc, char *argv[]) { (void)vmsplice; return 0; } mergerfs-2.33.5/libfuse/ecfd/tests/HAVE_FORK.c0000644000000000000000000000013414225522122017313 0ustar rootroot#include #include int main(void) { (void)fork; return 0; } mergerfs-2.33.5/libfuse/util/0000755000000000000000000000000014225522122014517 5ustar rootrootmergerfs-2.33.5/libfuse/util/mount_util.c0000644000000000000000000001726114225522122017071 0ustar rootroot/* 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 COPYING.LIB. */ #include "mount_util.h" #include #include #include #include #include #include #include #include #include #include #ifndef __NetBSD__ #include #endif #include #include #include #include #ifdef __NetBSD__ #define umount2(mnt, flags) unmount(mnt, (flags == 2) ? MNT_FORCE : 0) #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 a symlink, * - 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; if (S_ISLNK(stbuf.st_mode)) return 0; 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; } return 1; } #endif /* __NetBSD__ */ 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; 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); setuid(geteuid()); 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); setuid(geteuid()); if (lazy) { execle("/bin/umount", "/bin/umount", "-i", rel_mnt, "-l", 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; if (!mtab_needs_update(abs_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; 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); setuid(geteuid()); 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_empty(const char *progname, const char *mnt, mode_t rootmode, off_t rootsize) { int isempty = 1; if (S_ISDIR(rootmode)) { struct dirent *ent; DIR *dp = opendir(mnt); if (dp == NULL) { fprintf(stderr, "%s: failed to open mountpoint for reading: %s\n", progname, strerror(errno)); return -1; } while ((ent = readdir(dp)) != NULL) { if (strcmp(ent->d_name, ".") != 0 && strcmp(ent->d_name, "..") != 0) { isempty = 0; break; } } closedir(dp); } else if (rootsize) isempty = 0; if (!isempty) { fprintf(stderr, "%s: mountpoint is not empty\n", progname); fprintf(stderr, "%s: if you are sure this is safe, use the 'nonempty' mount option\n", progname); return -1; } return 0; } 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; } mergerfs-2.33.5/libfuse/util/fusermount.c0000644000000000000000000007043414225522122017102 0ustar rootroot/* FUSE: Filesystem in Userspace Copyright (C) 2001-2007 Miklos Szeredi This program can be distributed under the terms of the GNU GPL. See the file COPYING. */ /* This program does the mounting and unmounting of FUSE filesystems */ #define _GNU_SOURCE /* for clone */ #include #include "mount_util.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define FUSE_COMMFD_ENV "_FUSE_COMMFD" #define FUSE_DEV_OLD "/proc/fs/fuse/dev" #define FUSE_DEV_NEW "/dev/fuse" #define FUSE_VERSION_FILE_OLD "/proc/fs/fuse/version" #define FUSE_CONF "/etc/fuse.conf" #ifndef MS_DIRSYNC #define MS_DIRSYNC 128 #endif #ifndef MS_REC #define MS_REC 16384 #endif #ifndef MS_PRIVATE #define MS_PRIVATE (1<<18) #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 static const char *progname; static int user_allow_other = 0; static int mount_max = 1000; static int auto_unmount = 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; } /* * Check whether the file specified in "fusermount -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 fusermount 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]; 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) { 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) { pid_t pid, p; int status; const char *a[2] = { last, mnt }; 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; } /* Check whether the kernel supports UMOUNT_NOFOLLOW flag */ static int umount_nofollow_support(void) { int res = umount2("", UMOUNT_UNUSED); if (res != -1 || errno != EINVAL) return 0; res = umount2("", UMOUNT_NOFOLLOW); if (res != -1 || errno != ENOENT) return 0; return 1; } 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; 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; } res = chdir_to_parent(copy, &last); if (res == -1) goto out; if (umount_nofollow_support()) { umount_flags |= UMOUNT_NOFOLLOW; } else { res = check_is_mount(last, mnt); if (res == -1) goto out; } res = umount2(last, umount_flags); if (res == -1 && !quiet) { fprintf(stderr, "%s: failed to unmount %s: %s\n", progname, mnt, strerror(errno)); } out: 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(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; } #else /* IGNORE_MTAB */ static int count_fuse_fs() { 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) { return fuse_mnt_umount(progname, mnt, mnt, lazy); } #endif /* IGNORE_MTAB */ static void strip_line(char *line) { char *s = strchr(line, '#'); if (s != NULL) s[0] = '\0'; for (s = line + strlen(line) - 1; s >= line && isspace((unsigned char) *s); s--); s[1] = '\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) 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); } fclose(fp); } else if (errno != ENOENT) { fprintf(stderr, "%s: failed to open %s: %s\n", progname, FUSE_CONF, strerror(errno)); } } 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}, {"dirsync", MS_DIRSYNC, 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 ((*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; } static int do_mount(const char *mnt, char **typep, mode_t rootmode, int fd, const char *opts, const char *dev, char **sourcep, char **mnt_optsp, off_t rootsize) { 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 check_empty = 1; 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="; for (len = 0; s[len]; len++) { if (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, "nonempty")) { check_empty = 0; } else if (opt_eq(s, len, "auto_unmount")) { auto_unmount = 1; } else if (!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")) { fprintf(stderr, "%s: option %.*s only allowed if 'user_allow_other' is set in /etc/fuse.conf\n", progname, len, s); goto err; } if (!skip_option) { if (find_mount_flag(s, len, &on, &flag)) { if (on) flags |= flag; else flags &= ~flag; } else { memcpy(d, s, len); d += len; *d++ = ','; } } } 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()); if (check_empty && fuse_mnt_check_empty(progname, mnt, rootmode, rootsize) == -1) goto err; 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(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(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(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; } static int check_version(const char *dev) { int res; int majorver; int minorver; const char *version_file; FILE *vf; if (strcmp(dev, FUSE_DEV_OLD) != 0) return 0; version_file = FUSE_VERSION_FILE_OLD; vf = fopen(version_file, "r"); if (vf == NULL) { fprintf(stderr, "%s: kernel interface too old\n", progname); return -1; } res = fscanf(vf, "%i.%i", &majorver, &minorver); fclose(vf); if (res != 2) { fprintf(stderr, "%s: error reading %s\n", progname, version_file); return -1; } if (majorver < 3) { fprintf(stderr, "%s: kernel interface too old\n", progname); return -1; } return 0; } static int check_perm(const char **mntp, struct stat *stbuf, int *mountpoint_fd) { int res; const char *mnt = *mntp; const char *origmnt = mnt; res = lstat(mnt, stbuf); if (res == -1) { fprintf(stderr, "%s: failed to access mountpoint %s: %s\n", progname, mnt, strerror(errno)); return -1; } /* No permission checking is done for root */ if (getuid() == 0) return 0; if (S_ISDIR(stbuf->st_mode)) { res = chdir(mnt); if (res == -1) { fprintf(stderr, "%s: failed to chdir to mountpoint: %s\n", progname, strerror(errno)); return -1; } mnt = *mntp = "."; res = lstat(mnt, stbuf); if (res == -1) { fprintf(stderr, "%s: failed to access mountpoint %s: %s\n", progname, origmnt, strerror(errno)); return -1; } if ((stbuf->st_mode & S_ISVTX) && stbuf->st_uid != getuid()) { fprintf(stderr, "%s: mountpoint %s not owned by user\n", progname, origmnt); return -1; } res = access(mnt, W_OK); if (res == -1) { fprintf(stderr, "%s: user has no write access to mountpoint %s\n", progname, origmnt); return -1; } } else if (S_ISREG(stbuf->st_mode)) { static char procfile[256]; *mountpoint_fd = open(mnt, O_WRONLY); if (*mountpoint_fd == -1) { fprintf(stderr, "%s: failed to open %s: %s\n", progname, mnt, strerror(errno)); return -1; } res = fstat(*mountpoint_fd, stbuf); if (res == -1) { fprintf(stderr, "%s: failed to access mountpoint %s: %s\n", progname, mnt, strerror(errno)); return -1; } if (!S_ISREG(stbuf->st_mode)) { fprintf(stderr, "%s: mountpoint %s is no longer a regular file\n", progname, mnt); return -1; } sprintf(procfile, "/proc/self/fd/%i", *mountpoint_fd); *mntp = procfile; } else { fprintf(stderr, "%s: mountpoint %s is not a directory or a regular file\n", progname, mnt); return -1; } return 0; } static int try_open(const char *dev, char **devp, int silent) { int fd = open(dev, O_RDWR); if (fd != -1) { *devp = strdup(dev); if (*devp == NULL) { fprintf(stderr, "%s: failed to allocate memory\n", progname); close(fd); fd = -1; } } else if (errno == ENODEV || errno == ENOENT)/* check for ENOENT too, for the udev case */ return -2; else if (!silent) { fprintf(stderr, "%s: failed to open %s: %s\n", progname, dev, strerror(errno)); } return fd; } static int try_open_fuse_device(char **devp) { int fd; int err; drop_privs(); fd = try_open(FUSE_DEV_NEW, devp, 0); restore_privs(); if (fd >= 0) return fd; err = fd; fd = try_open(FUSE_DEV_OLD, devp, 1); if (fd >= 0) return fd; return err; } static int open_fuse_device(char **devp) { int fd = try_open_fuse_device(devp); if (fd >= -1) return fd; fprintf(stderr, "%s: fuse device not found, try 'modprobe fuse' first\n", progname); return -1; } static int mount_fuse(const char *mnt, const char *opts) { int res; int fd; char *dev; struct stat stbuf; char *type = NULL; char *source = NULL; char *mnt_opts = NULL; const char *real_mnt = mnt; int mountpoint_fd = -1; 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 /etc/fuse.conf\n", progname); goto fail_close_fd; } } res = check_version(dev); if (res != -1) { 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, opts, dev, &source, &mnt_opts, stbuf.st_size); } else restore_privs(); 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) { 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(type); free(mnt_opts); free(dev); 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; } static void usage(void) { fprintf(stderr, "%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("fusermount version: %s\n", PACKAGE_VERSION); exit(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; int cfd; const char *opts = ""; 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'}, {0, 0, 0, 0}}; progname = strdup(argv[0]); 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 '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); exit(1); } } restore_privs(); if (mnt == NULL) exit(1); umask(033); if (unmount) goto do_unmount; commfd = getenv(FUSE_COMMFD_ENV); if (commfd == NULL) { fprintf(stderr, "%s: old style mounting not supported\n", progname); exit(1); } fd = mount_fuse(mnt, opts); if (fd == -1) exit(1); cfd = atoi(commfd); res = send_fd(cfd, fd); if (res == -1) exit(1); close(fd); if (!auto_unmount) return 0; /* 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. */ setsid(); res = chdir("/"); if (res == -1) { fprintf(stderr, "%s: failed to chdir to '/'\n", progname); exit(1); } 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; } } do_unmount: if (geteuid() == 0) res = unmount_fuse(mnt, quiet, lazy); else { res = umount2(mnt, lazy ? UMOUNT_DETACH : 0); if (res == -1 && !quiet) fprintf(stderr, "%s: failed to unmount %s: %s\n", progname, mnt, strerror(errno)); } if (res == -1) exit(1); return 0; } mergerfs-2.33.5/libfuse/util/mount.mergerfs.c0000644000000000000000000001076114225522122017643 0ustar rootroot/* FUSE: Filesystem in Userspace Copyright (C) 2001-2007 Miklos Szeredi This program can be distributed under the terms of the GNU GPL. See the file COPYING. */ #include #include #include #include #include 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; 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; } int main(int argc, char *argv[]) { char *type = NULL; char *source; const char *mountpoint; char *basename; char *options = NULL; char *command = NULL; char *setuid = NULL; int i; int dev = 1; int suid = 1; progname = argv[0]; basename = strrchr(argv[0], '/'); if (basename) basename++; else basename = argv[0]; type = "mergerfs"; 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", "nouser", "users", "auto", "noauto", "_netdev", NULL}; if (strncmp(opt, "setuid=", 7) == 0) { setuid = xstrdup(opt + 7); 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, ","); } } } if (dev) options = add_option("dev", options); if (suid) options = add_option("suid", options); if (!type) { if (source) { type = xstrdup(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); } } add_arg(&command, type); if (source) add_arg(&command, source); add_arg(&command, mountpoint); if (options) { add_arg(&command, "-o"); add_arg(&command, options); } if (setuid && setuid[0]) { char *sucommand = command; command = NULL; add_arg(&command, "su"); add_arg(&command, "-"); add_arg(&command, setuid); add_arg(&command, "-c"); add_arg(&command, sucommand); } else if (!getenv("HOME")) { /* Hack to make filesystems work in the boot environment */ setenv("HOME", "/root", 0); } execl("/bin/sh", "/bin/sh", "-c", command, NULL); fprintf(stderr, "%s: failed to execute /bin/sh: %s\n", progname, strerror(errno)); return 1; } mergerfs-2.33.5/libfuse/COPYING.LIB0000644000000000000000000006364214225522122015215 0ustar rootroot 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! mergerfs-2.33.5/libfuse/include/0000755000000000000000000000000014225522122015165 5ustar rootrootmergerfs-2.33.5/libfuse/include/fuse_entry.h0000644000000000000000000000037014225522122017521 0ustar rootroot#pragma once #include typedef struct fuse_entry_s fuse_entry_t; struct fuse_entry_s { uint64_t nodeid; uint64_t generation; uint64_t entry_valid; uint64_t attr_valid; uint32_t entry_valid_nsec; uint32_t attr_valid_nsec; }; mergerfs-2.33.5/libfuse/include/fuse_common.h0000644000000000000000000003041114225522122017647 0ustar rootroot/* 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 COPYING.LIB. */ /** @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_ #include "extern_c.h" #include "fuse_opt.h" #include "fuse_timeouts.h" #include #include /** Major version of FUSE library interface */ #define FUSE_MAJOR_VERSION 2 /** Minor version of FUSE library interface */ #define FUSE_MINOR_VERSION 9 #define FUSE_MAKE_VERSION(maj, min) ((maj) * 10 + (min)) #define FUSE_VERSION FUSE_MAKE_VERSION(FUSE_MAJOR_VERSION, FUSE_MINOR_VERSION) /* This interface uses 64 bit off_t */ #if _FILE_OFFSET_BITS != 64 #error Please add -D_FILE_OFFSET_BITS=64 to your compile flags! #endif #define FUSE_DEFAULT_MAX_PAGES_PER_REQ 32 #define FUSE_MAX_MAX_PAGES 256 EXTERN_C_BEGIN /** * Information about open files * * Changed in version 2.5 */ typedef struct fuse_file_info_t fuse_file_info_t; struct fuse_file_info_t { /** Open flags. Available in open() and release() */ int flags; /** In case of a write operation indicates if this was caused by a writepage */ uint32_t writepage : 1; /** Can be filled in by open, to use direct I/O on this file. Introduced in version 2.4 */ uint32_t direct_io : 1; /** Can be filled in by open, to indicate, that cached file data need not be invalidated. Introduced in version 2.4 */ uint32_t keep_cache : 1; /** Indicates a flush operation. Set in flush operation, also maybe set in highlevel lock operation and lowlevel release operation. Introduced in version 2.6 */ uint32_t flush : 1; /** Can be filled in by open, to indicate that the file is not seekable. Introduced in version 2.8 */ 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(). Introduced in version 2.9 */ uint32_t flock_release : 1; /* Requests the kernel to cache entries returned by readdir */ uint32_t cache_readdir : 1; uint32_t auto_cache : 1; /** File handle. May be filled in by filesystem in open(). Available in all other file operations */ uint64_t fh; /** Lock owner id. Available in locking operations and flush */ uint64_t lock_owner; }; /** * Capability bits for 'fuse_conn_info.capable' and 'fuse_conn_info.want' * * FUSE_CAP_ASYNC_READ: filesystem supports asynchronous read requests * FUSE_CAP_POSIX_LOCKS: filesystem supports "remote" locking * FUSE_CAP_ATOMIC_O_TRUNC: filesystem handles the O_TRUNC open flag * FUSE_CAP_EXPORT_SUPPORT: filesystem handles lookups of "." and ".." * FUSE_CAP_BIG_WRITES: filesystem can handle write size larger than 4kB * FUSE_CAP_DONT_MASK: don't apply umask to file mode on create operations * FUSE_CAP_SPLICE_WRITE: ability to use splice() to write to the fuse device * FUSE_CAP_SPLICE_MOVE: ability to move data to the fuse device with splice() * FUSE_CAP_SPLICE_READ: ability to use splice() to read from the fuse device * FUSE_CAP_IOCTL_DIR: ioctl support on directories * FUSE_CAP_CACHE_SYMLINKS: cache READLINK responses */ #define FUSE_CAP_ASYNC_READ (1 << 0) #define FUSE_CAP_POSIX_LOCKS (1 << 1) #define FUSE_CAP_ATOMIC_O_TRUNC (1 << 3) #define FUSE_CAP_EXPORT_SUPPORT (1 << 4) #define FUSE_CAP_BIG_WRITES (1 << 5) #define FUSE_CAP_DONT_MASK (1 << 6) #define FUSE_CAP_SPLICE_WRITE (1 << 7) #define FUSE_CAP_SPLICE_MOVE (1 << 8) #define FUSE_CAP_SPLICE_READ (1 << 9) #define FUSE_CAP_FLOCK_LOCKS (1 << 10) #define FUSE_CAP_IOCTL_DIR (1 << 11) #define FUSE_CAP_READDIR_PLUS (1 << 13) #define FUSE_CAP_READDIR_PLUS_AUTO (1 << 14) #define FUSE_CAP_ASYNC_DIO (1 << 15) #define FUSE_CAP_WRITEBACK_CACHE (1 << 16) #define FUSE_CAP_PARALLEL_DIROPS (1 << 18) #define FUSE_CAP_POSIX_ACL (1 << 19) #define FUSE_CAP_CACHE_SYMLINKS (1 << 20) #define FUSE_CAP_MAX_PAGES (1 << 21) /** * 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. */ struct fuse_conn_info { /** * Major version of the protocol (read-only) */ unsigned proto_major; /** * Minor version of the protocol (read-only) */ unsigned proto_minor; /** * Maximum size of the write buffer */ unsigned max_write; /** * Maximum readahead */ unsigned max_readahead; /** * Capability flags, that the kernel supports */ unsigned capable; /** * Capability flags, that the filesystem wants to enable */ unsigned want; /** * Maximum number of backgrounded requests */ unsigned max_background; /** * Kernel congestion threshold parameter */ unsigned congestion_threshold; /** * Max pages */ uint16_t max_pages; /** * For future use. */ unsigned reserved[22]; }; struct fuse_session; struct fuse_chan; struct fuse_pollhandle_t; typedef struct fuse_pollhandle_t fuse_pollhandle_t; /** * Create a FUSE mountpoint * * Returns a control file descriptor suitable for passing to * fuse_new() * * @param mountpoint the mount point path * @param args argument vector * @return the communication channel on success, NULL on failure */ struct fuse_chan *fuse_mount(const char *mountpoint, struct fuse_args *args); /** * Umount a FUSE mountpoint * * @param mountpoint the mount point path * @param ch the communication channel */ void fuse_unmount(const char *mountpoint, struct fuse_chan *ch); /** * Parse common options * * The following options are parsed: * * '-f' foreground * '-d' '-odebug' foreground, but keep the debug option * '-h' '--help' help * '-ho' help without header * '-ofsname=..' file system name, if not present, then set to the program * name * * All parameters may be NULL * * @param args argument vector * @param mountpoint the returned mountpoint, should be freed after use * @param foreground set to 1 if one of the relevant options is present * @return 0 on success, -1 on failure */ int fuse_parse_cmdline(struct fuse_args *args, char **mountpoint, int *foreground); /** * 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); /** * Destroy poll handle * * @param ph the poll handle */ void fuse_pollhandle_destroy(fuse_pollhandle_t *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; }; /** * 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]; }; /* 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, \ } } \ } ) /** * 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. * * @param se the session to exit * @return 0 on success, -1 on failure */ int fuse_set_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() */ void fuse_remove_signal_handlers(struct fuse_session *se); EXTERN_C_END #endif /* _FUSE_COMMON_H_ */ mergerfs-2.33.5/libfuse/include/kvec.h0000644000000000000000000000664514225522122016301 0ustar rootroot/* The MIT License Copyright (c) 2008, by Attractive Chaos 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. */ /* An example: #include "kvec.h" int main() { kvec_t(int) array; kv_init(array); kv_push(int, array, 10); // append kv_a(int, array, 20) = 5; // dynamic kv_A(array, 20) = 4; // static kv_destroy(array); return 0; } */ /* 2008-09-22 (0.1.0): * The initial version. */ #ifndef AC_KVEC_H #define AC_KVEC_H #include #define kv_roundup32(x) (--(x), (x)|=(x)>>1, (x)|=(x)>>2, (x)|=(x)>>4, (x)|=(x)>>8, (x)|=(x)>>16, ++(x)) #define kvec_t(type) struct { size_t n, m; type *a; } #define kv_init(v) ((v).n = (v).m = 0, (v).a = 0) #define kv_destroy(v) free((v).a) #define kv_A(v, i) ((v).a[(i)]) #define kv_pop(v) ((v).a[--(v).n]) #define kv_size(v) ((v).n) #define kv_max(v) ((v).m) #define kv_first(v) (kv_A(v,0)) #define kv_last(v) (kv_A(v,kv_size(v)-1)) #define kv_end(v) (kv_A(v,kv_size(v))) #define kv_delete(v,i) (kv_A(v,i) = kv_pop(v)) #define kv_resize(type, v, s) ((v).m = (s), (v).a = (type*)realloc((v).a, sizeof(type) * (v).m)) #define kv_copy(type, v1, v0) do { \ if ((v1).m < (v0).n) kv_resize(type, v1, (v0).n); \ (v1).n = (v0).n; \ memcpy((v1).a, (v0).a, sizeof(type) * (v0).n); \ } while (0) \ #define kv_push(type, v, x) do { \ if ((v).n == (v).m) { \ (v).m = (v).m? (v).m<<1 : 2; \ (v).a = (type*)realloc((v).a, sizeof(type) * (v).m); \ } \ (v).a[(v).n++] = (x); \ } while (0) #define kv_pushp(type, v) (((v).n == (v).m)? \ ((v).m = ((v).m? (v).m<<1 : 2), \ (v).a = (type*)realloc((v).a, sizeof(type) * (v).m), 0) \ : 0), ((v).a + ((v).n++)) #define kv_a(type, v, i) (((v).m <= (size_t)(i)? \ ((v).m = (v).n = (i) + 1, kv_roundup32((v).m), \ (v).a = (type*)realloc((v).a, sizeof(type) * (v).m), 0) \ : (v).n <= (size_t)(i)? (v).n = (i) + 1 \ : 0), (v).a[(i)]) #endif mergerfs-2.33.5/libfuse/include/fuse_attr.h0000644000000000000000000000061414225522122017333 0ustar rootroot#pragma once #include typedef struct fuse_attr_s fuse_attr_t; struct fuse_attr_s { 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 _padding; }; mergerfs-2.33.5/libfuse/include/fuse_dirents.h0000644000000000000000000000506514225522122020036 0ustar rootroot/* ISC License Copyright (c) 2019, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include "extern_c.h" EXTERN_C_BEGIN #include "kvec.h" #include "fuse_dirent.h" #include "fuse_direntplus.h" #include "fuse_entry.h" #include "linux_dirent64.h" #include #include #include #include #include #include enum fuse_dirents_type_e { UNSET = 0, NORMAL, PLUS }; typedef enum fuse_dirents_type_e fuse_dirents_type_t; typedef struct fuse_dirents_t fuse_dirents_t; struct fuse_dirents_t { kvec_t(char) data; kvec_t(uint32_t) offs; fuse_dirents_type_t type; }; int fuse_dirents_init(fuse_dirents_t *d); void fuse_dirents_free(fuse_dirents_t *d); void fuse_dirents_reset(fuse_dirents_t *d); int fuse_dirents_add(fuse_dirents_t *d, const struct dirent *de, const uint64_t namelen); int fuse_dirents_add_plus(fuse_dirents_t *d, const struct dirent *de, const uint64_t namelen, const fuse_entry_t *entry, const struct stat *st); int fuse_dirents_add_linux(fuse_dirents_t *d, const struct linux_dirent64 *de, const uint64_t namelen); int fuse_dirents_add_linux_plus(fuse_dirents_t *d, const struct linux_dirent64 *de, const uint64_t namelen, const fuse_entry_t *entry, const struct stat *st); void *fuse_dirents_find(fuse_dirents_t *d, const uint64_t ino); int fuse_dirents_convert_plus2normal(fuse_dirents_t *d); EXTERN_C_END mergerfs-2.33.5/libfuse/include/fuse_opt.h0000644000000000000000000001653214225522122017171 0ustar rootroot/* 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 COPYING.LIB. */ #ifndef _FUSE_OPT_H_ #define _FUSE_OPT_H_ #include "extern_c.h" /** @file * * This file defines the option parsing interface of FUSE */ EXTERN_C_BEGIN /** * 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(). */ 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); EXTERN_C_END #endif /* _FUSE_OPT_H_ */ mergerfs-2.33.5/libfuse/include/fuse_direntplus.h0000644000000000000000000000202214225522122020545 0ustar rootroot/* ISC License Copyright (c) 2019, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include "fuse_attr.h" #include "fuse_dirent.h" #include "fuse_entry.h" typedef struct fuse_direntplus_s fuse_direntplus_t; struct fuse_direntplus_s { fuse_entry_t entry; fuse_attr_t attr; fuse_dirent_t dirent; }; mergerfs-2.33.5/libfuse/include/linux_dirent64.h0000644000000000000000000000022514225522122020213 0ustar rootroot#pragma once #include struct linux_dirent64 { uint64_t ino; int64_t off; uint16_t reclen; uint8_t type; char name[]; }; mergerfs-2.33.5/libfuse/include/fuse_lowlevel.h0000644000000000000000000014706014225522122020221 0ustar rootroot/* 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 COPYING.LIB. */ #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 26 (recommended for any * new application), to use the old API define it to 24 (default) or * 25 */ #ifndef FUSE_USE_VERSION #define FUSE_USE_VERSION 24 #endif #include "extern_c.h" #include "fuse_common.h" #include #include #include #include #include #include #include EXTERN_C_BEGIN /* ----------------------------------------------------------- * * Miscellaneous definitions * * ----------------------------------------------------------- */ /** The node ID of the root inode */ #define FUSE_ROOT_ID 1 /** Request pointer type */ typedef struct fuse_req *fuse_req_t; /** * Session * * This provides hooks for processing requests, and exiting */ struct fuse_session; /** * Channel * * A communication channel, providing hooks for sending and receiving * messages */ struct fuse_chan; /** 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. */ uint64_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. * * The generation must be non-zero, otherwise FUSE will treat * it as an error. * */ 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; fuse_timeouts_t timeout; }; /** Additional context associated with requests */ 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 (introduced in version 2.8) */ mode_t umask; }; struct fuse_forget_data { uint64_t ino; uint64_t nlookup; }; /* ----------------------------------------------------------- * * 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. * * The filesystem sometimes needs to handle a return value of -ENOENT * from the reply function, which means, that the request was * interrupted, and the reply discarded. For example if * fuse_reply_open() return -ENOENT means, that the release method for * this file will not be called. */ struct fuse_lowlevel_ops { /** * Initialize filesystem * * Called before any other filesystem method * * There's no reply to this function * * @param userdata the user data passed to fuse_lowlevel_new() */ void (*init)(void *userdata, struct fuse_conn_info *conn); /** * Clean up filesystem * * Called on filesystem exit * * There's no reply to this function * * @param userdata the user data passed to fuse_lowlevel_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, uint64_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, remdir 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, uint64_t ino, uint64_t nlookup); /** * Get file attributes * * Valid replies: * fuse_reply_attr * fuse_reply_err * * @param req request handle * @param ino the inode number * @param fi for future use, currently always NULL */ void (*getattr)(fuse_req_t req, uint64_t ino, fuse_file_info_t *fi); /** * Set file attributes * * In the 'attr' argument only members indicated by the 'to_set' * bitmask contain valid values. Other members contain undefined * values. * * 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 * * Changed in version 2.5: * file information filled in for ftruncate */ void (*setattr)(fuse_req_t req, uint64_t ino, struct stat *attr, int to_set, fuse_file_info_t *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, uint64_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, uint64_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, uint64_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, uint64_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, uint64_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, uint64_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). * * 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, uint64_t parent, const char *name, uint64_t newparent, const char *newname); /** * 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, uint64_t ino, uint64_t newparent, const char *newname); /** * Open a file * * Open flags (with the exception of O_CREAT, O_EXCL, O_NOCTTY and * O_TRUNC) are available in fi->flags. * * 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. * * 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, uint64_t ino, fuse_file_info_t *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, uint64_t ino, size_t size, off_t off, fuse_file_info_t *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. * * 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, uint64_t ino, const char *buf, size_t size, off_t off, fuse_file_info_t *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. * * If the filesystem supports file locking operations (setlk, * getlk) it should remove all locks belonging to 'fi->owner'. * * Valid replies: * fuse_reply_err * * @param req request handle * @param ino the inode number * @param fi file information */ void (*flush)(fuse_req_t req, uint64_t ino, fuse_file_info_t *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. * * 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, uint64_t ino, fuse_file_info_t *fi); /** * Synchronize file contents * * If the datasync parameter is non-zero, then only the user data * should be flushed, not the meta data. * * 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, uint64_t ino, int datasync, fuse_file_info_t *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). * * Filesystem may also implement stateless directory I/O and not * store anything in fi->fh, though that makes it impossible to * implement standard conforming directory stream operations in * case the contents of the directory can change between opendir * and releasedir. * * 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, uint64_t ino, fuse_file_info_t *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. * * 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, uint64_t ino, size_t size, off_t off, fuse_file_info_t *llffi); void (*readdir_plus)(fuse_req_t req, uint64_t ino, size_t size, off_t off, fuse_file_info_t *ffi); /** * Release an open directory * * For every opendir call there will be exactly one releasedir * call. * * 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, uint64_t ino, fuse_file_info_t *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. * * 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, uint64_t ino, int datasync, fuse_file_info_t *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, uint64_t ino); /** * Set an extended attribute * * Valid replies: * fuse_reply_err */ void (*setxattr)(fuse_req_t req, uint64_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. * * 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, uint64_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. * * 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, uint64_t ino, size_t size); /** * Remove an extended attribute * * 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, uint64_t ino, const char *name); /** * 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 * * Introduced in version 2.5 * * 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, uint64_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. * * Open flags (with the exception of O_NOCTTY) are available in * fi->flags. * * 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). * * 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 method is not implemented or under Linux kernel * versions earlier than 2.6.15, the mknod() and open() methods * will be called instead. * * Introduced in version 2.5 * * 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, uint64_t parent, const char *name, mode_t mode, fuse_file_info_t *fi); /** * Test for a POSIX file lock * * Introduced in version 2.6 * * 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, uint64_t ino, fuse_file_info_t *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. * * Introduced in version 2.6 * * 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, uint64_t ino, fuse_file_info_t *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 * * Introduced in version 2.6 * * 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, uint64_t ino, size_t blocksize, uint64_t idx); /** * 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. * * Introduced in version 2.8 * * 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 */ void (*ioctl)(fuse_req_t req, uint64_t ino, unsigned long cmd, void *arg, fuse_file_info_t *fi, unsigned flags, const void *in_buf, uint32_t in_bufsz, uint32_t out_bufsz); /** * Poll for IO readiness * * Introduced in version 2.8 * * Note: If ph is non-NULL, the client should notify * when IO readiness events occur by calling * fuse_lowelevel_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. * * 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, uint64_t ino, fuse_file_info_t *fi, fuse_pollhandle_t *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]). * * Introduced in version 2.9 * * 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, uint64_t ino, struct fuse_bufvec *bufv, off_t off, fuse_file_info_t *fi); /** * Callback function for the retrieve request * * Introduced in version 2.9 * * 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, uint64_t ino, off_t offset, struct fuse_bufvec *bufv); /** * Forget about multiple inodes * * See description of the forget function for more * information. * * Introduced in version 2.9 * * 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. * * Introduced in version 2.9 * * 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, uint64_t ino, fuse_file_info_t *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. * * Introduced in version 2.9 * * 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, uint64_t ino, int mode, off_t offset, off_t length, fuse_file_info_t *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 of 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 of 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, uint64_t ino_in, off_t off_in, fuse_file_info_t *fi_in, uint64_t ino_out, off_t off_out, fuse_file_info_t *fi_out, size_t len, int flags); }; /** * Reply with an error code or success * * Possible requests: * all except forget * * unlink, rmdir, rename, flush, release, fsync, fsyncdir, setxattr, * removexattr and setlk may send a zero code * * @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 * * @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 * * 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 fuse_file_info_t *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, const uint64_t 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); /** * Reply with open parameters * * currently the following members of 'fi' are used: * fh, direct_io, keep_cache * * 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 fuse_file_info_t *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) * * Possible requests: * read, readdir, getxattr, listxattr * * @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); /** * 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, uint32_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); /* ----------------------------------------------------------- * * 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(fuse_pollhandle_t *ph); /** * Notify to invalidate cache for an inode * * @param ch the channel through which to send the invalidation * @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_chan *ch, uint64_t ino, off_t off, off_t len); /** * Notify to invalidate parent attributes and the dentry matching * parent/name * * To avoid a deadlock don't call this function from a filesystem operation and * don't call it with a lock held that can also be held by a filesystem * operation. * * @param ch the channel through which to send the invalidation * @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_chan *ch, uint64_t parent, const char *name, size_t namelen); /** * Notify to invalidate parent attributes and delete the dentry matching * parent/name if the dentry's inode number matches child (otherwise it * will invalidate the matching dentry). * * To avoid a deadlock don't call this function from a filesystem operation and * don't call it with a lock held that can also be held by a filesystem * operation. * * @param ch the channel through which to send the notification * @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_chan *ch, uint64_t parent, uint64_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. * * @param ch the channel through which to send the invalidation * @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_chan *ch, uint64_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. * * @param ch the channel through which to send the invalidation * @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_chan *ch, uint64_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_lowlevel_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[]); /* ----------------------------------------------------------- * * Filesystem setup * * ----------------------------------------------------------- */ /* Deprecated, don't use */ int fuse_lowlevel_is_lib_option(const char *opt); /** * Create a low level session * * @param args argument vector * @param op the low level filesystem operations * @param op_size sizeof(struct fuse_lowlevel_ops) * @param userdata user data * @return the created session object, or NULL on failure */ struct fuse_session *fuse_lowlevel_new(struct fuse_args *args, const struct fuse_lowlevel_ops *op, size_t op_size, void *userdata); /* ----------------------------------------------------------- * * Session interface * * ----------------------------------------------------------- */ /** * Session operations * * This is used in session creation */ struct fuse_session_ops { /** * Hook to process a request (mandatory) * * @param data user data passed to fuse_session_new() * @param buf buffer containing the raw request * @param len request length * @param ch channel on which the request was received */ void (*process)(void *data, const char *buf, size_t len, struct fuse_chan *ch); /** * Hook for session exit and reset (optional) * * @param data user data passed to fuse_session_new() * @param val exited status (1 - exited, 0 - not exited) */ void (*exit)(void *data, int val); /** * Hook for querying the current exited status (optional) * * @param data user data passed to fuse_session_new() * @return 1 if exited, 0 if not exited */ int (*exited)(void *data); /** * Hook for cleaning up the channel on destroy (optional) * * @param data user data passed to fuse_session_new() */ void (*destroy)(void *data); }; /** * Create a new session * * @param op session operations * @param data user data * @return new session object, or NULL on failure */ struct fuse_session *fuse_session_new(struct fuse_session_ops *op, void *data); /** * Assign a channel to a session * * Note: currently only a single channel may be assigned. This may * change in the future * * If a session is destroyed, the assigned channel is also destroyed * * @param se the session * @param ch the channel */ void fuse_session_add_chan(struct fuse_session *se, struct fuse_chan *ch); /** * Remove a channel from a session * * If the channel is not assigned to a session, then this is a no-op * * @param ch the channel to remove */ void fuse_session_remove_chan(struct fuse_chan *ch); /** * Iterate over the channels assigned to a session * * The iterating function needs to start with a NULL channel, and * after that needs to pass the previously returned channel to the * function. * * @param se the session * @param ch the previous channel, or NULL * @return the next channel, or NULL if no more channels exist */ struct fuse_chan *fuse_session_next_chan(struct fuse_session *se, struct fuse_chan *ch); /** * Process a raw request * * @param se the session * @param buf buffer containing the raw request * @param len request length * @param ch channel on which the request was received */ void fuse_session_process(struct fuse_session *se, const char *buf, size_t len, struct fuse_chan *ch); /** * Process a raw request supplied in a generic buffer * * This is a more generic version of fuse_session_process(). 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 * @param ch channel on which the request was received */ void fuse_session_process_buf(struct fuse_session *se, const struct fuse_buf *buf, struct fuse_chan *ch); /** * Receive a raw request supplied in a generic buffer * * This is a more generic version of fuse_chan_recv(). The fuse_buf * supplied to this function contains a suitably allocated memory * buffer. This may be overwritten with a file descriptor buffer. * * @param se the session * @param buf the fuse_buf to store the request in * @param chp pointer to the channel * @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, struct fuse_chan **chp); /** * Destroy a session * * @param se the session */ void fuse_session_destroy(struct fuse_session *se); /** * Exit a session * * @param se the session */ void fuse_session_exit(struct fuse_session *se); /** * Reset the exited status of a session * * @param se the session */ void fuse_session_reset(struct fuse_session *se); /** * Query the exited status of a session * * @param se the session * @return 1 if exited, 0 if not exited */ int fuse_session_exited(struct fuse_session *se); /** * Get the user data provided to the session * * @param se the session * @return the user data */ void *fuse_session_data(struct fuse_session *se); /** * Enter a multi-threaded event loop * * @param se the session * @return 0 on success, -1 on error */ int fuse_session_loop_mt(struct fuse_session *se, const int threads); /* ----------------------------------------------------------- * * Channel interface * * ----------------------------------------------------------- */ /** * Channel operations * * This is used in channel creation */ struct fuse_chan_ops { /** * Hook for receiving a raw request * * @param ch pointer to the channel * @param buf the buffer to store the request in * @param size the size of the buffer * @return the actual size of the raw request, or -1 on error */ int (*receive)(struct fuse_chan **chp, char *buf, size_t size); /** * Hook for sending a raw reply * * A return value of -ENOENT means, that the request was * interrupted, and the reply was discarded * * @param ch the channel * @param iov vector of blocks * @param count the number of blocks in vector * @return zero on success, -errno on failure */ int (*send)(struct fuse_chan *ch, const struct iovec iov[], size_t count); /** * Destroy the channel * * @param ch the channel */ void (*destroy)(struct fuse_chan *ch); }; /** * Create a new channel * * @param op channel operations * @param fd file descriptor of the channel * @param bufsize the minimal receive buffer size * @param data user data * @return the new channel object, or NULL on failure */ struct fuse_chan *fuse_chan_new(struct fuse_chan_ops *op, int fd, size_t bufsize, void *data); /** * Query the file descriptor of the channel * * @param ch the channel * @return the file descriptor passed to fuse_chan_new() */ int fuse_chan_fd(struct fuse_chan *ch); /** * Query the minimal receive buffer size * * @param ch the channel * @return the buffer size passed to fuse_chan_new() */ size_t fuse_chan_bufsize(struct fuse_chan *ch); /** * Query the user data * * @param ch the channel * @return the user data passed to fuse_chan_new() */ void *fuse_chan_data(struct fuse_chan *ch); /** * Query the session to which this channel is assigned * * @param ch the channel * @return the session, or NULL if the channel is not assigned */ struct fuse_session *fuse_chan_session(struct fuse_chan *ch); /** * Receive a raw request * * A return value of -ENODEV means, that the filesystem was unmounted * * @param ch pointer to the channel * @param buf the buffer to store the request in * @param size the size of the buffer * @return the actual size of the raw request, or -errno on error */ int fuse_chan_recv(struct fuse_chan **ch, char *buf, size_t size); /** * Send a raw reply * * A return value of -ENOENT means, that the request was * interrupted, and the reply was discarded * * @param ch the channel * @param iov vector of blocks * @param count the number of blocks in vector * @return zero on success, -errno on failure */ int fuse_chan_send(struct fuse_chan *ch, const struct iovec iov[], size_t count); /** * Destroy a channel * * @param ch the channel */ void fuse_chan_destroy(struct fuse_chan *ch); EXTERN_C_END #endif /* _FUSE_LOWLEVEL_H_ */ mergerfs-2.33.5/libfuse/include/extern_c.h0000644000000000000000000000022514225522122017144 0ustar rootroot#pragma once #ifdef __cplusplus #define EXTERN_C_BEGIN extern "C" { #define EXTERN_C_END } #else #define EXTERN_C_BEGIN #define EXTERN_C_END #endif mergerfs-2.33.5/libfuse/include/stat_utils.h0000644000000000000000000000100214225522122017522 0ustar rootroot#pragma once #include "config.h" #ifdef HAVE_STRUCT_STAT_ST_ATIM #define ST_ATIM_NSEC(ST) ((ST)->st_atim.tv_nsec) #define ST_CTIM_NSEC(ST) ((ST)->st_ctim.tv_nsec) #define ST_MTIM_NSEC(ST) ((ST)->st_mtim.tv_nsec) #elif defined HAVE_STRUCT_STAT_ST_ATIMESPEC #define ST_ATIM_NSEC(ST) ((ST)->st_atimespec.tv_nsec) #define ST_CTIM_NSEC(ST) ((ST)->st_ctimespec.tv_nsec) #define ST_MTIM_NSEC(ST) ((ST)->st_mtimespec.tv_nsec) #else #define ST_ATIM_NSEC(ST) 0 #define ST_CTIM_NSEC(ST) 0 #define ST_MTIM_NSEC(ST) 0 #endif mergerfs-2.33.5/libfuse/include/fuse.h0000644000000000000000000007174714225522122016320 0ustar rootroot/* 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 COPYING.LIB. */ #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. To use the newest API define it to 26 (recommended for any * new application), to use the old API define it to 21 (default) 22 * or 25, to use the even older 1.X API define it to 11. */ #ifndef FUSE_USE_VERSION #define FUSE_USE_VERSION 21 #endif #include "extern_c.h" #include "fuse_common.h" #include #include #include #include #include #include #include EXTERN_C_BEGIN /* ----------------------------------------------------------- * * Basic FUSE API * * ----------------------------------------------------------- */ /** Handle for a FUSE filesystem */ struct fuse; /** Structure containing a raw command */ struct fuse_cmd; struct fuse_dirents_t; typedef struct fuse_dirents_t fuse_dirents_t; /** * 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, ftruncate, fgetattr, lock, * init and destroy are special purpose methods, without which a full * featured filesystem can still be implemented. * * Almost all operations take a path which can be of any length. * * Changed in fuse 2.8.0 (regardless of API version) * Previously, paths were limited to a length of PATH_MAX. * * See http://fuse.sourceforge.net/wiki/ for more information. There * is also a snapshot of the relevant wiki pages in the doc/ folder. */ 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. */ int (*getattr) (const char *, struct stat *, fuse_timeouts_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. */ 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); /** Hide files unlinked / renamed over * * Allows storing of a file handle when a file is unlinked * while open. Helps manage the fact the kernel usually does * not send fh with getattr requests. */ int (*prepare_hide)(const char *name_, uint64_t *fh_); int (*free_hide)(const uint64_t fh_); /** Remove a file */ int (*unlink) (const char *); /** Remove a directory */ int (*rmdir) (const char *); /** Create a symbolic link */ int (*symlink) (const char *, const char *, struct stat *, fuse_timeouts_t *); /** Rename a file */ int (*rename) (const char *, const char *); /** Create a hard link to a file */ int (*link) (const char *, const char *, struct stat *, fuse_timeouts_t *); /** Change the permission bits of a file */ int (*chmod) (const char *, mode_t); int (*fchmod)(const fuse_file_info_t *, const mode_t); /** Change the owner and group of a file */ int (*chown) (const char *, uid_t, gid_t); int (*fchown)(const fuse_file_info_t *, const uid_t, const gid_t); /** Change the size of a file */ int (*truncate) (const char *, off_t); /** File open operation * * No creation (O_CREAT, O_EXCL) and by default also no * truncation (O_TRUNC) flags will be passed to open(). If an * application specifies O_TRUNC, fuse first calls truncate() * and then open(). Only if 'atomic_o_trunc' has been * specified and kernel version is 2.6.24 or later, O_TRUNC is * passed on to open. * * Unless the 'default_permissions' mount option is given, * open should check if the operation is permitted for the * given flags. Optionally open may also return an arbitrary * filehandle in the fuse_file_info structure, which will be * passed to all file operations. * * Changed in version 2.2 */ int (*open) (const char *, fuse_file_info_t *); /** Get file system statistics * * The 'f_frsize', 'f_favail', 'f_fsid' and 'f_flag' fields are ignored * * Replaced 'struct statfs' parameter with 'struct statvfs' in * version 2.5 */ 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. So if a * filesystem wants to return write errors in close() and the file * has cached dirty data, this is a good place to write back data * and return any errors. Since many applications ignore close() * errors this is not always useful. * * NOTE: The flush() method may be called more than once for each * open(). This happens if more than one file descriptor refers * to an opened file 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 always be called * after some writes, or that if will be called at all. * * Changed in version 2.2 */ int (*flush) (const fuse_file_info_t *); /** 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 descriptor. 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. * * Changed in version 2.2 */ int (*release) (const fuse_file_info_t *); /** Synchronize file contents * * If the datasync parameter is non-zero, then only the user data * should be flushed, not the meta data. * * Changed in version 2.2 */ int (*fsync) (const fuse_file_info_t *, int); /** 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, closedir and fsyncdir. * * Introduced in version 2.3 */ int (*opendir) (const char *, fuse_file_info_t *); /** Read directory * * This supersedes the old getdir() interface. New applications * should use this. * * 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. This * works just like the old getdir() method. * * 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'. * * Introduced in version 2.3 */ int (*readdir)(const fuse_file_info_t *, fuse_dirents_t *); int (*readdir_plus)(const fuse_file_info_t *, fuse_dirents_t *); /** Release directory * * Introduced in version 2.3 */ int (*releasedir) (const fuse_file_info_t *); /** Synchronize directory contents * * If the datasync parameter is non-zero, then only the user data * should be flushed, not the meta data * * Introduced in version 2.3 */ int (*fsyncdir) (const fuse_file_info_t *, int); /** * Initialize filesystem * * The return value will passed in the private_data field of * fuse_context to all file operations and as a parameter to the * destroy() method. * * Introduced in version 2.3 * Changed in version 2.6 */ void *(*init) (struct fuse_conn_info *conn); /** * Clean up filesystem * * Called on filesystem exit. * * Introduced in version 2.3 */ void (*destroy) (void); /** * 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 * * Introduced in version 2.5 */ 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. * * Introduced in version 2.5 */ int (*create) (const char *, mode_t, fuse_file_info_t *); /** * Change the size of an open file * * This method is called instead of the truncate() method if the * truncation was invoked from an ftruncate() system call. * * If this method is not implemented or under Linux kernel * versions earlier than 2.6.15, the truncate() method will be * called instead. * * Introduced in version 2.5 */ int (*ftruncate) (const fuse_file_info_t *, off_t); /** * Get attributes from an open file * * This method is called instead of the getattr() method if the * file information is available. * * Currently this is only called after the create() method if that * is implemented (see above). Later it may be called for * invocations of fstat() too. * * Introduced in version 2.5 */ int (*fgetattr) (const fuse_file_info_t *, struct stat *, fuse_timeouts_t *); /** * 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. * * Introduced in version 2.6 */ int (*lock) (const fuse_file_info_t *, 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. * * See the utimensat(2) man page for details. * * Introduced in version 2.6 */ int (*utimens)(const char *, const struct timespec tv[2]); int (*futimens)(const fuse_file_info_t *ffi_, const struct timespec tv_[2]); /** * 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 * * Introduced in version 2.6 */ int (*bmap) (const char *, size_t blocksize, uint64_t *idx); /** * 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. * * Introduced in version 2.8 */ int (*ioctl) (const fuse_file_info_t *ffi, unsigned long cmd, void *arg, unsigned int flags, void *data, uint32_t *out_bufsz); /** * 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. * * Introduced in version 2.8 */ int (*poll) (const fuse_file_info_t *ffi, fuse_pollhandle_t *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. * * Introduced in version 2.9 */ int (*write_buf) (const fuse_file_info_t *ffi, struct fuse_bufvec *buf, off_t off); /** 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. * * Introduced in version 2.9 */ int (*read_buf) (const fuse_file_info_t *ffi, struct fuse_bufvec **bufp, size_t size, off_t off); /** * 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. * * Introduced in version 2.9 */ int (*flock) (const fuse_file_info_t *, 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. * * Introduced in version 2.9.1 */ int (*fallocate) (const fuse_file_info_t *, int, off_t, off_t); /** * 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. */ ssize_t (*copy_file_range)(const fuse_file_info_t *fi_in, off_t offset_in, const fuse_file_info_t *fi_out, off_t offset_out, size_t size, int flags); }; /** 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; /** Thread ID of the calling process */ pid_t pid; /** Umask of the calling process (introduced in version 2.8) */ mode_t umask; }; /** * 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 (-d -s and -h) * - passes relevant mount options to the fuse_mount() * - 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 * * Note: this is currently implemented as a macro. * * @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 * @return 0 on success, nonzero on failure */ /* int fuse_main(int argc, char *argv[], const struct fuse_operations *op); */ #define fuse_main(argc, argv, op) \ fuse_main_real(argc, argv, op, sizeof(*(op))) /* ----------------------------------------------------------- * * More detailed API * * ----------------------------------------------------------- */ /** * Create a new FUSE filesystem. * * @param ch the communication channel * @param args argument vector * @param op the filesystem operations * @param op_size the size of the fuse_operations structure * @return the created FUSE handle */ struct fuse *fuse_new(struct fuse_chan *ch, struct fuse_args *args, const struct fuse_operations *op, size_t op_size); /** * Destroy the FUSE handle. * * The communication channel attached to the handle is also destroyed. * * 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); /** * Exit from event loop * * @param f the FUSE handle */ void fuse_exit(struct fuse *f); int fuse_config_num_threads(const struct fuse *fuse_); /** * 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. * * Calling this function requires the pthreads library to be linked to * the application. * * @param f the FUSE handle * @return 0 if no error occurred, -1 otherwise */ int fuse_loop_mt(struct fuse *f); /** * 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); /** * Check if the current request has already been interrupted * * @return 1 if the request has been interrupted, 0 otherwise */ int fuse_interrupted(void); /** * Obsolete, doesn't do anything * * @return -EINVAL */ int fuse_invalidate(struct fuse *f, const char *path); /* Deprecated, don't use */ int fuse_is_lib_option(const char *opt); /** * The real main function * * Do not call this directly, use fuse_main() */ int fuse_main_real(int argc, char *argv[], const struct fuse_operations *op, size_t op_size); int fuse_start_maintenance_thread(struct fuse *fuse); void fuse_stop_maintenance_thread(struct fuse *fuse); int fuse_log_metrics_get(void); void fuse_log_metrics_set(int enabled); /** * 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, fuse_timeouts_t *timeout); int fuse_fs_fgetattr(struct fuse_fs *fs, struct stat *buf, fuse_file_info_t *fi, fuse_timeouts_t *timeout); int fuse_fs_rename(struct fuse_fs *fs, const char *oldpath, const char *newpath); 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, struct stat *st, fuse_timeouts_t *timeouts); int fuse_fs_link(struct fuse_fs *fs, const char *oldpath, const char *newpath, struct stat *st, fuse_timeouts_t *timeouts); int fuse_fs_release(struct fuse_fs *fs, fuse_file_info_t *fi); int fuse_fs_open(struct fuse_fs *fs, const char *path, fuse_file_info_t *fi); int fuse_fs_read_buf(struct fuse_fs *fs, struct fuse_bufvec **bufp, size_t size, off_t off, fuse_file_info_t *fi); int fuse_fs_write_buf(struct fuse_fs *fs, struct fuse_bufvec *buf, off_t off, fuse_file_info_t *fi); int fuse_fs_fsync(struct fuse_fs *fs, int datasync, fuse_file_info_t *fi); int fuse_fs_flush(struct fuse_fs *fs, fuse_file_info_t *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, fuse_file_info_t *fi); int fuse_fs_readdir(struct fuse_fs *fs, fuse_file_info_t *fi, fuse_dirents_t *buf); int fuse_fs_fsyncdir(struct fuse_fs *fs, int datasync, fuse_file_info_t *fi); int fuse_fs_releasedir(struct fuse_fs *fs, fuse_file_info_t *fi); int fuse_fs_create(struct fuse_fs *fs, const char *path, mode_t mode, fuse_file_info_t *fi); int fuse_fs_lock(struct fuse_fs *fs, fuse_file_info_t *fi, int cmd, struct flock *lock); int fuse_fs_flock(struct fuse_fs *fs, fuse_file_info_t *fi, int op); int fuse_fs_chmod(struct fuse_fs *fs, const char *path, mode_t mode); int fuse_fs_chown(struct fuse_fs *fs, const char *path, uid_t uid, gid_t gid); int fuse_fs_truncate(struct fuse_fs *fs, const char *path, off_t size); int fuse_fs_ftruncate(struct fuse_fs *fs, off_t size, fuse_file_info_t *fi); int fuse_fs_utimens(struct fuse_fs *fs, const char *path, const struct timespec tv[2]); 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); int fuse_fs_ioctl(struct fuse_fs *fs, unsigned long cmd, void *arg, fuse_file_info_t *fi, unsigned int flags, void *data, uint32_t *out_bufsz); int fuse_fs_poll(struct fuse_fs *fs, fuse_file_info_t *fi, fuse_pollhandle_t *ph, unsigned *reventsp); int fuse_fs_fallocate(struct fuse_fs *fs, int mode, off_t offset, off_t length, fuse_file_info_t *fi); void fuse_fs_init(struct fuse_fs *fs, struct fuse_conn_info *conn); void fuse_fs_destroy(struct fuse_fs *fs); int fuse_fs_prepare_hide(struct fuse_fs *fs, const char *path, uint64_t *fh); int fuse_fs_free_hide(struct fuse_fs *fs, uint64_t fh); ssize_t fuse_fs_copy_file_range(struct fuse_fs *fs, fuse_file_info_t *fi_in, off_t off_in, fuse_file_info_t *fi_out, off_t off_out, size_t len, int flags); int fuse_notify_poll(fuse_pollhandle_t *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 * @return a new filesystem object */ struct fuse_fs *fuse_fs_new(const struct fuse_operations *op, size_t op_size); /* ----------------------------------------------------------- * * Advanced API for event handling, don't worry about this... * * ----------------------------------------------------------- */ /* NOTE: the following functions are deprecated, and will be removed from the 3.0 API. Use the lowlevel session functions instead */ /** Function type used to process commands */ typedef void (*fuse_processor_t)(struct fuse *, struct fuse_cmd *, void *); /** This is the part of fuse_main() before the event loop */ struct fuse *fuse_setup(int argc, char *argv[], const struct fuse_operations *op, size_t op_size, char **mountpoint); /** This is the part of fuse_main() after the event loop */ void fuse_teardown(struct fuse *fuse, char *mountpoint); /** Read a single command. If none are read, return NULL */ struct fuse_cmd *fuse_read_cmd(struct fuse *f); /** Process a single command */ void fuse_process_cmd(struct fuse *f, struct fuse_cmd *cmd); /** Multi threaded event loop, which calls the custom command processor function */ int fuse_loop_mt_proc(struct fuse *f, fuse_processor_t proc, void *data); /** Return the exited flag, which indicates if fuse_exit() has been called */ int fuse_exited(struct fuse *f); /** This function is obsolete and implemented as a no-op */ void fuse_set_getcontext_func(struct fuse_context *(*func)(void)); /** Get session from fuse object */ struct fuse_session *fuse_get_session(struct fuse *f); EXTERN_C_END #endif /* _FUSE_H_ */ mergerfs-2.33.5/libfuse/include/fuse_dirent.h0000644000000000000000000000174514225522122017654 0ustar rootroot/* ISC License Copyright (c) 2019, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include typedef struct fuse_dirent_s fuse_dirent_t; struct fuse_dirent_s { uint64_t ino; uint64_t off; uint32_t namelen; uint32_t type; char name[]; }; mergerfs-2.33.5/libfuse/include/fuse_timeouts.h0000644000000000000000000000022214225522122020225 0ustar rootroot#pragma once #include typedef struct fuse_timeouts_s fuse_timeouts_t; struct fuse_timeouts_s { uint64_t entry; uint64_t attr; }; mergerfs-2.33.5/libfuse/include/fuse_kernel.h0000644000000000000000000004775714225522122017664 0ustar rootroot/* 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 */ #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 31 /** 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 padding; }; 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) /** * 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) */ #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) /** * 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: map_alignment field is valid */ #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) /** * 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_PRIV: kill suid and sgid bits */ #define FUSE_WRITE_CACHE (1 << 0) #define FUSE_WRITE_LOCKOWNER (1 << 1) #define FUSE_WRITE_KILL_PRIV (1 << 2) /** * 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) 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, /* 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_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; }; #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 unused; }; struct fuse_create_in { uint32_t flags; uint32_t mode; uint32_t umask; uint32_t padding; }; struct fuse_open_out { uint64_t fh; uint32_t open_flags; uint32_t padding; }; 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; }; struct fuse_setxattr_in { uint32_t size; uint32_t flags; }; 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; }; #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 unused[8]; }; #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; }; 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; uint32_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[]; }; #define FUSE_NAME_OFFSET offsetof(struct fuse_dirent, name) #define FUSE_DIRENT_ALIGN(x) \ (((x) + sizeof(uint64_t) - 1) & ~(sizeof(uint64_t) - 1)) #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 padding; }; 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; }; /* Device ioctls: */ #define FUSE_DEV_IOC_CLONE _IOR(229, 0, 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; }; #endif /* _LINUX_FUSE_H */ mergerfs-2.33.5/libfuse/lib/0000755000000000000000000000000014225522122014310 5ustar rootrootmergerfs-2.33.5/libfuse/lib/fuse_misc.h0000644000000000000000000000410314225522122016434 0ustar rootroot/* 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 COPYING.LIB */ #pragma once #include "config.h" #include /* Versioned symbols cannot be used in some cases because it - confuse the dynamic linker in uClibc - not supported on MacOSX (in MachO binary format) */ #if (!defined(__UCLIBC__) && !defined(__APPLE__)) #define FUSE_SYMVER(x) __asm__(x) #else #define FUSE_SYMVER(x) #endif #ifndef USE_UCLIBC #define fuse_mutex_init(mut) pthread_mutex_init(mut, NULL) #else /* Is this hack still needed? */ static inline void fuse_mutex_init(pthread_mutex_t *mut) { pthread_mutexattr_t attr; pthread_mutexattr_init(&attr); pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_ADAPTIVE_NP); pthread_mutex_init(mut, &attr); pthread_mutexattr_destroy(&attr); } #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 mergerfs-2.33.5/libfuse/lib/fuse_mt.c0000644000000000000000000000523614225522122016124 0ustar rootroot/* 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 COPYING.LIB. */ #include "fuse_i.h" #include "fuse_misc.h" #include "fuse_lowlevel.h" #include #include #include #include #include struct procdata { struct fuse *f; struct fuse_chan *prevch; struct fuse_session *prevse; fuse_processor_t proc; void *data; }; static void mt_session_proc(void *data, const char *buf, size_t len, struct fuse_chan *ch) { struct procdata *pd = (struct procdata *) data; struct fuse_cmd *cmd = *(struct fuse_cmd **) buf; (void) len; (void) ch; pd->proc(pd->f, cmd, pd->data); } static void mt_session_exit(void *data, int val) { struct procdata *pd = (struct procdata *) data; if (val) fuse_session_exit(pd->prevse); else fuse_session_reset(pd->prevse); } static int mt_session_exited(void *data) { struct procdata *pd = (struct procdata *) data; return fuse_session_exited(pd->prevse); } static int mt_chan_receive(struct fuse_chan **chp, char *buf, size_t size) { struct fuse_cmd *cmd; struct procdata *pd = (struct procdata *) fuse_chan_data(*chp); assert(size >= sizeof(cmd)); cmd = fuse_read_cmd(pd->f); if (cmd == NULL) return 0; *(struct fuse_cmd **)buf = cmd; return sizeof(cmd); } int fuse_loop_mt_proc(struct fuse *f, fuse_processor_t proc, void *data) { int res; struct procdata pd; struct fuse_session *prevse = fuse_get_session(f); struct fuse_session *se; struct fuse_chan *prevch = fuse_session_next_chan(prevse, NULL); struct fuse_chan *ch; struct fuse_session_ops sop = { .exit = mt_session_exit, .exited = mt_session_exited, .process = mt_session_proc, }; struct fuse_chan_ops cop = { .receive = mt_chan_receive, }; pd.f = f; pd.prevch = prevch; pd.prevse = prevse; pd.proc = proc; pd.data = data; se = fuse_session_new(&sop, &pd); if (se == NULL) return -1; ch = fuse_chan_new(&cop, fuse_chan_fd(prevch), sizeof(struct fuse_cmd *), &pd); if (ch == NULL) { fuse_session_destroy(se); return -1; } fuse_session_add_chan(se, ch); res = fuse_session_loop_mt(se, fuse_config_num_threads(f)); fuse_session_destroy(se); return res; } int fuse_loop_mt(struct fuse *f) { if (f == NULL) return -1; int res = fuse_start_maintenance_thread(f); if (res) return -1; res = fuse_session_loop_mt(fuse_get_session(f), fuse_config_num_threads(f)); fuse_stop_maintenance_thread(f); return res; } mergerfs-2.33.5/libfuse/lib/fuse_i.h0000644000000000000000000000507014225522122015735 0ustar rootroot/* 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 COPYING.LIB */ #pragma once #include "fuse.h" #include "fuse_lowlevel.h" struct fuse_chan; struct fuse_ll; struct fuse_session { struct fuse_session_ops op; int (*receive_buf)(struct fuse_session *se, struct fuse_buf *buf, struct fuse_chan **chp); void (*process_buf)(void *data, const struct fuse_buf *buf, struct fuse_chan *ch); void *data; volatile int exited; struct fuse_chan *ch; }; struct fuse_req { struct fuse_ll *f; uint64_t unique; struct fuse_ctx ctx; struct fuse_chan *ch; unsigned int ioctl_64bit : 1; }; struct fuse_notify_req { uint64_t unique; void (*reply)(struct fuse_notify_req *, fuse_req_t, uint64_t, const void *, const struct fuse_buf *); struct fuse_notify_req *next; struct fuse_notify_req *prev; }; struct fuse_ll { int debug; int no_remote_posix_lock; int no_remote_flock; int big_writes; int splice_write; int splice_move; int splice_read; int no_splice_write; int no_splice_move; int no_splice_read; struct fuse_lowlevel_ops op; int got_init; void *userdata; uid_t owner; struct fuse_conn_info conn; 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; }; struct fuse_cmd { char *buf; size_t buflen; struct fuse_chan *ch; }; struct fuse *fuse_new_common(struct fuse_chan *ch, struct fuse_args *args, const struct fuse_operations *op, size_t op_size); struct fuse_chan *fuse_kern_chan_new(int fd); struct fuse_session *fuse_lowlevel_new_common(struct fuse_args *args, const struct fuse_lowlevel_ops *op, size_t op_size, void *userdata); int fuse_chan_clearfd(struct fuse_chan *ch); void fuse_kern_unmount(const char *mountpoint, int fd); int fuse_kern_mount(const char *mountpoint, struct fuse_args *args); 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); struct fuse *fuse_setup_common(int argc, char *argv[], const struct fuse_operations *op, size_t op_size, char **mountpoint, int *fd); int fuse_start_thread(pthread_t *thread_id, void *(*func)(void *), void *arg); mergerfs-2.33.5/libfuse/lib/fuse_node.h0000644000000000000000000000246214225522122016434 0ustar rootroot#include typedef struct fuse_node_t fuse_node_t; struct fuse_node_t { uint64_t id; uint64_t generation; char *name; fuse_node_t *parent; uint32_t ref_count; uint64_t lookup_count; uint64_t open_count; }; struct fuse_node_hashtable_t; typedef struct fuse_node_hashtable_t fuse_node_hashtable_t; fuse_node_hashtable_t *fuse_node_hashtable_init(); fuse_node_t *fuse_node_hashtable_put(fuse_node_hashtable_t *ht, const uint64_t parent_id, const uint64_t child_id, const char *child_name); fuse_node_t* fuse_node_hashtable_get(fuse_node_hashtable_t *ht, const uint64_t id); fuse_node_t* fuse_node_hashtable_get_child(fuse_node_hashtable_t *ht, const uint64_t id, const char *name); void fuse_node_hashtable_del(fuse_node_hashtable_t *ht, fuse_node_t *node); void fuse_node_hashtable_get_path(fuse_node_hashtable_t *ht, char *buf, uint32_t buflen); mergerfs-2.33.5/libfuse/lib/lfmp.h0000644000000000000000000001000014225522122015406 0ustar rootroot/* ISC License Copyright (c) 2020, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include "fmp.h" #include typedef struct lfmp_t lfmp_t; struct lfmp_t { fmp_t fmp; pthread_mutex_t lock; }; static inline void lfmp_init(lfmp_t *lfmp_, const uint64_t obj_size_, const uint64_t page_multiple_) { fmp_init(&lfmp_->fmp,obj_size_,page_multiple_); pthread_mutex_init(&lfmp_->lock,NULL); } static inline void lfmp_lock(lfmp_t *lfmp_) { pthread_mutex_lock(&lfmp_->lock); } static inline void lfmp_unlock(lfmp_t *lfmp_) { pthread_mutex_unlock(&lfmp_->lock); } static inline uint64_t lfmp_slab_count(lfmp_t *lfmp_) { uint64_t rv; pthread_mutex_lock(&lfmp_->lock); rv = fmp_slab_count(&lfmp_->fmp); pthread_mutex_unlock(&lfmp_->lock); return rv; } static inline int lfmp_slab_alloc(lfmp_t *lfmp_) { int rv; pthread_mutex_lock(&lfmp_->lock); rv = fmp_slab_alloc(&lfmp_->fmp); pthread_mutex_unlock(&lfmp_->lock); return rv; } static inline void* lfmp_alloc(lfmp_t *lfmp_) { void *rv; pthread_mutex_lock(&lfmp_->lock); rv = fmp_alloc(&lfmp_->fmp); pthread_mutex_unlock(&lfmp_->lock); return rv; } static inline void* lfmp_calloc(lfmp_t *lfmp_) { void *rv; pthread_mutex_lock(&lfmp_->lock); rv = fmp_calloc(&lfmp_->fmp); pthread_mutex_unlock(&lfmp_->lock); return rv; } static inline void lfmp_free(lfmp_t *lfmp_, void *obj_) { pthread_mutex_lock(&lfmp_->lock); fmp_free(&lfmp_->fmp,obj_); pthread_mutex_unlock(&lfmp_->lock); } static inline void lfmp_clear(lfmp_t *lfmp_) { pthread_mutex_lock(&lfmp_->lock); fmp_clear(&lfmp_->fmp); pthread_mutex_unlock(&lfmp_->lock); } static inline void lfmp_destroy(lfmp_t *lfmp_) { pthread_mutex_lock(&lfmp_->lock); fmp_destroy(&lfmp_->fmp); pthread_mutex_unlock(&lfmp_->lock); pthread_mutex_destroy(&lfmp_->lock); } static inline uint64_t lfmp_avail_objs(lfmp_t *lfmp_) { uint64_t rv; pthread_mutex_lock(&lfmp_->lock); rv = fmp_avail_objs(&lfmp_->fmp); pthread_mutex_unlock(&lfmp_->lock); return rv; } static inline uint64_t lfmp_objs_in_slab(lfmp_t *lfmp_, void *slab_) { uint64_t rv; pthread_mutex_lock(&lfmp_->lock); rv = fmp_objs_in_slab(&lfmp_->fmp,slab_); pthread_mutex_unlock(&lfmp_->lock); return rv; } static inline void lfmp_remove_objs_in_slab(lfmp_t *lfmp_, void *slab_) { pthread_mutex_lock(&lfmp_->lock); fmp_remove_objs_in_slab(&lfmp_->fmp,slab_); pthread_mutex_unlock(&lfmp_->lock); } static inline int lfmp_gc(lfmp_t *lfmp_) { int rv; pthread_mutex_lock(&lfmp_->lock); rv = fmp_gc(&lfmp_->fmp); pthread_mutex_unlock(&lfmp_->lock); return rv; } static inline uint64_t lfmp_objs_per_slab(lfmp_t *lfmp_) { uint64_t rv; pthread_mutex_lock(&lfmp_->lock); rv = fmp_objs_per_slab(&lfmp_->fmp); pthread_mutex_unlock(&lfmp_->lock); return rv; } static inline double lfmp_slab_usage_ratio(lfmp_t *lfmp_) { double rv; pthread_mutex_lock(&lfmp_->lock); rv = fmp_slab_usage_ratio(&lfmp_->fmp); pthread_mutex_unlock(&lfmp_->lock); return rv; } static inline uint64_t lfmp_total_allocated_memory(lfmp_t *lfmp_) { uint64_t rv; pthread_mutex_lock(&lfmp_->lock); rv = fmp_total_allocated_memory(&lfmp_->fmp); pthread_mutex_unlock(&lfmp_->lock); return rv; } mergerfs-2.33.5/libfuse/lib/fuse.c0000644000000000000000000025434414225522122015432 0ustar rootroot/* 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 COPYING.LIB */ /* For pthread_rwlock_t */ #define _GNU_SOURCE #include "crc32b.h" #include "fuse_node.h" #include "khash.h" #include "kvec.h" #include "lfmp.h" #include "config.h" #include "fuse_dirents.h" #include "fuse_i.h" #include "fuse_kernel.h" #include "fuse_lowlevel.h" #include "fuse_misc.h" #include "fuse_opt.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_MALLOC_TRIM #include #endif #define FUSE_UNKNOWN_INO UINT64_MAX #define OFFSET_MAX 0x7fffffffffffffffLL #define NODE_TABLE_MIN_SIZE 8192 static int g_LOG_METRICS = 0; struct fuse_config { unsigned int uid; unsigned int gid; unsigned int umask; int remember; int debug; int nogc; int use_ino; int set_mode; int set_uid; int set_gid; int help; int threads; }; struct fuse_fs { struct fuse_operations op; }; struct lock_queue_element { struct lock_queue_element *next; pthread_cond_t cond; uint64_t nodeid1; const char *name1; char **path1; struct node **wnode1; uint64_t nodeid2; const char *name2; char **path2; struct node **wnode2; int err; bool first_locked : 1; bool second_locked : 1; bool done : 1; }; struct node_table { struct node **array; size_t use; size_t size; size_t split; }; #define container_of(ptr,type,member) ({ \ const typeof( ((type *)0)->member ) *__mptr = (ptr); \ (type *)( (char *)__mptr - offsetof(type,member) );}) #define list_entry(ptr,type,member) \ container_of(ptr,type,member) struct list_head { struct list_head *next; struct list_head *prev; }; typedef struct remembered_node_t remembered_node_t; struct remembered_node_t { struct node *node; time_t time; }; typedef struct nodeid_gen_t nodeid_gen_t; struct nodeid_gen_t { uint64_t nodeid; uint64_t generation; }; struct fuse { struct fuse_session *se; struct node_table name_table; struct node_table id_table; nodeid_gen_t nodeid_gen; unsigned int hidectr; pthread_mutex_t lock; struct fuse_config conf; struct fuse_fs *fs; struct lock_queue_element *lockq; pthread_t maintenance_thread; lfmp_t node_fmp; kvec_t(remembered_node_t) remembered_nodes; }; 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; uint64_t nodeid; char *name; struct node *parent; uint64_t nlookup; uint32_t refctr; uint32_t open_count; uint64_t hidden_fh; int32_t treelock; struct lock *locks; uint32_t stat_crc32b; uint8_t is_hidden:1; uint8_t is_stat_cache_valid:1; }; #define TREELOCK_WRITE -1 #define TREELOCK_WAIT_OFFSET INT_MIN struct fuse_dh { pthread_mutex_t lock; uint64_t fh; fuse_dirents_t d; }; struct fuse_context_i { struct fuse_context ctx; fuse_req_t req; }; static pthread_key_t fuse_context_key; static pthread_mutex_t fuse_context_lock = PTHREAD_MUTEX_INITIALIZER; static int fuse_context_ref; /* Why was the nodeid:generation logic simplified? nodeid is uint64_t: max value of 18446744073709551616 If nodes were created at a rate of 1048576 per second it would take over 500 thousand years to roll over. I'm fine with risking that. */ static uint64_t generate_nodeid(nodeid_gen_t *ng_) { ng_->nodeid++; return ng_->nodeid; } static char* filename_strdup(struct fuse *f_, const char *fn_) { return strdup(fn_); } static void filename_free(struct fuse *f_, char *fn_) { free(fn_); } 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 struct node* alloc_node(struct fuse *f) { return lfmp_calloc(&f->node_fmp); } static void free_node_mem(struct fuse *f, struct node *node) { return lfmp_free(&f->node_fmp,node); } static size_t id_hash(struct fuse *f, uint64_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, uint64_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, const uint64_t nodeid) { struct node *node = get_node_nocheck(f,nodeid); if(!node) { fprintf(stderr,"fuse internal error: node %llu not found\n", (unsigned long long)nodeid); abort(); } return node; } static void remove_remembered_node(struct fuse *f_, struct node *node_) { for(size_t i = 0; i < kv_size(f_->remembered_nodes); i++) { if(kv_A(f_->remembered_nodes,i).node != node_) continue; kv_delete(f_->remembered_nodes,i); break; } } static uint32_t stat_crc32b(const struct stat *st_) { uint32_t crc; crc = crc32b_start(); crc = crc32b_continue(&st_->st_ino,sizeof(st_->st_ino),crc); crc = crc32b_continue(&st_->st_size,sizeof(st_->st_size),crc); crc = crc32b_continue(&st_->st_mtim,sizeof(st_->st_mtim),crc); crc = crc32b_finish(crc); return crc; } #ifndef CLOCK_MONOTONIC # define CLOCK_MONOTONIC CLOCK_REALTIME #endif static time_t current_time() { int rv; struct timespec now; static clockid_t clockid = CLOCK_MONOTONIC; rv = clock_gettime(clockid,&now); if((rv == -1) && (errno == EINVAL)) { clockid = CLOCK_REALTIME; rv = clock_gettime(clockid,&now); } if(rv == -1) now.tv_sec = time(NULL); return now.tv_sec; } static void free_node(struct fuse *f_, struct node *node_) { filename_free(f_,node_->name); if(node_->is_hidden) fuse_fs_free_hide(f_->fs,node_->hidden_fh); 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; 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, uint64_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) { int iter; struct node_table *t = &f->name_table; 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); filename_free(f,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; } fprintf(stderr, "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, uint64_t parentid, const char *name) { size_t hash = name_hash(f,parentid,name); struct node *parent = get_node(f,parentid); node->name = filename_strdup(f,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 inline int remember_nodes(struct fuse *f_) { return (f_->conf.remember > 0); } static void delete_node(struct fuse *f, struct node *node) { assert(node->treelock == 0); unhash_name(f,node); if(remember_nodes(f)) remove_remembered_node(f,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 uint64_t rand64(void) { uint64_t rv; rv = rand(); rv <<= 32; rv |= rand(); return rv; } static struct node* lookup_node(struct fuse *f, uint64_t parent, const char *name) { size_t hash; struct node *node; hash = name_hash(f,parent,name); 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, uint64_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 = generate_nodeid(&f->nodeid_gen); 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); } else if((node->nlookup == 1) && remember_nodes(f)) { remove_remembered_node(f,node); } inc_nlookup(node); out_err: pthread_mutex_unlock(&f->lock); return node; } 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; strncpy(s,name,len); s--; *s = '/'; return s; } static void unlock_path(struct fuse *f, uint64_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, uint64_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); 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 void queue_element_unlock(struct fuse *f, struct lock_queue_element *qe) { struct node *wnode; if(qe->first_locked) { wnode = qe->wnode1 ? *qe->wnode1 : NULL; unlock_path(f,qe->nodeid1,wnode,NULL); qe->first_locked = false; } if(qe->second_locked) { wnode = qe->wnode2 ? *qe->wnode2 : NULL; unlock_path(f,qe->nodeid2,wnode,NULL); qe->second_locked = false; } } static void queue_element_wakeup(struct fuse *f, struct lock_queue_element *qe) { int err; bool first = (qe == f->lockq); 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->first_locked) { err = try_get_path(f,qe->nodeid1,qe->name1,qe->path1,qe->wnode1,true); if(!err) qe->first_locked = true; else if(err != -EAGAIN) goto err_unlock; } if(!qe->second_locked && qe->path2) { err = try_get_path(f,qe->nodeid2,qe->name2,qe->path2,qe->wnode2,true); if(!err) qe->second_locked = true; else if(err != -EAGAIN) goto err_unlock; } if(qe->first_locked && (qe->second_locked || !qe->path2)) { err = 0; goto done; } /* * Only let the first element be partially locked otherwise there could * be a deadlock. * * But do allow the first element to be partially locked to prevent * starvation. */ if(!first) queue_element_unlock(f,qe); /* keep trying */ return; err_unlock: queue_element_unlock(f,qe); done: 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 queue_path(struct fuse *f, struct lock_queue_element *qe) { struct lock_queue_element **qp; qe->done = false; qe->first_locked = false; qe->second_locked = 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, uint64_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 = {0}; qe.nodeid1 = nodeid; qe.name1 = name; qe.path1 = path; qe.wnode1 = wnode; err = wait_path(f,&qe); } pthread_mutex_unlock(&f->lock); return err; } static int get_path(struct fuse *f, uint64_t nodeid, char **path) { return get_path_common(f,nodeid,NULL,path,NULL); } static int get_path_name(struct fuse *f, uint64_t nodeid, const char *name, char **path) { return get_path_common(f,nodeid,name,path,NULL); } static int get_path_wrlock(struct fuse *f, uint64_t nodeid, const char *name, char **path, struct node **wnode) { return get_path_common(f,nodeid,name,path,wnode); } static int try_get_path2(struct fuse *f, uint64_t nodeid1, const char *name1, uint64_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 int get_path2(struct fuse *f, uint64_t nodeid1, const char *name1, uint64_t nodeid2, const char *name2, char **path1, char **path2, struct node **wnode1, struct node **wnode2) { int err; pthread_mutex_lock(&f->lock); err = try_get_path2(f,nodeid1,name1,nodeid2,name2, path1,path2,wnode1,wnode2); if(err == -EAGAIN) { struct lock_queue_element qe = {0}; qe.nodeid1 = nodeid1; qe.name1 = name1; qe.path1 = path1; qe.wnode1 = wnode1; qe.nodeid2 = nodeid2; qe.name2 = name2; qe.path2 = path2; qe.wnode2 = wnode2; err = wait_path(f,&qe); } pthread_mutex_unlock(&f->lock); return err; } static void free_path_wrlock(struct fuse *f, uint64_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, uint64_t nodeid, char *path) { if(path) free_path_wrlock(f,nodeid,NULL,path); } static void free_path2(struct fuse *f, uint64_t nodeid1, uint64_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, const uint64_t nodeid, const 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 = {0}; qe.nodeid1 = nodeid; queue_path(f,&qe); do { pthread_cond_wait(&qe.cond,&f->lock); } while((node->nlookup == nlookup) && node->treelock); dequeue_path(f,&qe); } assert(node->nlookup >= nlookup); node->nlookup -= nlookup; if(node->nlookup == 0) { unref_node(f,node); } else if((node->nlookup == 1) && remember_nodes(f)) { remembered_node_t fn; fn.node = node; fn.time = current_time(); kv_push(remembered_node_t,f->remembered_nodes,fn); } pthread_mutex_unlock(&f->lock); } static void unlink_node(struct fuse *f, struct node *node) { if(remember_nodes(f)) { assert(node->nlookup > 1); node->nlookup--; } unhash_name(f,node); } static void remove_node(struct fuse *f, uint64_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, uint64_t olddir, const char *oldname, uint64_t newdir, const char *newname) { 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) unlink_node(f,newnode); unhash_name(f,node); if(hash_name(f,node,newdir,newname) == -1) { err = -ENOMEM; goto out; } out: pthread_mutex_unlock(&f->lock); return err; } static void set_stat(struct fuse *f, uint64_t nodeid, struct stat *stbuf) { if(!f->conf.use_ino) stbuf->st_ino = nodeid; if(f->conf.set_mode) 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; } static struct fuse* req_fuse(fuse_req_t req) { return (struct fuse*)fuse_req_userdata(req); } int fuse_fs_getattr(struct fuse_fs *fs, const char *path, struct stat *buf, fuse_timeouts_t *timeout) { return fs->op.getattr(path,buf,timeout); } int fuse_fs_fgetattr(struct fuse_fs *fs, struct stat *buf, fuse_file_info_t *fi, fuse_timeouts_t *timeout) { return fs->op.fgetattr(fi,buf,timeout); } int fuse_fs_rename(struct fuse_fs *fs, const char *oldpath, const char *newpath) { return fs->op.rename(oldpath,newpath); } int fuse_fs_prepare_hide(struct fuse_fs *fs_, const char *path_, uint64_t *fh_) { return fs_->op.prepare_hide(path_,fh_); } int fuse_fs_free_hide(struct fuse_fs *fs_, uint64_t fh_) { return fs_->op.free_hide(fh_); } int fuse_fs_unlink(struct fuse_fs *fs, const char *path) { return fs->op.unlink(path); } int fuse_fs_rmdir(struct fuse_fs *fs, const char *path) { return fs->op.rmdir(path); } int fuse_fs_symlink(struct fuse_fs *fs_, const char *linkname_, const char *path_, struct stat *st_, fuse_timeouts_t *timeouts_) { return fs_->op.symlink(linkname_,path_,st_,timeouts_); } int fuse_fs_link(struct fuse_fs *fs, const char *oldpath, const char *newpath, struct stat *st_, fuse_timeouts_t *timeouts_) { return fs->op.link(oldpath,newpath,st_,timeouts_); } int fuse_fs_release(struct fuse_fs *fs, fuse_file_info_t *fi) { return fs->op.release(fi); } int fuse_fs_opendir(struct fuse_fs *fs, const char *path, fuse_file_info_t *fi) { return fs->op.opendir(path,fi); } int fuse_fs_open(struct fuse_fs *fs, const char *path, fuse_file_info_t *fi) { return fs->op.open(path,fi); } static void fuse_free_buf(struct fuse_bufvec *buf) { if(buf != NULL) { size_t i; for(i = 0; i < buf->count; i++) free(buf->buf[i].mem); free(buf); } } int fuse_fs_read_buf(struct fuse_fs *fs, struct fuse_bufvec **bufp, size_t size, off_t off, fuse_file_info_t *fi) { int res; res = fs->op.read_buf(fi,bufp,size,off); if(res < 0) return res; return 0; } int fuse_fs_write_buf(struct fuse_fs *fs, struct fuse_bufvec *buf, off_t off, fuse_file_info_t *fi) { return fs->op.write_buf(fi,buf,off); } int fuse_fs_fsync(struct fuse_fs *fs, int datasync, fuse_file_info_t *fi) { return fs->op.fsync(fi,datasync); } int fuse_fs_fsyncdir(struct fuse_fs *fs, int datasync, fuse_file_info_t *fi) { return fs->op.fsyncdir(fi,datasync); } int fuse_fs_flush(struct fuse_fs *fs, fuse_file_info_t *fi) { return fs->op.flush(fi); } int fuse_fs_statfs(struct fuse_fs *fs, const char *path, struct statvfs *buf) { return fs->op.statfs(path,buf); } int fuse_fs_releasedir(struct fuse_fs *fs, fuse_file_info_t *fi) { return fs->op.releasedir(fi); } int fuse_fs_readdir(struct fuse_fs *fs, fuse_file_info_t *fi, fuse_dirents_t *buf) { return fs->op.readdir(fi,buf); } int fuse_fs_readdir_plus(struct fuse_fs *fs_, fuse_file_info_t *ffi_, fuse_dirents_t *buf_) { return fs_->op.readdir_plus(ffi_,buf_); } int fuse_fs_create(struct fuse_fs *fs, const char *path, mode_t mode, fuse_file_info_t *fi) { return fs->op.create(path,mode,fi); } int fuse_fs_lock(struct fuse_fs *fs, fuse_file_info_t *fi, int cmd, struct flock *lock) { return fs->op.lock(fi,cmd,lock); } int fuse_fs_flock(struct fuse_fs *fs, fuse_file_info_t *fi, int op) { return fs->op.flock(fi,op); } int fuse_fs_chown(struct fuse_fs *fs, const char *path, uid_t uid, gid_t gid) { return fs->op.chown(path,uid,gid); } int fuse_fs_fchown(struct fuse_fs *fs_, const fuse_file_info_t *ffi_, const uid_t uid_, const gid_t gid_) { return fs_->op.fchown(ffi_,uid_,gid_); } int fuse_fs_truncate(struct fuse_fs *fs, const char *path, off_t size) { return fs->op.truncate(path,size); } int fuse_fs_ftruncate(struct fuse_fs *fs, off_t size, fuse_file_info_t *fi) { return fs->op.ftruncate(fi,size); } int fuse_fs_utimens(struct fuse_fs *fs, const char *path, const struct timespec tv[2]) { return fs->op.utimens(path,tv); } int fuse_fs_futimens(struct fuse_fs *fs_, const fuse_file_info_t *ffi_, const struct timespec tv_[2]) { return fs_->op.futimens(ffi_,tv_); } int fuse_fs_access(struct fuse_fs *fs, const char *path, int mask) { return fs->op.access(path,mask); } int fuse_fs_readlink(struct fuse_fs *fs, const char *path, char *buf, size_t 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) { return fs->op.mknod(path,mode,rdev); } int fuse_fs_mkdir(struct fuse_fs *fs, const char *path, mode_t mode) { 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) { 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) { return fs->op.getxattr(path,name,value,size); } int fuse_fs_listxattr(struct fuse_fs *fs, const char *path, char *list, size_t 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) { return fs->op.bmap(path,blocksize,idx); } int fuse_fs_removexattr(struct fuse_fs *fs, const char *path, const char *name) { return fs->op.removexattr(path,name); } int fuse_fs_ioctl(struct fuse_fs *fs, unsigned long cmd, void *arg, fuse_file_info_t *fi, unsigned int flags, void *data, uint32_t *out_size) { return fs->op.ioctl(fi,cmd,arg,flags,data,out_size); } int fuse_fs_poll(struct fuse_fs *fs, fuse_file_info_t *fi, fuse_pollhandle_t *ph, unsigned *reventsp) { return fs->op.poll(fi,ph,reventsp); } int fuse_fs_fallocate(struct fuse_fs *fs, int mode, off_t offset, off_t length, fuse_file_info_t *fi) { return fs->op.fallocate(fi,mode,offset,length); } ssize_t fuse_fs_copy_file_range(struct fuse_fs *fs_, fuse_file_info_t *ffi_in_, off_t off_in_, fuse_file_info_t *ffi_out_, off_t off_out_, size_t len_, int flags_) { return fs_->op.copy_file_range(ffi_in_, off_in_, ffi_out_, off_out_, len_, flags_); } static int node_open(const struct node *node_) { return ((node_ != NULL) && (node_->open_count > 0)); } static void update_stat(struct node *node_, const struct stat *stnew_) { uint32_t crc32b; crc32b = stat_crc32b(stnew_); if(node_->is_stat_cache_valid && (crc32b != node_->stat_crc32b)) node_->is_stat_cache_valid = 0; node_->stat_crc32b = crc32b; } static int set_path_info(struct fuse *f, uint64_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 = f->nodeid_gen.generation; 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, uint64_t nodeid, const char *name, const char *path, struct fuse_entry_param *e, fuse_file_info_t *fi) { int rv; memset(e,0,sizeof(struct fuse_entry_param)); rv = ((fi == NULL) ? fuse_fs_getattr(f->fs,path,&e->attr,&e->timeout) : fuse_fs_fgetattr(f->fs,&e->attr,fi,&e->timeout)); if(rv) return rv; return set_path_info(f,nodeid,name,e); } static struct fuse_context_i* fuse_get_context_internal(void) { struct fuse_context_i *c; c = (struct fuse_context_i *)pthread_getspecific(fuse_context_key); 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 */ fprintf(stderr,"fuse: failed to allocate thread specific data\n"); abort(); } pthread_setspecific(fuse_context_key,c); } 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) { fprintf(stderr,"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_get_context_internal(); const struct fuse_ctx *ctx = fuse_req_ctx(req); c->req = req; c->ctx.fuse = req_fuse(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) { fs->op.init(conn); } static void fuse_lib_init(void *data, struct fuse_conn_info *conn) { struct fuse *f = (struct fuse *)data; struct fuse_context_i *c = fuse_get_context_internal(); memset(c,0,sizeof(*c)); c->ctx.fuse = f; conn->want |= FUSE_CAP_EXPORT_SUPPORT; fuse_fs_init(f->fs,conn); } void fuse_fs_destroy(struct fuse_fs *fs) { if(fs->op.destroy) fs->op.destroy(); free(fs); } static void fuse_lib_destroy(void *data) { struct fuse *f = (struct fuse *)data; struct fuse_context_i *c = fuse_get_context_internal(); memset(c,0,sizeof(*c)); c->ctx.fuse = f; fuse_fs_destroy(f->fs); f->fs = NULL; } static void fuse_lib_lookup(fuse_req_t req, uint64_t parent, const char *name) { int err; char *path; struct node *dot = NULL; struct fuse *f = req_fuse_prepare(req); struct fuse_entry_param e = {0}; if(name[0] == '.') { if(name[1] == '\0') { name = NULL; pthread_mutex_lock(&f->lock); dot = get_node_nocheck(f,parent); if(dot == NULL) { pthread_mutex_unlock(&f->lock); reply_entry(req,&e,-ESTALE); return; } dot->refctr++; pthread_mutex_unlock(&f->lock); } else if((name[1] == '.') && (name[2] == '\0')) { if(parent == 1) { reply_entry(req,&e,-ENOENT); return; } name = NULL; pthread_mutex_lock(&f->lock); parent = get_node(f,parent)->parent->nodeid; pthread_mutex_unlock(&f->lock); } } err = get_path_name(f,parent,name,&path); if(!err) { err = lookup_path(f,parent,name,path,&e,NULL); if(err == -ENOENT) { e.ino = 0; err = 0; } 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, const uint64_t ino, const uint64_t nlookup) { forget_node(f,ino,nlookup); } static void fuse_lib_forget(fuse_req_t req, const uint64_t ino, const 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, uint64_t ino, fuse_file_info_t *fi) { int err; char *path; struct fuse *f; struct stat buf; struct node *node; fuse_timeouts_t timeout; fuse_file_info_t ffi = {0}; f = req_fuse_prepare(req); if(fi == NULL) { pthread_mutex_lock(&f->lock); node = get_node(f,ino); if(node->is_hidden) { fi = &ffi; fi->fh = node->hidden_fh; } pthread_mutex_unlock(&f->lock); } memset(&buf,0,sizeof(buf)); err = 0; path = NULL; if(fi == NULL) err = get_path(f,ino,&path); if(!err) { err = ((fi == NULL) ? fuse_fs_getattr(f->fs,path,&buf,&timeout) : fuse_fs_fgetattr(f->fs,&buf,fi,&timeout)); free_path(f,ino,path); } if(!err) { pthread_mutex_lock(&f->lock); node = get_node(f,ino); update_stat(node,&buf); pthread_mutex_unlock(&f->lock); set_stat(f,ino,&buf); fuse_reply_attr(req,&buf,timeout.attr); } else { reply_err(req,err); } } int fuse_fs_chmod(struct fuse_fs *fs, const char *path, mode_t mode) { return fs->op.chmod(path,mode); } int fuse_fs_fchmod(struct fuse_fs *fs_, const fuse_file_info_t *ffi_, const mode_t mode_) { return fs_->op.fchmod(ffi_,mode_); } static void fuse_lib_setattr(fuse_req_t req, uint64_t ino, struct stat *attr, int valid, fuse_file_info_t *fi) { struct fuse *f = req_fuse_prepare(req); struct stat buf; char *path; int err; struct node *node; fuse_timeouts_t timeout; fuse_file_info_t ffi = {0}; if(fi == NULL) { pthread_mutex_lock(&f->lock); node = get_node(f,ino); if(node->is_hidden) { fi = &ffi; fi->fh = node->hidden_fh; } pthread_mutex_unlock(&f->lock); } memset(&buf,0,sizeof(buf)); err = 0; path = NULL; if(fi == NULL) err = get_path(f,ino,&path); if(!err) { err = 0; if(!err && (valid & FATTR_MODE)) err = ((fi == NULL) ? fuse_fs_chmod(f->fs,path,attr->st_mode) : fuse_fs_fchmod(f->fs,fi,attr->st_mode)); if(!err && (valid & (FATTR_UID | FATTR_GID))) { uid_t uid = ((valid & FATTR_UID) ? attr->st_uid : (uid_t)-1); gid_t gid = ((valid & FATTR_GID) ? attr->st_gid : (gid_t)-1); err = ((fi == NULL) ? fuse_fs_chown(f->fs,path,uid,gid) : fuse_fs_fchown(f->fs,fi,uid,gid)); } if(!err && (valid & FATTR_SIZE)) err = ((fi == NULL) ? fuse_fs_truncate(f->fs,path,attr->st_size) : fuse_fs_ftruncate(f->fs,attr->st_size,fi)); #ifdef HAVE_UTIMENSAT if(!err && (valid & (FATTR_ATIME | FATTR_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 & FATTR_ATIME_NOW) tv[0].tv_nsec = UTIME_NOW; else if(valid & FATTR_ATIME) tv[0] = attr->st_atim; if(valid & FATTR_MTIME_NOW) tv[1].tv_nsec = UTIME_NOW; else if(valid & FATTR_MTIME) tv[1] = attr->st_mtim; err = ((fi == NULL) ? fuse_fs_utimens(f->fs,path,tv) : fuse_fs_futimens(f->fs,fi,tv)); } else #endif if(!err && ((valid & (FATTR_ATIME|FATTR_MTIME)) == (FATTR_ATIME|FATTR_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 = ((fi == NULL) ? fuse_fs_utimens(f->fs,path,tv) : fuse_fs_futimens(f->fs,fi,tv)); } if(!err) err = ((fi == NULL) ? fuse_fs_getattr(f->fs,path,&buf,&timeout) : fuse_fs_fgetattr(f->fs,&buf,fi,&timeout)); free_path(f,ino,path); } if(!err) { 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,timeout.attr); } else { reply_err(req,err); } } static void fuse_lib_access(fuse_req_t req, uint64_t ino, int mask) { struct fuse *f = req_fuse_prepare(req); char *path; int err; err = get_path(f,ino,&path); if(!err) { err = fuse_fs_access(f->fs,path,mask); free_path(f,ino,path); } reply_err(req,err); } static void fuse_lib_readlink(fuse_req_t req, uint64_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) { err = fuse_fs_readlink(f->fs,path,linkname,sizeof(linkname)); 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, uint64_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) { err = -ENOSYS; if(S_ISREG(mode)) { fuse_file_info_t 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,&fi); } } if(err == -ENOSYS) { err = fuse_fs_mknod(f->fs,path,mode,rdev); if(!err) err = lookup_path(f,parent,name,path,&e,NULL); } free_path(f,parent,path); } reply_entry(req,&e,err); } static void fuse_lib_mkdir(fuse_req_t req, uint64_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) { err = fuse_fs_mkdir(f->fs,path,mode); if(!err) err = lookup_path(f,parent,name,path,&e,NULL); free_path(f,parent,path); } reply_entry(req,&e,err); } static void fuse_lib_unlink(fuse_req_t req, uint64_t parent, const char *name) { int err; char *path; struct fuse *f; struct node *wnode; f = req_fuse_prepare(req); err = get_path_wrlock(f,parent,name,&path,&wnode); if(!err) { pthread_mutex_lock(&f->lock); if(node_open(wnode)) { err = fuse_fs_prepare_hide(f->fs,path,&wnode->hidden_fh); if(!err) wnode->is_hidden = 1; } pthread_mutex_unlock(&f->lock); err = fuse_fs_unlink(f->fs,path); if(!err) remove_node(f,parent,name); free_path_wrlock(f,parent,wnode,path); } reply_err(req,err); } static void fuse_lib_rmdir(fuse_req_t req, uint64_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) { err = fuse_fs_rmdir(f->fs,path); 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_, uint64_t parent_, const char *name_) { int rv; char *path; struct fuse *f; struct fuse_entry_param e = {0}; f = req_fuse_prepare(req_); rv = get_path_name(f,parent_,name_,&path); if(rv == 0) { rv = fuse_fs_symlink(f->fs,linkname_,path,&e.attr,&e.timeout); if(rv == 0) rv = set_path_info(f,parent_,name_,&e); free_path(f,parent_,path); } reply_entry(req_,&e,rv); } static void fuse_lib_rename(fuse_req_t req, uint64_t olddir, const char *oldname, uint64_t newdir, const char *newname) { int err; struct fuse *f; char *oldpath; char *newpath; struct node *wnode1; struct node *wnode2; f = req_fuse_prepare(req); err = get_path2(f,olddir,oldname,newdir,newname, &oldpath,&newpath,&wnode1,&wnode2); if(!err) { pthread_mutex_lock(&f->lock); if(node_open(wnode2)) { err = fuse_fs_prepare_hide(f->fs,newpath,&wnode2->hidden_fh); if(!err) wnode2->is_hidden = 1; } pthread_mutex_unlock(&f->lock); err = fuse_fs_rename(f->fs,oldpath,newpath); if(!err) err = rename_node(f,olddir,oldname,newdir,newname); free_path2(f,olddir,newdir,wnode1,wnode2,oldpath,newpath); } reply_err(req,err); } static void fuse_lib_link(fuse_req_t req, uint64_t ino, uint64_t newparent, const char *newname) { int rv; char *oldpath; char *newpath; struct fuse *f; struct fuse_entry_param e = {0}; f = req_fuse_prepare(req); rv = get_path2(f,ino,NULL,newparent,newname, &oldpath,&newpath,NULL,NULL); if(!rv) { rv = fuse_fs_link(f->fs,oldpath,newpath,&e.attr,&e.timeout); if(rv == 0) rv = set_path_info(f,newparent,newname,&e); free_path2(f,ino,newparent,NULL,NULL,oldpath,newpath); } reply_entry(req,&e,rv); } static void fuse_do_release(struct fuse *f, uint64_t ino, fuse_file_info_t *fi) { struct node *node; uint64_t fh; int was_hidden; fh = 0; fuse_fs_release(f->fs,fi); pthread_mutex_lock(&f->lock); node = get_node(f,ino); assert(node->open_count > 0); node->open_count--; was_hidden = 0; if(node->is_hidden && (node->open_count == 0)) { was_hidden = 1; node->is_hidden = 0; fh = node->hidden_fh; } pthread_mutex_unlock(&f->lock); if(was_hidden) fuse_fs_free_hide(f->fs,fh); } static void fuse_lib_create(fuse_req_t req, uint64_t parent, const char *name, mode_t mode, fuse_file_info_t *fi) { int err; char *path; struct fuse *f; struct fuse_entry_param e; f = req_fuse_prepare(req); err = get_path_name(f,parent,name,&path); if(!err) { 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,fi); } else if(!S_ISREG(e.attr.st_mode)) { err = -EIO; fuse_fs_release(f->fs,fi); forget_node(f,e.ino,1); } } } 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,fi); forget_node(f,e.ino,1); } } else { reply_err(req,err); } free_path(f,parent,path); } static void open_auto_cache(struct fuse *f, uint64_t ino, const char *path, fuse_file_info_t *fi) { struct node *node; fuse_timeouts_t timeout; pthread_mutex_lock(&f->lock); node = get_node(f,ino); if(node->is_stat_cache_valid) { int err; struct stat stbuf; pthread_mutex_unlock(&f->lock); err = fuse_fs_fgetattr(f->fs,&stbuf,fi,&timeout); pthread_mutex_lock(&f->lock); if(!err) update_stat(node,&stbuf); else node->is_stat_cache_valid = 0; } if(node->is_stat_cache_valid) fi->keep_cache = 1; node->is_stat_cache_valid = 1; pthread_mutex_unlock(&f->lock); } static void fuse_lib_open(fuse_req_t req, uint64_t ino, fuse_file_info_t *fi) { int err; char *path; struct fuse *f; f = req_fuse_prepare(req); err = get_path(f,ino,&path); if(!err) { err = fuse_fs_open(f->fs,path,fi); if(!err) { if(fi && fi->auto_cache) open_auto_cache(f,ino,path,fi); } } if(!err) { pthread_mutex_lock(&f->lock); get_node(f,ino)->open_count++; pthread_mutex_unlock(&f->lock); /* The open syscall was interrupted,so it must be cancelled */ if(fuse_reply_open(req,fi) == -ENOENT) fuse_do_release(f,ino,fi); } else { reply_err(req,err); } free_path(f,ino,path); } static void fuse_lib_read(fuse_req_t req, uint64_t ino, size_t size, off_t off, fuse_file_info_t *fi) { struct fuse *f = req_fuse_prepare(req); struct fuse_bufvec *buf = NULL; int res; res = fuse_fs_read_buf(f->fs,&buf,size,off,fi); 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, uint64_t ino, struct fuse_bufvec *buf, off_t off, fuse_file_info_t *fi) { int res; struct fuse *f = req_fuse_prepare(req); res = fuse_fs_write_buf(f->fs,buf,off,fi); free_path(f,ino,NULL); if(res >= 0) fuse_reply_write(req,res); else reply_err(req,res); } static void fuse_lib_fsync(fuse_req_t req, uint64_t ino, int datasync, fuse_file_info_t *fi) { int err; struct fuse *f = req_fuse_prepare(req); err = fuse_fs_fsync(f->fs,datasync,fi); reply_err(req,err); } static struct fuse_dh* get_dirhandle(const fuse_file_info_t *llfi, fuse_file_info_t *fi) { struct fuse_dh *dh = (struct fuse_dh *)(uintptr_t)llfi->fh; memset(fi,0,sizeof(fuse_file_info_t)); fi->fh = dh->fh; return dh; } static void fuse_lib_opendir(fuse_req_t req, uint64_t ino, fuse_file_info_t *llfi) { int err; char *path; struct fuse_dh *dh; fuse_file_info_t fi; struct fuse *f = req_fuse_prepare(req); dh = (struct fuse_dh *)calloc(1,sizeof(struct fuse_dh)); if(dh == NULL) { reply_err(req,-ENOMEM); return; } fuse_dirents_init(&dh->d); fuse_mutex_init(&dh->lock); llfi->fh = (uintptr_t)dh; memset(&fi,0,sizeof(fi)); fi.flags = llfi->flags; err = get_path(f,ino,&path); if(!err) { err = fuse_fs_opendir(f->fs,path,&fi); dh->fh = fi.fh; llfi->keep_cache = fi.keep_cache; llfi->cache_readdir = fi.cache_readdir; } if(!err) { if(fuse_reply_open(req,llfi) == -ENOENT) { /* The opendir syscall was interrupted,so it must be cancelled */ fuse_fs_releasedir(f->fs,&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 size_t readdir_buf_size(fuse_dirents_t *d_, size_t size_, off_t off_) { if(off_ >= kv_size(d_->offs)) return 0; if((kv_A(d_->offs,off_) + size_) > kv_size(d_->data)) return (kv_size(d_->data) - kv_A(d_->offs,off_)); return size_; } static char* readdir_buf(fuse_dirents_t *d_, off_t off_) { size_t i; i = kv_A(d_->offs,off_); return &kv_A(d_->data,i); } static void fuse_lib_readdir(fuse_req_t req_, uint64_t ino_, size_t size_, off_t off_, fuse_file_info_t *llffi_) { int rv; struct fuse *f; fuse_dirents_t *d; struct fuse_dh *dh; fuse_file_info_t fi; f = req_fuse_prepare(req_); dh = get_dirhandle(llffi_,&fi); d = &dh->d; pthread_mutex_lock(&dh->lock); rv = 0; if((off_ == 0) || (kv_size(d->data) == 0)) rv = fuse_fs_readdir(f->fs,&fi,d); if(rv) { reply_err(req_,rv); goto out; } size_ = readdir_buf_size(d,size_,off_); fuse_reply_buf(req_, readdir_buf(d,off_), size_); out: pthread_mutex_unlock(&dh->lock); } static void fuse_lib_readdir_plus(fuse_req_t req_, uint64_t ino_, size_t size_, off_t off_, fuse_file_info_t *llffi_) { int rv; struct fuse *f; fuse_dirents_t *d; struct fuse_dh *dh; fuse_file_info_t fi; f = req_fuse_prepare(req_); dh = get_dirhandle(llffi_,&fi); d = &dh->d; pthread_mutex_lock(&dh->lock); rv = 0; if((off_ == 0) || (kv_size(d->data) == 0)) rv = fuse_fs_readdir_plus(f->fs,&fi,d); if(rv) { reply_err(req_,rv); goto out; } size_ = readdir_buf_size(d,size_,off_); fuse_reply_buf(req_, readdir_buf(d,off_), size_); out: pthread_mutex_unlock(&dh->lock); } static void fuse_lib_releasedir(fuse_req_t req_, uint64_t ino_, fuse_file_info_t *llfi_) { struct fuse *f; struct fuse_dh *dh; fuse_file_info_t fi; f = req_fuse_prepare(req_); dh = get_dirhandle(llfi_,&fi); fuse_fs_releasedir(f->fs,&fi); /* Done to keep race condition between last readdir reply and the unlock */ pthread_mutex_lock(&dh->lock); pthread_mutex_unlock(&dh->lock); pthread_mutex_destroy(&dh->lock); fuse_dirents_free(&dh->d); free(dh); reply_err(req_,0); } static void fuse_lib_fsyncdir(fuse_req_t req, uint64_t ino, int datasync, fuse_file_info_t *llfi) { int err; fuse_file_info_t fi; struct fuse *f = req_fuse_prepare(req); get_dirhandle(llfi,&fi); err = fuse_fs_fsyncdir(f->fs,datasync,&fi); reply_err(req,err); } static void fuse_lib_statfs(fuse_req_t req, uint64_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) { err = fuse_fs_statfs(f->fs,path ? path : "/",&buf); 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, uint64_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) { err = fuse_fs_setxattr(f->fs,path,name,value,size,flags); free_path(f,ino,path); } reply_err(req,err); } static int common_getxattr(struct fuse *f, fuse_req_t req, uint64_t ino, const char *name, char *value, size_t size) { int err; char *path; err = get_path(f,ino,&path); if(!err) { err = fuse_fs_getxattr(f->fs,path,name,value,size); free_path(f,ino,path); } return err; } static void fuse_lib_getxattr(fuse_req_t req, uint64_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, uint64_t ino, char *list, size_t size) { char *path; int err; err = get_path(f,ino,&path); if(!err) { err = fuse_fs_listxattr(f->fs,path,list,size); free_path(f,ino,path); } return err; } static void fuse_lib_listxattr(fuse_req_t req, uint64_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, uint64_t ino, const char *name) { struct fuse *f = req_fuse_prepare(req); char *path; int err; err = get_path(f,ino,&path); if(!err) { err = fuse_fs_removexattr(f->fs,path,name); free_path(f,ino,path); } reply_err(req,err); } static void fuse_lib_copy_file_range(fuse_req_t req_, uint64_t nodeid_in_, off_t off_in_, fuse_file_info_t *ffi_in_, uint64_t nodeid_out_, off_t off_out_, fuse_file_info_t *ffi_out_, size_t len_, int flags_) { ssize_t rv; struct fuse *f; f = req_fuse_prepare(req_); rv = fuse_fs_copy_file_range(f->fs, ffi_in_, off_in_, ffi_out_, off_out_, len_, flags_); if(rv >= 0) fuse_reply_write(req_,rv); else reply_err(req_,rv); } 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, uint64_t ino, fuse_file_info_t *fi) { struct flock lock; struct lock l; int err; int errlock; memset(&lock,0,sizeof(lock)); lock.l_type = F_UNLCK; lock.l_whence = SEEK_SET; err = fuse_fs_flush(f->fs,fi); errlock = fuse_fs_lock(f->fs,fi,F_SETLK,&lock); 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, uint64_t ino, fuse_file_info_t *fi) { int err = 0; struct fuse *f = req_fuse_prepare(req); if(fi->flush) { err = fuse_flush_common(f,req,ino,fi); if(err == -ENOSYS) err = 0; } fuse_do_release(f,ino,fi); reply_err(req,err); } static void fuse_lib_flush(fuse_req_t req, uint64_t ino, fuse_file_info_t *fi) { int err; struct fuse *f = req_fuse_prepare(req); err = fuse_flush_common(f,req,ino,fi); reply_err(req,err); } static int fuse_lock_common(fuse_req_t req, uint64_t ino, fuse_file_info_t *fi, struct flock *lock, int cmd) { int err; struct fuse *f = req_fuse_prepare(req); err = fuse_fs_lock(f->fs,fi,cmd,lock); return err; } static void fuse_lib_getlk(fuse_req_t req, uint64_t ino, fuse_file_info_t *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, uint64_t ino, fuse_file_info_t *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, uint64_t ino, fuse_file_info_t *fi, int op) { int err; struct fuse *f = req_fuse_prepare(req); err = fuse_fs_flock(f->fs,fi,op); reply_err(req,err); } static void fuse_lib_bmap(fuse_req_t req, uint64_t ino, size_t blocksize, uint64_t idx) { int err; char *path; struct fuse *f = req_fuse_prepare(req); err = get_path(f,ino,&path); if(!err) { err = fuse_fs_bmap(f->fs,path,blocksize,&idx); 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, uint64_t ino, unsigned long cmd, void *arg, fuse_file_info_t *llfi, unsigned int flags, const void *in_buf, uint32_t in_bufsz, uint32_t out_bufsz_) { int err; char *out_buf = NULL; struct fuse *f = req_fuse_prepare(req); fuse_file_info_t fi; uint32_t out_bufsz = out_bufsz_; 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) memcpy(out_buf,in_buf,in_bufsz); err = fuse_fs_ioctl(f->fs,cmd,arg,&fi,flags, out_buf ?: (void *)in_buf,&out_bufsz); 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, uint64_t ino, fuse_file_info_t *fi, fuse_pollhandle_t *ph) { int err; struct fuse *f = req_fuse_prepare(req); unsigned revents = 0; err = fuse_fs_poll(f->fs,fi,ph,&revents); if(!err) fuse_reply_poll(req,revents); else reply_err(req,err); } static void fuse_lib_fallocate(fuse_req_t req, uint64_t ino, int mode, off_t offset, off_t length, fuse_file_info_t *fi) { int err; struct fuse *f = req_fuse_prepare(req); err = fuse_fs_fallocate(f->fs,mode,offset,length,fi); reply_err(req,err); } static int remembered_node_cmp(const void *a_, const void *b_) { const remembered_node_t *a = a_; const remembered_node_t *b = b_; return (a->time - b->time); } static void remembered_nodes_sort(struct fuse *f_) { pthread_mutex_lock(&f_->lock); qsort(&kv_first(f_->remembered_nodes), kv_size(f_->remembered_nodes), sizeof(remembered_node_t), remembered_node_cmp); pthread_mutex_unlock(&f_->lock); } #define MAX_PRUNE 100 #define MAX_CHECK 1000 int fuse_prune_some_remembered_nodes(struct fuse *f_, int *offset_) { time_t now; int pruned; int checked; pthread_mutex_lock(&f_->lock); pruned = 0; checked = 0; now = current_time(); while(*offset_ < kv_size(f_->remembered_nodes)) { time_t age; remembered_node_t *fn = &kv_A(f_->remembered_nodes,*offset_); if(pruned >= MAX_PRUNE) break; if(checked >= MAX_CHECK) break; checked++; age = (now - fn->time); if(f_->conf.remember > age) break; assert(fn->node->nlookup == 1); /* Don't forget active directories */ if(fn->node->refctr > 1) { (*offset_)++; continue; } fn->node->nlookup = 0; unref_node(f_,fn->node); kv_delete(f_->remembered_nodes,*offset_); pruned++; } pthread_mutex_unlock(&f_->lock); if((pruned < MAX_PRUNE) && (checked < MAX_CHECK)) *offset_ = -1; return pruned; } #undef MAX_PRUNE #undef MAX_CHECK static void sleep_100ms(void) { const struct timespec ms100 = {0,100 * 1000000}; nanosleep(&ms100,NULL); } void fuse_prune_remembered_nodes(struct fuse *f_) { int offset; int pruned; offset = 0; pruned = 0; for(;;) { pruned += fuse_prune_some_remembered_nodes(f_,&offset); if(offset >= 0) { sleep_100ms(); continue; } break; } if(pruned > 0) remembered_nodes_sort(f_); } static struct fuse_lowlevel_ops fuse_path_ops = { .access = fuse_lib_access, .bmap = fuse_lib_bmap, .copy_file_range = fuse_lib_copy_file_range, .create = fuse_lib_create, .destroy = fuse_lib_destroy, .fallocate = fuse_lib_fallocate, .flock = fuse_lib_flock, .flush = fuse_lib_flush, .forget = fuse_lib_forget, .forget_multi = fuse_lib_forget_multi, .fsync = fuse_lib_fsync, .fsyncdir = fuse_lib_fsyncdir, .getattr = fuse_lib_getattr, .getlk = fuse_lib_getlk, .getxattr = fuse_lib_getxattr, .init = fuse_lib_init, .ioctl = fuse_lib_ioctl, .link = fuse_lib_link, .listxattr = fuse_lib_listxattr, .lookup = fuse_lib_lookup, .mkdir = fuse_lib_mkdir, .mknod = fuse_lib_mknod, .open = fuse_lib_open, .opendir = fuse_lib_opendir, .poll = fuse_lib_poll, .read = fuse_lib_read, .readdir = fuse_lib_readdir, .readdir_plus = fuse_lib_readdir_plus, .readlink = fuse_lib_readlink, .release = fuse_lib_release, .releasedir = fuse_lib_releasedir, .removexattr = fuse_lib_removexattr, .rename = fuse_lib_rename, .retrieve_reply = NULL, .rmdir = fuse_lib_rmdir, .setattr = fuse_lib_setattr, .setlk = fuse_lib_setlk, .setxattr = fuse_lib_setxattr, .statfs = fuse_lib_statfs, .symlink = fuse_lib_symlink, .unlink = fuse_lib_unlink, .write_buf = fuse_lib_write_buf, }; int fuse_notify_poll(fuse_pollhandle_t *ph) { return fuse_lowlevel_notify_poll(ph); } static void free_cmd(struct fuse_cmd *cmd) { free(cmd->buf); free(cmd); } void fuse_process_cmd(struct fuse *f, struct fuse_cmd *cmd) { fuse_session_process(f->se,cmd->buf,cmd->buflen,cmd->ch); free_cmd(cmd); } int fuse_exited(struct fuse *f) { return fuse_session_exited(f->se); } struct fuse_session* fuse_get_session(struct fuse *f) { return f->se; } static struct fuse_cmd* fuse_alloc_cmd(size_t bufsize) { struct fuse_cmd *cmd = (struct fuse_cmd *)malloc(sizeof(*cmd)); if(cmd == NULL) { fprintf(stderr,"fuse: failed to allocate cmd\n"); return NULL; } cmd->buf = (char *)malloc(bufsize); if(cmd->buf == NULL) { fprintf(stderr,"fuse: failed to allocate read buffer\n"); free(cmd); return NULL; } return cmd; } struct fuse_cmd* fuse_read_cmd(struct fuse *f) { struct fuse_chan *ch = fuse_session_next_chan(f->se,NULL); size_t bufsize = fuse_chan_bufsize(ch); struct fuse_cmd *cmd = fuse_alloc_cmd(bufsize); if(cmd != NULL) { int res = fuse_chan_recv(&ch,cmd->buf,bufsize); if(res <= 0) { free_cmd(cmd); if(res < 0 && res != -EINTR && res != -EAGAIN) fuse_exit(f); return NULL; } cmd->buflen = res; cmd->ch = ch; } return cmd; } void fuse_exit(struct fuse *f) { fuse_session_exit(f->se); } struct fuse_context* fuse_get_context(void) { return &fuse_get_context_internal()->ctx; } enum { KEY_HELP, }; #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("-h", KEY_HELP), FUSE_OPT_KEY("--help", KEY_HELP), 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("nogc", nogc,1), FUSE_LIB_OPT("umask=", set_mode,1), FUSE_LIB_OPT("umask=%o", umask,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("noforget", remember,-1), FUSE_LIB_OPT("remember=%u", remember,0), FUSE_LIB_OPT("threads=%d", threads,0), FUSE_LIB_OPT("use_ino", use_ino,1), FUSE_OPT_END }; static void fuse_lib_help(void) { fprintf(stderr, " -o umask=M set file permissions (octal)\n" " -o uid=N set file owner\n" " -o gid=N set file group\n" " -o noforget never forget cached inodes\n" " -o remember=T remember cached inodes for T seconds (0s)\n" " -o threads=NUM number of worker threads. 0 = autodetect.\n" " Negative values autodetect then divide by\n" " absolute value. default = 0\n" "\n"); } static int fuse_lib_opt_proc(void *data, const char *arg, int key, struct fuse_args *outargs) { (void)arg; (void)outargs; if(key == KEY_HELP) { struct fuse_config *conf = (struct fuse_config *)data; fuse_lib_help(); conf->help = 1; } return 1; } int fuse_is_lib_option(const char *opt) { return fuse_lowlevel_is_lib_option(opt) || fuse_opt_match(fuse_lib_opts,opt); } struct fuse_fs* fuse_fs_new(const struct fuse_operations *op, size_t op_size) { struct fuse_fs *fs; if(sizeof(struct fuse_operations) < op_size) { fprintf(stderr,"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) { fprintf(stderr,"fuse: failed to allocate fuse_fs object\n"); return NULL; } 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) { fprintf(stderr,"fuse: memory allocation failed\n"); return -1; } t->use = 0; t->split = 0; return 0; } static void metrics_log_nodes_info(struct fuse *f_, FILE *file_) { char buf[1024]; lfmp_lock(&f_->node_fmp); snprintf(buf,sizeof(buf), "time: %zu\n" "sizeof(node): %zu\n" "node id_table size: %zu\n" "node id_table usage: %zu\n" "node id_table total allocated memory: %zu\n" "node name_table size: %zu\n" "node name_table usage: %zu\n" "node name_table total allocated memory: %zu\n" "node memory pool slab count: %zu\n" "node memory pool usage ratio: %f\n" "node memory pool avail objs: %zu\n" "node memory pool total allocated memory: %zu\n" "\n" , time(NULL), sizeof(struct node), f_->id_table.size, f_->id_table.use, (f_->id_table.size * sizeof(struct node*)), f_->name_table.size, f_->name_table.use, (f_->name_table.size * sizeof(struct node*)), fmp_slab_count(&f_->node_fmp.fmp), fmp_slab_usage_ratio(&f_->node_fmp.fmp), fmp_avail_objs(&f_->node_fmp.fmp), fmp_total_allocated_memory(&f_->node_fmp.fmp) ); lfmp_unlock(&f_->node_fmp); fputs(buf,file_); } static void metrics_log_nodes_info_to_tmp_dir(struct fuse *f_) { FILE *file; char filepath[256]; sprintf(filepath,"/tmp/mergerfs.%d.info",getpid()); file = fopen(filepath,"w"); if(file == NULL) return; metrics_log_nodes_info(f_,file); fclose(file); } static void fuse_malloc_trim(void) { #ifdef HAVE_MALLOC_TRIM malloc_trim(1024 * 1024); #endif } static void* fuse_maintenance_loop(void *fuse_) { int gc; int loops; int sleep_time; struct fuse *f = (struct fuse*)fuse_; gc = 0; loops = 0; sleep_time = 60; while(1) { if(remember_nodes(f)) fuse_prune_remembered_nodes(f); if((loops % 15) == 0) { fuse_malloc_trim(); gc = 1; } // Trigger a followup gc if this gc succeeds if(!f->conf.nogc && gc) gc = lfmp_gc(&f->node_fmp); if(g_LOG_METRICS) metrics_log_nodes_info_to_tmp_dir(f); loops++; sleep(sleep_time); } return NULL; } int fuse_start_maintenance_thread(struct fuse *f_) { return fuse_start_thread(&f_->maintenance_thread,fuse_maintenance_loop,f_); } void fuse_stop_maintenance_thread(struct fuse *f_) { pthread_mutex_lock(&f_->lock); pthread_cancel(f_->maintenance_thread); pthread_mutex_unlock(&f_->lock); pthread_join(f_->maintenance_thread,NULL); } struct fuse* fuse_new_common(struct fuse_chan *ch, struct fuse_args *args, const struct fuse_operations *op, size_t op_size) { struct fuse *f; struct node *root; struct fuse_fs *fs; struct fuse_lowlevel_ops llop = fuse_path_ops; if(fuse_create_context_key() == -1) goto out; f = (struct fuse *)calloc(1,sizeof(struct fuse)); if(f == NULL) { fprintf(stderr,"fuse: failed to allocate fuse object\n"); goto out_delete_context_key; } fs = fuse_fs_new(op,op_size); if(!fs) goto out_free; f->fs = fs; /* Oh f**k,this is ugly! */ if(!fs->op.lock) { llop.getlk = NULL; llop.setlk = NULL; } if(fuse_opt_parse(args,&f->conf,fuse_lib_opts,fuse_lib_opt_proc) == -1) goto out_free_fs; g_LOG_METRICS = f->conf.debug; f->se = fuse_lowlevel_new_common(args,&llop,sizeof(llop),f); if(f->se == NULL) goto out_free_fs; fuse_session_add_chan(f->se,ch); /* Trace topmost layer by default */ srand(time(NULL)); f->nodeid_gen.nodeid = FUSE_ROOT_ID; f->nodeid_gen.generation = rand64(); 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; fuse_mutex_init(&f->lock); lfmp_init(&f->node_fmp,sizeof(struct node),256); kv_init(f->remembered_nodes); root = alloc_node(f); if(root == NULL) { fprintf(stderr,"fuse: memory allocation failed\n"); goto out_free_id_table; } root->name = filename_strdup(f,"/"); 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: /* Horrible compatibility hack to stop the destructor from being called on the filesystem without init being called first */ fs->op.destroy = NULL; fuse_fs_destroy(f->fs); out_free: free(f); out_delete_context_key: fuse_delete_context_key(); out: return NULL; } struct fuse* fuse_new(struct fuse_chan *ch, struct fuse_args *args, const struct fuse_operations *op, size_t op_size) { return fuse_new_common(ch,args,op,op_size); } void fuse_destroy(struct fuse *f) { size_t i; if(f->fs) { struct fuse_context_i *c = fuse_get_context_internal(); memset(c,0,sizeof(*c)); c->ctx.fuse = 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) fuse_fs_free_hide(f->fs,node->hidden_fh); } } } 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--; } } free(f->id_table.array); free(f->name_table.array); pthread_mutex_destroy(&f->lock); fuse_session_destroy(f->se); lfmp_destroy(&f->node_fmp); kv_destroy(f->remembered_nodes); free(f); fuse_delete_context_key(); } int fuse_config_num_threads(const struct fuse *fuse_) { return fuse_->conf.threads; } void fuse_log_metrics_set(int log_) { g_LOG_METRICS = log_; } int fuse_log_metrics_get(void) { return g_LOG_METRICS; } mergerfs-2.33.5/libfuse/lib/fuse_dirents.c0000644000000000000000000001741114225522122017152 0ustar rootroot#define _FILE_OFFSET_BITS 64 #include "fuse_attr.h" #include "fuse_dirent.h" #include "fuse_direntplus.h" #include "fuse_dirents.h" #include "fuse_entry.h" #include "linux_dirent64.h" #include "stat_utils.h" #include #include #include #include #include #include #include /* 32KB - same as glibc getdents buffer size */ #define DEFAULT_SIZE (1024 * 32) static uint64_t round_up(const uint64_t number_, const uint64_t multiple_) { return (((number_ + multiple_ - 1) / multiple_) * multiple_); } static uint64_t align_uint64_t(uint64_t v_) { return ((v_ + sizeof(uint64_t) - 1) & ~(sizeof(uint64_t) - 1)); } static uint64_t fuse_dirent_size(const uint64_t namelen_) { uint64_t rv; rv = offsetof(fuse_dirent_t,name); rv += namelen_; rv = align_uint64_t(rv); return rv; } static uint64_t fuse_direntplus_size(const uint64_t namelen_) { uint64_t rv; rv = offsetof(fuse_direntplus_t,dirent.name); rv += namelen_; rv = align_uint64_t(rv); return rv; } static int fuse_dirents_buf_resize(fuse_dirents_t *d_, uint64_t size_) { if((kv_size(d_->data) + size_) >= kv_max(d_->data)) { uint64_t new_size; new_size = round_up((kv_size(d_->data) + size_),DEFAULT_SIZE); kv_resize(char,d_->data,new_size); if(d_->data.a == NULL) return -ENOMEM; } return 0; } static void* fuse_dirents_dirent_alloc(fuse_dirents_t *d_, uint64_t namelen_) { int rv; uint64_t size; fuse_dirent_t *d; size = fuse_dirent_size(namelen_); rv = fuse_dirents_buf_resize(d_,size); if(rv) return NULL; d = (fuse_dirent_t*)&kv_end(d_->data); kv_size(d_->data) += size; return d; } static void* fuse_dirents_direntplus_alloc(fuse_dirents_t *d_, uint64_t namelen_) { int rv; uint64_t size; fuse_dirent_t *d; size = fuse_direntplus_size(namelen_); rv = fuse_dirents_buf_resize(d_,size); if(rv) return NULL; d = (fuse_dirent_t*)&kv_end(d_->data); kv_size(d_->data) += size; return d; } static void fuse_dirents_fill_attr(fuse_attr_t *attr_, const struct stat *st_) { attr_->ino = st_->st_ino; attr_->size = st_->st_size; attr_->blocks = st_->st_blocks; attr_->atime = st_->st_atime; attr_->mtime = st_->st_mtime; attr_->ctime = st_->st_ctime; attr_->atimensec = ST_ATIM_NSEC(st_); attr_->mtimensec = ST_MTIM_NSEC(st_); attr_->ctimensec = ST_CTIM_NSEC(st_); attr_->mode = st_->st_mode; attr_->nlink = st_->st_nlink; attr_->uid = st_->st_uid; attr_->gid = st_->st_gid; attr_->rdev = st_->st_rdev; attr_->blksize = st_->st_blksize; } fuse_dirent_t* fuse_dirent_next(fuse_dirent_t *cur_) { char *buf; buf = (char*)cur_; buf += fuse_dirent_size(cur_->namelen); return (fuse_dirent_t*)buf; } fuse_direntplus_t* fuse_direntplus_next(fuse_direntplus_t *cur_) { char *buf; buf = (char*)cur_; buf += fuse_direntplus_size(cur_->dirent.namelen); return (fuse_direntplus_t*)buf; } fuse_dirent_t* fuse_dirent_find(fuse_dirents_t *d_, const uint64_t ino_) { fuse_dirent_t *cur; fuse_dirent_t *end; if(d_->type != NORMAL) return NULL; cur = (fuse_dirent_t*)&kv_first(d_->data); end = (fuse_dirent_t*)&kv_end(d_->data); while(cur < end) { if(cur->ino == ino_) return cur; cur = fuse_dirent_next(cur); } return NULL; } fuse_direntplus_t* fuse_direntplus_find(fuse_dirents_t *d_, const uint64_t ino_) { fuse_direntplus_t *cur; fuse_direntplus_t *end; if(d_->type != PLUS) return NULL; cur = (fuse_direntplus_t*)&kv_first(d_->data); end = (fuse_direntplus_t*)&kv_end(d_->data); while(cur < end) { if(cur->dirent.ino == ino_) return cur; cur = fuse_direntplus_next(cur); } return NULL; } void* fuse_dirents_find(fuse_dirents_t *d_, const uint64_t ino_) { switch(d_->type) { default: case UNSET: return NULL; case NORMAL: return fuse_dirent_find(d_,ino_); case PLUS: return fuse_direntplus_find(d_,ino_); } } int fuse_dirents_convert_plus2normal(fuse_dirents_t *d_) { return -ENOSYS; } int fuse_dirents_add(fuse_dirents_t *d_, const struct dirent *dirent_, const uint64_t namelen_) { fuse_dirent_t *d; switch(d_->type) { case UNSET: d_->type = NORMAL; break; case NORMAL: break; case PLUS: return -EINVAL; } d = fuse_dirents_dirent_alloc(d_,namelen_); if(d == NULL) return -ENOMEM; d->off = kv_size(d_->offs); kv_push(uint32_t,d_->offs,kv_size(d_->data)); d->ino = dirent_->d_ino; d->namelen = namelen_; d->type = dirent_->d_type; memcpy(d->name,dirent_->d_name,namelen_); return 0; } int fuse_dirents_add_plus(fuse_dirents_t *d_, const struct dirent *dirent_, const uint64_t namelen_, const fuse_entry_t *entry_, const struct stat *st_) { fuse_direntplus_t *d; switch(d_->type) { case UNSET: d_->type = PLUS; break; case NORMAL: return -EINVAL; case PLUS: break; } d = fuse_dirents_direntplus_alloc(d_,namelen_); if(d == NULL) return -ENOMEM; d->dirent.off = kv_size(d_->offs); kv_push(uint32_t,d_->offs,kv_size(d_->data)); d->dirent.ino = dirent_->d_ino; d->dirent.namelen = namelen_; d->dirent.type = dirent_->d_type; memcpy(d->dirent.name,dirent_->d_name,namelen_); d->entry = *entry_; fuse_dirents_fill_attr(&d->attr,st_); return 0; } int fuse_dirents_add_linux(fuse_dirents_t *d_, const struct linux_dirent64 *dirent_, const uint64_t namelen_) { fuse_dirent_t *d; switch(d_->type) { case UNSET: d_->type = NORMAL; break; case NORMAL: break; case PLUS: return -EINVAL; } d = fuse_dirents_dirent_alloc(d_,namelen_); if(d == NULL) return -ENOMEM; d->off = kv_size(d_->offs); kv_push(uint32_t,d_->offs,kv_size(d_->data)); d->ino = dirent_->ino; d->namelen = namelen_; d->type = dirent_->type; memcpy(d->name,dirent_->name,namelen_); return 0; } int fuse_dirents_add_linux_plus(fuse_dirents_t *d_, const struct linux_dirent64 *dirent_, const uint64_t namelen_, const fuse_entry_t *entry_, const struct stat *st_) { fuse_direntplus_t *d; switch(d_->type) { case UNSET: d_->type = PLUS; break; case NORMAL: return -EINVAL; case PLUS: break; } d = fuse_dirents_direntplus_alloc(d_,namelen_); if(d == NULL) return -ENOMEM; d->dirent.off = kv_size(d_->offs); kv_push(uint32_t,d_->offs,kv_size(d_->data)); d->dirent.ino = dirent_->ino; d->dirent.namelen = namelen_; d->dirent.type = dirent_->type; memcpy(d->dirent.name,dirent_->name,namelen_); d->entry = *entry_; fuse_dirents_fill_attr(&d->attr,st_); return 0; } void fuse_dirents_reset(fuse_dirents_t *d_) { d_->type = UNSET; kv_size(d_->data) = 0; kv_size(d_->offs) = 1; } int fuse_dirents_init(fuse_dirents_t *d_) { d_->type = UNSET; kv_init(d_->data); kv_resize(char,d_->data,DEFAULT_SIZE); if(d_->data.a == NULL) return -ENOMEM; kv_init(d_->offs); kv_resize(uint32_t,d_->offs,64); kv_push(uint32_t,d_->offs,0); return 0; } void fuse_dirents_free(fuse_dirents_t *d_) { kv_destroy(d_->data); kv_destroy(d_->offs); } mergerfs-2.33.5/libfuse/lib/mount_generic.c0000644000000000000000000003510114225522122017312 0ustar rootroot/* 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 COPYING.LIB. */ #include "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 #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 umount2(mnt, flags) unmount(mnt, (flags == 2) ? MNT_FORCE : 0) #endif #define FUSERMOUNT_PROG "fusermount" #define FUSE_COMMFD_ENV "_FUSE_COMMFD" #ifndef HAVE_FORK #define fork() vfork() #endif #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_RO, KEY_HELP, KEY_VERSION, }; struct mount_opts { int allow_other; int ishelp; int flags; int nonempty; int auto_unmount; int blkdev; char *fsname; char *subtype; char *subtype_opt; char *mtab_opts; char *fusermount_opts; char *kernel_opts; }; #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("nonempty", nonempty), FUSE_MOUNT_OPT("blkdev", blkdev), FUSE_MOUNT_OPT("auto_unmount", auto_unmount), FUSE_MOUNT_OPT("fsname=%s", fsname), FUSE_MOUNT_OPT("subtype=%s", subtype), FUSE_OPT_KEY("allow_other", KEY_KERN_OPT), FUSE_OPT_KEY("nonempty", KEY_FUSERMOUNT_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("large_read", KEY_KERN_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("max_read=", FUSE_OPT_KEY_KEEP), FUSE_OPT_KEY("user=", 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("atime", KEY_KERN_FLAG), FUSE_OPT_KEY("noatime", KEY_KERN_FLAG), FUSE_OPT_KEY("-h", KEY_HELP), FUSE_OPT_KEY("--help", KEY_HELP), FUSE_OPT_KEY("-V", KEY_VERSION), FUSE_OPT_KEY("--version", KEY_VERSION), FUSE_OPT_END }; static void mount_help(void) { fprintf(stderr, " -o allow_other allow access to other users\n" " -o auto_unmount auto unmount on process termination\n" " -o nonempty allow mounts over non-empty file/dir\n" " -o default_permissions enable permission checking by kernel\n" " -o fsname=NAME set filesystem name\n" " -o subtype=NAME set filesystem type\n" " -o large_read issue large read requests (2.4 only)\n" " -o max_read=N set maximum size of read requests\n" "\n"); } static void exec_fusermount(const char *argv[]) { execv(FUSERMOUNT_DIR "/mergerfs-" FUSERMOUNT_PROG, (char **) argv); execvp("mergerfs-" FUSERMOUNT_PROG, (char **) argv); execv(FUSERMOUNT_DIR "/" FUSERMOUNT_PROG, (char **) argv); execvp(FUSERMOUNT_PROG, (char **) argv); } static void mount_version(void) { int pid = fork(); if (!pid) { const char *argv[] = { FUSERMOUNT_PROG, "--version", NULL }; exec_fusermount(argv); _exit(1); } else if (pid != -1) waitpid(pid, NULL, 0); } 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}, {"atime", MS_NOATIME, 0}, {"noatime", MS_NOATIME, 1}, #ifndef __NetBSD__ {"dirsync", MS_DIRSYNC, 1}, #endif {NULL, 0, 0} }; 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; } } fprintf(stderr, "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) { 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); case KEY_HELP: mount_help(); mo->ishelp = 1; break; case KEY_VERSION: mount_version(); mo->ishelp = 1; break; } 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) { perror("recvmsg"); return -1; } if(!rv) { /* EOF */ return -1; } cmsg = CMSG_FIRSTHDR(&msg); if (cmsg->cmsg_type != SCM_RIGHTS) { fprintf(stderr, "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; int pid; if (!mountpoint) return; 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 */ if (res == 1 && (pfd.revents & POLLERR)) return; } if (geteuid() == 0) { fuse_mnt_umount("fuse", mountpoint, mountpoint, 1); return; } res = umount2(mountpoint, 2); if (res == 0) return; pid = fork(); if(pid == -1) return; if(pid == 0) { const char *argv[] = { FUSERMOUNT_PROG, "-u", "-q", "-z", "--", mountpoint, NULL }; exec_fusermount(argv); _exit(1); } waitpid(pid, NULL, 0); } static int fuse_mount_fusermount(const char *mountpoint, struct mount_opts *mo, const char *opts, int quiet) { int fds[2], pid; int res; int rv; if (!mountpoint) { fprintf(stderr, "fuse: missing mountpoint parameter\n"); return -1; } res = socketpair(PF_UNIX, SOCK_STREAM, 0, fds); if(res == -1) { perror("fuse: socketpair() failed"); return -1; } pid = fork(); if(pid == -1) { perror("fuse: fork() failed"); close(fds[0]); close(fds[1]); return -1; } if(pid == 0) { char env[10]; const char *argv[32]; int a = 0; if (quiet) { int fd = open("/dev/null", O_RDONLY); if (fd != -1) { dup2(fd, 1); dup2(fd, 2); } } argv[a++] = FUSERMOUNT_PROG; if (opts) { argv[a++] = "-o"; argv[a++] = opts; } argv[a++] = "--"; argv[a++] = mountpoint; argv[a++] = NULL; close(fds[1]); fcntl(fds[0], F_SETFD, 0); snprintf(env, sizeof(env), "%i", fds[0]); setenv(FUSE_COMMFD_ENV, env, 1); exec_fusermount(argv); perror("fuse: failed to exec fusermount"); _exit(1); } close(fds[0]); rv = receive_fd(fds[1]); if (!mo->auto_unmount) { /* with auto_unmount option fusermount will not exit until this socket is closed */ close(fds[1]); waitpid(pid, NULL, 0); /* bury zombie */ } return rv; } static int fuse_mount_sys(const char *mnt, struct mount_opts *mo, const char *mnt_opts) { char tmp[128]; const char *devname = "/dev/fuse"; char *source = NULL; char *type = NULL; struct stat stbuf; int fd; int res; if (!mnt) { fprintf(stderr, "fuse: missing mountpoint parameter\n"); return -1; } res = stat(mnt, &stbuf); if (res == -1) { fprintf(stderr ,"fuse: failed to access mountpoint %s: %s\n", mnt, strerror(errno)); return -1; } if (!mo->nonempty) { res = fuse_mnt_check_empty("fuse", mnt, stbuf.st_mode, stbuf.st_size); if (res == -1) return -1; } if (mo->auto_unmount) { /* Tell the caller to fallback to fusermount because auto-unmount does not work otherwise. */ return -2; } fd = open(devname, O_RDWR); if (fd == -1) { if (errno == ENODEV || errno == ENOENT) fprintf(stderr, "fuse: device not found, try 'modprobe fuse' first\n"); else fprintf(stderr, "fuse: failed to open %s: %s\n", devname, strerror(errno)); return -1; } 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) { fprintf(stderr, "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 fusermount */ if (errno == EPERM) { res = -2; } else { int errno_save = errno; if (mo->blkdev && errno == ENODEV && !fuse_mnt_check_fuseblk()) fprintf(stderr, "fuse: 'fuseblk' support missing\n"); else fprintf(stderr, "fuse: mount failed: %s\n", strerror(errno_save)); } goto out_close; } #ifndef __NetBSD__ #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 */ #endif /* __NetBSD__ */ free(type); free(source); return fd; out_umount: umount2(mnt, 2); /* 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; } int fuse_kern_mount(const char *mountpoint, struct fuse_args *args) { struct mount_opts mo; int res = -1; char *mnt_opts = NULL; memset(&mo, 0, sizeof(mo)); mo.flags = MS_NOSUID | MS_NODEV; if (args && fuse_opt_parse(args, &mo, fuse_mount_opts, fuse_mount_opt_proc) == -1) return -1; res = 0; if (mo.ishelp) goto out; 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 == -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); free(mo.fsname); free(mo.subtype); free(mo.fusermount_opts); free(mo.subtype_opt); free(mo.kernel_opts); free(mo.mtab_opts); return res; } mergerfs-2.33.5/libfuse/lib/mount_util.h0000644000000000000000000000134114225522122016657 0ustar rootroot/* 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 COPYING.LIB. */ #pragma once #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_empty(const char *progname, const char *mnt, mode_t rootmode, off_t rootsize); int fuse_mnt_check_fuseblk(void); mergerfs-2.33.5/libfuse/lib/crc32b.h0000644000000000000000000000220614225522122015537 0ustar rootroot/* ISC License Copyright (c) 2021, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #ifndef CRC32B_H_INCLUDED #define CRC32B_H_INCLUDED typedef unsigned int crc32b_t; crc32b_t crc32b_start(void); crc32b_t crc32b_continue(const void *buf, const crc32b_t len, const crc32b_t crc); crc32b_t crc32b_finish(const crc32b_t crc); crc32b_t crc32b(const void *buf, crc32b_t len); #endif mergerfs-2.33.5/libfuse/lib/mount_util.c0000644000000000000000000002023214225522122016652 0ustar rootroot/* 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 COPYING.LIB. */ #include "mount_util.h" #include #include #include #include #include #include #include #include #include #include #ifndef __NetBSD__ #include #endif #include #include #include #include #ifdef __NetBSD__ #define umount2(mnt, flags) unmount(mnt, (flags == 2) ? MNT_FORCE : 0) #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 a symlink, * - 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; if (S_ISLNK(stbuf.st_mode)) return 0; 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; } return 1; } #endif /* __NetBSD__ */ 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; 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); setuid(geteuid()); 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); setuid(geteuid()); if (lazy) { execle("/bin/umount", "/bin/umount", "-i", rel_mnt, "-l", 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; if (!mtab_needs_update(abs_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; 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); setuid(geteuid()); 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_empty(const char *progname, const char *mnt, mode_t rootmode, off_t rootsize) { int isempty = 1; if (S_ISDIR(rootmode)) { struct dirent *ent; DIR *dp = opendir(mnt); if (dp == NULL) { fprintf(stderr, "%s: failed to open mountpoint for reading: %s\n", progname, strerror(errno)); return -1; } while ((ent = readdir(dp)) != NULL) { if (strcmp(ent->d_name, ".") != 0 && strcmp(ent->d_name, "..") != 0) { isempty = 0; break; } } closedir(dp); } else if (rootsize) isempty = 0; if (!isempty) { fprintf(stderr, "%s: mountpoint is not empty\n", progname); fprintf(stderr, "%s: if you are sure this is safe, use the 'nonempty' mount option\n", progname); return -1; } return 0; } 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; } mergerfs-2.33.5/libfuse/lib/fuse_lowlevel.c0000644000000000000000000020360414225522122017334 0ustar rootroot/* 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 COPYING.LIB */ #define _GNU_SOURCE #include "lfmp.h" #include "config.h" #include "debug.h" #include "fuse_i.h" #include "fuse_kernel.h" #include "fuse_opt.h" #include "fuse_misc.h" #include #include #include #include #include #include #include #include #include #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 #define PARAM(inarg) (((char *)(inarg)) + sizeof(*(inarg))) #define OFFSET_MAX 0x7fffffffffffffffLL #define container_of(ptr, type, member) ({ \ const typeof( ((type *)0)->member ) *__mptr = (ptr); \ (type *)( (char *)__mptr - offsetof(type,member) );}) struct fuse_pollhandle_t { uint64_t kh; struct fuse_chan *ch; struct fuse_ll *f; }; static size_t pagesize; static lfmp_t g_FMP_fuse_req; static __attribute__((constructor)) void fuse_ll_constructor(void) { pagesize = getpagesize(); lfmp_init(&g_FMP_fuse_req,sizeof(struct fuse_req),1); } static __attribute__((destructor)) void fuse_ll_destructor(void) { lfmp_destroy(&g_FMP_fuse_req); } 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; } static void destroy_req(fuse_req_t req) { lfmp_free(&g_FMP_fuse_req,req); } static struct fuse_req* fuse_ll_alloc_req(struct fuse_ll *f) { struct fuse_req *req; req = (struct fuse_req*)lfmp_calloc(&g_FMP_fuse_req); if (req == NULL) { fprintf(stderr, "fuse: failed to allocate request\n"); } else { req->f = f; } return req; } static int fuse_send_msg(struct fuse_ll *f, struct fuse_chan *ch, struct iovec *iov, int count) { struct fuse_out_header *out = iov[0].iov_base; out->len = iov_length(iov, count); return fuse_chan_send(ch, iov, count); } int fuse_send_reply_iov_nofree(fuse_req_t req, int error, struct iovec *iov, int count) { struct fuse_out_header out; if (error <= -1000 || error > 0) { fprintf(stderr, "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->f, req->ch, iov, count); } 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); destroy_req(req); return res; } static int send_reply(fuse_req_t req, int error, const void *arg, size_t 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); } 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->ch) fuse_chan_send(req->ch, NULL, 0); destroy_req(req); } 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 = e->timeout.entry; arg->entry_valid_nsec = 0; arg->attr_valid = e->timeout.attr; arg->attr_valid_nsec = 0; convert_stat(&e->attr,&arg->attr); } static void fill_open(struct fuse_open_out *arg, const fuse_file_info_t *f) { arg->fh = f->fh; if (f->direct_io) arg->open_flags |= FOPEN_DIRECT_IO; if (f->keep_cache) arg->open_flags |= FOPEN_KEEP_CACHE; if (f->nonseekable) arg->open_flags |= FOPEN_NONSEEKABLE; if (f->cache_readdir) arg->open_flags |= FOPEN_CACHE_DIR; } int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e) { struct fuse_entry_out arg = {0}; size_t size = req->f->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->f->conn.proto_minor < 4) return fuse_reply_err(req, ENOENT); 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 fuse_file_info_t *f) { char buf[sizeof(struct fuse_entry_out) + sizeof(struct fuse_open_out)] = {0}; size_t entrysize = req->f->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); 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, const uint64_t timeout) { struct fuse_attr_out arg = {0}; size_t size = req->f->conn.proto_minor < 9 ? FUSE_COMPAT_ATTR_OUT_SIZE : sizeof(arg); arg.attr_valid = timeout; arg.attr_valid_nsec = 0; 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_reply_open(fuse_req_t req, const fuse_file_info_t *f) { struct fuse_open_out arg = {0}; fill_open(&arg, f); return send_reply_ok(req, &arg, sizeof(arg)); } int fuse_reply_write(fuse_req_t req, size_t count) { struct fuse_write_out arg = {0}; arg.size = count; return send_reply_ok(req, &arg, sizeof(arg)); } 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_ll *f, struct fuse_chan *ch, struct iovec *iov, int iov_count, struct fuse_bufvec *buf, size_t len) { int res; void *mbuf; struct fuse_bufvec mem_buf = FUSE_BUFVEC_INIT(len); /* 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(f, ch, iov, iov_count); } 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(f, ch, iov, iov_count); 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 static struct fuse_ll_pipe* fuse_ll_get_pipe(struct fuse_ll *f) { struct fuse_ll_pipe *llp = pthread_getspecific(f->pipe_key); if (llp == NULL) { int res; llp = malloc(sizeof(struct fuse_ll_pipe)); if (llp == NULL) return NULL; res = pipe(llp->pipe); if (res == -1) { free(llp); return NULL; } if (fcntl(llp->pipe[0], F_SETFL, O_NONBLOCK) == -1 || fcntl(llp->pipe[1], F_SETFL, O_NONBLOCK) == -1) { close(llp->pipe[0]); close(llp->pipe[1]); free(llp); return NULL; } /* *the default size is 16 pages on linux */ llp->size = pagesize * 16; llp->can_grow = 1; pthread_setspecific(f->pipe_key, llp); } return llp; } #endif static void fuse_ll_clear_pipe(struct fuse_ll *f) { struct fuse_ll_pipe *llp = pthread_getspecific(f->pipe_key); if (llp) { pthread_setspecific(f->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) { fprintf(stderr, "fuse: internal error: failed to read back from pipe: %s\n", strerror(errno)); return -EIO; } if (res != len) { fprintf(stderr, "fuse: internal error: short read back from pipe: %i from %zi\n", res, len); return -EIO; } return 0; } static int fuse_send_data_iov(struct fuse_ll *f, struct fuse_chan *ch, struct iovec *iov, int iov_count, struct fuse_bufvec *buf, unsigned int flags) { 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_fd_size; size_t idx; size_t headerlen; struct fuse_bufvec pipe_buf = FUSE_BUFVEC_INIT(len); if (f->broken_splice_nonblock) goto fallback; if (flags & FUSE_BUF_NO_SPLICE) goto fallback; total_fd_size = 0; for (idx = buf->idx; idx < buf->count; idx++) { if (buf->buf[idx].flags & FUSE_BUF_IS_FD) { total_fd_size = buf->buf[idx].size; if (idx == buf->idx) total_fd_size -= buf->off; } } if (total_fd_size < 2 * pagesize) goto fallback; if (f->conn.proto_minor < 14 || !(f->conn.want & FUSE_CAP_SPLICE_WRITE)) goto fallback; llp = fuse_ll_get_pipe(f); 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) { 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; fprintf(stderr, "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) f->broken_splice_nonblock = 1; pthread_setspecific(f->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(f, ch, iov, iov_count); free(mbuf); return res; } free(mbuf); res = now_len; } len = res; out->len = headerlen + len; splice_flags = 0; if ((flags & FUSE_BUF_SPLICE_MOVE) && (f->conn.want & FUSE_CAP_SPLICE_MOVE)) splice_flags |= SPLICE_F_MOVE; res = splice(llp->pipe[0], NULL, fuse_chan_fd(ch), NULL, out->len, splice_flags); if (res == -1) { res = -errno; perror("fuse: splice from pipe"); goto clear_pipe; } if (res != out->len) { res = -EIO; fprintf(stderr, "fuse: short splice from pipe: %u/%u\n", res, out->len); goto clear_pipe; } return 0; clear_pipe: fuse_ll_clear_pipe(f); return res; fallback: return fuse_send_data_iov_fallback(f, ch, iov, iov_count, buf, len); } #else static int fuse_send_data_iov(struct fuse_ll *f, struct fuse_chan *ch, struct iovec *iov, int iov_count, struct fuse_bufvec *buf, unsigned int flags) { size_t len = fuse_buf_size(buf); (void) flags; return fuse_send_data_iov_fallback(f, ch, iov, iov_count, buf, len); } #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; 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->f, req->ch, iov, 1, bufv, flags); if (res <= 0) { destroy_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 = {0}; size_t size = req->f->conn.proto_minor < 4 ? FUSE_COMPAT_STATFS_SIZE : 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 = {0}; 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 = {0}; 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 = {0}; 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 = {0}; struct fuse_ioctl_iovec *in_fiov = NULL; struct fuse_ioctl_iovec *out_fiov = NULL; struct iovec iov[4]; size_t count = 1; int res; 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->f->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->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, uint32_t size) { int count; struct iovec iov[3]; struct fuse_ioctl_out arg; arg.result = result; arg.flags = 0; arg.in_iovs = 0; arg.out_iovs = 0; count = 1; 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 = {0}; int res; padded_iov = malloc((count + 2) * sizeof(struct iovec)); if (padded_iov == NULL) return fuse_reply_err(req, ENOMEM); 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 = {0}; arg.revents = revents; return send_reply_ok(req, &arg, sizeof(arg)); } static void do_lookup(fuse_req_t req, uint64_t nodeid, const void *inarg) { char *name = (char*)inarg; req->f->op.lookup(req,nodeid,name); } static void do_forget(fuse_req_t req, uint64_t nodeid, const void *inarg) { struct fuse_forget_in *arg = (struct fuse_forget_in*)inarg; req->f->op.forget(req,nodeid,arg->nlookup); } static void do_batch_forget(fuse_req_t req, uint64_t nodeid, const void *inarg) { struct fuse_batch_forget_in *arg = (void *) inarg; struct fuse_forget_one *param = (void *) PARAM(arg); (void)nodeid; req->f->op.forget_multi(req, arg->count, (struct fuse_forget_data*)param); } static void do_getattr(fuse_req_t req, uint64_t nodeid, const void *inarg) { fuse_file_info_t *fip = NULL; fuse_file_info_t fi = {0}; if(req->f->conn.proto_minor >= 9) { struct fuse_getattr_in *arg = (struct fuse_getattr_in*)inarg; if(arg->getattr_flags & FUSE_GETATTR_FH) { fi.fh = arg->fh; fip = &fi; } } req->f->op.getattr(req, nodeid, fip); } static void do_setattr(fuse_req_t req_, uint64_t nodeid_, const void *inarg_) { struct stat stbuf = {0}; fuse_file_info_t *fi; fuse_file_info_t fi_store; struct fuse_setattr_in *arg; fi = NULL; arg = (struct fuse_setattr_in*)inarg_; convert_attr(arg,&stbuf); if(arg->valid & FATTR_FH) { arg->valid &= ~FATTR_FH; memset(&fi_store,0,sizeof(fi_store)); fi = &fi_store; fi->fh = arg->fh; } arg->valid &= (FATTR_MODE | FATTR_UID | FATTR_GID | FATTR_SIZE | FATTR_ATIME | FATTR_MTIME | FATTR_CTIME | FATTR_ATIME_NOW | FATTR_MTIME_NOW); req_->f->op.setattr(req_,nodeid_,&stbuf,arg->valid,fi); } static void do_access(fuse_req_t req, uint64_t nodeid, const void *inarg) { struct fuse_access_in *arg = (struct fuse_access_in *)inarg; req->f->op.access(req, nodeid, arg->mask); } static void do_readlink(fuse_req_t req, uint64_t nodeid, const void *inarg) { (void)inarg; req->f->op.readlink(req, nodeid); } static void do_mknod(fuse_req_t req, uint64_t nodeid, const void *inarg) { struct fuse_mknod_in *arg = (struct fuse_mknod_in *) inarg; char *name = PARAM(arg); if (req->f->conn.proto_minor >= 12) req->ctx.umask = arg->umask; else name = (char*)inarg + FUSE_COMPAT_MKNOD_IN_SIZE; req->f->op.mknod(req, nodeid, name, arg->mode, arg->rdev); } static void do_mkdir(fuse_req_t req, uint64_t nodeid, const void *inarg) { struct fuse_mkdir_in *arg = (struct fuse_mkdir_in *) inarg; if(req->f->conn.proto_minor >= 12) req->ctx.umask = arg->umask; req->f->op.mkdir(req, nodeid, PARAM(arg), arg->mode); } static void do_unlink(fuse_req_t req, uint64_t nodeid, const void *inarg) { char *name = (char*)inarg; req->f->op.unlink(req,nodeid,name); } static void do_rmdir(fuse_req_t req, uint64_t nodeid, const void *inarg) { char *name = (char*)inarg; req->f->op.rmdir(req, nodeid, name); } static void do_symlink(fuse_req_t req, uint64_t nodeid, const void *inarg) { char *name = (char*)inarg; char *linkname = (name + strlen(name) + 1); req->f->op.symlink(req, linkname, nodeid, name); } static void do_rename(fuse_req_t req, uint64_t nodeid, const void *inarg) { struct fuse_rename_in *arg = (struct fuse_rename_in*)inarg; char *oldname = PARAM(arg); char *newname = oldname + strlen(oldname) + 1; req->f->op.rename(req, nodeid, oldname, arg->newdir, newname); } static void do_link(fuse_req_t req, uint64_t nodeid, const void *inarg) { struct fuse_link_in *arg = (struct fuse_link_in*)inarg; req->f->op.link(req,arg->oldnodeid,nodeid,PARAM(arg)); } static void do_create(fuse_req_t req, uint64_t nodeid, const void *inarg) { struct fuse_create_in *arg = (struct fuse_create_in*)inarg; fuse_file_info_t fi = {0}; char *name = PARAM(arg); fi.flags = arg->flags; if (req->f->conn.proto_minor >= 12) req->ctx.umask = arg->umask; else name = (char*)inarg + sizeof(struct fuse_open_in); req->f->op.create(req, nodeid, name, arg->mode, &fi); } static void do_open(fuse_req_t req, uint64_t nodeid, const void *inarg) { fuse_file_info_t fi = {0}; struct fuse_open_in *arg = (struct fuse_open_in*)inarg; fi.flags = arg->flags; req->f->op.open(req, nodeid, &fi); } static void do_read(fuse_req_t req, uint64_t nodeid, const void *inarg) { fuse_file_info_t fi = {0}; struct fuse_read_in *arg = (struct fuse_read_in*)inarg; fi.fh = arg->fh; if (req->f->conn.proto_minor >= 9) { fi.lock_owner = arg->lock_owner; fi.flags = arg->flags; } req->f->op.read(req, nodeid, arg->size, arg->offset, &fi); } static void do_write(fuse_req_t req, uint64_t nodeid, const void *inarg) { char *param; fuse_file_info_t fi = {0}; struct fuse_write_in *arg = (struct fuse_write_in*)inarg; fi.fh = arg->fh; fi.writepage = arg->write_flags & 1; if(req->f->conn.proto_minor < 9) { param = ((char*)arg) + FUSE_COMPAT_WRITE_IN_SIZE; } else { fi.lock_owner = arg->lock_owner; fi.flags = arg->flags; param = PARAM(arg); } req->f->op.write(req,nodeid,param,arg->size,arg->offset,&fi); } static void do_write_buf(fuse_req_t req, uint64_t nodeid, const void *inarg, const struct fuse_buf *ibuf) { struct fuse_ll *f = req->f; struct fuse_bufvec bufv = { .buf[0] = *ibuf, .count = 1, }; fuse_file_info_t fi = {0}; struct fuse_write_in *arg = (struct fuse_write_in *) inarg; fi.fh = arg->fh; fi.writepage = arg->write_flags & 1; if (req->f->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 { fi.lock_owner = arg->lock_owner; fi.flags = arg->flags; 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) { fprintf(stderr, "fuse: do_write_buf: buffer size too small\n"); fuse_reply_err(req, EIO); goto out; } bufv.buf[0].size = arg->size; req->f->op.write_buf(req, nodeid, &bufv, arg->offset, &fi); 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(f); } static void do_flush(fuse_req_t req, uint64_t nodeid, const void *inarg) { fuse_file_info_t fi = {0}; struct fuse_flush_in *arg = (struct fuse_flush_in *) inarg; fi.fh = arg->fh; fi.flush = 1; if(req->f->conn.proto_minor >= 7) fi.lock_owner = arg->lock_owner; req->f->op.flush(req,nodeid,&fi); } static void do_release(fuse_req_t req, uint64_t nodeid, const void *inarg) { fuse_file_info_t fi = {0}; struct fuse_release_in *arg = (struct fuse_release_in*)inarg; fi.flags = arg->flags; fi.fh = arg->fh; if(req->f->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; } req->f->op.release(req,nodeid,&fi); } static void do_fsync(fuse_req_t req, uint64_t nodeid, const void *inarg) { fuse_file_info_t fi = {0}; struct fuse_fsync_in *arg = (struct fuse_fsync_in*)inarg; fi.fh = arg->fh; req->f->op.fsync(req,nodeid,arg->fsync_flags & 1, &fi); } static void do_opendir(fuse_req_t req, uint64_t nodeid, const void *inarg) { fuse_file_info_t fi = {0}; struct fuse_open_in *arg = (struct fuse_open_in*)inarg; fi.flags = arg->flags; req->f->op.opendir(req,nodeid,&fi); } static void do_readdir(fuse_req_t req, uint64_t nodeid, const void *inarg) { fuse_file_info_t fi = {0}; struct fuse_read_in *arg = (struct fuse_read_in*)inarg; fi.fh = arg->fh; req->f->op.readdir(req,nodeid,arg->size,arg->offset,&fi); } static void do_readdir_plus(fuse_req_t req_, uint64_t nodeid_, const void *inarg_) { const struct fuse_read_in *arg; fuse_file_info_t ffi = {0}; arg = (struct fuse_read_in*)inarg_; ffi.fh = arg->fh; req_->f->op.readdir_plus(req_,nodeid_,arg->size,arg->offset,&ffi); } static void do_releasedir(fuse_req_t req, uint64_t nodeid, const void *inarg) { fuse_file_info_t fi = {0}; struct fuse_release_in *arg = (struct fuse_release_in*)inarg; fi.flags = arg->flags; fi.fh = arg->fh; req->f->op.releasedir(req,nodeid,&fi); } static void do_fsyncdir(fuse_req_t req, uint64_t nodeid, const void *inarg) { fuse_file_info_t fi = {0}; struct fuse_fsync_in *arg = (struct fuse_fsync_in*)inarg; fi.fh = arg->fh; req->f->op.fsyncdir(req,nodeid,arg->fsync_flags & 1,&fi); } static void do_statfs(fuse_req_t req, uint64_t nodeid, const void *inarg) { (void)nodeid; (void)inarg; req->f->op.statfs(req, nodeid); } static void do_setxattr(fuse_req_t req, uint64_t nodeid, const void *inarg) { struct fuse_setxattr_in *arg = (struct fuse_setxattr_in*)inarg; char *name = PARAM(arg); char *value = name + strlen(name) + 1; req->f->op.setxattr(req, nodeid, name, value, arg->size, arg->flags); } static void do_getxattr(fuse_req_t req, uint64_t nodeid, const void *inarg) { struct fuse_getxattr_in *arg = (struct fuse_getxattr_in*)inarg; req->f->op.getxattr(req, nodeid, PARAM(arg), arg->size); } static void do_listxattr(fuse_req_t req, uint64_t nodeid, const void *inarg) { struct fuse_getxattr_in *arg = (struct fuse_getxattr_in*)inarg; req->f->op.listxattr(req, nodeid, arg->size); } static void do_removexattr(fuse_req_t req, uint64_t nodeid, const void *inarg) { char *name = (char *) inarg; req->f->op.removexattr(req, nodeid, name); } static void convert_fuse_file_lock(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, uint64_t nodeid, const void *inarg) { fuse_file_info_t fi = {0}; struct flock flock; struct fuse_lk_in *arg = (struct fuse_lk_in*)inarg; fi.fh = arg->fh; fi.lock_owner = arg->owner; convert_fuse_file_lock(&arg->lk, &flock); req->f->op.getlk(req, nodeid, &fi, &flock); } static void do_setlk_common(fuse_req_t req, uint64_t nodeid, const void *inarg, int sleep) { struct flock flock; fuse_file_info_t fi = {0}; struct fuse_lk_in *arg = (struct fuse_lk_in *) inarg; 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; req->f->op.flock(req,nodeid,&fi,op); } else { convert_fuse_file_lock(&arg->lk, &flock); req->f->op.setlk(req,nodeid,&fi,&flock,sleep); } } static void do_setlk(fuse_req_t req, uint64_t nodeid, const void *inarg) { do_setlk_common(req, nodeid, inarg, 0); } static void do_setlkw(fuse_req_t req, uint64_t nodeid, const void *inarg) { do_setlk_common(req, nodeid, inarg, 1); } static void do_interrupt(fuse_req_t req, uint64_t nodeid, const void *inarg) { destroy_req(req); } static void do_bmap(fuse_req_t req, uint64_t nodeid, const void *inarg) { struct fuse_bmap_in *arg = (struct fuse_bmap_in*)inarg; req->f->op.bmap(req,nodeid,arg->blocksize,arg->block); } static void do_ioctl(fuse_req_t req, uint64_t nodeid, const void *inarg) { fuse_file_info_t fi = {0}; struct fuse_ioctl_in *arg = (struct fuse_ioctl_in *) inarg; unsigned int flags = arg->flags; void *in_buf = arg->in_size ? PARAM(arg) : NULL; if((flags & FUSE_IOCTL_DIR) && !(req->f->conn.want & FUSE_CAP_IOCTL_DIR)) { fuse_reply_err(req,ENOTTY); return; } fi.fh = arg->fh; if((sizeof(void *) == 4) && (req->f->conn.proto_minor >= 16) && !(flags & FUSE_IOCTL_32BIT)) { req->ioctl_64bit = 1; } req->f->op.ioctl(req, nodeid, (unsigned long)arg->cmd, (void *)(uintptr_t)arg->arg, &fi, flags, in_buf, arg->in_size, arg->out_size); } void fuse_pollhandle_destroy(fuse_pollhandle_t *ph) { free(ph); } static void do_poll(fuse_req_t req, uint64_t nodeid, const void *inarg) { fuse_file_info_t fi = {0}; fuse_pollhandle_t *ph = NULL; struct fuse_poll_in *arg = (struct fuse_poll_in *) inarg; fi.fh = arg->fh; if(arg->flags & FUSE_POLL_SCHEDULE_NOTIFY) { ph = malloc(sizeof(fuse_pollhandle_t)); if (ph == NULL) { fuse_reply_err(req, ENOMEM); return; } ph->kh = arg->kh; ph->ch = req->ch; ph->f = req->f; } req->f->op.poll(req,nodeid,&fi,ph); } static void do_fallocate(fuse_req_t req, uint64_t nodeid, const void *inarg) { fuse_file_info_t fi = {0}; struct fuse_fallocate_in *arg = (struct fuse_fallocate_in *) inarg; fi.fh = arg->fh; req->f->op.fallocate(req, nodeid, arg->mode, arg->offset, arg->length, &fi); } static void do_init(fuse_req_t req, uint64_t nodeid, const void *inarg) { struct fuse_init_out outarg = {0}; struct fuse_init_in *arg = (struct fuse_init_in *) inarg; struct fuse_ll *f = req->f; size_t bufsize = fuse_chan_bufsize(req->ch); (void)nodeid; if(f->debug) debug_fuse_init_in(arg); f->conn.proto_major = arg->major; f->conn.proto_minor = arg->minor; f->conn.capable = 0; f->conn.want = 0; outarg.major = FUSE_KERNEL_VERSION; outarg.minor = FUSE_KERNEL_MINOR_VERSION; outarg.max_pages = FUSE_DEFAULT_MAX_PAGES_PER_REQ; if (arg->major < 7) { fprintf(stderr, "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 < f->conn.max_readahead) f->conn.max_readahead = arg->max_readahead; if (arg->flags & FUSE_ASYNC_READ) f->conn.capable |= FUSE_CAP_ASYNC_READ; if (arg->flags & FUSE_POSIX_LOCKS) f->conn.capable |= FUSE_CAP_POSIX_LOCKS; if (arg->flags & FUSE_ATOMIC_O_TRUNC) f->conn.capable |= FUSE_CAP_ATOMIC_O_TRUNC; if (arg->flags & FUSE_EXPORT_SUPPORT) f->conn.capable |= FUSE_CAP_EXPORT_SUPPORT; if (arg->flags & FUSE_BIG_WRITES) f->conn.capable |= FUSE_CAP_BIG_WRITES; if (arg->flags & FUSE_DONT_MASK) f->conn.capable |= FUSE_CAP_DONT_MASK; if (arg->flags & FUSE_FLOCK_LOCKS) f->conn.capable |= FUSE_CAP_FLOCK_LOCKS; if (arg->flags & FUSE_POSIX_ACL) f->conn.capable |= FUSE_CAP_POSIX_ACL; if (arg->flags & FUSE_CACHE_SYMLINKS) f->conn.capable |= FUSE_CAP_CACHE_SYMLINKS; if (arg->flags & FUSE_ASYNC_DIO) f->conn.capable |= FUSE_CAP_ASYNC_DIO; if (arg->flags & FUSE_PARALLEL_DIROPS) f->conn.capable |= FUSE_CAP_PARALLEL_DIROPS; if (arg->flags & FUSE_MAX_PAGES) f->conn.capable |= FUSE_CAP_MAX_PAGES; if (arg->flags & FUSE_WRITEBACK_CACHE) f->conn.capable |= FUSE_CAP_WRITEBACK_CACHE; if (arg->flags & FUSE_DO_READDIRPLUS) f->conn.capable |= FUSE_CAP_READDIR_PLUS; if (arg->flags & FUSE_READDIRPLUS_AUTO) f->conn.capable |= FUSE_CAP_READDIR_PLUS_AUTO; } else { f->conn.want &= ~FUSE_CAP_ASYNC_READ; f->conn.max_readahead = 0; } if (req->f->conn.proto_minor >= 14) { #ifdef HAVE_SPLICE #ifdef HAVE_VMSPLICE f->conn.capable |= FUSE_CAP_SPLICE_WRITE | FUSE_CAP_SPLICE_MOVE; if (f->splice_write) f->conn.want |= FUSE_CAP_SPLICE_WRITE; if (f->splice_move) f->conn.want |= FUSE_CAP_SPLICE_MOVE; #endif f->conn.capable |= FUSE_CAP_SPLICE_READ; if (f->splice_read) f->conn.want |= FUSE_CAP_SPLICE_READ; #endif } if (req->f->conn.proto_minor >= 18) f->conn.capable |= FUSE_CAP_IOCTL_DIR; if (f->op.getlk && f->op.setlk && !f->no_remote_posix_lock) f->conn.want |= FUSE_CAP_POSIX_LOCKS; if (f->op.flock && !f->no_remote_flock) f->conn.want |= FUSE_CAP_FLOCK_LOCKS; if (bufsize < FUSE_MIN_READ_BUFFER) { fprintf(stderr, "fuse: warning: buffer size too small: %zu\n", bufsize); bufsize = FUSE_MIN_READ_BUFFER; } bufsize -= 4096; if (bufsize < f->conn.max_write) f->conn.max_write = bufsize; f->got_init = 1; if (f->op.init) f->op.init(f->userdata, &f->conn); if (f->no_splice_read) f->conn.want &= ~FUSE_CAP_SPLICE_READ; if (f->no_splice_write) f->conn.want &= ~FUSE_CAP_SPLICE_WRITE; if (f->no_splice_move) f->conn.want &= ~FUSE_CAP_SPLICE_MOVE; if ((arg->flags & FUSE_MAX_PAGES) && (f->conn.want & FUSE_CAP_MAX_PAGES)) { outarg.flags |= FUSE_MAX_PAGES; outarg.max_pages = f->conn.max_pages; } if (f->conn.want & FUSE_CAP_ASYNC_READ) outarg.flags |= FUSE_ASYNC_READ; if (f->conn.want & FUSE_CAP_POSIX_LOCKS) outarg.flags |= FUSE_POSIX_LOCKS; if (f->conn.want & FUSE_CAP_ATOMIC_O_TRUNC) outarg.flags |= FUSE_ATOMIC_O_TRUNC; if (f->conn.want & FUSE_CAP_EXPORT_SUPPORT) outarg.flags |= FUSE_EXPORT_SUPPORT; if (f->conn.want & FUSE_CAP_BIG_WRITES) outarg.flags |= FUSE_BIG_WRITES; if (f->conn.want & FUSE_CAP_DONT_MASK) outarg.flags |= FUSE_DONT_MASK; if (f->conn.want & FUSE_CAP_FLOCK_LOCKS) outarg.flags |= FUSE_FLOCK_LOCKS; if (f->conn.want & FUSE_CAP_POSIX_ACL) outarg.flags |= FUSE_POSIX_ACL; if (f->conn.want & FUSE_CAP_CACHE_SYMLINKS) outarg.flags |= FUSE_CACHE_SYMLINKS; if (f->conn.want & FUSE_CAP_ASYNC_DIO) outarg.flags |= FUSE_ASYNC_DIO; if (f->conn.want & FUSE_CAP_PARALLEL_DIROPS) outarg.flags |= FUSE_PARALLEL_DIROPS; if (f->conn.want & FUSE_CAP_WRITEBACK_CACHE) outarg.flags |= FUSE_WRITEBACK_CACHE; if (f->conn.want & FUSE_CAP_READDIR_PLUS) outarg.flags |= FUSE_DO_READDIRPLUS; if (f->conn.want & FUSE_CAP_READDIR_PLUS_AUTO) outarg.flags |= FUSE_READDIRPLUS_AUTO; outarg.max_readahead = f->conn.max_readahead; outarg.max_write = f->conn.max_write; if (f->conn.proto_minor >= 13) { if (f->conn.max_background >= (1 << 16)) f->conn.max_background = (1 << 16) - 1; if (f->conn.congestion_threshold > f->conn.max_background) f->conn.congestion_threshold = f->conn.max_background; if (!f->conn.congestion_threshold) { f->conn.congestion_threshold = f->conn.max_background * 3 / 4; } outarg.max_background = f->conn.max_background; outarg.congestion_threshold = f->conn.congestion_threshold; } size_t outargsize; if(arg->minor < 5) outargsize = FUSE_COMPAT_INIT_OUT_SIZE; else if(arg->minor < 23) outargsize = FUSE_COMPAT_22_INIT_OUT_SIZE; else outargsize = sizeof(outarg); if(f->debug) debug_fuse_init_out(req->unique,&outarg,outargsize); send_reply_ok(req, &outarg, outargsize); } static void do_destroy(fuse_req_t req, uint64_t nodeid, const void *inarg) { struct fuse_ll *f = req->f; (void) nodeid; (void) inarg; f->got_destroy = 1; f->op.destroy(f->userdata); send_reply_ok(req,NULL,0); } 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, uint64_t nodeid, const void *inarg, const struct fuse_buf *buf) { struct fuse_ll *f = req->f; struct fuse_notify_req *nreq; struct fuse_notify_req *head; pthread_mutex_lock(&f->lock); head = &f->notify_list; for (nreq = head->next; nreq != head; nreq = nreq->next) { if (nreq->unique == req->unique) { list_del_nreq(nreq); break; } } pthread_mutex_unlock(&f->lock); if (nreq != head) nreq->reply(nreq, req, nodeid, inarg, buf); } static void do_copy_file_range(fuse_req_t req_, uint64_t nodeid_in_, const void *arg_) { fuse_file_info_t ffi_in = {0}; fuse_file_info_t ffi_out = {0}; struct fuse_copy_file_range_in *arg = (struct fuse_copy_file_range_in*)arg_; ffi_in.fh = arg->fh_in; ffi_out.fh = arg->fh_out; req_->f->op.copy_file_range(req_, nodeid_in_, arg->off_in, &ffi_in, arg->nodeid_out, arg->off_out, &ffi_out, arg->len, arg->flags); } static int send_notify_iov(struct fuse_ll *f, struct fuse_chan *ch, int notify_code, struct iovec *iov, int count) { struct fuse_out_header out; if (!f->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(f, ch, iov, count); } int fuse_lowlevel_notify_poll(fuse_pollhandle_t *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->f, ph->ch, FUSE_NOTIFY_POLL, iov, 2); } else { return 0; } } int fuse_lowlevel_notify_inval_inode(struct fuse_chan *ch, uint64_t ino, off_t off, off_t len) { struct fuse_notify_inval_inode_out outarg; struct fuse_ll *f; struct iovec iov[2]; if (!ch) return -EINVAL; f = (struct fuse_ll *)fuse_session_data(fuse_chan_session(ch)); if (!f) return -ENODEV; outarg.ino = ino; outarg.off = off; outarg.len = len; iov[1].iov_base = &outarg; iov[1].iov_len = sizeof(outarg); return send_notify_iov(f, ch, FUSE_NOTIFY_INVAL_INODE, iov, 2); } int fuse_lowlevel_notify_inval_entry(struct fuse_chan *ch, uint64_t parent, const char *name, size_t namelen) { struct fuse_notify_inval_entry_out outarg; struct fuse_ll *f; struct iovec iov[3]; if (!ch) return -EINVAL; f = (struct fuse_ll *)fuse_session_data(fuse_chan_session(ch)); if (!f) return -ENODEV; outarg.parent = parent; 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(f, ch, FUSE_NOTIFY_INVAL_ENTRY, iov, 3); } int fuse_lowlevel_notify_delete(struct fuse_chan *ch, uint64_t parent, uint64_t child, const char *name, size_t namelen) { struct fuse_notify_delete_out outarg; struct fuse_ll *f; struct iovec iov[3]; if (!ch) return -EINVAL; f = (struct fuse_ll *)fuse_session_data(fuse_chan_session(ch)); if (!f) return -ENODEV; if (f->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(f, ch, FUSE_NOTIFY_DELETE, iov, 3); } int fuse_lowlevel_notify_store(struct fuse_chan *ch, uint64_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 fuse_ll *f; struct iovec iov[3]; size_t size = fuse_buf_size(bufv); int res; if (!ch) return -EINVAL; f = (struct fuse_ll *)fuse_session_data(fuse_chan_session(ch)); if (!f) return -ENODEV; if (f->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(f, ch, iov, 2, bufv, flags); 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, uint64_t ino, const void *inarg, const struct fuse_buf *ibuf) { struct fuse_ll *f = req->f; 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_in_header) + sizeof(struct fuse_notify_retrieve_in); if (bufv.buf[0].size < arg->size) { fprintf(stderr, "fuse: retrieve reply: buffer size too small\n"); fuse_reply_none(req); goto out; } bufv.buf[0].size = arg->size; if (req->f->op.retrieve_reply) { req->f->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(f); } int fuse_lowlevel_notify_retrieve(struct fuse_chan *ch, uint64_t ino, size_t size, off_t offset, void *cookie) { struct fuse_notify_retrieve_out outarg; struct fuse_ll *f; struct iovec iov[2]; struct fuse_retrieve_req *rreq; int err; if (!ch) return -EINVAL; f = (struct fuse_ll *)fuse_session_data(fuse_chan_session(ch)); if (!f) return -ENODEV; if (f->conn.proto_minor < 15) return -ENOSYS; rreq = malloc(sizeof(*rreq)); if (rreq == NULL) return -ENOMEM; pthread_mutex_lock(&f->lock); rreq->cookie = cookie; rreq->nreq.unique = f->notify_ctr++; rreq->nreq.reply = fuse_ll_retrieve_reply; list_add_nreq(&rreq->nreq, &f->notify_list); pthread_mutex_unlock(&f->lock); outarg.notify_unique = rreq->nreq.unique; outarg.nodeid = ino; outarg.offset = offset; outarg.size = size; iov[1].iov_base = &outarg; iov[1].iov_len = sizeof(outarg); err = send_notify_iov(f, ch, FUSE_NOTIFY_RETRIEVE, iov, 2); if (err) { pthread_mutex_lock(&f->lock); list_del_nreq(&rreq->nreq); pthread_mutex_unlock(&f->lock); free(rreq); } return err; } void * fuse_req_userdata(fuse_req_t req) { return req->f->userdata; } const struct fuse_ctx * fuse_req_ctx(fuse_req_t req) { return &req->ctx; } static struct { void (*func)(fuse_req_t, uint64_t, const void *); 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_READDIRPLUS] = { do_readdir_plus, "READDIR_PLUS" }, [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_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_COPY_FILE_RANGE] = { do_copy_file_range, "COPY_FILE_RANGE" }, }; #define FUSE_MAXOP (sizeof(fuse_ll_ops) / sizeof(fuse_ll_ops[0])) static int fuse_ll_copy_from_pipe(struct fuse_bufvec *dst, struct fuse_bufvec *src) { int res = fuse_buf_copy(dst, src, 0); if (res < 0) { fprintf(stderr, "fuse: copy from pipe: %s\n", strerror(-res)); return res; } if (res < fuse_buf_size(dst)) { fprintf(stderr, "fuse: copy from pipe: short read\n"); return -1; } return 0; } static void fuse_ll_process_buf(void *data, const struct fuse_buf *buf, struct fuse_chan *ch) { struct fuse_ll *f = (struct fuse_ll *) data; 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) { fprintf(stderr, "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; } req = fuse_ll_alloc_req(f); 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(f, ch, &iov, 1); goto clear_pipe; } req->unique = in->unique; req->ctx.uid = in->uid; req->ctx.gid = in->gid; req->ctx.pid = in->pid; req->ch = ch; err = EIO; if(!f->got_init) { enum fuse_opcode expected; expected = FUSE_INIT; if (in->opcode != expected) goto reply_err; } else if(in->opcode == FUSE_INIT) { goto reply_err; } err = ENOSYS; if (in->opcode >= FUSE_MAXOP || !fuse_ll_ops[in->opcode].func) goto reply_err; if ((buf->flags & FUSE_BUF_IS_FD) && write_header_size < buf->size && (in->opcode != FUSE_WRITE || !f->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 = 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) 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(f); goto out_free; } static void fuse_ll_process(void *data, const char *buf, size_t len, struct fuse_chan *ch) { struct fuse_buf fbuf = { .mem = (void *) buf, .size = len, }; fuse_ll_process_buf(data, &fbuf, ch); } enum { KEY_HELP, KEY_VERSION, }; static const struct fuse_opt fuse_ll_opts[] = { { "debug", offsetof(struct fuse_ll, debug), 1 }, { "-d", offsetof(struct fuse_ll, debug), 1 }, { "max_readahead=%u", offsetof(struct fuse_ll, conn.max_readahead), 0 }, { "max_background=%u", offsetof(struct fuse_ll, conn.max_background), 0 }, { "congestion_threshold=%u", offsetof(struct fuse_ll, conn.congestion_threshold), 0 }, { "no_remote_lock", offsetof(struct fuse_ll, no_remote_posix_lock), 1}, { "no_remote_lock", offsetof(struct fuse_ll, no_remote_flock), 1}, { "no_remote_flock", offsetof(struct fuse_ll, no_remote_flock), 1}, { "no_remote_posix_lock", offsetof(struct fuse_ll, no_remote_posix_lock), 1}, { "splice_write", offsetof(struct fuse_ll, splice_write), 1}, { "no_splice_write", offsetof(struct fuse_ll, no_splice_write), 1}, { "splice_move", offsetof(struct fuse_ll, splice_move), 1}, { "no_splice_move", offsetof(struct fuse_ll, no_splice_move), 1}, { "splice_read", offsetof(struct fuse_ll, splice_read), 1}, { "no_splice_read", offsetof(struct fuse_ll, no_splice_read), 1}, FUSE_OPT_KEY("max_read=", FUSE_OPT_KEY_DISCARD), FUSE_OPT_KEY("-h", KEY_HELP), FUSE_OPT_KEY("--help", KEY_HELP), FUSE_OPT_KEY("-V", KEY_VERSION), FUSE_OPT_KEY("--version", KEY_VERSION), FUSE_OPT_END }; static void fuse_ll_version(void) { fprintf(stderr, "using FUSE kernel interface version %i.%i\n", FUSE_KERNEL_VERSION, FUSE_KERNEL_MINOR_VERSION); } static void fuse_ll_help(void) { fprintf(stderr, " -o max_readahead=N set maximum readahead\n" " -o max_background=N set number of maximum background requests\n" " -o congestion_threshold=N set kernel's congestion threshold\n" " -o no_remote_lock disable remote file locking\n" " -o no_remote_flock disable remote file locking (BSD)\n" " -o no_remote_posix_lock disable remove file locking (POSIX)\n" " -o [no_]splice_write use splice to write to the fuse device\n" " -o [no_]splice_move move data while splicing to the fuse device\n" " -o [no_]splice_read use splice to read from the fuse device\n" ); } static int fuse_ll_opt_proc(void *data, const char *arg, int key, struct fuse_args *outargs) { (void) data; (void) outargs; switch (key) { case KEY_HELP: fuse_ll_help(); break; case KEY_VERSION: fuse_ll_version(); break; default: fprintf(stderr, "fuse: unknown option `%s'\n", arg); } return -1; } int fuse_lowlevel_is_lib_option(const char *opt) { return fuse_opt_match(fuse_ll_opts, opt); } static void fuse_ll_destroy(void *data) { struct fuse_ll *f = (struct fuse_ll *) data; struct fuse_ll_pipe *llp; if (f->got_init && !f->got_destroy) { if (f->op.destroy) f->op.destroy(f->userdata); } llp = pthread_getspecific(f->pipe_key); if (llp != NULL) fuse_ll_pipe_free(llp); pthread_key_delete(f->pipe_key); pthread_mutex_destroy(&f->lock); free(f); lfmp_clear(&g_FMP_fuse_req); } static void fuse_ll_pipe_destructor(void *data) { struct fuse_ll_pipe *llp = data; fuse_ll_pipe_free(llp); } #ifdef HAVE_SPLICE static int fuse_ll_receive_buf(struct fuse_session *se, struct fuse_buf *buf, struct fuse_chan **chp) { struct fuse_chan *ch = *chp; struct fuse_ll *f = fuse_session_data(se); size_t bufsize = buf->size; struct fuse_ll_pipe *llp; struct fuse_buf tmpbuf; int err; int res; if (f->conn.proto_minor < 14 || !(f->conn.want & FUSE_CAP_SPLICE_READ)) goto fallback; llp = fuse_ll_get_pipe(f); 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; goto fallback; } llp->size = res; } if (llp->size < bufsize) goto fallback; } res = splice(fuse_chan_fd(ch), NULL, llp->pipe[1], NULL, bufsize, 0); err = errno; if(fuse_session_exited(se)) return 0; if (res == -1) { if (err == ENODEV) { fuse_session_exit(se); return 0; } if (err != EINTR && err != EAGAIN) perror("fuse: splice from device"); return -err; } if (res < sizeof(struct fuse_in_header)) { fprintf(stderr, "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 = { .buf[0] = *buf, .count = 1 }; res = fuse_buf_copy(&dst, &src, 0); if (res < 0) { fprintf(stderr, "fuse: copy from pipe: %s\n", strerror(-res)); fuse_ll_clear_pipe(f); return res; } if (res < tmpbuf.size) { fprintf(stderr, "fuse: copy from pipe: short read\n"); fuse_ll_clear_pipe(f); return -EIO; } buf->size = tmpbuf.size; return buf->size; } *buf = tmpbuf; return res; fallback: res = fuse_chan_recv(chp, buf->mem, bufsize); if (res <= 0) return res; buf->size = res; return res; } #else static int fuse_ll_receive_buf(struct fuse_session *se, struct fuse_buf *buf, struct fuse_chan **chp) { (void) se; int res = fuse_chan_recv(chp, buf->mem, buf->size); if (res <= 0) return res; buf->size = res; return res; } #endif /* * always call fuse_lowlevel_new_common() internally, to work around a * misfeature in the FreeBSD runtime linker, which links the old * version of a symbol to internal references. */ struct fuse_session * fuse_lowlevel_new_common(struct fuse_args *args, const struct fuse_lowlevel_ops *op, size_t op_size, void *userdata) { int err; struct fuse_ll *f; struct fuse_session *se; struct fuse_session_ops sop = { .process = fuse_ll_process, .destroy = fuse_ll_destroy, }; if (sizeof(struct fuse_lowlevel_ops) < op_size) { fprintf(stderr, "fuse: warning: library too old, some operations may not work\n"); op_size = sizeof(struct fuse_lowlevel_ops); } f = (struct fuse_ll *) calloc(1, sizeof(struct fuse_ll)); if (f == NULL) { fprintf(stderr, "fuse: failed to allocate fuse object\n"); goto out; } f->conn.max_write = UINT_MAX; f->conn.max_readahead = UINT_MAX; list_init_nreq(&f->notify_list); f->notify_ctr = 1; fuse_mutex_init(&f->lock); err = pthread_key_create(&f->pipe_key, fuse_ll_pipe_destructor); if (err) { fprintf(stderr, "fuse: failed to create thread specific key: %s\n", strerror(err)); goto out_free; } if (fuse_opt_parse(args, f, fuse_ll_opts, fuse_ll_opt_proc) == -1) goto out_key_destroy; memcpy(&f->op, op, op_size); f->owner = getuid(); f->userdata = userdata; se = fuse_session_new(&sop, f); if (!se) goto out_key_destroy; se->receive_buf = fuse_ll_receive_buf; se->process_buf = fuse_ll_process_buf; return se; out_key_destroy: pthread_key_delete(f->pipe_key); out_free: pthread_mutex_destroy(&f->lock); free(f); out: return NULL; } struct fuse_session* fuse_lowlevel_new(struct fuse_args *args, const struct fuse_lowlevel_ops *op, size_t op_size, void *userdata) { return fuse_lowlevel_new_common(args, op, op_size, userdata); } mergerfs-2.33.5/libfuse/lib/fuse_loop_mt.c0000644000000000000000000001160214225522122017147 0ustar rootroot/* 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 COPYING.LIB. */ #include "fuse_i.h" #include "fuse_kernel.h" #include "fuse_lowlevel.h" #include "fuse_misc.h" #include #include #include #include #include #include #include #include #include /* Environment var controlling the thread stack size */ #define ENVNAME_THREAD_STACK "FUSE_THREAD_STACK" struct fuse_worker { struct fuse_worker *prev; struct fuse_worker *next; pthread_t thread_id; size_t bufsize; char *buf; struct fuse_mt *mt; }; struct fuse_mt { struct fuse_session *se; struct fuse_chan *prevch; struct fuse_worker main; sem_t finish; int exit; int error; }; 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; while(!fuse_session_exited(mt->se)) { int res; struct fuse_buf fbuf; struct fuse_chan *ch = mt->prevch; fbuf = (struct fuse_buf){ .mem = w->buf, .size = w->bufsize }; pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL); res = fuse_session_receive_buf(mt->se, &fbuf, &ch); pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL); if(res == -EINTR) continue; if(res <= 0) { if(res < 0) { fuse_session_exit(mt->se); mt->error = -1; } break; } if(mt->exit) return NULL; fuse_session_process_buf(mt->se, &fbuf, ch); } sem_post(&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 */ pthread_attr_init(&attr); stack_size = getenv(ENVNAME_THREAD_STACK); if(stack_size && pthread_attr_setstacksize(&attr, atoi(stack_size))) fprintf(stderr, "fuse: invalid stack size: %s\n", stack_size); /* Disallow signal reception in worker threads */ sigfillset(&newset); 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) { fprintf(stderr, "fuse: error creating thread: %s\n", strerror(res)); return -1; } return 0; } static int fuse_loop_start_thread(struct fuse_mt *mt) { int res; struct fuse_worker *w = malloc(sizeof(struct fuse_worker)); if(!w) { fprintf(stderr, "fuse: failed to allocate worker structure\n"); return -1; } memset(w, 0, sizeof(struct fuse_worker)); w->bufsize = fuse_chan_bufsize(mt->prevch); w->buf = calloc(w->bufsize,1); w->mt = mt; if(!w->buf) { fprintf(stderr, "fuse: failed to allocate read buffer\n"); free(w); return -1; } res = fuse_start_thread(&w->thread_id, fuse_do_work, w); if(res == -1) { free(w->buf); free(w); return -1; } list_add_worker(w, &mt->main); return 0; } static void fuse_join_worker(struct fuse_worker *w) { pthread_join(w->thread_id, NULL); list_del_worker(w); free(w->buf); free(w); } static int number_of_threads(void) { #ifdef _SC_NPROCESSORS_ONLN return sysconf(_SC_NPROCESSORS_ONLN); #endif return 4; } int fuse_session_loop_mt(struct fuse_session *se_, const int threads_) { int i; int err; int threads; struct fuse_mt mt; struct fuse_worker *w; memset(&mt,0,sizeof(struct fuse_mt)); mt.se = se_; mt.prevch = fuse_session_next_chan(se_,NULL); mt.error = 0; mt.main.thread_id = pthread_self(); mt.main.prev = mt.main.next = &mt.main; sem_init(&mt.finish,0,0); threads = ((threads_ > 0) ? threads_ : number_of_threads()); if(threads_ < 0) threads /= -threads_; if(threads == 0) threads = 1; err = 0; for(i = 0; (i < threads) && !err; i++) err = fuse_loop_start_thread(&mt); if(!err) { /* sem_wait() is interruptible */ while(!fuse_session_exited(se_)) sem_wait(&mt.finish); for(w = mt.main.next; w != &mt.main; w = w->next) pthread_cancel(w->thread_id); mt.exit = 1; while(mt.main.next != &mt.main) fuse_join_worker(mt.main.next); err = mt.error; } sem_destroy(&mt.finish); fuse_session_reset(se_); return err; } mergerfs-2.33.5/libfuse/lib/fuse_opt.c0000644000000000000000000002214214225522122016301 0ustar rootroot/* 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 COPYING.LIB */ #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) { fprintf(stderr, "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) { fprintf(stderr, "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 *copy = strdup(param); if (!copy) return alloc_failed(); *(char **) var = copy; } else { if (sscanf(param, format, var) != 1) { fprintf(stderr, "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 = 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) { fprintf(stderr, "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; } mergerfs-2.33.5/libfuse/lib/debug.h0000644000000000000000000000507214225522122015553 0ustar rootroot/* ISC License Copyright (c) 2021, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include "fuse_kernel.h" void debug_fuse_open_out(const uint64_t unique, const struct fuse_open_out *arg, const uint64_t argsize); void debug_fuse_init_in(const struct fuse_init_in *arg); void debug_fuse_init_out(const uint64_t unique, const struct fuse_init_out *arg, const uint64_t argsize); void debug_fuse_entry_out(const uint64_t unique, const struct fuse_entry_out *arg, const uint64_t argsize); void debug_fuse_attr_out(const uint64_t unique, const struct fuse_attr_out *arg, const uint64_t argsize); void debug_fuse_entry_open_out(const uint64_t unique, const struct fuse_entry_out *earg, const struct fuse_open_out *oarg); void debug_fuse_readlink(const uint64_t unique, const char *linkname); void debug_fuse_write_out(const uint64_t unique, const struct fuse_write_out *arg); void debug_fuse_statfs_out(const uint64_t unique, const struct fuse_statfs_out *arg); void debug_fuse_getxattr_out(const uint64_t unique, const struct fuse_getxattr_out *arg); void debug_fuse_lk_out(const uint64_t unique, const struct fuse_lk_out *arg); void debug_fuse_bmap_out(const uint64_t unique, const struct fuse_bmap_out *arg); void debug_fuse_in_header(const struct fuse_in_header *hdr); mergerfs-2.33.5/libfuse/lib/buffer.c0000644000000000000000000001535214225522122015733 0ustar rootroot/* FUSE: Filesystem in Userspace Copyright (C) 2010 Miklos Szeredi This program can be distributed under the terms of the GNU LGPLv2. See the file COPYING.LIB */ #define _GNU_SOURCE #include "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 = SIZE_MAX; else 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, src->mem + src_off, len, dst->pos + dst_off); } else { res = write(dst->fd, 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, dst->mem + dst_off, len, src->pos + src_off); } else { res = read(src->fd, 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 = dst->mem + dst_off; char *srcmem = 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); 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; } mergerfs-2.33.5/libfuse/lib/fuse_signals.c0000644000000000000000000000336214225522122017142 0ustar rootroot/* 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 COPYING.LIB */ #include "fuse_lowlevel.h" #include #include #include static struct fuse_session *fuse_instance; static void exit_handler(int sig) { (void) sig; if (fuse_instance) fuse_session_exit(fuse_instance); } 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; } int fuse_set_signal_handlers(struct fuse_session *se) { if (set_one_signal_handler(SIGHUP, exit_handler, 0) == -1 || set_one_signal_handler(SIGINT, exit_handler, 0) == -1 || set_one_signal_handler(SIGTERM, exit_handler, 0) == -1 || set_one_signal_handler(SIGPIPE, SIG_IGN, 0) == -1) return -1; fuse_instance = se; return 0; } void fuse_remove_signal_handlers(struct fuse_session *se) { if (fuse_instance != se) fprintf(stderr, "fuse: fuse_remove_signal_handlers: unknown session\n"); else fuse_instance = NULL; set_one_signal_handler(SIGHUP, exit_handler, 1); set_one_signal_handler(SIGINT, exit_handler, 1); set_one_signal_handler(SIGTERM, exit_handler, 1); set_one_signal_handler(SIGPIPE, SIG_IGN, 1); } mergerfs-2.33.5/libfuse/lib/fuse_session.c0000644000000000000000000001027114225522122017162 0ustar rootroot/* 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 COPYING.LIB */ #include "fuse_i.h" #include "fuse_misc.h" #include #include #include #include #include struct fuse_chan { struct fuse_chan_ops op; struct fuse_session *se; int fd; size_t bufsize; void *data; }; struct fuse_session *fuse_session_new(struct fuse_session_ops *op, void *data) { struct fuse_session *se = (struct fuse_session *) malloc(sizeof(*se)); if (se == NULL) { fprintf(stderr, "fuse: failed to allocate session\n"); return NULL; } memset(se, 0, sizeof(*se)); se->op = *op; se->data = data; return se; } void fuse_session_add_chan(struct fuse_session *se, struct fuse_chan *ch) { assert(se->ch == NULL); assert(ch->se == NULL); se->ch = ch; ch->se = se; } void fuse_session_remove_chan(struct fuse_chan *ch) { struct fuse_session *se = ch->se; if (se) { assert(se->ch == ch); se->ch = NULL; ch->se = NULL; } } struct fuse_chan *fuse_session_next_chan(struct fuse_session *se, struct fuse_chan *ch) { assert(ch == NULL || ch == se->ch); if (ch == NULL) return se->ch; else return NULL; } void fuse_session_process(struct fuse_session *se, const char *buf, size_t len, struct fuse_chan *ch) { se->op.process(se->data, buf, len, ch); } void fuse_session_process_buf(struct fuse_session *se, const struct fuse_buf *buf, struct fuse_chan *ch) { if (se->process_buf) { se->process_buf(se->data, buf, ch); } else { assert(!(buf->flags & FUSE_BUF_IS_FD)); fuse_session_process(se->data, buf->mem, buf->size, ch); } } int fuse_session_receive_buf(struct fuse_session *se, struct fuse_buf *buf, struct fuse_chan **chp) { int res; if (se->receive_buf) { res = se->receive_buf(se, buf, chp); } else { res = fuse_chan_recv(chp, buf->mem, buf->size); if (res > 0) buf->size = res; } return res; } void fuse_session_destroy(struct fuse_session *se) { if (se->op.destroy) se->op.destroy(se->data); if (se->ch != NULL) fuse_chan_destroy(se->ch); free(se); } void fuse_session_exit(struct fuse_session *se) { if (se->op.exit) se->op.exit(se->data, 1); se->exited = 1; } void fuse_session_reset(struct fuse_session *se) { if (se->op.exit) se->op.exit(se->data, 0); se->exited = 0; } int fuse_session_exited(struct fuse_session *se) { if (se->op.exited) return se->op.exited(se->data); else return se->exited; } void *fuse_session_data(struct fuse_session *se) { return se->data; } static struct fuse_chan *fuse_chan_new_common(struct fuse_chan_ops *op, int fd, size_t bufsize, void *data) { struct fuse_chan *ch = (struct fuse_chan *) malloc(sizeof(*ch)); if (ch == NULL) { fprintf(stderr, "fuse: failed to allocate channel\n"); return NULL; } memset(ch, 0, sizeof(*ch)); ch->op = *op; ch->fd = fd; ch->bufsize = bufsize; ch->data = data; return ch; } struct fuse_chan *fuse_chan_new(struct fuse_chan_ops *op, int fd, size_t bufsize, void *data) { return fuse_chan_new_common(op, fd, bufsize, data); } int fuse_chan_fd(struct fuse_chan *ch) { return ch->fd; } int fuse_chan_clearfd(struct fuse_chan *ch) { int fd = ch->fd; ch->fd = -1; return fd; } size_t fuse_chan_bufsize(struct fuse_chan *ch) { return ch->bufsize; } void *fuse_chan_data(struct fuse_chan *ch) { return ch->data; } struct fuse_session *fuse_chan_session(struct fuse_chan *ch) { return ch->se; } int fuse_chan_recv(struct fuse_chan **chp, char *buf, size_t size) { struct fuse_chan *ch = *chp; return ch->op.receive(chp, buf, size); } int fuse_chan_receive(struct fuse_chan *ch, char *buf, size_t size) { int res; res = fuse_chan_recv(&ch, buf, size); return res >= 0 ? res : (res != -EINTR && res != -EAGAIN) ? -1 : 0; } int fuse_chan_send(struct fuse_chan *ch, const struct iovec iov[], size_t count) { return ch->op.send(ch, iov, count); } void fuse_chan_destroy(struct fuse_chan *ch) { fuse_session_remove_chan(ch); if (ch->op.destroy) ch->op.destroy(ch); free(ch); } mergerfs-2.33.5/libfuse/lib/helper.c0000644000000000000000000002101414225522122015731 0ustar rootroot/* 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 COPYING.LIB. */ #include "config.h" #include "fuse_i.h" #include "fuse_misc.h" #include "fuse_opt.h" #include "fuse_lowlevel.h" #include #include #include #include #include #include #include #include enum { KEY_HELP, KEY_HELP_NOHEADER, KEY_VERSION, }; struct helper_opts { int foreground; int nodefault_subtype; char *mountpoint; }; #define FUSE_HELPER_OPT(t, p) { t, offsetof(struct helper_opts, p), 1 } static const struct fuse_opt fuse_helper_opts[] = { FUSE_HELPER_OPT("-f", foreground), FUSE_HELPER_OPT("fsname=", nodefault_subtype), FUSE_HELPER_OPT("subtype=", nodefault_subtype), FUSE_OPT_KEY("-h", KEY_HELP), FUSE_OPT_KEY("--help", KEY_HELP), FUSE_OPT_KEY("-ho", KEY_HELP_NOHEADER), FUSE_OPT_KEY("-V", KEY_VERSION), FUSE_OPT_KEY("--version", KEY_VERSION), FUSE_OPT_KEY("-d", FUSE_OPT_KEY_KEEP), FUSE_OPT_KEY("debug", FUSE_OPT_KEY_KEEP), FUSE_OPT_KEY("fsname=", FUSE_OPT_KEY_KEEP), FUSE_OPT_KEY("subtype=", FUSE_OPT_KEY_KEEP), FUSE_OPT_END }; static void usage(const char *progname) { fprintf(stderr, "usage: %s mountpoint [options]\n\n", progname); fprintf(stderr, "general options:\n" " -o opt,[opt...] mount options\n" " -h --help print help\n" " -V --version print version\n" "\n"); } static void helper_help(void) { fprintf(stderr, "FUSE options:\n" " -d -o debug enable debug output (implies -f)\n" " -f foreground operation\n" "\n" ); } static void helper_version(void) { fprintf(stderr, "FUSE library version: %s\n", PACKAGE_VERSION); } static int fuse_helper_opt_proc(void *data, const char *arg, int key, struct fuse_args *outargs) { struct helper_opts *hopts = data; switch (key) { case KEY_HELP: usage(outargs->argv[0]); /* fall through */ case KEY_HELP_NOHEADER: helper_help(); return fuse_opt_add_arg(outargs, "-h"); case KEY_VERSION: helper_version(); return 1; case FUSE_OPT_KEY_NONOPT: if (!hopts->mountpoint) { char mountpoint[PATH_MAX]; if (realpath(arg, mountpoint) == NULL) { fprintf(stderr, "fuse: bad mount point `%s': %s\n", arg, strerror(errno)); return -1; } return fuse_opt_add_opt(&hopts->mountpoint, mountpoint); } else { fprintf(stderr, "fuse: invalid argument `%s'\n", arg); return -1; } default: return 1; } } 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) { fprintf(stderr, "fuse: memory allocation failed\n"); return -1; } sprintf(subtype_opt, "-osubtype=%s", basename); res = fuse_opt_add_arg(args, subtype_opt); free(subtype_opt); return res; } int fuse_parse_cmdline(struct fuse_args *args_, char **mountpoint_, int *foreground_) { int res; struct helper_opts hopts; memset(&hopts, 0, sizeof(hopts)); res = fuse_opt_parse(args_, &hopts, fuse_helper_opts, fuse_helper_opt_proc); if(res == -1) return -1; if(!hopts.nodefault_subtype) { res = add_default_subtype(args_->argv[0], args_); if(res == -1) goto err; } if(mountpoint_) *mountpoint_ = hopts.mountpoint; else free(hopts.mountpoint); if(foreground_) *foreground_ = hopts.foreground; return 0; err: free(hopts.mountpoint); return -1; } 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"); return -1; case 0: break; default: read(waiter[0], &completed, sizeof(completed)); _exit(0); } if (setsid() == -1) { perror("fuse_daemonize: setsid"); 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 initializatation */ completed = 1; write(waiter[1], &completed, sizeof(completed)); close(waiter[0]); close(waiter[1]); } return 0; } static struct fuse_chan * fuse_mount_common(const char *mountpoint_, struct fuse_args *args_) { struct fuse_chan *ch; int fd; /* * 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); fd = fuse_kern_mount(mountpoint_,args_); if(fd == -1) return NULL; ch = fuse_kern_chan_new(fd); if(!ch) fuse_kern_unmount(mountpoint_, fd); return ch; } struct fuse_chan * fuse_mount(const char *mountpoint_, struct fuse_args *args_) { return fuse_mount_common(mountpoint_,args_); } static void fuse_unmount_common(const char *mountpoint, struct fuse_chan *ch) { if (mountpoint) { int fd = ch ? fuse_chan_clearfd(ch) : -1; fuse_kern_unmount(mountpoint, fd); if (ch) fuse_chan_destroy(ch); } } void fuse_unmount(const char *mountpoint, struct fuse_chan *ch) { fuse_unmount_common(mountpoint, ch); } struct fuse *fuse_setup_common(int argc, char *argv[], const struct fuse_operations *op, size_t op_size, char **mountpoint, int *fd) { struct fuse_args args = FUSE_ARGS_INIT(argc, argv); struct fuse_chan *ch; struct fuse *fuse; int foreground; int res; res = fuse_parse_cmdline(&args, mountpoint, &foreground); if (res == -1) return NULL; ch = fuse_mount_common(*mountpoint, &args); if (!ch) { fuse_opt_free_args(&args); goto err_free; } fuse = fuse_new_common(ch, &args, op, op_size); fuse_opt_free_args(&args); if (fuse == NULL) goto err_unmount; res = fuse_daemonize(foreground); if (res == -1) goto err_unmount; res = fuse_set_signal_handlers(fuse_get_session(fuse)); if (res == -1) goto err_unmount; if (fd) *fd = fuse_chan_fd(ch); return fuse; err_unmount: fuse_unmount_common(*mountpoint, ch); if (fuse) fuse_destroy(fuse); err_free: free(*mountpoint); return NULL; } struct fuse *fuse_setup(int argc, char *argv[], const struct fuse_operations *op, size_t op_size, char **mountpoint) { return fuse_setup_common(argc, argv, op, op_size, mountpoint, NULL); } static void fuse_teardown_common(struct fuse *fuse, char *mountpoint) { struct fuse_session *se = fuse_get_session(fuse); struct fuse_chan *ch = fuse_session_next_chan(se, NULL); fuse_remove_signal_handlers(se); fuse_unmount_common(mountpoint, ch); fuse_destroy(fuse); free(mountpoint); } void fuse_teardown(struct fuse *fuse, char *mountpoint) { fuse_teardown_common(fuse, mountpoint); } static int fuse_main_common(int argc, char *argv[], const struct fuse_operations *op, size_t op_size) { struct fuse *fuse; char *mountpoint; int res; fuse = fuse_setup_common(argc, argv, op, op_size, &mountpoint, NULL); if (fuse == NULL) return 1; res = fuse_loop_mt(fuse); fuse_teardown_common(fuse, mountpoint); if (res == -1) return 1; return 0; } int fuse_main_real(int argc, char *argv[], const struct fuse_operations *op, size_t op_size) { return fuse_main_common(argc, argv, op, op_size); } #undef fuse_main int fuse_main(void); int fuse_main(void) { fprintf(stderr, "fuse_main(): This function does not exist\n"); return -1; } int fuse_version(void) { return FUSE_VERSION; } mergerfs-2.33.5/libfuse/lib/fuse_kern_chan.c0000644000000000000000000000437514225522122017437 0ustar rootroot/* 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 COPYING.LIB */ #include "fuse_lowlevel.h" #include "fuse_kernel.h" #include "fuse_i.h" #include #include #include #include static int fuse_kern_chan_receive(struct fuse_chan **chp, char *buf, size_t size) { struct fuse_chan *ch = *chp; int err; ssize_t res; struct fuse_session *se = fuse_chan_session(ch); assert(se != NULL); restart: res = read(fuse_chan_fd(ch), buf, size); err = errno; if (fuse_session_exited(se)) return 0; if (res == -1) { /* ENOENT means the operation was interrupted, it's safe to restart */ if (err == ENOENT) goto restart; if (err == ENODEV) { 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)) { fprintf(stderr, "short read on fuse device\n"); return -EIO; } return res; } static int fuse_kern_chan_send(struct fuse_chan *ch, const struct iovec iov[], size_t count) { if (iov) { ssize_t res = writev(fuse_chan_fd(ch), iov, count); int err = errno; if (res == -1) { struct fuse_session *se = fuse_chan_session(ch); assert(se != NULL); /* ENOENT means the operation was interrupted */ if (!fuse_session_exited(se) && err != ENOENT) perror("fuse: writing device"); return -err; } } return 0; } static void fuse_kern_chan_destroy(struct fuse_chan *ch) { int fd = fuse_chan_fd(ch); if (fd != -1) close(fd); } struct fuse_chan * fuse_kern_chan_new(int fd_) { long pagesize; size_t bufsize; struct fuse_chan_ops op = { .receive = fuse_kern_chan_receive, .send = fuse_kern_chan_send, .destroy = fuse_kern_chan_destroy, }; pagesize = sysconf(_SC_PAGESIZE); bufsize = ((FUSE_MAX_MAX_PAGES * pagesize) + 0x1000); return fuse_chan_new(&op, fd_, bufsize, NULL); } mergerfs-2.33.5/libfuse/lib/khash.h0000644000000000000000000006356614225522122015577 0ustar rootroot/* The MIT License Copyright (c) 2008, 2009, 2011 by Attractive Chaos 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. */ /* An example: #include "khash.h" KHASH_MAP_INIT_INT(32, char) int main() { int ret, is_missing; khiter_t k; khash_t(32) *h = kh_init(32); k = kh_put(32, h, 5, &ret); kh_value(h, k) = 10; k = kh_get(32, h, 10); is_missing = (k == kh_end(h)); k = kh_get(32, h, 5); kh_del(32, h, k); for (k = kh_begin(h); k != kh_end(h); ++k) if (kh_exist(h, k)) kh_value(h, k) = 1; kh_destroy(32, h); return 0; } */ /* 2013-05-02 (0.2.8): * Use quadratic probing. When the capacity is power of 2, stepping function i*(i+1)/2 guarantees to traverse each bucket. It is better than double hashing on cache performance and is more robust than linear probing. In theory, double hashing should be more robust than quadratic probing. However, my implementation is probably not for large hash tables, because the second hash function is closely tied to the first hash function, which reduce the effectiveness of double hashing. Reference: http://research.cs.vt.edu/AVresearch/hashing/quadratic.php 2011-12-29 (0.2.7): * Minor code clean up; no actual effect. 2011-09-16 (0.2.6): * The capacity is a power of 2. This seems to dramatically improve the speed for simple keys. Thank Zilong Tan for the suggestion. Reference: - http://code.google.com/p/ulib/ - http://nothings.org/computer/judy/ * Allow to optionally use linear probing which usually has better performance for random input. Double hashing is still the default as it is more robust to certain non-random input. * Added Wang's integer hash function (not used by default). This hash function is more robust to certain non-random input. 2011-02-14 (0.2.5): * Allow to declare global functions. 2009-09-26 (0.2.4): * Improve portability 2008-09-19 (0.2.3): * Corrected the example * Improved interfaces 2008-09-11 (0.2.2): * Improved speed a little in kh_put() 2008-09-10 (0.2.1): * Added kh_clear() * Fixed a compiling error 2008-09-02 (0.2.0): * Changed to token concatenation which increases flexibility. 2008-08-31 (0.1.2): * Fixed a bug in kh_get(), which has not been tested previously. 2008-08-31 (0.1.1): * Added destructor */ #ifndef __AC_KHASH_H #define __AC_KHASH_H /*! @header Generic hash table library. */ #define AC_VERSION_KHASH_H "0.2.8" #include #include #include /* compiler specific configuration */ #if UINT_MAX == 0xffffffffu typedef unsigned int khint32_t; #elif ULONG_MAX == 0xffffffffu typedef unsigned long khint32_t; #endif #if ULONG_MAX == ULLONG_MAX typedef unsigned long khint64_t; #else typedef unsigned long long khint64_t; #endif #ifndef kh_inline #ifdef _MSC_VER #define kh_inline __inline #else #define kh_inline inline #endif #endif /* kh_inline */ #ifndef klib_unused #if (defined __clang__ && __clang_major__ >= 3) || (defined __GNUC__ && __GNUC__ >= 3) #define klib_unused __attribute__ ((__unused__)) #else #define klib_unused #endif #endif /* klib_unused */ typedef khint32_t khint_t; typedef khint_t khiter_t; #define __ac_isempty(flag, i) ((flag[i>>4]>>((i&0xfU)<<1))&2) #define __ac_isdel(flag, i) ((flag[i>>4]>>((i&0xfU)<<1))&1) #define __ac_iseither(flag, i) ((flag[i>>4]>>((i&0xfU)<<1))&3) #define __ac_set_isdel_false(flag, i) (flag[i>>4]&=~(1ul<<((i&0xfU)<<1))) #define __ac_set_isempty_false(flag, i) (flag[i>>4]&=~(2ul<<((i&0xfU)<<1))) #define __ac_set_isboth_false(flag, i) (flag[i>>4]&=~(3ul<<((i&0xfU)<<1))) #define __ac_set_isdel_true(flag, i) (flag[i>>4]|=1ul<<((i&0xfU)<<1)) #define __ac_fsize(m) ((m) < 16? 1 : (m)>>4) #ifndef kroundup32 #define kroundup32(x) (--(x), (x)|=(x)>>1, (x)|=(x)>>2, (x)|=(x)>>4, (x)|=(x)>>8, (x)|=(x)>>16, ++(x)) #endif #ifndef kcalloc #define kcalloc(N,Z) calloc(N,Z) #endif #ifndef kmalloc #define kmalloc(Z) malloc(Z) #endif #ifndef krealloc #define krealloc(P,Z) realloc(P,Z) #endif #ifndef kfree #define kfree(P) free(P) #endif static const double __ac_HASH_UPPER = 0.77; #define __KHASH_TYPE(name, khkey_t, khval_t) \ typedef struct kh_##name##_s { \ khint_t n_buckets, size, n_occupied, upper_bound; \ khint32_t *flags; \ khkey_t *keys; \ khval_t *vals; \ } kh_##name##_t; #define __KHASH_PROTOTYPES(name, khkey_t, khval_t) \ extern kh_##name##_t *kh_init_##name(void); \ extern void kh_destroy_##name(kh_##name##_t *h); \ extern void kh_clear_##name(kh_##name##_t *h); \ extern khint_t kh_get_##name(const kh_##name##_t *h, khkey_t key); \ extern int kh_resize_##name(kh_##name##_t *h, khint_t new_n_buckets); \ extern khint_t kh_put_##name(kh_##name##_t *h, khkey_t key, int *ret); \ extern void kh_del_##name(kh_##name##_t *h, khint_t x); #define __KHASH_IMPL(name, SCOPE, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) \ SCOPE kh_##name##_t *kh_init_##name(void) { \ return (kh_##name##_t*)kcalloc(1, sizeof(kh_##name##_t)); \ } \ SCOPE void kh_destroy_##name(kh_##name##_t *h) \ { \ if (h) { \ kfree((void *)h->keys); kfree(h->flags); \ kfree((void *)h->vals); \ kfree(h); \ } \ } \ SCOPE void kh_clear_##name(kh_##name##_t *h) \ { \ if (h && h->flags) { \ memset(h->flags, 0xaa, __ac_fsize(h->n_buckets) * sizeof(khint32_t)); \ h->size = h->n_occupied = 0; \ } \ } \ SCOPE khint_t kh_get_##name(const kh_##name##_t *h, khkey_t key) \ { \ if (h->n_buckets) { \ khint_t k, i, last, mask, step = 0; \ mask = h->n_buckets - 1; \ k = __hash_func(key); i = k & mask; \ last = i; \ while (!__ac_isempty(h->flags, i) && (__ac_isdel(h->flags, i) || !__hash_equal(h->keys[i], key))) { \ i = (i + (++step)) & mask; \ if (i == last) return h->n_buckets; \ } \ return __ac_iseither(h->flags, i)? h->n_buckets : i; \ } else return 0; \ } \ SCOPE int kh_resize_##name(kh_##name##_t *h, khint_t new_n_buckets) \ { /* This function uses 0.25*n_buckets bytes of working space instead of [sizeof(key_t+val_t)+.25]*n_buckets. */ \ khint32_t *new_flags = 0; \ khint_t j = 1; \ { \ kroundup32(new_n_buckets); \ if (new_n_buckets < 4) new_n_buckets = 4; \ if (h->size >= (khint_t)(new_n_buckets * __ac_HASH_UPPER + 0.5)) j = 0; /* requested size is too small */ \ else { /* hash table size to be changed (shrink or expand); rehash */ \ new_flags = (khint32_t*)kmalloc(__ac_fsize(new_n_buckets) * sizeof(khint32_t)); \ if (!new_flags) return -1; \ memset(new_flags, 0xaa, __ac_fsize(new_n_buckets) * sizeof(khint32_t)); \ if (h->n_buckets < new_n_buckets) { /* expand */ \ khkey_t *new_keys = (khkey_t*)krealloc((void *)h->keys, new_n_buckets * sizeof(khkey_t)); \ if (!new_keys) { kfree(new_flags); return -1; } \ h->keys = new_keys; \ if (kh_is_map) { \ khval_t *new_vals = (khval_t*)krealloc((void *)h->vals, new_n_buckets * sizeof(khval_t)); \ if (!new_vals) { kfree(new_flags); return -1; } \ h->vals = new_vals; \ } \ } /* otherwise shrink */ \ } \ } \ if (j) { /* rehashing is needed */ \ for (j = 0; j != h->n_buckets; ++j) { \ if (__ac_iseither(h->flags, j) == 0) { \ khkey_t key = h->keys[j]; \ khval_t val; \ khint_t new_mask; \ new_mask = new_n_buckets - 1; \ if (kh_is_map) val = h->vals[j]; \ __ac_set_isdel_true(h->flags, j); \ while (1) { /* kick-out process; sort of like in Cuckoo hashing */ \ khint_t k, i, step = 0; \ k = __hash_func(key); \ i = k & new_mask; \ while (!__ac_isempty(new_flags, i)) i = (i + (++step)) & new_mask; \ __ac_set_isempty_false(new_flags, i); \ if (i < h->n_buckets && __ac_iseither(h->flags, i) == 0) { /* kick out the existing element */ \ { khkey_t tmp = h->keys[i]; h->keys[i] = key; key = tmp; } \ if (kh_is_map) { khval_t tmp = h->vals[i]; h->vals[i] = val; val = tmp; } \ __ac_set_isdel_true(h->flags, i); /* mark it as deleted in the old hash table */ \ } else { /* write the element and jump out of the loop */ \ h->keys[i] = key; \ if (kh_is_map) h->vals[i] = val; \ break; \ } \ } \ } \ } \ if (h->n_buckets > new_n_buckets) { /* shrink the hash table */ \ h->keys = (khkey_t*)krealloc((void *)h->keys, new_n_buckets * sizeof(khkey_t)); \ if (kh_is_map) h->vals = (khval_t*)krealloc((void *)h->vals, new_n_buckets * sizeof(khval_t)); \ } \ kfree(h->flags); /* free the working space */ \ h->flags = new_flags; \ h->n_buckets = new_n_buckets; \ h->n_occupied = h->size; \ h->upper_bound = (khint_t)(h->n_buckets * __ac_HASH_UPPER + 0.5); \ } \ return 0; \ } \ SCOPE khint_t kh_put_##name(kh_##name##_t *h, khkey_t key, int *ret) \ { \ khint_t x; \ if (h->n_occupied >= h->upper_bound) { /* update the hash table */ \ if (h->n_buckets > (h->size<<1)) { \ if (kh_resize_##name(h, h->n_buckets - 1) < 0) { /* clear "deleted" elements */ \ *ret = -1; return h->n_buckets; \ } \ } else if (kh_resize_##name(h, h->n_buckets + 1) < 0) { /* expand the hash table */ \ *ret = -1; return h->n_buckets; \ } \ } /* TODO: to implement automatically shrinking; resize() already support shrinking */ \ { \ khint_t k, i, site, last, mask = h->n_buckets - 1, step = 0; \ x = site = h->n_buckets; k = __hash_func(key); i = k & mask; \ if (__ac_isempty(h->flags, i)) x = i; /* for speed up */ \ else { \ last = i; \ while (!__ac_isempty(h->flags, i) && (__ac_isdel(h->flags, i) || !__hash_equal(h->keys[i], key))) { \ if (__ac_isdel(h->flags, i)) site = i; \ i = (i + (++step)) & mask; \ if (i == last) { x = site; break; } \ } \ if (x == h->n_buckets) { \ if (__ac_isempty(h->flags, i) && site != h->n_buckets) x = site; \ else x = i; \ } \ } \ } \ if (__ac_isempty(h->flags, x)) { /* not present at all */ \ h->keys[x] = key; \ __ac_set_isboth_false(h->flags, x); \ ++h->size; ++h->n_occupied; \ *ret = 1; \ } else if (__ac_isdel(h->flags, x)) { /* deleted */ \ h->keys[x] = key; \ __ac_set_isboth_false(h->flags, x); \ ++h->size; \ *ret = 2; \ } else *ret = 0; /* Don't touch h->keys[x] if present and not deleted */ \ return x; \ } \ SCOPE void kh_del_##name(kh_##name##_t *h, khint_t x) \ { \ if (x != h->n_buckets && !__ac_iseither(h->flags, x)) { \ __ac_set_isdel_true(h->flags, x); \ --h->size; \ } \ } #define KHASH_DECLARE(name, khkey_t, khval_t) \ __KHASH_TYPE(name, khkey_t, khval_t) \ __KHASH_PROTOTYPES(name, khkey_t, khval_t) #define KHASH_INIT2(name, SCOPE, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) \ __KHASH_TYPE(name, khkey_t, khval_t) \ __KHASH_IMPL(name, SCOPE, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) #define KHASH_INIT(name, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) \ KHASH_INIT2(name, static kh_inline klib_unused, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) /* --- BEGIN OF HASH FUNCTIONS --- */ /*! @function @abstract Integer hash function @param key The integer [khint32_t] @return The hash value [khint_t] */ #define kh_int_hash_func(key) (khint32_t)(key) /*! @function @abstract Integer comparison function */ #define kh_int_hash_equal(a, b) ((a) == (b)) /*! @function @abstract 64-bit integer hash function @param key The integer [khint64_t] @return The hash value [khint_t] */ #define kh_int64_hash_func(key) (khint32_t)((key)>>33^(key)^(key)<<11) /*! @function @abstract 64-bit integer comparison function */ #define kh_int64_hash_equal(a, b) ((a) == (b)) /*! @function @abstract const char* hash function @param s Pointer to a null terminated string @return The hash value */ static kh_inline khint_t __ac_X31_hash_string(const char *s) { khint_t h = (khint_t)*s; if (h) for (++s ; *s; ++s) h = (h << 5) - h + (khint_t)*s; return h; } /*! @function @abstract Another interface to const char* hash function @param key Pointer to a null terminated string [const char*] @return The hash value [khint_t] */ #define kh_str_hash_func(key) __ac_X31_hash_string(key) /*! @function @abstract Const char* comparison function */ #define kh_str_hash_equal(a, b) (strcmp(a, b) == 0) static kh_inline khint_t __ac_Wang_hash(khint_t key) { key += ~(key << 15); key ^= (key >> 10); key += (key << 3); key ^= (key >> 6); key += ~(key << 11); key ^= (key >> 16); return key; } #define kh_int_hash_func2(key) __ac_Wang_hash((khint_t)key) /* --- END OF HASH FUNCTIONS --- */ /* Other convenient macros... */ /*! @abstract Type of the hash table. @param name Name of the hash table [symbol] */ #define khash_t(name) kh_##name##_t /*! @function @abstract Initiate a hash table. @param name Name of the hash table [symbol] @return Pointer to the hash table [khash_t(name)*] */ #define kh_init(name) kh_init_##name() /*! @function @abstract Destroy a hash table. @param name Name of the hash table [symbol] @param h Pointer to the hash table [khash_t(name)*] */ #define kh_destroy(name, h) kh_destroy_##name(h) /*! @function @abstract Reset a hash table without deallocating memory. @param name Name of the hash table [symbol] @param h Pointer to the hash table [khash_t(name)*] */ #define kh_clear(name, h) kh_clear_##name(h) /*! @function @abstract Resize a hash table. @param name Name of the hash table [symbol] @param h Pointer to the hash table [khash_t(name)*] @param s New size [khint_t] */ #define kh_resize(name, h, s) kh_resize_##name(h, s) /*! @function @abstract Insert a key to the hash table. @param name Name of the hash table [symbol] @param h Pointer to the hash table [khash_t(name)*] @param k Key [type of keys] @param r Extra return code: -1 if the operation failed; 0 if the key is present in the hash table; 1 if the bucket is empty (never used); 2 if the element in the bucket has been deleted [int*] @return Iterator to the inserted element [khint_t] */ #define kh_put(name, h, k, r) kh_put_##name(h, k, r) /*! @function @abstract Retrieve a key from the hash table. @param name Name of the hash table [symbol] @param h Pointer to the hash table [khash_t(name)*] @param k Key [type of keys] @return Iterator to the found element, or kh_end(h) if the element is absent [khint_t] */ #define kh_get(name, h, k) kh_get_##name(h, k) /*! @function @abstract Remove a key from the hash table. @param name Name of the hash table [symbol] @param h Pointer to the hash table [khash_t(name)*] @param k Iterator to the element to be deleted [khint_t] */ #define kh_del(name, h, k) kh_del_##name(h, k) /*! @function @abstract Test whether a bucket contains data. @param h Pointer to the hash table [khash_t(name)*] @param x Iterator to the bucket [khint_t] @return 1 if containing data; 0 otherwise [int] */ #define kh_exist(h, x) (!__ac_iseither((h)->flags, (x))) /*! @function @abstract Get key given an iterator @param h Pointer to the hash table [khash_t(name)*] @param x Iterator to the bucket [khint_t] @return Key [type of keys] */ #define kh_key(h, x) ((h)->keys[x]) /*! @function @abstract Get value given an iterator @param h Pointer to the hash table [khash_t(name)*] @param x Iterator to the bucket [khint_t] @return Value [type of values] @discussion For hash sets, calling this results in segfault. */ #define kh_val(h, x) ((h)->vals[x]) /*! @function @abstract Alias of kh_val() */ #define kh_value(h, x) ((h)->vals[x]) /*! @function @abstract Get the start iterator @param h Pointer to the hash table [khash_t(name)*] @return The start iterator [khint_t] */ #define kh_begin(h) (khint_t)(0) /*! @function @abstract Get the end iterator @param h Pointer to the hash table [khash_t(name)*] @return The end iterator [khint_t] */ #define kh_end(h) ((h)->n_buckets) /*! @function @abstract Get the number of elements in the hash table @param h Pointer to the hash table [khash_t(name)*] @return Number of elements in the hash table [khint_t] */ #define kh_size(h) ((h)->size) /*! @function @abstract Get the number of buckets in the hash table @param h Pointer to the hash table [khash_t(name)*] @return Number of buckets in the hash table [khint_t] */ #define kh_n_buckets(h) ((h)->n_buckets) /*! @function @abstract Iterate over the entries in the hash table @param h Pointer to the hash table [khash_t(name)*] @param kvar Variable to which key will be assigned @param vvar Variable to which value will be assigned @param code Block of code to execute */ #define kh_foreach(h, kvar, vvar, code) { khint_t __i; \ for (__i = kh_begin(h); __i != kh_end(h); ++__i) { \ if (!kh_exist(h,__i)) continue; \ (kvar) = kh_key(h,__i); \ (vvar) = kh_val(h,__i); \ code; \ } } /*! @function @abstract Iterate over the values in the hash table @param h Pointer to the hash table [khash_t(name)*] @param vvar Variable to which value will be assigned @param code Block of code to execute */ #define kh_foreach_value(h, vvar, code) { khint_t __i; \ for (__i = kh_begin(h); __i != kh_end(h); ++__i) { \ if (!kh_exist(h,__i)) continue; \ (vvar) = kh_val(h,__i); \ code; \ } } /* More convenient interfaces */ /*! @function @abstract Instantiate a hash set containing integer keys @param name Name of the hash table [symbol] */ #define KHASH_SET_INIT_INT(name) \ KHASH_INIT(name, khint32_t, char, 0, kh_int_hash_func, kh_int_hash_equal) /*! @function @abstract Instantiate a hash map containing integer keys @param name Name of the hash table [symbol] @param khval_t Type of values [type] */ #define KHASH_MAP_INIT_INT(name, khval_t) \ KHASH_INIT(name, khint32_t, khval_t, 1, kh_int_hash_func, kh_int_hash_equal) /*! @function @abstract Instantiate a hash set containing 64-bit integer keys @param name Name of the hash table [symbol] */ #define KHASH_SET_INIT_INT64(name) \ KHASH_INIT(name, khint64_t, char, 0, kh_int64_hash_func, kh_int64_hash_equal) /*! @function @abstract Instantiate a hash map containing 64-bit integer keys @param name Name of the hash table [symbol] @param khval_t Type of values [type] */ #define KHASH_MAP_INIT_INT64(name, khval_t) \ KHASH_INIT(name, khint64_t, khval_t, 1, kh_int64_hash_func, kh_int64_hash_equal) typedef const char *kh_cstr_t; /*! @function @abstract Instantiate a hash map containing const char* keys @param name Name of the hash table [symbol] */ #define KHASH_SET_INIT_STR(name) \ KHASH_INIT(name, kh_cstr_t, char, 0, kh_str_hash_func, kh_str_hash_equal) /*! @function @abstract Instantiate a hash map containing const char* keys @param name Name of the hash table [symbol] @param khval_t Type of values [type] */ #define KHASH_MAP_INIT_STR(name, khval_t) \ KHASH_INIT(name, kh_cstr_t, khval_t, 1, kh_str_hash_func, kh_str_hash_equal) #endif /* __AC_KHASH_H */ mergerfs-2.33.5/libfuse/lib/debug.c0000644000000000000000000006621714225522122015556 0ustar rootroot/* ISC License Copyright (c) 2021, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #define _GNU_SOURCE #include "fuse_kernel.h" #include #include #include #include #include static FILE *g_OUTPUT = NULL; #define PARAM(inarg) (((char *)(inarg)) + sizeof(*(inarg))) static int debug_set_output_null() { g_OUTPUT = stderr; setvbuf(g_OUTPUT,NULL,_IOLBF,0); return 0; } static int debug_set_output_filepath(const char *filepath_) { FILE *tmp; tmp = fopen(filepath_,"a"); if(tmp == NULL) return -errno; g_OUTPUT = tmp; setvbuf(g_OUTPUT,NULL,_IOLBF,0); return 0; } int debug_set_output(const char *filepath_) { if(filepath_ == NULL) return debug_set_output_null(); return debug_set_output_filepath(filepath_); } static __attribute__((constructor)) void debug_constructor(void) { debug_set_output(NULL); } static const char* open_accmode_to_str(const int flags_) { switch(flags_ & O_ACCMODE) { case O_RDWR: return "O_RDWR"; case O_RDONLY: return "O_RDONLY"; case O_WRONLY: return "O_WRONLY"; } return ""; } #define FUSE_WRITE_FLAG_CASE(X) case FUSE_WRITE_##X: return #X static const char* fuse_write_flag_to_str(const uint32_t offset_) { switch(1 << offset_) { FUSE_WRITE_FLAG_CASE(CACHE); FUSE_WRITE_FLAG_CASE(LOCKOWNER); FUSE_WRITE_FLAG_CASE(KILL_PRIV); } return NULL; } #undef FUSE_WRITE_FLAG_CASE #define OPEN_FLAG_CASE(X) case X: return #X static const char* open_flag_to_str(const uint64_t offset_) { switch(1 << offset_) { OPEN_FLAG_CASE(O_APPEND); OPEN_FLAG_CASE(O_ASYNC); OPEN_FLAG_CASE(O_CLOEXEC); OPEN_FLAG_CASE(O_CREAT); OPEN_FLAG_CASE(O_DIRECT); OPEN_FLAG_CASE(O_DIRECTORY); #ifdef O_DSYNC OPEN_FLAG_CASE(O_DSYNC); #endif OPEN_FLAG_CASE(O_EXCL); #ifdef O_LARGEFILE OPEN_FLAG_CASE(O_LARGEFILE); #endif #ifdef O_NOATIME OPEN_FLAG_CASE(O_NOATIME); #endif OPEN_FLAG_CASE(O_NOCTTY); OPEN_FLAG_CASE(O_NOFOLLOW); OPEN_FLAG_CASE(O_NONBLOCK); #ifdef O_PATH OPEN_FLAG_CASE(O_PATH); #endif OPEN_FLAG_CASE(O_SYNC); #ifdef O_TMPFILE OPEN_FLAG_CASE(O_TMPFILE); #endif OPEN_FLAG_CASE(O_TRUNC); } return NULL; } #undef OPEN_FLAG_CASE #define FUSE_INIT_FLAG_CASE(X) case FUSE_##X: return #X static const char* fuse_flag_to_str(const uint32_t offset_) { switch(1 << offset_) { FUSE_INIT_FLAG_CASE(ASYNC_READ); FUSE_INIT_FLAG_CASE(POSIX_LOCKS); FUSE_INIT_FLAG_CASE(FILE_OPS); FUSE_INIT_FLAG_CASE(ATOMIC_O_TRUNC); FUSE_INIT_FLAG_CASE(EXPORT_SUPPORT); FUSE_INIT_FLAG_CASE(BIG_WRITES); FUSE_INIT_FLAG_CASE(DONT_MASK); FUSE_INIT_FLAG_CASE(SPLICE_WRITE); FUSE_INIT_FLAG_CASE(SPLICE_MOVE); FUSE_INIT_FLAG_CASE(SPLICE_READ); FUSE_INIT_FLAG_CASE(FLOCK_LOCKS); FUSE_INIT_FLAG_CASE(HAS_IOCTL_DIR); FUSE_INIT_FLAG_CASE(AUTO_INVAL_DATA); FUSE_INIT_FLAG_CASE(DO_READDIRPLUS); FUSE_INIT_FLAG_CASE(READDIRPLUS_AUTO); FUSE_INIT_FLAG_CASE(ASYNC_DIO); FUSE_INIT_FLAG_CASE(WRITEBACK_CACHE); FUSE_INIT_FLAG_CASE(NO_OPEN_SUPPORT); FUSE_INIT_FLAG_CASE(PARALLEL_DIROPS); FUSE_INIT_FLAG_CASE(HANDLE_KILLPRIV); FUSE_INIT_FLAG_CASE(POSIX_ACL); FUSE_INIT_FLAG_CASE(ABORT_ERROR); FUSE_INIT_FLAG_CASE(MAX_PAGES); FUSE_INIT_FLAG_CASE(CACHE_SYMLINKS); FUSE_INIT_FLAG_CASE(NO_OPENDIR_SUPPORT); FUSE_INIT_FLAG_CASE(EXPLICIT_INVAL_DATA); FUSE_INIT_FLAG_CASE(MAP_ALIGNMENT); } return NULL; } #undef FUSE_INIT_FLAG_CASE static void debug_open_flags(const uint32_t flags_) { fprintf(stderr,"%s,",open_accmode_to_str(flags_)); for(int i = 0; i < (sizeof(flags_) * 8); i++) { const char *str; if(!(flags_ & (1 << i))) continue; str = open_flag_to_str(i); if(str == NULL) continue; fprintf(stderr,"%s,",str); } } #define FOPEN_FLAG_CASE(X) case FOPEN_##X: return #X static const char* fuse_fopen_flag_to_str(const uint32_t offset_) { switch(1 << offset_) { FOPEN_FLAG_CASE(DIRECT_IO); FOPEN_FLAG_CASE(KEEP_CACHE); FOPEN_FLAG_CASE(NONSEEKABLE); FOPEN_FLAG_CASE(CACHE_DIR); FOPEN_FLAG_CASE(STREAM); } return NULL; } #undef FOPEN_FLAG_CASE void debug_fuse_open_out(const struct fuse_open_out *arg_) { fprintf(stderr, "fuse_open_out:" " fh=0x%"PRIx64";" " open_flags=0x%X (", arg_->fh, arg_->open_flags); for(int i = 0; i < (sizeof(arg_->open_flags) * 8); i++) { const char *str; if(!(arg_->open_flags & (1 << i))) continue; str = fuse_fopen_flag_to_str(i); if(str == NULL) continue; fprintf(stderr,"%s,",str); } fprintf(stderr,");\n"); } static void debug_fuse_lookup(const void *arg_) { const char *name = arg_; fprintf(g_OUTPUT, "fuse_lookup:" " name=%s;" "\n" , name); } static void debug_fuse_getattr_in(const void *arg_) { const struct fuse_getattr_in *arg = arg_; fprintf(g_OUTPUT, "fuse_getattr_in:" " getattr_flags=0x%08X;" " fh=0x%"PRIx64";\n", arg->getattr_flags, arg->fh); } static void debug_fuse_setattr_in(const void *arg_) { const struct fuse_setattr_in *arg = arg_; fprintf(g_OUTPUT, "fuse_setattr_in:" " valid=%u;" " fh=0x%"PRIx64";" " size=%zu;" " lock_owner=%zu;" " atime=%zu;" " atimensec=%u;" " mtime=%zu;" " mtimensec=%u;" " ctime=%zu;" " ctimensec=%u;" " mode=%o;" " uid=%u;" " gid=%u;" "\n" , arg->valid, arg->fh, arg->size, arg->lock_owner, arg->atime, arg->atimensec, arg->mtime, arg->mtimensec, arg->ctime, arg->ctimensec, arg->mode, arg->uid, arg->gid); } static void debug_fuse_access_in(const void *arg_) { const struct fuse_access_in *arg = arg_; fprintf(g_OUTPUT, "fuse_access_in:" " mask=0x%08X;" "\n" , arg->mask); } static void debug_fuse_mknod_in(const void *arg_) { const struct fuse_mknod_in *arg = arg_; fprintf(g_OUTPUT, "fuse_mknod_in:" " mode=%o;" " rdev=0x%08X;" " umask=%o;" "\n" , arg->mode, arg->rdev, arg->umask); } static void debug_fuse_mkdir_in(const void *arg_) { const struct fuse_mkdir_in *arg = arg_; fprintf(g_OUTPUT, "fuse_mkdir_in:" " mode=%o;" " umask=%o;" " name=%s;" "\n" , arg->mode, arg->umask, PARAM(arg)); } static void debug_fuse_unlink(const void *arg_) { const char *name = arg_; fprintf(g_OUTPUT, "fuse_unlink:" " name=%s;" "\n" , name); } static void debug_fuse_rmdir(const void *arg_) { const char *name = arg_; fprintf(g_OUTPUT, "fuse_mkdir:" " name=%s;" "\n" , name); } static void debug_fuse_symlink(const void *arg_) { const char *name; const char *linkname; name = arg_; linkname = (name + (strlen(name) + 1)); fprintf(g_OUTPUT, "fuse_mkdir:" " linkname=%s;" " name=%s;" "\n" , linkname, name); } static void debug_fuse_rename_in(const void *arg_) { const char *oldname; const char *newname; const struct fuse_rename_in *arg = arg_; oldname = PARAM(arg); newname = (oldname + strlen(oldname) + 1); fprintf(g_OUTPUT, "fuse_rename_in:" " oldname=%s;" " newdir=%zu;" " newname=%s;" "\n" , oldname, arg->newdir, newname); } static void debug_fuse_link_in(const void *arg_) { const char *name; const struct fuse_link_in *arg = arg_; name = PARAM(arg); fprintf(g_OUTPUT, "fuse_link_in:" " oldnodeid=%zu;" " name=%s;" "\n" , arg->oldnodeid, name); } static void debug_fuse_create_in(const void *arg_) { const char *name; const struct fuse_create_in *arg = arg_; name = PARAM(arg); fprintf(g_OUTPUT, "fuse_create_in:" " mode=%o;" " umask=%o;" " name=%s;" " flags=0x%X (", arg->mode, arg->umask, name, arg->flags); debug_open_flags(arg->flags); fprintf(g_OUTPUT,");\n"); } static void debug_fuse_open_in(const void *arg_) { const struct fuse_open_in *arg = arg_; fprintf(g_OUTPUT, "fuse_open_in:" " flags=0x%08X (", arg->flags); debug_open_flags(arg->flags); fprintf(g_OUTPUT,");\n"); } static void debug_fuse_read_in(const void *arg_) { const struct fuse_read_in *arg = arg_; fprintf(g_OUTPUT, "fuse_read_in:" " fh=0x%"PRIx64";" " offset=%zu;" " size=%u;" " read_flags=%X;" " lock_owner=0x%"PRIx64";" " flags=0x%X (" , arg->fh, arg->offset, arg->size, arg->read_flags, arg->lock_owner, arg->flags); debug_open_flags(arg->flags); fprintf(g_OUTPUT,");\n"); } static void debug_fuse_write_in(const void *arg_) { const struct fuse_write_in *arg = arg_; fprintf(g_OUTPUT, "fuse_write_in:" " fh=0x%"PRIx64";" " offset=%zu;" " size=%u;" " lock_owner=0x%"PRIx64";" " flags=0x%X (" , arg->fh, arg->offset, arg->size, arg->lock_owner, arg->flags); debug_open_flags(arg->flags); fprintf(g_OUTPUT, "); write_flags=0x%X (", arg->write_flags); for(int i = 0; i < (sizeof(arg->write_flags) * 8); i++) { const char *str; if(!(arg->write_flags & (1 << i))) continue; str = fuse_write_flag_to_str(i); if(str == NULL) continue; fprintf(g_OUTPUT,"%s,",str); } fprintf(g_OUTPUT,");\n"); } static void debug_fuse_flush_in(const void *arg_) { const struct fuse_flush_in *arg = arg_; fprintf(g_OUTPUT, "fuse_flush_in:" " fh=0x%"PRIx64";" " lock_owner=0x%"PRIx64";" "\n" , arg->fh, arg->lock_owner); } static void debug_fuse_release_in(const void *arg_) { const struct fuse_release_in *arg = arg_; fprintf(g_OUTPUT, "fuse_release_in:" " fh=0x%"PRIx64";" " release_flags=0x%X;" " lock_owner=0x%"PRIx64";" " flags=0x%X (" , arg->fh, arg->release_flags, arg->lock_owner, arg->flags); debug_open_flags(arg->flags); fprintf(g_OUTPUT,");\n"); } static void debug_fuse_fsync_in(const void *arg_) { const struct fuse_fsync_in *arg = arg_; fprintf(g_OUTPUT, "fuse_fsync_in:" " fh=0x%"PRIx64";" " fsync_flags=0x%X;" "\n" , arg->fh, arg->fsync_flags); } static void debug_fuse_setxattr_in(const void *arg_) { const char *name; const char *value; const struct fuse_setxattr_in *arg = arg_; name = PARAM(arg); value = (name + strlen(name) + 1); fprintf(g_OUTPUT, "fuse_setxattr_in:" " size=%u;" " flags=0x%X;" " name=%s;" " value=%s;" "\n" , arg->size, arg->flags, name, value); } static void debug_fuse_getxattr_in(const void *arg_) { const char *name; const struct fuse_getxattr_in *arg = arg_; name = PARAM(arg); fprintf(g_OUTPUT, "fuse_getxattr_in:" " size=%u;" " name=%s;" "\n" , arg->size, name); } static void debug_fuse_listxattr(const void *arg_) { const struct fuse_getxattr_in *arg = arg_; fprintf(g_OUTPUT, "fuse_listxattr:" " size=%u;" "\n" , arg->size); } static void debug_fuse_removexattr(const void *arg_) { const char *name = arg_; fprintf(g_OUTPUT, "fuse_removexattr:" " name=%s;" "\n" , name); } static void debug_fuse_fallocate_in(const void *arg_) { const struct fuse_fallocate_in *arg = arg_; fprintf(g_OUTPUT, "fuse_fallocate_in:" " fh=0x%"PRIx64";" " offset=%zu;" " length=%zu;" " mode=%o;" "\n" , arg->fh, arg->offset, arg->length, arg->mode); } void debug_fuse_init_in(const struct fuse_init_in *arg_) { fprintf(g_OUTPUT, "FUSE_INIT_IN: " " major=%u;" " minor=%u;" " max_readahead=%u;" " flags=0x%08X (", arg_->major, arg_->minor, arg_->max_readahead, arg_->flags); for(uint64_t i = 0; i < (sizeof(arg_->flags)*8); i++) { const char *str; if(!(arg_->flags & (1ULL << i))) continue; str = fuse_flag_to_str(i); if(str == NULL) continue; fprintf(g_OUTPUT,"%s,",str); } fprintf(g_OUTPUT,")\n"); } void debug_fuse_init_out(const uint64_t unique_, const struct fuse_init_out *arg_, const uint64_t argsize_) { const struct fuse_init_out *arg = arg_; fprintf(g_OUTPUT, /* "unique=0x%016"PRIx64";" */ /* " opcode=RESPONSE;" */ /* " error=0 (Success);" */ /* " len=%"PRIu64"; || " */ "FUSE_INIT_OUT:" " major=%u;" " minor=%u;" " max_readahead=%u;" " flags=0x%08X (" , /* unique_, */ /* sizeof(struct fuse_out_header) + argsize_, */ arg->major, arg->minor, arg->max_readahead, arg->flags); for(uint64_t i = 0; i < (sizeof(arg->flags)*8); i++) { const char *str; if(!(arg->flags & (1ULL << i))) continue; str = fuse_flag_to_str(i); if(str == NULL) continue; fprintf(g_OUTPUT,"%s,",str); } fprintf(g_OUTPUT, "); max_background=%u;" " congestion_threshold=%u;" " max_write=%u;" " time_gran=%u;" " max_pages=%u;" " map_alignment=%u;" "\n", arg->max_background, arg->congestion_threshold, arg->max_write, arg->time_gran, arg->max_pages, arg->map_alignment); } static void debug_fuse_attr(const struct fuse_attr *attr_) { fprintf(g_OUTPUT, "attr:" " ino=0x%016"PRIx64";" " size=%"PRIu64";" " blocks=%"PRIu64";" " atime=%"PRIu64";" " atimensec=%u;" " mtime=%"PRIu64";" " mtimensec=%u;" " ctime=%"PRIu64";" " ctimesec=%u;" " mode=%o;" " nlink=%u;" " uid=%u;" " gid=%u;" " rdev=%u;" " blksize=%u;" , attr_->ino, attr_->size, attr_->blocks, attr_->atime, attr_->atimensec, attr_->mtime, attr_->mtimensec, attr_->ctime, attr_->ctimensec, attr_->mode, attr_->nlink, attr_->uid, attr_->gid, attr_->rdev, attr_->blksize); } static void debug_fuse_entry(const struct fuse_entry_out *entry_) { fprintf(g_OUTPUT, " fuse_entry_out:" " nodeid=0x%016"PRIx64";" " generation=0x%016"PRIx64";" " entry_valid=%"PRIu64";" " entry_valid_nsec=%u;" " attr_valid=%"PRIu64";" " attr_valid_nsec=%u;" " ", entry_->nodeid, entry_->generation, entry_->entry_valid, entry_->entry_valid_nsec, entry_->attr_valid, entry_->attr_valid_nsec); debug_fuse_attr(&entry_->attr); } void debug_fuse_entry_out(const uint64_t unique_, const struct fuse_entry_out *arg_, const uint64_t argsize_) { fprintf(g_OUTPUT, "unique=0x%016"PRIx64";" " opcode=RESPONSE;" " error=0 (Success);" " len=%"PRIu64"; || " , unique_, sizeof(struct fuse_out_header) + argsize_); debug_fuse_entry(arg_); } void debug_fuse_attr_out(const uint64_t unique_, const struct fuse_attr_out *arg_, const uint64_t argsize_) { fprintf(g_OUTPUT, "unique=0x%016"PRIx64";" " opcode=RESPONSE;" " error=0 (Success);" " len=%"PRIu64"; || " "fuse_attr_out:" " attr_valid=%zu;" " attr_valid_nsec=%u;" " ino=%zu;" " size=%zu;" " blocks=%zu;" " atime=%zu;" " atimensec=%u;" " mtime=%zu;" " mtimensec=%u;" " ctime=%zu;" " ctimensec=%u;" " mode=%o;" " nlink=%u;" " uid=%u;" " gid=%u;" " rdev=%u;" " blksize=%u;" "\n", unique_, sizeof(struct fuse_out_header) + argsize_, arg_->attr_valid, arg_->attr_valid_nsec, arg_->attr.ino, arg_->attr.size, arg_->attr.blocks, arg_->attr.atime, arg_->attr.atimensec, arg_->attr.mtime, arg_->attr.mtimensec, arg_->attr.ctime, arg_->attr.ctimensec, arg_->attr.mode, arg_->attr.nlink, arg_->attr.uid, arg_->attr.gid, arg_->attr.rdev, arg_->attr.blksize); } static void debug_fuse_interrupt_in(const void *arg_) { const struct fuse_interrupt_in *arg = arg_; fprintf(g_OUTPUT, "fuse_interrupt_in:" " unique=0x%016"PRIx64";" "\n" , arg->unique); } static const char* opcode_name(enum fuse_opcode op_) { static const char *names[] = { [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_INTERRUPT] = "INTERRUPT", [FUSE_BMAP] = "BMAP", [FUSE_DESTROY] = "DESTROY", [FUSE_IOCTL] = "IOCTL", [FUSE_POLL] = "POLL", [FUSE_NOTIFY_REPLY] = "NOTIFY_REPLY", [FUSE_BATCH_FORGET] = "BATCH_FORGET", [FUSE_FALLOCATE] = "FALLOCATE", [FUSE_READDIRPLUS] = "READDIRPLUS", [FUSE_RENAME2] = "RENAME2", [FUSE_LSEEK] = "LSEEK", [FUSE_COPY_FILE_RANGE] = "COPY_FILE_RANGE", [FUSE_SETUPMAPPING] = "SETUPMAPPING", [FUSE_REMOVEMAPPING] = "REMOVEMAPPING" }; if(op_ >= (sizeof(names) / sizeof(names[0]))) return "::UNKNOWN::"; return names[op_]; } void debug_fuse_in_header(const struct fuse_in_header *hdr_) { const void *arg = &hdr_[1]; fprintf(stderr, "unique=0x%016"PRIx64";" " opcode=%s (%u);" " nodeid=%zu;" " uid=%u;" " gid=%u;" " pid=%u; || ", hdr_->unique, opcode_name(hdr_->opcode), hdr_->opcode, hdr_->nodeid, hdr_->uid, hdr_->gid, hdr_->pid); switch(hdr_->opcode) { case FUSE_LOOKUP: debug_fuse_lookup(arg); break; case FUSE_INIT: debug_fuse_init_in(arg); break; case FUSE_GETATTR: debug_fuse_getattr_in(arg); break; case FUSE_SETATTR: debug_fuse_setattr_in(arg); break; case FUSE_ACCESS: debug_fuse_access_in(arg); break; case FUSE_MKNOD: debug_fuse_mknod_in(arg); break; case FUSE_MKDIR: debug_fuse_mkdir_in(arg); break; case FUSE_UNLINK: debug_fuse_unlink(arg); break; case FUSE_RMDIR: debug_fuse_rmdir(arg); break; case FUSE_SYMLINK: debug_fuse_symlink(arg); break; case FUSE_RENAME: debug_fuse_rename_in(arg); break; case FUSE_LINK: debug_fuse_link_in(arg); break; case FUSE_CREATE: debug_fuse_create_in(arg); break; case FUSE_OPEN: debug_fuse_open_in(arg); break; case FUSE_OPENDIR: debug_fuse_open_in(arg); break; case FUSE_READ: debug_fuse_read_in(arg); break; case FUSE_READDIR: debug_fuse_read_in(arg); break; case FUSE_READDIRPLUS: debug_fuse_read_in(arg); break; case FUSE_WRITE: debug_fuse_write_in(arg); break; case FUSE_RELEASE: debug_fuse_release_in(arg); break; case FUSE_RELEASEDIR: debug_fuse_release_in(arg); break; case FUSE_FSYNCDIR: debug_fuse_fsync_in(arg); break; case FUSE_GETXATTR: debug_fuse_getxattr_in(arg); break; case FUSE_LISTXATTR: debug_fuse_listxattr(arg); break; case FUSE_SETXATTR: debug_fuse_setxattr_in(arg); break; case FUSE_REMOVEXATTR: debug_fuse_removexattr(arg); break; case FUSE_FALLOCATE: debug_fuse_fallocate_in(arg); break; case FUSE_FLUSH: debug_fuse_flush_in(arg); break; case FUSE_INTERRUPT: debug_fuse_interrupt_in(arg); break; default: fprintf(g_OUTPUT,"FIXME\n"); break; } } void debug_fuse_out_header(const struct fuse_out_header *hdr_) { fprintf(g_OUTPUT, "unique=0x%016"PRIx64";" " opcode=RESPONSE;" " error=%d (%s);" " len=%"PRIu64";" , hdr_->unique, hdr_->error, strerror(-hdr_->error), sizeof(struct fuse_out_header)); } void debug_fuse_entry_open_out(const uint64_t unique_, const struct fuse_entry_out *entry_, const struct fuse_open_out *open_) { fprintf(g_OUTPUT, "unique=0x%016"PRIx64";" " opcode=RESPONSE;" " error=0 (Success);" " len=%"PRIu64"; || " , unique_, sizeof(struct fuse_entry_out) + sizeof(struct fuse_open_out)); debug_fuse_entry(entry_); } void debug_fuse_readlink(const uint64_t unique_, const char *linkname_) { fprintf(g_OUTPUT, "unique=0x%016"PRIx64";" " opcode=RESPONSE;" " error=0 (Success);" " len=%"PRIu64"; || " "readlink: linkname=%s" "\n" , unique_, (sizeof(struct fuse_out_header) + strlen(linkname_)), linkname_); } void debug_fuse_write_out(const uint64_t unique_, const struct fuse_write_out *arg_) { fprintf(g_OUTPUT, "unique=0x%016"PRIx64";" " opcode=RESPONSE;" " error=0 (Success);" " len=%"PRIu64"; || " " fuse_write_out:" " size=%u" "\n" , unique_, sizeof(struct fuse_write_out), arg_->size); } void debug_fuse_statfs_out(const uint64_t unique_, const struct fuse_statfs_out *arg_) { fprintf(g_OUTPUT, "unique=0x%016"PRIx64";" " opcode=RESPONSE;" " error=0 (Success);" " len=%"PRIu64"; || " " fuse_statfs_out:" " blocks=%"PRIu64";" " bfree=%"PRIu64";" " bavail=%"PRIu64";" " files=%"PRIu64";" " ffree=%"PRIu64";" " bsize=%u;" " namelen=%u;" " frsize=%u;" "\n" , unique_, sizeof(struct fuse_statfs_out), arg_->st.blocks, arg_->st.bfree, arg_->st.bavail, arg_->st.files, arg_->st.ffree, arg_->st.bsize, arg_->st.namelen, arg_->st.frsize); } void debug_fuse_getxattr_out(const uint64_t unique_, const struct fuse_getxattr_out *arg_) { fprintf(g_OUTPUT, "unique=0x%016"PRIx64";" " opcode=RESPONSE;" " error=0 (Success);" " len=%"PRIu64"; || " " fuse_getxattr_out:" " size=%u;" "\n" , unique_, sizeof(struct fuse_out_header) + sizeof(struct fuse_getxattr_out), arg_->size); } void debug_fuse_lk_out(const uint64_t unique_, const struct fuse_lk_out *arg_) { fprintf(g_OUTPUT, "unique=0x%016"PRIx64";" " opcode=RESPONSE;" " error=0 (Success);" " len=%"PRIu64"; || " " fuse_file_lock:" " start=%"PRIu64";" " end=%"PRIu64";" " type=%u;" " pid=%u;" "\n" , unique_, sizeof(struct fuse_out_header) + sizeof(struct fuse_lk_out), arg_->lk.start, arg_->lk.end, arg_->lk.type, arg_->lk.pid); } void debug_fuse_bmap_out(const uint64_t unique_, const struct fuse_bmap_out *arg_) { fprintf(g_OUTPUT, "unique=0x%016"PRIx64";" " opcode=RESPONSE;" " error=0 (Success);" " len=%"PRIu64"; || " " fuse_bmap_out:" " block=%"PRIu64";" "\n" , unique_, sizeof(struct fuse_out_header) + sizeof(struct fuse_bmap_out), arg_->block); } mergerfs-2.33.5/libfuse/lib/fuse_node.c0000644000000000000000000001072014225522122016423 0ustar rootroot#include "fuse_node.h" #include "khash.h" #include #include #include #include // for debugging #define UNKNOWN_INO UINT64_MAX #define ROOT_NODE_ID 0 #define ROOT_NODE_NAME "/" typedef struct node_idname_t node_idname_t; struct node_idname_t { uint64_t id; const char *name; }; static khint_t idname_hash_func(const node_idname_t idname); static int idname_hash_equal(const node_idname_t idname0, const node_idname_t idname1); KHASH_INIT(node,node_idname_t,fuse_node_t*,1,idname_hash_func,idname_hash_equal); typedef struct fuse_node_hashtable_t fuse_node_hashtable_t; struct fuse_node_hashtable_t { kh_node_t *ht; uint64_t id; uint64_t generation; }; static inline khint_t idname_hash_func(const node_idname_t idname_) { if(idname_.name == NULL) return idname_.id; return (idname_.id ^ kh_str_hash_func(idname_.name)); } static inline int idname_hash_equal(const node_idname_t idname0_, const node_idname_t idname1_) { return ((idname0_.id == idname1_.id) && ((idname0_.name == idname1_.name) || (strcmp(idname0_.name,idname1_.name) == 0))); } static inline fuse_node_t* fuse_node_alloc(const uint64_t id_, const char *name_) { fuse_node_t *node; node = (fuse_node_t*)calloc(1,sizeof(fuse_node_t)); node->id = id_; node->name = strdup(name_); node->ref_count = 1; node->lookup_count = 1; return node; } static inline void fuse_node_free(fuse_node_t *node_) { free(node_->name); free(node_); } static inline uint64_t rand64() { uint64_t rv; rv = rand(); rv <<= 32; rv |= rand(); return rv; } static inline void node_hashtable_gen_unique_id(fuse_node_hashtable_t *ht_) { do { ht_->id++; if(ht_->id == 0) ht_->generation++; } while((ht_->id == 0) || (ht_->id == UNKNOWN_INO)); } static inline void node_hashtable_put_root(fuse_node_hashtable_t *ht_) { int rv; khint_t k; fuse_node_t *root_node; const node_idname_t idname0 = {ROOT_NODE_ID,""}; const node_idname_t idname1 = {ROOT_NODE_ID,ROOT_NODE_NAME}; root_node = fuse_node_alloc(ROOT_NODE_ID,ROOT_NODE_NAME); k = kh_put_node(ht_->ht,idname0,&rv); kh_value(ht_->ht,k) = root_node; k = kh_put_node(ht_->ht,idname1,&rv); kh_value(ht_->ht,k) = root_node; } static inline void node_hashtable_set_id_gen(fuse_node_hashtable_t *ht_, fuse_node_t *node_) { node_hashtable_gen_unique_id(ht_); node_->id = ht_->id; node_->generation = ht_->generation; } fuse_node_hashtable_t* fuse_node_hashtable_init() { fuse_node_hashtable_t *ht; ht = (fuse_node_hashtable_t*)calloc(sizeof(fuse_node_hashtable_t),1); if(ht == NULL) return NULL; ht->ht = kh_init_node(); if(ht->ht == NULL) { free(ht); return NULL; } srand(time(NULL)); ht->id = 0; ht->generation = rand64(); node_hashtable_put_root(ht); return ht; } fuse_node_t* fuse_node_hashtable_put(fuse_node_hashtable_t *ht_, const uint64_t parent_id_, const uint64_t child_id_, const char *child_name_) { int rv; khint_t k; fuse_node_t *child_node; const node_idname_t p_idname = {parent_id_,""}; const node_idname_t c0_idname = {child_id_,child_name_}; const node_idname_t c1_idname = {parent_id_,child_name_}; child_node = fuse_node_alloc(child_id_,child_name_); k = kh_get_node(ht_->ht,p_idname); child_node->parent = kh_value(ht_->ht,k); child_node->parent->ref_count++; k = kh_put_node(ht_->ht,c0_idname,&rv); kh_value(ht_->ht,k) = child_node; k = kh_put_node(ht_->ht,c1_idname,&rv); kh_value(ht_->ht,k) = child_node; return child_node; } fuse_node_t* fuse_node_hashtable_get(fuse_node_hashtable_t *ht_, const uint64_t id_) { return fuse_node_hashtable_get_child(ht_,id_,""); } fuse_node_t* fuse_node_hashtable_get_child(fuse_node_hashtable_t *ht_, const uint64_t parent_id_, const char *child_name_) { khint_t k; fuse_node_t *node; const node_idname_t idname = {parent_id_,child_name_}; k = kh_get_node(ht_->ht,idname); node = ((k != kh_end(ht_->ht)) ? kh_value(ht_->ht,k) : NULL); return node; } void fuse_node_hashtable_del(fuse_node_hashtable_t *ht_, fuse_node_t *node_) { } mergerfs-2.33.5/libfuse/lib/mount.c0000644000000000000000000000170414225522122015620 0ustar rootroot/* ISC License Copyright (c) 2019, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #if defined __FreeBSD__ || __NetBSD__ || __OpenBSD__ || __DragonFly__ # include "mount_bsd.c" #else # include "mount_generic.c" # include "mount_util.c" #endif mergerfs-2.33.5/libfuse/lib/fmp.h0000644000000000000000000001475614225522122015260 0ustar rootroot/* ISC License Copyright (c) 2021, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include "kvec.h" #include #include #include #include #include #include #define ROUND_UP(N,S) ((((N) + (S) - 1) / (S)) * (S)) typedef kvec_t(void*) slab_kvec_t; typedef struct mem_stack_t mem_stack_t; struct mem_stack_t { mem_stack_t *next; }; typedef struct fmp_t fmp_t; struct fmp_t { mem_stack_t *objs; slab_kvec_t slabs; uint64_t avail_objs; uint64_t obj_size; uint64_t page_size; uint64_t slab_size; }; static inline uint64_t fmp_page_size() { return sysconf(_SC_PAGESIZE); } static inline void fmp_init(fmp_t *fmp_, const uint64_t obj_size_, const uint64_t page_multiple_) { kv_init(fmp_->slabs); fmp_->objs = NULL; fmp_->avail_objs = 0; fmp_->obj_size = ROUND_UP(obj_size_,sizeof(void*)); fmp_->page_size = fmp_page_size(); fmp_->slab_size = (fmp_->page_size * page_multiple_); } static inline uint64_t fmp_slab_count(fmp_t *fmp_) { return kv_size(fmp_->slabs); } static inline void* fmp_slab_alloc_posix_memalign(fmp_t *fmp_) { int rv; void *mem; const size_t alignment = fmp_->page_size; const size_t size = fmp_->slab_size; rv = posix_memalign(&mem,alignment,size); if(rv != 0) return NULL; return NULL; } static inline void* fmp_slab_alloc_mmap(fmp_t *fmp_) { void *mem; void *address = NULL; const size_t length = fmp_->slab_size; const int protect = PROT_READ|PROT_WRITE; const int flags = MAP_PRIVATE|MAP_ANONYMOUS; const int filedes = -1; const off_t offset = 0; mem = mmap(address,length,protect,flags,filedes,offset); if(mem == MAP_FAILED) return NULL; return mem; } static inline void fmp_slab_free_posix_memalign(fmp_t* fmp_, void *mem_) { (void)fmp_; free(mem_); } static inline void fmp_slab_free_mmap(fmp_t* fmp_, void *mem_) { void *addr = mem_; size_t length = fmp_->slab_size; (void)munmap(addr,length); } static inline int fmp_slab_alloc(fmp_t *fmp_) { char *i; void *mem; mem = fmp_slab_alloc_mmap(fmp_); if(mem == NULL) return -ENOMEM; kv_push(void*,fmp_->slabs,mem); i = ((char*)mem + fmp_->slab_size - fmp_->obj_size); while(i >= (char*)mem) { mem_stack_t *obj = (mem_stack_t*)i; obj->next = fmp_->objs; fmp_->objs = obj; fmp_->avail_objs++; i -= fmp_->obj_size; } return 0; } static inline void* fmp_alloc(fmp_t *fmp_) { void *rv; if(fmp_->objs == NULL) fmp_slab_alloc(fmp_); if(fmp_->objs == NULL) return NULL; rv = fmp_->objs; fmp_->objs = fmp_->objs->next; fmp_->avail_objs--; return rv; } static inline void* fmp_calloc(fmp_t *fmp_) { void *obj; obj = fmp_alloc(fmp_); if(obj == NULL) return NULL; memset(obj,0,fmp_->obj_size); return obj; } static inline void fmp_free(fmp_t *fmp_, void *obj_) { mem_stack_t *obj = (mem_stack_t*)obj_; obj->next = fmp_->objs; fmp_->objs = obj; fmp_->avail_objs++; } static inline void fmp_clear(fmp_t *fmp_) { while(kv_size(fmp_->slabs)) { void *slab = kv_pop(fmp_->slabs); fmp_slab_free_mmap(fmp_,slab); } fmp_->objs = NULL; fmp_->avail_objs = 0; } static inline void fmp_destroy(fmp_t *fmp_) { fmp_clear(fmp_); kv_destroy(fmp_->slabs); } static inline uint64_t fmp_avail_objs(fmp_t *fmp_) { return fmp_->avail_objs; } static inline uint64_t fmp_objs_per_slab(fmp_t *fmp_) { return (fmp_->slab_size / fmp_->obj_size); } static inline uint64_t fmp_objs_in_slab(fmp_t *fmp_, void *slab_) { char *slab; uint64_t objs_per_slab; uint64_t objs_in_slab; slab = (char*)slab_; objs_in_slab = 0; objs_per_slab = fmp_objs_per_slab(fmp_); for(mem_stack_t *stack = fmp_->objs; stack != NULL; stack = stack->next) { char *obj = (char*)stack; if((obj >= slab) && (obj < (slab + fmp_->slab_size))) objs_in_slab++; if(objs_in_slab >= objs_per_slab) break; } return objs_in_slab; } static inline void fmp_remove_objs_in_slab(fmp_t *fmp_, void *slab_) { char *slab; uint64_t objs_per_slab; uint64_t objs_in_slab; mem_stack_t **p; p = &fmp_->objs; slab = (char*)slab_; objs_in_slab = 0; objs_per_slab = fmp_objs_per_slab(fmp_); while((*p) != NULL) { char *obj = (char*)*p; if((obj >= slab) && (obj < (slab + fmp_->slab_size))) { objs_in_slab++; *p = (*p)->next; fmp_->avail_objs--; if(objs_in_slab >= objs_per_slab) break; continue; } p = &(*p)->next; } } static inline int fmp_gc_slab(fmp_t *fmp_, uint64_t slab_idx_) { char *slab; uint64_t objs_in_slab; uint64_t objs_per_slab; slab_idx_ = (slab_idx_ % kv_size(fmp_->slabs)); slab = kv_A(fmp_->slabs,slab_idx_); objs_per_slab = fmp_objs_per_slab(fmp_); objs_in_slab = fmp_objs_in_slab(fmp_,slab); if(objs_in_slab != objs_per_slab) return 0; fmp_remove_objs_in_slab(fmp_,slab); kv_delete(fmp_->slabs,slab_idx_); fmp_slab_free_mmap(fmp_,slab); return 1; } static inline int fmp_gc(fmp_t *fmp_) { uint64_t slab_idx; slab_idx = rand(); return fmp_gc_slab(fmp_,slab_idx); } static inline double fmp_slab_usage_ratio(fmp_t *fmp_) { double avail_objs; double objs_per_slab; double nums_of_slabs; avail_objs = fmp_->avail_objs; objs_per_slab = fmp_objs_per_slab(fmp_); nums_of_slabs = kv_size(fmp_->slabs); return (avail_objs / (objs_per_slab * nums_of_slabs)); } static inline uint64_t fmp_total_allocated_memory(fmp_t *fmp_) { return (fmp_->slab_size * kv_size(fmp_->slabs)); } mergerfs-2.33.5/libfuse/lib/mount_bsd.c0000644000000000000000000001555414225522122016460 0ustar rootroot/* FUSE: Filesystem in Userspace Copyright (C) 2005-2008 Csaba Henk This program can be distributed under the terms of the GNU LGPLv2. See the file COPYING.LIB. */ #include "fuse_i.h" #include "fuse_misc.h" #include "fuse_opt.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #define FUSERMOUNT_PROG "mount_fusefs" #define FUSE_DEV_TRUNK "/dev/fuse" enum { KEY_RO, KEY_HELP, KEY_VERSION, KEY_KERN }; struct mount_opts { int allow_other; int ishelp; char *kernel_opts; }; #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 }, FUSE_OPT_KEY("-r", KEY_RO), FUSE_OPT_KEY("-h", KEY_HELP), FUSE_OPT_KEY("--help", KEY_HELP), FUSE_OPT_KEY("-V", KEY_VERSION), FUSE_OPT_KEY("--version", KEY_VERSION), /* 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), /* 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), FUSE_OPT_KEY("nosync_unmount", KEY_KERN), /* 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_KEY("nonempty", KEY_KERN), FUSE_OPT_KEY("large_read", KEY_KERN), FUSE_OPT_END }; static void mount_help(void) { system(FUSERMOUNT_PROG " --help"); fputc('\n', stderr); } static void mount_version(void) { system(FUSERMOUNT_PROG " --version"); } static int fuse_mount_opt_proc(void *data, const char *arg, int key, struct fuse_args *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); case KEY_HELP: mount_help(); mo->ishelp = 1; break; case KEY_VERSION: mount_version(); mo->ishelp = 1; break; } return 1; } static void do_unmount(char *dev, int fd) { char device_path[SPECNAMELEN + 12]; const char *argv[4]; const char umount_cmd[] = "/sbin/umount"; pid_t pid; snprintf(device_path, SPECNAMELEN + 12, _PATH_DEV "%s", dev); argv[0] = umount_cmd; argv[1] = "-f"; argv[2] = device_path; argv[3] = NULL; pid = fork(); if (pid == -1) return; if (pid == 0) { close(fd); execvp(umount_cmd, (char **)argv); exit(1); } waitpid(pid, NULL, 0); } void fuse_kern_unmount(const char *mountpoint, int fd) { char *ep, dev[128]; struct stat sbuf; (void)mountpoint; if (fstat(fd, &sbuf) == -1) goto out; devname_r(sbuf.st_rdev, S_IFCHR, dev, 128); if (strncmp(dev, "fuse", 4)) goto out; strtol(dev + 4, &ep, 10); if (*ep != '\0') goto out; do_unmount(dev, fd); out: close(fd); } /* Check if kernel is doing init in background */ static int init_backgrounded(void) { unsigned ibg, len; len = sizeof(ibg); if (sysctlbyname("vfs.fuse.init_backgrounded", &ibg, &len, NULL, 0)) return 0; return ibg; } static int fuse_mount_core(const char *mountpoint, const char *opts) { const char *mountprog = FUSERMOUNT_PROG; int fd; char *fdnam, *dev; pid_t pid, cpid; int status; fdnam = getenv("FUSE_DEV_FD"); if (fdnam) { char *ep; fd = strtol(fdnam, &ep, 10); if (*ep != '\0') { fprintf(stderr, "invalid value given in FUSE_DEV_FD\n"); return -1; } if (fd < 0) 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) { if (! init_backgrounded()) { /* * If init is not backgrounded, we have to * call the mount util backgrounded, to avoid * deadlock. */ pid = fork(); if (pid == -1) { perror("fuse: fork() failed"); close(fd); exit(1); } } if (pid == 0) { const char *argv[32]; int a = 0; if (! fdnam && asprintf(&fdnam, "%d", fd) == -1) { perror("fuse: failed to assemble mount arguments"); exit(1); } 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"); exit(1); } exit(0); } if (waitpid(cpid, &status, 0) == -1 || WEXITSTATUS(status) != 0) { perror("fuse: failed to mount file system"); close(fd); return -1; } out: return fd; } int fuse_kern_mount(const char *mountpoint, struct fuse_args *args) { struct mount_opts mo; int res = -1; memset(&mo, 0, sizeof(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); if (args && fuse_opt_parse(args, &mo, fuse_mount_opts, fuse_mount_opt_proc) == -1) return -1; if (mo.ishelp) return 0; res = fuse_mount_core(mountpoint, mo.kernel_opts); out: free(mo.kernel_opts); return res; } mergerfs-2.33.5/libfuse/lib/crc32b.c0000644000000000000000000001116514225522122015536 0ustar rootroot/* ISC License Copyright (c) 2021, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "crc32b.h" static const crc32b_t CRC32BTABLE[256] = { 0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, 0x076DC419, 0x706AF48F, 0xE963A535, 0x9E6495A3, 0x0EDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988, 0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, 0x90BF1D91, 0x1DB71064, 0x6AB020F2, 0xF3B97148, 0x84BE41DE, 0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7, 0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC, 0x14015C4F, 0x63066CD9, 0xFA0F3D63, 0x8D080DF5, 0x3B6E20C8, 0x4C69105E, 0xD56041E4, 0xA2677172, 0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B, 0x35B5A8FA, 0x42B2986C, 0xDBBBC9D6, 0xACBCF940, 0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59, 0x26D930AC, 0x51DE003A, 0xC8D75180, 0xBFD06116, 0x21B4F4B5, 0x56B3C423, 0xCFBA9599, 0xB8BDA50F, 0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924, 0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D, 0x76DC4190, 0x01DB7106, 0x98D220BC, 0xEFD5102A, 0x71B18589, 0x06B6B51F, 0x9FBFE4A5, 0xE8B8D433, 0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818, 0x7F6A0DBB, 0x086D3D2D, 0x91646C97, 0xE6635C01, 0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E, 0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457, 0x65B0D9C6, 0x12B7E950, 0x8BBEB8EA, 0xFCB9887C, 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65, 0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2, 0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB, 0x4369E96A, 0x346ED9FC, 0xAD678846, 0xDA60B8D0, 0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9, 0x5005713C, 0x270241AA, 0xBE0B1010, 0xC90C2086, 0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F, 0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, 0x59B33D17, 0x2EB40D81, 0xB7BD5C3B, 0xC0BA6CAD, 0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A, 0xEAD54739, 0x9DD277AF, 0x04DB2615, 0x73DC1683, 0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8, 0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1, 0xF00F9344, 0x8708A3D2, 0x1E01F268, 0x6906C2FE, 0xF762575D, 0x806567CB, 0x196C3671, 0x6E6B06E7, 0xFED41B76, 0x89D32BE0, 0x10DA7A5A, 0x67DD4ACC, 0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5, 0xD6D6A3E8, 0xA1D1937E, 0x38D8C2C4, 0x4FDFF252, 0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B, 0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60, 0xDF60EFC3, 0xA867DF55, 0x316E8EEF, 0x4669BE79, 0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236, 0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F, 0xC5BA3BBE, 0xB2BD0B28, 0x2BB45A92, 0x5CB36A04, 0xC2D7FFA7, 0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D, 0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A, 0x9C0906A9, 0xEB0E363F, 0x72076785, 0x05005713, 0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38, 0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21, 0x86D3D2D4, 0xF1D4E242, 0x68DDB3F8, 0x1FDA836E, 0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777, 0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C, 0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45, 0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2, 0xA7672661, 0xD06016F7, 0x4969474D, 0x3E6E77DB, 0xAED16A4A, 0xD9D65ADC, 0x40DF0B66, 0x37D83BF0, 0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9, 0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6, 0xBAD03605, 0xCDD70693, 0x54DE5729, 0x23D967BF, 0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94, 0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D }; crc32b_t crc32b_start(void) { return 0xFFFFFFFF; } crc32b_t crc32b_continue(const void *buf_, const crc32b_t len_, const crc32b_t crc_) { int i; char *buf; crc32b_t crc; crc = crc_; buf = (char*)buf_; for(i = 0; i < len_; i++) { crc = (CRC32BTABLE[(crc ^ buf[i]) & 0xFFL] ^ (crc >> 8)); } return crc; } crc32b_t crc32b_finish(const crc32b_t crc_) { return (crc_ ^ 0xFFFFFFFF); } crc32b_t crc32b(const void *buf_, const crc32b_t len_) { crc32b_t crc; crc = crc32b_start(); crc = crc32b_continue(buf_,len_,crc); crc = crc32b_finish(crc); return crc; } mergerfs-2.33.5/libfuse/COPYING0000644000000000000000000004325414225522122014605 0ustar rootroot 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. mergerfs-2.33.5/libfuse/AUTHORS0000644000000000000000000000351014225522122014611 0ustar rootrootCurrent Maintainer ------------------ Nikolaus Rath Past Maintainers ---------------- Miklos Szeredi (until 12/2015) Contributors ------------ CUSE has been written by Tejun Heo . Furthermore, the following people have contributed patches (autogenerated list): Anatol Pomozov Antonio SJ Musumeci Christopher Harrison Csaba Henk cvs2git <> Dalvik Khertel Daniel Thau David McNab David Sheets Emmanuel Dreyfus Enke Chen Eric Engestrom Eric Wong Fabrice Bauzac Feng Shuo Hendrik Brueckner Ikey Doherty Jan Blumschein Joachim Schiele Joachim Schiele John Muir Laszlo Papp Madan Valluri Mark Glines Max Krasnyansky Michael Grigoriev Miklos Szeredi Miklos Szeredi mkmm@gmx-topmail.de Natanael Copa Nikolaus Rath Olivier Blin Ratna_Bolla@dell.com Reuben Hawkins Richard W.M. Jones Riku Voipio Roland Bauerschmidt Sam Stuewe Sebastian Pipping therealneworld@gmail.com Winfried Koehler mergerfs-2.33.5/libfuse/README.md0000644000000000000000000000775614225522122015040 0ustar rootrootlibfuse ======= Warning: unresolved security issue ---------------------------------- Be aware that FUSE has an unresolved security bug ([bug #15](https://github.com/libfuse/libfuse/issues/15)): the permission check for accessing a cached directory is only done once when the directory entry is first loaded into the cache. Subsequent accesses will re-use the results of the first check, even if the directory permissions have since changed, and even if the subsequent access is made by a different user. 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 completely disable caching of directory entries. Alternatively, the severity of the bug can be somewhat reduced by not using the `allow_other` mount option. 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. Installation ------------ ./configure make -j8 make install You may also need to add `/usr/local/lib` to `/etc/ld.so.conf` and/or run *ldconfig*. If you're building from the git repository (instead of using a release tarball), you also need to run `./makeconf.sh` to create the `configure` script. You'll also need a fuse kernel module (Linux kernels 2.6.14 or later contain FUSE support). For more details see the file `INSTALL` Security implications --------------------- If you run `make install`, the *fusermount* program is installed set-user-id to root. This is done to allow normal users 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: - The user can only mount on a mountpoint, for which it has write permission - The mountpoint is not 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 `fuse.conf`) Building your own filesystem ------------------------------ FUSE comes with several example file systems in the `examples` directory. For example, the *fusexmp* example mirrors 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 at https://lists.sourceforge.net/lists/listinfo/fuse-devel). Please report any bugs on the GitHub issue tracker at https://github.com/libfuse/main/issues. mergerfs-2.33.5/libfuse/NEWS0000644000000000000000000001702414225522122014245 0ustar rootrootWhat is new in 2.9 - Add "zero copy" support for kernel 2.6.35 or newer - Make maximum background requests tunable on kernel 2.6.32 or newer - Require --no-canonicalize in (u)mount (util-linux version 2.18 or newer) to fix security problems with fusermount - Use dynamically sized hash tables in high level library - Memory use of filesystem daemon can shrink more easily - Add "auto_unmount" option - Add "remember" option - Add man pages for fusermount, mount.fuse and ulockmgr_server - API changes: o Introduce "store" and "retrieve" for accessing kernel buffers on kernel 2.6.36 or newer o Introduce abstract buffer for zero copy operations o Allow path calculation to be omitted on certain operations o Allow batching forget requests o Add "flock" method o Add support for ioctl on directories o Add delete notification o Add fallocate operation (linux kernel 3.5 or newer) - Bug fixes and small improvements ============================================================================ What is new in 2.8 - More scalable directory tree locking - Atomic open(O_TRUNC) support - Support big write requests on kernels 2.6.26 and newer - Out-of-tree fuse module removed - Better NFS exporting support - New ioctl and poll requests - New CUSE (Character Device in Userspace) interface - Allow umask processing in userspace - Added cache invalidation notifications - Bugfixes and small improvements ============================================================================ What is new in 2.7 - Stacking support for the high level API - Add filename charset conversion module - Improved mounting ============================================================================ What is new in 2.6 - Improved read characteristics (asynchronous reads) - Support for aborting filesystem connection - POSIX file locking support - Request interruption support - Building module for Linux kernels earlier than 2.6.9 not supported - Allow block device based filesystems to support swap files - Several bugs fixed, including a rare system hang on SMP ============================================================================ What is new in 2.5 - Merge library part of FreeBSD port - New atomic create+open, access and ftruncate operations - On filesystems implementing the new create+open operation, and running on Linux kernels 2.6.15 or later, the 'cp' operation will work correctly when copying read-only files. - New option parsing interface added to the library - Lots of minor improvements and fixes ============================================================================ What is new in 2.4 - Simplify device opening. Now '/dev/fuse' is a requirement - Allow module auto-loading if user has access to '/dev/fuse' - Allow mounting over a regular file for unprivileged users - Allow mounting of arbitrary FUSE filesystems from /etc/fstab - New mount options: 'umask=M', 'uid=N', 'gid=N' - Check for non-empty mountpoint, and refuse mount by default. New mount option: 'nonempty' - Low level (inode based) API added - Allow 'direct_io' and 'keep_cache' options to be set on a case-by-case basis on open. - Add 'attr_timeout' and 'entry_timeout' mount options to the high-level library. Until now these timeouts were fixed at 1 sec. - Some bugfixes ============================================================================ What is new in 2.3 - Add new directory related operations: opendir(), readdir(), releasedir() and fsyncdir() - Add init() and destroy() operations which are called before the event loop is started and after it has exited - Update kernel ABI so that on dual architectures (e.g. AMD64) 32bit binaries work under a 64bit kernel - Bugfixes ============================================================================ What is new in 2.2 Userspace changes: - Add fuse_file_info structure to file operations, this allows the filesystem to return a file handle in open() which is passed to read(), write(), flush(), fsync() and release(). - Add source compatibility with 2.1 and 1.4 releases - Binary compatibility with 2.1 release is retained Kernel changes: - Make requests interruptible. This prevents the filesystem to go into an unbreakable deadlock with itself. - Make readpages() synchronous. Asynchronous requests are deadlock prone, since they cannot be interrupted (see above) - Remove shared-writeable mapping support, which could deadlock the machine - Remove INVALIDATE userspace initiated request - Update ABI to be independent of sizeof(long), so dual-size archs don't cause problems - Remove /sys/fs/fuse/version. Version checking is now done through the fuse device - Replace directory reading method on the kernel interface. Instead of passing an open file descriptor to the kernel, send data through the FUSE device, like all other operations. ============================================================================ What is new in 2.1 * Bug fixes * Improved support for filesystems implementing a custom event-loop * Add 'pkg-config' support * Kernel module can be compiled separately ============================================================================ What is new in 1.9 * Lots of bugs fixed * Minor modifications to the library API * Improvements to the kernel/userspace interface * Mounting by non-root made more secure * Build shared library in addition to the static one * Consolidated mount options * Optimized reading under 2.6 kernels * Direct I/O support * Support file I/O on deleted files * Extended attributes support ============================================================================ What is new in 1.3 * Thanks to user bugreports and stress testing with LTP and sfx-linux a number of bugs were fixed, some quite serious. * Fix compile problems with recent SuSE kernles ============================================================================ What is new in 1.2 * Fix mount problems on recent 2.6 kernels with SELinux enabled * Fixed writing files lager than 2GBytes * Other bugfixes ============================================================================ What is new in 1.1 * Support for the 2.6 kernels * Support for exporting filesystem over NFS in 2.6 kernels * Read efficiency improvements: read in 64k blocks instead of 4k (Michael Grigoriev). Can be turned on with '-l' option of fusermount * Lazy automatic unmount * Added 'fsync()' VFS call to the FUSE interface * Bugfixes ============================================================================ What is new in 1.0 * Cleanups and bugfixes * Added 'release()' VFS call to the FUSE interface * 64 bit file offsets (handling of > 4 GByte files) * libfuse is now under LGPL * New 'statfs' call (Mark Glines) * Cleaned up mount procedure (mostly by Mark Glines) NOTE: Binaries linked with with a previous version of libavfs may not work with the new version of the fusermount program. In such case recompile the program after installing the new libavfs library. * Fix for problems under linux kernel 2.4.19 ============================================================================ What is new in 0.95 * Optimized read/write operations. Raw throughput has increased to about 60Mbyte/s on a Celeron/360 * Python bindings by Jeff Epler * Perl bindings by Mark Glines * Improved multithreaded operation * Simplified library interface * Bugfixes ============================================================================ What is new in 0.9: * Everything mergerfs-2.33.5/libfuse/Makefile0000644000000000000000000000510114225522122015177 0ustar rootrootVERSION = 2.9.7-mergerfs_2.30.0 OS := $(shell uname -s) ifeq ($(OS),Linux) UTILS := utils INSTALLUTILS := install-utils else UTILS := INSTALLUTILS := endif ifeq ($(DEBUG),1) OPT_FLAGS := -O0 -g -fsanitize=undefined else OPT_FLAGS := -O2 endif DESTDIR = PREFIX = /usr/local EXEC_PREFIX = $(PREFIX) DATAROOTDIR = $(PREFIX)/share DATADIR = $(DATAROOTDIR) BINDIR = $(EXEC_PREFIX)/bin SBINDIR = /sbin MANDIR = $(DATAROOTDIR)/man MAN1DIR = $(MANDIR)/man1 INSTALLBINDIR = $(DESTDIR)$(BINDIR) INSTALLSBINDIR = $(DESTDIR)$(SBINDIR) INSTALLMAN1DIR = $(DESTDIR)$(MAN1DIR) AR ?= ar SRC = \ lib/buffer.c \ lib/crc32b.c \ lib/debug.c \ lib/fuse.c \ lib/fuse_dirents.c \ lib/fuse_kern_chan.c \ lib/fuse_loop_mt.c \ lib/fuse_lowlevel.c \ lib/fuse_mt.c \ lib/fuse_node.c \ lib/fuse_opt.c \ lib/fuse_session.c \ lib/fuse_signals.c \ lib/helper.c \ lib/mount.c OBJS = $(SRC:lib/%.c=build/%.o) DEPS = $(SRC:lib/%.c=build/%.d) CFLAGS ?= \ $(OPT_FLAGS) CFLAGS := \ ${CFLAGS} \ -std=gnu99 \ -Wall \ -pipe \ -MMD FUSERMOUNT_DIR = $(BINDIR) FUSE_FLAGS = \ -Iinclude \ -Ibuild \ -D_REENTRANT \ -D_FILE_OFFSET_BITS=64 \ -DFUSE_USE_VERSION=29 \ -DPACKAGE_VERSION=\"$(VERSION)\" \ -DFUSERMOUNT_DIR=\"$(FUSERMOUNT_DIR)\" LDFLAGS := \ ${LDFLAGS} \ -lrt \ -pthread all: build/libfuse.a $(UTILS) build/config.h: build/stamp ecfd/build | tee build/config.h build/stamp: mkdir -p build touch $@ objects: build/config.h $(MAKE) $(OBJS) build/libfuse.a: objects ${AR} rcs build/libfuse.a $(OBJS) utils: mergerfs-fusermount mount.mergerfs build/mergerfs-fusermount: build/config.h util/fusermount.c lib/mount_util.c $(CC) $(CFLAGS) $(FUSE_FLAGS) -Ilib -o build/mergerfs-fusermount util/fusermount.c lib/mount_util.c mergerfs-fusermount: build/mergerfs-fusermount build/mount.mergerfs: build/libfuse.a util/mount.mergerfs.c $(CC) $(CFLAGS) $(FUSE_FLAGS) -o build/mount.mergerfs util/mount.mergerfs.c build/libfuse.a $(LDFLAGS) mount.mergerfs: build/mount.mergerfs build/%.o: lib/%.c $(CC) $(CFLAGS) $(FUSE_FLAGS) -c $< -o $@ clean: rm -rf build distclean: clean strip: strip --strip-all build/mount.mergerfs strip --strip-all build/mergerfs-fusermount install-utils: mergerfs-fusermount mount.mergerfs strip install -D build/mergerfs-fusermount "$(INSTALLBINDIR)/mergerfs-fusermount" install -D build/mount.mergerfs "$(INSTALLSBINDIR)/mount.mergerfs" chown root "$(INSTALLBINDIR)/mergerfs-fusermount" chmod u+s "$(INSTALLBINDIR)/mergerfs-fusermount" install: $(INSTALLUTILS) .PHONY: objects strip utils install install-utils -include $(DEPS) mergerfs-2.33.5/libfuse/README.NFS0000644000000000000000000000254614225522122015056 0ustar rootrootNFS 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 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. 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. mergerfs-2.33.5/README.md0000644000000000000000000030351114225522122013373 0ustar rootroot% mergerfs(1) mergerfs user manual % Antonio SJ Musumeci % 2021-10-25 # NAME mergerfs - a featureful union filesystem # SYNOPSIS mergerfs -o<options> <branches> <mountpoint> # DESCRIPTION **mergerfs** is a union filesystem geared towards simplifying storage and management of files across numerous commodity storage devices. It is similar to **mhddfs**, **unionfs**, and **aufs**. # FEATURES * Configurable behaviors / file placement * Ability to add or remove filesystems at will * Resistance to individual filesystem failure * Support for extended attributes (xattrs) * Support for file attributes (chattr) * Runtime configurable (via xattrs) * Works with heterogeneous filesystem types * Moving of file when filesystem runs out of space while writing * Ignore read-only filesystems when creating files * Turn read-only files into symlinks to underlying file * Hard link copy-on-write / CoW * Support for POSIX ACLs * Misc other things # HOW IT WORKS mergerfs logically merges multiple paths together. Think a union of sets. The file/s or directory/s acted on or presented through mergerfs are based on the policy chosen for that particular action. Read more about policies below. ``` A + B = C /disk1 /disk2 /merged | | | +-- /dir1 +-- /dir1 +-- /dir1 | | | | | | | +-- file1 | +-- file2 | +-- file1 | | +-- file3 | +-- file2 +-- /dir2 | | +-- file3 | | +-- /dir3 | | +-- file4 | +-- /dir2 | +-- file5 | | +-- file6 | +-- file4 | +-- /dir3 | | | +-- file5 | +-- file6 ``` mergerfs does **NOT** support the copy-on-write (CoW) or whiteout behaviors found in **aufs** and **overlayfs**. You can **not** mount a read-only filesystem and write to it. However, mergerfs will ignore read-only drives when creating new files so you can mix read-write and read-only drives. It also does **NOT** split data across drives. It is not RAID0 / striping. It is simply a union of other filesystems. # TERMINOLOGY * branch: A base path used in the pool. * pool: The mergerfs mount. The union of the branches. * relative path: The path in the pool relative to the branch and mount. * function: A filesystem call (open, unlink, create, getattr, rmdir, etc.) * category: A collection of functions based on basic behavior (action, create, search). * policy: The algorithm used to select a file when performing a function. * path preservation: Aspect of some policies which includes checking the path for which a file would be created. # BASIC SETUP If you don't already know that you have a special use case then just start with one of the following option sets. #### You need `mmap` (used by rtorrent and many sqlite3 base software) `allow_other,use_ino,cache.files=partial,dropcacheonclose=true,category.create=mfs` #### You don't need `mmap` `allow_other,use_ino,cache.files=off,dropcacheonclose=true,category.create=mfs` See the mergerfs [wiki for real world deployments](https://github.com/trapexit/mergerfs/wiki/Real-World-Deployments) for comparisons / ideas. # OPTIONS These options are the same regardless you use them with the `mergerfs` commandline program, used in fstab, or in a config file. ### mount options * **config**: Path to a config file. Same arguments as below in key=val / ini style format. * **branches**: Colon delimited list of branches. * **allow_other**: A libfuse option which allows users besides the one which ran mergerfs to see the filesystem. This is required for most use-cases. * **minfreespace=SIZE**: The minimum space value used for creation policies. Can be overridden by branch specific option. Understands 'K', 'M', and 'G' to represent kilobyte, megabyte, and gigabyte respectively. (default: 4G) * **moveonenospc=BOOL|POLICY**: When enabled if a **write** fails with **ENOSPC** (no space left on device) or **EDQUOT** (disk quota exceeded) the policy selected will run to find a new location for the file. An attempt to move the file to that branch will occur (keeping all metadata possible) and if successful the original is unlinked and the write retried. (default: false, true = mfs) * **use_ino**: Causes mergerfs to supply file/directory inodes rather than libfuse. While not a default it is recommended it be enabled so that linked files share the same inode value. * **inodecalc=passthrough|path-hash|devino-hash|hybrid-hash**: Selects the inode calculation algorithm. (default: hybrid-hash) * **dropcacheonclose=BOOL**: When a file is requested to be closed call `posix_fadvise` on it first to instruct the kernel that we no longer need the data and it can drop its cache. Recommended when **cache.files=partial|full|auto-full** to limit double caching. (default: false) * **symlinkify=BOOL**: When enabled and a file is not writable and its mtime or ctime is older than **symlinkify_timeout** files will be reported as symlinks to the original files. Please read more below before using. (default: false) * **symlinkify_timeout=UINT**: Time to wait, in seconds, to activate the **symlinkify** behavior. (default: 3600) * **nullrw=BOOL**: Turns reads and writes into no-ops. The request will succeed but do nothing. Useful for benchmarking mergerfs. (default: false) * **ignorepponrename=BOOL**: Ignore path preserving on rename. Typically rename and link act differently depending on the policy of `create` (read below). Enabling this will cause rename and link to always use the non-path preserving behavior. This means files, when renamed or linked, will stay on the same drive. (default: false) * **security_capability=BOOL**: If false return ENOATTR when xattr security.capability is queried. (default: true) * **xattr=passthrough|noattr|nosys**: Runtime control of xattrs. Default is to passthrough xattr requests. 'noattr' will short circuit as if nothing exists. 'nosys' will respond with ENOSYS as if xattrs are not supported or disabled. (default: passthrough) * **link_cow=BOOL**: When enabled if a regular file is opened which has a link count > 1 it will copy the file to a temporary file and rename over the original. Breaking the link and providing a basic copy-on-write function similar to cow-shell. (default: false) * **statfs=base|full**: Controls how statfs works. 'base' means it will always use all branches in statfs calculations. 'full' is in effect path preserving and only includes drives where the path exists. (default: base) * **statfs_ignore=none|ro|nc**: 'ro' will cause statfs calculations to ignore available space for branches mounted or tagged as 'read-only' or 'no create'. 'nc' will ignore available space for branches tagged as 'no create'. (default: none) * **nfsopenhack=off|git|all**: A workaround for exporting mergerfs over NFS where there are issues with creating files for write while setting the mode to read-only. (default: off) * **follow-symlinks=never|directory|regular|all**: Turns symlinks into what they point to. (default: never) * **link-exdev=passthrough|rel-symlink|abs-base-symlink|abs-pool-symlink**: When a link fails with EXDEV optionally create a symlink to the file instead. * **rename-exdev=passthrough|rel-symlink|abs-symlink**: When a rename fails with EXDEV optionally move the file to a special directory and symlink to it. * **posix_acl=BOOL**: Enable POSIX ACL support (if supported by kernel and underlying filesystem). (default: false) * **async_read=BOOL**: Perform reads asynchronously. If disabled or unavailable the kernel will ensure there is at most one pending read request per file handle and will attempt to order requests by offset. (default: true) * **fuse_msg_size=UINT**: Set the max number of pages per FUSE message. Only available on Linux >= 4.20 and ignored otherwise. (min: 1; max: 256; default: 256) * **threads=INT**: Number of threads to use in multithreaded mode. When set to zero it will attempt to discover and use the number of logical cores. If the lookup fails it will fall back to using 4. If the thread count is set negative it will look up the number of cores then divide by the absolute value. ie. threads=-2 on an 8 core machine will result in 8 / 2 = 4 threads. There will always be at least 1 thread. NOTE: higher number of threads increases parallelism but usually decreases throughput. (default: 0) * **fsname=STR**: Sets the name of the filesystem as seen in **mount**, **df**, etc. Defaults to a list of the source paths concatenated together with the longest common prefix removed. * **func.FUNC=POLICY**: Sets the specific FUSE function's policy. See below for the list of value types. Example: **func.getattr=newest** * **category.action=POLICY**: Sets policy of all FUSE functions in the action category. (default: epall) * **category.create=POLICY**: Sets policy of all FUSE functions in the create category. (default: epmfs) * **category.search=POLICY**: Sets policy of all FUSE functions in the search category. (default: ff) * **cache.open=UINT**: 'open' policy cache timeout in seconds. (default: 0) * **cache.statfs=UINT**: 'statfs' cache timeout in seconds. (default: 0) * **cache.attr=UINT**: File attribute cache timeout in seconds. (default: 1) * **cache.entry=UINT**: File name lookup cache timeout in seconds. (default: 1) * **cache.negative_entry=UINT**: Negative file name lookup cache timeout in seconds. (default: 0) * **cache.files=libfuse|off|partial|full|auto-full**: File page caching mode (default: libfuse) * **cache.writeback=BOOL**: Enable kernel writeback caching (default: false) * **cache.symlinks=BOOL**: Cache symlinks (if supported by kernel) (default: false) * **cache.readdir=BOOL**: Cache readdir (if supported by kernel) (default: false) * **direct_io**: deprecated - Bypass page cache. Use `cache.files=off` instead. (default: false) * **kernel_cache**: deprecated - Do not invalidate data cache on file open. Use `cache.files=full` instead. (default: false) * **auto_cache**: deprecated - Invalidate data cache if file mtime or size change. Use `cache.files=auto-full` instead. (default: false) * **async_read**: deprecated - Perform reads asynchronously. Use `async_read=true` instead. * **sync_read**: deprecated - Perform reads synchronously. Use `async_read=false` instead. **NOTE:** Options are evaluated in the order listed so if the options are **func.rmdir=rand,category.action=ff** the **action** category setting will override the **rmdir** setting. #### Value Types * BOOL = 'true' | 'false' * INT = [MIN_INT,MAX_INT] * UINT = [0,MAX_INT] * SIZE = 'NNM'; NN = INT, M = 'K' | 'M' | 'G' | 'T' * STR = string * FUNC = filesystem function * CATEGORY = function category * POLICY = mergerfs function policy ### branches The 'branches' argument is a colon (':') delimited list of paths to be pooled together. It does not matter if the paths are on the same or different drives nor does it matter the filesystem (within reason). Used and available space will not be duplicated for paths on the same device and any features which aren't supported by the underlying filesystem (such as file attributes or extended attributes) will return the appropriate errors. Branches currently have two options which can be set. A type which impacts whether or not the branch is included in a policy calculation and a individual minfreespace value. The values are set by prepending an `=` at the end of a branch designation and using commas as delimiters. Example: /mnt/drive=RW,1234 #### branch type * RW: (read/write) - Default behavior. Will be eligible in all policy categories. * RO: (read-only) - Will be excluded from `create` and `action` policies. Same as a read-only mounted filesystem would be (though faster to process). * NC: (no-create) - Will be excluded from `create` policies. You can't create on that branch but you can change or delete. #### minfreespace Same purpose as the global option but specific to the branch. If not set the global value is used. #### globbing To make it easier to include multiple branches mergerfs supports [globbing](http://linux.die.net/man/7/glob). **The globbing tokens MUST be escaped when using via the shell else the shell itself will apply the glob itself.** ``` # mergerfs -o allow_other,use_ino /mnt/disk\*:/mnt/cdrom /media/drives ``` The above line will use all mount points in /mnt prefixed with **disk** and the **cdrom**. To have the pool mounted at boot or otherwise accessible from related tools use **/etc/fstab**. ``` # /mnt/disk*:/mnt/cdrom /mnt/pool fuse.mergerfs allow_other,use_ino 0 0 ``` **NOTE:** the globbing is done at mount or when updated using the runtime API. If a new directory is added matching the glob after the fact it will not be automatically included. **NOTE:** for mounting via **fstab** to work you must have **mount.fuse** installed. For Ubuntu/Debian it is included in the **fuse** package. ### inodecalc Inodes (st_ino) are unique identifiers within a filesystem. Each mounted filesystem has device ID (st_dev) as well and together they can uniquely identify a file on the whole of the system. Entries on the same device with the same inode are in fact references to the same underlying file. It is a many to one relationship between names and an inode. Directories, however, do not have multiple links on most systems due to the complexity they add. FUSE allows the server (mergerfs) to set inode values but not device IDs. Creating an inode value is somewhat complex in mergerfs' case as files aren't really in its control. If a policy changes what directory or file is to be selected or something changes out of band it becomes unclear what value should be used. Most software does not to care what the values are but those that do often break if a value changes unexpectedly. The tool `find` will abort a directory walk if it sees a directory inode change. NFS will return stale handle errors if the inode changes out of band. File dedup tools will usually leverage device ids and inodes as a shortcut in searching for duplicate files and would resort to full file comparisons should it find different inode values. mergerfs offers multiple ways to calculate the inode in hopes of covering different usecases. * passthrough: Passes through the underlying inode value. Mostly intended for testing as using this does not address any of the problems mentioned above and could confuse file deduplication software as inodes from different filesystems can be the same. * path-hash: Hashes the relative path of the entry in question. The underlying file's values are completely ignored. This means the inode value will always be the same for that file path. This is useful when using NFS and you make changes out of band such as copy data between branches. This also means that entries that do point to the same file will not be recognizable via inodes. That **does not** mean hard links don't work. They will. * path-hash32: 32bit version of path-hash. * devino-hash: Hashes the device id and inode of the underlying entry. This won't prevent issues with NFS should the policy pick a different file or files move out of band but will present the same inode for underlying files that do too. * devino-hash32: 32bit version of devino-hash. * hybrid-hash: Performs `path-hash` on directories and `devino-hash` on other file types. Since directories can't have hard links the static value won't make a difference and the files will get values useful for finding duplicates. Probably the best to use if not using NFS. As such it is the default. * hybrid-hash32: 32bit version of hybrid-hash. 32bit versions are provided as there is some software which does not handle 64bit inodes well. While there is a risk of hash collision in tests of a couple million entries there were zero collisions. Unlike a typical filesystem FUSE filesystems can reuse inodes and not refer to the same entry. The internal identifier used to reference a file in FUSE is different from the inode value presented. The former is the `nodeid` and is actually a tuple of 2 64bit values: `nodeid` and `generation`. This tuple is not client facing. The inode that is presented to the client is passed through the kernel uninterpreted. From FUSE docs regarding `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"). ``` In the future the `use_ino` option will probably be removed as this feature should replace the original libfuse inode calculation strategy. Currently you still need to use `use_ino` in order to enable `inodecalc`. ### fuse_msg_size FUSE applications communicate with the kernel over a special character device: `/dev/fuse`. A large portion of the overhead associated with FUSE is the cost of going back and forth from user space and kernel space over that device. Generally speaking the fewer trips needed the better the performance will be. Reducing the number of trips can be done a number of ways. Kernel level caching and increasing message sizes being two significant ones. When it comes to reads and writes if the message size is doubled the number of trips are approximately halved. In Linux 4.20 a new feature was added allowing the negotiation of the max message size. Since the size is in multiples of [pages](https://en.wikipedia.org/wiki/Page_(computer_memory)) the feature is called `max_pages`. There is a maximum `max_pages` value of 256 (1MiB) and minimum of 1 (4KiB). The default used by Linux >=4.20, and hardcoded value used before 4.20, is 32 (128KiB). In mergerfs its referred to as `fuse_msg_size` to make it clear what it impacts and provide some abstraction. Since there should be no downsides to increasing `fuse_msg_size` / `max_pages`, outside a minor bump in RAM usage due to larger message buffers, mergerfs defaults the value to 256. On kernels before 4.20 the value has no effect. The reason the value is configurable is to enable experimentation and benchmarking. See the BENCHMARKING section for examples. ### follow-symlinks This feature, when enabled, will cause symlinks to be interpreted by mergerfs as their target (depending on the mode). When there is a getattr/stat request for a file mergerfs will check if the file is a symlink and depending on the `follow-symlinks` setting will replace the information about the symlink with that of that which it points to. When unlink'ing or rmdir'ing the followed symlink it will remove the symlink itself and not that which it points to. * never: Behave as normal. Symlinks are treated as such. * directory: Resolve symlinks only which point to directories. * regular: Resolve symlinks only which point to regular files. * all: Resolve all symlinks to that which they point to. Symlinks which do not point to anything are left as is. WARNING: This feature works but there might be edge cases yet found. If you find any odd behaviors please file a ticket on [github](https://github.com/trapexit/mergerfs/issues). ### link-exdev If using path preservation and a `link` fails with EXDEV make a call to `symlink` where the `target` is the `oldlink` and the `linkpath` is the `newpath`. The `target` value is determined by the value of `link-exdev`. * passthrough: Return EXDEV as normal. * rel-symlink: A relative path from the `newpath`. * abs-base-symlink: A absolute value using the underlying branch. * abs-pool-symlink: A absolute value using the mergerfs mount point. NOTE: It is possible that some applications check the file they link. In those cases it is possible it will error or complain. ### rename-exdev If using path preservation and a `rename` fails with EXDEV: 1. Move file from **/branch/a/b/c** to **/branch/.mergerfs_rename_exdev/a/b/c**. 2. symlink the rename's `newpath` to the moved file. The `target` value is determined by the value of `rename-exdev`. * passthrough: Return EXDEV as normal. * rel-symlink: A relative path from the `newpath`. * abs-symlink: A absolute value using the mergerfs mount point. NOTE: It is possible that some applications check the file they rename. In those cases it is possible it will error or complain. NOTE: The reason `abs-symlink` is not split into two like `link-exdev` is due to the complexities in managing absolute base symlinks when multiple `oldpaths` exist. ### symlinkify Due to the levels of indirection introduced by mergerfs and the underlying technology FUSE there can be varying levels of performance degradation. This feature will turn non-directories which are not writable into symlinks to the original file found by the `readlink` policy after the mtime and ctime are older than the timeout. **WARNING:** The current implementation has a known issue in which if the file is open and being used when the file is converted to a symlink then the application which has that file open will receive an error when using it. This is unlikely to occur in practice but is something to keep in mind. **WARNING:** Some backup solutions, such as CrashPlan, do not backup the target of a symlink. If using this feature it will be necessary to point any backup software to the original drives or configure the software to follow symlinks if such an option is available. Alternatively create two mounts. One for backup and one for general consumption. ### nullrw Due to how FUSE works there is an overhead to all requests made to a FUSE filesystem that wouldn't exist for an in kernel one. Meaning that even a simple passthrough will have some slowdown. However, generally the overhead is minimal in comparison to the cost of the underlying I/O. By disabling the underlying I/O we can test the theoretical performance boundaries. By enabling `nullrw` mergerfs will work as it always does **except** that all reads and writes will be no-ops. A write will succeed (the size of the write will be returned as if it were successful) but mergerfs does nothing with the data it was given. Similarly a read will return the size requested but won't touch the buffer. See the BENCHMARKING section for suggestions on how to test. ### xattr Runtime extended attribute support can be managed via the `xattr` option. By default it will passthrough any xattr calls. Given xattr support is rarely used and can have significant performance implications mergerfs allows it to be disabled at runtime. The performance problems mostly comes when file caching is enabled. The kernel will send a `getxattr` for `security.capability` *before every single write*. It doesn't cache the responses to any `getxattr`. This might be addressed in the future but for now mergerfs can really only offer the following workarounds. `noattr` will cause mergerfs to short circuit all xattr calls and return ENOATTR where appropriate. mergerfs still gets all the requests but they will not be forwarded on to the underlying filesystems. The runtime control will still function in this mode. `nosys` will cause mergerfs to return ENOSYS for any xattr call. The difference with `noattr` is that the kernel will cache this fact and itself short circuit future calls. This is more efficient than `noattr` but will cause mergerfs' runtime control via the hidden file to stop working. ### nfsopenhack NFS is not fully POSIX compliant and historically certain behaviors, such as opening files with O_EXCL, are not or not well supported. When mergerfs (or any FUSE filesystem) is exported over NFS some of these issues come up due to how NFS and FUSE interact. This hack addresses the issue where the creation of a file with a read-only mode but with a read/write or write only flag. Normally this is perfectly valid but NFS chops the one open call into multiple calls. Exactly how it is translated depends on the configuration and versions of the NFS server and clients but it results in a permission error because a normal user is not allowed to open a read-only file as writable. Even though it's a more niche situation this hack breaks normal security and behavior and as such is `off` by default. If set to `git` it will only perform the hack when the path in question includes `/.git/`. `all` will result it it applying anytime a readonly file which is empty is opened for writing. # FUNCTIONS, CATEGORIES and POLICIES The POSIX filesystem API is made up of a number of functions. **creat**, **stat**, **chown**, etc. For ease of configuration in mergerfs most of the core functions are grouped into 3 categories: **action**, **create**, and **search**. These functions and categories can be assigned a policy which dictates which branch is chosen when performing that function. Some functions, listed in the category `N/A` below, can not be assigned the normal policies. These functions work with file handles, rather than file paths, which were created by `open` or `create`. That said many times the current FUSE kernel driver will not always provide the file handle when a client calls `fgetattr`, `fchown`, `fchmod`, `futimens`, `ftruncate`, etc. This means it will call the regular, path based, versions. `readdir` has no real need for a policy given the purpose is merely to return a list of entries in a directory. `statfs`'s behavior can be modified via other options. When using policies which are based on a branch's available space the base path provided is used. Not the full path to the file in question. Meaning that mounts in the branch won't be considered in the space calculations. The reason is that it doesn't really work for non-path preserving policies and can lead to non-obvious behaviors. NOTE: While any policy can be assigned to a function or category though some may not be very useful in practice. For instance: **rand** (random) may be useful for file creation (create) but could lead to very odd behavior if used for `chmod` if there were more than one copy of the file. ### Functions and their Category classifications | Category | FUSE Functions | |----------|-------------------------------------------------------------------------------------| | action | chmod, chown, link, removexattr, rename, rmdir, setxattr, truncate, unlink, utimens | | create | create, mkdir, mknod, symlink | | search | access, getattr, getxattr, ioctl (directories), listxattr, open, readlink | | N/A | fchmod, fchown, futimens, ftruncate, fallocate, fgetattr, fsync, ioctl (files), read, readdir, release, statfs, write, copy_file_range | In cases where something may be searched for (such as a path to clone) **getattr** will usually be used. ### Policies A policy is the algorithm used to choose a branch or branches for a function to work on. Think of them as ways to filter and sort branches. Any function in the `create` category will clone the relative path if needed. Some other functions (`rename`,`link`,`ioctl`) have special requirements or behaviors which you can read more about below. #### Filtering Policies basically search branches and create a list of files / paths for functions to work on. The policy is responsible for filtering and sorting the branches. Filters include **minfreespace**, whether or not a branch is mounted read-only, and the branch tagging (RO,NC,RW). These filters are applied across all policies unless otherwise noted. * No **search** function policies filter. * All **action** function policies filter out branches which are mounted **read-only** or tagged as **RO (read-only)**. * All **create** function policies filter out branches which are mounted **read-only**, tagged **RO (read-only)** or **NC (no create)**, or has available space less than `minfreespace`. Policies may have their own additional filtering such as those that require existing paths to be present. If all branches are filtered an error will be returned. Typically **EROFS** (read-only filesystem) or **ENOSPC** (no space left on device) depending on the most recent reason for filtering a branch. **ENOENT** will be returned if no elegible branch is found. #### Path Preservation Policies, as described below, are of two basic types. `path preserving` and `non-path preserving`. All policies which start with `ep` (**epff**, **eplfs**, **eplus**, **epmfs**, **eprand**) are `path preserving`. `ep` stands for `existing path`. A path preserving policy will only consider drives where the relative path being accessed already exists. When using non-path preserving policies paths will be cloned to target drives as necessary. With the `msp` or `most shared path` policies they are defined as `path preserving` for the purpose of controlling `link` and `rename`'s behaviors since `ignorepponrename` is available to disable that behavior. In mergerfs v3.0 the path preserving behavior of rename and link will likely be separated from the policy all together. #### Policy descriptions A policy's behavior differs, as mentioned above, based on the function it is used with. Sometimes it really might not make sense to even offer certain policies because they are literally the same as others but it makes things a bit more uniform. In mergerfs 3.0 this might change. | Policy | Description | |------------------|------------------------------------------------------------| | all | Search: Same as **epall**. Action: Same as **epall**. Create: for **mkdir**, **mknod**, and **symlink** it will apply to all branches. **create** works like **ff**. | | epall (existing path, all) | Search: Same as **epff** (but more expensive because it doesn't stop after finding a valid branch). Action: apply to all found. Create: for **mkdir**, **mknod**, and **symlink** it will apply to all found. **create** works like **epff** (but more expensive because it doesn't stop after finding a valid branch). | | epff (existing path, first found) | Given the order of the branches, as defined at mount time or configured at runtime, act on the first one found where the relative path exists. | | eplfs (existing path, least free space) | Of all the branches on which the relative path exists choose the drive with the least free space. | | eplus (existing path, least used space) | Of all the branches on which the relative path exists choose the drive with the least used space. | | epmfs (existing path, most free space) | Of all the branches on which the relative path exists choose the drive with the most free space. | | eppfrd (existing path, percentage free random distribution) | Like **pfrd** but limited to existing paths. | | eprand (existing path, random) | Calls **epall** and then randomizes. Returns 1. | | erofs | Exclusively return **-1** with **errno** set to **EROFS** (read-only filesystem). | | ff (first found) | Search: Same as **epff**. Action: Same as **epff**. Create: Given the order of the drives, as defined at mount time or configured at runtime, act on the first one found. | | lfs (least free space) | Search: Same as **eplfs**. Action: Same as **eplfs**. Create: Pick the drive with the least available free space. | | lus (least used space) | Search: Same as **eplus**. Action: Same as **eplus**. Create: Pick the drive with the least used space. | | mfs (most free space) | Search: Same as **epmfs**. Action: Same as **epmfs**. Create: Pick the drive with the most available free space. | | msplfs (most shared path, least free space) | Search: Same as **eplfs**. Action: Same as **eplfs**. Create: like **eplfs** but walk back the path if it fails to find a branch at that level. | | msplus (most shared path, least used space) | Search: Same as **eplus**. Action: Same as **eplus**. Create: like **eplus** but walk back the path if it fails to find a branch at that level. | | mspmfs (most shared path, most free space) | Search: Same as **epmfs**. Action: Same as **epmfs**. Create: like **epmfs** but walk back the path if it fails to find a branch at that level. | | msppfrd (most shared path, percentage free random distribution) | Search: Same as **eppfrd**. Action: Same as **eppfrd**. Create: Like **eppfrd** but will walk back the path if it fails to find a branch at that level. | | newest | Pick the file / directory with the largest mtime. | | pfrd (percentage free random distribution) | Search: Same as **eppfrd**. Action: Same as **eppfrd**. Create: Chooses a branch at random with the likelihood of selection based on a branch's available space relative to the total. | | rand (random) | Calls **all** and then randomizes. Returns 1. | **NOTE:** If you are using an underlying filesystem that reserves blocks such as ext2, ext3, or ext4 be aware that mergerfs respects the reservation by using `f_bavail` (number of free blocks for unprivileged users) rather than `f_bfree` (number of free blocks) in policy calculations. **df** does NOT use `f_bavail`, it uses `f_bfree`, so direct comparisons between **df** output and mergerfs' policies is not appropriate. #### Defaults #### | Category | Policy | |----------|--------| | action | epall | | create | epmfs | | search | ff | #### ioctl When `ioctl` is used with an open file then it will use the file handle which was created at the original `open` call. However, when using `ioctl` with a directory mergerfs will use the `open` policy to find the directory to act on. #### rename & link #### **NOTE:** If you're receiving errors from software when files are moved / renamed / linked then you should consider changing the create policy to one which is **not** path preserving, enabling `ignorepponrename`, or contacting the author of the offending software and requesting that `EXDEV` (cross device / improper link) be properly handled. `rename` and `link` are tricky functions in a union filesystem. `rename` only works within a single filesystem or device. If a rename can't be done atomically due to the source and destination paths existing on different mount points it will return **-1** with **errno = EXDEV** (cross device / improper link). So if a `rename`'s source and target are on different drives within the pool it creates an issue. Originally mergerfs would return EXDEV whenever a rename was requested which was cross directory in any way. This made the code simple and was technically compliant with POSIX requirements. However, many applications fail to handle EXDEV at all and treat it as a normal error or otherwise handle it poorly. Such apps include: gvfsd-fuse v1.20.3 and prior, Finder / CIFS/SMB client in Apple OSX 10.9+, NZBGet, Samba's recycling bin feature. As a result a compromise was made in order to get most software to work while still obeying mergerfs' policies. Below is the basic logic. * If using a **create** policy which tries to preserve directory paths (epff,eplfs,eplus,epmfs) * Using the **rename** policy get the list of files to rename * For each file attempt rename: * If failure with ENOENT (no such file or directory) run **create** policy * If create policy returns the same drive as currently evaluating then clone the path * Re-attempt rename * If **any** of the renames succeed the higher level rename is considered a success * If **no** renames succeed the first error encountered will be returned * On success: * Remove the target from all drives with no source file * Remove the source from all drives which failed to rename * If using a **create** policy which does **not** try to preserve directory paths * Using the **rename** policy get the list of files to rename * Using the **getattr** policy get the target path * For each file attempt rename: * If the source drive != target drive: * Clone target path from target drive to source drive * Rename * If **any** of the renames succeed the higher level rename is considered a success * If **no** renames succeed the first error encountered will be returned * On success: * Remove the target from all drives with no source file * Remove the source from all drives which failed to rename The the removals are subject to normal entitlement checks. The above behavior will help minimize the likelihood of EXDEV being returned but it will still be possible. **link** uses the same strategy but without the removals. #### readdir #### [readdir](http://linux.die.net/man/3/readdir) is different from all other filesystem functions. While it could have its own set of policies to tweak its behavior at this time it provides a simple union of files and directories found. Remember that any action or information queried about these files and directories come from the respective function. For instance: an **ls** is a **readdir** and for each file/directory returned **getattr** is called. Meaning the policy of **getattr** is responsible for choosing the file/directory which is the source of the metadata you see in an **ls**. #### statfs / statvfs #### [statvfs](http://linux.die.net/man/2/statvfs) normalizes the source drives based on the fragment size and sums the number of adjusted blocks and inodes. This means you will see the combined space of all sources. Total, used, and free. The sources however are dedupped based on the drive so multiple sources on the same drive will not result in double counting its space. Filesystems mounted further down the tree of the branch will not be included when checking the mount's stats. The options `statfs` and `statfs_ignore` can be used to modify `statfs` behavior. # ERROR HANDLING POSIX filesystem functions offer a single return code meaning that there is some complication regarding the handling of multiple branches as mergerfs does. It tries to handle errors in a way that would generally return meaningful values for that particular function. ### chmod, chown, removexattr, setxattr, truncate, utimens 1) if no error: return 0 (success) 2) if no successes: return first error 3) if one of the files acted on was the same as the related search function: return its value 4) return 0 (success) While doing this increases the complexity and cost of error handling, particularly step 3, this provides probably the most reasonable return value. ### unlink, rmdir 1) if no errors: return 0 (success) 2) return first error Older version of mergerfs would return success if any success occurred but for unlink and rmdir there are downstream assumptions that, while not impossible to occur, can confuse some software. ### others For search functions there is always a single thing acted on and as such whatever return value that comes from the single function call is returned. For create functions `mkdir`, `mknod`, and `symlink` which don't return a file descriptor and therefore can have `all` or `epall` policies it will return success if any of the calls succeed and an error otherwise. # BUILD / UPDATE **NOTE:** Prebuilt packages can be found at and recommended for most users: https://github.com/trapexit/mergerfs/releases **NOTE:** Only tagged releases are supported. `master` and other branches should be considered works in progress. First get the code from [github](https://github.com/trapexit/mergerfs). ``` $ git clone https://github.com/trapexit/mergerfs.git $ # or $ wget https://github.com/trapexit/mergerfs/releases/download//mergerfs-.tar.gz ``` #### Debian / Ubuntu ``` $ cd mergerfs $ sudo tools/install-build-pkgs $ make deb $ sudo dpkg -i ../mergerfs__.deb ``` #### RHEL / CentOS /Fedora ``` $ su - # cd mergerfs # tools/install-build-pkgs # make rpm # rpm -i rpmbuild/RPMS//mergerfs-..rpm ``` #### Generically Have git, g++, make, python installed. ``` $ cd mergerfs $ make $ sudo make install ``` #### Build options ``` $ make help usage: make make USE_XATTR=0 - build program without xattrs functionality make STATIC=1 - build static binary make LTO=1 - build with link time optimization ``` # UPGRADE mergerfs can be upgraded live by mounting on top of the previous instance. Simply install the new version of mergerfs and follow the instructions below. Add `nonempty` to your mergerfs option list and call mergerfs again or if using `/etc/fstab` call for it to mount again. Existing open files and such will continue to work fine though they won't see runtime changes since any such change would be the new mount. If you plan on changing settings with the new mount you should / could apply those before mounting the new version. ``` $ sudo mount /mnt/mergerfs $ mount | grep mergerfs media on /mnt/mergerfs type fuse.mergerfs (rw,relatime,user_id=0,group_id=0,default_permissions,allow_other) media on /mnt/mergerfs type fuse.mergerfs (rw,relatime,user_id=0,group_id=0,default_permissions,allow_other) ``` A problem with this approach is that the underlying instance will continue to run even if the software using it stop or are restarted. To work around this you can use a "lazy umount". Before mounting over top the mount point with the new instance of mergerfs issue: `umount -l `. # RUNTIME CONFIG #### .mergerfs pseudo file #### ``` /.mergerfs ``` There is a pseudo file available at the mount point which allows for the runtime modification of certain **mergerfs** options. The file will not show up in **readdir** but can be **stat**'ed and manipulated via [{list,get,set}xattrs](http://linux.die.net/man/2/listxattr) calls. Any changes made at runtime are **not** persisted. If you wish for values to persist they must be included as options wherever you configure the mounting of mergerfs (/etc/fstab). ##### Keys ##### Use `getfattr -d /mountpoint/.mergerfs` or `xattr -l /mountpoint/.mergerfs` to see all supported keys. Some are informational and therefore read-only. `setxattr` will return EINVAL (invalid argument) on read-only keys. ##### Values ##### Same as the command line. ###### user.mergerfs.branches ###### Used to query or modify the list of branches. When modifying there are several shortcuts to easy manipulation of the list. | Value | Description | |--------------|-------------| | [list] | set | | +<[list] | prepend | | +>[list] | append | | -[list] | remove all values provided | | -< | remove first in list | | -> | remove last in list | `xattr -w user.mergerfs.branches + userspace round trips there are kernel side caches for file entries and attributes. The entry cache limits the `lookup` calls to mergerfs which ask if a file exists. The attribute cache limits the need to make `getattr` calls to mergerfs which provide file attributes (mode, size, type, etc.). As with the page cache these should not be used if the underlying filesystems are being manipulated at the same time as it could lead to odd behavior or data corruption. The options for setting these are `cache.entry` and `cache.negative_entry` for the entry cache and `cache.attr` for the attributes cache. `cache.negative_entry` refers to the timeout for negative responses to lookups (non-existent files). #### writeback caching When `cache.files` is enabled the default is for it to perform writethrough caching. This behavior won't help improve performance as each write still goes one for one through the filesystem. By enabling the FUSE writeback cache small writes may be aggregated by the kernel and then sent to mergerfs as one larger request. This can greatly improve the throughput for apps which write to files inefficiently. The amount the kernel can aggregate is limited by the size of a FUSE message. Read the `fuse_msg_size` section for more details. There is a small side effect as a result of enabling writeback caching. Underlying files won't ever be opened with O_APPEND or O_WRONLY. The former because the kernel then manages append mode and the latter because the kernel may request file data from mergerfs to populate the write cache. The O_APPEND change means that if a file is changed outside of mergerfs it could lead to corruption as the kernel won't know the end of the file has changed. That said any time you use caching you should keep from using the same file outside of mergerfs at the same time. Note that if an application is properly sizing writes then writeback caching will have little or no effect. It will only help with writes of sizes below the FUSE message size (128K on older kernels, 1M on newer). #### policy caching Policies are run every time a function (with a policy as mentioned above) is called. These policies can be expensive depending on mergerfs' setup and client usage patterns. Generally we wouldn't want to cache policy results because it may result in stale responses if the underlying drives are used directly. The `open` policy cache will cache the result of an `open` policy for a particular input for `cache.open` seconds or until the file is unlinked. Each file close (release) will randomly chose to clean up the cache of expired entries. This cache is really only useful in cases where you have a large number of branches and `open` is called on the same files repeatedly (like **Transmission** which opens and closes a file on every read/write presumably to keep file handle usage low). #### statfs caching Of the syscalls used by mergerfs in policies the `statfs` / `statvfs` call is perhaps the most expensive. It's used to find out the available space of a drive and whether it is mounted read-only. Depending on the setup and usage pattern these queries can be relatively costly. When `cache.statfs` is enabled all calls to `statfs` by a policy will be cached for the number of seconds its set to. Example: If the create policy is `mfs` and the timeout is 60 then for that 60 seconds the same drive will be returned as the target for creates because the available space won't be updated for that time. #### symlink caching As of version 4.20 Linux supports symlink caching. Significant performance increases can be had in workloads which use a lot of symlinks. Setting `cache.symlinks=true` will result in requesting symlink caching from the kernel only if supported. As a result its safe to enable it on systems prior to 4.20. That said it is disabled by default for now. You can see if caching is enabled by querying the xattr `user.mergerfs.cache.symlinks` but given it must be requested at startup you can not change it at runtime. #### readdir caching As of version 4.20 Linux supports readdir caching. This can have a significant impact on directory traversal. Especially when combined with entry (`cache.entry`) and attribute (`cache.attr`) caching. Setting `cache.readdir=true` will result in requesting readdir caching from the kernel on each `opendir`. If the kernel doesn't support readdir caching setting the option to `true` has no effect. This option is configurable at runtime via xattr `user.mergerfs.cache.readdir`. #### tiered caching Some storage technologies support what some call "tiered" caching. The placing of usually smaller, faster storage as a transparent cache to larger, slower storage. NVMe, SSD, Optane in front of traditional HDDs for instance. MergerFS does not natively support any sort of tiered caching. Most users have no use for such a feature and its inclusion would complicate the code. However, there are a few situations where a cache drive could help with a typical mergerfs setup. 1. Fast network, slow drives, many readers: You've a 10+Gbps network with many readers and your regular drives can't keep up. 2. Fast network, slow drives, small'ish bursty writes: You have a 10+Gbps network and wish to transfer amounts of data less than your cache drive but wish to do so quickly. With #1 its arguable if you should be using mergerfs at all. RAID would probably be the better solution. If you're going to use mergerfs there are other tactics that may help: spreading the data across drives (see the mergerfs.dup tool) and setting `func.open=rand`, using `symlinkify`, or using dm-cache or a similar technology to add tiered cache to the underlying device. With #2 one could use dm-cache as well but there is another solution which requires only mergerfs and a cronjob. 1. Create 2 mergerfs pools. One which includes just the slow drives and one which has both the fast drives (SSD,NVME,etc.) and slow drives. 2. The 'cache' pool should have the cache drives listed first. 3. The best `create` policies to use for the 'cache' pool would probably be `ff`, `epff`, `lfs`, or `eplfs`. The latter two under the assumption that the cache drive(s) are far smaller than the backing drives. If using path preserving policies remember that you'll need to manually create the core directories of those paths you wish to be cached. Be sure the permissions are in sync. Use `mergerfs.fsck` to check / correct them. You could also tag the slow drives as `=NC` though that'd mean if the cache drives fill you'd get "out of space" errors. 4. Enable `moveonenospc` and set `minfreespace` appropriately. To make sure there is enough room on the "slow" pool you might want to set `minfreespace` to at least as large as the size of the largest cache drive if not larger. This way in the worst case the whole of the cache drive(s) can be moved to the other drives. 5. Set your programs to use the cache pool. 6. Save one of the below scripts or create you're own. 7. Use `cron` (as root) to schedule the command at whatever frequency is appropriate for your workflow. ##### time based expiring Move files from cache to backing pool based only on the last time the file was accessed. Replace `-atime` with `-amin` if you want minutes rather than days. May want to use the `fadvise` / `--drop-cache` version of rsync or run rsync with the tool "nocache". *NOTE:* The arguments to these scripts include the cache **drive**. Not the pool with the cache drive. You could have data loss if the source is the cache pool. ``` #!/bin/bash if [ $# != 3 ]; then echo "usage: $0 " exit 1 fi CACHE="${1}" BACKING="${2}" N=${3} find "${CACHE}" -type f -atime +${N} -printf '%P\n' | \ rsync --files-from=- -axqHAXWES --preallocate --remove-source-files "${CACHE}/" "${BACKING}/" ``` ##### percentage full expiring Move the oldest file from the cache to the backing pool. Continue till below percentage threshold. *NOTE:* The arguments to these scripts include the cache **drive**. Not the pool with the cache drive. You could have data loss if the source is the cache pool. ``` #!/bin/bash if [ $# != 3 ]; then echo "usage: $0 " exit 1 fi CACHE="${1}" BACKING="${2}" PERCENTAGE=${3} set -o errexit while [ $(df --output=pcent "${CACHE}" | grep -v Use | cut -d'%' -f1) -gt ${PERCENTAGE} ] do FILE=$(find "${CACHE}" -type f -printf '%A@ %P\n' | \ sort | \ head -n 1 | \ cut -d' ' -f2-) test -n "${FILE}" rsync -axqHAXWESR --preallocate --remove-source-files "${CACHE}/./${FILE}" "${BACKING}/" done ``` # PERFORMANCE mergerfs is at its core just a proxy and therefore its theoretical max performance is that of the underlying devices. However, given it is a FUSE filesystem working from userspace there is an increase in overhead relative to kernel based solutions. That said the performance can match the theoretical max but it depends greatly on the system's configuration. Especially when adding network filesystems into the mix there are many variables which can impact performance. Drive speeds and latency, network speeds and latency, general concurrency, read/write sizes, etc. Unfortunately, given the number of variables it has been difficult to find a single set of settings which provide optimal performance. If you're having performance issues please look over the suggestions below (including the benchmarking section.) NOTE: be sure to read about these features before changing them to understand what behaviors it may impact * enable (or disable) `splice_move`, `splice_read`, and `splice_write` * disable `security_capability` and/or `xattr` * increase cache timeouts `cache.attr`, `cache.entry`, `cache.negative_entry` * enable (or disable) page caching (`cache.files`) * enable `cache.writeback` * enable `cache.open` * enable `cache.statfs` * enable `cache.symlinks` * enable `cache.readdir` * change the number of worker threads * disable `posix_acl` * disable `async_read` * test theoretical performance using `nullrw` or mounting a ram disk * use `symlinkify` if your data is largely static and read-only * use tiered cache drives * use LVM and LVM cache to place a SSD in front of your HDDs * if `cache.files` is enabled increase readahead: `echo "1024" > /sys/class/bdi/0:$(stat -c%d /MOUNT)/read_ahead_kb` * increase readahead on all devices: `ls -1 /sys/class/bdi/*/read_ahead_kb | xargs -n1 -I{} sh -c "echo 1024 > {}"` If you come across a setting that significantly impacts performance please contact trapexit so he may investigate further. # BENCHMARKING Filesystems are complicated. They do many things and many of those are interconnected. Additionally, the OS, drivers, hardware, etc. all can impact performance. Therefore, when benchmarking, it is **necessary** that the test focus as narrowly as possible. For most throughput is the key benchmark. To test throughput `dd` is useful but **must** be used with the correct settings in order to ensure the filesystem or device is actually being tested. The OS can and will cache data. Without forcing synchronous reads and writes and/or disabling caching the values returned will not be representative of the device's true performance. When benchmarking through mergerfs ensure you only use 1 branch to remove any possibility of the policies complicating the situation. Benchmark the underlying filesystem first and then mount mergerfs over it and test again. If you're experience speeds below your expectation you will need to narrow down precisely which component is leading to the slowdown. Preferably test the following in the order listed (but not combined). 1. Enable `nullrw` mode with `nullrw=true`. This will effectively make reads and writes no-ops. Removing the underlying device / filesystem from the equation. This will give us the top theoretical speeds. 2. Mount mergerfs over `tmpfs`. `tmpfs` is a RAM disk. Extremely high speed and very low latency. This is a more realistic best case scenario. Example: `mount -t tmpfs -o size=2G tmpfs /tmp/tmpfs` 3. Mount mergerfs over a local drive. NVMe, SSD, HDD, etc. If you have more than one I'd suggest testing each of them as drives and/or controllers (their drivers) could impact performance. 4. Finally, if you intend to use mergerfs with a network filesystem, either as the source of data or to combine with another through mergerfs, test each of those alone as above. Once you find the component which has the performance issue you can do further testing with different options to see if they impact performance. For reads and writes the most relevant would be: `cache.files`, `async_read`, `splice_move`, `splice_read`, `splice_write`. Less likely but relevant when using NFS or with certain filesystems would be `security_capability`, `xattr`, and `posix_acl`. If you find a specific system, drive, filesystem, controller, etc. that performs poorly contact trapexit so he may investigate further. Sometimes the problem is really the application accessing or writing data through mergerfs. Some software use small buffer sizes which can lead to more requests and therefore greater overhead. You can test this out yourself by replace `bs=1M` in the examples below with `ibs` or `obs` and using a size of `512` instead of `1M`. In one example test using `nullrw` the write speed dropped from 4.9GB/s to 69.7MB/s when moving from `1M` to `512`. Similar results were had when testing reads. Small writes overhead may be improved by leveraging a write cache but in casual tests little gain was found. More tests will need to be done before this feature would become available. If you have an app that appears slow with mergerfs it could be due to this. Contact trapexit so he may investigate further. ### write benchmark ``` $ dd if=/dev/zero of=/mnt/mergerfs/1GB.file bs=1M count=1024 oflag=nocache conv=fdatasync status=progress ``` ### read benchmark ``` $ dd if=/mnt/mergerfs/1GB.file of=/dev/null bs=1M count=1024 iflag=nocache conv=fdatasync status=progress ``` ### other benchmarks If you are attempting to benchmark other behaviors you must ensure you clear kernel caches before runs. In fact it would be a good deal to run before the read and write benchmarks as well just in case. ``` sync echo 3 | sudo tee /proc/sys/vm/drop_caches ``` # TIPS / NOTES * This document is very literal and thorough. Unless there is a bug things work as described. If a suspected feature isn't mentioned it doesn't exist. If certain libfuse arguments aren't listed they probably shouldn't be used. * Ensure you're using the latest version. Few distros have the latest version. * **use_ino** will only work when used with mergerfs 2.18.0 and above. * Run mergerfs as `root` (with **allow_other**) unless you're merging paths which are owned exclusively and fully by the same user otherwise strange permission issues may arise. mergerfs is designed and intended to be run as `root`. * If you don't see some directories and files you expect, policies seem to skip branches, you get strange permission errors, etc. be sure the underlying filesystems' permissions are all the same. Use `mergerfs.fsck` to audit the drive for out of sync permissions. * If you still have permission issues be sure you are using POSIX ACL compliant filesystems. mergerfs doesn't generally make exceptions for FAT, NTFS, or other non-POSIX filesystem. * Do **not** use `cache.files=off` if you expect applications (such as rtorrent) to use [mmap](http://linux.die.net/man/2/mmap) files. Shared mmap is not currently supported in FUSE w/ page caching disabled. Enabling `dropcacheonclose` is recommended when `cache.files=partial|full|auto-full`. * [Kodi](http://kodi.tv), [Plex](http://plex.tv), [Subsonic](http://subsonic.org), etc. can use directory [mtime](http://linux.die.net/man/2/stat) to more efficiently determine whether to scan for new content rather than simply performing a full scan. If using the default **getattr** policy of **ff** it's possible those programs will miss an update on account of it returning the first directory found's **stat** info and its a later directory on another mount which had the **mtime** recently updated. To fix this you will want to set **func.getattr=newest**. Remember though that this is just **stat**. If the file is later **open**'ed or **unlink**'ed and the policy is different for those then a completely different file or directory could be acted on. * Some policies mixed with some functions may result in strange behaviors. Not that some of these behaviors and race conditions couldn't happen outside **mergerfs** but that they are far more likely to occur on account of the attempt to merge together multiple sources of data which could be out of sync due to the different policies. * For consistency its generally best to set **category** wide policies rather than individual **func**'s. This will help limit the confusion of tools such as [rsync](http://linux.die.net/man/1/rsync). However, the flexibility is there if needed. # KNOWN ISSUES / BUGS #### kernel issues & bugs [https://github.com/trapexit/mergerfs/wiki/Kernel-Issues-&-Bugs](https://github.com/trapexit/mergerfs/wiki/Kernel-Issues-&-Bugs) #### directory mtime is not being updated Remember that the default policy for `getattr` is `ff`. The information for the first directory found will be returned. If it wasn't the directory which had been updated then it will appear outdated. The reason this is the default is because any other policy would be more expensive and for many applications it is unnecessary. To always return the directory with the most recent mtime or a faked value based on all found would require a scan of all drives. If you always want the directory information from the one with the most recent mtime then use the `newest` policy for `getattr`. #### 'mv /mnt/pool/foo /mnt/disk1/foo' removes 'foo' This is not a bug. Run in verbose mode to better understand what's happening: ``` $ mv -v /mnt/pool/foo /mnt/disk1/foo copied '/mnt/pool/foo' -> '/mnt/disk1/foo' removed '/mnt/pool/foo' $ ls /mnt/pool/foo ls: cannot access '/mnt/pool/foo': No such file or directory ``` `mv`, when working across devices, is copying the source to target and then removing the source. Since the source **is** the target in this case, depending on the unlink policy, it will remove the just copied file and other files across the branches. If you want to move files to one drive just copy them there and use mergerfs.dedup to clean up the old paths or manually remove them from the branches directly. #### cached memory appears greater than it should be Use `cache.files=off` and/or `dropcacheonclose=true`. See the section on page caching. #### NFS clients returning ESTALE / Stale file handle NFS does not like out of band changes. That is especially true of inode values. Be sure to use the following options: * noforget * use_ino * inodecalc=path-hash #### rtorrent fails with ENODEV (No such device) Be sure to set `cache.files=partial|full|auto-full` or turn off `direct_io`. rtorrent and some other applications use [mmap](http://linux.die.net/man/2/mmap) to read and write to files and offer no fallback to traditional methods. FUSE does not currently support mmap while using `direct_io`. There may be a performance penalty on writes with `direct_io` off as well as the problem of double caching but it's the only way to get such applications to work. If the performance loss is too high for other apps you can mount mergerfs twice. Once with `direct_io` enabled and one without it. Be sure to set `dropcacheonclose=true` if not using `direct_io`. #### Plex doesn't work with mergerfs It does. If you're trying to put Plex's config / metadata / database on mergerfs you can't set `cache.files=off` because Plex is using sqlite3 with mmap enabled. Shared mmap is not supported by Linux's FUSE implementation when page caching is disabled. To fix this place the data elsewhere (preferable) or enable `cache.files` (with `dropcacheonclose=true`). Sqlite3 does not need mmap but the developer needs to fall back to standard IO if mmap fails. If the issue is that scanning doesn't seem to pick up media then be sure to set `func.getattr=newest` though generally a full scan will pick up all media anyway. #### When a program tries to move or rename a file it fails Please read the section above regarding [rename & link](#rename--link). The problem is that many applications do not properly handle `EXDEV` errors which `rename` and `link` may return even though they are perfectly valid situations which do not indicate actual drive or OS errors. The error will only be returned by mergerfs if using a path preserving policy as described in the policy section above. If you do not care about path preservation simply change the mergerfs policy to the non-path preserving version. For example: `-o category.create=mfs` Ideally the offending software would be fixed and it is recommended that if you run into this problem you contact the software's author and request proper handling of `EXDEV` errors. #### my 32bit software has problems Some software have problems with 64bit inode values. The symptoms can include EOVERFLOW errors when trying to list files. You can address this by setting `inodecalc` to one of the 32bit based algos as described in the relevant section. #### Samba: Moving files / directories fails Workaround: Copy the file/directory and then remove the original rather than move. This isn't an issue with Samba but some SMB clients. GVFS-fuse v1.20.3 and prior (found in Ubuntu 14.04 among others) failed to handle certain error codes correctly. Particularly **STATUS_NOT_SAME_DEVICE** which comes from the **EXDEV** which is returned by **rename** when the call is crossing mount points. When a program gets an **EXDEV** it needs to explicitly take an alternate action to accomplish its goal. In the case of **mv** or similar it tries **rename** and on **EXDEV** falls back to a manual copying of data between the two locations and unlinking the source. In these older versions of GVFS-fuse if it received **EXDEV** it would translate that into **EIO**. This would cause **mv** or most any application attempting to move files around on that SMB share to fail with a IO error. [GVFS-fuse v1.22.0](https://bugzilla.gnome.org/show_bug.cgi?id=734568) and above fixed this issue but a large number of systems use the older release. On Ubuntu the version can be checked by issuing `apt-cache showpkg gvfs-fuse`. Most distros released in 2015 seem to have the updated release and will work fine but older systems may not. Upgrading gvfs-fuse or the distro in general will address the problem. In Apple's MacOSX 10.9 they replaced Samba (client and server) with their own product. It appears their new client does not handle **EXDEV** either and responds similar to older release of gvfs on Linux. #### Trashing files occasionally fails This is the same issue as with Samba. `rename` returns `EXDEV` (in our case that will really only happen with path preserving policies like `epmfs`) and the software doesn't handle the situation well. This is unfortunately a common failure of software which moves files around. The standard indicates that an implementation `MAY` choose to support non-user home directory trashing of files (which is a `MUST`). The implementation `MAY` also support "top directory trashes" which many probably do. To create a `$topdir/.Trash` directory as defined in the standard use the [mergerfs-tools](https://github.com/trapexit/mergerfs-tools) tool `mergerfs.mktrash`. #### tar: Directory renamed before its status could be extracted Make sure to use the `use_ino` option. #### Supplemental user groups Due to the overhead of [getgroups/setgroups](http://linux.die.net/man/2/setgroups) mergerfs utilizes a cache. This cache is opportunistic and per thread. Each thread will query the supplemental groups for a user when that particular thread needs to change credentials and will keep that data for the lifetime of the thread. This means that if a user is added to a group it may not be picked up without the restart of mergerfs. However, since the high level FUSE API's (at least the standard version) thread pool dynamically grows and shrinks it's possible that over time a thread will be killed and later a new thread with no cache will start and query the new data. The gid cache uses fixed storage to simplify the design and be compatible with older systems which may not have C++11 compilers. There is enough storage for 256 users' supplemental groups. Each user is allowed up to 32 supplemental groups. Linux >= 2.6.3 allows up to 65535 groups per user but most other *nixs allow far less. NFS allowing only 16. The system does handle overflow gracefully. If the user has more than 32 supplemental groups only the first 32 will be used. If more than 256 users are using the system when an uncached user is found it will evict an existing user's cache at random. So long as there aren't more than 256 active users this should be fine. If either value is too low for your needs you will have to modify `gidcache.hpp` to increase the values. Note that doing so will increase the memory needed by each thread. While not a bug some users have found when using containers that supplemental groups defined inside the container don't work properly with regard to permissions. This is expected as mergerfs lives outside the container and therefore is querying the host's group database. There might be a hack to work around this (make mergerfs read the /etc/group file in the container) but it is not yet implemented and would be limited to Linux and the /etc/group DB. Preferably users would mount in the host group file into the containers or use a standard shared user & groups technology like NIS or LDAP. #### mergerfs or libfuse crashing First... always upgrade to the latest version unless told otherwise. If using mergerfs below 2.22.0: If suddenly the mergerfs mount point disappears and `Transport endpoint is not connected` is returned when attempting to perform actions within the mount directory **and** the version of libfuse (use `mergerfs -v` to find the version) is older than `2.9.4` its likely due to a bug in libfuse. Affected versions of libfuse can be found in Debian Wheezy, Ubuntu Precise and others. In order to fix this please install newer versions of libfuse. If using a Debian based distro (Debian,Ubuntu,Mint) you can likely just install newer versions of [libfuse](https://packages.debian.org/unstable/libfuse2) and [fuse](https://packages.debian.org/unstable/fuse) from the repo of a newer release. If using mergerfs at or above 2.22.0: First upgrade if possible, check the known bugs section, and contact trapexit. #### mergerfs appears to be crashing or exiting There seems to be an issue with Linux version `4.9.0` and above in which an invalid message appears to be transmitted to libfuse (used by mergerfs) causing it to exit. No messages will be printed in any logs as it's not a proper crash. Debugging of the issue is still ongoing and can be followed via the [fuse-devel thread](https://sourceforge.net/p/fuse/mailman/message/35662577). #### rm: fts_read failed: No such file or directory Please update. This is only happened to mergerfs versions at or below v2.25.x and will not occur in more recent versions. # FAQ #### How well does mergerfs scale? Is it "production ready?" Users have reported running mergerfs on everything from a Raspberry Pi to dual socket Xeon systems with >20 cores. I'm aware of at least a few companies which use mergerfs in production. [Open Media Vault](https://www.openmediavault.org) includes mergerfs as its sole solution for pooling drives. The author of mergerfs had it running for over 300 days managing 16+ drives with reasonably heavy 24/7 read and write usage. Stopping only after the machine's power supply died. Most serious issues (crashes or data corruption) have been due to [kernel bugs](https://github.com/trapexit/mergerfs/wiki/Kernel-Issues-&-Bugs). All of which are fixed in stable releases. #### Can mergerfs be used with drives which already have data / are in use? Yes. MergerFS is a proxy and does **NOT** interfere with the normal form or function of the drives / mounts / paths it manages. MergerFS is **not** a traditional filesystem. MergerFS is **not** RAID. It does **not** manipulate the data that passes through it. It does **not** shard data across drives. It merely shards some **behavior** and aggregates others. #### Can mergerfs be removed without affecting the data? See the previous question's answer. #### What policies should I use? Unless you're doing something more niche the average user is probably best off using `mfs` for `category.create`. It will spread files out across your branches based on available space. Use `mspmfs` if you want to try to colocate the data a bit more. You may want to use `lus` if you prefer a slightly different distribution of data if you have a mix of smaller and larger drives. Generally though `mfs`, `lus`, or even `rand` are good for the general use case. If you are starting with an imbalanced pool you can use the tool **mergerfs.balance** to redistribute files across the pool. If you really wish to try to colocate files based on directory you can set `func.create` to `epmfs` or similar and `func.mkdir` to `rand` or `eprand` depending on if you just want to colocate generally or on specific branches. Either way the *need* to colocate is rare. For instance: if you wish to remove the drive regularly and want the data to predictably be on that drive or if you don't use backup at all and don't wish to replace that data piecemeal. In which case using path preservation can help but will require some manual attention. Colocating after the fact can be accomplished using the **mergerfs.consolidate** tool. If you don't need strict colocation which the `ep` policies provide then you can use the `msp` based policies which will walk back the path till finding a branch that works. Ultimately there is no correct answer. It is a preference or based on some particular need. mergerfs is very easy to test and experiment with. I suggest creating a test setup and experimenting to get a sense of what you want. The reason `mfs` is not the default `category.create` policy is historical. When/if a 3.X gets released it will be changed to minimize confusion people often have with path preserving policies. #### What settings should I use? Depends on what features you want. Generally speaking there are no "wrong" settings. All settings are performance or feature related. The best bet is to read over the available options and choose what fits your situation. If something isn't clear from the documentation please reach out and the documentation will be improved. That said, for the average person, the following should be fine: `-o use_ino,cache.files=off,dropcacheonclose=true,allow_other,category.create=mfs` #### Why are all my files ending up on 1 drive?! Did you start with empty drives? Did you explicitly configure a `category.create` policy? Are you using an `existing path` / `path preserving` policy? The default create policy is `epmfs`. That is a path preserving algorithm. With such a policy for `mkdir` and `create` with a set of empty drives it will select only 1 drive when the first directory is created. Anything, files or directories, created in that first directory will be placed on the same branch because it is preserving paths. This catches a lot of new users off guard but changing the default would break the setup for many existing users. If you do not care about path preservation and wish your files to be spread across all your drives change to `mfs` or similar policy as described above. If you do want path preservation you'll need to perform the manual act of creating paths on the drives you want the data to land on before transferring your data. Setting `func.mkdir=epall` can simplify managing path preservation for `create`. Or use `func.mkdir=rand` if you're interested in just grouping together directory content by drive. #### Do hardlinks work? Yes. You need to use `use_ino` to support proper reporting of inodes but they work regardless. See also the option `inodecalc`. What mergerfs does not do is fake hard links across branches. Read the section "rename & link" for how it works. Remember that hardlinks will NOT work across devices. That includes between the original filesystem and a mergerfs pool, between two separate pools of the same underlying filesystems, or bind mounts of paths within the mergerfs pool. The latter is common when using Docker or Podman. Multiple volumes (bind mounts) to the same underlying filesystem are considered different devices. There is no way to link between them. You should mount in the highest directory in the mergerfs pool that includes all the paths you need if you want links to work. #### Can I use mergerfs without SnapRAID? SnapRAID without mergerfs? Yes. They are completely unreleated pieces of software. #### Can mergerfs run via Docker, Podman, Kubernetes, etc. Yes. With Docker you'll need to include `--cap-add=SYS_ADMIN --device=/dev/fuse --security-opt=apparmor:unconfined` or similar with other container runtimes. You should also be running it as root or given sufficient caps to change user and group identity as well as have root like filesystem permissions. Keep in mind that you **MUST** consider identity when using containers. For example: supplemental groups will be picked up from the container unless you properly manage users and groups by sharing relevant /etc files or by using some other means to share identity across containers. Similarly if you use "rootless" containers and user namespaces to do uid/gid translations you **MUST** consider that while managing shared files. Also, as mentioned by [hotio](https://hotio.dev/containers/mergerfs), with Docker you should probably be mounting with `bind-propagation` set to `slave`. #### Does mergerfs support CoW / copy-on-write / writes to read-only filesystems? Not in the sense of a filesystem like BTRFS or ZFS nor in the overlayfs or aufs sense. It does offer a [cow-shell](http://manpages.ubuntu.com/manpages/bionic/man1/cow-shell.1.html) like hard link breaking (copy to temp file then rename over original) which can be useful when wanting to save space by hardlinking duplicate files but wish to treat each name as if it were a unique and separate file. If you want to write to a read-only filesystem you should look at overlayfs. You can always include the overlayfs mount into a mergerfs pool. #### Why can't I see my files / directories? It's almost always a permissions issue. Unlike mhddfs and unionfs-fuse, which runs as root and attempts to access content as such, mergerfs always changes its credentials to that of the caller. This means that if the user does not have access to a file or directory than neither will mergerfs. However, because mergerfs is creating a union of paths it may be able to read some files and directories on one drive but not another resulting in an incomplete set. Whenever you run into a split permission issue (seeing some but not all files) try using [mergerfs.fsck](https://github.com/trapexit/mergerfs-tools) tool to check for and fix the mismatch. If you aren't seeing anything at all be sure that the basic permissions are correct. The user and group values are correct and that directories have their executable bit set. A common mistake by users new to Linux is to `chmod -R 644` when they should have `chmod -R u=rwX,go=rX`. If using a network filesystem such as NFS, SMB, CIFS (Samba) be sure to pay close attention to anything regarding permissioning and users. Root squashing and user translation for instance has bitten a few mergerfs users. Some of these also affect the use of mergerfs from container platforms such as Docker. #### Is my OS's libfuse needed for mergerfs to work? No. Normally `mount.fuse` is needed to get mergerfs (or any FUSE filesystem to mount using the `mount` command but in vendoring the libfuse library the `mount.fuse` app has been renamed to `mount.mergerfs` meaning the filesystem type in `fstab` can simply be `mergerfs`. That said there should be no harm in having it installed and continuing to using `fuse.mergerfs` as the type in `/etc/fstab`. If `mergerfs` doesn't work as a type it could be due to how the `mount.mergerfs` tool was installed. Must be in `/sbin/` with proper permissions. #### Why was libfuse embedded into mergerfs? 1. A significant number of users use mergerfs on distros with old versions of libfuse which have serious bugs. Requiring updated versions of libfuse on those distros isn't practical (no package offered, user inexperience, etc.). The only practical way to provide a stable runtime on those systems was to "vendor" / embed the library into the project. 2. mergerfs was written to use the high level API. There are a number of limitations in the HLAPI that make certain features difficult or impossible to implement. While some of these features could be patched into newer versions of libfuse without breaking the public API some of them would require hacky code to provide backwards compatibility. While it may still be worth working with upstream to address these issues in future versions, since the library needs to be vendored for stability and compatibility reasons it is preferable / easier to modify the API. Longer term the plan is to rewrite mergerfs to use the low level API. #### Why did support for system libfuse get removed? See above first. If/when mergerfs is rewritten to use the low-level API then it'll be plausible to support system libfuse but till then it's simply too much work to manage the differences across the versions. #### Why use mergerfs over mhddfs? mhddfs is no longer maintained and has some known stability and security issues (see below). MergerFS provides a superset of mhddfs' features and should offer the same or maybe better performance. Below is an example of mhddfs and mergerfs setup to work similarly. `mhddfs -o mlimit=4G,allow_other /mnt/drive1,/mnt/drive2 /mnt/pool` `mergerfs -o minfreespace=4G,allow_other,category.create=ff /mnt/drive1:/mnt/drive2 /mnt/pool` #### Why use mergerfs over aufs? aufs is mostly abandoned and no longer available in many distros. While aufs can offer better peak performance mergerfs provides more configurability and is generally easier to use. mergerfs however does not offer the overlay / copy-on-write (CoW) features which aufs and overlayfs have. #### Why use mergerfs over unionfs? UnionFS is more like aufs than mergerfs in that it offers overlay / CoW features. If you're just looking to create a union of drives and want flexibility in file/directory placement then mergerfs offers that whereas unionfs is more for overlaying RW filesystems over RO ones. #### Why use mergerfs over overlayfs? Same reasons as with unionfs. #### Why use mergerfs over LVM/ZFS/BTRFS/RAID0 drive concatenation / striping? With simple JBOD / drive concatenation / stripping / RAID0 a single drive failure will result in full pool failure. mergerfs performs a similar function without the possibility of catastrophic failure and the difficulties in recovery. Drives may fail, however, all other data will continue to be accessible. When combined with something like [SnapRaid](http://www.snapraid.it) and/or an offsite backup solution you can have the flexibility of JBOD without the single point of failure. #### Why use mergerfs over ZFS? MergerFS is not intended to be a replacement for ZFS. MergerFS is intended to provide flexible pooling of arbitrary drives (local or remote), of arbitrary sizes, and arbitrary filesystems. For `write once, read many` usecases such as bulk media storage. Where data integrity and backup is managed in other ways. In that situation ZFS can introduce a number of costs and limitations as described [here](http://louwrentius.com/the-hidden-cost-of-using-zfs-for-your-home-nas.html), [here](https://markmcb.com/2020/01/07/five-years-of-btrfs/), and [here](https://utcc.utoronto.ca/~cks/space/blog/solaris/ZFSWhyNoRealReshaping). #### Why use mergerfs over UnRAID? UnRAID is a full OS and its storage layer, as I understand, is proprietary and closed source. Users who have experience with both have said they prefer the flexibility offered by mergerfs and for some the fact it is free and open source is important. There are a number of UnRAID users who use mergerfs as well though I'm not entirely familiar with the use case. #### What should mergerfs NOT be used for? * databases: Even if the database stored data in separate files (mergerfs wouldn't offer much otherwise) the higher latency of the indirection will kill performance. If it is a lightly used SQLITE database then it may be fine but you'll need to test. * VM images: For the same reasons as databases. VM images are accessed very aggressively and mergerfs will introduce too much latency (if it works at all). * As replacement for RAID: mergerfs is just for pooling branches. If you need that kind of device performance aggregation or high availability you should stick with RAID. #### Can drives be written to directly? Outside of mergerfs while pooled? Yes, however it's not recommended to use the same file from within the pool and from without at the same time (particularly writing). Especially if using caching of any kind (cache.files, cache.entry, cache.attr, cache.negative_entry, cache.symlinks, cache.readdir, etc.) as there could be a conflict between cached data and not. #### Why do I get an "out of space" / "no space left on device" / ENOSPC error even though there appears to be lots of space available? First make sure you've read the sections above about policies, path preservation, branch filtering, and the options **minfreespace**, **moveonenospc**, **statfs**, and **statfs_ignore**. mergerfs is simply presenting a union of the content within multiple branches. The reported free space is an aggregate of space available within the pool (behavior modified by **statfs** and **statfs_ignore**). It does not represent a contiguous space. In the same way that read-only filesystems, those with quotas, or reserved space report the full theoretical space available. Due to path preservation, branch tagging, read-only status, and **minfreespace** settings it is perfectly valid that `ENOSPC` / "out of space" / "no space left on device" be returned. It is doing what was asked of it: filtering possible branches due to those settings. Only one error can be returned and if one of the reasons for filtering a branch was **minfreespace** then it will be returned as such. **moveonenospc** is only relevant to writing a file which is too large for the drive its currently on. It is also possible that the filesystem selected has run out of inodes. Use `df -i` to list the total and available inodes per filesystem. If you don't care about path preservation then simply change the `create` policy to one which isn't. `mfs` is probably what most are looking for. The reason it's not default is because it was originally set to `epmfs` and changing it now would change people's setup. Such a setting change will likely occur in mergerfs 3. #### Why does the total available space in mergerfs not equal outside? Are you using ext2/3/4? With reserve for root? mergerfs uses available space for statfs calculations. If you've reserved space for root then it won't show up. You can remove the reserve by running: `tune2fs -m 0 ` #### Can mergerfs mounts be exported over NFS? Yes, however if you do anything which may changes files out of band (including for example using the `newest` policy) it will result in "stale file handle" errors unless properly setup. Be sure to use the following options: * noforget * use_ino * inodecalc=path-hash #### Can mergerfs mounts be exported over Samba / SMB? Yes. While some users have reported problems it appears to always be related to how Samba is setup in relation to permissions. #### Can mergerfs mounts be used over SSHFS? Yes. #### I notice massive slowdowns of writes when enabling cache.files. When file caching is enabled in any form (`cache.files!=off` or `direct_io=false`) it will issue `getxattr` requests for `security.capability` prior to *every single write*. This will usually result in a performance degradation, especially when using a network filesystem (such as NFS or CIFS/SMB/Samba.) Unfortunately at this moment the kernel is not caching the response. To work around this situation mergerfs offers a few solutions. 1. Set `security_capability=false`. It will short circuit any call and return `ENOATTR`. This still means though that mergerfs will receive the request before every write but at least it doesn't get passed through to the underlying filesystem. 2. Set `xattr=noattr`. Same as above but applies to *all* calls to getxattr. Not just `security.capability`. This will not be cached by the kernel either but mergerfs' runtime config system will still function. 3. Set `xattr=nosys`. Results in mergerfs returning `ENOSYS` which *will* be cached by the kernel. No future xattr calls will be forwarded to mergerfs. The downside is that also means the xattr based config and query functionality won't work either. 4. Disable file caching. If you aren't using applications which use `mmap` it's probably simpler to just disable it all together. The kernel won't send the requests when caching is disabled. #### What are these .fuse_hidden files? Please upgrade. mergerfs >= 2.26.0 will not have these temporary files. See the notes on `unlink`. #### It's mentioned that there are some security issues with mhddfs. What are they? How does mergerfs address them? [mhddfs](https://github.com/trapexit/mhddfs) manages running as **root** by calling [getuid()](https://github.com/trapexit/mhddfs/blob/cae96e6251dd91e2bdc24800b4a18a74044f6672/src/main.c#L319) and if it returns **0** then it will [chown](http://linux.die.net/man/1/chown) the file. Not only is that a race condition but it doesn't handle other situations. Rather than attempting to simulate POSIX ACL behavior the proper way to manage this is to use [seteuid](http://linux.die.net/man/2/seteuid) and [setegid](http://linux.die.net/man/2/setegid), in effect becoming the user making the original call, and perform the action as them. This is what mergerfs does and why mergerfs should always run as root. In Linux setreuid syscalls apply only to the thread. GLIBC hides this away by using realtime signals to inform all threads to change credentials. Taking after **Samba**, mergerfs uses **syscall(SYS_setreuid,...)** to set the callers credentials for that thread only. Jumping back to **root** as necessary should escalated privileges be needed (for instance: to clone paths between drives). For non-Linux systems mergerfs uses a read-write lock and changes credentials only when necessary. If multiple threads are to be user X then only the first one will need to change the processes credentials. So long as the other threads need to be user X they will take a readlock allowing multiple threads to share the credentials. Once a request comes in to run as user Y that thread will attempt a write lock and change to Y's credentials when it can. If the ability to give writers priority is supported then that flag will be used so threads trying to change credentials don't starve. This isn't the best solution but should work reasonably well assuming there are few users. # SUPPORT Filesystems are complex and difficult to debug. mergerfs, while being just a proxy of sorts, is also very difficult to debug given the large number of possible settings it can have itself and the massive number of environments it can run in. When reporting on a suspected issue **please, please** include as much of the below information as possible otherwise it will be difficult or impossible to diagnose. Also please make sure to read all of the above documentation as it includes nearly every known system or user issue previously encountered. **Please make sure you are using the [latest release](https://github.com/trapexit/mergerfs/releases) or have tried it in comparison. Old versions, which are often included in distros like Debian and Ubuntu, are not ever going to be updated and your bug may have been addressed already.** #### Information to include in bug reports * Version of mergerfs: `mergerfs -V` * mergerfs settings / arguments: from fstab, systemd unit, command line, etc. * Version of the OS: `uname -a` * List of branches, their filesystem types, sizes (before and after issue): `df -h` * **All** information about the relevant branches and paths: permissions, ownership, etc. * **All** information about the client app making the requests: version, uid/gid * Runtime environment: mostly are things running inside containers or not * A `strace` of the app having problems: * `strace -fvTtt -s 256 -o /tmp/app.strace.txt ` * A `strace` of mergerfs while the program is trying to do whatever it's failing to do: * `strace -fvTtt -s 256 -p -o /tmp/mergerfs.strace.txt` * **Precise** directions on replicating the issue. Do not leave **anything** out. * Try to recreate the problem in the simplest way using standard programs: ln, mv, cp, ls, dd, etc. #### Contact / Issue submission * github.com: https://github.com/trapexit/mergerfs/issues * email: trapexit@spawn.link * discord: https://discord.gg/MpAr69V * twitter: https://twitter.com/_trapexit * reddit: https://www.reddit.com/user/trapexit #### Support development This software is free to use and released under a very liberal license (ISC). That said if you like this software and would like to support its development donations are welcome. Crypto is fine in whatever protocol you prefer. My preferences for fiat would be GitHub Sponsors or PayPal though feel free to use any platform listed below. * GitHub Sponsors: https://github.com/sponsors/trapexit * PayPal: https://paypal.me/trapexit * Patreon: https://www.patreon.com/trapexit * BuyMeACoffee: https://buymeacoff.ee/trapexit * Ko-Fi: https://ko-fi.com/trapexit * Open Collective: https://opencollective.com/trapexit * Bitcoin (BTC): bc1qu537hqlnmn2wawx9n7nws0dlkz55h0cd93ny28 * Bitcoin Cash (BCH): bitcoincash:qqp0vh9v44us74gaggwjfv9y54zfjmmd7srlqxa3xt * Bitcoin SV (BSV): 1FkFuxRtt3f8LbkpeUKRZq7gKJFzGSGgZV * Bitcoin Gold (BTG): AaPuJgJeohPjkB3LxJM6NKGnaHoRJ8ieT3 * Litecoin (LTC): MJQzsHBdNnkyGqCFdcAdHYKugicBmfAXfQ * Dogecoin (DOGE): DLJNLVe28vZ4SMQSxDJLBQBv57rGtUoWFh * Ethereum (ETH): 0xB8d6d55c0319aacC327860d13f891427caEede7a * Any ERC20 Token: 0xB8d6d55c0319aacC327860d13f891427caEede7a * Ethereum Classic (ETC): 0x2B6054428e69a1201B6555f7a2aEc0Fba01EAD9F * Harmony (ONE): one1hrtd2hqrrx4vcvncvrgnlzg5yl9wahn66lq6rw (0xB8d6d55c0319aacC327860d13f891427caEede7a) * Monero (XMR): 45BBZMrJwPSaFwSoqLVNEggWR2BJJsXxz7bNz8FXnnFo3GyhVJFSCrCFSS7zYwDa9r1TmFmGMxQ2HTntuc11yZ9q1LeCE8f * Dash (DASH): XvsFrohu8tbjA4E8p7xsc86E2ADxLHGXHL * Chia (XCH): xch18l7e2q34jtuyzkq0jg8vp7yrtnfwzly30w3yyhkk96um2ur4yqcq6p2een * LBRY Credits (LBC): bFusyoZPkSuzM2Pr8mcthgvkymaosJZt5r * Ripple (XRP): r9f6aoxaGD8aymxqH89Ke1PCUPkNiFdZZC * Tezos (XTZ): tz1ZxerkbbALsuU9XGV9K9fFpuLWnKAGfc1C * Zcash (ZEC): t1Zo1GGn2T3GrhKvgdtnTsTnWu6tCPaCaHG * DigiByte (DGB): Sb8r1qTrryY9Sp4YkTE1eeKEGVzgArnE5N * Namecoin (NMC): NDzb9FkoptGu5QbgetCkodJqo2zE1cTwyb * Vertcoin (VTC): 3PYdhokAGXJwWrwHRoTywxG4iUDk6EHjKe * Other crypto currencies: contact me for address # LINKS * https://spawn.link * https://github.com/trapexit/mergerfs * https://github.com/trapexit/mergerfs/wiki * https://github.com/trapexit/mergerfs-tools * https://github.com/trapexit/scorch * https://github.com/trapexit/bbf * https://github.com/trapexit/backup-and-recovery-howtos mergerfs-2.33.5/Makefile0000644000000000000000000001446014225522122013556 0ustar rootroot# Copyright (c) 2016, Antonio SJ Musumeci # # Permission to use, copy, modify, and/or distribute this software for any # purpose with or without fee is hereby granted, provided that the above # copyright notice and this permission notice appear in all copies. # # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. GIT = git TAR = tar MKDIR = mkdir TOUCH = touch CP = cp RM = rm LN = ln FIND = find INSTALL = install MKTEMP = mktemp STRIP = strip PANDOC = pandoc SED = sed RPMBUILD = rpmbuild GIT2DEBCL = ./tools/git2debcl PKGCONFIG = pkg-config GIT_REPO = 0 ifneq ($(shell $(GIT) --version 2> /dev/null),) ifeq ($(shell test -e .git; echo $$?),0) GIT_REPO = 1 endif endif USE_XATTR = 1 UGID_USE_RWLOCK = 0 ifeq ($(DEBUG),1) OPT_FLAGS := -O0 -g -fsanitize=undefined else OPT_FLAGS := -O2 endif ifeq ($(STATIC),1) STATIC_FLAGS := -static else STATIC_FLAGS := endif ifeq ($(LTO),1) LTO_FLAGS := -flto else LTO_FLAGS := endif SRC = $(wildcard src/*.cpp) OBJS = $(SRC:src/%.cpp=build/.src/%.o) DEPS = $(SRC:src/%.cpp=build/.src/%.d) TESTS = $(wildcard tests/*.cpp) TESTS_OBJS = $(filter-out build/.src/mergerfs.o,$(OBJS)) TESTS_OBJS += $(TESTS:tests/%.cpp=build/.tests/%.o) TESTS_DEPS = $(TESTS:tests/%.cpp=build/.tests/%.d) TESTS_DEPS += $(DEPS) MANPAGE = mergerfs.1 CXXFLAGS ?= ${OPT_FLAGS} CXXFLAGS := \ ${CXXFLAGS} \ -std=c++11 \ $(STATIC_FLAGS) \ $(LTO_FLAGS) \ -Wall \ -Wno-unused-result \ -MMD FUSE_FLAGS = \ -Ilibfuse/include \ -D_FILE_OFFSET_BITS=64 \ -DFUSE_USE_VERSION=29 MFS_FLAGS = \ -DUSE_XATTR=$(USE_XATTR) \ -DUGID_USE_RWLOCK=$(UGID_USE_RWLOCK) TESTS_FLAGS = \ -Isrc \ -DTESTS LDFLAGS := \ ${LDFLAGS} \ -pthread \ -lrt DESTDIR = PREFIX = /usr/local EXEC_PREFIX = $(PREFIX) DATAROOTDIR = $(PREFIX)/share DATADIR = $(DATAROOTDIR) BINDIR = $(EXEC_PREFIX)/bin SBINDIR = $(EXEC_PREFIX)/sbin MANDIR = $(DATAROOTDIR)/man MAN1DIR = $(MANDIR)/man1 INSTALLBINDIR = $(DESTDIR)$(BINDIR) INSTALLSBINDIR = $(DESTDIR)$(SBINDIR) INSTALLMAN1DIR = $(DESTDIR)$(MAN1DIR) .PHONY: all all: mergerfs .PHONY: help help: @echo "usage: make\n" @echo "make USE_XATTR=0 - build program without xattrs functionality" @echo "make STATIC=1 - build static binary" @echo "make LTO=1 - build with link time optimization" objects: version build/stamp $(MAKE) $(OBJS) tests-objects: $(MAKE) $(TESTS_OBJS) build/mergerfs: libfuse objects $(CXX) $(CXXFLAGS) $(FUSE_FLAGS) $(MFS_FLAGS) $(CPPFLAGS) $(OBJS) -o $@ libfuse/build/libfuse.a $(LDFLAGS) build/tests: build/mergerfs tests-objects $(CXX) $(CXXFLAGS) $(TESTS_FLAGS) $(FUSE_FLAGS) $(MFS_FLAGS) $(CPPFLAGS) $(TESTS_OBJS) -o $@ libfuse/build/libfuse.a $(LDFLAGS) mergerfs: build/mergerfs tests: build/tests changelog: ifeq ($(GIT_REPO),1) $(GIT2DEBCL) --name mergerfs > ChangeLog else @echo "WARNING: need git repo to generate ChangeLog" endif .PHONY: version version: tools/update-version build/stamp: $(MKDIR) -p build/.src build/.tests $(TOUCH) $@ build/.src/%.o: src/%.cpp $(CXX) $(CXXFLAGS) $(FUSE_FLAGS) $(MFS_FLAGS) $(CPPFLAGS) -c $< -o $@ build/.tests/%.o: tests/%.cpp $(CXX) $(CXXFLAGS) $(TESTS_FLAGS) $(FUSE_FLAGS) $(MFS_FLAGS) $(CPPFLAGS) -c $< -o $@ .PHONY: clean clean: rpm-clean $(RM) -rf build $(FIND) . -name "*~" -delete $(MAKE) -C libfuse clean distclean: clean ifeq ($(GIT_REPO),1) $(GIT) clean -xfd endif .PHONY: install install: install-base install-mount-tools install-man install-base: build/mergerfs $(MKDIR) -p "$(INSTALLBINDIR)" $(INSTALL) -v -m 0755 build/mergerfs "$(INSTALLBINDIR)/mergerfs" install-mount-tools: install-base $(MKDIR) -p "$(INSTALLBINDIR)" $(MAKE) -C libfuse install install-man: $(MANPAGE) $(MKDIR) -p "$(INSTALLMAN1DIR)" $(INSTALL) -v -m 0644 "man/$(MANPAGE)" "$(INSTALLMAN1DIR)/$(MANPAGE)" install-strip: install-base $(STRIP) "$(INSTALLBINDIR)/mergerfs" .PHONY: uninstall uninstall: uninstall-base uninstall-mount.mergerfs uninstall-man uninstall-base: $(RM) -f "$(INSTALLBINDIR)/mergerfs" uninstall-mount.mergerfs: $(RM) -f "$(INSTALLBINDIR)/mount.mergerfs" uninstall-man: $(RM) -f "$(INSTALLMAN1DIR)/$(MANPAGE)" $(MANPAGE): README.md ifneq ($(shell $(PANDOC) --version 2> /dev/null),) $(PANDOC) -s -t man -o "man/$(MANPAGE)" README.md else $(warning "pandoc does not appear available: unable to build manpage") endif man: $(MANPAGE) .PHONY: tarball tarball: man changelog version $(eval VERSION := $(shell cat VERSION)) $(eval VERSION := $(subst -,_,$(VERSION))) $(eval FILENAME := mergerfs-$(VERSION)) $(eval TMPDIR := $(shell $(MKTEMP) --tmpdir -d .$(FILENAME).XXXXXXXX)) $(MKDIR) $(TMPDIR)/$(FILENAME) $(CP) -ar . $(TMPDIR)/$(FILENAME) $(TAR) --exclude=.git -cz -C $(TMPDIR) -f $(FILENAME).tar.gz $(FILENAME) $(RM) -rf $(TMPDIR) debian-changelog: ifeq ($(GIT_REPO),1) $(GIT2DEBCL) --name mergerfs > debian/changelog else cp ChangeLog debian/changelog endif signed-deb: $(MAKE) distclean $(MAKE) debian-changelog dpkg-source -b . dpkg-buildpackage -nc deb: $(MAKE) distclean $(MAKE) debian-changelog dpkg-source -b . dpkg-buildpackage -nc -uc -us .PHONY: rpm-clean rpm-clean: $(RM) -rf rpmbuild rpm: tarball $(eval VERSION := $(shell cat VERSION)) $(eval VERSION := $(subst -,_,$(VERSION))) $(MKDIR) -p rpmbuild/BUILD rpmbuild/RPMS rpmbuild/SOURCES $(SED) 's/__VERSION__/$(VERSION)/g' mergerfs.spec > \ rpmbuild/SOURCES/mergerfs.spec cp -ar mergerfs-$(VERSION).tar.gz rpmbuild/SOURCES $(RPMBUILD) -ba rpmbuild/SOURCES/mergerfs.spec \ --define "_topdir $(CURDIR)/rpmbuild" .PHONY: install-build-pkgs install-build-pkgs: tools/install-build-pkgs .PHONY: libfuse libfuse: $(MAKE) DEBUG=$(DEBUG) -C libfuse -include $(DEPS) mergerfs-2.33.5/tests/0000755000000000000000000000000014225522122013253 5ustar rootrootmergerfs-2.33.5/tests/TEST_no_fuse_hidden0000755000000000000000000000054114225522122017011 0ustar rootroot#!/usr/bin/env python3 import os import sys import tempfile (fd,filepath) = tempfile.mkstemp(dir=sys.argv[1]) ino = os.fstat(fd).st_ino os.unlink(filepath) for entry in os.scandir(sys.argv[1]): if entry.inode() == ino: print("found .fuse_hidden file {}".format(entry.name),end='') os.close(fd) sys.exit(1) os.close(fd) mergerfs-2.33.5/tests/TEST_use_fchown_after_unlink0000755000000000000000000000034114225522122020737 0ustar rootroot#!/usr/bin/env python3 import os import sys import tempfile (fd,filepath) = tempfile.mkstemp(dir=sys.argv[1]) os.fchown(fd,os.getuid(),os.getgid()) os.unlink(filepath) os.fchown(fd,os.getuid(),os.getgid()) os.close(fd) mergerfs-2.33.5/tests/TEST_use_fstat_after_unlink0000755000000000000000000000025714225522122020602 0ustar rootroot#!/usr/bin/env python3 import os import sys import tempfile (fd,filepath) = tempfile.mkstemp(dir=sys.argv[1]) os.fstat(fd) os.unlink(filepath) os.fstat(fd) os.close(fd) mergerfs-2.33.5/tests/TEST_use_fchmod_after_unlink0000755000000000000000000000027514225522122020721 0ustar rootroot#!/usr/bin/env python3 import os import sys import tempfile (fd,filepath) = tempfile.mkstemp(dir=sys.argv[1]) os.fchmod(fd,0o777) os.unlink(filepath) os.fchmod(fd,0o777) os.close(fd) mergerfs-2.33.5/tests/acutest.h0000644000000000000000000016233414225522122015105 0ustar rootroot/* * Acutest -- Another C/C++ Unit Test facility * * * Copyright 2013-2020 Martin Mitas * Copyright 2019 Garrett D'Amore * * 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 ACUTEST_H #define ACUTEST_H /************************ *** Public interface *** ************************/ /* By default, "acutest.h" provides the main program entry point (function * main()). However, if the test suite is composed of multiple source files * which include "acutest.h", then this causes a problem of multiple main() * definitions. To avoid this problem, #define macro TEST_NO_MAIN in all * compilation units but one. */ /* Macro to specify list of unit tests in the suite. * The unit test implementation MUST provide list of unit tests it implements * with this macro: * * TEST_LIST = { * { "test1_name", test1_func_ptr }, * { "test2_name", test2_func_ptr }, * ... * { NULL, NULL } // zeroed record marking the end of the list * }; * * The list specifies names of each test (must be unique) and pointer to * a function implementing it. The function does not take any arguments * and has no return values, i.e. every test function has to be compatible * with this prototype: * * void test_func(void); * * Note the list has to be ended with a zeroed record. */ #define TEST_LIST const struct test_ test_list_[] /* Macros for testing whether an unit test succeeds or fails. These macros * can be used arbitrarily in functions implementing the unit tests. * * If any condition fails throughout execution of a test, the test fails. * * TEST_CHECK takes only one argument (the condition), TEST_CHECK_ allows * also to specify an error message to print out if the condition fails. * (It expects printf-like format string and its parameters). The macros * return non-zero (condition passes) or 0 (condition fails). * * That can be useful when more conditions should be checked only if some * preceding condition passes, as illustrated in this code snippet: * * SomeStruct* ptr = allocate_some_struct(); * if(TEST_CHECK(ptr != NULL)) { * TEST_CHECK(ptr->member1 < 100); * TEST_CHECK(ptr->member2 > 200); * } */ #define TEST_CHECK_(cond,...) test_check_((cond), __FILE__, __LINE__, __VA_ARGS__) #define TEST_CHECK(cond) test_check_((cond), __FILE__, __LINE__, "%s", #cond) /* These macros are the same as TEST_CHECK_ and TEST_CHECK except that if the * condition fails, the currently executed unit test is immediately aborted. * * That is done either by calling abort() if the unit test is executed as a * child process; or via longjmp() if the unit test is executed within the * main Acutest process. * * As a side effect of such abortion, your unit tests may cause memory leaks, * unflushed file descriptors, and other phenomena caused by the abortion. * * Therefore you should not use these as a general replacement for TEST_CHECK. * Use it with some caution, especially if your test causes some other side * effects to the outside world (e.g. communicating with some server, inserting * into a database etc.). */ #define TEST_ASSERT_(cond,...) \ do { \ if(!test_check_((cond), __FILE__, __LINE__, __VA_ARGS__)) \ test_abort_(); \ } while(0) #define TEST_ASSERT(cond) \ do { \ if(!test_check_((cond), __FILE__, __LINE__, "%s", #cond)) \ test_abort_(); \ } while(0) #ifdef __cplusplus /* Macros to verify that the code (the 1st argument) throws exception of given * type (the 2nd argument). (Note these macros are only available in C++.) * * TEST_EXCEPTION_ is like TEST_EXCEPTION but accepts custom printf-like * message. * * For example: * * TEST_EXCEPTION(function_that_throw(), ExpectedExceptionType); * * If the function_that_throw() throws ExpectedExceptionType, the check passes. * If the function throws anything incompatible with ExpectedExceptionType * (or if it does not thrown an exception at all), the check fails. */ #define TEST_EXCEPTION(code, exctype) \ do { \ bool exc_ok_ = false; \ const char *msg_ = NULL; \ try { \ code; \ msg_ = "No exception thrown."; \ } catch(exctype const&) { \ exc_ok_= true; \ } catch(...) { \ msg_ = "Unexpected exception thrown."; \ } \ test_check_(exc_ok_, __FILE__, __LINE__, #code " throws " #exctype); \ if(msg_ != NULL) \ test_message_("%s", msg_); \ } while(0) #define TEST_EXCEPTION_(code, exctype, ...) \ do { \ bool exc_ok_ = false; \ const char *msg_ = NULL; \ try { \ code; \ msg_ = "No exception thrown."; \ } catch(exctype const&) { \ exc_ok_= true; \ } catch(...) { \ msg_ = "Unexpected exception thrown."; \ } \ test_check_(exc_ok_, __FILE__, __LINE__, __VA_ARGS__); \ if(msg_ != NULL) \ test_message_("%s", msg_); \ } while(0) #endif /* #ifdef __cplusplus */ /* Sometimes it is useful to split execution of more complex unit tests to some * smaller parts and associate those parts with some names. * * This is especially handy if the given unit test is implemented as a loop * over some vector of multiple testing inputs. Using these macros allow to use * sort of subtitle for each iteration of the loop (e.g. outputting the input * itself or a name associated to it), so that if any TEST_CHECK condition * fails in the loop, it can be easily seen which iteration triggers the * failure, without the need to manually output the iteration-specific data in * every single TEST_CHECK inside the loop body. * * TEST_CASE allows to specify only single string as the name of the case, * TEST_CASE_ provides all the power of printf-like string formatting. * * Note that the test cases cannot be nested. Starting a new test case ends * implicitly the previous one. To end the test case explicitly (e.g. to end * the last test case after exiting the loop), you may use TEST_CASE(NULL). */ #define TEST_CASE_(...) test_case_(__VA_ARGS__) #define TEST_CASE(name) test_case_("%s", name) /* Maximal output per TEST_CASE call. Longer messages are cut. * You may define another limit prior including "acutest.h" */ #ifndef TEST_CASE_MAXSIZE #define TEST_CASE_MAXSIZE 64 #endif /* printf-like macro for outputting an extra information about a failure. * * Intended use is to output some computed output versus the expected value, * e.g. like this: * * if(!TEST_CHECK(produced == expected)) { * TEST_MSG("Expected: %d", expected); * TEST_MSG("Produced: %d", produced); * } * * Note the message is only written down if the most recent use of any checking * macro (like e.g. TEST_CHECK or TEST_EXCEPTION) in the current test failed. * This means the above is equivalent to just this: * * TEST_CHECK(produced == expected); * TEST_MSG("Expected: %d", expected); * TEST_MSG("Produced: %d", produced); * * The macro can deal with multi-line output fairly well. It also automatically * adds a final new-line if there is none present. */ #define TEST_MSG(...) test_message_(__VA_ARGS__) /* Maximal output per TEST_MSG call. Longer messages are cut. * You may define another limit prior including "acutest.h" */ #ifndef TEST_MSG_MAXSIZE #define TEST_MSG_MAXSIZE 1024 #endif /* Macro for dumping a block of memory. * * Its intended use is very similar to what TEST_MSG is for, but instead of * generating any printf-like message, this is for dumping raw block of a * memory in a hexadecimal form: * * TEST_CHECK(size_produced == size_expected && * memcmp(addr_produced, addr_expected, size_produced) == 0); * TEST_DUMP("Expected:", addr_expected, size_expected); * TEST_DUMP("Produced:", addr_produced, size_produced); */ #define TEST_DUMP(title, addr, size) test_dump_(title, addr, size) /* Maximal output per TEST_DUMP call (in bytes to dump). Longer blocks are cut. * You may define another limit prior including "acutest.h" */ #ifndef TEST_DUMP_MAXSIZE #define TEST_DUMP_MAXSIZE 1024 #endif /* Common test initialiation/clean-up * * In some test suites, it may be needed to perform some sort of the same * initialization and/or clean-up in all the tests. * * Such test suites may use macros TEST_INIT and/or TEST_FINI prior including * this header. The expansion of the macro is then used as a body of helper * function called just before executing every single (TEST_INIT) or just after * it ends (TEST_FINI). * * Examples of various ways how to use the macro TEST_INIT: * * #define TEST_INIT my_init_func(); * #define TEST_INIT my_init_func() // Works even without the semicolon * #define TEST_INIT setlocale(LC_ALL, NULL); * #define TEST_INIT { setlocale(LC_ALL, NULL); my_init_func(); } * * TEST_FINI is to be used in the same way. */ /********************** *** Implementation *** **********************/ /* The unit test files should not rely on anything below. */ #include #include #include #include #include #include #if defined(unix) || defined(__unix__) || defined(__unix) || defined(__APPLE__) #define ACUTEST_UNIX_ 1 #include #include #include #include #include #include #include #if defined CLOCK_PROCESS_CPUTIME_ID && defined CLOCK_MONOTONIC #define ACUTEST_HAS_POSIX_TIMER_ 1 #endif #endif #if defined(_gnu_linux_) || defined(__linux__) #define ACUTEST_LINUX_ 1 #include #include #endif #if defined(_WIN32) || defined(__WIN32__) || defined(__WINDOWS__) #define ACUTEST_WIN_ 1 #include #include #endif #ifdef __cplusplus #include #endif /* Load valgrind.h, if available. This allows to detect valgrind's presence via RUNNING_ON_VALGRIND. */ #ifdef __has_include #if __has_include() #include #endif #endif /* Enable the use of the non-standard keyword __attribute__ to silence warnings under some compilers */ #if defined(__GNUC__) || defined(__clang__) #define TEST_ATTRIBUTE_(attr) __attribute__((attr)) #else #define TEST_ATTRIBUTE_(attr) #endif /* Note our global private identifiers end with '_' to mitigate risk of clash * with the unit tests implementation. */ #ifdef __cplusplus extern "C" { #endif #ifdef _MSC_VER /* In the multi-platform code like ours, we cannot use the non-standard * "safe" functions from Microsoft C lib like e.g. sprintf_s() instead of * standard sprintf(). Hence, lets disable the warning C4996. */ #pragma warning(push) #pragma warning(disable: 4996) #endif struct test_ { const char* name; void (*func)(void); }; struct test_detail_ { unsigned char flags; double duration; }; enum { TEST_FLAG_RUN_ = 1 << 0, TEST_FLAG_SUCCESS_ = 1 << 1, TEST_FLAG_FAILURE_ = 1 << 2, }; extern const struct test_ test_list_[]; int test_check_(int cond, const char* file, int line, const char* fmt, ...); void test_case_(const char* fmt, ...); void test_message_(const char* fmt, ...); void test_dump_(const char* title, const void* addr, size_t size); void test_abort_(void) TEST_ATTRIBUTE_(noreturn); #ifndef TEST_NO_MAIN static char* test_argv0_ = NULL; static size_t test_list_size_ = 0; static struct test_detail_ *test_details_ = NULL; static size_t test_count_ = 0; static int test_no_exec_ = -1; static int test_no_summary_ = 0; static int test_tap_ = 0; static int test_skip_mode_ = 0; static int test_worker_ = 0; static int test_worker_index_ = 0; static int test_cond_failed_ = 0; static int test_was_aborted_ = 0; static FILE *test_xml_output_ = NULL; static int test_stat_failed_units_ = 0; static int test_stat_run_units_ = 0; static const struct test_* test_current_unit_ = NULL; static int test_current_index_ = 0; static char test_case_name_[TEST_CASE_MAXSIZE] = ""; static int test_current_already_logged_ = 0; static int test_case_current_already_logged_ = 0; static int test_verbose_level_ = 2; static int test_current_failures_ = 0; static int test_colorize_ = 0; static int test_timer_ = 0; static int test_abort_has_jmp_buf_ = 0; static jmp_buf test_abort_jmp_buf_; static void test_cleanup_(void) { free((void*) test_details_); } static void test_exit_(int exit_code) { test_cleanup_(); exit(exit_code); } #if defined ACUTEST_WIN_ typedef LARGE_INTEGER test_timer_type_; static LARGE_INTEGER test_timer_freq_; static test_timer_type_ test_timer_start_; static test_timer_type_ test_timer_end_; static void test_timer_init_(void) { QueryPerformanceFrequency(&test_timer_freq_); } static void test_timer_get_time_(LARGE_INTEGER* ts) { QueryPerformanceCounter(ts); } static double test_timer_diff_(LARGE_INTEGER start, LARGE_INTEGER end) { double duration = (double)(end.QuadPart - start.QuadPart); duration /= (double)test_timer_freq_.QuadPart; return duration; } static void test_timer_print_diff_(void) { printf("%.6lf secs", test_timer_diff_(test_timer_start_, test_timer_end_)); } #elif defined ACUTEST_HAS_POSIX_TIMER_ static clockid_t test_timer_id_; typedef struct timespec test_timer_type_; static test_timer_type_ test_timer_start_; static test_timer_type_ test_timer_end_; static void test_timer_init_(void) { if(test_timer_ == 1) test_timer_id_ = CLOCK_MONOTONIC; else if(test_timer_ == 2) test_timer_id_ = CLOCK_PROCESS_CPUTIME_ID; } static void test_timer_get_time_(struct timespec* ts) { clock_gettime(test_timer_id_, ts); } static double test_timer_diff_(struct timespec start, struct timespec end) { double endns; double startns; endns = end.tv_sec; endns *= 1e9; endns += end.tv_nsec; startns = start.tv_sec; startns *= 1e9; startns += start.tv_nsec; return ((endns - startns)/ 1e9); } static void test_timer_print_diff_(void) { printf("%.6lf secs", test_timer_diff_(test_timer_start_, test_timer_end_)); } #else typedef int test_timer_type_; static test_timer_type_ test_timer_start_; static test_timer_type_ test_timer_end_; void test_timer_init_(void) {} static void test_timer_get_time_(int* ts) { (void) ts; } static double test_timer_diff_(int start, int end) { (void) start; (void) end; return 0.0; } static void test_timer_print_diff_(void) {} #endif #define TEST_COLOR_DEFAULT_ 0 #define TEST_COLOR_GREEN_ 1 #define TEST_COLOR_RED_ 2 #define TEST_COLOR_DEFAULT_INTENSIVE_ 3 #define TEST_COLOR_GREEN_INTENSIVE_ 4 #define TEST_COLOR_RED_INTENSIVE_ 5 static int TEST_ATTRIBUTE_(format (printf, 2, 3)) test_print_in_color_(int color, const char* fmt, ...) { va_list args; char buffer[256]; int n; va_start(args, fmt); vsnprintf(buffer, sizeof(buffer), fmt, args); va_end(args); buffer[sizeof(buffer)-1] = '\0'; if(!test_colorize_) { return printf("%s", buffer); } #if defined ACUTEST_UNIX_ { const char* col_str; switch(color) { case TEST_COLOR_GREEN_: col_str = "\033[0;32m"; break; case TEST_COLOR_RED_: col_str = "\033[0;31m"; break; case TEST_COLOR_GREEN_INTENSIVE_: col_str = "\033[1;32m"; break; case TEST_COLOR_RED_INTENSIVE_: col_str = "\033[1;31m"; break; case TEST_COLOR_DEFAULT_INTENSIVE_: col_str = "\033[1m"; break; default: col_str = "\033[0m"; break; } printf("%s", col_str); n = printf("%s", buffer); printf("\033[0m"); return n; } #elif defined ACUTEST_WIN_ { HANDLE h; CONSOLE_SCREEN_BUFFER_INFO info; WORD attr; h = GetStdHandle(STD_OUTPUT_HANDLE); GetConsoleScreenBufferInfo(h, &info); switch(color) { case TEST_COLOR_GREEN_: attr = FOREGROUND_GREEN; break; case TEST_COLOR_RED_: attr = FOREGROUND_RED; break; case TEST_COLOR_GREEN_INTENSIVE_: attr = FOREGROUND_GREEN | FOREGROUND_INTENSITY; break; case TEST_COLOR_RED_INTENSIVE_: attr = FOREGROUND_RED | FOREGROUND_INTENSITY; break; case TEST_COLOR_DEFAULT_INTENSIVE_: attr = FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_INTENSITY; break; default: attr = 0; break; } if(attr != 0) SetConsoleTextAttribute(h, attr); n = printf("%s", buffer); SetConsoleTextAttribute(h, info.wAttributes); return n; } #else n = printf("%s", buffer); return n; #endif } static void test_begin_test_line_(const struct test_* test) { if(!test_tap_) { if(test_verbose_level_ >= 3) { test_print_in_color_(TEST_COLOR_DEFAULT_INTENSIVE_, "Test %s:\n", test->name); test_current_already_logged_++; } else if(test_verbose_level_ >= 1) { int n; char spaces[48]; n = test_print_in_color_(TEST_COLOR_DEFAULT_INTENSIVE_, "Test %s... ", test->name); memset(spaces, ' ', sizeof(spaces)); if(n < (int) sizeof(spaces)) printf("%.*s", (int) sizeof(spaces) - n, spaces); } else { test_current_already_logged_ = 1; } } } static void test_finish_test_line_(int result) { if(test_tap_) { const char* str = (result == 0) ? "ok" : "not ok"; printf("%s %d - %s\n", str, test_current_index_ + 1, test_current_unit_->name); if(result == 0 && test_timer_) { printf("# Duration: "); test_timer_print_diff_(); printf("\n"); } } else { int color = (result == 0) ? TEST_COLOR_GREEN_INTENSIVE_ : TEST_COLOR_RED_INTENSIVE_; const char* str = (result == 0) ? "OK" : "FAILED"; printf("[ "); test_print_in_color_(color, "%s", str); printf(" ]"); if(result == 0 && test_timer_) { printf(" "); test_timer_print_diff_(); } printf("\n"); } } static void test_line_indent_(int level) { static const char spaces[] = " "; int n = level * 2; if(test_tap_ && n > 0) { n--; printf("#"); } while(n > 16) { printf("%s", spaces); n -= 16; } printf("%.*s", n, spaces); } int TEST_ATTRIBUTE_(format (printf, 4, 5)) test_check_(int cond, const char* file, int line, const char* fmt, ...) { const char *result_str; int result_color; int verbose_level; if(cond) { result_str = "ok"; result_color = TEST_COLOR_GREEN_; verbose_level = 3; } else { if(!test_current_already_logged_ && test_current_unit_ != NULL) test_finish_test_line_(-1); result_str = "failed"; result_color = TEST_COLOR_RED_; verbose_level = 2; test_current_failures_++; test_current_already_logged_++; } if(test_verbose_level_ >= verbose_level) { va_list args; if(!test_case_current_already_logged_ && test_case_name_[0]) { test_line_indent_(1); test_print_in_color_(TEST_COLOR_DEFAULT_INTENSIVE_, "Case %s:\n", test_case_name_); test_current_already_logged_++; test_case_current_already_logged_++; } test_line_indent_(test_case_name_[0] ? 2 : 1); if(file != NULL) { #ifdef ACUTEST_WIN_ const char* lastsep1 = strrchr(file, '\\'); const char* lastsep2 = strrchr(file, '/'); if(lastsep1 == NULL) lastsep1 = file-1; if(lastsep2 == NULL) lastsep2 = file-1; file = (lastsep1 > lastsep2 ? lastsep1 : lastsep2) + 1; #else const char* lastsep = strrchr(file, '/'); if(lastsep != NULL) file = lastsep+1; #endif printf("%s:%d: Check ", file, line); } va_start(args, fmt); vprintf(fmt, args); va_end(args); printf("... "); test_print_in_color_(result_color, "%s", result_str); printf("\n"); test_current_already_logged_++; } test_cond_failed_ = (cond == 0); return !test_cond_failed_; } void TEST_ATTRIBUTE_(format (printf, 1, 2)) test_case_(const char* fmt, ...) { va_list args; if(test_verbose_level_ < 2) return; if(test_case_name_[0]) { test_case_current_already_logged_ = 0; test_case_name_[0] = '\0'; } if(fmt == NULL) return; va_start(args, fmt); vsnprintf(test_case_name_, sizeof(test_case_name_) - 1, fmt, args); va_end(args); test_case_name_[sizeof(test_case_name_) - 1] = '\0'; if(test_verbose_level_ >= 3) { test_line_indent_(1); test_print_in_color_(TEST_COLOR_DEFAULT_INTENSIVE_, "Case %s:\n", test_case_name_); test_current_already_logged_++; test_case_current_already_logged_++; } } void TEST_ATTRIBUTE_(format (printf, 1, 2)) test_message_(const char* fmt, ...) { char buffer[TEST_MSG_MAXSIZE]; char* line_beg; char* line_end; va_list args; if(test_verbose_level_ < 2) return; /* We allow extra message only when something is already wrong in the * current test. */ if(test_current_unit_ == NULL || !test_cond_failed_) return; va_start(args, fmt); vsnprintf(buffer, TEST_MSG_MAXSIZE, fmt, args); va_end(args); buffer[TEST_MSG_MAXSIZE-1] = '\0'; line_beg = buffer; while(1) { line_end = strchr(line_beg, '\n'); if(line_end == NULL) break; test_line_indent_(test_case_name_[0] ? 3 : 2); printf("%.*s\n", (int)(line_end - line_beg), line_beg); line_beg = line_end + 1; } if(line_beg[0] != '\0') { test_line_indent_(test_case_name_[0] ? 3 : 2); printf("%s\n", line_beg); } } void test_dump_(const char* title, const void* addr, size_t size) { static const size_t BYTES_PER_LINE = 16; size_t line_beg; size_t truncate = 0; if(test_verbose_level_ < 2) return; /* We allow extra message only when something is already wrong in the * current test. */ if(test_current_unit_ == NULL || !test_cond_failed_) return; if(size > TEST_DUMP_MAXSIZE) { truncate = size - TEST_DUMP_MAXSIZE; size = TEST_DUMP_MAXSIZE; } test_line_indent_(test_case_name_[0] ? 3 : 2); printf((title[strlen(title)-1] == ':') ? "%s\n" : "%s:\n", title); for(line_beg = 0; line_beg < size; line_beg += BYTES_PER_LINE) { size_t line_end = line_beg + BYTES_PER_LINE; size_t off; test_line_indent_(test_case_name_[0] ? 4 : 3); printf("%08lx: ", (unsigned long)line_beg); for(off = line_beg; off < line_end; off++) { if(off < size) printf(" %02x", ((const unsigned char*)addr)[off]); else printf(" "); } printf(" "); for(off = line_beg; off < line_end; off++) { unsigned char byte = ((const unsigned char*)addr)[off]; if(off < size) printf("%c", (iscntrl(byte) ? '.' : byte)); else break; } printf("\n"); } if(truncate > 0) { test_line_indent_(test_case_name_[0] ? 4 : 3); printf(" ... (and more %u bytes)\n", (unsigned) truncate); } } /* This is called just before each test */ static void test_init_(const char *test_name) { #ifdef TEST_INIT TEST_INIT ; /* Allow for a single unterminated function call */ #endif /* Suppress any warnings about unused variable. */ (void) test_name; } /* This is called after each test */ static void test_fini_(const char *test_name) { #ifdef TEST_FINI TEST_FINI ; /* Allow for a single unterminated function call */ #endif /* Suppress any warnings about unused variable. */ (void) test_name; } void test_abort_(void) { if(test_abort_has_jmp_buf_) { longjmp(test_abort_jmp_buf_, 1); } else { if(test_current_unit_ != NULL) test_fini_(test_current_unit_->name); abort(); } } static void test_list_names_(void) { const struct test_* test; printf("Unit tests:\n"); for(test = &test_list_[0]; test->func != NULL; test++) printf(" %s\n", test->name); } static void test_remember_(int i) { if(test_details_[i].flags & TEST_FLAG_RUN_) return; test_details_[i].flags |= TEST_FLAG_RUN_; test_count_++; } static void test_set_success_(int i, int success) { test_details_[i].flags |= success ? TEST_FLAG_SUCCESS_ : TEST_FLAG_FAILURE_; } static void test_set_duration_(int i, double duration) { test_details_[i].duration = duration; } static int test_name_contains_word_(const char* name, const char* pattern) { static const char word_delim[] = " \t-_/.,:;"; const char* substr; size_t pattern_len; pattern_len = strlen(pattern); substr = strstr(name, pattern); while(substr != NULL) { int starts_on_word_boundary = (substr == name || strchr(word_delim, substr[-1]) != NULL); int ends_on_word_boundary = (substr[pattern_len] == '\0' || strchr(word_delim, substr[pattern_len]) != NULL); if(starts_on_word_boundary && ends_on_word_boundary) return 1; substr = strstr(substr+1, pattern); } return 0; } static int test_lookup_(const char* pattern) { int i; int n = 0; /* Try exact match. */ for(i = 0; i < (int) test_list_size_; i++) { if(strcmp(test_list_[i].name, pattern) == 0) { test_remember_(i); n++; break; } } if(n > 0) return n; /* Try word match. */ for(i = 0; i < (int) test_list_size_; i++) { if(test_name_contains_word_(test_list_[i].name, pattern)) { test_remember_(i); n++; } } if(n > 0) return n; /* Try relaxed match. */ for(i = 0; i < (int) test_list_size_; i++) { if(strstr(test_list_[i].name, pattern) != NULL) { test_remember_(i); n++; } } return n; } /* Called if anything goes bad in Acutest, or if the unit test ends in other * way then by normal returning from its function (e.g. exception or some * abnormal child process termination). */ static void TEST_ATTRIBUTE_(format (printf, 1, 2)) test_error_(const char* fmt, ...) { if(test_verbose_level_ == 0) return; if(test_verbose_level_ >= 2) { va_list args; test_line_indent_(1); if(test_verbose_level_ >= 3) test_print_in_color_(TEST_COLOR_RED_INTENSIVE_, "ERROR: "); va_start(args, fmt); vprintf(fmt, args); va_end(args); printf("\n"); } if(test_verbose_level_ >= 3) { printf("\n"); } } /* Call directly the given test unit function. */ static int test_do_run_(const struct test_* test, int index) { int status = -1; test_was_aborted_ = 0; test_current_unit_ = test; test_current_index_ = index; test_current_failures_ = 0; test_current_already_logged_ = 0; test_cond_failed_ = 0; #ifdef __cplusplus try { #endif test_init_(test->name); test_begin_test_line_(test); /* This is good to do in case the test unit crashes. */ fflush(stdout); fflush(stderr); if(!test_worker_) { test_abort_has_jmp_buf_ = 1; if(setjmp(test_abort_jmp_buf_) != 0) { test_was_aborted_ = 1; goto aborted; } } test_timer_get_time_(&test_timer_start_); test->func(); aborted: test_abort_has_jmp_buf_ = 0; test_timer_get_time_(&test_timer_end_); if(test_verbose_level_ >= 3) { test_line_indent_(1); if(test_current_failures_ == 0) { test_print_in_color_(TEST_COLOR_GREEN_INTENSIVE_, "SUCCESS: "); printf("All conditions have passed.\n"); if(test_timer_) { test_line_indent_(1); printf("Duration: "); test_timer_print_diff_(); printf("\n"); } } else { test_print_in_color_(TEST_COLOR_RED_INTENSIVE_, "FAILED: "); if(!test_was_aborted_) { printf("%d condition%s %s failed.\n", test_current_failures_, (test_current_failures_ == 1) ? "" : "s", (test_current_failures_ == 1) ? "has" : "have"); } else { printf("Aborted.\n"); } } printf("\n"); } else if(test_verbose_level_ >= 1 && test_current_failures_ == 0) { test_finish_test_line_(0); } status = (test_current_failures_ == 0) ? 0 : -1; #ifdef __cplusplus } catch(std::exception& e) { const char* what = e.what(); test_check_(0, NULL, 0, "Threw std::exception"); if(what != NULL) test_message_("std::exception::what(): %s", what); if(test_verbose_level_ >= 3) { test_line_indent_(1); test_print_in_color_(TEST_COLOR_RED_INTENSIVE_, "FAILED: "); printf("C++ exception.\n\n"); } } catch(...) { test_check_(0, NULL, 0, "Threw an exception"); if(test_verbose_level_ >= 3) { test_line_indent_(1); test_print_in_color_(TEST_COLOR_RED_INTENSIVE_, "FAILED: "); printf("C++ exception.\n\n"); } } #endif test_fini_(test->name); test_case_(NULL); test_current_unit_ = NULL; return status; } /* Trigger the unit test. If possible (and not suppressed) it starts a child * process who calls test_do_run_(), otherwise it calls test_do_run_() * directly. */ static void test_run_(const struct test_* test, int index, int master_index) { int failed = 1; test_timer_type_ start, end; test_current_unit_ = test; test_current_already_logged_ = 0; test_timer_get_time_(&start); if(!test_no_exec_) { #if defined(ACUTEST_UNIX_) pid_t pid; int exit_code; /* Make sure the child starts with empty I/O buffers. */ fflush(stdout); fflush(stderr); pid = fork(); if(pid == (pid_t)-1) { test_error_("Cannot fork. %s [%d]", strerror(errno), errno); failed = 1; } else if(pid == 0) { /* Child: Do the test. */ test_worker_ = 1; failed = (test_do_run_(test, index) != 0); test_exit_(failed ? 1 : 0); } else { /* Parent: Wait until child terminates and analyze its exit code. */ waitpid(pid, &exit_code, 0); if(WIFEXITED(exit_code)) { switch(WEXITSTATUS(exit_code)) { case 0: failed = 0; break; /* test has passed. */ case 1: /* noop */ break; /* "normal" failure. */ default: test_error_("Unexpected exit code [%d]", WEXITSTATUS(exit_code)); } } else if(WIFSIGNALED(exit_code)) { char tmp[32]; const char* signame; switch(WTERMSIG(exit_code)) { case SIGINT: signame = "SIGINT"; break; case SIGHUP: signame = "SIGHUP"; break; case SIGQUIT: signame = "SIGQUIT"; break; case SIGABRT: signame = "SIGABRT"; break; case SIGKILL: signame = "SIGKILL"; break; case SIGSEGV: signame = "SIGSEGV"; break; case SIGILL: signame = "SIGILL"; break; case SIGTERM: signame = "SIGTERM"; break; default: sprintf(tmp, "signal %d", WTERMSIG(exit_code)); signame = tmp; break; } test_error_("Test interrupted by %s.", signame); } else { test_error_("Test ended in an unexpected way [%d].", exit_code); } } #elif defined(ACUTEST_WIN_) char buffer[512] = {0}; STARTUPINFOA startupInfo; PROCESS_INFORMATION processInfo; DWORD exitCode; /* Windows has no fork(). So we propagate all info into the child * through a command line arguments. */ _snprintf(buffer, sizeof(buffer)-1, "%s --worker=%d %s --no-exec --no-summary %s --verbose=%d --color=%s -- \"%s\"", test_argv0_, index, test_timer_ ? "--time" : "", test_tap_ ? "--tap" : "", test_verbose_level_, test_colorize_ ? "always" : "never", test->name); memset(&startupInfo, 0, sizeof(startupInfo)); startupInfo.cb = sizeof(STARTUPINFO); if(CreateProcessA(NULL, buffer, NULL, NULL, FALSE, 0, NULL, NULL, &startupInfo, &processInfo)) { WaitForSingleObject(processInfo.hProcess, INFINITE); GetExitCodeProcess(processInfo.hProcess, &exitCode); CloseHandle(processInfo.hThread); CloseHandle(processInfo.hProcess); failed = (exitCode != 0); if(exitCode > 1) { switch(exitCode) { case 3: test_error_("Aborted."); break; case 0xC0000005: test_error_("Access violation."); break; default: test_error_("Test ended in an unexpected way [%lu].", exitCode); break; } } } else { test_error_("Cannot create unit test subprocess [%ld].", GetLastError()); failed = 1; } #else /* A platform where we don't know how to run child process. */ failed = (test_do_run_(test, index) != 0); #endif } else { /* Child processes suppressed through --no-exec. */ failed = (test_do_run_(test, index) != 0); } test_timer_get_time_(&end); test_current_unit_ = NULL; test_stat_run_units_++; if(failed) test_stat_failed_units_++; test_set_success_(master_index, !failed); test_set_duration_(master_index, test_timer_diff_(start, end)); } #if defined(ACUTEST_WIN_) /* Callback for SEH events. */ static LONG CALLBACK test_seh_exception_filter_(EXCEPTION_POINTERS *ptrs) { test_check_(0, NULL, 0, "Unhandled SEH exception"); test_message_("Exception code: 0x%08lx", ptrs->ExceptionRecord->ExceptionCode); test_message_("Exception address: 0x%p", ptrs->ExceptionRecord->ExceptionAddress); fflush(stdout); fflush(stderr); return EXCEPTION_EXECUTE_HANDLER; } #endif #define TEST_CMDLINE_OPTFLAG_OPTIONALARG_ 0x0001 #define TEST_CMDLINE_OPTFLAG_REQUIREDARG_ 0x0002 #define TEST_CMDLINE_OPTID_NONE_ 0 #define TEST_CMDLINE_OPTID_UNKNOWN_ (-0x7fffffff + 0) #define TEST_CMDLINE_OPTID_MISSINGARG_ (-0x7fffffff + 1) #define TEST_CMDLINE_OPTID_BOGUSARG_ (-0x7fffffff + 2) typedef struct TEST_CMDLINE_OPTION_ { char shortname; const char* longname; int id; unsigned flags; } TEST_CMDLINE_OPTION_; static int test_cmdline_handle_short_opt_group_(const TEST_CMDLINE_OPTION_* options, const char* arggroup, int (*callback)(int /*optval*/, const char* /*arg*/)) { const TEST_CMDLINE_OPTION_* opt; int i; int ret = 0; for(i = 0; arggroup[i] != '\0'; i++) { for(opt = options; opt->id != 0; opt++) { if(arggroup[i] == opt->shortname) break; } if(opt->id != 0 && !(opt->flags & TEST_CMDLINE_OPTFLAG_REQUIREDARG_)) { ret = callback(opt->id, NULL); } else { /* Unknown option. */ char badoptname[3]; badoptname[0] = '-'; badoptname[1] = arggroup[i]; badoptname[2] = '\0'; ret = callback((opt->id != 0 ? TEST_CMDLINE_OPTID_MISSINGARG_ : TEST_CMDLINE_OPTID_UNKNOWN_), badoptname); } if(ret != 0) break; } return ret; } #define TEST_CMDLINE_AUXBUF_SIZE_ 32 static int test_cmdline_read_(const TEST_CMDLINE_OPTION_* options, int argc, char** argv, int (*callback)(int /*optval*/, const char* /*arg*/)) { const TEST_CMDLINE_OPTION_* opt; char auxbuf[TEST_CMDLINE_AUXBUF_SIZE_+1]; int after_doubledash = 0; int i = 1; int ret = 0; auxbuf[TEST_CMDLINE_AUXBUF_SIZE_] = '\0'; while(i < argc) { if(after_doubledash || strcmp(argv[i], "-") == 0) { /* Non-option argument. */ ret = callback(TEST_CMDLINE_OPTID_NONE_, argv[i]); } else if(strcmp(argv[i], "--") == 0) { /* End of options. All the remaining members are non-option arguments. */ after_doubledash = 1; } else if(argv[i][0] != '-') { /* Non-option argument. */ ret = callback(TEST_CMDLINE_OPTID_NONE_, argv[i]); } else { for(opt = options; opt->id != 0; opt++) { if(opt->longname != NULL && strncmp(argv[i], "--", 2) == 0) { size_t len = strlen(opt->longname); if(strncmp(argv[i]+2, opt->longname, len) == 0) { /* Regular long option. */ if(argv[i][2+len] == '\0') { /* with no argument provided. */ if(!(opt->flags & TEST_CMDLINE_OPTFLAG_REQUIREDARG_)) ret = callback(opt->id, NULL); else ret = callback(TEST_CMDLINE_OPTID_MISSINGARG_, argv[i]); break; } else if(argv[i][2+len] == '=') { /* with an argument provided. */ if(opt->flags & (TEST_CMDLINE_OPTFLAG_OPTIONALARG_ | TEST_CMDLINE_OPTFLAG_REQUIREDARG_)) { ret = callback(opt->id, argv[i]+2+len+1); } else { sprintf(auxbuf, "--%s", opt->longname); ret = callback(TEST_CMDLINE_OPTID_BOGUSARG_, auxbuf); } break; } else { continue; } } } else if(opt->shortname != '\0' && argv[i][0] == '-') { if(argv[i][1] == opt->shortname) { /* Regular short option. */ if(opt->flags & TEST_CMDLINE_OPTFLAG_REQUIREDARG_) { if(argv[i][2] != '\0') ret = callback(opt->id, argv[i]+2); else if(i+1 < argc) ret = callback(opt->id, argv[++i]); else ret = callback(TEST_CMDLINE_OPTID_MISSINGARG_, argv[i]); break; } else { ret = callback(opt->id, NULL); /* There might be more (argument-less) short options * grouped together. */ if(ret == 0 && argv[i][2] != '\0') ret = test_cmdline_handle_short_opt_group_(options, argv[i]+2, callback); break; } } } } if(opt->id == 0) { /* still not handled? */ if(argv[i][0] != '-') { /* Non-option argument. */ ret = callback(TEST_CMDLINE_OPTID_NONE_, argv[i]); } else { /* Unknown option. */ char* badoptname = argv[i]; if(strncmp(badoptname, "--", 2) == 0) { /* Strip any argument from the long option. */ char* assignment = strchr(badoptname, '='); if(assignment != NULL) { size_t len = assignment - badoptname; if(len > TEST_CMDLINE_AUXBUF_SIZE_) len = TEST_CMDLINE_AUXBUF_SIZE_; strncpy(auxbuf, badoptname, len); auxbuf[len] = '\0'; badoptname = auxbuf; } } ret = callback(TEST_CMDLINE_OPTID_UNKNOWN_, badoptname); } } } if(ret != 0) return ret; i++; } return ret; } static void test_help_(void) { printf("Usage: %s [options] [test...]\n", test_argv0_); printf("\n"); printf("Run the specified unit tests; or if the option '--skip' is used, run all\n"); printf("tests in the suite but those listed. By default, if no tests are specified\n"); printf("on the command line, all unit tests in the suite are run.\n"); printf("\n"); printf("Options:\n"); printf(" -s, --skip Execute all unit tests but the listed ones\n"); printf(" --exec[=WHEN] If supported, execute unit tests as child processes\n"); printf(" (WHEN is one of 'auto', 'always', 'never')\n"); printf(" -E, --no-exec Same as --exec=never\n"); #if defined ACUTEST_WIN_ printf(" -t, --time Measure test duration\n"); #elif defined ACUTEST_HAS_POSIX_TIMER_ printf(" -t, --time Measure test duration (real time)\n"); printf(" --time=TIMER Measure test duration, using given timer\n"); printf(" (TIMER is one of 'real', 'cpu')\n"); #endif printf(" --no-summary Suppress printing of test results summary\n"); printf(" --tap Produce TAP-compliant output\n"); printf(" (See https://testanything.org/)\n"); printf(" -x, --xml-output=FILE Enable XUnit output to the given file\n"); printf(" -l, --list List unit tests in the suite and exit\n"); printf(" -v, --verbose Make output more verbose\n"); printf(" --verbose=LEVEL Set verbose level to LEVEL:\n"); printf(" 0 ... Be silent\n"); printf(" 1 ... Output one line per test (and summary)\n"); printf(" 2 ... As 1 and failed conditions (this is default)\n"); printf(" 3 ... As 1 and all conditions (and extended summary)\n"); printf(" -q, --quiet Same as --verbose=0\n"); printf(" --color[=WHEN] Enable colorized output\n"); printf(" (WHEN is one of 'auto', 'always', 'never')\n"); printf(" --no-color Same as --color=never\n"); printf(" -h, --help Display this help and exit\n"); if(test_list_size_ < 16) { printf("\n"); test_list_names_(); } } static const TEST_CMDLINE_OPTION_ test_cmdline_options_[] = { { 's', "skip", 's', 0 }, { 0, "exec", 'e', TEST_CMDLINE_OPTFLAG_OPTIONALARG_ }, { 'E', "no-exec", 'E', 0 }, #if defined ACUTEST_WIN_ { 't', "time", 't', 0 }, { 0, "timer", 't', 0 }, /* kept for compatibility */ #elif defined ACUTEST_HAS_POSIX_TIMER_ { 't', "time", 't', TEST_CMDLINE_OPTFLAG_OPTIONALARG_ }, { 0, "timer", 't', TEST_CMDLINE_OPTFLAG_OPTIONALARG_ }, /* kept for compatibility */ #endif { 0, "no-summary", 'S', 0 }, { 0, "tap", 'T', 0 }, { 'l', "list", 'l', 0 }, { 'v', "verbose", 'v', TEST_CMDLINE_OPTFLAG_OPTIONALARG_ }, { 'q', "quiet", 'q', 0 }, { 0, "color", 'c', TEST_CMDLINE_OPTFLAG_OPTIONALARG_ }, { 0, "no-color", 'C', 0 }, { 'h', "help", 'h', 0 }, { 0, "worker", 'w', TEST_CMDLINE_OPTFLAG_REQUIREDARG_ }, /* internal */ { 'x', "xml-output", 'x', TEST_CMDLINE_OPTFLAG_REQUIREDARG_ }, { 0, NULL, 0, 0 } }; static int test_cmdline_callback_(int id, const char* arg) { switch(id) { case 's': test_skip_mode_ = 1; break; case 'e': if(arg == NULL || strcmp(arg, "always") == 0) { test_no_exec_ = 0; } else if(strcmp(arg, "never") == 0) { test_no_exec_ = 1; } else if(strcmp(arg, "auto") == 0) { /*noop*/ } else { fprintf(stderr, "%s: Unrecognized argument '%s' for option --exec.\n", test_argv0_, arg); fprintf(stderr, "Try '%s --help' for more information.\n", test_argv0_); test_exit_(2); } break; case 'E': test_no_exec_ = 1; break; case 't': #if defined ACUTEST_WIN_ || defined ACUTEST_HAS_POSIX_TIMER_ if(arg == NULL || strcmp(arg, "real") == 0) { test_timer_ = 1; #ifndef ACUTEST_WIN_ } else if(strcmp(arg, "cpu") == 0) { test_timer_ = 2; #endif } else { fprintf(stderr, "%s: Unrecognized argument '%s' for option --time.\n", test_argv0_, arg); fprintf(stderr, "Try '%s --help' for more information.\n", test_argv0_); test_exit_(2); } #endif break; case 'S': test_no_summary_ = 1; break; case 'T': test_tap_ = 1; break; case 'l': test_list_names_(); test_exit_(0); break; case 'v': test_verbose_level_ = (arg != NULL ? atoi(arg) : test_verbose_level_+1); break; case 'q': test_verbose_level_ = 0; break; case 'c': if(arg == NULL || strcmp(arg, "always") == 0) { test_colorize_ = 1; } else if(strcmp(arg, "never") == 0) { test_colorize_ = 0; } else if(strcmp(arg, "auto") == 0) { /*noop*/ } else { fprintf(stderr, "%s: Unrecognized argument '%s' for option --color.\n", test_argv0_, arg); fprintf(stderr, "Try '%s --help' for more information.\n", test_argv0_); test_exit_(2); } break; case 'C': test_colorize_ = 0; break; case 'h': test_help_(); test_exit_(0); break; case 'w': test_worker_ = 1; test_worker_index_ = atoi(arg); break; case 'x': test_xml_output_ = fopen(arg, "w"); if (!test_xml_output_) { fprintf(stderr, "Unable to open '%s': %s\n", arg, strerror(errno)); test_exit_(2); } break; case 0: if(test_lookup_(arg) == 0) { fprintf(stderr, "%s: Unrecognized unit test '%s'\n", test_argv0_, arg); fprintf(stderr, "Try '%s --list' for list of unit tests.\n", test_argv0_); test_exit_(2); } break; case TEST_CMDLINE_OPTID_UNKNOWN_: fprintf(stderr, "Unrecognized command line option '%s'.\n", arg); fprintf(stderr, "Try '%s --help' for more information.\n", test_argv0_); test_exit_(2); break; case TEST_CMDLINE_OPTID_MISSINGARG_: fprintf(stderr, "The command line option '%s' requires an argument.\n", arg); fprintf(stderr, "Try '%s --help' for more information.\n", test_argv0_); test_exit_(2); break; case TEST_CMDLINE_OPTID_BOGUSARG_: fprintf(stderr, "The command line option '%s' does not expect an argument.\n", arg); fprintf(stderr, "Try '%s --help' for more information.\n", test_argv0_); test_exit_(2); break; } return 0; } #ifdef ACUTEST_LINUX_ static int test_is_tracer_present_(void) { /* Must be large enough so the line 'TracerPid: ${PID}' can fit in. */ static const int OVERLAP = 32; char buf[256+OVERLAP+1]; int tracer_present = 0; int fd; size_t n_read = 0; fd = open("/proc/self/status", O_RDONLY); if(fd == -1) return 0; while(1) { static const char pattern[] = "TracerPid:"; const char* field; while(n_read < sizeof(buf) - 1) { ssize_t n; n = read(fd, buf + n_read, sizeof(buf) - 1 - n_read); if(n <= 0) break; n_read += n; } buf[n_read] = '\0'; field = strstr(buf, pattern); if(field != NULL && field < buf + sizeof(buf) - OVERLAP) { pid_t tracer_pid = (pid_t) atoi(field + sizeof(pattern) - 1); tracer_present = (tracer_pid != 0); break; } if(n_read == sizeof(buf)-1) { memmove(buf, buf + sizeof(buf)-1 - OVERLAP, OVERLAP); n_read = OVERLAP; } else { break; } } close(fd); return tracer_present; } #endif int main(int argc, char** argv) { int i; test_argv0_ = argv[0]; #if defined ACUTEST_UNIX_ test_colorize_ = isatty(STDOUT_FILENO); #elif defined ACUTEST_WIN_ #if defined _BORLANDC_ test_colorize_ = isatty(_fileno(stdout)); #else test_colorize_ = _isatty(_fileno(stdout)); #endif #else test_colorize_ = 0; #endif /* Count all test units */ test_list_size_ = 0; for(i = 0; test_list_[i].func != NULL; i++) test_list_size_++; test_details_ = (struct test_detail_*)calloc(test_list_size_, sizeof(struct test_detail_)); if(test_details_ == NULL) { fprintf(stderr, "Out of memory.\n"); test_exit_(2); } /* Parse options */ test_cmdline_read_(test_cmdline_options_, argc, argv, test_cmdline_callback_); /* Initialize the proper timer. */ test_timer_init_(); #if defined(ACUTEST_WIN_) SetUnhandledExceptionFilter(test_seh_exception_filter_); #ifdef _MSC_VER _set_abort_behavior(0, _WRITE_ABORT_MSG); #endif #endif /* By default, we want to run all tests. */ if(test_count_ == 0) { for(i = 0; test_list_[i].func != NULL; i++) test_remember_(i); } /* Guess whether we want to run unit tests as child processes. */ if(test_no_exec_ < 0) { test_no_exec_ = 0; if(test_count_ <= 1) { test_no_exec_ = 1; } else { #ifdef ACUTEST_WIN_ if(IsDebuggerPresent()) test_no_exec_ = 1; #endif #ifdef ACUTEST_LINUX_ if(test_is_tracer_present_()) test_no_exec_ = 1; #endif #ifdef RUNNING_ON_VALGRIND /* RUNNING_ON_VALGRIND is provided by valgrind.h */ if(RUNNING_ON_VALGRIND) test_no_exec_ = 1; #endif } } if(test_tap_) { /* TAP requires we know test result ("ok", "not ok") before we output * anything about the test, and this gets problematic for larger verbose * levels. */ if(test_verbose_level_ > 2) test_verbose_level_ = 2; /* TAP harness should provide some summary. */ test_no_summary_ = 1; if(!test_worker_) printf("1..%d\n", (int) test_count_); } int index = test_worker_index_; for(i = 0; test_list_[i].func != NULL; i++) { int run = (test_details_[i].flags & TEST_FLAG_RUN_); if (test_skip_mode_) /* Run all tests except those listed. */ run = !run; if(run) test_run_(&test_list_[i], index++, i); } /* Write a summary */ if(!test_no_summary_ && test_verbose_level_ >= 1) { if(test_verbose_level_ >= 3) { test_print_in_color_(TEST_COLOR_DEFAULT_INTENSIVE_, "Summary:\n"); printf(" Count of all unit tests: %4d\n", (int) test_list_size_); printf(" Count of run unit tests: %4d\n", test_stat_run_units_); printf(" Count of failed unit tests: %4d\n", test_stat_failed_units_); printf(" Count of skipped unit tests: %4d\n", (int) test_list_size_ - test_stat_run_units_); } if(test_stat_failed_units_ == 0) { test_print_in_color_(TEST_COLOR_GREEN_INTENSIVE_, "SUCCESS:"); printf(" All unit tests have passed.\n"); } else { test_print_in_color_(TEST_COLOR_RED_INTENSIVE_, "FAILED:"); printf(" %d of %d unit tests %s failed.\n", test_stat_failed_units_, test_stat_run_units_, (test_stat_failed_units_ == 1) ? "has" : "have"); } if(test_verbose_level_ >= 3) printf("\n"); } if (test_xml_output_) { #if defined ACUTEST_UNIX_ char *suite_name = basename(argv[0]); #elif defined ACUTEST_WIN_ char suite_name[_MAX_FNAME]; _splitpath(argv[0], NULL, NULL, suite_name, NULL); #else const char *suite_name = argv[0]; #endif fprintf(test_xml_output_, "\n"); fprintf(test_xml_output_, "\n", suite_name, (int)test_list_size_, test_stat_failed_units_, test_stat_failed_units_, (int)test_list_size_ - test_stat_run_units_); for(i = 0; test_list_[i].func != NULL; i++) { struct test_detail_ *details = &test_details_[i]; fprintf(test_xml_output_, " \n", test_list_[i].name, details->duration); if (details->flags & TEST_FLAG_FAILURE_) fprintf(test_xml_output_, " \n"); if (!(details->flags & TEST_FLAG_FAILURE_) && !(details->flags & TEST_FLAG_SUCCESS_)) fprintf(test_xml_output_, " \n"); fprintf(test_xml_output_, " \n"); } fprintf(test_xml_output_, "\n"); fclose(test_xml_output_); } test_cleanup_(); return (test_stat_failed_units_ == 0) ? 0 : 1; } #endif /* #ifndef TEST_NO_MAIN */ #ifdef _MSC_VER #pragma warning(pop) #endif #ifdef __cplusplus } /* extern "C" */ #endif #endif /* #ifndef ACUTEST_H */ mergerfs-2.33.5/tests/tests.cpp0000644000000000000000000001601114225522122015120 0ustar rootroot#include "acutest.h" #include "config.hpp" void test_nop() { TEST_CHECK(true); } void test_config_bool() { ConfigBOOL v; TEST_CHECK(v.from_string("true") == 0); TEST_CHECK(v == true); TEST_CHECK(v.to_string() == "true"); TEST_CHECK(v.from_string("false") == 0); TEST_CHECK(v == false); TEST_CHECK(v.to_string() == "false"); TEST_CHECK(v.from_string("asdf") == -EINVAL); } void test_config_uint64() { ConfigUINT64 v; TEST_CHECK(v.from_string("0") == 0); TEST_CHECK(v == (uint64_t)0); TEST_CHECK(v.to_string() == "0"); TEST_CHECK(v.from_string("123") == 0); TEST_CHECK(v == (uint64_t)123); TEST_CHECK(v.to_string() == "123"); TEST_CHECK(v.from_string("1234567890") == 0); TEST_CHECK(v == (uint64_t)1234567890); TEST_CHECK(v.to_string() == "1234567890"); TEST_CHECK(v.from_string("asdf") == -EINVAL); } void test_config_int() { ConfigINT v; TEST_CHECK(v.from_string("0") == 0); TEST_CHECK(v == (int)0); TEST_CHECK(v.to_string() == "0"); TEST_CHECK(v.from_string("123") == 0); TEST_CHECK(v == (int)123); TEST_CHECK(v.to_string() == "123"); TEST_CHECK(v.from_string("1234567890") == 0); TEST_CHECK(v == (int)1234567890); TEST_CHECK(v.to_string() == "1234567890"); TEST_CHECK(v.from_string("asdf") == -EINVAL); } void test_config_str() { ConfigSTR v; TEST_CHECK(v.from_string("foo") == 0); TEST_CHECK(v == "foo"); TEST_CHECK(v.to_string() == "foo"); } void test_config_branches() { uint64_t minfreespace; Branches b(minfreespace); Branches::CPtr bcp0; Branches::CPtr bcp1; minfreespace = 1234; TEST_CHECK(b->minfreespace() == 1234); TEST_CHECK(b.to_string() == ""); bcp0 = b; TEST_CHECK(b.from_string("/foo/bar") == 0); TEST_CHECK(b.to_string() == "/foo/bar=RW"); bcp1 = b; TEST_CHECK(bcp0.get() != bcp1.get()); TEST_CHECK(b.from_string("/foo/bar=RW,1234") == 0); TEST_CHECK(b.to_string() == "/foo/bar=RW,1234"); TEST_CHECK(b.from_string("/foo/bar=RO,1234:/foo/baz=NC,4321") == 0); TEST_CHECK(b.to_string() == "/foo/bar=RO,1234:/foo/baz=NC,4321"); bcp0 = b; TEST_CHECK((*bcp0).size() == 2); TEST_CHECK((*bcp0)[0].path == "/foo/bar"); TEST_CHECK((*bcp0)[0].minfreespace() == 1234); TEST_MSG("minfreespace: expected = %lu; produced = %lu", 1234, (*bcp0)[0].minfreespace()); TEST_CHECK((*bcp0)[1].path == "/foo/baz"); TEST_CHECK((*bcp0)[1].minfreespace() == 4321); TEST_MSG("minfreespace: expected = %lu; produced = %lu", 4321, (*bcp0)[1].minfreespace()); TEST_CHECK(b.from_string("foo/bar") == 0); TEST_CHECK(b.from_string("./foo/bar") == 0); TEST_CHECK(b.from_string("./foo/bar:/bar/baz:blah/asdf") == 0); } void test_config_cachefiles() { CacheFiles cf; TEST_CHECK(cf.from_string("libfuse") == 0); TEST_CHECK(cf.to_string() == "libfuse"); TEST_CHECK(cf.from_string("off") == 0); TEST_CHECK(cf.to_string() == "off"); TEST_CHECK(cf.from_string("partial") == 0); TEST_CHECK(cf.to_string() == "partial"); TEST_CHECK(cf.from_string("full") == 0); TEST_CHECK(cf.to_string() == "full"); TEST_CHECK(cf.from_string("auto-full") == 0); TEST_CHECK(cf.to_string() == "auto-full"); TEST_CHECK(cf.from_string("foobar") == -EINVAL); } void test_config_inodecalc() { InodeCalc ic("passthrough"); TEST_CHECK(ic.from_string("passthrough") == 0); TEST_CHECK(ic.to_string() == "passthrough"); TEST_CHECK(ic.from_string("path-hash") == 0); TEST_CHECK(ic.to_string() == "path-hash"); TEST_CHECK(ic.from_string("devino-hash") == 0); TEST_CHECK(ic.to_string() == "devino-hash"); TEST_CHECK(ic.from_string("hybrid-hash") == 0); TEST_CHECK(ic.to_string() == "hybrid-hash"); TEST_CHECK(ic.from_string("path-hash32") == 0); TEST_CHECK(ic.to_string() == "path-hash32"); TEST_CHECK(ic.from_string("devino-hash32") == 0); TEST_CHECK(ic.to_string() == "devino-hash32"); TEST_CHECK(ic.from_string("hybrid-hash32") == 0); TEST_CHECK(ic.to_string() == "hybrid-hash32"); TEST_CHECK(ic.from_string("asdf") == -EINVAL); } void test_config_moveonenospc() { MoveOnENOSPC m(false); TEST_CHECK(m.to_string() == "false"); TEST_CHECK(m.from_string("mfs") == 0); TEST_CHECK(m.to_string() == "mfs"); TEST_CHECK(m.from_string("mspmfs") == 0); TEST_CHECK(m.to_string() == "mspmfs"); TEST_CHECK(m.from_string("true") == 0); TEST_CHECK(m.to_string() == "mfs"); TEST_CHECK(m.from_string("blah") == -EINVAL); } void test_config_nfsopenhack() { NFSOpenHack n; TEST_CHECK(n.from_string("off") == 0); TEST_CHECK(n.to_string() == "off"); TEST_CHECK(n == NFSOpenHack::ENUM::OFF); TEST_CHECK(n.from_string("git") == 0); TEST_CHECK(n.to_string() == "git"); TEST_CHECK(n == NFSOpenHack::ENUM::GIT); TEST_CHECK(n.from_string("all") == 0); TEST_CHECK(n.to_string() == "all"); TEST_CHECK(n == NFSOpenHack::ENUM::ALL); } void test_config_readdir() { ReadDir r; TEST_CHECK(r.from_string("linux") == 0); TEST_CHECK(r.to_string() == "linux"); TEST_CHECK(r == ReadDir::ENUM::LINUX); TEST_CHECK(r.from_string("posix") == 0); TEST_CHECK(r.to_string() == "posix"); TEST_CHECK(r == ReadDir::ENUM::POSIX); } void test_config_statfs() { StatFS s; TEST_CHECK(s.from_string("base") == 0); TEST_CHECK(s.to_string() == "base"); TEST_CHECK(s == StatFS::ENUM::BASE); TEST_CHECK(s.from_string("full") == 0); TEST_CHECK(s.to_string() == "full"); TEST_CHECK(s == StatFS::ENUM::FULL); TEST_CHECK(s.from_string("asdf") == -EINVAL); } void test_config_statfs_ignore() { StatFSIgnore s; TEST_CHECK(s.from_string("none") == 0); TEST_CHECK(s.to_string() == "none"); TEST_CHECK(s == StatFSIgnore::ENUM::NONE); TEST_CHECK(s.from_string("ro") == 0); TEST_CHECK(s.to_string() == "ro"); TEST_CHECK(s == StatFSIgnore::ENUM::RO); TEST_CHECK(s.from_string("nc") == 0); TEST_CHECK(s.to_string() == "nc"); TEST_CHECK(s == StatFSIgnore::ENUM::NC); TEST_CHECK(s.from_string("asdf") == -EINVAL); } void test_config_xattr() { XAttr x; TEST_CHECK(x.from_string("passthrough") == 0); TEST_CHECK(x.to_string() == "passthrough"); TEST_CHECK(x == XAttr::ENUM::PASSTHROUGH); TEST_CHECK(x.from_string("noattr") == 0); TEST_CHECK(x.to_string() == "noattr"); TEST_CHECK(x == XAttr::ENUM::NOATTR); TEST_CHECK(x.from_string("nosys") == 0); TEST_CHECK(x.to_string() == "nosys"); TEST_CHECK(x == XAttr::ENUM::NOSYS); TEST_CHECK(x.from_string("asdf") == -EINVAL); } void test_config() { Config cfg; TEST_CHECK(cfg.set_raw("async_read","true") == 0); } TEST_LIST = { {"nop",test_nop}, {"config_bool",test_config_bool}, {"config_uint64",test_config_uint64}, {"config_int",test_config_int}, {"config_str",test_config_str}, {"config_branches",test_config_branches}, {"config_cachefiles",test_config_cachefiles}, {"config_inodecalc",test_config_inodecalc}, {"config_moveonenospc",test_config_moveonenospc}, {"config_nfsopenhack",test_config_nfsopenhack}, {"config_readdir",test_config_readdir}, {"config_statfs",test_config_statfs}, {"config_statfsignore",test_config_statfs_ignore}, {"config_xattr",test_config_xattr}, {"config",test_config}, {NULL,NULL} }; mergerfs-2.33.5/tests/run-tests0000755000000000000000000000144114225522122015145 0ustar rootroot#!/usr/bin/env python3 import os import sys import subprocess if len(sys.argv) != 2: print('usage: run-test \n',file=sys.stderr) sys.exit(1) test_path = os.path.realpath(sys.argv[0]) test_path = os.path.dirname(test_path) for entry in os.scandir(test_path): if not entry.name.startswith('TEST_'): continue try: print(entry.name + ': ',end='') fullpath = os.path.join(test_path,entry.name) args = [fullpath,sys.argv[1]] rv = subprocess.Popen(args,stdout=subprocess.PIPE) rv.wait(timeout=10000) if rv.returncode: output = rv.stdout.read().decode() print('FAIL - {}'.format(output)) else: print('PASS') except Exception as e: print('FAIL - {}'.format(e)) mergerfs-2.33.5/tests/TEST_unlink_rename0000755000000000000000000000034214225522122016666 0ustar rootroot#!/usr/bin/env python3 import os import sys import tempfile (srcfd,src) = tempfile.mkstemp(dir=sys.argv[1]) (tgtfd,tgt) = tempfile.mkstemp(dir=sys.argv[1]) os.unlink(tgt) os.rename(src,tgt) os.close(srcfd) os.close(tgtfd) mergerfs-2.33.5/src/0000755000000000000000000000000014225522122012700 5ustar rootrootmergerfs-2.33.5/src/config_statfsignore.hpp0000644000000000000000000000166314225522122017454 0ustar rootroot/* ISC License Copyright (c) 2020, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include "enum.hpp" enum class StatFSIgnoreEnum { NONE, RO, NC }; typedef Enum StatFSIgnore; mergerfs-2.33.5/src/func.cpp0000644000000000000000000000270414225522122014342 0ustar rootroot/* ISC License Copyright (c) 2019, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "func.hpp" int Func::Base::Action::from_string(const std::string &policyname_) { policy = Policies::Action::find(policyname_); return 0; } std::string Func::Base::Action::to_string(void) const { return policy.name(); } int Func::Base::Create::from_string(const std::string &policyname_) { policy = Policies::Create::find(policyname_); return 0; } std::string Func::Base::Create::to_string(void) const { return policy.name(); } int Func::Base::Search::from_string(const std::string &policyname_) { policy = Policies::Search::find(policyname_); return 0; } std::string Func::Base::Search::to_string(void) const { return policy.name(); } mergerfs-2.33.5/src/fs_lchown.hpp0000644000000000000000000000377314225522122015405 0ustar rootroot/* ISC License Copyright (c) 2020, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include "fs_lstat.hpp" #include namespace fs { static inline int lchown(const char *pathname_, const uid_t uid_, const gid_t gid_) { return ::lchown(pathname_,uid_,gid_); } static inline int lchown(const std::string &pathname_, const uid_t uid_, const gid_t gid_) { return fs::lchown(pathname_.c_str(),uid_,gid_); } static inline int lchown(const char *pathname_, const struct stat &st_) { return fs::lchown(pathname_,st_.st_uid,st_.st_gid); } static inline int lchown(const std::string &pathname_, const struct stat &st_) { return fs::lchown(pathname_.c_str(),st_); } static inline int lchown_check_on_error(const std::string &path_, const struct stat &st_) { int rv; rv = fs::lchown(path_,st_); if(rv == -1) { int error; struct stat tmpst; error = errno; rv = fs::lstat(path_,&tmpst); if(rv == -1) return -1; if((st_.st_uid != tmpst.st_uid) || (st_.st_gid != tmpst.st_gid)) return (errno=error,-1); } return 0; } } mergerfs-2.33.5/src/fs_flock.hpp0000644000000000000000000000172414225522122015203 0ustar rootroot/* ISC License Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include namespace fs { static inline int flock(const int fd_, const int operation_) { return ::flock(fd_,operation_); } } mergerfs-2.33.5/src/policy_msplus.hpp0000644000000000000000000000314414225522122016315 0ustar rootroot/* ISC License Copyright (c) 2020, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include "policy.hpp" namespace Policy { namespace MSPLUS { class Action final : public Policy::ActionImpl { public: Action() : Policy::ActionImpl("msplus") {} public: int operator()(const Branches::CPtr&,const char*,StrVec*) const final; }; class Create final : public Policy::CreateImpl { public: Create() : Policy::CreateImpl("msplus") {} public: int operator()(const Branches::CPtr&,const char*,StrVec*) const final; bool path_preserving() const final { return true; } }; class Search final : public Policy::SearchImpl { public: Search() : Policy::SearchImpl("msplus") {} public: int operator()(const Branches::CPtr&,const char*,StrVec*) const final; }; } } mergerfs-2.33.5/src/resources.cpp0000644000000000000000000000337314225522122015424 0ustar rootroot/* Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include namespace resources { int reset_umask(void) { umask(0); return 0; } int maxout_rlimit(const int resource) { int rv; struct rlimit rlim; rlim.rlim_cur = RLIM_INFINITY; rlim.rlim_max = RLIM_INFINITY; rv = ::setrlimit(resource,&rlim); if(rv == 0) return 0; rv = ::getrlimit(resource,&rlim); if(rv == -1) return -1; rv = 0; rlim.rlim_cur = rlim.rlim_max; while(rv == 0) { rv = ::setrlimit(resource,&rlim); rlim.rlim_max *= 2; rlim.rlim_cur = rlim.rlim_max; } return rv; } int maxout_rlimit_nofile(void) { return maxout_rlimit(RLIMIT_NOFILE); } int maxout_rlimit_fsize(void) { return maxout_rlimit(RLIMIT_FSIZE); } int setpriority(const int prio) { const int SELF = 0; return ::setpriority(PRIO_PROCESS,SELF,prio); } } mergerfs-2.33.5/src/fh.hpp0000644000000000000000000000165414225522122014014 0ustar rootroot/* Copyright (c) 2020, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include class FH { public: FH(const char *fusepath_) : fusepath(fusepath_) { } public: std::string fusepath; }; mergerfs-2.33.5/src/policy_lfs.hpp0000644000000000000000000000313114225522122015552 0ustar rootroot/* ISC License Copyright (c) 2020, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include "policy.hpp" namespace Policy { namespace LFS { class Action final : public Policy::ActionImpl { public: Action() : Policy::ActionImpl("lfs") {} public: int operator()(const Branches::CPtr&,const char*,StrVec*) const final; }; class Create final : public Policy::CreateImpl { public: Create() : Policy::CreateImpl("lfs") {} public: int operator()(const Branches::CPtr&,const char*,StrVec*) const final; bool path_preserving() const final { return false; } }; class Search final : public Policy::SearchImpl { public: Search() : Policy::SearchImpl("lfs") {} public: int operator()(const Branches::CPtr&,const char*,StrVec*) const final; }; } } mergerfs-2.33.5/src/fs_fchmodat.hpp0000644000000000000000000000253014225522122015666 0ustar rootroot/* ISC License Copyright (c) 2020, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include #include #include namespace fs { static inline int fchmodat(const int dirfd_, const char *pathname_, const mode_t mode_, const int flags_) { return ::fchmodat(dirfd_,pathname_,mode_,flags_); } static inline int fchmodat(const int dirfd_, const std::string &pathname_, const mode_t mode_, const int flags_) { return fs::fchmodat(dirfd_,pathname_.c_str(),mode_,flags_); } } mergerfs-2.33.5/src/policy_epff.cpp0000644000000000000000000000727514225522122015716 0ustar rootroot/* Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "branches.hpp" #include "errno.hpp" #include "fs_exists.hpp" #include "fs_info.hpp" #include "fs_path.hpp" #include "fs_statvfs_cache.hpp" #include "policy.hpp" #include "policy_epff.hpp" #include "policy_error.hpp" #include "rwlock.hpp" #include #include using std::string; using std::vector; namespace epff { static int create(const Branches::CPtr &branches_, const char *fusepath_, StrVec *paths_) { int rv; int error; fs::info_t info; error = ENOENT; for(auto &branch : *branches_) { if(branch.ro_or_nc()) error_and_continue(error,EROFS); if(!fs::exists(branch.path,fusepath_)) error_and_continue(error,ENOENT); rv = fs::info(branch.path,&info); if(rv == -1) error_and_continue(error,ENOENT); if(info.readonly) error_and_continue(error,EROFS); if(info.spaceavail < branch.minfreespace()) error_and_continue(error,ENOSPC); paths_->push_back(branch.path); return 0; } return (errno=error,-1); } static int action(const Branches::CPtr &branches_, const char *fusepath_, StrVec *paths_) { int rv; int error; bool readonly; error = ENOENT; for(auto &branch : *branches_) { if(branch.ro()) error_and_continue(error,EROFS); if(!fs::exists(branch.path,fusepath_)) error_and_continue(error,ENOENT); rv = fs::statvfs_cache_readonly(branch.path,&readonly); if(rv == -1) error_and_continue(error,ENOENT); if(readonly) error_and_continue(error,EROFS); paths_->push_back(branch.path); return 0; } return (errno=error,-1); } static int search(const Branches::CPtr &branches_, const char *fusepath_, StrVec *paths_) { for(auto &branch : *branches_) { if(!fs::exists(branch.path,fusepath_)) continue; paths_->push_back(branch.path); return 0; } return (errno=ENOENT,-1); } } int Policy::EPFF::Action::operator()(const Branches::CPtr &branches_, const char *fusepath_, StrVec *paths_) const { return ::epff::action(branches_,fusepath_,paths_); } int Policy::EPFF::Create::operator()(const Branches::CPtr &branches_, const char *fusepath_, StrVec *paths_) const { return ::epff::create(branches_,fusepath_,paths_); } int Policy::EPFF::Search::operator()(const Branches::CPtr &branches_, const char *fusepath_, StrVec *paths_) const { return ::epff::search(branches_,fusepath_,paths_); } mergerfs-2.33.5/src/fs_rename.hpp0000644000000000000000000000220514225522122015347 0ustar rootroot/* ISC License Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include namespace fs { static inline int rename(const char *oldpath_, const char *newpath_) { return ::rename(oldpath_,newpath_); } static inline int rename(const std::string &oldpath_, const std::string &newpath_) { return fs::rename(oldpath_.c_str(),newpath_.c_str()); } } mergerfs-2.33.5/src/fs_lsetxattr.hpp0000644000000000000000000000331314225522122016133 0ustar rootroot/* ISC License Copyright (c) 2020, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include "errno.hpp" #include "xattr.hpp" #include #include namespace fs { static inline int lsetxattr(const char *path_, const char *name_, const void *value_, const size_t size_, const int flags_) { #ifdef USE_XATTR return ::lsetxattr(path_, name_, value_, size_, flags_); #else return (errno=ENOTSUP,-1); #endif } static inline int lsetxattr(const std::string &path_, const std::string &name_, const void *value_, const size_t size_, const int flags_) { return fs::lsetxattr(path_.c_str(), name_.c_str(), value_, size_, flags_); } } mergerfs-2.33.5/src/fuse_statfs.hpp0000644000000000000000000000163414225522122015743 0ustar rootroot/* Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include namespace FUSE { int statfs(const char *fusepath, struct statvfs *fsstat); } mergerfs-2.33.5/src/fuse_futimens.hpp0000644000000000000000000000167214225522122016273 0ustar rootroot/* Copyright (c) 2019, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include "fuse.h" #include namespace FUSE { int futimens(const fuse_file_info_t *ffi, const timespec ts[2]); } mergerfs-2.33.5/src/mempools.cpp0000644000000000000000000000157314225522122015245 0ustar rootroot/* ISC License Copyright (c) 2020, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "locked_fixed_mem_pool.hpp" LockedFixedMemPool<128 * 1024> g_DENTS_BUF_POOL; mergerfs-2.33.5/src/policy_rand.hpp0000644000000000000000000000313514225522122015716 0ustar rootroot/* ISC License Copyright (c) 2020, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include "policy.hpp" namespace Policy { namespace Rand { class Action final : public Policy::ActionImpl { public: Action() : Policy::ActionImpl("rand") {} public: int operator()(const Branches::CPtr&,const char*,StrVec*) const final; }; class Create final : public Policy::CreateImpl { public: Create() : Policy::CreateImpl("rand") {} public: int operator()(const Branches::CPtr&,const char*,StrVec*) const final; bool path_preserving() const final { return false; } }; class Search final : public Policy::SearchImpl { public: Search() : Policy::SearchImpl("rand") {} public: int operator()(const Branches::CPtr&,const char*,StrVec*) const final; }; } } mergerfs-2.33.5/src/fs_futimens.hpp0000644000000000000000000000260014225522122015731 0ustar rootroot/* ISC License Copyright (c) 2020, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include "fs_stat_utils.hpp" #include #ifdef __linux__ #warning "using fs_futimens_linux.hpp" #include "fs_futimens_linux.hpp" #elif __FreeBSD__ >= 11 #warning "using fs_futimens_freebsd_11.hpp" #include "fs_futimens_freebsd_11.hpp" #else #warning "using fs_futimens_generic.hpp" #include "fs_futimens_generic.hpp" #endif namespace fs { static inline int futimens(const int fd_, const struct stat &st_) { struct timespec ts[2]; ts[0] = *fs::stat_atime(&st_); ts[1] = *fs::stat_mtime(&st_); return fs::futimens(fd_,ts); } } mergerfs-2.33.5/src/branch.cpp0000644000000000000000000000372114225522122014644 0ustar rootroot/* ISC License Copyright (c) 2018, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "branch.hpp" #include "ef.hpp" #include "errno.hpp" #include "num.hpp" Branch::Branch(const uint64_t &default_minfreespace_) : _default_minfreespace(&default_minfreespace_) { } int Branch::from_string(const std::string &str_) { return -EINVAL; } std::string Branch::to_string(void) const { std::string rv; rv = path; rv += '='; switch(mode) { default: case Branch::Mode::RW: rv += "RW"; break; case Branch::Mode::RO: rv += "RO"; break; case Branch::Mode::NC: rv += "NC"; break; } if(_minfreespace.has_value()) { rv += ','; rv += num::humanize(_minfreespace.value()); } return rv; } void Branch::set_minfreespace(const uint64_t minfreespace_) { _minfreespace = minfreespace_; } uint64_t Branch::minfreespace(void) const { if(_minfreespace.has_value()) return _minfreespace.value(); return *_default_minfreespace; } bool Branch::ro(void) const { return (mode == Branch::Mode::RO); } bool Branch::nc(void) const { return (mode == Branch::Mode::NC); } bool Branch::ro_or_nc(void) const { return ((mode == Branch::Mode::RO) || (mode == Branch::Mode::NC)); } mergerfs-2.33.5/src/policy_lus.hpp0000644000000000000000000000313114225522122015571 0ustar rootroot/* ISC License Copyright (c) 2020, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include "policy.hpp" namespace Policy { namespace LUS { class Action final : public Policy::ActionImpl { public: Action() : Policy::ActionImpl("lus") {} public: int operator()(const Branches::CPtr&,const char*,StrVec*) const final; }; class Create final : public Policy::CreateImpl { public: Create() : Policy::CreateImpl("lus") {} public: int operator()(const Branches::CPtr&,const char*,StrVec*) const final; bool path_preserving() const final { return false; } }; class Search final : public Policy::SearchImpl { public: Search() : Policy::SearchImpl("lus") {} public: int operator()(const Branches::CPtr&,const char*,StrVec*) const final; }; } } mergerfs-2.33.5/src/fuse_readdir_plus_posix.cpp0000644000000000000000000000721014225522122020325 0ustar rootroot/* Copyright (c) 2019, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #define _DEFAULT_SOURCE #include "branches.hpp" #include "errno.hpp" #include "fs_closedir.hpp" #include "fs_devid.hpp" #include "fs_dirfd.hpp" #include "fs_fstatat.hpp" #include "fs_inode.hpp" #include "fs_opendir.hpp" #include "fs_path.hpp" #include "fs_readdir.hpp" #include "fs_stat.hpp" #include "hashset.hpp" #include "fuse.h" #include "fuse_dirents.h" #include #include #include using std::string; using std::vector; namespace l { static uint64_t dirent_exact_namelen(const struct dirent *d_) { #ifdef _D_EXACT_NAMLEN return _D_EXACT_NAMLEN(d_); #elif defined _DIRENT_HAVE_D_NAMLEN return d_->d_namlen; #else return strlen(d_->d_name); #endif } static int readdir_plus(const Branches::CPtr &branches_, const char *dirname_, const uint64_t entry_timeout_, const uint64_t attr_timeout_, fuse_dirents_t *buf_) { dev_t dev; HashSet names; string basepath; string fullpath; struct stat st; uint64_t namelen; fuse_entry_t entry; fuse_dirents_reset(buf_); entry.nodeid = 0; entry.generation = 0; entry.entry_valid = entry_timeout_; entry.attr_valid = attr_timeout_; entry.entry_valid_nsec = 0; entry.attr_valid_nsec = 0; for(auto &branch : *branches_) { int rv; int dirfd; DIR *dh; basepath = fs::path::make(branch.path,dirname_); dh = fs::opendir(basepath); if(!dh) continue; dirfd = fs::dirfd(dh); dev = fs::devid(dirfd); rv = 0; for(struct dirent *de = fs::readdir(dh); de && !rv; de = fs::readdir(dh)) { namelen = l::dirent_exact_namelen(de); rv = names.put(de->d_name,namelen); if(rv == 0) continue; rv = fs::fstatat_nofollow(dirfd,de->d_name,&st); if(rv == -1) { memset(&st,0,sizeof(st)); st.st_ino = de->d_ino; st.st_dev = dev; st.st_mode = DTTOIF(de->d_type); } fullpath = fs::path::make(dirname_,de->d_name); fs::inode::calc(fullpath,&st); de->d_ino = st.st_ino; rv = fuse_dirents_add_plus(buf_,de,namelen,&entry,&st); if(rv) return (fs::closedir(dh),-ENOMEM); } fs::closedir(dh); } return 0; } } namespace FUSE { int readdir_plus_posix(const Branches::CPtr &branches_, const char *dirname_, const uint64_t entry_timeout_, const uint64_t attr_timeout_, fuse_dirents_t *buf_) { return l::readdir_plus(branches_,dirname_,entry_timeout_,attr_timeout_,buf_); } } mergerfs-2.33.5/src/fs_stat.hpp0000644000000000000000000000222414225522122015054 0ustar rootroot/* ISC License Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include #include #include #include namespace fs { static inline int stat(const char *path_, struct stat *st_) { return ::stat(path_,st_); } static inline int stat(const std::string &path_, struct stat *st_) { return fs::stat(path_.c_str(),st_); } } mergerfs-2.33.5/src/ugid_rwlock.hpp0000644000000000000000000000413614225522122015726 0ustar rootroot/* Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include #include #include #include namespace ugid { extern uid_t currentuid; extern gid_t currentgid; extern pthread_rwlock_t rwlock; static void ugid_set(const uid_t newuid_, const gid_t newgid_) { pthread_rwlock_rdlock(&rwlock); if((newuid_ == currentuid) && (newgid_ == currentgid)) return; pthread_rwlock_unlock(&rwlock); pthread_rwlock_wrlock(&rwlock); if((newuid_ == currentuid) && (newgid_ == currentgid)) return; if(currentuid != 0) { ::seteuid(0); ::setegid(0); } if(newgid_) { ::setegid(newgid_); initgroups(newuid_,newgid_); } if(newuid_) ::seteuid(newuid_); currentuid = newuid_; currentgid = newgid_; } struct Set { Set(const uid_t newuid_, const gid_t newgid_) { ugid_set(newuid_,newgid_); } ~Set() { pthread_rwlock_unlock(&rwlock); } }; struct SetRootGuard { SetRootGuard() : prevuid(currentuid), prevgid(currentgid) { pthread_rwlock_unlock(&rwlock); ugid_set(0,0); } ~SetRootGuard() { pthread_rwlock_unlock(&rwlock); ugid_set(prevuid,prevgid); } const uid_t prevuid; const gid_t prevgid; }; } mergerfs-2.33.5/src/branch.hpp0000644000000000000000000000310614225522122014646 0ustar rootroot/* ISC License Copyright (c) 2018, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include "nonstd/optional.hpp" #include "strvec.hpp" #include "tofrom_string.hpp" #include #include #include class Branch final : public ToFromString { public: typedef std::vector Vector; public: Branch(const uint64_t &default_minfreespace_); public: enum class Mode { INVALID, RO, RW, NC }; public: bool ro(void) const; bool nc(void) const; bool ro_or_nc(void) const; public: int from_string(const std::string &str) final; std::string to_string(void) const final; public: uint64_t minfreespace() const; void set_minfreespace(const uint64_t); public: Mode mode; std::string path; private: nonstd::optional _minfreespace; const uint64_t *_default_minfreespace; }; mergerfs-2.33.5/src/fuse_lock.hpp0000644000000000000000000000171514225522122015367 0ustar rootroot/* ISC License Copyright (c) 2021, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include "fuse.h" namespace FUSE { int lock(const fuse_file_info_t *ffi, int cmd, struct flock *flock); } mergerfs-2.33.5/src/fs_fstat.hpp0000644000000000000000000000177014225522122015227 0ustar rootroot/* ISC License Copyright (c) 2020, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include #include #include namespace fs { static inline int fstat(const int fd_, struct stat *st_) { return ::fstat(fd_,st_); } } mergerfs-2.33.5/src/mergerfs.hpp0000644000000000000000000000144214225522122015224 0ustar rootroot/* Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once mergerfs-2.33.5/src/fs_fallocate.cpp0000644000000000000000000000204414225522122016026 0ustar rootroot/* Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #ifdef __linux__ # include "fs_fallocate_linux.icpp" #elif _XOPEN_SOURCE >= 600 || _POSIX_C_SOURCE >= 200112L # include "fs_fallocate_posix.icpp" #elif __APPLE__ # include "fs_fallocate_osx.icpp" #else # include "fs_fallocate_unsupported.icpp" #endif mergerfs-2.33.5/src/fuse_readdir_plus.cpp0000644000000000000000000000367414225522122017115 0ustar rootroot/* ISC License Copyright (c) 2020, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "fuse_readdir_plus_linux.hpp" #include "fuse_readdir_plus_posix.hpp" #include "config.hpp" #include "dirinfo.hpp" #include "rwlock.hpp" #include "ugid.hpp" #include "fuse.h" namespace FUSE { int readdir_plus(const fuse_file_info_t *ffi_, fuse_dirents_t *buf_) { Config::Read cfg; DirInfo *di = reinterpret_cast(ffi_->fh); const fuse_context *fc = fuse_get_context(); const ugid::Set ugid(fc->uid,fc->gid); switch(cfg->readdir) { case ReadDir::ENUM::LINUX: return FUSE::readdir_plus_linux(cfg->branches, di->fusepath.c_str(), cfg->cache_entry, cfg->cache_attr, buf_); default: case ReadDir::ENUM::POSIX: return FUSE::readdir_plus_posix(cfg->branches, di->fusepath.c_str(), cfg->cache_entry, cfg->cache_attr, buf_); } } } mergerfs-2.33.5/src/fs_utimensat.hpp0000644000000000000000000000211014225522122016104 0ustar rootroot/* ISC License Copyright (c) 2020, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #ifdef __linux__ #warning "using fs_utimensat_linux.hpp" #include "fs_utimensat_linux.hpp" #elif __FreeBSD__ #warning "using fs_utimensat_freebsd.hpp" #include "fs_utimensat_freebsd.hpp" #else #warning "using fs_utimensat_generic.hpp" #include "fs_utimensat_generic.hpp" #endif mergerfs-2.33.5/src/fuse_rename.hpp0000644000000000000000000000156214225522122015706 0ustar rootroot/* Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once namespace FUSE { int rename(const char *from, const char *to); } mergerfs-2.33.5/src/config_statfs.hpp0000644000000000000000000000163414225522122016246 0ustar rootroot/* ISC License Copyright (c) 2020, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include "enum.hpp" enum class StatFSEnum { BASE, FULL }; typedef Enum StatFS; mergerfs-2.33.5/src/policy_eplus.cpp0000644000000000000000000001077214225522122016122 0ustar rootroot/* Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "errno.hpp" #include "fs_exists.hpp" #include "fs_info.hpp" #include "fs_path.hpp" #include "fs_statvfs_cache.hpp" #include "policy.hpp" #include "policy_eplus.hpp" #include "policy_error.hpp" #include "rwlock.hpp" #include #include using std::string; namespace eplus { static int create(const Branches::CPtr &branches_, const char *fusepath_, StrVec *paths_) { int rv; int error; uint64_t eplus; fs::info_t info; const string *basepath; error = ENOENT; eplus = std::numeric_limits::max(); basepath = NULL; for(auto &branch : *branches_) { if(branch.ro_or_nc()) error_and_continue(error,EROFS); if(!fs::exists(branch.path,fusepath_)) error_and_continue(error,ENOENT); rv = fs::info(branch.path,&info); if(rv == -1) error_and_continue(error,ENOENT); if(info.readonly) error_and_continue(error,EROFS); if(info.spaceavail < branch.minfreespace()) error_and_continue(error,ENOSPC); if(info.spaceused >= eplus) continue; eplus = info.spaceused; basepath = &branch.path; } if(basepath == NULL) return (errno=error,-1); paths_->push_back(*basepath); return 0; } static int action(const Branches::CPtr &branches_, const char *fusepath_, StrVec *paths_) { int rv; int error; uint64_t eplus; fs::info_t info; const string *basepath; error = ENOENT; eplus = std::numeric_limits::max(); basepath = NULL; for(auto &branch : *branches_) { if(branch.ro()) error_and_continue(error,EROFS); if(!fs::exists(branch.path,fusepath_)) error_and_continue(error,ENOENT); rv = fs::info(branch.path,&info); if(rv == -1) error_and_continue(error,ENOENT); if(info.readonly) error_and_continue(error,EROFS); if(info.spaceused >= eplus) continue; eplus = info.spaceused; basepath = &branch.path; } if(basepath == NULL) return (errno=error,-1); paths_->push_back(*basepath); return 0; } static int search(const Branches::CPtr &branches_, const char *fusepath_, StrVec *paths_) { int rv; uint64_t eplus; uint64_t spaceused; const string *basepath; eplus = 0; basepath = NULL; for(auto &branch : *branches_) { if(!fs::exists(branch.path,fusepath_)) continue; rv = fs::statvfs_cache_spaceused(branch.path,&spaceused); if(rv == -1) continue; if(spaceused >= eplus) continue; eplus = spaceused; basepath = &branch.path; } if(basepath == NULL) return (errno=ENOENT,-1); paths_->push_back(*basepath); return 0; } } int Policy::EPLUS::Action::operator()(const Branches::CPtr &branches_, const char *fusepath_, StrVec *paths_) const { return ::eplus::action(branches_,fusepath_,paths_); } int Policy::EPLUS::Create::operator()(const Branches::CPtr &branches_, const char *fusepath_, StrVec *paths_) const { return ::eplus::create(branches_,fusepath_,paths_); } int Policy::EPLUS::Search::operator()(const Branches::CPtr &branches_, const char *fusepath_, StrVec *paths_) const { return ::eplus::search(branches_,fusepath_,paths_); } mergerfs-2.33.5/src/fs_mktemp.hpp0000644000000000000000000000162714225522122015404 0ustar rootroot/* ISC License Copyright (c) 2018, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include namespace fs { int mktemp(std::string *base, const int flags); } mergerfs-2.33.5/src/fuse_readdir.hpp0000644000000000000000000000163714225522122016054 0ustar rootroot/* Copyright (c) 2019, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include "fuse.h" namespace FUSE { int readdir(const fuse_file_info_t *ffi, fuse_dirents_t *buf); } mergerfs-2.33.5/src/policy_mfs.cpp0000644000000000000000000000517114225522122015554 0ustar rootroot/* Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "errno.hpp" #include "fs_exists.hpp" #include "fs_info.hpp" #include "fs_path.hpp" #include "policies.hpp" #include "policy.hpp" #include "policy_error.hpp" #include using std::string; namespace mfs { static int create(const Branches::CPtr &branches_, StrVec *paths_) { int rv; int error; uint64_t mfs; fs::info_t info; const string *basepath; error = ENOENT; mfs = 0; basepath = NULL; for(const auto &branch : *branches_) { if(branch.ro_or_nc()) error_and_continue(error,EROFS); rv = fs::info(branch.path,&info); if(rv == -1) error_and_continue(error,ENOENT); if(info.readonly) error_and_continue(error,EROFS); if(info.spaceavail < branch.minfreespace()) error_and_continue(error,ENOSPC); if(info.spaceavail < mfs) continue; mfs = info.spaceavail; basepath = &branch.path; } if(basepath == NULL) return (errno=error,-1); paths_->push_back(*basepath); return 0; } } int Policy::MFS::Action::operator()(const Branches::CPtr &branches_, const char *fusepath_, StrVec *paths_) const { return Policies::Action::epmfs(branches_,fusepath_,paths_); } int Policy::MFS::Create::operator()(const Branches::CPtr &branches_, const char *fusepath_, StrVec *paths_) const { return ::mfs::create(branches_,paths_); } int Policy::MFS::Search::operator()(const Branches::CPtr &branches_, const char *fusepath_, StrVec *paths_) const { return Policies::Search::epmfs(branches_,fusepath_,paths_); } mergerfs-2.33.5/src/fuse_copy_file_range.cpp0000644000000000000000000000422114225522122017552 0ustar rootroot/* Copyright (c) 2019, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "errno.hpp" #include "fileinfo.hpp" #include "fs_copy_file_range.hpp" #include "fuse.h" #include namespace l { static ssize_t copy_file_range(const int fd_in_, off_t offset_in_, const int fd_out_, off_t offset_out_, size_t size_, int flags_) { ssize_t rv; rv = fs::copy_file_range(fd_in_, &offset_in_, fd_out_, &offset_out_, size_, flags_); return ((rv == -1) ? -errno : rv); } } namespace FUSE { ssize_t copy_file_range(const fuse_file_info_t *ffi_in_, off_t offset_in_, const fuse_file_info_t *ffi_out_, off_t offset_out_, size_t size_, int flags_) { FileInfo *fi_in = reinterpret_cast(ffi_in_->fh); FileInfo *fi_out = reinterpret_cast(ffi_out_->fh); return l::copy_file_range(fi_in->fd, offset_in_, fi_out->fd, offset_out_, size_, flags_); } } mergerfs-2.33.5/src/fs_copy_file_range_unsupported.icpp0000644000000000000000000000270014225522122022041 0ustar rootroot/* ISC License Copyright (c) 2018, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "errno.h" #include #include #include namespace fs { ssize_t copy_file_range(const int fd_in_, int64_t *off_in_, const int fd_out_, int64_t *off_out_, const size_t len_, const unsigned int flags_) { return (errno=EOPNOTSUPP,-1); } ssize_t copy_file_range(const int fd_in_, const int fd_out_, const size_t len_, const unsigned int flags_) { return (errno=EOPNOTSUPP,-1); } } mergerfs-2.33.5/src/fs_realpathize.cpp0000644000000000000000000000220514225522122016403 0ustar rootroot/* ISC License Copyright (c) 2020, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "fs_realpath.hpp" #include #include namespace fs { void realpathize(std::vector *strs_) { char *rv; for(size_t i = 0; i < strs_->size(); i++) { rv = fs::realpath((*strs_)[i]); if(rv == NULL) continue; (*strs_)[i] = rv; ::free(rv); } } } mergerfs-2.33.5/src/config_inodecalc.hpp0000644000000000000000000000201014225522122016650 0ustar rootroot/* ISC License Copyright (c) 2020, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include "tofrom_string.hpp" class InodeCalc : public ToFromString { public: InodeCalc(const std::string &); public: std::string to_string(void) const final; int from_string(const std::string &) final; }; mergerfs-2.33.5/src/fuse_readdir_plus_linux.hpp0000644000000000000000000000223114225522122020325 0ustar rootroot/* ISC License Copyright (c) 2020, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include "branches.hpp" #include "fuse.h" #include namespace FUSE { int readdir_plus_linux(const Branches::CPtr &branches, const char *dirname, const uint64_t entry_timeout, const uint64_t attr_timeout, fuse_dirents_t *buf); } mergerfs-2.33.5/src/policy_cache.cpp0000644000000000000000000000360114225522122016026 0ustar rootroot#include "policy_cache.hpp" #include #include #include #include using std::map; using std::string; static const uint64_t DEFAULT_TIMEOUT = 0; namespace l { static uint64_t get_time(void) { uint64_t rv; rv = ::time(NULL); return rv; } } PolicyCache::Value::Value() : time(0), paths() { } PolicyCache::PolicyCache(void) : timeout(DEFAULT_TIMEOUT) { pthread_mutex_init(&_lock,NULL); } void PolicyCache::erase(const char *fusepath_) { if(timeout == 0) return; pthread_mutex_lock(&_lock); _cache.erase(fusepath_); pthread_mutex_unlock(&_lock); } void PolicyCache::cleanup(const int prob_) { uint64_t now; map::iterator i; if(timeout == 0) return; if(rand() % prob_) return; now = l::get_time(); pthread_mutex_lock(&_lock); i = _cache.begin(); while(i != _cache.end()) { if((now - i->second.time) >= timeout) _cache.erase(i++); else ++i; } pthread_mutex_unlock(&_lock); } void PolicyCache::clear(void) { pthread_mutex_lock(&_lock); _cache.clear(); pthread_mutex_unlock(&_lock); } int PolicyCache::operator()(const Policy::Search &policy_, const Branches &branches_, const char *fusepath_, StrVec *paths_) { int rv; Value *v; uint64_t now; if(timeout == 0) return policy_(branches_,fusepath_,paths_); now = l::get_time(); pthread_mutex_lock(&_lock); v = &_cache[fusepath_]; if((now - v->time) >= timeout) { pthread_mutex_unlock(&_lock); rv = policy_(branches_,fusepath_,paths_); if(rv == -1) return -1; pthread_mutex_lock(&_lock); v->time = now; v->paths = *paths_; } else { *paths_ = v->paths; } pthread_mutex_unlock(&_lock); return 0; } mergerfs-2.33.5/src/fs_glob.cpp0000644000000000000000000000231014225522122015013 0ustar rootroot/* Copyright (c) 2018, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include using std::string; using std::vector; namespace fs { void glob(const string &pattern_, vector *strs_) { int flags; glob_t gbuf = {0}; flags = GLOB_NOCHECK; ::glob(pattern_.c_str(),flags,NULL,&gbuf); for(size_t i = 0; i < gbuf.gl_pathc; i++) strs_->push_back(gbuf.gl_pathv[i]); ::globfree(&gbuf); } } mergerfs-2.33.5/src/fs_devid.hpp0000644000000000000000000000201514225522122015172 0ustar rootroot/* ISC License Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include "fs_fstat.hpp" namespace fs { static inline dev_t devid(const int fd_) { int rv; struct stat st; rv = fs::fstat(fd_,&st); if(rv == -1) return -1; return st.st_dev; } } mergerfs-2.33.5/src/fs_fsetxattr.hpp0000644000000000000000000000313314225522122016125 0ustar rootroot/* ISC License Copyright (c) 2020, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include "errno.hpp" #include "xattr.hpp" #include #include namespace fs { static inline int fsetxattr(const int fd_, const char *name_, const void *value_, const size_t size_, const int flags_) { #ifdef USE_XATTR return ::fsetxattr(fd_,name_,value_,size_,flags_); #else return (errno=ENOTSUP,-1); #endif } static inline int fsetxattr(const int fd_, const std::string &name_, const void *value_, const size_t size_, const int flags_) { return fs::fsetxattr(fd_, name_.c_str(), value_, size_, flags_); } } mergerfs-2.33.5/src/policy_epff.hpp0000644000000000000000000000314014225522122015706 0ustar rootroot/* ISC License Copyright (c) 2020, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include "policy.hpp" namespace Policy { namespace EPFF { class Action final : public Policy::ActionImpl { public: Action() : Policy::ActionImpl("epff") {} public: int operator()(const Branches::CPtr&,const char*,StrVec*) const final; }; class Create final : public Policy::CreateImpl { public: Create() : Policy::CreateImpl("epff") {} public: int operator()(const Branches::CPtr&,const char*,StrVec*) const final; bool path_preserving(void) const final { return true; } }; class Search final : public Policy::SearchImpl { public: Search() : Policy::SearchImpl("epff") {} public: int operator()(const Branches::CPtr&,const char*,StrVec*) const final; }; } } mergerfs-2.33.5/src/fs_getdents64.hpp0000644000000000000000000000166014225522122016073 0ustar rootroot/* ISC License Copyright (c) 2020, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once namespace fs { int getdents_64(unsigned int fd, void *dirp, unsigned int count); } mergerfs-2.33.5/src/fs_findallfiles.cpp0000644000000000000000000000250714225522122016534 0ustar rootroot/* ISC License Copyright (c) 2020, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "fs_exists.hpp" #include "fs_path.hpp" #include #include namespace fs { void findallfiles(const std::vector &basepaths_, const char *fusepath_, std::vector *paths_) { std::string fullpath; for(size_t i = 0, ei = basepaths_.size(); i != ei; i++) { fullpath = fs::path::make(basepaths_[i],fusepath_); if(!fs::exists(fullpath)) continue; paths_->push_back(fullpath); } } } mergerfs-2.33.5/src/config_follow_symlinks.cpp0000644000000000000000000000310714225522122020165 0ustar rootroot/* ISC License Copyright (c) 2020, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "config_follow_symlinks.hpp" #include "ef.hpp" #include "errno.hpp" template<> std::string FollowSymlinks::to_string(void) const { switch(_data) { case FollowSymlinks::ENUM::NEVER: return "never"; case FollowSymlinks::ENUM::DIRECTORY: return "directory"; case FollowSymlinks::ENUM::REGULAR: return "regular"; case FollowSymlinks::ENUM::ALL: return "all"; } return "invalid"; } template<> int FollowSymlinks::from_string(const std::string &s_) { if(s_ == "never") _data = FollowSymlinks::ENUM::NEVER; ef(s_ == "directory") _data = FollowSymlinks::ENUM::DIRECTORY; ef(s_ == "regular") _data = FollowSymlinks::ENUM::REGULAR; ef(s_ == "all") _data = FollowSymlinks::ENUM::ALL; else return -EINVAL; return 0; } mergerfs-2.33.5/src/strvec.hpp0000644000000000000000000000160014225522122014714 0ustar rootroot/* ISC License Copyright (c) 2020, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include #include typedef std::vector StrVec; mergerfs-2.33.5/src/fs_statvfs_cache.hpp0000644000000000000000000000257714225522122016731 0ustar rootroot/* ISC License Copyright (c) 2019, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include #include namespace fs { uint64_t statvfs_cache_timeout(void); void statvfs_cache_timeout(const uint64_t timeout); int statvfs_cache(const char *path, struct statvfs *st); int statvfs_cache_readonly(const std::string &path, bool *readonly); int statvfs_cache_spaceavail(const std::string &path, uint64_t *spaceavail); int statvfs_cache_spaceused(const std::string &path, uint64_t *spaceused); } mergerfs-2.33.5/src/func.hpp0000644000000000000000000000667214225522122014357 0ustar rootroot/* ISC License Copyright (c) 2019, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include "policy.hpp" #include "policies.hpp" #include "tofrom_string.hpp" #include namespace Func { namespace Base { class Action : public ToFromString { public: Action(Policy::ActionImpl *policy_) : policy(policy_) {} public: int from_string(const std::string &s) final; std::string to_string() const final; public: Policy::Action policy; }; class Create : public ToFromString { public: Create(Policy::CreateImpl *policy_) : policy(policy_) {} public: int from_string(const std::string &s) final; std::string to_string() const final; public: Policy::Create policy; }; class Search : public ToFromString { public: Search(Policy::SearchImpl *policy_) : policy(policy_) {} public: int from_string(const std::string &s) final; std::string to_string() const final; public: Policy::Search policy; }; class ActionDefault : public Action { public: ActionDefault() : Func::Base::Action(&Policies::Action::epall) { } }; class CreateDefault : public Create { public: CreateDefault() : Func::Base::Create(&Policies::Create::epmfs) { } }; class SearchDefault : public Search { public: SearchDefault() : Func::Base::Search(&Policies::Search::ff) { } }; } class Access final : public Base::SearchDefault { }; class Chmod final : public Base::ActionDefault { }; class Chown final : public Base::ActionDefault { }; class Create final : public Base::CreateDefault { }; class GetAttr final : public Base::SearchDefault { }; class GetXAttr final : public Base::SearchDefault { }; class Link final : public Base::ActionDefault { }; class ListXAttr final : public Base::SearchDefault { }; class Mkdir final : public Base::CreateDefault { }; class Mknod final : public Base::CreateDefault { }; class Open final : public Base::SearchDefault { }; class Readlink final : public Base::SearchDefault { }; class RemoveXAttr final : public Base::ActionDefault { }; class Rename final : public Base::ActionDefault { }; class Rmdir final : public Base::ActionDefault { }; class SetXAttr final : public Base::ActionDefault { }; class Symlink final : public Base::CreateDefault { }; class Truncate final : public Base::ActionDefault { }; class Unlink final : public Base::ActionDefault { }; class Utimens final : public Base::ActionDefault { }; } mergerfs-2.33.5/src/fs_copy_file_range.hpp0000644000000000000000000000223714225522122017232 0ustar rootroot/* ISC License Copyright (c) 2018, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include #include #include namespace fs { int64_t copy_file_range(const int fd_in, int64_t *off_in, const int fd_out, int64_t *off_out, const uint64_t len, const unsigned int flags); } mergerfs-2.33.5/src/fuse_read_buf.cpp0000644000000000000000000000454514225522122016205 0ustar rootroot/* Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "errno.hpp" #include "fileinfo.hpp" #include "fuse.h" #include #include typedef struct fuse_bufvec fuse_bufvec; namespace l { static int read_buf(const int fd_, fuse_bufvec **bufp_, const size_t size_, const off_t offset_) { fuse_bufvec *src; src = (fuse_bufvec*)malloc(sizeof(fuse_bufvec)); if(src == NULL) return -ENOMEM; *src = FUSE_BUFVEC_INIT(size_); src->buf->flags = (fuse_buf_flags)(FUSE_BUF_IS_FD|FUSE_BUF_FD_SEEK|FUSE_BUF_FD_RETRY); src->buf->fd = fd_; src->buf->pos = offset_; *bufp_ = src; return 0; } } namespace FUSE { int read_buf(const fuse_file_info_t *ffi_, fuse_bufvec **bufp_, size_t size_, off_t offset_) { FileInfo *fi = reinterpret_cast(ffi_->fh); return l::read_buf(fi->fd, bufp_, size_, offset_); } int read_buf_null(const fuse_file_info_t *ffi_, fuse_bufvec **bufp_, size_t size_, off_t offset_) { void *mem; struct fuse_bufvec *buf; buf = (struct fuse_bufvec*)malloc(sizeof(struct fuse_bufvec)); if(buf == NULL) return -ENOMEM; mem = (void*)calloc(1,size_); if(mem == NULL) { free(buf); return -ENOMEM; } *buf = FUSE_BUFVEC_INIT(size_); buf->buf[0].mem = mem; *bufp_ = buf; return 0; } } mergerfs-2.33.5/src/hw_cpu.hpp0000644000000000000000000000157614225522122014707 0ustar rootroot/* ISC License Copyright (c) 2020, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once namespace hw { namespace cpu { int logical_core_count(void); } } mergerfs-2.33.5/src/fuse_mkdir.cpp0000644000000000000000000000763314225522122015545 0ustar rootroot/* Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "config.hpp" #include "errno.hpp" #include "fs_acl.hpp" #include "fs_clonepath.hpp" #include "fs_mkdir.hpp" #include "fs_path.hpp" #include "policy.hpp" #include "ugid.hpp" #include "fuse.h" #include using std::string; namespace error { static inline int calc(const int rv_, const int prev_, const int cur_) { if(rv_ == -1) { if(prev_ == 0) return 0; return cur_; } return 0; } } namespace l { static int mkdir_core(const string &fullpath_, mode_t mode_, const mode_t umask_) { if(!fs::acl::dir_has_defaults(fullpath_)) mode_ &= ~umask_; return fs::mkdir(fullpath_,mode_); } static int mkdir_loop_core(const string &createpath_, const char *fusepath_, const mode_t mode_, const mode_t umask_, const int error_) { int rv; string fullpath; fullpath = fs::path::make(createpath_,fusepath_); rv = l::mkdir_core(fullpath,mode_,umask_); return error::calc(rv,error_,errno); } static int mkdir_loop(const string &existingpath_, const StrVec &createpaths_, const char *fusepath_, const string &fusedirpath_, const mode_t mode_, const mode_t umask_) { int rv; int error; error = -1; for(size_t i = 0, ei = createpaths_.size(); i != ei; i++) { rv = fs::clonepath_as_root(existingpath_,createpaths_[i],fusedirpath_); if(rv == -1) error = error::calc(rv,error,errno); else error = l::mkdir_loop_core(createpaths_[i], fusepath_, mode_, umask_, error); } return -error; } static int mkdir(const Policy::Search &getattrPolicy_, const Policy::Create &mkdirPolicy_, const Branches &branches_, const char *fusepath_, const mode_t mode_, const mode_t umask_) { int rv; string fusedirpath; StrVec createpaths; StrVec existingpaths; fusedirpath = fs::path::dirname(fusepath_); rv = getattrPolicy_(branches_,fusedirpath.c_str(),&existingpaths); if(rv == -1) return -errno; rv = mkdirPolicy_(branches_,fusedirpath.c_str(),&createpaths); if(rv == -1) return -errno; return l::mkdir_loop(existingpaths[0], createpaths, fusepath_, fusedirpath, mode_, umask_); } } namespace FUSE { int mkdir(const char *fusepath_, mode_t mode_) { Config::Read cfg; const fuse_context *fc = fuse_get_context(); const ugid::Set ugid(fc->uid,fc->gid); return l::mkdir(cfg->func.getattr.policy, cfg->func.mkdir.policy, cfg->branches, fusepath_, mode_, fc->umask); } } mergerfs-2.33.5/src/fuse_flush.hpp0000644000000000000000000000156614225522122015564 0ustar rootroot/* Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include "fuse.h" namespace FUSE { int flush(const fuse_file_info_t *ffi); } mergerfs-2.33.5/src/config_log_metrics.cpp0000644000000000000000000000240114225522122017235 0ustar rootroot/* ISC License Copyright (c) 2021, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "config_log_metrics.hpp" #include "from_string.hpp" #include "to_string.hpp" #include "fuse.h" LogMetrics::LogMetrics(const bool val_) { fuse_log_metrics_set(val_); } std::string LogMetrics::to_string(void) const { bool val; val = fuse_log_metrics_get(); return str::to(val); } int LogMetrics::from_string(const std::string &s_) { int rv; bool val; rv = str::from(s_,&val); if(rv < 0) return rv; fuse_log_metrics_set(val); return 0; } mergerfs-2.33.5/src/fuse_readdir_posix.hpp0000644000000000000000000000202714225522122017270 0ustar rootroot/* ISC License Copyright (c) 2020, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include "branches.hpp" #include "fuse.h" #include namespace FUSE { int readdir_posix(const Branches::CPtr &branches, const char *dirname, fuse_dirents_t *buf); } mergerfs-2.33.5/src/config_nfsopenhack.hpp0000644000000000000000000000166214225522122017242 0ustar rootroot/* ISC License Copyright (c) 2020, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include "enum.hpp" enum class NFSOpenHackEnum { OFF, GIT, ALL }; typedef Enum NFSOpenHack; mergerfs-2.33.5/src/rwlock.hpp0000644000000000000000000000261114225522122014712 0ustar rootroot/* Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include namespace rwlock { class ReadGuard { public: ReadGuard(pthread_rwlock_t &lock_) : _lock(lock_) { pthread_rwlock_rdlock(&_lock); } ~ReadGuard() { pthread_rwlock_unlock(&_lock); } private: ReadGuard(); private: pthread_rwlock_t &_lock; }; class WriteGuard { public: WriteGuard(pthread_rwlock_t &lock_) : _lock(lock_) { pthread_rwlock_wrlock(&_lock); } ~WriteGuard() { pthread_rwlock_unlock(&_lock); } private: WriteGuard(); private: pthread_rwlock_t &_lock; }; } mergerfs-2.33.5/src/fs_copydata_copy_file_range.cpp0000644000000000000000000000346214225522122021112 0ustar rootroot/* Copyright (c) 2019, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "errno.hpp" #include "fs_copy_file_range.hpp" #include "fs_fstat.hpp" #include namespace l { int64_t copydata_copy_file_range(const int src_fd_, const int dst_fd_, uint64_t size_) { int64_t rv; uint64_t nleft; int64_t src_off; int64_t dst_off; src_off = 0; dst_off = 0; nleft = size_; do { rv = fs::copy_file_range(src_fd_,&src_off,dst_fd_,&dst_off,nleft,0); if((rv == -1) && (errno == EINTR)) continue; if(rv == -1) return -1; nleft -= rv; } while(nleft > 0); return size_; } } namespace fs { int64_t copydata_copy_file_range(const int src_fd_, const int dst_fd_) { int rv; struct stat st; rv = fs::fstat(src_fd_,&st); if(rv == -1) return -1; return l::copydata_copy_file_range(src_fd_, dst_fd_, st.st_size); } } mergerfs-2.33.5/src/fs_copydata_readwrite.hpp0000644000000000000000000000161214225522122017753 0ustar rootroot/* Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once namespace fs { int copydata_readwrite(const int src_fd, const int dst_fd); } mergerfs-2.33.5/src/fs_realpathize.hpp0000644000000000000000000000163714225522122016420 0ustar rootroot/* ISC License Copyright (c) 2020, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include #include namespace fs { void realpathize(std::vector *strs); } mergerfs-2.33.5/src/fs_clonefile.hpp0000644000000000000000000000157014225522122016044 0ustar rootroot/* Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once namespace fs { int clonefile(const int src_fd, const int dst_fd); } mergerfs-2.33.5/src/fuse_destroy.hpp0000644000000000000000000000151714225522122016130 0ustar rootroot/* Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once namespace FUSE { void destroy(void); } mergerfs-2.33.5/src/policy_msppfrd.cpp0000644000000000000000000001071314225522122016440 0ustar rootroot/* ISC License Copyright (c) 2020, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "errno.hpp" #include "fs_exists.hpp" #include "fs_info.hpp" #include "fs_path.hpp" #include "fs_statvfs_cache.hpp" #include "policy.hpp" #include "policy_msppfrd.hpp" #include "policies.hpp" #include "policy_error.hpp" #include "rnd.hpp" #include #include using std::string; using std::vector; struct BranchInfo { uint64_t spaceavail; const string *basepath; }; typedef vector BranchInfoVec; namespace msppfrd { static int create_1(const Branches::CPtr &branches_, const string &fusepath_, BranchInfoVec *branchinfo_, uint64_t *sum_) { int rv; int error; BranchInfo bi; fs::info_t info; *sum_ = 0; error = ENOENT; for(auto &branch : *branches_) { if(branch.ro_or_nc()) error_and_continue(error,EROFS); if(!fs::exists(branch.path,fusepath_)) error_and_continue(error,ENOENT); rv = fs::info(branch.path,&info); if(rv == -1) error_and_continue(error,ENOENT); if(info.readonly) error_and_continue(error,EROFS); if(info.spaceavail < branch.minfreespace()) error_and_continue(error,ENOSPC); *sum_ += info.spaceavail; bi.spaceavail = info.spaceavail; bi.basepath = &branch.path; branchinfo_->push_back(bi); } return error; } static int get_branchinfo(const Branches::CPtr &branches_, const char *fusepath_, BranchInfoVec *branchinfo_, uint64_t *sum_) { int error; string fusepath; fusepath = fusepath_; for(;;) { error = msppfrd::create_1(branches_,fusepath,branchinfo_,sum_); if(branchinfo_->size()) break; if(fusepath == "/") break; fusepath = fs::path::dirname(fusepath); } return error; } static const string* get_branch(const BranchInfoVec &branchinfo_, const uint64_t sum_) { uint64_t idx; uint64_t threshold; if(sum_ == 0) return NULL; idx = 0; threshold = RND::rand64(sum_); for(size_t i = 0; i < branchinfo_.size(); i++) { idx += branchinfo_[i].spaceavail; if(idx < threshold) continue; return branchinfo_[i].basepath; } return NULL; } static int create(const Branches::CPtr &branches_, const char *fusepath_, StrVec *paths_) { int error; uint64_t sum; const string *basepath; BranchInfoVec branchinfo; error = msppfrd::get_branchinfo(branches_,fusepath_,&branchinfo,&sum); basepath = msppfrd::get_branch(branchinfo,sum); if(basepath == NULL) return (errno=error,-1); paths_->push_back(*basepath); return 0; } } int Policy::MSPPFRD::Action::operator()(const Branches::CPtr &branches_, const char *fusepath_, StrVec *paths_) const { return Policies::Action::eppfrd(branches_,fusepath_,paths_); } int Policy::MSPPFRD::Create::operator()(const Branches::CPtr &branches_, const char *fusepath_, StrVec *paths_) const { return ::msppfrd::create(branches_,fusepath_,paths_); } int Policy::MSPPFRD::Search::operator()(const Branches::CPtr &branches_, const char *fusepath_, StrVec *paths_) const { return Policies::Search::eppfrd(branches_,fusepath_,paths_); } mergerfs-2.33.5/src/policy_pfrd.cpp0000644000000000000000000000731714225522122015726 0ustar rootroot/* Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "errno.hpp" #include "fs_info.hpp" #include "fs_path.hpp" #include "policies.hpp" #include "policy.hpp" #include "policy_error.hpp" #include "policy_pfrd.hpp" #include "rnd.hpp" #include "strvec.hpp" #include #include using std::string; using std::vector; struct BranchInfo { uint64_t spaceavail; const string *basepath; }; typedef vector BranchInfoVec; namespace pfrd { static int get_branchinfo(const Branches::CPtr &branches_, BranchInfoVec *branchinfo_, uint64_t *sum_) { int rv; int error; BranchInfo bi; fs::info_t info; *sum_ = 0; error = ENOENT; for(auto &branch : *branches_) { if(branch.ro_or_nc()) error_and_continue(error,EROFS); rv = fs::info(branch.path,&info); if(rv == -1) error_and_continue(error,ENOENT); if(info.readonly) error_and_continue(error,EROFS); if(info.spaceavail < branch.minfreespace()) error_and_continue(error,ENOSPC); *sum_ += info.spaceavail; bi.spaceavail = info.spaceavail; bi.basepath = &branch.path; branchinfo_->push_back(bi); } return error; } static const string* get_branch(const BranchInfoVec &branchinfo_, const uint64_t sum_) { uint64_t idx; uint64_t threshold; if(sum_ == 0) return NULL; idx = 0; threshold = RND::rand64(sum_); for(size_t i = 0; i < branchinfo_.size(); i++) { idx += branchinfo_[i].spaceavail; if(idx < threshold) continue; return branchinfo_[i].basepath; } return NULL; } static int create(const Branches::CPtr &branches_, const char *fusepath_, StrVec *paths_) { int error; uint64_t sum; const string *basepath; BranchInfoVec branchinfo; error = pfrd::get_branchinfo(branches_,&branchinfo,&sum); basepath = pfrd::get_branch(branchinfo,sum); if(basepath == NULL) return (errno=error,-1); paths_->push_back(*basepath); return 0; } } int Policy::PFRD::Action::operator()(const Branches::CPtr &branches_, const char *fusepath_, StrVec *paths_) const { return Policies::Action::eppfrd(branches_,fusepath_,paths_); } int Policy::PFRD::Create::operator()(const Branches::CPtr &branches_, const char *fusepath_, StrVec *paths_) const { return ::pfrd::create(branches_,fusepath_,paths_); } int Policy::PFRD::Search::operator()(const Branches::CPtr &branches_, const char *fusepath_, StrVec *paths_) const { return Policies::Search::eppfrd(branches_,fusepath_,paths_); } mergerfs-2.33.5/src/ugid_linux.icpp0000644000000000000000000000202714225522122015725 0ustar rootroot/* Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include namespace ugid { __thread uid_t currentuid = 0; __thread gid_t currentgid = 0; __thread bool initialized = false; void init() { } } mergerfs-2.33.5/src/num.cpp0000644000000000000000000000520614225522122014206 0ustar rootroot/* Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "ef.hpp" #include #include #include #include #include #define KB (1024UL) #define MB (KB * 1024UL) #define GB (MB * 1024UL) #define TB (GB * 1024UL) namespace num { int to_uint64_t(const std::string &str, uint64_t &value) { char *endptr; uint64_t tmp; tmp = strtoll(str.c_str(),&endptr,10); switch(*endptr) { case 'k': case 'K': tmp *= 1024; break; case 'm': case 'M': tmp *= (1024 * 1024); break; case 'g': case 'G': tmp *= (1024 * 1024 * 1024); break; case 't': case 'T': tmp *= (1024ULL * 1024 * 1024 * 1024); break; case '\0': break; default: return -1; } value = tmp; return 0; } int to_double(const std::string &str_, double *d_) { double tmp; char *endptr; tmp = strtod(str_.c_str(),&endptr); if(*endptr != '\0') return -1; *d_ = tmp; return 0; } int to_time_t(const std::string &str, time_t &value) { time_t tmp; char *endptr; tmp = strtoll(str.c_str(),&endptr,10); if(*endptr != '\0') return -1; if(tmp < 0) return -1; value = tmp; return 0; } } namespace num { std::string humanize(const uint64_t bytes_) { char buf[64]; if(bytes_ < KB) sprintf(buf,"%lu",bytes_); ef(((bytes_ / TB) * TB) == bytes_) sprintf(buf,"%luT",bytes_ / TB); ef(((bytes_ / GB) * GB) == bytes_) sprintf(buf,"%luG",bytes_ / GB); ef(((bytes_ / MB) * MB) == bytes_) sprintf(buf,"%luM",bytes_ / MB); ef(((bytes_ / KB) * KB) == bytes_) sprintf(buf,"%luK",bytes_ / KB); else sprintf(buf,"%lu",bytes_); return std::string(buf); } } mergerfs-2.33.5/src/fs_acl.hpp0000644000000000000000000000165314225522122014645 0ustar rootroot/* ISC License Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include namespace fs { namespace acl { bool dir_has_defaults(const std::string &fullpath); } } mergerfs-2.33.5/src/fuse_free_hide.hpp0000644000000000000000000000160014225522122016342 0ustar rootroot/* ISC License Copyright (c) 2019, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include namespace FUSE { int free_hide(const uint64_t fh); } mergerfs-2.33.5/src/policy_eplfs.cpp0000644000000000000000000001113614225522122016076 0ustar rootroot/* Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "errno.hpp" #include "fs_exists.hpp" #include "fs_info.hpp" #include "fs_path.hpp" #include "fs_statvfs_cache.hpp" #include "policies.hpp" #include "policy.hpp" #include "policy_eplfs.hpp" #include "policy_error.hpp" #include #include #include using std::string; using std::vector; namespace eplfs { static int create(const Branches::CPtr &branches_, const char *fusepath_, StrVec *paths_) { int rv; int error; uint64_t eplfs; fs::info_t info; const string *basepath; error = ENOENT; eplfs = std::numeric_limits::max(); basepath = NULL; for(const auto &branch : *branches_) { if(branch.ro_or_nc()) error_and_continue(error,EROFS); if(!fs::exists(branch.path,fusepath_)) error_and_continue(error,ENOENT); rv = fs::info(branch.path,&info); if(rv == -1) error_and_continue(error,ENOENT); if(info.readonly) error_and_continue(error,EROFS); if(info.spaceavail < branch.minfreespace()) error_and_continue(error,ENOSPC); if(info.spaceavail > eplfs) continue; eplfs = info.spaceavail; basepath = &branch.path; } if(basepath == NULL) return (errno=error,-1); paths_->push_back(*basepath); return 0; } static int action(const Branches::CPtr &branches_, const char *fusepath_, StrVec *paths_) { int rv; int error; uint64_t eplfs; fs::info_t info; const string *basepath; error = ENOENT; eplfs = std::numeric_limits::max(); basepath = NULL; for(const auto &branch : *branches_) { if(branch.ro()) error_and_continue(error,EROFS); if(!fs::exists(branch.path,fusepath_)) error_and_continue(error,ENOENT); rv = fs::info(branch.path,&info); if(rv == -1) error_and_continue(error,ENOENT); if(info.readonly) error_and_continue(error,EROFS); if(info.spaceavail > eplfs) continue; eplfs = info.spaceavail; basepath = &branch.path; } if(basepath == NULL) return (errno=error,-1); paths_->push_back(*basepath); return 0; } static int search(const Branches::CPtr &branches_, const char *fusepath_, StrVec *paths_) { int rv; uint64_t eplfs; uint64_t spaceavail; const string *basepath; eplfs = std::numeric_limits::max(); basepath = NULL; for(const auto &branch : *branches_) { if(!fs::exists(branch.path,fusepath_)) continue; rv = fs::statvfs_cache_spaceavail(branch.path,&spaceavail); if(rv == -1) continue; if(spaceavail > eplfs) continue; eplfs = spaceavail; basepath = &branch.path; } if(basepath == NULL) return (errno=ENOENT,-1); paths_->push_back(*basepath); return 0; } } int Policy::EPLFS::Action::operator()(const Branches::CPtr &branches_, const char *fusepath_, StrVec *paths_) const { return ::eplfs::action(branches_,fusepath_,paths_); } int Policy::EPLFS::Create::operator()(const Branches::CPtr &branches_, const char *fusepath_, StrVec *paths_) const { return ::eplfs::create(branches_,fusepath_,paths_); } int Policy::EPLFS::Search::operator()(const Branches::CPtr &branches_, const char *fusepath_, StrVec *paths_) const { return ::eplfs::search(branches_,fusepath_,paths_); } mergerfs-2.33.5/src/fuse_readlink.cpp0000644000000000000000000000661214225522122016224 0ustar rootroot/* Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "config.hpp" #include "errno.hpp" #include "fs_lstat.hpp" #include "fs_path.hpp" #include "fs_readlink.hpp" #include "symlinkify.hpp" #include "ugid.hpp" #include "fuse.h" #include using std::string; namespace l { static int readlink_core_standard(const string &fullpath_, char *buf_, const size_t size_) { int rv; rv = fs::readlink(fullpath_,buf_,size_); if(rv == -1) return -errno; buf_[rv] = '\0'; return 0; } static int readlink_core_symlinkify(const string &fullpath_, char *buf_, const size_t size_, const time_t symlinkify_timeout_) { int rv; struct stat st; rv = fs::lstat(fullpath_,&st); if(rv == -1) return -errno; if(!symlinkify::can_be_symlink(st,symlinkify_timeout_)) return l::readlink_core_standard(fullpath_,buf_,size_); strncpy(buf_,fullpath_.c_str(),size_); return 0; } static int readlink_core(const string &basepath_, const char *fusepath_, char *buf_, const size_t size_, const bool symlinkify_, const time_t symlinkify_timeout_) { string fullpath; fullpath = fs::path::make(basepath_,fusepath_); if(symlinkify_) return l::readlink_core_symlinkify(fullpath,buf_,size_,symlinkify_timeout_); return l::readlink_core_standard(fullpath,buf_,size_); } static int readlink(const Policy::Search &searchFunc_, const Branches &branches_, const char *fusepath_, char *buf_, const size_t size_, const bool symlinkify_, const time_t symlinkify_timeout_) { int rv; StrVec basepaths; rv = searchFunc_(branches_,fusepath_,&basepaths); if(rv == -1) return -errno; return l::readlink_core(basepaths[0],fusepath_,buf_,size_, symlinkify_,symlinkify_timeout_); } } namespace FUSE { int readlink(const char *fusepath_, char *buf_, size_t size_) { Config::Read cfg; const fuse_context *fc = fuse_get_context(); const ugid::Set ugid(fc->uid,fc->gid); return l::readlink(cfg->func.readlink.policy, cfg->branches, fusepath_, buf_, size_, cfg->symlinkify, cfg->symlinkify_timeout); } } mergerfs-2.33.5/src/fs_fadvise_unsupported.icpp0000644000000000000000000000175314225522122020344 0ustar rootroot/* Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "errno.hpp" namespace fs { static int fadvise(const int fd_, const off_t offset_, const off_t len_, const int advice_) { return (errno=EOPNOTSUPP,-1); } } mergerfs-2.33.5/src/policy_ff.cpp0000644000000000000000000000464714225522122015371 0ustar rootroot/* Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "errno.hpp" #include "fs_exists.hpp" #include "fs_info.hpp" #include "fs_path.hpp" #include "policies.hpp" #include "policy.hpp" #include "policy_error.hpp" #include "policy_ff.hpp" #include using std::string; namespace ff { static int create(const Branches::CPtr &branches_, StrVec *paths_) { int rv; int error; fs::info_t info; error = ENOENT; for(auto &branch : *branches_) { if(branch.ro_or_nc()) error_and_continue(error,EROFS); rv = fs::info(branch.path,&info); if(rv == -1) error_and_continue(error,ENOENT); if(info.readonly) error_and_continue(error,EROFS); if(info.spaceavail < branch.minfreespace()) error_and_continue(error,ENOSPC); paths_->push_back(branch.path); return 0; } return (errno=error,-1); } } int Policy::FF::Action::operator()(const Branches::CPtr &branches_, const char *fusepath_, StrVec *paths_) const { return Policies::Action::epff(branches_,fusepath_,paths_); } int Policy::FF::Create::operator()(const Branches::CPtr &branches_, const char *fusepath_, StrVec *paths_) const { return ::ff::create(branches_,paths_); } int Policy::FF::Search::operator()(const Branches::CPtr &branches_, const char *fusepath_, StrVec *paths_) const { return Policies::Search::epff(branches_,fusepath_,paths_); } mergerfs-2.33.5/src/fuse_utimens.cpp0000644000000000000000000000630314225522122016114 0ustar rootroot/* Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "config.hpp" #include "errno.hpp" #include "fs_lutimens.hpp" #include "fs_path.hpp" #include "policy_rv.hpp" #include "ugid.hpp" #include "fuse.h" #include #include using std::string; namespace l { static int get_error(const PolicyRV &prv_, const string &basepath_) { for(int i = 0, ei = prv_.success.size(); i < ei; i++) { if(prv_.success[i].basepath == basepath_) return prv_.success[i].rv; } for(int i = 0, ei = prv_.error.size(); i < ei; i++) { if(prv_.error[i].basepath == basepath_) return prv_.error[i].rv; } return 0; } static void utimens_loop_core(const string &basepath_, const char *fusepath_, const timespec ts_[2], PolicyRV *prv_) { string fullpath; fullpath = fs::path::make(basepath_,fusepath_); errno = 0; fs::lutimens(fullpath,ts_); prv_->insert(errno,basepath_); } static void utimens_loop(const StrVec &basepaths_, const char *fusepath_, const timespec ts_[2], PolicyRV *prv_) { for(size_t i = 0, ei = basepaths_.size(); i != ei; i++) { l::utimens_loop_core(basepaths_[i],fusepath_,ts_,prv_); } } static int utimens(const Policy::Action &utimensPolicy_, const Policy::Search &getattrPolicy_, const Branches &branches_, const char *fusepath_, const timespec ts_[2]) { int rv; PolicyRV prv; StrVec basepaths; rv = utimensPolicy_(branches_,fusepath_,&basepaths); if(rv == -1) return -errno; l::utimens_loop(basepaths,fusepath_,ts_,&prv); if(prv.error.empty()) return 0; if(prv.success.empty()) return prv.error[0].rv; basepaths.clear(); rv = getattrPolicy_(branches_,fusepath_,&basepaths); if(rv == -1) return -errno; return l::get_error(prv,basepaths[0]); } } namespace FUSE { int utimens(const char *fusepath_, const timespec ts_[2]) { Config::Read cfg; const fuse_context *fc = fuse_get_context(); const ugid::Set ugid(fc->uid,fc->gid); return l::utimens(cfg->func.utimens.policy, cfg->func.getattr.policy, cfg->branches, fusepath_, ts_); } } mergerfs-2.33.5/src/fs_fallocate_linux.icpp0000644000000000000000000000176414225522122017426 0ustar rootroot/* Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include namespace fs { int fallocate(const int fd_, const int mode_, const off_t offset_, const off_t len_) { return ::fallocate(fd_,mode_,offset_,len_); } } mergerfs-2.33.5/src/policy_msplfs.hpp0000644000000000000000000000314414225522122016276 0ustar rootroot/* ISC License Copyright (c) 2020, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include "policy.hpp" namespace Policy { namespace MSPLFS { class Action final : public Policy::ActionImpl { public: Action() : Policy::ActionImpl("msplfs") {} public: int operator()(const Branches::CPtr&,const char*,StrVec*) const final; }; class Create final : public Policy::CreateImpl { public: Create() : Policy::CreateImpl("msplfs") {} public: int operator()(const Branches::CPtr&,const char*,StrVec*) const final; bool path_preserving() const final { return true; } }; class Search final : public Policy::SearchImpl { public: Search() : Policy::SearchImpl("msplfs") {} public: int operator()(const Branches::CPtr&,const char*,StrVec*) const final; }; } } mergerfs-2.33.5/src/fs_faccessat.hpp0000644000000000000000000000251014225522122016033 0ustar rootroot/* ISC License Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include #include #include namespace fs { static inline int faccessat(const int dirfd_, const char *path_, const int mode_, const int flags_) { return ::faccessat(dirfd_,path_,mode_,flags_); } static inline int faccessat(const int dirfd_, const std::string &path_, const int mode_, const int flags_) { return fs::faccessat(dirfd_,path_.c_str(),mode_,flags_); } } mergerfs-2.33.5/src/fs_readlink.hpp0000644000000000000000000000205714225522122015676 0ustar rootroot/* ISC License Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include #include namespace fs { static inline int readlink(const std::string &path_, char *buf_, const size_t bufsiz_) { return ::readlink(path_.c_str(),buf_,bufsiz_); } } mergerfs-2.33.5/src/fuse_chown.cpp0000644000000000000000000000654014225522122015551 0ustar rootroot/* Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "config.hpp" #include "errno.hpp" #include "fs_lchown.hpp" #include "fs_path.hpp" #include "policy_rv.hpp" #include "ugid.hpp" #include "fuse.h" #include #include using std::string; using std::vector; namespace l { static int get_error(const PolicyRV &prv_, const string &basepath_) { for(int i = 0, ei = prv_.success.size(); i < ei; i++) { if(prv_.success[i].basepath == basepath_) return prv_.success[i].rv; } for(int i = 0, ei = prv_.error.size(); i < ei; i++) { if(prv_.error[i].basepath == basepath_) return prv_.error[i].rv; } return 0; } static void chown_loop_core(const string &basepath_, const char *fusepath_, const uid_t uid_, const gid_t gid_, PolicyRV *prv_) { string fullpath; fullpath = fs::path::make(basepath_,fusepath_); errno = 0; fs::lchown(fullpath,uid_,gid_); prv_->insert(errno,basepath_); } static void chown_loop(const vector &basepaths_, const char *fusepath_, const uid_t uid_, const gid_t gid_, PolicyRV *prv_) { for(size_t i = 0, ei = basepaths_.size(); i != ei; i++) { l::chown_loop_core(basepaths_[i],fusepath_,uid_,gid_,prv_); } } static int chown(const Policy::Action &actionFunc_, const Policy::Search &searchFunc_, const Branches &branches_, const char *fusepath_, const uid_t uid_, const gid_t gid_) { int rv; PolicyRV prv; vector basepaths; rv = actionFunc_(branches_,fusepath_,&basepaths); if(rv == -1) return -errno; l::chown_loop(basepaths,fusepath_,uid_,gid_,&prv); if(prv.error.empty()) return 0; if(prv.success.empty()) return prv.error[0].rv; basepaths.clear(); rv = searchFunc_(branches_,fusepath_,&basepaths); if(rv == -1) return -errno; return l::get_error(prv,basepaths[0]); } } namespace FUSE { int chown(const char *fusepath_, uid_t uid_, gid_t gid_) { Config::Read cfg; const fuse_context *fc = fuse_get_context(); const ugid::Set ugid(fc->uid,fc->gid); return l::chown(cfg->func.chown.policy, cfg->func.getattr.policy, cfg->branches, fusepath_, uid_, gid_); } } mergerfs-2.33.5/src/enum.hpp0000644000000000000000000000303514225522122014356 0ustar rootroot/* ISC License Copyright (c) 2019, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include "tofrom_string.hpp" #include template class Enum : public ToFromString { public: typedef E ENUM; public: Enum() { } Enum(const ENUM data_) : _data(data_) { } public: Enum& operator=(const ENUM data_) { _data = data_; return *this; } Enum& operator=(const std::string &s_) { from_string(s_); return *this; } public: operator ENUM() const { return _data; } public: bool operator==(const ENUM data_) const { return (_data == data_); } public: std::string to_string() const final; int from_string(const std::string &) final; public: int to_int() const { return (int)_data; } private: ENUM _data; }; mergerfs-2.33.5/src/fs_fchown.hpp0000644000000000000000000000330514225522122015366 0ustar rootroot/* ISC License Copyright (c) 2019, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include "errno.hpp" #include "fs_fstat.hpp" #include #include #include namespace fs { static inline int fchown(const int fd_, const uid_t uid_, const gid_t gid_) { return ::fchown(fd_,uid_,gid_); } static inline int fchown(const int fd_, const struct stat &st_) { return fs::fchown(fd_,st_.st_uid,st_.st_gid); } static inline int fchown_check_on_error(const int fd_, const struct stat &st_) { int rv; rv = fs::fchown(fd_,st_); if(rv == -1) { int error; struct stat tmpst; error = errno; rv = fs::fstat(fd_,&tmpst); if(rv == -1) return -1; if((st_.st_uid != tmpst.st_uid) || (st_.st_gid != tmpst.st_gid)) return (errno=error,-1); } return 0; } } mergerfs-2.33.5/src/fs_copy_file_range_linux.icpp0000644000000000000000000000517714225522122020623 0ustar rootroot/* ISC License Copyright (c) 2018, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #ifndef _GNU_SOURCE # define _GNU_SOURCE #endif #include "errno.hpp" #include #include #include #include #include #include namespace l { static int64_t copy_file_range_(int src_fd_, int64_t *src_off_, int tgt_fd_, int64_t *tgt_off_, const uint64_t len_, const unsigned int flags_) { #ifdef SYS_copy_file_range int64_t rv; loff_t src_off; loff_t tgt_off; loff_t *src_off_ptr; loff_t *tgt_off_ptr; src_off = ((src_off_ == NULL) ? 0 : *src_off_); tgt_off = ((tgt_off_ == NULL) ? 0 : *tgt_off_); src_off_ptr = ((src_off_ == NULL) ? NULL : &src_off); tgt_off_ptr = ((tgt_off_ == NULL) ? NULL : &tgt_off); rv = ::syscall(SYS_copy_file_range, src_fd_, src_off_ptr, tgt_fd_, tgt_off_ptr, len_, flags_); if(rv != -1) { if(src_off_ != NULL) *src_off_ = src_off; if(tgt_off_ != NULL) *tgt_off_ = tgt_off; } return rv; #else return (errno=EOPNOTSUPP,-1); #endif } } namespace fs { int64_t copy_file_range(const int src_fd_, int64_t *src_off_, const int tgt_fd_, int64_t *tgt_off_, const uint64_t len_, const unsigned int flags_) { return l::copy_file_range_(src_fd_, src_off_, tgt_fd_, tgt_off_, len_, flags_); } } mergerfs-2.33.5/src/policy_eppfrd.hpp0000644000000000000000000000315014225522122016247 0ustar rootroot/* ISC License Copyright (c) 2020, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include "policy.hpp" namespace Policy { namespace EPPFRD { class Action final : public Policy::ActionImpl { public: Action() : Policy::ActionImpl("eppfrd") {} public: int operator()(const Branches::CPtr&,const char*,StrVec*) const final; }; class Create final : public Policy::CreateImpl { public: Create() : Policy::CreateImpl("eppfrd") {} public: int operator()(const Branches::CPtr&,const char*,StrVec*) const final; bool path_preserving(void) const final { return true; } }; class Search final : public Policy::SearchImpl { public: Search() : Policy::SearchImpl("eppfrd") {} public: int operator()(const Branches::CPtr&,const char*,StrVec*) const final; }; } } mergerfs-2.33.5/src/fs_info.cpp0000644000000000000000000000256314225522122015035 0ustar rootroot/* ISC License Copyright (c) 2018, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "fs_info_t.hpp" #include "fs_path.hpp" #include "fs_stat.hpp" #include "fs_statvfs.hpp" #include "fs_statvfs_cache.hpp" #include "statvfs_util.hpp" #include #include using std::string; namespace fs { int info(const string &path_, fs::info_t *info_) { int rv; struct statvfs st; rv = fs::statvfs_cache(path_.c_str(),&st); if(rv == 0) { info_->readonly = StatVFS::readonly(st); info_->spaceavail = StatVFS::spaceavail(st); info_->spaceused = StatVFS::spaceused(st); } return rv; } } mergerfs-2.33.5/src/fs_fallocate.hpp0000644000000000000000000000170714225522122016040 0ustar rootroot/* Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include namespace fs { int fallocate(const int fd, const int mode, const off_t offset, const off_t len); } mergerfs-2.33.5/src/fs_lgetxattr.hpp0000644000000000000000000000363614225522122016127 0ustar rootroot/* ISC License Copyright (c) 2020, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include "errno.hpp" #include "xattr.hpp" #include #include namespace fs { static inline int lgetxattr(const char *path_, const char *attrname_, void *value_, const size_t size_) { #ifdef USE_XATTR return ::lgetxattr(path_, attrname_, value_, size_); #else return (errno=ENOTSUP,-1); #endif } static inline int lgetxattr(const std::string &path_, const char *attrname_, void *value_, const size_t size_) { return fs::lgetxattr(path_.c_str(), attrname_, value_, size_); } static inline int lgetxattr(const std::string &path_, const std::string &attrname_, void *value_, const size_t size_) { return fs::lgetxattr(path_.c_str(), attrname_.c_str(), value_, size_); } } mergerfs-2.33.5/src/fs_lstat.hpp0000644000000000000000000000223214225522122015227 0ustar rootroot/* ISC License Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include #include #include #include namespace fs { static inline int lstat(const char *path_, struct stat *st_) { return ::lstat(path_,st_); } static inline int lstat(const std::string &path_, struct stat *st_) { return fs::lstat(path_.c_str(),st_); } } mergerfs-2.33.5/src/fuse_free_hide.cpp0000644000000000000000000000202714225522122016341 0ustar rootroot/* ISC License Copyright (c) 2019, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "fileinfo.hpp" #include "fs_close.hpp" #include namespace FUSE { int free_hide(const uint64_t fh_) { FileInfo *fi = reinterpret_cast(fh_); fs::close(fi->fd); delete fi; return 0; } } mergerfs-2.33.5/src/fuse_fsyncdir.hpp0000644000000000000000000000165014225522122016256 0ustar rootroot/* Copyright (c) 2017, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include "fuse.h" namespace FUSE { int fsyncdir(const fuse_file_info_t *ffi, int isdatasync); } mergerfs-2.33.5/src/fuse_bmap.hpp0000644000000000000000000000166214225522122015357 0ustar rootroot/* ISC License Copyright (c) 2021, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include "fuse.h" namespace FUSE { int bmap(const char *fusepath, size_t blocksize, uint64_t *idx); } mergerfs-2.33.5/src/buildmap.hpp0000644000000000000000000000233614225522122015212 0ustar rootroot/* Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include #include template class buildmap { public: buildmap(const K &key_, const V &val_) { _map.insert(std::make_pair(key_,val_)); } buildmap &operator()(const K &key_, const V &val_) { _map.insert(std::make_pair(key_,val_)); return *this; } operator std::map() { return _map; } private: std::map _map; }; mergerfs-2.33.5/src/fs_write.hpp0000644000000000000000000000230214225522122015230 0ustar rootroot/* ISC License Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include namespace fs { static inline ssize_t write(const int fd_, const void *buf_, const size_t count_) { return ::write(fd_,buf_,count_); } static inline ssize_t pwrite(const int fd_, const void *buf_, const size_t count_, const off_t offset_) { return ::pwrite(fd_,buf_,count_,offset_); } } mergerfs-2.33.5/src/fs_futimesat_osx.icpp0000644000000000000000000000410114225522122017133 0ustar rootroot/* ISC License Copyright (c) 2017, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include #include #include namespace l { static int getpath(const int dirfd_, const char *path_, char *fullpath_) { int rv; struct stat st; rv = ::fstat(dirfd_,&st); if(rv == -1) return -1; if(!S_ISDIR(st.st_mode)) return (errno=ENOTDIR,-1); rv = ::fcntl(dirfd_,F_GETPATH,fullpath); if(rv == -1) return -1; rv = ::strlcat(fullpath_,"/",MAXPATHLEN); if(rv > MAXPATHLEN) return (errno=ENAMETOOLONG,-1); rv = ::strlcat(fullpath_,path_,MAXPATHLEN); if(rv > MAXPATHLEN) return (errno=ENAMETOOLONG,-1); return 0; } } namespace fs { int futimesat(const int dirfd_, const char *pathname_, const struct timeval times_[2]) { int rv; char fullpath[MAXPATHLEN]; if((dirfd_ == AT_FDCWD) || ((pathname_ != NULL) && (pathname_[0] == '/'))) return ::utimes(pathname_,times_); if(dirfd_ < 0) return (errno=EBADF,-1); rv = l::getpath(dirfd_,pathname_,fullpath); if(rv == -1) return -1; return ::utimes(fullpath,times_); } } mergerfs-2.33.5/src/fileinfo.hpp0000644000000000000000000000175614225522122015215 0ustar rootroot/* Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include "fh.hpp" #include class FileInfo : public FH { public: FileInfo(const int fd_, const char *fusepath_) : FH(fusepath_), fd(fd_) { } public: int fd; }; mergerfs-2.33.5/src/fuse_futimens.cpp0000644000000000000000000000246714225522122016271 0ustar rootroot/* Copyright (c) 2019, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "errno.hpp" #include "fileinfo.hpp" #include "fs_futimens.hpp" #include "fuse.h" #include namespace l { static int futimens(const int fd_, const struct timespec ts_[2]) { int rv; rv = fs::futimens(fd_,ts_); if(rv == -1) return -errno; return rv; } } namespace FUSE { int futimens(const fuse_file_info_t *ffi_, const struct timespec ts_[2]) { FileInfo *fi = reinterpret_cast(ffi_->fh); return l::futimens(fi->fd,ts_); } } mergerfs-2.33.5/src/fs_utimensat_freebsd.hpp0000644000000000000000000000264014225522122017606 0ustar rootroot/* ISC License Copyright (c) 2020, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include #include #include namespace fs { static inline int utimensat(const int dirfd_, const char *pathname_, const struct timespec times_[2], const int flags_) { return ::utimensat(dirfd_,pathname_,times_,flags_); } static inline int utimensat(const int dirfd_, const std::string &pathname_, const struct timespec times_[2], const int flags_) { return fs::utimensat(dirfd_,pathname_.c_str(),times_,flags_); } } mergerfs-2.33.5/src/fuse_utimens.hpp0000644000000000000000000000163214225522122016121 0ustar rootroot/* Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include namespace FUSE { int utimens(const char *fusepath, const timespec ts[2]); } mergerfs-2.33.5/src/fs_attr_linux.icpp0000644000000000000000000000557314225522122016450 0ustar rootroot/* Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "errno.hpp" #include "fs_close.hpp" #include "fs_open.hpp" #include "fs_ioctl.hpp" #include #include #include using std::string; namespace fs { namespace attr { static int get_fs_ioc_flags(const int fd, int &flags) { int rv; rv = fs::ioctl(fd,FS_IOC_GETFLAGS,(void*)&flags); if((rv == -1) && (errno == EINVAL)) errno = ENOTSUP; return rv; } static int get_fs_ioc_flags(const string &file, int &flags) { int fd; int rv; const int openflags = O_RDONLY|O_NONBLOCK; fd = fs::open(file,openflags); if(fd == -1) return -1; rv = get_fs_ioc_flags(fd,flags); if(rv == -1) { int error = errno; fs::close(fd); errno = error; return -1; } return fs::close(fd); } static int set_fs_ioc_flags(const int fd, const int flags) { int rv; rv = fs::ioctl(fd,FS_IOC_SETFLAGS,(void*)&flags); if((rv == -1) && (errno == EINVAL)) errno = ENOTSUP; return rv; } static int set_fs_ioc_flags(const string &file, const int flags) { int fd; int rv; const int openflags = O_RDONLY|O_NONBLOCK; fd = fs::open(file,openflags); if(fd == -1) return -1; rv = set_fs_ioc_flags(fd,flags); if(rv == -1) { int error = errno; fs::close(fd); errno = error; return -1; } return fs::close(fd); } int copy(const int fdin, const int fdout) { int rv; int flags; rv = get_fs_ioc_flags(fdin,flags); if(rv == -1) return -1; return set_fs_ioc_flags(fdout,flags); } int copy(const string &from, const string &to) { int rv; int flags; rv = get_fs_ioc_flags(from,flags); if(rv == -1) return -1; return set_fs_ioc_flags(to,flags); } } } mergerfs-2.33.5/src/fs_attr.hpp0000644000000000000000000000175014225522122015056 0ustar rootroot/* Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include namespace fs { namespace attr { int copy(const int fdin, const int fdout); int copy(const std::string &from, const std::string &to); } } mergerfs-2.33.5/src/fuse_symlink.hpp0000644000000000000000000000200714225522122016120 0ustar rootroot/* Copyright (c) 2020, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include "fuse.h" #include namespace FUSE { int symlink(const char *target, const char *linkpath, struct stat *st = NULL, fuse_timeouts_t *timeouts = NULL); } mergerfs-2.33.5/src/fs_statvfs.hpp0000644000000000000000000000324314225522122015575 0ustar rootroot/* ISC License Copyright (c) 2019, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include "errno.hpp" #include "fs_close.hpp" #include "fs_open.hpp" #include #include #include #ifndef O_PATH # define O_PATH 0 #endif namespace fs { static inline int statvfs(const char *path_, struct statvfs *st_) { return ::statvfs(path_,st_); } static inline int statvfs(const std::string &path_, struct statvfs *st_) { return fs::statvfs(path_.c_str(),st_); } static inline int fstatvfs(const int fd_, struct statvfs *st_) { return ::fstatvfs(fd_,st_); } static inline int lstatvfs(const std::string &path_, struct statvfs *st_) { int fd; int rv; fd = fs::open(path_,O_RDONLY|O_NOFOLLOW|O_PATH); if(fd == -1) return -1; rv = fs::fstatvfs(fd,st_); fs::close(fd); return rv; } } mergerfs-2.33.5/src/fs_futimesat_generic.icpp0000644000000000000000000000204314225522122017741 0ustar rootroot/* ISC License Copyright (c) 2017, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include namespace fs { int futimesat(const int dirfd_, const char *pathname_, const struct timeval times_[2]) { return ::futimesat(dirfd_,pathname_,times_); } } mergerfs-2.33.5/src/fuse_access.cpp0000644000000000000000000000343714225522122015676 0ustar rootroot/* Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "config.hpp" #include "errno.hpp" #include "fs_eaccess.hpp" #include "fs_path.hpp" #include "ugid.hpp" #include #include using std::string; using std::vector; namespace l { static int access(const Policy::Search &searchFunc_, const Branches &branches_, const char *fusepath_, const int mask_) { int rv; string fullpath; StrVec basepaths; rv = searchFunc_(branches_,fusepath_,&basepaths); if(rv == -1) return -errno; fullpath = fs::path::make(basepaths[0],fusepath_); rv = fs::eaccess(fullpath,mask_); return ((rv == -1) ? -errno : 0); } } namespace FUSE { int access(const char *fusepath_, int mask_) { Config::Read cfg; const fuse_context *fc = fuse_get_context(); const ugid::Set ugid(fc->uid,fc->gid); return l::access(cfg->func.access.policy, cfg->branches, fusepath_, mask_); } } mergerfs-2.33.5/src/fuse_copy_file_range.hpp0000644000000000000000000000216314225522122017562 0ustar rootroot/* Copyright (c) 2019, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once namespace FUSE { ssize_t copy_file_range(const fuse_file_info_t *ffi_in, off_t offset_in, const fuse_file_info_t *ffi_out, off_t offset_out, size_t size, int flags); } mergerfs-2.33.5/src/ugid.hpp0000644000000000000000000000215414225522122014343 0ustar rootroot/* Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include #include #include namespace ugid { void init(); void initgroups(const uid_t uid, const gid_t gid); } #if defined __linux__ and UGID_USE_RWLOCK == 0 #warning "using ugid_linux.hpp" #include "ugid_linux.hpp" #else #warning "using ugid_rwlock.hpp" #include "ugid_rwlock.hpp" #endif mergerfs-2.33.5/src/to_string.hpp0000644000000000000000000000175614225522122015432 0ustar rootroot/* ISC License Copyright (c) 2019, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include #include namespace str { std::string to(const bool); std::string to(const int); std::string to(const uint64_t); std::string to(const std::string&); } mergerfs-2.33.5/src/rnd.cpp0000644000000000000000000000245114225522122014171 0ustar rootroot/* ISC License Copyright (c) 2020, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "rnd.hpp" #include "wyhash.h" #include #include static uint64_t G_SEED; static RND G_RND; RND::RND() { struct timeval tv; gettimeofday(&tv,NULL); G_SEED = ((tv.tv_sec << 32) | (tv.tv_usec)); } uint64_t RND::rand64(void) { return wyrand(&G_SEED); } uint64_t RND::rand64(const uint64_t max_) { return (wyrand(&G_SEED) % max_); } uint64_t RND::rand64(const uint64_t min_, const uint64_t max_) { return (min_ + (wyrand(&G_SEED) % (max_ - min_))); } mergerfs-2.33.5/src/category.cpp0000644000000000000000000000234214225522122015222 0ustar rootroot/* ISC License Copyright (c) 2020, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "category.hpp" #include "errno.hpp" #include "str.hpp" #include int Category::Base::from_string(const std::string &s_) { int rv; for(auto func : funcs) { rv = func->from_string(s_); if(rv < 0) return rv; } return 0; } std::string Category::Base::to_string(void) const { std::set rv; for(auto func : funcs) rv.insert(func->to_string()); return str::join(rv,','); } mergerfs-2.33.5/src/policy_lfs.cpp0000644000000000000000000000537014225522122015554 0ustar rootroot/* Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "errno.hpp" #include "fs_exists.hpp" #include "fs_info.hpp" #include "fs_path.hpp" #include "policies.hpp" #include "policy.hpp" #include "policy_error.hpp" #include "policy_lfs.hpp" #include "strvec.hpp" #include #include using std::string; namespace lfs { static int create(const Branches::CPtr &branches_, StrVec *paths_) { int rv; int error; uint64_t lfs; fs::info_t info; const Branch *branch; const string *basepath; error = ENOENT; lfs = std::numeric_limits::max(); basepath = NULL; for(const auto &branch : *branches_) { if(branch.ro_or_nc()) error_and_continue(error,EROFS); rv = fs::info(branch.path,&info); if(rv == -1) error_and_continue(error,ENOENT); if(info.readonly) error_and_continue(error,EROFS); if(info.spaceavail < branch.minfreespace()) error_and_continue(error,ENOSPC); if(info.spaceavail > lfs) continue; lfs = info.spaceavail; basepath = &branch.path; } if(basepath == NULL) return (errno=error,-1); paths_->push_back(*basepath); return 0; } } int Policy::LFS::Action::operator()(const Branches::CPtr &branches_, const char *fusepath_, StrVec *paths_) const { return Policies::Action::eplfs(branches_,fusepath_,paths_); } int Policy::LFS::Create::operator()(const Branches::CPtr &branches_, const char *fusepath_, StrVec *paths_) const { return ::lfs::create(branches_,paths_); } int Policy::LFS::Search::operator()(const Branches::CPtr &branches_, const char *fusepath_, StrVec *paths_) const { return Policies::Search::eplfs(branches_,fusepath_,paths_); } mergerfs-2.33.5/src/fuse_readdir_linux.cpp0000644000000000000000000000647714225522122017275 0ustar rootroot/* Copyright (c) 2020, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "branches.hpp" #include "errno.hpp" #include "fs_close.hpp" #include "fs_devid.hpp" #include "fs_getdents64.hpp" #include "fs_inode.hpp" #include "fs_open.hpp" #include "fs_path.hpp" #include "fs_stat.hpp" #include "hashset.hpp" #include "linux_dirent64.h" #include "mempools.hpp" #include "fuse.h" #include "fuse_dirents.h" #include #include #include using std::string; using std::vector; namespace l { static int close_free_ret_enomem(int fd_, void *buf_) { fs::close(fd_); g_DENTS_BUF_POOL.free(buf_); return -ENOMEM; } static int readdir(const Branches::CPtr &branches_, const char *dirname_, fuse_dirents_t *buf_) { int rv; dev_t dev; char *buf; HashSet names; string basepath; string fullpath; uint64_t namelen; struct linux_dirent64 *d; fuse_dirents_reset(buf_); buf = (char*)g_DENTS_BUF_POOL.alloc(); if(buf == NULL) return -ENOMEM; for(const auto &branch : *branches_) { int dirfd; int64_t nread; basepath = fs::path::make(branch.path,dirname_); dirfd = fs::open_dir_ro(basepath); if(dirfd == -1) continue; dev = fs::devid(dirfd); for(;;) { nread = fs::getdents_64(dirfd,buf,g_DENTS_BUF_POOL.size()); if(nread == -1) break; if(nread == 0) break; for(int64_t pos = 0; pos < nread; pos += d->reclen) { d = (struct linux_dirent64*)(buf + pos); namelen = strlen(d->name); rv = names.put(d->name,namelen); if(rv == 0) continue; fullpath = fs::path::make(dirname_,d->name); d->ino = fs::inode::calc(fullpath.c_str(), fullpath.size(), DTTOIF(d->type), dev, d->ino); rv = fuse_dirents_add_linux(buf_,d,namelen); if(rv) return close_free_ret_enomem(dirfd,buf); } } fs::close(dirfd); } g_DENTS_BUF_POOL.free(buf); return 0; } } namespace FUSE { int readdir_linux(const Branches::CPtr &branches_, const char *dirname_, fuse_dirents_t *buf_) { return l::readdir(branches_,dirname_,buf_); } } mergerfs-2.33.5/src/fs_cow.hpp0000644000000000000000000000220614225522122014671 0ustar rootroot/* ISC License Copyright (c) 2018, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include #include namespace fs { namespace cow { bool is_eligible(const int flags); bool is_eligible(const struct stat &st); bool is_eligible(const int flags, const struct stat &st); bool is_eligible(const char *fullpath, const int flags); int break_link(const char *fullpath); } } mergerfs-2.33.5/src/config.cpp0000644000000000000000000002245614225522122014662 0ustar rootroot/* Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "config.hpp" #include "ef.hpp" #include "errno.hpp" #include "from_string.hpp" #include "num.hpp" #include "rwlock.hpp" #include "str.hpp" #include "to_string.hpp" #include "version.hpp" #include #include #include #include #include #include #include #include #include #define MINFREESPACE_DEFAULT (4294967295ULL) using std::string; #define IFERT(S) if(S == s_) return true const std::string CONTROLFILE = "/.mergerfs"; Config Config::_singleton; namespace l { static bool readonly(const std::string &s_) { IFERT("async_read"); IFERT("cache.symlinks"); IFERT("cache.writeback"); IFERT("fsname"); IFERT("fuse_msg_size"); IFERT("mount"); IFERT("nullrw"); IFERT("pid"); IFERT("readdirplus"); IFERT("threads"); IFERT("version"); return false; } } Config::Config() : async_read(true), auto_cache(false), minfreespace(MINFREESPACE_DEFAULT), branches(minfreespace), cache_attr(1), cache_entry(1), cache_files(CacheFiles::ENUM::LIBFUSE), cache_negative_entry(0), cache_readdir(false), cache_statfs(0), cache_symlinks(false), category(func), direct_io(false), dropcacheonclose(false), fsname(), follow_symlinks(FollowSymlinks::ENUM::NEVER), func(), fuse_msg_size(FUSE_MAX_MAX_PAGES), ignorepponrename(false), inodecalc("hybrid-hash"), link_cow(false), link_exdev(LinkEXDEV::ENUM::PASSTHROUGH), log_metrics(false), mount(), moveonenospc(false), nfsopenhack(NFSOpenHack::ENUM::OFF), nullrw(false), pid(::getpid()), posix_acl(false), readdir(ReadDir::ENUM::POSIX), readdirplus(false), rename_exdev(RenameEXDEV::ENUM::PASSTHROUGH), security_capability(true), srcmounts(branches), statfs(StatFS::ENUM::BASE), statfs_ignore(StatFSIgnore::ENUM::NONE), symlinkify(false), symlinkify_timeout(3600), threads(0), version(MERGERFS_VERSION), writeback_cache(false), xattr(XAttr::ENUM::PASSTHROUGH) { _map["async_read"] = &async_read; _map["auto_cache"] = &auto_cache; _map["branches"] = &branches; _map["cache.attr"] = &cache_attr; _map["cache.entry"] = &cache_entry; _map["cache.files"] = &cache_files; _map["cache.negative_entry"] = &cache_negative_entry; _map["cache.readdir"] = &cache_readdir; _map["cache.statfs"] = &cache_statfs; _map["cache.symlinks"] = &cache_symlinks; _map["cache.writeback"] = &writeback_cache; _map["category.action"] = &category.action; _map["category.create"] = &category.create; _map["category.search"] = &category.search; _map["direct_io"] = &direct_io; _map["dropcacheonclose"] = &dropcacheonclose; _map["follow-symlinks"] = &follow_symlinks; _map["fsname"] = &fsname; _map["func.access"] = &func.access; _map["func.chmod"] = &func.chmod; _map["func.chown"] = &func.chown; _map["func.create"] = &func.create; _map["func.getattr"] = &func.getattr; _map["func.getxattr"] = &func.getxattr; _map["func.link"] = &func.link; _map["func.listxattr"] = &func.listxattr; _map["func.mkdir"] = &func.mkdir; _map["func.mknod"] = &func.mknod; _map["func.open"] = &func.open; _map["func.readlink"] = &func.readlink; _map["func.removexattr"] = &func.removexattr; _map["func.rename"] = &func.rename; _map["func.rmdir"] = &func.rmdir; _map["func.setxattr"] = &func.setxattr; _map["func.symlink"] = &func.symlink; _map["func.truncate"] = &func.truncate; _map["func.unlink"] = &func.unlink; _map["func.utimens"] = &func.utimens; _map["fuse_msg_size"] = &fuse_msg_size; _map["ignorepponrename"] = &ignorepponrename; _map["inodecalc"] = &inodecalc; _map["kernel_cache"] = &kernel_cache; _map["link_cow"] = &link_cow; _map["link-exdev"] = &link_exdev; _map["log.metrics"] = &log_metrics; _map["minfreespace"] = &minfreespace; _map["mount"] = &mount; _map["moveonenospc"] = &moveonenospc; _map["nfsopenhack"] = &nfsopenhack; _map["nullrw"] = &nullrw; _map["pid"] = &pid; _map["posix_acl"] = &posix_acl; // _map["readdir"] = &readdir; _map["readdirplus"] = &readdirplus; _map["rename-exdev"] = &rename_exdev; _map["security_capability"] = &security_capability; _map["srcmounts"] = &srcmounts; _map["statfs"] = &statfs; _map["statfs_ignore"] = &statfs_ignore; _map["symlinkify"] = &symlinkify; _map["symlinkify_timeout"] = &symlinkify_timeout; _map["threads"] = &threads; _map["version"] = &version; _map["xattr"] = &xattr; } Config& Config::operator=(const Config &cfg_) { int rv; std::string val; for(auto &kv : _map) { rv = cfg_.get(kv.first,&val); if(rv) continue; kv.second->from_string(val); } return *this; } bool Config::has_key(const std::string &key_) const { return _map.count(key_); } void Config::keys(std::string &s_) const { Str2TFStrMap::const_iterator i; Str2TFStrMap::const_iterator ei; s_.reserve(512); i = _map.begin(); ei = _map.end(); for(; i != ei; ++i) { s_ += i->first; s_ += '\0'; } s_.resize(s_.size() - 1); } void Config::keys_xattr(std::string &s_) const { Str2TFStrMap::const_iterator i; Str2TFStrMap::const_iterator ei; s_.reserve(1024); for(i = _map.begin(), ei = _map.end(); i != ei; ++i) { s_ += "user.mergerfs."; s_ += i->first; s_ += '\0'; } } int Config::get(const std::string &key_, std::string *val_) const { Str2TFStrMap::const_iterator i; i = _map.find(key_); if(i == _map.end()) return -ENOATTR; *val_ = i->second->to_string(); return 0; } int Config::set_raw(const std::string &key_, const std::string &value_) { Str2TFStrMap::iterator i; i = _map.find(key_); if(i == _map.end()) return -ENOATTR; return i->second->from_string(value_); } int Config::set(const std::string &key_, const std::string &value_) { if(l::readonly(key_)) return -EROFS; return set_raw(key_,value_); } int Config::set(const std::string &kv_) { std::string key; std::string val; str::splitkv(kv_,'=',&key,&val); key = str::trim(key); val = str::trim(val); return set(key,val); } int Config::from_stream(std::istream &istrm_, ErrVec *errs_) { int rv; std::string line; std::string key; std::string val; Config newcfg; newcfg = *this; while(std::getline(istrm_,line,'\n')) { line = str::trim(line); if(!line.empty() && (line[0] == '#')) continue; str::splitkv(line,'=',&key,&val); key = str::trim(key); val = str::trim(val); rv = newcfg.set(key,val); if(rv < 0) errs_->push_back({rv,key}); } if(!errs_->empty()) return -EINVAL; *this = newcfg; return 0; } int Config::from_file(const std::string &filepath_, ErrVec *errs_) { int rv; std::ifstream ifstrm; ifstrm.open(filepath_); if(!ifstrm.good()) { errs_->push_back({-errno,filepath_}); return -errno; } rv = from_stream(ifstrm,errs_); ifstrm.close(); return rv; } std::ostream& operator<<(std::ostream &os_, const Config &c_) { Str2TFStrMap::const_iterator i; Str2TFStrMap::const_iterator ei; for(i = c_._map.begin(), ei = c_._map.end(); i != ei; ++i) { os_ << i->first << '=' << i->second->to_string() << std::endl; } return os_; } static std::string err2str(const int err_) { switch(err_) { case 0: return std::string(); case -EINVAL: return "invalid value"; case -ENOATTR: return "unknown option"; case -EROFS: return "read-only option"; default: return strerror(-err_); } return std::string(); } std::ostream& operator<<(std::ostream &os_, const Config::ErrVec &ev_) { std::string errstr; for(auto &err : ev_) { os_ << "* ERROR: "; errstr = err2str(err.err); if(!errstr.empty()) os_ << errstr << " - "; os_ << err.str << std::endl; } return os_; } mergerfs-2.33.5/src/fuse_releasedir.hpp0000644000000000000000000000157314225522122016560 0ustar rootroot/* Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include "fuse.h" namespace FUSE { int releasedir(const fuse_file_info_t *ffi); } mergerfs-2.33.5/src/fs_exists.hpp0000644000000000000000000000350114225522122015417 0ustar rootroot/* ISC License Copyright (c) 2018, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include "fs_lstat.hpp" #include "fs_path.hpp" #include namespace fs { static inline bool exists(const std::string &path_, struct stat *st_) { int rv; rv = fs::lstat(path_,st_); return (rv == 0); } static inline bool exists(const std::string &path_) { struct stat st; return fs::exists(path_,&st); } static inline bool exists(const std::string &basepath_, const std::string &relpath_) { std::string fullpath; fullpath = fs::path::make(basepath_,relpath_); return fs::exists(fullpath); } static inline bool exists(const std::string &basepath_, const char *relpath_, struct stat *st_) { std::string fullpath; fullpath = fs::path::make(basepath_,relpath_); return fs::exists(fullpath,st_); } static inline bool exists(const std::string &basepath_, const char *relpath_) { struct stat st; return fs::exists(basepath_,relpath_,&st); } } mergerfs-2.33.5/src/xattr.hpp0000644000000000000000000000234214225522122014554 0ustar rootroot/* Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #if defined(__ANDROID__) # include #elif defined(__APPLE__) # include #elif defined(__linux__) # include #endif #ifdef USE_XATTR # ifdef __linux__ # include # include # else # undef USE_XATTR # warning "USE_XATTR unset: xattrs disabled" # endif #endif #ifndef XATTR_CREATE # define XATTR_CREATE 0x1 #endif #ifndef XATTR_REPLACE # define XATTR_REPLACE 0x2 #endif mergerfs-2.33.5/src/fuse_getxattr.cpp0000644000000000000000000001341614225522122016275 0ustar rootroot/* Copyright (c) 2018, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "config.hpp" #include "errno.hpp" #include "fs_findallfiles.hpp" #include "fs_lgetxattr.hpp" #include "fs_path.hpp" #include "fs_statvfs_cache.hpp" #include "str.hpp" #include "ugid.hpp" #include "version.hpp" #include "fuse.h" #include #include #include #include #include static const char SECURITY_CAPABILITY[] = "security.capability"; using std::string; namespace l { static bool is_attrname_security_capability(const char *attrname_) { return (strcmp(attrname_,SECURITY_CAPABILITY) == 0); } static int lgetxattr(const string &path_, const char *attrname_, void *value_, const size_t size_) { int rv; rv = fs::lgetxattr(path_,attrname_,value_,size_); return ((rv == -1) ? -errno : rv); } static int getxattr_controlfile(Config::Read &cfg_, const char *attrname_, char *buf_, const size_t count_) { int rv; size_t len; string key; string val; StrVec attr; if(!str::startswith(attrname_,"user.mergerfs.")) return -ENOATTR; key = &attrname_[14]; rv = cfg_->get(key,&val); if(rv < 0) return rv; len = val.size(); if(count_ == 0) return len; if(count_ < len) return -ERANGE; memcpy(buf_,val.c_str(),len); return (int)len; } static int getxattr_from_string(char *destbuf_, const size_t destbufsize_, const string &src_) { const size_t srcbufsize = src_.size(); if(destbufsize_ == 0) return srcbufsize; if(srcbufsize > destbufsize_) return -ERANGE; memcpy(destbuf_,src_.data(),srcbufsize); return srcbufsize; } static int getxattr_user_mergerfs_allpaths(const Branches::CPtr &branches_, const char *fusepath_, char *buf_, const size_t count_) { string concated; StrVec paths; StrVec branches; branches_->to_paths(branches); fs::findallfiles(branches,fusepath_,&paths); concated = str::join(paths,'\0'); return l::getxattr_from_string(buf_,count_,concated); } static int getxattr_user_mergerfs(const string &basepath_, const char *fusepath_, const string &fullpath_, const Branches &branches_, const char *attrname_, char *buf_, const size_t count_) { StrVec attr; str::split(attrname_,'.',&attr); if(attr[2] == "basepath") return l::getxattr_from_string(buf_,count_,basepath_); else if(attr[2] == "relpath") return l::getxattr_from_string(buf_,count_,fusepath_); else if(attr[2] == "fullpath") return l::getxattr_from_string(buf_,count_,fullpath_); else if(attr[2] == "allpaths") return l::getxattr_user_mergerfs_allpaths(branches_,fusepath_,buf_,count_); return -ENOATTR; } static int getxattr(const Policy::Search &searchFunc_, const Branches &branches_, const char *fusepath_, const char *attrname_, char *buf_, const size_t count_) { int rv; string fullpath; StrVec basepaths; rv = searchFunc_(branches_,fusepath_,&basepaths); if(rv == -1) return -errno; fullpath = fs::path::make(basepaths[0],fusepath_); if(str::startswith(attrname_,"user.mergerfs.")) return l::getxattr_user_mergerfs(basepaths[0], fusepath_, fullpath, branches_, attrname_, buf_, count_); return l::lgetxattr(fullpath,attrname_,buf_,count_); } } namespace FUSE { int getxattr(const char *fusepath_, const char *attrname_, char *buf_, size_t count_) { Config::Read cfg; if(fusepath_ == CONTROLFILE) return l::getxattr_controlfile(cfg, attrname_, buf_, count_); if((cfg->security_capability == false) && l::is_attrname_security_capability(attrname_)) return -ENOATTR; if(cfg->xattr.to_int()) return -cfg->xattr.to_int(); const fuse_context *fc = fuse_get_context(); const ugid::Set ugid(fc->uid,fc->gid); return l::getxattr(cfg->func.getxattr.policy, cfg->branches, fusepath_, attrname_, buf_, count_); } } mergerfs-2.33.5/src/fs_sendfile_linux.icpp0000644000000000000000000000201614225522122017254 0ustar rootroot/* Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "errno.hpp" #include namespace fs { ssize_t sendfile(const int fdin, const int fdout, const size_t count) { off_t offset = 0; return ::sendfile(fdout,fdin,&offset,count); } } mergerfs-2.33.5/src/fuse_destroy.cpp0000644000000000000000000000151014225522122016114 0ustar rootroot/* Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ namespace FUSE { void destroy(void) { } } mergerfs-2.33.5/src/fuse_symlink.cpp0000644000000000000000000001065114225522122016117 0ustar rootroot/* Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "config.hpp" #include "errno.hpp" #include "fs_clonepath.hpp" #include "fs_lstat.hpp" #include "fs_path.hpp" #include "fs_inode.hpp" #include "fs_symlink.hpp" #include "fuse_getattr.hpp" #include "ugid.hpp" #include "fuse.h" #include #include #include using std::string; namespace error { static inline int calc(const int rv_, const int prev_, const int cur_) { if(rv_ == -1) { if(prev_ == 0) return 0; return cur_; } return 0; } } namespace l { static int symlink_loop_core(const string &newbasepath_, const char *target_, const char *linkpath_, struct stat *st_, const int error_) { int rv; string fullnewpath; fullnewpath = fs::path::make(newbasepath_,linkpath_); rv = fs::symlink(target_,fullnewpath); if((rv != -1) && (st_ != NULL) && (st_->st_ino == 0)) { fs::lstat(fullnewpath,st_); if(st_->st_ino != 0) fs::inode::calc(linkpath_,st_); } return error::calc(rv,error_,errno); } static int symlink_loop(const string &existingpath_, const StrVec &newbasepaths_, const char *target_, const char *linkpath_, const string &newdirpath_, struct stat *st_) { int rv; int error; error = -1; for(size_t i = 0, ei = newbasepaths_.size(); i != ei; i++) { rv = fs::clonepath_as_root(existingpath_,newbasepaths_[i],newdirpath_); if(rv == -1) error = error::calc(rv,error,errno); else error = l::symlink_loop_core(newbasepaths_[i], target_, linkpath_, st_, error); } return -error; } static int symlink(const Policy::Search &searchFunc_, const Policy::Create &createFunc_, const Branches &branches_, const char *target_, const char *linkpath_, struct stat *st_) { int rv; string newdirpath; StrVec newbasepaths; StrVec existingpaths; newdirpath = fs::path::dirname(linkpath_); rv = searchFunc_(branches_,newdirpath,&existingpaths); if(rv == -1) return -errno; rv = createFunc_(branches_,newdirpath,&newbasepaths); if(rv == -1) return -errno; return l::symlink_loop(existingpaths[0],newbasepaths, target_,linkpath_,newdirpath,st_); } } namespace FUSE { int symlink(const char *target_, const char *linkpath_, struct stat *st_, fuse_timeouts_t *timeouts_) { int rv; Config::Read cfg; const fuse_context *fc = fuse_get_context(); const ugid::Set ugid(fc->uid,fc->gid); rv = l::symlink(cfg->func.getattr.policy, cfg->func.symlink.policy, cfg->branches, target_, linkpath_, st_); if(timeouts_ != NULL) { switch(cfg->follow_symlinks) { case FollowSymlinks::ENUM::NEVER: timeouts_->entry = ((rv >= 0) ? cfg->cache_entry : cfg->cache_negative_entry); timeouts_->attr = cfg->cache_attr; break; default: timeouts_->entry = 0; timeouts_->attr = 0; break; } } return rv; } } mergerfs-2.33.5/src/fs_lremovexattr.hpp0000644000000000000000000000215614225522122016641 0ustar rootroot/* ISC License Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include "errno.hpp" #include "xattr.hpp" #include namespace fs { static inline int lremovexattr(const std::string &path_, const char *attrname_) { #ifdef USE_XATTR return ::lremovexattr(path_.c_str(),attrname_); #else return (errno=ENOTSUP,-1); #endif } } mergerfs-2.33.5/src/fs_cow.cpp0000644000000000000000000000602514225522122014667 0ustar rootroot/* ISC License Copyright (c) 2018, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "errno.hpp" #include "fs_clonefile.hpp" #include "fs_close.hpp" #include "fs_lstat.hpp" #include "fs_mktemp.hpp" #include "fs_open.hpp" #include "fs_rename.hpp" #include "fs_unlink.hpp" #include #include #include #include #include using std::string; namespace l { static int cleanup_on_error(const int src_fd_, const int dst_fd_ = -1, const string &dst_fullpath_ = string()) { int error = errno; if(src_fd_ >= 0) fs::close(src_fd_); if(dst_fd_ >= 0) fs::close(dst_fd_); if(!dst_fullpath_.empty()) fs::unlink(dst_fullpath_); errno = error; return -1; } } namespace fs { namespace cow { bool is_eligible(const int flags_) { int accmode; accmode = (flags_ & O_ACCMODE); return ((accmode == O_RDWR) || (accmode == O_WRONLY)); } bool is_eligible(const struct stat &st_) { return ((S_ISREG(st_.st_mode)) && (st_.st_nlink > 1)); } bool is_eligible(const int flags_, const struct stat &st_) { return (is_eligible(flags_) && is_eligible(st_)); } bool is_eligible(const char *fullpath_, const int flags_) { int rv; struct stat st; if(!fs::cow::is_eligible(flags_)) return false; rv = fs::lstat(fullpath_,&st); if(rv == -1) return false; return fs::cow::is_eligible(st); } int break_link(const char *src_fullpath_) { int rv; int src_fd; int dst_fd; string dst_fullpath; src_fd = fs::open(src_fullpath_,O_RDONLY|O_NOFOLLOW); if(src_fd == -1) return -1; dst_fullpath = src_fullpath_; dst_fd = fs::mktemp(&dst_fullpath,O_WRONLY); if(dst_fd == -1) return l::cleanup_on_error(src_fd); rv = fs::clonefile(src_fd,dst_fd); if(rv == -1) return l::cleanup_on_error(src_fd,dst_fd,dst_fullpath); rv = fs::rename(dst_fullpath,src_fullpath_); if(rv == -1) return l::cleanup_on_error(src_fd,dst_fd,dst_fullpath); fs::close(src_fd); fs::close(dst_fd); return 0; } } } mergerfs-2.33.5/src/fuse_removexattr.cpp0000644000000000000000000000673314225522122017017 0ustar rootroot/* Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "config.hpp" #include "errno.hpp" #include "fs_lremovexattr.hpp" #include "fs_path.hpp" #include "policy_rv.hpp" #include "ugid.hpp" #include "fuse.h" #include #include using std::string; using std::vector; namespace l { static int get_error(const PolicyRV &prv_, const string &basepath_) { for(int i = 0, ei = prv_.success.size(); i < ei; i++) { if(prv_.success[i].basepath == basepath_) return prv_.success[i].rv; } for(int i = 0, ei = prv_.error.size(); i < ei; i++) { if(prv_.error[i].basepath == basepath_) return prv_.error[i].rv; } return 0; } static void removexattr_loop_core(const string &basepath_, const char *fusepath_, const char *attrname_, PolicyRV *prv_) { string fullpath; fullpath = fs::path::make(basepath_,fusepath_); errno = 0; fs::lremovexattr(fullpath,attrname_); prv_->insert(errno,basepath_); } static void removexattr_loop(const vector &basepaths_, const char *fusepath_, const char *attrname_, PolicyRV *prv_) { for(size_t i = 0, ei = basepaths_.size(); i != ei; i++) { l::removexattr_loop_core(basepaths_[i],fusepath_,attrname_,prv_); } } static int removexattr(const Policy::Action &actionFunc_, const Policy::Search &searchFunc_, const Branches &branches_, const char *fusepath_, const char *attrname_) { int rv; PolicyRV prv; vector basepaths; rv = actionFunc_(branches_,fusepath_,&basepaths); if(rv == -1) return -errno; l::removexattr_loop(basepaths,fusepath_,attrname_,&prv); if(prv.error.empty()) return 0; if(prv.success.empty()) return prv.error[0].rv; basepaths.clear(); rv = searchFunc_(branches_,fusepath_,&basepaths); if(rv == -1) return -errno; return l::get_error(prv,basepaths[0]); } } namespace FUSE { int removexattr(const char *fusepath_, const char *attrname_) { Config::Read cfg; if(fusepath_ == CONTROLFILE) return -ENOATTR; if(cfg->xattr.to_int()) return -cfg->xattr.to_int(); const fuse_context *fc = fuse_get_context(); const ugid::Set ugid(fc->uid,fc->gid); return l::removexattr(cfg->func.removexattr.policy, cfg->func.getxattr.policy, cfg->branches, fusepath_, attrname_); } } mergerfs-2.33.5/src/fuse_fallocate.cpp0000644000000000000000000000300114225522122016352 0ustar rootroot/* Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "errno.hpp" #include "fileinfo.hpp" #include "fs_fallocate.hpp" #include "fuse.h" namespace l { static int fallocate(const int fd_, const int mode_, const off_t offset_, const off_t len_) { int rv; rv = fs::fallocate(fd_,mode_,offset_,len_); return ((rv == -1) ? -errno : 0); } } namespace FUSE { int fallocate(const fuse_file_info_t *ffi_, int mode_, off_t offset_, off_t len_) { FileInfo *fi = reinterpret_cast(ffi_->fh); return l::fallocate(fi->fd, mode_, offset_, len_); } } mergerfs-2.33.5/src/fs_dup.hpp0000644000000000000000000000164514225522122014677 0ustar rootroot/* ISC License Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include namespace fs { static inline int dup(const int fd_) { return ::dup(fd_); } } mergerfs-2.33.5/src/policy_rand.cpp0000644000000000000000000000407714225522122015717 0ustar rootroot/* Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "errno.hpp" #include "policy.hpp" #include "policy_rand.hpp" #include "policies.hpp" #include int Policy::Rand::Action::operator()(const Branches::CPtr &branches_, const char *fusepath_, StrVec *paths_) const { int rv; rv = Policies::Action::all(branches_,fusepath_,paths_); if(rv == 0) { std::random_shuffle(paths_->begin(),paths_->end()); paths_->resize(1); } return rv; } int Policy::Rand::Create::operator()(const Branches::CPtr &branches_, const char *fusepath_, StrVec *paths_) const { int rv; rv = Policies::Create::all(branches_,fusepath_,paths_); if(rv == 0) { std::random_shuffle(paths_->begin(),paths_->end()); paths_->resize(1); } return rv; } int Policy::Rand::Search::operator()(const Branches::CPtr &branches_, const char *fusepath_, StrVec *paths_) const { int rv; rv = Policies::Search::all(branches_,fusepath_,paths_); if(rv == 0) { std::random_shuffle(paths_->begin(),paths_->end()); paths_->resize(1); } return rv; } mergerfs-2.33.5/src/fuse_ftruncate.hpp0000644000000000000000000000172014225522122016426 0ustar rootroot/* Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include "fuse.h" #include #include namespace FUSE { int ftruncate(const fuse_file_info_t *ffi, off_t size); } mergerfs-2.33.5/src/fuse_prepare_hide.cpp0000644000000000000000000000221014225522122017050 0ustar rootroot/* ISC License Copyright (c) 2019, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "fuse_open.hpp" #include #include namespace FUSE { int prepare_hide(const char *fusepath_, uint64_t *fh_) { int rv; fuse_file_info_t ffi = {0}; ffi.flags = O_RDONLY|O_NOFOLLOW; rv = FUSE::open(fusepath_,&ffi); if(rv < 0) return rv; *fh_ = ffi.fh; return 0; } } mergerfs-2.33.5/src/policy_rv.hpp0000644000000000000000000000245714225522122015427 0ustar rootroot/* ISC License Copyright (c) 2020, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include #include struct PolicyRV { struct RV { RV(const int rv_, const std::string &basepath_) : rv(rv_), basepath(basepath_) { } int rv; std::string basepath; }; std::vector success; std::vector error; void insert(const int err_, const std::string &basepath_) { if(err_ == 0) success.push_back(RV(err_,basepath_)); else error.push_back(RV(-err_,basepath_)); } }; mergerfs-2.33.5/src/policy_newest.hpp0000644000000000000000000000314514225522122016300 0ustar rootroot/* ISC License Copyright (c) 2020, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include "policy.hpp" namespace Policy { namespace Newest { class Action final : public Policy::ActionImpl { public: Action() : Policy::ActionImpl("newest") {} public: int operator()(const Branches::CPtr&,const char*,StrVec*) const final; }; class Create final : public Policy::CreateImpl { public: Create() : Policy::CreateImpl("newest") {} public: int operator()(const Branches::CPtr&,const char*,StrVec*) const final; bool path_preserving() const final { return false; } }; class Search final : public Policy::SearchImpl { public: Search() : Policy::SearchImpl("newest") {} public: int operator()(const Branches::CPtr&,const char*,StrVec*) const final; }; } } mergerfs-2.33.5/src/fuse_read_buf.hpp0000644000000000000000000000232514225522122016204 0ustar rootroot/* Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include "fuse.h" #include namespace FUSE { int read_buf(const fuse_file_info_t *ffi, struct fuse_bufvec **buf, size_t size, off_t offset); int read_buf_null(const fuse_file_info_t *ffi, struct fuse_bufvec **buf, size_t size, off_t offset); } mergerfs-2.33.5/src/fuse_init.hpp0000644000000000000000000000156114225522122015401 0ustar rootroot/* Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include "fuse.h" namespace FUSE { void * init(fuse_conn_info *conn); } mergerfs-2.33.5/src/fuse_write.cpp0000644000000000000000000000663714225522122015574 0ustar rootroot/* Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "config.hpp" #include "errno.hpp" #include "fileinfo.hpp" #include "fs_movefile.hpp" #include "fs_write.hpp" #include "ugid.hpp" #include "fuse.h" #include #include using std::string; using std::vector; typedef int (*WriteFunc)(const int,const void*,const size_t,const off_t); namespace l { static bool out_of_space(const int error_) { return ((error_ == ENOSPC) || (error_ == EDQUOT)); } static int write_regular(const int fd_, const void *buf_, const size_t count_, const off_t offset_) { int rv; rv = fs::pwrite(fd_,buf_,count_,offset_); if(rv == -1) return -errno; if(rv == 0) return 0; return count_; } static int write_direct_io(const int fd_, const void *buf_, const size_t count_, const off_t offset_) { int rv; rv = fs::pwrite(fd_,buf_,count_,offset_); if(rv == -1) return -errno; return rv; } static int move_and_write(WriteFunc func_, const char *buf_, const size_t count_, const off_t offset_, FileInfo *fi_, int err_) { int rv; Config::Read cfg; if(cfg->moveonenospc.enabled == false) return err_; rv = fs::movefile_as_root(cfg->moveonenospc.policy, cfg->branches, fi_->fusepath, &fi_->fd); if(rv == -1) return err_; return func_(fi_->fd,buf_,count_,offset_); } static int write(const fuse_file_info_t *ffi_, WriteFunc func_, const char *buf_, const size_t count_, const off_t offset_) { int rv; FileInfo* fi; fi = reinterpret_cast(ffi_->fh); rv = func_(fi->fd,buf_,count_,offset_); if(l::out_of_space(-rv)) rv = l::move_and_write(func_,buf_,count_,offset_,fi,rv); return rv; } } namespace FUSE { int write(const fuse_file_info_t *ffi_, const char *buf_, size_t count_, off_t offset_) { WriteFunc wf; wf = ((ffi_->direct_io) ? l::write_direct_io : l::write_regular); return l::write(ffi_,wf,buf_,count_,offset_); } int write_null(const fuse_file_info_t *ffi_, const char *buf_, size_t count_, off_t offset_) { return count_; } } mergerfs-2.33.5/src/policy_error.hpp0000644000000000000000000000261114225522122016121 0ustar rootroot/* ISC License Copyright (c) 2018, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #define error_and_continue(CUR,ERR) \ { \ policy::calc_error(CUR,ERR); \ continue; \ } namespace policy { static inline void calc_error(int &cur_, int err_) { switch(cur_) { default: case ENOENT: cur_ = err_; break; case ENOSPC: if(err_ != ENOENT) cur_ = err_; break; case EROFS: if((err_ != ENOENT) && (err_ != ENOSPC)) cur_ = err_; break; } } } mergerfs-2.33.5/src/fuse_fsyncdir.cpp0000644000000000000000000000246414225522122016255 0ustar rootroot/* Copyright (c) 2017, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "errno.hpp" #include "dirinfo.hpp" #include "fs_fsync.hpp" #include "fuse.h" #include #include namespace l { static int fsyncdir(const DirInfo *di_, const int isdatasync_) { int rv; rv = -1; errno = ENOSYS; return ((rv == -1) ? -errno : 0); } } namespace FUSE { int fsyncdir(const fuse_file_info_t *ffi_, int isdatasync_) { DirInfo *di = reinterpret_cast(ffi_->fh); return l::fsyncdir(di,isdatasync_); } } mergerfs-2.33.5/src/fs_findonfs.cpp0000644000000000000000000000355414225522122015711 0ustar rootroot/* ISC License Copyright (c) 2020, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "branches.hpp" #include "errno.hpp" #include "fs_fstat.hpp" #include "fs_lstat.hpp" #include "fs_path.hpp" #include namespace l { static int findonfs(const Branches::CPtr &branches_, const std::string &fusepath_, const int fd_, std::string *basepath_) { int rv; dev_t dev; struct stat st; std::string fullpath; rv = fs::fstat(fd_,&st); if(rv == -1) return -1; dev = st.st_dev; for(const auto &branch : *branches_) { fullpath = fs::path::make(branch.path,fusepath_); rv = fs::lstat(fullpath,&st); if(rv == -1) continue; if(st.st_dev != dev) continue; *basepath_ = branch.path; return 0; } return (errno=ENOENT,-1); } } namespace fs { int findonfs(const Branches::CPtr &branches_, const std::string &fusepath_, const int fd_, std::string *basepath_) { return l::findonfs(branches_,fusepath_,fd_,basepath_); } } mergerfs-2.33.5/src/fuse_opendir.cpp0000644000000000000000000000223014225522122016063 0ustar rootroot/* Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "config.hpp" #include "dirinfo.hpp" #include "fuse.h" namespace FUSE { int opendir(const char *fusepath_, fuse_file_info_t *ffi_) { Config::Read cfg; ffi_->fh = reinterpret_cast(new DirInfo(fusepath_)); if(cfg->cache_readdir) { ffi_->keep_cache = 1; ffi_->cache_readdir = 1; } return 0; } } mergerfs-2.33.5/src/fuse_write_buf.hpp0000644000000000000000000000217414225522122016425 0ustar rootroot/* Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include "fuse.h" #include namespace FUSE { int write_buf(const fuse_file_info_t *ffi, struct fuse_bufvec *buf, off_t offset); int write_buf_null(const fuse_file_info_t *ffi, struct fuse_bufvec *buf, off_t offset); } mergerfs-2.33.5/src/str.hpp0000644000000000000000000000472614225522122014232 0ustar rootroot/* Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include #include #include namespace str { void split(const char *str, const char delimiter, std::vector *result); void split(const std::string &str, const char delimiter, std::vector *result); void rsplit1(const std::string &str, const char delimiter, std::vector *result); void splitkv(const std::string &str, const char delimiter, std::string *key, std::string *value); std::string join(const std::vector &vec, const size_t substridx, const char sep); std::string join(const std::vector &vec, const char sep); std::string join(const std::set &s, const char sep); size_t longest_common_prefix_index(const std::vector &vec); std::string longest_common_prefix(const std::vector &vec); std::string remove_common_prefix_and_join(const std::vector &vec, const char sep); void erase_fnmatches(const std::vector &pattern, std::vector &strs); bool isprefix(const std::string &s0, const std::string &s1); bool startswith(const std::string &str_, const std::string &prefix_); bool endswith(const std::string &str_, const std::string &suffix_); std::string trim(const std::string &str); } mergerfs-2.33.5/src/stat_util.hpp0000644000000000000000000000231414225522122015421 0ustar rootroot/* ISC License Copyright (c) 2020, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include namespace StatUtil { static inline bool empty(const struct stat &st_) { return (st_.st_size == 0); } static inline bool writable(const struct stat &st_) { return (st_.st_mode & (S_IWUSR|S_IWGRP|S_IWOTH)); } static inline bool writable_or_not_empty(const struct stat &st_) { return (StatUtil::writable(st_) || !StatUtil::empty(st_)); } } mergerfs-2.33.5/src/policy_eplus.hpp0000644000000000000000000000314414225522122016122 0ustar rootroot/* ISC License Copyright (c) 2020, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include "policy.hpp" namespace Policy { namespace EPLUS { class Action final : public Policy::ActionImpl { public: Action() : Policy::ActionImpl("eplus") {} public: int operator()(const Branches::CPtr&,const char*,StrVec*) const final; }; class Create final : public Policy::CreateImpl { public: Create() : Policy::CreateImpl("eplus") {} public: int operator()(const Branches::CPtr&,const char*,StrVec*) const final; bool path_preserving(void) const final { return true; } }; class Search final : public Policy::SearchImpl { public: Search() : Policy::SearchImpl("eplus") {} public: int operator()(const Branches::CPtr&,const char*,StrVec*) const final; }; } } mergerfs-2.33.5/src/fuse_create.hpp0000644000000000000000000000171714225522122015704 0ustar rootroot/* Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include "fuse.h" #include namespace FUSE { int create(const char *fusepath, mode_t mode, fuse_file_info_t *ffi); } mergerfs-2.33.5/src/policy_eprand.hpp0000644000000000000000000000315014225522122016240 0ustar rootroot/* ISC License Copyright (c) 2020, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include "policy.hpp" namespace Policy { namespace EPRand { class Action final : public Policy::ActionImpl { public: Action() : Policy::ActionImpl("eprand") {} public: int operator()(const Branches::CPtr&,const char*,StrVec*) const final; }; class Create final : public Policy::CreateImpl { public: Create() : Policy::CreateImpl("eprand") {} public: int operator()(const Branches::CPtr&,const char*,StrVec*) const final; bool path_preserving(void) const final { return true; } }; class Search final : public Policy::SearchImpl { public: Search() : Policy::SearchImpl("eprand") {} public: int operator()(const Branches::CPtr&,const char*,StrVec*) const final; }; } } mergerfs-2.33.5/src/fs_rmdir.hpp0000644000000000000000000000205214225522122015215 0ustar rootroot/* ISC License Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include #include namespace fs { static inline int rmdir(const char *path_) { return ::rmdir(path_); } static inline int rmdir(const std::string &path_) { return fs::rmdir(path_.c_str()); } } mergerfs-2.33.5/src/policy_mspmfs.hpp0000644000000000000000000000314414225522122016277 0ustar rootroot/* ISC License Copyright (c) 2020, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include "policy.hpp" namespace Policy { namespace MSPMFS { class Action final : public Policy::ActionImpl { public: Action() : Policy::ActionImpl("mspmfs") {} public: int operator()(const Branches::CPtr&,const char*,StrVec*) const final; }; class Create final : public Policy::CreateImpl { public: Create() : Policy::CreateImpl("mspmfs") {} public: int operator()(const Branches::CPtr&,const char*,StrVec*) const final; bool path_preserving() const final { return true; } }; class Search final : public Policy::SearchImpl { public: Search() : Policy::SearchImpl("mspmfs") {} public: int operator()(const Branches::CPtr&,const char*,StrVec*) const final; }; } } mergerfs-2.33.5/src/option_parser.hpp0000644000000000000000000000165014225522122016277 0ustar rootroot/* Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include "config.hpp" #include "fuse.h" namespace options { void parse(fuse_args *args, Config::ErrVec *errs); } mergerfs-2.33.5/src/fuse_readdir_plus.hpp0000644000000000000000000000165114225522122017113 0ustar rootroot/* Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include "fuse.h" namespace FUSE { int readdir_plus(const fuse_file_info_t *ffi, fuse_dirents_t *buf); } mergerfs-2.33.5/src/hw_cpu.cpp0000644000000000000000000000200014225522122014661 0ustar rootroot/* ISC License Copyright (c) 2019, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include namespace hw { namespace cpu { int logical_core_count(void) { #if defined _SC_NPROCESSORS_ONLN return ::sysconf(_SC_NPROCESSORS_ONLN); #else return 1; #endif } } } mergerfs-2.33.5/src/config_readdir.hpp0000644000000000000000000000164114225522122016352 0ustar rootroot/* ISC License Copyright (c) 2020, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include "enum.hpp" enum class ReadDirEnum { POSIX, LINUX }; typedef Enum ReadDir; mergerfs-2.33.5/src/fuse_mkdir.hpp0000644000000000000000000000156614225522122015551 0ustar rootroot/* Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once namespace FUSE { int mkdir(const char *fusepath, mode_t mode); } mergerfs-2.33.5/src/fs_closedir.hpp0000644000000000000000000000170514225522122015710 0ustar rootroot/* ISC License Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include #include namespace fs { static inline int closedir(DIR *dirp_) { return ::closedir(dirp_); } } mergerfs-2.33.5/src/version.hpp0000644000000000000000000000007614225522122015101 0ustar rootroot#pragma once static const char MERGERFS_VERSION[] = "2.33.5"; mergerfs-2.33.5/src/fuse_unlink.cpp0000644000000000000000000000472014225522122015731 0ustar rootroot/* Copyright (c) 2019, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "config.hpp" #include "errno.hpp" #include "fs_path.hpp" #include "fs_unlink.hpp" #include "ugid.hpp" #include "fuse.h" #include #include #include using std::string; using std::vector; namespace error { static inline int calc(const int rv_, const int prev_, const int cur_) { if(prev_ != 0) return prev_; if(rv_ == -1) return cur_; return 0; } } namespace l { static int unlink_loop_core(const string &basepath_, const char *fusepath_, const int error_) { int rv; string fullpath; fullpath = fs::path::make(basepath_,fusepath_); rv = fs::unlink(fullpath); return error::calc(rv,error_,errno); } static int unlink_loop(const vector &basepaths_, const char *fusepath_) { int error; error = 0; for(size_t i = 0, ei = basepaths_.size(); i != ei; i++) { error = l::unlink_loop_core(basepaths_[i],fusepath_,error); } return -error; } static int unlink(const Policy::Action &unlinkPolicy_, const Branches &branches_, const char *fusepath_) { int rv; vector basepaths; rv = unlinkPolicy_(branches_,fusepath_,&basepaths); if(rv == -1) return -errno; return l::unlink_loop(basepaths,fusepath_); } } namespace FUSE { int unlink(const char *fusepath_) { Config::Read cfg; const fuse_context *fc = fuse_get_context(); const ugid::Set ugid(fc->uid,fc->gid); return l::unlink(cfg->func.unlink.policy, cfg->branches, fusepath_); } } mergerfs-2.33.5/src/symlinkify.hpp0000644000000000000000000000250514225522122015611 0ustar rootroot/* ISC License Copyright (c) 2017, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include #include namespace symlinkify { static inline bool can_be_symlink(const struct stat &st_, const time_t timeout_) { if(S_ISDIR(st_.st_mode) || (st_.st_mode & (S_IWUSR|S_IWGRP|S_IWOTH))) return false; const time_t now = ::time(NULL); return (((now - st_.st_mtime) > timeout_) && ((now - st_.st_ctime) > timeout_)); } static inline mode_t convert(const mode_t mode_) { return ((mode_ & ~S_IFMT) | S_IFLNK); } } mergerfs-2.33.5/src/fuse_fsync.cpp0000644000000000000000000000255414225522122015556 0ustar rootroot/* Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "errno.hpp" #include "fileinfo.hpp" #include "fs_fdatasync.hpp" #include "fs_fsync.hpp" #include "fuse.h" #include #include namespace l { static int fsync(const int fd_, const int isdatasync_) { int rv; rv = (isdatasync_ ? fs::fdatasync(fd_) : fs::fsync(fd_)); return ((rv == -1) ? -errno : 0); } } namespace FUSE { int fsync(const fuse_file_info_t *ffi_, int isdatasync_) { FileInfo *fi = reinterpret_cast(ffi_->fh); return l::fsync(fi->fd,isdatasync_); } } mergerfs-2.33.5/src/fuse_mknod.cpp0000644000000000000000000001003714225522122015537 0ustar rootroot/* Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "config.hpp" #include "errno.hpp" #include "fs_acl.hpp" #include "fs_mknod.hpp" #include "fs_clonepath.hpp" #include "fs_path.hpp" #include "ugid.hpp" #include "fuse.h" #include #include using std::string; using std::vector; namespace error { static inline int calc(const int rv_, const int prev_, const int cur_) { if(rv_ == -1) { if(prev_ == 0) return 0; return cur_; } return 0; } } namespace l { static inline int mknod_core(const string &fullpath_, mode_t mode_, const mode_t umask_, const dev_t dev_) { if(!fs::acl::dir_has_defaults(fullpath_)) mode_ &= ~umask_; return fs::mknod(fullpath_,mode_,dev_); } static int mknod_loop_core(const string &createpath_, const char *fusepath_, const mode_t mode_, const mode_t umask_, const dev_t dev_, const int error_) { int rv; string fullpath; fullpath = fs::path::make(createpath_,fusepath_); rv = l::mknod_core(fullpath,mode_,umask_,dev_); return error::calc(rv,error_,errno); } static int mknod_loop(const string &existingpath_, const vector &createpaths_, const char *fusepath_, const string &fusedirpath_, const mode_t mode_, const mode_t umask_, const dev_t dev_) { int rv; int error; error = -1; for(size_t i = 0, ei = createpaths_.size(); i != ei; i++) { rv = fs::clonepath_as_root(existingpath_,createpaths_[i],fusedirpath_); if(rv == -1) error = error::calc(rv,error,errno); else error = l::mknod_loop_core(createpaths_[i], fusepath_, mode_,umask_,dev_,error); } return -error; } static int mknod(const Policy::Search &searchFunc_, const Policy::Create &createFunc_, const Branches &branches_, const char *fusepath_, const mode_t mode_, const mode_t umask_, const dev_t dev_) { int rv; string fusedirpath; vector createpaths; vector existingpaths; fusedirpath = fs::path::dirname(fusepath_); rv = searchFunc_(branches_,fusedirpath,&existingpaths); if(rv == -1) return -errno; rv = createFunc_(branches_,fusedirpath,&createpaths); if(rv == -1) return -errno; return l::mknod_loop(existingpaths[0],createpaths, fusepath_,fusedirpath, mode_,umask_,dev_); } } namespace FUSE { int mknod(const char *fusepath_, mode_t mode_, dev_t rdev_) { Config::Read cfg; const fuse_context *fc = fuse_get_context(); const ugid::Set ugid(fc->uid,fc->gid); return l::mknod(cfg->func.getattr.policy, cfg->func.mknod.policy, cfg->branches, fusepath_, mode_, fc->umask, rdev_); } } mergerfs-2.33.5/src/fuse_flock.cpp0000644000000000000000000000235414225522122015530 0ustar rootroot/* Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "errno.hpp" #include "fileinfo.hpp" #include "fs_flock.hpp" #include "fuse.h" namespace l { static int flock(const int fd_, const int operation_) { int rv; rv = fs::flock(fd_,operation_); return ((rv == -1) ? -errno : 0); } } namespace FUSE { int flock(const fuse_file_info_t *ffi_, int op_) { FileInfo* fi = reinterpret_cast(ffi_->fh); return l::flock(fi->fd,op_); } } mergerfs-2.33.5/src/fs_mktemp.cpp0000644000000000000000000000336114225522122015374 0ustar rootroot/* ISC License Copyright (c) 2018, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "errno.hpp" #include "fs_open.hpp" #include #include #include using std::string; #define PADLEN 6 #define MAX_ATTEMPTS 10 static string generate_tmp_path(const string &base_) { string tmp; tmp = base_; if((tmp.size() + PADLEN + 1) > PATH_MAX) tmp.resize(tmp.size() - PADLEN - 1); tmp += '.'; for(int i = 0; i < PADLEN; i++) tmp += ('A' + (std::rand() % 26)); return tmp; } namespace fs { int mktemp(string *base_, const int flags_) { int fd; int count; int flags; string tmppath; fd = -1; count = MAX_ATTEMPTS; flags = (flags_ | O_EXCL | O_CREAT); while(count-- > 0) { tmppath = generate_tmp_path(*base_); fd = fs::open(tmppath,flags,S_IWUSR); if((fd == -1) && (errno == EEXIST)) continue; else if(fd != -1) *base_ = tmppath; return fd; } return (errno=EEXIST,-1); } } mergerfs-2.33.5/src/fs_clonefile.cpp0000644000000000000000000000367614225522122016050 0ustar rootroot/* Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "fs_fstat.hpp" #include "fs_copydata.hpp" #include "fs_attr.hpp" #include "fs_xattr.hpp" #include "fs_fchown.hpp" #include "fs_fchmod.hpp" #include "fs_futimens.hpp" namespace l { static bool ignorable_error(const int err_) { switch(err_) { case ENOTTY: case ENOTSUP: #if ENOTSUP != EOPNOTSUPP case EOPNOTSUPP: #endif return true; } return false; } } namespace fs { int clonefile(const int src_fd_, const int dst_fd_) { int rv; struct stat src_st; rv = fs::fstat(src_fd_,&src_st); if(rv == -1) return -1; rv = fs::copydata(src_fd_,dst_fd_,src_st.st_size); if(rv == -1) return -1; rv = fs::attr::copy(src_fd_,dst_fd_); if((rv == -1) && !l::ignorable_error(errno)) return -1; rv = fs::xattr::copy(src_fd_,dst_fd_); if((rv == -1) && !l::ignorable_error(errno)) return -1; rv = fs::fchown_check_on_error(dst_fd_,src_st); if(rv == -1) return -1; rv = fs::fchmod_check_on_error(dst_fd_,src_st); if(rv == -1) return -1; rv = fs::futimens(dst_fd_,src_st); if(rv == -1) return -1; return 0; } } mergerfs-2.33.5/src/fs_setfl.hpp0000644000000000000000000000162314225522122015220 0ustar rootroot/* ISC License Copyright (c) 2020, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include namespace fs { int setfl(const int fd, const mode_t mode); } mergerfs-2.33.5/src/fuse_chmod.hpp0000644000000000000000000000156614225522122015535 0ustar rootroot/* Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once namespace FUSE { int chmod(const char *fusepath, mode_t mode); } mergerfs-2.33.5/src/fuse_link.cpp0000644000000000000000000002352014225522122015365 0ustar rootroot/* Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "config.hpp" #include "errno.hpp" #include "fs_clonepath.hpp" #include "fs_link.hpp" #include "fs_lstat.hpp" #include "fs_path.hpp" #include "fuse_getattr.hpp" #include "fuse_symlink.hpp" #include "ghc/filesystem.hpp" #include "ugid.hpp" #include "fuse.h" #include #include using std::string; using std::vector; namespace gfs = ghc::filesystem; namespace error { static inline int calc(const int rv_, const int prev_, const int cur_) { if(rv_ == -1) { if(prev_ == 0) return 0; return cur_; } return 0; } } namespace l { static int link_create_path_loop(const StrVec &oldbasepaths_, const string &newbasepath_, const char *oldfusepath_, const char *newfusepath_, const string &newfusedirpath_) { int rv; int error; string oldfullpath; string newfullpath; error = -1; for(auto &oldbasepath : oldbasepaths_) { oldfullpath = fs::path::make(oldbasepath,oldfusepath_); newfullpath = fs::path::make(oldbasepath,newfusepath_); rv = fs::link(oldfullpath,newfullpath); if((rv == -1) && (errno == ENOENT)) { rv = fs::clonepath_as_root(newbasepath_,oldbasepath,newfusedirpath_); if(rv == 0) rv = fs::link(oldfullpath,newfullpath); } error = error::calc(rv,error,errno); } return -error; } static int link_create_path(const Policy::Search &searchFunc_, const Policy::Action &actionFunc_, const Branches &branches_, const char *oldfusepath_, const char *newfusepath_) { int rv; string newfusedirpath; StrVec oldbasepaths; StrVec newbasepaths; rv = actionFunc_(branches_,oldfusepath_,&oldbasepaths); if(rv == -1) return -errno; newfusedirpath = fs::path::dirname(newfusepath_); rv = searchFunc_(branches_,newfusedirpath,&newbasepaths); if(rv == -1) return -errno; return l::link_create_path_loop(oldbasepaths,newbasepaths[0], oldfusepath_,newfusepath_, newfusedirpath); } static int link_preserve_path_core(const string &oldbasepath_, const char *oldfusepath_, const char *newfusepath_, struct stat *st_, const int error_) { int rv; string oldfullpath; string newfullpath; oldfullpath = fs::path::make(oldbasepath_,oldfusepath_); newfullpath = fs::path::make(oldbasepath_,newfusepath_); rv = fs::link(oldfullpath,newfullpath); if((rv == -1) && (errno == ENOENT)) errno = EXDEV; if((rv == 0) && (st_->st_ino == 0)) rv = fs::lstat(oldfullpath,st_); return error::calc(rv,error_,errno); } static int link_preserve_path_loop(const StrVec &oldbasepaths_, const char *oldfusepath_, const char *newfusepath_, struct stat *st_) { int error; error = -1; for(auto &oldbasepath : oldbasepaths_) { error = l::link_preserve_path_core(oldbasepath, oldfusepath_, newfusepath_, st_, error); } return -error; } static int link_preserve_path(const Policy::Action &actionFunc_, const Branches &branches_, const char *oldfusepath_, const char *newfusepath_, struct stat *st_) { int rv; StrVec oldbasepaths; rv = actionFunc_(branches_,oldfusepath_,&oldbasepaths); if(rv == -1) return -errno; return l::link_preserve_path_loop(oldbasepaths, oldfusepath_, newfusepath_, st_); } static int link(Config::Read &cfg_, const char *oldpath_, const char *newpath_, struct stat *st_) { if(cfg_->func.create.policy.path_preserving() && !cfg_->ignorepponrename) return l::link_preserve_path(cfg_->func.link.policy, cfg_->branches, oldpath_, newpath_, st_); return l::link_create_path(cfg_->func.getattr.policy, cfg_->func.link.policy, cfg_->branches, oldpath_, newpath_); } static int link(Config::Read &cfg_, const char *oldpath_, const char *newpath_, struct stat *st_, fuse_timeouts_t *timeouts_) { int rv; rv = l::link(cfg_,oldpath_,newpath_,st_); if(rv < 0) return rv; return FUSE::getattr(newpath_,st_,timeouts_); } static int link_exdev_rel_symlink(const char *oldpath_, const char *newpath_, struct stat *st_, fuse_timeouts_t *timeouts_) { int rv; gfs::path target(oldpath_); gfs::path linkpath(newpath_); target = target.lexically_relative(linkpath.parent_path()); rv = FUSE::symlink(target.c_str(),linkpath.c_str()); if(rv == 0) rv = FUSE::getattr(oldpath_,st_,timeouts_); // Disable caching since we created a symlink but should be a regular. timeouts_->attr = 0; timeouts_->entry = 0; return rv; } static int link_exdev_abs_base_symlink(const Policy::Search &openPolicy_, const Branches::CPtr &branches_, const char *oldpath_, const char *newpath_, struct stat *st_, fuse_timeouts_t *timeouts_) { int rv; StrVec basepaths; std::string target; rv = openPolicy_(branches_,oldpath_,&basepaths); if(rv == -1) return -errno; target = fs::path::make(basepaths[0],oldpath_); rv = FUSE::symlink(target.c_str(),newpath_); if(rv == 0) rv = FUSE::getattr(oldpath_,st_,timeouts_); // Disable caching since we created a symlink but should be a regular. timeouts_->attr = 0; timeouts_->entry = 0; return rv; } static int link_exdev_abs_pool_symlink(const std::string &mount_, const char *oldpath_, const char *newpath_, struct stat *st_, fuse_timeouts_t *timeouts_) { int rv; StrVec basepaths; std::string target; target = fs::path::make(mount_,oldpath_); rv = FUSE::symlink(target.c_str(),newpath_); if(rv == 0) rv = FUSE::getattr(oldpath_,st_,timeouts_); // Disable caching since we created a symlink but should be a regular. timeouts_->attr = 0; timeouts_->entry = 0; return rv; } static int link_exdev(Config::Read &cfg_, const char *oldpath_, const char *newpath_, struct stat *st_, fuse_timeouts_t *timeouts_) { switch(cfg_->link_exdev) { case LinkEXDEV::ENUM::PASSTHROUGH: return -EXDEV; case LinkEXDEV::ENUM::REL_SYMLINK: return l::link_exdev_rel_symlink(oldpath_, newpath_, st_, timeouts_); case LinkEXDEV::ENUM::ABS_BASE_SYMLINK: return l::link_exdev_abs_base_symlink(cfg_->func.open.policy, cfg_->branches, oldpath_, newpath_, st_, timeouts_); case LinkEXDEV::ENUM::ABS_POOL_SYMLINK: return l::link_exdev_abs_pool_symlink(cfg_->mount, oldpath_, newpath_, st_, timeouts_); } return -EXDEV; } } namespace FUSE { int link(const char *oldpath_, const char *newpath_, struct stat *st_, fuse_timeouts_t *timeouts_) { int rv; Config::Read cfg; const fuse_context *fc = fuse_get_context(); const ugid::Set ugid(fc->uid,fc->gid); rv = l::link(cfg,oldpath_,newpath_,st_,timeouts_); if(rv == -EXDEV) return l::link_exdev(cfg,oldpath_,newpath_,st_,timeouts_); return rv; } } mergerfs-2.33.5/src/fuse_fchmod.cpp0000644000000000000000000000240214225522122015664 0ustar rootroot/* Copyright (c) 2019, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "errno.hpp" #include "fileinfo.hpp" #include "fs_fchmod.hpp" #include "fuse.h" namespace l { static int fchmod(const int fd_, const mode_t mode_) { int rv; rv = fs::fchmod(fd_,mode_); if(rv == -1) return -errno; return rv; } } namespace FUSE { int fchmod(const fuse_file_info_t *ffi_, const mode_t mode_) { FileInfo *fi = reinterpret_cast(ffi_->fh); return l::fchmod(fi->fd,mode_); } } mergerfs-2.33.5/src/assert.hpp0000644000000000000000000000206614225522122014716 0ustar rootroot/* Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #define STATIC_ASSERT(cond) assert::StaticAssert< (cond) >() #define STATIC_ARRAYLENGTH_ASSERT(array,size) STATIC_ASSERT(((sizeof(array)/sizeof(array[0]))==(size))) namespace assert { template struct StaticAssert; template<> struct StaticAssert {}; } mergerfs-2.33.5/src/fs_has_space.hpp0000644000000000000000000000167314225522122016036 0ustar rootroot/* ISC License Copyright (c) 2020, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include #include namespace fs { bool has_space(const std::string &str, const int64_t size); } mergerfs-2.33.5/src/fuse_ioctl.cpp0000644000000000000000000002130014225522122015534 0ustar rootroot/* Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "config.hpp" #include "dirinfo.hpp" #include "endian.hpp" #include "errno.hpp" #include "fileinfo.hpp" #include "fs_close.hpp" #include "fs_findallfiles.hpp" #include "fs_ioctl.hpp" #include "fs_open.hpp" #include "fs_path.hpp" #include "str.hpp" #include "ugid.hpp" #include #include #include #include using std::string; using std::vector; #ifndef _IOC_TYPE #define _IOC_TYPE(X) (((X) >> 8) & 0xFF) #endif typedef char IOCTL_BUF[4096]; #define IOCTL_APP_TYPE 0xDF #define IOCTL_FILE_INFO _IOWR(IOCTL_APP_TYPE,0,IOCTL_BUF) #ifndef FS_IOC_GETFLAGS # define FS_IOC_GETFLAGS _IOR('f',1,long) #endif #ifndef FS_IOC_SETFLAGS # define FS_IOC_SETFLAGS _IOW('f',2,long) #endif #ifndef FS_IOC_GETVERSION # define FS_IOC_GETVERSION _IOR('v',1,long) #endif #ifndef FS_IOC_SETVERSION # define FS_IOC_SETVERSION _IOW('v',2,long) #endif /* There is a bug with FUSE and these ioctl commands. The regular libfuse high level API assumes the output buffer size based on the command and gives no control over this. FS_IOC_GETFLAGS and FS_IOC_SETFLAGS however are defined as `long` when in fact it is an `int`. On 64bit systems where long is 8 bytes this can lead to libfuse telling the kernel to write 8 bytes and if the user only allocated an integer then it will overwrite the 4 bytes after the variable which could result in data corruption and/or crashes. I've modified the API to allow changing of the output buffer size. This fixes the issue on little endian systems because the lower 4 bytes are the same regardless of what the user allocated. However, on big endian systems that's not the case and it is not possible to safely handle the situation. https://lwn.net/Articles/575846/ */ namespace l { static int ioctl(const int fd_, const uint32_t cmd_, void *data_, uint32_t *out_bufsz_) { int rv; switch(cmd_) { case FS_IOC_GETFLAGS: case FS_IOC_SETFLAGS: case FS_IOC_GETVERSION: case FS_IOC_SETVERSION: if(endian::is_big() && (sizeof(long) != sizeof(int))) return -ENOTTY; if((data_ != NULL) && (*out_bufsz_ > 4)) *out_bufsz_ = 4; break; } rv = fs::ioctl(fd_,cmd_,data_); return ((rv == -1) ? -errno : rv); } static int ioctl_file(const fuse_file_info_t *ffi_, const uint32_t cmd_, void *data_, uint32_t *out_bufsz_) { FileInfo *fi = reinterpret_cast(ffi_->fh); const fuse_context *fc = fuse_get_context(); const ugid::Set ugid(fc->uid,fc->gid); return l::ioctl(fi->fd,cmd_,data_,out_bufsz_); } #ifndef O_NOATIME #define O_NOATIME 0 #endif static int ioctl_dir_base(const Policy::Search &searchFunc_, const Branches &branches_, const char *fusepath_, const uint32_t cmd_, void *data_, uint32_t *out_bufsz_) { int fd; int rv; string fullpath; StrVec basepaths; rv = searchFunc_(branches_,fusepath_,&basepaths); if(rv == -1) return -errno; fullpath = fs::path::make(basepaths[0],fusepath_); fd = fs::open(fullpath,O_RDONLY|O_NOATIME|O_NONBLOCK); if(fd == -1) return -errno; rv = l::ioctl(fd,cmd_,data_,out_bufsz_); fs::close(fd); return rv; } static int ioctl_dir(const fuse_file_info_t *ffi_, const uint32_t cmd_, void *data_, uint32_t *out_bufsz_) { Config::Read cfg; DirInfo *di = reinterpret_cast(ffi_->fh); const fuse_context *fc = fuse_get_context(); const ugid::Set ugid(fc->uid,fc->gid); return l::ioctl_dir_base(cfg->func.open.policy, cfg->branches, di->fusepath.c_str(), cmd_, data_, out_bufsz_); } static int strcpy(const std::string &s_, void *data_) { char *data = (char*)data_; if(s_.size() >= (sizeof(IOCTL_BUF) - 1)) return -ERANGE; memcpy(data,s_.c_str(),s_.size()); data[s_.size()] = '\0'; return s_.size(); } static int file_basepath(const Policy::Search &searchFunc_, const Branches &branches_, const char *fusepath_, void *data_) { int rv; StrVec basepaths; rv = searchFunc_(branches_,fusepath_,&basepaths); if(rv == -1) return -errno; return l::strcpy(basepaths[0],data_); } static int file_basepath(const fuse_file_info_t *ffi_, void *data_) { Config::Read cfg; std::string &fusepath = reinterpret_cast(ffi_->fh)->fusepath; return l::file_basepath(cfg->func.open.policy, cfg->branches, fusepath.c_str(), data_); } static int file_relpath(const fuse_file_info_t *ffi_, void *data_) { std::string &fusepath = reinterpret_cast(ffi_->fh)->fusepath; return l::strcpy(fusepath,data_); } static int file_fullpath(const Policy::Search &searchFunc_, const Branches &branches_, const string &fusepath_, void *data_) { int rv; string fullpath; StrVec basepaths; rv = searchFunc_(branches_,fusepath_,&basepaths); if(rv == -1) return -errno; fullpath = fs::path::make(basepaths[0],fusepath_); return l::strcpy(fullpath,data_); } static int file_fullpath(const fuse_file_info_t *ffi_, void *data_) { Config::Read cfg; std::string &fusepath = reinterpret_cast(ffi_->fh)->fusepath; return l::file_fullpath(cfg->func.open.policy, cfg->branches, fusepath, data_); } static int file_allpaths(const fuse_file_info_t *ffi_, void *data_) { Config::Read cfg; string concated; StrVec paths; StrVec branches; string &fusepath = reinterpret_cast(ffi_->fh)->fusepath; cfg->branches->to_paths(branches); fs::findallfiles(branches,fusepath.c_str(),&paths); concated = str::join(paths,'\0'); return l::strcpy(concated,data_); } static int file_info(const fuse_file_info_t *ffi_, void *data_) { char *key = (char*)data_; if(!strcmp("basepath",key)) return l::file_basepath(ffi_,data_); if(!strcmp("relpath",key)) return l::file_relpath(ffi_,data_); if(!strcmp("fullpath",key)) return l::file_fullpath(ffi_,data_); if(!strcmp("allpaths",key)) return l::file_allpaths(ffi_,data_); return -ENOATTR; } static bool is_mergerfs_ioctl_cmd(const unsigned long cmd_) { return (_IOC_TYPE(cmd_) == IOCTL_APP_TYPE); } static int ioctl_custom(const fuse_file_info_t *ffi_, unsigned long cmd_, void *data_) { switch(cmd_) { case IOCTL_FILE_INFO: return l::file_info(ffi_,data_); } return -ENOTTY; } } namespace FUSE { int ioctl(const fuse_file_info_t *ffi_, unsigned long cmd_, void *arg_, unsigned int flags_, void *data_, uint32_t *out_bufsz_) { if(l::is_mergerfs_ioctl_cmd(cmd_)) return l::ioctl_custom(ffi_,cmd_,data_); if(flags_ & FUSE_IOCTL_DIR) return l::ioctl_dir(ffi_,cmd_,data_,out_bufsz_); return l::ioctl_file(ffi_,cmd_,data_,out_bufsz_); } } mergerfs-2.33.5/src/mempools.hpp0000644000000000000000000000162014225522122015243 0ustar rootroot/* ISC License Copyright (c) 2020, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include "locked_fixed_mem_pool.hpp" extern LockedFixedMemPool<128 * 1024> g_DENTS_BUF_POOL; mergerfs-2.33.5/src/fs_lchmod.hpp0000644000000000000000000000341114225522122015346 0ustar rootroot/* ISC License Copyright (c) 2020, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include "fs_lstat.hpp" #include #include #define MODE_BITS (S_ISUID|S_ISGID|S_ISVTX|S_IRWXU|S_IRWXG|S_IRWXO) namespace fs { static inline int lchmod(const char *pathname_, const mode_t mode_) { #if defined __linux__ return ::chmod(pathname_,mode_); #else return ::lchmod(pathname_,mode_); #endif } static inline int lchmod(const std::string &pathname_, const mode_t mode_) { return fs::lchmod(pathname_.c_str(),mode_); } static inline int lchmod_check_on_error(const std::string &path_, const mode_t mode_) { int rv; rv = fs::lchmod(path_,mode_); if(rv == -1) { int error; struct stat st; error = errno; rv = fs::lstat(path_,&st); if(rv == -1) return -1; if((st.st_mode & MODE_BITS) != (mode_ & MODE_BITS)) return (errno=error,-1); } return 0; } } mergerfs-2.33.5/src/policy_eprand.cpp0000644000000000000000000000413114225522122016233 0ustar rootroot/* Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "errno.hpp" #include "policies.hpp" #include "policy.hpp" #include "policy_eprand.hpp" #include int Policy::EPRand::Action::operator()(const Branches::CPtr &branches_, const char *fusepath_, StrVec *paths_) const { int rv; rv = Policies::Action::epall(branches_,fusepath_,paths_); if(rv == 0) { std::random_shuffle(paths_->begin(),paths_->end()); paths_->resize(1); } return rv; } int Policy::EPRand::Create::operator()(const Branches::CPtr &branches_, const char *fusepath_, StrVec *paths_) const { int rv; rv = Policies::Create::epall(branches_,fusepath_,paths_); if(rv == 0) { std::random_shuffle(paths_->begin(),paths_->end()); paths_->resize(1); } return rv; } int Policy::EPRand::Search::operator()(const Branches::CPtr &branches_, const char *fusepath_, StrVec *paths_) const { int rv; rv = Policies::Search::epall(branches_,fusepath_,paths_); if(rv == 0) { std::random_shuffle(paths_->begin(),paths_->end()); paths_->resize(1); } return rv; } mergerfs-2.33.5/src/fs_getfl.hpp0000644000000000000000000000153714225522122015210 0ustar rootroot/* ISC License Copyright (c) 2020, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once namespace fs { int getfl(const int fd); } mergerfs-2.33.5/src/policies.cpp0000644000000000000000000001223614225522122015217 0ustar rootroot/* ISC License Copyright (c) 2020, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "policies.hpp" #define IFERTA(X) if(name_ == #X) return &Policies::Action::X; #define IFERTC(X) if(name_ == #X) return &Policies::Create::X; #define IFERTS(X) if(name_ == #X) return &Policies::Search::X; #define IFERT(FUNC) \ FUNC(all) \ FUNC(epall) \ FUNC(epff) \ FUNC(eplfs) \ FUNC(eplus) \ FUNC(epmfs) \ FUNC(eppfrd) \ FUNC(eprand) \ FUNC(erofs) \ FUNC(ff) \ FUNC(lfs) \ FUNC(lus) \ FUNC(mfs) \ FUNC(msplfs) \ FUNC(msplus) \ FUNC(mspmfs) \ FUNC(msppfrd) \ FUNC(newest) \ FUNC(pfrd) \ FUNC(rand) Policy::ActionImpl* Policies::Action::find(const std::string &name_) { IFERT(IFERTA); return NULL; } Policy::CreateImpl* Policies::Create::find(const std::string &name_) { IFERT(IFERTC); return NULL; } Policy::SearchImpl* Policies::Search::find(const std::string &name_) { IFERT(IFERTS); return NULL; } Policy::All::Action Policies::Action::all; Policy::EPAll::Action Policies::Action::epall; Policy::EPFF::Action Policies::Action::epff; Policy::EPLFS::Action Policies::Action::eplfs; Policy::EPLUS::Action Policies::Action::eplus; Policy::EPMFS::Action Policies::Action::epmfs; Policy::EPPFRD::Action Policies::Action::eppfrd; Policy::EPRand::Action Policies::Action::eprand; Policy::ERoFS::Action Policies::Action::erofs; Policy::FF::Action Policies::Action::ff; Policy::LFS::Action Policies::Action::lfs; Policy::LUS::Action Policies::Action::lus; Policy::MFS::Action Policies::Action::mfs; Policy::MSPLFS::Action Policies::Action::msplfs; Policy::MSPLUS::Action Policies::Action::msplus; Policy::MSPMFS::Action Policies::Action::mspmfs; Policy::MSPPFRD::Action Policies::Action::msppfrd; Policy::Newest::Action Policies::Action::newest; Policy::PFRD::Action Policies::Action::pfrd; Policy::Rand::Action Policies::Action::rand; Policy::All::Create Policies::Create::all; Policy::EPAll::Create Policies::Create::epall; Policy::EPFF::Create Policies::Create::epff; Policy::EPLFS::Create Policies::Create::eplfs; Policy::EPLUS::Create Policies::Create::eplus; Policy::EPMFS::Create Policies::Create::epmfs; Policy::EPPFRD::Create Policies::Create::eppfrd; Policy::EPRand::Create Policies::Create::eprand; Policy::ERoFS::Create Policies::Create::erofs; Policy::FF::Create Policies::Create::ff; Policy::LFS::Create Policies::Create::lfs; Policy::LUS::Create Policies::Create::lus; Policy::MFS::Create Policies::Create::mfs; Policy::MSPLFS::Create Policies::Create::msplfs; Policy::MSPLUS::Create Policies::Create::msplus; Policy::MSPMFS::Create Policies::Create::mspmfs; Policy::MSPPFRD::Create Policies::Create::msppfrd; Policy::Newest::Create Policies::Create::newest; Policy::PFRD::Create Policies::Create::pfrd; Policy::Rand::Create Policies::Create::rand; Policy::All::Search Policies::Search::all; Policy::EPAll::Search Policies::Search::epall; Policy::EPFF::Search Policies::Search::epff; Policy::EPLFS::Search Policies::Search::eplfs; Policy::EPLUS::Search Policies::Search::eplus; Policy::EPMFS::Search Policies::Search::epmfs; Policy::EPPFRD::Search Policies::Search::eppfrd; Policy::EPRand::Search Policies::Search::eprand; Policy::ERoFS::Search Policies::Search::erofs; Policy::FF::Search Policies::Search::ff; Policy::LFS::Search Policies::Search::lfs; Policy::LUS::Search Policies::Search::lus; Policy::MFS::Search Policies::Search::mfs; Policy::MSPLFS::Search Policies::Search::msplfs; Policy::MSPLUS::Search Policies::Search::msplus; Policy::MSPMFS::Search Policies::Search::mspmfs; Policy::MSPPFRD::Search Policies::Search::msppfrd; Policy::Newest::Search Policies::Search::newest; Policy::PFRD::Search Policies::Search::pfrd; Policy::Rand::Search Policies::Search::rand; mergerfs-2.33.5/src/policy.hpp0000644000000000000000000000760314225522122014716 0ustar rootroot/* Copyright (c) 2020, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include "branches.hpp" #include "strvec.hpp" #include namespace Policy { class ActionImpl { public: ActionImpl(const std::string &name_) : name(name_) { } public: std::string name; virtual int operator()(const Branches::CPtr&,const char*,StrVec*) const = 0; }; class Action { public: Action(ActionImpl *impl_) : impl(impl_) {} Action& operator=(ActionImpl *impl_) { impl = impl_; return *this; } const std::string& name(void) const { return impl->name; } int operator()(const Branches::CPtr &branches_, const char *fusepath_, StrVec *paths_) const { return (*impl)(branches_,fusepath_,paths_); } int operator()(const Branches::CPtr &branches_, const std::string &fusepath_, StrVec *paths_) const { return (*impl)(branches_,fusepath_.c_str(),paths_); } private: ActionImpl *impl; }; class CreateImpl { public: CreateImpl(const std::string &name_) : name(name_) { } public: std::string name; virtual int operator()(const Branches::CPtr&,const char*,StrVec*) const = 0; virtual bool path_preserving(void) const = 0; }; class Create { public: Create(CreateImpl *impl_) : impl(impl_) {} Create& operator=(CreateImpl *impl_) { impl = impl_; return *this; } const std::string& name(void) const { return impl->name; } bool path_preserving(void) const { return impl->path_preserving(); } int operator()(const Branches::CPtr &branches_, const char *fusepath_, StrVec *paths_) const { return (*impl)(branches_,fusepath_,paths_); } int operator()(const Branches::CPtr &branches_, const std::string &fusepath_, StrVec *paths_) const { return (*impl)(branches_,fusepath_.c_str(),paths_); } private: CreateImpl *impl; }; class SearchImpl { public: SearchImpl(const std::string &name_) : name(name_) { } public: std::string name; virtual int operator()(const Branches::CPtr&,const char*,StrVec*) const = 0; }; class Search { public: Search(SearchImpl *impl_) : impl(impl_) {} Search& operator=(SearchImpl *impl_) { impl = impl_; return *this; } const std::string& name(void) const { return impl->name; } int operator()(const Branches::CPtr &branches_, const char *fusepath_, StrVec *paths_) const { return (*impl)(branches_,fusepath_,paths_); } int operator()(const Branches::CPtr &branches_, const std::string &fusepath_, StrVec *paths_) const { return (*impl)(branches_,fusepath_.c_str(),paths_); } private: SearchImpl *impl; }; } mergerfs-2.33.5/src/fs_ioctl.hpp0000644000000000000000000000251414225522122015215 0ustar rootroot/* ISC License Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include namespace fs { static inline int ioctl(const int fd_, const unsigned long request_) { return ::ioctl(fd_,request_); } static inline int ioctl(const int fd_, const unsigned long request_, void *data_) { return ::ioctl(fd_,request_,data_); } static inline int ioctl(const int fd_, const unsigned long request_, const int int_) { return ::ioctl(fd_,request_,int_); } } mergerfs-2.33.5/src/config_moveonenospc.cpp0000644000000000000000000000254714225522122017454 0ustar rootroot/* ISC License Copyright (c) 2020, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "config_moveonenospc.hpp" #include "ef.hpp" #include "errno.hpp" #include "from_string.hpp" int MoveOnENOSPC::from_string(const std::string &s_) { int rv; std::string s; Policy::CreateImpl *tmp; rv = str::from(s_,&enabled); if((rv == 0) && (enabled == true)) s = "mfs"; ef(rv != 0) s = s_; else return 0; tmp = Policies::Create::find(s); if(tmp == NULL) return -EINVAL; policy = tmp; enabled = true; return 0; } std::string MoveOnENOSPC::to_string(void) const { if(enabled) return policy.name(); return "false"; } mergerfs-2.33.5/src/category.hpp0000644000000000000000000000471614225522122015236 0ustar rootroot/* Copyright (c) 2020, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include "tofrom_string.hpp" #include "funcs.hpp" #include "func.hpp" #include namespace Category { class Base : public ToFromString { public: int from_string(const std::string &s) final; std::string to_string() const final; protected: std::vector funcs; }; class Action final : public Base { private: Action(); public: Action(Funcs &funcs_) { funcs.push_back(&funcs_.chmod); funcs.push_back(&funcs_.chown); funcs.push_back(&funcs_.link); funcs.push_back(&funcs_.removexattr); funcs.push_back(&funcs_.rename); funcs.push_back(&funcs_.rmdir); funcs.push_back(&funcs_.setxattr); funcs.push_back(&funcs_.truncate); funcs.push_back(&funcs_.unlink); funcs.push_back(&funcs_.utimens); } }; class Create final : public Base { private: Create(); public: Create(Funcs &funcs_) { funcs.push_back(&funcs_.create); funcs.push_back(&funcs_.mkdir); funcs.push_back(&funcs_.mknod); funcs.push_back(&funcs_.symlink); } }; class Search final : public Base { private: Search(); public: Search(Funcs &funcs_) { funcs.push_back(&funcs_.access); funcs.push_back(&funcs_.getattr); funcs.push_back(&funcs_.getxattr); funcs.push_back(&funcs_.listxattr); funcs.push_back(&funcs_.open); funcs.push_back(&funcs_.readlink); } }; } class Categories final { private: Categories(); public: Categories(Funcs &funcs_) : action(funcs_), create(funcs_), search(funcs_) {} public: Category::Action action; Category::Create create; Category::Search search; }; mergerfs-2.33.5/src/fuse_prepare_hide.hpp0000644000000000000000000000164114225522122017064 0ustar rootroot/* ISC License Copyright (c) 2019, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include namespace FUSE { int prepare_hide(const char *name, uint64_t *fh); } mergerfs-2.33.5/src/config_moveonenospc.hpp0000644000000000000000000000230314225522122017447 0ustar rootroot/* ISC License Copyright (c) 2020, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include "policy.hpp" #include "policies.hpp" #include "tofrom_string.hpp" #include class MoveOnENOSPC : public ToFromString { public: MoveOnENOSPC(const bool enabled_) : enabled(enabled_), policy(&Policies::Create::mfs) { } public: int from_string(const std::string &s) final; std::string to_string() const final; public: bool enabled; Policy::Create policy; }; mergerfs-2.33.5/src/policy_epall.hpp0000644000000000000000000000317114225522122016067 0ustar rootroot/* ISC License Copyright (c) 2020, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include "policy.hpp" namespace Policy { namespace EPAll { class Action final : public Policy::ActionImpl { public: Action() : Policy::ActionImpl("epall") { } public: int operator()(const Branches::CPtr&,const char*,StrVec*) const final; }; class Create final : public Policy::CreateImpl { public: Create() : Policy::CreateImpl("epall") { } public: int operator()(const Branches::CPtr&,const char*,StrVec*) const final; bool path_preserving(void) const final { return true; } }; class Search final : public Policy::SearchImpl { public: Search() : Policy::SearchImpl("epall") { } public: int operator()(const Branches::CPtr&,const char*,StrVec*) const final; }; } } mergerfs-2.33.5/src/policy_epmfs.hpp0000644000000000000000000000317114225522122016104 0ustar rootroot/* ISC License Copyright (c) 2020, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include "policy.hpp" namespace Policy { namespace EPMFS { class Action final : public Policy::ActionImpl { public: Action() : Policy::ActionImpl("epmfs") { } public: int operator()(const Branches::CPtr&,const char*,StrVec*) const final; }; class Create final : public Policy::CreateImpl { public: Create() : Policy::CreateImpl("epmfs") { } public: int operator()(const Branches::CPtr&,const char*,StrVec*) const final; bool path_preserving(void) const final { return true; } }; class Search final : public Policy::SearchImpl { public: Search() : Policy::SearchImpl("epmfs") { } public: int operator()(const Branches::CPtr&,const char*,StrVec*) const final; }; } } mergerfs-2.33.5/src/config.hpp0000644000000000000000000001100614225522122014654 0ustar rootroot/* Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include "branches.hpp" #include "category.hpp" #include "config_cachefiles.hpp" #include "config_follow_symlinks.hpp" #include "config_inodecalc.hpp" #include "config_link_exdev.hpp" #include "config_log_metrics.hpp" #include "config_moveonenospc.hpp" #include "config_nfsopenhack.hpp" #include "config_readdir.hpp" #include "config_rename_exdev.hpp" #include "config_statfs.hpp" #include "config_statfsignore.hpp" #include "config_xattr.hpp" #include "enum.hpp" #include "errno.hpp" #include "funcs.hpp" #include "policy.hpp" #include "rwlock.hpp" #include "tofrom_wrapper.hpp" #include "fuse.h" #include #include #include #include #include #include typedef ToFromWrapper ConfigBOOL; typedef ToFromWrapper ConfigUINT64; typedef ToFromWrapper ConfigINT; typedef ToFromWrapper ConfigSTR; typedef std::map Str2TFStrMap; extern const std::string CONTROLFILE; class Config { public: struct Err { int err; std::string str; }; typedef std::vector ErrVec; public: class Read { public: Read(); public: inline const Config* operator->() const; private: const Config &_cfg; }; public: class Write { public: Write(); public: Config* operator->(); private: Config &_cfg; }; public: Config(); public: Config& operator=(const Config&); public: ConfigBOOL async_read; ConfigBOOL auto_cache; ConfigUINT64 minfreespace; Branches branches; ConfigUINT64 cache_attr; ConfigUINT64 cache_entry; CacheFiles cache_files; ConfigUINT64 cache_negative_entry; ConfigBOOL cache_readdir; ConfigUINT64 cache_statfs; ConfigBOOL cache_symlinks; Categories category; ConfigBOOL direct_io; ConfigBOOL dropcacheonclose; ConfigSTR fsname; FollowSymlinks follow_symlinks; Funcs func; ConfigUINT64 fuse_msg_size; ConfigBOOL ignorepponrename; InodeCalc inodecalc; ConfigBOOL kernel_cache; ConfigBOOL link_cow; LinkEXDEV link_exdev; LogMetrics log_metrics; ConfigSTR mount; MoveOnENOSPC moveonenospc; NFSOpenHack nfsopenhack; ConfigBOOL nullrw; ConfigUINT64 pid; ConfigBOOL posix_acl; ReadDir readdir; ConfigBOOL readdirplus; RenameEXDEV rename_exdev; ConfigBOOL security_capability; SrcMounts srcmounts; StatFS statfs; StatFSIgnore statfs_ignore; ConfigBOOL symlinkify; ConfigUINT64 symlinkify_timeout; ConfigINT threads; ConfigSTR version; ConfigBOOL writeback_cache; XAttr xattr; public: friend std::ostream& operator<<(std::ostream &s, const Config &c); public: bool has_key(const std::string &key) const; void keys(std::string &s) const; void keys_xattr(std::string &s) const; public: int get(const std::string &key, std::string *val) const; int set_raw(const std::string &key, const std::string &val); int set(const std::string &key, const std::string &val); int set(const std::string &kv); public: int from_stream(std::istream &istrm, ErrVec *errs); int from_file(const std::string &filepath, ErrVec *errs); private: Str2TFStrMap _map; private: static Config _singleton; public: friend class Read; friend class Write; }; std::ostream& operator<<(std::ostream &s,const Config::ErrVec &ev); inline Config::Read::Read() : _cfg(Config::_singleton) { } inline const Config* Config::Read::operator->() const { return &_cfg; } inline Config::Write::Write() : _cfg(Config::_singleton) { } inline Config* Config::Write::operator->() { return &_cfg; } mergerfs-2.33.5/src/fs_unlink.hpp0000644000000000000000000000205614225522122015404 0ustar rootroot/* ISC License Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include #include namespace fs { static inline int unlink(const char *path_) { return ::unlink(path_); } static inline int unlink(const std::string &path_) { return fs::unlink(path_.c_str()); } } mergerfs-2.33.5/src/ugid.cpp0000644000000000000000000000212114225522122014330 0ustar rootroot/* Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "gidcache.hpp" #if defined __linux__ and UGID_USE_RWLOCK == 0 #include "ugid_linux.icpp" #else #include "ugid_rwlock.icpp" #endif namespace ugid { void initgroups(const uid_t uid_, const gid_t gid_) { static __thread gid_t_cache cache = {0}; cache.initgroups(uid_,gid_); } } mergerfs-2.33.5/src/policy_all.hpp0000644000000000000000000000313514225522122015542 0ustar rootroot/* ISC License Copyright (c) 2020, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include "policy.hpp" namespace Policy { namespace All { class Action final : public Policy::ActionImpl { public: Action() : Policy::ActionImpl("all") {} public: int operator()(const Branches::CPtr&,const char*,StrVec*) const final; }; class Create final : public Policy::CreateImpl { public: Create() : Policy::CreateImpl("all") {} public: int operator()(const Branches::CPtr&,const char*,StrVec*) const final; bool path_preserving(void) const final { return false; } }; class Search final : public Policy::SearchImpl { public: Search() : Policy::SearchImpl("all") {} public: int operator()(const Branches::CPtr&,const char*,StrVec*) const final; }; } } mergerfs-2.33.5/src/policy_all.cpp0000644000000000000000000000470714225522122015543 0ustar rootroot/* Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "errno.hpp" #include "fs_exists.hpp" #include "fs_info.hpp" #include "fs_path.hpp" #include "policy.hpp" #include "policies.hpp" #include "policy_error.hpp" #include "strvec.hpp" #include using std::string; namespace all { static int create(const Branches::CPtr &branches_, StrVec *paths_) { int rv; int error; fs::info_t info; error = ENOENT; for(auto &branch : *branches_) { if(branch.ro_or_nc()) error_and_continue(error,EROFS); rv = fs::info(branch.path,&info); if(rv == -1) error_and_continue(error,ENOENT); if(info.readonly) error_and_continue(error,EROFS); if(info.spaceavail < branch.minfreespace()) error_and_continue(error,ENOSPC); paths_->push_back(branch.path); } if(paths_->empty()) return (errno=error,-1); return 0; } } int Policy::All::Action::operator()(const Branches::CPtr &branches_, const char *fusepath_, StrVec *paths_) const { return Policies::Action::epall(branches_,fusepath_,paths_); } int Policy::All::Create::operator()(const Branches::CPtr &branches_, const char *fusepath_, StrVec *paths_) const { return ::all::create(branches_,paths_); } int Policy::All::Search::operator()(const Branches::CPtr &branches_, const char *fusepath_, StrVec *paths_) const { return Policies::Search::epall(branches_,fusepath_,paths_); } mergerfs-2.33.5/src/fs_xattr.hpp0000644000000000000000000000361714225522122015252 0ustar rootroot/* Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include #include #include namespace fs { namespace xattr { using std::string; using std::vector; using std::map; int list(const string &path, vector *attrs); int list(const string &path, string *attrs); int list(const string &path, vector *attrs); int get(const string &path, const string &attr, vector *value); int get(const string &path, const string &attr, string *value); int get(const string &path, map *attrs); int set(const string &path, const string &key, const string &value, const int flags); int set(const int fd, const string &key, const string &value, const int flags); int set(const string &path, const map &attrs); int copy(const int fdin, const int fdout); int copy(const string &from, const string &to); } } mergerfs-2.33.5/src/fs_sendfile.cpp0000644000000000000000000000172314225522122015670 0ustar rootroot/* Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #ifdef __linux__ #warning "using fs_sendfile_linux.icpp" #include "fs_sendfile_linux.icpp" #else #warning "using fs_sendfile_unsupported.icpp" #include "fs_sendfile_unsupported.icpp" #endif mergerfs-2.33.5/src/fs_ficlone_unsupported.icpp0000644000000000000000000000166614225522122020345 0ustar rootroot/* ISC License Copyright (c) 2018, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "errno.hpp" namespace fs { int ficlone(const int src_fd_, const int dst_fd_) { return (errno=EOPNOTSUPP,-1); } } mergerfs-2.33.5/src/fuse_fchmod.hpp0000644000000000000000000000166514225522122015703 0ustar rootroot/* Copyright (c) 2019, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include "fuse.h" #include namespace FUSE { int fchmod(const fuse_file_info_t *ffi, const mode_t mode); } mergerfs-2.33.5/src/fs_mknod.hpp0000644000000000000000000000211414225522122015207 0ustar rootroot/* ISC License Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include #include #include #include namespace fs { static inline int mknod(const std::string &path_, const mode_t mode_, const dev_t dev_) { return ::mknod(path_.c_str(),mode_,dev_); } } mergerfs-2.33.5/src/fs_xattr.cpp0000644000000000000000000001644514225522122015250 0ustar rootroot/* Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "errno.hpp" #include "fs_close.hpp" #include "fs_fgetxattr.hpp" #include "fs_flistxattr.hpp" #include "fs_fsetxattr.hpp" #include "fs_lgetxattr.hpp" #include "fs_llistxattr.hpp" #include "fs_lremovexattr.hpp" #include "fs_lsetxattr.hpp" #include "fs_open.hpp" #include "str.hpp" #include #include #include #include using std::string; using std::vector; using std::map; using std::istringstream; namespace fs { namespace xattr { int list(const int fd_, vector *attrs_) { ssize_t rv; rv = -1; errno = ERANGE; while((rv == -1) && (errno == ERANGE)) { rv = fs::flistxattr(fd_,NULL,0); if(rv <= 0) return rv; attrs_->resize(rv); rv = fs::flistxattr(fd_,&(*attrs_)[0],rv); } return rv; } int list(const string &path_, vector *attrs_) { ssize_t rv; rv = -1; errno = ERANGE; while((rv == -1) && (errno == ERANGE)) { rv = fs::llistxattr(path_,NULL,0); if(rv <= 0) return rv; attrs_->resize(rv); rv = fs::llistxattr(path_,&(*attrs_)[0],rv); } return rv; } int list(const int fd_, vector *attrvector_) { int rv; vector attrs; rv = fs::xattr::list(fd_,&attrs); if(rv != -1) { string tmp(attrs.begin(),attrs.end()); str::split(tmp,'\0',attrvector_); } return rv; } int list(const string &path_, vector *attrvector_) { int rv; vector attrs; rv = fs::xattr::list(path_,&attrs); if(rv != -1) { string tmp(attrs.begin(),attrs.end()); str::split(tmp,'\0',attrvector_); } return rv; } int list(const int fd_, string *attrstr_) { int rv; vector attrs; rv = fs::xattr::list(fd_,&attrs); if(rv != -1) *attrstr_ = string(attrs.begin(),attrs.end()); return rv; } int list(const string &path_, string *attrstr_) { int rv; vector attrs; rv = fs::xattr::list(path_,&attrs); if(rv != -1) *attrstr_ = string(attrs.begin(),attrs.end()); return rv; } int get(const int fd_, const string &attr_, vector *value_) { ssize_t rv; rv = -1; errno = ERANGE; while((rv == -1) && (errno == ERANGE)) { rv = fs::fgetxattr(fd_,attr_,NULL,0); if(rv <= 0) return rv; value_->resize(rv); rv = fs::fgetxattr(fd_,attr_,&(*value_)[0],rv); } return rv; } int get(const string &path_, const string &attr_, vector *value_) { ssize_t rv; rv = -1; errno = ERANGE; while((rv == -1) && (errno == ERANGE)) { rv = fs::lgetxattr(path_,attr_,NULL,0); if(rv <= 0) return rv; value_->resize(rv); rv = fs::lgetxattr(path_,attr_,&(*value_)[0],rv); } return rv; } int get(const int fd_, const string &attr_, string *value_) { int rv; vector tmpvalue; rv = get(fd_,attr_,&tmpvalue); if(rv != -1) *value_ = string(tmpvalue.begin(),tmpvalue.end()); return rv; } int get(const string &path_, const string &attr_, string *value_) { int rv; vector tmpvalue; rv = fs::xattr::get(path_,attr_,&tmpvalue); if(rv != -1) *value_ = string(tmpvalue.begin(),tmpvalue.end()); return rv; } int get(const int fd_, map *attrs_) { int rv; string attrstr; rv = fs::xattr::list(fd_,&attrstr); if(rv == -1) return -1; { string key; istringstream ss(attrstr); while(getline(ss,key,'\0')) { string value; rv = fs::xattr::get(fd_,key,&value); if(rv != -1) (*attrs_)[key] = value; } } return 0; } int get(const string &path_, map *attrs_) { int rv; string attrstr; rv = fs::xattr::list(path_,&attrstr); if(rv == -1) return -1; { string key; istringstream ss(attrstr); while(getline(ss,key,'\0')) { string value; rv = fs::xattr::get(path_,key,&value); if(rv != -1) (*attrs_)[key] = value; } } return 0; } int set(const int fd_, const string &key_, const string &value_, const int flags_) { return fs::fsetxattr(fd_, key_, value_.data(), value_.size(), flags_); } int set(const string &path_, const string &key_, const string &value_, const int flags_) { return fs::lsetxattr(path_, key_, value_.data(), value_.size(), flags_); } int set(const int fd_, const map &attrs_) { int rv; for(map::const_iterator i = attrs_.begin(), ei = attrs_.end(); i != ei; ++i) { rv = fs::xattr::set(fd_,i->first,i->second,0); if(rv == -1) return -1; } return 0; } int set(const string &path_, const map &attrs_) { int fd; fd = fs::open(path_,O_RDONLY|O_NONBLOCK); if(fd == -1) return -1; fs::xattr::set(fd,attrs_); return fs::close(fd); } int copy(const int fdin_, const int fdout_) { int rv; map attrs; rv = fs::xattr::get(fdin_,&attrs); if(rv == -1) return -1; return fs::xattr::set(fdout_,attrs); } int copy(const string &from_, const string &to_) { int rv; map attrs; rv = fs::xattr::get(from_,&attrs); if(rv == -1) return -1; return fs::xattr::set(to_,attrs); } } } mergerfs-2.33.5/src/fs_utimensat_generic.hpp0000644000000000000000000001535414225522122017616 0ustar rootroot/* ISC License Copyright (c) 2020, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "fs_fstat.hpp" #include "fs_fstatat.hpp" #include "fs_futimesat.hpp" #include "fs_lutimens.hpp" #include "fs_stat_utils.hpp" #include #include #include #include #ifndef UTIME_NOW # define UTIME_NOW ((1l << 30) - 1l) #endif #ifndef UTIME_OMIT # define UTIME_OMIT ((1l << 30) - 2l) #endif namespace l { static inline bool can_call_lutimes(const int dirfd_, const std::string &path_, const int flags_) { return ((flags_ == AT_SYMLINK_NOFOLLOW) && ((dirfd_ == AT_FDCWD) || (path_[0] == '/'))); } static inline bool should_ignore(const struct timespec ts_[2]) { return ((ts_ != NULL) && (ts_[0].tv_nsec == UTIME_OMIT) && (ts_[1].tv_nsec == UTIME_OMIT)); } static inline bool should_be_set_to_now(const struct timespec ts_[2]) { return ((ts_ == NULL) || ((ts_[0].tv_nsec == UTIME_NOW) && (ts_[1].tv_nsec == UTIME_NOW))); } static inline bool timespec_invalid(const struct timespec &ts_) { return (((ts_.tv_nsec < 0) || (ts_.tv_nsec > 999999999)) && ((ts_.tv_nsec != UTIME_NOW) && (ts_.tv_nsec != UTIME_OMIT))); } static inline bool timespec_invalid(const struct timespec ts_[2]) { return ((ts_ != NULL) && (l::timespec_invalid(ts_[0]) || l::timespec_invalid(ts_[1]))); } static inline bool flags_invalid(const int flags_) { return ((flags_ & ~AT_SYMLINK_NOFOLLOW) != 0); } static inline bool any_timespec_is_utime_omit(const struct timespec ts_[2]) { return ((ts_[0].tv_nsec == UTIME_OMIT) || (ts_[1].tv_nsec == UTIME_OMIT)); } static inline bool any_timespec_is_utime_now(const struct timespec ts_[2]) { return ((ts_[0].tv_nsec == UTIME_NOW) || (ts_[1].tv_nsec == UTIME_NOW)); } static inline int set_utime_omit_to_current_value(const int dirfd_, const std::string &path_, const struct timespec ts_[2], struct timeval tv_[2], const int flags_) { int rv; struct stat st; timespec *atime; timespec *mtime; if(!l::any_timespec_is_utime_omit(ts_)) return 0; rv = fs::fstatat(dirfd_,path_,&st,flags_); if(rv == -1) return -1; atime = fs::stat_atime(st); mtime = fs::stat_mtime(st); if(ts_[0].tv_nsec == UTIME_OMIT) TIMESPEC_TO_TIMEVAL(&tv[0],atime); if(ts_[1].tv_nsec == UTIME_OMIT) TIMESPEC_TO_TIMEVAL(&tv[1],mtime); return 0; } static inline int set_utime_omit_to_current_value(const int fd_, const struct timespec ts_[2], struct timeval tv_[2]) { int rv; struct stat st; timespec *atime; timespec *mtime; if(!l::any_timespec_is_utime_omit(ts_)) return 0; rv = fs::fstat(fd_,&st); if(rv == -1) return -1; atime = fs::stat_atime(st); mtime = fs::stat_mtime(st); if(ts_[0].tv_nsec == UTIME_OMIT) TIMESPEC_TO_TIMEVAL(&tv_[0],atime); if(ts_[1].tv_nsec == UTIME_OMIT) TIMESPEC_TO_TIMEVAL(&tv_[1],mtime); return 0; } static inline int set_utime_now_to_now(const struct timespec ts_[2], struct timeval tv_[2]) { int rv; struct timeval now; if(l::any_timespec_is_utime_now(ts_)) return 0; rv = time::gettimeofday(&now,NULL); if(rv == -1) return -1; if(ts_[0].tv_nsec == UTIME_NOW) tv_[0] = now; if(ts_[1].tv_nsec == UTIME_NOW) tv_[1] = now; return 0; } static inline int convert_timespec_to_timeval(const int dirfd_, const std::string &path_, const struct timespec ts_[2], struct timeval tv_[2], struct timeval **tvp_, const int flags_) { int rv; if(l::should_be_set_to_now(ts_)) return (tvp=NULL,0); TIMESPEC_TO_TIMEVAL(&tv_[0],&ts_[0]); TIMESPEC_TO_TIMEVAL(&tv_[1],&ts_[1]); rv = l::set_utime_omit_to_current_value(dirfd_,path_,ts_,tv_,flags_); if(rv == -1) return -1; rv = l::set_utime_now_to_now(ts_,tv_); if(rv == -1) return -1; return (*tvp_=tv_,0); } static inline int convert_timespec_to_timeval(const int fd_, const struct timespec ts_[2], struct timeval tv_[2], struct timeval **tvp_) { int rv; if(l::should_be_set_to_now(ts_)) return (*tvp=NULL,0); TIMESPEC_TO_TIMEVAL(&tv_[0],&ts_[0]); TIMESPEC_TO_TIMEVAL(&tv_[1],&ts_[1]); rv = l::set_utime_omit_to_current_value(fd_,ts_,tv_); if(rv == -1) return -1; rv = l::set_utime_now_to_now(ts_,tv_); if(rv == -1) return -1; return (*tvp=tv,0); } } namespace fs { static inline int utimensat(const int dirfd_, const std::string &path_, const struct timespec ts_[2], const int flags_) { int rv; struct timeval tv[2]; struct timeval *tvp; if(l::flags_invalid(flags)) return (errno=EINVAL,-1); if(l::timespec_invalid(ts_)) return (errno=EINVAL,-1); if(l::should_ignore(ts_)) return 0; rv = l::convert_timespec_to_timeval(dirfd_,path_,ts_,tv,&tvp,flags_); if(rv == -1) return -1; if((flags_ & AT_SYMLINK_NOFOLLOW) == 0) return fs::futimesat(dirfd_,path_,tvp); if(l::can_call_lutimes(dirfd_,path_,flags)) return fs::lutimes(path_,tvp); return (errno=ENOTSUP,-1); } } mergerfs-2.33.5/src/fs_inode.cpp0000644000000000000000000001305414225522122015175 0ustar rootroot/* ISC License Copyright (c) 2020, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "ef.hpp" #include "errno.hpp" #include "fs_inode.hpp" #include "wyhash.h" #include #include #include #include #include typedef uint64_t (*inodefunc_t)(const char*,const uint64_t,const mode_t,const dev_t,const ino_t); static uint64_t hybrid_hash(const char*,const uint64_t,const mode_t,const dev_t,const ino_t); static inodefunc_t g_func = hybrid_hash; static uint32_t h64_to_h32(uint64_t h_) { return (h_ - (h_ >> 32)); } static uint64_t passthrough(const char *fusepath_, const uint64_t fusepath_len_, const mode_t mode_, const dev_t dev_, const ino_t ino_) { return ino_; } static uint64_t path_hash(const char *fusepath_, const uint64_t fusepath_len_, const mode_t mode_, const dev_t dev_, const ino_t ino_) { return wyhash(fusepath_, fusepath_len_, fs::inode::MAGIC, _wyp); } static uint64_t path_hash32(const char *fusepath_, const uint64_t fusepath_len_, const mode_t mode_, const dev_t dev_, const ino_t ino_) { uint64_t h; h = path_hash(fusepath_, fusepath_len_, mode_, dev_, ino_); return h64_to_h32(h); } static uint64_t devino_hash(const char *fusepath_, const uint64_t fusepath_len_, const mode_t mode_, const dev_t dev_, const ino_t ino_) { uint64_t buf[2]; buf[0] = dev_; buf[1] = ino_; return wyhash((void*)&buf[0], sizeof(buf), fs::inode::MAGIC, _wyp); } static uint64_t devino_hash32(const char *fusepath_, const uint64_t fusepath_len_, const mode_t mode_, const dev_t dev_, const ino_t ino_) { uint64_t h; h = devino_hash(fusepath_, fusepath_len_, mode_, dev_, ino_); return h64_to_h32(h); } static uint64_t hybrid_hash(const char *fusepath_, const uint64_t fusepath_len_, const mode_t mode_, const dev_t dev_, const ino_t ino_) { return (S_ISDIR(mode_) ? path_hash(fusepath_,fusepath_len_,mode_,dev_,ino_) : devino_hash(fusepath_,fusepath_len_,mode_,dev_,ino_)); } static uint64_t hybrid_hash32(const char *fusepath_, const uint64_t fusepath_len_, const mode_t mode_, const dev_t dev_, const ino_t ino_) { return (S_ISDIR(mode_) ? path_hash32(fusepath_,fusepath_len_,mode_,dev_,ino_) : devino_hash32(fusepath_,fusepath_len_,mode_,dev_,ino_)); } namespace fs { namespace inode { int set_algo(const std::string &algo_) { if(algo_ == "passthrough") g_func = passthrough; ef(algo_ == "path-hash") g_func = path_hash; ef(algo_ == "path-hash32") g_func = path_hash32; ef(algo_ == "devino-hash") g_func = devino_hash; ef(algo_ == "devino-hash32") g_func = devino_hash32; ef(algo_ == "hybrid-hash") g_func = hybrid_hash; ef(algo_ == "hybrid-hash32") g_func = hybrid_hash32; else return -EINVAL; return 0; } std::string get_algo(void) { if(g_func == passthrough) return "passthrough"; if(g_func == path_hash) return "path-hash"; if(g_func == path_hash32) return "path-hash32"; if(g_func == devino_hash) return "devino-hash"; if(g_func == devino_hash32) return "devino-hash32"; if(g_func == hybrid_hash) return "hybrid-hash"; if(g_func == hybrid_hash32) return "hybrid-hash32"; return std::string(); } uint64_t calc(const char *fusepath_, const uint64_t fusepath_len_, const mode_t mode_, const dev_t dev_, const ino_t ino_) { return g_func(fusepath_,fusepath_len_,mode_,dev_,ino_); } void calc(const char *fusepath_, const uint64_t fusepath_len_, struct stat *st_) { st_->st_ino = calc(fusepath_, fusepath_len_, st_->st_mode, st_->st_dev, st_->st_ino); } void calc(const char *fusepath_, struct stat *st_) { calc(fusepath_,strlen(fusepath_),st_); } void calc(const std::string &fusepath_, struct stat *st_) { calc(fusepath_.c_str(),fusepath_.size(),st_); } } } mergerfs-2.33.5/src/fs_movefile.cpp0000644000000000000000000000716714225522122015715 0ustar rootroot/* Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "errno.hpp" #include "fs_clonefile.hpp" #include "fs_clonepath.hpp" #include "fs_close.hpp" #include "fs_file_size.hpp" #include "fs_findonfs.hpp" #include "fs_getfl.hpp" #include "fs_has_space.hpp" #include "fs_mktemp.hpp" #include "fs_open.hpp" #include "fs_path.hpp" #include "fs_rename.hpp" #include "fs_stat.hpp" #include "fs_unlink.hpp" #include "policy.hpp" #include "ugid.hpp" #include #include #include #include #include #include #include using std::string; using std::vector; namespace l { static int movefile(const Policy::Create &createFunc_, const Branches::CPtr &branches_, const string &fusepath_, int *origfd_) { int rv; int fdin; int fdout; int fdin_flags; int64_t fdin_size; string fusedir; string fdin_path; string fdout_temp; vector fdout_path; fdin = *origfd_; fdin_flags = fs::getfl(fdin); if(fdin_flags == -1) return -1; rv = fs::findonfs(branches_,fusepath_,fdin,&fdin_path); if(rv == -1) return -1; rv = createFunc_(branches_,fusepath_,&fdout_path); if(rv == -1) return -1; fdin_size = fs::file_size(fdin); if(fdin_size == -1) return -1; if(fs::has_space(fdout_path[0],fdin_size) == false) return (errno=ENOSPC,-1); fusedir = fs::path::dirname(fusepath_); rv = fs::clonepath(fdin_path,fdout_path[0],fusedir); if(rv == -1) return -1; fs::path::append(fdin_path,fusepath_); fdin = fs::open(fdin_path,O_RDONLY); if(fdin == -1) return -1; fs::path::append(fdout_path[0],fusepath_); fdout_temp = fdout_path[0]; fdout = fs::mktemp(&fdout_temp,fdin_flags); if(fdout == -1) return -1; rv = fs::clonefile(fdin,fdout); if(rv == -1) goto cleanup; rv = fs::rename(fdout_temp,fdout_path[0]); if(rv == -1) goto cleanup; // should we care if it fails? fs::unlink(fdin_path); std::swap(*origfd_,fdout); fs::close(fdin); fs::close(fdout); return 0; cleanup: rv = errno; if(fdin != -1) fs::close(fdin); if(fdout != -1) fs::close(fdout); fs::unlink(fdout_temp); errno = rv; return -1; } } namespace fs { int movefile(const Policy::Create &policy_, const Branches::CPtr &basepaths_, const string &fusepath_, int *origfd_) { return l::movefile(policy_,basepaths_,fusepath_,origfd_); } int movefile_as_root(const Policy::Create &policy_, const Branches::CPtr &basepaths_, const string &fusepath_, int *origfd_) { const ugid::Set ugid(0,0); return fs::movefile(policy_,basepaths_,fusepath_,origfd_); } } mergerfs-2.33.5/src/fuse_bmap.cpp0000644000000000000000000000205314225522122015345 0ustar rootroot/* ISC License Copyright (c) 2021, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "errno.hpp" #include #include namespace FUSE { int bmap(const char *fusepath_, size_t blocksize_, uint64_t *idx_) { (void)fusepath_; (void)blocksize_; (void)idx_; return -ENOSYS; } } mergerfs-2.33.5/src/fs_ftruncate.hpp0000644000000000000000000000175714225522122016106 0ustar rootroot/* ISC License Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include #include namespace fs { static inline int ftruncate(const int fd_, const off_t size_) { return ::ftruncate(fd_,size_); } } mergerfs-2.33.5/src/dirinfo.hpp0000644000000000000000000000165614225522122015053 0ustar rootroot/* Copyright (c) 2017, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include "fh.hpp" #include class DirInfo : public FH { public: DirInfo(const char *fusepath_) : FH(fusepath_) { } }; mergerfs-2.33.5/src/fs_lutimens.hpp0000644000000000000000000000244214225522122015743 0ustar rootroot/* ISC License Copyright (c) 2020, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include "fs_utimensat.hpp" #include "fs_stat_utils.hpp" namespace fs { static inline int lutimens(const std::string &path_, const struct timespec ts_[2]) { return fs::utimensat(AT_FDCWD,path_,ts_,AT_SYMLINK_NOFOLLOW); } static inline int lutimens(const std::string &path_, const struct stat &st_) { struct timespec ts[2]; ts[0] = *fs::stat_atime(&st_); ts[1] = *fs::stat_mtime(&st_); return fs::lutimens(path_,ts); } } mergerfs-2.33.5/src/fs_futimesat.hpp0000644000000000000000000000174314225522122016107 0ustar rootroot/* ISC License Copyright (c) 2017, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include namespace fs { int futimesat(const int dirfd, const char *pathname, const struct timeval times[2]); } mergerfs-2.33.5/src/fs_llistxattr.hpp0000644000000000000000000000252514225522122016317 0ustar rootroot/* ISC License Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include "errno.hpp" #include "xattr.hpp" #include #include namespace fs { static inline int llistxattr(const char *path_, char *list_, const size_t size_) { #ifdef USE_XATTR return ::llistxattr(path_,list_,size_); #else return (errno=ENOTSUP,-1); #endif } static inline int llistxattr(const std::string &path_, char *list_, const size_t size_) { return fs::llistxattr(path_.c_str(),list_,size_); } } mergerfs-2.33.5/src/fs_mkdir_as_root.hpp0000644000000000000000000000205314225522122016735 0ustar rootroot/* ISC License Copyright (c) 2021, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "fs_mkdir.hpp" #include "ugid.hpp" namespace fs { template static inline int mkdir_as_root(const T &path_, const mode_t mode_) { const ugid::SetRootGuard guard; return fs::mkdir(path_,mode_); } } mergerfs-2.33.5/src/fs_file_size.hpp0000644000000000000000000000157514225522122016062 0ustar rootroot/* ISC License Copyright (c) 2020, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include namespace fs { int64_t file_size(const int fd); } mergerfs-2.33.5/src/fuse_readlink.hpp0000644000000000000000000000163014225522122016224 0ustar rootroot/* Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once namespace FUSE { int readlink(const char *fusepath, char *buf, size_t size); } mergerfs-2.33.5/src/fuse_removexattr.hpp0000644000000000000000000000160614225522122017016 0ustar rootroot/* Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once namespace FUSE { int removexattr(const char *fusepath, const char *attrname); } mergerfs-2.33.5/src/fs_fchmod.hpp0000644000000000000000000000322314225522122015341 0ustar rootroot/* ISC License Copyright (c) 2019, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include "fs_fstat.hpp" #include #define MODE_BITS (S_ISUID|S_ISGID|S_ISVTX|S_IRWXU|S_IRWXG|S_IRWXO) namespace fs { static inline int fchmod(const int fd_, const mode_t mode_) { return ::fchmod(fd_,mode_); } static inline int fchmod(const int fd_, const struct stat &st_) { return ::fchmod(fd_,st_.st_mode); } static inline int fchmod_check_on_error(const int fd_, const struct stat &st_) { int rv; rv = fs::fchmod(fd_,st_); if(rv == -1) { int error; struct stat tmpst; error = errno; rv = fs::fstat(fd_,&tmpst); if(rv == -1) return -1; if((st_.st_mode & MODE_BITS) != (tmpst.st_mode & MODE_BITS)) return (errno=error,-1); } return 0; } } mergerfs-2.33.5/src/policy_epall.cpp0000644000000000000000000000730714225522122016067 0ustar rootroot/* Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "errno.hpp" #include "fs_exists.hpp" #include "fs_info.hpp" #include "fs_path.hpp" #include "fs_statvfs_cache.hpp" #include "policy.hpp" #include "policy_epall.hpp" #include "policy_error.hpp" #include "strvec.hpp" #include using std::string; namespace epall { static int create(const Branches::CPtr &branches_, const char *fusepath_, StrVec *paths_) { int rv; int error; fs::info_t info; error = ENOENT; for(auto &branch : *branches_) { if(branch.ro_or_nc()) error_and_continue(error,EROFS); if(!fs::exists(branch.path,fusepath_)) error_and_continue(error,ENOENT); rv = fs::info(branch.path,&info); if(rv == -1) error_and_continue(error,ENOENT); if(info.readonly) error_and_continue(error,EROFS); if(info.spaceavail < branch.minfreespace()) error_and_continue(error,ENOSPC); paths_->push_back(branch.path); } if(paths_->empty()) return (errno=error,-1); return 0; } static int action(const Branches::CPtr &branches_, const char *fusepath_, StrVec *paths_) { int rv; int error; bool readonly; error = ENOENT; for(auto &branch : *branches_) { if(branch.ro()) error_and_continue(error,EROFS); if(!fs::exists(branch.path,fusepath_)) error_and_continue(error,ENOENT); rv = fs::statvfs_cache_readonly(branch.path,&readonly); if(rv == -1) error_and_continue(error,ENOENT); if(readonly) error_and_continue(error,EROFS); paths_->push_back(branch.path); } if(paths_->empty()) return (errno=error,-1); return 0; } static int search(const Branches::CPtr &branches_, const char *fusepath_, StrVec *paths_) { for(auto &branch : *branches_) { if(!fs::exists(branch.path,fusepath_)) continue; paths_->push_back(branch.path); } if(paths_->empty()) return (errno=ENOENT,-1); return 0; } } int Policy::EPAll::Action::operator()(const Branches::CPtr &branches_, const char *fusepath_, StrVec *paths_) const { return ::epall::action(branches_,fusepath_,paths_); } int Policy::EPAll::Create::operator()(const Branches::CPtr &branches_, const char *fusepath_, StrVec *paths_) const { return ::epall::create(branches_,fusepath_,paths_); } int Policy::EPAll::Search::operator()(const Branches::CPtr &branches_, const char *fusepath_, StrVec *paths_) const { return ::epall::search(branches_,fusepath_,paths_); } mergerfs-2.33.5/src/fuse_setxattr.hpp0000644000000000000000000000174114225522122016314 0ustar rootroot/* Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once namespace FUSE { int setxattr(const char *fusepath, const char *attrname, const char *attrval, size_t attrvalize, int flags); } mergerfs-2.33.5/src/nonstd/0000755000000000000000000000000014225522122014205 5ustar rootrootmergerfs-2.33.5/src/nonstd/optional.hpp0000644000000000000000000014723314225522122016555 0ustar rootroot// // Copyright (c) 2014-2021 Martin Moene // // https://github.com/martinmoene/optional-lite // // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE.txt or copy at http://www.boost.org/LICENSE_1_0.txt) #pragma once #ifndef NONSTD_OPTIONAL_LITE_HPP #define NONSTD_OPTIONAL_LITE_HPP #define optional_lite_MAJOR 3 #define optional_lite_MINOR 5 #define optional_lite_PATCH 0 #define optional_lite_VERSION optional_STRINGIFY(optional_lite_MAJOR) "." optional_STRINGIFY(optional_lite_MINOR) "." optional_STRINGIFY(optional_lite_PATCH) #define optional_STRINGIFY( x ) optional_STRINGIFY_( x ) #define optional_STRINGIFY_( x ) #x // optional-lite configuration: #define optional_OPTIONAL_DEFAULT 0 #define optional_OPTIONAL_NONSTD 1 #define optional_OPTIONAL_STD 2 // tweak header support: #ifdef __has_include # if __has_include() # include # endif #define optional_HAVE_TWEAK_HEADER 1 #else #define optional_HAVE_TWEAK_HEADER 0 //# pragma message("optional.hpp: Note: Tweak header not supported.") #endif // optional selection and configuration: #if !defined( optional_CONFIG_SELECT_OPTIONAL ) # define optional_CONFIG_SELECT_OPTIONAL ( optional_HAVE_STD_OPTIONAL ? optional_OPTIONAL_STD : optional_OPTIONAL_NONSTD ) #endif // Control presence of exception handling (try and auto discover): #ifndef optional_CONFIG_NO_EXCEPTIONS # if defined(_MSC_VER) # include // for _HAS_EXCEPTIONS # endif # if defined(__cpp_exceptions) || defined(__EXCEPTIONS) || (_HAS_EXCEPTIONS) # define optional_CONFIG_NO_EXCEPTIONS 0 # else # define optional_CONFIG_NO_EXCEPTIONS 1 # endif #endif // C++ language version detection (C++20 is speculative): // Note: VC14.0/1900 (VS2015) lacks too much from C++14. #ifndef optional_CPLUSPLUS # if defined(_MSVC_LANG ) && !defined(__clang__) # define optional_CPLUSPLUS (_MSC_VER == 1900 ? 201103L : _MSVC_LANG ) # else # define optional_CPLUSPLUS __cplusplus # endif #endif #define optional_CPP98_OR_GREATER ( optional_CPLUSPLUS >= 199711L ) #define optional_CPP11_OR_GREATER ( optional_CPLUSPLUS >= 201103L ) #define optional_CPP11_OR_GREATER_ ( optional_CPLUSPLUS >= 201103L ) #define optional_CPP14_OR_GREATER ( optional_CPLUSPLUS >= 201402L ) #define optional_CPP17_OR_GREATER ( optional_CPLUSPLUS >= 201703L ) #define optional_CPP20_OR_GREATER ( optional_CPLUSPLUS >= 202000L ) // C++ language version (represent 98 as 3): #define optional_CPLUSPLUS_V ( optional_CPLUSPLUS / 100 - (optional_CPLUSPLUS > 200000 ? 2000 : 1994) ) // Use C++17 std::optional if available and requested: #if optional_CPP17_OR_GREATER && defined(__has_include ) # if __has_include( ) # define optional_HAVE_STD_OPTIONAL 1 # else # define optional_HAVE_STD_OPTIONAL 0 # endif #else # define optional_HAVE_STD_OPTIONAL 0 #endif #define optional_USES_STD_OPTIONAL ( (optional_CONFIG_SELECT_OPTIONAL == optional_OPTIONAL_STD) || ((optional_CONFIG_SELECT_OPTIONAL == optional_OPTIONAL_DEFAULT) && optional_HAVE_STD_OPTIONAL) ) // // in_place: code duplicated in any-lite, expected-lite, optional-lite, value-ptr-lite, variant-lite: // #ifndef nonstd_lite_HAVE_IN_PLACE_TYPES #define nonstd_lite_HAVE_IN_PLACE_TYPES 1 // C++17 std::in_place in : #if optional_CPP17_OR_GREATER #include namespace nonstd { using std::in_place; using std::in_place_type; using std::in_place_index; using std::in_place_t; using std::in_place_type_t; using std::in_place_index_t; #define nonstd_lite_in_place_t( T) std::in_place_t #define nonstd_lite_in_place_type_t( T) std::in_place_type_t #define nonstd_lite_in_place_index_t(K) std::in_place_index_t #define nonstd_lite_in_place( T) std::in_place_t{} #define nonstd_lite_in_place_type( T) std::in_place_type_t{} #define nonstd_lite_in_place_index(K) std::in_place_index_t{} } // namespace nonstd #else // optional_CPP17_OR_GREATER #include namespace nonstd { namespace detail { template< class T > struct in_place_type_tag {}; template< std::size_t K > struct in_place_index_tag {}; } // namespace detail struct in_place_t {}; template< class T > inline in_place_t in_place( detail::in_place_type_tag /*unused*/ = detail::in_place_type_tag() ) { return in_place_t(); } template< std::size_t K > inline in_place_t in_place( detail::in_place_index_tag /*unused*/ = detail::in_place_index_tag() ) { return in_place_t(); } template< class T > inline in_place_t in_place_type( detail::in_place_type_tag /*unused*/ = detail::in_place_type_tag() ) { return in_place_t(); } template< std::size_t K > inline in_place_t in_place_index( detail::in_place_index_tag /*unused*/ = detail::in_place_index_tag() ) { return in_place_t(); } // mimic templated typedef: #define nonstd_lite_in_place_t( T) nonstd::in_place_t(&)( nonstd::detail::in_place_type_tag ) #define nonstd_lite_in_place_type_t( T) nonstd::in_place_t(&)( nonstd::detail::in_place_type_tag ) #define nonstd_lite_in_place_index_t(K) nonstd::in_place_t(&)( nonstd::detail::in_place_index_tag ) #define nonstd_lite_in_place( T) nonstd::in_place_type #define nonstd_lite_in_place_type( T) nonstd::in_place_type #define nonstd_lite_in_place_index(K) nonstd::in_place_index } // namespace nonstd #endif // optional_CPP17_OR_GREATER #endif // nonstd_lite_HAVE_IN_PLACE_TYPES // // Using std::optional: // #if optional_USES_STD_OPTIONAL #include namespace nonstd { using std::optional; using std::bad_optional_access; using std::hash; using std::nullopt; using std::nullopt_t; using std::operator==; using std::operator!=; using std::operator<; using std::operator<=; using std::operator>; using std::operator>=; using std::make_optional; using std::swap; } #else // optional_USES_STD_OPTIONAL #include #include // optional-lite alignment configuration: #ifndef optional_CONFIG_MAX_ALIGN_HACK # define optional_CONFIG_MAX_ALIGN_HACK 0 #endif #ifndef optional_CONFIG_ALIGN_AS // no default, used in #if defined() #endif #ifndef optional_CONFIG_ALIGN_AS_FALLBACK # define optional_CONFIG_ALIGN_AS_FALLBACK double #endif // Compiler warning suppression: #if defined(__clang__) # pragma clang diagnostic push # pragma clang diagnostic ignored "-Wundef" #elif defined(__GNUC__) # pragma GCC diagnostic push # pragma GCC diagnostic ignored "-Wundef" #elif defined(_MSC_VER ) # pragma warning( push ) #endif // half-open range [lo..hi): #define optional_BETWEEN( v, lo, hi ) ( (lo) <= (v) && (v) < (hi) ) // Compiler versions: // // MSVC++ 6.0 _MSC_VER == 1200 optional_COMPILER_MSVC_VERSION == 60 (Visual Studio 6.0) // MSVC++ 7.0 _MSC_VER == 1300 optional_COMPILER_MSVC_VERSION == 70 (Visual Studio .NET 2002) // MSVC++ 7.1 _MSC_VER == 1310 optional_COMPILER_MSVC_VERSION == 71 (Visual Studio .NET 2003) // MSVC++ 8.0 _MSC_VER == 1400 optional_COMPILER_MSVC_VERSION == 80 (Visual Studio 2005) // MSVC++ 9.0 _MSC_VER == 1500 optional_COMPILER_MSVC_VERSION == 90 (Visual Studio 2008) // MSVC++ 10.0 _MSC_VER == 1600 optional_COMPILER_MSVC_VERSION == 100 (Visual Studio 2010) // MSVC++ 11.0 _MSC_VER == 1700 optional_COMPILER_MSVC_VERSION == 110 (Visual Studio 2012) // MSVC++ 12.0 _MSC_VER == 1800 optional_COMPILER_MSVC_VERSION == 120 (Visual Studio 2013) // MSVC++ 14.0 _MSC_VER == 1900 optional_COMPILER_MSVC_VERSION == 140 (Visual Studio 2015) // MSVC++ 14.1 _MSC_VER >= 1910 optional_COMPILER_MSVC_VERSION == 141 (Visual Studio 2017) // MSVC++ 14.2 _MSC_VER >= 1920 optional_COMPILER_MSVC_VERSION == 142 (Visual Studio 2019) #if defined(_MSC_VER ) && !defined(__clang__) # define optional_COMPILER_MSVC_VER (_MSC_VER ) # define optional_COMPILER_MSVC_VERSION (_MSC_VER / 10 - 10 * ( 5 + (_MSC_VER < 1900 ) ) ) #else # define optional_COMPILER_MSVC_VER 0 # define optional_COMPILER_MSVC_VERSION 0 #endif #define optional_COMPILER_VERSION( major, minor, patch ) ( 10 * (10 * (major) + (minor) ) + (patch) ) #if defined(__GNUC__) && !defined(__clang__) # define optional_COMPILER_GNUC_VERSION optional_COMPILER_VERSION(__GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__) #else # define optional_COMPILER_GNUC_VERSION 0 #endif #if defined(__clang__) # define optional_COMPILER_CLANG_VERSION optional_COMPILER_VERSION(__clang_major__, __clang_minor__, __clang_patchlevel__) #else # define optional_COMPILER_CLANG_VERSION 0 #endif #if optional_BETWEEN(optional_COMPILER_MSVC_VERSION, 70, 140 ) # pragma warning( disable: 4345 ) // initialization behavior changed #endif #if optional_BETWEEN(optional_COMPILER_MSVC_VERSION, 70, 150 ) # pragma warning( disable: 4814 ) // in C++14 'constexpr' will not imply 'const' #endif // Presence of language and library features: #define optional_HAVE(FEATURE) ( optional_HAVE_##FEATURE ) #ifdef _HAS_CPP0X # define optional_HAS_CPP0X _HAS_CPP0X #else # define optional_HAS_CPP0X 0 #endif // Unless defined otherwise below, consider VC14 as C++11 for optional-lite: #if optional_COMPILER_MSVC_VER >= 1900 # undef optional_CPP11_OR_GREATER # define optional_CPP11_OR_GREATER 1 #endif #define optional_CPP11_90 (optional_CPP11_OR_GREATER_ || optional_COMPILER_MSVC_VER >= 1500) #define optional_CPP11_100 (optional_CPP11_OR_GREATER_ || optional_COMPILER_MSVC_VER >= 1600) #define optional_CPP11_110 (optional_CPP11_OR_GREATER_ || optional_COMPILER_MSVC_VER >= 1700) #define optional_CPP11_120 (optional_CPP11_OR_GREATER_ || optional_COMPILER_MSVC_VER >= 1800) #define optional_CPP11_140 (optional_CPP11_OR_GREATER_ || optional_COMPILER_MSVC_VER >= 1900) #define optional_CPP11_141 (optional_CPP11_OR_GREATER_ || optional_COMPILER_MSVC_VER >= 1910) #define optional_CPP14_000 (optional_CPP14_OR_GREATER) #define optional_CPP17_000 (optional_CPP17_OR_GREATER) // clang >= 2.9, gcc >= 4.9, msvc >= vc14.0/1900 (vs15): #define optional_CPP11_140_C290_G490 ((optional_CPP11_OR_GREATER_ && (optional_COMPILER_CLANG_VERSION >= 290 || optional_COMPILER_GNUC_VERSION >= 490)) || (optional_COMPILER_MSVC_VER >= 1900)) // clang >= 3.5, msvc >= vc11 (vs12): #define optional_CPP11_110_C350 ( optional_CPP11_110 && !optional_BETWEEN( optional_COMPILER_CLANG_VERSION, 1, 350 ) ) // clang >= 3.5, gcc >= 5.0, msvc >= vc11 (vs12): #define optional_CPP11_110_C350_G500 \ ( optional_CPP11_110 && \ !( optional_BETWEEN( optional_COMPILER_CLANG_VERSION, 1, 350 ) \ || optional_BETWEEN( optional_COMPILER_GNUC_VERSION , 1, 500 ) ) ) // Presence of C++11 language features: #define optional_HAVE_CONSTEXPR_11 optional_CPP11_140 #define optional_HAVE_IS_DEFAULT optional_CPP11_140 #define optional_HAVE_NOEXCEPT optional_CPP11_140 #define optional_HAVE_NULLPTR optional_CPP11_100 #define optional_HAVE_REF_QUALIFIER optional_CPP11_140_C290_G490 #define optional_HAVE_STATIC_ASSERT optional_CPP11_110 #define optional_HAVE_INITIALIZER_LIST optional_CPP11_140 // Presence of C++14 language features: #define optional_HAVE_CONSTEXPR_14 optional_CPP14_000 // Presence of C++17 language features: #define optional_HAVE_NODISCARD optional_CPP17_000 // Presence of C++ library features: #define optional_HAVE_CONDITIONAL optional_CPP11_120 #define optional_HAVE_REMOVE_CV optional_CPP11_120 #define optional_HAVE_TYPE_TRAITS optional_CPP11_90 #define optional_HAVE_TR1_TYPE_TRAITS (!! optional_COMPILER_GNUC_VERSION ) #define optional_HAVE_TR1_ADD_POINTER (!! optional_COMPILER_GNUC_VERSION ) #define optional_HAVE_IS_ASSIGNABLE optional_CPP11_110_C350 #define optional_HAVE_IS_MOVE_CONSTRUCTIBLE optional_CPP11_110_C350 #define optional_HAVE_IS_NOTHROW_MOVE_ASSIGNABLE optional_CPP11_110_C350 #define optional_HAVE_IS_NOTHROW_MOVE_CONSTRUCTIBLE optional_CPP11_110_C350 #define optional_HAVE_IS_TRIVIALLY_COPY_CONSTRUCTIBLE optional_CPP11_110_C350_G500 #define optional_HAVE_IS_TRIVIALLY_MOVE_CONSTRUCTIBLE optional_CPP11_110_C350_G500 // C++ feature usage: #if optional_HAVE( CONSTEXPR_11 ) # define optional_constexpr constexpr #else # define optional_constexpr /*constexpr*/ #endif #if optional_HAVE( IS_DEFAULT ) # define optional_is_default = default; #else # define optional_is_default {} #endif #if optional_HAVE( CONSTEXPR_14 ) # define optional_constexpr14 constexpr #else # define optional_constexpr14 /*constexpr*/ #endif #if optional_HAVE( NODISCARD ) # define optional_nodiscard [[nodiscard]] #else # define optional_nodiscard /*[[nodiscard]]*/ #endif #if optional_HAVE( NOEXCEPT ) # define optional_noexcept noexcept #else # define optional_noexcept /*noexcept*/ #endif #if optional_HAVE( NULLPTR ) # define optional_nullptr nullptr #else # define optional_nullptr NULL #endif #if optional_HAVE( REF_QUALIFIER ) // NOLINTNEXTLINE( bugprone-macro-parentheses ) # define optional_ref_qual & # define optional_refref_qual && #else # define optional_ref_qual /*&*/ # define optional_refref_qual /*&&*/ #endif #if optional_HAVE( STATIC_ASSERT ) # define optional_static_assert(expr, text) static_assert(expr, text); #else # define optional_static_assert(expr, text) /*static_assert(expr, text);*/ #endif // additional includes: #if optional_CONFIG_NO_EXCEPTIONS // already included: #else # include #endif #if optional_CPP11_OR_GREATER # include #endif #if optional_HAVE( INITIALIZER_LIST ) # include #endif #if optional_HAVE( TYPE_TRAITS ) # include #elif optional_HAVE( TR1_TYPE_TRAITS ) # include #endif // Method enabling #if optional_CPP11_OR_GREATER #define optional_REQUIRES_0(...) \ template< bool B = (__VA_ARGS__), typename std::enable_if::type = 0 > #define optional_REQUIRES_T(...) \ , typename std::enable_if< (__VA_ARGS__), int >::type = 0 #define optional_REQUIRES_R(R, ...) \ typename std::enable_if< (__VA_ARGS__), R>::type #define optional_REQUIRES_A(...) \ , typename std::enable_if< (__VA_ARGS__), void*>::type = nullptr #endif // // optional: // namespace nonstd { namespace optional_lite { namespace std11 { template< class T, T v > struct integral_constant { enum { value = v }; }; template< bool B > struct bool_constant : integral_constant{}; typedef bool_constant< true > true_type; typedef bool_constant< false > false_type; #if optional_CPP11_OR_GREATER using std::move; #else template< typename T > T & move( T & t ) { return t; } #endif #if optional_HAVE( CONDITIONAL ) using std::conditional; #else template< bool B, typename T, typename F > struct conditional { typedef T type; }; template< typename T, typename F > struct conditional { typedef F type; }; #endif // optional_HAVE_CONDITIONAL #if optional_HAVE( IS_ASSIGNABLE ) using std::is_assignable; #else template< class T, class U > struct is_assignable : std11::true_type{}; #endif #if optional_HAVE( IS_MOVE_CONSTRUCTIBLE ) using std::is_move_constructible; #else template< class T > struct is_move_constructible : std11::true_type{}; #endif #if optional_HAVE( IS_NOTHROW_MOVE_ASSIGNABLE ) using std::is_nothrow_move_assignable; #else template< class T > struct is_nothrow_move_assignable : std11::true_type{}; #endif #if optional_HAVE( IS_NOTHROW_MOVE_CONSTRUCTIBLE ) using std::is_nothrow_move_constructible; #else template< class T > struct is_nothrow_move_constructible : std11::true_type{}; #endif #if optional_HAVE( IS_TRIVIALLY_COPY_CONSTRUCTIBLE ) using std::is_trivially_copy_constructible; #else template< class T > struct is_trivially_copy_constructible : std11::true_type{}; #endif #if optional_HAVE( IS_TRIVIALLY_MOVE_CONSTRUCTIBLE ) using std::is_trivially_move_constructible; #else template< class T > struct is_trivially_move_constructible : std11::true_type{}; #endif } // namespace std11 #if optional_CPP11_OR_GREATER /// type traits C++17: namespace std17 { #if optional_CPP17_OR_GREATER using std::is_swappable; using std::is_nothrow_swappable; #elif optional_CPP11_OR_GREATER namespace detail { using std::swap; struct is_swappable { template< typename T, typename = decltype( swap( std::declval(), std::declval() ) ) > static std11::true_type test( int /*unused*/ ); template< typename > static std11::false_type test(...); }; struct is_nothrow_swappable { // wrap noexcept(expr) in separate function as work-around for VC140 (VS2015): template< typename T > static constexpr bool satisfies() { return noexcept( swap( std::declval(), std::declval() ) ); } template< typename T > static auto test( int /*unused*/ ) -> std11::integral_constant()>{} template< typename > static auto test(...) -> std11::false_type; }; } // namespace detail // is [nothow] swappable: template< typename T > struct is_swappable : decltype( detail::is_swappable::test(0) ){}; template< typename T > struct is_nothrow_swappable : decltype( detail::is_nothrow_swappable::test(0) ){}; #endif // optional_CPP17_OR_GREATER } // namespace std17 /// type traits C++20: namespace std20 { template< typename T > struct remove_cvref { typedef typename std::remove_cv< typename std::remove_reference::type >::type type; }; } // namespace std20 #endif // optional_CPP11_OR_GREATER /// class optional template< typename T > class optional; namespace detail { // C++11 emulation: struct nulltype{}; template< typename Head, typename Tail > struct typelist { typedef Head head; typedef Tail tail; }; #if optional_CONFIG_MAX_ALIGN_HACK // Max align, use most restricted type for alignment: #define optional_UNIQUE( name ) optional_UNIQUE2( name, __LINE__ ) #define optional_UNIQUE2( name, line ) optional_UNIQUE3( name, line ) #define optional_UNIQUE3( name, line ) name ## line #define optional_ALIGN_TYPE( type ) \ type optional_UNIQUE( _t ); struct_t< type > optional_UNIQUE( _st ) template< typename T > struct struct_t { T _; }; union max_align_t { optional_ALIGN_TYPE( char ); optional_ALIGN_TYPE( short int ); optional_ALIGN_TYPE( int ); optional_ALIGN_TYPE( long int ); optional_ALIGN_TYPE( float ); optional_ALIGN_TYPE( double ); optional_ALIGN_TYPE( long double ); optional_ALIGN_TYPE( char * ); optional_ALIGN_TYPE( short int * ); optional_ALIGN_TYPE( int * ); optional_ALIGN_TYPE( long int * ); optional_ALIGN_TYPE( float * ); optional_ALIGN_TYPE( double * ); optional_ALIGN_TYPE( long double * ); optional_ALIGN_TYPE( void * ); #ifdef HAVE_LONG_LONG optional_ALIGN_TYPE( long long ); #endif struct Unknown; Unknown ( * optional_UNIQUE(_) )( Unknown ); Unknown * Unknown::* optional_UNIQUE(_); Unknown ( Unknown::* optional_UNIQUE(_) )( Unknown ); struct_t< Unknown ( * )( Unknown) > optional_UNIQUE(_); struct_t< Unknown * Unknown::* > optional_UNIQUE(_); struct_t< Unknown ( Unknown::* )(Unknown) > optional_UNIQUE(_); }; #undef optional_UNIQUE #undef optional_UNIQUE2 #undef optional_UNIQUE3 #undef optional_ALIGN_TYPE #elif defined( optional_CONFIG_ALIGN_AS ) // optional_CONFIG_MAX_ALIGN_HACK // Use user-specified type for alignment: #define optional_ALIGN_AS( unused ) \ optional_CONFIG_ALIGN_AS #else // optional_CONFIG_MAX_ALIGN_HACK // Determine POD type to use for alignment: #define optional_ALIGN_AS( to_align ) \ typename type_of_size< alignment_types, alignment_of< to_align >::value >::type template< typename T > struct alignment_of; template< typename T > struct alignment_of_hack { char c; T t; alignment_of_hack(); }; template< size_t A, size_t S > struct alignment_logic { enum { value = A < S ? A : S }; }; template< typename T > struct alignment_of { enum { value = alignment_logic< sizeof( alignment_of_hack ) - sizeof(T), sizeof(T) >::value }; }; template< typename List, size_t N > struct type_of_size { typedef typename std11::conditional< N == sizeof( typename List::head ), typename List::head, typename type_of_size::type >::type type; }; template< size_t N > struct type_of_size< nulltype, N > { typedef optional_CONFIG_ALIGN_AS_FALLBACK type; }; template< typename T> struct struct_t { T _; }; #define optional_ALIGN_TYPE( type ) \ typelist< type , typelist< struct_t< type > struct Unknown; typedef optional_ALIGN_TYPE( char ), optional_ALIGN_TYPE( short ), optional_ALIGN_TYPE( int ), optional_ALIGN_TYPE( long ), optional_ALIGN_TYPE( float ), optional_ALIGN_TYPE( double ), optional_ALIGN_TYPE( long double ), optional_ALIGN_TYPE( char *), optional_ALIGN_TYPE( short * ), optional_ALIGN_TYPE( int * ), optional_ALIGN_TYPE( long * ), optional_ALIGN_TYPE( float * ), optional_ALIGN_TYPE( double * ), optional_ALIGN_TYPE( long double * ), optional_ALIGN_TYPE( Unknown ( * )( Unknown ) ), optional_ALIGN_TYPE( Unknown * Unknown::* ), optional_ALIGN_TYPE( Unknown ( Unknown::* )( Unknown ) ), nulltype > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > alignment_types; #undef optional_ALIGN_TYPE #endif // optional_CONFIG_MAX_ALIGN_HACK /// C++03 constructed union to hold value. template< typename T > union storage_t { //private: // template< typename > friend class optional; typedef T value_type; storage_t() optional_is_default explicit storage_t( value_type const & v ) { construct_value( v ); } void construct_value( value_type const & v ) { ::new( value_ptr() ) value_type( v ); } #if optional_CPP11_OR_GREATER explicit storage_t( value_type && v ) { construct_value( std::move( v ) ); } void construct_value( value_type && v ) { ::new( value_ptr() ) value_type( std::move( v ) ); } template< class... Args > storage_t( nonstd_lite_in_place_t(T), Args&&... args ) { emplace( std::forward(args)... ); } template< class... Args > void emplace( Args&&... args ) { ::new( value_ptr() ) value_type( std::forward(args)... ); } template< class U, class... Args > void emplace( std::initializer_list il, Args&&... args ) { ::new( value_ptr() ) value_type( il, std::forward(args)... ); } #endif void destruct_value() { value_ptr()->~T(); } optional_nodiscard value_type const * value_ptr() const { return as(); } value_type * value_ptr() { return as(); } optional_nodiscard value_type const & value() const optional_ref_qual { return * value_ptr(); } value_type & value() optional_ref_qual { return * value_ptr(); } #if optional_HAVE( REF_QUALIFIER ) optional_nodiscard value_type const && value() const optional_refref_qual { return std::move( value() ); } value_type && value() optional_refref_qual { return std::move( value() ); } #endif #if optional_CPP11_OR_GREATER using aligned_storage_t = typename std::aligned_storage< sizeof(value_type), alignof(value_type) >::type; aligned_storage_t data; #elif optional_CONFIG_MAX_ALIGN_HACK typedef struct { unsigned char data[ sizeof(value_type) ]; } aligned_storage_t; max_align_t hack; aligned_storage_t data; #else typedef optional_ALIGN_AS(value_type) align_as_type; typedef struct { align_as_type data[ 1 + ( sizeof(value_type) - 1 ) / sizeof(align_as_type) ]; } aligned_storage_t; aligned_storage_t data; # undef optional_ALIGN_AS #endif // optional_CONFIG_MAX_ALIGN_HACK optional_nodiscard void * ptr() optional_noexcept { return &data; } optional_nodiscard void const * ptr() const optional_noexcept { return &data; } template optional_nodiscard U * as() { return reinterpret_cast( ptr() ); } template optional_nodiscard U const * as() const { return reinterpret_cast( ptr() ); } }; } // namespace detail /// disengaged state tag struct nullopt_t { struct init{}; explicit optional_constexpr nullopt_t( init /*unused*/ ) optional_noexcept {} }; #if optional_HAVE( CONSTEXPR_11 ) constexpr nullopt_t nullopt{ nullopt_t::init{} }; #else // extra parenthesis to prevent the most vexing parse: const nullopt_t nullopt(( nullopt_t::init() )); #endif /// optional access error #if ! optional_CONFIG_NO_EXCEPTIONS class bad_optional_access : public std::logic_error { public: explicit bad_optional_access() : logic_error( "bad optional access" ) {} }; #endif //optional_CONFIG_NO_EXCEPTIONS /// optional template< typename T> class optional { optional_static_assert(( !std::is_same::type, nullopt_t>::value ), "T in optional must not be of type 'nullopt_t'.") optional_static_assert(( !std::is_same::type, in_place_t>::value ), "T in optional must not be of type 'in_place_t'.") optional_static_assert(( std::is_object::value && std::is_destructible::value && !std::is_array::value ), "T in optional must meet the Cpp17Destructible requirements.") private: template< typename > friend class optional; typedef void (optional::*safe_bool)() const; public: typedef T value_type; // x.x.3.1, constructors // 1a - default construct optional_constexpr optional() optional_noexcept : has_value_( false ) , contained() {} // 1b - construct explicitly empty // NOLINTNEXTLINE( google-explicit-constructor, hicpp-explicit-conversions ) optional_constexpr optional( nullopt_t /*unused*/ ) optional_noexcept : has_value_( false ) , contained() {} // 2 - copy-construct #if optional_CPP11_OR_GREATER // template< typename U = T // optional_REQUIRES_T( // std::is_copy_constructible::value // || std11::is_trivially_copy_constructible::value // ) // > #endif optional_constexpr14 optional( optional const & other ) : has_value_( other.has_value() ) { if ( other.has_value() ) { contained.construct_value( other.contained.value() ); } } #if optional_CPP11_OR_GREATER // 3 (C++11) - move-construct from optional template< typename U = T optional_REQUIRES_T( std11::is_move_constructible::value || std11::is_trivially_move_constructible::value ) > optional_constexpr14 optional( optional && other ) // NOLINTNEXTLINE( performance-noexcept-move-constructor ) noexcept( std11::is_nothrow_move_constructible::value ) : has_value_( other.has_value() ) { if ( other.has_value() ) { contained.construct_value( std::move( other.contained.value() ) ); } } // 4a (C++11) - explicit converting copy-construct from optional template< typename U optional_REQUIRES_T( std::is_constructible::value && !std::is_constructible & >::value && !std::is_constructible && >::value && !std::is_constructible const & >::value && !std::is_constructible const && >::value && !std::is_convertible< optional & , T>::value && !std::is_convertible< optional && , T>::value && !std::is_convertible< optional const & , T>::value && !std::is_convertible< optional const &&, T>::value && !std::is_convertible< U const & , T>::value /*=> explicit */ ) > explicit optional( optional const & other ) : has_value_( other.has_value() ) { if ( other.has_value() ) { contained.construct_value( T{ other.contained.value() } ); } } #endif // optional_CPP11_OR_GREATER // 4b (C++98 and later) - non-explicit converting copy-construct from optional template< typename U #if optional_CPP11_OR_GREATER optional_REQUIRES_T( std::is_constructible::value && !std::is_constructible & >::value && !std::is_constructible && >::value && !std::is_constructible const & >::value && !std::is_constructible const && >::value && !std::is_convertible< optional & , T>::value && !std::is_convertible< optional && , T>::value && !std::is_convertible< optional const & , T>::value && !std::is_convertible< optional const &&, T>::value && std::is_convertible< U const & , T>::value /*=> non-explicit */ ) #endif // optional_CPP11_OR_GREATER > // NOLINTNEXTLINE( google-explicit-constructor, hicpp-explicit-conversions ) /*non-explicit*/ optional( optional const & other ) : has_value_( other.has_value() ) { if ( other.has_value() ) { contained.construct_value( other.contained.value() ); } } #if optional_CPP11_OR_GREATER // 5a (C++11) - explicit converting move-construct from optional template< typename U optional_REQUIRES_T( std::is_constructible::value && !std::is_constructible & >::value && !std::is_constructible && >::value && !std::is_constructible const & >::value && !std::is_constructible const && >::value && !std::is_convertible< optional & , T>::value && !std::is_convertible< optional && , T>::value && !std::is_convertible< optional const & , T>::value && !std::is_convertible< optional const &&, T>::value && !std::is_convertible< U &&, T>::value /*=> explicit */ ) > explicit optional( optional && other ) : has_value_( other.has_value() ) { if ( other.has_value() ) { contained.construct_value( T{ std::move( other.contained.value() ) } ); } } // 5a (C++11) - non-explicit converting move-construct from optional template< typename U optional_REQUIRES_T( std::is_constructible::value && !std::is_constructible & >::value && !std::is_constructible && >::value && !std::is_constructible const & >::value && !std::is_constructible const && >::value && !std::is_convertible< optional & , T>::value && !std::is_convertible< optional && , T>::value && !std::is_convertible< optional const & , T>::value && !std::is_convertible< optional const &&, T>::value && std::is_convertible< U &&, T>::value /*=> non-explicit */ ) > // NOLINTNEXTLINE( google-explicit-constructor, hicpp-explicit-conversions ) /*non-explicit*/ optional( optional && other ) : has_value_( other.has_value() ) { if ( other.has_value() ) { contained.construct_value( std::move( other.contained.value() ) ); } } // 6 (C++11) - in-place construct template< typename... Args optional_REQUIRES_T( std::is_constructible::value ) > optional_constexpr explicit optional( nonstd_lite_in_place_t(T), Args&&... args ) : has_value_( true ) , contained( in_place, std::forward(args)... ) {} // 7 (C++11) - in-place construct, initializer-list template< typename U, typename... Args optional_REQUIRES_T( std::is_constructible&, Args&&...>::value ) > optional_constexpr explicit optional( nonstd_lite_in_place_t(T), std::initializer_list il, Args&&... args ) : has_value_( true ) , contained( T( il, std::forward(args)...) ) {} // 8a (C++11) - explicit move construct from value template< typename U = T optional_REQUIRES_T( std::is_constructible::value && !std::is_same::type, nonstd_lite_in_place_t(U)>::value && !std::is_same::type, optional>::value && !std::is_convertible::value /*=> explicit */ ) > optional_constexpr explicit optional( U && value ) : has_value_( true ) , contained( nonstd_lite_in_place(T), std::forward( value ) ) {} // 8b (C++11) - non-explicit move construct from value template< typename U = T optional_REQUIRES_T( std::is_constructible::value && !std::is_same::type, nonstd_lite_in_place_t(U)>::value && !std::is_same::type, optional>::value && std::is_convertible::value /*=> non-explicit */ ) > // NOLINTNEXTLINE( google-explicit-constructor, hicpp-explicit-conversions ) optional_constexpr /*non-explicit*/ optional( U && value ) : has_value_( true ) , contained( nonstd_lite_in_place(T), std::forward( value ) ) {} #else // optional_CPP11_OR_GREATER // 8 (C++98) optional( value_type const & value ) : has_value_( true ) , contained( value ) {} #endif // optional_CPP11_OR_GREATER // x.x.3.2, destructor ~optional() { if ( has_value() ) { contained.destruct_value(); } } // x.x.3.3, assignment // 1 (C++98and later) - assign explicitly empty optional & operator=( nullopt_t /*unused*/) optional_noexcept { reset(); return *this; } // 2 (C++98and later) - copy-assign from optional #if optional_CPP11_OR_GREATER // NOLINTNEXTLINE( cppcoreguidelines-c-copy-assignment-signature, misc-unconventional-assign-operator ) optional_REQUIRES_R( optional &, true // std::is_copy_constructible::value // && std::is_copy_assignable::value ) operator=( optional const & other ) noexcept( std11::is_nothrow_move_assignable::value && std11::is_nothrow_move_constructible::value ) #else optional & operator=( optional const & other ) #endif { if ( (has_value() == true ) && (other.has_value() == false) ) { reset(); } else if ( (has_value() == false) && (other.has_value() == true ) ) { initialize( *other ); } else if ( (has_value() == true ) && (other.has_value() == true ) ) { contained.value() = *other; } return *this; } #if optional_CPP11_OR_GREATER // 3 (C++11) - move-assign from optional // NOLINTNEXTLINE( cppcoreguidelines-c-copy-assignment-signature, misc-unconventional-assign-operator ) optional_REQUIRES_R( optional &, true // std11::is_move_constructible::value // && std::is_move_assignable::value ) operator=( optional && other ) noexcept { if ( (has_value() == true ) && (other.has_value() == false) ) { reset(); } else if ( (has_value() == false) && (other.has_value() == true ) ) { initialize( std::move( *other ) ); } else if ( (has_value() == true ) && (other.has_value() == true ) ) { contained.value() = std::move( *other ); } return *this; } // 4 (C++11) - move-assign from value template< typename U = T > // NOLINTNEXTLINE( cppcoreguidelines-c-copy-assignment-signature, misc-unconventional-assign-operator ) optional_REQUIRES_R( optional &, std::is_constructible::value && std11::is_assignable::value && !std::is_same::type, nonstd_lite_in_place_t(U)>::value && !std::is_same::type, optional>::value && !(std::is_scalar::value && std::is_same::type>::value) ) operator=( U && value ) { if ( has_value() ) { contained.value() = std::forward( value ); } else { initialize( T( std::forward( value ) ) ); } return *this; } #else // optional_CPP11_OR_GREATER // 4 (C++98) - copy-assign from value template< typename U /*= T*/ > optional & operator=( U const & value ) { if ( has_value() ) contained.value() = value; else initialize( T( value ) ); return *this; } #endif // optional_CPP11_OR_GREATER // 5 (C++98 and later) - converting copy-assign from optional template< typename U > #if optional_CPP11_OR_GREATER // NOLINTNEXTLINE( cppcoreguidelines-c-copy-assignment-signature, misc-unconventional-assign-operator ) optional_REQUIRES_R( optional&, std::is_constructible< T , U const &>::value && std11::is_assignable< T&, U const &>::value && !std::is_constructible & >::value && !std::is_constructible && >::value && !std::is_constructible const & >::value && !std::is_constructible const && >::value && !std::is_convertible< optional & , T>::value && !std::is_convertible< optional && , T>::value && !std::is_convertible< optional const & , T>::value && !std::is_convertible< optional const &&, T>::value && !std11::is_assignable< T&, optional & >::value && !std11::is_assignable< T&, optional && >::value && !std11::is_assignable< T&, optional const & >::value && !std11::is_assignable< T&, optional const && >::value ) #else optional& #endif // optional_CPP11_OR_GREATER operator=( optional const & other ) { return *this = optional( other ); } #if optional_CPP11_OR_GREATER // 6 (C++11) - converting move-assign from optional template< typename U > // NOLINTNEXTLINE( cppcoreguidelines-c-copy-assignment-signature, misc-unconventional-assign-operator ) optional_REQUIRES_R( optional&, std::is_constructible< T , U>::value && std11::is_assignable< T&, U>::value && !std::is_constructible & >::value && !std::is_constructible && >::value && !std::is_constructible const & >::value && !std::is_constructible const && >::value && !std::is_convertible< optional & , T>::value && !std::is_convertible< optional && , T>::value && !std::is_convertible< optional const & , T>::value && !std::is_convertible< optional const &&, T>::value && !std11::is_assignable< T&, optional & >::value && !std11::is_assignable< T&, optional && >::value && !std11::is_assignable< T&, optional const & >::value && !std11::is_assignable< T&, optional const && >::value ) operator=( optional && other ) { return *this = optional( std::move( other ) ); } // 7 (C++11) - emplace template< typename... Args optional_REQUIRES_T( std::is_constructible::value ) > T& emplace( Args&&... args ) { *this = nullopt; contained.emplace( std::forward(args)... ); has_value_ = true; return contained.value(); } // 8 (C++11) - emplace, initializer-list template< typename U, typename... Args optional_REQUIRES_T( std::is_constructible&, Args&&...>::value ) > T& emplace( std::initializer_list il, Args&&... args ) { *this = nullopt; contained.emplace( il, std::forward(args)... ); has_value_ = true; return contained.value(); } #endif // optional_CPP11_OR_GREATER // x.x.3.4, swap void swap( optional & other ) #if optional_CPP11_OR_GREATER noexcept( std11::is_nothrow_move_constructible::value && std17::is_nothrow_swappable::value ) #endif { using std::swap; if ( (has_value() == true ) && (other.has_value() == true ) ) { swap( **this, *other ); } else if ( (has_value() == false) && (other.has_value() == true ) ) { initialize( std11::move(*other) ); other.reset(); } else if ( (has_value() == true ) && (other.has_value() == false) ) { other.initialize( std11::move(**this) ); reset(); } } // x.x.3.5, observers optional_constexpr value_type const * operator ->() const { return assert( has_value() ), contained.value_ptr(); } optional_constexpr14 value_type * operator ->() { return assert( has_value() ), contained.value_ptr(); } optional_constexpr value_type const & operator *() const optional_ref_qual { return assert( has_value() ), contained.value(); } optional_constexpr14 value_type & operator *() optional_ref_qual { return assert( has_value() ), contained.value(); } #if optional_HAVE( REF_QUALIFIER ) optional_constexpr value_type const && operator *() const optional_refref_qual { return std::move( **this ); } optional_constexpr14 value_type && operator *() optional_refref_qual { return std::move( **this ); } #endif #if optional_CPP11_OR_GREATER optional_constexpr explicit operator bool() const optional_noexcept { return has_value(); } #else optional_constexpr operator safe_bool() const optional_noexcept { return has_value() ? &optional::this_type_does_not_support_comparisons : 0; } #endif // NOLINTNEXTLINE( modernize-use-nodiscard ) /*optional_nodiscard*/ optional_constexpr bool has_value() const optional_noexcept { return has_value_; } // NOLINTNEXTLINE( modernize-use-nodiscard ) /*optional_nodiscard*/ optional_constexpr14 value_type const & value() const optional_ref_qual { #if optional_CONFIG_NO_EXCEPTIONS assert( has_value() ); #else if ( ! has_value() ) { throw bad_optional_access(); } #endif return contained.value(); } optional_constexpr14 value_type & value() optional_ref_qual { #if optional_CONFIG_NO_EXCEPTIONS assert( has_value() ); #else if ( ! has_value() ) { throw bad_optional_access(); } #endif return contained.value(); } #if optional_HAVE( REF_QUALIFIER ) && ( !optional_COMPILER_GNUC_VERSION || optional_COMPILER_GNUC_VERSION >= 490 ) // NOLINTNEXTLINE( modernize-use-nodiscard ) /*optional_nodiscard*/ optional_constexpr value_type const && value() const optional_refref_qual { return std::move( value() ); } optional_constexpr14 value_type && value() optional_refref_qual { return std::move( value() ); } #endif #if optional_HAVE( REF_QUALIFIER ) template< typename U > optional_constexpr value_type value_or( U && v ) const optional_ref_qual { return has_value() ? contained.value() : static_cast(std::forward( v ) ); } template< typename U > optional_constexpr14 value_type value_or( U && v ) optional_refref_qual { #if optional_COMPILER_CLANG_VERSION return has_value() ? /*std::move*/( contained.value() ) : static_cast(std::forward( v ) ); #else return has_value() ? std::move( contained.value() ) : static_cast(std::forward( v ) ); #endif } #else template< typename U > optional_constexpr value_type value_or( U const & v ) const { return has_value() ? contained.value() : static_cast( v ); } #endif // optional_CPP11_OR_GREATER // x.x.3.6, modifiers void reset() optional_noexcept { if ( has_value() ) { contained.destruct_value(); } has_value_ = false; } private: void this_type_does_not_support_comparisons() const {} template< typename V > void initialize( V const & value ) { assert( ! has_value() ); contained.construct_value( value ); has_value_ = true; } #if optional_CPP11_OR_GREATER template< typename V > void initialize( V && value ) { assert( ! has_value() ); contained.construct_value( std::move( value ) ); has_value_ = true; } #endif private: bool has_value_; detail::storage_t< value_type > contained; }; // Relational operators template< typename T, typename U > inline optional_constexpr bool operator==( optional const & x, optional const & y ) { return bool(x) != bool(y) ? false : !bool( x ) ? true : *x == *y; } template< typename T, typename U > inline optional_constexpr bool operator!=( optional const & x, optional const & y ) { return !(x == y); } template< typename T, typename U > inline optional_constexpr bool operator<( optional const & x, optional const & y ) { return (!y) ? false : (!x) ? true : *x < *y; } template< typename T, typename U > inline optional_constexpr bool operator>( optional const & x, optional const & y ) { return (y < x); } template< typename T, typename U > inline optional_constexpr bool operator<=( optional const & x, optional const & y ) { return !(y < x); } template< typename T, typename U > inline optional_constexpr bool operator>=( optional const & x, optional const & y ) { return !(x < y); } // Comparison with nullopt template< typename T > inline optional_constexpr bool operator==( optional const & x, nullopt_t /*unused*/ ) optional_noexcept { return (!x); } template< typename T > inline optional_constexpr bool operator==( nullopt_t /*unused*/, optional const & x ) optional_noexcept { return (!x); } template< typename T > inline optional_constexpr bool operator!=( optional const & x, nullopt_t /*unused*/ ) optional_noexcept { return bool(x); } template< typename T > inline optional_constexpr bool operator!=( nullopt_t /*unused*/, optional const & x ) optional_noexcept { return bool(x); } template< typename T > inline optional_constexpr bool operator<( optional const & /*unused*/, nullopt_t /*unused*/ ) optional_noexcept { return false; } template< typename T > inline optional_constexpr bool operator<( nullopt_t /*unused*/, optional const & x ) optional_noexcept { return bool(x); } template< typename T > inline optional_constexpr bool operator<=( optional const & x, nullopt_t /*unused*/ ) optional_noexcept { return (!x); } template< typename T > inline optional_constexpr bool operator<=( nullopt_t /*unused*/, optional const & /*unused*/ ) optional_noexcept { return true; } template< typename T > inline optional_constexpr bool operator>( optional const & x, nullopt_t /*unused*/ ) optional_noexcept { return bool(x); } template< typename T > inline optional_constexpr bool operator>( nullopt_t /*unused*/, optional const & /*unused*/ ) optional_noexcept { return false; } template< typename T > inline optional_constexpr bool operator>=( optional const & /*unused*/, nullopt_t /*unused*/ ) optional_noexcept { return true; } template< typename T > inline optional_constexpr bool operator>=( nullopt_t /*unused*/, optional const & x ) optional_noexcept { return (!x); } // Comparison with T template< typename T, typename U > inline optional_constexpr bool operator==( optional const & x, U const & v ) { return bool(x) ? *x == v : false; } template< typename T, typename U > inline optional_constexpr bool operator==( U const & v, optional const & x ) { return bool(x) ? v == *x : false; } template< typename T, typename U > inline optional_constexpr bool operator!=( optional const & x, U const & v ) { return bool(x) ? *x != v : true; } template< typename T, typename U > inline optional_constexpr bool operator!=( U const & v, optional const & x ) { return bool(x) ? v != *x : true; } template< typename T, typename U > inline optional_constexpr bool operator<( optional const & x, U const & v ) { return bool(x) ? *x < v : true; } template< typename T, typename U > inline optional_constexpr bool operator<( U const & v, optional const & x ) { return bool(x) ? v < *x : false; } template< typename T, typename U > inline optional_constexpr bool operator<=( optional const & x, U const & v ) { return bool(x) ? *x <= v : true; } template< typename T, typename U > inline optional_constexpr bool operator<=( U const & v, optional const & x ) { return bool(x) ? v <= *x : false; } template< typename T, typename U > inline optional_constexpr bool operator>( optional const & x, U const & v ) { return bool(x) ? *x > v : false; } template< typename T, typename U > inline optional_constexpr bool operator>( U const & v, optional const & x ) { return bool(x) ? v > *x : true; } template< typename T, typename U > inline optional_constexpr bool operator>=( optional const & x, U const & v ) { return bool(x) ? *x >= v : false; } template< typename T, typename U > inline optional_constexpr bool operator>=( U const & v, optional const & x ) { return bool(x) ? v >= *x : true; } // Specialized algorithms template< typename T #if optional_CPP11_OR_GREATER optional_REQUIRES_T( std11::is_move_constructible::value && std17::is_swappable::value ) #endif > void swap( optional & x, optional & y ) #if optional_CPP11_OR_GREATER noexcept( noexcept( x.swap(y) ) ) #endif { x.swap( y ); } #if optional_CPP11_OR_GREATER template< typename T > optional_constexpr optional< typename std::decay::type > make_optional( T && value ) { return optional< typename std::decay::type >( std::forward( value ) ); } template< typename T, typename...Args > optional_constexpr optional make_optional( Args&&... args ) { return optional( nonstd_lite_in_place(T), std::forward(args)...); } template< typename T, typename U, typename... Args > optional_constexpr optional make_optional( std::initializer_list il, Args&&... args ) { return optional( nonstd_lite_in_place(T), il, std::forward(args)...); } #else template< typename T > optional make_optional( T const & value ) { return optional( value ); } #endif // optional_CPP11_OR_GREATER } // namespace optional_lite using optional_lite::optional; using optional_lite::nullopt_t; using optional_lite::nullopt; #if ! optional_CONFIG_NO_EXCEPTIONS using optional_lite::bad_optional_access; #endif using optional_lite::make_optional; } // namespace nonstd #if optional_CPP11_OR_GREATER // specialize the std::hash algorithm: namespace std { template< class T > struct hash< nonstd::optional > { public: std::size_t operator()( nonstd::optional const & v ) const optional_noexcept { return bool( v ) ? std::hash{}( *v ) : 0; } }; } //namespace std #endif // optional_CPP11_OR_GREATER #if defined(__clang__) # pragma clang diagnostic pop #elif defined(__GNUC__) # pragma GCC diagnostic pop #elif defined(_MSC_VER ) # pragma warning( pop ) #endif #endif // optional_USES_STD_OPTIONAL #endif // NONSTD_OPTIONAL_LITE_HPP mergerfs-2.33.5/src/fs_fsync.hpp0000644000000000000000000000165114225522122015226 0ustar rootroot/* ISC License Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include namespace fs { static inline int fsync(const int fd_) { return ::fsync(fd_); } } mergerfs-2.33.5/src/config_cachefiles.hpp0000644000000000000000000000172014225522122017024 0ustar rootroot/* ISC License Copyright (c) 2020, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include "enum.hpp" enum class CacheFilesEnum { LIBFUSE, OFF, PARTIAL, FULL, AUTO_FULL }; typedef Enum CacheFiles; mergerfs-2.33.5/src/fuse_getattr.cpp0000644000000000000000000001077314225522122016110 0ustar rootroot/* Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "config.hpp" #include "errno.hpp" #include "fs_inode.hpp" #include "fs_lstat.hpp" #include "fs_path.hpp" #include "fs_stat.hpp" #include "symlinkify.hpp" #include "ugid.hpp" #include "fuse.h" #include using std::string; namespace l { static void set_stat_if_leads_to_dir(const std::string &path_, struct stat *st_) { int rv; struct stat st; rv = fs::stat(path_,&st); if(rv == -1) return; if(S_ISDIR(st.st_mode)) *st_ = st; return; } static void set_stat_if_leads_to_reg(const std::string &path_, struct stat *st_) { int rv; struct stat st; rv = fs::stat(path_,&st); if(rv == -1) return; if(S_ISREG(st.st_mode)) *st_ = st; return; } static int getattr_controlfile(struct stat *st_) { static const uid_t uid = ::getuid(); static const gid_t gid = ::getgid(); static const time_t now = ::time(NULL); st_->st_dev = 0; st_->st_ino = fs::inode::MAGIC; st_->st_mode = (S_IFREG|S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH); st_->st_nlink = 1; st_->st_uid = uid; st_->st_gid = gid; st_->st_rdev = 0; st_->st_size = 0; st_->st_blksize = 512; st_->st_blocks = 0; st_->st_atime = now; st_->st_mtime = now; st_->st_ctime = now; return 0; } static int getattr(const Policy::Search &searchFunc_, const Branches &branches_, const char *fusepath_, struct stat *st_, const bool symlinkify_, const time_t symlinkify_timeout_, FollowSymlinks followsymlinks_) { int rv; string fullpath; StrVec basepaths; rv = searchFunc_(branches_,fusepath_,&basepaths); if(rv == -1) return -errno; fullpath = fs::path::make(basepaths[0],fusepath_); switch(followsymlinks_) { case FollowSymlinks::ENUM::NEVER: rv = fs::lstat(fullpath,st_); break; case FollowSymlinks::ENUM::DIRECTORY: rv = fs::lstat(fullpath,st_); if(S_ISLNK(st_->st_mode)) l::set_stat_if_leads_to_dir(fullpath,st_); break; case FollowSymlinks::ENUM::REGULAR: rv = fs::lstat(fullpath,st_); if(S_ISLNK(st_->st_mode)) l::set_stat_if_leads_to_reg(fullpath,st_); break; case FollowSymlinks::ENUM::ALL: rv = fs::stat(fullpath,st_); if(rv != 0) rv = fs::lstat(fullpath,st_); break; } if(rv == -1) return -errno; if(symlinkify_ && symlinkify::can_be_symlink(*st_,symlinkify_timeout_)) st_->st_mode = symlinkify::convert(st_->st_mode); fs::inode::calc(fusepath_,st_); return 0; } int getattr(const char *fusepath_, struct stat *st_, fuse_timeouts_t *timeout_) { int rv; Config::Read cfg; const fuse_context *fc = fuse_get_context(); const ugid::Set ugid(fc->uid,fc->gid); rv = l::getattr(cfg->func.getattr.policy, cfg->branches, fusepath_, st_, cfg->symlinkify, cfg->symlinkify_timeout, cfg->follow_symlinks); timeout_->entry = ((rv >= 0) ? cfg->cache_entry : cfg->cache_negative_entry); timeout_->attr = cfg->cache_attr; return rv; } } namespace FUSE { int getattr(const char *fusepath_, struct stat *st_, fuse_timeouts_t *timeout_) { if(fusepath_ == CONTROLFILE) return l::getattr_controlfile(st_); return l::getattr(fusepath_,st_,timeout_); } } mergerfs-2.33.5/src/fuse_fallocate.hpp0000644000000000000000000000177114225522122016373 0ustar rootroot/* Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include "fuse.h" namespace FUSE { int fallocate(const fuse_file_info_t *ffi, int mode, off_t offset, off_t len); } mergerfs-2.33.5/src/fs_mkdir.hpp0000644000000000000000000000252314225522122015211 0ustar rootroot/* ISC License Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include "ghc/filesystem.hpp" #include #include #include namespace fs { static inline int mkdir(const char *path_, const mode_t mode_) { return ::mkdir(path_,mode_); } static inline int mkdir(const std::string &path_, const mode_t mode_) { return fs::mkdir(path_.c_str(),mode_); } static inline int mkdir(const ghc::filesystem::path &path_, const mode_t mode_) { return fs::mkdir(path_.c_str(),mode_); } } mergerfs-2.33.5/src/branches.hpp0000644000000000000000000000454214225522122015203 0ustar rootroot/* ISC License Copyright (c) 2021, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include "branch.hpp" #include "nonstd/optional.hpp" #include "strvec.hpp" #include "tofrom_string.hpp" #include #include #include #include #include class Branches final : public ToFromString { public: class Impl final : public ToFromString, public Branch::Vector { public: typedef std::shared_ptr Ptr; typedef std::shared_ptr CPtr; public: Impl(const uint64_t &default_minfreespace_); public: int from_string(const std::string &str) final; std::string to_string(void) const final; public: const uint64_t& minfreespace(void) const; void to_paths(StrVec &strvec) const; public: Impl& operator=(Impl &impl_); Impl& operator=(Impl &&impl_); private: const uint64_t &_default_minfreespace; }; public: typedef Branches::Impl::Ptr Ptr; typedef Branches::Impl::CPtr CPtr; public: Branches(const uint64_t &default_minfreespace_) : _impl(std::make_shared(default_minfreespace_)) {} public: int from_string(const std::string &str) final; std::string to_string(void) const final; public: operator CPtr() const { std::lock_guard lg(_mutex); return _impl; } CPtr operator->() const { std::lock_guard lg(_mutex); return _impl; } private: mutable std::mutex _mutex; Ptr _impl; }; class SrcMounts : public ToFromString { public: SrcMounts(Branches &b_); public: int from_string(const std::string &str) final; std::string to_string(void) const final; private: Branches &_branches; }; mergerfs-2.33.5/src/fs_ficlone.cpp0000644000000000000000000000173614225522122015522 0ustar rootroot/* ISC License Copyright (c) 2018, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #ifdef __linux__ #warning "using fs_ficlone_linux.icpp" #include "fs_ficlone_linux.icpp" #else #warning "using fs_ficlone_unsupported.icpp" #include "fs_ficlone_unsupported.icpp" #endif mergerfs-2.33.5/src/fs_attr.cpp0000644000000000000000000000170314225522122015047 0ustar rootroot/* Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #ifdef __linux__ #warning "using fs_attr_linux.icpp" #include "fs_attr_linux.icpp" #else #warning "using fs_attr_unsupported.icpp" #include "fs_attr_unsupported.icpp" #endif mergerfs-2.33.5/src/fuse_mknod.hpp0000644000000000000000000000162114225522122015543 0ustar rootroot /* Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once namespace FUSE { int mknod(const char *fusepath, mode_t mode, dev_t rdev); } mergerfs-2.33.5/src/ghc/0000755000000000000000000000000014225522122013441 5ustar rootrootmergerfs-2.33.5/src/ghc/filesystem.hpp0000644000000000000000000056051114225522122016346 0ustar rootroot//--------------------------------------------------------------------------------------- // // ghc::filesystem - A C++17-like filesystem implementation for C++11/C++14/C++17/C++20 // //--------------------------------------------------------------------------------------- // // Copyright (c) 2018, Steffen Schümann // // 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. // //--------------------------------------------------------------------------------------- // // To dynamically select std::filesystem where available on most platforms, // you could use: // // #if ((defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) || (defined(__cplusplus) && __cplusplus >= 201703L)) && defined(__has_include) // #if __has_include() && (!defined(__MAC_OS_X_VERSION_MIN_REQUIRED) || __MAC_OS_X_VERSION_MIN_REQUIRED >= 101500) // #define GHC_USE_STD_FS // #include // namespace fs = std::filesystem; // #endif // #endif // #ifndef GHC_USE_STD_FS // #include // namespace fs = ghc::filesystem; // #endif // //--------------------------------------------------------------------------------------- #ifndef GHC_FILESYSTEM_H #define GHC_FILESYSTEM_H // #define BSD manifest constant only in // sys/param.h #ifndef _WIN32 #include #endif #ifndef GHC_OS_DETECTED #if defined(__APPLE__) && defined(__MACH__) #define GHC_OS_MACOS #elif defined(__linux__) #define GHC_OS_LINUX #if defined(__ANDROID__) #define GHC_OS_ANDROID #endif #elif defined(_WIN64) #define GHC_OS_WINDOWS #define GHC_OS_WIN64 #elif defined(_WIN32) #define GHC_OS_WINDOWS #define GHC_OS_WIN32 #elif defined(__CYGWIN__) #define GHC_OS_CYGWIN #elif defined(__svr4__) #define GHC_OS_SYS5R4 #elif defined(BSD) #define GHC_OS_BSD #elif defined(__EMSCRIPTEN__) #define GHC_OS_WEB #include #elif defined(__QNX__) #define GHC_OS_QNX #define GHC_NO_DIRENT_D_TYPE #else #error "Operating system currently not supported!" #endif #define GHC_OS_DETECTED #if (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) #if _MSVC_LANG == 201703L #define GHC_FILESYSTEM_RUNNING_CPP17 #else #define GHC_FILESYSTEM_RUNNING_CPP20 #endif #elif (defined(__cplusplus) && __cplusplus >= 201703L) #if __cplusplus == 201703L #define GHC_FILESYSTEM_RUNNING_CPP17 #else #define GHC_FILESYSTEM_RUNNING_CPP20 #endif #endif #endif #if defined(GHC_FILESYSTEM_IMPLEMENTATION) #define GHC_EXPAND_IMPL #define GHC_INLINE #ifdef GHC_OS_WINDOWS #ifndef GHC_FS_API #define GHC_FS_API #endif #ifndef GHC_FS_API_CLASS #define GHC_FS_API_CLASS #endif #else #ifndef GHC_FS_API #define GHC_FS_API __attribute__((visibility("default"))) #endif #ifndef GHC_FS_API_CLASS #define GHC_FS_API_CLASS __attribute__((visibility("default"))) #endif #endif #elif defined(GHC_FILESYSTEM_FWD) #define GHC_INLINE #ifdef GHC_OS_WINDOWS #ifndef GHC_FS_API #define GHC_FS_API extern #endif #ifndef GHC_FS_API_CLASS #define GHC_FS_API_CLASS #endif #else #ifndef GHC_FS_API #define GHC_FS_API extern #endif #ifndef GHC_FS_API_CLASS #define GHC_FS_API_CLASS #endif #endif #else #define GHC_EXPAND_IMPL #define GHC_INLINE inline #ifndef GHC_FS_API #define GHC_FS_API #endif #ifndef GHC_FS_API_CLASS #define GHC_FS_API_CLASS #endif #endif #ifdef GHC_EXPAND_IMPL #ifdef GHC_OS_WINDOWS #include // additional includes #include #include #include #include #include #else #include #include #include #include #include #include #include #include #ifdef GHC_OS_ANDROID #include #if __ANDROID_API__ < 12 #include #endif #include #define statvfs statfs #else #include #endif #ifdef GHC_OS_CYGWIN #include #endif #if !defined(__ANDROID__) || __ANDROID_API__ >= 26 #include #endif #endif #ifdef GHC_OS_MACOS #include #endif #if defined(__cpp_impl_three_way_comparison) && defined(__has_include) #if __has_include() #define GHC_HAS_THREEWAY_COMP #include #endif #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #else // GHC_EXPAND_IMPL #if defined(__cpp_impl_three_way_comparison) && defined(__has_include) #if __has_include() #define GHC_HAS_THREEWAY_COMP #include #endif #endif #include #include #include #include #include #include #include #ifdef GHC_OS_WINDOWS #include #endif #endif // GHC_EXPAND_IMPL // After standard library includes. // Standard library support for std::string_view. #if defined(__cpp_lib_string_view) #define GHC_HAS_STD_STRING_VIEW #elif defined(_LIBCPP_VERSION) && (_LIBCPP_VERSION >= 4000) && (__cplusplus >= 201402) #define GHC_HAS_STD_STRING_VIEW #elif defined(_GLIBCXX_RELEASE) && (_GLIBCXX_RELEASE >= 7) && (__cplusplus >= 201703) #define GHC_HAS_STD_STRING_VIEW #elif defined(_MSC_VER) && (_MSC_VER >= 1910 && _MSVC_LANG >= 201703) #define GHC_HAS_STD_STRING_VIEW #endif // Standard library support for std::experimental::string_view. #if defined(_LIBCPP_VERSION) && (_LIBCPP_VERSION >= 3700 && _LIBCPP_VERSION < 7000) && (__cplusplus >= 201402) #define GHC_HAS_STD_EXPERIMENTAL_STRING_VIEW #elif defined(__GNUC__) && (((__GNUC__ == 4) && (__GNUC_MINOR__ >= 9)) || (__GNUC__ > 4)) && (__cplusplus >= 201402) #define GHC_HAS_STD_EXPERIMENTAL_STRING_VIEW #elif defined(__GLIBCXX__) && defined(_GLIBCXX_USE_DUAL_ABI) && (__cplusplus >= 201402) // macro _GLIBCXX_USE_DUAL_ABI is always defined in libstdc++ from gcc-5 and newer #define GHC_HAS_STD_EXPERIMENTAL_STRING_VIEW #endif #if defined(GHC_HAS_STD_STRING_VIEW) #include #elif defined(GHC_HAS_STD_EXPERIMENTAL_STRING_VIEW) #include #endif //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Behaviour Switches (see README.md, should match the config in test/filesystem_test.cpp): //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Enforce C++17 API where possible when compiling for C++20, handles the following cases: // * fs::path::u8string() returns std::string instead of std::u8string // #define GHC_FILESYSTEM_ENFORCE_CPP17_API //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // LWG #2682 disables the since then invalid use of the copy option create_symlinks on directories // configure LWG conformance () #define LWG_2682_BEHAVIOUR //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // LWG #2395 makes crate_directory/create_directories not emit an error if there is a regular // file with that name, it is superseded by P1164R1, so only activate if really needed // #define LWG_2935_BEHAVIOUR //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // LWG #2936 enables new element wise (more expensive) path comparison // * if this->root_name().native().compare(p.root_name().native()) != 0 return result // * if this->has_root_directory() and !p.has_root_directory() return -1 // * if !this->has_root_directory() and p.has_root_directory() return -1 // * else result of element wise comparison of path iteration where first comparison is != 0 or 0 // if all comparisons are 0 (on Windows this implementation does case insensitive root_name() // comparison) #define LWG_2936_BEHAVIOUR //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // LWG #2937 enforces that fs::equivalent emits an error, if !fs::exists(p1)||!exists(p2) #define LWG_2937_BEHAVIOUR //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // UTF8-Everywhere is the original behaviour of ghc::filesystem. But since v1.5 the windows // version defaults to std::wstring storage backend. Still all std::string will be interpreted // as UTF-8 encoded. With this define you can enfoce the old behavior on Windows, using // std::string as backend and for fs::path::native() and char for fs::path::c_str(). This // needs more conversions so it is (an was before v1.5) slower, bot might help keeping source // homogeneous in a multi platform project. // #define GHC_WIN_DISABLE_WSTRING_STORAGE_TYPE //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Raise errors/exceptions when invalid unicode codepoints or UTF-8 sequences are found, // instead of replacing them with the unicode replacement character (U+FFFD). // #define GHC_RAISE_UNICODE_ERRORS //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Automatic prefix windows path with "\\?\" if they would break the MAX_PATH length. // instead of replacing them with the unicode replacement character (U+FFFD). #ifndef GHC_WIN_DISABLE_AUTO_PREFIXES #define GHC_WIN_AUTO_PREFIX_LONG_PATH #endif // GHC_WIN_DISABLE_AUTO_PREFIXES //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // ghc::filesystem version in decimal (major * 10000 + minor * 100 + patch) #define GHC_FILESYSTEM_VERSION 10509L #if !defined(GHC_WITH_EXCEPTIONS) && (defined(__EXCEPTIONS) || defined(__cpp_exceptions) || defined(_CPPUNWIND)) #define GHC_WITH_EXCEPTIONS #endif #if !defined(GHC_WITH_EXCEPTIONS) && defined(GHC_RAISE_UNICODE_ERRORS) #error "Can't raise unicode errors with exception support disabled" #endif namespace ghc { namespace filesystem { #if defined(GHC_HAS_CUSTOM_STRING_VIEW) #define GHC_WITH_STRING_VIEW #elif defined(GHC_HAS_STD_STRING_VIEW) #define GHC_WITH_STRING_VIEW using std::basic_string_view; #elif defined(GHC_HAS_STD_EXPERIMENTAL_STRING_VIEW) #define GHC_WITH_STRING_VIEW using std::experimental::basic_string_view; #endif // temporary existing exception type for yet unimplemented parts class GHC_FS_API_CLASS not_implemented_exception : public std::logic_error { public: not_implemented_exception() : std::logic_error("function not implemented yet.") { } }; template class path_helper_base { public: using value_type = char_type; #ifdef GHC_OS_WINDOWS static constexpr value_type preferred_separator = '\\'; #else static constexpr value_type preferred_separator = '/'; #endif }; #if __cplusplus < 201703L template constexpr char_type path_helper_base::preferred_separator; #endif #ifdef GHC_OS_WINDOWS class path; namespace detail { bool has_executable_extension(const path& p); } #endif // [fs.class.path] class path class GHC_FS_API_CLASS path #if defined(GHC_OS_WINDOWS) && !defined(GHC_WIN_DISABLE_WSTRING_STORAGE_TYPE) #define GHC_USE_WCHAR_T #define GHC_NATIVEWP(p) p.c_str() #define GHC_PLATFORM_LITERAL(str) L##str : private path_helper_base { public: using path_helper_base::value_type; #else #define GHC_NATIVEWP(p) p.wstring().c_str() #define GHC_PLATFORM_LITERAL(str) str : private path_helper_base { public: using path_helper_base::value_type; #endif using string_type = std::basic_string; using path_helper_base::preferred_separator; // [fs.enum.path.format] enumeration format /// The path format in which the constructor argument is given. enum format { generic_format, ///< The generic format, internally used by ///< ghc::filesystem::path with slashes native_format, ///< The format native to the current platform this code ///< is build for auto_format, ///< Try to auto-detect the format, fallback to native }; template struct _is_basic_string : std::false_type { }; template struct _is_basic_string> : std::true_type { }; template struct _is_basic_string, std::allocator>> : std::true_type { }; #ifdef GHC_WITH_STRING_VIEW template struct _is_basic_string> : std::true_type { }; template struct _is_basic_string>> : std::true_type { }; #endif template using path_type = typename std::enable_if::value, path>::type; template #if defined(__cpp_lib_char8_t) && !defined(GHC_FILESYSTEM_ENFORCE_CPP17_API) using path_from_string = typename std::enable_if<_is_basic_string::value || std::is_same::type>::value || std::is_same::type>::value || std::is_same::type>::value || std::is_same::type>::value || std::is_same::type>::value || std::is_same::type>::value || std::is_same::type>::value || std::is_same::type>::value || std::is_same::type>::value || std::is_same::type>::value, path>::type; template using path_type_EcharT = typename std::enable_if::value || std::is_same::value || std::is_same::value || std::is_same::value || std::is_same::value, path>::type; #else using path_from_string = typename std::enable_if<_is_basic_string::value || std::is_same::type>::value || std::is_same::type>::value || std::is_same::type>::value || std::is_same::type>::value || std::is_same::type>::value || std::is_same::type>::value || std::is_same::type>::value || std::is_same::type>::value, path>::type; template using path_type_EcharT = typename std::enable_if::value || std::is_same::value || std::is_same::value || std::is_same::value, path>::type; #endif // [fs.path.construct] constructors and destructor path() noexcept; path(const path& p); path(path&& p) noexcept; path(string_type&& source, format fmt = auto_format); template > path(const Source& source, format fmt = auto_format); template path(InputIterator first, InputIterator last, format fmt = auto_format); #ifdef GHC_WITH_EXCEPTIONS template > path(const Source& source, const std::locale& loc, format fmt = auto_format); template path(InputIterator first, InputIterator last, const std::locale& loc, format fmt = auto_format); #endif ~path(); // [fs.path.assign] assignments path& operator=(const path& p); path& operator=(path&& p) noexcept; path& operator=(string_type&& source); path& assign(string_type&& source); template path& operator=(const Source& source); template path& assign(const Source& source); template path& assign(InputIterator first, InputIterator last); // [fs.path.append] appends path& operator/=(const path& p); template path& operator/=(const Source& source); template path& append(const Source& source); template path& append(InputIterator first, InputIterator last); // [fs.path.concat] concatenation path& operator+=(const path& x); path& operator+=(const string_type& x); #ifdef GHC_WITH_STRING_VIEW path& operator+=(basic_string_view x); #endif path& operator+=(const value_type* x); path& operator+=(value_type x); template path_from_string& operator+=(const Source& x); template path_type_EcharT& operator+=(EcharT x); template path& concat(const Source& x); template path& concat(InputIterator first, InputIterator last); // [fs.path.modifiers] modifiers void clear() noexcept; path& make_preferred(); path& remove_filename(); path& replace_filename(const path& replacement); path& replace_extension(const path& replacement = path()); void swap(path& rhs) noexcept; // [fs.path.native.obs] native format observers const string_type& native() const noexcept; const value_type* c_str() const noexcept; operator string_type() const; template , class Allocator = std::allocator> std::basic_string string(const Allocator& a = Allocator()) const; std::string string() const; std::wstring wstring() const; #if defined(__cpp_lib_char8_t) && !defined(GHC_FILESYSTEM_ENFORCE_CPP17_API) std::u8string u8string() const; #else std::string u8string() const; #endif std::u16string u16string() const; std::u32string u32string() const; // [fs.path.generic.obs] generic format observers template , class Allocator = std::allocator> std::basic_string generic_string(const Allocator& a = Allocator()) const; std::string generic_string() const; std::wstring generic_wstring() const; #if defined(__cpp_lib_char8_t) && !defined(GHC_FILESYSTEM_ENFORCE_CPP17_API) std::u8string generic_u8string() const; #else std::string generic_u8string() const; #endif std::u16string generic_u16string() const; std::u32string generic_u32string() const; // [fs.path.compare] compare int compare(const path& p) const noexcept; int compare(const string_type& s) const; #ifdef GHC_WITH_STRING_VIEW int compare(basic_string_view s) const; #endif int compare(const value_type* s) const; // [fs.path.decompose] decomposition path root_name() const; path root_directory() const; path root_path() const; path relative_path() const; path parent_path() const; path filename() const; path stem() const; path extension() const; // [fs.path.query] query bool empty() const noexcept; bool has_root_name() const; bool has_root_directory() const; bool has_root_path() const; bool has_relative_path() const; bool has_parent_path() const; bool has_filename() const; bool has_stem() const; bool has_extension() const; bool is_absolute() const; bool is_relative() const; // [fs.path.gen] generation path lexically_normal() const; path lexically_relative(const path& base) const; path lexically_proximate(const path& base) const; // [fs.path.itr] iterators class iterator; using const_iterator = iterator; iterator begin() const; iterator end() const; private: using impl_value_type = value_type; using impl_string_type = std::basic_string; friend class directory_iterator; void append_name(const value_type* name); static constexpr impl_value_type generic_separator = '/'; template class input_iterator_range { public: typedef InputIterator iterator; typedef InputIterator const_iterator; typedef typename InputIterator::difference_type difference_type; input_iterator_range(const InputIterator& first, const InputIterator& last) : _first(first) , _last(last) { } InputIterator begin() const { return _first; } InputIterator end() const { return _last; } private: InputIterator _first; InputIterator _last; }; friend void swap(path& lhs, path& rhs) noexcept; friend size_t hash_value(const path& p) noexcept; friend path canonical(const path& p, std::error_code& ec); friend bool create_directories(const path& p, std::error_code& ec) noexcept; string_type::size_type root_name_length() const noexcept; void postprocess_path_with_format(format fmt); void check_long_path(); impl_string_type _path; #ifdef GHC_OS_WINDOWS void handle_prefixes(); friend bool detail::has_executable_extension(const path& p); #ifdef GHC_WIN_AUTO_PREFIX_LONG_PATH string_type::size_type _prefixLength{0}; #else // GHC_WIN_AUTO_PREFIX_LONG_PATH static const string_type::size_type _prefixLength{0}; #endif // GHC_WIN_AUTO_PREFIX_LONG_PATH #else static const string_type::size_type _prefixLength{0}; #endif }; // [fs.path.nonmember] path non-member functions GHC_FS_API void swap(path& lhs, path& rhs) noexcept; GHC_FS_API size_t hash_value(const path& p) noexcept; #ifdef GHC_HAS_THREEWAY_COMP GHC_FS_API std::strong_ordering operator<=>(const path& lhs, const path& rhs) noexcept; #endif GHC_FS_API bool operator==(const path& lhs, const path& rhs) noexcept; GHC_FS_API bool operator!=(const path& lhs, const path& rhs) noexcept; GHC_FS_API bool operator<(const path& lhs, const path& rhs) noexcept; GHC_FS_API bool operator<=(const path& lhs, const path& rhs) noexcept; GHC_FS_API bool operator>(const path& lhs, const path& rhs) noexcept; GHC_FS_API bool operator>=(const path& lhs, const path& rhs) noexcept; GHC_FS_API path operator/(const path& lhs, const path& rhs); // [fs.path.io] path inserter and extractor template std::basic_ostream& operator<<(std::basic_ostream& os, const path& p); template std::basic_istream& operator>>(std::basic_istream& is, path& p); // [pfs.path.factory] path factory functions template > #if defined(__cpp_lib_char8_t) && !defined(GHC_FILESYSTEM_ENFORCE_CPP17_API) [[deprecated("use ghc::filesystem::path::path() with std::u8string instead")]] #endif path u8path(const Source& source); template #if defined(__cpp_lib_char8_t) && !defined(GHC_FILESYSTEM_ENFORCE_CPP17_API) [[deprecated("use ghc::filesystem::path::path() with std::u8string instead")]] #endif path u8path(InputIterator first, InputIterator last); // [fs.class.filesystem_error] class filesystem_error class GHC_FS_API_CLASS filesystem_error : public std::system_error { public: filesystem_error(const std::string& what_arg, std::error_code ec); filesystem_error(const std::string& what_arg, const path& p1, std::error_code ec); filesystem_error(const std::string& what_arg, const path& p1, const path& p2, std::error_code ec); const path& path1() const noexcept; const path& path2() const noexcept; const char* what() const noexcept override; private: std::string _what_arg; std::error_code _ec; path _p1, _p2; }; class GHC_FS_API_CLASS path::iterator { public: using value_type = const path; using difference_type = std::ptrdiff_t; using pointer = const path*; using reference = const path&; using iterator_category = std::bidirectional_iterator_tag; iterator(); iterator(const path& p, const impl_string_type::const_iterator& pos); iterator& operator++(); iterator operator++(int); iterator& operator--(); iterator operator--(int); bool operator==(const iterator& other) const; bool operator!=(const iterator& other) const; reference operator*() const; pointer operator->() const; private: friend class path; impl_string_type::const_iterator increment(const impl_string_type::const_iterator& pos) const; impl_string_type::const_iterator decrement(const impl_string_type::const_iterator& pos) const; void updateCurrent(); impl_string_type::const_iterator _first; impl_string_type::const_iterator _last; impl_string_type::const_iterator _prefix; impl_string_type::const_iterator _root; impl_string_type::const_iterator _iter; path _current; }; struct space_info { uintmax_t capacity; uintmax_t free; uintmax_t available; }; // [fs.enum] enumerations // [fs.enum.file_type] enum class file_type { none, not_found, regular, directory, symlink, block, character, fifo, socket, unknown, }; // [fs.enum.perms] enum class perms : uint16_t { none = 0, owner_read = 0400, owner_write = 0200, owner_exec = 0100, owner_all = 0700, group_read = 040, group_write = 020, group_exec = 010, group_all = 070, others_read = 04, others_write = 02, others_exec = 01, others_all = 07, all = 0777, set_uid = 04000, set_gid = 02000, sticky_bit = 01000, mask = 07777, unknown = 0xffff }; // [fs.enum.perm.opts] enum class perm_options : uint16_t { replace = 3, add = 1, remove = 2, nofollow = 4, }; // [fs.enum.copy.opts] enum class copy_options : uint16_t { none = 0, skip_existing = 1, overwrite_existing = 2, update_existing = 4, recursive = 8, copy_symlinks = 0x10, skip_symlinks = 0x20, directories_only = 0x40, create_symlinks = 0x80, #ifndef GHC_OS_WEB create_hard_links = 0x100 #endif }; // [fs.enum.dir.opts] enum class directory_options : uint16_t { none = 0, follow_directory_symlink = 1, skip_permission_denied = 2, }; // [fs.class.file_status] class file_status class GHC_FS_API_CLASS file_status { public: // [fs.file_status.cons] constructors and destructor file_status() noexcept; explicit file_status(file_type ft, perms prms = perms::unknown) noexcept; file_status(const file_status&) noexcept; file_status(file_status&&) noexcept; ~file_status(); // assignments: file_status& operator=(const file_status&) noexcept; file_status& operator=(file_status&&) noexcept; // [fs.file_status.mods] modifiers void type(file_type ft) noexcept; void permissions(perms prms) noexcept; // [fs.file_status.obs] observers file_type type() const noexcept; perms permissions() const noexcept; friend bool operator==(const file_status& lhs, const file_status& rhs) noexcept { return lhs.type() == rhs.type() && lhs.permissions() == rhs.permissions(); } private: file_type _type; perms _perms; }; using file_time_type = std::chrono::time_point; // [fs.class.directory_entry] Class directory_entry class GHC_FS_API_CLASS directory_entry { public: // [fs.dir.entry.cons] constructors and destructor directory_entry() noexcept = default; directory_entry(const directory_entry&) = default; directory_entry(directory_entry&&) noexcept = default; #ifdef GHC_WITH_EXCEPTIONS explicit directory_entry(const path& p); #endif directory_entry(const path& p, std::error_code& ec); ~directory_entry(); // assignments: directory_entry& operator=(const directory_entry&) = default; directory_entry& operator=(directory_entry&&) noexcept = default; // [fs.dir.entry.mods] modifiers #ifdef GHC_WITH_EXCEPTIONS void assign(const path& p); void replace_filename(const path& p); void refresh(); #endif void assign(const path& p, std::error_code& ec); void replace_filename(const path& p, std::error_code& ec); void refresh(std::error_code& ec) noexcept; // [fs.dir.entry.obs] observers const filesystem::path& path() const noexcept; operator const filesystem::path&() const noexcept; #ifdef GHC_WITH_EXCEPTIONS bool exists() const; bool is_block_file() const; bool is_character_file() const; bool is_directory() const; bool is_fifo() const; bool is_other() const; bool is_regular_file() const; bool is_socket() const; bool is_symlink() const; uintmax_t file_size() const; file_time_type last_write_time() const; file_status status() const; file_status symlink_status() const; #endif bool exists(std::error_code& ec) const noexcept; bool is_block_file(std::error_code& ec) const noexcept; bool is_character_file(std::error_code& ec) const noexcept; bool is_directory(std::error_code& ec) const noexcept; bool is_fifo(std::error_code& ec) const noexcept; bool is_other(std::error_code& ec) const noexcept; bool is_regular_file(std::error_code& ec) const noexcept; bool is_socket(std::error_code& ec) const noexcept; bool is_symlink(std::error_code& ec) const noexcept; uintmax_t file_size(std::error_code& ec) const noexcept; file_time_type last_write_time(std::error_code& ec) const noexcept; file_status status(std::error_code& ec) const noexcept; file_status symlink_status(std::error_code& ec) const noexcept; #ifndef GHC_OS_WEB #ifdef GHC_WITH_EXCEPTIONS uintmax_t hard_link_count() const; #endif uintmax_t hard_link_count(std::error_code& ec) const noexcept; #endif #ifdef GHC_HAS_THREEWAY_COMP std::strong_ordering operator<=>(const directory_entry& rhs) const noexcept; #endif bool operator<(const directory_entry& rhs) const noexcept; bool operator==(const directory_entry& rhs) const noexcept; bool operator!=(const directory_entry& rhs) const noexcept; bool operator<=(const directory_entry& rhs) const noexcept; bool operator>(const directory_entry& rhs) const noexcept; bool operator>=(const directory_entry& rhs) const noexcept; private: friend class directory_iterator; #ifdef GHC_WITH_EXCEPTIONS file_type status_file_type() const; #endif file_type status_file_type(std::error_code& ec) const noexcept; filesystem::path _path; file_status _status; file_status _symlink_status; uintmax_t _file_size = static_cast(-1); #ifndef GHC_OS_WINDOWS uintmax_t _hard_link_count = static_cast(-1); #endif time_t _last_write_time = 0; }; // [fs.class.directory.iterator] Class directory_iterator class GHC_FS_API_CLASS directory_iterator { public: class GHC_FS_API_CLASS proxy { public: const directory_entry& operator*() const& noexcept { return _dir_entry; } directory_entry operator*() && noexcept { return std::move(_dir_entry); } private: explicit proxy(const directory_entry& dir_entry) : _dir_entry(dir_entry) { } friend class directory_iterator; friend class recursive_directory_iterator; directory_entry _dir_entry; }; using iterator_category = std::input_iterator_tag; using value_type = directory_entry; using difference_type = std::ptrdiff_t; using pointer = const directory_entry*; using reference = const directory_entry&; // [fs.dir.itr.members] member functions directory_iterator() noexcept; #ifdef GHC_WITH_EXCEPTIONS explicit directory_iterator(const path& p); directory_iterator(const path& p, directory_options options); #endif directory_iterator(const path& p, std::error_code& ec) noexcept; directory_iterator(const path& p, directory_options options, std::error_code& ec) noexcept; directory_iterator(const directory_iterator& rhs); directory_iterator(directory_iterator&& rhs) noexcept; ~directory_iterator(); directory_iterator& operator=(const directory_iterator& rhs); directory_iterator& operator=(directory_iterator&& rhs) noexcept; const directory_entry& operator*() const; const directory_entry* operator->() const; #ifdef GHC_WITH_EXCEPTIONS directory_iterator& operator++(); #endif directory_iterator& increment(std::error_code& ec) noexcept; // other members as required by [input.iterators] #ifdef GHC_WITH_EXCEPTIONS proxy operator++(int) { proxy p{**this}; ++*this; return p; } #endif bool operator==(const directory_iterator& rhs) const; bool operator!=(const directory_iterator& rhs) const; private: friend class recursive_directory_iterator; class impl; std::shared_ptr _impl; }; // [fs.dir.itr.nonmembers] directory_iterator non-member functions GHC_FS_API directory_iterator begin(directory_iterator iter) noexcept; GHC_FS_API directory_iterator end(const directory_iterator&) noexcept; // [fs.class.re.dir.itr] class recursive_directory_iterator class GHC_FS_API_CLASS recursive_directory_iterator { public: using iterator_category = std::input_iterator_tag; using value_type = directory_entry; using difference_type = std::ptrdiff_t; using pointer = const directory_entry*; using reference = const directory_entry&; // [fs.rec.dir.itr.members] constructors and destructor recursive_directory_iterator() noexcept; #ifdef GHC_WITH_EXCEPTIONS explicit recursive_directory_iterator(const path& p); recursive_directory_iterator(const path& p, directory_options options); #endif recursive_directory_iterator(const path& p, directory_options options, std::error_code& ec) noexcept; recursive_directory_iterator(const path& p, std::error_code& ec) noexcept; recursive_directory_iterator(const recursive_directory_iterator& rhs); recursive_directory_iterator(recursive_directory_iterator&& rhs) noexcept; ~recursive_directory_iterator(); // [fs.rec.dir.itr.members] observers directory_options options() const; int depth() const; bool recursion_pending() const; const directory_entry& operator*() const; const directory_entry* operator->() const; // [fs.rec.dir.itr.members] modifiers recursive_directory_iterator& recursive_directory_iterator& operator=(const recursive_directory_iterator& rhs); recursive_directory_iterator& operator=(recursive_directory_iterator&& rhs) noexcept; #ifdef GHC_WITH_EXCEPTIONS recursive_directory_iterator& operator++(); #endif recursive_directory_iterator& increment(std::error_code& ec) noexcept; #ifdef GHC_WITH_EXCEPTIONS void pop(); #endif void pop(std::error_code& ec); void disable_recursion_pending(); // other members as required by [input.iterators] #ifdef GHC_WITH_EXCEPTIONS directory_iterator::proxy operator++(int) { directory_iterator::proxy proxy{**this}; ++*this; return proxy; } #endif bool operator==(const recursive_directory_iterator& rhs) const; bool operator!=(const recursive_directory_iterator& rhs) const; private: struct recursive_directory_iterator_impl { directory_options _options; bool _recursion_pending; std::stack _dir_iter_stack; recursive_directory_iterator_impl(directory_options options, bool recursion_pending) : _options(options) , _recursion_pending(recursion_pending) { } }; std::shared_ptr _impl; }; // [fs.rec.dir.itr.nonmembers] directory_iterator non-member functions GHC_FS_API recursive_directory_iterator begin(recursive_directory_iterator iter) noexcept; GHC_FS_API recursive_directory_iterator end(const recursive_directory_iterator&) noexcept; // [fs.op.funcs] filesystem operations #ifdef GHC_WITH_EXCEPTIONS GHC_FS_API path absolute(const path& p); GHC_FS_API path canonical(const path& p); GHC_FS_API void copy(const path& from, const path& to); GHC_FS_API void copy(const path& from, const path& to, copy_options options); GHC_FS_API bool copy_file(const path& from, const path& to); GHC_FS_API bool copy_file(const path& from, const path& to, copy_options option); GHC_FS_API void copy_symlink(const path& existing_symlink, const path& new_symlink); GHC_FS_API bool create_directories(const path& p); GHC_FS_API bool create_directory(const path& p); GHC_FS_API bool create_directory(const path& p, const path& attributes); GHC_FS_API void create_directory_symlink(const path& to, const path& new_symlink); GHC_FS_API void create_symlink(const path& to, const path& new_symlink); GHC_FS_API path current_path(); GHC_FS_API void current_path(const path& p); GHC_FS_API bool exists(const path& p); GHC_FS_API bool equivalent(const path& p1, const path& p2); GHC_FS_API uintmax_t file_size(const path& p); GHC_FS_API bool is_block_file(const path& p); GHC_FS_API bool is_character_file(const path& p); GHC_FS_API bool is_directory(const path& p); GHC_FS_API bool is_empty(const path& p); GHC_FS_API bool is_fifo(const path& p); GHC_FS_API bool is_other(const path& p); GHC_FS_API bool is_regular_file(const path& p); GHC_FS_API bool is_socket(const path& p); GHC_FS_API bool is_symlink(const path& p); GHC_FS_API file_time_type last_write_time(const path& p); GHC_FS_API void last_write_time(const path& p, file_time_type new_time); GHC_FS_API void permissions(const path& p, perms prms, perm_options opts = perm_options::replace); GHC_FS_API path proximate(const path& p, const path& base = current_path()); GHC_FS_API path read_symlink(const path& p); GHC_FS_API path relative(const path& p, const path& base = current_path()); GHC_FS_API bool remove(const path& p); GHC_FS_API uintmax_t remove_all(const path& p); GHC_FS_API void rename(const path& from, const path& to); GHC_FS_API void resize_file(const path& p, uintmax_t size); GHC_FS_API space_info space(const path& p); GHC_FS_API file_status status(const path& p); GHC_FS_API file_status symlink_status(const path& p); GHC_FS_API path temp_directory_path(); GHC_FS_API path weakly_canonical(const path& p); #endif GHC_FS_API path absolute(const path& p, std::error_code& ec); GHC_FS_API path canonical(const path& p, std::error_code& ec); GHC_FS_API void copy(const path& from, const path& to, std::error_code& ec) noexcept; GHC_FS_API void copy(const path& from, const path& to, copy_options options, std::error_code& ec) noexcept; GHC_FS_API bool copy_file(const path& from, const path& to, std::error_code& ec) noexcept; GHC_FS_API bool copy_file(const path& from, const path& to, copy_options option, std::error_code& ec) noexcept; GHC_FS_API void copy_symlink(const path& existing_symlink, const path& new_symlink, std::error_code& ec) noexcept; GHC_FS_API bool create_directories(const path& p, std::error_code& ec) noexcept; GHC_FS_API bool create_directory(const path& p, std::error_code& ec) noexcept; GHC_FS_API bool create_directory(const path& p, const path& attributes, std::error_code& ec) noexcept; GHC_FS_API void create_directory_symlink(const path& to, const path& new_symlink, std::error_code& ec) noexcept; GHC_FS_API void create_symlink(const path& to, const path& new_symlink, std::error_code& ec) noexcept; GHC_FS_API path current_path(std::error_code& ec); GHC_FS_API void current_path(const path& p, std::error_code& ec) noexcept; GHC_FS_API bool exists(file_status s) noexcept; GHC_FS_API bool exists(const path& p, std::error_code& ec) noexcept; GHC_FS_API bool equivalent(const path& p1, const path& p2, std::error_code& ec) noexcept; GHC_FS_API uintmax_t file_size(const path& p, std::error_code& ec) noexcept; GHC_FS_API bool is_block_file(file_status s) noexcept; GHC_FS_API bool is_block_file(const path& p, std::error_code& ec) noexcept; GHC_FS_API bool is_character_file(file_status s) noexcept; GHC_FS_API bool is_character_file(const path& p, std::error_code& ec) noexcept; GHC_FS_API bool is_directory(file_status s) noexcept; GHC_FS_API bool is_directory(const path& p, std::error_code& ec) noexcept; GHC_FS_API bool is_empty(const path& p, std::error_code& ec) noexcept; GHC_FS_API bool is_fifo(file_status s) noexcept; GHC_FS_API bool is_fifo(const path& p, std::error_code& ec) noexcept; GHC_FS_API bool is_other(file_status s) noexcept; GHC_FS_API bool is_other(const path& p, std::error_code& ec) noexcept; GHC_FS_API bool is_regular_file(file_status s) noexcept; GHC_FS_API bool is_regular_file(const path& p, std::error_code& ec) noexcept; GHC_FS_API bool is_socket(file_status s) noexcept; GHC_FS_API bool is_socket(const path& p, std::error_code& ec) noexcept; GHC_FS_API bool is_symlink(file_status s) noexcept; GHC_FS_API bool is_symlink(const path& p, std::error_code& ec) noexcept; GHC_FS_API file_time_type last_write_time(const path& p, std::error_code& ec) noexcept; GHC_FS_API void last_write_time(const path& p, file_time_type new_time, std::error_code& ec) noexcept; GHC_FS_API void permissions(const path& p, perms prms, std::error_code& ec) noexcept; GHC_FS_API void permissions(const path& p, perms prms, perm_options opts, std::error_code& ec) noexcept; GHC_FS_API path proximate(const path& p, std::error_code& ec); GHC_FS_API path proximate(const path& p, const path& base, std::error_code& ec); GHC_FS_API path read_symlink(const path& p, std::error_code& ec); GHC_FS_API path relative(const path& p, std::error_code& ec); GHC_FS_API path relative(const path& p, const path& base, std::error_code& ec); GHC_FS_API bool remove(const path& p, std::error_code& ec) noexcept; GHC_FS_API uintmax_t remove_all(const path& p, std::error_code& ec) noexcept; GHC_FS_API void rename(const path& from, const path& to, std::error_code& ec) noexcept; GHC_FS_API void resize_file(const path& p, uintmax_t size, std::error_code& ec) noexcept; GHC_FS_API space_info space(const path& p, std::error_code& ec) noexcept; GHC_FS_API file_status status(const path& p, std::error_code& ec) noexcept; GHC_FS_API bool status_known(file_status s) noexcept; GHC_FS_API file_status symlink_status(const path& p, std::error_code& ec) noexcept; GHC_FS_API path temp_directory_path(std::error_code& ec) noexcept; GHC_FS_API path weakly_canonical(const path& p, std::error_code& ec) noexcept; #ifndef GHC_OS_WEB #ifdef GHC_WITH_EXCEPTIONS GHC_FS_API void create_hard_link(const path& to, const path& new_hard_link); GHC_FS_API uintmax_t hard_link_count(const path& p); #endif GHC_FS_API void create_hard_link(const path& to, const path& new_hard_link, std::error_code& ec) noexcept; GHC_FS_API uintmax_t hard_link_count(const path& p, std::error_code& ec) noexcept; #endif // Non-C++17 add-on std::fstream wrappers with path template > class basic_filebuf : public std::basic_filebuf { public: basic_filebuf() {} ~basic_filebuf() override {} basic_filebuf(const basic_filebuf&) = delete; const basic_filebuf& operator=(const basic_filebuf&) = delete; basic_filebuf* open(const path& p, std::ios_base::openmode mode) { #if defined(GHC_OS_WINDOWS) && !defined(__GLIBCXX__) return std::basic_filebuf::open(p.wstring().c_str(), mode) ? this : 0; #else return std::basic_filebuf::open(p.string().c_str(), mode) ? this : 0; #endif } }; template > class basic_ifstream : public std::basic_ifstream { public: basic_ifstream() {} #if defined(GHC_OS_WINDOWS) && !defined(__GLIBCXX__) explicit basic_ifstream(const path& p, std::ios_base::openmode mode = std::ios_base::in) : std::basic_ifstream(p.wstring().c_str(), mode) { } void open(const path& p, std::ios_base::openmode mode = std::ios_base::in) { std::basic_ifstream::open(p.wstring().c_str(), mode); } #else explicit basic_ifstream(const path& p, std::ios_base::openmode mode = std::ios_base::in) : std::basic_ifstream(p.string().c_str(), mode) { } void open(const path& p, std::ios_base::openmode mode = std::ios_base::in) { std::basic_ifstream::open(p.string().c_str(), mode); } #endif basic_ifstream(const basic_ifstream&) = delete; const basic_ifstream& operator=(const basic_ifstream&) = delete; ~basic_ifstream() override {} }; template > class basic_ofstream : public std::basic_ofstream { public: basic_ofstream() {} #if defined(GHC_OS_WINDOWS) && !defined(__GLIBCXX__) explicit basic_ofstream(const path& p, std::ios_base::openmode mode = std::ios_base::out) : std::basic_ofstream(p.wstring().c_str(), mode) { } void open(const path& p, std::ios_base::openmode mode = std::ios_base::out) { std::basic_ofstream::open(p.wstring().c_str(), mode); } #else explicit basic_ofstream(const path& p, std::ios_base::openmode mode = std::ios_base::out) : std::basic_ofstream(p.string().c_str(), mode) { } void open(const path& p, std::ios_base::openmode mode = std::ios_base::out) { std::basic_ofstream::open(p.string().c_str(), mode); } #endif basic_ofstream(const basic_ofstream&) = delete; const basic_ofstream& operator=(const basic_ofstream&) = delete; ~basic_ofstream() override {} }; template > class basic_fstream : public std::basic_fstream { public: basic_fstream() {} #if defined(GHC_OS_WINDOWS) && !defined(__GLIBCXX__) explicit basic_fstream(const path& p, std::ios_base::openmode mode = std::ios_base::in | std::ios_base::out) : std::basic_fstream(p.wstring().c_str(), mode) { } void open(const path& p, std::ios_base::openmode mode = std::ios_base::in | std::ios_base::out) { std::basic_fstream::open(p.wstring().c_str(), mode); } #else explicit basic_fstream(const path& p, std::ios_base::openmode mode = std::ios_base::in | std::ios_base::out) : std::basic_fstream(p.string().c_str(), mode) { } void open(const path& p, std::ios_base::openmode mode = std::ios_base::in | std::ios_base::out) { std::basic_fstream::open(p.string().c_str(), mode); } #endif basic_fstream(const basic_fstream&) = delete; const basic_fstream& operator=(const basic_fstream&) = delete; ~basic_fstream() override {} }; typedef basic_filebuf filebuf; typedef basic_filebuf wfilebuf; typedef basic_ifstream ifstream; typedef basic_ifstream wifstream; typedef basic_ofstream ofstream; typedef basic_ofstream wofstream; typedef basic_fstream fstream; typedef basic_fstream wfstream; class GHC_FS_API_CLASS u8arguments { public: u8arguments(int& argc, char**& argv); ~u8arguments() { _refargc = _argc; _refargv = _argv; } bool valid() const { return _isvalid; } private: int _argc; char** _argv; int& _refargc; char**& _refargv; bool _isvalid; #ifdef GHC_OS_WINDOWS std::vector _args; std::vector _argp; #endif }; //------------------------------------------------------------------------------------------------- // Implementation //------------------------------------------------------------------------------------------------- namespace detail { enum utf8_states_t { S_STRT = 0, S_RJCT = 8 }; GHC_FS_API void appendUTF8(std::string& str, uint32_t unicode); GHC_FS_API bool is_surrogate(uint32_t c); GHC_FS_API bool is_high_surrogate(uint32_t c); GHC_FS_API bool is_low_surrogate(uint32_t c); GHC_FS_API unsigned consumeUtf8Fragment(const unsigned state, const uint8_t fragment, uint32_t& codepoint); enum class portable_error { none = 0, exists, not_found, not_supported, not_implemented, invalid_argument, is_a_directory, }; GHC_FS_API std::error_code make_error_code(portable_error err); #ifdef GHC_OS_WINDOWS GHC_FS_API std::error_code make_system_error(uint32_t err = 0); #else GHC_FS_API std::error_code make_system_error(int err = 0); #endif } // namespace detail namespace detail { #ifdef GHC_EXPAND_IMPL GHC_INLINE std::error_code make_error_code(portable_error err) { #ifdef GHC_OS_WINDOWS switch (err) { case portable_error::none: return std::error_code(); case portable_error::exists: return std::error_code(ERROR_ALREADY_EXISTS, std::system_category()); case portable_error::not_found: return std::error_code(ERROR_PATH_NOT_FOUND, std::system_category()); case portable_error::not_supported: return std::error_code(ERROR_NOT_SUPPORTED, std::system_category()); case portable_error::not_implemented: return std::error_code(ERROR_CALL_NOT_IMPLEMENTED, std::system_category()); case portable_error::invalid_argument: return std::error_code(ERROR_INVALID_PARAMETER, std::system_category()); case portable_error::is_a_directory: #ifdef ERROR_DIRECTORY_NOT_SUPPORTED return std::error_code(ERROR_DIRECTORY_NOT_SUPPORTED, std::system_category()); #else return std::error_code(ERROR_NOT_SUPPORTED, std::system_category()); #endif } #else switch (err) { case portable_error::none: return std::error_code(); case portable_error::exists: return std::error_code(EEXIST, std::system_category()); case portable_error::not_found: return std::error_code(ENOENT, std::system_category()); case portable_error::not_supported: return std::error_code(ENOTSUP, std::system_category()); case portable_error::not_implemented: return std::error_code(ENOSYS, std::system_category()); case portable_error::invalid_argument: return std::error_code(EINVAL, std::system_category()); case portable_error::is_a_directory: return std::error_code(EISDIR, std::system_category()); } #endif return std::error_code(); } #ifdef GHC_OS_WINDOWS GHC_INLINE std::error_code make_system_error(uint32_t err) { return std::error_code(err ? static_cast(err) : static_cast(::GetLastError()), std::system_category()); } #else GHC_INLINE std::error_code make_system_error(int err) { return std::error_code(err ? err : errno, std::system_category()); } #endif #endif // GHC_EXPAND_IMPL template using EnableBitmask = typename std::enable_if::value || std::is_same::value || std::is_same::value || std::is_same::value, Enum>::type; } // namespace detail template constexpr detail::EnableBitmask operator&(Enum X, Enum Y) { using underlying = typename std::underlying_type::type; return static_cast(static_cast(X) & static_cast(Y)); } template constexpr detail::EnableBitmask operator|(Enum X, Enum Y) { using underlying = typename std::underlying_type::type; return static_cast(static_cast(X) | static_cast(Y)); } template constexpr detail::EnableBitmask operator^(Enum X, Enum Y) { using underlying = typename std::underlying_type::type; return static_cast(static_cast(X) ^ static_cast(Y)); } template constexpr detail::EnableBitmask operator~(Enum X) { using underlying = typename std::underlying_type::type; return static_cast(~static_cast(X)); } template detail::EnableBitmask& operator&=(Enum& X, Enum Y) { X = X & Y; return X; } template detail::EnableBitmask& operator|=(Enum& X, Enum Y) { X = X | Y; return X; } template detail::EnableBitmask& operator^=(Enum& X, Enum Y) { X = X ^ Y; return X; } #ifdef GHC_EXPAND_IMPL namespace detail { GHC_INLINE bool in_range(uint32_t c, uint32_t lo, uint32_t hi) { return (static_cast(c - lo) < (hi - lo + 1)); } GHC_INLINE bool is_surrogate(uint32_t c) { return in_range(c, 0xd800, 0xdfff); } GHC_INLINE bool is_high_surrogate(uint32_t c) { return (c & 0xfffffc00) == 0xd800; } GHC_INLINE bool is_low_surrogate(uint32_t c) { return (c & 0xfffffc00) == 0xdc00; } GHC_INLINE void appendUTF8(std::string& str, uint32_t unicode) { if (unicode <= 0x7f) { str.push_back(static_cast(unicode)); } else if (unicode >= 0x80 && unicode <= 0x7ff) { str.push_back(static_cast((unicode >> 6) + 192)); str.push_back(static_cast((unicode & 0x3f) + 128)); } else if ((unicode >= 0x800 && unicode <= 0xd7ff) || (unicode >= 0xe000 && unicode <= 0xffff)) { str.push_back(static_cast((unicode >> 12) + 224)); str.push_back(static_cast(((unicode & 0xfff) >> 6) + 128)); str.push_back(static_cast((unicode & 0x3f) + 128)); } else if (unicode >= 0x10000 && unicode <= 0x10ffff) { str.push_back(static_cast((unicode >> 18) + 240)); str.push_back(static_cast(((unicode & 0x3ffff) >> 12) + 128)); str.push_back(static_cast(((unicode & 0xfff) >> 6) + 128)); str.push_back(static_cast((unicode & 0x3f) + 128)); } else { #ifdef GHC_RAISE_UNICODE_ERRORS throw filesystem_error("Illegal code point for unicode character.", str, std::make_error_code(std::errc::illegal_byte_sequence)); #else appendUTF8(str, 0xfffd); #endif } } // Thanks to Bjoern Hoehrmann (https://bjoern.hoehrmann.de/utf-8/decoder/dfa/) // and Taylor R Campbell for the ideas to this DFA approach of UTF-8 decoding; // Generating debugging and shrinking my own DFA from scratch was a day of fun! GHC_INLINE unsigned consumeUtf8Fragment(const unsigned state, const uint8_t fragment, uint32_t& codepoint) { static const uint32_t utf8_state_info[] = { // encoded states 0x11111111u, 0x11111111u, 0x77777777u, 0x77777777u, 0x88888888u, 0x88888888u, 0x88888888u, 0x88888888u, 0x22222299u, 0x22222222u, 0x22222222u, 0x22222222u, 0x3333333au, 0x33433333u, 0x9995666bu, 0x99999999u, 0x88888880u, 0x22818108u, 0x88888881u, 0x88888882u, 0x88888884u, 0x88888887u, 0x88888886u, 0x82218108u, 0x82281108u, 0x88888888u, 0x88888883u, 0x88888885u, 0u, 0u, 0u, 0u, }; uint8_t category = fragment < 128 ? 0 : (utf8_state_info[(fragment >> 3) & 0xf] >> ((fragment & 7) << 2)) & 0xf; codepoint = (state ? (codepoint << 6) | (fragment & 0x3fu) : (0xffu >> category) & fragment); return state == S_RJCT ? static_cast(S_RJCT) : static_cast((utf8_state_info[category + 16] >> (state << 2)) & 0xf); } GHC_INLINE bool validUtf8(const std::string& utf8String) { std::string::const_iterator iter = utf8String.begin(); unsigned utf8_state = S_STRT; std::uint32_t codepoint = 0; while (iter < utf8String.end()) { if ((utf8_state = consumeUtf8Fragment(utf8_state, static_cast(*iter++), codepoint)) == S_RJCT) { return false; } } if (utf8_state) { return false; } return true; } } // namespace detail #endif namespace detail { template ::value && (sizeof(typename Utf8String::value_type) == 1) && (sizeof(typename StringType::value_type) == 1)>::type* = nullptr> inline StringType fromUtf8(const Utf8String& utf8String, const typename StringType::allocator_type& alloc = typename StringType::allocator_type()) { return StringType(utf8String.begin(), utf8String.end(), alloc); } template ::value && (sizeof(typename Utf8String::value_type) == 1) && (sizeof(typename StringType::value_type) == 2)>::type* = nullptr> inline StringType fromUtf8(const Utf8String& utf8String, const typename StringType::allocator_type& alloc = typename StringType::allocator_type()) { StringType result(alloc); result.reserve(utf8String.length()); auto iter = utf8String.cbegin(); unsigned utf8_state = S_STRT; std::uint32_t codepoint = 0; while (iter < utf8String.cend()) { if ((utf8_state = consumeUtf8Fragment(utf8_state, static_cast(*iter++), codepoint)) == S_STRT) { if (codepoint <= 0xffff) { result += static_cast(codepoint); } else { codepoint -= 0x10000; result += static_cast((codepoint >> 10) + 0xd800); result += static_cast((codepoint & 0x3ff) + 0xdc00); } codepoint = 0; } else if (utf8_state == S_RJCT) { #ifdef GHC_RAISE_UNICODE_ERRORS throw filesystem_error("Illegal byte sequence for unicode character.", utf8String, std::make_error_code(std::errc::illegal_byte_sequence)); #else result += static_cast(0xfffd); utf8_state = S_STRT; codepoint = 0; #endif } } if (utf8_state) { #ifdef GHC_RAISE_UNICODE_ERRORS throw filesystem_error("Illegal byte sequence for unicode character.", utf8String, std::make_error_code(std::errc::illegal_byte_sequence)); #else result += static_cast(0xfffd); #endif } return result; } template ::value && (sizeof(typename Utf8String::value_type) == 1) && (sizeof(typename StringType::value_type) == 4)>::type* = nullptr> inline StringType fromUtf8(const Utf8String& utf8String, const typename StringType::allocator_type& alloc = typename StringType::allocator_type()) { StringType result(alloc); result.reserve(utf8String.length()); auto iter = utf8String.cbegin(); unsigned utf8_state = S_STRT; std::uint32_t codepoint = 0; while (iter < utf8String.cend()) { if ((utf8_state = consumeUtf8Fragment(utf8_state, static_cast(*iter++), codepoint)) == S_STRT) { result += static_cast(codepoint); codepoint = 0; } else if (utf8_state == S_RJCT) { #ifdef GHC_RAISE_UNICODE_ERRORS throw filesystem_error("Illegal byte sequence for unicode character.", utf8String, std::make_error_code(std::errc::illegal_byte_sequence)); #else result += static_cast(0xfffd); utf8_state = S_STRT; codepoint = 0; #endif } } if (utf8_state) { #ifdef GHC_RAISE_UNICODE_ERRORS throw filesystem_error("Illegal byte sequence for unicode character.", utf8String, std::make_error_code(std::errc::illegal_byte_sequence)); #else result += static_cast(0xfffd); #endif } return result; } template inline StringType fromUtf8(const charT (&utf8String)[N]) { #ifdef GHC_WITH_STRING_VIEW return fromUtf8(basic_string_view(utf8String, N - 1)); #else return fromUtf8(std::basic_string(utf8String, N - 1)); #endif } template ::value && (sizeof(typename strT::value_type) == 1), int>::type size = 1> inline std::string toUtf8(const strT& unicodeString) { return std::string(unicodeString.begin(), unicodeString.end()); } template ::value && (sizeof(typename strT::value_type) == 2), int>::type size = 2> inline std::string toUtf8(const strT& unicodeString) { std::string result; for (auto iter = unicodeString.begin(); iter != unicodeString.end(); ++iter) { char32_t c = *iter; if (is_surrogate(c)) { ++iter; if (iter != unicodeString.end() && is_high_surrogate(c) && is_low_surrogate(*iter)) { appendUTF8(result, (char32_t(c) << 10) + *iter - 0x35fdc00); } else { #ifdef GHC_RAISE_UNICODE_ERRORS throw filesystem_error("Illegal code point for unicode character.", result, std::make_error_code(std::errc::illegal_byte_sequence)); #else appendUTF8(result, 0xfffd); if (iter == unicodeString.end()) { break; } #endif } } else { appendUTF8(result, c); } } return result; } template ::value && (sizeof(typename strT::value_type) == 4), int>::type size = 4> inline std::string toUtf8(const strT& unicodeString) { std::string result; for (auto c : unicodeString) { appendUTF8(result, static_cast(c)); } return result; } template inline std::string toUtf8(const charT* unicodeString) { #ifdef GHC_WITH_STRING_VIEW return toUtf8(basic_string_view>(unicodeString)); #else return toUtf8(std::basic_string>(unicodeString)); #endif } #ifdef GHC_USE_WCHAR_T template ::value && (sizeof(typename WString::value_type) == 2) && (sizeof(typename StringType::value_type) == 1), bool>::type = false> inline StringType fromWChar(const WString& wString, const typename StringType::allocator_type& alloc = typename StringType::allocator_type()) { auto temp = toUtf8(wString); return StringType(temp.begin(), temp.end(), alloc); } template ::value && (sizeof(typename WString::value_type) == 2) && (sizeof(typename StringType::value_type) == 2), bool>::type = false> inline StringType fromWChar(const WString& wString, const typename StringType::allocator_type& alloc = typename StringType::allocator_type()) { return StringType(wString.begin(), wString.end(), alloc); } template ::value && (sizeof(typename WString::value_type) == 2) && (sizeof(typename StringType::value_type) == 4), bool>::type = false> inline StringType fromWChar(const WString& wString, const typename StringType::allocator_type& alloc = typename StringType::allocator_type()) { auto temp = toUtf8(wString); return fromUtf8(temp, alloc); } template ::value && (sizeof(typename strT::value_type) == 1), bool>::type = false> inline std::wstring toWChar(const strT& unicodeString) { return fromUtf8(unicodeString); } template ::value && (sizeof(typename strT::value_type) == 2), bool>::type = false> inline std::wstring toWChar(const strT& unicodeString) { return std::wstring(unicodeString.begin(), unicodeString.end()); } template ::value && (sizeof(typename strT::value_type) == 4), bool>::type = false> inline std::wstring toWChar(const strT& unicodeString) { auto temp = toUtf8(unicodeString); return fromUtf8(temp); } template inline std::wstring toWChar(const charT* unicodeString) { #ifdef GHC_WITH_STRING_VIEW return toWChar(basic_string_view>(unicodeString)); #else return toWChar(std::basic_string>(unicodeString)); #endif } #endif // GHC_USE_WCHAR_T } // namespace detail #ifdef GHC_EXPAND_IMPL namespace detail { template ::value, bool>::type = true> GHC_INLINE bool startsWith(const strT& what, const strT& with) { return with.length() <= what.length() && equal(with.begin(), with.end(), what.begin()); } template ::value, bool>::type = true> GHC_INLINE bool endsWith(const strT& what, const strT& with) { return with.length() <= what.length() && what.compare(what.length() - with.length(), with.size(), with) == 0; } } // namespace detail GHC_INLINE void path::check_long_path() { #if defined(GHC_OS_WINDOWS) && defined(GHC_WIN_AUTO_PREFIX_LONG_PATH) if (is_absolute() && _path.length() >= MAX_PATH - 12 && !detail::startsWith(_path, impl_string_type(GHC_PLATFORM_LITERAL("\\\\?\\")))) { postprocess_path_with_format(native_format); } #endif } GHC_INLINE void path::postprocess_path_with_format(path::format fmt) { #ifdef GHC_RAISE_UNICODE_ERRORS if (!detail::validUtf8(_path)) { path t; t._path = _path; throw filesystem_error("Illegal byte sequence for unicode character.", t, std::make_error_code(std::errc::illegal_byte_sequence)); } #endif switch (fmt) { #ifdef GHC_OS_WINDOWS case path::native_format: case path::auto_format: case path::generic_format: for (auto& c : _path) { if (c == generic_separator) { c = preferred_separator; } } #ifdef GHC_WIN_AUTO_PREFIX_LONG_PATH if (is_absolute() && _path.length() >= MAX_PATH - 12 && !detail::startsWith(_path, impl_string_type(GHC_PLATFORM_LITERAL("\\\\?\\")))) { _path = GHC_PLATFORM_LITERAL("\\\\?\\") + _path; } #endif handle_prefixes(); break; #else case path::auto_format: case path::native_format: case path::generic_format: // nothing to do break; #endif } if (_path.length() > _prefixLength + 2 && _path[_prefixLength] == preferred_separator && _path[_prefixLength + 1] == preferred_separator && _path[_prefixLength + 2] != preferred_separator) { impl_string_type::iterator new_end = std::unique(_path.begin() + static_cast(_prefixLength) + 2, _path.end(), [](path::value_type lhs, path::value_type rhs) { return lhs == rhs && lhs == preferred_separator; }); _path.erase(new_end, _path.end()); } else { impl_string_type::iterator new_end = std::unique(_path.begin() + static_cast(_prefixLength), _path.end(), [](path::value_type lhs, path::value_type rhs) { return lhs == rhs && lhs == preferred_separator; }); _path.erase(new_end, _path.end()); } } #endif // GHC_EXPAND_IMPL template inline path::path(const Source& source, format fmt) #ifdef GHC_USE_WCHAR_T : _path(detail::toWChar(source)) #else : _path(detail::toUtf8(source)) #endif { postprocess_path_with_format(fmt); } template inline path u8path(const Source& source) { return path(source); } template inline path u8path(InputIterator first, InputIterator last) { return path(first, last); } template inline path::path(InputIterator first, InputIterator last, format fmt) : path(std::basic_string::value_type>(first, last), fmt) { // delegated } #ifdef GHC_EXPAND_IMPL namespace detail { GHC_INLINE bool equals_simple_insensitive(const path::value_type* str1, const path::value_type* str2) { #ifdef GHC_OS_WINDOWS #ifdef __GNUC__ while (::tolower((unsigned char)*str1) == ::tolower((unsigned char)*str2++)) { if (*str1++ == 0) return true; } return false; #else // __GNUC__ #ifdef GHC_USE_WCHAR_T return 0 == ::_wcsicmp(str1, str2); #else // GHC_USE_WCHAR_T return 0 == ::_stricmp(str1, str2); #endif // GHC_USE_WCHAR_T #endif // __GNUC__ #else // GHC_OS_WINDOWS return 0 == ::strcasecmp(str1, str2); #endif // GHC_OS_WINDOWS } GHC_INLINE int compare_simple_insensitive(const path::value_type* str1, size_t len1, const path::value_type* str2, size_t len2) { while (len1 > 0 && len2 > 0 && ::tolower(static_cast(*str1)) == ::tolower(static_cast(*str2))) { --len1; --len2; ++str1; ++str2; } if (len1 && len2) { return *str1 < *str2 ? -1 : 1; } if (len1 == 0 && len2 == 0) { return 0; } return len1 == 0 ? -1 : 1; } GHC_INLINE const char* strerror_adapter(char* gnu, char*) { return gnu; } GHC_INLINE const char* strerror_adapter(int posix, char* buffer) { if (posix) { return "Error in strerror_r!"; } return buffer; } template GHC_INLINE std::string systemErrorText(ErrorNumber code = 0) { #if defined(GHC_OS_WINDOWS) LPVOID msgBuf; DWORD dw = code ? static_cast(code) : ::GetLastError(); FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, dw, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPWSTR)&msgBuf, 0, NULL); std::string msg = toUtf8(std::wstring((LPWSTR)msgBuf)); LocalFree(msgBuf); return msg; #else char buffer[512]; return strerror_adapter(strerror_r(code ? code : errno, buffer, sizeof(buffer)), buffer); #endif } #ifdef GHC_OS_WINDOWS using CreateSymbolicLinkW_fp = BOOLEAN(WINAPI*)(LPCWSTR, LPCWSTR, DWORD); using CreateHardLinkW_fp = BOOLEAN(WINAPI*)(LPCWSTR, LPCWSTR, LPSECURITY_ATTRIBUTES); GHC_INLINE void create_symlink(const path& target_name, const path& new_symlink, bool to_directory, std::error_code& ec) { std::error_code tec; auto fs = status(target_name, tec); if ((fs.type() == file_type::directory && !to_directory) || (fs.type() == file_type::regular && to_directory)) { ec = detail::make_error_code(detail::portable_error::not_supported); return; } #if defined(__GNUC__) && __GNUC__ >= 8 #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wcast-function-type" #endif static CreateSymbolicLinkW_fp api_call = reinterpret_cast(GetProcAddress(GetModuleHandleW(L"kernel32.dll"), "CreateSymbolicLinkW")); #if defined(__GNUC__) && __GNUC__ >= 8 #pragma GCC diagnostic pop #endif if (api_call) { if (api_call(GHC_NATIVEWP(new_symlink), GHC_NATIVEWP(target_name), to_directory ? 1 : 0) == 0) { auto result = ::GetLastError(); if (result == ERROR_PRIVILEGE_NOT_HELD && api_call(GHC_NATIVEWP(new_symlink), GHC_NATIVEWP(target_name), to_directory ? 3 : 2) != 0) { return; } ec = detail::make_system_error(result); } } else { ec = detail::make_system_error(ERROR_NOT_SUPPORTED); } } GHC_INLINE void create_hardlink(const path& target_name, const path& new_hardlink, std::error_code& ec) { #if defined(__GNUC__) && __GNUC__ >= 8 #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wcast-function-type" #endif static CreateHardLinkW_fp api_call = reinterpret_cast(GetProcAddress(GetModuleHandleW(L"kernel32.dll"), "CreateHardLinkW")); #if defined(__GNUC__) && __GNUC__ >= 8 #pragma GCC diagnostic pop #endif if (api_call) { if (api_call(GHC_NATIVEWP(new_hardlink), GHC_NATIVEWP(target_name), NULL) == 0) { ec = detail::make_system_error(); } } else { ec = detail::make_system_error(ERROR_NOT_SUPPORTED); } } GHC_INLINE path getFullPathName(const wchar_t* p, std::error_code& ec) { ULONG size = ::GetFullPathNameW(p, 0, 0, 0); if (size) { std::vector buf(size, 0); ULONG s2 = GetFullPathNameW(p, size, buf.data(), nullptr); if (s2 && s2 < size) { return path(std::wstring(buf.data(), s2)); } } ec = detail::make_system_error(); return path(); } #else GHC_INLINE void create_symlink(const path& target_name, const path& new_symlink, bool, std::error_code& ec) { if (::symlink(target_name.c_str(), new_symlink.c_str()) != 0) { ec = detail::make_system_error(); } } #ifndef GHC_OS_WEB GHC_INLINE void create_hardlink(const path& target_name, const path& new_hardlink, std::error_code& ec) { if (::link(target_name.c_str(), new_hardlink.c_str()) != 0) { ec = detail::make_system_error(); } } #endif #endif template GHC_INLINE file_status file_status_from_st_mode(T mode) { #ifdef GHC_OS_WINDOWS file_type ft = file_type::unknown; if ((mode & _S_IFDIR) == _S_IFDIR) { ft = file_type::directory; } else if ((mode & _S_IFREG) == _S_IFREG) { ft = file_type::regular; } else if ((mode & _S_IFCHR) == _S_IFCHR) { ft = file_type::character; } perms prms = static_cast(mode & 0xfff); return file_status(ft, prms); #else file_type ft = file_type::unknown; if (S_ISDIR(mode)) { ft = file_type::directory; } else if (S_ISREG(mode)) { ft = file_type::regular; } else if (S_ISCHR(mode)) { ft = file_type::character; } else if (S_ISBLK(mode)) { ft = file_type::block; } else if (S_ISFIFO(mode)) { ft = file_type::fifo; } else if (S_ISLNK(mode)) { ft = file_type::symlink; } else if (S_ISSOCK(mode)) { ft = file_type::socket; } perms prms = static_cast(mode & 0xfff); return file_status(ft, prms); #endif } #ifdef GHC_OS_WINDOWS #ifndef REPARSE_DATA_BUFFER_HEADER_SIZE typedef struct _REPARSE_DATA_BUFFER { ULONG ReparseTag; USHORT ReparseDataLength; USHORT Reserved; union { struct { USHORT SubstituteNameOffset; USHORT SubstituteNameLength; USHORT PrintNameOffset; USHORT PrintNameLength; ULONG Flags; WCHAR PathBuffer[1]; } SymbolicLinkReparseBuffer; struct { USHORT SubstituteNameOffset; USHORT SubstituteNameLength; USHORT PrintNameOffset; USHORT PrintNameLength; WCHAR PathBuffer[1]; } MountPointReparseBuffer; struct { UCHAR DataBuffer[1]; } GenericReparseBuffer; } DUMMYUNIONNAME; } REPARSE_DATA_BUFFER; #ifndef MAXIMUM_REPARSE_DATA_BUFFER_SIZE #define MAXIMUM_REPARSE_DATA_BUFFER_SIZE (16 * 1024) #endif #endif GHC_INLINE std::shared_ptr getReparseData(const path& p, std::error_code& ec) { std::shared_ptr file(CreateFileW(GHC_NATIVEWP(p), 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, 0, OPEN_EXISTING, FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS, 0), CloseHandle); if (file.get() == INVALID_HANDLE_VALUE) { ec = detail::make_system_error(); return nullptr; } std::shared_ptr reparseData((REPARSE_DATA_BUFFER*)std::calloc(1, MAXIMUM_REPARSE_DATA_BUFFER_SIZE), std::free); ULONG bufferUsed; if (DeviceIoControl(file.get(), FSCTL_GET_REPARSE_POINT, 0, 0, reparseData.get(), MAXIMUM_REPARSE_DATA_BUFFER_SIZE, &bufferUsed, 0)) { return reparseData; } else { ec = detail::make_system_error(); } return nullptr; } #endif GHC_INLINE path resolveSymlink(const path& p, std::error_code& ec) { #ifdef GHC_OS_WINDOWS path result; auto reparseData = detail::getReparseData(p, ec); if (!ec) { if (reparseData && IsReparseTagMicrosoft(reparseData->ReparseTag)) { switch (reparseData->ReparseTag) { case IO_REPARSE_TAG_SYMLINK: { auto printName = std::wstring(&reparseData->SymbolicLinkReparseBuffer.PathBuffer[reparseData->SymbolicLinkReparseBuffer.PrintNameOffset / sizeof(WCHAR)], reparseData->SymbolicLinkReparseBuffer.PrintNameLength / sizeof(WCHAR)); auto substituteName = std::wstring(&reparseData->SymbolicLinkReparseBuffer.PathBuffer[reparseData->SymbolicLinkReparseBuffer.SubstituteNameOffset / sizeof(WCHAR)], reparseData->SymbolicLinkReparseBuffer.SubstituteNameLength / sizeof(WCHAR)); if (detail::endsWith(substituteName, printName) && detail::startsWith(substituteName, std::wstring(L"\\??\\"))) { result = printName; } else { result = substituteName; } if (reparseData->SymbolicLinkReparseBuffer.Flags & 0x1 /*SYMLINK_FLAG_RELATIVE*/) { result = p.parent_path() / result; } break; } case IO_REPARSE_TAG_MOUNT_POINT: result = detail::getFullPathName(GHC_NATIVEWP(p), ec); //result = std::wstring(&reparseData->MountPointReparseBuffer.PathBuffer[reparseData->MountPointReparseBuffer.SubstituteNameOffset / sizeof(WCHAR)], reparseData->MountPointReparseBuffer.SubstituteNameLength / sizeof(WCHAR)); break; default: break; } } } return result; #else size_t bufferSize = 256; while (true) { std::vector buffer(bufferSize, static_cast(0)); auto rc = ::readlink(p.c_str(), buffer.data(), buffer.size()); if (rc < 0) { ec = detail::make_system_error(); return path(); } else if (rc < static_cast(bufferSize)) { return path(std::string(buffer.data(), static_cast(rc))); } bufferSize *= 2; } return path(); #endif } #ifdef GHC_OS_WINDOWS GHC_INLINE time_t timeFromFILETIME(const FILETIME& ft) { ULARGE_INTEGER ull; ull.LowPart = ft.dwLowDateTime; ull.HighPart = ft.dwHighDateTime; return static_cast(ull.QuadPart / 10000000ULL - 11644473600ULL); } GHC_INLINE void timeToFILETIME(time_t t, FILETIME& ft) { LONGLONG ll; ll = Int32x32To64(t, 10000000) + 116444736000000000; ft.dwLowDateTime = static_cast(ll); ft.dwHighDateTime = static_cast(ll >> 32); } template GHC_INLINE uintmax_t hard_links_from_INFO(const INFO* info) { return static_cast(-1); } template <> GHC_INLINE uintmax_t hard_links_from_INFO(const BY_HANDLE_FILE_INFORMATION* info) { return info->nNumberOfLinks; } template GHC_INLINE DWORD reparse_tag_from_INFO(const INFO*) { return 0; } template <> GHC_INLINE DWORD reparse_tag_from_INFO(const WIN32_FIND_DATAW* info) { return info->dwReserved0; } template GHC_INLINE file_status status_from_INFO(const path& p, const INFO* info, std::error_code& ec, uintmax_t* sz = nullptr, time_t* lwt = nullptr) { file_type ft = file_type::unknown; if (sizeof(INFO) == sizeof(WIN32_FIND_DATAW)) { if (detail::reparse_tag_from_INFO(info) == IO_REPARSE_TAG_SYMLINK) { ft = file_type::symlink; } } else { if ((info->dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)) { auto reparseData = detail::getReparseData(p, ec); if (!ec && reparseData && IsReparseTagMicrosoft(reparseData->ReparseTag) && reparseData->ReparseTag == IO_REPARSE_TAG_SYMLINK) { ft = file_type::symlink; } } } if (ft == file_type::unknown) { if ((info->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) { ft = file_type::directory; } else { ft = file_type::regular; } } perms prms = perms::owner_read | perms::group_read | perms::others_read; if (!(info->dwFileAttributes & FILE_ATTRIBUTE_READONLY)) { prms = prms | perms::owner_write | perms::group_write | perms::others_write; } if (has_executable_extension(p)) { prms = prms | perms::owner_exec | perms::group_exec | perms::others_exec; } if (sz) { *sz = static_cast(info->nFileSizeHigh) << (sizeof(info->nFileSizeHigh) * 8) | info->nFileSizeLow; } if (lwt) { *lwt = detail::timeFromFILETIME(info->ftLastWriteTime); } return file_status(ft, prms); } #endif GHC_INLINE bool is_not_found_error(std::error_code& ec) { #ifdef GHC_OS_WINDOWS return ec.value() == ERROR_FILE_NOT_FOUND || ec.value() == ERROR_PATH_NOT_FOUND || ec.value() == ERROR_INVALID_NAME; #else return ec.value() == ENOENT || ec.value() == ENOTDIR; #endif } GHC_INLINE file_status symlink_status_ex(const path& p, std::error_code& ec, uintmax_t* sz = nullptr, uintmax_t* nhl = nullptr, time_t* lwt = nullptr) noexcept { #ifdef GHC_OS_WINDOWS file_status fs; WIN32_FILE_ATTRIBUTE_DATA attr; if (!GetFileAttributesExW(GHC_NATIVEWP(p), GetFileExInfoStandard, &attr)) { ec = detail::make_system_error(); } else { ec.clear(); fs = detail::status_from_INFO(p, &attr, ec, sz, lwt); if (nhl) { *nhl = 0; } } if (detail::is_not_found_error(ec)) { return file_status(file_type::not_found); } return ec ? file_status(file_type::none) : fs; #else (void)sz; (void)nhl; (void)lwt; struct ::stat fs; auto result = ::lstat(p.c_str(), &fs); if (result == 0) { ec.clear(); file_status f_s = detail::file_status_from_st_mode(fs.st_mode); return f_s; } ec = detail::make_system_error(); if (detail::is_not_found_error(ec)) { return file_status(file_type::not_found, perms::unknown); } return file_status(file_type::none); #endif } GHC_INLINE file_status status_ex(const path& p, std::error_code& ec, file_status* sls = nullptr, uintmax_t* sz = nullptr, uintmax_t* nhl = nullptr, time_t* lwt = nullptr, int recurse_count = 0) noexcept { ec.clear(); #ifdef GHC_OS_WINDOWS if (recurse_count > 16) { ec = detail::make_system_error(0x2A9 /*ERROR_STOPPED_ON_SYMLINK*/); return file_status(file_type::unknown); } WIN32_FILE_ATTRIBUTE_DATA attr; if (!::GetFileAttributesExW(GHC_NATIVEWP(p), GetFileExInfoStandard, &attr)) { ec = detail::make_system_error(); } else if (attr.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) { auto reparseData = detail::getReparseData(p, ec); if (!ec && reparseData && IsReparseTagMicrosoft(reparseData->ReparseTag) && reparseData->ReparseTag == IO_REPARSE_TAG_SYMLINK) { path target = resolveSymlink(p, ec); file_status result; if (!ec && !target.empty()) { if (sls) { *sls = status_from_INFO(p, &attr, ec); } return detail::status_ex(target, ec, nullptr, sz, nhl, lwt, recurse_count + 1); } return file_status(file_type::unknown); } } if (ec) { if (detail::is_not_found_error(ec)) { return file_status(file_type::not_found); } return file_status(file_type::none); } if (nhl) { *nhl = 0; } return detail::status_from_INFO(p, &attr, ec, sz, lwt); #else (void)recurse_count; struct ::stat st; auto result = ::lstat(p.c_str(), &st); if (result == 0) { ec.clear(); file_status fs = detail::file_status_from_st_mode(st.st_mode); if (sls) { *sls = fs; } if (fs.type() == file_type::symlink) { result = ::stat(p.c_str(), &st); if (result == 0) { fs = detail::file_status_from_st_mode(st.st_mode); } else { ec = detail::make_system_error(); if (detail::is_not_found_error(ec)) { return file_status(file_type::not_found, perms::unknown); } return file_status(file_type::none); } } if (sz) { *sz = static_cast(st.st_size); } if (nhl) { *nhl = st.st_nlink; } if (lwt) { *lwt = st.st_mtime; } return fs; } else { ec = detail::make_system_error(); if (detail::is_not_found_error(ec)) { return file_status(file_type::not_found, perms::unknown); } return file_status(file_type::none); } #endif } } // namespace detail GHC_INLINE u8arguments::u8arguments(int& argc, char**& argv) : _argc(argc) , _argv(argv) , _refargc(argc) , _refargv(argv) , _isvalid(false) { #ifdef GHC_OS_WINDOWS LPWSTR* p; p = ::CommandLineToArgvW(::GetCommandLineW(), &argc); _args.reserve(static_cast(argc)); _argp.reserve(static_cast(argc)); for (size_t i = 0; i < static_cast(argc); ++i) { _args.push_back(detail::toUtf8(std::wstring(p[i]))); _argp.push_back((char*)_args[i].data()); } argv = _argp.data(); ::LocalFree(p); _isvalid = true; #else std::setlocale(LC_ALL, ""); #if defined(__ANDROID__) && __ANDROID_API__ < 26 _isvalid = true; #else if (detail::equals_simple_insensitive(::nl_langinfo(CODESET), "UTF-8")) { _isvalid = true; } #endif #endif } //----------------------------------------------------------------------------- // [fs.path.construct] constructors and destructor GHC_INLINE path::path() noexcept {} GHC_INLINE path::path(const path& p) : _path(p._path) #if defined(GHC_OS_WINDOWS) && defined(GHC_WIN_AUTO_PREFIX_LONG_PATH) , _prefixLength(p._prefixLength) #endif { } GHC_INLINE path::path(path&& p) noexcept : _path(std::move(p._path)) #if defined(GHC_OS_WINDOWS) && defined(GHC_WIN_AUTO_PREFIX_LONG_PATH) , _prefixLength(p._prefixLength) #endif { } GHC_INLINE path::path(string_type&& source, format fmt) : _path(std::move(source)) { postprocess_path_with_format(fmt); } #endif // GHC_EXPAND_IMPL #ifdef GHC_WITH_EXCEPTIONS template inline path::path(const Source& source, const std::locale& loc, format fmt) : path(source, fmt) { std::string locName = loc.name(); if (!(locName.length() >= 5 && (locName.substr(locName.length() - 5) == "UTF-8" || locName.substr(locName.length() - 5) == "utf-8"))) { throw filesystem_error("This implementation only supports UTF-8 locales!", path(_path), detail::make_error_code(detail::portable_error::not_supported)); } } template inline path::path(InputIterator first, InputIterator last, const std::locale& loc, format fmt) : path(std::basic_string::value_type>(first, last), fmt) { std::string locName = loc.name(); if (!(locName.length() >= 5 && (locName.substr(locName.length() - 5) == "UTF-8" || locName.substr(locName.length() - 5) == "utf-8"))) { throw filesystem_error("This implementation only supports UTF-8 locales!", path(_path), detail::make_error_code(detail::portable_error::not_supported)); } } #endif #ifdef GHC_EXPAND_IMPL GHC_INLINE path::~path() {} //----------------------------------------------------------------------------- // [fs.path.assign] assignments GHC_INLINE path& path::operator=(const path& p) { _path = p._path; #if defined(GHC_OS_WINDOWS) && defined(GHC_WIN_AUTO_PREFIX_LONG_PATH) _prefixLength = p._prefixLength; #endif return *this; } GHC_INLINE path& path::operator=(path&& p) noexcept { _path = std::move(p._path); #if defined(GHC_OS_WINDOWS) && defined(GHC_WIN_AUTO_PREFIX_LONG_PATH) _prefixLength = p._prefixLength; #endif return *this; } GHC_INLINE path& path::operator=(path::string_type&& source) { return assign(source); } GHC_INLINE path& path::assign(path::string_type&& source) { _path = std::move(source); postprocess_path_with_format(native_format); return *this; } #endif // GHC_EXPAND_IMPL template inline path& path::operator=(const Source& source) { return assign(source); } template inline path& path::assign(const Source& source) { #ifdef GHC_USE_WCHAR_T _path.assign(detail::toWChar(source)); #else _path.assign(detail::toUtf8(source)); #endif postprocess_path_with_format(native_format); return *this; } template <> inline path& path::assign(const path& source) { _path = source._path; #if defined(GHC_OS_WINDOWS) && defined(GHC_WIN_AUTO_PREFIX_LONG_PATH) _prefixLength = source._prefixLength; #endif return *this; } template inline path& path::assign(InputIterator first, InputIterator last) { _path.assign(first, last); postprocess_path_with_format(native_format); return *this; } #ifdef GHC_EXPAND_IMPL //----------------------------------------------------------------------------- // [fs.path.append] appends GHC_INLINE path& path::operator/=(const path& p) { if (p.empty()) { // was: if ((!has_root_directory() && is_absolute()) || has_filename()) if (!_path.empty() && _path[_path.length() - 1] != preferred_separator && _path[_path.length() - 1] != ':') { _path += preferred_separator; } return *this; } if ((p.is_absolute() && (_path != root_name()._path || p._path != "/")) || (p.has_root_name() && p.root_name() != root_name())) { assign(p); return *this; } if (p.has_root_directory()) { assign(root_name()); } else if ((!has_root_directory() && is_absolute()) || has_filename()) { _path += preferred_separator; } auto iter = p.begin(); bool first = true; if (p.has_root_name()) { ++iter; } while (iter != p.end()) { if (!first && !(!_path.empty() && _path[_path.length() - 1] == preferred_separator)) { _path += preferred_separator; } first = false; _path += (*iter++).native(); } check_long_path(); return *this; } GHC_INLINE void path::append_name(const value_type* name) { if (_path.empty()) { this->operator/=(path(name)); } else { if (_path.back() != path::preferred_separator) { _path.push_back(path::preferred_separator); } _path += name; check_long_path(); } } #endif // GHC_EXPAND_IMPL template inline path& path::operator/=(const Source& source) { return append(source); } template inline path& path::append(const Source& source) { return this->operator/=(path(source)); } template <> inline path& path::append(const path& p) { return this->operator/=(p); } template inline path& path::append(InputIterator first, InputIterator last) { std::basic_string::value_type> part(first, last); return append(part); } #ifdef GHC_EXPAND_IMPL //----------------------------------------------------------------------------- // [fs.path.concat] concatenation GHC_INLINE path& path::operator+=(const path& x) { return concat(x._path); } GHC_INLINE path& path::operator+=(const string_type& x) { return concat(x); } #ifdef GHC_WITH_STRING_VIEW GHC_INLINE path& path::operator+=(basic_string_view x) { return concat(x); } #endif GHC_INLINE path& path::operator+=(const value_type* x) { #ifdef GHC_WITH_STRING_VIEW basic_string_view part(x); #else string_type part(x); #endif return concat(part); } GHC_INLINE path& path::operator+=(value_type x) { #ifdef GHC_OS_WINDOWS if (x == generic_separator) { x = preferred_separator; } #endif if (_path.empty() || _path.back() != preferred_separator) { _path += x; } check_long_path(); return *this; } #endif // GHC_EXPAND_IMPL template inline path::path_from_string& path::operator+=(const Source& x) { return concat(x); } template inline path::path_type_EcharT& path::operator+=(EcharT x) { #ifdef GHC_WITH_STRING_VIEW basic_string_view part(&x, 1); #else std::basic_string part(1, x); #endif concat(part); return *this; } template inline path& path::concat(const Source& x) { path p(x); _path += p._path; postprocess_path_with_format(native_format); return *this; } template inline path& path::concat(InputIterator first, InputIterator last) { _path.append(first, last); postprocess_path_with_format(native_format); return *this; } #ifdef GHC_EXPAND_IMPL //----------------------------------------------------------------------------- // [fs.path.modifiers] modifiers GHC_INLINE void path::clear() noexcept { _path.clear(); #if defined(GHC_OS_WINDOWS) && defined(GHC_WIN_AUTO_PREFIX_LONG_PATH) _prefixLength = 0; #endif } GHC_INLINE path& path::make_preferred() { // as this filesystem implementation only uses generic_format // internally, this must be a no-op return *this; } GHC_INLINE path& path::remove_filename() { if (has_filename()) { _path.erase(_path.size() - filename()._path.size()); } return *this; } GHC_INLINE path& path::replace_filename(const path& replacement) { remove_filename(); return append(replacement); } GHC_INLINE path& path::replace_extension(const path& replacement) { if (has_extension()) { _path.erase(_path.size() - extension()._path.size()); } if (!replacement.empty() && replacement._path[0] != '.') { _path += '.'; } return concat(replacement); } GHC_INLINE void path::swap(path& rhs) noexcept { _path.swap(rhs._path); #if defined(GHC_OS_WINDOWS) && defined(GHC_WIN_AUTO_PREFIX_LONG_PATH) std::swap(_prefixLength, rhs._prefixLength); #endif } //----------------------------------------------------------------------------- // [fs.path.native.obs] native format observers GHC_INLINE const path::string_type& path::native() const noexcept { return _path; } GHC_INLINE const path::value_type* path::c_str() const noexcept { return native().c_str(); } GHC_INLINE path::operator path::string_type() const { return native(); } #endif // GHC_EXPAND_IMPL template inline std::basic_string path::string(const Allocator& a) const { #ifdef GHC_USE_WCHAR_T return detail::fromWChar>(_path, a); #else return detail::fromUtf8>(_path, a); #endif } #ifdef GHC_EXPAND_IMPL GHC_INLINE std::string path::string() const { #ifdef GHC_USE_WCHAR_T return detail::toUtf8(native()); #else return native(); #endif } GHC_INLINE std::wstring path::wstring() const { #ifdef GHC_USE_WCHAR_T return native(); #else return detail::fromUtf8(native()); #endif } #if defined(__cpp_lib_char8_t) && !defined(GHC_FILESYSTEM_ENFORCE_CPP17_API) GHC_INLINE std::u8string path::u8string() const { #ifdef GHC_USE_WCHAR_T return std::u8string(reinterpret_cast(detail::toUtf8(native()).c_str())); #else return std::u8string(reinterpret_cast(c_str())); #endif } #else GHC_INLINE std::string path::u8string() const { #ifdef GHC_USE_WCHAR_T return detail::toUtf8(native()); #else return native(); #endif } #endif GHC_INLINE std::u16string path::u16string() const { // TODO: optimize return detail::fromUtf8(string()); } GHC_INLINE std::u32string path::u32string() const { // TODO: optimize return detail::fromUtf8(string()); } #endif // GHC_EXPAND_IMPL //----------------------------------------------------------------------------- // [fs.path.generic.obs] generic format observers template inline std::basic_string path::generic_string(const Allocator& a) const { #ifdef GHC_OS_WINDOWS #ifdef GHC_USE_WCHAR_T auto result = detail::fromWChar, path::string_type>(_path, a); #else auto result = detail::fromUtf8>(_path, a); #endif for (auto& c : result) { if (c == preferred_separator) { c = generic_separator; } } return result; #else return detail::fromUtf8>(_path, a); #endif } #ifdef GHC_EXPAND_IMPL GHC_INLINE std::string path::generic_string() const { #ifdef GHC_OS_WINDOWS return generic_string(); #else return _path; #endif } GHC_INLINE std::wstring path::generic_wstring() const { #ifdef GHC_OS_WINDOWS return generic_string(); #else return detail::fromUtf8(_path); #endif } // namespace filesystem #if defined(__cpp_lib_char8_t) && !defined(GHC_FILESYSTEM_ENFORCE_CPP17_API) GHC_INLINE std::u8string path::generic_u8string() const { #ifdef GHC_OS_WINDOWS return generic_string(); #else return std::u8string(reinterpret_cast(_path.c_str())); #endif } #else GHC_INLINE std::string path::generic_u8string() const { #ifdef GHC_OS_WINDOWS return generic_string(); #else return _path; #endif } #endif GHC_INLINE std::u16string path::generic_u16string() const { #ifdef GHC_OS_WINDOWS return generic_string(); #else return detail::fromUtf8(_path); #endif } GHC_INLINE std::u32string path::generic_u32string() const { #ifdef GHC_OS_WINDOWS return generic_string(); #else return detail::fromUtf8(_path); #endif } //----------------------------------------------------------------------------- // [fs.path.compare] compare GHC_INLINE int path::compare(const path& p) const noexcept { #ifdef LWG_2936_BEHAVIOUR auto rnl1 = root_name_length(); auto rnl2 = p.root_name_length(); #ifdef GHC_OS_WINDOWS auto rnc = detail::compare_simple_insensitive(_path.c_str(), rnl1, p._path.c_str(), rnl2); #else auto rnc = _path.compare(0, rnl1, p._path, 0, (std::min(rnl1, rnl2))); #endif if (rnc) { return rnc; } bool hrd1 = has_root_directory(), hrd2 = p.has_root_directory(); if (hrd1 != hrd2) { return hrd1 ? 1 : -1; } if (hrd1) { ++rnl1; ++rnl2; } auto iter1 = _path.begin() + static_cast(rnl1); auto iter2 = p._path.begin() + static_cast(rnl2); while (iter1 != _path.end() && iter2 != p._path.end() && *iter1 == *iter2) { ++iter1; ++iter2; } if (iter1 == _path.end()) { return iter2 == p._path.end() ? 0 : -1; } if (iter2 == p._path.end()) { return 1; } if (*iter1 == preferred_separator) { return -1; } if (*iter2 == preferred_separator) { return 1; } return *iter1 < *iter2 ? -1 : 1; #else // LWG_2936_BEHAVIOUR #ifdef GHC_OS_WINDOWS auto rnl1 = root_name_length(); auto rnl2 = p.root_name_length(); auto rnc = detail::compare_simple_insensitive(_path.c_str(), rnl1, p._path.c_str(), rnl2); if (rnc) { return rnc; } return _path.compare(rnl1, std::string::npos, p._path, rnl2, std::string::npos); #else return _path.compare(p._path); #endif #endif } GHC_INLINE int path::compare(const string_type& s) const { return compare(path(s)); } #ifdef GHC_WITH_STRING_VIEW GHC_INLINE int path::compare(basic_string_view s) const { return compare(path(s)); } #endif GHC_INLINE int path::compare(const value_type* s) const { return compare(path(s)); } //----------------------------------------------------------------------------- // [fs.path.decompose] decomposition #ifdef GHC_OS_WINDOWS GHC_INLINE void path::handle_prefixes() { #if defined(GHC_WIN_AUTO_PREFIX_LONG_PATH) _prefixLength = 0; if (_path.length() >= 6 && _path[2] == '?' && std::toupper(static_cast(_path[4])) >= 'A' && std::toupper(static_cast(_path[4])) <= 'Z' && _path[5] == ':') { if (detail::startsWith(_path, impl_string_type(GHC_PLATFORM_LITERAL("\\\\?\\"))) || detail::startsWith(_path, impl_string_type(GHC_PLATFORM_LITERAL("\\??\\")))) { _prefixLength = 4; } } #endif // GHC_WIN_AUTO_PREFIX_LONG_PATH } #endif GHC_INLINE path::string_type::size_type path::root_name_length() const noexcept { #ifdef GHC_OS_WINDOWS if (_path.length() >= _prefixLength + 2 && std::toupper(static_cast(_path[_prefixLength])) >= 'A' && std::toupper(static_cast(_path[_prefixLength])) <= 'Z' && _path[_prefixLength + 1] == ':') { return 2; } #endif if (_path.length() > _prefixLength + 2 && _path[_prefixLength] == preferred_separator && _path[_prefixLength + 1] == preferred_separator && _path[_prefixLength + 2] != preferred_separator && std::isprint(_path[_prefixLength + 2])) { impl_string_type::size_type pos = _path.find(preferred_separator, _prefixLength + 3); if (pos == impl_string_type::npos) { return _path.length(); } else { return pos; } } return 0; } GHC_INLINE path path::root_name() const { return path(_path.substr(_prefixLength, root_name_length()), native_format); } GHC_INLINE path path::root_directory() const { if (has_root_directory()) { static const path _root_dir(std::string(1, preferred_separator), native_format); return _root_dir; } return path(); } GHC_INLINE path path::root_path() const { return path(root_name().string() + root_directory().string(), native_format); } GHC_INLINE path path::relative_path() const { auto rootPathLen = _prefixLength + root_name_length() + (has_root_directory() ? 1 : 0); return path(_path.substr((std::min)(rootPathLen, _path.length())), generic_format); } GHC_INLINE path path::parent_path() const { auto rootPathLen = _prefixLength + root_name_length() + (has_root_directory() ? 1 : 0); if (rootPathLen < _path.length()) { if (empty()) { return path(); } else { auto piter = end(); auto iter = piter.decrement(_path.end()); if (iter > _path.begin() + static_cast(rootPathLen) && *iter != preferred_separator) { --iter; } return path(_path.begin(), iter, native_format); } } else { return *this; } } GHC_INLINE path path::filename() const { return !has_relative_path() ? path() : path(*--end()); } GHC_INLINE path path::stem() const { impl_string_type fn = filename().native(); if (fn != "." && fn != "..") { impl_string_type::size_type pos = fn.rfind('.'); if (pos != impl_string_type::npos && pos > 0) { return path{fn.substr(0, pos), native_format}; } } return path{fn, native_format}; } GHC_INLINE path path::extension() const { if (has_relative_path()) { auto iter = end(); const auto& fn = *--iter; impl_string_type::size_type pos = fn._path.rfind('.'); if (pos != std::string::npos && pos > 0) { return path(fn._path.substr(pos), native_format); } } return path(); } #ifdef GHC_OS_WINDOWS namespace detail { GHC_INLINE bool has_executable_extension(const path& p) { if (p.has_relative_path()) { auto iter = p.end(); const auto& fn = *--iter; auto pos = fn._path.find_last_of('.'); if (pos == std::string::npos || pos == 0 || fn._path.length() - pos != 3) { return false; } const path::value_type* ext = fn._path.c_str() + pos + 1; if (detail::equals_simple_insensitive(ext, GHC_PLATFORM_LITERAL("exe")) || detail::equals_simple_insensitive(ext, GHC_PLATFORM_LITERAL("cmd")) || detail::equals_simple_insensitive(ext, GHC_PLATFORM_LITERAL("bat")) || detail::equals_simple_insensitive(ext, GHC_PLATFORM_LITERAL("com"))) { return true; } } return false; } } // namespace detail #endif //----------------------------------------------------------------------------- // [fs.path.query] query GHC_INLINE bool path::empty() const noexcept { return _path.empty(); } GHC_INLINE bool path::has_root_name() const { return root_name_length() > 0; } GHC_INLINE bool path::has_root_directory() const { auto rootLen = _prefixLength + root_name_length(); return (_path.length() > rootLen && _path[rootLen] == preferred_separator); } GHC_INLINE bool path::has_root_path() const { return has_root_name() || has_root_directory(); } GHC_INLINE bool path::has_relative_path() const { auto rootPathLen = _prefixLength + root_name_length() + (has_root_directory() ? 1 : 0); return rootPathLen < _path.length(); } GHC_INLINE bool path::has_parent_path() const { return !parent_path().empty(); } GHC_INLINE bool path::has_filename() const { return has_relative_path() && !filename().empty(); } GHC_INLINE bool path::has_stem() const { return !stem().empty(); } GHC_INLINE bool path::has_extension() const { return !extension().empty(); } GHC_INLINE bool path::is_absolute() const { #ifdef GHC_OS_WINDOWS return has_root_name() && has_root_directory(); #else return has_root_directory(); #endif } GHC_INLINE bool path::is_relative() const { return !is_absolute(); } //----------------------------------------------------------------------------- // [fs.path.gen] generation GHC_INLINE path path::lexically_normal() const { path dest; bool lastDotDot = false; for (string_type s : *this) { if (s == ".") { dest /= ""; continue; } else if (s == ".." && !dest.empty()) { auto root = root_path(); if (dest == root) { continue; } else if (*(--dest.end()) != "..") { if (dest._path.back() == preferred_separator) { dest._path.pop_back(); } dest.remove_filename(); continue; } } if (!(s.empty() && lastDotDot)) { dest /= s; } lastDotDot = s == ".."; } if (dest.empty()) { dest = "."; } return dest; } GHC_INLINE path path::lexically_relative(const path& base) const { if (root_name() != base.root_name() || is_absolute() != base.is_absolute() || (!has_root_directory() && base.has_root_directory())) { return path(); } const_iterator a = begin(), b = base.begin(); while (a != end() && b != base.end() && *a == *b) { ++a; ++b; } if (a == end() && b == base.end()) { return path("."); } int count = 0; for (const auto& element : input_iterator_range(b, base.end())) { if (element != "." && element != "" && element != "..") { ++count; } else if (element == "..") { --count; } } if (count < 0) { return path(); } path result; for (int i = 0; i < count; ++i) { result /= ".."; } for (const auto& element : input_iterator_range(a, end())) { result /= element; } return result; } GHC_INLINE path path::lexically_proximate(const path& base) const { path result = lexically_relative(base); return result.empty() ? *this : result; } //----------------------------------------------------------------------------- // [fs.path.itr] iterators GHC_INLINE path::iterator::iterator() {} GHC_INLINE path::iterator::iterator(const path& p, const impl_string_type::const_iterator& pos) : _first(p._path.begin()) , _last(p._path.end()) , _prefix(_first + static_cast(p._prefixLength)) , _root(p.has_root_directory() ? _first + static_cast(p._prefixLength + p.root_name_length()) : _last) , _iter(pos) { if(pos != _last) { updateCurrent(); } } GHC_INLINE path::impl_string_type::const_iterator path::iterator::increment(const path::impl_string_type::const_iterator& pos) const { path::impl_string_type::const_iterator i = pos; bool fromStart = i == _first || i == _prefix; if (i != _last) { if (fromStart && i == _first && _prefix > _first) { i = _prefix; } else if (*i++ == preferred_separator) { // we can only sit on a slash if it is a network name or a root if (i != _last && *i == preferred_separator) { if (fromStart && !(i + 1 != _last && *(i + 1) == preferred_separator)) { // leadind double slashes detected, treat this and the // following until a slash as one unit i = std::find(++i, _last, preferred_separator); } else { // skip redundant slashes while (i != _last && *i == preferred_separator) { ++i; } } } } else { if (fromStart && i != _last && *i == ':') { ++i; } else { i = std::find(i, _last, preferred_separator); } } } return i; } GHC_INLINE path::impl_string_type::const_iterator path::iterator::decrement(const path::impl_string_type::const_iterator& pos) const { path::impl_string_type::const_iterator i = pos; if (i != _first) { --i; // if this is now the root slash or the trailing slash, we are done, // else check for network name if (i != _root && (pos != _last || *i != preferred_separator)) { #ifdef GHC_OS_WINDOWS static const impl_string_type seps = GHC_PLATFORM_LITERAL("\\:"); i = std::find_first_of(std::reverse_iterator(i), std::reverse_iterator(_first), seps.begin(), seps.end()).base(); if (i > _first && *i == ':') { i++; } #else i = std::find(std::reverse_iterator(i), std::reverse_iterator(_first), preferred_separator).base(); #endif // Now we have to check if this is a network name if (i - _first == 2 && *_first == preferred_separator && *(_first + 1) == preferred_separator) { i -= 2; } } } return i; } GHC_INLINE void path::iterator::updateCurrent() { if ((_iter == _last) || (_iter != _first && _iter != _last && (*_iter == preferred_separator && _iter != _root) && (_iter + 1 == _last))) { _current.clear(); } else { _current.assign(_iter, increment(_iter)); } } GHC_INLINE path::iterator& path::iterator::operator++() { _iter = increment(_iter); while (_iter != _last && // we didn't reach the end _iter != _root && // this is not a root position *_iter == preferred_separator && // we are on a separator (_iter + 1) != _last // the slash is not the last char ) { ++_iter; } updateCurrent(); return *this; } GHC_INLINE path::iterator path::iterator::operator++(int) { path::iterator i{*this}; ++(*this); return i; } GHC_INLINE path::iterator& path::iterator::operator--() { _iter = decrement(_iter); updateCurrent(); return *this; } GHC_INLINE path::iterator path::iterator::operator--(int) { auto i = *this; --(*this); return i; } GHC_INLINE bool path::iterator::operator==(const path::iterator& other) const { return _iter == other._iter; } GHC_INLINE bool path::iterator::operator!=(const path::iterator& other) const { return _iter != other._iter; } GHC_INLINE path::iterator::reference path::iterator::operator*() const { return _current; } GHC_INLINE path::iterator::pointer path::iterator::operator->() const { return &_current; } GHC_INLINE path::iterator path::begin() const { return iterator(*this, _path.begin()); } GHC_INLINE path::iterator path::end() const { return iterator(*this, _path.end()); } //----------------------------------------------------------------------------- // [fs.path.nonmember] path non-member functions GHC_INLINE void swap(path& lhs, path& rhs) noexcept { swap(lhs._path, rhs._path); } GHC_INLINE size_t hash_value(const path& p) noexcept { return std::hash()(p.generic_string()); } #ifdef GHC_HAS_THREEWAY_COMP GHC_INLINE std::strong_ordering operator<=>(const path& lhs, const path& rhs) noexcept { return lhs.compare(rhs) <=> 0; } #endif GHC_INLINE bool operator==(const path& lhs, const path& rhs) noexcept { return lhs.compare(rhs) == 0; } GHC_INLINE bool operator!=(const path& lhs, const path& rhs) noexcept { return !(lhs == rhs); } GHC_INLINE bool operator<(const path& lhs, const path& rhs) noexcept { return lhs.compare(rhs) < 0; } GHC_INLINE bool operator<=(const path& lhs, const path& rhs) noexcept { return lhs.compare(rhs) <= 0; } GHC_INLINE bool operator>(const path& lhs, const path& rhs) noexcept { return lhs.compare(rhs) > 0; } GHC_INLINE bool operator>=(const path& lhs, const path& rhs) noexcept { return lhs.compare(rhs) >= 0; } GHC_INLINE path operator/(const path& lhs, const path& rhs) { path result(lhs); result /= rhs; return result; } #endif // GHC_EXPAND_IMPL //----------------------------------------------------------------------------- // [fs.path.io] path inserter and extractor template inline std::basic_ostream& operator<<(std::basic_ostream& os, const path& p) { os << "\""; auto ps = p.string(); for (auto c : ps) { if (c == '"' || c == '\\') { os << '\\'; } os << c; } os << "\""; return os; } template inline std::basic_istream& operator>>(std::basic_istream& is, path& p) { std::basic_string tmp; charT c; is >> c; if (c == '"') { auto sf = is.flags(); is >> std::noskipws; while (is) { auto c2 = is.get(); if (is) { if (c2 == '\\') { c2 = is.get(); if (is) { tmp += static_cast(c2); } } else if (c2 == '"') { break; } else { tmp += static_cast(c2); } } } if ((sf & std::ios_base::skipws) == std::ios_base::skipws) { is >> std::skipws; } p = path(tmp); } else { is >> tmp; p = path(static_cast(c) + tmp); } return is; } #ifdef GHC_EXPAND_IMPL //----------------------------------------------------------------------------- // [fs.class.filesystem_error] Class filesystem_error GHC_INLINE filesystem_error::filesystem_error(const std::string& what_arg, std::error_code ec) : std::system_error(ec, what_arg) , _what_arg(what_arg) , _ec(ec) { } GHC_INLINE filesystem_error::filesystem_error(const std::string& what_arg, const path& p1, std::error_code ec) : std::system_error(ec, what_arg) , _what_arg(what_arg) , _ec(ec) , _p1(p1) { if (!_p1.empty()) { _what_arg += ": '" + _p1.string() + "'"; } } GHC_INLINE filesystem_error::filesystem_error(const std::string& what_arg, const path& p1, const path& p2, std::error_code ec) : std::system_error(ec, what_arg) , _what_arg(what_arg) , _ec(ec) , _p1(p1) , _p2(p2) { if (!_p1.empty()) { _what_arg += ": '" + _p1.string() + "'"; } if (!_p2.empty()) { _what_arg += ", '" + _p2.string() + "'"; } } GHC_INLINE const path& filesystem_error::path1() const noexcept { return _p1; } GHC_INLINE const path& filesystem_error::path2() const noexcept { return _p2; } GHC_INLINE const char* filesystem_error::what() const noexcept { return _what_arg.c_str(); } //----------------------------------------------------------------------------- // [fs.op.funcs] filesystem operations #ifdef GHC_WITH_EXCEPTIONS GHC_INLINE path absolute(const path& p) { std::error_code ec; path result = absolute(p, ec); if (ec) { throw filesystem_error(detail::systemErrorText(ec.value()), p, ec); } return result; } #endif GHC_INLINE path absolute(const path& p, std::error_code& ec) { ec.clear(); #ifdef GHC_OS_WINDOWS if (p.empty()) { return absolute(current_path(ec), ec) / ""; } ULONG size = ::GetFullPathNameW(GHC_NATIVEWP(p), 0, 0, 0); if (size) { std::vector buf(size, 0); ULONG s2 = GetFullPathNameW(GHC_NATIVEWP(p), size, buf.data(), nullptr); if (s2 && s2 < size) { path result = path(std::wstring(buf.data(), s2)); if (p.filename() == ".") { result /= "."; } return result; } } ec = detail::make_system_error(); return path(); #else path base = current_path(ec); if (!ec) { if (p.empty()) { return base / p; } if (p.has_root_name()) { if (p.has_root_directory()) { return p; } else { return p.root_name() / base.root_directory() / base.relative_path() / p.relative_path(); } } else { if (p.has_root_directory()) { return base.root_name() / p; } else { return base / p; } } } ec = detail::make_system_error(); return path(); #endif } #ifdef GHC_WITH_EXCEPTIONS GHC_INLINE path canonical(const path& p) { std::error_code ec; auto result = canonical(p, ec); if (ec) { throw filesystem_error(detail::systemErrorText(ec.value()), p, ec); } return result; } #endif GHC_INLINE path canonical(const path& p, std::error_code& ec) { if (p.empty()) { ec = detail::make_error_code(detail::portable_error::not_found); return path(); } path work = p.is_absolute() ? p : absolute(p, ec); path result; auto fs = status(work, ec); if (ec) { return path(); } if (fs.type() == file_type::not_found) { ec = detail::make_error_code(detail::portable_error::not_found); return path(); } bool redo; do { auto rootPathLen = work._prefixLength + work.root_name_length() + (work.has_root_directory() ? 1 : 0); redo = false; result.clear(); for (auto pe : work) { if (pe.empty() || pe == ".") { continue; } else if (pe == "..") { result = result.parent_path(); continue; } else if ((result / pe).string().length() <= rootPathLen) { result /= pe; continue; } auto sls = symlink_status(result / pe, ec); if (ec) { return path(); } if (is_symlink(sls)) { redo = true; auto target = read_symlink(result / pe, ec); if (ec) { return path(); } if (target.is_absolute()) { result = target; continue; } else { result /= target; continue; } } else { result /= pe; } } work = result; } while (redo); ec.clear(); return result; } #ifdef GHC_WITH_EXCEPTIONS GHC_INLINE void copy(const path& from, const path& to) { copy(from, to, copy_options::none); } GHC_INLINE void copy(const path& from, const path& to, copy_options options) { std::error_code ec; copy(from, to, options, ec); if (ec) { throw filesystem_error(detail::systemErrorText(ec.value()), from, to, ec); } } #endif GHC_INLINE void copy(const path& from, const path& to, std::error_code& ec) noexcept { copy(from, to, copy_options::none, ec); } GHC_INLINE void copy(const path& from, const path& to, copy_options options, std::error_code& ec) noexcept { std::error_code tec; file_status fs_from, fs_to; ec.clear(); if ((options & (copy_options::skip_symlinks | copy_options::copy_symlinks | copy_options::create_symlinks)) != copy_options::none) { fs_from = symlink_status(from, ec); } else { fs_from = status(from, ec); } if (!exists(fs_from)) { if (!ec) { ec = detail::make_error_code(detail::portable_error::not_found); } return; } if ((options & (copy_options::skip_symlinks | copy_options::create_symlinks)) != copy_options::none) { fs_to = symlink_status(to, tec); } else { fs_to = status(to, tec); } if (is_other(fs_from) || is_other(fs_to) || (is_directory(fs_from) && is_regular_file(fs_to)) || (exists(fs_to) && equivalent(from, to, ec))) { ec = detail::make_error_code(detail::portable_error::invalid_argument); } else if (is_symlink(fs_from)) { if ((options & copy_options::skip_symlinks) == copy_options::none) { if (!exists(fs_to) && (options & copy_options::copy_symlinks) != copy_options::none) { copy_symlink(from, to, ec); } else { ec = detail::make_error_code(detail::portable_error::invalid_argument); } } } else if (is_regular_file(fs_from)) { if ((options & copy_options::directories_only) == copy_options::none) { if ((options & copy_options::create_symlinks) != copy_options::none) { create_symlink(from.is_absolute() ? from : canonical(from, ec), to, ec); } #ifndef GHC_OS_WEB else if ((options & copy_options::create_hard_links) != copy_options::none) { create_hard_link(from, to, ec); } #endif else if (is_directory(fs_to)) { copy_file(from, to / from.filename(), options, ec); } else { copy_file(from, to, options, ec); } } } #ifdef LWG_2682_BEHAVIOUR else if (is_directory(fs_from) && (options & copy_options::create_symlinks) != copy_options::none) { ec = detail::make_error_code(detail::portable_error::is_a_directory); } #endif else if (is_directory(fs_from) && (options == copy_options::none || (options & copy_options::recursive) != copy_options::none)) { if (!exists(fs_to)) { create_directory(to, from, ec); if (ec) { return; } } for (auto iter = directory_iterator(from, ec); iter != directory_iterator(); iter.increment(ec)) { if (!ec) { copy(iter->path(), to / iter->path().filename(), options | static_cast(0x8000), ec); } if (ec) { return; } } } return; } #ifdef GHC_WITH_EXCEPTIONS GHC_INLINE bool copy_file(const path& from, const path& to) { return copy_file(from, to, copy_options::none); } GHC_INLINE bool copy_file(const path& from, const path& to, copy_options option) { std::error_code ec; auto result = copy_file(from, to, option, ec); if (ec) { throw filesystem_error(detail::systemErrorText(ec.value()), from, to, ec); } return result; } #endif GHC_INLINE bool copy_file(const path& from, const path& to, std::error_code& ec) noexcept { return copy_file(from, to, copy_options::none, ec); } GHC_INLINE bool copy_file(const path& from, const path& to, copy_options options, std::error_code& ec) noexcept { std::error_code tecf, tect; auto sf = status(from, tecf); auto st = status(to, tect); bool overwrite = false; ec.clear(); if (!is_regular_file(sf)) { ec = tecf; return false; } if (exists(st) && (!is_regular_file(st) || equivalent(from, to, ec) || (options & (copy_options::skip_existing | copy_options::overwrite_existing | copy_options::update_existing)) == copy_options::none)) { ec = tect ? tect : detail::make_error_code(detail::portable_error::exists); return false; } if (exists(st)) { if ((options & copy_options::update_existing) == copy_options::update_existing) { auto from_time = last_write_time(from, ec); if (ec) { ec = detail::make_system_error(); return false; } auto to_time = last_write_time(to, ec); if (ec) { ec = detail::make_system_error(); return false; } if (from_time <= to_time) { return false; } } overwrite = true; } #ifdef GHC_OS_WINDOWS if (!::CopyFileW(GHC_NATIVEWP(from), GHC_NATIVEWP(to), !overwrite)) { ec = detail::make_system_error(); return false; } return true; #else std::vector buffer(16384, '\0'); int in = -1, out = -1; if ((in = ::open(from.c_str(), O_RDONLY)) < 0) { ec = detail::make_system_error(); return false; } int mode = O_CREAT | O_WRONLY | O_TRUNC; if (!overwrite) { mode |= O_EXCL; } if ((out = ::open(to.c_str(), mode, static_cast(sf.permissions() & perms::all))) < 0) { ec = detail::make_system_error(); ::close(in); return false; } ssize_t br, bw; while ((br = ::read(in, buffer.data(), buffer.size())) > 0) { ssize_t offset = 0; do { if ((bw = ::write(out, buffer.data() + offset, static_cast(br))) > 0) { br -= bw; offset += bw; } else if (bw < 0) { ec = detail::make_system_error(); ::close(in); ::close(out); return false; } } while (br); } ::close(in); ::close(out); return true; #endif } #ifdef GHC_WITH_EXCEPTIONS GHC_INLINE void copy_symlink(const path& existing_symlink, const path& new_symlink) { std::error_code ec; copy_symlink(existing_symlink, new_symlink, ec); if (ec) { throw filesystem_error(detail::systemErrorText(ec.value()), existing_symlink, new_symlink, ec); } } #endif GHC_INLINE void copy_symlink(const path& existing_symlink, const path& new_symlink, std::error_code& ec) noexcept { ec.clear(); auto to = read_symlink(existing_symlink, ec); if (!ec) { if (exists(to, ec) && is_directory(to, ec)) { create_directory_symlink(to, new_symlink, ec); } else { create_symlink(to, new_symlink, ec); } } } #ifdef GHC_WITH_EXCEPTIONS GHC_INLINE bool create_directories(const path& p) { std::error_code ec; auto result = create_directories(p, ec); if (ec) { throw filesystem_error(detail::systemErrorText(ec.value()), p, ec); } return result; } #endif GHC_INLINE bool create_directories(const path& p, std::error_code& ec) noexcept { path current; ec.clear(); bool didCreate = false; auto rootPathLen = p._prefixLength + p.root_name_length() + (p.has_root_directory() ? 1 : 0); current = p.native().substr(0, rootPathLen); path folders(p._path.substr(rootPathLen)); for (path::string_type part : folders) { current /= part; std::error_code tec; auto fs = status(current, tec); if (tec && fs.type() != file_type::not_found) { ec = tec; return false; } if (!exists(fs)) { create_directory(current, ec); if (ec) { std::error_code tmp_ec; if (is_directory(current, tmp_ec)) { ec.clear(); } else { return false; } } didCreate = true; } #ifndef LWG_2935_BEHAVIOUR else if (!is_directory(fs)) { ec = detail::make_error_code(detail::portable_error::exists); return false; } #endif } return didCreate; } #ifdef GHC_WITH_EXCEPTIONS GHC_INLINE bool create_directory(const path& p) { std::error_code ec; auto result = create_directory(p, path(), ec); if (ec) { throw filesystem_error(detail::systemErrorText(ec.value()), p, ec); } return result; } #endif GHC_INLINE bool create_directory(const path& p, std::error_code& ec) noexcept { return create_directory(p, path(), ec); } #ifdef GHC_WITH_EXCEPTIONS GHC_INLINE bool create_directory(const path& p, const path& attributes) { std::error_code ec; auto result = create_directory(p, attributes, ec); if (ec) { throw filesystem_error(detail::systemErrorText(ec.value()), p, ec); } return result; } #endif GHC_INLINE bool create_directory(const path& p, const path& attributes, std::error_code& ec) noexcept { std::error_code tec; ec.clear(); auto fs = status(p, tec); #ifdef LWG_2935_BEHAVIOUR if (status_known(fs) && exists(fs)) { return false; } #else if (status_known(fs) && exists(fs) && is_directory(fs)) { return false; } #endif #ifdef GHC_OS_WINDOWS if (!attributes.empty()) { if (!::CreateDirectoryExW(GHC_NATIVEWP(attributes), GHC_NATIVEWP(p), NULL)) { ec = detail::make_system_error(); return false; } } else if (!::CreateDirectoryW(GHC_NATIVEWP(p), NULL)) { ec = detail::make_system_error(); return false; } #else ::mode_t attribs = static_cast(perms::all); if (!attributes.empty()) { struct ::stat fileStat; if (::stat(attributes.c_str(), &fileStat) != 0) { ec = detail::make_system_error(); return false; } attribs = fileStat.st_mode; } if (::mkdir(p.c_str(), attribs) != 0) { ec = detail::make_system_error(); return false; } #endif return true; } #ifdef GHC_WITH_EXCEPTIONS GHC_INLINE void create_directory_symlink(const path& to, const path& new_symlink) { std::error_code ec; create_directory_symlink(to, new_symlink, ec); if (ec) { throw filesystem_error(detail::systemErrorText(ec.value()), to, new_symlink, ec); } } #endif GHC_INLINE void create_directory_symlink(const path& to, const path& new_symlink, std::error_code& ec) noexcept { detail::create_symlink(to, new_symlink, true, ec); } #ifndef GHC_OS_WEB #ifdef GHC_WITH_EXCEPTIONS GHC_INLINE void create_hard_link(const path& to, const path& new_hard_link) { std::error_code ec; create_hard_link(to, new_hard_link, ec); if (ec) { throw filesystem_error(detail::systemErrorText(ec.value()), to, new_hard_link, ec); } } #endif GHC_INLINE void create_hard_link(const path& to, const path& new_hard_link, std::error_code& ec) noexcept { detail::create_hardlink(to, new_hard_link, ec); } #endif #ifdef GHC_WITH_EXCEPTIONS GHC_INLINE void create_symlink(const path& to, const path& new_symlink) { std::error_code ec; create_symlink(to, new_symlink, ec); if (ec) { throw filesystem_error(detail::systemErrorText(ec.value()), to, new_symlink, ec); } } #endif GHC_INLINE void create_symlink(const path& to, const path& new_symlink, std::error_code& ec) noexcept { detail::create_symlink(to, new_symlink, false, ec); } #ifdef GHC_WITH_EXCEPTIONS GHC_INLINE path current_path() { std::error_code ec; auto result = current_path(ec); if (ec) { throw filesystem_error(detail::systemErrorText(ec.value()), ec); } return result; } #endif GHC_INLINE path current_path(std::error_code& ec) { ec.clear(); #ifdef GHC_OS_WINDOWS DWORD pathlen = ::GetCurrentDirectoryW(0, 0); std::unique_ptr buffer(new wchar_t[size_t(pathlen) + 1]); if (::GetCurrentDirectoryW(pathlen, buffer.get()) == 0) { ec = detail::make_system_error(); return path(); } return path(std::wstring(buffer.get()), path::native_format); #else size_t pathlen = static_cast(std::max(int(::pathconf(".", _PC_PATH_MAX)), int(PATH_MAX))); std::unique_ptr buffer(new char[pathlen + 1]); if (::getcwd(buffer.get(), pathlen) == nullptr) { ec = detail::make_system_error(); return path(); } return path(buffer.get()); #endif } #ifdef GHC_WITH_EXCEPTIONS GHC_INLINE void current_path(const path& p) { std::error_code ec; current_path(p, ec); if (ec) { throw filesystem_error(detail::systemErrorText(ec.value()), p, ec); } } #endif GHC_INLINE void current_path(const path& p, std::error_code& ec) noexcept { ec.clear(); #ifdef GHC_OS_WINDOWS if (!::SetCurrentDirectoryW(GHC_NATIVEWP(p))) { ec = detail::make_system_error(); } #else if (::chdir(p.string().c_str()) == -1) { ec = detail::make_system_error(); } #endif } GHC_INLINE bool exists(file_status s) noexcept { return status_known(s) && s.type() != file_type::not_found; } #ifdef GHC_WITH_EXCEPTIONS GHC_INLINE bool exists(const path& p) { return exists(status(p)); } #endif GHC_INLINE bool exists(const path& p, std::error_code& ec) noexcept { file_status s = status(p, ec); if (status_known(s)) { ec.clear(); } return exists(s); } #ifdef GHC_WITH_EXCEPTIONS GHC_INLINE bool equivalent(const path& p1, const path& p2) { std::error_code ec; bool result = equivalent(p1, p2, ec); if (ec) { throw filesystem_error(detail::systemErrorText(ec.value()), p1, p2, ec); } return result; } #endif GHC_INLINE bool equivalent(const path& p1, const path& p2, std::error_code& ec) noexcept { ec.clear(); #ifdef GHC_OS_WINDOWS std::shared_ptr file1(::CreateFileW(GHC_NATIVEWP(p1), 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, 0, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, 0), CloseHandle); auto e1 = ::GetLastError(); std::shared_ptr file2(::CreateFileW(GHC_NATIVEWP(p2), 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, 0, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, 0), CloseHandle); if (file1.get() == INVALID_HANDLE_VALUE || file2.get() == INVALID_HANDLE_VALUE) { #ifdef LWG_2937_BEHAVIOUR ec = detail::make_system_error(e1 ? e1 : ::GetLastError()); #else if (file1 == file2) { ec = detail::make_system_error(e1 ? e1 : ::GetLastError()); } #endif return false; } BY_HANDLE_FILE_INFORMATION inf1, inf2; if (!::GetFileInformationByHandle(file1.get(), &inf1)) { ec = detail::make_system_error(); return false; } if (!::GetFileInformationByHandle(file2.get(), &inf2)) { ec = detail::make_system_error(); return false; } return inf1.ftLastWriteTime.dwLowDateTime == inf2.ftLastWriteTime.dwLowDateTime && inf1.ftLastWriteTime.dwHighDateTime == inf2.ftLastWriteTime.dwHighDateTime && inf1.nFileIndexHigh == inf2.nFileIndexHigh && inf1.nFileIndexLow == inf2.nFileIndexLow && inf1.nFileSizeHigh == inf2.nFileSizeHigh && inf1.nFileSizeLow == inf2.nFileSizeLow && inf1.dwVolumeSerialNumber == inf2.dwVolumeSerialNumber; #else struct ::stat s1, s2; auto rc1 = ::stat(p1.c_str(), &s1); auto e1 = errno; auto rc2 = ::stat(p2.c_str(), &s2); if (rc1 || rc2) { #ifdef LWG_2937_BEHAVIOUR ec = detail::make_system_error(e1 ? e1 : errno); #else if (rc1 && rc2) { ec = detail::make_system_error(e1 ? e1 : errno); } #endif return false; } return s1.st_dev == s2.st_dev && s1.st_ino == s2.st_ino && s1.st_size == s2.st_size && s1.st_mtime == s2.st_mtime; #endif } #ifdef GHC_WITH_EXCEPTIONS GHC_INLINE uintmax_t file_size(const path& p) { std::error_code ec; auto result = file_size(p, ec); if (ec) { throw filesystem_error(detail::systemErrorText(ec.value()), p, ec); } return result; } #endif GHC_INLINE uintmax_t file_size(const path& p, std::error_code& ec) noexcept { ec.clear(); #ifdef GHC_OS_WINDOWS WIN32_FILE_ATTRIBUTE_DATA attr; if (!GetFileAttributesExW(GHC_NATIVEWP(p), GetFileExInfoStandard, &attr)) { ec = detail::make_system_error(); return static_cast(-1); } return static_cast(attr.nFileSizeHigh) << (sizeof(attr.nFileSizeHigh) * 8) | attr.nFileSizeLow; #else struct ::stat fileStat; if (::stat(p.c_str(), &fileStat) == -1) { ec = detail::make_system_error(); return static_cast(-1); } return static_cast(fileStat.st_size); #endif } #ifndef GHC_OS_WEB #ifdef GHC_WITH_EXCEPTIONS GHC_INLINE uintmax_t hard_link_count(const path& p) { std::error_code ec; auto result = hard_link_count(p, ec); if (ec) { throw filesystem_error(detail::systemErrorText(ec.value()), p, ec); } return result; } #endif GHC_INLINE uintmax_t hard_link_count(const path& p, std::error_code& ec) noexcept { ec.clear(); #ifdef GHC_OS_WINDOWS uintmax_t result = static_cast(-1); std::shared_ptr file(::CreateFileW(GHC_NATIVEWP(p), 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, 0, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, 0), CloseHandle); BY_HANDLE_FILE_INFORMATION inf; if (file.get() == INVALID_HANDLE_VALUE) { ec = detail::make_system_error(); } else { if (!::GetFileInformationByHandle(file.get(), &inf)) { ec = detail::make_system_error(); } else { result = inf.nNumberOfLinks; } } return result; #else uintmax_t result = 0; file_status fs = detail::status_ex(p, ec, nullptr, nullptr, &result, nullptr); if (fs.type() == file_type::not_found) { ec = detail::make_error_code(detail::portable_error::not_found); } return ec ? static_cast(-1) : result; #endif } #endif GHC_INLINE bool is_block_file(file_status s) noexcept { return s.type() == file_type::block; } #ifdef GHC_WITH_EXCEPTIONS GHC_INLINE bool is_block_file(const path& p) { return is_block_file(status(p)); } #endif GHC_INLINE bool is_block_file(const path& p, std::error_code& ec) noexcept { return is_block_file(status(p, ec)); } GHC_INLINE bool is_character_file(file_status s) noexcept { return s.type() == file_type::character; } #ifdef GHC_WITH_EXCEPTIONS GHC_INLINE bool is_character_file(const path& p) { return is_character_file(status(p)); } #endif GHC_INLINE bool is_character_file(const path& p, std::error_code& ec) noexcept { return is_character_file(status(p, ec)); } GHC_INLINE bool is_directory(file_status s) noexcept { return s.type() == file_type::directory; } #ifdef GHC_WITH_EXCEPTIONS GHC_INLINE bool is_directory(const path& p) { return is_directory(status(p)); } #endif GHC_INLINE bool is_directory(const path& p, std::error_code& ec) noexcept { return is_directory(status(p, ec)); } #ifdef GHC_WITH_EXCEPTIONS GHC_INLINE bool is_empty(const path& p) { if (is_directory(p)) { return directory_iterator(p) == directory_iterator(); } else { return file_size(p) == 0; } } #endif GHC_INLINE bool is_empty(const path& p, std::error_code& ec) noexcept { auto fs = status(p, ec); if (ec) { return false; } if (is_directory(fs)) { directory_iterator iter(p, ec); if (ec) { return false; } return iter == directory_iterator(); } else { auto sz = file_size(p, ec); if (ec) { return false; } return sz == 0; } } GHC_INLINE bool is_fifo(file_status s) noexcept { return s.type() == file_type::fifo; } #ifdef GHC_WITH_EXCEPTIONS GHC_INLINE bool is_fifo(const path& p) { return is_fifo(status(p)); } #endif GHC_INLINE bool is_fifo(const path& p, std::error_code& ec) noexcept { return is_fifo(status(p, ec)); } GHC_INLINE bool is_other(file_status s) noexcept { return exists(s) && !is_regular_file(s) && !is_directory(s) && !is_symlink(s); } #ifdef GHC_WITH_EXCEPTIONS GHC_INLINE bool is_other(const path& p) { return is_other(status(p)); } #endif GHC_INLINE bool is_other(const path& p, std::error_code& ec) noexcept { return is_other(status(p, ec)); } GHC_INLINE bool is_regular_file(file_status s) noexcept { return s.type() == file_type::regular; } #ifdef GHC_WITH_EXCEPTIONS GHC_INLINE bool is_regular_file(const path& p) { return is_regular_file(status(p)); } #endif GHC_INLINE bool is_regular_file(const path& p, std::error_code& ec) noexcept { return is_regular_file(status(p, ec)); } GHC_INLINE bool is_socket(file_status s) noexcept { return s.type() == file_type::socket; } #ifdef GHC_WITH_EXCEPTIONS GHC_INLINE bool is_socket(const path& p) { return is_socket(status(p)); } #endif GHC_INLINE bool is_socket(const path& p, std::error_code& ec) noexcept { return is_socket(status(p, ec)); } GHC_INLINE bool is_symlink(file_status s) noexcept { return s.type() == file_type::symlink; } #ifdef GHC_WITH_EXCEPTIONS GHC_INLINE bool is_symlink(const path& p) { return is_symlink(symlink_status(p)); } #endif GHC_INLINE bool is_symlink(const path& p, std::error_code& ec) noexcept { return is_symlink(symlink_status(p, ec)); } #ifdef GHC_WITH_EXCEPTIONS GHC_INLINE file_time_type last_write_time(const path& p) { std::error_code ec; auto result = last_write_time(p, ec); if (ec) { throw filesystem_error(detail::systemErrorText(ec.value()), p, ec); } return result; } #endif GHC_INLINE file_time_type last_write_time(const path& p, std::error_code& ec) noexcept { time_t result = 0; ec.clear(); file_status fs = detail::status_ex(p, ec, nullptr, nullptr, nullptr, &result); return ec ? (file_time_type::min)() : std::chrono::system_clock::from_time_t(result); } #ifdef GHC_WITH_EXCEPTIONS GHC_INLINE void last_write_time(const path& p, file_time_type new_time) { std::error_code ec; last_write_time(p, new_time, ec); if (ec) { throw filesystem_error(detail::systemErrorText(ec.value()), p, ec); } } #endif GHC_INLINE void last_write_time(const path& p, file_time_type new_time, std::error_code& ec) noexcept { ec.clear(); auto d = new_time.time_since_epoch(); #ifdef GHC_OS_WINDOWS std::shared_ptr file(::CreateFileW(GHC_NATIVEWP(p), FILE_WRITE_ATTRIBUTES, FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL), ::CloseHandle); FILETIME ft; auto tt = std::chrono::duration_cast(d).count() * 10 + 116444736000000000; ft.dwLowDateTime = static_cast(tt); ft.dwHighDateTime = static_cast(tt >> 32); if (!::SetFileTime(file.get(), 0, 0, &ft)) { ec = detail::make_system_error(); } #elif defined(GHC_OS_MACOS) #ifdef __MAC_OS_X_VERSION_MIN_REQUIRED #if __MAC_OS_X_VERSION_MIN_REQUIRED < 101300 struct ::stat fs; if (::stat(p.c_str(), &fs) == 0) { struct ::timeval tv[2]; tv[0].tv_sec = fs.st_atimespec.tv_sec; tv[0].tv_usec = static_cast(fs.st_atimespec.tv_nsec / 1000); tv[1].tv_sec = std::chrono::duration_cast(d).count(); tv[1].tv_usec = static_cast(std::chrono::duration_cast(d).count() % 1000000); if (::utimes(p.c_str(), tv) == 0) { return; } } ec = detail::make_system_error(); return; #else struct ::timespec times[2]; times[0].tv_sec = 0; times[0].tv_nsec = UTIME_OMIT; times[1].tv_sec = std::chrono::duration_cast(d).count(); times[1].tv_nsec = 0; // std::chrono::duration_cast(d).count() % 1000000000; if (::utimensat(AT_FDCWD, p.c_str(), times, AT_SYMLINK_NOFOLLOW) != 0) { ec = detail::make_system_error(); } return; #endif #endif #else #ifndef UTIME_OMIT #define UTIME_OMIT ((1l << 30) - 2l) #endif struct ::timespec times[2]; times[0].tv_sec = 0; times[0].tv_nsec = UTIME_OMIT; times[1].tv_sec = static_cast(std::chrono::duration_cast(d).count()); times[1].tv_nsec = static_cast(std::chrono::duration_cast(d).count() % 1000000000); #if defined(__ANDROID_API__) && __ANDROID_API__ < 12 if (syscall(__NR_utimensat, AT_FDCWD, p.c_str(), times, AT_SYMLINK_NOFOLLOW) != 0) { #else if (::utimensat(AT_FDCWD, p.c_str(), times, AT_SYMLINK_NOFOLLOW) != 0) { #endif ec = detail::make_system_error(); } return; #endif } #ifdef GHC_WITH_EXCEPTIONS GHC_INLINE void permissions(const path& p, perms prms, perm_options opts) { std::error_code ec; permissions(p, prms, opts, ec); if (ec) { throw filesystem_error(detail::systemErrorText(ec.value()), p, ec); } } #endif GHC_INLINE void permissions(const path& p, perms prms, std::error_code& ec) noexcept { permissions(p, prms, perm_options::replace, ec); } GHC_INLINE void permissions(const path& p, perms prms, perm_options opts, std::error_code& ec) noexcept { if (static_cast(opts & (perm_options::replace | perm_options::add | perm_options::remove)) == 0) { ec = detail::make_error_code(detail::portable_error::invalid_argument); return; } auto fs = symlink_status(p, ec); if ((opts & perm_options::replace) != perm_options::replace) { if ((opts & perm_options::add) == perm_options::add) { prms = fs.permissions() | prms; } else { prms = fs.permissions() & ~prms; } } #ifdef GHC_OS_WINDOWS #ifdef __GNUC__ auto oldAttr = GetFileAttributesW(GHC_NATIVEWP(p)); if (oldAttr != INVALID_FILE_ATTRIBUTES) { DWORD newAttr = ((prms & perms::owner_write) == perms::owner_write) ? oldAttr & ~(static_cast(FILE_ATTRIBUTE_READONLY)) : oldAttr | FILE_ATTRIBUTE_READONLY; if (oldAttr == newAttr || SetFileAttributesW(GHC_NATIVEWP(p), newAttr)) { return; } } ec = detail::make_system_error(); #else int mode = 0; if ((prms & perms::owner_read) == perms::owner_read) { mode |= _S_IREAD; } if ((prms & perms::owner_write) == perms::owner_write) { mode |= _S_IWRITE; } if (::_wchmod(p.wstring().c_str(), mode) != 0) { ec = detail::make_system_error(); } #endif #else if ((opts & perm_options::nofollow) != perm_options::nofollow) { if (::chmod(p.c_str(), static_cast(prms)) != 0) { ec = detail::make_system_error(); } } #endif } #ifdef GHC_WITH_EXCEPTIONS GHC_INLINE path proximate(const path& p, std::error_code& ec) { auto cp = current_path(ec); if (!ec) { return proximate(p, cp, ec); } return path(); } #endif #ifdef GHC_WITH_EXCEPTIONS GHC_INLINE path proximate(const path& p, const path& base) { return weakly_canonical(p).lexically_proximate(weakly_canonical(base)); } #endif GHC_INLINE path proximate(const path& p, const path& base, std::error_code& ec) { return weakly_canonical(p, ec).lexically_proximate(weakly_canonical(base, ec)); } #ifdef GHC_WITH_EXCEPTIONS GHC_INLINE path read_symlink(const path& p) { std::error_code ec; auto result = read_symlink(p, ec); if (ec) { throw filesystem_error(detail::systemErrorText(ec.value()), p, ec); } return result; } #endif GHC_INLINE path read_symlink(const path& p, std::error_code& ec) { file_status fs = symlink_status(p, ec); if (fs.type() != file_type::symlink) { ec = detail::make_error_code(detail::portable_error::invalid_argument); return path(); } auto result = detail::resolveSymlink(p, ec); return ec ? path() : result; } GHC_INLINE path relative(const path& p, std::error_code& ec) { return relative(p, current_path(ec), ec); } #ifdef GHC_WITH_EXCEPTIONS GHC_INLINE path relative(const path& p, const path& base) { return weakly_canonical(p).lexically_relative(weakly_canonical(base)); } #endif GHC_INLINE path relative(const path& p, const path& base, std::error_code& ec) { return weakly_canonical(p, ec).lexically_relative(weakly_canonical(base, ec)); } #ifdef GHC_WITH_EXCEPTIONS GHC_INLINE bool remove(const path& p) { std::error_code ec; auto result = remove(p, ec); if (ec) { throw filesystem_error(detail::systemErrorText(ec.value()), p, ec); } return result; } #endif GHC_INLINE bool remove(const path& p, std::error_code& ec) noexcept { ec.clear(); #ifdef GHC_OS_WINDOWS #ifdef GHC_USE_WCHAR_T auto cstr = p.c_str(); #else std::wstring np = detail::fromUtf8(p.u8string()); auto cstr = np.c_str(); #endif DWORD attr = GetFileAttributesW(cstr); if (attr == INVALID_FILE_ATTRIBUTES) { auto error = ::GetLastError(); if (error == ERROR_FILE_NOT_FOUND || error == ERROR_PATH_NOT_FOUND) { return false; } ec = detail::make_system_error(error); } else if(attr & FILE_ATTRIBUTE_READONLY) { auto new_attr = attr & ~static_cast(FILE_ATTRIBUTE_READONLY); if(!SetFileAttributesW(cstr, new_attr)) { auto error = ::GetLastError(); ec = detail::make_system_error(error); } } if (!ec) { if (attr & FILE_ATTRIBUTE_DIRECTORY) { if (!RemoveDirectoryW(cstr)) { ec = detail::make_system_error(); } } else { if (!DeleteFileW(cstr)) { ec = detail::make_system_error(); } } } #else if (::remove(p.c_str()) == -1) { auto error = errno; if (error == ENOENT) { return false; } ec = detail::make_system_error(); } #endif return ec ? false : true; } #ifdef GHC_WITH_EXCEPTIONS GHC_INLINE uintmax_t remove_all(const path& p) { std::error_code ec; auto result = remove_all(p, ec); if (ec) { throw filesystem_error(detail::systemErrorText(ec.value()), p, ec); } return result; } #endif GHC_INLINE uintmax_t remove_all(const path& p, std::error_code& ec) noexcept { ec.clear(); uintmax_t count = 0; if (p == "/") { ec = detail::make_error_code(detail::portable_error::not_supported); return static_cast(-1); } std::error_code tec; auto fs = status(p, tec); if (exists(fs) && is_directory(fs)) { for (auto iter = directory_iterator(p, ec); iter != directory_iterator(); iter.increment(ec)) { if (ec && !detail::is_not_found_error(ec)) { break; } bool is_symlink_result = iter->is_symlink(ec); if (ec) return static_cast(-1); if (!is_symlink_result && iter->is_directory(ec)) { count += remove_all(iter->path(), ec); if (ec) { return static_cast(-1); } } else { if (!ec) { remove(iter->path(), ec); } if (ec) { return static_cast(-1); } ++count; } } } if (!ec) { if (remove(p, ec)) { ++count; } } if (ec) { return static_cast(-1); } return count; } #ifdef GHC_WITH_EXCEPTIONS GHC_INLINE void rename(const path& from, const path& to) { std::error_code ec; rename(from, to, ec); if (ec) { throw filesystem_error(detail::systemErrorText(ec.value()), from, to, ec); } } #endif GHC_INLINE void rename(const path& from, const path& to, std::error_code& ec) noexcept { ec.clear(); #ifdef GHC_OS_WINDOWS if (from != to) { if (!MoveFileExW(GHC_NATIVEWP(from), GHC_NATIVEWP(to), (DWORD)MOVEFILE_REPLACE_EXISTING)) { ec = detail::make_system_error(); } } #else if (from != to) { if (::rename(from.c_str(), to.c_str()) != 0) { ec = detail::make_system_error(); } } #endif } #ifdef GHC_WITH_EXCEPTIONS GHC_INLINE void resize_file(const path& p, uintmax_t size) { std::error_code ec; resize_file(p, size, ec); if (ec) { throw filesystem_error(detail::systemErrorText(ec.value()), p, ec); } } #endif GHC_INLINE void resize_file(const path& p, uintmax_t size, std::error_code& ec) noexcept { ec.clear(); #ifdef GHC_OS_WINDOWS LARGE_INTEGER lisize; lisize.QuadPart = static_cast(size); if (lisize.QuadPart < 0) { #ifdef ERROR_FILE_TOO_LARGE ec = detail::make_system_error(ERROR_FILE_TOO_LARGE); #else ec = detail::make_system_error(223); #endif return; } std::shared_ptr file(CreateFileW(GHC_NATIVEWP(p), GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL), CloseHandle); if (file.get() == INVALID_HANDLE_VALUE) { ec = detail::make_system_error(); } else if (SetFilePointerEx(file.get(), lisize, NULL, FILE_BEGIN) == 0 || SetEndOfFile(file.get()) == 0) { ec = detail::make_system_error(); } #else if (::truncate(p.c_str(), static_cast(size)) != 0) { ec = detail::make_system_error(); } #endif } #ifdef GHC_WITH_EXCEPTIONS GHC_INLINE space_info space(const path& p) { std::error_code ec; auto result = space(p, ec); if (ec) { throw filesystem_error(detail::systemErrorText(ec.value()), p, ec); } return result; } #endif GHC_INLINE space_info space(const path& p, std::error_code& ec) noexcept { ec.clear(); #ifdef GHC_OS_WINDOWS ULARGE_INTEGER freeBytesAvailableToCaller = {{ 0, 0 }}; ULARGE_INTEGER totalNumberOfBytes = {{ 0, 0 }}; ULARGE_INTEGER totalNumberOfFreeBytes = {{ 0, 0 }}; if (!GetDiskFreeSpaceExW(GHC_NATIVEWP(p), &freeBytesAvailableToCaller, &totalNumberOfBytes, &totalNumberOfFreeBytes)) { ec = detail::make_system_error(); return {static_cast(-1), static_cast(-1), static_cast(-1)}; } return {static_cast(totalNumberOfBytes.QuadPart), static_cast(totalNumberOfFreeBytes.QuadPart), static_cast(freeBytesAvailableToCaller.QuadPart)}; #else struct ::statvfs sfs; if (::statvfs(p.c_str(), &sfs) != 0) { ec = detail::make_system_error(); return {static_cast(-1), static_cast(-1), static_cast(-1)}; } return {static_cast(sfs.f_blocks) * static_cast(sfs.f_frsize), static_cast(sfs.f_bfree) * static_cast(sfs.f_frsize), static_cast(sfs.f_bavail) * static_cast(sfs.f_frsize)}; #endif } #ifdef GHC_WITH_EXCEPTIONS GHC_INLINE file_status status(const path& p) { std::error_code ec; auto result = status(p, ec); if (result.type() == file_type::none) { throw filesystem_error(detail::systemErrorText(ec.value()), p, ec); } return result; } #endif GHC_INLINE file_status status(const path& p, std::error_code& ec) noexcept { return detail::status_ex(p, ec); } GHC_INLINE bool status_known(file_status s) noexcept { return s.type() != file_type::none; } #ifdef GHC_WITH_EXCEPTIONS GHC_INLINE file_status symlink_status(const path& p) { std::error_code ec; auto result = symlink_status(p, ec); if (result.type() == file_type::none) { throw filesystem_error(detail::systemErrorText(ec.value()), ec); } return result; } #endif GHC_INLINE file_status symlink_status(const path& p, std::error_code& ec) noexcept { return detail::symlink_status_ex(p, ec); } #ifdef GHC_WITH_EXCEPTIONS GHC_INLINE path temp_directory_path() { std::error_code ec; path result = temp_directory_path(ec); if (ec) { throw filesystem_error(detail::systemErrorText(ec.value()), ec); } return result; } #endif GHC_INLINE path temp_directory_path(std::error_code& ec) noexcept { ec.clear(); #ifdef GHC_OS_WINDOWS wchar_t buffer[512]; auto rc = GetTempPathW(511, buffer); if (!rc || rc > 511) { ec = detail::make_system_error(); return path(); } return path(std::wstring(buffer)); #else static const char* temp_vars[] = {"TMPDIR", "TMP", "TEMP", "TEMPDIR", nullptr}; const char* temp_path = nullptr; for (auto temp_name = temp_vars; *temp_name != nullptr; ++temp_name) { temp_path = std::getenv(*temp_name); if (temp_path) { return path(temp_path); } } return path("/tmp"); #endif } #ifdef GHC_WITH_EXCEPTIONS GHC_INLINE path weakly_canonical(const path& p) { std::error_code ec; auto result = weakly_canonical(p, ec); if (ec) { throw filesystem_error(detail::systemErrorText(ec.value()), p, ec); } return result; } #endif GHC_INLINE path weakly_canonical(const path& p, std::error_code& ec) noexcept { path result; ec.clear(); bool scan = true; for (auto pe : p) { if (scan) { std::error_code tec; if (exists(result / pe, tec)) { result /= pe; } else { if (ec) { return path(); } scan = false; if (!result.empty()) { result = canonical(result, ec) / pe; if (ec) { break; } } else { result /= pe; } } } else { result /= pe; } } if (scan) { if (!result.empty()) { result = canonical(result, ec); } } return ec ? path() : result.lexically_normal(); } //----------------------------------------------------------------------------- // [fs.class.file_status] class file_status // [fs.file_status.cons] constructors and destructor GHC_INLINE file_status::file_status() noexcept : file_status(file_type::none) { } GHC_INLINE file_status::file_status(file_type ft, perms prms) noexcept : _type(ft) , _perms(prms) { } GHC_INLINE file_status::file_status(const file_status& other) noexcept : _type(other._type) , _perms(other._perms) { } GHC_INLINE file_status::file_status(file_status&& other) noexcept : _type(other._type) , _perms(other._perms) { } GHC_INLINE file_status::~file_status() {} // assignments: GHC_INLINE file_status& file_status::operator=(const file_status& rhs) noexcept { _type = rhs._type; _perms = rhs._perms; return *this; } GHC_INLINE file_status& file_status::operator=(file_status&& rhs) noexcept { _type = rhs._type; _perms = rhs._perms; return *this; } // [fs.file_status.mods] modifiers GHC_INLINE void file_status::type(file_type ft) noexcept { _type = ft; } GHC_INLINE void file_status::permissions(perms prms) noexcept { _perms = prms; } // [fs.file_status.obs] observers GHC_INLINE file_type file_status::type() const noexcept { return _type; } GHC_INLINE perms file_status::permissions() const noexcept { return _perms; } //----------------------------------------------------------------------------- // [fs.class.directory_entry] class directory_entry // [fs.dir.entry.cons] constructors and destructor // directory_entry::directory_entry() noexcept = default; // directory_entry::directory_entry(const directory_entry&) = default; // directory_entry::directory_entry(directory_entry&&) noexcept = default; #ifdef GHC_WITH_EXCEPTIONS GHC_INLINE directory_entry::directory_entry(const filesystem::path& p) : _path(p) , _file_size(static_cast(-1)) #ifndef GHC_OS_WINDOWS , _hard_link_count(static_cast(-1)) #endif , _last_write_time(0) { refresh(); } #endif GHC_INLINE directory_entry::directory_entry(const filesystem::path& p, std::error_code& ec) : _path(p) , _file_size(static_cast(-1)) #ifndef GHC_OS_WINDOWS , _hard_link_count(static_cast(-1)) #endif , _last_write_time(0) { refresh(ec); } GHC_INLINE directory_entry::~directory_entry() {} // assignments: // directory_entry& directory_entry::operator=(const directory_entry&) = default; // directory_entry& directory_entry::operator=(directory_entry&&) noexcept = default; // [fs.dir.entry.mods] directory_entry modifiers #ifdef GHC_WITH_EXCEPTIONS GHC_INLINE void directory_entry::assign(const filesystem::path& p) { _path = p; refresh(); } #endif GHC_INLINE void directory_entry::assign(const filesystem::path& p, std::error_code& ec) { _path = p; refresh(ec); } #ifdef GHC_WITH_EXCEPTIONS GHC_INLINE void directory_entry::replace_filename(const filesystem::path& p) { _path.replace_filename(p); refresh(); } #endif GHC_INLINE void directory_entry::replace_filename(const filesystem::path& p, std::error_code& ec) { _path.replace_filename(p); refresh(ec); } #ifdef GHC_WITH_EXCEPTIONS GHC_INLINE void directory_entry::refresh() { std::error_code ec; refresh(ec); if (ec) { throw filesystem_error(detail::systemErrorText(ec.value()), _path, ec); } } #endif GHC_INLINE void directory_entry::refresh(std::error_code& ec) noexcept { #ifdef GHC_OS_WINDOWS _status = detail::status_ex(_path, ec, &_symlink_status, &_file_size, nullptr, &_last_write_time); #else _status = detail::status_ex(_path, ec, &_symlink_status, &_file_size, &_hard_link_count, &_last_write_time); #endif } // [fs.dir.entry.obs] directory_entry observers GHC_INLINE const filesystem::path& directory_entry::path() const noexcept { return _path; } GHC_INLINE directory_entry::operator const filesystem::path&() const noexcept { return _path; } #ifdef GHC_WITH_EXCEPTIONS GHC_INLINE file_type directory_entry::status_file_type() const { return _status.type() != file_type::none ? _status.type() : filesystem::status(path()).type(); } #endif GHC_INLINE file_type directory_entry::status_file_type(std::error_code& ec) const noexcept { if(_status.type() != file_type::none) { ec.clear(); return _status.type(); } return filesystem::status(path(), ec).type(); } #ifdef GHC_WITH_EXCEPTIONS GHC_INLINE bool directory_entry::exists() const { return status_file_type() != file_type::not_found; } #endif GHC_INLINE bool directory_entry::exists(std::error_code& ec) const noexcept { return status_file_type(ec) != file_type::not_found; } #ifdef GHC_WITH_EXCEPTIONS GHC_INLINE bool directory_entry::is_block_file() const { return status_file_type() == file_type::block; } #endif GHC_INLINE bool directory_entry::is_block_file(std::error_code& ec) const noexcept { return status_file_type(ec) == file_type::block; } #ifdef GHC_WITH_EXCEPTIONS GHC_INLINE bool directory_entry::is_character_file() const { return status_file_type() == file_type::character; } #endif GHC_INLINE bool directory_entry::is_character_file(std::error_code& ec) const noexcept { return status_file_type(ec) == file_type::character; } #ifdef GHC_WITH_EXCEPTIONS GHC_INLINE bool directory_entry::is_directory() const { return status_file_type() == file_type::directory; } #endif GHC_INLINE bool directory_entry::is_directory(std::error_code& ec) const noexcept { return status_file_type(ec) == file_type::directory; } #ifdef GHC_WITH_EXCEPTIONS GHC_INLINE bool directory_entry::is_fifo() const { return status_file_type() == file_type::fifo; } #endif GHC_INLINE bool directory_entry::is_fifo(std::error_code& ec) const noexcept { return status_file_type(ec) == file_type::fifo; } #ifdef GHC_WITH_EXCEPTIONS GHC_INLINE bool directory_entry::is_other() const { auto ft = status_file_type(); return ft != file_type::none && ft != file_type::not_found && ft != file_type::regular && ft != file_type::directory && !is_symlink(); } #endif GHC_INLINE bool directory_entry::is_other(std::error_code& ec) const noexcept { auto ft = status_file_type(ec); bool other = ft != file_type::none && ft != file_type::not_found && ft != file_type::regular && ft != file_type::directory && !is_symlink(ec); return !ec && other; } #ifdef GHC_WITH_EXCEPTIONS GHC_INLINE bool directory_entry::is_regular_file() const { return status_file_type() == file_type::regular; } #endif GHC_INLINE bool directory_entry::is_regular_file(std::error_code& ec) const noexcept { return status_file_type(ec) == file_type::regular; } #ifdef GHC_WITH_EXCEPTIONS GHC_INLINE bool directory_entry::is_socket() const { return status_file_type() == file_type::socket; } #endif GHC_INLINE bool directory_entry::is_socket(std::error_code& ec) const noexcept { return status_file_type(ec) == file_type::socket; } #ifdef GHC_WITH_EXCEPTIONS GHC_INLINE bool directory_entry::is_symlink() const { return _symlink_status.type() != file_type::none ? _symlink_status.type() == file_type::symlink : filesystem::is_symlink(symlink_status()); } #endif GHC_INLINE bool directory_entry::is_symlink(std::error_code& ec) const noexcept { if(_symlink_status.type() != file_type::none) { ec.clear(); return _symlink_status.type() == file_type::symlink; } return filesystem::is_symlink(symlink_status(ec)); } #ifdef GHC_WITH_EXCEPTIONS GHC_INLINE uintmax_t directory_entry::file_size() const { if (_file_size != static_cast(-1)) { return _file_size; } return filesystem::file_size(path()); } #endif GHC_INLINE uintmax_t directory_entry::file_size(std::error_code& ec) const noexcept { if (_file_size != static_cast(-1)) { ec.clear(); return _file_size; } return filesystem::file_size(path(), ec); } #ifndef GHC_OS_WEB #ifdef GHC_WITH_EXCEPTIONS GHC_INLINE uintmax_t directory_entry::hard_link_count() const { #ifndef GHC_OS_WINDOWS if (_hard_link_count != static_cast(-1)) { return _hard_link_count; } #endif return filesystem::hard_link_count(path()); } #endif GHC_INLINE uintmax_t directory_entry::hard_link_count(std::error_code& ec) const noexcept { #ifndef GHC_OS_WINDOWS if (_hard_link_count != static_cast(-1)) { ec.clear(); return _hard_link_count; } #endif return filesystem::hard_link_count(path(), ec); } #endif #ifdef GHC_WITH_EXCEPTIONS GHC_INLINE file_time_type directory_entry::last_write_time() const { if (_last_write_time != 0) { return std::chrono::system_clock::from_time_t(_last_write_time); } return filesystem::last_write_time(path()); } #endif GHC_INLINE file_time_type directory_entry::last_write_time(std::error_code& ec) const noexcept { if (_last_write_time != 0) { ec.clear(); return std::chrono::system_clock::from_time_t(_last_write_time); } return filesystem::last_write_time(path(), ec); } #ifdef GHC_WITH_EXCEPTIONS GHC_INLINE file_status directory_entry::status() const { if (_status.type() != file_type::none && _status.permissions() != perms::unknown) { return _status; } return filesystem::status(path()); } #endif GHC_INLINE file_status directory_entry::status(std::error_code& ec) const noexcept { if (_status.type() != file_type::none && _status.permissions() != perms::unknown) { ec.clear(); return _status; } return filesystem::status(path(), ec); } #ifdef GHC_WITH_EXCEPTIONS GHC_INLINE file_status directory_entry::symlink_status() const { if (_symlink_status.type() != file_type::none && _symlink_status.permissions() != perms::unknown) { return _symlink_status; } return filesystem::symlink_status(path()); } #endif GHC_INLINE file_status directory_entry::symlink_status(std::error_code& ec) const noexcept { if (_symlink_status.type() != file_type::none && _symlink_status.permissions() != perms::unknown) { ec.clear(); return _symlink_status; } return filesystem::symlink_status(path(), ec); } #ifdef GHC_HAS_THREEWAY_COMP GHC_INLINE std::strong_ordering directory_entry::operator<=>(const directory_entry& rhs) const noexcept { return _path <=> rhs._path; } #endif GHC_INLINE bool directory_entry::operator<(const directory_entry& rhs) const noexcept { return _path < rhs._path; } GHC_INLINE bool directory_entry::operator==(const directory_entry& rhs) const noexcept { return _path == rhs._path; } GHC_INLINE bool directory_entry::operator!=(const directory_entry& rhs) const noexcept { return _path != rhs._path; } GHC_INLINE bool directory_entry::operator<=(const directory_entry& rhs) const noexcept { return _path <= rhs._path; } GHC_INLINE bool directory_entry::operator>(const directory_entry& rhs) const noexcept { return _path > rhs._path; } GHC_INLINE bool directory_entry::operator>=(const directory_entry& rhs) const noexcept { return _path >= rhs._path; } //----------------------------------------------------------------------------- // [fs.class.directory_iterator] class directory_iterator #ifdef GHC_OS_WINDOWS class directory_iterator::impl { public: impl(const path& p, directory_options options) : _base(p) , _options(options) , _dirHandle(INVALID_HANDLE_VALUE) { if (!_base.empty()) { ZeroMemory(&_findData, sizeof(WIN32_FIND_DATAW)); if ((_dirHandle = FindFirstFileW(GHC_NATIVEWP((_base / "*")), &_findData)) != INVALID_HANDLE_VALUE) { if (std::wstring(_findData.cFileName) == L"." || std::wstring(_findData.cFileName) == L"..") { increment(_ec); } else { _dir_entry._path = _base / std::wstring(_findData.cFileName); copyToDirEntry(_ec); } } else { auto error = ::GetLastError(); _base = filesystem::path(); if (error != ERROR_ACCESS_DENIED || (options & directory_options::skip_permission_denied) == directory_options::none) { _ec = detail::make_system_error(); } } } } impl(const impl& other) = delete; ~impl() { if (_dirHandle != INVALID_HANDLE_VALUE) { FindClose(_dirHandle); _dirHandle = INVALID_HANDLE_VALUE; } } void increment(std::error_code& ec) { if (_dirHandle != INVALID_HANDLE_VALUE) { do { if (FindNextFileW(_dirHandle, &_findData)) { _dir_entry._path = _base; #ifdef GHC_USE_WCHAR_T _dir_entry._path.append_name(_findData.cFileName); #else #ifdef GHC_RAISE_UNICODE_ERRORS try { _dir_entry._path.append_name(detail::toUtf8(_findData.cFileName).c_str()); } catch (filesystem_error& fe) { ec = fe.code(); return; } #else _dir_entry._path.append_name(detail::toUtf8(_findData.cFileName).c_str()); #endif #endif copyToDirEntry(ec); } else { auto err = ::GetLastError(); if (err != ERROR_NO_MORE_FILES) { _ec = ec = detail::make_system_error(err); } FindClose(_dirHandle); _dirHandle = INVALID_HANDLE_VALUE; _dir_entry._path.clear(); break; } } while (std::wstring(_findData.cFileName) == L"." || std::wstring(_findData.cFileName) == L".."); } else { ec = _ec; } } void copyToDirEntry(std::error_code& ec) { if (_findData.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) { _dir_entry._status = detail::status_ex(_dir_entry._path, ec, &_dir_entry._symlink_status, &_dir_entry._file_size, nullptr, &_dir_entry._last_write_time); } else { _dir_entry._status = detail::status_from_INFO(_dir_entry._path, &_findData, ec, &_dir_entry._file_size, &_dir_entry._last_write_time); _dir_entry._symlink_status = _dir_entry._status; } if (ec) { if (_dir_entry._status.type() != file_type::none && _dir_entry._symlink_status.type() != file_type::none) { ec.clear(); } else { _dir_entry._file_size = static_cast(-1); _dir_entry._last_write_time = 0; } } } path _base; directory_options _options; WIN32_FIND_DATAW _findData; HANDLE _dirHandle; directory_entry _dir_entry; std::error_code _ec; }; #else // POSIX implementation class directory_iterator::impl { public: impl(const path& path, directory_options options) : _base(path) , _options(options) , _dir(nullptr) , _entry(nullptr) { if (!path.empty()) { _dir = ::opendir(path.native().c_str()); if (!_dir) { auto error = errno; _base = filesystem::path(); if ((error != EACCES && error != EPERM) || (options & directory_options::skip_permission_denied) == directory_options::none) { _ec = detail::make_system_error(); } } else { increment(_ec); } } } impl(const impl& other) = delete; ~impl() { if (_dir) { ::closedir(_dir); } } void increment(std::error_code& ec) { if (_dir) { bool skip; do { skip = false; errno = 0; _entry = ::readdir(_dir); if (_entry) { _dir_entry._path = _base; _dir_entry._path.append_name(_entry->d_name); copyToDirEntry(); if (ec && (ec.value() == EACCES || ec.value() == EPERM) && (_options & directory_options::skip_permission_denied) == directory_options::skip_permission_denied) { ec.clear(); skip = true; } } else { ::closedir(_dir); _dir = nullptr; _dir_entry._path.clear(); if (errno) { ec = detail::make_system_error(); } break; } } while (skip || std::strcmp(_entry->d_name, ".") == 0 || std::strcmp(_entry->d_name, "..") == 0); } } void copyToDirEntry() { #ifdef GHC_NO_DIRENT_D_TYPE _dir_entry._symlink_status = file_status(); _dir_entry._status = file_status(); #else _dir_entry._symlink_status.permissions(perms::unknown); switch(_entry->d_type) { case DT_BLK: _dir_entry._symlink_status.type(file_type::block); break; case DT_CHR: _dir_entry._symlink_status.type(file_type::character); break; case DT_DIR: _dir_entry._symlink_status.type(file_type::directory); break; case DT_FIFO: _dir_entry._symlink_status.type(file_type::fifo); break; case DT_LNK: _dir_entry._symlink_status.type(file_type::symlink); break; case DT_REG: _dir_entry._symlink_status.type(file_type::regular); break; case DT_SOCK: _dir_entry._symlink_status.type(file_type::socket); break; case DT_UNKNOWN: _dir_entry._symlink_status.type(file_type::none); break; default: _dir_entry._symlink_status.type(file_type::unknown); break; } if (_entry->d_type != DT_LNK) { _dir_entry._status = _dir_entry._symlink_status; } else { _dir_entry._status.type(file_type::none); _dir_entry._status.permissions(perms::unknown); } #endif _dir_entry._file_size = static_cast(-1); _dir_entry._hard_link_count = static_cast(-1); _dir_entry._last_write_time = 0; } path _base; directory_options _options; DIR* _dir; struct ::dirent* _entry; directory_entry _dir_entry; std::error_code _ec; }; #endif // [fs.dir.itr.members] member functions GHC_INLINE directory_iterator::directory_iterator() noexcept : _impl(new impl(path(), directory_options::none)) { } #ifdef GHC_WITH_EXCEPTIONS GHC_INLINE directory_iterator::directory_iterator(const path& p) : _impl(new impl(p, directory_options::none)) { if (_impl->_ec) { throw filesystem_error(detail::systemErrorText(_impl->_ec.value()), p, _impl->_ec); } _impl->_ec.clear(); } GHC_INLINE directory_iterator::directory_iterator(const path& p, directory_options options) : _impl(new impl(p, options)) { if (_impl->_ec) { throw filesystem_error(detail::systemErrorText(_impl->_ec.value()), p, _impl->_ec); } } #endif GHC_INLINE directory_iterator::directory_iterator(const path& p, std::error_code& ec) noexcept : _impl(new impl(p, directory_options::none)) { if (_impl->_ec) { ec = _impl->_ec; } } GHC_INLINE directory_iterator::directory_iterator(const path& p, directory_options options, std::error_code& ec) noexcept : _impl(new impl(p, options)) { if (_impl->_ec) { ec = _impl->_ec; } } GHC_INLINE directory_iterator::directory_iterator(const directory_iterator& rhs) : _impl(rhs._impl) { } GHC_INLINE directory_iterator::directory_iterator(directory_iterator&& rhs) noexcept : _impl(std::move(rhs._impl)) { } GHC_INLINE directory_iterator::~directory_iterator() {} GHC_INLINE directory_iterator& directory_iterator::operator=(const directory_iterator& rhs) { _impl = rhs._impl; return *this; } GHC_INLINE directory_iterator& directory_iterator::operator=(directory_iterator&& rhs) noexcept { _impl = std::move(rhs._impl); return *this; } GHC_INLINE const directory_entry& directory_iterator::operator*() const { return _impl->_dir_entry; } GHC_INLINE const directory_entry* directory_iterator::operator->() const { return &_impl->_dir_entry; } #ifdef GHC_WITH_EXCEPTIONS GHC_INLINE directory_iterator& directory_iterator::operator++() { std::error_code ec; _impl->increment(ec); if (ec) { throw filesystem_error(detail::systemErrorText(ec.value()), _impl->_dir_entry._path, ec); } return *this; } #endif GHC_INLINE directory_iterator& directory_iterator::increment(std::error_code& ec) noexcept { _impl->increment(ec); return *this; } GHC_INLINE bool directory_iterator::operator==(const directory_iterator& rhs) const { return _impl->_dir_entry._path == rhs._impl->_dir_entry._path; } GHC_INLINE bool directory_iterator::operator!=(const directory_iterator& rhs) const { return _impl->_dir_entry._path != rhs._impl->_dir_entry._path; } // [fs.dir.itr.nonmembers] directory_iterator non-member functions GHC_INLINE directory_iterator begin(directory_iterator iter) noexcept { return iter; } GHC_INLINE directory_iterator end(const directory_iterator&) noexcept { return directory_iterator(); } //----------------------------------------------------------------------------- // [fs.class.rec.dir.itr] class recursive_directory_iterator GHC_INLINE recursive_directory_iterator::recursive_directory_iterator() noexcept : _impl(new recursive_directory_iterator_impl(directory_options::none, true)) { _impl->_dir_iter_stack.push(directory_iterator()); } #ifdef GHC_WITH_EXCEPTIONS GHC_INLINE recursive_directory_iterator::recursive_directory_iterator(const path& p) : _impl(new recursive_directory_iterator_impl(directory_options::none, true)) { _impl->_dir_iter_stack.push(directory_iterator(p)); } GHC_INLINE recursive_directory_iterator::recursive_directory_iterator(const path& p, directory_options options) : _impl(new recursive_directory_iterator_impl(options, true)) { _impl->_dir_iter_stack.push(directory_iterator(p, options)); } #endif GHC_INLINE recursive_directory_iterator::recursive_directory_iterator(const path& p, directory_options options, std::error_code& ec) noexcept : _impl(new recursive_directory_iterator_impl(options, true)) { _impl->_dir_iter_stack.push(directory_iterator(p, options, ec)); } GHC_INLINE recursive_directory_iterator::recursive_directory_iterator(const path& p, std::error_code& ec) noexcept : _impl(new recursive_directory_iterator_impl(directory_options::none, true)) { _impl->_dir_iter_stack.push(directory_iterator(p, ec)); } GHC_INLINE recursive_directory_iterator::recursive_directory_iterator(const recursive_directory_iterator& rhs) : _impl(rhs._impl) { } GHC_INLINE recursive_directory_iterator::recursive_directory_iterator(recursive_directory_iterator&& rhs) noexcept : _impl(std::move(rhs._impl)) { } GHC_INLINE recursive_directory_iterator::~recursive_directory_iterator() {} // [fs.rec.dir.itr.members] observers GHC_INLINE directory_options recursive_directory_iterator::options() const { return _impl->_options; } GHC_INLINE int recursive_directory_iterator::depth() const { return static_cast(_impl->_dir_iter_stack.size() - 1); } GHC_INLINE bool recursive_directory_iterator::recursion_pending() const { return _impl->_recursion_pending; } GHC_INLINE const directory_entry& recursive_directory_iterator::operator*() const { return *(_impl->_dir_iter_stack.top()); } GHC_INLINE const directory_entry* recursive_directory_iterator::operator->() const { return &(*(_impl->_dir_iter_stack.top())); } // [fs.rec.dir.itr.members] modifiers recursive_directory_iterator& GHC_INLINE recursive_directory_iterator& recursive_directory_iterator::operator=(const recursive_directory_iterator& rhs) { _impl = rhs._impl; return *this; } GHC_INLINE recursive_directory_iterator& recursive_directory_iterator::operator=(recursive_directory_iterator&& rhs) noexcept { _impl = std::move(rhs._impl); return *this; } #ifdef GHC_WITH_EXCEPTIONS GHC_INLINE recursive_directory_iterator& recursive_directory_iterator::operator++() { std::error_code ec; increment(ec); if (ec) { throw filesystem_error(detail::systemErrorText(ec.value()), _impl->_dir_iter_stack.empty() ? path() : _impl->_dir_iter_stack.top()->path(), ec); } return *this; } #endif GHC_INLINE recursive_directory_iterator& recursive_directory_iterator::increment(std::error_code& ec) noexcept { bool isSymLink = (*this)->is_symlink(ec); bool isDir = !ec && (*this)->is_directory(ec); if(isSymLink && detail::is_not_found_error(ec)) { ec.clear(); } if(!ec) { if (recursion_pending() && isDir && (!isSymLink || (options() & directory_options::follow_directory_symlink) != directory_options::none)) { _impl->_dir_iter_stack.push(directory_iterator((*this)->path(), _impl->_options, ec)); } else { _impl->_dir_iter_stack.top().increment(ec); } if (!ec) { while (depth() && _impl->_dir_iter_stack.top() == directory_iterator()) { _impl->_dir_iter_stack.pop(); _impl->_dir_iter_stack.top().increment(ec); } } else if (!_impl->_dir_iter_stack.empty()) { _impl->_dir_iter_stack.pop(); } _impl->_recursion_pending = true; } return *this; } #ifdef GHC_WITH_EXCEPTIONS GHC_INLINE void recursive_directory_iterator::pop() { std::error_code ec; pop(ec); if (ec) { throw filesystem_error(detail::systemErrorText(ec.value()), _impl->_dir_iter_stack.empty() ? path() : _impl->_dir_iter_stack.top()->path(), ec); } } #endif GHC_INLINE void recursive_directory_iterator::pop(std::error_code& ec) { if (depth() == 0) { *this = recursive_directory_iterator(); } else { do { _impl->_dir_iter_stack.pop(); _impl->_dir_iter_stack.top().increment(ec); } while (depth() && _impl->_dir_iter_stack.top() == directory_iterator()); } } GHC_INLINE void recursive_directory_iterator::disable_recursion_pending() { _impl->_recursion_pending = false; } // other members as required by [input.iterators] GHC_INLINE bool recursive_directory_iterator::operator==(const recursive_directory_iterator& rhs) const { return _impl->_dir_iter_stack.top() == rhs._impl->_dir_iter_stack.top(); } GHC_INLINE bool recursive_directory_iterator::operator!=(const recursive_directory_iterator& rhs) const { return _impl->_dir_iter_stack.top() != rhs._impl->_dir_iter_stack.top(); } // [fs.rec.dir.itr.nonmembers] directory_iterator non-member functions GHC_INLINE recursive_directory_iterator begin(recursive_directory_iterator iter) noexcept { return iter; } GHC_INLINE recursive_directory_iterator end(const recursive_directory_iterator&) noexcept { return recursive_directory_iterator(); } #endif // GHC_EXPAND_IMPL } // namespace filesystem } // namespace ghc // cleanup some macros #undef GHC_INLINE #undef GHC_EXPAND_IMPL #endif // GHC_FILESYSTEM_H mergerfs-2.33.5/src/policy_eplfs.hpp0000644000000000000000000000314414225522122016103 0ustar rootroot/* ISC License Copyright (c) 2020, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include "policy.hpp" namespace Policy { namespace EPLFS { class Action final : public Policy::ActionImpl { public: Action() : Policy::ActionImpl("eplfs") {} public: int operator()(const Branches::CPtr&,const char*,StrVec*) const final; }; class Create final : public Policy::CreateImpl { public: Create() : Policy::CreateImpl("eplfs") {} public: int operator()(const Branches::CPtr&,const char*,StrVec*) const final; bool path_preserving(void) const final { return true; } }; class Search final : public Policy::SearchImpl { public: Search() : Policy::SearchImpl("eplfs") {} public: int operator()(const Branches::CPtr&,const char*,StrVec*) const final; }; } } mergerfs-2.33.5/src/fs_flistxattr.hpp0000644000000000000000000000216214225522122016306 0ustar rootroot/* ISC License Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include "errno.hpp" #include "xattr.hpp" #include namespace fs { static inline int flistxattr(const int fd_, char *list_, const size_t size_) { #ifdef USE_XATTR return ::flistxattr(fd_,list_,size_); #else return (errno=ENOTSUP,-1); #endif } } mergerfs-2.33.5/src/fs_fallocate_unsupported.icpp0000644000000000000000000000175014225522122020652 0ustar rootroot/* Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "errno.hpp" namespace fs { int fallocate(const int fd_, const int mode_, const off_t offset_, const off_t len_) { return (errno=EOPNOTSUPP,-1); } } mergerfs-2.33.5/src/fuse_lock.cpp0000644000000000000000000000176114225522122015363 0ustar rootroot/* ISC License Copyright (c) 2021, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "errno.hpp" #include "fuse.h" namespace FUSE { int lock(const fuse_file_info_t *ffi, int cmd, struct flock *flock) { return -ENOSYS; } } mergerfs-2.33.5/src/locked_fixed_mem_pool.hpp0000644000000000000000000000271014225522122017720 0ustar rootroot/* ISC License Copyright (c) 2020, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include "fixed_mem_pool.hpp" #include template class LockedFixedMemPool { public: LockedFixedMemPool() { pthread_mutex_init(&_mutex,NULL); } ~LockedFixedMemPool() { pthread_mutex_destroy(&_mutex); } public: void* alloc(void) { void *mem; pthread_mutex_lock(&_mutex); mem = _fmp.alloc(); pthread_mutex_unlock(&_mutex); return mem; } void free(void *mem_) { pthread_mutex_lock(&_mutex); _fmp.free(mem_); pthread_mutex_unlock(&_mutex); } uint64_t size(void) { return _fmp.size(); } private: FixedMemPool _fmp; pthread_mutex_t _mutex; }; mergerfs-2.33.5/src/fuse_write.hpp0000644000000000000000000000223714225522122015571 0ustar rootroot/* Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include "fuse.h" namespace FUSE { int write(const fuse_file_info_t *ffi, const char *buf, size_t count, off_t offset); int write_null(const fuse_file_info_t *ffi, const char *buf, size_t count, off_t offset); } mergerfs-2.33.5/src/fuse_release.hpp0000644000000000000000000000157014225522122016056 0ustar rootroot/* Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include "fuse.h" namespace FUSE { int release(const fuse_file_info_t *ffi); } mergerfs-2.33.5/src/config_statfsignore.cpp0000644000000000000000000000261414225522122017444 0ustar rootroot/* ISC License Copyright (c) 2020, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "config_statfsignore.hpp" #include "ef.hpp" #include "errno.hpp" template<> std::string StatFSIgnore::to_string() const { switch(_data) { case StatFSIgnore::ENUM::NONE: return "none"; case StatFSIgnore::ENUM::RO: return "ro"; case StatFSIgnore::ENUM::NC: return "nc"; } return "invalid"; } template<> int StatFSIgnore::from_string(const std::string &s_) { if(s_ == "none") _data = StatFSIgnore::ENUM::NONE; ef(s_ == "ro") _data = StatFSIgnore::ENUM::RO; ef(s_ == "nc") _data = StatFSIgnore::ENUM::NC; else return -EINVAL; return 0; } mergerfs-2.33.5/src/config_cachefiles.cpp0000644000000000000000000000321314225522122017016 0ustar rootroot/* ISC License Copyright (c) 2020, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "config_cachefiles.hpp" #include "ef.hpp" #include "errno.hpp" template<> std::string CacheFiles::to_string() const { switch(_data) { case CacheFiles::ENUM::LIBFUSE: return "libfuse"; case CacheFiles::ENUM::OFF: return "off"; case CacheFiles::ENUM::PARTIAL: return "partial"; case CacheFiles::ENUM::FULL: return "full"; case CacheFiles::ENUM::AUTO_FULL: return "auto-full"; } return "invalid"; } template<> int CacheFiles::from_string(const std::string &s_) { if(s_ == "libfuse") _data = CacheFiles::ENUM::LIBFUSE; ef(s_ == "off") _data = CacheFiles::ENUM::OFF; ef(s_ == "partial") _data = CacheFiles::ENUM::PARTIAL; ef(s_ == "full") _data = CacheFiles::ENUM::FULL; ef(s_ == "auto-full") _data = CacheFiles::ENUM::AUTO_FULL; else return -EINVAL; return 0; } mergerfs-2.33.5/src/fs_fstatat.hpp0000644000000000000000000000274114225522122015553 0ustar rootroot/* ISC License Copyright (c) 2019, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include #include #include #include namespace fs { static inline int fstatat(const int dirfd_, const char *pathname_, struct stat *statbuf_, const int flags_) { return ::fstatat(dirfd_, pathname_, statbuf_, flags_); } static inline int fstatat_nofollow(const int dirfd_, const char *pathname_, struct stat *statbuf_) { return fs::fstatat(dirfd_, pathname_, statbuf_, AT_SYMLINK_NOFOLLOW); } } mergerfs-2.33.5/src/fs_clonepath.cpp0000644000000000000000000001004314225522122016047 0ustar rootroot/* Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "errno.h" #include "fs_attr.hpp" #include "fs_clonepath.hpp" #include "fs_lchown.hpp" #include "fs_lstat.hpp" #include "fs_lutimens.hpp" #include "fs_mkdir.hpp" #include "fs_path.hpp" #include "fs_xattr.hpp" #include "ugid.hpp" #include using std::string; namespace l { static bool ignorable_error(const int err_) { switch(err_) { case ENOTTY: case ENOTSUP: #if ENOTSUP != EOPNOTSUPP case EOPNOTSUPP: #endif return true; } return false; } } namespace fs { /* Attempts to clone a path. The directories which already exist are left alone. The new directories have metadata set to match the original if possible. Optionally ignore errors on metadata copies. */ int clonepath(const string &fromsrc_, const string &tosrc_, const char *relative_, const bool return_metadata_errors_) { int rv; struct stat st; string topath; string frompath; string dirname; if((relative_ == NULL) || (relative_[0] == '\0')) return 0; dirname = fs::path::dirname(relative_); if(dirname != "/") { rv = fs::clonepath(fromsrc_,tosrc_,dirname,return_metadata_errors_); if(rv == -1) return -1; } frompath = fs::path::make(fromsrc_,relative_); rv = fs::lstat(frompath,&st); if(rv == -1) return -1; else if(!S_ISDIR(st.st_mode)) return (errno=ENOTDIR,-1); topath = fs::path::make(tosrc_,relative_); rv = fs::mkdir(topath,st.st_mode); if(rv == -1) { if(errno == EEXIST) return 0; else return -1; } // it may not support it... it's fine... rv = fs::attr::copy(frompath,topath); if(return_metadata_errors_ && (rv == -1) && !l::ignorable_error(errno)) return -1; // it may not support it... it's fine... rv = fs::xattr::copy(frompath,topath); if(return_metadata_errors_ && (rv == -1) && !l::ignorable_error(errno)) return -1; rv = fs::lchown_check_on_error(topath,st); if(return_metadata_errors_ && (rv == -1)) return -1; rv = fs::lutimens(topath,st); if(return_metadata_errors_ && (rv == -1)) return -1; return 0; } int clonepath(const string &from_, const string &to_, const string &relative_, const bool return_metadata_errors_) { return fs::clonepath(from_, to_, relative_.c_str(), return_metadata_errors_); } int clonepath_as_root(const string &from_, const string &to_, const char *relative_, const bool return_metadata_errors_) { if((relative_ == NULL) || (relative_[0] == '\0')) return 0; if(from_ == to_) return 0; { const ugid::SetRootGuard ugidGuard; return fs::clonepath(from_,to_,relative_,return_metadata_errors_); } } int clonepath_as_root(const string &from_, const string &to_, const string &relative_, const bool return_metadata_errors_) { return fs::clonepath_as_root(from_,to_,relative_.c_str(),return_metadata_errors_); } } mergerfs-2.33.5/src/tofrom_string.hpp0000644000000000000000000000170614225522122016311 0ustar rootroot/* ISC License Copyright (c) 2019, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include class ToFromString { public: virtual std::string to_string() const = 0; virtual int from_string(const std::string &) = 0; }; mergerfs-2.33.5/src/fs_link.hpp0000644000000000000000000000203514225522122015036 0ustar rootroot/* ISC License Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include #include namespace fs { static inline int link(const std::string &oldpath_, const std::string &newpath_) { return ::link(oldpath_.c_str(), newpath_.c_str()); } } mergerfs-2.33.5/src/fuse_rmdir.hpp0000644000000000000000000000153414225522122015553 0ustar rootroot/* Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once namespace FUSE { int rmdir(const char *fusepath); } mergerfs-2.33.5/src/fs_dirfd.hpp0000644000000000000000000000167714225522122015204 0ustar rootroot/* ISC License Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include #include namespace fs { static inline int dirfd(DIR *dirp_) { return ::dirfd(dirp_); } } mergerfs-2.33.5/src/fs_fgetxattr.hpp0000644000000000000000000000310214225522122016105 0ustar rootroot/* ISC License Copyright (c) 2020, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include "errno.hpp" #include "xattr.hpp" #include #include namespace fs { static inline int fgetxattr(const int fd_, const char *attrname_, void *value_, const size_t size_) { #ifdef USE_XATTR return ::fgetxattr(fd_, attrname_, value_, size_); #else return (errno=ENOTSUP,-1); #endif } static inline int fgetxattr(const int fd_, const std::string &attrname_, void *value_, const size_t size_) { return fs::fgetxattr(fd_, attrname_.c_str(), value_, size_); } } mergerfs-2.33.5/src/ugid_rwlock.icpp0000644000000000000000000000245714225522122016076 0ustar rootroot/* Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include namespace ugid { uid_t currentuid; gid_t currentgid; pthread_rwlock_t rwlock; void init() { pthread_rwlockattr_t attr; pthread_rwlockattr_init(&attr); # if defined PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP pthread_rwlockattr_setkind_np(&attr,PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP); # endif pthread_rwlock_init(&rwlock,&attr); currentuid = ::geteuid(); currentgid = ::getegid(); } } mergerfs-2.33.5/src/statvfs_util.hpp0000644000000000000000000000234414225522122016143 0ustar rootroot/* Copyright (c) 2019, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include #include #include namespace StatVFS { static inline bool readonly(const struct statvfs &st_) { return (st_.f_flag & ST_RDONLY); } static inline int64_t spaceavail(const struct statvfs &st_) { return (st_.f_frsize * st_.f_bavail); } static inline int64_t spaceused(const struct statvfs &st_) { return (st_.f_frsize * (st_.f_blocks - st_.f_bavail)); } } mergerfs-2.33.5/src/policy_epmfs.cpp0000644000000000000000000001100014225522122016065 0ustar rootroot/* Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "errno.hpp" #include "fs_exists.hpp" #include "fs_info.hpp" #include "fs_path.hpp" #include "fs_statvfs_cache.hpp" #include "policy.hpp" #include "policy_epmfs.hpp" #include "policy_error.hpp" #include #include using std::string; namespace epmfs { static int create(const Branches::CPtr &branches_, const char *fusepath_, StrVec *paths_) { int rv; int error; uint64_t epmfs; fs::info_t info; const string *basepath; error = ENOENT; epmfs = std::numeric_limits::min(); basepath = NULL; for(const auto &branch : *branches_) { if(branch.ro_or_nc()) error_and_continue(error,EROFS); if(!fs::exists(branch.path,fusepath_)) error_and_continue(error,ENOENT); rv = fs::info(branch.path,&info); if(rv == -1) error_and_continue(error,ENOENT); if(info.readonly) error_and_continue(error,EROFS); if(info.spaceavail < branch.minfreespace()) error_and_continue(error,ENOSPC); if(info.spaceavail < epmfs) continue; epmfs = info.spaceavail; basepath = &branch.path; } if(basepath == NULL) return (errno=error,-1); paths_->push_back(*basepath); return 0; } static int action(const Branches::CPtr &branches_, const char *fusepath_, StrVec *paths_) { int rv; int error; uint64_t epmfs; fs::info_t info; const string *basepath; error = ENOENT; epmfs = std::numeric_limits::min(); basepath = NULL; for(const auto &branch : *branches_) { if(branch.ro()) error_and_continue(error,EROFS); if(!fs::exists(branch.path,fusepath_)) error_and_continue(error,ENOENT); rv = fs::info(branch.path,&info); if(rv == -1) error_and_continue(error,ENOENT); if(info.readonly) error_and_continue(error,EROFS); if(info.spaceavail < epmfs) continue; epmfs = info.spaceavail; basepath = &branch.path; } if(basepath == NULL) return (errno=error,-1); paths_->push_back(*basepath); return 0; } static int search(const Branches::CPtr &branches_, const char *fusepath_, StrVec *paths_) { int rv; uint64_t epmfs; uint64_t spaceavail; const string *basepath; epmfs = 0; basepath = NULL; for(const auto &branch : *branches_) { if(!fs::exists(branch.path,fusepath_)) continue; rv = fs::statvfs_cache_spaceavail(branch.path,&spaceavail); if(rv == -1) continue; if(spaceavail < epmfs) continue; epmfs = spaceavail; basepath = &branch.path; } if(basepath == NULL) return (errno=ENOENT,-1); paths_->push_back(*basepath); return 0; } } int Policy::EPMFS::Action::operator()(const Branches::CPtr &branches_, const char *fusepath_, StrVec *paths_) const { return ::epmfs::action(branches_,fusepath_,paths_); } int Policy::EPMFS::Create::operator()(const Branches::CPtr &branches_, const char *fusepath_, StrVec *paths_) const { return ::epmfs::create(branches_,fusepath_,paths_); } int Policy::EPMFS::Search::operator()(const Branches::CPtr &branches_, const char *fusepath_, StrVec *paths_) const { return ::epmfs::search(branches_,fusepath_,paths_); } mergerfs-2.33.5/src/fs_copydata.cpp0000644000000000000000000000275014225522122015704 0ustar rootroot/* ISC License Copyright (c) 2020, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "fs_copydata_copy_file_range.hpp" #include "fs_copydata_readwrite.hpp" #include "fs_fadvise.hpp" #include "fs_ficlone.hpp" #include "fs_ftruncate.hpp" #include namespace fs { int copydata(const int src_fd_, const int dst_fd_, const size_t count_) { int rv; rv = fs::ftruncate(dst_fd_,count_); if(rv == -1) return -1; rv = fs::ficlone(src_fd_,dst_fd_); if(rv != -1) return rv; fs::fadvise_willneed(src_fd_,0,count_); fs::fadvise_sequential(src_fd_,0,count_); rv = fs::copydata_copy_file_range(src_fd_,dst_fd_); if(rv != -1) return rv; return fs::copydata_readwrite(src_fd_,dst_fd_); } } mergerfs-2.33.5/src/fs_sendfile_unsupported.icpp0000644000000000000000000000174114225522122020511 0ustar rootroot/* Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "errno.hpp" #include namespace fs { ssize_t sendfile(const int fdin, const int fdout, const size_t count) { return (errno=EINVAL,-1); } } mergerfs-2.33.5/src/policy_mspmfs.cpp0000644000000000000000000000664514225522122016303 0ustar rootroot/* Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "errno.hpp" #include "fs_exists.hpp" #include "fs_info.hpp" #include "fs_path.hpp" #include "fs_statvfs_cache.hpp" #include "policies.hpp" #include "policy.hpp" #include "policy_error.hpp" #include "policy_mspmfs.hpp" #include #include #include using std::string; using std::vector; namespace mspmfs { static const string* create_1(const Branches::CPtr &branches_, const string &fusepath_, int *err_) { int rv; uint64_t mfs; fs::info_t info; const string *basepath; basepath = NULL; mfs = std::numeric_limits::min(); for(const auto &branch : *branches_) { if(branch.ro_or_nc()) error_and_continue(*err_,EROFS); if(!fs::exists(branch.path,fusepath_)) error_and_continue(*err_,ENOENT); rv = fs::info(branch.path,&info); if(rv == -1) error_and_continue(*err_,ENOENT); if(info.readonly) error_and_continue(*err_,EROFS); if(info.spaceavail < branch.minfreespace()) error_and_continue(*err_,ENOSPC); if(info.spaceavail < mfs) continue; mfs = info.spaceavail; basepath = &branch.path; } return basepath; } static int create(const Branches::CPtr &branches_, const char *fusepath_, StrVec *paths_) { int error; string fusepath; const string *basepath; error = ENOENT; fusepath = fusepath_; for(;;) { basepath = mspmfs::create_1(branches_,fusepath,&error); if(basepath) break; if(fusepath == "/") break; fusepath = fs::path::dirname(fusepath); } if(basepath == NULL) return (errno=error,-1); paths_->push_back(*basepath); return 0; } } int Policy::MSPMFS::Action::operator()(const Branches::CPtr &branches_, const char *fusepath_, StrVec *paths_) const { return Policies::Action::epmfs(branches_,fusepath_,paths_); } int Policy::MSPMFS::Create::operator()(const Branches::CPtr &branches_, const char *fusepath_, StrVec *paths_) const { return ::mspmfs::create(branches_,fusepath_,paths_); } int Policy::MSPMFS::Search::operator()(const Branches::CPtr &branches_, const char *fusepath_, StrVec *paths_) const { return Policies::Search::epmfs(branches_,fusepath_,paths_); } mergerfs-2.33.5/src/fuse_statfs.cpp0000644000000000000000000001136514225522122015740 0ustar rootroot/* Copyright (c) 2018, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "config.hpp" #include "errno.hpp" #include "fs_lstat.hpp" #include "fs_path.hpp" #include "fs_statvfs.hpp" #include "statvfs_util.hpp" #include "ugid.hpp" #include "fuse.h" #include #include #include #include #include using std::string; using std::map; using std::vector; namespace l { static void normalize_statvfs(struct statvfs *fsstat_, const unsigned long min_bsize_, const unsigned long min_frsize_, const unsigned long min_namemax_) { fsstat_->f_blocks = (fsblkcnt_t)((fsstat_->f_blocks * fsstat_->f_frsize) / min_frsize_); fsstat_->f_bfree = (fsblkcnt_t)((fsstat_->f_bfree * fsstat_->f_frsize) / min_frsize_); fsstat_->f_bavail = (fsblkcnt_t)((fsstat_->f_bavail * fsstat_->f_frsize) / min_frsize_); fsstat_->f_bsize = min_bsize_; fsstat_->f_frsize = min_frsize_; fsstat_->f_namemax = min_namemax_; } static void merge_statvfs(struct statvfs * const out_, const struct statvfs * const in_) { out_->f_blocks += in_->f_blocks; out_->f_bfree += in_->f_bfree; out_->f_bavail += in_->f_bavail; out_->f_files += in_->f_files; out_->f_ffree += in_->f_ffree; out_->f_favail += in_->f_favail; } static bool should_ignore(const StatFSIgnore ignore_, const Branch &branch_, const bool readonly_) { return ((((ignore_ == StatFSIgnore::ENUM::RO) || readonly_) && (branch_.ro_or_nc())) || ((ignore_ == StatFSIgnore::ENUM::NC) && (branch_.nc()))); } static int statfs(const Branches::CPtr &branches_, const char *fusepath_, const StatFS mode_, const StatFSIgnore ignore_, struct statvfs *fsstat_) { int rv; string fullpath; struct stat st; struct statvfs stvfs; unsigned long min_bsize; unsigned long min_frsize; unsigned long min_namemax; map fsstats; min_bsize = std::numeric_limits::max(); min_frsize = std::numeric_limits::max(); min_namemax = std::numeric_limits::max(); for(const auto &branch : *branches_) { fullpath = ((mode_ == StatFS::ENUM::FULL) ? fs::path::make(branch.path,fusepath_) : branch.path); rv = fs::lstat(fullpath,&st); if(rv == -1) continue; rv = fs::lstatvfs(fullpath,&stvfs); if(rv == -1) continue; if(stvfs.f_bsize && (min_bsize > stvfs.f_bsize)) min_bsize = stvfs.f_bsize; if(stvfs.f_frsize && (min_frsize > stvfs.f_frsize)) min_frsize = stvfs.f_frsize; if(stvfs.f_namemax && (min_namemax > stvfs.f_namemax)) min_namemax = stvfs.f_namemax; if(l::should_ignore(ignore_,branch,StatVFS::readonly(stvfs))) { stvfs.f_bavail = 0; stvfs.f_favail = 0; } fsstats.insert(std::make_pair(st.st_dev,stvfs)); } map::iterator iter = fsstats.begin(); map::iterator enditer = fsstats.end(); if(iter != enditer) { *fsstat_ = iter->second; l::normalize_statvfs(fsstat_,min_bsize,min_frsize,min_namemax); for(++iter; iter != enditer; ++iter) { l::normalize_statvfs(&iter->second,min_bsize,min_frsize,min_namemax); l::merge_statvfs(fsstat_,&iter->second); } } return 0; } } namespace FUSE { int statfs(const char *fusepath_, struct statvfs *st_) { Config::Read cfg; const fuse_context *fc = fuse_get_context(); const ugid::Set ugid(fc->uid,fc->gid); return l::statfs(cfg->branches, fusepath_, cfg->statfs, cfg->statfs_ignore, st_); } } mergerfs-2.33.5/src/rnd.hpp0000644000000000000000000000203414225522122014173 0ustar rootroot/* ISC License Copyright (c) 2020, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include class RND { public: RND(); public: static uint64_t rand64(void); static uint64_t rand64(const uint64_t max_); static uint64_t rand64(const uint64_t min_, const uint64_t max_); }; mergerfs-2.33.5/src/fs_copy_file_range.cpp0000644000000000000000000000177614225522122017234 0ustar rootroot/* ISC License Copyright (c) 2018, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #ifdef __linux__ #warning "using fs_copy_file_range_linux.icpp" #include "fs_copy_file_range_linux.icpp" #else #warning "using fs_copy_file_range_unsupported.icpp" #include "fs_copy_file_range_unsupported.icpp" #endif mergerfs-2.33.5/src/fuse_ftruncate.cpp0000644000000000000000000000240614225522122016423 0ustar rootroot/* Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "errno.hpp" #include "fileinfo.hpp" #include "fs_ftruncate.hpp" #include "fuse.h" namespace l { static int ftruncate(const int fd_, const off_t size_) { int rv; rv = fs::ftruncate(fd_,size_); return ((rv == -1) ? -errno : 0); } } namespace FUSE { int ftruncate(const fuse_file_info_t *ffi_, off_t size_) { FileInfo *fi = reinterpret_cast(ffi_->fh); return l::ftruncate(fi->fd,size_); } } mergerfs-2.33.5/src/fs_sendfile.hpp0000644000000000000000000000163414225522122015676 0ustar rootroot/* Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once namespace fs { ssize_t sendfile(const int fdin, const int fdout, const size_t count); } mergerfs-2.33.5/src/fs_glob.hpp0000644000000000000000000000166314225522122015032 0ustar rootroot/* Copyright (c) 2018, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include #include namespace fs { void glob(const std::string &pattern, std::vector *strs); } mergerfs-2.33.5/src/tofrom_wrapper.hpp0000644000000000000000000000423314225522122016461 0ustar rootroot/* ISC License Copyright (c) 2020, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include "from_string.hpp" #include "to_string.hpp" #include "tofrom_string.hpp" #include template class ToFromWrapper : public ToFromString { public: int from_string(const std::string &s_) final { return str::from(s_,&_data); } std::string to_string(void) const final { return str::to(_data); } public: ToFromWrapper() { } ToFromWrapper(const T data_) : _data(data_) { } public: ToFromWrapper& operator=(const T &data_) { _data = data_; return *this; } public: operator const T&() const { return _data; } T* operator->() { return &_data; } const T* operator->() const { return &_data; } public: bool operator==(const T &data_) const { return (_data == data_); } private: T _data; }; template class ROToFromWrapper : public ToFromString { public: int from_string(const std::string &s_) final { return -EINVAL; } std::string to_string(void) const final { return str::to(_data); } public: ROToFromWrapper() { } ROToFromWrapper(const T data_) : _data(data_) { } public: operator T() const { return _data; } T* operator->() { return &_data; } public: bool operator==(const T &data_) const { return (_data == data_); } private: T _data; }; mergerfs-2.33.5/src/config_xattr.hpp0000644000000000000000000000174414225522122016106 0ustar rootroot/* ISC License Copyright (c) 2020, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include "enum.hpp" #include "errno.hpp" enum class XAttrEnum { PASSTHROUGH = 0, NOSYS = ENOSYS, NOATTR = ENOATTR }; typedef Enum XAttr; mergerfs-2.33.5/src/fs_remove.hpp0000644000000000000000000000207514225522122015402 0ustar rootroot/* ISC License Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include #include namespace fs { static inline int remove(const char *pathname_) { return ::remove(pathname_); } static inline int remove(const std::string &pathname_) { return fs::remove(pathname_.c_str()); } } mergerfs-2.33.5/src/fuse_readdir_posix.cpp0000644000000000000000000000575014225522122017271 0ustar rootroot/* Copyright (c) 2019, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #define _DEFAULT_SOURCE #include "branches.hpp" #include "errno.hpp" #include "fs_closedir.hpp" #include "fs_devid.hpp" #include "fs_dirfd.hpp" #include "fs_inode.hpp" #include "fs_opendir.hpp" #include "fs_path.hpp" #include "fs_readdir.hpp" #include "fs_stat.hpp" #include "hashset.hpp" #include "fuse.h" #include "fuse_dirents.h" #include #include #include using std::string; using std::vector; namespace l { static uint64_t dirent_exact_namelen(const struct dirent *d_) { #ifdef _D_EXACT_NAMLEN return _D_EXACT_NAMLEN(d_); #elif defined _DIRENT_HAVE_D_NAMLEN return d_->d_namlen; #else return strlen(d_->d_name); #endif } static int readdir(const Branches::CPtr &branches_, const char *dirname_, fuse_dirents_t *buf_) { dev_t dev; HashSet names; string basepath; string fullpath; uint64_t namelen; fuse_dirents_reset(buf_); for(const auto &branch : *branches_) { int rv; int dirfd; DIR *dh; basepath = fs::path::make(branch.path,dirname_); dh = fs::opendir(basepath); if(!dh) continue; dirfd = fs::dirfd(dh); dev = fs::devid(dirfd); rv = 0; for(struct dirent *de = fs::readdir(dh); de && !rv; de = fs::readdir(dh)) { namelen = l::dirent_exact_namelen(de); rv = names.put(de->d_name,namelen); if(rv == 0) continue; fullpath = fs::path::make(dirname_,de->d_name); de->d_ino = fs::inode::calc(fullpath.c_str(), fullpath.size(), DTTOIF(de->d_type), dev, de->d_ino); rv = fuse_dirents_add(buf_,de,namelen); if(rv) return (fs::closedir(dh),-ENOMEM); } fs::closedir(dh); } return 0; } } namespace FUSE { int readdir_posix(const Branches::CPtr &branches_, const char *dirname_, fuse_dirents_t *buf_) { return l::readdir(branches_,dirname_,buf_); } } mergerfs-2.33.5/src/fuse_poll.cpp0000644000000000000000000000205314225522122015374 0ustar rootroot/* ISC License Copyright (c) 2021, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "errno.hpp" #include "fuse.h" namespace FUSE { int poll(const fuse_file_info_t *ffi_, fuse_pollhandle_t *ph_, unsigned *reventsp_) { (void)ffi_; (void)ph_; (void)reventsp_; return -ENOSYS; } } mergerfs-2.33.5/src/fuse_open.cpp0000644000000000000000000001332514225522122015373 0ustar rootroot/* Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "config.hpp" #include "errno.hpp" #include "fileinfo.hpp" #include "fs_lchmod.hpp" #include "fs_cow.hpp" #include "fs_fchmod.hpp" #include "fs_open.hpp" #include "fs_path.hpp" #include "fs_stat.hpp" #include "stat_util.hpp" #include "ugid.hpp" #include "fuse.h" #include #include using std::string; using std::vector; namespace l { static bool rdonly(const int flags_) { return ((flags_ & O_ACCMODE) == O_RDONLY); } static int lchmod_and_open_if_not_writable_and_empty(const string &fullpath_, const int flags_) { int rv; struct stat st; rv = fs::lstat(fullpath_,&st); if(rv == -1) return (errno=EACCES,-1); if(StatUtil::writable(st)) return (errno=EACCES,-1); rv = fs::lchmod(fullpath_,(st.st_mode|S_IWUSR|S_IWGRP)); if(rv == -1) return (errno=EACCES,-1); rv = fs::open(fullpath_,flags_); if(rv == -1) return (errno=EACCES,-1); fs::fchmod(rv,st.st_mode); return rv; } static int nfsopenhack(const std::string &fullpath_, const int flags_, const NFSOpenHack nfsopenhack_) { switch(nfsopenhack_) { default: case NFSOpenHack::ENUM::OFF: return (errno=EACCES,-1); case NFSOpenHack::ENUM::GIT: if(l::rdonly(flags_)) return (errno=EACCES,-1); if(fullpath_.find("/.git/") == string::npos) return (errno=EACCES,-1); return l::lchmod_and_open_if_not_writable_and_empty(fullpath_,flags_); case NFSOpenHack::ENUM::ALL: if(l::rdonly(flags_)) return (errno=EACCES,-1); return l::lchmod_and_open_if_not_writable_and_empty(fullpath_,flags_); } } /* The kernel expects being able to issue read requests when running with writeback caching enabled so we must change O_WRONLY to O_RDWR. With writeback caching enabled the kernel handles O_APPEND. Could be an issue if the underlying file changes out of band but that is true of any caching. */ static void tweak_flags_writeback_cache(int *flags_) { if((*flags_ & O_ACCMODE) == O_WRONLY) *flags_ = ((*flags_ & ~O_ACCMODE) | O_RDWR); if(*flags_ & O_APPEND) *flags_ &= ~O_APPEND; } static void config_to_ffi_flags(Config::Read &cfg_, fuse_file_info_t *ffi_) { switch(cfg_->cache_files) { case CacheFiles::ENUM::LIBFUSE: ffi_->direct_io = cfg_->direct_io; ffi_->keep_cache = cfg_->kernel_cache; ffi_->auto_cache = cfg_->auto_cache; break; case CacheFiles::ENUM::OFF: ffi_->direct_io = 1; ffi_->keep_cache = 0; ffi_->auto_cache = 0; break; case CacheFiles::ENUM::PARTIAL: ffi_->direct_io = 0; ffi_->keep_cache = 0; ffi_->auto_cache = 0; break; case CacheFiles::ENUM::FULL: ffi_->direct_io = 0; ffi_->keep_cache = 1; ffi_->auto_cache = 0; break; case CacheFiles::ENUM::AUTO_FULL: ffi_->direct_io = 0; ffi_->keep_cache = 0; ffi_->auto_cache = 1; break; } } static int open_core(const string &basepath_, const char *fusepath_, const int flags_, const bool link_cow_, const NFSOpenHack nfsopenhack_, uint64_t *fh_) { int fd; string fullpath; fullpath = fs::path::make(basepath_,fusepath_); if(link_cow_ && fs::cow::is_eligible(fullpath.c_str(),flags_)) fs::cow::break_link(fullpath.c_str()); fd = fs::open(fullpath,flags_); if((fd == -1) && (errno == EACCES)) fd = l::nfsopenhack(fullpath,flags_,nfsopenhack_); if(fd == -1) return -errno; *fh_ = reinterpret_cast(new FileInfo(fd,fusepath_)); return 0; } static int open(const Policy::Search &searchFunc_, const Branches &branches_, const char *fusepath_, const int flags_, const bool link_cow_, const NFSOpenHack nfsopenhack_, uint64_t *fh_) { int rv; StrVec basepaths; rv = searchFunc_(branches_,fusepath_,&basepaths); if(rv == -1) return -errno; return l::open_core(basepaths[0],fusepath_,flags_,link_cow_,nfsopenhack_,fh_); } } namespace FUSE { int open(const char *fusepath_, fuse_file_info_t *ffi_) { Config::Read cfg; const fuse_context *fc = fuse_get_context(); const ugid::Set ugid(fc->uid,fc->gid); l::config_to_ffi_flags(cfg,ffi_); if(cfg->writeback_cache) l::tweak_flags_writeback_cache(&ffi_->flags); return l::open(cfg->func.open.policy, cfg->branches, fusepath_, ffi_->flags, cfg->link_cow, cfg->nfsopenhack, &ffi_->fh); } } mergerfs-2.33.5/src/fs_fadvise_posix.icpp0000644000000000000000000000177514225522122017122 0ustar rootroot/* Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include namespace fs { static int fadvise(const int fd_, const off_t offset_, const off_t len_, const int advice_) { return ::posix_fadvise(fd_,offset_,len_,advice_); } } mergerfs-2.33.5/src/ef.hpp0000644000000000000000000000151314225522122014003 0ustar rootroot/* ISC License Copyright (c) 2019, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #define ef(X) else if(X) mergerfs-2.33.5/src/fuse_ioctl.hpp0000644000000000000000000000211414225522122015543 0ustar rootroot/* Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include #include "fuse.h" namespace FUSE { int ioctl(const fuse_file_info_t *ffi, unsigned long cmd, void *arg, unsigned int flags, void *data, uint32_t *out_bufsz); } mergerfs-2.33.5/src/fs_fallocate_osx.icpp0000644000000000000000000000305314225522122017071 0ustar rootroot/* Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "errno.hpp" #include #include namespace l { static int fallocate_core(const int fd_, const off_t offset_, const off_t len_) { int rv; fstore_t store = {F_ALLOCATECONTIG,F_PEOFPOSMODE,offset,len,0}; rv = ::fcntl(fd_,F_PREALLOCATE,&store); if(rv == -1) { store.fst_flags = F_ALLOCATEALL; rv = ::fcntl(fd_,F_PREALLOCATE,&store); } if(rv == -1) return rv; return ::ftruncate(fd_,(offset_+len_)); } } namespace fs { int fallocate(const int fd_, const int mode_, const off_t offset_, const off_t len_) { if(mode_) return (errno=EOPNOTSUPP,-1); return l::fallocate_core(fd_,offset_,len_); } } mergerfs-2.33.5/src/fs_acl.cpp0000644000000000000000000000232614225522122014636 0ustar rootroot/* ISC License Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "fs_lgetxattr.hpp" #include "fs_path.hpp" #include const char POSIX_ACL_DEFAULT_XATTR[] = "system.posix_acl_default"; namespace fs { namespace acl { bool dir_has_defaults(const std::string &fullpath_) { int rv; std::string dirpath; dirpath = fs::path::dirname(fullpath_); rv = fs::lgetxattr(dirpath,POSIX_ACL_DEFAULT_XATTR,NULL,0); return (rv != -1); } } } mergerfs-2.33.5/src/fs_fadvise.cpp0000644000000000000000000000375014225522122015522 0ustar rootroot/* Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #if _XOPEN_SOURCE >= 600 || _POSIX_C_SOURCE >= 200112L #warning "using fs_fadvise_posix.icpp" #include "fs_fadvise_posix.icpp" #else #warning "using fs_fadvise_unsupported.icpp" #include "fs_fadvise_unsupported.icpp" #endif #ifndef POSIX_FADV_NORMAL # define POSIX_FADV_NORMAL 0 #endif #ifndef POSIX_FADV_RANDOM # define POSIX_FADV_RANDOM 1 #endif #ifndef POSIX_FADV_SEQUENTIAL # define POSIX_FADV_SEQUENTIAL 2 #endif #ifndef POSIX_FADV_WILLNEED # define POSIX_FADV_WILLNEED 3 #endif #ifndef POSIX_FADV_DONTNEED # define POSIX_FADV_DONTNEED 4 #endif #ifndef POSIX_FADV_NOREUSE # define POSIX_FADV_NOREUSE 5 #endif namespace fs { int fadvise_dontneed(const int fd_, const off_t offset_, const off_t len_) { return fs::fadvise(fd_,offset_,len_,POSIX_FADV_DONTNEED); } int fadvise_willneed(const int fd_, const off_t offset_, const off_t len_) { return fs::fadvise(fd_,offset_,len_,POSIX_FADV_WILLNEED); } int fadvise_sequential(const int fd_, const off_t offset_, const off_t len_) { return fs::fadvise(fd_,offset_,len_,POSIX_FADV_SEQUENTIAL); } } mergerfs-2.33.5/src/config_nfsopenhack.cpp0000644000000000000000000000261714225522122017236 0ustar rootroot/* ISC License Copyright (c) 2020, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "config_nfsopenhack.hpp" #include "ef.hpp" #include "errno.hpp" template<> int NFSOpenHack::from_string(const std::string &s_) { if(s_ == "off") _data = NFSOpenHack::ENUM::OFF; ef(s_ == "git") _data = NFSOpenHack::ENUM::GIT; ef(s_ == "all") _data = NFSOpenHack::ENUM::ALL; else return -EINVAL; return 0; } template<> std::string NFSOpenHack::to_string(void) const { switch(_data) { case NFSOpenHack::ENUM::OFF: return "off"; case NFSOpenHack::ENUM::GIT: return "git"; case NFSOpenHack::ENUM::ALL: return "all"; } return std::string(); } mergerfs-2.33.5/src/fuse_fchown.hpp0000644000000000000000000000170314225522122015720 0ustar rootroot/* Copyright (c) 2019, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include "fuse.h" namespace FUSE { int fchown(const fuse_file_info_t *ffi, uid_t uid, gid_t gid); } mergerfs-2.33.5/src/config_follow_symlinks.hpp0000644000000000000000000000171614225522122020176 0ustar rootroot/* ISC License Copyright (c) 2020, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include "enum.hpp" enum class FollowSymlinksEnum { NEVER, DIRECTORY, REGULAR, ALL }; typedef Enum FollowSymlinks; mergerfs-2.33.5/src/fs_movefile.hpp0000644000000000000000000000236614225522122015716 0ustar rootroot/* Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include "branches.hpp" #include "policy.hpp" #include namespace fs { int movefile(const Policy::Create &policy, const Branches::CPtr &branches, const std::string &fusepath, int *origfd); int movefile_as_root(const Policy::Create &policy, const Branches::CPtr &branches, const std::string &fusepath, int *origfd); } mergerfs-2.33.5/src/fuse_fchown.cpp0000644000000000000000000000253514225522122015717 0ustar rootroot/* Copyright (c) 2019, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "errno.hpp" #include "fileinfo.hpp" #include "fs_fchown.hpp" #include "fuse.h" #include namespace l { static int fchown(const int fd_, const uid_t uid_, const gid_t gid_) { int rv; rv = fs::fchown(fd_,uid_,gid_); if(rv == -1) return -errno; return rv; } } namespace FUSE { int fchown(const fuse_file_info_t *ffi_, const uid_t uid_, const gid_t gid_) { FileInfo *fi = reinterpret_cast(ffi_->fh); return l::fchown(fi->fd,uid_,gid_); } } mergerfs-2.33.5/src/buildvector.hpp0000644000000000000000000000233714225522122015740 0ustar rootroot/* Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include #include template class buildvector { public: buildvector(const V &val_) { _vector.push_back(val_); } buildvector &operator()(const V &val_) { _vector.push_back(val_); return *this; } operator std::vector() { if(SORT == true) std::sort(_vector.begin(),_vector.end()); return _vector; } private: std::vector _vector; }; mergerfs-2.33.5/src/policy_msplus.cpp0000644000000000000000000000657114225522122016317 0ustar rootroot/* Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "errno.hpp" #include "fs_exists.hpp" #include "fs_info.hpp" #include "fs_path.hpp" #include "fs_statvfs_cache.hpp" #include "policies.hpp" #include "policy.hpp" #include "policy_error.hpp" #include "policy_msplus.hpp" #include #include using std::string; namespace msplus { static const string* create_1(const Branches::CPtr &branches_, const string &fusepath_, int *err_) { int rv; uint64_t lus; fs::info_t info; const string *basepath; basepath = NULL; lus = std::numeric_limits::max(); for(auto &branch : *branches_) { if(branch.ro_or_nc()) error_and_continue(*err_,EROFS); if(!fs::exists(branch.path,fusepath_)) error_and_continue(*err_,ENOENT); rv = fs::info(branch.path,&info); if(rv == -1) error_and_continue(*err_,ENOENT); if(info.readonly) error_and_continue(*err_,EROFS); if(info.spaceavail < branch.minfreespace()) error_and_continue(*err_,ENOSPC); if(info.spaceused >= lus) continue; lus = info.spaceused;; basepath = &branch.path; } return basepath; } static int create(const Branches::CPtr &branches_, const char *fusepath_, StrVec *paths_) { int error; string fusepath; const string *basepath; error = ENOENT; fusepath = fusepath_; for(;;) { basepath = msplus::create_1(branches_,fusepath,&error); if(basepath) break; if(fusepath == "/") break; fusepath = fs::path::dirname(fusepath); } if(basepath == NULL) return (errno=error,-1); paths_->push_back(*basepath); return 0; } } int Policy::MSPLUS::Action::operator()(const Branches::CPtr &branches_, const char *fusepath_, StrVec *paths_) const { return Policies::Action::eplus(branches_,fusepath_,paths_); } int Policy::MSPLUS::Create::operator()(const Branches::CPtr &branches_, const char *fusepath_, StrVec *paths_) const { return ::msplus::create(branches_,fusepath_,paths_); } int Policy::MSPLUS::Search::operator()(const Branches::CPtr &branches_, const char *fusepath_, StrVec *paths_) const { return Policies::Search::eplus(branches_,fusepath_,paths_); } mergerfs-2.33.5/src/mergerfs.cpp0000644000000000000000000001241614225522122015222 0ustar rootroot/* Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "fs_path.hpp" #include "mergerfs.hpp" #include "option_parser.hpp" #include "resources.hpp" #include "strvec.hpp" #include "fuse_access.hpp" #include "fuse_bmap.hpp" #include "fuse_chmod.hpp" #include "fuse_chown.hpp" #include "fuse_copy_file_range.hpp" #include "fuse_create.hpp" #include "fuse_destroy.hpp" #include "fuse_fallocate.hpp" #include "fuse_fchmod.hpp" #include "fuse_fchown.hpp" #include "fuse_fgetattr.hpp" #include "fuse_flock.hpp" #include "fuse_flush.hpp" #include "fuse_free_hide.hpp" #include "fuse_fsync.hpp" #include "fuse_fsyncdir.hpp" #include "fuse_ftruncate.hpp" #include "fuse_futimens.hpp" #include "fuse_getattr.hpp" #include "fuse_getxattr.hpp" #include "fuse_init.hpp" #include "fuse_ioctl.hpp" #include "fuse_link.hpp" #include "fuse_listxattr.hpp" #include "fuse_lock.hpp" #include "fuse_mkdir.hpp" #include "fuse_mknod.hpp" #include "fuse_open.hpp" #include "fuse_opendir.hpp" #include "fuse_poll.hpp" #include "fuse_prepare_hide.hpp" #include "fuse_read_buf.hpp" #include "fuse_readdir.hpp" #include "fuse_readdir_plus.hpp" #include "fuse_readlink.hpp" #include "fuse_release.hpp" #include "fuse_releasedir.hpp" #include "fuse_removexattr.hpp" #include "fuse_rename.hpp" #include "fuse_rmdir.hpp" #include "fuse_setxattr.hpp" #include "fuse_statfs.hpp" #include "fuse_symlink.hpp" #include "fuse_truncate.hpp" #include "fuse_unlink.hpp" #include "fuse_utimens.hpp" #include "fuse_write_buf.hpp" #include "fuse.h" #include #include #include namespace l { static void get_fuse_operations(struct fuse_operations &ops_, const bool nullrw_) { ops_.access = FUSE::access; ops_.bmap = FUSE::bmap; ops_.chmod = FUSE::chmod; ops_.chown = FUSE::chown; ops_.copy_file_range = FUSE::copy_file_range; ops_.create = FUSE::create; ops_.destroy = FUSE::destroy; ops_.fallocate = FUSE::fallocate; ops_.fchmod = FUSE::fchmod; ops_.fchown = FUSE::fchown; ops_.fgetattr = FUSE::fgetattr; ops_.flock = FUSE::flock; ops_.flush = FUSE::flush; ops_.free_hide = FUSE::free_hide; ops_.fsync = FUSE::fsync; ops_.fsyncdir = FUSE::fsyncdir; ops_.ftruncate = FUSE::ftruncate; ops_.futimens = FUSE::futimens; ops_.getattr = FUSE::getattr; ops_.getxattr = FUSE::getxattr; ops_.init = FUSE::init; ops_.ioctl = FUSE::ioctl; ops_.link = FUSE::link; ops_.listxattr = FUSE::listxattr; ops_.lock = FUSE::lock; ops_.mkdir = FUSE::mkdir; ops_.mknod = FUSE::mknod; ops_.open = FUSE::open; ops_.opendir = FUSE::opendir; ops_.poll = FUSE::poll;; ops_.prepare_hide = FUSE::prepare_hide; ops_.read_buf = (nullrw_ ? FUSE::read_buf_null : FUSE::read_buf); ops_.readdir = FUSE::readdir; ops_.readdir_plus = FUSE::readdir_plus; ops_.readlink = FUSE::readlink; ops_.release = FUSE::release; ops_.releasedir = FUSE::releasedir; ops_.removexattr = FUSE::removexattr; ops_.rename = FUSE::rename; ops_.rmdir = FUSE::rmdir; ops_.setxattr = FUSE::setxattr; ops_.statfs = FUSE::statfs; ops_.symlink = FUSE::symlink; ops_.truncate = FUSE::truncate; ops_.unlink = FUSE::unlink; ops_.utimens = FUSE::utimens; ops_.write_buf = (nullrw_ ? FUSE::write_buf_null : FUSE::write_buf); return; } static void setup_resources(void) { const int prio = -10; std::srand(time(NULL)); resources::reset_umask(); resources::maxout_rlimit_nofile(); resources::maxout_rlimit_fsize(); resources::setpriority(prio); } int main(const int argc_, char **argv_) { Config::Read cfg; Config::ErrVec errs; fuse_args args; fuse_operations ops; memset(&ops,0,sizeof(fuse_operations)); args.argc = argc_; args.argv = argv_; args.allocated = 0; options::parse(&args,&errs); if(errs.size()) std::cerr << errs << std::endl; l::setup_resources(); l::get_fuse_operations(ops,cfg->nullrw); return fuse_main(args.argc, args.argv, &ops); } } int main(int argc_, char **argv_) { return l::main(argc_,argv_); } mergerfs-2.33.5/src/fuse_flock.hpp0000644000000000000000000000163214225522122015533 0ustar rootroot/* Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include "fuse.h" namespace FUSE { int flock(const fuse_file_info_t *ffi, int op); } mergerfs-2.33.5/src/wyhash.h0000644000000000000000000001410114225522122014351 0ustar rootroot//Author: Wang Yi #ifndef wyhash_final_version #define wyhash_final_version //defines that change behavior #ifndef WYHASH_CONDOM #define WYHASH_CONDOM 1 //0: read 8 bytes before and after boudaries, dangerous but fastest. 1: normal valid behavior 2: extra protection against entropy loss (probability=2^-63), aka. "blind multiplication" #endif #define WYHASH_32BIT_MUM 0 //faster on 32 bit system //includes #include #include #if defined(_MSC_VER) && defined(_M_X64) #include #pragma intrinsic(_umul128) #endif #if defined(__GNUC__) || defined(__INTEL_COMPILER) || defined(__clang__) #define _likely_(x) __builtin_expect(x,1) #define _unlikely_(x) __builtin_expect(x,0) #else #define _likely_(x) (x) #define _unlikely_(x) (x) #endif //mum function static inline uint64_t _wyrot(uint64_t x) { return (x>>32)|(x<<32); } static inline void _wymum(uint64_t *A, uint64_t *B){ #if(WYHASH_32BIT_MUM) uint64_t hh=(*A>>32)*(*B>>32), hl=(*A>>32)*(unsigned)*B, lh=(unsigned)*A*(*B>>32), ll=(uint64_t)(unsigned)*A*(unsigned)*B; #if(WYHASH_CONDOM>1) *A^=_wyrot(hl)^hh; *B^=_wyrot(lh)^ll; #else *A=_wyrot(hl)^hh; *B=_wyrot(lh)^ll; #endif #elif defined(__SIZEOF_INT128__) __uint128_t r=*A; r*=*B; #if(WYHASH_CONDOM>1) *A^=(uint64_t)r; *B^=(uint64_t)(r>>64); #else *A=(uint64_t)r; *B=(uint64_t)(r>>64); #endif #elif defined(_MSC_VER) && defined(_M_X64) #if(WYHASH_CONDOM>1) uint64_t a, b; a=_umul128(*A,*B,&b); *A^=a; *B^=b; #else *A=_umul128(*A,*B,B); #endif #else uint64_t ha=*A>>32, hb=*B>>32, la=(uint32_t)*A, lb=(uint32_t)*B, hi, lo; uint64_t rh=ha*hb, rm0=ha*lb, rm1=hb*la, rl=la*lb, t=rl+(rm0<<32), c=t>32)+(rm1>>32)+c; #if(WYHASH_CONDOM>1) *A^=lo; *B^=hi; #else *A=lo; *B=hi; #endif #endif } static inline uint64_t _wymix(uint64_t A, uint64_t B){ _wymum(&A,&B); return A^B; } //read functions #ifndef WYHASH_LITTLE_ENDIAN #if defined(_WIN32) || defined(__LITTLE_ENDIAN__) || (defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) #define WYHASH_LITTLE_ENDIAN 1 #elif defined(__BIG_ENDIAN__) || (defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__) #define WYHASH_LITTLE_ENDIAN 0 #endif #endif #if (WYHASH_LITTLE_ENDIAN) static inline uint64_t _wyr8(const uint8_t *p) { uint64_t v; memcpy(&v, p, 8); return v;} static inline uint64_t _wyr4(const uint8_t *p) { unsigned v; memcpy(&v, p, 4); return v;} #elif defined(__GNUC__) || defined(__INTEL_COMPILER) || defined(__clang__) static inline uint64_t _wyr8(const uint8_t *p) { uint64_t v; memcpy(&v, p, 8); return __builtin_bswap64(v);} static inline uint64_t _wyr4(const uint8_t *p) { unsigned v; memcpy(&v, p, 4); return __builtin_bswap32(v);} #elif defined(_MSC_VER) static inline uint64_t _wyr8(const uint8_t *p) { uint64_t v; memcpy(&v, p, 8); return _byteswap_uint64(v);} static inline uint64_t _wyr4(const uint8_t *p) { unsigned v; memcpy(&v, p, 4); return _byteswap_ulong(v);} #endif static inline uint64_t _wyr3(const uint8_t *p, unsigned k) { return (((uint64_t)p[0])<<16)|(((uint64_t)p[k>>1])<<8)|p[k-1];} //wyhash function static inline uint64_t _wyfinish16(const uint8_t *p, uint64_t len, uint64_t seed, const uint64_t *secret, uint64_t i){ #if(WYHASH_CONDOM>0) uint64_t a, b; if(_likely_(i<=8)){ if(_likely_(i>=4)){ a=_wyr4(p); b=_wyr4(p+i-4); } else if (_likely_(i)){ a=_wyr3(p,i); b=0; } else a=b=0; } else{ a=_wyr8(p); b=_wyr8(p+i-8); } return _wymix(secret[1]^len,_wymix(a^secret[1], b^seed)); #else #define oneshot_shift ((i<8)*((8-i)<<3)) return _wymix(secret[1]^len,_wymix((_wyr8(p)<>oneshot_shift)^seed)); #endif } static inline uint64_t _wyfinish(const uint8_t *p, uint64_t len, uint64_t seed, const uint64_t *secret, uint64_t i){ if(_likely_(i<=16)) return _wyfinish16(p,len,seed,secret,i); return _wyfinish(p+16,len,_wymix(_wyr8(p)^secret[1],_wyr8(p+8)^seed),secret,i-16); } static inline uint64_t wyhash(const void *key, uint64_t len, uint64_t seed, const uint64_t *secret){ const uint8_t *p=(const uint8_t *)key; uint64_t i=len; seed^=*secret; if(_unlikely_(i>64)){ uint64_t see1=seed; do{ seed=_wymix(_wyr8(p)^secret[1],_wyr8(p+8)^seed)^_wymix(_wyr8(p+16)^secret[2],_wyr8(p+24)^seed); see1=_wymix(_wyr8(p+32)^secret[3],_wyr8(p+40)^see1)^_wymix(_wyr8(p+48)^secret[4],_wyr8(p+56)^see1); p+=64; i-=64; }while(i>64); seed^=see1; } return _wyfinish(p,len,seed,secret,i); } //utility functions const uint64_t _wyp[5] = {0xa0761d6478bd642full, 0xe7037ed1a0b428dbull, 0x8ebc6af09c88c6e3ull, 0x589965cc75374cc3ull, 0x1d8e4e27c47d124full}; static inline uint64_t wyhash64(uint64_t A, uint64_t B){ A^=_wyp[0]; B^=_wyp[1]; _wymum(&A,&B); return _wymix(A^_wyp[0],B^_wyp[1]);} static inline uint64_t wyrand(uint64_t *seed){ *seed+=_wyp[0]; return _wymix(*seed,*seed^_wyp[1]);} static inline double wy2u01(uint64_t r){ const double _wynorm=1.0/(1ull<<52); return (r>>12)*_wynorm;} static inline double wy2gau(uint64_t r){ const double _wynorm=1.0/(1ull<<20); return ((r&0x1fffff)+((r>>21)&0x1fffff)+((r>>42)&0x1fffff))*_wynorm-3.0;} static inline void make_secret(uint64_t seed, uint64_t *secret){ uint8_t c[] = {15, 23, 27, 29, 30, 39, 43, 45, 46, 51, 53, 54, 57, 58, 60, 71, 75, 77, 78, 83, 85, 86, 89, 90, 92, 99, 101, 102, 105, 106, 108, 113, 114, 116, 120, 135, 139, 141, 142, 147, 149, 150, 153, 154, 156, 163, 165, 166, 169, 170, 172, 177, 178, 180, 184, 195, 197, 198, 201, 202, 204, 209, 210, 212, 216, 225, 226, 228, 232, 240 }; for(size_t i=0;i<5;i++){ uint8_t ok; do{ ok=1; secret[i]=0; for(size_t j=0;j<64;j+=8) secret[i]|=((uint64_t)c[wyrand(&seed)%sizeof(c)])< Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include namespace fs { struct info_t { bool readonly; uint64_t spaceavail; uint64_t spaceused; }; } mergerfs-2.33.5/src/policy_cache.hpp0000644000000000000000000000272514225522122016041 0ustar rootroot/* ISC License Copyright (c) 2019, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include "policy.hpp" #include "strvec.hpp" #include #include #include #include class PolicyCache { public: struct Value { Value(); uint64_t time; StrVec paths; }; public: PolicyCache(void); public: void erase(const char *fusepath); void cleanup(const int prob = 1); void clear(void); public: int operator()(const Policy::Search &policy, const Branches &branches, const char *fusepath, StrVec *paths); public: uint64_t timeout; private: pthread_mutex_t _lock; std::map _cache; }; mergerfs-2.33.5/src/policy_erofs.cpp0000644000000000000000000000312414225522122016101 0ustar rootroot/* Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "errno.hpp" #include "policy.hpp" #include "policy_erofs.hpp" #include using std::string; int Policy::ERoFS::Action::operator()(const Branches::CPtr &branches_, const char *fusepath_, StrVec *paths_) const { return (errno=EROFS,-1); } int Policy::ERoFS::Create::operator()(const Branches::CPtr &branches_, const char *fusepath_, StrVec *paths_) const { return (errno=EROFS,-1); } int Policy::ERoFS::Search::operator()(const Branches::CPtr &branches_, const char *fusepath_, StrVec *paths_) const { return (errno=EROFS,-1); } mergerfs-2.33.5/src/fixed_mem_pool.hpp0000644000000000000000000000320014225522122016372 0ustar rootroot/* ISC License Copyright (c) 2020, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include #include typedef struct fixed_mem_pool_t fixed_mem_pool_t; struct fixed_mem_pool_t { fixed_mem_pool_t *next; }; template class FixedMemPool { public: FixedMemPool() { list.next = NULL; } ~FixedMemPool() { void *mem; while(!empty()) { mem = alloc(); ::free(mem); } } bool empty(void) { return (list.next == NULL); } uint64_t size(void) { return SIZE; } void* alloc(void) { void *rv; if(list.next == NULL) return malloc(SIZE); rv = (void*)list.next; list.next = list.next->next; return rv; } void free(void *mem_) { fixed_mem_pool_t *next; next = (fixed_mem_pool_t*)mem_; next->next = list.next; list.next = next; } private: fixed_mem_pool_t list; }; mergerfs-2.33.5/src/policy_ff.hpp0000644000000000000000000000313114225522122015361 0ustar rootroot/* ISC License Copyright (c) 2020, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include "policy.hpp" namespace Policy { namespace FF { class Action final : public Policy::ActionImpl { public: Action() : Policy::ActionImpl("ff") {} public: int operator()(const Branches::CPtr&,const char*,StrVec*) const final; }; class Create final : public Policy::CreateImpl { public: Create() : Policy::CreateImpl("ff") {} public: int operator()(const Branches::CPtr&,const char*,StrVec*) const final; bool path_preserving(void) const final { return false; } }; class Search final : public Policy::SearchImpl { public: Search() : Policy::SearchImpl("ff") {} public: int operator()(const Branches::CPtr&,const char*,StrVec*) const final; }; } } mergerfs-2.33.5/src/fuse_chown.hpp0000644000000000000000000000161614225522122015555 0ustar rootroot/* Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once namespace FUSE { int chown(const char *fusepath, uid_t uid, gid_t gid); } mergerfs-2.33.5/src/fs_utimensat_linux.hpp0000644000000000000000000000264014225522122017333 0ustar rootroot/* ISC License Copyright (c) 2020, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include #include #include namespace fs { static inline int utimensat(const int dirfd_, const char *pathname_, const struct timespec times_[2], const int flags_) { return ::utimensat(dirfd_,pathname_,times_,flags_); } static inline int utimensat(const int dirfd_, const std::string &pathname_, const struct timespec times_[2], const int flags_) { return fs::utimensat(dirfd_,pathname_.c_str(),times_,flags_); } } mergerfs-2.33.5/src/fs_copydata.hpp0000644000000000000000000000167714225522122015720 0ustar rootroot/* ISC License Copyright (c) 2020, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include namespace fs { int copydata(const int src_fd, const int dst_fd, const size_t count); } mergerfs-2.33.5/src/fuse_listxattr.hpp0000644000000000000000000000163414225522122016475 0ustar rootroot/* Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once namespace FUSE { int listxattr(const char *fusepath, char *buf, size_t count); } mergerfs-2.33.5/src/fs_close.hpp0000644000000000000000000000165114225522122015211 0ustar rootroot/* ISC License Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include namespace fs { static inline int close(const int fd_) { return ::close(fd_); } } mergerfs-2.33.5/src/endian.hpp0000644000000000000000000000171414225522122014652 0ustar rootroot/* ISC License Copyright (c) 2019, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once namespace endian { static inline bool is_big(void) { const union { uint32_t i; char c[4]; } u = { 0x01000000 }; return u.c[0]; } }; mergerfs-2.33.5/src/fuse_fsync.hpp0000644000000000000000000000164314225522122015561 0ustar rootroot/* Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include "fuse.h" namespace FUSE { int fsync(const fuse_file_info_t *ffi, int isdatasync); } mergerfs-2.33.5/src/fs_realpath.hpp0000644000000000000000000000223114225522122015677 0ustar rootroot/* ISC License Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include #include #include namespace fs { static inline char * realpath(const std::string &path_, char *resolved_path_) { return ::realpath(path_.c_str(),resolved_path_); } static inline char * realpath(const std::string &path_) { return fs::realpath(path_,NULL); } } mergerfs-2.33.5/src/fs_readdir.hpp0000644000000000000000000000167014225522122015517 0ustar rootroot/* ISC License Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include namespace fs { static inline struct dirent * readdir(DIR *dirp_) { return ::readdir(dirp_); } } mergerfs-2.33.5/src/to_string.cpp0000644000000000000000000000242514225522122015417 0ustar rootroot/* ISC License Copyright (c) 2019, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include namespace str { std::string to(const bool bool_) { return (bool_ ? "true" : "false"); } std::string to(const int int_) { char buf[24]; sprintf(buf,"%d",int_); return buf; } std::string to(const uint64_t uint64_) { char buf[64]; sprintf(buf,"%llu",(unsigned long long)uint64_); return buf; } std::string to(const std::string &s_) { return s_; } } mergerfs-2.33.5/src/fuse_rename.cpp0000644000000000000000000002373514225522122015707 0ustar rootroot/* Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "config.hpp" #include "errno.hpp" #include "fs_clonepath.hpp" #include "fs_link.hpp" #include "fs_mkdir_as_root.hpp" #include "fs_path.hpp" #include "fs_remove.hpp" #include "fs_rename.hpp" #include "fs_symlink.hpp" #include "fs_unlink.hpp" #include "fuse_symlink.hpp" #include "ugid.hpp" #include "ghc/filesystem.hpp" #include #include #include #include using std::string; namespace gfs = ghc::filesystem; namespace error { static inline int calc(const int rv_, const int prev_, const int cur_) { if(rv_ == -1) { if(prev_ == 0) return 0; return cur_; } return 0; } } namespace l { static bool contains(const StrVec &haystack_, const char *needle_) { for(auto &hay : haystack_) { if(hay == needle_) return true; } return false; } static bool contains(const StrVec &haystack_, const string &needle_) { return l::contains(haystack_,needle_.c_str()); } static void remove(const StrVec &toremove_) { for(auto &path : toremove_) fs::remove(path); } static void remove(const Branches::CPtr &branches_, const std::string &relpath_) { std::string fullpath; for(auto &branch : *branches_) { fullpath = fs::path::make(branch.path,relpath_); fs::remove(fullpath); } } static int rename_create_path(const Policy::Search &searchPolicy_, const Policy::Action &actionPolicy_, const Branches::CPtr &branches_, const gfs::path &oldfusepath_, const gfs::path &newfusepath_) { int rv; int error; StrVec toremove; StrVec newbasepath; StrVec oldbasepaths; gfs::path oldfullpath; gfs::path newfullpath; rv = actionPolicy_(branches_,oldfusepath_,&oldbasepaths); if(rv == -1) return -errno; rv = searchPolicy_(branches_,newfusepath_.parent_path(),&newbasepath); if(rv == -1) return -errno; error = -1; for(auto &branch : *branches_) { newfullpath = branch.path; newfullpath += newfusepath_; if(!l::contains(oldbasepaths,branch.path)) { toremove.push_back(newfullpath); continue; } oldfullpath = branch.path; oldfullpath += oldfusepath_; rv = fs::rename(oldfullpath,newfullpath); if(rv == -1) { rv = fs::clonepath_as_root(newbasepath[0],branch.path,newfusepath_.parent_path()); if(rv == 0) rv = fs::rename(oldfullpath,newfullpath); } error = error::calc(rv,error,errno); if(rv == -1) toremove.push_back(oldfullpath); } if(error == 0) l::remove(toremove); return -error; } static int rename_preserve_path(const Policy::Action &actionPolicy_, const Branches::CPtr &branches_, const gfs::path &oldfusepath_, const gfs::path &newfusepath_) { int rv; bool success; StrVec toremove; StrVec oldbasepaths; gfs::path oldfullpath; gfs::path newfullpath; rv = actionPolicy_(branches_,oldfusepath_,&oldbasepaths); if(rv == -1) return -errno; success = false; for(auto &branch : *branches_) { newfullpath = branch.path; newfullpath += newfusepath_; if(!l::contains(oldbasepaths,branch.path)) { toremove.push_back(newfullpath); continue; } oldfullpath = branch.path; oldfullpath += oldfusepath_; rv = fs::rename(oldfullpath,newfullpath); if(rv == -1) { toremove.push_back(oldfullpath); continue; } success = true; } // TODO: probably should try to be nuanced here. if(success == false) return -EXDEV; l::remove(toremove); return 0; } static void rename_exdev_rename_back(const StrVec &basepaths_, const gfs::path &oldfusepath_) { gfs::path oldpath; gfs::path newpath; for(auto &basepath : basepaths_) { oldpath = basepath; oldpath /= ".mergerfs_rename_exdev"; oldpath += oldfusepath_; newpath = basepath; newpath += oldfusepath_; fs::rename(oldpath,newpath); } } static int rename_exdev_rename_target(const Policy::Action &actionPolicy_, const Branches::CPtr &branches_, const gfs::path &oldfusepath_, StrVec *basepaths_) { int rv; gfs::path clonesrc; gfs::path clonetgt; rv = actionPolicy_(branches_,oldfusepath_,basepaths_); if(rv == -1) return -errno; ugid::SetRootGuard ugidGuard; for(auto &basepath : *basepaths_) { clonesrc = basepath; clonetgt = basepath; clonetgt /= ".mergerfs_rename_exdev"; rv = fs::clonepath(clonesrc,clonetgt,oldfusepath_.parent_path()); if((rv == -1) && (errno == ENOENT)) { fs::mkdir(clonetgt,01777); rv = fs::clonepath(clonesrc,clonetgt,oldfusepath_.parent_path()); } if(rv == -1) goto error; clonesrc += oldfusepath_; clonetgt += oldfusepath_; rv = fs::rename(clonesrc,clonetgt); if(rv == -1) goto error; } return 0; error: l::rename_exdev_rename_back(*basepaths_,oldfusepath_); return -EXDEV; } static int rename_exdev_rel_symlink(const Policy::Action &actionPolicy_, const Branches::CPtr &branches_, const gfs::path &oldfusepath_, const gfs::path &newfusepath_) { int rv; StrVec basepaths; gfs::path target; gfs::path linkpath; rv = l::rename_exdev_rename_target(actionPolicy_,branches_,oldfusepath_,&basepaths); if(rv < 0) return rv; linkpath = newfusepath_; target = "/.mergerfs_rename_exdev"; target += oldfusepath_; target = target.lexically_relative(linkpath.parent_path()); rv = FUSE::symlink(target.c_str(),linkpath.c_str()); if(rv < 0) l::rename_exdev_rename_back(basepaths,oldfusepath_); return rv; } static int rename_exdev_abs_symlink(const Policy::Action &actionPolicy_, const Branches::CPtr &branches_, const std::string &mount_, const gfs::path &oldfusepath_, const gfs::path &newfusepath_) { int rv; StrVec basepaths; gfs::path target; gfs::path linkpath; rv = l::rename_exdev_rename_target(actionPolicy_,branches_,oldfusepath_,&basepaths); if(rv < 0) return rv; linkpath = newfusepath_; target = mount_; target /= ".mergerfs_rename_exdev"; target += oldfusepath_; rv = FUSE::symlink(target.c_str(),linkpath.c_str()); if(rv < 0) l::rename_exdev_rename_back(basepaths,oldfusepath_); return rv; } static int rename_exdev(Config::Read &cfg_, const gfs::path &oldfusepath_, const gfs::path &newfusepath_) { switch(cfg_->rename_exdev) { case RenameEXDEV::ENUM::PASSTHROUGH: return -EXDEV; case RenameEXDEV::ENUM::REL_SYMLINK: return l::rename_exdev_rel_symlink(cfg_->func.rename.policy, cfg_->branches, oldfusepath_, newfusepath_); case RenameEXDEV::ENUM::ABS_SYMLINK: return l::rename_exdev_abs_symlink(cfg_->func.rename.policy, cfg_->branches, cfg_->mount, oldfusepath_, newfusepath_); } return -EXDEV; } static int rename(Config::Read &cfg_, const gfs::path &oldpath_, const gfs::path &newpath_) { if(cfg_->func.create.policy.path_preserving() && !cfg_->ignorepponrename) return l::rename_preserve_path(cfg_->func.rename.policy, cfg_->branches, oldpath_, newpath_); return l::rename_create_path(cfg_->func.getattr.policy, cfg_->func.rename.policy, cfg_->branches, oldpath_, newpath_); } } namespace FUSE { int rename(const char *oldfusepath_, const char *newfusepath_) { int rv; Config::Read cfg; gfs::path oldfusepath(oldfusepath_); gfs::path newfusepath(newfusepath_); const fuse_context *fc = fuse_get_context(); const ugid::Set ugid(fc->uid,fc->gid); rv = l::rename(cfg,oldfusepath,newfusepath); if(rv == -EXDEV) return l::rename_exdev(cfg,oldfusepath,newfusepath); return rv; } } mergerfs-2.33.5/src/fs_ficlone.hpp0000644000000000000000000000160314225522122015520 0ustar rootroot/* ISC License Copyright (c) 2018, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once namespace fs { int ficlone(const int src_fd, const int dst_fd); } mergerfs-2.33.5/src/fs_statvfs_cache.cpp0000644000000000000000000000547614225522122016725 0ustar rootroot/* ISC License Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "fs_statvfs.hpp" #include "statvfs_util.hpp" #include #include #include #include #include #include struct Element { uint64_t time; struct statvfs st; }; typedef std::map statvfs_cache; static uint64_t g_timeout = 0; static statvfs_cache g_cache; static pthread_mutex_t g_cache_lock = PTHREAD_MUTEX_INITIALIZER; namespace l { static uint64_t get_time(void) { uint64_t rv; rv = ::time(NULL); return rv; } } namespace fs { uint64_t statvfs_cache_timeout(void) { return g_timeout; } void statvfs_cache_timeout(const uint64_t timeout_) { g_timeout = timeout_; } int statvfs_cache(const char *path_, struct statvfs *st_) { int rv; Element *e; uint64_t now; if(g_timeout == 0) return fs::statvfs(path_,st_); rv = 0; now = l::get_time(); pthread_mutex_lock(&g_cache_lock); e = &g_cache[path_]; if((now - e->time) > g_timeout) { e->time = now; rv = fs::statvfs(path_,&e->st); } *st_ = e->st; pthread_mutex_unlock(&g_cache_lock); return rv; } int statvfs_cache_readonly(const std::string &path_, bool *readonly_) { int rv; struct statvfs st; rv = fs::statvfs_cache(path_.c_str(),&st); if(rv == 0) *readonly_ = StatVFS::readonly(st); return rv; } int statvfs_cache_spaceavail(const std::string &path_, uint64_t *spaceavail_) { int rv; struct statvfs st; rv = fs::statvfs_cache(path_.c_str(),&st); if(rv == 0) *spaceavail_ = StatVFS::spaceavail(st); return rv; } int statvfs_cache_spaceused(const std::string &path_, uint64_t *spaceused_) { int rv; struct statvfs st; rv = fs::statvfs_cache(path_.c_str(),&st); if(rv == 0) *spaceused_ = StatVFS::spaceused(st); return rv; } } mergerfs-2.33.5/src/fuse_release.cpp0000644000000000000000000000300714225522122016046 0ustar rootroot/* Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "config.hpp" #include "errno.hpp" #include "fileinfo.hpp" #include "fs_close.hpp" #include "fs_fadvise.hpp" #include "fuse.h" #include namespace l { static int release(FileInfo *fi_, const bool dropcacheonclose_) { // according to Feh of nocache calling it once doesn't always work // https://github.com/Feh/nocache if(dropcacheonclose_) { fs::fadvise_dontneed(fi_->fd); fs::fadvise_dontneed(fi_->fd); } fs::close(fi_->fd); delete fi_; return 0; } } namespace FUSE { int release(const fuse_file_info_t *ffi_) { Config::Read cfg; FileInfo *fi = reinterpret_cast(ffi_->fh); return l::release(fi,cfg->dropcacheonclose); } } mergerfs-2.33.5/src/khash.h0000644000000000000000000005204714225522122014157 0ustar rootroot/* The MIT License Copyright (c) 2008, 2009, 2011 by Attractive Chaos 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. */ /* An example: #include "khash.h" KHASH_MAP_INIT_INT(32, char) int main() { int ret, is_missing; khiter_t k; khash_t(32) *h = kh_init(32); k = kh_put(32, h, 5, &ret); kh_value(h, k) = 10; k = kh_get(32, h, 10); is_missing = (k == kh_end(h)); k = kh_get(32, h, 5); kh_del(32, h, k); for (k = kh_begin(h); k != kh_end(h); ++k) if (kh_exist(h, k)) kh_value(h, k) = 1; kh_destroy(32, h); return 0; } */ /* 2013-05-02 (0.2.8): * Use quadratic probing. When the capacity is power of 2, stepping function i*(i+1)/2 guarantees to traverse each bucket. It is better than double hashing on cache performance and is more robust than linear probing. In theory, double hashing should be more robust than quadratic probing. However, my implementation is probably not for large hash tables, because the second hash function is closely tied to the first hash function, which reduce the effectiveness of double hashing. Reference: http://research.cs.vt.edu/AVresearch/hashing/quadratic.php 2011-12-29 (0.2.7): * Minor code clean up; no actual effect. 2011-09-16 (0.2.6): * The capacity is a power of 2. This seems to dramatically improve the speed for simple keys. Thank Zilong Tan for the suggestion. Reference: - http://code.google.com/p/ulib/ - http://nothings.org/computer/judy/ * Allow to optionally use linear probing which usually has better performance for random input. Double hashing is still the default as it is more robust to certain non-random input. * Added Wang's integer hash function (not used by default). This hash function is more robust to certain non-random input. 2011-02-14 (0.2.5): * Allow to declare global functions. 2009-09-26 (0.2.4): * Improve portability 2008-09-19 (0.2.3): * Corrected the example * Improved interfaces 2008-09-11 (0.2.2): * Improved speed a little in kh_put() 2008-09-10 (0.2.1): * Added kh_clear() * Fixed a compiling error 2008-09-02 (0.2.0): * Changed to token concatenation which increases flexibility. 2008-08-31 (0.1.2): * Fixed a bug in kh_get(), which has not been tested previously. 2008-08-31 (0.1.1): * Added destructor */ #ifndef __AC_KHASH_H #define __AC_KHASH_H /*! @header Generic hash table library. */ #define AC_VERSION_KHASH_H "0.2.8" #include #include #include /* compiler specific configuration */ #if UINT_MAX == 0xffffffffu typedef unsigned int khint32_t; #elif ULONG_MAX == 0xffffffffu typedef unsigned long khint32_t; #endif #if ULONG_MAX == ULLONG_MAX typedef unsigned long khint64_t; #else typedef unsigned long long khint64_t; #endif #ifndef kh_inline #ifdef _MSC_VER #define kh_inline __inline #else #define kh_inline inline #endif #endif /* kh_inline */ #ifndef klib_unused #if (defined __clang__ && __clang_major__ >= 3) || (defined __GNUC__ && __GNUC__ >= 3) #define klib_unused __attribute__ ((__unused__)) #else #define klib_unused #endif #endif /* klib_unused */ typedef khint32_t khint_t; typedef khint_t khiter_t; #define __ac_isempty(flag, i) ((flag[i>>4]>>((i&0xfU)<<1))&2) #define __ac_isdel(flag, i) ((flag[i>>4]>>((i&0xfU)<<1))&1) #define __ac_iseither(flag, i) ((flag[i>>4]>>((i&0xfU)<<1))&3) #define __ac_set_isdel_false(flag, i) (flag[i>>4]&=~(1ul<<((i&0xfU)<<1))) #define __ac_set_isempty_false(flag, i) (flag[i>>4]&=~(2ul<<((i&0xfU)<<1))) #define __ac_set_isboth_false(flag, i) (flag[i>>4]&=~(3ul<<((i&0xfU)<<1))) #define __ac_set_isdel_true(flag, i) (flag[i>>4]|=1ul<<((i&0xfU)<<1)) #define __ac_fsize(m) ((m) < 16? 1 : (m)>>4) #ifndef kroundup32 #define kroundup32(x) (--(x), (x)|=(x)>>1, (x)|=(x)>>2, (x)|=(x)>>4, (x)|=(x)>>8, (x)|=(x)>>16, ++(x)) #endif #ifndef kcalloc #define kcalloc(N,Z) calloc(N,Z) #endif #ifndef kmalloc #define kmalloc(Z) malloc(Z) #endif #ifndef krealloc #define krealloc(P,Z) realloc(P,Z) #endif #ifndef kfree #define kfree(P) free(P) #endif static const double __ac_HASH_UPPER = 0.77; #define __KHASH_TYPE(name, khkey_t, khval_t) \ typedef struct kh_##name##_s { \ khint_t n_buckets, size, n_occupied, upper_bound; \ khint32_t *flags; \ khkey_t *keys; \ khval_t *vals; \ } kh_##name##_t; #define __KHASH_PROTOTYPES(name, khkey_t, khval_t) \ extern kh_##name##_t *kh_init_##name(void); \ extern void kh_destroy_##name(kh_##name##_t *h); \ extern void kh_clear_##name(kh_##name##_t *h); \ extern khint_t kh_get_##name(const kh_##name##_t *h, khkey_t key); \ extern int kh_resize_##name(kh_##name##_t *h, khint_t new_n_buckets); \ extern khint_t kh_put_##name(kh_##name##_t *h, khkey_t key, int *ret); \ extern void kh_del_##name(kh_##name##_t *h, khint_t x); #define __KHASH_IMPL(name, SCOPE, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) \ SCOPE kh_##name##_t *kh_init_##name(void) { \ return (kh_##name##_t*)kcalloc(1, sizeof(kh_##name##_t)); \ } \ SCOPE void kh_destroy_##name(kh_##name##_t *h) \ { \ if (h) { \ kfree((void *)h->keys); kfree(h->flags); \ kfree((void *)h->vals); \ kfree(h); \ } \ } \ SCOPE void kh_clear_##name(kh_##name##_t *h) \ { \ if (h && h->flags) { \ memset(h->flags, 0xaa, __ac_fsize(h->n_buckets) * sizeof(khint32_t)); \ h->size = h->n_occupied = 0; \ } \ } \ SCOPE khint_t kh_get_##name(const kh_##name##_t *h, khkey_t key) \ { \ if (h->n_buckets) { \ khint_t k, i, last, mask, step = 0; \ mask = h->n_buckets - 1; \ k = __hash_func(key); i = k & mask; \ last = i; \ while (!__ac_isempty(h->flags, i) && (__ac_isdel(h->flags, i) || !__hash_equal(h->keys[i], key))) { \ i = (i + (++step)) & mask; \ if (i == last) return h->n_buckets; \ } \ return __ac_iseither(h->flags, i)? h->n_buckets : i; \ } else return 0; \ } \ SCOPE int kh_resize_##name(kh_##name##_t *h, khint_t new_n_buckets) \ { /* This function uses 0.25*n_buckets bytes of working space instead of [sizeof(key_t+val_t)+.25]*n_buckets. */ \ khint32_t *new_flags = 0; \ khint_t j = 1; \ { \ kroundup32(new_n_buckets); \ if (new_n_buckets < 4) new_n_buckets = 4; \ if (h->size >= (khint_t)(new_n_buckets * __ac_HASH_UPPER + 0.5)) j = 0; /* requested size is too small */ \ else { /* hash table size to be changed (shrink or expand); rehash */ \ new_flags = (khint32_t*)kmalloc(__ac_fsize(new_n_buckets) * sizeof(khint32_t)); \ if (!new_flags) return -1; \ memset(new_flags, 0xaa, __ac_fsize(new_n_buckets) * sizeof(khint32_t)); \ if (h->n_buckets < new_n_buckets) { /* expand */ \ khkey_t *new_keys = (khkey_t*)krealloc((void *)h->keys, new_n_buckets * sizeof(khkey_t)); \ if (!new_keys) { kfree(new_flags); return -1; } \ h->keys = new_keys; \ if (kh_is_map) { \ khval_t *new_vals = (khval_t*)krealloc((void *)h->vals, new_n_buckets * sizeof(khval_t)); \ if (!new_vals) { kfree(new_flags); return -1; } \ h->vals = new_vals; \ } \ } /* otherwise shrink */ \ } \ } \ if (j) { /* rehashing is needed */ \ for (j = 0; j != h->n_buckets; ++j) { \ if (__ac_iseither(h->flags, j) == 0) { \ khkey_t key = h->keys[j]; \ khval_t val; \ khint_t new_mask; \ new_mask = new_n_buckets - 1; \ if (kh_is_map) val = h->vals[j]; \ __ac_set_isdel_true(h->flags, j); \ while (1) { /* kick-out process; sort of like in Cuckoo hashing */ \ khint_t k, i, step = 0; \ k = __hash_func(key); \ i = k & new_mask; \ while (!__ac_isempty(new_flags, i)) i = (i + (++step)) & new_mask; \ __ac_set_isempty_false(new_flags, i); \ if (i < h->n_buckets && __ac_iseither(h->flags, i) == 0) { /* kick out the existing element */ \ { khkey_t tmp = h->keys[i]; h->keys[i] = key; key = tmp; } \ if (kh_is_map) { khval_t tmp = h->vals[i]; h->vals[i] = val; val = tmp; } \ __ac_set_isdel_true(h->flags, i); /* mark it as deleted in the old hash table */ \ } else { /* write the element and jump out of the loop */ \ h->keys[i] = key; \ if (kh_is_map) h->vals[i] = val; \ break; \ } \ } \ } \ } \ if (h->n_buckets > new_n_buckets) { /* shrink the hash table */ \ h->keys = (khkey_t*)krealloc((void *)h->keys, new_n_buckets * sizeof(khkey_t)); \ if (kh_is_map) h->vals = (khval_t*)krealloc((void *)h->vals, new_n_buckets * sizeof(khval_t)); \ } \ kfree(h->flags); /* free the working space */ \ h->flags = new_flags; \ h->n_buckets = new_n_buckets; \ h->n_occupied = h->size; \ h->upper_bound = (khint_t)(h->n_buckets * __ac_HASH_UPPER + 0.5); \ } \ return 0; \ } \ SCOPE khint_t kh_put_##name(kh_##name##_t *h, khkey_t key, int *ret) \ { \ khint_t x; \ if (h->n_occupied >= h->upper_bound) { /* update the hash table */ \ if (h->n_buckets > (h->size<<1)) { \ if (kh_resize_##name(h, h->n_buckets - 1) < 0) { /* clear "deleted" elements */ \ *ret = -1; return h->n_buckets; \ } \ } else if (kh_resize_##name(h, h->n_buckets + 1) < 0) { /* expand the hash table */ \ *ret = -1; return h->n_buckets; \ } \ } /* TODO: to implement automatically shrinking; resize() already support shrinking */ \ { \ khint_t k, i, site, last, mask = h->n_buckets - 1, step = 0; \ x = site = h->n_buckets; k = __hash_func(key); i = k & mask; \ if (__ac_isempty(h->flags, i)) x = i; /* for speed up */ \ else { \ last = i; \ while (!__ac_isempty(h->flags, i) && (__ac_isdel(h->flags, i) || !__hash_equal(h->keys[i], key))) { \ if (__ac_isdel(h->flags, i)) site = i; \ i = (i + (++step)) & mask; \ if (i == last) { x = site; break; } \ } \ if (x == h->n_buckets) { \ if (__ac_isempty(h->flags, i) && site != h->n_buckets) x = site; \ else x = i; \ } \ } \ } \ if (__ac_isempty(h->flags, x)) { /* not present at all */ \ h->keys[x] = key; \ __ac_set_isboth_false(h->flags, x); \ ++h->size; ++h->n_occupied; \ *ret = 1; \ } else if (__ac_isdel(h->flags, x)) { /* deleted */ \ h->keys[x] = key; \ __ac_set_isboth_false(h->flags, x); \ ++h->size; \ *ret = 2; \ } else *ret = 0; /* Don't touch h->keys[x] if present and not deleted */ \ return x; \ } \ SCOPE void kh_del_##name(kh_##name##_t *h, khint_t x) \ { \ if (x != h->n_buckets && !__ac_iseither(h->flags, x)) { \ __ac_set_isdel_true(h->flags, x); \ --h->size; \ } \ } #define KHASH_DECLARE(name, khkey_t, khval_t) \ __KHASH_TYPE(name, khkey_t, khval_t) \ __KHASH_PROTOTYPES(name, khkey_t, khval_t) #define KHASH_INIT2(name, SCOPE, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) \ __KHASH_TYPE(name, khkey_t, khval_t) \ __KHASH_IMPL(name, SCOPE, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) #define KHASH_INIT(name, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) \ KHASH_INIT2(name, static kh_inline klib_unused, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) /* --- BEGIN OF HASH FUNCTIONS --- */ /*! @function @abstract Integer hash function @param key The integer [khint32_t] @return The hash value [khint_t] */ #define kh_int_hash_func(key) (khint32_t)(key) /*! @function @abstract Integer comparison function */ #define kh_int_hash_equal(a, b) ((a) == (b)) /*! @function @abstract 64-bit integer hash function @param key The integer [khint64_t] @return The hash value [khint_t] */ #define kh_int64_hash_func(key) (khint32_t)((key)>>33^(key)^(key)<<11) /*! @function @abstract 64-bit integer comparison function */ #define kh_int64_hash_equal(a, b) ((a) == (b)) /*! @function @abstract const char* hash function @param s Pointer to a null terminated string @return The hash value */ static kh_inline khint_t __ac_X31_hash_string(const char *s) { khint_t h = (khint_t)*s; if (h) for (++s ; *s; ++s) h = (h << 5) - h + (khint_t)*s; return h; } /*! @function @abstract Another interface to const char* hash function @param key Pointer to a null terminated string [const char*] @return The hash value [khint_t] */ #define kh_str_hash_func(key) __ac_X31_hash_string(key) /*! @function @abstract Const char* comparison function */ #define kh_str_hash_equal(a, b) (strcmp(a, b) == 0) static kh_inline khint_t __ac_Wang_hash(khint_t key) { key += ~(key << 15); key ^= (key >> 10); key += (key << 3); key ^= (key >> 6); key += ~(key << 11); key ^= (key >> 16); return key; } #define kh_int_hash_func2(key) __ac_Wang_hash((khint_t)key) /* --- END OF HASH FUNCTIONS --- */ /* Other convenient macros... */ /*! @abstract Type of the hash table. @param name Name of the hash table [symbol] */ #define khash_t(name) kh_##name##_t /*! @function @abstract Initiate a hash table. @param name Name of the hash table [symbol] @return Pointer to the hash table [khash_t(name)*] */ #define kh_init(name) kh_init_##name() /*! @function @abstract Destroy a hash table. @param name Name of the hash table [symbol] @param h Pointer to the hash table [khash_t(name)*] */ #define kh_destroy(name, h) kh_destroy_##name(h) /*! @function @abstract Reset a hash table without deallocating memory. @param name Name of the hash table [symbol] @param h Pointer to the hash table [khash_t(name)*] */ #define kh_clear(name, h) kh_clear_##name(h) /*! @function @abstract Resize a hash table. @param name Name of the hash table [symbol] @param h Pointer to the hash table [khash_t(name)*] @param s New size [khint_t] */ #define kh_resize(name, h, s) kh_resize_##name(h, s) /*! @function @abstract Insert a key to the hash table. @param name Name of the hash table [symbol] @param h Pointer to the hash table [khash_t(name)*] @param k Key [type of keys] @param r Extra return code: -1 if the operation failed; 0 if the key is present in the hash table; 1 if the bucket is empty (never used); 2 if the element in the bucket has been deleted [int*] @return Iterator to the inserted element [khint_t] */ #define kh_put(name, h, k, r) kh_put_##name(h, k, r) /*! @function @abstract Retrieve a key from the hash table. @param name Name of the hash table [symbol] @param h Pointer to the hash table [khash_t(name)*] @param k Key [type of keys] @return Iterator to the found element, or kh_end(h) if the element is absent [khint_t] */ #define kh_get(name, h, k) kh_get_##name(h, k) /*! @function @abstract Remove a key from the hash table. @param name Name of the hash table [symbol] @param h Pointer to the hash table [khash_t(name)*] @param k Iterator to the element to be deleted [khint_t] */ #define kh_del(name, h, k) kh_del_##name(h, k) /*! @function @abstract Test whether a bucket contains data. @param h Pointer to the hash table [khash_t(name)*] @param x Iterator to the bucket [khint_t] @return 1 if containing data; 0 otherwise [int] */ #define kh_exist(h, x) (!__ac_iseither((h)->flags, (x))) /*! @function @abstract Get key given an iterator @param h Pointer to the hash table [khash_t(name)*] @param x Iterator to the bucket [khint_t] @return Key [type of keys] */ #define kh_key(h, x) ((h)->keys[x]) /*! @function @abstract Get value given an iterator @param h Pointer to the hash table [khash_t(name)*] @param x Iterator to the bucket [khint_t] @return Value [type of values] @discussion For hash sets, calling this results in segfault. */ #define kh_val(h, x) ((h)->vals[x]) /*! @function @abstract Alias of kh_val() */ #define kh_value(h, x) ((h)->vals[x]) /*! @function @abstract Get the start iterator @param h Pointer to the hash table [khash_t(name)*] @return The start iterator [khint_t] */ #define kh_begin(h) (khint_t)(0) /*! @function @abstract Get the end iterator @param h Pointer to the hash table [khash_t(name)*] @return The end iterator [khint_t] */ #define kh_end(h) ((h)->n_buckets) /*! @function @abstract Get the number of elements in the hash table @param h Pointer to the hash table [khash_t(name)*] @return Number of elements in the hash table [khint_t] */ #define kh_size(h) ((h)->size) /*! @function @abstract Get the number of buckets in the hash table @param h Pointer to the hash table [khash_t(name)*] @return Number of buckets in the hash table [khint_t] */ #define kh_n_buckets(h) ((h)->n_buckets) /*! @function @abstract Iterate over the entries in the hash table @param h Pointer to the hash table [khash_t(name)*] @param kvar Variable to which key will be assigned @param vvar Variable to which value will be assigned @param code Block of code to execute */ #define kh_foreach(h, kvar, vvar, code) { khint_t __i; \ for (__i = kh_begin(h); __i != kh_end(h); ++__i) { \ if (!kh_exist(h,__i)) continue; \ (kvar) = kh_key(h,__i); \ (vvar) = kh_val(h,__i); \ code; \ } } /*! @function @abstract Iterate over the values in the hash table @param h Pointer to the hash table [khash_t(name)*] @param vvar Variable to which value will be assigned @param code Block of code to execute */ #define kh_foreach_value(h, vvar, code) { khint_t __i; \ for (__i = kh_begin(h); __i != kh_end(h); ++__i) { \ if (!kh_exist(h,__i)) continue; \ (vvar) = kh_val(h,__i); \ code; \ } } /* More convenient interfaces */ /*! @function @abstract Instantiate a hash set containing integer keys @param name Name of the hash table [symbol] */ #define KHASH_SET_INIT_INT(name) \ KHASH_INIT(name, khint32_t, char, 0, kh_int_hash_func, kh_int_hash_equal) /*! @function @abstract Instantiate a hash map containing integer keys @param name Name of the hash table [symbol] @param khval_t Type of values [type] */ #define KHASH_MAP_INIT_INT(name, khval_t) \ KHASH_INIT(name, khint32_t, khval_t, 1, kh_int_hash_func, kh_int_hash_equal) /*! @function @abstract Instantiate a hash set containing 64-bit integer keys @param name Name of the hash table [symbol] */ #define KHASH_SET_INIT_INT64(name) \ KHASH_INIT(name, khint64_t, char, 0, kh_int64_hash_func, kh_int64_hash_equal) /*! @function @abstract Instantiate a hash map containing 64-bit integer keys @param name Name of the hash table [symbol] @param khval_t Type of values [type] */ #define KHASH_MAP_INIT_INT64(name, khval_t) \ KHASH_INIT(name, khint64_t, khval_t, 1, kh_int64_hash_func, kh_int64_hash_equal) typedef const char *kh_cstr_t; /*! @function @abstract Instantiate a hash map containing const char* keys @param name Name of the hash table [symbol] */ #define KHASH_SET_INIT_STR(name) \ KHASH_INIT(name, kh_cstr_t, char, 0, kh_str_hash_func, kh_str_hash_equal) /*! @function @abstract Instantiate a hash map containing const char* keys @param name Name of the hash table [symbol] @param khval_t Type of values [type] */ #define KHASH_MAP_INIT_STR(name, khval_t) \ KHASH_INIT(name, kh_cstr_t, khval_t, 1, kh_str_hash_func, kh_str_hash_equal) #endif /* __AC_KHASH_H */ mergerfs-2.33.5/src/fs_has_space.cpp0000644000000000000000000000220514225522122016021 0ustar rootroot/* ISC License Copyright (c) 2020, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "fs_statvfs.hpp" #include "statvfs_util.hpp" #include #include namespace fs { bool has_space(const std::string &str_, const int64_t size_) { int rv; struct statvfs st; rv = fs::statvfs(str_,&st); if(rv == -1) return false; return (StatVFS::spaceavail(st) > size_); } } mergerfs-2.33.5/src/fuse_create.cpp0000644000000000000000000001165214225522122015676 0ustar rootroot/* Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "config.hpp" #include "errno.hpp" #include "fileinfo.hpp" #include "fs_acl.hpp" #include "fs_clonepath.hpp" #include "fs_open.hpp" #include "fs_path.hpp" #include "ugid.hpp" #include "fuse.h" #include #include using std::string; using std::vector; namespace l { /* The kernel expects being able to issue read requests when running with writeback caching enabled so we must change O_WRONLY to O_RDWR. With writeback caching enabled the kernel handles O_APPEND. Could be an issue if the underlying file changes out of band but that is true of any caching. */ static void tweak_flags_writeback_cache(int *flags_) { if((*flags_ & O_ACCMODE) == O_WRONLY) *flags_ = ((*flags_ & ~O_ACCMODE) | O_RDWR); if(*flags_ & O_APPEND) *flags_ &= ~O_APPEND; } static void config_to_ffi_flags(Config::Read &cfg_, fuse_file_info_t *ffi_) { switch(cfg_->cache_files) { case CacheFiles::ENUM::LIBFUSE: ffi_->direct_io = cfg_->direct_io; ffi_->keep_cache = cfg_->kernel_cache; ffi_->auto_cache = cfg_->auto_cache; break; case CacheFiles::ENUM::OFF: ffi_->direct_io = 1; ffi_->keep_cache = 0; ffi_->auto_cache = 0; break; case CacheFiles::ENUM::PARTIAL: ffi_->direct_io = 0; ffi_->keep_cache = 0; ffi_->auto_cache = 0; break; case CacheFiles::ENUM::FULL: ffi_->direct_io = 0; ffi_->keep_cache = 1; ffi_->auto_cache = 0; break; case CacheFiles::ENUM::AUTO_FULL: ffi_->direct_io = 0; ffi_->keep_cache = 0; ffi_->auto_cache = 1; break; } } static int create_core(const string &fullpath_, mode_t mode_, const mode_t umask_, const int flags_) { if(!fs::acl::dir_has_defaults(fullpath_)) mode_ &= ~umask_; return fs::open(fullpath_,flags_,mode_); } static int create_core(const string &createpath_, const char *fusepath_, const mode_t mode_, const mode_t umask_, const int flags_, uint64_t *fh_) { int rv; string fullpath; fullpath = fs::path::make(createpath_,fusepath_); rv = l::create_core(fullpath,mode_,umask_,flags_); if(rv == -1) return -errno; *fh_ = reinterpret_cast(new FileInfo(rv,fusepath_)); return 0; } static int create(const Policy::Search &searchFunc_, const Policy::Create &createFunc_, const Branches &branches_, const char *fusepath_, const mode_t mode_, const mode_t umask_, const int flags_, uint64_t *fh_) { int rv; string fullpath; string fusedirpath; StrVec createpaths; StrVec existingpaths; fusedirpath = fs::path::dirname(fusepath_); rv = searchFunc_(branches_,fusedirpath,&existingpaths); if(rv == -1) return -errno; rv = createFunc_(branches_,fusedirpath,&createpaths); if(rv == -1) return -errno; rv = fs::clonepath_as_root(existingpaths[0],createpaths[0],fusedirpath); if(rv == -1) return -errno; return l::create_core(createpaths[0], fusepath_, mode_, umask_, flags_, fh_); } } namespace FUSE { int create(const char *fusepath_, mode_t mode_, fuse_file_info_t *ffi_) { Config::Read cfg; const fuse_context *fc = fuse_get_context(); const ugid::Set ugid(fc->uid,fc->gid); l::config_to_ffi_flags(cfg,ffi_); if(cfg->writeback_cache) l::tweak_flags_writeback_cache(&ffi_->flags); return l::create(cfg->func.getattr.policy, cfg->func.create.policy, cfg->branches, fusepath_, mode_, fc->umask, ffi_->flags, &ffi_->fh); } } mergerfs-2.33.5/src/policy_msplfs.cpp0000644000000000000000000000662614225522122016301 0ustar rootroot/* Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "errno.hpp" #include "fs_exists.hpp" #include "fs_info.hpp" #include "fs_path.hpp" #include "fs_statvfs_cache.hpp" #include "policies.hpp" #include "policy.hpp" #include "policy_error.hpp" #include "policy_msplfs.hpp" #include "strvec.hpp" #include #include using std::string; namespace msplfs { static const string* create_1(const Branches::CPtr &branches_, const string &fusepath_, int *err_) { int rv; uint64_t lfs; fs::info_t info; const string *basepath; basepath = NULL; lfs = std::numeric_limits::max(); for(const auto &branch : *branches_) { if(branch.ro_or_nc()) error_and_continue(*err_,EROFS); if(!fs::exists(branch.path,fusepath_)) error_and_continue(*err_,ENOENT); rv = fs::info(branch.path,&info); if(rv == -1) error_and_continue(*err_,ENOENT); if(info.readonly) error_and_continue(*err_,EROFS); if(info.spaceavail < branch.minfreespace()) error_and_continue(*err_,ENOSPC); if(info.spaceavail > lfs) continue; lfs = info.spaceavail; basepath = &branch.path; } return basepath; } static int create(const Branches::CPtr &branches_, const char *fusepath_, StrVec *paths_) { int error; string fusepath; const string *basepath; error = ENOENT; fusepath = fusepath_; for(;;) { basepath = msplfs::create_1(branches_,fusepath,&error); if(basepath) break; if(fusepath == "/") break; fusepath = fs::path::dirname(fusepath); } if(basepath == NULL) return (errno=error,-1); paths_->push_back(*basepath); return 0; } } int Policy::MSPLFS::Action::operator()(const Branches::CPtr &branches_, const char *fusepath_, StrVec *paths_) const { return Policies::Action::eplfs(branches_,fusepath_,paths_); } int Policy::MSPLFS::Create::operator()(const Branches::CPtr &branches_, const char *fusepath_, StrVec *paths_) const { return ::msplfs::create(branches_,fusepath_,paths_); } int Policy::MSPLFS::Search::operator()(const Branches::CPtr &branches_, const char *fusepath_, StrVec *paths_) const { return Policies::Search::eplfs(branches_,fusepath_,paths_); } mergerfs-2.33.5/src/fuse_write_buf.cpp0000644000000000000000000000536314225522122016423 0ustar rootroot/* Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "config.hpp" #include "errno.hpp" #include "fileinfo.hpp" #include "fs_movefile.hpp" #include "fuse_write.hpp" #include "fuse.h" #include #include #include #include using std::string; using std::vector; namespace l { static bool out_of_space(const int error_) { return ((error_ == ENOSPC) || (error_ == EDQUOT)); } static int write_buf(const int fd_, fuse_bufvec *src_, const off_t offset_) { size_t size = fuse_buf_size(src_); fuse_bufvec dst = FUSE_BUFVEC_INIT(size); const fuse_buf_copy_flags cpflags = (fuse_buf_copy_flags)(FUSE_BUF_SPLICE_MOVE|FUSE_BUF_SPLICE_NONBLOCK); dst.buf->flags = (fuse_buf_flags)(FUSE_BUF_IS_FD|FUSE_BUF_FD_SEEK); dst.buf->fd = fd_; dst.buf->pos = offset_; return fuse_buf_copy(&dst,src_,cpflags); } static int move_and_write_buf(FileInfo *fi_, fuse_bufvec *src_, off_t offset_, int err_) { int rv; Config::Read cfg; if(cfg->moveonenospc.enabled == false) return err_; rv = fs::movefile_as_root(cfg->moveonenospc.policy, cfg->branches, fi_->fusepath, &fi_->fd); if(rv == -1) return err_; return l::write_buf(fi_->fd,src_,offset_); } } namespace FUSE { int write_buf(const fuse_file_info_t *ffi_, fuse_bufvec *src_, off_t offset_) { int rv; FileInfo *fi = reinterpret_cast(ffi_->fh); rv = l::write_buf(fi->fd,src_,offset_); if(l::out_of_space(-rv)) rv = l::move_and_write_buf(fi,src_,offset_,rv); return rv; } int write_buf_null(const fuse_file_info_t *ffi_, fuse_bufvec *src_, off_t offset_) { return src_->buf[0].size; } } mergerfs-2.33.5/src/fs_getfl.cpp0000644000000000000000000000162214225522122015176 0ustar rootroot/* ISC License Copyright (c) 2020, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include namespace fs { int getfl(const int fd_) { return ::fcntl(fd_,F_GETFL,0); } } mergerfs-2.33.5/src/fs_setfl.cpp0000644000000000000000000000166514225522122015221 0ustar rootroot/* ISC License Copyright (c) 2020, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include namespace fs { int setfl(const int fd_, const mode_t mode_) { return ::fcntl(fd_,F_SETFL,mode_); } } mergerfs-2.33.5/src/config_inodecalc.cpp0000644000000000000000000000212414225522122016651 0ustar rootroot/* ISC License Copyright (c) 2020, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "config_inodecalc.hpp" #include "fs_inode.hpp" InodeCalc::InodeCalc(const std::string &s_) { fs::inode::set_algo(s_); } std::string InodeCalc::to_string(void) const { return fs::inode::get_algo(); } int InodeCalc::from_string(const std::string &s_) { return fs::inode::set_algo(s_); } mergerfs-2.33.5/src/fs_getdents64.cpp0000644000000000000000000000221314225522122016061 0ustar rootroot/* ISC License Copyright (c) 2020, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "errno.hpp" #if defined __linux__ #include #include #endif namespace fs { int getdents_64(unsigned int fd_, void *dirp_, unsigned int count_) { #if defined SYS_getdents64 return ::syscall(SYS_getdents64,fd_,dirp_,count_); #else return (errno=ENOTSUP,-1); #endif } } mergerfs-2.33.5/src/fuse_readdir_linux.hpp0000644000000000000000000000202714225522122017265 0ustar rootroot/* ISC License Copyright (c) 2020, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include "branches.hpp" #include "fuse.h" #include namespace FUSE { int readdir_linux(const Branches::CPtr &branches, const char *dirname, fuse_dirents_t *buf); } mergerfs-2.33.5/src/config_log_metrics.hpp0000644000000000000000000000200014225522122017235 0ustar rootroot/* ISC License Copyright (c) 2021, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include "tofrom_string.hpp" class LogMetrics : public ToFromString { public: LogMetrics(const bool); public: std::string to_string(void) const final; int from_string(const std::string &) final; }; mergerfs-2.33.5/src/fs_path.cpp0000644000000000000000000000276114225522122015036 0ustar rootroot/* Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "fs_path.hpp" #include #include #include #include #include using std::string; namespace fs { namespace path { string dirname(const char *path_) { string path(path_); return fs::path::dirname(path); } string dirname(const string &path_) { std::size_t i; i = path_.size() - 1; while((i > 0) && (path_[i] == '/')) i--; while((i > 0) && (path_[i] != '/')) i--; while((i > 0) && (path_[i] == '/')) i--; return path_.substr(0,i+1); } string basename(const string &path_) { return path_.substr(path_.find_last_of('/')+1); } } } mergerfs-2.33.5/src/fuse_truncate.cpp0000644000000000000000000000632214225522122016256 0ustar rootroot/* Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "config.hpp" #include "errno.hpp" #include "fs_path.hpp" #include "fs_truncate.hpp" #include "policy_rv.hpp" #include "ugid.hpp" #include "fuse.h" #include #include #include using std::string; namespace l { static int get_error(const PolicyRV &prv_, const string &basepath_) { for(int i = 0, ei = prv_.success.size(); i < ei; i++) { if(prv_.success[i].basepath == basepath_) return prv_.success[i].rv; } for(int i = 0, ei = prv_.error.size(); i < ei; i++) { if(prv_.error[i].basepath == basepath_) return prv_.error[i].rv; } return 0; } static void truncate_loop_core(const string &basepath_, const char *fusepath_, const off_t size_, PolicyRV *prv_) { string fullpath; fullpath = fs::path::make(basepath_,fusepath_); errno = 0; fs::truncate(fullpath,size_); prv_->insert(errno,basepath_); } static void truncate_loop(const StrVec &basepaths_, const char *fusepath_, const off_t size_, PolicyRV *prv_) { for(size_t i = 0, ei = basepaths_.size(); i != ei; i++) { l::truncate_loop_core(basepaths_[i],fusepath_,size_,prv_); } } static int truncate(const Policy::Action &actionFunc_, const Policy::Search &searchFunc_, const Branches &branches_, const char *fusepath_, const off_t size_) { int rv; PolicyRV prv; StrVec basepaths; rv = actionFunc_(branches_,fusepath_,&basepaths); if(rv == -1) return -errno; l::truncate_loop(basepaths,fusepath_,size_,&prv); if(prv.error.empty()) return 0; if(prv.success.empty()) return prv.error[0].rv; basepaths.clear(); rv = searchFunc_(branches_,fusepath_,&basepaths); if(rv == -1) return -errno; return l::get_error(prv,basepaths[0]); } } namespace FUSE { int truncate(const char *fusepath_, off_t size_) { Config::Read cfg; const fuse_context *fc = fuse_get_context(); const ugid::Set ugid(fc->uid,fc->gid); return l::truncate(cfg->func.truncate.policy, cfg->func.getattr.policy, cfg->branches, fusepath_, size_); } } mergerfs-2.33.5/src/resources.hpp0000644000000000000000000000174314225522122015430 0ustar rootroot/* Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once namespace resources { int reset_umask(void); int maxout_rlimit(const int resource); int maxout_rlimit_nofile(void); int maxout_rlimit_fsize(void); int setpriority(const int prio); } mergerfs-2.33.5/src/ugid_linux.hpp0000644000000000000000000000504314225522122015562 0ustar rootroot/* Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include #include #include #include #include #include #include #include #if defined SYS_setreuid32 #define SETREUID(R,E) (::syscall(SYS_setreuid32,(R),(E))) #else #define SETREUID(R,E) (::syscall(SYS_setreuid,(R),(E))) #endif #if defined SYS_setregid32 #define SETREGID(R,E) (::syscall(SYS_setregid32,(R),(E))) #else #define SETREGID(R,E) (::syscall(SYS_setregid,(R),(E))) #endif #if defined SYS_geteuid32 #define GETEUID() (::syscall(SYS_geteuid32)) #else #define GETEUID() (::syscall(SYS_geteuid)) #endif #if defined SYS_getegid32 #define GETEGID() (::syscall(SYS_getegid32)) #else #define GETEGID() (::syscall(SYS_getegid)) #endif namespace ugid { extern __thread uid_t currentuid; extern __thread gid_t currentgid; extern __thread bool initialized; struct Set { Set(const uid_t newuid_, const gid_t newgid_) { if(!initialized) { currentuid = GETEUID(); currentgid = GETEGID(); initialized = true; } if((newuid_ == currentuid) && (newgid_ == currentgid)) return; if(currentuid != 0) { SETREUID(-1,0); SETREGID(-1,0); } if(newgid_) { SETREGID(-1,newgid_); initgroups(newuid_,newgid_); } if(newuid_) SETREUID(-1,newuid_); currentuid = newuid_; currentgid = newgid_; } }; struct SetRootGuard { SetRootGuard() : prevuid(currentuid), prevgid(currentgid) { Set(0,0); } ~SetRootGuard() { Set(prevuid,prevgid); } const uid_t prevuid; const gid_t prevgid; }; } #undef SETREUID #undef SETREGID #undef GETEUID #undef GETEGID mergerfs-2.33.5/src/policy_pfrd.hpp0000644000000000000000000000313514225522122015725 0ustar rootroot/* ISC License Copyright (c) 2020, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include "policy.hpp" namespace Policy { namespace PFRD { class Action final : public Policy::ActionImpl { public: Action() : Policy::ActionImpl("pfrd") {} public: int operator()(const Branches::CPtr&,const char*,StrVec*) const final; }; class Create final : public Policy::CreateImpl { public: Create() : Policy::CreateImpl("pfrd") {} public: int operator()(const Branches::CPtr&,const char*,StrVec*) const final; bool path_preserving() const final { return false; } }; class Search final : public Policy::SearchImpl { public: Search() : Policy::SearchImpl("pfrd") {} public: int operator()(const Branches::CPtr&,const char*,StrVec*) const final; }; } } mergerfs-2.33.5/src/config_rename_exdev.hpp0000644000000000000000000000171014225522122017377 0ustar rootroot/* ISC License Copyright (c) 2021, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include "enum.hpp" enum class RenameEXDEVEnum { PASSTHROUGH, REL_SYMLINK, ABS_SYMLINK }; typedef Enum RenameEXDEV; mergerfs-2.33.5/src/fs_fdatasync.hpp0000644000000000000000000000210114225522122016047 0ustar rootroot/* ISC License Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #ifndef _GNU_SOURCE #define _GNU_SOURCE #endif #include "errno.hpp" #include namespace fs { static inline int fdatasync(const int fd_) { #if _POSIX_SYNCHRONIZED_IO > 0 return ::fdatasync(fd_); #else return (errno=ENOSYS,-1); #endif } } mergerfs-2.33.5/src/fs_futimesat.cpp0000644000000000000000000000172714225522122016104 0ustar rootroot/* ISC License Copyright (c) 2017, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #if __APPLE__ #warning "using fs_futimesat_osx.icpp" #include "fs_futimesat_osx.icpp" #else #warning "using fs_futimesat_generic.icpp" #include "fs_futimesat_generic.icpp" #endif mergerfs-2.33.5/src/fuse_readdir_plus_linux.cpp0000644000000000000000000000773014225522122020331 0ustar rootroot/* Copyright (c) 2019, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "branches.hpp" #include "errno.hpp" #include "fs_close.hpp" #include "fs_devid.hpp" #include "fs_fstatat.hpp" #include "fs_getdents64.hpp" #include "fs_inode.hpp" #include "fs_open.hpp" #include "fs_path.hpp" #include "fs_stat.hpp" #include "hashset.hpp" #include "linux_dirent64.h" #include "mempools.hpp" #include "fuse.h" #include "fuse_dirents.h" #include #include #include using std::string; using std::vector; namespace l { static int close_free_ret_enomem(int fd_, void *buf_) { fs::close(fd_); g_DENTS_BUF_POOL.free(buf_); return -ENOMEM; } static int readdir_plus(const Branches::CPtr &branches_, const char *dirname_, const uint64_t entry_timeout_, const uint64_t attr_timeout_, fuse_dirents_t *buf_) { int rv; dev_t dev; char *buf; HashSet names; string basepath; string fullpath; uint64_t namelen; struct stat st; fuse_entry_t entry; struct linux_dirent64 *d; fuse_dirents_reset(buf_); buf = (char*)g_DENTS_BUF_POOL.alloc(); entry.nodeid = 0; entry.generation = 0; entry.entry_valid = entry_timeout_; entry.attr_valid = attr_timeout_; entry.entry_valid_nsec = 0; entry.attr_valid_nsec = 0; for(auto &branch : *branches_) { int dirfd; int64_t nread; basepath = fs::path::make(branch.path,dirname_); dirfd = fs::open_dir_ro(basepath); if(dirfd == -1) continue; dev = fs::devid(dirfd); for(;;) { nread = fs::getdents_64(dirfd,buf,g_DENTS_BUF_POOL.size()); if(nread == -1) break; if(nread == 0) break; for(int64_t pos = 0; pos < nread; pos += d->reclen) { d = (struct linux_dirent64*)(buf + pos); namelen = (strlen(d->name) + 1); rv = names.put(d->name,namelen); if(rv == 0) continue; rv = fs::fstatat_nofollow(dirfd,d->name,&st); if(rv == -1) { memset(&st,0,sizeof(st)); st.st_ino = d->ino; st.st_dev = dev; st.st_mode = DTTOIF(d->type); } fullpath = fs::path::make(dirname_,d->name); fs::inode::calc(fullpath,&st); d->ino = st.st_ino; rv = fuse_dirents_add_linux_plus(buf_,d,namelen,&entry,&st); if(rv) return close_free_ret_enomem(dirfd,buf); } } fs::close(dirfd); } g_DENTS_BUF_POOL.free(buf); return 0; } } namespace FUSE { int readdir_plus_linux(const Branches::CPtr &branches_, const char *dirname_, const uint64_t entry_timeout_, const uint64_t attr_timeout_, fuse_dirents_t *buf_) { return l::readdir_plus(branches_,dirname_,entry_timeout_,attr_timeout_,buf_); } } mergerfs-2.33.5/src/fs_fallocate_posix.icpp0000644000000000000000000000207514225522122017425 0ustar rootroot/* Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "errno.hpp" #include namespace fs { int fallocate(const int fd_, const int mode_, const off_t offset_, const off_t len_) { if(mode_) return (errno=EOPNOTSUPP,-1); return ::posix_fallocate(fd_,offset_,len_); } } mergerfs-2.33.5/src/from_string.cpp0000644000000000000000000000471414225522122015743 0ustar rootroot/* ISC License Copyright (c) 2019, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "ef.hpp" #include "errno.hpp" #include #include #include namespace str { int from(const std::string &value_, bool *bool_) { if((value_ == "true") || (value_ == "1") || (value_ == "on") || (value_ == "yes")) *bool_ = true; ef((value_ == "false") || (value_ == "0") || (value_ == "off") || (value_ == "no")) *bool_ = false; else return -EINVAL; return 0; } int from(const std::string &value_, int *int_) { int tmp; char *endptr; errno = 0; tmp = ::strtol(value_.c_str(),&endptr,10); if(errno != 0) return -EINVAL; if(endptr == value_.c_str()) return -EINVAL; *int_ = tmp; return 0; } int from(const std::string &value_, uint64_t *uint64_) { char *endptr; uint64_t tmp; tmp = ::strtoll(value_.c_str(),&endptr,10); switch(*endptr) { case 'k': case 'K': tmp *= 1024ULL; break; case 'm': case 'M': tmp *= (1024ULL * 1024ULL); break; case 'g': case 'G': tmp *= (1024ULL * 1024ULL * 1024ULL); break; case 't': case 'T': tmp *= (1024ULL * 1024ULL * 1024ULL * 1024ULL); break; case '\0': break; default: return -EINVAL; } *uint64_ = tmp; return 0; } int from(const std::string &value_, std::string *str_) { *str_ = value_; return 0; } int from(const std::string &value_, const std::string *key_) { return -EINVAL; } } mergerfs-2.33.5/src/fs_clonepath.hpp0000644000000000000000000000321514225522122016057 0ustar rootroot/* Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include namespace fs { int clonepath(const std::string &from, const std::string &to, const char *relative, const bool return_metadata_errors = false); int clonepath(const std::string &from, const std::string &to, const std::string &relative, const bool return_metadata_errors = false); int clonepath_as_root(const std::string &from, const std::string &to, const char *relative, const bool return_metadata_errors = false); int clonepath_as_root(const std::string &from, const std::string &to, const std::string &relative, const bool return_metadata_errors = false); } mergerfs-2.33.5/src/fuse_fgetattr.cpp0000644000000000000000000000323314225522122016247 0ustar rootroot/* Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "config.hpp" #include "errno.hpp" #include "fileinfo.hpp" #include "fs_fstat.hpp" #include "fs_inode.hpp" #include "fuse.h" namespace l { static int fgetattr(const int fd_, const std::string &fusepath_, struct stat *st_) { int rv; rv = fs::fstat(fd_,st_); if(rv == -1) return -errno; fs::inode::calc(fusepath_,st_); return 0; } } namespace FUSE { int fgetattr(const fuse_file_info_t *ffi_, struct stat *st_, fuse_timeouts_t *timeout_) { int rv; Config::Read cfg; FileInfo *fi = reinterpret_cast(ffi_->fh); rv = l::fgetattr(fi->fd,fi->fusepath,st_); timeout_->entry = ((rv >= 0) ? cfg->cache_entry : cfg->cache_negative_entry); timeout_->attr = cfg->cache_attr; return rv; } } mergerfs-2.33.5/src/fs_read.hpp0000644000000000000000000000227114225522122015016 0ustar rootroot/* ISC License Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include namespace fs { static inline ssize_t read(const int fd_, void *buf_, const size_t count_) { return ::read(fd_,buf_,count_); } static inline ssize_t pread(const int fd_, void *buf_, const size_t count_, const off_t offset_) { return ::pread(fd_,buf_,count_,offset_); } } mergerfs-2.33.5/src/fuse_poll.hpp0000644000000000000000000000172014225522122015401 0ustar rootroot/* ISC License Copyright (c) 2021, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include "fuse.h" namespace FUSE { int poll(const fuse_file_info_t *ffi, fuse_pollhandle_t *ph, unsigned *reventsp); } mergerfs-2.33.5/src/fuse_releasedir.cpp0000644000000000000000000000213414225522122016545 0ustar rootroot/* Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "config.hpp" #include "dirinfo.hpp" #include "fuse.h" namespace l { static int releasedir(DirInfo *di_) { delete di_; return 0; } } namespace FUSE { int releasedir(const fuse_file_info_t *ffi_) { DirInfo *di = reinterpret_cast(ffi_->fh); return l::releasedir(di); } } mergerfs-2.33.5/src/gidcache.hpp0000644000000000000000000000267714225522122015154 0ustar rootroot/* Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include #include #define MAXGIDS 32 #define MAXRECS 256 struct gid_t_rec { uid_t uid; int size; gid_t gids[MAXGIDS]; bool operator<(const struct gid_t_rec &b) const; }; struct gid_t_cache { public: size_t size; gid_t_rec recs[MAXRECS]; private: gid_t_rec * begin(void); gid_t_rec * end(void); gid_t_rec * allocrec(void); gid_t_rec * lower_bound(gid_t_rec *begin, gid_t_rec *end, const uid_t uid); gid_t_rec * cache(const uid_t uid, const gid_t gid); public: int initgroups(const uid_t uid, const gid_t gid); }; mergerfs-2.33.5/src/fs_path.hpp0000644000000000000000000000360114225522122015035 0ustar rootroot/* Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include #include namespace fs { namespace path { std::string dirname(const char *path); std::string dirname(const std::string &path); std::string basename(const std::string &path); static inline void append(std::string &base_, const char *suffix_) { base_ += suffix_; } static inline void append(std::string &base_, const std::string &suffix_) { base_ += suffix_; } static inline std::string make(const char *base_, const char *suffix_) { char back; std::string path(base_); back = *path.rbegin(); if((back != '/') && (suffix_[0] != '/')) path.push_back('/'); path += suffix_; return path; } static inline std::string make(const std::string &base_, const char *suffix_) { return (base_ + suffix_); } static inline std::string make(const std::string &base_, const std::string &suffix_) { return (base_ + suffix_); } } }; mergerfs-2.33.5/src/fuse_chmod.cpp0000644000000000000000000000616314225522122015526 0ustar rootroot/* Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "config.hpp" #include "errno.hpp" #include "fs_lchmod.hpp" #include "fs_path.hpp" #include "policy_rv.hpp" #include "ugid.hpp" #include "fuse.h" #include #include using std::string; namespace l { static int get_error(const PolicyRV &prv_, const string &basepath_) { for(int i = 0, ei = prv_.success.size(); i < ei; i++) { if(prv_.success[i].basepath == basepath_) return prv_.success[i].rv; } for(int i = 0, ei = prv_.error.size(); i < ei; i++) { if(prv_.error[i].basepath == basepath_) return prv_.error[i].rv; } return 0; } static void chmod_loop_core(const string &basepath_, const char *fusepath_, const mode_t mode_, PolicyRV *prv_) { string fullpath; fullpath = fs::path::make(basepath_,fusepath_); errno = 0; fs::lchmod(fullpath,mode_); prv_->insert(errno,basepath_); } static void chmod_loop(const StrVec &basepaths_, const char *fusepath_, const mode_t mode_, PolicyRV *prv_) { for(size_t i = 0, ei = basepaths_.size(); i != ei; i++) { l::chmod_loop_core(basepaths_[i],fusepath_,mode_,prv_); } } static int chmod(const Policy::Action &actionFunc_, const Policy::Search &searchFunc_, const Branches &branches_, const char *fusepath_, const mode_t mode_) { int rv; PolicyRV prv; StrVec basepaths; rv = actionFunc_(branches_,fusepath_,&basepaths); if(rv == -1) return -errno; l::chmod_loop(basepaths,fusepath_,mode_,&prv); if(prv.error.empty()) return 0; if(prv.success.empty()) return prv.error[0].rv; basepaths.clear(); rv = searchFunc_(branches_,fusepath_,&basepaths); if(rv == -1) return -errno; return l::get_error(prv,basepaths[0]); } } namespace FUSE { int chmod(const char *fusepath_, mode_t mode_) { Config::Read cfg; const fuse_context *fc = fuse_get_context(); const ugid::Set ugid(fc->uid,fc->gid); return l::chmod(cfg->func.chmod.policy, cfg->func.getattr.policy, cfg->branches, fusepath_, mode_); } } mergerfs-2.33.5/src/str.cpp0000644000000000000000000001210714225522122014215 0ustar rootroot/* Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include using std::istringstream; using std::set; using std::string; using std::vector; namespace str { void split(const char *str_, const char delimiter_, vector *result_) { string part; istringstream ss(str_); while(std::getline(ss,part,delimiter_)) result_->push_back(part); } void split(const string &str_, const char delimiter_, vector *result_) { return str::split(str_.c_str(),delimiter_,result_); } void rsplit1(const string &str_, const char delimiter_, vector *result_) { std::size_t off; off = str_.rfind('='); if(off == std::string::npos) { result_->push_back(str_); } else { result_->push_back(str_.substr(0,off)); result_->push_back(str_.substr(off+1)); } } void splitkv(const string &str_, const char delimiter_, string *key_, string *val_) { istringstream iss; std::string key; std::string val; iss.str(str_); std::getline(iss,key,delimiter_); std::getline(iss,val,'\0'); *key_ = key; *val_ = val; } string join(const vector &vec_, const size_t substridx_, const char sep_) { if(vec_.empty()) return string(); string rv = vec_[0].substr(substridx_); for(size_t i = 1; i < vec_.size(); i++) rv += sep_ + vec_[i].substr(substridx_); return rv; } string join(const vector &vec_, const char sep_) { return str::join(vec_,0,sep_); } string join(const set &set_, const char sep_) { string rv; set::iterator i; if(set_.empty()) return string(); i = set_.begin(); rv += *i; ++i; for(; i != set_.end(); ++i) rv += sep_ + *i; return rv; } size_t longest_common_prefix_index(const vector &vec_) { if(vec_.empty()) return string::npos; for(size_t n = 0; n < vec_[0].size(); n++) { char chr = vec_[0][n]; for(size_t i = 1; i < vec_.size(); i++) { if(n >= vec_[i].size()) return n; if(vec_[i][n] != chr) return n; } } return string::npos; } string longest_common_prefix(const vector &vec_) { size_t idx; idx = longest_common_prefix_index(vec_); if(idx != string::npos) return vec_[0].substr(0,idx); return string(); } string remove_common_prefix_and_join(const vector &vec_, const char sep_) { size_t idx; idx = str::longest_common_prefix_index(vec_); if(idx == string::npos) idx = 0; return str::join(vec_,idx,sep_); } void erase_fnmatches(const vector &patterns_, vector &strs_) { vector::iterator si; vector::const_iterator pi; si = strs_.begin(); while(si != strs_.end()) { int match = FNM_NOMATCH; for(pi = patterns_.begin(); pi != patterns_.end() && match != 0; ++pi) { match = fnmatch(pi->c_str(),si->c_str(),0); } if(match == 0) si = strs_.erase(si); else ++si; } } bool isprefix(const string &s0_, const string &s1_) { return ((s0_.size() >= s1_.size()) && (s0_.compare(0,s1_.size(),s1_) == 0)); } bool startswith(const string &str_, const string &prefix_) { return ((str_.size() >= prefix_.size()) && (str_.compare(0,prefix_.size(),prefix_) == 0)); } bool endswith(const string &str_, const string &suffix_) { if(suffix_.size() > str_.size()) return false; return std::equal(suffix_.rbegin(), suffix_.rend(), str_.rbegin()); } std::string trim(const std::string &str_) { std::string rv; rv = str_; while(!rv.empty() && (rv[0] == ' ')) rv.erase(0); while(!rv.empty() && (rv[rv.size()-1] == ' ')) rv.erase(rv.size()-1); return rv; } } mergerfs-2.33.5/src/fuse_setxattr.cpp0000644000000000000000000001275314225522122016314 0ustar rootroot/* Copyright (c) 2018, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "config.hpp" #include "errno.hpp" #include "fs_glob.hpp" #include "fs_lsetxattr.hpp" #include "fs_path.hpp" #include "fs_statvfs_cache.hpp" #include "num.hpp" #include "policy_rv.hpp" #include "str.hpp" #include "ugid.hpp" #include "fuse.h" #include #include #include static const char SECURITY_CAPABILITY[] = "security.capability"; using std::string; using std::vector; namespace l { static int get_error(const PolicyRV &prv_, const string &basepath_) { for(int i = 0, ei = prv_.success.size(); i < ei; i++) { if(prv_.success[i].basepath == basepath_) return prv_.success[i].rv; } for(int i = 0, ei = prv_.error.size(); i < ei; i++) { if(prv_.error[i].basepath == basepath_) return prv_.error[i].rv; } return 0; } static bool is_attrname_security_capability(const char *attrname_) { return (strcmp(attrname_,SECURITY_CAPABILITY) == 0); } static int setxattr_controlfile(const string &attrname_, const string &attrval_, const int flags_) { int rv; string key; Config::Write cfg; if(!str::startswith(attrname_,"user.mergerfs.")) return -ENOATTR; key = &attrname_[14]; if(cfg->has_key(key) == false) return -ENOATTR; if((flags_ & XATTR_CREATE) == XATTR_CREATE) return -EEXIST; rv = cfg->set(key,attrval_); if(rv < 0) return rv; fs::statvfs_cache_timeout(cfg->cache_statfs); return rv; } static void setxattr_loop_core(const string &basepath_, const char *fusepath_, const char *attrname_, const char *attrval_, const size_t attrvalsize_, const int flags_, PolicyRV *prv_) { string fullpath; fullpath = fs::path::make(basepath_,fusepath_); errno = 0; fs::lsetxattr(fullpath,attrname_,attrval_,attrvalsize_,flags_); prv_->insert(errno,basepath_); } static void setxattr_loop(const StrVec &basepaths_, const char *fusepath_, const char *attrname_, const char *attrval_, const size_t attrvalsize_, const int flags_, PolicyRV *prv_) { for(size_t i = 0, ei = basepaths_.size(); i != ei; i++) { l::setxattr_loop_core(basepaths_[i],fusepath_, attrname_,attrval_,attrvalsize_, flags_,prv_); } } static int setxattr(const Policy::Action &setxattrPolicy_, const Policy::Search &getxattrPolicy_, const Branches &branches_, const char *fusepath_, const char *attrname_, const char *attrval_, const size_t attrvalsize_, const int flags_) { int rv; PolicyRV prv; StrVec basepaths; rv = setxattrPolicy_(branches_,fusepath_,&basepaths); if(rv == -1) return -errno; l::setxattr_loop(basepaths,fusepath_,attrname_,attrval_,attrvalsize_,flags_,&prv); if(prv.error.empty()) return 0; if(prv.success.empty()) return prv.error[0].rv; basepaths.clear(); rv = getxattrPolicy_(branches_,fusepath_,&basepaths); if(rv == -1) return -errno; return l::get_error(prv,basepaths[0]); } int setxattr(const char *fusepath_, const char *attrname_, const char *attrval_, size_t attrvalsize_, int flags_) { Config::Read cfg; if((cfg->security_capability == false) && l::is_attrname_security_capability(attrname_)) return -ENOATTR; if(cfg->xattr.to_int()) return -cfg->xattr.to_int(); const fuse_context *fc = fuse_get_context(); const ugid::Set ugid(fc->uid,fc->gid); return l::setxattr(cfg->func.setxattr.policy, cfg->func.getxattr.policy, cfg->branches, fusepath_, attrname_, attrval_, attrvalsize_, flags_); } } namespace FUSE { int setxattr(const char *fusepath_, const char *attrname_, const char *attrval_, size_t attrvalsize_, int flags_) { if(fusepath_ == CONTROLFILE) return l::setxattr_controlfile(attrname_, string(attrval_,attrvalsize_), flags_); return l::setxattr(fusepath_,attrname_,attrval_,attrvalsize_,flags_); } } mergerfs-2.33.5/src/fs_stat_utils.hpp0000644000000000000000000000304014225522122016271 0ustar rootroot/* ISC License Copyright (c) 2020, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include #include #include #include namespace fs { static inline timespec * stat_atime(struct stat *st_) { #if __APPLE__ return &st_->st_atimespec; #else return &st_->st_atim; #endif } static inline const timespec * stat_atime(const struct stat *st_) { #if __APPLE__ return &st_->st_atimespec; #else return &st_->st_atim; #endif } static inline timespec * stat_mtime(struct stat *st_) { #if __APPLE__ return &st_->st_mtimespec; #else return &st_->st_mtim; #endif } static inline const timespec * stat_mtime(const struct stat *st_) { #if __APPLE__ return &st_->st_mtimespec; #else return &st_->st_mtim; #endif } } mergerfs-2.33.5/src/policy_erofs.hpp0000644000000000000000000000314514225522122016111 0ustar rootroot/* ISC License Copyright (c) 2020, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include "policy.hpp" namespace Policy { namespace ERoFS { class Action final : public Policy::ActionImpl { public: Action() : Policy::ActionImpl("erofs") {} public: int operator()(const Branches::CPtr&,const char*,StrVec*) const final; }; class Create final : public Policy::CreateImpl { public: Create() : Policy::CreateImpl("erofs") {} public: int operator()(const Branches::CPtr&,const char*,StrVec*) const final; bool path_preserving(void) const final { return false; } }; class Search final : public Policy::SearchImpl { public: Search() : Policy::SearchImpl("erofs") {} public: int operator()(const Branches::CPtr&,const char*,StrVec*) const final; }; } } mergerfs-2.33.5/src/fuse_open.hpp0000644000000000000000000000162214225522122015375 0ustar rootroot/* Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include "fuse.h" namespace FUSE { int open(const char *fusepath, fuse_file_info_t *ffi); } mergerfs-2.33.5/src/fuse_fgetattr.hpp0000644000000000000000000000201614225522122016252 0ustar rootroot/* Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include "fuse.h" #include #include #include namespace FUSE { int fgetattr(const fuse_file_info_t *ffi, struct stat *st, fuse_timeouts_t *timeout); } mergerfs-2.33.5/src/fs_inode.hpp0000644000000000000000000000277414225522122015211 0ustar rootroot/* ISC License Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include #include #include namespace fs { namespace inode { static const uint64_t MAGIC = 0x7472617065786974; int set_algo(const std::string &s); std::string get_algo(void); uint64_t calc(const char *fusepath, const uint64_t fusepath_len, const mode_t mode, const dev_t dev, const ino_t ion); void calc(const char *fusepath, const uint64_t fusepath_len, struct stat *st); void calc(const char *fusepath, struct stat *st); void calc(const std::string &fusepath, struct stat *st); } } mergerfs-2.33.5/src/fs_findonfs.hpp0000644000000000000000000000203514225522122015707 0ustar rootroot/* ISC License Copyright (c) 2020, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include "branches.hpp" #include namespace fs { int findonfs(const Branches::CPtr &branches, const std::string &fusepath, const int fd, std::string *basepath); } mergerfs-2.33.5/src/fs_fadvise.hpp0000644000000000000000000000232514225522122015524 0ustar rootroot/* Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include namespace fs { int fadvise_dontneed(const int fd, const off_t offset = 0, const off_t len = 0); int fadvise_willneed(const int fd, const off_t offset = 0, const off_t len = 0); int fadvise_sequential(const int fd, const off_t offset = 0, const off_t len = 0); } mergerfs-2.33.5/src/fs_futimens_linux.hpp0000644000000000000000000000175214225522122017157 0ustar rootroot/* ISC License Copyright (c) 2020, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include namespace fs { static inline int futimens(const int fd_, const struct timespec ts_[2]) { return ::futimens(fd_,ts_); } } mergerfs-2.33.5/src/fuse_readdir_plus_posix.hpp0000644000000000000000000000223114225522122020330 0ustar rootroot/* ISC License Copyright (c) 2020, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include "branches.hpp" #include "fuse.h" #include namespace FUSE { int readdir_plus_posix(const Branches::CPtr &branches, const char *dirname, const uint64_t entry_timeout, const uint64_t attr_timeout, fuse_dirents_t *buf); } mergerfs-2.33.5/src/fuse_link.hpp0000644000000000000000000000172114225522122015371 0ustar rootroot/* Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include "fuse.h" namespace FUSE { int link(const char *oldpath, const char *newpath, struct stat *st, fuse_timeouts_t *timeouts); } mergerfs-2.33.5/src/fs_ficlone_linux.icpp0000644000000000000000000000206014225522122017101 0ustar rootroot/* ISC License Copyright (c) 2018, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "errno.hpp" #include "fs_ioctl.hpp" #include namespace fs { int ficlone(const int src_fd_, const int dst_fd_) { #ifdef FICLONE return fs::ioctl(dst_fd_,FICLONE,src_fd_); #else return (errno=EOPNOTSUPP,-1); #endif } } mergerfs-2.33.5/src/gidcache.cpp0000644000000000000000000000672714225522122015147 0ustar rootroot/* Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #if defined __linux__ and UGID_USE_RWLOCK == 0 # include #elif __APPLE__ # include #endif #include #include #include "gidcache.hpp" inline bool gid_t_rec::operator<(const struct gid_t_rec &b) const { return uid < b.uid; } inline gid_t_rec * gid_t_cache::begin(void) { return recs; } inline gid_t_rec * gid_t_cache::end(void) { return recs + size; } inline gid_t_rec * gid_t_cache::allocrec(void) { if(size == MAXRECS) return &recs[rand() % MAXRECS]; else return &recs[size++]; } inline gid_t_rec * gid_t_cache::lower_bound(gid_t_rec *begin, gid_t_rec *end, const uid_t uid) { int step; int count; gid_t_rec *iter; count = std::distance(begin,end); while(count > 0) { iter = begin; step = count / 2; std::advance(iter,step); if(iter->uid < uid) { begin = ++iter; count -= step + 1; } else { count = step; } } return begin; } static int _getgrouplist(const char *user, const gid_t group, gid_t *groups, int *ngroups) { #if __APPLE__ return ::getgrouplist(user,group,(int*)groups,ngroups); #else return ::getgrouplist(user,group,groups,ngroups); #endif } gid_t_rec * gid_t_cache::cache(const uid_t uid, const gid_t gid) { int rv; char buf[4096]; struct passwd pwd; struct passwd *pwdrv; gid_t_rec *rec; rec = allocrec(); rec->uid = uid; rv = ::getpwuid_r(uid,&pwd,buf,sizeof(buf),&pwdrv); if(pwdrv != NULL && rv == 0) { rec->size = 0; ::_getgrouplist(pwd.pw_name,gid,NULL,&rec->size); rec->size = std::min(MAXGIDS,rec->size); rv = ::_getgrouplist(pwd.pw_name,gid,rec->gids,&rec->size); if(rv == -1) { rec->gids[0] = gid; rec->size = 1; } } return rec; } static inline int setgroups(const gid_t_rec *rec) { #if defined __linux__ and UGID_USE_RWLOCK == 0 # if defined SYS_setgroups32 return ::syscall(SYS_setgroups32,rec->size,rec->gids); # else return ::syscall(SYS_setgroups,rec->size,rec->gids); # endif #else return ::setgroups(rec->size,rec->gids); #endif } int gid_t_cache::initgroups(const uid_t uid, const gid_t gid) { int rv; gid_t_rec *rec; rec = lower_bound(begin(),end(),uid); if(rec == end() || rec->uid != uid) { rec = cache(uid,gid); rv = ::setgroups(rec); std::sort(begin(),end()); } else { rv = ::setgroups(rec); } return rv; } mergerfs-2.33.5/src/fuse_flush.cpp0000644000000000000000000000236514225522122015555 0ustar rootroot/* Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "errno.hpp" #include "fileinfo.hpp" #include "fs_close.hpp" #include "fs_dup.hpp" #include "fuse.h" namespace l { static int flush(const int fd_) { int rv; rv = fs::dup(fd_); if(rv == -1) errno = EIO; else rv = fs::close(rv); return ((rv == -1) ? -errno : 0); } } namespace FUSE { int flush(const fuse_file_info_t *ffi_) { FileInfo *fi = reinterpret_cast(ffi_->fh); return l::flush(fi->fd); } } mergerfs-2.33.5/src/fs_findallfiles.hpp0000644000000000000000000000177314225522122016545 0ustar rootroot/* ISC License Copyright (c) 2020, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include "strvec.hpp" #include #include namespace fs { void findallfiles(const StrVec &basepaths, const char *fusepath, StrVec *paths); } mergerfs-2.33.5/src/fuse_init.cpp0000644000000000000000000000551714225522122015401 0ustar rootroot/* Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "config.hpp" #include "ugid.hpp" #include "fuse.h" namespace l { static void want(fuse_conn_info *conn_, const int flag_) { conn_->want |= flag_; } static bool capable(fuse_conn_info *conn_, const int flag_) { return !!(conn_->capable & flag_); } static void want_if_capable(fuse_conn_info *conn_, const int flag_) { if(capable(conn_,flag_)) want(conn_,flag_); } static void want_if_capable(fuse_conn_info *conn_, const int flag_, ConfigBOOL *want_) { if(*want_ && l::capable(conn_,flag_)) { l::want(conn_,flag_); return; } *want_ = false; } static void want_if_capable_max_pages(fuse_conn_info *conn_, Config::Write &cfg_) { if(l::capable(conn_,FUSE_CAP_MAX_PAGES)) { l::want(conn_,FUSE_CAP_MAX_PAGES); conn_->max_pages = cfg_->fuse_msg_size; } else { cfg_->fuse_msg_size = FUSE_DEFAULT_MAX_PAGES_PER_REQ; } } } namespace FUSE { void * init(fuse_conn_info *conn_) { Config::Write cfg; ugid::init(); l::want_if_capable(conn_,FUSE_CAP_ASYNC_DIO); l::want_if_capable(conn_,FUSE_CAP_ASYNC_READ,&cfg->async_read); l::want_if_capable(conn_,FUSE_CAP_ATOMIC_O_TRUNC); l::want_if_capable(conn_,FUSE_CAP_BIG_WRITES); l::want_if_capable(conn_,FUSE_CAP_CACHE_SYMLINKS,&cfg->cache_symlinks); l::want_if_capable(conn_,FUSE_CAP_DONT_MASK); l::want_if_capable(conn_,FUSE_CAP_IOCTL_DIR); l::want_if_capable(conn_,FUSE_CAP_PARALLEL_DIROPS); l::want_if_capable(conn_,FUSE_CAP_READDIR_PLUS,&cfg->readdirplus); //l::want_if_capable(conn_,FUSE_CAP_READDIR_PLUS_AUTO); l::want_if_capable(conn_,FUSE_CAP_POSIX_ACL,&cfg->posix_acl); l::want_if_capable(conn_,FUSE_CAP_WRITEBACK_CACHE,&cfg->writeback_cache); l::want_if_capable_max_pages(conn_,cfg); conn_->want &= ~FUSE_CAP_POSIX_LOCKS; conn_->want &= ~FUSE_CAP_FLOCK_LOCKS; return NULL; } } mergerfs-2.33.5/src/fuse_getxattr.hpp0000644000000000000000000000167214225522122016303 0ustar rootroot/* Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once namespace FUSE { int getxattr(const char *fusepath, const char *attrname, char *buf, size_t count); } mergerfs-2.33.5/src/funcs.hpp0000644000000000000000000000262414225522122014533 0ustar rootroot/* ISC License Copyright (c) 2019, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include "func.hpp" struct Funcs { Func::Access access; Func::Chmod chmod; Func::Chown chown; Func::Create create; Func::GetAttr getattr; Func::GetXAttr getxattr; Func::Link link; Func::ListXAttr listxattr; Func::Mkdir mkdir; Func::Mknod mknod; Func::Open open; Func::Readlink readlink; Func::RemoveXAttr removexattr; Func::Rename rename; Func::Rmdir rmdir; Func::SetXAttr setxattr; Func::Symlink symlink; Func::Truncate truncate; Func::Unlink unlink; Func::Utimens utimens; }; mergerfs-2.33.5/src/num.hpp0000644000000000000000000000213314225522122014207 0ustar rootroot/* Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include #include #include namespace num { int to_uint64_t(const std::string &str, uint64_t *value); int to_double(const std::string &str, double *value); int to_time_t(const std::string &str, time_t *value); } namespace num { std::string humanize(const uint64_t bytes); } mergerfs-2.33.5/src/branches.cpp0000644000000000000000000002023414225522122015172 0ustar rootroot/* ISC License Copyright (c) 2021, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "branches.hpp" #include "ef.hpp" #include "errno.hpp" #include "from_string.hpp" #include "fs_glob.hpp" #include "fs_realpathize.hpp" #include "nonstd/optional.hpp" #include "num.hpp" #include "str.hpp" #include #include using std::string; using std::vector; using nonstd::optional; Branches::Impl::Impl(const uint64_t &default_minfreespace_) : _default_minfreespace(default_minfreespace_) { } Branches::Impl& Branches::Impl::operator=(Branches::Impl &rval_) { auto this_base = dynamic_cast(this); auto rval_base = dynamic_cast(&rval_); *this_base = *rval_base; return *this; } Branches::Impl& Branches::Impl::operator=(Branches::Impl &&rval_) { auto this_base = dynamic_cast(this); auto rval_base = dynamic_cast(&rval_); *this_base = std::move(*rval_base); return *this; } const uint64_t& Branches::Impl::minfreespace(void) const { return _default_minfreespace; } namespace l { static void split(const std::string &s_, std::string *instr_, std::string *values_) { uint64_t offset; offset = s_.find_first_not_of("+<>-="); if(offset > 1) offset = 2; *instr_ = s_.substr(0,offset); if(offset != std::string::npos) *values_ = s_.substr(offset); } static int parse_mode(const string &str_, Branch::Mode *mode_) { if(str_ == "RW") *mode_ = Branch::Mode::RW; ef(str_ == "RO") *mode_ = Branch::Mode::RO; ef(str_ == "NC") *mode_ = Branch::Mode::NC; else return -EINVAL; return 0; } static int parse_minfreespace(const string &str_, optional *minfreespace_) { int rv; uint64_t uint64; rv = str::from(str_,&uint64); if(rv < 0) return rv; *minfreespace_ = uint64; return 0; } static int parse_branch(const string &str_, string *glob_, Branch::Mode *mode_, optional *minfreespace_) { int rv; string options; vector v; str::rsplit1(str_,'=',&v); switch(v.size()) { case 1: *glob_ = v[0]; *mode_ = Branch::Mode::RW; break; case 2: *glob_ = v[0]; options = v[1]; v.clear(); str::split(options,',',&v); switch(v.size()) { case 2: rv = l::parse_minfreespace(v[1],minfreespace_); if(rv < 0) return rv; case 1: rv = l::parse_mode(v[0],mode_); if(rv < 0) return rv; break; case 0: return -EINVAL; } break; default: return -EINVAL; } return 0; } static int parse(const string &str_, Branches::Impl *branches_) { int rv; string glob; StrVec paths; optional minfreespace; Branch branch(branches_->minfreespace()); rv = l::parse_branch(str_,&glob,&branch.mode,&minfreespace); if(rv < 0) return rv; if(minfreespace.has_value()) branch.set_minfreespace(minfreespace.value()); fs::glob(glob,&paths); fs::realpathize(&paths); for(auto &path : paths) { branch.path = path; branches_->push_back(branch); } return 0; } static int set(const std::string &str_, Branches::Impl *branches_) { int rv; StrVec paths; Branches::Impl tmp_branches(branches_->minfreespace()); str::split(str_,':',&paths); for(auto &path : paths) { rv = l::parse(path,&tmp_branches); if(rv < 0) return rv; } *branches_ = std::move(tmp_branches); return 0; } static int add_begin(const std::string &str_, Branches::Impl *branches_) { int rv; vector paths; Branches::Impl tmp_branches(branches_->minfreespace()); str::split(str_,':',&paths); for(auto &path : paths) { rv = l::parse(path,&tmp_branches); if(rv < 0) return rv; } branches_->insert(branches_->begin(), tmp_branches.begin(), tmp_branches.end()); return 0; } static int add_end(const std::string &str_, Branches::Impl *branches_) { int rv; StrVec paths; Branches::Impl tmp_branches(branches_->minfreespace()); str::split(str_,':',&paths); for(auto &path : paths) { rv = l::parse(path,&tmp_branches); if(rv < 0) return rv; } branches_->insert(branches_->end(), tmp_branches.begin(), tmp_branches.end()); return 0; } static int erase_begin(Branches::Impl *branches_) { branches_->erase(branches_->begin()); return 0; } static int erase_end(Branches::Impl *branches_) { branches_->pop_back(); return 0; } static int erase_fnmatch(const std::string &str_, Branches::Impl *branches_) { StrVec patterns; str::split(str_,':',&patterns); for(auto i = branches_->begin(); i != branches_->end();) { int match = FNM_NOMATCH; for(auto pi = patterns.cbegin(); pi != patterns.cend() && match != 0; ++pi) { match = ::fnmatch(pi->c_str(),i->path.c_str(),0); } i = ((match == 0) ? branches_->erase(i) : (i+1)); } return 0; } } int Branches::Impl::from_string(const std::string &s_) { std::string instr; std::string values; l::split(s_,&instr,&values); if(instr == "+") return l::add_end(values,this); if(instr == "+<") return l::add_begin(values,this); if(instr == "+>") return l::add_end(values,this); if(instr == "-") return l::erase_fnmatch(values,this); if(instr == "-<") return l::erase_begin(this); if(instr == "->") return l::erase_end(this); if(instr == "=") return l::set(values,this); if(instr.empty()) return l::set(values,this); return -EINVAL; } std::string Branches::Impl::to_string(void) const { string tmp; if(empty()) return tmp; for(auto &branch : *this) { tmp += branch.to_string(); tmp += ':'; } tmp.pop_back(); return tmp; } void Branches::Impl::to_paths(StrVec &paths_) const { for(auto &branch : *this) { paths_.push_back(branch.path); } } int Branches::from_string(const std::string &str_) { int rv; Branches::Ptr impl; Branches::Ptr new_impl; { std::lock_guard lock_guard(_mutex); impl = _impl; } new_impl = std::make_shared(impl->minfreespace()); *new_impl = *impl; rv = new_impl->from_string(str_); if(rv < 0) return rv; { std::lock_guard lock_guard(_mutex); _impl = new_impl; } return 0; } string Branches::to_string(void) const { std::lock_guard lock_guard(_mutex); return _impl->to_string(); } SrcMounts::SrcMounts(Branches &b_) : _branches(b_) { } int SrcMounts::from_string(const std::string &s_) { return _branches.from_string(s_); } std::string SrcMounts::to_string(void) const { std::string rv; Branches::CPtr branches = _branches; if(branches->empty()) return rv; for(const auto &branch : *branches) { rv += branch.path; rv += ':'; } rv.pop_back(); return rv; } mergerfs-2.33.5/src/fuse_opendir.hpp0000644000000000000000000000163014225522122016073 0ustar rootroot/* Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include "fuse.h" namespace FUSE { int opendir(const char *fusepath, fuse_file_info_t *ffi); } mergerfs-2.33.5/src/fs_truncate.hpp0000644000000000000000000000224614225522122015732 0ustar rootroot/* ISC License Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include #include #include namespace fs { static inline int truncate(const char *path_, const off_t length_) { return ::truncate(path_,length_); } static inline int truncate(const std::string &path_, const off_t length_) { return fs::truncate(path_.c_str(),length_); } } mergerfs-2.33.5/src/fs_symlink.hpp0000644000000000000000000000247514225522122015577 0ustar rootroot/* ISC License Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include #include namespace fs { static inline int symlink(const char *target_, const char *linkpath_) { return ::symlink(target_,linkpath_); } static inline int symlink(const std::string &target_, const std::string &linkpath_) { return ::symlink(target_.c_str(),linkpath_.c_str()); } static inline int symlink(const char *target_, const std::string &linkpath_) { return ::symlink(target_,linkpath_.c_str()); } } mergerfs-2.33.5/src/fuse_listxattr.cpp0000644000000000000000000000520314225522122016464 0ustar rootroot/* Copyright (c) 2018, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "category.hpp" #include "config.hpp" #include "errno.hpp" #include "fs_llistxattr.hpp" #include "fs_path.hpp" #include "ugid.hpp" #include "xattr.hpp" #include "fuse.h" #include #include using std::string; namespace l { static int listxattr_controlfile(Config::Read &cfg_, char *list_, const size_t size_) { string xattrs; cfg_->keys_xattr(xattrs); if(size_ == 0) return xattrs.size(); if(size_ < xattrs.size()) return -ERANGE; memcpy(list_,xattrs.c_str(),xattrs.size()); return xattrs.size(); } static int listxattr(const Policy::Search &searchFunc_, const Branches &branches_, const char *fusepath_, char *list_, const size_t size_) { int rv; string fullpath; StrVec basepaths; rv = searchFunc_(branches_,fusepath_,&basepaths); if(rv == -1) return -errno; fullpath = fs::path::make(basepaths[0],fusepath_); rv = fs::llistxattr(fullpath,list_,size_); return ((rv == -1) ? -errno : rv); } } namespace FUSE { int listxattr(const char *fusepath_, char *list_, size_t size_) { Config::Read cfg; if(fusepath_ == CONTROLFILE) return l::listxattr_controlfile(cfg,list_,size_); switch(cfg->xattr) { case XAttr::ENUM::PASSTHROUGH: break; case XAttr::ENUM::NOATTR: return 0; case XAttr::ENUM::NOSYS: return -ENOSYS; } const fuse_context *fc = fuse_get_context(); const ugid::Set ugid(fc->uid,fc->gid); return l::listxattr(cfg->func.listxattr.policy, cfg->branches, fusepath_, list_, size_); } } mergerfs-2.33.5/src/hashset.hpp0000644000000000000000000000272314225522122015054 0ustar rootroot/* ISC License Copyright (c) 2018, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include "khash.h" #include "wyhash.h" KHASH_SET_INIT_INT64(hashset); class HashSet { public: HashSet() { _set = kh_init(hashset); } ~HashSet() { kh_destroy(hashset,_set); } inline int put(const char *str_, const uint64_t len_) { int rv; uint64_t h; khint_t key; h = wyhash(str_,len_,0x7472617065786974,_wyp); key = kh_put(hashset,_set,h,&rv); if(rv == 0) return 0; kh_key(_set,key) = h; return rv; } inline int put(const char *str_) { return put(str_,strlen(str_)); } inline int size(void) { return kh_size(_set); } private: khash_t(hashset) *_set; }; mergerfs-2.33.5/src/policy_newest.cpp0000644000000000000000000001100114225522122016261 0ustar rootroot/* Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "errno.hpp" #include "fs_exists.hpp" #include "fs_info.hpp" #include "fs_path.hpp" #include "fs_statvfs_cache.hpp" #include "policy.hpp" #include "policy_error.hpp" #include "policy_newest.hpp" #include "rwlock.hpp" #include #include #include using std::string; namespace newest { static int create(const Branches::CPtr &branches_, const char *fusepath_, StrVec *paths_) { int rv; int error; time_t newest; struct stat st; fs::info_t info; const string *basepath; error = ENOENT; newest = std::numeric_limits::min(); basepath = NULL; for(auto &branch : *branches_) { if(branch.ro_or_nc()) error_and_continue(error,EROFS); if(!fs::exists(branch.path,fusepath_,&st)) error_and_continue(error,ENOENT); if(st.st_mtime < newest) continue; rv = fs::info(branch.path,&info); if(rv == -1) error_and_continue(error,ENOENT); if(info.readonly) error_and_continue(error,EROFS); if(info.spaceavail < branch.minfreespace()) error_and_continue(error,ENOSPC); newest = st.st_mtime; basepath = &branch.path; } if(basepath == NULL) return (errno=error,-1); paths_->push_back(*basepath); return 0; } static int action(const Branches::CPtr &branches_, const char *fusepath_, StrVec *paths_) { int rv; int error; bool readonly; time_t newest; struct stat st; const string *basepath; error = ENOENT; newest = std::numeric_limits::min(); basepath = NULL; for(auto &branch : *branches_) { if(branch.ro()) error_and_continue(error,EROFS); if(!fs::exists(branch.path,fusepath_,&st)) error_and_continue(error,ENOENT); if(st.st_mtime < newest) continue; rv = fs::statvfs_cache_readonly(branch.path,&readonly); if(rv == -1) error_and_continue(error,ENOENT); if(readonly) error_and_continue(error,EROFS); newest = st.st_mtime; basepath = &branch.path; } if(basepath == NULL) return (errno=error,-1); paths_->push_back(*basepath); return 0; } static int search(const Branches::CPtr &branches_, const char *fusepath_, StrVec *paths_) { time_t newest; struct stat st; const string *basepath; newest = std::numeric_limits::min(); basepath = NULL; for(auto &branch : *branches_) { if(!fs::exists(branch.path,fusepath_,&st)) continue; if(st.st_mtime < newest) continue; newest = st.st_mtime; basepath = &branch.path; } if(basepath == NULL) return (errno=ENOENT,-1); paths_->push_back(*basepath); return 0; } } int Policy::Newest::Action::operator()(const Branches::CPtr &branches_, const char *fusepath_, StrVec *paths_) const { return ::newest::action(branches_,fusepath_,paths_); } int Policy::Newest::Create::operator()(const Branches::CPtr &branches_, const char *fusepath_, StrVec *paths_) const { return ::newest::create(branches_,fusepath_,paths_); } int Policy::Newest::Search::operator()(const Branches::CPtr &branches_, const char *fusepath_, StrVec *paths_) const { return ::newest::search(branches_,fusepath_,paths_); } mergerfs-2.33.5/src/fs_futimens_generic.hpp0000644000000000000000000001450314225522122017432 0ustar rootroot/* ISC License Copyright (c) 2020, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "fs_futimesat.hpp" #include "fs_stat_utils.hpp" #include #include #include #include #ifndef UTIME_NOW # define UTIME_NOW ((1l << 30) - 1l) #endif #ifndef UTIME_OMIT # define UTIME_OMIT ((1l << 30) - 2l) #endif namespace l { static inline bool can_call_lutimes(const int dirfd_, const std::string &path_, const int flags_) { return ((flags_ == AT_SYMLINK_NOFOLLOW) && ((dirfd_ == AT_FDCWD) || (path_[0] == '/'))); } static inline bool should_ignore(const struct timespec ts_[2]) { return ((ts_ != NULL) && (ts_[0].tv_nsec == UTIME_OMIT) && (ts_[1].tv_nsec == UTIME_OMIT)); } static inline bool should_be_set_to_now(const struct timespec ts_[2]) { return ((ts_ == NULL) || ((ts_[0].tv_nsec == UTIME_NOW) && (ts_[1].tv_nsec == UTIME_NOW))); } static inline bool timespec_invalid(const struct timespec &ts_) { return (((ts_.tv_nsec < 0) || (ts_.tv_nsec > 999999999)) && ((ts_.tv_nsec != UTIME_NOW) && (ts_.tv_nsec != UTIME_OMIT))); } static inline bool timespec_invalid(const struct timespec ts_[2]) { return ((ts_ != NULL) && (l::timespec_invalid(ts_[0]) || l::timespec_invalid(ts_[1]))); } static inline bool flags_invalid(const int flags_) { return ((flags_ & ~AT_SYMLINK_NOFOLLOW) != 0); } static inline bool any_timespec_is_utime_omit(const struct timespec ts_[2]) { return ((ts_[0].tv_nsec == UTIME_OMIT) || (ts_[1].tv_nsec == UTIME_OMIT)); } static inline bool any_timespec_is_utime_now(const struct timespec ts_[2]) { return ((ts_[0].tv_nsec == UTIME_NOW) || (ts_[1].tv_nsec == UTIME_NOW)); } static inline int set_utime_omit_to_current_value(const int dirfd_, const std::string &path_, const struct timespec ts_[2], struct timeval tv_[2], const int flags_) { int rv; struct stat st; timespec *atime; timespec *mtime; if(!l::any_timespec_is_utime_omit(ts_)) return 0; rv = fs::fstatat(dirfd_,path_,&st,flags_); if(rv == -1) return -1; atime = fs::stat_atime(st); mtime = fs::stat_mtime(st); if(ts_[0].tv_nsec == UTIME_OMIT) TIMESPEC_TO_TIMEVAL(&tv[0],atime); if(ts_[1].tv_nsec == UTIME_OMIT) TIMESPEC_TO_TIMEVAL(&tv[1],mtime); return 0; } static inline int set_utime_omit_to_current_value(const int fd_, const struct timespec ts_[2], struct timeval tv_[2]) { int rv; struct stat st; timespec *atime; timespec *mtime; if(!l::any_timespec_is_utime_omit(ts_)) return 0; rv = fs::fstat(fd_,&st); if(rv == -1) return -1; atime = fs::stat_atime(st); mtime = fs::stat_mtime(st); if(ts_[0].tv_nsec == UTIME_OMIT) TIMESPEC_TO_TIMEVAL(&tv_[0],atime); if(ts_[1].tv_nsec == UTIME_OMIT) TIMESPEC_TO_TIMEVAL(&tv_[1],mtime); return 0; } static inline int set_utime_now_to_now(const struct timespec ts_[2], struct timeval tv_[2]) { int rv; struct timeval now; if(l::any_timespec_is_utime_now(ts_)) return 0; rv = time::gettimeofday(&now,NULL); if(rv == -1) return -1; if(ts_[0].tv_nsec == UTIME_NOW) tv_[0] = now; if(ts_[1].tv_nsec == UTIME_NOW) tv_[1] = now; return 0; } static inline int convert_timespec_to_timeval(const int dirfd_, const std::string &path_, const struct timespec ts_[2], struct timeval tv_[2], struct timeval **tvp_, const int flags_) { int rv; if(l::should_be_set_to_now(ts_)) return (tvp=NULL,0); TIMESPEC_TO_TIMEVAL(&tv_[0],&ts_[0]); TIMESPEC_TO_TIMEVAL(&tv_[1],&ts_[1]); rv = l::set_utime_omit_to_current_value(dirfd_,path_,ts_,tv_,flags_); if(rv == -1) return -1; rv = l::set_utime_now_to_now(ts_,tv_); if(rv == -1) return -1; return (*tvp_=tv_,0); } static inline int convert_timespec_to_timeval(const int fd_, const struct timespec ts_[2], struct timeval tv_[2], struct timeval **tvp_) { int rv; if(l::should_be_set_to_now(ts_)) return (*tvp=NULL,0); TIMESPEC_TO_TIMEVAL(&tv_[0],&ts_[0]); TIMESPEC_TO_TIMEVAL(&tv_[1],&ts_[1]); rv = l::set_utime_omit_to_current_value(fd_,ts_,tv_); if(rv == -1) return -1; rv = l::set_utime_now_to_now(ts_,tv_); if(rv == -1) return -1; return (*tvp=tv,0); } } namespace fs { static inline int futimens(const int fd_, const struct timespec ts_[2]) { int rv; struct timeval tv[2]; struct timeval *tvp; if(l::timespec_invalid(ts_)) return (errno=EINVAL,-1); if(l::should_ignore(ts_)) return 0; rv = l::convert_timespec_to_timeval(fd_,ts_,tv,&tvp); if(rv == -1) return -1; return ::futimes(fd_,tvp); } } mergerfs-2.33.5/src/policy_eppfrd.cpp0000644000000000000000000001473414225522122016254 0ustar rootroot/* Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "errno.hpp" #include "fs_exists.hpp" #include "fs_info.hpp" #include "fs_path.hpp" #include "fs_statvfs_cache.hpp" #include "policy.hpp" #include "policy_eppfrd.hpp" #include "policy_error.hpp" #include "rnd.hpp" #include "rwlock.hpp" #include "strvec.hpp" #include #include using std::string; using std::vector; struct BranchInfo { uint64_t spaceavail; const string *basepath; }; typedef vector BranchInfoVec; namespace eppfrd { static int get_branchinfo_create(const Branches::CPtr &branches_, const char *fusepath_, BranchInfoVec *branchinfo_, uint64_t *sum_) { int rv; int error; BranchInfo bi; fs::info_t info; *sum_ = 0; error = ENOENT; for(auto &branch : *branches_) { if(branch.ro_or_nc()) error_and_continue(error,EROFS); if(!fs::exists(branch.path,fusepath_)) error_and_continue(error,ENOENT); rv = fs::info(branch.path,&info); if(rv == -1) error_and_continue(error,ENOENT); if(info.readonly) error_and_continue(error,EROFS); if(info.spaceavail < branch.minfreespace()) error_and_continue(error,ENOSPC); *sum_ += info.spaceavail; bi.spaceavail = info.spaceavail; bi.basepath = &branch.path; branchinfo_->push_back(bi); } return error; } static int get_branchinfo_action(const Branches::CPtr &branches_, const char *fusepath_, BranchInfoVec *branchinfo_, uint64_t *sum_) { int rv; int error; BranchInfo bi; fs::info_t info; *sum_ = 0; error = ENOENT; for(auto &branch : *branches_) { if(branch.ro()) error_and_continue(error,EROFS); if(!fs::exists(branch.path,fusepath_)) error_and_continue(error,ENOENT); rv = fs::info(branch.path,&info); if(rv == -1) error_and_continue(error,ENOENT); if(info.readonly) error_and_continue(error,EROFS); *sum_ += info.spaceavail; bi.spaceavail = info.spaceavail; bi.basepath = &branch.path; branchinfo_->push_back(bi); } return error; } static int get_branchinfo_search(const Branches::CPtr &branches_, const char *fusepath_, BranchInfoVec *branchinfo_, uint64_t *sum_) { int rv; BranchInfo bi; uint64_t spaceavail; *sum_ = 0; for(auto &branch : *branches_) { if(!fs::exists(branch.path,fusepath_)) continue; rv = fs::statvfs_cache_spaceavail(branch.path,&spaceavail); if(rv == -1) continue; *sum_ += spaceavail; bi.spaceavail = spaceavail; bi.basepath = &branch.path; branchinfo_->push_back(bi); } return ENOENT; } static const string* get_branch(const BranchInfoVec &branchinfo_, const uint64_t sum_) { uint64_t idx; uint64_t threshold; if(sum_ == 0) return NULL; idx = 0; threshold = RND::rand64(sum_); for(size_t i = 0; i < branchinfo_.size(); i++) { idx += branchinfo_[i].spaceavail; if(idx < threshold) continue; return branchinfo_[i].basepath; } return NULL; } static int create(const Branches::CPtr &branches_, const char *fusepath_, StrVec *paths_) { int error; uint64_t sum; const string *basepath; BranchInfoVec branchinfo; error = eppfrd::get_branchinfo_create(branches_,fusepath_,&branchinfo,&sum); basepath = eppfrd::get_branch(branchinfo,sum); if(basepath == NULL) return (errno=error,-1); paths_->push_back(*basepath); return 0; } static int action(const Branches::CPtr &branches_, const char *fusepath_, StrVec *paths_) { int error; uint64_t sum; const string *basepath; BranchInfoVec branchinfo; error = eppfrd::get_branchinfo_action(branches_,fusepath_,&branchinfo,&sum); basepath = eppfrd::get_branch(branchinfo,sum); if(basepath == NULL) return (errno=error,-1); paths_->push_back(*basepath); return 0; } static int search(const Branches::CPtr &branches_, const char *fusepath_, StrVec *paths_) { int error; uint64_t sum; const string *basepath; BranchInfoVec branchinfo; error = eppfrd::get_branchinfo_search(branches_,fusepath_,&branchinfo,&sum); basepath = eppfrd::get_branch(branchinfo,sum); if(basepath == NULL) return (errno=error,-1); paths_->push_back(*basepath); return 0; } } int Policy::EPPFRD::Action::operator()(const Branches::CPtr &branches_, const char *fusepath_, StrVec *paths_) const { return ::eppfrd::action(branches_,fusepath_,paths_); } int Policy::EPPFRD::Create::operator()(const Branches::CPtr &branches_, const char *fusepath_, StrVec *paths_) const { return ::eppfrd::create(branches_,fusepath_,paths_); } int Policy::EPPFRD::Search::operator()(const Branches::CPtr &branches_, const char *fusepath_, StrVec *paths_) const { return ::eppfrd::search(branches_,fusepath_,paths_); } mergerfs-2.33.5/src/fs_info.hpp0000644000000000000000000000167014225522122015040 0ustar rootroot/* ISC License Copyright (c) 2018, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include "fs_info_t.hpp" #include namespace fs { int info(const std::string &path, fs::info_t *info); } mergerfs-2.33.5/src/option_parser.cpp0000644000000000000000000003102214225522122016266 0ustar rootroot/* Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "config.hpp" #include "ef.hpp" #include "errno.hpp" #include "fs_glob.hpp" #include "fs_statvfs_cache.hpp" #include "hw_cpu.hpp" #include "num.hpp" #include "policy.hpp" #include "str.hpp" #include "version.hpp" #include "fuse.h" #include #include #include #include #include #include #include #include #include #include using std::string; using std::vector; enum { MERGERFS_OPT_HELP, MERGERFS_OPT_VERSION }; namespace l { static int calculate_thread_count(int threads_) { int tc; if(threads_ > 0) return threads_; tc = hw::cpu::logical_core_count(); if(threads_ < 0) tc /= -threads_; if(tc == 0) tc = 1; return tc; } } static void set_option(const std::string &option_, fuse_args *args_) { fuse_opt_add_arg(args_,"-o"); fuse_opt_add_arg(args_,option_.c_str()); } static void set_kv_option(const std::string &key_, const std::string &val_, fuse_args *args_) { std::string option; option = key_ + '=' + val_; set_option(option,args_); } static void set_threads(Config::Write &cfg_, fuse_args *args_) { int threads; threads = l::calculate_thread_count(cfg_->threads); cfg_->threads = threads; set_kv_option("threads",cfg_->threads.to_string(),args_); } static void set_fsname(Config::Write &cfg_, fuse_args *args_) { if(cfg_->fsname->empty()) { vector paths; cfg_->branches->to_paths(paths); if(paths.size() > 0) cfg_->fsname = str::remove_common_prefix_and_join(paths,':'); } set_kv_option("fsname",cfg_->fsname,args_); } static void set_subtype(fuse_args *args_) { set_kv_option("subtype","mergerfs",args_); } static void set_default_options(fuse_args *args_) { set_option("default_permissions",args_); } static int parse_and_process_kv_arg(Config::Write &cfg_, Config::ErrVec *errs_, const std::string &key_, const std::string &val_) { int rv; std::string key(key_); std::string val(val_); rv = 0; if(key == "config") return ((cfg_->from_file(val_,errs_) < 0) ? 1 : 0); ef(key == "attr_timeout") key = "cache.attr"; ef(key == "entry_timeout") key = "cache.entry"; ef(key == "negative_entry") key = "cache.negative_entry"; ef(key == "direct_io" && val.empty()) val = "true"; ef(key == "kernel_cache" && val.empty()) val = "true"; ef(key == "auto_cache" && val.empty()) val = "true"; ef(key == "async_read" && val.empty()) val = "true"; ef(key == "sync_read" && val.empty()) {key = "async_read", val = "false";} ef(key == "defaults") return 0; ef(key == "hard_remove") return 0; ef(key == "atomic_o_trunc") return 0; ef(key == "big_writes") return 0; ef(key == "cache.open") return 0; if(cfg_->has_key(key) == false) return 1; rv = cfg_->set_raw(key,val); if(rv) errs_->push_back({rv,key+'='+val}); return 0; } static int process_opt(Config::Write &cfg_, Config::ErrVec *errs_, const std::string &arg_) { std::string key; std::string val; str::splitkv(arg_,'=',&key,&val); key = str::trim(key); val = str::trim(val); return parse_and_process_kv_arg(cfg_,errs_,key,val); } static int process_branches(Config::Write &cfg_, Config::ErrVec *errs_, const char *arg_) { int rv; string arg; arg = arg_; rv = cfg_->set_raw("branches",arg); if(rv) errs_->push_back({rv,"branches="+arg}); return 0; } static int process_mount(Config::Write &cfg_, Config::ErrVec *errs_, const char *arg_) { int rv; string arg; arg = arg_; rv = cfg_->set_raw("mount",arg); if(rv) errs_->push_back({rv,"mount="+arg}); return 1; } static void usage(void) { std::cout << "Usage: mergerfs [options] \n" "\n" " -o [opt,...] mount options\n" " -h --help print help\n" " -v --version print version\n" "\n" "mergerfs options:\n" " ':' delimited list of directories. Supports\n" " shell globbing (must be escaped in shell)\n" " -o config=FILE Read options from file in key=val format\n" " -o func.FUNC=POLICY Set function FUNC to policy POLICY\n" " -o category.CAT=POLICY Set functions in category CAT to POLICY\n" " -o fsname=STR Sets the name of the filesystem.\n" " -o cache.open=INT 'open' policy cache timeout in seconds.\n" " default = 0 (disabled)\n" " -o cache.statfs=INT 'statfs' cache timeout in seconds. Used by\n" " policies. default = 0 (disabled)\n" " -o cache.files=libfuse|off|partial|full|auto-full\n" " * libfuse: Use direct_io, kernel_cache, auto_cache\n" " values directly\n" " * off: Disable page caching\n" " * partial: Clear page cache on file open\n" " * full: Keep cache on file open\n" " * auto-full: Keep cache if mtime & size not changed\n" " default = libfuse\n" " -o cache.writeback=BOOL\n" " Enable kernel writeback caching (if supported)\n" " cache.files must be enabled as well.\n" " default = false\n" " -o cache.symlinks=BOOL\n" " Enable kernel caching of symlinks (if supported)\n" " default = false\n" " -o cache.readdir=BOOL\n" " Enable kernel caching readdir (if supported)\n" " default = false\n" " -o cache.attr=INT File attribute cache timeout in seconds.\n" " default = 1\n" " -o cache.entry=INT File name lookup cache timeout in seconds.\n" " default = 1\n" " -o cache.negative_entry=INT\n" " Negative file name lookup cache timeout in\n" " seconds. default = 0\n" " -o use_ino Have mergerfs generate inode values rather than\n" " autogenerated by libfuse. Suggested.\n" " -o inodecalc=passthrough|path-hash|devino-hash|hybrid-hash\n" " Selects the inode calculation algorithm.\n" " default = hybrid-hash\n" " -o minfreespace=INT Minimum free space needed for certain policies.\n" " default = 4G\n" " -o moveonenospc=BOOL Try to move file to another drive when ENOSPC\n" " on write. default = false\n" " -o dropcacheonclose=BOOL\n" " When a file is closed suggest to OS it drop\n" " the file's cache. This is useful when using\n" " 'cache.files'. default = false\n" " -o symlinkify=BOOL Read-only files, after a timeout, will be turned\n" " into symlinks. Read docs for limitations and\n" " possible issues. default = false\n" " -o symlinkify_timeout=INT\n" " Timeout in seconds before files turn to symlinks.\n" " default = 3600\n" " -o nullrw=BOOL Disables reads and writes. For benchmarking.\n" " default = false\n" " -o ignorepponrename=BOOL\n" " Ignore path preserving when performing renames\n" " and links. default = false\n" " -o link_cow=BOOL Delink/clone file on open to simulate CoW.\n" " default = false\n" " -o nfsopenhack=off|git|all\n" " A workaround for exporting mergerfs over NFS\n" " where there are issues with creating files for\n" " write while setting the mode to read-only.\n" " default = off\n" " -o security_capability=BOOL\n" " When disabled return ENOATTR when the xattr\n" " security.capability is queried. default = true\n" " -o xattr=passthrough|noattr|nosys\n" " Runtime control of xattrs. By default xattr\n" " requests will pass through to the underlying\n" " filesystems. notattr will short circuit as if\n" " nothing exists. nosys will respond as if not\n" " supported or disabled. default = passthrough\n" " -o statfs=base|full When set to 'base' statfs will use all branches\n" " when performing statfs calculations. 'full' will\n" " only include branches on which that path is\n" " available. default = base\n" " -o statfs_ignore=none|ro|nc\n" " 'ro' will cause statfs calculations to ignore\n" " available space for branches mounted or tagged\n" " as 'read only' or 'no create'. 'nc' will ignore\n" " available space for branches tagged as\n" " 'no create'. default = none\n" " -o posix_acl=BOOL Enable POSIX ACL support. default = false\n" " -o async_read=BOOL If disabled or unavailable the kernel will\n" " ensure there is at most one pending read \n" " request per file and will attempt to order\n" " requests by offset. default = true\n" << std::endl; } static int option_processor(void *data_, const char *arg_, int key_, fuse_args *outargs_) { Config::Write cfg; Config::ErrVec *errs = (Config::ErrVec*)data_; switch(key_) { case FUSE_OPT_KEY_OPT: return process_opt(cfg,errs,arg_); case FUSE_OPT_KEY_NONOPT: if(cfg->branches->empty()) return process_branches(cfg,errs,arg_); else return process_mount(cfg,errs,arg_); case MERGERFS_OPT_HELP: usage(); exit(0); case MERGERFS_OPT_VERSION: std::cout << "mergerfs version: " << (MERGERFS_VERSION[0] ? MERGERFS_VERSION : "unknown") << std::endl; exit(0); default: break; } return 0; } namespace options { void parse(fuse_args *args_, Config::ErrVec *errs_) { Config::Write cfg; const struct fuse_opt opts[] = { FUSE_OPT_KEY("-h",MERGERFS_OPT_HELP), FUSE_OPT_KEY("--help",MERGERFS_OPT_HELP), FUSE_OPT_KEY("-v",MERGERFS_OPT_VERSION), FUSE_OPT_KEY("-V",MERGERFS_OPT_VERSION), FUSE_OPT_KEY("--version",MERGERFS_OPT_VERSION), {NULL,-1U,0} }; fuse_opt_parse(args_, errs_, opts, ::option_processor); if(cfg->branches->empty()) errs_->push_back({0,"branches not set"}); if(cfg->mount->empty()) errs_->push_back({0,"mountpoint not set"}); set_default_options(args_); set_fsname(cfg,args_); set_subtype(args_); set_threads(cfg,args_); } } mergerfs-2.33.5/src/fuse_rmdir.cpp0000644000000000000000000000600214225522122015541 0ustar rootroot/* Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "config.hpp" #include "errno.hpp" #include "fs_path.hpp" #include "fs_rmdir.hpp" #include "fs_unlink.hpp" #include "ugid.hpp" #include "fuse.h" #include #include using std::string; using std::vector; namespace error { static int calc(const int rv_, const int prev_, const int cur_) { if(prev_ != 0) return prev_; if(rv_ == -1) return cur_; return 0; } } namespace l { static int should_unlink(int rv_, int errno_, FollowSymlinks followsymlinks_) { return ((rv_ == -1) && (errno_ == ENOTDIR) && (followsymlinks_ != FollowSymlinks::ENUM::NEVER)); } static int rmdir_core(const string &basepath_, const char *fusepath_, const FollowSymlinks followsymlinks_, const int error_) { int rv; string fullpath; fullpath = fs::path::make(basepath_,fusepath_); rv = fs::rmdir(fullpath); if(l::should_unlink(rv,errno,followsymlinks_)) rv = fs::unlink(fullpath); return error::calc(rv,error_,errno); } static int rmdir_loop(const StrVec &basepaths_, const char *fusepath_, const FollowSymlinks followsymlinks_) { int error; error = 0; for(size_t i = 0, ei = basepaths_.size(); i != ei; i++) { error = l::rmdir_core(basepaths_[i],fusepath_,followsymlinks_,error); } return -error; } static int rmdir(const Policy::Action &actionFunc_, const Branches &branches_, const FollowSymlinks followsymlinks_, const char *fusepath_) { int rv; vector basepaths; rv = actionFunc_(branches_,fusepath_,&basepaths); if(rv == -1) return -errno; return l::rmdir_loop(basepaths,fusepath_,followsymlinks_); } } namespace FUSE { int rmdir(const char *fusepath_) { Config::Read cfg; const fuse_context *fc = fuse_get_context(); const ugid::Set ugid(fc->uid,fc->gid); return l::rmdir(cfg->func.rmdir.policy, cfg->branches, cfg->follow_symlinks, fusepath_); } } mergerfs-2.33.5/src/config_xattr.cpp0000644000000000000000000000260514225522122016076 0ustar rootroot/* ISC License Copyright (c) 2020, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "config_xattr.hpp" #include "ef.hpp" #include "errno.hpp" template<> std::string XAttr::to_string() const { switch(_data) { case XAttr::ENUM::PASSTHROUGH: return "passthrough"; case XAttr::ENUM::NOSYS: return "nosys"; case XAttr::ENUM::NOATTR: return "noattr"; } return "invalid"; } template<> int XAttr::from_string(const std::string &s_) { if(s_ == "passthrough") _data = XAttr::ENUM::PASSTHROUGH; ef(s_ == "nosys") _data = XAttr::ENUM::NOSYS; ef(s_ == "noattr") _data = XAttr::ENUM::NOATTR; else return -EINVAL; return 0; } mergerfs-2.33.5/src/fs_open.hpp0000644000000000000000000000315214225522122015043 0ustar rootroot/* ISC License Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include #include #include #include namespace fs { static inline int open(const char *path_, const int flags_) { return ::open(path_,flags_); } static inline int open(const char *path_, const int flags_, const mode_t mode_) { return ::open(path_,flags_,mode_); } static inline int open(const std::string &path_, const int flags_) { return fs::open(path_.c_str(),flags_); } static inline int open(const std::string &path_, const int flags_, const mode_t mode_) { return fs::open(path_.c_str(),flags_,mode_); } static inline int open_dir_ro(const std::string &path_) { return fs::open(path_,O_RDONLY|O_DIRECTORY); } } mergerfs-2.33.5/src/policies.hpp0000644000000000000000000001011214225522122015213 0ustar rootroot/* ISC License Copyright (c) 2020, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include "policy_all.hpp" #include "policy_epall.hpp" #include "policy_epff.hpp" #include "policy_eplfs.hpp" #include "policy_eplus.hpp" #include "policy_epmfs.hpp" #include "policy_eppfrd.hpp" #include "policy_eprand.hpp" #include "policy_erofs.hpp" #include "policy_ff.hpp" #include "policy_lfs.hpp" #include "policy_lus.hpp" #include "policy_mfs.hpp" #include "policy_msplfs.hpp" #include "policy_msplus.hpp" #include "policy_mspmfs.hpp" #include "policy_msppfrd.hpp" #include "policy_newest.hpp" #include "policy_pfrd.hpp" #include "policy_rand.hpp" struct Policies { struct Action { static Policy::ActionImpl *find(const std::string &name); static Policy::All::Action all; static Policy::EPAll::Action epall; static Policy::EPFF::Action epff; static Policy::EPLFS::Action eplfs; static Policy::EPLUS::Action eplus; static Policy::EPMFS::Action epmfs; static Policy::EPPFRD::Action eppfrd; static Policy::EPRand::Action eprand; static Policy::ERoFS::Action erofs; static Policy::FF::Action ff; static Policy::LFS::Action lfs; static Policy::LUS::Action lus; static Policy::MFS::Action mfs; static Policy::MSPLFS::Action msplfs; static Policy::MSPLUS::Action msplus; static Policy::MSPMFS::Action mspmfs; static Policy::MSPPFRD::Action msppfrd; static Policy::Newest::Action newest; static Policy::PFRD::Action pfrd; static Policy::Rand::Action rand; }; struct Create { static Policy::CreateImpl *find(const std::string &name); static Policy::All::Create all; static Policy::EPAll::Create epall; static Policy::EPFF::Create epff; static Policy::EPLFS::Create eplfs; static Policy::EPLUS::Create eplus; static Policy::EPMFS::Create epmfs; static Policy::EPPFRD::Create eppfrd; static Policy::EPRand::Create eprand; static Policy::ERoFS::Create erofs; static Policy::FF::Create ff; static Policy::LFS::Create lfs; static Policy::LUS::Create lus; static Policy::MFS::Create mfs; static Policy::MSPLFS::Create msplfs; static Policy::MSPLUS::Create msplus; static Policy::MSPMFS::Create mspmfs; static Policy::MSPPFRD::Create msppfrd; static Policy::Newest::Create newest; static Policy::PFRD::Create pfrd; static Policy::Rand::Create rand; }; struct Search { static Policy::SearchImpl *find(const std::string &name); static Policy::All::Search all; static Policy::EPAll::Search epall; static Policy::EPFF::Search epff; static Policy::EPLFS::Search eplfs; static Policy::EPLUS::Search eplus; static Policy::EPMFS::Search epmfs; static Policy::EPPFRD::Search eppfrd; static Policy::EPRand::Search eprand; static Policy::ERoFS::Search erofs; static Policy::FF::Search ff; static Policy::LFS::Search lfs; static Policy::LUS::Search lus; static Policy::MFS::Search mfs; static Policy::MSPLFS::Search msplfs; static Policy::MSPLUS::Search msplus; static Policy::MSPMFS::Search mspmfs; static Policy::MSPPFRD::Search msppfrd; static Policy::Newest::Search newest; static Policy::PFRD::Search pfrd; static Policy::Rand::Search rand; }; }; mergerfs-2.33.5/src/fs_copydata_readwrite.cpp0000644000000000000000000000502514225522122017750 0ustar rootroot/* Copyright (c) 2018, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "errno.hpp" #include "fs_fstat.hpp" #include "fs_lseek.hpp" #include "fs_read.hpp" #include "fs_write.hpp" #include using std::vector; namespace l { static int writen(const int fd_, const char *buf_, const size_t size_) { ssize_t rv; size_t nleft; nleft = size_; do { rv = fs::write(fd_,buf_,nleft); if((rv == -1) && (errno == EINTR)) continue; if(rv == -1) return -1; nleft -= rv; buf_ += rv; } while(nleft > 0); return size_; } static int copydata_readwrite(const int src_fd_, const int dst_fd_, const size_t count_, const size_t blocksize_) { ssize_t nr; ssize_t nw; ssize_t bufsize; size_t totalwritten; vector buf; bufsize = (blocksize_ * 16); buf.resize(bufsize); fs::lseek(src_fd_,0,SEEK_SET); totalwritten = 0; while(totalwritten < count_) { nr = fs::read(src_fd_,&buf[0],bufsize); if(nr == 0) return totalwritten; if((nr == -1) && (errno == EINTR)) continue; if(nr == -1) return -1; nw = l::writen(dst_fd_,&buf[0],nr); if(nw == -1) return -1; totalwritten += nw; } return totalwritten; } } namespace fs { int copydata_readwrite(const int src_fd_, const int dst_fd_) { int rv; struct stat st; rv = fs::fstat(src_fd_,&st); if(rv == -1) return -1; return l::copydata_readwrite(src_fd_, dst_fd_, st.st_size, st.st_blksize); } } mergerfs-2.33.5/src/policy_mfs.hpp0000644000000000000000000000313114225522122015553 0ustar rootroot/* ISC License Copyright (c) 2020, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include "policy.hpp" namespace Policy { namespace MFS { class Action final : public Policy::ActionImpl { public: Action() : Policy::ActionImpl("mfs") {} public: int operator()(const Branches::CPtr&,const char*,StrVec*) const final; }; class Create final : public Policy::CreateImpl { public: Create() : Policy::CreateImpl("mfs") {} public: int operator()(const Branches::CPtr&,const char*,StrVec*) const final; bool path_preserving() const final { return false; } }; class Search final : public Policy::SearchImpl { public: Search() : Policy::SearchImpl("mfs") {} public: int operator()(const Branches::CPtr&,const char*,StrVec*) const final; }; } } mergerfs-2.33.5/src/fs_eaccess.hpp0000644000000000000000000000221414225522122015506 0ustar rootroot/* ISC License Copyright (c) 2020, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include "fs_faccessat.hpp" namespace fs { static inline int eaccess(const char *path_, const int mode_) { return fs::faccessat(AT_FDCWD,path_,mode_,AT_EACCESS); } static inline int eaccess(const std::string &path_, const int mode_) { return fs::eaccess(path_.c_str(),mode_); } } mergerfs-2.33.5/src/fuse_unlink.hpp0000644000000000000000000000153514225522122015737 0ustar rootroot/* Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once namespace FUSE { int unlink(const char *fusepath); } mergerfs-2.33.5/src/config_rename_exdev.cpp0000644000000000000000000000275314225522122017402 0ustar rootroot/* ISC License Copyright (c) 2021, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "config_rename_exdev.hpp" #include "ef.hpp" #include "errno.hpp" template<> std::string RenameEXDEV::to_string(void) const { switch(_data) { case RenameEXDEV::ENUM::PASSTHROUGH: return "passthrough"; case RenameEXDEV::ENUM::REL_SYMLINK: return "rel-symlink"; case RenameEXDEV::ENUM::ABS_SYMLINK: return "abs-symlink"; } return "invalid"; } template<> int RenameEXDEV::from_string(const std::string &s_) { if(s_ == "passthrough") _data = RenameEXDEV::ENUM::PASSTHROUGH; ef(s_ == "rel-symlink") _data = RenameEXDEV::ENUM::REL_SYMLINK; ef(s_ == "abs-symlink") _data = RenameEXDEV::ENUM::ABS_SYMLINK; else return -EINVAL; return 0; } mergerfs-2.33.5/src/errno.hpp0000644000000000000000000000214314225522122014536 0ustar rootroot/* Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include #if defined(ENODATA) && !defined(ENOATTR) #define ENOATTR ENODATA #endif #if defined(ENOATTR) && !defined(ENODATA) #define ENODATA ENOATTR #endif #if !defined(ENOATTR) && !defined(ENODATA) #error "Neither ENOATTR or ENODATA defined: please contact mergerfs author with platform information" #endif mergerfs-2.33.5/src/policy_lus.cpp0000644000000000000000000000540114225522122015566 0ustar rootroot/* Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "errno.hpp" #include "fs_exists.hpp" #include "fs_info.hpp" #include "fs_path.hpp" #include "policies.hpp" #include "policy.hpp" #include "policy_error.hpp" #include "policy_lus.hpp" #include "strvec.hpp" #include #include #include using std::string; using std::vector; namespace lus { static int create(const Branches::CPtr &branches_, StrVec *paths_) { int rv; int error; uint64_t lus; fs::info_t info; const string *basepath; error = ENOENT; lus = std::numeric_limits::max(); basepath = NULL; for(auto &branch : *branches_) { if(branch.ro_or_nc()) error_and_continue(error,EROFS); rv = fs::info(branch.path,&info); if(rv == -1) error_and_continue(error,ENOENT); if(info.readonly) error_and_continue(error,EROFS); if(info.spaceavail < branch.minfreespace()) error_and_continue(error,ENOSPC); if(info.spaceused >= lus) continue; lus = info.spaceused; basepath = &branch.path; } if(basepath == NULL) return (errno=error,-1); paths_->push_back(*basepath); return 0; } } int Policy::LUS::Action::operator()(const Branches::CPtr &branches_, const char *fusepath_, StrVec *paths_) const { return Policies::Action::eplus(branches_,fusepath_,paths_); } int Policy::LUS::Create::operator()(const Branches::CPtr &branches_, const char *fusepath_, StrVec *paths_) const { return ::lus::create(branches_,paths_); } int Policy::LUS::Search::operator()(const Branches::CPtr &branches_, const char *fusepath_, StrVec *paths_) const { return Policies::Search::eplus(branches_,fusepath_,paths_); } mergerfs-2.33.5/src/from_string.hpp0000644000000000000000000000211714225522122015743 0ustar rootroot/* ISC License Copyright (c) 2019, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include #include namespace str { int from(const std::string &, bool *); int from(const std::string &, int *); int from(const std::string &, uint64_t *); int from(const std::string &, std::string *); int from(const std::string &, const std::string *); } mergerfs-2.33.5/src/policy_msppfrd.hpp0000644000000000000000000000315114225522122016443 0ustar rootroot/* ISC License Copyright (c) 2020, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include "policy.hpp" namespace Policy { namespace MSPPFRD { class Action final : public Policy::ActionImpl { public: Action() : Policy::ActionImpl("msppfrd") {} public: int operator()(const Branches::CPtr&,const char*,StrVec*) const final; }; class Create final : public Policy::CreateImpl { public: Create() : Policy::CreateImpl("msppfrd") {} public: int operator()(const Branches::CPtr&,const char*,StrVec*) const final; bool path_preserving() const final { return true; }; }; class Search final : public Policy::SearchImpl { public: Search() : Policy::SearchImpl("msppfrd") {} public: int operator()(const Branches::CPtr&,const char*,StrVec*) const final; }; } } mergerfs-2.33.5/src/config_link_exdev.hpp0000644000000000000000000000173514225522122017074 0ustar rootroot/* ISC License Copyright (c) 2021, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include "enum.hpp" enum class LinkEXDEVEnum { PASSTHROUGH, REL_SYMLINK, ABS_BASE_SYMLINK, ABS_POOL_SYMLINK }; typedef Enum LinkEXDEV; mergerfs-2.33.5/src/fs_attr_unsupported.icpp0000644000000000000000000000211114225522122017662 0ustar rootroot/* Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "errno.hpp" #include namespace fs { namespace attr { int copy(const int fdin, const int fdout) { return (errno=ENOTSUP,-1); } int copy(const std::string &from, const std::string &to) { return (errno=ENOTSUP,-1); } } } mergerfs-2.33.5/src/fuse_readdir.cpp0000644000000000000000000000302414225522122016037 0ustar rootroot/* ISC License Copyright (c) 2020, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "fuse_readdir_posix.hpp" #include "fuse_readdir_linux.hpp" #include "config.hpp" #include "dirinfo.hpp" #include "rwlock.hpp" #include "ugid.hpp" #include "fuse.h" namespace FUSE { int readdir(const fuse_file_info_t *ffi_, fuse_dirents_t *buf_) { Config::Read cfg; DirInfo *di = reinterpret_cast(ffi_->fh); const fuse_context *fc = fuse_get_context(); const ugid::Set ugid(fc->uid,fc->gid); switch(cfg->readdir) { case ReadDir::ENUM::LINUX: return FUSE::readdir_linux(cfg->branches,di->fusepath.c_str(),buf_); default: case ReadDir::ENUM::POSIX: return FUSE::readdir_posix(cfg->branches,di->fusepath.c_str(),buf_); } } } mergerfs-2.33.5/src/fs_copydata_copy_file_range.hpp0000644000000000000000000000165614225522122021122 0ustar rootroot/* Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include namespace fs { int64_t copydata_copy_file_range(const int src_fd, const int dst_fd); } mergerfs-2.33.5/src/fuse_access.hpp0000644000000000000000000000157014225522122015677 0ustar rootroot/* Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once namespace FUSE { int access(const char *fusepath, int mask); } mergerfs-2.33.5/src/fs_file_size.cpp0000644000000000000000000000201014225522122016036 0ustar rootroot/* ISC License Copyright (c) 2020, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "fs_fstat.hpp" #include namespace fs { int64_t file_size(const int fd_) { int rv; struct stat st; rv = fs::fstat(fd_,&st); if(rv == -1) return -1; return st.st_size; } } mergerfs-2.33.5/src/fuse_truncate.hpp0000644000000000000000000000162414225522122016263 0ustar rootroot/* Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include namespace FUSE { int truncate(const char *fusepath, off_t size); } mergerfs-2.33.5/src/fs_opendir.hpp0000644000000000000000000000175614225522122015552 0ustar rootroot/* ISC License Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include #include #include namespace fs { static inline DIR * opendir(const std::string &name_) { return ::opendir(name_.c_str()); } } mergerfs-2.33.5/src/fs_futimens_freebsd_11.hpp0000644000000000000000000000175214225522122017733 0ustar rootroot/* ISC License Copyright (c) 2020, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include namespace fs { static inline int futimens(const int fd_, const struct timespec ts_[2]) { return ::futimens(fd_,ts_); } } mergerfs-2.33.5/src/config_readdir.cpp0000644000000000000000000000242414225522122016345 0ustar rootroot/* ISC License Copyright (c) 2020, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "config_readdir.hpp" #include "ef.hpp" #include "errno.hpp" template<> int ReadDir::from_string(const std::string &s_) { if(s_ == "posix") _data = ReadDir::ENUM::POSIX; ef(s_ == "linux") _data = ReadDir::ENUM::LINUX; else return -EINVAL; return 0; } template<> std::string ReadDir::to_string(void) const { switch(_data) { case ReadDir::ENUM::POSIX: return "posix"; case ReadDir::ENUM::LINUX: return "linux"; } return "invalid"; } mergerfs-2.33.5/src/config_link_exdev.cpp0000644000000000000000000000321014225522122017055 0ustar rootroot/* ISC License Copyright (c) 2021, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "config_link_exdev.hpp" #include "ef.hpp" #include "errno.hpp" template<> std::string LinkEXDEV::to_string(void) const { switch(_data) { case LinkEXDEV::ENUM::PASSTHROUGH: return "passthrough"; case LinkEXDEV::ENUM::REL_SYMLINK: return "rel-symlink"; case LinkEXDEV::ENUM::ABS_BASE_SYMLINK: return "abs-base-symlink"; case LinkEXDEV::ENUM::ABS_POOL_SYMLINK: return "abs-pool-symlink"; } return "invalid"; } template<> int LinkEXDEV::from_string(const std::string &s_) { if(s_ == "passthrough") _data = LinkEXDEV::ENUM::PASSTHROUGH; ef(s_ == "rel-symlink") _data = LinkEXDEV::ENUM::REL_SYMLINK; ef(s_ == "abs-base-symlink") _data = LinkEXDEV::ENUM::ABS_BASE_SYMLINK; ef(s_ == "abs-pool-symlink") _data = LinkEXDEV::ENUM::ABS_POOL_SYMLINK; else return -EINVAL; return 0; } mergerfs-2.33.5/src/config_statfs.cpp0000644000000000000000000000240114225522122016232 0ustar rootroot/* ISC License Copyright (c) 2020, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "config_statfs.hpp" #include "ef.hpp" #include "errno.hpp" template<> std::string StatFS::to_string() const { switch(_data) { case StatFS::ENUM::BASE: return "base"; case StatFS::ENUM::FULL: return "full"; } return "invalid"; } template<> int StatFS::from_string(const std::string &s_) { if(s_ == "base") _data = StatFS::ENUM::BASE; ef(s_ == "full") _data = StatFS::ENUM::FULL; else return -EINVAL; return 0; } mergerfs-2.33.5/src/fuse_getattr.hpp0000644000000000000000000000175114225522122016111 0ustar rootroot/* Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include #include #include namespace FUSE { int getattr(const char *fusepath, struct stat *buf, fuse_timeouts_t *timeout); } mergerfs-2.33.5/src/fs_lseek.hpp0000644000000000000000000000201614225522122015203 0ustar rootroot/* ISC License Copyright (c) 2016, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #pragma once #include #include namespace fs { static inline off_t lseek(const int fd_, const off_t offset_, const int whence_) { return ::lseek(fd_,offset_,whence_); } }