mergerfs-2.28.1/0000775000175000017500000000000013477573310012042 5ustar bilebilemergerfs-2.28.1/debian/0000775000175000017500000000000013471033317013254 5ustar bilebilemergerfs-2.28.1/debian/docs0000664000175000017500000000001212560567537014136 0ustar bilebileREADME.md mergerfs-2.28.1/debian/rules0000775000175000017500000000032613471033317014335 0ustar bilebile#!/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.28.1/debian/control0000664000175000017500000000106213467101456014663 0ustar bilebileSource: 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.28.1/debian/compat0000664000175000017500000000000212560567537014470 0ustar bilebile9 mergerfs-2.28.1/debian/copyright0000664000175000017500000000203513361446236015215 0ustar bilebileFormat: 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.28.1/VERSION0000664000175000017500000000000613477573310013106 0ustar bilebile2.28.1mergerfs-2.28.1/ChangeLog0000664000175000017500000007175113477573310013627 0ustar bilebilemergerfs (2.28.1~ubuntu-bionic) bionic; urgency=medium * 5ca928e accept old arguments for backwards compatibility -- trapexit Mon, 10 Jun 2019 20:24:36 -0400 mergerfs (2.28.0~ubuntu-bionic) bionic; 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~ubuntu-bionic) bionic; urgency=medium * 61cded5 fix for unlink race condition -- trapexit Tue, 21 May 2019 23:09:13 -0400 mergerfs (2.27.0~ubuntu-bionic) bionic; 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~ubuntu-bionic) bionic; 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~ubuntu-bionic) bionic; urgency=medium * e052446 fix setting of fsname -- trapexit Sun, 12 May 2019 14:30:37 -0400 mergerfs (2.26.0~ubuntu-bionic) bionic; 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~ubuntu-bionic) bionic; 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~ubuntu-bionic) bionic; 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~ubuntu-bionic) bionic; urgency=medium * 73e8867 fix version.hpp creation, again -- trapexit Sat, 24 Mar 2018 06:53:35 -0700 mergerfs (2.24.1~ubuntu-bionic) bionic; urgency=medium * 24e690b fix versioning with tarball building -- trapexit Fri, 16 Mar 2018 23:27:04 -0400 mergerfs (2.24.0~ubuntu-bionic) bionic; 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~ubuntu-bionic) bionic; 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~ubuntu-bionic) bionic; 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~ubuntu-bionic) bionic; 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~ubuntu-bionic) bionic; 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~ubuntu-bionic) bionic; 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~ubuntu-bionic) bionic; 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~ubuntu-bionic) bionic; 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~ubuntu-bionic) bionic; 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~ubuntu-bionic) bionic; 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~ubuntu-bionic) bionic; 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~ubuntu-bionic) bionic; 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~ubuntu-bionic) bionic; 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~ubuntu-bionic) bionic; 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~ubuntu-bionic) bionic; 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~ubuntu-bionic) bionic; 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~ubuntu-bionic) bionic; 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~ubuntu-bionic) bionic; 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~ubuntu-bionic) bionic; 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~ubuntu-bionic) bionic; 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~ubuntu-bionic) bionic; 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~ubuntu-bionic) bionic; 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~ubuntu-bionic) bionic; urgency=medium * f779f82 fix statvfs drive dedup. closes #209 -- Antonio SJ Musumeci Fri, 22 Jan 2016 10:16:01 -0500 mergerfs (2.9.0~ubuntu-bionic) bionic; 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~ubuntu-bionic) bionic; 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~ubuntu-bionic) bionic; 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~ubuntu-bionic) bionic; 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~ubuntu-bionic) bionic; 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~ubuntu-bionic) bionic; 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~ubuntu-bionic) bionic; 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~ubuntu-bionic) bionic; 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~ubuntu-bionic) bionic; 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~ubuntu-bionic) bionic; 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~ubuntu-bionic) bionic; 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~ubuntu-bionic) bionic; 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~ubuntu-bionic) bionic; 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~ubuntu-bionic) bionic; 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~ubuntu-bionic) bionic; 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~ubuntu-bionic) bionic; 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~ubuntu-bionic) bionic; 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~ubuntu-bionic) bionic; 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~ubuntu-bionic) bionic; 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~ubuntu-bionic) bionic; 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~ubuntu-bionic) bionic; 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~ubuntu-bionic) bionic; 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~ubuntu-bionic) bionic; 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~ubuntu-bionic) bionic; 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~ubuntu-bionic) bionic; urgency=medium * 3b6c748 use geteuid syscall as well -- Antonio SJ Musumeci Mon, 23 Jun 2014 12:17:09 -0400 mergerfs (1.2.0~ubuntu-bionic) bionic; 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~ubuntu-bionic) bionic; 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~ubuntu-bionic) bionic; 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~ubuntu-bionic) bionic; urgency=medium * 7f640c4 fix building without libattr -- Antonio SJ Musumeci Mon, 19 May 2014 09:34:31 -0400 mergerfs-2.28.1/.gitignore0000664000175000017500000000036413361446236014033 0ustar bilebile# 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.28.1/mergerfs.spec0000664000175000017500000000243113471033317014520 0ustar bilebileName: mergerfs Version: __VERSION__ Release: 1%{?dist} Summary: A FUSE 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 %{_bindir}/* %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.28.1/tools/0000775000175000017500000000000013471033317013172 5ustar bilebilemergerfs-2.28.1/tools/git2debcl0000775000175000017500000001062613367322164014771 0ustar bilebile#!/usr/bin/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 git_tags(): args = ["git", "tag", '-l'] tags = subprocess.check_output(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 subprocess.check_output(args).strip().split('\n') def git_author_and_time(tag): args = ['git','log','-1','--format=-- %an <%ae> %cD',tag] return subprocess.check_output(args).strip() def git_version(): args = ['git','describe','--always','--tags','--dirty'] return subprocess.check_output(args).strip() def guess_distro(): try: args = ['lsb_release','-i','-s'] return subprocess.check_output(args).strip().lower() except: return 'unknown' def guess_codename(): try: args = ['lsb_release','-c','-s'] return subprocess.check_output(args).strip().lower() except: return 'unknown' def patch_subprocess(): if "check_output" not in dir( subprocess ): # duck punch it in! def check_output(*popenargs, **kwargs): r"""Run command with arguments and return its output as a byte string. Backported from Python 2.7 as it's implemented as pure python on stdlib. >>> check_output(['/usr/bin/python', '--version']) Python 2.6.2 """ process = subprocess.Popen(stdout=subprocess.PIPE, *popenargs, **kwargs) output, unused_err = process.communicate() retcode = process.poll() if retcode: cmd = kwargs.get("args") if cmd is None: cmd = popenargs[0] error = subprocess.CalledProcessError(retcode, cmd) error.output = output raise error return output subprocess.check_output = check_output def main(): patch_subprocess() 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 = zip(tags,tags) else: tags = zip(tags,tags) tags.insert(0,(args.version,'HEAD')) for i in xrange(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('%s (%s) %s; urgency=%s\n' % (args.name,tag[0],args.codename,args.urgency)) for line in lines: print " * " + line authorandtime = git_author_and_time(tag[1]) print(' %s\n' % authorandtime) tag = prev if __name__ == "__main__": main() mergerfs-2.28.1/tools/update-version0000775000175000017500000000061613471033317016070 0ustar bilebile#!/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.28.1/tools/cppfind0000775000175000017500000000032513427205466014552 0ustar bilebile#!/bin/sh CXX="${CXX:-c++}" FUSE_CFLAGS="-Ilibfuse/include -D_FILE_OFFSET_BITS=64 -DFUSE_USE_VERSION=29" echo "#include " | ${CXX} -E ${FUSE_CFLAGS} - | grep "${1}" > /dev/null [ "$?" != "0" ]; echo $? mergerfs-2.28.1/tools/install-build-pkgs0000775000175000017500000000155113471033317016627 0ustar bilebile#!/bin/sh if [ -e /usr/bin/apt-get ]; then apt-get -qy update apt-get -qy --no-install-suggests --no-install-recommends --force-yes \ install \ build-essential git g++ debhelper \ python \ automake libtool lsb-release elif [ -e /usr/bin/dnf ]; then dnf -y update dnf -y install \ git rpm-build gcc-c++ make which python 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 mergerfs-2.28.1/man/0000775000175000017500000000000013475343677012626 5ustar bilebilemergerfs-2.28.1/man/mergerfs.10000664000175000017500000024371013477573303014522 0ustar bilebile.\"t .\" Automatically generated by Pandoc 1.19.2.4 .\" .TH "mergerfs" "1" "2019\-06\-03" "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 Runs in userspace (FUSE) .IP \[bu] 2 Configurable behaviors / file placement .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 Safe to run as root .IP \[bu] 2 Opportunistic credential caching .IP \[bu] 2 Works with heterogeneous filesystem types .IP \[bu] 2 Handling of writes to full drives (transparently move file to drive with capacity) .IP \[bu] 2 Handles pool of read\-only and read/write drives .IP \[bu] 2 Can turn read\-only files into symlinks to underlying file .IP \[bu] 2 Hard link copy\-on\-write / CoW .IP \[bu] 2 supports POSIX ACLs .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) behavior 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. .SH OPTIONS .SS mount options .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. 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\f[]: When enabled if a \f[B]write\f[] fails with \f[B]ENOSPC\f[] or \f[B]EDQUOT\f[] a scan of all drives will be done looking for the drive with the most free space which is at least the size of the file plus the amount which failed to write. An attempt to move the file to that drive will occur (keeping all metadata possible) and if successful the original is unlinked and the write retried. (default: false) .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]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=INT\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]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=INT\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.CATEGORY=POLICY\f[]: Sets policy of all FUSE functions in the provided category. Example: \f[B]category.create=mfs\f[] .IP \[bu] 2 \f[B]cache.open=INT\f[]: \[aq]open\[aq] policy cache timeout in seconds. (default: 0) .IP \[bu] 2 \f[B]cache.statfs=INT\f[]: \[aq]statfs\[aq] cache timeout in seconds. (default: 0) .IP \[bu] 2 \f[B]cache.attr=INT\f[]: File attribute cache timeout in seconds. (default: 1) .IP \[bu] 2 \f[B]cache.entry=INT\f[]: File name lookup cache timeout in seconds. (default: 1) .IP \[bu] 2 \f[B]cache.negative_entry=INT\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.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 = [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 = FUSE function .IP \[bu] 2 CATEGORY = FUSE function category .IP \[bu] 2 POLICY = mergerfs function policy .SS branches .PP The \[aq]branches\[aq] (formerly \[aq]srcmounts\[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. 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 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[] .PP Each branch can have a suffix of \f[C]=RW\f[] (read / write), \f[C]=RO\f[] (read\-only), or \f[C]=NC\f[] (no create). These suffixes work with globs as well and will apply to each path found. \f[C]RW\f[] is the default behavior and those paths will be eligible for all policy categories. \f[C]RO\f[] will exclude those paths from \f[C]create\f[] and \f[C]action\f[] policies (just as a filesystem being mounted \f[C]ro\f[] would). \f[C]NC\f[] will exclude those paths from \f[C]create\f[] policies (you can\[aq]t create but you can change / delete). .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 accessable from related tools use \f[B]/etc/fstab\f[]. .IP .nf \f[C] #\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ /mnt/disk*:/mnt/cdrom\ \ /media/drives\ \ fuse.mergerfs\ \ allow_other,use_ino\ \ \ 0\ \ \ \ \ \ \ 0 \f[] .fi .PP \f[B]NOTE:\f[] the globbing is done at mount or xattr update time (see below). 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 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 appoximately 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 \f[C]nullrw\f[] section for benchmarking examples. .SS symlinkify .PP Due to the levels of indirection introduced by mergerfs and the underlying technology FUSE there can be varying levels of performance degredation. 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. 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 boundries. .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 Example: .IP .nf \f[C] $\ dd\ if=/dev/zero\ of=/path/to/mergerfs/mount/benchmark\ ibs=1M\ obs=512\ count=1024\ iflag=dsync,nocache\ oflag=dsync,nocache\ conv=fdatasync\ status=progress 1024+0\ records\ in 2097152+0\ records\ out 1073741824\ bytes\ (1.1\ GB,\ 1.0\ GiB)\ copied,\ 15.4067\ s,\ 69.7\ MB/s $\ dd\ if=/dev/zero\ of=/path/to/mergerfs/mount/benchmark\ ibs=1M\ obs=1M\ count=1024\ iflag=dsync,nocache\ oflag=dsync,nocache\ conv=fdatasync\ status=progress 1024+0\ records\ in 1024+0\ records\ out 1073741824\ bytes\ (1.1\ GB,\ 1.0\ GiB)\ copied,\ 0.219585\ s,\ 4.9\ GB/s $\ dd\ if=/path/to/mergerfs/mount/benchmark\ of=/dev/null\ bs=512\ count=102400\ iflag=dsync,nocache\ oflag=dsync,nocache\ conv=fdatasync\ status=progress 102400+0\ records\ in 102400+0\ records\ out 52428800\ bytes\ (52\ MB,\ 50\ MiB)\ copied,\ 0.757991\ s,\ 69.2\ MB/s $\ dd\ if=/path/to/mergerfs/mount/benchmark\ of=/dev/null\ bs=1M\ count=1024\ iflag=dsync,nocache\ oflag=dsync,nocache\ conv=fdatasync\ status=progress 1024+0\ records\ in 1024+0\ records\ out 1073741824\ bytes\ (1.1\ GB,\ 1.0\ GiB)\ copied,\ 0.18405\ s,\ 5.8\ GB/s \f[] .fi .PP It\[aq]s important to test with different \f[C]obs\f[] (output block size) values since the relative overhead is greater with smaller values. As you can see above the size of a read or write can massively impact theoretical performance. If an application performs much worse through mergerfs it could very well be that it doesn\[aq]t optimally size its read and write requests. In such cases contact the mergerfs author so it can be investigated. .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. .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 will be more efficient than \f[C]noattr\f[] but will cause mergerfs\[aq] runtime control via the hidden file to stop working. .SH FUNCTIONS / POLICIES / CATEGORIES .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. 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 what file or directory is chosen when performing that behavior. 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. .PP Some functions, listed in the category \f[C]N/A\f[] below, can not be assigned the normal policies. All functions which work on file handles use the handle which was acquired by \f[C]open\f[] or \f[C]create\f[]. \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. 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. .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 sub mounts 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. .SS Function / 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 (to confirm a directory exists across all source mounts) \f[B]getattr\f[] will be used. .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. .SS Filters .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 policy type defines the sorting but filtering is mostly uniform as described below. .IP \[bu] 2 No \f[B]search\f[] policies filter. .IP \[bu] 2 All \f[B]action\f[] policies will 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[] policies will 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 If all branches are filtered an error will be returned. Typically \f[B]EROFS\f[] or \f[B]ENOSPC\f[] depending on the reasons. .SS Policy descriptions .PP .TS tab(@); lw(16.6n) lw(53.4n). T{ Policy T}@T{ Description T} _ T{ all T}@T{ Search category: same as \f[B]epall\f[]. Action category: same as \f[B]epall\f[]. Create category: 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 category: same as \f[B]epff\f[] (but more expensive because it doesn\[aq]t stop after finding a valid branch). Action category: apply to all found. Create category: 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{ eprand (existing path, random) T}@T{ Calls \f[B]epall\f[] and then randomizes. 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 category: same as \f[B]epff\f[]. Action category: same as \f[B]epff\f[]. Create category: 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 category: same as \f[B]eplfs\f[]. Action category: same as \f[B]eplfs\f[]. Create category: Pick the drive with the least available free space. T} T{ lus (least used space) T}@T{ Search category: same as \f[B]eplus\f[]. Action category: same as \f[B]eplus\f[]. Create category: Pick the drive with the least used space. T} T{ mfs (most free space) T}@T{ Search category: same as \f[B]epmfs\f[]. Action category: same as \f[B]epmfs\f[]. Create category: Pick the drive with the most available free space. T} T{ newest T}@T{ Pick the file / directory with the largest mtime. T} T{ rand (random) T}@T{ Calls \f[B]all\f[] and then randomizes. T} .TE .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 unlink .PP In FUSE there is an opaque "file handle" which is created by \f[C]open\f[], \f[C]create\f[], or \f[C]opendir\f[], passed to the kernel, and then is passed back to the FUSE userland application by the kernel. Unfortunately, the FUSE kernel driver does not always send the file handle when it theoretically could/should. This complicates certain behaviors / workflows particularly in the high level API. As a result mergerfs is currently doing a few hacky things. .PP libfuse2 and libfuse3, when using the high level API, will rename names to \f[C]\&.fuse_hiddenXXXXXX\f[] if the file is open when unlinked or renamed over. It does this so the file is still available when a request referencing the now missing file is made. This file however keeps a \f[C]rmdir\f[] from succeeding and can be picked up by software reading directories. .PP The change mergerfs has done is that if a file is open when an unlink or rename happens it will open the file and keep it open till closed by all those who opened it prior. When a request comes in referencing that file and it doesn\[aq]t include a file handle it will instead use the file handle created at unlink/rename time. .PP This won\[aq]t result in technically proper behavior but close enough for many usecases. .PP The plan is to rewrite mergerfs to use the low level API so these invasive libfuse changes are no longer necessary. .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[] 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). 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 complient 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 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 it\[aq]s 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 it\[aq]s 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 BUILDING .PP \f[B]NOTE:\f[] Prebuilt packages can be found at: https://github.com/trapexit/mergerfs/releases .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_version_arch.deb \f[] .fi .SS 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 RUNTIME CONFIG .SS .mergerfs pseudo file .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 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\-existant files). .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 configuarable at runtime via xattr \f[C]user.mergerfs.cache.readdir\f[]. .SS writeback caching .PP writeback caching is a technique for improving write speeds by batching writes at a faster device and then bulk writing to the slower device. With FUSE the kernel will wait for a number of writes to be made and then send it to the filesystem as one request. mergerfs currently uses a modified and vendored libfuse 2.9.7 which does not support writeback caching. Adding said feature should not be difficult but benchmarking needs to be done to see if what effect it will have. .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. Perhaps setting \f[C]minfreespace\f[] to the size of the largest cache drive. .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". .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. .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\ \-axqHAXWES\ \-\-preallocate\ \-\-remove\-source\-files\ "${CACHE}/./${FILE}"\ "${BACKING}/" done \f[] .fi .SH TIPS / NOTES .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 by the same user otherwise strange permission issues may arise. .IP \[bu] 2 https://github.com/trapexit/backup\-and\-recovery\-howtos : A set of guides / howtos on creating a data storage system, backing it up, maintaining it, and recovering from failure. .IP \[bu] 2 If you don\[aq]t see some directories and files you expect in a merged point or policies seem to skip drives be sure the user has permission to all the underlying directories. Use \f[C]mergerfs.fsck\f[] to audit the drive for out of sync permissions. .IP \[bu] 2 Do \f[B]not\f[] use \f[C]cache.files=off\f[] or \f[C]direct_io\f[] if you expect applications (such as rtorrent) to mmap (http://linux.die.net/man/2/mmap) files. Shared mmap is not currently supported in FUSE w/ \f[C]direct_io\f[] enabled. Enabling \f[C]dropcacheonclose\f[] is recommended when \f[C]cache.files=partial|full|auto\-full\f[] or \f[C]direct_io=false\f[]. .IP \[bu] 2 Since POSIX functions give only a singular error or success its difficult to determine the proper behavior when applying the function to multiple targets. \f[B]mergerfs\f[] will return an error only if all attempts of an action fail. Any success will lead to a success returned. This means however that some odd situations may arise. .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[] its 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 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 \f[C]mv\ /mnt/pool/foo\ /mnt/disk1/foo\f[] removes \f[C]foo\f[] .PP This is not a bug. .PP Run in verbose mode to better undertand 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[] or \f[C]direct_io=true\f[]. See the section on page caching. .SS NFS clients returning ESTALE / Stale file handle .PP Be sure to use \f[C]noforget\f[] and \f[C]use_ino\f[] arguments. .SS NFS clients don\[aq]t work .PP Some NFS clients appear to fail when a mergerfs mount is exported. Kodi in particular seems to have issues. .PP Try enabling the \f[C]use_ino\f[] option. Some have reported that it fixes the issue. .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 failback 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 rtorrent fails with files >= 4GiB .PP This is a kernel bug with mmap and FUSE on 32bit platforms. A fix should become available for all LTS releases. .PP https://marc.info/?l=linux\-fsdevel&m=155550785230874&w=2 .SS Plex doesn\[aq]t work with mergerfs .PP It does. If you\[aq]re trying to put Plex\[aq]s config / metadata on mergerfs you have to leave \f[C]direct_io\f[] off because Plex is using sqlite which apparently needs mmap. mmap doesn\[aq]t work with \f[C]direct_io\f[]. To fix this place the data elsewhere or disable \f[C]direct_io\f[] (with \f[C]dropcacheonclose=true\f[]). .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[] as mentioned above. .SS mmap performance is really bad .PP There is a bug (https://lkml.org/lkml/2016/3/16/260) in caching which affects overall performance of mmap through FUSE in Linux 4.x kernels. It is fixed in 4.4.10 and 4.5.4 (https://lkml.org/lkml/2016/5/11/59). .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 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 it\[aq]s 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 situtation 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 upto 32 supplemental groups. Linux >= 2.6.3 allows upto 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. .SS mergerfs or libfuse crashing .PP \f[B]NOTE:\f[] as of mergerfs 2.22.0 it includes the most recent version of libfuse (or requires libfuse\-2.9.7) so any crash should be reported. For older releases continue reading... .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. .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 its 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 mergerfs under heavy load and memory preasure leads to kernel panic .PP https://lkml.org/lkml/2016/9/14/527 .IP .nf \f[C] [25192.515454]\ kernel\ BUG\ at\ /build/linux\-a2WvEb/linux\-4.4.0/mm/workingset.c:346! [25192.517521]\ invalid\ opcode:\ 0000\ [#1]\ SMP [25192.519602]\ Modules\ linked\ in:\ netconsole\ ip6t_REJECT\ nf_reject_ipv6\ ipt_REJECT\ nf_reject_ipv4\ configfs\ binfmt_misc\ veth\ bridge\ stp\ llc\ nf_conntrack_ipv6\ nf_defrag_ipv6\ xt_conntrack\ ip6table_filter\ ip6_tables\ xt_multiport\ iptable_filter\ ipt_MASQUERADE\ nf_nat_masquerade_ipv4\ xt_comment\ xt_nat\ iptable_nat\ nf_conntrack_ipv4\ nf_defrag_ipv4\ nf_nat_ipv4\ nf_nat\ nf_conntrack\ xt_CHECKSUM\ xt_tcpudp\ iptable_mangle\ ip_tables\ x_tables\ intel_rapl\ x86_pkg_temp_thermal\ intel_powerclamp\ eeepc_wmi\ asus_wmi\ coretemp\ sparse_keymap\ kvm_intel\ ppdev\ kvm\ irqbypass\ mei_me\ 8250_fintek\ input_leds\ serio_raw\ parport_pc\ tpm_infineon\ mei\ shpchp\ mac_hid\ parport\ lpc_ich\ autofs4\ drbg\ ansi_cprng\ dm_crypt\ algif_skcipher\ af_alg\ btrfs\ raid456\ async_raid6_recov\ async_memcpy\ async_pq\ async_xor\ async_tx\ xor\ raid6_pq\ libcrc32c\ raid0\ multipath\ linear\ raid10\ raid1\ i915\ crct10dif_pclmul\ crc32_pclmul\ aesni_intel\ i2c_algo_bit\ aes_x86_64\ drm_kms_helper\ lrw\ gf128mul\ glue_helper\ ablk_helper\ syscopyarea\ cryptd\ sysfillrect\ sysimgblt\ fb_sys_fops\ drm\ ahci\ r8169\ libahci\ mii\ wmi\ fjes\ video\ [last\ unloaded:\ netconsole] [25192.540910]\ CPU:\ 2\ PID:\ 63\ Comm:\ kswapd0\ Not\ tainted\ 4.4.0\-36\-generic\ #55\-Ubuntu [25192.543411]\ Hardware\ name:\ System\ manufacturer\ System\ Product\ Name/P8H67\-M\ PRO,\ BIOS\ 3904\ 04/27/2013 [25192.545840]\ task:\ ffff88040cae6040\ ti:\ ffff880407488000\ task.ti:\ ffff880407488000 [25192.548277]\ RIP:\ 0010:[]\ \ []\ shadow_lru_isolate+0x181/0x190 [25192.550706]\ RSP:\ 0018:ffff88040748bbe0\ \ EFLAGS:\ 00010002 [25192.553127]\ RAX:\ 0000000000001c81\ RBX:\ ffff8802f91ee928\ RCX:\ ffff8802f91eeb38 [25192.555544]\ RDX:\ ffff8802f91ee938\ RSI:\ ffff8802f91ee928\ RDI:\ ffff8804099ba2c0 [25192.557914]\ RBP:\ ffff88040748bc08\ R08:\ 000000000001a7b6\ R09:\ 000000000000003f [25192.560237]\ R10:\ 000000000001a750\ R11:\ 0000000000000000\ R12:\ ffff8804099ba2c0 [25192.562512]\ R13:\ ffff8803157e9680\ R14:\ ffff8803157e9668\ R15:\ ffff8804099ba2c8 [25192.564724]\ FS:\ \ 0000000000000000(0000)\ GS:ffff88041f280000(0000)\ knlGS:0000000000000000 [25192.566990]\ CS:\ \ 0010\ DS:\ 0000\ ES:\ 0000\ CR0:\ 0000000080050033 [25192.569201]\ CR2:\ 00007ffabb690000\ CR3:\ 0000000001e0a000\ CR4:\ 00000000000406e0 [25192.571419]\ Stack: [25192.573550]\ \ ffff8804099ba2c0\ ffff88039e4f86f0\ ffff8802f91ee928\ ffff8804099ba2c8 [25192.575695]\ \ ffff88040748bd08\ ffff88040748bc58\ ffffffff811b99bf\ 0000000000000052 [25192.577814]\ \ 0000000000000000\ ffffffff811ba380\ 000000000000008a\ 0000000000000080 [25192.579947]\ Call\ Trace: [25192.582022]\ \ []\ __list_lru_walk_one.isra.3+0x8f/0x130 [25192.584137]\ \ []\ ?\ memcg_drain_all_list_lrus+0x190/0x190 [25192.586165]\ \ []\ list_lru_walk_one+0x23/0x30 [25192.588145]\ \ []\ scan_shadow_nodes+0x34/0x50 [25192.590074]\ \ []\ shrink_slab.part.40+0x1ed/0x3d0 [25192.591985]\ \ []\ shrink_zone+0x2ca/0x2e0 [25192.593863]\ \ []\ kswapd+0x51e/0x990 [25192.595737]\ \ []\ ?\ mem_cgroup_shrink_node_zone+0x1c0/0x1c0 [25192.597613]\ \ []\ kthread+0xd8/0xf0 [25192.599495]\ \ []\ ?\ kthread_create_on_node+0x1e0/0x1e0 [25192.601335]\ \ []\ ret_from_fork+0x3f/0x70 [25192.603193]\ \ []\ ?\ kthread_create_on_node+0x1e0/0x1e0 \f[] .fi .PP There is a bug in the kernel. A work around appears to be turning off \f[C]splice\f[]. Don\[aq]t add the \f[C]splice_*\f[] arguments or add \f[C]no_splice_write,no_splice_move,no_splice_read\f[]. This, however, is not guaranteed to work. .SS rm: fts_read failed: No such file or directory .PP NOTE: This is only relevant to mergerfs versions at or below v2.25.x and should not occur in more recent versions. See the notes on \f[C]unlink\f[]. .PP Not \f[I]really\f[] a bug. The FUSE library will move files when asked to delete them as a way to deal with certain edge cases and then later delete that file when its clear the file is no longer needed. This however can lead to two issues. One is that these hidden files are noticed by \f[C]rm\ \-rf\f[] or \f[C]find\f[] when scanning directories and they may try to remove them and they might have disappeared already. There is nothing \f[I]wrong\f[] about this happening but it can be annoying. The second issue is that a directory might not be able to removed on account of the hidden file being still there. .PP Using the \f[B]hard_remove\f[] option will make it so these temporary files are not used and files are deleted immedately. That has a side effect however. Files which are unlinked and then they are still used (in certain forms) will result in an error (ENOENT). .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 is it\[aq]s sole solution for pooling drives. .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 Do hard links work? .PP Yes. You need to use \f[C]use_ino\f[] to support proper reporting of inodes. .PP What mergerfs does not do is fake hard links across branches. Read the section "rename & link" for how it works. .SS Does mergerfs support CoW / copy\-on\-write? .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. .SS Why can\[aq]t I see my files / directories? .PP It\[aq]s almost always a permissions issue. Unlike mhddfs, which runs as root and attempts to access content as such, mergerfs always changes it\[aq]s 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 Why is only one drive being used? .PP Are you using a path preserving policy? The default policy for file creation is \f[C]epmfs\f[]. That means only the drives with the path preexisting will be considered when creating a file. If you don\[aq]t care about where files and directories are created you likely shouldn\[aq]t be using a path preserving policy and instead something like \f[C]mfs\f[]. .PP This can be especially apparent when filling an empty pool from an external source. 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 transfering your data. Setting \f[C]func.mkdir=epall\f[] can simplify managing path perservation for \f[C]create\f[]. .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 pratical (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 its 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 then 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 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 behavior without the possibility of catastrophic failure and the difficulties in recovery. Drives may fail however all other data will continue to be accessable. .PP When combined with something like SnapRaid (http://www.snapraid.it) and/or an offsite backup solution you can have the flexibilty 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 major maintance and cost burdens as described here (http://louwrentius.com/the-hidden-cost-of-using-zfs-for-your-home-nas.html). .SS Can drives be written to directly? Outside of mergerfs while pooled? .PP Yes, however its not recommended to use the same file from within the pool and from without at the same time. Especially if using caching of any kind (cache.files, cache.entry, cache.attr, cache.negative_entry, cache.symlinks, cache.readdir, etc.). .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 its 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 Can mergerfs mounts be exported over NFS? .PP Yes. Due to current usage of libfuse by mergerfs and how NFS interacts with it it is necessary to add \f[C]noforget\f[] to mergerfs options to keep from getting "stale file handle" errors. .PP Some clients (Kodi) have issues in which the contents of the NFS mount will not be presented but users have found that enabling the \f[C]use_ino\f[] option often fixes that problem. .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 How are inodes calculated? .PP mergerfs\-inode = (original\-inode | (device\-id << 32)) .PP While \f[C]ino_t\f[] is 64 bits only a few filesystems use more than 32. Similarly, while \f[C]dev_t\f[] is also 64 bits it was traditionally 16 bits. Bitwise or\[aq]ing them together should work most of the time. While totally unique inodes are preferred the overhead which would be needed does not seem to outweighted by the benefits. .PP While atypical, yes, inodes can be reused and not refer to the same file. The internal id 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 (nodeid,generation). That tuple is not user facing. The inode is merely metadata passed through the kernel and found using the \f[C]stat\f[] family of calls or \f[C]readdir\f[]. .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 .SS I notice massive slowdowns of writes over NFS .PP Due to how NFS works and interacts with FUSE when not using \f[C]cache.files=off\f[] or \f[C]direct_io\f[] its possible that a getxattr for \f[C]security.capability\f[] will be issued prior to any write. This will usually result in a massive slowdown for writes. Using \f[C]cache.files=off\f[] or \f[C]direct_io\f[] will keep this from happening (and generally good to enable unless you need the features it disables) but the \f[C]security_capability\f[] option can also help by short circuiting the call and returning \f[C]ENOATTR\f[]. .PP You could also set \f[C]xattr\f[] to \f[C]noattr\f[] or \f[C]nosys\f[] to short circuit or stop all xattr requests. .SS What are these .fuse_hidden files? .PP NOTE: mergerfs >= 2.26.0 will not have these temporary files. See the notes on \f[C]unlink\f[]. .PP When not using \f[B]hard_remove\f[] libfuse will create \&.fuse_hiddenXXXXXXXX files when an opened file is unlinked. This is to simplify "use after unlink" usecases. There is a possibility these files end up being picked up by software scanning directories and not ignoring hidden files. This is rarely a problem but a solution is in the works. .PP The files are cleaned up once the file is finally closed. Only if mergerfs crashes or is killed would they be left around. They are safe to remove as they are already unlinked files. .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 PERFORMANCE TWEAKING .PP NOTE: be sure to read about these features before changing them .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 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.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]security_capability\f[] and/or \f[C]xattr\f[] .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 .IP \[bu] 2 use tiered cache drives .IP \[bu] 2 use lvm and lvm cache to place a SSD in front of your HDDs (howto coming) .SH SUPPORT .PP Filesystems are very 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. .SS Information to include in bug reports .IP \[bu] 2 Version of mergerfs: \f[C]mergerfs\ \-V\f[] .IP \[bu] 2 mergerfs settings: from \f[C]/etc/fstab\f[] or command line execution .IP \[bu] 2 Version of Linux: \f[C]uname\ \-a\f[] .IP \[bu] 2 Versions of any additional software being used .IP \[bu] 2 List of drives, their filesystems, and sizes (before and after issue): \f[C]df\ \-h\f[] .IP \[bu] 2 A \f[C]strace\f[] of the app having problems: .IP \[bu] 2 \f[C]strace\ \-f\ \-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\ \-f\ \-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 simplist way using standard programs. .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 twitter: https://twitter.com/_trapexit .IP \[bu] 2 reddit: https://www.reddit.com/user/trapexit .IP \[bu] 2 discord: https://discord.gg/MpAr69V .SS Support development .PP This software is free to use and released under a very liberal license. That said if you like this software and would like to support its development donations are welcome. .IP \[bu] 2 PayPal: https://paypal.me/trapexit .IP \[bu] 2 Patreon: https://www.patreon.com/trapexit .IP \[bu] 2 SubscribeStar: https://www.subscribestar.com/trapexit .IP \[bu] 2 Bitcoin (BTC): 12CdMhEPQVmjz3SSynkAEuD5q9JmhTDCZA .IP \[bu] 2 Bitcoin Cash (BCH): 1AjPqZZhu7GVEs6JFPjHmtsvmDL4euzMzp .IP \[bu] 2 Ethereum (ETH): 0x09A166B11fCC127324C7fc5f1B572255b3046E94 .IP \[bu] 2 Litecoin (LTC): LXAsq6yc6zYU3EbcqyWtHBrH1Ypx4GjUjm .SH LINKS .IP \[bu] 2 https://spawn.link .IP \[bu] 2 https://github.com/trapexit/mergerfs .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.28.1/.travis.yml0000664000175000017500000000113213471033317014140 0ustar bilebilelanguage: cpp matrix: include: - os: linux dist: precise compiler: gcc sudo: required - os: linux dist: precise compiler: clang sudo: required - os: linux dist: trusty compiler: gcc sudo: required - os: linux dist: trusty compiler: clang sudo: required - os: linux dist: xenial compiler: gcc sudo: required - os: linux dist: xenial compiler: clang sudo: required script: - sudo -E apt-get install fakeroot - sudo -E make install-build-pkgs - make - make deb mergerfs-2.28.1/.github/0000775000175000017500000000000013427205466013401 5ustar bilebilemergerfs-2.28.1/.github/issue_template.md0000664000175000017500000000222113427205466016743 0ustar bilebileGeneral description =================== Expected behavior ================= Actual behavior =============== **Precise** steps to reproduce the behavior =========================================== > Explicitly list **all** steps to reproduce. Preferably create a minimal example of the problem using standard command line tools. The more variables (apps, settings, etc.) that are involved the more difficult it is to debug. Also, **please** be sure to have read all of the README. It contains a lot of information regarding known system and user issues. System information ================== Please provide as much of the following information as possible: * [ ] mergerfs version: `mergerfs -V` * [ ] mergerfs settings: `cat /etc/fstab` or the command line arguments * [ ] Linux version: `uname -a` * [ ] Versions of any additional software being used * [ ] List of drives, filesystems, & sizes: `df -h` * [ ] strace of application having problem: `strace -f -o /tmp/app.strace.txt ` or `strace -f -p -o /tmp/app.strace.txt` * [ ] strace of mergerfs while app tried to do it's thing: `strace -f -p -o /tmp/mergerfs.strace.txt` mergerfs-2.28.1/LICENSE0000664000175000017500000000144313367322163013045 0ustar bilebile/* 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. */ mergerfs-2.28.1/libfuse/0000775000175000017500000000000013477573276013506 5ustar bilebilemergerfs-2.28.1/libfuse/fuse.pc.in0000664000175000017500000000041113467103610015351 0ustar bilebileprefix=@prefix@ exec_prefix=@exec_prefix@ libdir=@libdir@ includedir=@includedir@ Name: fuse Description: Filesystem in Userspace Version: @VERSION@ Libs: -L${libdir} -lfuse -pthread Libs.private: @libfuse_libs@ Cflags: -I${includedir}/fuse -D_FILE_OFFSET_BITS=64 mergerfs-2.28.1/libfuse/ChangeLog0000664000175000017500000032307013467103610015241 0ustar bilebileFUSE 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.28.1/libfuse/ecfd/0000775000175000017500000000000013471033317014364 5ustar bilebilemergerfs-2.28.1/libfuse/ecfd/build0000775000175000017500000000106113471033317015407 0ustar bilebile#!/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.28.1/libfuse/ecfd/LICENSE0000664000175000017500000000144313471033317015373 0ustar bilebile/* 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.28.1/libfuse/ecfd/README.md0000664000175000017500000000160013471033317015640 0ustar bilebile# 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.28.1/libfuse/ecfd/tests/0000775000175000017500000000000013477570754015547 5ustar bilebilemergerfs-2.28.1/libfuse/ecfd/tests/HAVE_STRUCT_STAT_ST_ATIM.c0000664000175000017500000000024213471033317021572 0ustar bilebile#include #include #include int main(int argc, char *argv[]) { struct stat st; (void)st.st_atim; return 0; } mergerfs-2.28.1/libfuse/ecfd/tests/HAVE_SPLICE.c0000664000175000017500000000016113471033317017452 0ustar bilebile#define _GNU_SOURCE #include int main(int argc, char *argv[]) { (void)splice; return 0; } mergerfs-2.28.1/libfuse/ecfd/tests/static_assert.h0000664000175000017500000000013313471033317020544 0ustar bilebile#pragma once #define STATIC_ASSERT(condition) \ ((void)sizeof(char[1 - 2*!(condition)])) mergerfs-2.28.1/libfuse/ecfd/tests/HAVE_UTIMENSAT.c0000664000175000017500000000016613471033317020051 0ustar bilebile#include #include int main(int argc, char *argv[]) { (void)utimensat; return 0; } mergerfs-2.28.1/libfuse/ecfd/tests/HAVE_VMSPLICE.c0000664000175000017500000000021013471033317017710 0ustar bilebile#define _GNU_SOURCE #include #include int main(int argc, char *argv[]) { (void)vmsplice; return 0; } mergerfs-2.28.1/libfuse/ecfd/tests/HAVE_FORK.c0000664000175000017500000000013413471033317017234 0ustar bilebile#include #include int main(void) { (void)fork; return 0; } mergerfs-2.28.1/libfuse/util/0000775000175000017500000000000013471033315014436 5ustar bilebilemergerfs-2.28.1/libfuse/util/udev.rules0000664000175000017500000000003413467103610016453 0ustar bilebileKERNEL=="fuse", MODE="0666" mergerfs-2.28.1/libfuse/util/init_script0000775000175000017500000000354513467103610016723 0ustar bilebile#! /bin/sh ### BEGIN INIT INFO # Provides: fuse # Required-Start: # Should-Start: udev # Required-Stop: # Default-Start: S # Default-Stop: # Short-Description: Start and stop fuse. # Description: Load the fuse module and mount the fuse control # filesystem. ### END INIT INFO set -e PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin MOUNTPOINT=/sys/fs/fuse/connections # Gracefully exit if the package has been removed. which fusermount &>/dev/null || exit 5 case "$1" in start|restart|force-reload) if ! grep -qw fuse /proc/filesystems; then echo -n "Loading fuse module" if ! modprobe fuse >/dev/null 2>&1; then echo " failed!" exit 1 else echo "." fi else echo "Fuse filesystem already available." fi if grep -qw fusectl /proc/filesystems && \ ! grep -qw $MOUNTPOINT /proc/mounts; then echo -n "Mounting fuse control filesystem" if ! mount -t fusectl fusectl $MOUNTPOINT >/dev/null 2>&1; then echo " failed!" exit 1 else echo "." fi else echo "Fuse control filesystem already available." fi ;; stop) if ! grep -qw fuse /proc/filesystems; then echo "Fuse filesystem not loaded." exit 7 fi if grep -qw $MOUNTPOINT /proc/mounts; then echo -n "Unmounting fuse control filesystem" if ! umount $MOUNTPOINT >/dev/null 2>&1; then echo " failed!" else echo "." fi else echo "Fuse control filesystem not mounted." fi if grep -qw "^fuse" /proc/modules; then echo -n "Unloading fuse module" if ! rmmod fuse >/dev/null 2>&1; then echo " failed!" else echo "." fi else echo "Fuse module not loaded." fi ;; status) echo -n "Checking fuse filesystem" if ! grep -qw fuse /proc/filesystems; then echo " not available." exit 3 else echo " ok." fi ;; *) echo "Usage: $0 {start|stop|restart|force-reload|status}" exit 1 ;; esac exit 0 mergerfs-2.28.1/libfuse/util/Makefile.am0000664000175000017500000000401213467103610016470 0ustar bilebile## Process this file with automake to produce Makefile.in AM_CPPFLAGS = -D_FILE_OFFSET_BITS=64 bin_PROGRAMS = fusermount ulockmgr_server noinst_PROGRAMS = mount.fuse # we re-use mount_util.c from the library, but do want to keep ourself # as stand-alone as possible. in order to make an out-of-source build # possible, we "generate" the file from its original location by # copying it over. fusermount_SOURCES = fusermount.c mount_util.c fusermount_CPPFLAGS = -I$(top_srcdir)/lib BUILT_SOURCES = mount_util.c mount_util.c: $(top_srcdir)/lib/mount_util.c @cp $(top_srcdir)/lib/mount_util.c . mount_fuse_SOURCES = mount.fuse.c ulockmgr_server_SOURCES = ulockmgr_server.c ulockmgr_server_CPPFLAGS = -D_FILE_OFFSET_BITS=64 -D_REENTRANT ulockmgr_server_LDFLAGS = -pthread install-exec-hook: -chmod u+s $(DESTDIR)$(bindir)/fusermount @if test ! -e $(DESTDIR)/dev/fuse; then \ $(MKDIR_P) $(DESTDIR)/dev; \ echo "mknod $(DESTDIR)/dev/fuse -m 0666 c 10 229 || true"; \ mknod $(DESTDIR)/dev/fuse -m 0666 c 10 229 || true; \ fi EXTRA_DIST = udev.rules init_script MOUNT_FUSE_PATH = @MOUNT_FUSE_PATH@ UDEV_RULES_PATH = @UDEV_RULES_PATH@ INIT_D_PATH = @INIT_D_PATH@ install-exec-local: $(MKDIR_P) $(DESTDIR)$(MOUNT_FUSE_PATH) $(INSTALL_PROGRAM) $(builddir)/mount.fuse $(DESTDIR)$(MOUNT_FUSE_PATH)/mount.fuse $(MKDIR_P) $(DESTDIR)$(INIT_D_PATH) $(INSTALL_SCRIPT) $(srcdir)/init_script $(DESTDIR)$(INIT_D_PATH)/fuse @if test -x /usr/sbin/update-rc.d; then \ echo "/usr/sbin/update-rc.d fuse start 34 S . start 41 0 6 . || true"; \ /usr/sbin/update-rc.d fuse start 34 S . start 41 0 6 . || true; \ fi install-data-local: $(MKDIR_P) $(DESTDIR)$(UDEV_RULES_PATH) $(INSTALL_DATA) $(srcdir)/udev.rules $(DESTDIR)$(UDEV_RULES_PATH)/99-fuse.rules uninstall-local: rm -f $(DESTDIR)$(MOUNT_FUSE_PATH)/mount.fuse rm -f $(DESTDIR)$(UDEV_RULES_PATH)/99-fuse.rules rm -f $(DESTDIR)$(INIT_D_PATH)/fuse @if test -x /usr/sbin/update-rc.d; then \ echo "/usr/sbin/update-rc.d fuse remove || true"; \ /usr/sbin/update-rc.d fuse remove || true; \ fi mergerfs-2.28.1/libfuse/util/mount_util.c0000664000175000017500000001726113467103610017011 0ustar bilebile/* 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.28.1/libfuse/util/fusermount.c0000664000175000017500000007051613467103610017023 0ustar bilebile/* 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") || opt_eq(s, len, "allow_root"))) { 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.28.1/libfuse/util/ulockmgr_server.c0000664000175000017500000001777613467103610020036 0ustar bilebile/* ulockmgr_server: Userspace Lock Manager Server Copyright (C) 2006 Miklos Szeredi This program can be distributed under the terms of the GNU GPL. See the file COPYING. */ /* #define DEBUG 1 */ #include #include #include #include #include #include #include #include #include #include #include #include #include struct message { unsigned intr : 1; unsigned nofd : 1; pthread_t thr; int cmd; int fd; struct flock lock; int error; }; struct fd_store { struct fd_store *next; int fd; int origfd; int inuse; }; struct owner { struct fd_store *fds; pthread_mutex_t lock; }; struct req_data { struct owner *o; int cfd; struct fd_store *f; struct message msg; }; #define MAX_SEND_FDS 2 static int receive_message(int sock, void *buf, size_t buflen, int *fdp, int *numfds) { struct msghdr msg; struct iovec iov; size_t ccmsg[CMSG_SPACE(sizeof(int) * MAX_SEND_FDS) / sizeof(size_t)]; struct cmsghdr *cmsg; int res; int i; assert(*numfds <= MAX_SEND_FDS); iov.iov_base = buf; iov.iov_len = buflen; memset(&msg, 0, sizeof(msg)); memset(ccmsg, -1, sizeof(ccmsg)); msg.msg_iov = &iov; msg.msg_iovlen = 1; msg.msg_control = ccmsg; msg.msg_controllen = sizeof(ccmsg); res = recvmsg(sock, &msg, MSG_WAITALL); if (!res) { /* retry on zero return, see do_recv() in ulockmgr.c */ res = recvmsg(sock, &msg, MSG_WAITALL); if (!res) return 0; } if (res == -1) { perror("ulockmgr_server: recvmsg"); return -1; } if ((size_t) res != buflen) { fprintf(stderr, "ulockmgr_server: short message received\n"); return -1; } cmsg = CMSG_FIRSTHDR(&msg); if (cmsg) { if (cmsg->cmsg_type != SCM_RIGHTS) { fprintf(stderr, "ulockmgr_server: unknown control message %d\n", cmsg->cmsg_type); return -1; } memcpy(fdp, CMSG_DATA(cmsg), sizeof(int) * *numfds); if (msg.msg_flags & MSG_CTRUNC) { fprintf(stderr, "ulockmgr_server: control message truncated\n"); for (i = 0; i < *numfds; i++) close(fdp[i]); *numfds = 0; } } else { if (msg.msg_flags & MSG_CTRUNC) { fprintf(stderr, "ulockmgr_server: control message truncated(*)\n"); /* There's a bug in the Linux kernel, that if not all file descriptors were allocated, then the cmsg header is not filled in */ cmsg = (struct cmsghdr *) ccmsg; memcpy(fdp, CMSG_DATA(cmsg), sizeof(int) * *numfds); for (i = 0; i < *numfds; i++) close(fdp[i]); } *numfds = 0; } return res; } static int closefrom(int minfd) { DIR *dir = opendir("/proc/self/fd"); if (dir) { int dfd = dirfd(dir); struct dirent *ent; while ((ent = readdir(dir))) { char *end; int fd = strtol(ent->d_name, &end, 10); if (ent->d_name[0] && !end[0] && fd >= minfd && fd != dfd) close(fd); } closedir(dir); } return 0; } static void send_reply(int cfd, struct message *msg) { int res = send(cfd, msg, sizeof(struct message), MSG_NOSIGNAL); if (res == -1) perror("ulockmgr_server: sending reply"); #ifdef DEBUG fprintf(stderr, "ulockmgr_server: error: %i\n", msg->error); #endif } static void *process_request(void *d_) { struct req_data *d = d_; int res; assert(d->msg.cmd == F_SETLKW); res = fcntl(d->f->fd, F_SETLK, &d->msg.lock); if (res == -1 && errno == EAGAIN) { d->msg.error = EAGAIN; d->msg.thr = pthread_self(); send_reply(d->cfd, &d->msg); res = fcntl(d->f->fd, F_SETLKW, &d->msg.lock); } d->msg.error = (res == -1) ? errno : 0; pthread_mutex_lock(&d->o->lock); d->f->inuse--; pthread_mutex_unlock(&d->o->lock); send_reply(d->cfd, &d->msg); close(d->cfd); free(d); return NULL; } static void process_message(struct owner *o, struct message *msg, int cfd, int fd) { struct fd_store *f = NULL; struct fd_store *newf = NULL; struct fd_store **fp; struct req_data *d; pthread_t tid; int res; #ifdef DEBUG fprintf(stderr, "ulockmgr_server: %i %i %i %lli %lli\n", msg->cmd, msg->lock.l_type, msg->lock.l_whence, msg->lock.l_start, msg->lock.l_len); #endif if (msg->cmd == F_SETLK && msg->lock.l_type == F_UNLCK && msg->lock.l_start == 0 && msg->lock.l_len == 0) { for (fp = &o->fds; *fp;) { f = *fp; if (f->origfd == msg->fd && !f->inuse) { close(f->fd); *fp = f->next; free(f); } else fp = &f->next; } if (!msg->nofd) close(fd); msg->error = 0; send_reply(cfd, msg); close(cfd); return; } if (msg->nofd) { for (fp = &o->fds; *fp; fp = &(*fp)->next) { f = *fp; if (f->origfd == msg->fd) break; } if (!*fp) { fprintf(stderr, "ulockmgr_server: fd %i not found\n", msg->fd); msg->error = EIO; send_reply(cfd, msg); close(cfd); return; } } else { newf = f = malloc(sizeof(struct fd_store)); if (!f) { msg->error = ENOLCK; send_reply(cfd, msg); close(cfd); return; } f->fd = fd; f->origfd = msg->fd; f->inuse = 0; } if (msg->cmd == F_GETLK || msg->cmd == F_SETLK || msg->lock.l_type == F_UNLCK) { res = fcntl(f->fd, msg->cmd, &msg->lock); msg->error = (res == -1) ? errno : 0; send_reply(cfd, msg); close(cfd); if (newf) { newf->next = o->fds; o->fds = newf; } return; } d = malloc(sizeof(struct req_data)); if (!d) { msg->error = ENOLCK; send_reply(cfd, msg); close(cfd); free(newf); return; } f->inuse++; d->o = o; d->cfd = cfd; d->f = f; d->msg = *msg; res = pthread_create(&tid, NULL, process_request, d); if (res) { msg->error = ENOLCK; send_reply(cfd, msg); close(cfd); free(d); f->inuse--; free(newf); return; } if (newf) { newf->next = o->fds; o->fds = newf; } pthread_detach(tid); } static void sigusr1_handler(int sig) { (void) sig; /* Nothing to do */ } static void process_owner(int cfd) { struct owner o; struct sigaction sa; memset(&sa, 0, sizeof(struct sigaction)); sa.sa_handler = sigusr1_handler; sigemptyset(&sa.sa_mask); if (sigaction(SIGUSR1, &sa, NULL) == -1) { perror("ulockmgr_server: cannot set sigusr1 signal handler"); exit(1); } memset(&o, 0, sizeof(struct owner)); pthread_mutex_init(&o.lock, NULL); while (1) { struct message msg; int rfds[2]; int res; int numfds = 2; res = receive_message(cfd, &msg, sizeof(msg), rfds, &numfds); if (!res) break; if (res == -1) exit(1); if (msg.intr) { if (numfds != 0) fprintf(stderr, "ulockmgr_server: too many fds for intr\n"); pthread_kill(msg.thr, SIGUSR1); } else { if (numfds != 2) continue; pthread_mutex_lock(&o.lock); process_message(&o, &msg, rfds[0], rfds[1]); pthread_mutex_unlock(&o.lock); } } if (o.fds) fprintf(stderr, "ulockmgr_server: open file descriptors on exit\n"); } int main(int argc, char *argv[]) { int nullfd; char *end; int cfd; sigset_t empty; if (argc != 2 || !argv[1][0]) goto out_inval; cfd = strtol(argv[1], &end, 10); if (*end) goto out_inval; /* demonize current process */ switch(fork()) { case -1: perror("ulockmgr_server: fork"); exit(1); case 0: break; default: _exit(0); } if (setsid() == -1) { perror("ulockmgr_server: setsid"); exit(1); } (void) chdir("/"); sigemptyset(&empty); sigprocmask(SIG_SETMASK, &empty, NULL); if (dup2(cfd, 4) == -1) { perror("ulockmgr_server: dup2"); exit(1); } cfd = 4; nullfd = open("/dev/null", O_RDWR); if (nullfd >= 0) { dup2(nullfd, 0); dup2(nullfd, 1); } close(3); closefrom(5); while (1) { char c; int sock; int pid; int numfds = 1; int res = receive_message(cfd, &c, sizeof(c), &sock, &numfds); if (!res) break; if (res == -1) exit(1); assert(numfds == 1); pid = fork(); if (pid == -1) { perror("ulockmgr_server: fork"); close(sock); continue; } if (pid == 0) { close(cfd); pid = fork(); if (pid == -1) { perror("ulockmgr_server: fork"); _exit(1); } if (pid == 0) process_owner(sock); _exit(0); } waitpid(pid, NULL, 0); close(sock); } return 0; out_inval: fprintf(stderr, "%s should be started by libulockmgr\n", argv[0]); return 1; } mergerfs-2.28.1/libfuse/util/mount.fuse.c0000664000175000017500000001072613467103610016714 0ustar bilebile/* 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]; 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.28.1/libfuse/COPYING.LIB0000664000175000017500000006364213467103610015135 0ustar bilebile 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.28.1/libfuse/include/0000775000175000017500000000000013475734103015113 5ustar bilebilemergerfs-2.28.1/libfuse/include/fuse_common.h0000664000175000017500000003146513475361401017604 0ustar bilebile/* 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 "fuse_opt.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 #ifdef __cplusplus extern "C" { #endif /** * Information about open files * * Changed in version 2.5 */ struct fuse_file_info { /** 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; /** Padding. Do not use*/ uint32_t padding : 24; /** 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_ASYNC_DIO (1 << 15) #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; /** * 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 * '-s' single threaded * '-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 multithreaded set to 1 unless the '-s' option is present * @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 *multithreaded, 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(struct fuse_pollhandle *ph); /* ----------------------------------------------------------- * * Data buffer * * ----------------------------------------------------------- */ /** * Buffer flags */ enum fuse_buf_flags { /** * Buffer contains a file descriptor * * If this flag is set, the .fd field is valid, otherwise the * .mem fields is valid. */ FUSE_BUF_IS_FD = (1 << 1), /** * Seek on the file descriptor * * If this flag is set then the .pos field is valid and is * used to seek to the given offset before performing * operation on file descriptor. */ FUSE_BUF_FD_SEEK = (1 << 2), /** * Retry operation on file descriptor * * If this flag is set then retry operation on file descriptor * until .size bytes have been copied or an error or EOF is * detected. */ FUSE_BUF_FD_RETRY = (1 << 3), }; /** * Buffer copy flags */ enum fuse_buf_copy_flags { /** * Don't use splice(2) * * Always fall back to using read and write instead of * splice(2) to copy data from one file descriptor to another. * * If this flag is not set, then only fall back if splice is * unavailable. */ FUSE_BUF_NO_SPLICE = (1 << 1), /** * Force splice * * Always use splice(2) to copy data from one file descriptor * to another. If splice is not available, return -EINVAL. */ FUSE_BUF_FORCE_SPLICE = (1 << 2), /** * Try to move data with splice. * * If splice is used, try to move pages from the source to the * destination instead of copying. See documentation of * SPLICE_F_MOVE in splice(2) man page. */ FUSE_BUF_SPLICE_MOVE = (1 << 3), /** * Don't block on the pipe when copying data with splice * * Makes the operations on the pipe non-blocking (if the pipe * is full or empty). See SPLICE_F_NONBLOCK in the splice(2) * man page. */ FUSE_BUF_SPLICE_NONBLOCK= (1 << 4), }; /** * Single data buffer * * Generic data buffer for I/O, extended attributes, etc... Data may * be supplied as a memory pointer or as a file descriptor */ struct fuse_buf { /** * Size of data in bytes */ size_t size; /** * Buffer flags */ enum fuse_buf_flags flags; /** * Memory pointer * * Used unless FUSE_BUF_IS_FD flag is set. */ void *mem; /** * File descriptor * * Used if FUSE_BUF_IS_FD flag is set. */ int fd; /** * File position * * Used if FUSE_BUF_FD_SEEK flag is set. */ off_t pos; }; /** * 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); /* ----------------------------------------------------------- * * Compatibility stuff * * ----------------------------------------------------------- */ #if FUSE_USE_VERSION < 26 # ifdef __FreeBSD__ # if FUSE_USE_VERSION < 25 # error On FreeBSD API version 25 or greater must be used # endif # endif # include "fuse_common_compat.h" # undef FUSE_MINOR_VERSION # undef fuse_main # define fuse_unmount fuse_unmount_compat22 # if FUSE_USE_VERSION == 25 # define FUSE_MINOR_VERSION 5 # define fuse_mount fuse_mount_compat25 # elif FUSE_USE_VERSION == 24 || FUSE_USE_VERSION == 22 # define FUSE_MINOR_VERSION 4 # define fuse_mount fuse_mount_compat22 # elif FUSE_USE_VERSION == 21 # define FUSE_MINOR_VERSION 1 # define fuse_mount fuse_mount_compat22 # elif FUSE_USE_VERSION == 11 # warning Compatibility with API version 11 is deprecated # undef FUSE_MAJOR_VERSION # define FUSE_MAJOR_VERSION 1 # define FUSE_MINOR_VERSION 1 # define fuse_mount fuse_mount_compat1 # else # error Compatibility with API version other than 21, 22, 24, 25 and 11 not supported # endif #endif #ifdef __cplusplus } #endif #endif /* _FUSE_COMMON_H_ */ mergerfs-2.28.1/libfuse/include/fuse_opt.h0000664000175000017500000001646513467103610017116 0ustar bilebile/* 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_ /** @file * * This file defines the option parsing interface of FUSE */ #ifdef __cplusplus extern "C" { #endif /** * Option description * * This structure describes a single option, and action associated * with it, in case it matches. * * More than one such match may occur, in which case the action for * each match is executed. * * There are three possible actions in case of a match: * * i) An integer (int or unsigned) variable determined by 'offset' is * set to 'value' * * ii) The processing function is called, with 'value' as the key * * iii) An integer (any) or string (char *) variable determined by * 'offset' is set to the value of an option parameter * * 'offset' should normally be either set to * * - 'offsetof(struct foo, member)' actions i) and iii) * * - -1 action ii) * * The 'offsetof()' macro is defined in the header. * * The template determines which options match, and also have an * effect on the action. Normally the action is either i) or ii), but * if a format is present in the template, then action iii) is * performed. * * The types of templates are: * * 1) "-x", "-foo", "--foo", "--foo-bar", etc. These match only * themselves. Invalid values are "--" and anything beginning * with "-o" * * 2) "foo", "foo-bar", etc. These match "-ofoo", "-ofoo-bar" or * the relevant option in a comma separated option list * * 3) "bar=", "--foo=", etc. These are variations of 1) and 2) * which have a parameter * * 4) "bar=%s", "--foo=%lu", etc. Same matching as above but perform * action iii). * * 5) "-x ", etc. Matches either "-xparam" or "-x param" as * two separate arguments * * 6) "-x %s", etc. Combination of 4) and 5) * * If the format is "%s", memory is allocated for the string unlike * with scanf(). */ struct fuse_opt { /** Matching template and optional parameter formatting */ const char *templ; /** * Offset of variable within 'data' parameter of fuse_opt_parse() * or -1 */ unsigned long offset; /** * Value to set the variable to, or to be passed as 'key' to the * processing function. Ignored if template has a format */ int value; }; /** * Key option. In case of a match, the processing function will be * called with the specified key. */ #define FUSE_OPT_KEY(templ, key) { templ, -1U, key } /** * Last option. An array of 'struct fuse_opt' must end with a NULL * template value */ #define FUSE_OPT_END { NULL, 0, 0 } /** * Argument list */ struct fuse_args { /** Argument count */ int argc; /** Argument vector. NULL terminated */ char **argv; /** Is 'argv' allocated? */ int allocated; }; /** * Initializer for 'struct fuse_args' */ #define FUSE_ARGS_INIT(argc, argv) { argc, argv, 0 } /** * Key value passed to the processing function if an option did not * match any template */ #define FUSE_OPT_KEY_OPT -1 /** * Key value passed to the processing function for all non-options * * Non-options are the arguments beginning with a character other than * '-' or all arguments after the special '--' option */ #define FUSE_OPT_KEY_NONOPT -2 /** * Special key value for options to keep * * Argument is not passed to processing function, but behave as if the * processing function returned 1 */ #define FUSE_OPT_KEY_KEEP -3 /** * Special key value for options to discard * * Argument is not passed to processing function, but behave as if the * processing function returned zero */ #define FUSE_OPT_KEY_DISCARD -4 /** * Processing function * * This function is called if * - option did not match any 'struct fuse_opt' * - argument is a non-option * - option did match and offset was set to -1 * * The 'arg' parameter will always contain the whole argument or * option including the parameter if exists. A two-argument option * ("-x foo") is always converted to single argument option of the * form "-xfoo" before this function is called. * * Options of the form '-ofoo' are passed to this function without the * '-o' prefix. * * The return value of this function determines whether this argument * is to be inserted into the output argument vector, or discarded. * * @param data is the user data passed to the fuse_opt_parse() function * @param arg is the whole argument or option * @param key determines why the processing function was called * @param outargs the current output argument list * @return -1 on error, 0 if arg is to be discarded, 1 if arg should be kept */ typedef int (*fuse_opt_proc_t)(void *data, const char *arg, int key, struct fuse_args *outargs); /** * Option parsing function * * If 'args' was returned from a previous call to fuse_opt_parse() or * it was constructed from * * A NULL 'args' is equivalent to an empty argument vector * * A NULL 'opts' is equivalent to an 'opts' array containing a single * end marker * * A NULL 'proc' is equivalent to a processing function always * returning '1' * * @param args is the input and output argument list * @param data is the user data * @param opts is the option description array * @param proc is the processing function * @return -1 on error, 0 on success */ int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc); /** * Add an option to a comma separated option list * * @param opts is a pointer to an option list, may point to a NULL value * @param opt is the option to add * @return -1 on allocation error, 0 on success */ int fuse_opt_add_opt(char **opts, const char *opt); /** * Add an option, escaping commas, to a comma separated option list * * @param opts is a pointer to an option list, may point to a NULL value * @param opt is the option to add * @return -1 on allocation error, 0 on success */ int fuse_opt_add_opt_escaped(char **opts, const char *opt); /** * Add an argument to a NULL terminated argument vector * * @param args is the structure containing the current argument list * @param arg is the new argument to add * @return -1 on allocation error, 0 on success */ int fuse_opt_add_arg(struct fuse_args *args, const char *arg); /** * Add an argument at the specified position in a NULL terminated * argument vector * * Adds the argument to the N-th position. This is useful for adding * options at the beginning of the array which must not come after the * special '--' option. * * @param args is the structure containing the current argument list * @param pos is the position at which to add the argument * @param arg is the new argument to add * @return -1 on allocation error, 0 on success */ int fuse_opt_insert_arg(struct fuse_args *args, int pos, const char *arg); /** * Free the contents of argument list * * The structure itself is not freed * * @param args is the structure containing the argument list */ void fuse_opt_free_args(struct fuse_args *args); /** * Check if an option matches * * @param opts is the option description array * @param opt is the option to match * @return 1 if a match is found, 0 if not */ int fuse_opt_match(const struct fuse_opt opts[], const char *opt); #ifdef __cplusplus } #endif #endif /* _FUSE_OPT_H_ */ mergerfs-2.28.1/libfuse/include/Makefile.am0000664000175000017500000000053013467103610017137 0ustar bilebile## Process this file with automake to produce Makefile.in fuseincludedir=$(includedir)/fuse fuseinclude_HEADERS = \ fuse.h \ fuse_compat.h \ fuse_common.h \ fuse_common_compat.h \ fuse_lowlevel.h \ fuse_lowlevel_compat.h \ fuse_opt.h \ cuse_lowlevel.h include_HEADERS = old/fuse.h ulockmgr.h noinst_HEADERS = fuse_kernel.h mergerfs-2.28.1/libfuse/include/fuse_lowlevel_compat.h0000664000175000017500000001535513474472141021513 0ustar bilebile/* 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. */ /* these definitions provide source compatibility to prior versions. Do not include this file directly! */ struct fuse_lowlevel_ops_compat25 { void (*init) (void *userdata); void (*destroy) (void *userdata); void (*lookup) (fuse_req_t req, fuse_ino_t parent, const char *name); void (*forget) (fuse_req_t req, fuse_ino_t ino, unsigned long nlookup); void (*getattr) (fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi); void (*setattr) (fuse_req_t req, fuse_ino_t ino, struct stat *attr, int to_set, struct fuse_file_info *fi); void (*readlink) (fuse_req_t req, fuse_ino_t ino); void (*mknod) (fuse_req_t req, fuse_ino_t parent, const char *name, mode_t mode, dev_t rdev); void (*mkdir) (fuse_req_t req, fuse_ino_t parent, const char *name, mode_t mode); void (*unlink) (fuse_req_t req, fuse_ino_t parent, const char *name); void (*rmdir) (fuse_req_t req, fuse_ino_t parent, const char *name); void (*symlink) (fuse_req_t req, const char *link, fuse_ino_t parent, const char *name); void (*rename) (fuse_req_t req, fuse_ino_t parent, const char *name, fuse_ino_t newparent, const char *newname); void (*link) (fuse_req_t req, fuse_ino_t ino, fuse_ino_t newparent, const char *newname); void (*open) (fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi); void (*read) (fuse_req_t req, fuse_ino_t ino, size_t size, off_t off, struct fuse_file_info *fi); void (*write) (fuse_req_t req, fuse_ino_t ino, const char *buf, size_t size, off_t off, struct fuse_file_info *fi); void (*flush) (fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi); void (*release) (fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi); void (*fsync) (fuse_req_t req, fuse_ino_t ino, int datasync, struct fuse_file_info *fi); void (*opendir) (fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi); void (*readdir) (fuse_req_t req, fuse_ino_t ino, size_t size, off_t off, struct fuse_file_info *fi); void (*releasedir) (fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi); void (*fsyncdir) (fuse_req_t req, fuse_ino_t ino, int datasync, struct fuse_file_info *fi); void (*statfs) (fuse_req_t req); void (*setxattr) (fuse_req_t req, fuse_ino_t ino, const char *name, const char *value, size_t size, int flags); void (*getxattr) (fuse_req_t req, fuse_ino_t ino, const char *name, size_t size); void (*listxattr) (fuse_req_t req, fuse_ino_t ino, size_t size); void (*removexattr) (fuse_req_t req, fuse_ino_t ino, const char *name); void (*access) (fuse_req_t req, fuse_ino_t ino, int mask); void (*create) (fuse_req_t req, fuse_ino_t parent, const char *name, mode_t mode, struct fuse_file_info *fi); }; struct fuse_session *fuse_lowlevel_new_compat25(struct fuse_args *args, const struct fuse_lowlevel_ops_compat25 *op, size_t op_size, void *userdata); size_t fuse_dirent_size(size_t namelen); char *fuse_add_dirent(char *buf, const char *name, const struct stat *stbuf, off_t off); #if !defined(__FreeBSD__) && !defined(__NetBSD__) #include struct fuse_lowlevel_ops_compat { void (*init) (void *userdata); void (*destroy) (void *userdata); void (*lookup) (fuse_req_t req, fuse_ino_t parent, const char *name); void (*forget) (fuse_req_t req, fuse_ino_t ino, unsigned long nlookup); void (*getattr) (fuse_req_t req, fuse_ino_t ino, struct fuse_file_info_compat *fi); void (*setattr) (fuse_req_t req, fuse_ino_t ino, struct stat *attr, int to_set, struct fuse_file_info_compat *fi); void (*readlink) (fuse_req_t req, fuse_ino_t ino); void (*mknod) (fuse_req_t req, fuse_ino_t parent, const char *name, mode_t mode, dev_t rdev); void (*mkdir) (fuse_req_t req, fuse_ino_t parent, const char *name, mode_t mode); void (*unlink) (fuse_req_t req, fuse_ino_t parent, const char *name); void (*rmdir) (fuse_req_t req, fuse_ino_t parent, const char *name); void (*symlink) (fuse_req_t req, const char *link, fuse_ino_t parent, const char *name); void (*rename) (fuse_req_t req, fuse_ino_t parent, const char *name, fuse_ino_t newparent, const char *newname); void (*link) (fuse_req_t req, fuse_ino_t ino, fuse_ino_t newparent, const char *newname); void (*open) (fuse_req_t req, fuse_ino_t ino, struct fuse_file_info_compat *fi); void (*read) (fuse_req_t req, fuse_ino_t ino, size_t size, off_t off, struct fuse_file_info_compat *fi); void (*write) (fuse_req_t req, fuse_ino_t ino, const char *buf, size_t size, off_t off, struct fuse_file_info_compat *fi); void (*flush) (fuse_req_t req, fuse_ino_t ino, struct fuse_file_info_compat *fi); void (*release) (fuse_req_t req, fuse_ino_t ino, struct fuse_file_info_compat *fi); void (*fsync) (fuse_req_t req, fuse_ino_t ino, int datasync, struct fuse_file_info_compat *fi); void (*opendir) (fuse_req_t req, fuse_ino_t ino, struct fuse_file_info_compat *fi); void (*readdir) (fuse_req_t req, fuse_ino_t ino, size_t size, off_t off, struct fuse_file_info_compat *fi); void (*releasedir) (fuse_req_t req, fuse_ino_t ino, struct fuse_file_info_compat *fi); void (*fsyncdir) (fuse_req_t req, fuse_ino_t ino, int datasync, struct fuse_file_info_compat *fi); void (*statfs) (fuse_req_t req); void (*setxattr) (fuse_req_t req, fuse_ino_t ino, const char *name, const char *value, size_t size, int flags); void (*getxattr) (fuse_req_t req, fuse_ino_t ino, const char *name, size_t size); void (*listxattr) (fuse_req_t req, fuse_ino_t ino, size_t size); void (*removexattr) (fuse_req_t req, fuse_ino_t ino, const char *name); void (*access) (fuse_req_t req, fuse_ino_t ino, int mask); void (*create) (fuse_req_t req, fuse_ino_t parent, const char *name, mode_t mode, struct fuse_file_info_compat *fi); }; int fuse_reply_statfs_compat(fuse_req_t req, const struct statfs *stbuf); int fuse_reply_open_compat(fuse_req_t req, const struct fuse_file_info_compat *fi); struct fuse_session *fuse_lowlevel_new_compat(const char *opts, const struct fuse_lowlevel_ops_compat *op, size_t op_size, void *userdata); #endif /* __FreeBSD__ || __NetBSD__ */ struct fuse_chan_ops_compat24 { int (*receive)(struct fuse_chan *ch, char *buf, size_t size); int (*send)(struct fuse_chan *ch, const struct iovec iov[], size_t count); void (*destroy)(struct fuse_chan *ch); }; struct fuse_chan *fuse_chan_new_compat24(struct fuse_chan_ops_compat24 *op, int fd, size_t bufsize, void *data); int fuse_chan_receive(struct fuse_chan *ch, char *buf, size_t size); struct fuse_chan *fuse_kern_chan_new(int fd); mergerfs-2.28.1/libfuse/include/cuse_lowlevel.h0000664000175000017500000000502013467103610020123 0ustar bilebile/* CUSE: Character device in Userspace Copyright (C) 2008-2009 SUSE Linux Products GmbH Copyright (C) 2008-2009 Tejun Heo This program can be distributed under the terms of the GNU LGPLv2. See the file COPYING.LIB. Read example/cusexmp.c for usages. */ #ifndef _CUSE_LOWLEVEL_H_ #define _CUSE_LOWLEVEL_H_ #ifndef FUSE_USE_VERSION #define FUSE_USE_VERSION 29 #endif #include "fuse_lowlevel.h" #include #include #include #ifdef __cplusplus extern "C" { #endif #define CUSE_UNRESTRICTED_IOCTL (1 << 0) /* use unrestricted ioctl */ struct fuse_session; struct cuse_info { unsigned dev_major; unsigned dev_minor; unsigned dev_info_argc; const char **dev_info_argv; unsigned flags; }; /* * Most ops behave almost identically to the matching fuse_lowlevel * ops except that they don't take @ino. * * init_done : called after initialization is complete * read/write : always direct IO, simultaneous operations allowed * ioctl : might be in unrestricted mode depending on ci->flags */ struct cuse_lowlevel_ops { void (*init) (void *userdata, struct fuse_conn_info *conn); void (*init_done) (void *userdata); void (*destroy) (void *userdata); void (*open) (fuse_req_t req, struct fuse_file_info *fi); void (*read) (fuse_req_t req, size_t size, off_t off, struct fuse_file_info *fi); void (*write) (fuse_req_t req, const char *buf, size_t size, off_t off, struct fuse_file_info *fi); void (*flush) (fuse_req_t req, struct fuse_file_info *fi); void (*release) (fuse_req_t req, struct fuse_file_info *fi); void (*fsync) (fuse_req_t req, int datasync, struct fuse_file_info *fi); void (*ioctl) (fuse_req_t req, int cmd, void *arg, struct fuse_file_info *fi, unsigned int flags, const void *in_buf, size_t in_bufsz, size_t out_bufsz); void (*poll) (fuse_req_t req, struct fuse_file_info *fi, struct fuse_pollhandle *ph); }; struct fuse_session *cuse_lowlevel_new(struct fuse_args *args, const struct cuse_info *ci, const struct cuse_lowlevel_ops *clop, void *userdata); struct fuse_session *cuse_lowlevel_setup(int argc, char *argv[], const struct cuse_info *ci, const struct cuse_lowlevel_ops *clop, int *multithreaded, void *userdata); void cuse_lowlevel_teardown(struct fuse_session *se); int cuse_lowlevel_main(int argc, char *argv[], const struct cuse_info *ci, const struct cuse_lowlevel_ops *clop, void *userdata); #ifdef __cplusplus } #endif #endif /* _CUSE_LOWLEVEL_H_ */ mergerfs-2.28.1/libfuse/include/fuse_lowlevel.h0000664000175000017500000015315713475734103020153 0ustar bilebile/* 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 "fuse_common.h" #include #include #include #include #include #include #ifdef __cplusplus extern "C" { #endif /* ----------------------------------------------------------- * * Miscellaneous definitions * * ----------------------------------------------------------- */ /** The node ID of the root inode */ #define FUSE_ROOT_ID 1 /** Inode number type */ typedef unsigned long fuse_ino_t; /** 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. */ fuse_ino_t ino; /** Generation number for this entry. * * If the file system will be exported over NFS, the * ino/generation pairs need to be unique over the file * system's lifetime (rather than just the mount time). So if * the file system reuses an inode after it has been deleted, * it must assign a new, previously unused generation number * to the inode at the same time. * * The generation must be non-zero, otherwise FUSE will treat * it as an error. * */ unsigned long generation; /** Inode attributes. * * Even if attr_timeout == 0, attr must be correct. For example, * for open(), FUSE uses attr.st_size from lookup() to determine * how many bytes to request. If this value is not correct, * incorrect data will be returned. */ struct stat attr; /** Validity timeout (in seconds) for the attributes */ double attr_timeout; /** Validity timeout (in seconds) for the name */ double entry_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; }; /* 'to_set' flags in setattr */ #define FUSE_SET_ATTR_MODE (1 << 0) #define FUSE_SET_ATTR_UID (1 << 1) #define FUSE_SET_ATTR_GID (1 << 2) #define FUSE_SET_ATTR_SIZE (1 << 3) #define FUSE_SET_ATTR_ATIME (1 << 4) #define FUSE_SET_ATTR_MTIME (1 << 5) #define FUSE_SET_ATTR_ATIME_NOW (1 << 7) #define FUSE_SET_ATTR_MTIME_NOW (1 << 8) /* ----------------------------------------------------------- * * 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, fuse_ino_t parent, const char *name); /** * Forget about an inode * * This function is called when the kernel removes an inode * from its internal caches. * * The inode's lookup count increases by one for every call to * fuse_reply_entry and fuse_reply_create. The nlookup parameter * indicates by how much the lookup count should be decreased. * * Inodes with a non-zero lookup count may receive request from * the kernel even after calls to unlink, rmdir or (when * overwriting an existing file) rename. Filesystems must handle * such requests properly and it is recommended to defer removal * of the inode until the lookup count reaches zero. Calls to * unlink, 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, fuse_ino_t ino, unsigned long 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, fuse_ino_t ino, struct fuse_file_info *fi); /** * Set file attributes * * In the 'attr' argument only members indicated by the 'to_set' * bitmask contain valid values. Other members contain undefined * values. * * 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, fuse_ino_t ino, struct stat *attr, int to_set, struct fuse_file_info *fi); /** * Read symbolic link * * Valid replies: * fuse_reply_readlink * fuse_reply_err * * @param req request handle * @param ino the inode number */ void (*readlink) (fuse_req_t req, fuse_ino_t ino); /** * Create file node * * Create a regular file, character device, block device, fifo or * socket node. * * Valid replies: * fuse_reply_entry * fuse_reply_err * * @param req request handle * @param parent inode number of the parent directory * @param name to create * @param mode file type and mode with which to create the new file * @param rdev the device number (only valid if created file is a device) */ void (*mknod) (fuse_req_t req, fuse_ino_t parent, const char *name, mode_t mode, dev_t rdev); /** * Create a directory * * Valid replies: * fuse_reply_entry * fuse_reply_err * * @param req request handle * @param parent inode number of the parent directory * @param name to create * @param mode with which to create the new file */ void (*mkdir) (fuse_req_t req, fuse_ino_t parent, const char *name, mode_t mode); /** * Remove a file * * If the file's inode's lookup count is non-zero, the file * system is expected to postpone any removal of the inode * until the lookup count reaches zero (see description of the * forget function). * * Valid replies: * fuse_reply_err * * @param req request handle * @param parent inode number of the parent directory * @param name to remove */ void (*unlink) (fuse_req_t req, fuse_ino_t parent, const char *name); /** * Remove a directory * * If the directory's inode's lookup count is non-zero, the * file system is expected to postpone any removal of the * inode until the lookup count reaches zero (see description * of the forget function). * * Valid replies: * fuse_reply_err * * @param req request handle * @param parent inode number of the parent directory * @param name to remove */ void (*rmdir) (fuse_req_t req, fuse_ino_t parent, const char *name); /** * Create a symbolic link * * Valid replies: * fuse_reply_entry * fuse_reply_err * * @param req request handle * @param link the contents of the symbolic link * @param parent inode number of the parent directory * @param name to create */ void (*symlink) (fuse_req_t req, const char *link, fuse_ino_t parent, const char *name); /** Rename a file * * If the target exists it should be atomically replaced. If * the target's inode's lookup count is non-zero, the file * system is expected to postpone any removal of the inode * until the lookup count reaches zero (see description of the * forget function). * * Valid replies: * fuse_reply_err * * @param req request handle * @param parent inode number of the old parent directory * @param name old name * @param newparent inode number of the new parent directory * @param newname new name */ void (*rename) (fuse_req_t req, fuse_ino_t parent, const char *name, fuse_ino_t newparent, const char *newname); /** * Create a hard link * * Valid replies: * fuse_reply_entry * fuse_reply_err * * @param req request handle * @param ino the old inode number * @param newparent inode number of the new parent directory * @param newname new name to create */ void (*link) (fuse_req_t req, fuse_ino_t ino, fuse_ino_t newparent, const char *newname); /** * Open a file * * Open flags (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, fuse_ino_t ino, struct fuse_file_info *fi); /** * Read data * * Read should send exactly the number of bytes requested except * on EOF or error, otherwise the rest of the data will be * substituted with zeroes. An exception to this is when the file * has been opened in 'direct_io' mode, in which case the return * value of the read system call will reflect the return value of * this operation. * * fi->fh will contain the value set by the open method, or will * be undefined if the open method didn't set any value. * * Valid replies: * fuse_reply_buf * fuse_reply_iov * fuse_reply_data * fuse_reply_err * * @param req request handle * @param ino the inode number * @param size number of bytes to read * @param off offset to read from * @param fi file information */ void (*read) (fuse_req_t req, fuse_ino_t ino, size_t size, off_t off, struct fuse_file_info *fi); /** * Write data * * Write should return exactly the number of bytes requested * except on error. An exception to this is when the file has * been opened in 'direct_io' mode, in which case the return value * of the write system call will reflect the return value of this * operation. * * fi->fh will contain the value set by the open method, or will * be undefined if the open method didn't set any value. * * Valid replies: * fuse_reply_write * fuse_reply_err * * @param req request handle * @param ino the inode number * @param buf data to write * @param size number of bytes to write * @param off offset to write to * @param fi file information */ void (*write) (fuse_req_t req, fuse_ino_t ino, const char *buf, size_t size, off_t off, struct fuse_file_info *fi); /** * Flush method * * This is called on each close() of the opened file. * * Since file descriptors can be duplicated (dup, dup2, fork), for * one open call there may be many flush calls. * * Filesystems shouldn't assume that flush will always be called * after some writes, or that if will be called at all. * * fi->fh will contain the value set by the open method, or will * be undefined if the open method didn't set any value. * * NOTE: the name of the method is misleading, since (unlike * fsync) the filesystem is not forced to flush pending writes. * One reason to flush data, is if the filesystem wants to return * write errors. * * 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, fuse_ino_t ino, struct fuse_file_info *fi); /** * Release an open file * * Release is called when there are no more references to an open * file: all file descriptors are closed and all memory mappings * are unmapped. * * For every open call there will be exactly one release call. * * The filesystem may reply with an error, but error values are * not returned to close() or munmap() which triggered the * release. * * fi->fh will contain the value set by the open method, or will * be undefined if the open method didn't set any value. * fi->flags will contain the same flags as for open. * * Valid replies: * fuse_reply_err * * @param req request handle * @param ino the inode number * @param fi file information */ void (*release) (fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi); /** * Synchronize file contents * * If the datasync parameter is non-zero, then only the user data * should be flushed, not the meta data. * * Valid replies: * fuse_reply_err * * @param req request handle * @param ino the inode number * @param datasync flag indicating if only data should be flushed * @param fi file information */ void (*fsync) (fuse_req_t req, fuse_ino_t ino, int datasync, struct fuse_file_info *fi); /** * Open a directory * * Filesystem may store an arbitrary file handle (pointer, index, * etc) in fi->fh, and use this in other all other directory * stream operations (readdir, releasedir, fsyncdir). * * 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, fuse_ino_t ino, struct fuse_file_info *fi); /** * Read directory * * Send a buffer filled using fuse_add_direntry(), with size not * exceeding the requested size. Send an empty buffer on end of * stream. * * fi->fh will contain the value set by the opendir method, or * will be undefined if the opendir method didn't set any value. * * Valid replies: * fuse_reply_buf * fuse_reply_data * fuse_reply_err * * @param req request handle * @param ino the inode number * @param size maximum number of bytes to send * @param off offset to continue reading the directory stream * @param fi file information */ void (*readdir) (fuse_req_t req, fuse_ino_t ino, size_t size, off_t off, struct fuse_file_info *fi); /** * Release an open directory * * For every opendir call there will be exactly one releasedir * call. * * fi->fh will contain the value set by the opendir method, or * will be undefined if the opendir method didn't set any value. * * Valid replies: * fuse_reply_err * * @param req request handle * @param ino the inode number * @param fi file information */ void (*releasedir) (fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi); /** * Synchronize directory contents * * If the datasync parameter is non-zero, then only the directory * contents should be flushed, not the meta data. * * fi->fh will contain the value set by the opendir method, or * will be undefined if the opendir method didn't set any value. * * Valid replies: * fuse_reply_err * * @param req request handle * @param ino the inode number * @param datasync flag indicating if only data should be flushed * @param fi file information */ void (*fsyncdir) (fuse_req_t req, fuse_ino_t ino, int datasync, struct fuse_file_info *fi); /** * Get file system statistics * * Valid replies: * fuse_reply_statfs * fuse_reply_err * * @param req request handle * @param ino the inode number, zero means "undefined" */ void (*statfs) (fuse_req_t req, fuse_ino_t ino); /** * Set an extended attribute * * Valid replies: * fuse_reply_err */ void (*setxattr) (fuse_req_t req, fuse_ino_t ino, const char *name, const char *value, size_t size, int flags); /** * Get an extended attribute * * If size is zero, the size of the value should be sent with * fuse_reply_xattr. * * If the size is non-zero, and the value fits in the buffer, the * value should be sent with fuse_reply_buf. * * If the size is too small for the value, the ERANGE error should * be sent. * * Valid replies: * fuse_reply_buf * fuse_reply_data * fuse_reply_xattr * fuse_reply_err * * @param req request handle * @param ino the inode number * @param name of the extended attribute * @param size maximum size of the value to send */ void (*getxattr) (fuse_req_t req, fuse_ino_t ino, const char *name, size_t size); /** * List extended attribute names * * If size is zero, the total size of the attribute list should be * sent with fuse_reply_xattr. * * If the size is non-zero, and the null character separated * attribute list fits in the buffer, the list should be sent with * fuse_reply_buf. * * If the size is too small for the list, the ERANGE error should * be sent. * * Valid replies: * fuse_reply_buf * fuse_reply_data * fuse_reply_xattr * fuse_reply_err * * @param req request handle * @param ino the inode number * @param size maximum size of the list to send */ void (*listxattr) (fuse_req_t req, fuse_ino_t ino, size_t size); /** * Remove an extended attribute * * Valid replies: * fuse_reply_err * * @param req request handle * @param ino the inode number * @param name of the extended attribute */ void (*removexattr) (fuse_req_t req, fuse_ino_t ino, const char *name); /** * Check file access permissions * * This will be called for the access() 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, fuse_ino_t ino, int mask); /** * Create and open a file * * If the file does not exist, first create it with the specified * mode, and then open it. * * 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, fuse_ino_t parent, const char *name, mode_t mode, struct fuse_file_info *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, fuse_ino_t ino, struct fuse_file_info *fi, struct flock *lock); /** * Acquire, modify or release a POSIX file lock * * For POSIX threads (NPTL) there's a 1-1 relation between pid and * owner, but otherwise this is not always the case. For checking * lock ownership, 'fi->owner' must be used. The l_pid field in * 'struct flock' should only be used to fill in this field in * getlk(). * * Note: if the locking methods are not implemented, the kernel * will still allow file locking to work locally. Hence these are * only interesting for network filesystems and similar. * * 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, fuse_ino_t ino, struct fuse_file_info *fi, struct flock *lock, int sleep); /** * Map block index within file to block index within device * * Note: This makes sense only for block device backed filesystems * mounted with the 'blkdev' option * * 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, fuse_ino_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, fuse_ino_t ino, int cmd, void *arg, struct fuse_file_info *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, fuse_ino_t ino, struct fuse_file_info *fi, struct fuse_pollhandle *ph); /** * Write data made available in a buffer * * This is a more generic version of the ->write() method. If * FUSE_CAP_SPLICE_READ is set in fuse_conn_info.want and the * kernel supports splicing from the fuse device, then the * data will be made available in pipe for supporting zero * copy data transfer. * * buf->count is guaranteed to be one (and thus buf->idx is * always zero). The write_buf handler must ensure that * bufv->off is correctly updated (reflecting the number of * bytes read from bufv->buf[0]). * * 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, fuse_ino_t ino, struct fuse_bufvec *bufv, off_t off, struct fuse_file_info *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, fuse_ino_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, fuse_ino_t ino, struct fuse_file_info *fi, int op); /** * Allocate requested space. If this function returns success then * subsequent writes to the specified range shall not fail due to the lack * of free space on the file system storage media. * * 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, fuse_ino_t ino, int mode, off_t offset, off_t length, struct fuse_file_info *fi); /** * Copy a range of data from one file to another * * Performs an optimized copy between two file descriptors without * the * additional cost of transferring data through the FUSE kernel * module * to user space (glibc) and then back into the FUSE filesystem * again. * * In case this method is not implemented, glibc falls back to * reading * data from the source and writing to the destination. Effectively * doing an inefficient copy of the data. * * If this request is answered with an error code of ENOSYS, this is * treated as a permanent failure with error code EOPNOTSUPP, * i.e. all * future copy_file_range() requests will fail with EOPNOTSUPP * without * being send to the filesystem process. * * Valid replies: * fuse_reply_write * fuse_reply_err * * @param req request handle * @param ino_in the inode number 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, fuse_ino_t ino_in, off_t off_in, struct fuse_file_info *fi_in, fuse_ino_t ino_out, off_t off_out, struct fuse_file_info *fi_out, size_t len, int flags); }; /** * 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 struct fuse_file_info *fi); /** * Reply with attributes * * Possible requests: * getattr, setattr * * @param req request handle * @param attr the attributes * @param attr_timeout validity timeout (in seconds) for the attributes * @return zero for success, -errno for failure to send reply */ int fuse_reply_attr(fuse_req_t req, const struct stat *attr, double attr_timeout); /** * Reply with the contents of a symbolic link * * Possible requests: * readlink * * @param req request handle * @param link symbolic link contents * @return zero for success, -errno for failure to send reply */ int fuse_reply_readlink(fuse_req_t req, const char *link); /** * 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 struct fuse_file_info *fi); /** * Reply with number of bytes written * * Possible requests: * write * * @param req request handle * @param count the number of bytes written * @return zero for success, -errno for failure to send reply */ int fuse_reply_write(fuse_req_t req, size_t count); /** * Reply with data * * Possible requests: * read, readdir, getxattr, listxattr * * @param req request handle * @param buf buffer containing data * @param size the size of data in bytes * @return zero for success, -errno for failure to send reply */ int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size); /** * Reply with data copied/moved from buffer(s) * * 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); /* ----------------------------------------------------------- * * Filling a buffer in readdir * * ----------------------------------------------------------- */ /** * Add a directory entry to the buffer * * Buffer needs to be large enough to hold the entry. If it's not, * then the entry is not filled in but the size of the entry is still * returned. The caller can check this by comparing the bufsize * parameter with the returned entry size. If the entry size is * larger than the buffer size, the operation failed. * * From the 'stbuf' argument the st_ino field and bits 12-15 of the * st_mode field are used. The other fields are ignored. * * Note: offsets do not necessarily represent physical offsets, and * could be any marker, that enables the implementation to find a * specific point in the directory stream. * * @param req request handle * @param buf the point where the new entry will be added to the buffer * @param bufsize remaining size of the buffer * @param name the name of the entry * @param stbuf the file attributes * @param off the offset of the next entry * @return the space needed for the entry */ size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct stat *stbuf, off_t off); /** * 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(struct fuse_pollhandle *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, fuse_ino_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, fuse_ino_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, fuse_ino_t parent, fuse_ino_t child, const char *name, size_t namelen); /** * Store data to the kernel buffers * * Synchronously store data in the kernel buffers belonging to the * given inode. The stored data is marked up-to-date (no read will be * performed against it, unless it's invalidated or evicted from the * cache). * * If the stored data overflows the current file size, then the size * is extended, similarly to a write(2) on the filesystem. * * If this function returns an error, then the store wasn't fully * completed, but it may have been partially completed. * * @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, fuse_ino_t ino, off_t offset, struct fuse_bufvec *bufv, enum fuse_buf_copy_flags flags); /** * Retrieve data from the kernel buffers * * Retrieve data in the kernel buffers belonging to the given inode. * If successful then the retrieve_reply() method will be called with * the returned data. * * Only present pages are returned in the retrieve reply. Retrieving * stops when it finds a non-present page and only data prior to that is * returned. * * If this function returns an error, then the retrieve will not be * completed and no reply will be sent. * * This function doesn't change the dirty state of pages in the kernel * buffer. For dirty pages the write() method will be called * regardless of having been retrieved previously. * * @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, fuse_ino_t ino, size_t size, off_t offset, void *cookie); /* ----------------------------------------------------------- * * Utility functions * * ----------------------------------------------------------- */ /** * Get the userdata from the request * * @param req request handle * @return the user data passed to fuse_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[]); /** * Callback function for an interrupt * * @param req interrupted request * @param data user data */ typedef void (*fuse_interrupt_func_t)(fuse_req_t req, void *data); /** * Register/unregister callback for an interrupt * * If an interrupt has already happened, then the callback function is * called from within this function, hence it's not possible for * interrupts to be lost. * * @param req request handle * @param func the callback function or NULL for unregister * @param data user data passed to the callback function */ void fuse_req_interrupt_func(fuse_req_t req, fuse_interrupt_func_t func, void *data); /** * Check if a request has already been interrupted * * @param req request handle * @return 1 if the request has been interrupted, 0 otherwise */ int fuse_req_interrupted(fuse_req_t req); /* ----------------------------------------------------------- * * 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 single threaded event loop * * @param se the session * @return 0 on success, -1 on error */ int fuse_session_loop(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); /* ----------------------------------------------------------- * * Compatibility stuff * * ----------------------------------------------------------- */ #if FUSE_USE_VERSION < 26 # include "fuse_lowlevel_compat.h" # define fuse_chan_ops fuse_chan_ops_compat24 # define fuse_chan_new fuse_chan_new_compat24 # if FUSE_USE_VERSION == 25 # define fuse_lowlevel_ops fuse_lowlevel_ops_compat25 # define fuse_lowlevel_new fuse_lowlevel_new_compat25 # elif FUSE_USE_VERSION == 24 # define fuse_lowlevel_ops fuse_lowlevel_ops_compat # define fuse_lowlevel_new fuse_lowlevel_new_compat # define fuse_file_info fuse_file_info_compat # define fuse_reply_statfs fuse_reply_statfs_compat # define fuse_reply_open fuse_reply_open_compat # else # error Compatibility with low-level API version < 24 not supported # endif #endif #ifdef __cplusplus } #endif #endif /* _FUSE_LOWLEVEL_H_ */ mergerfs-2.28.1/libfuse/include/fuse_common_compat.h0000664000175000017500000000131213467103610021130 0ustar bilebile/* 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. */ /* these definitions provide source compatibility to prior versions. Do not include this file directly! */ struct fuse_file_info_compat { int flags; unsigned long fh; int writepage; unsigned int direct_io : 1; unsigned int keep_cache : 1; }; int fuse_mount_compat25(const char *mountpoint, struct fuse_args *args); int fuse_mount_compat22(const char *mountpoint, const char *opts); int fuse_mount_compat1(const char *mountpoint, const char *args[]); void fuse_unmount_compat22(const char *mountpoint); mergerfs-2.28.1/libfuse/include/ulockmgr.h0000664000175000017500000000124713467103610017105 0ustar bilebile/* libulockmgr: Userspace Lock Manager Library Copyright (C) 2006 Miklos Szeredi This program can be distributed under the terms of the GNU LGPLv2. See the file COPYING.LIB. */ #include #include #include /** * Perform POSIX locking operation * * @param fd the file descriptor * @param cmd the locking command (F_GETFL, F_SETLK or F_SETLKW) * @param lock the lock parameters * @param owner the lock owner ID cookie * @param owner_len length of the lock owner ID cookie * @return 0 on success -errno on error */ int ulockmgr_op(int fd, int cmd, struct flock *lock, const void *owner, size_t owner_len); mergerfs-2.28.1/libfuse/include/old/0000775000175000017500000000000013467103610015663 5ustar bilebilemergerfs-2.28.1/libfuse/include/old/fuse.h0000664000175000017500000000036613467103610017003 0ustar bilebile/* This header is for compatibility with older software using FUSE. Please use 'pkg-config --cflags fuse' to set include path. The correct usage is still '#include ', not '#include '. */ #include "fuse/fuse.h" mergerfs-2.28.1/libfuse/include/fuse_compat.h0000664000175000017500000001746513474102430017575 0ustar bilebile/* 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. */ /* these definitions provide source compatibility to prior versions. Do not include this file directly! */ struct fuse_operations_compat25 { int (*getattr) (const char *, struct stat *); int (*readlink) (const char *, char *, size_t); int (*getdir) (const char *, fuse_dirh_t, fuse_dirfil_t); int (*mknod) (const char *, mode_t, dev_t); int (*mkdir) (const char *, mode_t); int (*unlink) (const char *); int (*rmdir) (const char *); int (*symlink) (const char *, const char *); int (*rename) (const char *, const char *); int (*link) (const char *, const char *); int (*chmod) (const char *, mode_t); int (*chown) (const char *, uid_t, gid_t); int (*truncate) (const char *, off_t); int (*utime) (const char *, struct utimbuf *); int (*open) (const char *, struct fuse_file_info *); int (*read) (const char *, char *, size_t, off_t, struct fuse_file_info *); int (*write) (const char *, const char *, size_t, off_t, struct fuse_file_info *); int (*statfs) (const char *, struct statvfs *); int (*flush) (const char *, struct fuse_file_info *); int (*release) (const char *, struct fuse_file_info *); int (*fsync) (const char *, int, struct fuse_file_info *); int (*setxattr) (const char *, const char *, const char *, size_t, int); int (*getxattr) (const char *, const char *, char *, size_t); int (*listxattr) (const char *, char *, size_t); int (*removexattr) (const char *, const char *); int (*opendir) (const char *, struct fuse_file_info *); int (*readdir) (const char *, void *, fuse_fill_dir_t, off_t, struct fuse_file_info *); int (*releasedir) (const char *, struct fuse_file_info *); int (*fsyncdir) (const char *, int, struct fuse_file_info *); void *(*init) (void); void (*destroy) (void *); int (*access) (const char *, int); int (*create) (const char *, mode_t, struct fuse_file_info *); int (*ftruncate) (const char *, off_t, struct fuse_file_info *); int (*fgetattr) (const char *, struct stat *, struct fuse_file_info *); }; struct fuse *fuse_new_compat25(int fd, struct fuse_args *args, const struct fuse_operations_compat25 *op, size_t op_size); int fuse_main_real_compat25(int argc, char *argv[], const struct fuse_operations_compat25 *op, size_t op_size); struct fuse *fuse_setup_compat25(int argc, char *argv[], const struct fuse_operations_compat25 *op, size_t op_size, char **mountpoint, int *multithreaded, int *fd); void fuse_teardown_compat22(struct fuse *fuse, int fd, char *mountpoint); #if !defined(__FreeBSD__) && !defined(__NetBSD__) #include struct fuse_operations_compat22 { int (*getattr) (const char *, struct stat *); int (*readlink) (const char *, char *, size_t); int (*getdir) (const char *, fuse_dirh_t, fuse_dirfil_t); int (*mknod) (const char *, mode_t, dev_t); int (*mkdir) (const char *, mode_t); int (*unlink) (const char *); int (*rmdir) (const char *); int (*symlink) (const char *, const char *); int (*rename) (const char *, const char *); int (*link) (const char *, const char *); int (*chmod) (const char *, mode_t); int (*chown) (const char *, uid_t, gid_t); int (*truncate) (const char *, off_t); int (*utime) (const char *, struct utimbuf *); int (*open) (const char *, struct fuse_file_info_compat *); int (*read) (const char *, char *, size_t, off_t, struct fuse_file_info_compat *); int (*write) (const char *, const char *, size_t, off_t, struct fuse_file_info_compat *); int (*statfs) (const char *, struct statfs *); int (*flush) (const char *, struct fuse_file_info_compat *); int (*release) (const char *, struct fuse_file_info_compat *); int (*fsync) (const char *, int, struct fuse_file_info_compat *); int (*setxattr) (const char *, const char *, const char *, size_t, int); int (*getxattr) (const char *, const char *, char *, size_t); int (*listxattr) (const char *, char *, size_t); int (*removexattr) (const char *, const char *); int (*opendir) (const char *, struct fuse_file_info_compat *); int (*readdir) (const char *, void *, fuse_fill_dir_t, off_t, struct fuse_file_info_compat *); int (*releasedir) (const char *, struct fuse_file_info_compat *); int (*fsyncdir) (const char *, int, struct fuse_file_info_compat *); void *(*init) (void); void (*destroy) (void *); }; struct fuse *fuse_new_compat22(int fd, const char *opts, const struct fuse_operations_compat22 *op, size_t op_size); struct fuse *fuse_setup_compat22(int argc, char *argv[], const struct fuse_operations_compat22 *op, size_t op_size, char **mountpoint, int *multithreaded, int *fd); int fuse_main_real_compat22(int argc, char *argv[], const struct fuse_operations_compat22 *op, size_t op_size); typedef int (*fuse_dirfil_t_compat) (fuse_dirh_t h, const char *name, int type); struct fuse_operations_compat2 { int (*getattr) (const char *, struct stat *); int (*readlink) (const char *, char *, size_t); int (*getdir) (const char *, fuse_dirh_t, fuse_dirfil_t_compat); int (*mknod) (const char *, mode_t, dev_t); int (*mkdir) (const char *, mode_t); int (*unlink) (const char *); int (*rmdir) (const char *); int (*symlink) (const char *, const char *); int (*rename) (const char *, const char *); int (*link) (const char *, const char *); int (*chmod) (const char *, mode_t); int (*chown) (const char *, uid_t, gid_t); int (*truncate) (const char *, off_t); int (*utime) (const char *, struct utimbuf *); int (*open) (const char *, int); int (*read) (const char *, char *, size_t, off_t); int (*write) (const char *, const char *, size_t, off_t); int (*statfs) (const char *, struct statfs *); int (*flush) (const char *); int (*release) (const char *, int); int (*fsync) (const char *, int); int (*setxattr) (const char *, const char *, const char *, size_t, int); int (*getxattr) (const char *, const char *, char *, size_t); int (*listxattr) (const char *, char *, size_t); int (*removexattr) (const char *, const char *); }; int fuse_main_compat2(int argc, char *argv[], const struct fuse_operations_compat2 *op); struct fuse *fuse_new_compat2(int fd, const char *opts, const struct fuse_operations_compat2 *op); struct fuse *fuse_setup_compat2(int argc, char *argv[], const struct fuse_operations_compat2 *op, char **mountpoint, int *multithreaded, int *fd); struct fuse_statfs_compat1 { long block_size; long blocks; long blocks_free; long files; long files_free; long namelen; }; struct fuse_operations_compat1 { int (*getattr) (const char *, struct stat *); int (*readlink) (const char *, char *, size_t); int (*getdir) (const char *, fuse_dirh_t, fuse_dirfil_t_compat); int (*mknod) (const char *, mode_t, dev_t); int (*mkdir) (const char *, mode_t); int (*unlink) (const char *); int (*rmdir) (const char *); int (*symlink) (const char *, const char *); int (*rename) (const char *, const char *); int (*link) (const char *, const char *); int (*chmod) (const char *, mode_t); int (*chown) (const char *, uid_t, gid_t); int (*truncate) (const char *, off_t); int (*utime) (const char *, struct utimbuf *); int (*open) (const char *, int); int (*read) (const char *, char *, size_t, off_t); int (*write) (const char *, const char *, size_t, off_t); int (*statfs) (struct fuse_statfs_compat1 *); int (*release) (const char *, int); int (*fsync) (const char *, int); }; #define FUSE_DEBUG_COMPAT1 (1 << 1) struct fuse *fuse_new_compat1(int fd, int flags, const struct fuse_operations_compat1 *op); void fuse_main_compat1(int argc, char *argv[], const struct fuse_operations_compat1 *op); #endif /* __FreeBSD__ || __NetBSD__ */ mergerfs-2.28.1/libfuse/include/fuse.h0000664000175000017500000011123213475734103016226 0ustar bilebile/* 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 "fuse_common.h" #include #include #include #include #include #include #include #ifdef __cplusplus extern "C" { #endif /* ----------------------------------------------------------- * * Basic FUSE API * * ----------------------------------------------------------- */ /** Handle for a FUSE filesystem */ struct fuse; /** Structure containing a raw command */ struct fuse_cmd; /** Function to add an entry in a readdir() operation * * @param buf the buffer passed to the readdir() operation * @param name the file name of the directory entry * @param stat file attributes, can be NULL * @param off offset of the next entry or zero * @return 1 if buffer is full, zero otherwise */ typedef int (*fuse_fill_dir_t) (void *buf, const char *name, const struct stat *stbuf, off_t off); /* Used by deprecated getdir() method */ typedef struct fuse_dirhandle *fuse_dirh_t; typedef int (*fuse_dirfil_t) (fuse_dirh_t h, const char *name, int type, ino_t ino); /** * 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 *); /** 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); /* Deprecated, use readdir() instead */ int (*getdir) (const char *, fuse_dirh_t, fuse_dirfil_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 *); /** Rename a file */ int (*rename) (const char *, const char *); /** Create a hard link to a file */ int (*link) (const char *, const char *); /** Change the permission bits of a file */ int (*chmod) (const char *, mode_t); int (*fchmod)(const struct fuse_file_info *, const mode_t); /** Change the owner and group of a file */ int (*chown) (const char *, uid_t, gid_t); int (*fchown)(const struct fuse_file_info *, const uid_t, const gid_t); /** Change the size of a file */ int (*truncate) (const char *, off_t); /** Change the access and/or modification times of a file * * Deprecated, use utimens() instead. */ int (*utime) (const char *, struct utimbuf *); /** 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 *, struct fuse_file_info *); /** Read data from an open file * * Read should return exactly the number of bytes requested except * on EOF or error, otherwise the rest of the data will be * substituted with zeroes. An exception to this is when the * 'direct_io' mount option is specified, in which case the return * value of the read system call will reflect the return value of * this operation. * * Changed in version 2.2 */ int (*read) (const char *, char *, size_t, off_t, struct fuse_file_info *); /** Write data to an open file * * Write should return exactly the number of bytes requested * except on error. An exception to this is when the 'direct_io' * mount option is specified (see read operation). * * Changed in version 2.2 */ int (*write) (const char *, const char *, size_t, off_t, struct fuse_file_info *); /** 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 char *, struct fuse_file_info *); /** Release an open file * * Release is called when there are no more references to an open * file: all file descriptors are closed and all memory mappings * are unmapped. * * For every open() call there will be exactly one release() call * with the same flags and file 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 char *, struct fuse_file_info *); /** Synchronize file contents * * If the datasync parameter is non-zero, then only the user data * should be flushed, not the meta data. * * Changed in version 2.2 */ int (*fsync) (const char *, int, struct fuse_file_info *); /** Set extended attributes */ int (*setxattr) (const char *, const char *, const char *, size_t, int); /** Get extended attributes */ int (*getxattr) (const char *, const char *, char *, size_t); /** List extended attributes */ int (*listxattr) (const char *, char *, size_t); /** Remove extended attributes */ int (*removexattr) (const char *, const char *); /** Open directory * * Unless the 'default_permissions' mount option is given, * this method should check if opendir is permitted for this * directory. Optionally opendir may also return an arbitrary * filehandle in the fuse_file_info structure, which will be * passed to readdir, closedir and fsyncdir. * * Introduced in version 2.3 */ int (*opendir) (const char *, struct fuse_file_info *); /** 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 char *, void *, fuse_fill_dir_t, off_t, struct fuse_file_info *); /** Release directory * * Introduced in version 2.3 */ int (*releasedir) (const char *, struct fuse_file_info *); /** 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 char *, int, struct fuse_file_info *); /** * 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, struct fuse_file_info *); /** * 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 char *, off_t, struct fuse_file_info *); /** * 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 char *, struct stat *, struct fuse_file_info *); /** * Perform POSIX file locking operation * * The cmd argument will be either F_GETLK, F_SETLK or F_SETLKW. * * For the meaning of fields in 'struct flock' see the man page * for fcntl(2). The l_whence field will always be set to * SEEK_SET. * * For checking lock ownership, the 'fuse_file_info->owner' * argument must be used. * * For F_GETLK operation, the library will first check currently * held locks, and if a conflicting lock is found it will return * information without calling this method. This ensures, that * for local locks the l_pid field is correctly filled in. The * results may not be accurate in case of race conditions and in * the presence of hard links, but it's unlikely that an * application would rely on accurate GETLK results in these * cases. If a conflicting lock is not found, this method will be * called, and the filesystem may fill out l_pid by a meaningful * value, or it may leave this field zero. * * For F_SETLK and F_SETLKW the l_pid field will be set to the pid * of the process performing the locking operation. * * Note: if this method is not implemented, the kernel will still * allow file locking to work locally. Hence it is only * interesting for network filesystems and similar. * * Introduced in version 2.6 */ int (*lock) (const char *, struct fuse_file_info *, int cmd, struct flock *); /** * Change the access and modification times of a file with * nanosecond resolution * * This supersedes the old utime() interface. New applications * should use this. * * 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 struct fuse_file_info *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); /** * Flag indicating that the filesystem can accept a NULL path * as the first argument for the following operations: * * read, write, flush, release, fsync, readdir, releasedir, * fsyncdir, ftruncate, fgetattr, lock, ioctl and poll * * If this flag is set these operations continue to work on * unlinked files even if "-ohard_remove" option was specified. */ unsigned int flag_nullpath_ok:1; /** * Flag indicating that the path need not be calculated for * the following operations: * * read, write, flush, release, fsync, readdir, releasedir, * fsyncdir, ftruncate, fgetattr, lock, ioctl and poll * * Closely related to flag_nullpath_ok, but if this flag is * set then the path will not be calculaged even if the file * wasn't unlinked. However the path can still be non-NULL if * it needs to be calculated for some other reason. */ unsigned int flag_nopath:1; /** * Flag indicating that the filesystem accepts special * UTIME_NOW and UTIME_OMIT values in its utimens operation. */ unsigned int flag_utime_omit_ok:1; /** * Reserved flags, don't set */ unsigned int flag_reserved:29; /** * 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 char *fusepath, int cmd, void *arg, struct fuse_file_info *ffi, 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 char *, struct fuse_file_info *, struct fuse_pollhandle *ph, unsigned *reventsp); /** Write contents of buffer to an open file * * Similar to the write() method, but data is supplied in a * generic buffer. Use fuse_buf_copy() to transfer data to * the destination. * * Introduced in version 2.9 */ int (*write_buf) (const char *, struct fuse_bufvec *buf, off_t off, struct fuse_file_info *); /** Store data from an open file in a buffer * * Similar to the read() method, but data is stored and * returned in a generic buffer. * * No actual copying of data has to take place, the source * file descriptor may simply be stored in the buffer for * later data transfer. * * The buffer must be allocated dynamically and stored at the * location pointed to by bufp. If the buffer contains memory * regions, they too must be allocated using malloc(). The * allocated memory will be freed by the caller. * * Introduced in version 2.9 */ int (*read_buf) (const char *, struct fuse_bufvec **bufp, size_t size, off_t off, struct fuse_file_info *); /** * Perform BSD file locking operation * * The op argument will be either LOCK_SH, LOCK_EX or LOCK_UN * * Nonblocking requests will be indicated by ORing LOCK_NB to * the above operations * * For more information see the flock(2) manual page. * * Additionally fi->owner will be set to a value unique to * this open file. This same value will be supplied to * ->release() when the file is released. * * Note: if this method is not implemented, the kernel will still * allow file locking to work locally. Hence it is only * interesting for network filesystems and similar. * * Introduced in version 2.9 */ int (*flock) (const char *, struct fuse_file_info *, int op); /** * Allocates space for an open file * * This function ensures that required space is allocated for specified * file. If this function returns success then any subsequent write * request to specified range is guaranteed not to fail because of lack * of space on the file system media. * * Introduced in version 2.9.1 */ int (*fallocate) (const char *, int, off_t, off_t, struct fuse_file_info *); /** * Copy a range of data from one file to another * * Performs an optimized copy between two file descriptors without * the * additional cost of transferring data through the FUSE kernel * module * to user space (glibc) and then back into the FUSE filesystem * again. * * In case this method is not implemented, 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 char *path_in, struct fuse_file_info *fi_in, off_t offset_in, const char *path_out, struct fuse_file_info *fi_out, off_t offset_out, size_t size, int flags); }; /** 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; /** Private filesystem data */ void *private_data; /** 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 * @param user_data user data supplied in the context during the init() method * @return 0 on success, nonzero on failure */ /* int fuse_main(int argc, char *argv[], const struct fuse_operations *op, void *user_data); */ #define fuse_main(argc, argv, op, user_data) \ fuse_main_real(argc, argv, op, sizeof(*(op)), user_data) /* ----------------------------------------------------------- * * 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 * @param user_data user data supplied in the context during the init() method * @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, void *user_data); /** * 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); /** * FUSE event loop. * * Requests from the kernel are processed, and the appropriate * operations are called. * * @param f the FUSE handle * @return 0 if no error occurred, -1 otherwise */ int fuse_loop(struct fuse *f); /** * Exit from event loop * * @param f the FUSE handle */ void fuse_exit(struct fuse *f); void fuse_config_set_entry_timeout(struct fuse *fuse_, const double entry_timeout_); void fuse_config_set_negative_entry_timeout(struct fuse *fuse_, const double entry_timeout_); void fuse_config_set_attr_timeout(struct fuse *fuse_, const double attr_timeout_); int fuse_config_num_threads(const struct fuse *fuse_); double fuse_config_get_entry_timeout(const struct fuse *fuse_); double fuse_config_get_negative_entry_timeout(const struct fuse *fuse_); double fuse_config_get_attr_timeout(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); /** * Get the current supplementary group IDs for the current request * * Similar to the getgroups(2) system call, except the return value is * always the total number of group IDs, even if it is larger than the * specified size. * * The current fuse kernel module in linux (as of 2.6.30) doesn't pass * the group list to userspace, hence this function needs to parse * "/proc/$TID/task/$TID/status" to get the group IDs. * * This feature may not be supported on all operating systems. In * such a case this function will return -ENOSYS. * * @param size size of given array * @param list array of group IDs to be filled in * @return the total number of supplementary group IDs or -errno on failure */ int fuse_getgroups(int size, gid_t list[]); /** * Check if the current request has already been interrupted * * @return 1 if the request has been interrupted, 0 otherwise */ int fuse_interrupted(void); /** * 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, void *user_data); /** * Start the cleanup thread when using option "remember". * * This is done automatically by fuse_loop_mt() * @param fuse struct fuse pointer for fuse instance * @return 0 on success and -1 on error */ int fuse_start_cleanup_thread(struct fuse *fuse); /** * Stop the cleanup thread when using option "remember". * * This is done automatically by fuse_loop_mt() * @param fuse struct fuse pointer for fuse instance */ void fuse_stop_cleanup_thread(struct fuse *fuse); /** * Iterate over cache removing stale entries * use in conjunction with "-oremember" * * NOTE: This is already done for the standard sessions * * @param fuse struct fuse pointer for fuse instance * @return the number of seconds until the next cleanup */ int fuse_clean_cache(struct fuse *fuse); /* * Stacking API */ /** * Fuse filesystem object * * This is opaque object represents a filesystem layer */ struct fuse_fs; /* * These functions call the relevant filesystem operation, and return * the result. * * If the operation is not defined, they return -ENOSYS, with the * exception of fuse_fs_open, fuse_fs_release, fuse_fs_opendir, * fuse_fs_releasedir and fuse_fs_statfs, which return 0. */ int fuse_fs_getattr(struct fuse_fs *fs, const char *path, struct stat *buf); int fuse_fs_fgetattr(struct fuse_fs *fs, const char *path, struct stat *buf, struct fuse_file_info *fi); int fuse_fs_rename(struct fuse_fs *fs, const char *oldpath, const char *newpath); int fuse_fs_unlink(struct fuse_fs *fs, const char *path); int fuse_fs_rmdir(struct fuse_fs *fs, const char *path); int fuse_fs_symlink(struct fuse_fs *fs, const char *linkname, const char *path); int fuse_fs_link(struct fuse_fs *fs, const char *oldpath, const char *newpath); int fuse_fs_release(struct fuse_fs *fs, const char *path, struct fuse_file_info *fi); int fuse_fs_open(struct fuse_fs *fs, const char *path, struct fuse_file_info *fi); int fuse_fs_read(struct fuse_fs *fs, const char *path, char *buf, size_t size, off_t off, struct fuse_file_info *fi); int fuse_fs_read_buf(struct fuse_fs *fs, const char *path, struct fuse_bufvec **bufp, size_t size, off_t off, struct fuse_file_info *fi); int fuse_fs_write(struct fuse_fs *fs, const char *path, const char *buf, size_t size, off_t off, struct fuse_file_info *fi); int fuse_fs_write_buf(struct fuse_fs *fs, const char *path, struct fuse_bufvec *buf, off_t off, struct fuse_file_info *fi); int fuse_fs_fsync(struct fuse_fs *fs, const char *path, int datasync, struct fuse_file_info *fi); int fuse_fs_flush(struct fuse_fs *fs, const char *path, struct fuse_file_info *fi); int fuse_fs_statfs(struct fuse_fs *fs, const char *path, struct statvfs *buf); int fuse_fs_opendir(struct fuse_fs *fs, const char *path, struct fuse_file_info *fi); int fuse_fs_readdir(struct fuse_fs *fs, const char *path, void *buf, fuse_fill_dir_t filler, off_t off, struct fuse_file_info *fi); int fuse_fs_fsyncdir(struct fuse_fs *fs, const char *path, int datasync, struct fuse_file_info *fi); int fuse_fs_releasedir(struct fuse_fs *fs, const char *path, struct fuse_file_info *fi); int fuse_fs_create(struct fuse_fs *fs, const char *path, mode_t mode, struct fuse_file_info *fi); int fuse_fs_lock(struct fuse_fs *fs, const char *path, struct fuse_file_info *fi, int cmd, struct flock *lock); int fuse_fs_flock(struct fuse_fs *fs, const char *path, struct fuse_file_info *fi, int op); int fuse_fs_chmod(struct fuse_fs *fs, const char *path, mode_t mode); 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, const char *path, off_t size, struct fuse_file_info *fi); int fuse_fs_utimens(struct fuse_fs *fs, const char *path, const struct timespec tv[2]); 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, const char *path, int cmd, void *arg, struct fuse_file_info *fi, unsigned int flags, void *data, uint32_t *out_bufsz); int fuse_fs_poll(struct fuse_fs *fs, const char *path, struct fuse_file_info *fi, struct fuse_pollhandle *ph, unsigned *reventsp); int fuse_fs_fallocate(struct fuse_fs *fs, const char *path, int mode, off_t offset, off_t length, struct fuse_file_info *fi); 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, const char *path_in, struct fuse_file_info *fi_in, off_t off_in, const char *path_out, struct fuse_file_info *fi_out, off_t off_out, size_t len, int flags); int fuse_notify_poll(struct fuse_pollhandle *ph); /** * Create a new fuse filesystem object * * This is usually called from the factory of a fuse module to create * a new instance of a filesystem. * * @param op the filesystem operations * @param op_size the size of the fuse_operations structure * @param user_data user data supplied in the context during the init() method * @return a new filesystem object */ struct fuse_fs *fuse_fs_new(const struct fuse_operations *op, size_t op_size, void *user_data); /** * Filesystem module * * Filesystem modules are registered with the FUSE_REGISTER_MODULE() * macro. * * If the "-omodules=modname:..." option is present, filesystem * objects are created and pushed onto the stack with the 'factory' * function. */ struct fuse_module { /** * Name of filesystem */ const char *name; /** * Factory for creating filesystem objects * * The function may use and remove options from 'args' that belong * to this module. * * For now the 'fs' vector always contains exactly one filesystem. * This is the filesystem which will be below the newly created * filesystem in the stack. * * @param args the command line arguments * @param fs NULL terminated filesystem object vector * @return the new filesystem object */ struct fuse_fs *(*factory)(struct fuse_args *args, struct fuse_fs *fs[]); struct fuse_module *next; struct fusemod_so *so; int ctr; }; /** * Register a filesystem module * * This function is used by FUSE_REGISTER_MODULE and there's usually * no need to call it directly */ void fuse_register_module(struct fuse_module *mod); /** * Register filesystem module * * For the parameters, see description of the fields in 'struct * fuse_module' */ #define FUSE_REGISTER_MODULE(name_, factory_) \ static __attribute__((constructor)) void name_ ## _register(void) \ { \ static struct fuse_module mod = \ { #name_, factory_, NULL, NULL, 0 }; \ fuse_register_module(&mod); \ } /* ----------------------------------------------------------- * * 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, int *multithreaded, void *user_data); /** 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); /* ----------------------------------------------------------- * * Compatibility stuff * * ----------------------------------------------------------- */ #if FUSE_USE_VERSION < 26 # include "fuse_compat.h" # undef fuse_main # if FUSE_USE_VERSION == 25 # define fuse_main(argc, argv, op) \ fuse_main_real_compat25(argc, argv, op, sizeof(*(op))) # define fuse_new fuse_new_compat25 # define fuse_setup fuse_setup_compat25 # define fuse_teardown fuse_teardown_compat22 # define fuse_operations fuse_operations_compat25 # elif FUSE_USE_VERSION == 22 # define fuse_main(argc, argv, op) \ fuse_main_real_compat22(argc, argv, op, sizeof(*(op))) # define fuse_new fuse_new_compat22 # define fuse_setup fuse_setup_compat22 # define fuse_teardown fuse_teardown_compat22 # define fuse_operations fuse_operations_compat22 # define fuse_file_info fuse_file_info_compat # elif FUSE_USE_VERSION == 24 # error Compatibility with high-level API version 24 not supported # else # define fuse_dirfil_t fuse_dirfil_t_compat # define __fuse_read_cmd fuse_read_cmd # define __fuse_process_cmd fuse_process_cmd # define __fuse_loop_mt fuse_loop_mt_proc # if FUSE_USE_VERSION == 21 # define fuse_operations fuse_operations_compat2 # define fuse_main fuse_main_compat2 # define fuse_new fuse_new_compat2 # define __fuse_setup fuse_setup_compat2 # define __fuse_teardown fuse_teardown_compat22 # define __fuse_exited fuse_exited # define __fuse_set_getcontext_func fuse_set_getcontext_func # else # define fuse_statfs fuse_statfs_compat1 # define fuse_operations fuse_operations_compat1 # define fuse_main fuse_main_compat1 # define fuse_new fuse_new_compat1 # define FUSE_DEBUG FUSE_DEBUG_COMPAT1 # endif # endif #endif #ifdef __cplusplus } #endif #endif /* _FUSE_H_ */ mergerfs-2.28.1/libfuse/include/fuse_kernel.h0000664000175000017500000004344613471622322017574 0ustar bilebile/* 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.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 * * 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 open, 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 */ #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 29 /** 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 */ #define FOPEN_DIRECT_IO (1 << 0) #define FOPEN_KEEP_CACHE (1 << 1) #define FOPEN_NONSEEKABLE (1 << 2) #define FOPEN_CACHE_DIR (1 << 3) /** * 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 */ #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) /** * 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 */ #define FUSE_WRITE_CACHE (1 << 0) #define FUSE_WRITE_LOCKOWNER (1 << 1) /** * 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_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_MAX_IOV 256 /** * Poll flags * * FUSE_POLL_SCHEDULE_NOTIFY: request poll notify */ #define FUSE_POLL_SCHEDULE_NOTIFY (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, /* CUSE specific operations */ CUSE_INIT = 4096, }; 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 padding; 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.28.1/libfuse/lib/0000775000175000017500000000000013475734107014242 5ustar bilebilemergerfs-2.28.1/libfuse/lib/fuse_misc.h0000664000175000017500000000347113467103610016363 0ustar bilebile/* 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 /* 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_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_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_MTIM_NSEC_SET(stbuf, val) do { } while (0) #endif mergerfs-2.28.1/libfuse/lib/fuse_mt.c0000664000175000017500000000520113474102430016031 0ustar bilebile/* 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_cleanup_thread(f); if (res) return -1; res = fuse_session_loop_mt(fuse_get_session(f), fuse_config_num_threads(f)); fuse_stop_cleanup_thread(f); return res; } FUSE_SYMVER(".symver fuse_loop_mt_proc,__fuse_loop_mt@"); mergerfs-2.28.1/libfuse/lib/fuse_i.h0000664000175000017500000000573013474472141015666 0ustar bilebile/* 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.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; int ctr; pthread_mutex_t lock; struct fuse_ctx ctx; struct fuse_chan *ch; int interrupted; unsigned int ioctl_64bit : 1; union { struct { uint64_t unique; } i; struct { fuse_interrupt_func_t func; void *data; } ni; } u; struct fuse_req *next; struct fuse_req *prev; }; struct fuse_notify_req { uint64_t unique; void (*reply)(struct fuse_notify_req *, fuse_req_t, fuse_ino_t, const void *, const struct fuse_buf *); struct fuse_notify_req *next; struct fuse_notify_req *prev; }; struct fuse_ll { int debug; int allow_root; int atomic_o_trunc; 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; struct cuse_data *cuse_data; void *userdata; uid_t owner; struct fuse_conn_info conn; struct fuse_req list; struct fuse_req interrupts; pthread_mutex_t lock; int got_destroy; pthread_key_t pipe_key; int broken_splice_nonblock; uint64_t notify_ctr; struct fuse_notify_req notify_list; }; 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, void *user_data, int compat); int fuse_sync_compat_args(struct fuse_args *args); 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); void fuse_kern_unmount_compat22(const char *mountpoint); 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 *multithreaded, int *fd, void *user_data, int compat); void cuse_lowlevel_init(fuse_req_t req, fuse_ino_t nodeide, const void *inarg); int fuse_start_thread(pthread_t *thread_id, void *(*func)(void *), void *arg); mergerfs-2.28.1/libfuse/lib/fuse_loop.c0000664000175000017500000000165713474111762016405 0ustar bilebile/* 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 int fuse_session_loop(struct fuse_session *se) { int res = 0; struct fuse_chan *ch = fuse_session_next_chan(se, NULL); size_t bufsize = fuse_chan_bufsize(ch); char *buf = (char *) malloc(bufsize); if (!buf) { fprintf(stderr, "fuse: failed to allocate read buffer\n"); return -1; } while (!fuse_session_exited(se)) { struct fuse_chan *tmpch = ch; struct fuse_buf fbuf = { .mem = buf, .size = bufsize, }; res = fuse_session_receive_buf(se, &fbuf, &tmpch); if (res == -EINTR) continue; if (res <= 0) break; fuse_session_process_buf(se, &fbuf, tmpch); } free(buf); fuse_session_reset(se); return res < 0 ? -1 : 0; } mergerfs-2.28.1/libfuse/lib/fuse.c0000664000175000017500000033635013475734103015356 0ustar bilebile/* 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 "config.h" #include "fuse_i.h" #include "fuse_lowlevel.h" #include "fuse_opt.h" #include "fuse_misc.h" #include "fuse_common_compat.h" #include "fuse_compat.h" #include "fuse_kernel.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define FUSE_NODE_SLAB 1 #ifndef MAP_ANONYMOUS #undef FUSE_NODE_SLAB #endif #define FUSE_DEFAULT_INTR_SIGNAL SIGUSR1 #define FUSE_UNKNOWN_INO 0xffffffff #define OFFSET_MAX 0x7fffffffffffffffLL #define NODE_TABLE_MIN_SIZE 8192 struct fuse_config { unsigned int uid; unsigned int gid; unsigned int umask; double entry_timeout; double negative_timeout; double attr_timeout; int remember; int nopath; int debug; int use_ino; int readdir_ino; int set_mode; int set_uid; int set_gid; int intr; int intr_signal; int help; int threads; }; struct fuse_fs { struct fuse_operations op; void *user_data; int compat; int debug; }; struct lock_queue_element { struct lock_queue_element *next; pthread_cond_t cond; fuse_ino_t nodeid1; const char *name1; char **path1; struct node **wnode1; fuse_ino_t nodeid2; const char *name2; char **path2; struct node **wnode2; int err; bool 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; }; struct node_slab { struct list_head list; /* must be the first member */ struct list_head freelist; int used; }; struct fuse { struct fuse_session *se; struct node_table name_table; struct node_table id_table; struct list_head lru_table; fuse_ino_t ctr; unsigned int generation; unsigned int hidectr; pthread_mutex_t lock; struct fuse_config conf; int intr_installed; struct fuse_fs *fs; int nullpath_ok; int utime_omit_ok; struct lock_queue_element *lockq; int pagesize; struct list_head partial_slabs; struct list_head full_slabs; pthread_t prune_thread; }; struct lock { int type; off_t start; off_t end; pid_t pid; uint64_t owner; struct lock *next; }; struct node { struct node *name_next; struct node *id_next; fuse_ino_t nodeid; unsigned int generation; int refctr; struct node *parent; char *name; uint64_t nlookup; int open_count; struct lock *locks; uint64_t hidden_fh; char is_hidden; int treelock; struct stat stat_cache; char stat_cache_valid; char inline_name[32]; }; #define TREELOCK_WRITE -1 #define TREELOCK_WAIT_OFFSET INT_MIN struct node_lru { struct node node; struct list_head lru; struct timespec forget_time; }; struct fuse_dh { pthread_mutex_t lock; struct fuse *fuse; fuse_req_t req; char *contents; int allocated; unsigned len; unsigned size; unsigned needlen; int filled; uint64_t fh; int error; fuse_ino_t nodeid; }; /* old dir handle */ struct fuse_dirhandle { fuse_fill_dir_t filler; void *buf; }; 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; static void init_list_head(struct list_head *list) { list->next = list; list->prev = list; } static int list_empty(const struct list_head *head) { return head->next == head; } static void list_add(struct list_head *new, struct list_head *prev, struct list_head *next) { next->prev = new; new->next = next; new->prev = prev; prev->next = new; } static inline void list_add_head(struct list_head *new, struct list_head *head) { list_add(new, head, head->next); } static inline void list_add_tail(struct list_head *new, struct list_head *head) { list_add(new, head->prev, head); } static inline void list_del(struct list_head *entry) { struct list_head *prev = entry->prev; struct list_head *next = entry->next; next->prev = prev; prev->next = next; } static inline int lru_enabled(struct fuse *f) { return f->conf.remember > 0; } static struct node_lru *node_lru(struct node *node) { return (struct node_lru *) node; } static size_t get_node_size(struct fuse *f) { if (lru_enabled(f)) return sizeof(struct node_lru); else return sizeof(struct node); } #ifdef FUSE_NODE_SLAB static struct node_slab *list_to_slab(struct list_head *head) { return (struct node_slab *) head; } static struct node_slab *node_to_slab(struct fuse *f, struct node *node) { return (struct node_slab *) (((uintptr_t) node) & ~((uintptr_t) f->pagesize - 1)); } static int alloc_slab(struct fuse *f) { void *mem; struct node_slab *slab; char *start; size_t num; size_t i; size_t node_size = get_node_size(f); mem = mmap(NULL, f->pagesize, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); if (mem == MAP_FAILED) return -1; slab = mem; init_list_head(&slab->freelist); slab->used = 0; num = (f->pagesize - sizeof(struct node_slab)) / node_size; start = (char *) mem + f->pagesize - num * node_size; for (i = 0; i < num; i++) { struct list_head *n; n = (struct list_head *) (start + i * node_size); list_add_tail(n, &slab->freelist); } list_add_tail(&slab->list, &f->partial_slabs); return 0; } static struct node *alloc_node(struct fuse *f) { struct node_slab *slab; struct list_head *node; if (list_empty(&f->partial_slabs)) { int res = alloc_slab(f); if (res != 0) return NULL; } slab = list_to_slab(f->partial_slabs.next); slab->used++; node = slab->freelist.next; list_del(node); if (list_empty(&slab->freelist)) { list_del(&slab->list); list_add_tail(&slab->list, &f->full_slabs); } memset(node, 0, sizeof(struct node)); return (struct node *) node; } static void free_slab(struct fuse *f, struct node_slab *slab) { int res; list_del(&slab->list); res = munmap(slab, f->pagesize); if (res == -1) fprintf(stderr, "fuse warning: munmap(%p) failed\n", slab); } static void free_node_mem(struct fuse *f, struct node *node) { struct node_slab *slab = node_to_slab(f, node); struct list_head *n = (struct list_head *) node; slab->used--; if (slab->used) { if (list_empty(&slab->freelist)) { list_del(&slab->list); list_add_tail(&slab->list, &f->partial_slabs); } list_add_head(n, &slab->freelist); } else { free_slab(f, slab); } } #else static struct node *alloc_node(struct fuse *f) { return (struct node *) calloc(1, get_node_size(f)); } static void free_node_mem(struct fuse *f, struct node *node) { (void) f; free(node); } #endif static size_t id_hash(struct fuse *f, fuse_ino_t ino) { uint64_t hash = ((uint32_t) ino * 2654435761U) % f->id_table.size; uint64_t oldhash = hash % (f->id_table.size / 2); if (oldhash >= f->id_table.split) return oldhash; else return hash; } static struct node *get_node_nocheck(struct fuse *f, fuse_ino_t nodeid) { size_t hash = id_hash(f, nodeid); struct node *node; for (node = f->id_table.array[hash]; node != NULL; node = node->id_next) if (node->nodeid == nodeid) return node; return NULL; } static struct node *get_node(struct fuse *f, fuse_ino_t nodeid) { struct node *node = get_node_nocheck(f, nodeid); if (!node) { fprintf(stderr, "fuse internal error: node %llu not found\n", (unsigned long long) nodeid); abort(); } return node; } static void curr_time(struct timespec *now); static double diff_timespec(const struct timespec *t1, const struct timespec *t2); static void remove_node_lru(struct node *node) { struct node_lru *lnode = node_lru(node); list_del(&lnode->lru); init_list_head(&lnode->lru); } static void set_forget_time(struct fuse *f, struct node *node) { struct node_lru *lnode = node_lru(node); list_del(&lnode->lru); list_add_tail(&lnode->lru, &f->lru_table); curr_time(&lnode->forget_time); } static void free_node(struct fuse *f_, struct node *node_) { if(node_->name != node_->inline_name) free(node_->name); 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 = id_hash(f, node->nodeid); node->id_next = f->id_table.array[hash]; f->id_table.array[hash] = node; f->id_table.use++; if (f->id_table.use >= f->id_table.size / 2) rehash_id(f); } static size_t name_hash(struct fuse *f, fuse_ino_t parent, const char *name) { uint64_t hash = parent; uint64_t oldhash; for (; *name; name++) hash = hash * 31 + (unsigned char) *name; hash %= f->name_table.size; oldhash = hash % (f->name_table.size / 2); if (oldhash >= f->name_table.split) return oldhash; else return hash; } static void unref_node(struct fuse *f, struct node *node); static void remerge_name(struct fuse *f) { struct node_table *t = &f->name_table; int iter; if (t->split == 0) node_table_reduce(t); for (iter = 8; t->split > 0 && iter; iter--) { struct node **upper; t->split--; upper = &t->array[t->split + t->size / 2]; if (*upper) { struct node **nodep; for (nodep = &t->array[t->split]; *nodep; nodep = &(*nodep)->name_next); *nodep = *upper; *upper = NULL; break; } } } static void unhash_name(struct fuse *f, struct node *node) { if (node->name) { size_t hash = name_hash(f, node->parent->nodeid, node->name); struct node **nodep = &f->name_table.array[hash]; for (; *nodep != NULL; nodep = &(*nodep)->name_next) if (*nodep == node) { *nodep = node->name_next; node->name_next = NULL; unref_node(f, node->parent); if (node->name != node->inline_name) free(node->name); node->name = NULL; node->parent = NULL; f->name_table.use--; if (f->name_table.use < f->name_table.size / 4) remerge_name(f); return; } 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, fuse_ino_t parentid, const char *name) { size_t hash = name_hash(f, parentid, name); struct node *parent = get_node(f, parentid); if (strlen(name) < sizeof(node->inline_name)) { strcpy(node->inline_name, name); node->name = node->inline_name; } else { node->name = strdup(name); if (node->name == NULL) return -1; } parent->refctr ++; node->parent = parent; node->name_next = f->name_table.array[hash]; f->name_table.array[hash] = node; f->name_table.use++; if (f->name_table.use >= f->name_table.size / 2) rehash_name(f); return 0; } static void delete_node(struct fuse *f, struct node *node) { if (f->conf.debug) fprintf(stderr, "DELETE: %llu\n", (unsigned long long) node->nodeid); assert(node->treelock == 0); unhash_name(f, node); if (lru_enabled(f)) remove_node_lru(node); unhash_id(f, node); free_node(f, node); } static void unref_node(struct fuse *f, struct node *node) { assert(node->refctr > 0); node->refctr --; if (!node->refctr) delete_node(f, node); } static fuse_ino_t next_id(struct fuse *f) { do { f->ctr = (f->ctr + 1) & 0xffffffff; if (!f->ctr) f->generation ++; } while (f->ctr == 0 || f->ctr == FUSE_UNKNOWN_INO || get_node_nocheck(f, f->ctr) != NULL); return f->ctr; } static struct node *lookup_node(struct fuse *f, fuse_ino_t parent, const char *name) { size_t hash = name_hash(f, parent, name); struct node *node; for (node = f->name_table.array[hash]; node != NULL; node = node->name_next) if (node->parent->nodeid == parent && strcmp(node->name, name) == 0) return node; return NULL; } static void inc_nlookup(struct node *node) { if (!node->nlookup) node->refctr++; node->nlookup++; } static struct node *find_node(struct fuse *f, fuse_ino_t parent, const char *name) { struct node *node; pthread_mutex_lock(&f->lock); if (!name) node = get_node(f, parent); else node = lookup_node(f, parent, name); if (node == NULL) { node = alloc_node(f); if (node == NULL) goto out_err; node->nodeid = next_id(f); node->generation = f->generation; if (f->conf.remember) inc_nlookup(node); if (hash_name(f, node, parent, name) == -1) { free_node(f, node); node = NULL; goto out_err; } hash_id(f, node); if (lru_enabled(f)) { struct node_lru *lnode = node_lru(node); init_list_head(&lnode->lru); } } else if (lru_enabled(f) && node->nlookup == 1) { remove_node_lru(node); } inc_nlookup(node); out_err: pthread_mutex_unlock(&f->lock); return node; } static 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, fuse_ino_t nodeid, struct node *wnode, struct node *end) { struct node *node; if (wnode) { assert(wnode->treelock == TREELOCK_WRITE); wnode->treelock = 0; } for (node = get_node(f, nodeid); node != end && node->nodeid != FUSE_ROOT_ID; node = node->parent) { assert(node->treelock != 0); assert(node->treelock != TREELOCK_WAIT_OFFSET); assert(node->treelock != TREELOCK_WRITE); node->treelock--; if (node->treelock == TREELOCK_WAIT_OFFSET) node->treelock = 0; } } static int try_get_path(struct fuse *f, fuse_ino_t nodeid, const char *name, char **path, struct node **wnodep, bool need_lock) { unsigned bufsize = 256; char *buf; char *s; struct node *node; struct node *wnode = NULL; int err; *path = NULL; err = -ENOMEM; buf = malloc(bufsize); if (buf == NULL) goto out_err; s = buf + bufsize - 1; *s = '\0'; if (name != NULL) { s = add_name(&buf, &bufsize, s, name); err = -ENOMEM; if (s == NULL) goto out_free; } if (wnodep) { assert(need_lock); 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 = -ENOENT; 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 debug_path(struct fuse *f, const char *msg, fuse_ino_t nodeid, const char *name, bool wr) { if (f->conf.debug) { struct node *wnode = NULL; if (wr) wnode = lookup_node(f, nodeid, name); if (wnode) fprintf(stderr, "%s %li (w)\n", msg, wnode->nodeid); else fprintf(stderr, "%s %li\n", msg, nodeid); } } 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, fuse_ino_t nodeid, const char *name, char **path, struct node **wnode) { int err; pthread_mutex_lock(&f->lock); err = try_get_path(f, nodeid, name, path, wnode, true); if (err == -EAGAIN) { struct lock_queue_element qe = { .nodeid1 = nodeid, .name1 = name, .path1 = path, .wnode1 = wnode, }; debug_path(f, "QUEUE PATH", nodeid, name, !!wnode); err = wait_path(f, &qe); debug_path(f, "DEQUEUE PATH", nodeid, name, !!wnode); } pthread_mutex_unlock(&f->lock); return err; } static int get_path(struct fuse *f, fuse_ino_t nodeid, char **path) { return get_path_common(f, nodeid, NULL, path, NULL); } static int get_path_nullok(struct fuse *f, fuse_ino_t nodeid, char **path) { int err = 0; if (f->conf.nopath) { *path = NULL; } else { err = get_path_common(f, nodeid, NULL, path, NULL); if (err == -ENOENT && f->nullpath_ok) err = 0; } return err; } static int get_path_name(struct fuse *f, fuse_ino_t nodeid, const char *name, char **path) { return get_path_common(f, nodeid, name, path, NULL); } static int get_path_wrlock(struct fuse *f, fuse_ino_t nodeid, const char *name, char **path, struct node **wnode) { return get_path_common(f, nodeid, name, path, wnode); } static int try_get_path2(struct fuse *f, fuse_ino_t nodeid1, const char *name1, fuse_ino_t nodeid2, const char *name2, char **path1, char **path2, struct node **wnode1, struct node **wnode2) { int err; /* FIXME: locking two paths needs deadlock checking */ err = try_get_path(f, nodeid1, name1, path1, wnode1, true); if (!err) { err = try_get_path(f, nodeid2, name2, path2, wnode2, true); if (err) { struct node *wn1 = wnode1 ? *wnode1 : NULL; unlock_path(f, nodeid1, wn1, NULL); free(*path1); } } return err; } static int get_path2(struct fuse *f, fuse_ino_t nodeid1, const char *name1, fuse_ino_t nodeid2, const char *name2, char **path1, char **path2, struct node **wnode1, struct node **wnode2) { int err; pthread_mutex_lock(&f->lock); err = try_get_path2(f, nodeid1, name1, nodeid2, name2, path1, path2, wnode1, wnode2); if (err == -EAGAIN) { struct lock_queue_element qe = { .nodeid1 = nodeid1, .name1 = name1, .path1 = path1, .wnode1 = wnode1, .nodeid2 = nodeid2, .name2 = name2, .path2 = path2, .wnode2 = wnode2, }; debug_path(f, "QUEUE PATH1", nodeid1, name1, !!wnode1); debug_path(f, " PATH2", nodeid2, name2, !!wnode2); err = wait_path(f, &qe); debug_path(f, "DEQUEUE PATH1", nodeid1, name1, !!wnode1); debug_path(f, " PATH2", nodeid2, name2, !!wnode2); } pthread_mutex_unlock(&f->lock); return err; } static void free_path_wrlock(struct fuse *f, fuse_ino_t nodeid, struct node *wnode, char *path) { pthread_mutex_lock(&f->lock); unlock_path(f, nodeid, wnode, NULL); if (f->lockq) wake_up_queued(f); pthread_mutex_unlock(&f->lock); free(path); } static void free_path(struct fuse *f, fuse_ino_t nodeid, char *path) { if (path) free_path_wrlock(f, nodeid, NULL, path); } static void free_path2(struct fuse *f, fuse_ino_t nodeid1, fuse_ino_t nodeid2, struct node *wnode1, struct node *wnode2, char *path1, char *path2) { pthread_mutex_lock(&f->lock); unlock_path(f, nodeid1, wnode1, NULL); unlock_path(f, nodeid2, wnode2, NULL); wake_up_queued(f); pthread_mutex_unlock(&f->lock); free(path1); free(path2); } static void forget_node(struct fuse *f, fuse_ino_t nodeid, uint64_t nlookup) { struct node *node; if (nodeid == FUSE_ROOT_ID) return; pthread_mutex_lock(&f->lock); node = get_node(f, nodeid); /* * Node may still be locked due to interrupt idiocy in open, * create and opendir */ while (node->nlookup == nlookup && node->treelock) { struct lock_queue_element qe = { .nodeid1 = nodeid, }; debug_path(f, "QUEUE PATH (forget)", nodeid, NULL, false); queue_path(f, &qe); do { pthread_cond_wait(&qe.cond, &f->lock); } while (node->nlookup == nlookup && node->treelock); dequeue_path(f, &qe); debug_path(f, "DEQUEUE_PATH (forget)", nodeid, NULL, false); } assert(node->nlookup >= nlookup); node->nlookup -= nlookup; if (!node->nlookup) { unref_node(f, node); } else if (lru_enabled(f) && node->nlookup == 1) { set_forget_time(f, node); } pthread_mutex_unlock(&f->lock); } static void unlink_node(struct fuse *f, struct node *node) { if (f->conf.remember) { assert(node->nlookup > 1); node->nlookup--; } unhash_name(f, node); } static void remove_node(struct fuse *f, fuse_ino_t dir, const char *name) { struct node *node; pthread_mutex_lock(&f->lock); node = lookup_node(f, dir, name); if (node != NULL) unlink_node(f, node); pthread_mutex_unlock(&f->lock); } static int rename_node(struct fuse *f, fuse_ino_t olddir, const char *oldname, fuse_ino_t newdir, const char *newname) { 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, fuse_ino_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); } static void fuse_intr_sighandler(int sig) { (void) sig; /* Nothing to do */ } struct fuse_intr_data { pthread_t id; pthread_cond_t cond; int finished; }; static void fuse_interrupt(fuse_req_t req, void *d_) { struct fuse_intr_data *d = d_; struct fuse *f = req_fuse(req); if (d->id == pthread_self()) return; pthread_mutex_lock(&f->lock); while (!d->finished) { struct timeval now; struct timespec timeout; pthread_kill(d->id, f->conf.intr_signal); gettimeofday(&now, NULL); timeout.tv_sec = now.tv_sec + 1; timeout.tv_nsec = now.tv_usec * 1000; pthread_cond_timedwait(&d->cond, &f->lock, &timeout); } pthread_mutex_unlock(&f->lock); } static void fuse_do_finish_interrupt(struct fuse *f, fuse_req_t req, struct fuse_intr_data *d) { pthread_mutex_lock(&f->lock); d->finished = 1; pthread_cond_broadcast(&d->cond); pthread_mutex_unlock(&f->lock); fuse_req_interrupt_func(req, NULL, NULL); pthread_cond_destroy(&d->cond); } static void fuse_do_prepare_interrupt(fuse_req_t req, struct fuse_intr_data *d) { d->id = pthread_self(); pthread_cond_init(&d->cond, NULL); d->finished = 0; fuse_req_interrupt_func(req, fuse_interrupt, d); } static inline void fuse_finish_interrupt(struct fuse *f, fuse_req_t req, struct fuse_intr_data *d) { if (f->conf.intr) fuse_do_finish_interrupt(f, req, d); } static inline void fuse_prepare_interrupt(struct fuse *f, fuse_req_t req, struct fuse_intr_data *d) { if (f->conf.intr) fuse_do_prepare_interrupt(req, d); } #if !defined(__FreeBSD__) && !defined(__NetBSD__) static int fuse_compat_open(struct fuse_fs *fs, const char *path, struct fuse_file_info *fi) { int err; if (!fs->compat || fs->compat >= 25) err = fs->op.open(path, fi); else if (fs->compat == 22) { struct fuse_file_info_compat tmp; memcpy(&tmp, fi, sizeof(tmp)); err = ((struct fuse_operations_compat22 *) &fs->op)->open(path, &tmp); memcpy(fi, &tmp, sizeof(tmp)); fi->fh = tmp.fh; } else err = ((struct fuse_operations_compat2 *) &fs->op) ->open(path, fi->flags); return err; } static int fuse_compat_release(struct fuse_fs *fs, const char *path, struct fuse_file_info *fi) { if (!fs->compat || fs->compat >= 22) return fs->op.release(path, fi); else return ((struct fuse_operations_compat2 *) &fs->op) ->release(path, fi->flags); } static int fuse_compat_opendir(struct fuse_fs *fs, const char *path, struct fuse_file_info *fi) { if (!fs->compat || fs->compat >= 25) return fs->op.opendir(path, fi); else { int err; struct fuse_file_info_compat tmp; memcpy(&tmp, fi, sizeof(tmp)); err = ((struct fuse_operations_compat22 *) &fs->op) ->opendir(path, &tmp); memcpy(fi, &tmp, sizeof(tmp)); fi->fh = tmp.fh; return err; } } static void convert_statfs_compat(struct fuse_statfs_compat1 *compatbuf, struct statvfs *stbuf) { stbuf->f_bsize = compatbuf->block_size; stbuf->f_blocks = compatbuf->blocks; stbuf->f_bfree = compatbuf->blocks_free; stbuf->f_bavail = compatbuf->blocks_free; stbuf->f_files = compatbuf->files; stbuf->f_ffree = compatbuf->files_free; stbuf->f_namemax = compatbuf->namelen; } static void convert_statfs_old(struct statfs *oldbuf, struct statvfs *stbuf) { stbuf->f_bsize = oldbuf->f_bsize; stbuf->f_blocks = oldbuf->f_blocks; stbuf->f_bfree = oldbuf->f_bfree; stbuf->f_bavail = oldbuf->f_bavail; stbuf->f_files = oldbuf->f_files; stbuf->f_ffree = oldbuf->f_ffree; stbuf->f_namemax = oldbuf->f_namelen; } static int fuse_compat_statfs(struct fuse_fs *fs, const char *path, struct statvfs *buf) { int err; if (!fs->compat || fs->compat >= 25) { err = fs->op.statfs(fs->compat == 25 ? "/" : path, buf); } else if (fs->compat > 11) { struct statfs oldbuf; err = ((struct fuse_operations_compat22 *) &fs->op) ->statfs("/", &oldbuf); if (!err) convert_statfs_old(&oldbuf, buf); } else { struct fuse_statfs_compat1 compatbuf; memset(&compatbuf, 0, sizeof(struct fuse_statfs_compat1)); err = ((struct fuse_operations_compat1 *) &fs->op) ->statfs(&compatbuf); if (!err) convert_statfs_compat(&compatbuf, buf); } return err; } #else /* __FreeBSD__ || __NetBSD__ */ static inline int fuse_compat_open(struct fuse_fs *fs, char *path, struct fuse_file_info *fi) { return fs->op.open(path, fi); } static inline int fuse_compat_release(struct fuse_fs *fs, const char *path, struct fuse_file_info *fi) { return fs->op.release(path, fi); } static inline int fuse_compat_opendir(struct fuse_fs *fs, const char *path, struct fuse_file_info *fi) { return fs->op.opendir(path, fi); } static inline int fuse_compat_statfs(struct fuse_fs *fs, const char *path, struct statvfs *buf) { return fs->op.statfs(fs->compat == 25 ? "/" : path, buf); } #endif /* __FreeBSD__ || __NetBSD__ */ int fuse_fs_getattr(struct fuse_fs *fs, const char *path, struct stat *buf) { fuse_get_context()->private_data = fs->user_data; if (fs->op.getattr) { if (fs->debug) fprintf(stderr, "getattr %s\n", path); return fs->op.getattr(path, buf); } else { return -ENOSYS; } } int fuse_fs_fgetattr(struct fuse_fs *fs, const char *path, struct stat *buf, struct fuse_file_info *fi) { fuse_get_context()->private_data = fs->user_data; if (fs->op.fgetattr) { if (fs->debug) fprintf(stderr, "fgetattr[%llu] %s\n", (unsigned long long) fi->fh, path); return fs->op.fgetattr(path, buf, fi); } else if (path && fs->op.getattr) { if (fs->debug) fprintf(stderr, "getattr %s\n", path); return fs->op.getattr(path, buf); } else { return -ENOSYS; } } int fuse_fs_rename(struct fuse_fs *fs, const char *oldpath, const char *newpath) { fuse_get_context()->private_data = fs->user_data; if(fs->op.rename) return fs->op.rename(oldpath, newpath); return -ENOSYS; } int fuse_fs_prepare_hide(struct fuse_fs *fs_, const char *path_, uint64_t *fh_) { fuse_get_context()->private_data = fs_->user_data; if(fs_->op.prepare_hide) return fs_->op.prepare_hide(path_,fh_); return -ENOSYS; } int fuse_fs_free_hide(struct fuse_fs *fs_, uint64_t fh_) { fuse_get_context()->private_data = fs_->user_data; if(fs_->op.free_hide) return fs_->op.free_hide(fh_); return -ENOSYS; } int fuse_fs_unlink(struct fuse_fs *fs, const char *path) { fuse_get_context()->private_data = fs->user_data; if (fs->op.unlink) { if (fs->debug) fprintf(stderr, "unlink %s\n", path); return fs->op.unlink(path); } else { return -ENOSYS; } } int fuse_fs_rmdir(struct fuse_fs *fs, const char *path) { fuse_get_context()->private_data = fs->user_data; if (fs->op.rmdir) { if (fs->debug) fprintf(stderr, "rmdir %s\n", path); return fs->op.rmdir(path); } else { return -ENOSYS; } } int fuse_fs_symlink(struct fuse_fs *fs, const char *linkname, const char *path) { fuse_get_context()->private_data = fs->user_data; if (fs->op.symlink) { if (fs->debug) fprintf(stderr, "symlink %s %s\n", linkname, path); return fs->op.symlink(linkname, path); } else { return -ENOSYS; } } int fuse_fs_link(struct fuse_fs *fs, const char *oldpath, const char *newpath) { fuse_get_context()->private_data = fs->user_data; if (fs->op.link) { if (fs->debug) fprintf(stderr, "link %s %s\n", oldpath, newpath); return fs->op.link(oldpath, newpath); } else { return -ENOSYS; } } int fuse_fs_release(struct fuse_fs *fs, const char *path, struct fuse_file_info *fi) { fuse_get_context()->private_data = fs->user_data; if (fs->op.release) { if (fs->debug) fprintf(stderr, "release%s[%llu] flags: 0x%x\n", fi->flush ? "+flush" : "", (unsigned long long) fi->fh, fi->flags); return fuse_compat_release(fs, path, fi); } else { return 0; } } int fuse_fs_opendir(struct fuse_fs *fs, const char *path, struct fuse_file_info *fi) { fuse_get_context()->private_data = fs->user_data; if (fs->op.opendir) { int err; if (fs->debug) fprintf(stderr, "opendir flags: 0x%x %s\n", fi->flags, path); err = fuse_compat_opendir(fs, path, fi); if (fs->debug && !err) fprintf(stderr, " opendir[%lli] flags: 0x%x %s\n", (unsigned long long) fi->fh, fi->flags, path); return err; } else { return 0; } } int fuse_fs_open(struct fuse_fs *fs, const char *path, struct fuse_file_info *fi) { fuse_get_context()->private_data = fs->user_data; if (fs->op.open) { int err; if (fs->debug) fprintf(stderr, "open flags: 0x%x %s\n", fi->flags, path); err = fuse_compat_open(fs, path, fi); if (fs->debug && !err) fprintf(stderr, " open[%lli] flags: 0x%x %s\n", (unsigned long long) fi->fh, fi->flags, path); return err; } else { return 0; } } 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, const char *path, struct fuse_bufvec **bufp, size_t size, off_t off, struct fuse_file_info *fi) { fuse_get_context()->private_data = fs->user_data; if (fs->op.read || fs->op.read_buf) { int res; if (fs->debug) fprintf(stderr, "read[%llu] %zu bytes from %llu flags: 0x%x\n", (unsigned long long) fi->fh, size, (unsigned long long) off, fi->flags); if (fs->op.read_buf) { res = fs->op.read_buf(path, bufp, size, off, fi); } else { struct fuse_bufvec *buf; void *mem; buf = malloc(sizeof(struct fuse_bufvec)); if (buf == NULL) return -ENOMEM; mem = malloc(size); if (mem == NULL) { free(buf); return -ENOMEM; } *buf = FUSE_BUFVEC_INIT(size); buf->buf[0].mem = mem; *bufp = buf; res = fs->op.read(path, mem, size, off, fi); if (res >= 0) buf->buf[0].size = res; } if (fs->debug && res >= 0) fprintf(stderr, " read[%llu] %zu bytes from %llu\n", (unsigned long long) fi->fh, fuse_buf_size(*bufp), (unsigned long long) off); if (res >= 0 && fuse_buf_size(*bufp) > (int) size) fprintf(stderr, "fuse: read too many bytes\n"); if (res < 0) return res; return 0; } else { return -ENOSYS; } } int fuse_fs_read(struct fuse_fs *fs, const char *path, char *mem, size_t size, off_t off, struct fuse_file_info *fi) { int res; struct fuse_bufvec *buf = NULL; res = fuse_fs_read_buf(fs, path, &buf, size, off, fi); if (res == 0) { struct fuse_bufvec dst = FUSE_BUFVEC_INIT(size); dst.buf[0].mem = mem; res = fuse_buf_copy(&dst, buf, 0); } fuse_free_buf(buf); return res; } int fuse_fs_write_buf(struct fuse_fs *fs, const char *path, struct fuse_bufvec *buf, off_t off, struct fuse_file_info *fi) { fuse_get_context()->private_data = fs->user_data; if (fs->op.write_buf || fs->op.write) { int res; size_t size = fuse_buf_size(buf); assert(buf->idx == 0 && buf->off == 0); if (fs->debug) fprintf(stderr, "write%s[%llu] %zu bytes to %llu flags: 0x%x\n", fi->writepage ? "page" : "", (unsigned long long) fi->fh, size, (unsigned long long) off, fi->flags); if (fs->op.write_buf) { res = fs->op.write_buf(path, buf, off, fi); } else { void *mem = NULL; struct fuse_buf *flatbuf; struct fuse_bufvec tmp = FUSE_BUFVEC_INIT(size); if (buf->count == 1 && !(buf->buf[0].flags & FUSE_BUF_IS_FD)) { flatbuf = &buf->buf[0]; } else { res = -ENOMEM; mem = malloc(size); if (mem == NULL) goto out; tmp.buf[0].mem = mem; res = fuse_buf_copy(&tmp, buf, 0); if (res <= 0) goto out_free; tmp.buf[0].size = res; flatbuf = &tmp.buf[0]; } res = fs->op.write(path, flatbuf->mem, flatbuf->size, off, fi); out_free: free(mem); } out: if (fs->debug && res >= 0) fprintf(stderr, " write%s[%llu] %u bytes to %llu\n", fi->writepage ? "page" : "", (unsigned long long) fi->fh, res, (unsigned long long) off); if (res > (int) size) fprintf(stderr, "fuse: wrote too many bytes\n"); return res; } else { return -ENOSYS; } } int fuse_fs_write(struct fuse_fs *fs, const char *path, const char *mem, size_t size, off_t off, struct fuse_file_info *fi) { struct fuse_bufvec bufv = FUSE_BUFVEC_INIT(size); bufv.buf[0].mem = (void *) mem; return fuse_fs_write_buf(fs, path, &bufv, off, fi); } int fuse_fs_fsync(struct fuse_fs *fs, const char *path, int datasync, struct fuse_file_info *fi) { fuse_get_context()->private_data = fs->user_data; if (fs->op.fsync) { if (fs->debug) fprintf(stderr, "fsync[%llu] datasync: %i\n", (unsigned long long) fi->fh, datasync); return fs->op.fsync(path, datasync, fi); } else { return -ENOSYS; } } int fuse_fs_fsyncdir(struct fuse_fs *fs, const char *path, int datasync, struct fuse_file_info *fi) { fuse_get_context()->private_data = fs->user_data; if (fs->op.fsyncdir) { if (fs->debug) fprintf(stderr, "fsyncdir[%llu] datasync: %i\n", (unsigned long long) fi->fh, datasync); return fs->op.fsyncdir(path, datasync, fi); } else { return -ENOSYS; } } int fuse_fs_flush(struct fuse_fs *fs, const char *path, struct fuse_file_info *fi) { fuse_get_context()->private_data = fs->user_data; if (fs->op.flush) { if (fs->debug) fprintf(stderr, "flush[%llu]\n", (unsigned long long) fi->fh); return fs->op.flush(path, fi); } else { return -ENOSYS; } } int fuse_fs_statfs(struct fuse_fs *fs, const char *path, struct statvfs *buf) { fuse_get_context()->private_data = fs->user_data; if (fs->op.statfs) { if (fs->debug) fprintf(stderr, "statfs %s\n", path); return fuse_compat_statfs(fs, path, buf); } else { buf->f_namemax = 255; buf->f_bsize = 512; return 0; } } int fuse_fs_releasedir(struct fuse_fs *fs, const char *path, struct fuse_file_info *fi) { fuse_get_context()->private_data = fs->user_data; if (fs->op.releasedir) { if (fs->debug) fprintf(stderr, "releasedir[%llu] flags: 0x%x\n", (unsigned long long) fi->fh, fi->flags); return fs->op.releasedir(path, fi); } else { return 0; } } static int fill_dir_old(struct fuse_dirhandle *dh, const char *name, int type, ino_t ino) { int res; struct stat stbuf; memset(&stbuf, 0, sizeof(stbuf)); stbuf.st_mode = type << 12; stbuf.st_ino = ino; res = dh->filler(dh->buf, name, &stbuf, 0); return res ? -ENOMEM : 0; } int fuse_fs_readdir(struct fuse_fs *fs, const char *path, void *buf, fuse_fill_dir_t filler, off_t off, struct fuse_file_info *fi) { fuse_get_context()->private_data = fs->user_data; if (fs->op.readdir) { if (fs->debug) fprintf(stderr, "readdir[%llu] from %llu\n", (unsigned long long) fi->fh, (unsigned long long) off); return fs->op.readdir(path, buf, filler, off, fi); } else if (fs->op.getdir) { struct fuse_dirhandle dh; if (fs->debug) fprintf(stderr, "getdir[%llu]\n", (unsigned long long) fi->fh); dh.filler = filler; dh.buf = buf; return fs->op.getdir(path, &dh, fill_dir_old); } else { return -ENOSYS; } } int fuse_fs_create(struct fuse_fs *fs, const char *path, mode_t mode, struct fuse_file_info *fi) { fuse_get_context()->private_data = fs->user_data; if (fs->op.create) { int err; if (fs->debug) fprintf(stderr, "create flags: 0x%x %s 0%o umask=0%03o\n", fi->flags, path, mode, fuse_get_context()->umask); err = fs->op.create(path, mode, fi); if (fs->debug && !err) fprintf(stderr, " create[%llu] flags: 0x%x %s\n", (unsigned long long) fi->fh, fi->flags, path); return err; } else { return -ENOSYS; } } int fuse_fs_lock(struct fuse_fs *fs, const char *path, struct fuse_file_info *fi, int cmd, struct flock *lock) { fuse_get_context()->private_data = fs->user_data; if (fs->op.lock) { if (fs->debug) fprintf(stderr, "lock[%llu] %s %s start: %llu len: %llu pid: %llu\n", (unsigned long long) fi->fh, (cmd == F_GETLK ? "F_GETLK" : (cmd == F_SETLK ? "F_SETLK" : (cmd == F_SETLKW ? "F_SETLKW" : "???"))), (lock->l_type == F_RDLCK ? "F_RDLCK" : (lock->l_type == F_WRLCK ? "F_WRLCK" : (lock->l_type == F_UNLCK ? "F_UNLCK" : "???"))), (unsigned long long) lock->l_start, (unsigned long long) lock->l_len, (unsigned long long) lock->l_pid); return fs->op.lock(path, fi, cmd, lock); } else { return -ENOSYS; } } int fuse_fs_flock(struct fuse_fs *fs, const char *path, struct fuse_file_info *fi, int op) { fuse_get_context()->private_data = fs->user_data; if (fs->op.flock) { if (fs->debug) { int xop = op & ~LOCK_NB; fprintf(stderr, "lock[%llu] %s%s\n", (unsigned long long) fi->fh, xop == LOCK_SH ? "LOCK_SH" : (xop == LOCK_EX ? "LOCK_EX" : (xop == LOCK_UN ? "LOCK_UN" : "???")), (op & LOCK_NB) ? "|LOCK_NB" : ""); } return fs->op.flock(path, fi, op); } else { return -ENOSYS; } } int fuse_fs_chown(struct fuse_fs *fs, const char *path, uid_t uid, gid_t gid) { fuse_get_context()->private_data = fs->user_data; if (fs->op.chown) { if (fs->debug) fprintf(stderr, "chown %s %lu %lu\n", path, (unsigned long) uid, (unsigned long) gid); return fs->op.chown(path, uid, gid); } else { return -ENOSYS; } } int fuse_fs_fchown(struct fuse_fs *fs_, const struct fuse_file_info *ffi_, const uid_t uid_, const gid_t gid_) { fuse_get_context()->private_data = fs_->user_data; if(fs_->op.fchown) return fs_->op.fchown(ffi_,uid_,gid_); return -ENOSYS; } int fuse_fs_truncate(struct fuse_fs *fs, const char *path, off_t size) { fuse_get_context()->private_data = fs->user_data; if (fs->op.truncate) { if (fs->debug) fprintf(stderr, "truncate %s %llu\n", path, (unsigned long long) size); return fs->op.truncate(path, size); } else { return -ENOSYS; } } int fuse_fs_ftruncate(struct fuse_fs *fs, const char *path, off_t size, struct fuse_file_info *fi) { fuse_get_context()->private_data = fs->user_data; if (fs->op.ftruncate) { if (fs->debug) fprintf(stderr, "ftruncate[%llu] %llu\n", (unsigned long long) fi->fh, (unsigned long long) size); return fs->op.ftruncate(path, size, fi); } else if (path && fs->op.truncate) { if (fs->debug) fprintf(stderr, "truncate %s %llu\n", path, (unsigned long long) size); return fs->op.truncate(path, size); } else { return -ENOSYS; } } int fuse_fs_utimens(struct fuse_fs *fs, const char *path, const struct timespec tv[2]) { fuse_get_context()->private_data = fs->user_data; if (fs->op.utimens) { if (fs->debug) fprintf(stderr, "utimens %s %li.%09lu %li.%09lu\n", path, tv[0].tv_sec, tv[0].tv_nsec, tv[1].tv_sec, tv[1].tv_nsec); return fs->op.utimens(path, tv); } else if(fs->op.utime) { struct utimbuf buf; if (fs->debug) fprintf(stderr, "utime %s %li %li\n", path, tv[0].tv_sec, tv[1].tv_sec); buf.actime = tv[0].tv_sec; buf.modtime = tv[1].tv_sec; return fs->op.utime(path, &buf); } else { return -ENOSYS; } } int fuse_fs_futimens(struct fuse_fs *fs_, const struct fuse_file_info *ffi_, const struct timespec tv_[2]) { fuse_get_context()->private_data = fs_->user_data; if(fs_->op.futimens) return fs_->op.futimens(ffi_,tv_); return -ENOSYS; } int fuse_fs_access(struct fuse_fs *fs, const char *path, int mask) { fuse_get_context()->private_data = fs->user_data; if (fs->op.access) { if (fs->debug) fprintf(stderr, "access %s 0%o\n", path, mask); return fs->op.access(path, mask); } else { return -ENOSYS; } } int fuse_fs_readlink(struct fuse_fs *fs, const char *path, char *buf, size_t len) { fuse_get_context()->private_data = fs->user_data; if (fs->op.readlink) { if (fs->debug) fprintf(stderr, "readlink %s %lu\n", path, (unsigned long) len); return fs->op.readlink(path, buf, len); } else { return -ENOSYS; } } int fuse_fs_mknod(struct fuse_fs *fs, const char *path, mode_t mode, dev_t rdev) { fuse_get_context()->private_data = fs->user_data; if (fs->op.mknod) { if (fs->debug) fprintf(stderr, "mknod %s 0%o 0x%llx umask=0%03o\n", path, mode, (unsigned long long) rdev, fuse_get_context()->umask); return fs->op.mknod(path, mode, rdev); } else { return -ENOSYS; } } int fuse_fs_mkdir(struct fuse_fs *fs, const char *path, mode_t mode) { fuse_get_context()->private_data = fs->user_data; if (fs->op.mkdir) { if (fs->debug) fprintf(stderr, "mkdir %s 0%o umask=0%03o\n", path, mode, fuse_get_context()->umask); return fs->op.mkdir(path, mode); } else { return -ENOSYS; } } int fuse_fs_setxattr(struct fuse_fs *fs, const char *path, const char *name, const char *value, size_t size, int flags) { fuse_get_context()->private_data = fs->user_data; if (fs->op.setxattr) { if (fs->debug) fprintf(stderr, "setxattr %s %s %lu 0x%x\n", path, name, (unsigned long) size, flags); return fs->op.setxattr(path, name, value, size, flags); } else { return -ENOSYS; } } int fuse_fs_getxattr(struct fuse_fs *fs, const char *path, const char *name, char *value, size_t size) { fuse_get_context()->private_data = fs->user_data; if (fs->op.getxattr) { if (fs->debug) fprintf(stderr, "getxattr %s %s %lu\n", path, name, (unsigned long) size); return fs->op.getxattr(path, name, value, size); } else { return -ENOSYS; } } int fuse_fs_listxattr(struct fuse_fs *fs, const char *path, char *list, size_t size) { fuse_get_context()->private_data = fs->user_data; if (fs->op.listxattr) { if (fs->debug) fprintf(stderr, "listxattr %s %lu\n", path, (unsigned long) size); return fs->op.listxattr(path, list, size); } else { return -ENOSYS; } } int fuse_fs_bmap(struct fuse_fs *fs, const char *path, size_t blocksize, uint64_t *idx) { fuse_get_context()->private_data = fs->user_data; if (fs->op.bmap) { if (fs->debug) fprintf(stderr, "bmap %s blocksize: %lu index: %llu\n", path, (unsigned long) blocksize, (unsigned long long) *idx); return fs->op.bmap(path, blocksize, idx); } else { return -ENOSYS; } } int fuse_fs_removexattr(struct fuse_fs *fs, const char *path, const char *name) { fuse_get_context()->private_data = fs->user_data; if (fs->op.removexattr) { if (fs->debug) fprintf(stderr, "removexattr %s %s\n", path, name); return fs->op.removexattr(path, name); } else { return -ENOSYS; } } int fuse_fs_ioctl(struct fuse_fs *fs, const char *path, int cmd, void *arg, struct fuse_file_info *fi, unsigned int flags, void *data, uint32_t *out_size) { fuse_get_context()->private_data = fs->user_data; if (fs->op.ioctl) { if (fs->debug) fprintf(stderr, "ioctl[%llu] 0x%x flags: 0x%x\n", (unsigned long long) fi->fh, cmd, flags); return fs->op.ioctl(path, cmd, arg, fi, flags, data, out_size); } else return -ENOSYS; } int fuse_fs_poll(struct fuse_fs *fs, const char *path, struct fuse_file_info *fi, struct fuse_pollhandle *ph, unsigned *reventsp) { fuse_get_context()->private_data = fs->user_data; if (fs->op.poll) { int res; if (fs->debug) fprintf(stderr, "poll[%llu] ph: %p\n", (unsigned long long) fi->fh, ph); res = fs->op.poll(path, fi, ph, reventsp); if (fs->debug && !res) fprintf(stderr, " poll[%llu] revents: 0x%x\n", (unsigned long long) fi->fh, *reventsp); return res; } else return -ENOSYS; } int fuse_fs_fallocate(struct fuse_fs *fs, const char *path, int mode, off_t offset, off_t length, struct fuse_file_info *fi) { fuse_get_context()->private_data = fs->user_data; if (fs->op.fallocate) { if (fs->debug) fprintf(stderr, "fallocate %s mode %x, offset: %llu, length: %llu\n", path, mode, (unsigned long long) offset, (unsigned long long) length); return fs->op.fallocate(path, mode, offset, length, fi); } else return -ENOSYS; } ssize_t fuse_fs_copy_file_range(struct fuse_fs *fs_, const char *path_in_, struct fuse_file_info *ffi_in_, off_t off_in_, const char *path_out_, struct fuse_file_info *ffi_out_, off_t off_out_, size_t len_, int flags_) { fuse_get_context()->private_data = fs_->user_data; if(fs_->op.copy_file_range == NULL) return -ENOSYS; return fs_->op.copy_file_range(path_in_, ffi_in_, off_in_, path_out_, ffi_out_, off_out_, len_, flags_); } int node_open(const struct node *node_) { return ((node_ != NULL) && (node_->open_count > 0)); } #ifndef CLOCK_MONOTONIC #define CLOCK_MONOTONIC CLOCK_REALTIME #endif static void curr_time(struct timespec *now) { static clockid_t clockid = CLOCK_MONOTONIC; int res = clock_gettime(clockid, now); if (res == -1 && errno == EINVAL) { clockid = CLOCK_REALTIME; res = clock_gettime(clockid, now); } if (res == -1) { perror("fuse: clock_gettime"); abort(); } } static void update_stat(struct node *node_, const struct stat *stnew_) { struct stat *stold; stold = &node_->stat_cache; if((node_->stat_cache_valid) && ((stold->st_mtim.tv_sec != stnew_->st_mtim.tv_sec) || (stold->st_mtim.tv_nsec != stnew_->st_mtim.tv_nsec) || (stold->st_size != stnew_->st_size))) node_->stat_cache_valid = 0; *stold = *stnew_; } static int lookup_path(struct fuse *f, fuse_ino_t nodeid, const char *name, const char *path, struct fuse_entry_param *e, struct fuse_file_info *fi) { int res; memset(e, 0, sizeof(struct fuse_entry_param)); if (fi) res = fuse_fs_fgetattr(f->fs, path, &e->attr, fi); else res = fuse_fs_getattr(f->fs, path, &e->attr); if (res == 0) { struct node *node; node = find_node(f, nodeid, name); if (node == NULL) res = -ENOMEM; else { e->ino = node->nodeid; e->generation = node->generation; e->entry_timeout = f->conf.entry_timeout; e->attr_timeout = f->conf.attr_timeout; pthread_mutex_lock(&f->lock); update_stat(node, &e->attr); pthread_mutex_unlock(&f->lock); set_stat(f, e->ino, &e->attr); if (f->conf.debug) fprintf(stderr, " NODEID: %lu\n", (unsigned long) e->ino); } } return res; } 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) { fuse_get_context()->private_data = fs->user_data; if (!fs->op.write_buf) conn->want &= ~FUSE_CAP_SPLICE_READ; if (!fs->op.lock) conn->want &= ~FUSE_CAP_POSIX_LOCKS; if (!fs->op.flock) conn->want &= ~FUSE_CAP_FLOCK_LOCKS; if (fs->op.init) fs->user_data = 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) { fuse_get_context()->private_data = fs->user_data; if (fs->op.destroy) fs->op.destroy(fs->user_data); 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, fuse_ino_t parent, const char *name) { struct fuse *f = req_fuse_prepare(req); struct fuse_entry_param e; char *path; int err; struct node *dot = NULL; if (name[0] == '.') { int len = strlen(name); if (len == 1 || (name[1] == '.' && len == 2)) { pthread_mutex_lock(&f->lock); if (len == 1) { if (f->conf.debug) fprintf(stderr, "LOOKUP-DOT\n"); dot = get_node_nocheck(f, parent); if (dot == NULL) { pthread_mutex_unlock(&f->lock); reply_entry(req, &e, -ESTALE); return; } dot->refctr++; } else { if (f->conf.debug) fprintf(stderr, "LOOKUP-DOTDOT\n"); parent = get_node(f, parent)->parent->nodeid; } pthread_mutex_unlock(&f->lock); name = NULL; } } err = get_path_name(f, parent, name, &path); if (!err) { struct fuse_intr_data d; if (f->conf.debug) fprintf(stderr, "LOOKUP %s\n", path); fuse_prepare_interrupt(f, req, &d); err = lookup_path(f, parent, name, path, &e, NULL); if (err == -ENOENT && f->conf.negative_timeout != 0.0) { e.ino = 0; e.entry_timeout = f->conf.negative_timeout; err = 0; } fuse_finish_interrupt(f, req, &d); free_path(f, parent, path); } if (dot) { pthread_mutex_lock(&f->lock); unref_node(f, dot); pthread_mutex_unlock(&f->lock); } reply_entry(req, &e, err); } static void do_forget(struct fuse *f, fuse_ino_t ino, uint64_t nlookup) { if (f->conf.debug) fprintf(stderr, "FORGET %llu/%llu\n", (unsigned long long)ino, (unsigned long long) nlookup); forget_node(f, ino, nlookup); } static void fuse_lib_forget(fuse_req_t req, fuse_ino_t ino, unsigned long nlookup) { do_forget(req_fuse(req), ino, nlookup); fuse_reply_none(req); } static void fuse_lib_forget_multi(fuse_req_t req, size_t count, struct fuse_forget_data *forgets) { struct fuse *f = req_fuse(req); size_t i; for (i = 0; i < count; i++) do_forget(f, forgets[i].ino, forgets[i].nlookup); fuse_reply_none(req); } static void fuse_lib_getattr(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi) { struct fuse *f = req_fuse_prepare(req); struct stat buf; char *path; int err; struct node *node; struct fuse_file_info 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 = (((fi == NULL) || (f->fs->op.fgetattr == NULL)) ? get_path(f,ino,&path) : get_path_nullok(f,ino,&path)); if (!err) { struct fuse_intr_data d; fuse_prepare_interrupt(f, req, &d); err = ((fi == NULL) ? fuse_fs_getattr(f->fs,path,&buf) : fuse_fs_fgetattr(f->fs,path,&buf,fi)); fuse_finish_interrupt(f, req, &d); 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, f->conf.attr_timeout); } else reply_err(req, err); } int fuse_fs_chmod(struct fuse_fs *fs, const char *path, mode_t mode) { fuse_get_context()->private_data = fs->user_data; if (fs->op.chmod) return fs->op.chmod(path, mode); else return -ENOSYS; } int fuse_fs_fchmod(struct fuse_fs *fs_, const struct fuse_file_info *ffi_, const mode_t mode_) { fuse_get_context()->private_data = fs_->user_data; if(fs_->op.fchmod) return fs_->op.fchmod(ffi_,mode_); return -ENOSYS; } static void fuse_lib_setattr(fuse_req_t req, fuse_ino_t ino, struct stat *attr, int valid, struct fuse_file_info *fi) { struct fuse *f = req_fuse_prepare(req); struct stat buf; char *path; int err; struct node *node; struct fuse_file_info 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 = ((fi == NULL) ? get_path(f,ino,&path) : get_path_nullok(f, ino, &path)); if (!err) { struct fuse_intr_data d; fuse_prepare_interrupt(f, req, &d); err = 0; if (!err && (valid & FUSE_SET_ATTR_MODE)) err = ((fi == NULL) ? fuse_fs_chmod(f->fs, path, attr->st_mode) : fuse_fs_fchmod(f->fs, fi, attr->st_mode)); if (!err && (valid & (FUSE_SET_ATTR_UID | FUSE_SET_ATTR_GID))) { uid_t uid = (valid & FUSE_SET_ATTR_UID) ? attr->st_uid : (uid_t) -1; gid_t gid = (valid & FUSE_SET_ATTR_GID) ? attr->st_gid : (gid_t) -1; err = ((fi == NULL) ? fuse_fs_chown(f->fs, path, uid, gid) : fuse_fs_fchown(f->fs, fi, uid, gid)); } if (!err && (valid & FUSE_SET_ATTR_SIZE)) { err = ((fi == NULL) ? fuse_fs_truncate(f->fs, path, attr->st_size) : fuse_fs_ftruncate(f->fs, path, attr->st_size, fi)); } #ifdef HAVE_UTIMENSAT if (!err && f->utime_omit_ok && (valid & (FUSE_SET_ATTR_ATIME | FUSE_SET_ATTR_MTIME))) { struct timespec tv[2]; tv[0].tv_sec = 0; tv[1].tv_sec = 0; tv[0].tv_nsec = UTIME_OMIT; tv[1].tv_nsec = UTIME_OMIT; if (valid & FUSE_SET_ATTR_ATIME_NOW) tv[0].tv_nsec = UTIME_NOW; else if (valid & FUSE_SET_ATTR_ATIME) tv[0] = attr->st_atim; if (valid & FUSE_SET_ATTR_MTIME_NOW) tv[1].tv_nsec = UTIME_NOW; else if (valid & FUSE_SET_ATTR_MTIME) tv[1] = attr->st_mtim; err = ((fi == NULL) ? fuse_fs_utimens(f->fs, path, tv) : fuse_fs_futimens(f->fs, fi, tv)); } else #endif if (!err && (valid & (FUSE_SET_ATTR_ATIME | FUSE_SET_ATTR_MTIME)) == (FUSE_SET_ATTR_ATIME | FUSE_SET_ATTR_MTIME)) { struct timespec tv[2]; tv[0].tv_sec = attr->st_atime; tv[0].tv_nsec = ST_ATIM_NSEC(attr); tv[1].tv_sec = attr->st_mtime; tv[1].tv_nsec = ST_MTIM_NSEC(attr); err = ((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) : fuse_fs_fgetattr(f->fs, path, &buf, fi)); fuse_finish_interrupt(f, req, &d); 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, f->conf.attr_timeout); } else { reply_err(req, err); } } static void fuse_lib_access(fuse_req_t req, fuse_ino_t ino, int mask) { struct fuse *f = req_fuse_prepare(req); char *path; int err; err = get_path(f, ino, &path); if (!err) { struct fuse_intr_data d; fuse_prepare_interrupt(f, req, &d); err = fuse_fs_access(f->fs, path, mask); fuse_finish_interrupt(f, req, &d); free_path(f, ino, path); } reply_err(req, err); } static void fuse_lib_readlink(fuse_req_t req, fuse_ino_t ino) { struct fuse *f = req_fuse_prepare(req); char linkname[PATH_MAX + 1]; char *path; int err; err = get_path(f, ino, &path); if (!err) { struct fuse_intr_data d; fuse_prepare_interrupt(f, req, &d); err = fuse_fs_readlink(f->fs, path, linkname, sizeof(linkname)); fuse_finish_interrupt(f, req, &d); free_path(f, ino, path); } if (!err) { linkname[PATH_MAX] = '\0'; fuse_reply_readlink(req, linkname); } else reply_err(req, err); } static void fuse_lib_mknod(fuse_req_t req, fuse_ino_t parent, const char *name, mode_t mode, dev_t rdev) { struct fuse *f = req_fuse_prepare(req); struct fuse_entry_param e; char *path; int err; err = get_path_name(f, parent, name, &path); if (!err) { struct fuse_intr_data d; fuse_prepare_interrupt(f, req, &d); err = -ENOSYS; if (S_ISREG(mode)) { struct fuse_file_info fi; memset(&fi, 0, sizeof(fi)); fi.flags = O_CREAT | O_EXCL | O_WRONLY; err = fuse_fs_create(f->fs, path, mode, &fi); if (!err) { err = lookup_path(f, parent, name, path, &e, &fi); fuse_fs_release(f->fs, path, &fi); } } if (err == -ENOSYS) { err = fuse_fs_mknod(f->fs, path, mode, rdev); if (!err) err = lookup_path(f, parent, name, path, &e, NULL); } fuse_finish_interrupt(f, req, &d); free_path(f, parent, path); } reply_entry(req, &e, err); } static void fuse_lib_mkdir(fuse_req_t req, fuse_ino_t parent, const char *name, mode_t mode) { struct fuse *f = req_fuse_prepare(req); struct fuse_entry_param e; char *path; int err; err = get_path_name(f, parent, name, &path); if (!err) { struct fuse_intr_data d; fuse_prepare_interrupt(f, req, &d); err = fuse_fs_mkdir(f->fs, path, mode); if (!err) err = lookup_path(f, parent, name, path, &e, NULL); fuse_finish_interrupt(f, req, &d); free_path(f, parent, path); } reply_entry(req, &e, err); } static void fuse_lib_unlink(fuse_req_t req, fuse_ino_t parent, const char *name) { int err; char *path; struct fuse *f; struct node *wnode; struct fuse_intr_data d; f = req_fuse_prepare(req); err = get_path_wrlock(f,parent,name,&path,&wnode); if(!err) { fuse_prepare_interrupt(f,req,&d); 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); fuse_finish_interrupt(f,req,&d); free_path_wrlock(f,parent,wnode,path); } reply_err(req,err); } static void fuse_lib_rmdir(fuse_req_t req, fuse_ino_t parent, const char *name) { struct fuse *f = req_fuse_prepare(req); struct node *wnode; char *path; int err; err = get_path_wrlock(f, parent, name, &path, &wnode); if (!err) { struct fuse_intr_data d; fuse_prepare_interrupt(f, req, &d); err = fuse_fs_rmdir(f->fs, path); fuse_finish_interrupt(f, req, &d); if (!err) remove_node(f, parent, name); free_path_wrlock(f, parent, wnode, path); } reply_err(req, err); } static void fuse_lib_symlink(fuse_req_t req, const char *linkname, fuse_ino_t parent, const char *name) { struct fuse *f = req_fuse_prepare(req); struct fuse_entry_param e; char *path; int err; err = get_path_name(f, parent, name, &path); if (!err) { struct fuse_intr_data d; fuse_prepare_interrupt(f, req, &d); err = fuse_fs_symlink(f->fs, linkname, path); if (!err) err = lookup_path(f, parent, name, path, &e, NULL); fuse_finish_interrupt(f, req, &d); free_path(f, parent, path); } reply_entry(req, &e, err); } static void fuse_lib_rename(fuse_req_t req, fuse_ino_t olddir, const char *oldname, fuse_ino_t newdir, const char *newname) { int err; struct fuse *f; char *oldpath; char *newpath; struct node *wnode1; struct node *wnode2; struct fuse_intr_data d; f = req_fuse_prepare(req); err = get_path2(f,olddir,oldname,newdir,newname, &oldpath,&newpath,&wnode1,&wnode2); if(!err) { fuse_prepare_interrupt(f,req,&d); 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); fuse_finish_interrupt(f,req,&d); free_path2(f,olddir,newdir,wnode1,wnode2,oldpath,newpath); } reply_err(req,err); } static void fuse_lib_link(fuse_req_t req, fuse_ino_t ino, fuse_ino_t newparent, const char *newname) { struct fuse *f = req_fuse_prepare(req); struct fuse_entry_param e; char *oldpath; char *newpath; int err; err = get_path2(f, ino, NULL, newparent, newname, &oldpath, &newpath, NULL, NULL); if (!err) { struct fuse_intr_data d; fuse_prepare_interrupt(f, req, &d); err = fuse_fs_link(f->fs, oldpath, newpath); if (!err) err = lookup_path(f, newparent, newname, newpath, &e, NULL); fuse_finish_interrupt(f, req, &d); free_path2(f, ino, newparent, NULL, NULL, oldpath, newpath); } reply_entry(req, &e, err); } static void fuse_do_release(struct fuse *f, fuse_ino_t ino, const char *path, struct fuse_file_info *fi) { struct node *node; uint64_t fh; int was_hidden; const char *compatpath; fh = 0; if (path != NULL || f->nullpath_ok || f->conf.nopath) compatpath = path; else compatpath = "-"; fuse_fs_release(f->fs, compatpath, 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, fuse_ino_t parent, const char *name, mode_t mode, struct fuse_file_info *fi) { int err; char *path; struct fuse *f; struct fuse_intr_data d; struct fuse_entry_param e; f = req_fuse_prepare(req); err = get_path_name(f, parent, name, &path); if(!err) { fuse_prepare_interrupt(f, req, &d); err = fuse_fs_create(f->fs, path, mode, fi); if(!err) { err = lookup_path(f, parent, name, path, &e, fi); if(err) { fuse_fs_release(f->fs, path, fi); } else if(!S_ISREG(e.attr.st_mode)) { err = -EIO; fuse_fs_release(f->fs, path, fi); forget_node(f, e.ino, 1); } } fuse_finish_interrupt(f, req, &d); } if(!err) { pthread_mutex_lock(&f->lock); get_node(f,e.ino)->open_count++; pthread_mutex_unlock(&f->lock); if (fuse_reply_create(req, &e, fi) == -ENOENT) { /* The open syscall was interrupted, so it must be cancelled */ fuse_do_release(f, e.ino, path, fi); forget_node(f, e.ino, 1); } } else { reply_err(req, err); } free_path(f, parent, path); } static double diff_timespec(const struct timespec *t1, const struct timespec *t2) { return (t1->tv_sec - t2->tv_sec) + ((double) t1->tv_nsec - (double) t2->tv_nsec) / 1000000000.0; } static void open_auto_cache(struct fuse *f, fuse_ino_t ino, const char *path, struct fuse_file_info *fi) { struct node *node; pthread_mutex_lock(&f->lock); node = get_node(f,ino); if(node->stat_cache_valid) { int err; struct stat stbuf; pthread_mutex_unlock(&f->lock); err = fuse_fs_fgetattr(f->fs,path,&stbuf,fi); pthread_mutex_lock(&f->lock); if(!err) update_stat(node,&stbuf); else node->stat_cache_valid = 0; } if(node->stat_cache_valid) fi->keep_cache = 1; node->stat_cache_valid = 1; pthread_mutex_unlock(&f->lock); } static void fuse_lib_open(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi) { int err; char *path; struct fuse *f; struct fuse_intr_data d; f = req_fuse_prepare(req); err = get_path(f, ino, &path); if(!err) { fuse_prepare_interrupt(f, req, &d); err = fuse_fs_open(f->fs, path, fi); if(!err) { if (fi && fi->auto_cache) open_auto_cache(f, ino, path, fi); } fuse_finish_interrupt(f, req, &d); } 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, path, fi); } else { reply_err(req, err); } free_path(f, ino, path); } static void fuse_lib_read(fuse_req_t req, fuse_ino_t ino, size_t size, off_t off, struct fuse_file_info *fi) { struct fuse *f = req_fuse_prepare(req); struct fuse_bufvec *buf = NULL; char *path; int res; res = get_path_nullok(f, ino, &path); if (res == 0) { struct fuse_intr_data d; fuse_prepare_interrupt(f, req, &d); res = fuse_fs_read_buf(f->fs, path, &buf, size, off, fi); fuse_finish_interrupt(f, req, &d); free_path(f, ino, path); } if (res == 0) fuse_reply_data(req, buf, FUSE_BUF_SPLICE_MOVE); else reply_err(req, res); fuse_free_buf(buf); } static void fuse_lib_write_buf(fuse_req_t req, fuse_ino_t ino, struct fuse_bufvec *buf, off_t off, struct fuse_file_info *fi) { struct fuse *f = req_fuse_prepare(req); char *path; int res; res = get_path_nullok(f, ino, &path); if (res == 0) { struct fuse_intr_data d; fuse_prepare_interrupt(f, req, &d); res = fuse_fs_write_buf(f->fs, path, buf, off, fi); fuse_finish_interrupt(f, req, &d); free_path(f, ino, path); } if (res >= 0) fuse_reply_write(req, res); else reply_err(req, res); } static void fuse_lib_fsync(fuse_req_t req, fuse_ino_t ino, int datasync, struct fuse_file_info *fi) { struct fuse *f = req_fuse_prepare(req); char *path; int err; err = get_path_nullok(f, ino, &path); if (!err) { struct fuse_intr_data d; fuse_prepare_interrupt(f, req, &d); err = fuse_fs_fsync(f->fs, path, datasync, fi); fuse_finish_interrupt(f, req, &d); free_path(f, ino, path); } reply_err(req, err); } static struct fuse_dh *get_dirhandle(const struct fuse_file_info *llfi, struct fuse_file_info *fi) { struct fuse_dh *dh = (struct fuse_dh *) (uintptr_t) llfi->fh; memset(fi, 0, sizeof(struct fuse_file_info)); fi->fh = dh->fh; return dh; } static void fuse_lib_opendir(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *llfi) { struct fuse *f = req_fuse_prepare(req); struct fuse_intr_data d; struct fuse_dh *dh; struct fuse_file_info fi; char *path; int err; dh = (struct fuse_dh *) malloc(sizeof(struct fuse_dh)); if (dh == NULL) { reply_err(req, -ENOMEM); return; } memset(dh, 0, sizeof(struct fuse_dh)); dh->fuse = f; dh->contents = NULL; dh->len = 0; dh->filled = 0; dh->nodeid = ino; 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) { fuse_prepare_interrupt(f, req, &d); err = fuse_fs_opendir(f->fs, path, &fi); fuse_finish_interrupt(f, req, &d); dh->fh = fi.fh; llfi->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, path, &fi); pthread_mutex_destroy(&dh->lock); free(dh); } } else { reply_err(req, err); pthread_mutex_destroy(&dh->lock); free(dh); } free_path(f, ino, path); } static int extend_contents(struct fuse_dh *dh, unsigned minsize) { if (minsize > dh->size) { char *newptr; unsigned newsize = dh->size; if (!newsize) newsize = 1024; while (newsize < minsize) { if (newsize >= 0x80000000) newsize = 0xffffffff; else newsize *= 2; } newptr = (char *) realloc(dh->contents, newsize); if (!newptr) { dh->error = -ENOMEM; return -1; } dh->contents = newptr; dh->size = newsize; } return 0; } static int fill_dir(void *dh_, const char *name, const struct stat *statp, off_t off) { struct fuse_dh *dh = (struct fuse_dh *) dh_; struct stat stbuf; size_t newlen; if (statp) stbuf = *statp; else { memset(&stbuf, 0, sizeof(stbuf)); stbuf.st_ino = FUSE_UNKNOWN_INO; } if (!dh->fuse->conf.use_ino) { stbuf.st_ino = FUSE_UNKNOWN_INO; if (dh->fuse->conf.readdir_ino) { struct node *node; pthread_mutex_lock(&dh->fuse->lock); node = lookup_node(dh->fuse, dh->nodeid, name); if (node) stbuf.st_ino = (ino_t) node->nodeid; pthread_mutex_unlock(&dh->fuse->lock); } } if (off) { if (extend_contents(dh, dh->needlen) == -1) return 1; dh->filled = 0; newlen = dh->len + fuse_add_direntry(dh->req, dh->contents + dh->len, dh->needlen - dh->len, name, &stbuf, off); if (newlen > dh->needlen) return 1; } else { newlen = dh->len + fuse_add_direntry(dh->req, NULL, 0, name, NULL, 0); if (extend_contents(dh, newlen) == -1) return 1; fuse_add_direntry(dh->req, dh->contents + dh->len, dh->size - dh->len, name, &stbuf, newlen); } dh->len = newlen; return 0; } static int readdir_fill(struct fuse *f, fuse_req_t req, fuse_ino_t ino, size_t size, off_t off, struct fuse_dh *dh, struct fuse_file_info *fi) { char *path; int err; if (f->fs->op.readdir) err = get_path_nullok(f, ino, &path); else err = get_path(f, ino, &path); if (!err) { struct fuse_intr_data d; dh->len = 0; dh->error = 0; dh->needlen = size; dh->filled = 1; dh->req = req; fuse_prepare_interrupt(f, req, &d); err = fuse_fs_readdir(f->fs, path, dh, fill_dir, off, fi); fuse_finish_interrupt(f, req, &d); dh->req = NULL; if (!err) err = dh->error; if (err) dh->filled = 0; free_path(f, ino, path); } return err; } static void fuse_lib_readdir(fuse_req_t req, fuse_ino_t ino, size_t size, off_t off, struct fuse_file_info *llfi) { struct fuse *f = req_fuse_prepare(req); struct fuse_file_info fi; struct fuse_dh *dh = get_dirhandle(llfi, &fi); pthread_mutex_lock(&dh->lock); /* According to SUS, directory contents need to be refreshed on rewinddir() */ if (!off) dh->filled = 0; if (!dh->filled) { int err = readdir_fill(f, req, ino, size, off, dh, &fi); if (err) { reply_err(req, err); goto out; } } if (dh->filled) { if (off < dh->len) { if (off + size > dh->len) size = dh->len - off; } else size = 0; } else { size = dh->len; off = 0; } fuse_reply_buf(req, dh->contents + off, size); out: pthread_mutex_unlock(&dh->lock); } static void fuse_lib_releasedir(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *llfi) { struct fuse *f = req_fuse_prepare(req); struct fuse_intr_data d; struct fuse_file_info fi; struct fuse_dh *dh = get_dirhandle(llfi, &fi); char *path; const char *compatpath; get_path_nullok(f, ino, &path); if (path != NULL || f->nullpath_ok || f->conf.nopath) compatpath = path; else compatpath = "-"; fuse_prepare_interrupt(f, req, &d); fuse_fs_releasedir(f->fs, compatpath, &fi); fuse_finish_interrupt(f, req, &d); free_path(f, ino, path); pthread_mutex_lock(&dh->lock); pthread_mutex_unlock(&dh->lock); pthread_mutex_destroy(&dh->lock); free(dh->contents); free(dh); reply_err(req, 0); } static void fuse_lib_fsyncdir(fuse_req_t req, fuse_ino_t ino, int datasync, struct fuse_file_info *llfi) { struct fuse *f = req_fuse_prepare(req); struct fuse_file_info fi; char *path; int err; get_dirhandle(llfi, &fi); err = get_path_nullok(f, ino, &path); if (!err) { struct fuse_intr_data d; fuse_prepare_interrupt(f, req, &d); err = fuse_fs_fsyncdir(f->fs, path, datasync, &fi); fuse_finish_interrupt(f, req, &d); free_path(f, ino, path); } reply_err(req, err); } static void fuse_lib_statfs(fuse_req_t req, fuse_ino_t ino) { struct fuse *f = req_fuse_prepare(req); struct statvfs buf; char *path = NULL; int err = 0; memset(&buf, 0, sizeof(buf)); if (ino) err = get_path(f, ino, &path); if (!err) { struct fuse_intr_data d; fuse_prepare_interrupt(f, req, &d); err = fuse_fs_statfs(f->fs, path ? path : "/", &buf); fuse_finish_interrupt(f, req, &d); free_path(f, ino, path); } if (!err) fuse_reply_statfs(req, &buf); else reply_err(req, err); } static void fuse_lib_setxattr(fuse_req_t req, fuse_ino_t ino, const char *name, const char *value, size_t size, int flags) { struct fuse *f = req_fuse_prepare(req); char *path; int err; err = get_path(f, ino, &path); if (!err) { struct fuse_intr_data d; fuse_prepare_interrupt(f, req, &d); err = fuse_fs_setxattr(f->fs, path, name, value, size, flags); fuse_finish_interrupt(f, req, &d); free_path(f, ino, path); } reply_err(req, err); } static int common_getxattr(struct fuse *f, fuse_req_t req, fuse_ino_t ino, const char *name, char *value, size_t size) { int err; char *path; err = get_path(f, ino, &path); if (!err) { struct fuse_intr_data d; fuse_prepare_interrupt(f, req, &d); err = fuse_fs_getxattr(f->fs, path, name, value, size); fuse_finish_interrupt(f, req, &d); free_path(f, ino, path); } return err; } static void fuse_lib_getxattr(fuse_req_t req, fuse_ino_t ino, const char *name, size_t size) { struct fuse *f = req_fuse_prepare(req); int res; if (size) { char *value = (char *) malloc(size); if (value == NULL) { reply_err(req, -ENOMEM); return; } res = common_getxattr(f, req, ino, name, value, size); if (res > 0) fuse_reply_buf(req, value, res); else reply_err(req, res); free(value); } else { res = common_getxattr(f, req, ino, name, NULL, 0); if (res >= 0) fuse_reply_xattr(req, res); else reply_err(req, res); } } static int common_listxattr(struct fuse *f, fuse_req_t req, fuse_ino_t ino, char *list, size_t size) { char *path; int err; err = get_path(f, ino, &path); if (!err) { struct fuse_intr_data d; fuse_prepare_interrupt(f, req, &d); err = fuse_fs_listxattr(f->fs, path, list, size); fuse_finish_interrupt(f, req, &d); free_path(f, ino, path); } return err; } static void fuse_lib_listxattr(fuse_req_t req, fuse_ino_t ino, size_t size) { struct fuse *f = req_fuse_prepare(req); int res; if (size) { char *list = (char *) malloc(size); if (list == NULL) { reply_err(req, -ENOMEM); return; } res = common_listxattr(f, req, ino, list, size); if (res > 0) fuse_reply_buf(req, list, res); else reply_err(req, res); free(list); } else { res = common_listxattr(f, req, ino, NULL, 0); if (res >= 0) fuse_reply_xattr(req, res); else reply_err(req, res); } } static void fuse_lib_removexattr(fuse_req_t req, fuse_ino_t ino, const char *name) { struct fuse *f = req_fuse_prepare(req); char *path; int err; err = get_path(f, ino, &path); if (!err) { struct fuse_intr_data d; fuse_prepare_interrupt(f, req, &d); err = fuse_fs_removexattr(f->fs, path, name); fuse_finish_interrupt(f, req, &d); free_path(f, ino, path); } reply_err(req, err); } static void fuse_lib_copy_file_range(fuse_req_t req_, fuse_ino_t nodeid_in_, off_t off_in_, struct fuse_file_info *ffi_in_, fuse_ino_t nodeid_out_, off_t off_out_, struct fuse_file_info *ffi_out_, size_t len_, int flags_) { int err; ssize_t rv; char *path_in; char *path_out; struct fuse *f; struct fuse_intr_data d; f = req_fuse_prepare(req_); err = get_path_nullok(f,nodeid_in_,&path_in); if(err) return reply_err(req_,err); err = get_path_nullok(f,nodeid_out_,&path_out); if(err) { free_path(f,nodeid_in_,path_in); return reply_err(req_,err); } fuse_prepare_interrupt(f,req_,&d); rv = fuse_fs_copy_file_range(f->fs, path_in, ffi_in_, off_in_, path_out, ffi_out_, off_out_, len_, flags_); fuse_finish_interrupt(f,req_,&d); if(rv >= 0) fuse_reply_write(req_,rv); else reply_err(req_,rv); free_path(f,nodeid_in_,path_in); free_path(f,nodeid_out_,path_out); } static struct lock *locks_conflict(struct node *node, const struct lock *lock) { struct lock *l; for (l = node->locks; l; l = l->next) if (l->owner != lock->owner && lock->start <= l->end && l->start <= lock->end && (l->type == F_WRLCK || lock->type == F_WRLCK)) break; return l; } static void delete_lock(struct lock **lockp) { struct lock *l = *lockp; *lockp = l->next; free(l); } static void insert_lock(struct lock **pos, struct lock *lock) { lock->next = *pos; *pos = lock; } static int locks_insert(struct node *node, struct lock *lock) { struct lock **lp; struct lock *newl1 = NULL; struct lock *newl2 = NULL; if (lock->type != F_UNLCK || lock->start != 0 || lock->end != OFFSET_MAX) { newl1 = malloc(sizeof(struct lock)); newl2 = malloc(sizeof(struct lock)); if (!newl1 || !newl2) { free(newl1); free(newl2); return -ENOLCK; } } for (lp = &node->locks; *lp;) { struct lock *l = *lp; if (l->owner != lock->owner) goto skip; if (lock->type == l->type) { if (l->end < lock->start - 1) goto skip; if (lock->end < l->start - 1) break; if (l->start <= lock->start && lock->end <= l->end) goto out; if (l->start < lock->start) lock->start = l->start; if (lock->end < l->end) lock->end = l->end; goto delete; } else { if (l->end < lock->start) goto skip; if (lock->end < l->start) break; if (lock->start <= l->start && l->end <= lock->end) goto delete; if (l->end <= lock->end) { l->end = lock->start - 1; goto skip; } if (lock->start <= l->start) { l->start = lock->end + 1; break; } *newl2 = *l; newl2->start = lock->end + 1; l->end = lock->start - 1; insert_lock(&l->next, newl2); newl2 = NULL; } skip: lp = &l->next; continue; delete: delete_lock(lp); } if (lock->type != F_UNLCK) { *newl1 = *lock; insert_lock(lp, newl1); newl1 = NULL; } out: free(newl1); free(newl2); return 0; } static void flock_to_lock(struct flock *flock, struct lock *lock) { memset(lock, 0, sizeof(struct lock)); lock->type = flock->l_type; lock->start = flock->l_start; lock->end = flock->l_len ? flock->l_start + flock->l_len - 1 : OFFSET_MAX; lock->pid = flock->l_pid; } static void lock_to_flock(struct lock *lock, struct flock *flock) { flock->l_type = lock->type; flock->l_start = lock->start; flock->l_len = (lock->end == OFFSET_MAX) ? 0 : lock->end - lock->start + 1; flock->l_pid = lock->pid; } static int fuse_flush_common(struct fuse *f, fuse_req_t req, fuse_ino_t ino, const char *path, struct fuse_file_info *fi) { struct fuse_intr_data d; struct flock lock; struct lock l; int err; int errlock; fuse_prepare_interrupt(f, req, &d); memset(&lock, 0, sizeof(lock)); lock.l_type = F_UNLCK; lock.l_whence = SEEK_SET; err = fuse_fs_flush(f->fs, path, fi); errlock = fuse_fs_lock(f->fs, path, fi, F_SETLK, &lock); fuse_finish_interrupt(f, req, &d); if (errlock != -ENOSYS) { flock_to_lock(&lock, &l); l.owner = fi->lock_owner; pthread_mutex_lock(&f->lock); locks_insert(get_node(f, ino), &l); pthread_mutex_unlock(&f->lock); /* if op.lock() is defined FLUSH is needed regardless of op.flush() */ if (err == -ENOSYS) err = 0; } return err; } static void fuse_lib_release(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi) { struct fuse *f = req_fuse_prepare(req); struct fuse_intr_data d; char *path; int err = 0; get_path_nullok(f, ino, &path); if (fi->flush) { err = fuse_flush_common(f, req, ino, path, fi); if (err == -ENOSYS) err = 0; } fuse_prepare_interrupt(f, req, &d); fuse_do_release(f, ino, path, fi); fuse_finish_interrupt(f, req, &d); free_path(f, ino, path); reply_err(req, err); } static void fuse_lib_flush(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi) { struct fuse *f = req_fuse_prepare(req); char *path; int err; get_path_nullok(f, ino, &path); err = fuse_flush_common(f, req, ino, path, fi); free_path(f, ino, path); reply_err(req, err); } static int fuse_lock_common(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi, struct flock *lock, int cmd) { struct fuse *f = req_fuse_prepare(req); char *path; int err; err = get_path_nullok(f, ino, &path); if (!err) { struct fuse_intr_data d; fuse_prepare_interrupt(f, req, &d); err = fuse_fs_lock(f->fs, path, fi, cmd, lock); fuse_finish_interrupt(f, req, &d); free_path(f, ino, path); } return err; } static void fuse_lib_getlk(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi, struct flock *lock) { int err; struct lock l; struct lock *conflict; struct fuse *f = req_fuse(req); flock_to_lock(lock, &l); l.owner = fi->lock_owner; pthread_mutex_lock(&f->lock); conflict = locks_conflict(get_node(f, ino), &l); if (conflict) lock_to_flock(conflict, lock); pthread_mutex_unlock(&f->lock); if (!conflict) err = fuse_lock_common(req, ino, fi, lock, F_GETLK); else err = 0; if (!err) fuse_reply_lock(req, lock); else reply_err(req, err); } static void fuse_lib_setlk(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi, struct flock *lock, int sleep) { int err = fuse_lock_common(req, ino, fi, lock, sleep ? F_SETLKW : F_SETLK); if (!err) { struct fuse *f = req_fuse(req); struct lock l; flock_to_lock(lock, &l); l.owner = fi->lock_owner; pthread_mutex_lock(&f->lock); locks_insert(get_node(f, ino), &l); pthread_mutex_unlock(&f->lock); } reply_err(req, err); } static void fuse_lib_flock(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi, int op) { struct fuse *f = req_fuse_prepare(req); char *path; int err; err = get_path_nullok(f, ino, &path); if (err == 0) { struct fuse_intr_data d; fuse_prepare_interrupt(f, req, &d); err = fuse_fs_flock(f->fs, path, fi, op); fuse_finish_interrupt(f, req, &d); free_path(f, ino, path); } reply_err(req, err); } static void fuse_lib_bmap(fuse_req_t req, fuse_ino_t ino, size_t blocksize, uint64_t idx) { struct fuse *f = req_fuse_prepare(req); struct fuse_intr_data d; char *path; int err; err = get_path(f, ino, &path); if (!err) { fuse_prepare_interrupt(f, req, &d); err = fuse_fs_bmap(f->fs, path, blocksize, &idx); fuse_finish_interrupt(f, req, &d); free_path(f, ino, path); } if (!err) fuse_reply_bmap(req, idx); else reply_err(req, err); } static void fuse_lib_ioctl(fuse_req_t req, fuse_ino_t ino, int cmd, void *arg, struct fuse_file_info *llfi, unsigned int flags, const void *in_buf, uint32_t in_bufsz, uint32_t out_bufsz_) { struct fuse *f = req_fuse_prepare(req); struct fuse_intr_data d; struct fuse_file_info fi; char *path, *out_buf = NULL; int err; 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 = get_path_nullok(f, ino, &path); if (err) goto err; fuse_prepare_interrupt(f, req, &d); err = fuse_fs_ioctl(f->fs, path, cmd, arg, &fi, flags, out_buf ?: (void *)in_buf, &out_bufsz); fuse_finish_interrupt(f, req, &d); free_path(f, ino, path); fuse_reply_ioctl(req, err, out_buf, out_bufsz); goto out; err: reply_err(req, err); out: free(out_buf); } static void fuse_lib_poll(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi, struct fuse_pollhandle *ph) { struct fuse *f = req_fuse_prepare(req); struct fuse_intr_data d; char *path; int err; unsigned revents = 0; err = get_path_nullok(f, ino, &path); if (!err) { fuse_prepare_interrupt(f, req, &d); err = fuse_fs_poll(f->fs, path, fi, ph, &revents); fuse_finish_interrupt(f, req, &d); free_path(f, ino, path); } if (!err) fuse_reply_poll(req, revents); else reply_err(req, err); } static void fuse_lib_fallocate(fuse_req_t req, fuse_ino_t ino, int mode, off_t offset, off_t length, struct fuse_file_info *fi) { struct fuse *f = req_fuse_prepare(req); struct fuse_intr_data d; char *path; int err; err = get_path_nullok(f, ino, &path); if (!err) { fuse_prepare_interrupt(f, req, &d); err = fuse_fs_fallocate(f->fs, path, mode, offset, length, fi); fuse_finish_interrupt(f, req, &d); free_path(f, ino, path); } reply_err(req, err); } static int clean_delay(struct fuse *f) { /* * This is calculating the delay between clean runs. To * reduce the number of cleans we are doing them 10 times * within the remember window. */ int min_sleep = 60; int max_sleep = 3600; int sleep_time = f->conf.remember / 10; if (sleep_time > max_sleep) return max_sleep; if (sleep_time < min_sleep) return min_sleep; return sleep_time; } int fuse_clean_cache(struct fuse *f) { struct node_lru *lnode; struct list_head *curr, *next; struct node *node; struct timespec now; pthread_mutex_lock(&f->lock); curr_time(&now); for (curr = f->lru_table.next; curr != &f->lru_table; curr = next) { double age; next = curr->next; lnode = list_entry(curr, struct node_lru, lru); node = &lnode->node; age = diff_timespec(&now, &lnode->forget_time); if (age <= f->conf.remember) break; assert(node->nlookup == 1); /* Don't forget active directories */ if (node->refctr > 1) continue; node->nlookup = 0; unhash_name(f, node); unref_node(f, node); } pthread_mutex_unlock(&f->lock); return clean_delay(f); } static struct fuse_lowlevel_ops fuse_path_ops = { .init = fuse_lib_init, .destroy = fuse_lib_destroy, .lookup = fuse_lib_lookup, .forget = fuse_lib_forget, .forget_multi = fuse_lib_forget_multi, .getattr = fuse_lib_getattr, .setattr = fuse_lib_setattr, .access = fuse_lib_access, .readlink = fuse_lib_readlink, .mknod = fuse_lib_mknod, .mkdir = fuse_lib_mkdir, .unlink = fuse_lib_unlink, .rmdir = fuse_lib_rmdir, .symlink = fuse_lib_symlink, .rename = fuse_lib_rename, .link = fuse_lib_link, .create = fuse_lib_create, .open = fuse_lib_open, .read = fuse_lib_read, .write_buf = fuse_lib_write_buf, .flush = fuse_lib_flush, .release = fuse_lib_release, .fsync = fuse_lib_fsync, .opendir = fuse_lib_opendir, .readdir = fuse_lib_readdir, .releasedir = fuse_lib_releasedir, .fsyncdir = fuse_lib_fsyncdir, .statfs = fuse_lib_statfs, .setxattr = fuse_lib_setxattr, .getxattr = fuse_lib_getxattr, .listxattr = fuse_lib_listxattr, .removexattr = fuse_lib_removexattr, .getlk = fuse_lib_getlk, .setlk = fuse_lib_setlk, .flock = fuse_lib_flock, .bmap = fuse_lib_bmap, .ioctl = fuse_lib_ioctl, .poll = fuse_lib_poll, .fallocate = fuse_lib_fallocate, .copy_file_range = fuse_lib_copy_file_range, }; int fuse_notify_poll(struct fuse_pollhandle *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; } static int fuse_session_loop_remember(struct fuse *f) { struct fuse_session *se = f->se; int res = 0; struct timespec now; time_t next_clean; struct fuse_chan *ch = fuse_session_next_chan(se, NULL); size_t bufsize = fuse_chan_bufsize(ch); char *buf = (char *) malloc(bufsize); struct pollfd fds = { .fd = fuse_chan_fd(ch), .events = POLLIN }; if (!buf) { fprintf(stderr, "fuse: failed to allocate read buffer\n"); return -1; } curr_time(&now); next_clean = now.tv_sec; while (!fuse_session_exited(se)) { struct fuse_chan *tmpch = ch; struct fuse_buf fbuf = { .mem = buf, .size = bufsize, }; unsigned timeout; curr_time(&now); if (now.tv_sec < next_clean) timeout = next_clean - now.tv_sec; else timeout = 0; res = poll(&fds, 1, timeout * 1000); if (res == -1) { if (errno == -EINTR) continue; else break; } else if (res > 0) { res = fuse_session_receive_buf(se, &fbuf, &tmpch); if (res == -EINTR) continue; if (res <= 0) break; fuse_session_process_buf(se, &fbuf, tmpch); } else { timeout = fuse_clean_cache(f); curr_time(&now); next_clean = now.tv_sec + timeout; } } free(buf); fuse_session_reset(se); return res < 0 ? -1 : 0; } int fuse_loop(struct fuse *f) { if (!f) return -1; if (lru_enabled(f)) return fuse_session_loop_remember(f); return fuse_session_loop(f->se); } int fuse_invalidate(struct fuse *f, const char *path) { (void) f; (void) path; return -EINVAL; } void fuse_exit(struct fuse *f) { fuse_session_exit(f->se); } struct fuse_context *fuse_get_context(void) { return &fuse_get_context_internal()->ctx; } /* * The size of fuse_context got extended, so need to be careful about * incompatibility (i.e. a new binary cannot work with an old * library). */ struct fuse_context *fuse_get_context_compat22(void); struct fuse_context *fuse_get_context_compat22(void) { return &fuse_get_context_internal()->ctx; } FUSE_SYMVER(".symver fuse_get_context_compat22,fuse_get_context@FUSE_2.2"); int fuse_getgroups(int size, gid_t list[]) { fuse_req_t req = fuse_get_context_internal()->req; return fuse_req_getgroups(req, size, list); } int fuse_interrupted(void) { return fuse_req_interrupted(fuse_get_context_internal()->req); } void fuse_set_getcontext_func(struct fuse_context *(*func)(void)) { (void) func; /* no-op */ } 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("use_ino", use_ino, 1), FUSE_LIB_OPT("readdir_ino", readdir_ino, 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("entry_timeout=%lf", entry_timeout, 0), FUSE_LIB_OPT("attr_timeout=%lf", attr_timeout, 0), FUSE_LIB_OPT("negative_timeout=%lf", negative_timeout, 0), FUSE_LIB_OPT("noforget", remember, -1), FUSE_LIB_OPT("remember=%u", remember, 0), FUSE_LIB_OPT("nopath", nopath, 1), FUSE_LIB_OPT("intr", intr, 1), FUSE_LIB_OPT("intr_signal=%d", intr_signal, 0), FUSE_LIB_OPT("threads=%d", threads, 0), FUSE_OPT_END }; static void fuse_lib_help(void) { fprintf(stderr, " -o use_ino let filesystem set inode numbers\n" " -o readdir_ino try to fill in d_ino in readdir\n" " -o umask=M set file permissions (octal)\n" " -o uid=N set file owner\n" " -o gid=N set file group\n" " -o entry_timeout=T cache timeout for names (1.0s)\n" " -o negative_timeout=T cache timeout for deleted names (0.0s)\n" " -o attr_timeout=T cache timeout for attributes (1.0s)\n" " -o noforget never forget cached inodes\n" " -o remember=T remember cached inodes for T seconds (0s)\n" " -o nopath don't supply path if not necessary\n" " -o intr allow requests to be interrupted\n" " -o intr_signal=NUM signal to send on interrupt (%i)\n" " -o threads=NUM number of worker threads. 0 = autodetect.\n" " Negative values autodetect then divide by\n" " absolute value. default = 0\n" "\n", FUSE_DEFAULT_INTR_SIGNAL); } 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); } static int fuse_init_intr_signal(int signum, int *installed) { struct sigaction old_sa; if (sigaction(signum, NULL, &old_sa) == -1) { perror("fuse: cannot get old signal handler"); return -1; } if (old_sa.sa_handler == SIG_DFL) { struct sigaction sa; memset(&sa, 0, sizeof(struct sigaction)); sa.sa_handler = fuse_intr_sighandler; sigemptyset(&sa.sa_mask); if (sigaction(signum, &sa, NULL) == -1) { perror("fuse: cannot set interrupt signal handler"); return -1; } *installed = 1; } return 0; } static void fuse_restore_intr_signal(int signum) { struct sigaction sa; memset(&sa, 0, sizeof(struct sigaction)); sa.sa_handler = SIG_DFL; sigaction(signum, &sa, NULL); } struct fuse_fs *fuse_fs_new(const struct fuse_operations *op, size_t op_size, void *user_data) { struct fuse_fs *fs; if (sizeof(struct fuse_operations) < op_size) { 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; } fs->user_data = user_data; if (op) memcpy(&fs->op, op, op_size); return fs; } static int node_table_init(struct node_table *t) { t->size = NODE_TABLE_MIN_SIZE; t->array = (struct node **) calloc(1, sizeof(struct node *) * t->size); if (t->array == NULL) { fprintf(stderr, "fuse: memory allocation failed\n"); return -1; } t->use = 0; t->split = 0; return 0; } static void *fuse_prune_nodes(void *fuse) { struct fuse *f = fuse; int sleep_time; while(1) { sleep_time = fuse_clean_cache(f); sleep(sleep_time); } return NULL; } int fuse_start_cleanup_thread(struct fuse *f) { if (lru_enabled(f)) return fuse_start_thread(&f->prune_thread, fuse_prune_nodes, f); return 0; } void fuse_stop_cleanup_thread(struct fuse *f) { if (lru_enabled(f)) { pthread_mutex_lock(&f->lock); pthread_cancel(f->prune_thread); pthread_mutex_unlock(&f->lock); pthread_join(f->prune_thread, NULL); } } struct fuse *fuse_new_common(struct fuse_chan *ch, struct fuse_args *args, const struct fuse_operations *op, size_t op_size, void *user_data, int compat) { 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, user_data); if (!fs) goto out_free; fs->compat = compat; f->fs = fs; f->nullpath_ok = fs->op.flag_nullpath_ok; f->conf.nopath = fs->op.flag_nopath; f->utime_omit_ok = fs->op.flag_utime_omit_ok; /* Oh f**k, this is ugly! */ if (!fs->op.lock) { llop.getlk = NULL; llop.setlk = NULL; } f->conf.entry_timeout = 1.0; f->conf.attr_timeout = 1.0; f->conf.negative_timeout = 0.0; f->conf.intr_signal = FUSE_DEFAULT_INTR_SIGNAL; f->pagesize = getpagesize(); init_list_head(&f->partial_slabs); init_list_head(&f->full_slabs); init_list_head(&f->lru_table); if (fuse_opt_parse(args, &f->conf, fuse_lib_opts, fuse_lib_opt_proc) == -1) goto out_free_fs; #if defined(__FreeBSD__) || defined(__NetBSD__) /* * In FreeBSD, we always use these settings as inode numbers * are needed to make getcwd(3) work. */ f->conf.readdir_ino = 1; #endif if (compat && compat <= 25) { if (fuse_sync_compat_args(args) == -1) goto out_free_fs; } 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); if (f->conf.debug) { fprintf(stderr, "nullpath_ok: %i\n", f->nullpath_ok); fprintf(stderr, "nopath: %i\n", f->conf.nopath); fprintf(stderr, "utime_omit_ok: %i\n", f->utime_omit_ok); } /* Trace topmost layer by default */ f->fs->debug = f->conf.debug; f->ctr = 0; f->generation = 0; if (node_table_init(&f->name_table) == -1) goto out_free_session; if (node_table_init(&f->id_table) == -1) goto out_free_name_table; fuse_mutex_init(&f->lock); root = alloc_node(f); if (root == NULL) { fprintf(stderr, "fuse: memory allocation failed\n"); goto out_free_id_table; } if (lru_enabled(f)) { struct node_lru *lnode = node_lru(root); init_list_head(&lnode->lru); } strcpy(root->inline_name, "/"); root->name = root->inline_name; if (f->conf.intr && fuse_init_intr_signal(f->conf.intr_signal, &f->intr_installed) == -1) goto out_free_root; root->parent = NULL; root->nodeid = FUSE_ROOT_ID; inc_nlookup(root); hash_id(f, root); return f; out_free_root: free(root); 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, void *user_data) { return fuse_new_common(ch, args, op, op_size, user_data, 0); } void fuse_destroy(struct fuse *f) { size_t i; if (f->conf.intr && f->intr_installed) fuse_restore_intr_signal(f->conf.intr_signal); if (f->fs) { 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--; } } assert(list_empty(&f->partial_slabs)); assert(list_empty(&f->full_slabs)); free(f->id_table.array); free(f->name_table.array); pthread_mutex_destroy(&f->lock); fuse_session_destroy(f->se); free(f); fuse_delete_context_key(); } static struct fuse * fuse_new_common_compat25(int fd, struct fuse_args *args, const struct fuse_operations *op, size_t op_size, int compat) { struct fuse *f = NULL; struct fuse_chan *ch = fuse_kern_chan_new(fd); if (ch) f = fuse_new_common(ch, args, op, op_size, NULL, compat); return f; } #if !defined(__FreeBSD__) && !defined(__NetBSD__) static struct fuse *fuse_new_common_compat(int fd, const char *opts, const struct fuse_operations *op, size_t op_size, int compat) { struct fuse *f; struct fuse_args args = FUSE_ARGS_INIT(0, NULL); if (fuse_opt_add_arg(&args, "") == -1) return NULL; if (opts && (fuse_opt_add_arg(&args, "-o") == -1 || fuse_opt_add_arg(&args, opts) == -1)) { fuse_opt_free_args(&args); return NULL; } f = fuse_new_common_compat25(fd, &args, op, op_size, compat); fuse_opt_free_args(&args); return f; } struct fuse *fuse_new_compat22(int fd, const char *opts, const struct fuse_operations_compat22 *op, size_t op_size) { return fuse_new_common_compat(fd, opts, (struct fuse_operations *) op, op_size, 22); } struct fuse *fuse_new_compat2(int fd, const char *opts, const struct fuse_operations_compat2 *op) { return fuse_new_common_compat(fd, opts, (struct fuse_operations *) op, sizeof(struct fuse_operations_compat2), 21); } struct fuse *fuse_new_compat1(int fd, int flags, const struct fuse_operations_compat1 *op) { const char *opts = NULL; if (flags & FUSE_DEBUG_COMPAT1) opts = "debug"; return fuse_new_common_compat(fd, opts, (struct fuse_operations *) op, sizeof(struct fuse_operations_compat1), 11); } FUSE_SYMVER(".symver fuse_exited,__fuse_exited@"); FUSE_SYMVER(".symver fuse_process_cmd,__fuse_process_cmd@"); FUSE_SYMVER(".symver fuse_read_cmd,__fuse_read_cmd@"); FUSE_SYMVER(".symver fuse_set_getcontext_func,__fuse_set_getcontext_func@"); FUSE_SYMVER(".symver fuse_new_compat2,fuse_new@"); FUSE_SYMVER(".symver fuse_new_compat22,fuse_new@FUSE_2.2"); #endif /* __FreeBSD__ || __NetBSD__ */ struct fuse *fuse_new_compat25(int fd, struct fuse_args *args, const struct fuse_operations_compat25 *op, size_t op_size) { return fuse_new_common_compat25(fd, args, (struct fuse_operations *) op, op_size, 25); } FUSE_SYMVER(".symver fuse_new_compat25,fuse_new@FUSE_2.5"); int fuse_config_num_threads(const struct fuse *fuse_) { return fuse_->conf.threads; } void fuse_config_set_entry_timeout(struct fuse *fuse_, const double entry_timeout_) { fuse_->conf.entry_timeout = entry_timeout_; } double fuse_config_get_entry_timeout(const struct fuse *fuse_) { return fuse_->conf.entry_timeout; } void fuse_config_set_negative_entry_timeout(struct fuse *fuse_, const double entry_timeout_) { fuse_->conf.negative_timeout = entry_timeout_; } double fuse_config_get_negative_entry_timeout(const struct fuse *fuse_) { return fuse_->conf.negative_timeout; } void fuse_config_set_attr_timeout(struct fuse *fuse_, const double attr_timeout_) { fuse_->conf.attr_timeout = attr_timeout_; } double fuse_config_get_attr_timeout(const struct fuse *fuse_) { return fuse_->conf.attr_timeout; } mergerfs-2.28.1/libfuse/lib/mount_generic.c0000664000175000017500000003474313471033317017246 0ustar bilebile/* 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_common_compat.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_ALLOW_ROOT, KEY_RO, KEY_HELP, KEY_VERSION, }; struct mount_opts { int allow_other; int allow_root; 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("allow_root", allow_root), 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("allow_root", KEY_ALLOW_ROOT), 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 allow_root allow access to root\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 "/" 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_ALLOW_ROOT: if (fuse_opt_add_opt(&mo->kernel_opts, "allow_other") == -1 || fuse_opt_add_arg(outargs, "-oallow_root") == -1) return -1; return 0; 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); } void fuse_unmount_compat22(const char *mountpoint) { fuse_kern_unmount(mountpoint, -1); } 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; } int fuse_mount_compat22(const char *mountpoint, const char *opts) { struct mount_opts mo; memset(&mo, 0, sizeof(mo)); mo.flags = MS_NOSUID | MS_NODEV; return fuse_mount_fusermount(mountpoint, &mo, opts, 0); } 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; if (mo.allow_other && mo.allow_root) { fprintf(stderr, "fuse: 'allow_other' and 'allow_root' options are mutually exclusive\n"); goto out; } 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; } FUSE_SYMVER(".symver fuse_mount_compat22,fuse_mount@FUSE_2.2"); FUSE_SYMVER(".symver fuse_unmount_compat22,fuse_unmount@FUSE_2.2"); mergerfs-2.28.1/libfuse/lib/Makefile.am0000664000175000017500000000166713471033317016277 0ustar bilebile## Process this file with automake to produce Makefile.in AUTOMAKE_OPTIONS = subdir-objects AM_CPPFLAGS = -I$(top_srcdir)/include -DFUSERMOUNT_DIR=\"$(bindir)\" \ -D_FILE_OFFSET_BITS=64 -D_REENTRANT -DFUSE_USE_VERSION=26 lib_LTLIBRARIES = libfuse.la libulockmgr.la if BSD mount_source = mount_bsd.c else mount_source = mount.c mount_util.c mount_util.h endif libfuse_la_SOURCES = \ fuse.c \ fuse_i.h \ fuse_kern_chan.c \ fuse_loop.c \ fuse_loop_mt.c \ fuse_lowlevel.c \ fuse_misc.h \ fuse_mt.c \ fuse_opt.c \ fuse_session.c \ fuse_signals.c \ buffer.c \ cuse_lowlevel.c \ helper.c \ $(mount_source) libfuse_la_LDFLAGS = -pthread @libfuse_libs@ -version-number 2:9:7 \ -Wl,--version-script,$(srcdir)/fuse_versionscript if NETBSD libfuse_la_LIBADD = -lperfuse -lpuffs endif libulockmgr_la_SOURCES = ulockmgr.c libulockmgr_la_LDFLAGS = -pthread -version-number 1:0:1 EXTRA_DIST = fuse_versionscript mergerfs-2.28.1/libfuse/lib/mount_util.h0000664000175000017500000000132313467103610016577 0ustar bilebile/* 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 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.28.1/libfuse/lib/mount_util.c0000664000175000017500000001726113467103610016602 0ustar bilebile/* 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.28.1/libfuse/lib/fuse_lowlevel.c0000664000175000017500000022125513475734103017264 0ustar bilebile/* 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 "config.h" #include "fuse_i.h" #include "fuse_kernel.h" #include "fuse_opt.h" #include "fuse_misc.h" #include "fuse_common_compat.h" #include "fuse_lowlevel_compat.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 { uint64_t kh; struct fuse_chan *ch; struct fuse_ll *f; }; static size_t pagesize; static __attribute__((constructor)) void fuse_ll_init_pagesize(void) { pagesize = getpagesize(); } 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; ST_ATIM_NSEC_SET(stbuf, attr->atimensec); ST_MTIM_NSEC_SET(stbuf, attr->mtimensec); } 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 list_init_req(struct fuse_req *req) { req->next = req; req->prev = req; } static void list_del_req(struct fuse_req *req) { struct fuse_req *prev = req->prev; struct fuse_req *next = req->next; prev->next = next; next->prev = prev; } static void list_add_req(struct fuse_req *req, struct fuse_req *next) { struct fuse_req *prev = next->prev; req->next = next; req->prev = prev; prev->next = req; next->prev = req; } static void destroy_req(fuse_req_t req) { pthread_mutex_destroy(&req->lock); free(req); } void fuse_free_req(fuse_req_t req) { int ctr; struct fuse_ll *f = req->f; pthread_mutex_lock(&f->lock); req->u.ni.func = NULL; req->u.ni.data = NULL; list_del_req(req); ctr = --req->ctr; pthread_mutex_unlock(&f->lock); if (!ctr) destroy_req(req); } static struct fuse_req *fuse_ll_alloc_req(struct fuse_ll *f) { struct fuse_req *req; req = (struct fuse_req *) calloc(1, sizeof(struct fuse_req)); if (req == NULL) { fprintf(stderr, "fuse: failed to allocate request\n"); } else { req->f = f; req->ctr = 1; list_init_req(req); fuse_mutex_init(&req->lock); } 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); if (f->debug) { if (out->unique == 0) { fprintf(stderr, "NOTIFY: code=%d length=%u\n", out->error, out->len); } else if (out->error) { fprintf(stderr, " unique: %llu, error: %i (%s), outsize: %i\n", (unsigned long long) out->unique, out->error, strerror(-out->error), out->len); } else { fprintf(stderr, " unique: %llu, success, outsize: %i\n", (unsigned long long) out->unique, out->len); } } 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); fuse_free_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); } int fuse_reply_iov(fuse_req_t req, const struct iovec *iov, int count) { int res; struct iovec *padded_iov; padded_iov = malloc((count + 1) * sizeof(struct iovec)); if (padded_iov == NULL) return fuse_reply_err(req, ENOMEM); memcpy(padded_iov + 1, iov, count * sizeof(struct iovec)); count++; res = send_reply_iov(req, 0, padded_iov, count); free(padded_iov); return res; } size_t fuse_dirent_size(size_t namelen) { return FUSE_DIRENT_ALIGN(FUSE_NAME_OFFSET + namelen); } char *fuse_add_dirent(char *buf, const char *name, const struct stat *stbuf, off_t off) { unsigned namelen = strlen(name); unsigned entlen = FUSE_NAME_OFFSET + namelen; unsigned entsize = fuse_dirent_size(namelen); unsigned padlen = entsize - entlen; struct fuse_dirent *dirent = (struct fuse_dirent *) buf; dirent->ino = stbuf->st_ino; dirent->off = off; dirent->namelen = namelen; dirent->type = (stbuf->st_mode & 0170000) >> 12; strncpy(dirent->name, name, namelen); if (padlen) memset(buf + entlen, 0, padlen); return buf + entsize; } size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct stat *stbuf, off_t off) { size_t entsize; (void) req; entsize = fuse_dirent_size(strlen(name)); if (entsize <= bufsize && buf) fuse_add_dirent(buf, name, stbuf, off); return entsize; } 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); fuse_free_req(req); } static unsigned long calc_timeout_sec(double t) { if (t > (double) ULONG_MAX) return ULONG_MAX; else if (t < 0.0) return 0; else return (unsigned long) t; } static unsigned int calc_timeout_nsec(double t) { double f = t - (double) calc_timeout_sec(t); if (f < 0.0) return 0; else if (f >= 0.999999999) return 999999999; else return (unsigned int) (f * 1.0e9); } static void fill_entry(struct fuse_entry_out *arg, const struct fuse_entry_param *e) { arg->nodeid = e->ino; arg->generation = e->generation; arg->entry_valid = calc_timeout_sec(e->entry_timeout); arg->entry_valid_nsec = calc_timeout_nsec(e->entry_timeout); arg->attr_valid = calc_timeout_sec(e->attr_timeout); arg->attr_valid_nsec = calc_timeout_nsec(e->attr_timeout); convert_stat(&e->attr, &arg->attr); } static void fill_open(struct fuse_open_out *arg, const struct fuse_file_info *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; 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); memset(&arg, 0, sizeof(arg)); fill_entry(&arg, e); return send_reply_ok(req, &arg, size); } int fuse_reply_create(fuse_req_t req, const struct fuse_entry_param *e, const struct fuse_file_info *f) { char buf[sizeof(struct fuse_entry_out) + sizeof(struct fuse_open_out)]; 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); memset(buf, 0, sizeof(buf)); fill_entry(earg, e); fill_open(oarg, f); return send_reply_ok(req, buf, entrysize + sizeof(struct fuse_open_out)); } int fuse_reply_attr(fuse_req_t req, const struct stat *attr, double attr_timeout) { struct fuse_attr_out arg; size_t size = req->f->conn.proto_minor < 9 ? FUSE_COMPAT_ATTR_OUT_SIZE : sizeof(arg); memset(&arg, 0, sizeof(arg)); arg.attr_valid = calc_timeout_sec(attr_timeout); arg.attr_valid_nsec = calc_timeout_nsec(attr_timeout); convert_stat(attr, &arg.attr); return send_reply_ok(req, &arg, size); } int fuse_reply_readlink(fuse_req_t req, const char *linkname) { return send_reply_ok(req, linkname, strlen(linkname)); } int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *f) { struct fuse_open_out arg; memset(&arg, 0, sizeof(arg)); fill_open(&arg, f); return send_reply_ok(req, &arg, sizeof(arg)); } int fuse_reply_write(fuse_req_t req, size_t count) { struct fuse_write_out arg; memset(&arg, 0, sizeof(arg)); arg.size = count; return send_reply_ok(req, &arg, sizeof(arg)); } 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) { struct fuse_bufvec mem_buf = FUSE_BUFVEC_INIT(len); void *mbuf; int res; /* Optimize common case */ if (buf->count == 1 && buf->idx == 0 && buf->off == 0 && !(buf->buf[0].flags & FUSE_BUF_IS_FD)) { /* FIXME: also avoid memory copy if there are multiple buffers but none of them contain an fd */ iov[iov_count].iov_base = buf->buf[0].mem; iov[iov_count].iov_len = len; iov_count++; return fuse_send_msg(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; if (f->debug) { fprintf(stderr, " unique: %llu, success, outsize: %i (splice)\n", (unsigned long long) out->unique, out->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) { fuse_free_req(req); return res; } else { return fuse_reply_err(req, res); } } int fuse_reply_statfs(fuse_req_t req, const struct statvfs *stbuf) { struct fuse_statfs_out arg; size_t size = req->f->conn.proto_minor < 4 ? FUSE_COMPAT_STATFS_SIZE : sizeof(arg); memset(&arg, 0, sizeof(arg)); convert_statfs(stbuf, &arg.st); return send_reply_ok(req, &arg, size); } int fuse_reply_xattr(fuse_req_t req, size_t count) { struct fuse_getxattr_out arg; memset(&arg, 0, sizeof(arg)); arg.size = count; return send_reply_ok(req, &arg, sizeof(arg)); } int fuse_reply_lock(fuse_req_t req, const struct flock *lock) { struct fuse_lk_out arg; memset(&arg, 0, sizeof(arg)); arg.lk.type = lock->l_type; if (lock->l_type != F_UNLCK) { arg.lk.start = lock->l_start; if (lock->l_len == 0) arg.lk.end = OFFSET_MAX; else arg.lk.end = lock->l_start + lock->l_len - 1; } arg.lk.pid = lock->l_pid; return send_reply_ok(req, &arg, sizeof(arg)); } int fuse_reply_bmap(fuse_req_t req, uint64_t idx) { struct fuse_bmap_out arg; memset(&arg, 0, sizeof(arg)); arg.block = idx; return send_reply_ok(req, &arg, sizeof(arg)); } static struct fuse_ioctl_iovec *fuse_ioctl_iovec_copy(const struct iovec *iov, size_t count) { struct fuse_ioctl_iovec *fiov; size_t i; fiov = malloc(sizeof(fiov[0]) * count); if (!fiov) return NULL; for (i = 0; i < count; i++) { fiov[i].base = (uintptr_t) iov[i].iov_base; fiov[i].len = iov[i].iov_len; } return fiov; } int fuse_reply_ioctl_retry(fuse_req_t req, const struct iovec *in_iov, size_t in_count, const struct iovec *out_iov, size_t out_count) { struct fuse_ioctl_out arg; struct fuse_ioctl_iovec *in_fiov = NULL; struct fuse_ioctl_iovec *out_fiov = NULL; struct iovec iov[4]; size_t count = 1; int res; memset(&arg, 0, sizeof(arg)); arg.flags |= FUSE_IOCTL_RETRY; arg.in_iovs = in_count; arg.out_iovs = out_count; iov[count].iov_base = &arg; iov[count].iov_len = sizeof(arg); count++; if (req->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; int res; padded_iov = malloc((count + 2) * sizeof(struct iovec)); if (padded_iov == NULL) return fuse_reply_err(req, ENOMEM); memset(&arg, 0, sizeof(arg)); arg.result = result; padded_iov[1].iov_base = &arg; padded_iov[1].iov_len = sizeof(arg); memcpy(&padded_iov[2], iov, count * sizeof(struct iovec)); res = send_reply_iov(req, 0, padded_iov, count + 2); free(padded_iov); return res; } int fuse_reply_poll(fuse_req_t req, unsigned revents) { struct fuse_poll_out arg; memset(&arg, 0, sizeof(arg)); arg.revents = revents; return send_reply_ok(req, &arg, sizeof(arg)); } static void do_lookup(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) { char *name = (char *) inarg; if (req->f->op.lookup) req->f->op.lookup(req, nodeid, name); else fuse_reply_err(req, ENOSYS); } static void do_forget(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) { struct fuse_forget_in *arg = (struct fuse_forget_in *) inarg; if (req->f->op.forget) req->f->op.forget(req, nodeid, arg->nlookup); else fuse_reply_none(req); } static void do_batch_forget(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) { struct fuse_batch_forget_in *arg = (void *) inarg; struct fuse_forget_one *param = (void *) PARAM(arg); unsigned int i; (void) nodeid; if (req->f->op.forget_multi) { req->f->op.forget_multi(req, arg->count, (struct fuse_forget_data *) param); } else if (req->f->op.forget) { for (i = 0; i < arg->count; i++) { struct fuse_forget_one *forget = ¶m[i]; struct fuse_req *dummy_req; dummy_req = fuse_ll_alloc_req(req->f); if (dummy_req == NULL) break; dummy_req->unique = req->unique; dummy_req->ctx = req->ctx; dummy_req->ch = NULL; req->f->op.forget(dummy_req, forget->nodeid, forget->nlookup); } fuse_reply_none(req); } else { fuse_reply_none(req); } } static void do_getattr(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) { struct fuse_file_info *fip = NULL; struct fuse_file_info fi; if (req->f->conn.proto_minor >= 9) { struct fuse_getattr_in *arg = (struct fuse_getattr_in *) inarg; if (arg->getattr_flags & FUSE_GETATTR_FH) { memset(&fi, 0, sizeof(fi)); fi.fh = arg->fh; fip = &fi; } } if (req->f->op.getattr) req->f->op.getattr(req, nodeid, fip); else fuse_reply_err(req, ENOSYS); } static void do_setattr(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) { struct fuse_setattr_in *arg = (struct fuse_setattr_in *) inarg; if (req->f->op.setattr) { struct fuse_file_info *fi = NULL; struct fuse_file_info fi_store; struct stat stbuf; memset(&stbuf, 0, sizeof(stbuf)); convert_attr(arg, &stbuf); if (arg->valid & FATTR_FH) { arg->valid &= ~FATTR_FH; memset(&fi_store, 0, sizeof(fi_store)); fi = &fi_store; fi->fh = arg->fh; } arg->valid &= FUSE_SET_ATTR_MODE | FUSE_SET_ATTR_UID | FUSE_SET_ATTR_GID | FUSE_SET_ATTR_SIZE | FUSE_SET_ATTR_ATIME | FUSE_SET_ATTR_MTIME | FUSE_SET_ATTR_ATIME_NOW | FUSE_SET_ATTR_MTIME_NOW; req->f->op.setattr(req, nodeid, &stbuf, arg->valid, fi); } else fuse_reply_err(req, ENOSYS); } static void do_access(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) { struct fuse_access_in *arg = (struct fuse_access_in *) inarg; if (req->f->op.access) req->f->op.access(req, nodeid, arg->mask); else fuse_reply_err(req, ENOSYS); } static void do_readlink(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) { (void) inarg; if (req->f->op.readlink) req->f->op.readlink(req, nodeid); else fuse_reply_err(req, ENOSYS); } static void do_mknod(fuse_req_t req, fuse_ino_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; if (req->f->op.mknod) req->f->op.mknod(req, nodeid, name, arg->mode, arg->rdev); else fuse_reply_err(req, ENOSYS); } static void do_mkdir(fuse_req_t req, fuse_ino_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; if (req->f->op.mkdir) req->f->op.mkdir(req, nodeid, PARAM(arg), arg->mode); else fuse_reply_err(req, ENOSYS); } static void do_unlink(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) { char *name = (char *) inarg; if (req->f->op.unlink) req->f->op.unlink(req, nodeid, name); else fuse_reply_err(req, ENOSYS); } static void do_rmdir(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) { char *name = (char *) inarg; if (req->f->op.rmdir) req->f->op.rmdir(req, nodeid, name); else fuse_reply_err(req, ENOSYS); } static void do_symlink(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) { char *name = (char *) inarg; char *linkname = ((char *) inarg) + strlen((char *) inarg) + 1; if (req->f->op.symlink) req->f->op.symlink(req, linkname, nodeid, name); else fuse_reply_err(req, ENOSYS); } static void do_rename(fuse_req_t req, fuse_ino_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; if (req->f->op.rename) req->f->op.rename(req, nodeid, oldname, arg->newdir, newname); else fuse_reply_err(req, ENOSYS); } static void do_link(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) { struct fuse_link_in *arg = (struct fuse_link_in *) inarg; if (req->f->op.link) req->f->op.link(req, arg->oldnodeid, nodeid, PARAM(arg)); else fuse_reply_err(req, ENOSYS); } static void do_create(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) { struct fuse_create_in *arg = (struct fuse_create_in *) inarg; if (req->f->op.create) { struct fuse_file_info fi; char *name = PARAM(arg); memset(&fi, 0, sizeof(fi)); 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); } else fuse_reply_err(req, ENOSYS); } static void do_open(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) { struct fuse_open_in *arg = (struct fuse_open_in *) inarg; struct fuse_file_info fi; memset(&fi, 0, sizeof(fi)); fi.flags = arg->flags; if (req->f->op.open) req->f->op.open(req, nodeid, &fi); else fuse_reply_open(req, &fi); } static void do_read(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) { struct fuse_read_in *arg = (struct fuse_read_in *) inarg; if (req->f->op.read) { struct fuse_file_info fi; memset(&fi, 0, sizeof(fi)); 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); } else fuse_reply_err(req, ENOSYS); } static void do_write(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) { struct fuse_write_in *arg = (struct fuse_write_in *) inarg; struct fuse_file_info fi; char *param; memset(&fi, 0, sizeof(fi)); 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); } if (req->f->op.write) req->f->op.write(req, nodeid, param, arg->size, arg->offset, &fi); else fuse_reply_err(req, ENOSYS); } static void do_write_buf(fuse_req_t req, fuse_ino_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, }; struct fuse_write_in *arg = (struct fuse_write_in *) inarg; struct fuse_file_info fi; memset(&fi, 0, sizeof(fi)); 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, fuse_ino_t nodeid, const void *inarg) { struct fuse_flush_in *arg = (struct fuse_flush_in *) inarg; struct fuse_file_info fi; memset(&fi, 0, sizeof(fi)); fi.fh = arg->fh; fi.flush = 1; if (req->f->conn.proto_minor >= 7) fi.lock_owner = arg->lock_owner; if (req->f->op.flush) req->f->op.flush(req, nodeid, &fi); else fuse_reply_err(req, ENOSYS); } static void do_release(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) { struct fuse_release_in *arg = (struct fuse_release_in *) inarg; struct fuse_file_info fi; memset(&fi, 0, sizeof(fi)); 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; } if (req->f->op.release) req->f->op.release(req, nodeid, &fi); else fuse_reply_err(req, 0); } static void do_fsync(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) { struct fuse_fsync_in *arg = (struct fuse_fsync_in *) inarg; struct fuse_file_info fi; memset(&fi, 0, sizeof(fi)); fi.fh = arg->fh; if (req->f->op.fsync) req->f->op.fsync(req, nodeid, arg->fsync_flags & 1, &fi); else fuse_reply_err(req, ENOSYS); } static void do_opendir(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) { struct fuse_open_in *arg = (struct fuse_open_in *) inarg; struct fuse_file_info fi; memset(&fi, 0, sizeof(fi)); fi.flags = arg->flags; if (req->f->op.opendir) req->f->op.opendir(req, nodeid, &fi); else fuse_reply_open(req, &fi); } static void do_readdir(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) { struct fuse_read_in *arg = (struct fuse_read_in *) inarg; struct fuse_file_info fi; memset(&fi, 0, sizeof(fi)); fi.fh = arg->fh; if (req->f->op.readdir) req->f->op.readdir(req, nodeid, arg->size, arg->offset, &fi); else fuse_reply_err(req, ENOSYS); } static void do_releasedir(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) { struct fuse_release_in *arg = (struct fuse_release_in *) inarg; struct fuse_file_info fi; memset(&fi, 0, sizeof(fi)); fi.flags = arg->flags; fi.fh = arg->fh; if (req->f->op.releasedir) req->f->op.releasedir(req, nodeid, &fi); else fuse_reply_err(req, 0); } static void do_fsyncdir(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) { struct fuse_fsync_in *arg = (struct fuse_fsync_in *) inarg; struct fuse_file_info fi; memset(&fi, 0, sizeof(fi)); fi.fh = arg->fh; if (req->f->op.fsyncdir) req->f->op.fsyncdir(req, nodeid, arg->fsync_flags & 1, &fi); else fuse_reply_err(req, ENOSYS); } static void do_statfs(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) { (void) nodeid; (void) inarg; if (req->f->op.statfs) req->f->op.statfs(req, nodeid); else { struct statvfs buf = { .f_namemax = 255, .f_bsize = 512, }; fuse_reply_statfs(req, &buf); } } static void do_setxattr(fuse_req_t req, fuse_ino_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; if (req->f->op.setxattr) req->f->op.setxattr(req, nodeid, name, value, arg->size, arg->flags); else fuse_reply_err(req, ENOSYS); } static void do_getxattr(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) { struct fuse_getxattr_in *arg = (struct fuse_getxattr_in *) inarg; if (req->f->op.getxattr) req->f->op.getxattr(req, nodeid, PARAM(arg), arg->size); else fuse_reply_err(req, ENOSYS); } static void do_listxattr(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) { struct fuse_getxattr_in *arg = (struct fuse_getxattr_in *) inarg; if (req->f->op.listxattr) req->f->op.listxattr(req, nodeid, arg->size); else fuse_reply_err(req, ENOSYS); } static void do_removexattr(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) { char *name = (char *) inarg; if (req->f->op.removexattr) req->f->op.removexattr(req, nodeid, name); else fuse_reply_err(req, ENOSYS); } 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, fuse_ino_t nodeid, const void *inarg) { struct fuse_lk_in *arg = (struct fuse_lk_in *) inarg; struct fuse_file_info fi; struct flock flock; memset(&fi, 0, sizeof(fi)); fi.fh = arg->fh; fi.lock_owner = arg->owner; convert_fuse_file_lock(&arg->lk, &flock); if (req->f->op.getlk) req->f->op.getlk(req, nodeid, &fi, &flock); else fuse_reply_err(req, ENOSYS); } static void do_setlk_common(fuse_req_t req, fuse_ino_t nodeid, const void *inarg, int sleep) { struct fuse_lk_in *arg = (struct fuse_lk_in *) inarg; struct fuse_file_info fi; struct flock flock; memset(&fi, 0, sizeof(fi)); fi.fh = arg->fh; fi.lock_owner = arg->owner; if (arg->lk_flags & FUSE_LK_FLOCK) { int op = 0; switch (arg->lk.type) { case F_RDLCK: op = LOCK_SH; break; case F_WRLCK: op = LOCK_EX; break; case F_UNLCK: op = LOCK_UN; break; } if (!sleep) op |= LOCK_NB; if (req->f->op.flock) req->f->op.flock(req, nodeid, &fi, op); else fuse_reply_err(req, ENOSYS); } else { convert_fuse_file_lock(&arg->lk, &flock); if (req->f->op.setlk) req->f->op.setlk(req, nodeid, &fi, &flock, sleep); else fuse_reply_err(req, ENOSYS); } } static void do_setlk(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) { do_setlk_common(req, nodeid, inarg, 0); } static void do_setlkw(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) { do_setlk_common(req, nodeid, inarg, 1); } static int find_interrupted(struct fuse_ll *f, struct fuse_req *req) { struct fuse_req *curr; for (curr = f->list.next; curr != &f->list; curr = curr->next) { if (curr->unique == req->u.i.unique) { fuse_interrupt_func_t func; void *data; curr->ctr++; pthread_mutex_unlock(&f->lock); /* Ugh, ugly locking */ pthread_mutex_lock(&curr->lock); pthread_mutex_lock(&f->lock); curr->interrupted = 1; func = curr->u.ni.func; data = curr->u.ni.data; pthread_mutex_unlock(&f->lock); if (func) func(curr, data); pthread_mutex_unlock(&curr->lock); pthread_mutex_lock(&f->lock); curr->ctr--; if (!curr->ctr) destroy_req(curr); return 1; } } for (curr = f->interrupts.next; curr != &f->interrupts; curr = curr->next) { if (curr->u.i.unique == req->u.i.unique) return 1; } return 0; } static void do_interrupt(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) { struct fuse_interrupt_in *arg = (struct fuse_interrupt_in *) inarg; struct fuse_ll *f = req->f; (void) nodeid; if (f->debug) fprintf(stderr, "INTERRUPT: %llu\n", (unsigned long long) arg->unique); req->u.i.unique = arg->unique; pthread_mutex_lock(&f->lock); if (find_interrupted(f, req)) destroy_req(req); else list_add_req(req, &f->interrupts); pthread_mutex_unlock(&f->lock); } static struct fuse_req *check_interrupt(struct fuse_ll *f, struct fuse_req *req) { struct fuse_req *curr; for (curr = f->interrupts.next; curr != &f->interrupts; curr = curr->next) { if (curr->u.i.unique == req->unique) { req->interrupted = 1; list_del_req(curr); free(curr); return NULL; } } curr = f->interrupts.next; if (curr != &f->interrupts) { list_del_req(curr); list_init_req(curr); return curr; } else return NULL; } static void do_bmap(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) { struct fuse_bmap_in *arg = (struct fuse_bmap_in *) inarg; if (req->f->op.bmap) req->f->op.bmap(req, nodeid, arg->blocksize, arg->block); else fuse_reply_err(req, ENOSYS); } static void do_ioctl(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) { struct fuse_ioctl_in *arg = (struct fuse_ioctl_in *) inarg; unsigned int flags = arg->flags; void *in_buf = arg->in_size ? PARAM(arg) : NULL; struct fuse_file_info fi; if (flags & FUSE_IOCTL_DIR && !(req->f->conn.want & FUSE_CAP_IOCTL_DIR)) { fuse_reply_err(req, ENOTTY); return; } memset(&fi, 0, sizeof(fi)); fi.fh = arg->fh; if (sizeof(void *) == 4 && req->f->conn.proto_minor >= 16 && !(flags & FUSE_IOCTL_32BIT)) { req->ioctl_64bit = 1; } if (req->f->op.ioctl) req->f->op.ioctl(req, nodeid, arg->cmd, (void *)(uintptr_t)arg->arg, &fi, flags, in_buf, arg->in_size, arg->out_size); else fuse_reply_err(req, ENOSYS); } void fuse_pollhandle_destroy(struct fuse_pollhandle *ph) { free(ph); } static void do_poll(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) { struct fuse_poll_in *arg = (struct fuse_poll_in *) inarg; struct fuse_file_info fi; memset(&fi, 0, sizeof(fi)); fi.fh = arg->fh; if (req->f->op.poll) { struct fuse_pollhandle *ph = NULL; if (arg->flags & FUSE_POLL_SCHEDULE_NOTIFY) { ph = malloc(sizeof(struct fuse_pollhandle)); if (ph == NULL) { fuse_reply_err(req, ENOMEM); return; } ph->kh = arg->kh; ph->ch = req->ch; ph->f = req->f; } req->f->op.poll(req, nodeid, &fi, ph); } else { fuse_reply_err(req, ENOSYS); } } static void do_fallocate(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) { struct fuse_fallocate_in *arg = (struct fuse_fallocate_in *) inarg; struct fuse_file_info fi; memset(&fi, 0, sizeof(fi)); fi.fh = arg->fh; if (req->f->op.fallocate) req->f->op.fallocate(req, nodeid, arg->mode, arg->offset, arg->length, &fi); else fuse_reply_err(req, ENOSYS); } static void do_init(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) { struct fuse_init_in *arg = (struct fuse_init_in *) inarg; struct fuse_init_out outarg; struct fuse_ll *f = req->f; size_t bufsize = fuse_chan_bufsize(req->ch); (void) nodeid; if (f->debug) { fprintf(stderr, "INIT: %u.%u\n", arg->major, arg->minor); if (arg->major == 7 && arg->minor >= 6) { fprintf(stderr, "flags=0x%08x\n", arg->flags); fprintf(stderr, "max_readahead=0x%08x\n", arg->max_readahead); } } f->conn.proto_major = arg->major; f->conn.proto_minor = arg->minor; f->conn.capable = 0; f->conn.want = 0; memset(&outarg, 0, sizeof(outarg)); 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; } 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; 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; } if (f->debug) { fprintf(stderr, " INIT: %u.%u\n", outarg.major, outarg.minor); fprintf(stderr, " flags=0x%08x\n", outarg.flags); fprintf(stderr, " max_readahead=0x%08x\n", outarg.max_readahead); fprintf(stderr, " max_write=0x%08x\n", outarg.max_write); fprintf(stderr, " max_background=%i\n", outarg.max_background); fprintf(stderr, " congestion_threshold=%i\n", outarg.congestion_threshold); fprintf(stderr, " max_pages=%d\n",outarg.max_pages); } 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); send_reply_ok(req, &outarg, outargsize); } static void do_destroy(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) { struct fuse_ll *f = req->f; (void) nodeid; (void) inarg; f->got_destroy = 1; if (f->op.destroy) 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, fuse_ino_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_, fuse_ino_t nodeid_in_, const void *arg_) { struct fuse_file_info ffi_in = {0}; struct fuse_file_info 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; if(req_->f->op.copy_file_range == NULL) fuse_reply_err(req_,ENOSYS); else 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(struct fuse_pollhandle *ph) { if (ph != NULL) { struct fuse_notify_poll_wakeup_out outarg; struct iovec iov[2]; outarg.kh = ph->kh; iov[1].iov_base = &outarg; iov[1].iov_len = sizeof(outarg); return send_notify_iov(ph->f, ph->ch, FUSE_NOTIFY_POLL, iov, 2); } else { return 0; } } int fuse_lowlevel_notify_inval_inode(struct fuse_chan *ch, fuse_ino_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, fuse_ino_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, fuse_ino_t parent, fuse_ino_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, fuse_ino_t ino, off_t offset, struct fuse_bufvec *bufv, enum fuse_buf_copy_flags flags) { struct fuse_out_header out; struct fuse_notify_store_out outarg; struct 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, fuse_ino_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, fuse_ino_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; } /* * The size of fuse_ctx got extended, so need to be careful about * incompatibility (i.e. a new binary cannot work with an old * library). */ const struct fuse_ctx *fuse_req_ctx_compat24(fuse_req_t req); const struct fuse_ctx *fuse_req_ctx_compat24(fuse_req_t req) { return fuse_req_ctx(req); } #ifndef __NetBSD__ FUSE_SYMVER(".symver fuse_req_ctx_compat24,fuse_req_ctx@FUSE_2.4"); #endif void fuse_req_interrupt_func(fuse_req_t req, fuse_interrupt_func_t func, void *data) { pthread_mutex_lock(&req->lock); pthread_mutex_lock(&req->f->lock); req->u.ni.func = func; req->u.ni.data = data; pthread_mutex_unlock(&req->f->lock); if (req->interrupted && func) func(req, data); pthread_mutex_unlock(&req->lock); } int fuse_req_interrupted(fuse_req_t req) { int interrupted; pthread_mutex_lock(&req->f->lock); interrupted = req->interrupted; pthread_mutex_unlock(&req->f->lock); return interrupted; } static struct { void (*func)(fuse_req_t, fuse_ino_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_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" }, [CUSE_INIT] = { cuse_lowlevel_init, "CUSE_INIT" }, }; #define FUSE_MAXOP (sizeof(fuse_ll_ops) / sizeof(fuse_ll_ops[0])) static const char *opname(enum fuse_opcode opcode) { if (opcode >= FUSE_MAXOP || !fuse_ll_ops[opcode].name) return "???"; else return fuse_ll_ops[opcode].name; } static 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; } if (f->debug) { fprintf(stderr, "unique: %llu, opcode: %s (%i), nodeid: %lu, insize: %zu, pid: %u\n", (unsigned long long) in->unique, opname((enum fuse_opcode) in->opcode), in->opcode, (unsigned long) in->nodeid, buf->size, in->pid); } 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 = f->cuse_data ? CUSE_INIT : FUSE_INIT; if (in->opcode != expected) goto reply_err; } else if (in->opcode == FUSE_INIT || in->opcode == CUSE_INIT) goto reply_err; err = EACCES; if (f->allow_root && in->uid != f->owner && in->uid != 0 && in->opcode != FUSE_INIT && in->opcode != FUSE_READ && in->opcode != FUSE_WRITE && in->opcode != FUSE_FSYNC && in->opcode != FUSE_RELEASE && in->opcode != FUSE_READDIR && in->opcode != FUSE_FSYNCDIR && in->opcode != FUSE_RELEASEDIR && in->opcode != FUSE_NOTIFY_REPLY) goto reply_err; err = ENOSYS; if (in->opcode >= FUSE_MAXOP || !fuse_ll_ops[in->opcode].func) goto reply_err; if (in->opcode != FUSE_INTERRUPT) { struct fuse_req *intr; pthread_mutex_lock(&f->lock); intr = check_interrupt(f, req); list_add_req(req, &f->list); pthread_mutex_unlock(&f->lock); if (intr) fuse_reply_err(intr, EAGAIN); } 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 && f->op.write_buf) do_write_buf(req, in->nodeid, inarg, buf); else if (in->opcode == FUSE_NOTIFY_REPLY) do_notify_reply(req, in->nodeid, inarg, buf); else fuse_ll_ops[in->opcode].func(req, in->nodeid, inarg); out_free: free(mbuf); return; reply_err: fuse_reply_err(req, err); clear_pipe: if (buf->flags & FUSE_BUF_IS_FD) fuse_ll_clear_pipe(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 }, { "allow_root", offsetof(struct fuse_ll, allow_root), 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->cuse_data); free(f); } 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_req(&f->list); list_init_req(&f->interrupts); 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; if (f->debug) fprintf(stderr, "FUSE library version: %s\n", PACKAGE_VERSION); 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); } #ifdef linux int fuse_req_getgroups(fuse_req_t req, int size, gid_t list[]) { char *buf; size_t bufsize = 1024; char path[128]; int ret; int fd; unsigned long pid = req->ctx.pid; char *s; sprintf(path, "/proc/%lu/task/%lu/status", pid, pid); retry: buf = malloc(bufsize); if (buf == NULL) return -ENOMEM; ret = -EIO; fd = open(path, O_RDONLY); if (fd == -1) goto out_free; ret = read(fd, buf, bufsize); close(fd); if (ret == -1) { ret = -EIO; goto out_free; } if (ret == bufsize) { free(buf); bufsize *= 4; goto retry; } ret = -EIO; s = strstr(buf, "\nGroups:"); if (s == NULL) goto out_free; s += 8; ret = 0; while (1) { char *end; unsigned long val = strtoul(s, &end, 0); if (end == s) break; s = end; if (ret < size) list[ret] = val; ret++; } out_free: free(buf); return ret; } #else /* linux */ /* * This is currently not implemented on other than Linux... */ int fuse_req_getgroups(fuse_req_t req, int size, gid_t list[]) { return -ENOSYS; } #endif #if !defined(__FreeBSD__) && !defined(__NetBSD__) static void fill_open_compat(struct fuse_open_out *arg, const struct fuse_file_info_compat *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; } static void convert_statfs_compat(const struct statfs *compatbuf, struct statvfs *buf) { buf->f_bsize = compatbuf->f_bsize; buf->f_blocks = compatbuf->f_blocks; buf->f_bfree = compatbuf->f_bfree; buf->f_bavail = compatbuf->f_bavail; buf->f_files = compatbuf->f_files; buf->f_ffree = compatbuf->f_ffree; buf->f_namemax = compatbuf->f_namelen; } int fuse_reply_open_compat(fuse_req_t req, const struct fuse_file_info_compat *f) { struct fuse_open_out arg; memset(&arg, 0, sizeof(arg)); fill_open_compat(&arg, f); return send_reply_ok(req, &arg, sizeof(arg)); } int fuse_reply_statfs_compat(fuse_req_t req, const struct statfs *stbuf) { struct statvfs newbuf; memset(&newbuf, 0, sizeof(newbuf)); convert_statfs_compat(stbuf, &newbuf); return fuse_reply_statfs(req, &newbuf); } struct fuse_session *fuse_lowlevel_new_compat(const char *opts, const struct fuse_lowlevel_ops_compat *op, size_t op_size, void *userdata) { struct fuse_session *se; struct fuse_args args = FUSE_ARGS_INIT(0, NULL); if (opts && (fuse_opt_add_arg(&args, "") == -1 || fuse_opt_add_arg(&args, "-o") == -1 || fuse_opt_add_arg(&args, opts) == -1)) { fuse_opt_free_args(&args); return NULL; } se = fuse_lowlevel_new(&args, (const struct fuse_lowlevel_ops *) op, op_size, userdata); fuse_opt_free_args(&args); return se; } struct fuse_ll_compat_conf { unsigned max_read; int set_max_read; }; static const struct fuse_opt fuse_ll_opts_compat[] = { { "max_read=", offsetof(struct fuse_ll_compat_conf, set_max_read), 1 }, { "max_read=%u", offsetof(struct fuse_ll_compat_conf, max_read), 0 }, FUSE_OPT_KEY("max_read=", FUSE_OPT_KEY_KEEP), FUSE_OPT_END }; int fuse_sync_compat_args(struct fuse_args *args) { struct fuse_ll_compat_conf conf; memset(&conf, 0, sizeof(conf)); if (fuse_opt_parse(args, &conf, fuse_ll_opts_compat, NULL) == -1) return -1; if (fuse_opt_insert_arg(args, 1, "-osync_read")) return -1; if (conf.set_max_read) { char tmpbuf[64]; sprintf(tmpbuf, "-omax_readahead=%u", conf.max_read); if (fuse_opt_insert_arg(args, 1, tmpbuf) == -1) return -1; } return 0; } FUSE_SYMVER(".symver fuse_reply_statfs_compat,fuse_reply_statfs@FUSE_2.4"); FUSE_SYMVER(".symver fuse_reply_open_compat,fuse_reply_open@FUSE_2.4"); FUSE_SYMVER(".symver fuse_lowlevel_new_compat,fuse_lowlevel_new@FUSE_2.4"); #else /* __FreeBSD__ || __NetBSD__ */ int fuse_sync_compat_args(struct fuse_args *args) { (void) args; return 0; } #endif /* __FreeBSD__ || __NetBSD__ */ struct fuse_session *fuse_lowlevel_new_compat25(struct fuse_args *args, const struct fuse_lowlevel_ops_compat25 *op, size_t op_size, void *userdata) { if (fuse_sync_compat_args(args) == -1) return NULL; return fuse_lowlevel_new_common(args, (const struct fuse_lowlevel_ops *) op, op_size, userdata); } FUSE_SYMVER(".symver fuse_lowlevel_new_compat25,fuse_lowlevel_new@FUSE_2.5"); mergerfs-2.28.1/libfuse/lib/fuse_loop_mt.c0000664000175000017500000001142013474105451017070 0ustar bilebile/* 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_misc.h" #include "fuse_kernel.h" #include "fuse_i.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)) { struct fuse_chan *ch = mt->prevch; struct fuse_buf fbuf = { .mem = w->buf, .size = w->bufsize, }; int res; 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 */ sigemptyset(&newset); sigaddset(&newset, SIGTERM); sigaddset(&newset, SIGINT); sigaddset(&newset, SIGHUP); sigaddset(&newset, SIGQUIT); pthread_sigmask(SIG_BLOCK, &newset, &oldset); res = pthread_create(thread_id, &attr, func, arg); pthread_sigmask(SIG_SETMASK, &oldset, NULL); pthread_attr_destroy(&attr); if (res != 0) { 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 = malloc(w->bufsize); 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.28.1/libfuse/lib/fuse_opt.c0000664000175000017500000002166613467103610016233 0ustar bilebile/* 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); } int fuse_opt_insert_arg_compat(struct fuse_args *args, int pos, const char *arg); int fuse_opt_insert_arg_compat(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; } /* This symbol version was mistakenly added to the version script */ FUSE_SYMVER(".symver fuse_opt_insert_arg_compat,fuse_opt_insert_arg@FUSE_2.5"); mergerfs-2.28.1/libfuse/lib/buffer.c0000664000175000017500000001446513467103610015657 0ustar bilebile/* 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) { void *dstmem = dst->mem + dst_off; void *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.28.1/libfuse/lib/fuse_signals.c0000664000175000017500000000330013467103610017052 0ustar bilebile/* 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.28.1/libfuse/lib/fuse_session.c0000664000175000017500000001117513467103610017106 0ustar bilebile/* 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_common_compat.h" #include "fuse_lowlevel_compat.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; int compat; }; 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, int compat) { 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; ch->compat = compat; 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, 0); } struct fuse_chan *fuse_chan_new_compat24(struct fuse_chan_ops_compat24 *op, int fd, size_t bufsize, void *data) { return fuse_chan_new_common((struct fuse_chan_ops *) op, fd, bufsize, data, 24); } 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; if (ch->compat) return ((struct fuse_chan_ops_compat24 *) &ch->op) ->receive(ch, buf, size); else 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); } #ifndef __FreeBSD__ FUSE_SYMVER(".symver fuse_chan_new_compat24,fuse_chan_new@FUSE_2.4"); #endif mergerfs-2.28.1/libfuse/lib/cuse_lowlevel.c0000664000175000017500000002121213474472141017250 0ustar bilebile/* CUSE: Character device in Userspace Copyright (C) 2008 SUSE Linux Products GmbH Copyright (C) 2008 Tejun Heo This program can be distributed under the terms of the GNU LGPLv2. See the file COPYING.LIB. */ #include "cuse_lowlevel.h" #include "fuse_kernel.h" #include "fuse_i.h" #include "fuse_opt.h" #include "fuse_misc.h" #include #include #include #include #include #include struct cuse_data { struct cuse_lowlevel_ops clop; unsigned max_read; unsigned dev_major; unsigned dev_minor; unsigned flags; unsigned dev_info_len; char dev_info[]; }; static struct cuse_lowlevel_ops *req_clop(fuse_req_t req) { return &req->f->cuse_data->clop; } static void cuse_fll_open(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi) { (void)ino; req_clop(req)->open(req, fi); } static void cuse_fll_read(fuse_req_t req, fuse_ino_t ino, size_t size, off_t off, struct fuse_file_info *fi) { (void)ino; req_clop(req)->read(req, size, off, fi); } static void cuse_fll_write(fuse_req_t req, fuse_ino_t ino, const char *buf, size_t size, off_t off, struct fuse_file_info *fi) { (void)ino; req_clop(req)->write(req, buf, size, off, fi); } static void cuse_fll_flush(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi) { (void)ino; req_clop(req)->flush(req, fi); } static void cuse_fll_release(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi) { (void)ino; req_clop(req)->release(req, fi); } static void cuse_fll_fsync(fuse_req_t req, fuse_ino_t ino, int datasync, struct fuse_file_info *fi) { (void)ino; req_clop(req)->fsync(req, datasync, fi); } static void cuse_fll_ioctl(fuse_req_t req, fuse_ino_t ino, int cmd, void *arg, struct fuse_file_info *fi, unsigned int flags, const void *in_buf, size_t in_bufsz, size_t out_bufsz) { (void)ino; req_clop(req)->ioctl(req, cmd, arg, fi, flags, in_buf, in_bufsz, out_bufsz); } static void cuse_fll_poll(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi, struct fuse_pollhandle *ph) { (void)ino; req_clop(req)->poll(req, fi, ph); } static size_t cuse_pack_info(int argc, const char **argv, char *buf) { size_t size = 0; int i; for (i = 0; i < argc; i++) { size_t len; len = strlen(argv[i]) + 1; size += len; if (buf) { memcpy(buf, argv[i], len); buf += len; } } return size; } static struct cuse_data *cuse_prep_data(const struct cuse_info *ci, const struct cuse_lowlevel_ops *clop) { struct cuse_data *cd; size_t dev_info_len; dev_info_len = cuse_pack_info(ci->dev_info_argc, ci->dev_info_argv, NULL); if (dev_info_len > CUSE_INIT_INFO_MAX) { fprintf(stderr, "cuse: dev_info (%zu) too large, limit=%u\n", dev_info_len, CUSE_INIT_INFO_MAX); return NULL; } cd = calloc(1, sizeof(*cd) + dev_info_len); if (!cd) { fprintf(stderr, "cuse: failed to allocate cuse_data\n"); return NULL; } memcpy(&cd->clop, clop, sizeof(cd->clop)); cd->max_read = 131072; cd->dev_major = ci->dev_major; cd->dev_minor = ci->dev_minor; cd->dev_info_len = dev_info_len; cd->flags = ci->flags; cuse_pack_info(ci->dev_info_argc, ci->dev_info_argv, cd->dev_info); return cd; } struct fuse_session *cuse_lowlevel_new(struct fuse_args *args, const struct cuse_info *ci, const struct cuse_lowlevel_ops *clop, void *userdata) { struct fuse_lowlevel_ops lop; struct cuse_data *cd; struct fuse_session *se; struct fuse_ll *ll; cd = cuse_prep_data(ci, clop); if (!cd) return NULL; memset(&lop, 0, sizeof(lop)); lop.init = clop->init; lop.destroy = clop->destroy; lop.open = clop->open ? cuse_fll_open : NULL; lop.read = clop->read ? cuse_fll_read : NULL; lop.write = clop->write ? cuse_fll_write : NULL; lop.flush = clop->flush ? cuse_fll_flush : NULL; lop.release = clop->release ? cuse_fll_release : NULL; lop.fsync = clop->fsync ? cuse_fll_fsync : NULL; lop.ioctl = clop->ioctl ? cuse_fll_ioctl : NULL; lop.poll = clop->poll ? cuse_fll_poll : NULL; se = fuse_lowlevel_new_common(args, &lop, sizeof(lop), userdata); if (!se) { free(cd); return NULL; } ll = se->data; ll->cuse_data = cd; return se; } static int cuse_reply_init(fuse_req_t req, struct cuse_init_out *arg, char *dev_info, unsigned dev_info_len) { struct iovec iov[3]; iov[1].iov_base = arg; iov[1].iov_len = sizeof(struct cuse_init_out); iov[2].iov_base = dev_info; iov[2].iov_len = dev_info_len; return fuse_send_reply_iov_nofree(req, 0, iov, 3); } void cuse_lowlevel_init(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) { struct fuse_init_in *arg = (struct fuse_init_in *) inarg; struct cuse_init_out outarg; struct fuse_ll *f = req->f; struct cuse_data *cd = f->cuse_data; size_t bufsize = fuse_chan_bufsize(req->ch); struct cuse_lowlevel_ops *clop = req_clop(req); (void) nodeid; if (f->debug) { fprintf(stderr, "CUSE_INIT: %u.%u\n", arg->major, arg->minor); fprintf(stderr, "flags=0x%08x\n", arg->flags); } f->conn.proto_major = arg->major; f->conn.proto_minor = arg->minor; f->conn.capable = 0; f->conn.want = 0; if (arg->major < 7) { fprintf(stderr, "cuse: unsupported protocol version: %u.%u\n", arg->major, arg->minor); fuse_reply_err(req, EPROTO); return; } if (bufsize < FUSE_MIN_READ_BUFFER) { fprintf(stderr, "cuse: 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); memset(&outarg, 0, sizeof(outarg)); outarg.major = FUSE_KERNEL_VERSION; outarg.minor = FUSE_KERNEL_MINOR_VERSION; outarg.flags = cd->flags; outarg.max_read = cd->max_read; outarg.max_write = f->conn.max_write; outarg.dev_major = cd->dev_major; outarg.dev_minor = cd->dev_minor; if (f->debug) { fprintf(stderr, " CUSE_INIT: %u.%u\n", outarg.major, outarg.minor); fprintf(stderr, " flags=0x%08x\n", outarg.flags); fprintf(stderr, " max_read=0x%08x\n", outarg.max_read); fprintf(stderr, " max_write=0x%08x\n", outarg.max_write); fprintf(stderr, " dev_major=%u\n", outarg.dev_major); fprintf(stderr, " dev_minor=%u\n", outarg.dev_minor); fprintf(stderr, " dev_info: %.*s\n", cd->dev_info_len, cd->dev_info); } cuse_reply_init(req, &outarg, cd->dev_info, cd->dev_info_len); if (clop->init_done) clop->init_done(f->userdata); fuse_free_req(req); } struct fuse_session *cuse_lowlevel_setup(int argc, char *argv[], const struct cuse_info *ci, const struct cuse_lowlevel_ops *clop, int *multithreaded, void *userdata) { const char *devname = "/dev/cuse"; static const struct fuse_opt kill_subtype_opts[] = { FUSE_OPT_KEY("subtype=", FUSE_OPT_KEY_DISCARD), FUSE_OPT_END }; struct fuse_args args = FUSE_ARGS_INIT(argc, argv); struct fuse_session *se; struct fuse_chan *ch; int fd; int foreground; int res; res = fuse_parse_cmdline(&args, NULL, multithreaded, &foreground); if (res == -1) goto err_args; res = fuse_opt_parse(&args, NULL, kill_subtype_opts, NULL); if (res == -1) goto err_args; /* * Make sure file descriptors 0, 1 and 2 are open, otherwise chaos * would ensue. */ do { fd = open("/dev/null", O_RDWR); if (fd > 2) close(fd); } while (fd >= 0 && fd <= 2); se = cuse_lowlevel_new(&args, ci, clop, userdata); fuse_opt_free_args(&args); if (se == NULL) goto err_args; fd = open(devname, O_RDWR); if (fd == -1) { if (errno == ENODEV || errno == ENOENT) fprintf(stderr, "cuse: device not found, try 'modprobe cuse' first\n"); else fprintf(stderr, "cuse: failed to open %s: %s\n", devname, strerror(errno)); goto err_se; } ch = fuse_kern_chan_new(fd); if (!ch) { close(fd); goto err_se; } fuse_session_add_chan(se, ch); res = fuse_set_signal_handlers(se); if (res == -1) goto err_se; res = fuse_daemonize(foreground); if (res == -1) goto err_sig; return se; err_sig: fuse_remove_signal_handlers(se); err_se: fuse_session_destroy(se); err_args: fuse_opt_free_args(&args); return NULL; } void cuse_lowlevel_teardown(struct fuse_session *se) { fuse_remove_signal_handlers(se); fuse_session_destroy(se); } int cuse_lowlevel_main(int argc, char *argv[], const struct cuse_info *ci, const struct cuse_lowlevel_ops *clop, void *userdata) { struct fuse_session *se; int multithreaded; int res; se = cuse_lowlevel_setup(argc, argv, ci, clop, &multithreaded, userdata); if (se == NULL) return 1; if (multithreaded) res = fuse_session_loop_mt(se, 0); else res = fuse_session_loop(se); cuse_lowlevel_teardown(se); if (res == -1) return 1; return 0; } mergerfs-2.28.1/libfuse/lib/helper.c0000664000175000017500000002713613475343677015706 0ustar bilebile/* 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 "fuse_common_compat.h" #include #include #include #include #include #include #include #include enum { KEY_HELP, KEY_HELP_NOHEADER, KEY_VERSION, }; struct helper_opts { int singlethread; 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("-d", foreground), FUSE_HELPER_OPT("debug", foreground), FUSE_HELPER_OPT("-f", foreground), FUSE_HELPER_OPT("-s", singlethread), 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" " -s disable multi-threaded 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 *multithreaded_, 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(multithreaded_) *multithreaded_ = !hopts.singlethread; 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_mount_compat25(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 *multithreaded, int *fd, void *user_data, int compat) { 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, multithreaded, &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, user_data, compat); 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, int *multithreaded, void *user_data) { return fuse_setup_common(argc, argv, op, op_size, mountpoint, multithreaded, NULL, user_data, 0); } 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, void *user_data, int compat) { struct fuse *fuse; char *mountpoint; int multithreaded; int res; fuse = fuse_setup_common(argc, argv, op, op_size, &mountpoint, &multithreaded, NULL, user_data, compat); if (fuse == NULL) return 1; if (multithreaded) res = fuse_loop_mt(fuse); else res = fuse_loop(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, void *user_data) { return fuse_main_common(argc, argv, op, op_size, user_data, 0); } #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; } #include "fuse_compat.h" #if !defined(__FreeBSD__) && !defined(__NetBSD__) struct fuse *fuse_setup_compat22(int argc, char *argv[], const struct fuse_operations_compat22 *op, size_t op_size, char **mountpoint, int *multithreaded, int *fd) { return fuse_setup_common(argc, argv, (struct fuse_operations *) op, op_size, mountpoint, multithreaded, fd, NULL, 22); } struct fuse *fuse_setup_compat2(int argc, char *argv[], const struct fuse_operations_compat2 *op, char **mountpoint, int *multithreaded, int *fd) { return fuse_setup_common(argc, argv, (struct fuse_operations *) op, sizeof(struct fuse_operations_compat2), mountpoint, multithreaded, fd, NULL, 21); } int fuse_main_real_compat22(int argc, char *argv[], const struct fuse_operations_compat22 *op, size_t op_size) { return fuse_main_common(argc, argv, (struct fuse_operations *) op, op_size, NULL, 22); } void fuse_main_compat1(int argc, char *argv[], const struct fuse_operations_compat1 *op) { fuse_main_common(argc, argv, (struct fuse_operations *) op, sizeof(struct fuse_operations_compat1), NULL, 11); } int fuse_main_compat2(int argc, char *argv[], const struct fuse_operations_compat2 *op) { return fuse_main_common(argc, argv, (struct fuse_operations *) op, sizeof(struct fuse_operations_compat2), NULL, 21); } int fuse_mount_compat1(const char *mountpoint, const char *args[]) { /* just ignore mount args for now */ (void) args; return fuse_mount_compat22(mountpoint, NULL); } FUSE_SYMVER(".symver fuse_setup_compat2,__fuse_setup@"); FUSE_SYMVER(".symver fuse_setup_compat22,fuse_setup@FUSE_2.2"); FUSE_SYMVER(".symver fuse_teardown,__fuse_teardown@"); FUSE_SYMVER(".symver fuse_main_compat2,fuse_main@"); FUSE_SYMVER(".symver fuse_main_real_compat22,fuse_main_real@FUSE_2.2"); #endif /* __FreeBSD__ || __NetBSD__ */ struct fuse *fuse_setup_compat25(int argc, char *argv[], const struct fuse_operations_compat25 *op, size_t op_size, char **mountpoint, int *multithreaded, int *fd) { return fuse_setup_common(argc, argv, (struct fuse_operations *) op, op_size, mountpoint, multithreaded, fd, NULL, 25); } int fuse_main_real_compat25(int argc, char *argv[], const struct fuse_operations_compat25 *op, size_t op_size) { return fuse_main_common(argc, argv, (struct fuse_operations *) op, op_size, NULL, 25); } void fuse_teardown_compat22(struct fuse *fuse, int fd, char *mountpoint) { (void) fd; fuse_teardown_common(fuse, mountpoint); } int fuse_mount_compat25(const char *mountpoint, struct fuse_args *args) { return fuse_kern_mount(mountpoint, args); } FUSE_SYMVER(".symver fuse_setup_compat25,fuse_setup@FUSE_2.5"); FUSE_SYMVER(".symver fuse_teardown_compat22,fuse_teardown@FUSE_2.2"); FUSE_SYMVER(".symver fuse_main_real_compat25,fuse_main_real@FUSE_2.5"); FUSE_SYMVER(".symver fuse_mount_compat25,fuse_mount@FUSE_2.5"); mergerfs-2.28.1/libfuse/lib/fuse_kern_chan.c0000664000175000017500000000424713475343677017377 0ustar bilebile/* 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.28.1/libfuse/lib/mount.c0000664000175000017500000000170413471033317015541 0ustar bilebile/* 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.28.1/libfuse/lib/fuse_versionscript0000664000175000017500000000740613467103610020116 0ustar bilebileFUSE_2.2 { global: fuse_destroy; fuse_exit; fuse_exited; fuse_invalidate; fuse_is_lib_option; fuse_loop; fuse_loop_mt; fuse_loop_mt_proc; fuse_main; fuse_main_compat1; fuse_main_compat2; fuse_mount_compat1; fuse_new_compat1; fuse_new_compat2; fuse_process_cmd; fuse_read_cmd; fuse_set_getcontext_func; fuse_setup_compat2; }; FUSE_2.4 { global: fuse_add_dirent; fuse_chan_bufsize; fuse_chan_data; fuse_chan_destroy; fuse_chan_fd; fuse_chan_receive; fuse_chan_send; fuse_chan_session; fuse_dirent_size; fuse_kern_chan_new; fuse_lowlevel_is_lib_option; fuse_reply_attr; fuse_reply_buf; fuse_reply_entry; fuse_reply_err; fuse_reply_none; fuse_reply_readlink; fuse_reply_write; fuse_reply_xattr; fuse_req_userdata; fuse_session_add_chan; fuse_session_destroy; fuse_session_exit; fuse_session_exited; fuse_session_loop; fuse_session_loop_mt; fuse_session_new; fuse_session_next_chan; fuse_session_process; fuse_session_reset; } FUSE_2.2; FUSE_2.5 { global: fuse_lowlevel_new_compat; fuse_main_real_compat22; fuse_mount_compat22; fuse_new_compat22; fuse_opt_parse; fuse_opt_add_opt; fuse_opt_add_arg; fuse_opt_free_args; fuse_opt_match; fuse_parse_cmdline; fuse_remove_signal_handlers; fuse_reply_create; fuse_reply_open; fuse_reply_open_compat; fuse_reply_statfs; fuse_reply_statfs_compat; fuse_setup_compat22; fuse_set_signal_handlers; } FUSE_2.4; FUSE_2.6 { global: fuse_add_direntry; fuse_chan_new; fuse_chan_new_compat24; fuse_chan_recv; fuse_daemonize; fuse_get_session; fuse_interrupted; fuse_lowlevel_new; fuse_lowlevel_new_compat25; fuse_main_real; fuse_main_real_compat25; fuse_mount; fuse_mount_compat25; fuse_new; fuse_new_compat25; fuse_opt_insert_arg; fuse_reply_lock; fuse_req_interrupt_func; fuse_req_interrupted; fuse_session_remove_chan; fuse_setup; fuse_setup_compat25; fuse_teardown; fuse_teardown_compat22; fuse_unmount; fuse_unmount_compat22; } FUSE_2.5; FUSE_2.7 { global: fuse_fs_access; fuse_fs_bmap; fuse_fs_chmod; fuse_fs_chown; fuse_fs_create; fuse_fs_destroy; fuse_fs_fgetattr; fuse_fs_flush; fuse_fs_fsync; fuse_fs_fsyncdir; fuse_fs_ftruncate; fuse_fs_getattr; fuse_fs_getxattr; fuse_fs_init; fuse_fs_link; fuse_fs_listxattr; fuse_fs_lock; fuse_fs_mkdir; fuse_fs_mknod; fuse_fs_new; fuse_fs_open; fuse_fs_opendir; fuse_fs_read; fuse_fs_readdir; fuse_fs_readlink; fuse_fs_release; fuse_fs_releasedir; fuse_fs_removexattr; fuse_fs_rename; fuse_fs_rmdir; fuse_fs_setxattr; fuse_fs_statfs; fuse_fs_symlink; fuse_fs_truncate; fuse_fs_unlink; fuse_fs_utimens; fuse_fs_write; fuse_register_module; fuse_reply_iov; fuse_version; } FUSE_2.6; FUSE_2.7.5 { global: fuse_reply_bmap; } FUSE_2.7; FUSE_2.8 { global: cuse_lowlevel_new; cuse_lowlevel_main; cuse_lowlevel_setup; cuse_lowlevel_teardown; fuse_fs_ioctl; fuse_fs_poll; fuse_get_context; fuse_getgroups; fuse_lowlevel_notify_inval_entry; fuse_lowlevel_notify_inval_inode; fuse_lowlevel_notify_poll; fuse_notify_poll; fuse_opt_add_opt_escaped; fuse_pollhandle_destroy; fuse_reply_ioctl; fuse_reply_ioctl_iov; fuse_reply_ioctl_retry; fuse_reply_poll; fuse_req_ctx; fuse_req_getgroups; fuse_session_data; } FUSE_2.7.5; FUSE_2.9 { global: fuse_buf_copy; fuse_buf_size; fuse_fs_read_buf; fuse_fs_write_buf; fuse_lowlevel_notify_retrieve; fuse_lowlevel_notify_store; fuse_reply_data; fuse_session_process_buf; fuse_session_receive_buf; fuse_start_cleanup_thread; fuse_stop_cleanup_thread; fuse_clean_cache; fuse_lowlevel_notify_delete; fuse_fs_flock; } FUSE_2.8; FUSE_2.9.1 { global: fuse_fs_fallocate; local: *; } FUSE_2.9; mergerfs-2.28.1/libfuse/lib/mount_bsd.c0000664000175000017500000002057413467103610016376 0ustar bilebile/* 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_ALLOW_ROOT, KEY_RO, KEY_HELP, KEY_VERSION, KEY_KERN }; struct mount_opts { int allow_other; int allow_root; 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 }, { "allow_root", offsetof(struct mount_opts, allow_root), 1 }, FUSE_OPT_KEY("allow_root", KEY_ALLOW_ROOT), 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) { fprintf(stderr, " -o allow_root allow access to root\n" ); 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_ALLOW_ROOT: if (fuse_opt_add_opt(&mo->kernel_opts, "allow_other") == -1 || fuse_opt_add_arg(outargs, "-oallow_root") == -1) return -1; return 0; 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; } void fuse_unmount_compat22(const char *mountpoint) { char dev[128]; char *ssc, *umount_cmd; FILE *sf; int rv; char seekscript[] = /* error message is annoying in help output */ "exec 2>/dev/null; " "/usr/bin/fstat " FUSE_DEV_TRUNK "* | " "/usr/bin/awk 'BEGIN{ getline; if (! ($3 == \"PID\" && $10 == \"NAME\")) exit 1; }; " " { if ($3 == %d) print $10; }' | " "/usr/bin/sort | " "/usr/bin/uniq | " "/usr/bin/awk '{ i += 1; if (i > 1){ exit 1; }; printf; }; END{ if (i == 0) exit 1; }'"; (void) mountpoint; /* * If we don't know the fd, we have to resort to the scripted * solution -- iterating over the fd-s is unpractical, as we * don't know how many of open files we have. (This could be * looked up in procfs -- however, that's optional on FBSD; or * read out from the kmem -- however, that's bound to * privileges (in fact, that's what happens when we call the * setgid kmem fstat(1) utility). */ if (asprintf(&ssc, seekscript, getpid()) == -1) return; errno = 0; sf = popen(ssc, "r"); free(ssc); if (! sf) return; fgets(dev, sizeof(dev), sf); rv = pclose(sf); if (rv) return; if (asprintf(&umount_cmd, "/sbin/umount %s", dev) == -1) return; system(umount_cmd); free(umount_cmd); } 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.allow_other && mo.allow_root) { fprintf(stderr, "fuse: 'allow_other' and 'allow_root' options are mutually exclusive\n"); goto out; } if (mo.ishelp) return 0; res = fuse_mount_core(mountpoint, mo.kernel_opts); out: free(mo.kernel_opts); return res; } FUSE_SYMVER(".symver fuse_unmount_compat22,fuse_unmount@FUSE_2.2"); mergerfs-2.28.1/libfuse/lib/ulockmgr.c0000664000175000017500000002244113467103610016222 0ustar bilebile/* libulockmgr: Userspace Lock Manager Library Copyright (C) 2006 Miklos Szeredi This program can be distributed under the terms of the GNU LGPLv2. See the file COPYING.LIB */ /* #define DEBUG 1 */ #include "ulockmgr.h" #include #include #include #include #include #include #include #include #include #include #include struct message { unsigned intr : 1; unsigned nofd : 1; pthread_t thr; int cmd; int fd; struct flock lock; int error; }; struct fd_store { struct fd_store *next; int fd; int inuse; }; struct owner { struct owner *next; struct owner *prev; struct fd_store *fds; void *id; size_t id_len; int cfd; }; static pthread_mutex_t ulockmgr_lock; static int ulockmgr_cfd = -1; static struct owner owner_list = { .next = &owner_list, .prev = &owner_list }; #define MAX_SEND_FDS 2 static void list_del_owner(struct owner *owner) { struct owner *prev = owner->prev; struct owner *next = owner->next; prev->next = next; next->prev = prev; } static void list_add_owner(struct owner *owner, struct owner *next) { struct owner *prev = next->prev; owner->next = next; owner->prev = prev; prev->next = owner; next->prev = owner; } /* * There's a bug in the linux kernel (< 2.6.22) recv() implementation * on AF_UNIX, SOCK_STREAM sockets, that could cause it to return * zero, even if data was available. Retrying the recv will return * the data in this case. */ static int do_recv(int sock, void *buf, size_t len, int flags) { int res = recv(sock, buf, len, flags); if (res == 0) res = recv(sock, buf, len, flags); return res; } static int ulockmgr_send_message(int sock, void *buf, size_t buflen, int *fdp, int numfds) { struct msghdr msg; struct cmsghdr *p_cmsg; struct iovec vec; size_t cmsgbuf[CMSG_SPACE(sizeof(int) * MAX_SEND_FDS) / sizeof(size_t)]; int res; assert(numfds <= MAX_SEND_FDS); 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(int) * numfds); memcpy(CMSG_DATA(p_cmsg), fdp, sizeof(int) * numfds); 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; vec.iov_base = buf; vec.iov_len = buflen; res = sendmsg(sock, &msg, MSG_NOSIGNAL); if (res == -1) { perror("libulockmgr: sendmsg"); return -1; } if ((size_t) res != buflen) { fprintf(stderr, "libulockmgr: sendmsg short\n"); return -1; } return 0; } static int ulockmgr_start_daemon(void) { int sv[2]; int res; char tmp[64]; res = socketpair(AF_UNIX, SOCK_STREAM, 0, sv); if (res == -1) { perror("libulockmgr: socketpair"); return -1; } snprintf(tmp, sizeof(tmp), "exec ulockmgr_server %i", sv[0]); res = system(tmp); close(sv[0]); if (res == -1 || !WIFEXITED(res) || WEXITSTATUS(res) != 0) { close(sv[1]); return -1; } ulockmgr_cfd = sv[1]; return 0; } static struct owner *ulockmgr_new_owner(const void *id, size_t id_len) { int sv[2]; int res; char c = 'm'; struct owner *o; if (ulockmgr_cfd == -1 && ulockmgr_start_daemon() == -1) return NULL; o = calloc(1, sizeof(struct owner) + id_len); if (!o) { fprintf(stderr, "libulockmgr: failed to allocate memory\n"); return NULL; } o->id = o + 1; o->id_len = id_len; res = socketpair(AF_UNIX, SOCK_STREAM, 0, sv); if (res == -1) { perror("libulockmgr: socketpair"); goto out_free; } res = ulockmgr_send_message(ulockmgr_cfd, &c, sizeof(c), &sv[0], 1); close(sv[0]); if (res == -1) { close(ulockmgr_cfd); ulockmgr_cfd = -1; goto out_close; } o->cfd = sv[1]; memcpy(o->id, id, id_len); list_add_owner(o, &owner_list); return o; out_close: close(sv[1]); out_free: free(o); return NULL; } static int ulockmgr_send_request(struct message *msg, const void *id, size_t id_len) { int sv[2]; int cfd; struct owner *o; struct fd_store *f = NULL; struct fd_store *newf = NULL; struct fd_store **fp; int fd = msg->fd; int cmd = msg->cmd; int res; int unlockall = (cmd == F_SETLK && msg->lock.l_type == F_UNLCK && msg->lock.l_start == 0 && msg->lock.l_len == 0); for (o = owner_list.next; o != &owner_list; o = o->next) if (o->id_len == id_len && memcmp(o->id, id, id_len) == 0) break; if (o == &owner_list) o = NULL; if (!o && cmd != F_GETLK && msg->lock.l_type != F_UNLCK) o = ulockmgr_new_owner(id, id_len); if (!o) { if (cmd == F_GETLK) { res = fcntl(msg->fd, F_GETLK, &msg->lock); return (res == -1) ? -errno : 0; } else if (msg->lock.l_type == F_UNLCK) return 0; else return -ENOLCK; } if (unlockall) msg->nofd = 1; else { for (fp = &o->fds; *fp; fp = &(*fp)->next) { f = *fp; if (f->fd == fd) { msg->nofd = 1; break; } } } if (!msg->nofd) { newf = f = calloc(1, sizeof(struct fd_store)); if (!f) { fprintf(stderr, "libulockmgr: failed to allocate memory\n"); return -ENOLCK; } } res = socketpair(AF_UNIX, SOCK_STREAM, 0, sv); if (res == -1) { perror("libulockmgr: socketpair"); free(newf); return -ENOLCK; } cfd = sv[1]; sv[1] = msg->fd; res = ulockmgr_send_message(o->cfd, msg, sizeof(struct message), sv, msg->nofd ? 1 : 2); close(sv[0]); if (res == -1) { free(newf); close(cfd); return -EIO; } if (newf) { newf->fd = msg->fd; newf->next = o->fds; o->fds = newf; } if (f) f->inuse++; res = do_recv(cfd, msg, sizeof(struct message), MSG_WAITALL); if (res == -1) { perror("libulockmgr: recv"); msg->error = EIO; } else if (res != sizeof(struct message)) { fprintf(stderr, "libulockmgr: recv short\n"); msg->error = EIO; } else if (cmd == F_SETLKW && msg->error == EAGAIN) { pthread_mutex_unlock(&ulockmgr_lock); while (1) { sigset_t old; sigset_t unblock; int errno_save; sigemptyset(&unblock); sigaddset(&unblock, SIGUSR1); pthread_sigmask(SIG_UNBLOCK, &unblock, &old); res = do_recv(cfd, msg, sizeof(struct message), MSG_WAITALL); errno_save = errno; pthread_sigmask(SIG_SETMASK, &old, NULL); if (res == sizeof(struct message)) break; else if (res >= 0) { fprintf(stderr, "libulockmgr: recv short\n"); msg->error = EIO; break; } else if (errno_save != EINTR) { errno = errno_save; perror("libulockmgr: recv"); msg->error = EIO; break; } msg->intr = 1; res = send(o->cfd, msg, sizeof(struct message), MSG_NOSIGNAL); if (res == -1) { perror("libulockmgr: send"); msg->error = EIO; break; } if (res != sizeof(struct message)) { fprintf(stderr, "libulockmgr: send short\n"); msg->error = EIO; break; } } pthread_mutex_lock(&ulockmgr_lock); } if (f) f->inuse--; close(cfd); if (unlockall) { for (fp = &o->fds; *fp;) { f = *fp; if (f->fd == fd && !f->inuse) { *fp = f->next; free(f); } else fp = &f->next; } if (!o->fds) { list_del_owner(o); close(o->cfd); free(o); } /* Force OK on unlock-all, since it _will_ succeed once the owner is deleted */ msg->error = 0; } return -msg->error; } #ifdef DEBUG static uint32_t owner_hash(const unsigned char *id, size_t id_len) { uint32_t h = 0; size_t i; for (i = 0; i < id_len; i++) h = ((h << 8) | (h >> 24)) ^ id[i]; return h; } #endif static int ulockmgr_canonicalize(int fd, struct flock *lock) { off_t offset; if (lock->l_whence == SEEK_CUR) { offset = lseek(fd, 0, SEEK_CUR); if (offset == (off_t) -1) return -errno; } else if (lock->l_whence == SEEK_END) { struct stat stbuf; int res = fstat(fd, &stbuf); if (res == -1) return -errno; offset = stbuf.st_size; } else offset = 0; lock->l_whence = SEEK_SET; lock->l_start += offset; if (lock->l_start < 0) return -EINVAL; if (lock->l_len < 0) { lock->l_start += lock->l_len; if (lock->l_start < 0) return -EINVAL; lock->l_len = -lock->l_len; } if (lock->l_len && lock->l_start + lock->l_len - 1 < 0) return -EINVAL; return 0; } int ulockmgr_op(int fd, int cmd, struct flock *lock, const void *owner, size_t owner_len) { int err; struct message msg; sigset_t old; sigset_t block; if (cmd != F_GETLK && cmd != F_SETLK && cmd != F_SETLKW) return -EINVAL; if (lock->l_type != F_RDLCK && lock->l_type != F_WRLCK && lock->l_type != F_UNLCK) return -EINVAL; if (lock->l_whence != SEEK_SET && lock->l_whence != SEEK_CUR && lock->l_whence != SEEK_END) return -EINVAL; #ifdef DEBUG fprintf(stderr, "libulockmgr: %i %i %i %lli %lli own: 0x%08x\n", cmd, lock->l_type, lock->l_whence, lock->l_start, lock->l_len, owner_hash(owner, owner_len)); #endif /* Unlock should never block anyway */ if (cmd == F_SETLKW && lock->l_type == F_UNLCK) cmd = F_SETLK; memset(&msg, 0, sizeof(struct message)); msg.cmd = cmd; msg.fd = fd; msg.lock = *lock; err = ulockmgr_canonicalize(fd, &msg.lock); if (err) return err; sigemptyset(&block); sigaddset(&block, SIGUSR1); pthread_sigmask(SIG_BLOCK, &block, &old); pthread_mutex_lock(&ulockmgr_lock); err = ulockmgr_send_request(&msg, owner, owner_len); pthread_mutex_unlock(&ulockmgr_lock); pthread_sigmask(SIG_SETMASK, &old, NULL); if (!err && cmd == F_GETLK) { if (msg.lock.l_type == F_UNLCK) lock->l_type = F_UNLCK; else *lock = msg.lock; } return err; } mergerfs-2.28.1/libfuse/COPYING0000664000175000017500000004325413467103610014525 0ustar bilebile 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.28.1/libfuse/AUTHORS0000664000175000017500000000351013467103610014531 0ustar bilebileCurrent 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.28.1/libfuse/README.md0000664000175000017500000000775613467103610014760 0ustar bilebilelibfuse ======= 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.28.1/libfuse/NEWS0000664000175000017500000001702413467103610014165 0ustar bilebileWhat 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.28.1/libfuse/Makefile0000664000175000017500000000321413475343677015144 0ustar bilebileVERSION = "2.9.7-mergerfs_2.28.0" OPTS = -O2 ifeq ($(DEBUG),1) DEBUG_FLAGS := -g else DEBUG_FLAGS := endif DESTDIR = "/" BINDIR = "/bin" SBINDIR = "/sbin" SRC = \ lib/buffer.c \ lib/cuse_lowlevel.c \ lib/fuse.c \ lib/fuse_kern_chan.c \ lib/fuse_loop.c \ lib/fuse_loop_mt.c \ lib/fuse_lowlevel.c \ lib/fuse_mt.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 += \ $(OPTS) \ $(DEBUG_FLAGS) \ -Wall \ -pipe \ '-D_FILE_OFFSET_BITS=64' \ '-D_REENTRANT' \ '-DFUSE_USE_VERSION=29' \ '-DFUSERMOUNT_DIR="/usr/local/bin"' \ '-DPACKAGE_VERSION=$(VERSION)' \ -Iinclude \ -Ibuild \ -MMD LDFLAGS += \ -lrt \ -pthread all: build/libfuse.a 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) build/mergerfs-mount: build/config.h util/fusermount.c lib/mount_util.c $(CC) $(CFLAGS) -Ilib -o mergerfs-mount util/fusermount.c lib/mount_util.c build/mount.mergerfs: build/libfuse.a util/mount.fuse.c $(CC) $(CFLAGS) -o mount.mergerfs util/mount.fuse.c build/libfuse.a $(LDFLAGS) build/%.o: lib/%.c $(CC) $(CFLAGS) -c $< -o $@ clean: rm -rf build distclean: clean install: build/mergerfs-mount build/mount.mergerfs install -D build/mergerfs-mount "$(DESTDIR)$(BINDIR)/mergerfs-mount" chown root:root "$(DESTDIR)$(BINDIR)/mergerfs-mount" chmod u+s "$(DESTDIR)$(BINDIR)/mergerfs-mount" install -D build/mount.mergerfs "$(DESTDIR)$(SBINDIR)/mount.mergerfs" .PHONY: objects -include $(DEPS) mergerfs-2.28.1/libfuse/README.NFS0000664000175000017500000000254613467103610014776 0ustar bilebileNFS 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.28.1/README.md0000664000175000017500000022635013475343677013342 0ustar bilebile% mergerfs(1) mergerfs user manual % Antonio SJ Musumeci % 2019-06-03 # 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 * Runs in userspace (FUSE) * Configurable behaviors / file placement * Support for extended attributes (xattrs) * Support for file attributes (chattr) * Runtime configurable (via xattrs) * Safe to run as root * Opportunistic credential caching * Works with heterogeneous filesystem types * Handling of writes to full drives (transparently move file to drive with capacity) * Handles pool of read-only and read/write drives * Can turn read-only files into symlinks to underlying file * Hard link copy-on-write / CoW * supports POSIX ACLs # 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) behavior 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. # OPTIONS ### mount options * **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. Understands 'K', 'M', and 'G' to represent kilobyte, megabyte, and gigabyte respectively. (default: 4G) * **moveonenospc=BOOL**: When enabled if a **write** fails with **ENOSPC** or **EDQUOT** a scan of all drives will be done looking for the drive with the most free space which is at least the size of the file plus the amount which failed to write. An attempt to move the file to that drive will occur (keeping all metadata possible) and if successful the original is unlinked and the write retried. (default: false) * **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. * **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=INT**: 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) * **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=INT**: 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.CATEGORY=POLICY**: Sets policy of all FUSE functions in the provided category. Example: **category.create=mfs** * **cache.open=INT**: 'open' policy cache timeout in seconds. (default: 0) * **cache.statfs=INT**: 'statfs' cache timeout in seconds. (default: 0) * **cache.attr=INT**: File attribute cache timeout in seconds. (default: 1) * **cache.entry=INT**: File name lookup cache timeout in seconds. (default: 1) * **cache.negative_entry=INT**: 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.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 = [0,MAX_INT] * SIZE = 'NNM'; NN = INT, M = 'K' | 'M' | 'G' | 'T' * STR = string * FUNC = FUSE function * CATEGORY = FUSE function category * POLICY = mergerfs function policy ### branches The 'branches' (formerly 'srcmounts') 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. 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. 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.** Each branch can have a suffix of `=RW` (read / write), `=RO` (read-only), or `=NC` (no create). These suffixes work with globs as well and will apply to each path found. `RW` is the default behavior and those paths will be eligible for all policy categories. `RO` will exclude those paths from `create` and `action` policies (just as a filesystem being mounted `ro` would). `NC` will exclude those paths from `create` policies (you can't create but you can change / delete). ``` # 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 accessable from related tools use **/etc/fstab**. ``` # /mnt/disk*:/mnt/cdrom /media/drives fuse.mergerfs allow_other,use_ino 0 0 ``` **NOTE:** the globbing is done at mount or xattr update time (see below). 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. ### 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 appoximately 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 `nullrw` section for benchmarking examples. ### symlinkify Due to the levels of indirection introduced by mergerfs and the underlying technology FUSE there can be varying levels of performance degredation. 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. 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 boundries. 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. Example: ``` $ dd if=/dev/zero of=/path/to/mergerfs/mount/benchmark ibs=1M obs=512 count=1024 iflag=dsync,nocache oflag=dsync,nocache conv=fdatasync status=progress 1024+0 records in 2097152+0 records out 1073741824 bytes (1.1 GB, 1.0 GiB) copied, 15.4067 s, 69.7 MB/s $ dd if=/dev/zero of=/path/to/mergerfs/mount/benchmark ibs=1M obs=1M count=1024 iflag=dsync,nocache oflag=dsync,nocache conv=fdatasync status=progress 1024+0 records in 1024+0 records out 1073741824 bytes (1.1 GB, 1.0 GiB) copied, 0.219585 s, 4.9 GB/s $ dd if=/path/to/mergerfs/mount/benchmark of=/dev/null bs=512 count=102400 iflag=dsync,nocache oflag=dsync,nocache conv=fdatasync status=progress 102400+0 records in 102400+0 records out 52428800 bytes (52 MB, 50 MiB) copied, 0.757991 s, 69.2 MB/s $ dd if=/path/to/mergerfs/mount/benchmark of=/dev/null bs=1M count=1024 iflag=dsync,nocache oflag=dsync,nocache conv=fdatasync status=progress 1024+0 records in 1024+0 records out 1073741824 bytes (1.1 GB, 1.0 GiB) copied, 0.18405 s, 5.8 GB/s ``` It's important to test with different `obs` (output block size) values since the relative overhead is greater with smaller values. As you can see above the size of a read or write can massively impact theoretical performance. If an application performs much worse through mergerfs it could very well be that it doesn't optimally size its read and write requests. In such cases contact the mergerfs author so it can be investigated. ### 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. `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 will be more efficient than `noattr` but will cause mergerfs' runtime control via the hidden file to stop working. # FUNCTIONS / POLICIES / CATEGORIES The POSIX filesystem API is made up of a number of functions. **creat**, **stat**, **chown**, etc. 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 what file or directory is chosen when performing that behavior. 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. Some functions, listed in the category `N/A` below, can not be assigned the normal policies. All functions which work on file handles use the handle which was acquired by `open` or `create`. `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. 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. 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 sub mounts 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. #### Function / 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 (to confirm a directory exists across all source mounts) **getattr** will be used. #### 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. #### Filters 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 policy type defines the sorting but filtering is mostly uniform as described below. * No **search** policies filter. * All **action** policies will filter out branches which are mounted **read-only** or tagged as **RO (read-only)**. * All **create** policies will filter out branches which are mounted **read-only**, tagged **RO (read-only)** or **NC (no create)**, or has available space less than `minfreespace`. If all branches are filtered an error will be returned. Typically **EROFS** or **ENOSPC** depending on the reasons. #### Policy descriptions | Policy | Description | |------------------|------------------------------------------------------------| | all | Search category: same as **epall**. Action category: same as **epall**. Create category: for **mkdir**, **mknod**, and **symlink** it will apply to all branches. **create** works like **ff**. | | epall (existing path, all) | Search category: same as **epff** (but more expensive because it doesn't stop after finding a valid branch). Action category: apply to all found. Create category: 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. | | eprand (existing path, random) | Calls **epall** and then randomizes. | | erofs | Exclusively return **-1** with **errno** set to **EROFS** (read-only filesystem). | | ff (first found) | Search category: same as **epff**. Action category: same as **epff**. Create category: 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 category: same as **eplfs**. Action category: same as **eplfs**. Create category: Pick the drive with the least available free space. | | lus (least used space) | Search category: same as **eplus**. Action category: same as **eplus**. Create category: Pick the drive with the least used space. | | mfs (most free space) | Search category: same as **epmfs**. Action category: same as **epmfs**. Create category: Pick the drive with the most available free space. | | newest | Pick the file / directory with the largest mtime. | | rand (random) | Calls **all** and then randomizes. | #### 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. #### unlink In FUSE there is an opaque "file handle" which is created by `open`, `create`, or `opendir`, passed to the kernel, and then is passed back to the FUSE userland application by the kernel. Unfortunately, the FUSE kernel driver does not always send the file handle when it theoretically could/should. This complicates certain behaviors / workflows particularly in the high level API. As a result mergerfs is currently doing a few hacky things. libfuse2 and libfuse3, when using the high level API, will rename names to `.fuse_hiddenXXXXXX` if the file is open when unlinked or renamed over. It does this so the file is still available when a request referencing the now missing file is made. This file however keeps a `rmdir` from succeeding and can be picked up by software reading directories. The change mergerfs has done is that if a file is open when an unlink or rename happens it will open the file and keep it open till closed by all those who opened it prior. When a request comes in referencing that file and it doesn't include a file handle it will instead use the file handle created at unlink/rename time. This won't result in technically proper behavior but close enough for many usecases. The plan is to rewrite mergerfs to use the low level API so these invasive libfuse changes are no longer necessary. #### 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` 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). 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 complient 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 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 it's 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 it's 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. # BUILDING **NOTE:** Prebuilt packages can be found at: https://github.com/trapexit/mergerfs/releases 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_version_arch.deb ``` #### 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 ``` # 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 `xattr -l /mountpoint/.mergerfs` to see all supported keys. Some are informational and therefore read-only. `setxattr` will return EINVAL on read-only keys. ##### Values ##### Same as the command line. ###### user.mergerfs.branches ###### **NOTE:** formerly `user.mergerfs.srcmounts` but said key is still supported. 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-existant files). #### 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 configuarable at runtime via xattr `user.mergerfs.cache.readdir`. #### writeback caching writeback caching is a technique for improving write speeds by batching writes at a faster device and then bulk writing to the slower device. With FUSE the kernel will wait for a number of writes to be made and then send it to the filesystem as one request. mergerfs currently uses a modified and vendored libfuse 2.9.7 which does not support writeback caching. Adding said feature should not be difficult but benchmarking needs to be done to see if what effect it will have. #### 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. Perhaps setting `minfreespace` to the size of the largest cache drive. 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". ``` #!/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. ``` #!/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 -axqHAXWES --preallocate --remove-source-files "${CACHE}/./${FILE}" "${BACKING}/" done ``` # TIPS / NOTES * **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 by the same user otherwise strange permission issues may arise. * https://github.com/trapexit/backup-and-recovery-howtos : A set of guides / howtos on creating a data storage system, backing it up, maintaining it, and recovering from failure. * If you don't see some directories and files you expect in a merged point or policies seem to skip drives be sure the user has permission to all the underlying directories. Use `mergerfs.fsck` to audit the drive for out of sync permissions. * Do **not** use `cache.files=off` or `direct_io` if you expect applications (such as rtorrent) to [mmap](http://linux.die.net/man/2/mmap) files. Shared mmap is not currently supported in FUSE w/ `direct_io` enabled. Enabling `dropcacheonclose` is recommended when `cache.files=partial|full|auto-full` or `direct_io=false`. * Since POSIX functions give only a singular error or success its difficult to determine the proper behavior when applying the function to multiple targets. **mergerfs** will return an error only if all attempts of an action fail. Any success will lead to a success returned. This means however that some odd situations may arise. * [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** its 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 #### 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 undertand 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` or `direct_io=true`. See the section on page caching. #### NFS clients returning ESTALE / Stale file handle Be sure to use `noforget` and `use_ino` arguments. #### NFS clients don't work Some NFS clients appear to fail when a mergerfs mount is exported. Kodi in particular seems to have issues. Try enabling the `use_ino` option. Some have reported that it fixes the issue. #### 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 failback 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`. #### rtorrent fails with files >= 4GiB This is a kernel bug with mmap and FUSE on 32bit platforms. A fix should become available for all LTS releases. https://marc.info/?l=linux-fsdevel&m=155550785230874&w=2 #### Plex doesn't work with mergerfs It does. If you're trying to put Plex's config / metadata on mergerfs you have to leave `direct_io` off because Plex is using sqlite which apparently needs mmap. mmap doesn't work with `direct_io`. To fix this place the data elsewhere or disable `direct_io` (with `dropcacheonclose=true`). If the issue is that scanning doesn't seem to pick up media then be sure to set `func.getattr=newest` as mentioned above. #### mmap performance is really bad There [is a bug](https://lkml.org/lkml/2016/3/16/260) in caching which affects overall performance of mmap through FUSE in Linux 4.x kernels. It is fixed in [4.4.10 and 4.5.4](https://lkml.org/lkml/2016/5/11/59). #### 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. #### 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 it's 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 situtation 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 upto 32 supplemental groups. Linux >= 2.6.3 allows upto 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. #### mergerfs or libfuse crashing **NOTE:** as of mergerfs 2.22.0 it includes the most recent version of libfuse (or requires libfuse-2.9.7) so any crash should be reported. For older releases continue reading... 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. #### 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 its 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). #### mergerfs under heavy load and memory preasure leads to kernel panic https://lkml.org/lkml/2016/9/14/527 ``` [25192.515454] kernel BUG at /build/linux-a2WvEb/linux-4.4.0/mm/workingset.c:346! [25192.517521] invalid opcode: 0000 [#1] SMP [25192.519602] Modules linked in: netconsole ip6t_REJECT nf_reject_ipv6 ipt_REJECT nf_reject_ipv4 configfs binfmt_misc veth bridge stp llc nf_conntrack_ipv6 nf_defrag_ipv6 xt_conntrack ip6table_filter ip6_tables xt_multiport iptable_filter ipt_MASQUERADE nf_nat_masquerade_ipv4 xt_comment xt_nat iptable_nat nf_conntrack_ipv4 nf_defrag_ipv4 nf_nat_ipv4 nf_nat nf_conntrack xt_CHECKSUM xt_tcpudp iptable_mangle ip_tables x_tables intel_rapl x86_pkg_temp_thermal intel_powerclamp eeepc_wmi asus_wmi coretemp sparse_keymap kvm_intel ppdev kvm irqbypass mei_me 8250_fintek input_leds serio_raw parport_pc tpm_infineon mei shpchp mac_hid parport lpc_ich autofs4 drbg ansi_cprng dm_crypt algif_skcipher af_alg btrfs raid456 async_raid6_recov async_memcpy async_pq async_xor async_tx xor raid6_pq libcrc32c raid0 multipath linear raid10 raid1 i915 crct10dif_pclmul crc32_pclmul aesni_intel i2c_algo_bit aes_x86_64 drm_kms_helper lrw gf128mul glue_helper ablk_helper syscopyarea cryptd sysfillrect sysimgblt fb_sys_fops drm ahci r8169 libahci mii wmi fjes video [last unloaded: netconsole] [25192.540910] CPU: 2 PID: 63 Comm: kswapd0 Not tainted 4.4.0-36-generic #55-Ubuntu [25192.543411] Hardware name: System manufacturer System Product Name/P8H67-M PRO, BIOS 3904 04/27/2013 [25192.545840] task: ffff88040cae6040 ti: ffff880407488000 task.ti: ffff880407488000 [25192.548277] RIP: 0010:[] [] shadow_lru_isolate+0x181/0x190 [25192.550706] RSP: 0018:ffff88040748bbe0 EFLAGS: 00010002 [25192.553127] RAX: 0000000000001c81 RBX: ffff8802f91ee928 RCX: ffff8802f91eeb38 [25192.555544] RDX: ffff8802f91ee938 RSI: ffff8802f91ee928 RDI: ffff8804099ba2c0 [25192.557914] RBP: ffff88040748bc08 R08: 000000000001a7b6 R09: 000000000000003f [25192.560237] R10: 000000000001a750 R11: 0000000000000000 R12: ffff8804099ba2c0 [25192.562512] R13: ffff8803157e9680 R14: ffff8803157e9668 R15: ffff8804099ba2c8 [25192.564724] FS: 0000000000000000(0000) GS:ffff88041f280000(0000) knlGS:0000000000000000 [25192.566990] CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 [25192.569201] CR2: 00007ffabb690000 CR3: 0000000001e0a000 CR4: 00000000000406e0 [25192.571419] Stack: [25192.573550] ffff8804099ba2c0 ffff88039e4f86f0 ffff8802f91ee928 ffff8804099ba2c8 [25192.575695] ffff88040748bd08 ffff88040748bc58 ffffffff811b99bf 0000000000000052 [25192.577814] 0000000000000000 ffffffff811ba380 000000000000008a 0000000000000080 [25192.579947] Call Trace: [25192.582022] [] __list_lru_walk_one.isra.3+0x8f/0x130 [25192.584137] [] ? memcg_drain_all_list_lrus+0x190/0x190 [25192.586165] [] list_lru_walk_one+0x23/0x30 [25192.588145] [] scan_shadow_nodes+0x34/0x50 [25192.590074] [] shrink_slab.part.40+0x1ed/0x3d0 [25192.591985] [] shrink_zone+0x2ca/0x2e0 [25192.593863] [] kswapd+0x51e/0x990 [25192.595737] [] ? mem_cgroup_shrink_node_zone+0x1c0/0x1c0 [25192.597613] [] kthread+0xd8/0xf0 [25192.599495] [] ? kthread_create_on_node+0x1e0/0x1e0 [25192.601335] [] ret_from_fork+0x3f/0x70 [25192.603193] [] ? kthread_create_on_node+0x1e0/0x1e0 ``` There is a bug in the kernel. A work around appears to be turning off `splice`. Don't add the `splice_*` arguments or add `no_splice_write,no_splice_move,no_splice_read`. This, however, is not guaranteed to work. #### rm: fts_read failed: No such file or directory NOTE: This is only relevant to mergerfs versions at or below v2.25.x and should not occur in more recent versions. See the notes on `unlink`. Not *really* a bug. The FUSE library will move files when asked to delete them as a way to deal with certain edge cases and then later delete that file when its clear the file is no longer needed. This however can lead to two issues. One is that these hidden files are noticed by `rm -rf` or `find` when scanning directories and they may try to remove them and they might have disappeared already. There is nothing *wrong* about this happening but it can be annoying. The second issue is that a directory might not be able to removed on account of the hidden file being still there. Using the **hard_remove** option will make it so these temporary files are not used and files are deleted immedately. That has a side effect however. Files which are unlinked and then they are still used (in certain forms) will result in an error (ENOENT). # 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 is it's sole solution for pooling drives. #### 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. #### Do hard links work? Yes. You need to use `use_ino` to support proper reporting of inodes. What mergerfs does not do is fake hard links across branches. Read the section "rename & link" for how it works. #### Does mergerfs support CoW / copy-on-write? 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. #### Why can't I see my files / directories? It's almost always a permissions issue. Unlike mhddfs, which runs as root and attempts to access content as such, mergerfs always changes it's 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. #### Why is only one drive being used? Are you using a path preserving policy? The default policy for file creation is `epmfs`. That means only the drives with the path preexisting will be considered when creating a file. If you don't care about where files and directories are created you likely shouldn't be using a path preserving policy and instead something like `mfs`. This can be especially apparent when filling an empty pool from an external source. 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 transfering your data. Setting `func.mkdir=epall` can simplify managing path perservation for `create`. #### 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 pratical (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 its 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 then 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 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 behavior without the possibility of catastrophic failure and the difficulties in recovery. Drives may fail however all other data will continue to be accessable. When combined with something like [SnapRaid](http://www.snapraid.it) and/or an offsite backup solution you can have the flexibilty 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 major maintance and cost burdens as described [here](http://louwrentius.com/the-hidden-cost-of-using-zfs-for-your-home-nas.html). #### Can drives be written to directly? Outside of mergerfs while pooled? Yes, however its not recommended to use the same file from within the pool and from without at the same time. Especially if using caching of any kind (cache.files, cache.entry, cache.attr, cache.negative_entry, cache.symlinks, cache.readdir, etc.). #### 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 its 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. #### Can mergerfs mounts be exported over NFS? Yes. Due to current usage of libfuse by mergerfs and how NFS interacts with it it is necessary to add `noforget` to mergerfs options to keep from getting "stale file handle" errors. Some clients (Kodi) have issues in which the contents of the NFS mount will not be presented but users have found that enabling the `use_ino` option often fixes that problem. #### 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. #### How are inodes calculated? mergerfs-inode = (original-inode | (device-id << 32)) While `ino_t` is 64 bits only a few filesystems use more than 32. Similarly, while `dev_t` is also 64 bits it was traditionally 16 bits. Bitwise or'ing them together should work most of the time. While totally unique inodes are preferred the overhead which would be needed does not seem to outweighted by the benefits. While atypical, yes, inodes can be reused and not refer to the same file. The internal id 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 (nodeid,generation). That tuple is not user facing. The inode is merely metadata passed through the kernel and found using the `stat` family of calls or `readdir`. 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"). ``` #### I notice massive slowdowns of writes over NFS Due to how NFS works and interacts with FUSE when not using `cache.files=off` or `direct_io` its possible that a getxattr for `security.capability` will be issued prior to any write. This will usually result in a massive slowdown for writes. Using `cache.files=off` or `direct_io` will keep this from happening (and generally good to enable unless you need the features it disables) but the `security_capability` option can also help by short circuiting the call and returning `ENOATTR`. You could also set `xattr` to `noattr` or `nosys` to short circuit or stop all xattr requests. #### What are these .fuse_hidden files? NOTE: mergerfs >= 2.26.0 will not have these temporary files. See the notes on `unlink`. When not using **hard_remove** libfuse will create .fuse_hiddenXXXXXXXX files when an opened file is unlinked. This is to simplify "use after unlink" usecases. There is a possibility these files end up being picked up by software scanning directories and not ignoring hidden files. This is rarely a problem but a solution is in the works. The files are cleaned up once the file is finally closed. Only if mergerfs crashes or is killed would they be left around. They are safe to remove as they are already unlinked files. #### 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. # PERFORMANCE TWEAKING NOTE: be sure to read about these features before changing them * enable (or disable) `splice_move`, `splice_read`, and `splice_write` * increase cache timeouts `cache.attr`, `cache.entry`, `cache.negative_entry` * enable (or disable) page caching (`cache.files`) * enable `cache.open` * enable `cache.statfs` * enable `cache.symlinks` * enable `cache.readdir` * change the number of worker threads * disable `security_capability` and/or `xattr` * disable `posix_acl` * disable `async_read` * test theoretical performance using `nullrw` or mounting a ram disk * use `symlinkify` if your data is largely static * use tiered cache drives * use lvm and lvm cache to place a SSD in front of your HDDs (howto coming) # SUPPORT Filesystems are very 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. #### Information to include in bug reports * Version of mergerfs: `mergerfs -V` * mergerfs settings: from `/etc/fstab` or command line execution * Version of Linux: `uname -a` * Versions of any additional software being used * List of drives, their filesystems, and sizes (before and after issue): `df -h` * A `strace` of the app having problems: * `strace -f -o /tmp/app.strace.txt ` * A `strace` of mergerfs while the program is trying to do whatever it's failing to do: * `strace -f -p -o /tmp/mergerfs.strace.txt` * **Precise** directions on replicating the issue. Do not leave **anything** out. * Try to recreate the problem in the simplist way using standard programs. #### Contact / Issue submission * github.com: https://github.com/trapexit/mergerfs/issues * email: trapexit@spawn.link * twitter: https://twitter.com/_trapexit * reddit: https://www.reddit.com/user/trapexit * discord: https://discord.gg/MpAr69V #### Support development This software is free to use and released under a very liberal license. That said if you like this software and would like to support its development donations are welcome. * PayPal: https://paypal.me/trapexit * Patreon: https://www.patreon.com/trapexit * SubscribeStar: https://www.subscribestar.com/trapexit * Bitcoin (BTC): 12CdMhEPQVmjz3SSynkAEuD5q9JmhTDCZA * Bitcoin Cash (BCH): 1AjPqZZhu7GVEs6JFPjHmtsvmDL4euzMzp * Ethereum (ETH): 0x09A166B11fCC127324C7fc5f1B572255b3046E94 * Litecoin (LTC): LXAsq6yc6zYU3EbcqyWtHBrH1Ypx4GjUjm # LINKS * https://spawn.link * https://github.com/trapexit/mergerfs * 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.28.1/Makefile0000664000175000017500000001304113471033317013471 0ustar bilebile# 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 FUSE_CFLAGS = -D_FILE_OFFSET_BITS=64 -Ilibfuse/include ifeq ($(DEBUG),1) DEBUG_FLAGS := -g else DEBUG_FLAGS := endif ifeq ($(STATIC),1) STATIC_FLAGS := -static else STATIC_FLAGS := endif ifeq ($(LTO),1) LTO_FLAGS := -flto else LTO_FLAGS := endif UGID_USE_RWLOCK = 0 OPTS = -O2 SRC = $(wildcard src/*.cpp) OBJS = $(SRC:src/%.cpp=build/%.o) DEPS = $(SRC:src/%.cpp=build/%.d) MANPAGE = mergerfs.1 CXXFLAGS += \ $(OPTS) \ $(DEBUG_FLAGS) \ $(STATIC_FLAGS) \ $(LTO_FLAGS) \ -Wall \ -Wno-unused-result \ $(FUSE_CFLAGS) \ -DFUSE_USE_VERSION=29 \ -MMD \ -DUSE_XATTR=$(USE_XATTR) \ -DUGID_USE_RWLOCK=$(UGID_USE_RWLOCK) LDFLAGS += \ -pthread \ -lrt 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) build/mergerfs: libfuse objects $(CXX) $(CXXFLAGS) $(CPPFLAGS) $(OBJS) -o $@ libfuse/build/libfuse.a $(LDFLAGS) mergerfs: build/mergerfs 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 $(TOUCH) $@ build/%.o: src/%.cpp $(CXX) $(CXXFLAGS) $(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.mergerfs install-man install-base: build/mergerfs $(MKDIR) -p "$(INSTALLBINDIR)" $(INSTALL) -v -m 0755 build/mergerfs "$(INSTALLBINDIR)/mergerfs" install-mount.mergerfs: install-base $(MKDIR) -p "$(INSTALLBINDIR)" $(LN) -s "mergerfs" "$(INSTALLBINDIR)/mount.mergerfs" 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) -C libfuse -include $(DEPS) mergerfs-2.28.1/tests/0000775000175000017500000000000013476464517013213 5ustar bilebilemergerfs-2.28.1/tests/TEST_no_fuse_hidden0000775000175000017500000000054113471033317016732 0ustar bilebile#!/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.28.1/tests/TEST_use_fchown_after_unlink0000775000175000017500000000034113471033317020660 0ustar bilebile#!/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.28.1/tests/TEST_use_fstat_after_unlink0000775000175000017500000000025713476464517020542 0ustar bilebile#!/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.28.1/tests/TEST_use_fchmod_after_unlink0000775000175000017500000000027513471033317020642 0ustar bilebile#!/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.28.1/tests/run-tests0000775000175000017500000000144113471033317015066 0ustar bilebile#!/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.28.1/tests/TEST_unlink_rename0000775000175000017500000000034213471033317016607 0ustar bilebile#!/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.28.1/src/0000775000175000017500000000000013477573310012631 5ustar bilebilemergerfs-2.28.1/src/fs_base_rename.hpp0000664000175000017500000000220413467101456016266 0ustar bilebile/* 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.28.1/src/fs_base_write.hpp0000664000175000017500000000226313427205466016160 0ustar bilebile/* 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.28.1/src/resources.cpp0000664000175000017500000000337313471033317015345 0ustar bilebile/* 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.28.1/src/fs_base_ftruncate.hpp0000664000175000017500000000175613471033317017020 0ustar bilebile/* 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.28.1/src/policy_epff.cpp0000664000175000017500000000736513471033317015637 0ustar bilebile/* 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.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 #include using std::string; using std::vector; namespace epff { static int create(const Branches &branches_, const char *fusepath, const uint64_t minfreespace, vector &paths) { int rv; int error; fs::info_t info; const Branch *branch; error = ENOENT; for(size_t i = 0, ei = branches_.size(); i != ei; i++) { branch = &branches_[i]; if(!fs::exists(branch->path,fusepath)) error_and_continue(error,ENOENT); 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 < minfreespace) error_and_continue(error,ENOSPC); paths.push_back(&branch->path); return 0; } return (errno=error,-1); } static int action(const Branches &branches_, const char *fusepath, vector &paths) { int rv; int error; bool readonly; const Branch *branch; error = ENOENT; for(size_t i = 0, ei = branches_.size(); i != ei; i++) { branch = &branches_[i]; if(!fs::exists(branch->path,fusepath)) error_and_continue(error,ENOENT); if(branch->ro()) error_and_continue(error,EROFS); 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 &branches_, const char *fusepath, vector &paths) { const Branch *branch; for(size_t i = 0, ei = branches_.size(); i != ei; i++) { branch = &branches_[i]; if(!fs::exists(branch->path,fusepath)) continue; paths.push_back(&branch->path); return 0; } return (errno=ENOENT,-1); } } int Policy::Func::epff(const Category::Enum::Type type, const Branches &branches_, const char *fusepath, const uint64_t minfreespace, vector &paths) { switch(type) { case Category::Enum::create: return epff::create(branches_,fusepath,minfreespace,paths); case Category::Enum::action: return epff::action(branches_,fusepath,paths); case Category::Enum::search: default: return epff::search(branches_,fusepath,paths); } } mergerfs-2.28.1/src/fuse_statfs.hpp0000664000175000017500000000163513471033317015665 0ustar bilebile/* 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.28.1/src/fuse_futimens.hpp0000664000175000017500000000170513471033317016211 0ustar bilebile/* 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 FUSE { int futimens(const struct fuse_file_info *ffi_, const timespec ts_[2]); } mergerfs-2.28.1/src/fs_base_dup.hpp0000664000175000017500000000164413471033317015611 0ustar bilebile/* 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.28.1/src/branch.cpp0000664000175000017500000001012513467101456014566 0ustar bilebile/* 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 "fs.hpp" #include "fs_glob.hpp" #include "str.hpp" #include #include using std::string; using std::vector; bool Branch::ro(void) const { return (mode == Branch::RO); } bool Branch::nc(void) const { return (mode == Branch::NC); } bool Branch::ro_or_nc(void) const { return ((mode == Branch::RO) || (mode == Branch::NC)); } string Branches::to_string(const bool mode_) const { string tmp; for(size_t i = 0; i < size(); i++) { const Branch &branch = (*this)[i]; tmp += branch.path; if(mode_) { tmp += '='; switch(branch.mode) { default: case Branch::RW: tmp += "RW"; break; case Branch::RO: tmp += "RO"; break; case Branch::NC: tmp += "NC"; break; } } tmp += ':'; } if(*tmp.rbegin() == ':') tmp.erase(tmp.size() - 1); return tmp; } void Branches::to_paths(vector &vec_) const { for(size_t i = 0; i < size(); i++) { const Branch &branch = (*this)[i]; vec_.push_back(branch.path); } } static void parse(const string &str_, Branches &branches_) { string str; Branch branch; vector globbed; str = str_; branch.mode = Branch::INVALID; if(str::ends_with(str,"=RO")) branch.mode = Branch::RO; else if(str::ends_with(str,"=RW")) branch.mode = Branch::RW; else if(str::ends_with(str,"=NC")) branch.mode = Branch::NC; if(branch.mode != Branch::INVALID) str.resize(str.size() - 3); else branch.mode = Branch::RW; fs::glob(str,globbed); fs::realpathize(globbed); for(size_t i = 0; i < globbed.size(); i++) { branch.path = globbed[i]; branches_.push_back(branch); } } void Branches::set(const std::string &str_) { vector paths; clear(); str::split(paths,str_,':'); for(size_t i = 0; i < paths.size(); i++) { Branches branches; parse(paths[i],branches); insert(end(), branches.begin(), branches.end()); } } void Branches::add_begin(const std::string &str_) { vector paths; str::split(paths,str_,':'); for(size_t i = 0; i < paths.size(); i++) { Branches branches; parse(paths[i],branches); insert(begin(), branches.begin(), branches.end()); } } void Branches::add_end(const std::string &str_) { vector paths; str::split(paths,str_,':'); for(size_t i = 0; i < paths.size(); i++) { Branches branches; parse(paths[i],branches); insert(end(), branches.begin(), branches.end()); } } void Branches::erase_begin(void) { erase(begin()); } void Branches::erase_end(void) { pop_back(); } void Branches::erase_fnmatch(const std::string &str_) { vector patterns; str::split(patterns,str_,':'); for(iterator i = begin(); i != end();) { int match = FNM_NOMATCH; for(vector::const_iterator pi = patterns.begin(); pi != patterns.end() && match != 0; ++pi) { match = ::fnmatch(pi->c_str(),i->path.c_str(),0); } i = ((match == 0) ? erase(i) : (i+1)); } } mergerfs-2.28.1/src/fs_base_mknod.hpp0000664000175000017500000000211313471033317016121 0ustar bilebile/* 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.28.1/src/fs_base_dirfd.hpp0000664000175000017500000000167613471033317016116 0ustar bilebile/* 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.28.1/src/ugid_rwlock.hpp0000664000175000017500000000410413427205466015651 0ustar bilebile/* 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.28.1/src/branch.hpp0000664000175000017500000000266213467101456014602 0ustar bilebile/* 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 struct Branch { enum Mode { INVALID, RO, RW, NC }; Mode mode; std::string path; bool ro(void) const; bool nc(void) const; bool ro_or_nc(void) const; }; class Branches : public std::vector { public: std::string to_string(const bool mode_ = false) const; void to_paths(std::vector &vec_) const; public: void set(const std::string &str_); void add_begin(const std::string &str_); void add_end(const std::string &str_); void erase_begin(void); void erase_end(void); void erase_fnmatch(const std::string &str_); }; mergerfs-2.28.1/src/fs.cpp0000664000175000017500000000653313471033317013744 0ustar bilebile/* 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 #include #include "errno.hpp" #include "fs_attr.hpp" #include "fs_base_realpath.hpp" #include "fs_base_stat.hpp" #include "fs_exists.hpp" #include "fs_path.hpp" #include "fs_statvfs_cache.hpp" #include "fs_xattr.hpp" #include "str.hpp" using std::string; using std::vector; namespace fs { void findallfiles(const vector &basepaths, const char *fusepath, vector &paths) { 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); } } int findonfs(const vector &basepaths, const string &fusepath, const int fd, string &basepath) { int rv; dev_t dev; string fullpath; struct stat st; rv = fs::fstat(fd,&st); if(rv == -1) return -1; dev = st.st_dev; for(size_t i = 0, ei = basepaths.size(); i != ei; i++) { fullpath = fs::path::make(basepaths[i],fusepath); rv = fs::lstat(fullpath,&st); if(rv == -1) continue; if(st.st_dev != dev) continue; basepath = basepaths[i]; return 0; } return (errno=ENOENT,-1); } void realpathize(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); } } int getfl(const int fd) { return ::fcntl(fd,F_GETFL,0); } int setfl(const int fd, const mode_t mode) { return ::fcntl(fd,F_SETFL,mode); } int mfs(const vector &basepaths, const uint64_t minfreespace, string &path) { int rv; uint64_t mfs; uint64_t spaceavail; const string *mfsbasepath; mfs = 0; mfsbasepath = NULL; for(size_t i = 0, ei = basepaths.size(); i != ei; i++) { rv = fs::statvfs_cache_spaceavail(basepaths[i],&spaceavail); if(rv == -1) continue; if(spaceavail < minfreespace) continue; if(spaceavail <= mfs) continue; mfs = spaceavail; mfsbasepath = &basepaths[i]; } if(mfsbasepath == NULL) return (errno=ENOENT,-1); path = *mfsbasepath; return 0; } }; mergerfs-2.28.1/src/mergerfs.hpp0000664000175000017500000000144213427205466015154 0ustar bilebile/* 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.28.1/src/fuse_read.cpp0000664000175000017500000000412313471033317015262 0ustar bilebile/* 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_base_read.hpp" #include namespace l { static inline int read_regular(const int fd_, void *buf_, const size_t count_, const off_t offset_) { int rv; rv = fs::pread(fd_,buf_,count_,offset_); if(rv == -1) return -errno; if(rv == 0) return 0; return count_; } static inline int read_direct_io(const int fd_, void *buf_, const size_t count_, const off_t offset_) { int rv; rv = fs::pread(fd_,buf_,count_,offset_); if(rv == -1) return -errno; return rv; } } namespace FUSE { int read(const char *fusepath_, char *buf_, size_t count_, off_t offset_, fuse_file_info *ffi_) { FileInfo *fi; fi = reinterpret_cast(ffi_->fh); if(ffi_->direct_io) return l::read_direct_io(fi->fd,buf_,count_,offset_); return l::read_regular(fi->fd,buf_,count_,offset_); } int read_null(const char *fusepath_, char *buf_, size_t count_, off_t offset_, fuse_file_info *ffi_) { return count_; } } mergerfs-2.28.1/src/fuse_rename.hpp0000664000175000017500000000156313475734103015635 0ustar bilebile/* 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.28.1/src/fs_base_futimesat.hpp0000664000175000017500000000174513471033317017024 0ustar bilebile/* 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.28.1/src/policy_eplus.cpp0000664000175000017500000001126013471033317016034 0ustar bilebile/* 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.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 #include #include using std::string; using std::vector; namespace eplus { static int create(const Branches &branches_, const char *fusepath, const uint64_t minfreespace, vector &paths) { int rv; int error; uint64_t eplus; fs::info_t info; const Branch *branch; const string *eplusbasepath; error = ENOENT; eplus = std::numeric_limits::max(); eplusbasepath = NULL; for(size_t i = 0, ei = branches_.size(); i != ei; i++) { branch = &branches_[i]; if(!fs::exists(branch->path,fusepath)) error_and_continue(error,ENOENT); 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 < minfreespace) error_and_continue(error,ENOSPC); if(info.spaceused >= eplus) continue; eplus = info.spaceused; eplusbasepath = &branch->path; } if(eplusbasepath == NULL) return (errno=error,-1); paths.push_back(eplusbasepath); return 0; } static int action(const Branches &branches_, const char *fusepath, vector &paths) { int rv; int error; uint64_t eplus; fs::info_t info; const Branch *branch; const string *eplusbasepath; error = ENOENT; eplus = std::numeric_limits::max(); eplusbasepath = NULL; for(size_t i = 0, ei = branches_.size(); i != ei; i++) { branch = &branches_[i]; if(!fs::exists(branch->path,fusepath)) error_and_continue(error,ENOENT); if(branch->ro()) 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.spaceused >= eplus) continue; eplus = info.spaceused; eplusbasepath = &branch->path; } if(eplusbasepath == NULL) return (errno=error,-1); paths.push_back(eplusbasepath); return 0; } static int search(const Branches &branches_, const char *fusepath, vector &paths) { int rv; uint64_t eplus; uint64_t spaceused; const Branch *branch; const string *eplusbasepath; eplus = 0; eplusbasepath = NULL; for(size_t i = 0, ei = branches_.size(); i != ei; i++) { branch = &branches_[i]; 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; eplusbasepath = &branch->path; } if(eplusbasepath == NULL) return (errno=ENOENT,-1); paths.push_back(eplusbasepath); return 0; } } int Policy::Func::eplus(const Category::Enum::Type type, const Branches &branches_, const char *fusepath, const uint64_t minfreespace, vector &paths) { switch(type) { case Category::Enum::create: return eplus::create(branches_,fusepath,minfreespace,paths); case Category::Enum::action: return eplus::action(branches_,fusepath,paths); case Category::Enum::search: default: return eplus::search(branches_,fusepath,paths); } } mergerfs-2.28.1/src/fs_mktemp.hpp0000664000175000017500000000163013467101456015324 0ustar bilebile/* 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.28.1/src/fs_base_mkstemp.hpp0000664000175000017500000000224113471033317016473 0ustar bilebile/* 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 mkstemp(std::string &filepath_) { int fd; char buf[filepath_.size()+8]; strcpy(buf,filepath_.c_str()); strcpy(buf+filepath_.size(),".XXXXXX"); fd = ::mkstemp(buf); if(fd == -1) return -1; filepath_ = buf; return fd; } } mergerfs-2.28.1/src/fuse_readdir.hpp0000664000175000017500000000210213471033317015761 0ustar bilebile/* 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 FUSE { int readdir(const char *fusepath_, void *buf_, fuse_fill_dir_t filler_, off_t offset_, fuse_file_info *ffi_); } mergerfs-2.28.1/src/policy_mfs.cpp0000664000175000017500000000471513471033317015500 0ustar bilebile/* 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.hpp" #include "fs_exists.hpp" #include "fs_info.hpp" #include "fs_path.hpp" #include "policy.hpp" #include "policy_error.hpp" #include #include using std::string; using std::vector; namespace mfs { static int create(const Branches &branches_, const uint64_t minfreespace, vector &paths) { int rv; int error; uint64_t mfs; fs::info_t info; const Branch *branch; const string *mfsbasepath; error = ENOENT; mfs = 0; mfsbasepath = NULL; for(size_t i = 0, ei = branches_.size(); i != ei; i++) { branch = &branches_[i]; 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 < minfreespace) error_and_continue(error,ENOSPC); if(info.spaceavail < mfs) continue; mfs = info.spaceavail; mfsbasepath = &branch->path; } if(mfsbasepath == NULL) return (errno=error,-1); paths.push_back(mfsbasepath); return 0; } } int Policy::Func::mfs(const Category::Enum::Type type, const Branches &branches_, const char *fusepath, const uint64_t minfreespace, vector &paths) { if(type == Category::Enum::create) return mfs::create(branches_,minfreespace,paths); return Policy::Func::epmfs(type,branches_,fusepath,minfreespace,paths); } mergerfs-2.28.1/src/fuse_copy_file_range.cpp0000664000175000017500000000436113475211553017504 0ustar bilebile/* 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 #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 char *path_in_, struct fuse_file_info *ffi_in_, off_t offset_in_, const char *path_out_, struct fuse_file_info *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.28.1/src/fs_base_fallocate.hpp0000664000175000017500000000171213471033317016747 0ustar bilebile/* 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.28.1/src/fs_copy_file_range_unsupported.icpp0000664000175000017500000000267713471033317021777 0ustar bilebile/* 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.28.1/src/policy_cache.cpp0000664000175000017500000000361713471033317015756 0ustar bilebile#include "policy_cache.hpp" #include #include #include #include #include using std::map; using std::string; using std::vector; 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), path() { } PolicyCache::PolicyCache(void) : timeout(DEFAULT_TIMEOUT) { } void PolicyCache::erase(const char *fusepath_) { pthread_mutex_lock(&_lock); _cache.erase(fusepath_); pthread_mutex_unlock(&_lock); } void PolicyCache::cleanup(const int prob_) { uint64_t now; map::iterator i; 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()(Policy::Func::Search &func_, const Branches &branches_, const char *fusepath_, const uint64_t minfreespace_, std::string *branch_) { int rv; Value *v; uint64_t now; string branch; if(timeout == 0) return func_(branches_,fusepath_,minfreespace_,branch_); now = l::get_time(); pthread_mutex_lock(&_lock); v = &_cache[fusepath_]; if((now - v->time) >= timeout) { pthread_mutex_unlock(&_lock); rv = func_(branches_,fusepath_,minfreespace_,&branch); if(rv == -1) return -1; pthread_mutex_lock(&_lock); v->time = now; v->path = branch; } *branch_ = v->path; pthread_mutex_unlock(&_lock); return 0; } mergerfs-2.28.1/src/fs_glob.cpp0000664000175000017500000000231013467103610014733 0ustar bilebile/* 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.28.1/src/fs_devid.hpp0000664000175000017500000000206313471033317015116 0ustar bilebile/* 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 dev_t devid(const int fd_) { int rv; struct stat st; rv = ::fstat(fd_,&st); if(rv == -1) return -1; return st.st_dev; } } mergerfs-2.28.1/src/fs_base_closedir.hpp0000664000175000017500000000170413471033317016622 0ustar bilebile/* 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.28.1/src/fs_statvfs_cache.hpp0000664000175000017500000000257113471033317016644 0ustar bilebile/* 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 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.28.1/src/fs_copy_file_range.hpp0000664000175000017500000000255313471033317017154 0ustar bilebile/* 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 { 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_); ssize_t copy_file_range(const int fd_in_, const int fd_out_, const size_t len_, const unsigned int flags_ = 0); } mergerfs-2.28.1/src/fuse_read_buf.cpp0000664000175000017500000000345513471033317016125 0ustar bilebile/* 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 #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 char *fusepath_, fuse_bufvec **bufp_, size_t size_, off_t offset_, fuse_file_info *ffi_) { FileInfo *fi = reinterpret_cast(ffi_->fh); return l::read_buf(fi->fd, bufp_, size_, offset_); } } mergerfs-2.28.1/src/fuse_mkdir.cpp0000664000175000017500000001003113471033317015450 0ustar bilebile/* 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 "config.hpp" #include "errno.hpp" #include "fs_acl.hpp" #include "fs_base_mkdir.hpp" #include "fs_clonepath.hpp" #include "fs_path.hpp" #include "rv.hpp" #include "rwlock.hpp" #include "ugid.hpp" using std::string; using std::vector; 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 vector &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(Policy::Func::Search searchFunc_, Policy::Func::Create createFunc_, const Branches &branches_, const uint64_t minfreespace_, const char *fusepath_, const mode_t mode_, const mode_t umask_) { int rv; string fusedirpath; vector createpaths; vector existingpaths; fusedirpath = fs::path::dirname(fusepath_); rv = searchFunc_(branches_,fusedirpath,minfreespace_,existingpaths); if(rv == -1) return -errno; rv = createFunc_(branches_,fusedirpath,minfreespace_,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_) { const fuse_context *fc = fuse_get_context(); const Config &config = Config::get(fc); const ugid::Set ugid(fc->uid,fc->gid); const rwlock::ReadGuard readlock(&config.branches_lock); return l::mkdir(config.getattr, config.mkdir, config.branches, config.minfreespace, fusepath_, mode_, fc->umask); } } mergerfs-2.28.1/src/fuse_flush.hpp0000664000175000017500000000161513471033317015500 0ustar bilebile/* 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 flush(const char *path_, fuse_file_info *ffi_); } mergerfs-2.28.1/src/policy_invalid.cpp0000664000175000017500000000233313471033317016333 0ustar bilebile/* 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 #include using std::string; using std::vector; int Policy::Func::invalid(const Category::Enum::Type type, const Branches &branches_, const char *fusepath, const uint64_t minfreespace, vector &paths) { return (errno=EINVAL,-1); } mergerfs-2.28.1/src/policy.cpp0000664000175000017500000000456413471033317014635 0ustar bilebile/* 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 "buildvector.hpp" #include "fs.hpp" #include "policy.hpp" #define POLICY(X,PP) (Policy(Policy::Enum::X,#X,Policy::Func::X,PP)) #define PRESERVES_PATH true #define DOESNT_PRESERVE_PATH false const std::vector Policy::_policies_ = buildvector (POLICY(invalid,DOESNT_PRESERVE_PATH)) (POLICY(all,DOESNT_PRESERVE_PATH)) (POLICY(epall,PRESERVES_PATH)) (POLICY(epff,PRESERVES_PATH)) (POLICY(eplfs,PRESERVES_PATH)) (POLICY(eplus,PRESERVES_PATH)) (POLICY(epmfs,PRESERVES_PATH)) (POLICY(eprand,PRESERVES_PATH)) (POLICY(erofs,DOESNT_PRESERVE_PATH)) (POLICY(ff,DOESNT_PRESERVE_PATH)) (POLICY(lfs,DOESNT_PRESERVE_PATH)) (POLICY(lus,DOESNT_PRESERVE_PATH)) (POLICY(mfs,DOESNT_PRESERVE_PATH)) (POLICY(newest,DOESNT_PRESERVE_PATH)) (POLICY(rand,DOESNT_PRESERVE_PATH)); const Policy * const Policy::policies = &_policies_[1]; #define CONST_POLICY(X) const Policy &Policy::X = Policy::policies[Policy::Enum::X] CONST_POLICY(invalid); CONST_POLICY(all); CONST_POLICY(epall); CONST_POLICY(epff); CONST_POLICY(eplfs); CONST_POLICY(eplus); CONST_POLICY(epmfs); CONST_POLICY(eprand); CONST_POLICY(erofs); CONST_POLICY(ff); CONST_POLICY(lfs); CONST_POLICY(lus); CONST_POLICY(mfs); CONST_POLICY(newest); CONST_POLICY(rand); const Policy& Policy::find(const std::string &str) { for(int i = Enum::BEGIN; i != Enum::END; ++i) { if(policies[i] == str) return policies[i]; } return invalid; } const Policy& Policy::find(const Policy::Enum::Type i) { if(i >= Policy::Enum::BEGIN && i < Policy::Enum::END) return policies[i]; return invalid; } mergerfs-2.28.1/src/rwlock.hpp0000664000175000017500000000260513471033317014636 0ustar bilebile/* 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.28.1/src/fs_clonefile.hpp0000664000175000017500000000157113467101456015773 0ustar bilebile/* 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.28.1/src/fs_base_futimesat_generic.icpp0000664000175000017500000000203413467103610020653 0ustar bilebile/* 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.28.1/src/fuse_destroy.hpp0000664000175000017500000000152013471033317016043 0ustar bilebile/* 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.28.1/src/ugid_linux.icpp0000664000175000017500000000202713427205466015655 0ustar bilebile/* 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.28.1/src/num.cpp0000664000175000017500000000375413474062160014136 0ustar bilebile/* 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 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; } } mergerfs-2.28.1/src/fs_acl.hpp0000664000175000017500000000165313471033317014566 0ustar bilebile/* 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.28.1/src/fuse_free_hide.hpp0000664000175000017500000000160113471033317016264 0ustar bilebile/* 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.28.1/src/policy_eplfs.cpp0000664000175000017500000001133113471033317016014 0ustar bilebile/* 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.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 #include #include using std::string; using std::vector; namespace eplfs { static int create(const Branches &branches_, const char *fusepath, const uint64_t minfreespace, vector &paths) { int rv; int error; uint64_t eplfs; fs::info_t info; const Branch *branch; const string *eplfsbasepath; error = ENOENT; eplfs = std::numeric_limits::max(); eplfsbasepath = NULL; for(size_t i = 0, ei = branches_.size(); i != ei; i++) { branch = &branches_[i]; if(!fs::exists(branch->path,fusepath)) error_and_continue(error,ENOENT); 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 < minfreespace) error_and_continue(error,ENOSPC); if(info.spaceavail > eplfs) continue; eplfs = info.spaceavail; eplfsbasepath = &branch->path; } if(eplfsbasepath == NULL) return (errno=error,-1); paths.push_back(eplfsbasepath); return 0; } static int action(const Branches &branches_, const char *fusepath, vector &paths) { int rv; int error; uint64_t eplfs; fs::info_t info; const Branch *branch; const string *eplfsbasepath; error = ENOENT; eplfs = std::numeric_limits::max(); eplfsbasepath = NULL; for(size_t i = 0, ei = branches_.size(); i != ei; i++) { branch = &branches_[i]; if(!fs::exists(branch->path,fusepath)) error_and_continue(error,ENOENT); if(branch->ro()) 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 > eplfs) continue; eplfs = info.spaceavail; eplfsbasepath = &branch->path; } if(eplfsbasepath == NULL) return (errno=error,-1); paths.push_back(eplfsbasepath); return 0; } static int search(const Branches &branches_, const char *fusepath, vector &paths) { int rv; uint64_t eplfs; uint64_t spaceavail; const Branch *branch; const string *eplfsbasepath; eplfs = std::numeric_limits::max(); eplfsbasepath = NULL; for(size_t i = 0, ei = branches_.size(); i != ei; i++) { branch = &branches_[i]; 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; eplfsbasepath = &branch->path; } if(eplfsbasepath == NULL) return (errno=ENOENT,-1); paths.push_back(eplfsbasepath); return 0; } } int Policy::Func::eplfs(const Category::Enum::Type type, const Branches &branches_, const char *fusepath, const uint64_t minfreespace, vector &paths) { switch(type) { case Category::Enum::create: return eplfs::create(branches_,fusepath,minfreespace,paths); case Category::Enum::action: return eplfs::action(branches_,fusepath,paths); case Category::Enum::search: default: return eplfs::search(branches_,fusepath,paths); } } mergerfs-2.28.1/src/fuse_readlink.cpp0000664000175000017500000000723413471033317016146 0ustar bilebile/* 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_base_readlink.hpp" #include "fs_base_stat.hpp" #include "fs_path.hpp" #include "rwlock.hpp" #include "symlinkify.hpp" #include "ugid.hpp" #include #include using std::string; using std::vector; 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(Policy::Func::Search searchFunc_, const Branches &branches_, const uint64_t minfreespace_, const char *fusepath_, char *buf_, const size_t size_, const bool symlinkify_, const time_t symlinkify_timeout_) { int rv; vector basepaths; rv = searchFunc_(branches_,fusepath_,minfreespace_,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_) { const fuse_context *fc = fuse_get_context(); const Config &config = Config::get(fc); const ugid::Set ugid(fc->uid,fc->gid); const rwlock::ReadGuard readlock(&config.branches_lock); return l::readlink(config.readlink, config.branches, config.minfreespace, fusepath_, buf_, size_, config.symlinkify, config.symlinkify_timeout); } } mergerfs-2.28.1/src/policy_ff.cpp0000664000175000017500000000434013471033317015300 0ustar bilebile/* 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.hpp" #include "fs_exists.hpp" #include "fs_info.hpp" #include "fs_path.hpp" #include "policy.hpp" #include "policy_error.hpp" #include #include using std::string; using std::vector; namespace ff { static int create(const Branches &branches_, const uint64_t minfreespace, vector &paths) { int rv; int error; fs::info_t info; const Branch *branch; error = ENOENT; for(size_t i = 0, ei = branches_.size(); i != ei; i++) { branch = &branches_[i]; 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 < minfreespace) error_and_continue(error,ENOSPC); paths.push_back(&branch->path); return 0; } return (errno=error,-1); } } int Policy::Func::ff(const Category::Enum::Type type, const Branches &branches_, const char *fusepath, const uint64_t minfreespace, vector &paths) { if(type == Category::Enum::create) return ff::create(branches_,minfreespace,paths); return Policy::Func::epff(type,branches_,fusepath,minfreespace,paths); } mergerfs-2.28.1/src/fuse_utimens.cpp0000664000175000017500000000535513471033317016043 0ustar bilebile/* 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_base_utime.hpp" #include "fs_path.hpp" #include "rv.hpp" #include "rwlock.hpp" #include "ugid.hpp" #include #include #include #include using std::string; using std::vector; namespace l { static int utimens_loop_core(const string *basepath_, const char *fusepath_, const timespec ts_[2], const int error_) { int rv; string fullpath; fullpath = fs::path::make(basepath_,fusepath_); rv = fs::lutime(fullpath,ts_); return error::calc(rv,error_,errno); } static int utimens_loop(const vector &basepaths_, const char *fusepath_, const timespec ts_[2]) { int error; error = -1; for(size_t i = 0, ei = basepaths_.size(); i != ei; i++) { error = l::utimens_loop_core(basepaths_[i],fusepath_,ts_,error); } return -error; } static int utimens(Policy::Func::Action actionFunc_, const Branches &branches_, const uint64_t minfreespace_, const char *fusepath_, const timespec ts_[2]) { int rv; vector basepaths; rv = actionFunc_(branches_,fusepath_,minfreespace_,basepaths); if(rv == -1) return -errno; return l::utimens_loop(basepaths,fusepath_,ts_); } } namespace FUSE { int utimens(const char *fusepath_, const timespec ts_[2]) { const fuse_context *fc = fuse_get_context(); const Config &config = Config::get(fc); const ugid::Set ugid(fc->uid,fc->gid); const rwlock::ReadGuard readlock(&config.branches_lock); return l::utimens(config.utimens, config.branches, config.minfreespace, fusepath_, ts_); } } mergerfs-2.28.1/src/fuse_chown.cpp0000664000175000017500000000552713471033317015476 0ustar bilebile/* 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_base_chown.hpp" #include "fs_path.hpp" #include "rv.hpp" #include "rwlock.hpp" #include "ugid.hpp" #include #include #include using std::string; using std::vector; namespace l { static int chown_loop_core(const string *basepath_, const char *fusepath_, const uid_t uid_, const gid_t gid_, const int error_) { int rv; string fullpath; fullpath = fs::path::make(basepath_,fusepath_); rv = fs::lchown(fullpath,uid_,gid_); return error::calc(rv,error_,errno); } static int chown_loop(const vector &basepaths_, const char *fusepath_, const uid_t uid_, const gid_t gid_) { int error; error = -1; for(size_t i = 0, ei = basepaths_.size(); i != ei; i++) { error = l::chown_loop_core(basepaths_[i],fusepath_,uid_,gid_,error); } return -error; } static int chown(Policy::Func::Action actionFunc_, const Branches &branches_, const uint64_t minfreespace_, const char *fusepath_, const uid_t uid_, const gid_t gid_) { int rv; vector basepaths; rv = actionFunc_(branches_,fusepath_,minfreespace_,basepaths); if(rv == -1) return -errno; return l::chown_loop(basepaths,fusepath_,uid_,gid_); } } namespace FUSE { int chown(const char *fusepath_, uid_t uid_, gid_t gid_) { const fuse_context *fc = fuse_get_context(); const Config &config = Config::get(fc); const ugid::Set ugid(fc->uid,fc->gid); const rwlock::ReadGuard readlock(&config.branches_lock); return l::chown(config.chown, config.branches, config.minfreespace, fusepath_, uid_, gid_); } } mergerfs-2.28.1/src/fs_copy_file_range_linux.icpp0000664000175000017500000000544513471033317020542 0ustar bilebile/* 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 ssize_t copy_file_range_(int fd_in_, int64_t *off_in_, int fd_out_, int64_t *off_out_, size_t len_, unsigned int flags_) { #ifdef SYS_copy_file_range ssize_t rv; loff_t off_in; loff_t off_out; off_in = *off_in_; off_out = *off_out_; rv = ::syscall(SYS_copy_file_range, fd_in_, &off_in, fd_out_, &off_out, len_, flags_); if(rv != -1) { *off_in_ = off_in; *off_out_ = off_out; } return rv; #else return (errno=EOPNOTSUPP,-1); #endif } } 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 l::copy_file_range_(fd_in_, off_in_, fd_out_, off_out_, len_, flags_); } ssize_t copy_file_range(const int fd_in_, const int fd_out_, const size_t len_, const unsigned int flags_) { int64_t off_in; int64_t off_out; off_in = 0; off_out = 0; return fs::copy_file_range(fd_in_, &off_in, fd_out_, &off_out, len_, flags_); } } mergerfs-2.28.1/src/fs_info.cpp0000664000175000017500000000257713471033317014763 0ustar bilebile/* 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_base_stat.hpp" #include "fs_base_statvfs.hpp" #include "fs_info_t.hpp" #include "fs_path.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.28.1/src/fuse_free_hide.cpp0000664000175000017500000000203413471033317016260 0ustar bilebile/* 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_base_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.28.1/src/fuse_fsyncdir.hpp0000664000175000017500000000167713471033317016210 0ustar bilebile/* 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 FUSE { int fsyncdir(const char *fusepath_, int isdatasync_, fuse_file_info *ffi_); } mergerfs-2.28.1/src/fs_base_link.hpp0000664000175000017500000000203413471033317015750 0ustar bilebile/* 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.28.1/src/fs_base_fallocate_posix.icpp0000664000175000017500000000207513467103610020337 0ustar bilebile/* 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 "errno.hpp" namespace fs { int fallocate(const int fd, const int mode, const off_t offset, const off_t len) { return (mode ? (errno=EOPNOTSUPP,-1) : (::posix_fallocate(fd,offset,len))); } } mergerfs-2.28.1/src/buildmap.hpp0000664000175000017500000000230713427205466015140 0ustar bilebile/* 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 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.28.1/src/fileinfo.hpp0000664000175000017500000000175413427205466015143 0ustar bilebile/* 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 class FileInfo { public: FileInfo(const int fd_, const char *fusepath_) : fd(fd_), fusepath(fusepath_) { } public: int fd; std::string fusepath; }; mergerfs-2.28.1/src/fuse_futimens.cpp0000664000175000017500000000250213471033317016200 0ustar bilebile/* 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_base_utime.hpp" #include #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 struct fuse_file_info *ffi_, const struct timespec ts_[2]) { FileInfo *fi = reinterpret_cast(ffi_->fh); return l::futimens(fi->fd,ts_); } } mergerfs-2.28.1/src/fuse_utimens.hpp0000664000175000017500000000163313471033317016043 0ustar bilebile/* 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.28.1/src/fs_attr_linux.icpp0000664000175000017500000000561113467103610016361 0ustar bilebile/* 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 "errno.hpp" #include "fs_base_close.hpp" #include "fs_base_open.hpp" #include "fs_base_ioctl.hpp" 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.28.1/src/fs_attr.hpp0000664000175000017500000000175313471033317015002 0ustar bilebile/* 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.28.1/src/fuse_symlink.hpp0000664000175000017500000000157513471033317016052 0ustar bilebile/* 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 symlink(const char *oldpath_, const char *newpath_); } mergerfs-2.28.1/src/fs_base_fadvise_unsupported.icpp0000664000175000017500000000174613467103610021260 0ustar bilebile/* 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.28.1/src/fuse_access.cpp0000664000175000017500000000400313471033317015605 0ustar bilebile/* 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 "config.hpp" #include "errno.hpp" #include "fs_base_access.hpp" #include "fs_path.hpp" #include "rwlock.hpp" #include "ugid.hpp" using std::string; using std::vector; namespace l { static int access(Policy::Func::Search searchFunc, const Branches &branches_, const uint64_t minfreespace, const char *fusepath, const int mask) { int rv; string fullpath; vector basepaths; rv = searchFunc(branches_,fusepath,minfreespace,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) { const fuse_context *fc = fuse_get_context(); const Config &config = Config::get(fc); const ugid::Set ugid(fc->uid,fc->gid); const rwlock::ReadGuard readlock(&config.branches_lock); return l::access(config.access, config.branches, config.minfreespace, fusepath, mask); } } mergerfs-2.28.1/src/fuse_copy_file_range.hpp0000664000175000017500000000232113475211553017503 0ustar bilebile/* 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 char *path_in, struct fuse_file_info *ffi_in, off_t offset_in, const char *path_out, struct fuse_file_info *ffi_out, off_t offset_out, size_t size, int flags); } mergerfs-2.28.1/src/ugid.hpp0000664000175000017500000000205313467103610014261 0ustar bilebile/* 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 #include "ugid_linux.hpp" #else #include "ugid_rwlock.hpp" #endif mergerfs-2.28.1/src/fs_base_realpath.hpp0000664000175000017500000000223013471033317016611 0ustar bilebile/* 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.28.1/src/fs_base_opendir.hpp0000664000175000017500000000175513471033317016464 0ustar bilebile/* 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.28.1/src/category.cpp0000664000175000017500000000351613471033317015147 0ustar bilebile/* 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 "category.hpp" #include "buildvector.hpp" #define CATEGORY(X) Category(Category::Enum::X,#X) const std::vector Category::_categories_ = buildvector (CATEGORY(invalid)) (CATEGORY(action)) (CATEGORY(create)) (CATEGORY(search)); const Category * const Category::categories = &_categories_[1]; const Category &Category::invalid = Category::categories[Category::Enum::invalid]; const Category &Category::action = Category::categories[Category::Enum::action]; const Category &Category::create = Category::categories[Category::Enum::create]; const Category &Category::search = Category::categories[Category::Enum::search]; const Category& Category::find(const std::string &str) { for(int i = Enum::BEGIN; i != Enum::END; ++i) { if(categories[i] == str) return categories[i]; } return invalid; } const Category& Category::find(const Category::Enum::Type i) { if(i >= Category::Enum::BEGIN && i < Category::Enum::END) return categories[i]; return invalid; } mergerfs-2.28.1/src/policy_lfs.cpp0000664000175000017500000000500213471033317015465 0ustar bilebile/* 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.hpp" #include "fs_exists.hpp" #include "fs_info.hpp" #include "fs_path.hpp" #include "policy.hpp" #include "policy_error.hpp" #include #include #include using std::string; using std::vector; namespace lfs { static int create(const Branches &branches_, const uint64_t minfreespace, vector &paths) { int rv; int error; uint64_t lfs; fs::info_t info; const Branch *branch; const string *lfsbasepath; error = ENOENT; lfs = std::numeric_limits::max(); lfsbasepath = NULL; for(size_t i = 0, ei = branches_.size(); i != ei; i++) { branch = &branches_[i]; 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 < minfreespace) error_and_continue(error,ENOSPC); if(info.spaceavail > lfs) continue; lfs = info.spaceavail; lfsbasepath = &branch->path; } if(lfsbasepath == NULL) return (errno=error,-1); paths.push_back(lfsbasepath); return 0; } } int Policy::Func::lfs(const Category::Enum::Type type, const Branches &branches_, const char *fusepath, const uint64_t minfreespace, vector &paths) { if(type == Category::Enum::create) return lfs::create(branches_,minfreespace,paths); return Policy::Func::eplfs(type,branches_,fusepath,minfreespace,paths); } mergerfs-2.28.1/src/fs_cow.hpp0000664000175000017500000000221413471033317014611 0ustar bilebile/* 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.28.1/src/config.cpp0000664000175000017500000001061213475343677014613 0ustar bilebile/* 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.hpp" #include "rwlock.hpp" #include #include #include #include #define MINFREESPACE_DEFAULT (4294967295ULL) #define POLICYINIT(X) X(policies[FuseFunc::Enum::X]) using std::string; using std::vector; Config::Config() : destmount(), branches(), branches_lock(), minfreespace(MINFREESPACE_DEFAULT), moveonenospc(false), direct_io(false), dropcacheonclose(false), symlinkify(false), symlinkify_timeout(3600), nullrw(false), ignorepponrename(false), security_capability(true), link_cow(false), xattr(0), statfs(StatFS::BASE), statfs_ignore(StatFSIgnore::NONE), posix_acl(false), cache_symlinks(false), cache_readdir(false), async_read(true), cache_files(CacheFiles::LIBFUSE), fuse_msg_size(FUSE_MAX_MAX_PAGES), POLICYINIT(access), POLICYINIT(chmod), POLICYINIT(chown), POLICYINIT(create), POLICYINIT(getattr), POLICYINIT(getxattr), POLICYINIT(link), POLICYINIT(listxattr), POLICYINIT(mkdir), POLICYINIT(mknod), POLICYINIT(open), POLICYINIT(readlink), POLICYINIT(removexattr), POLICYINIT(rename), POLICYINIT(rmdir), POLICYINIT(setxattr), POLICYINIT(symlink), POLICYINIT(truncate), POLICYINIT(unlink), POLICYINIT(utimens), controlfile("/.mergerfs") { pthread_rwlock_init(&branches_lock,NULL); set_category_policy("action","epall"); set_category_policy("create","epmfs"); set_category_policy("search","ff"); } int Config::set_func_policy(const string &fusefunc_, const string &policy_) { const Policy *policy; const FuseFunc *fusefunc; fusefunc = FuseFunc::find(fusefunc_); if(fusefunc == FuseFunc::invalid) return (errno=ENODATA,-1); policy = Policy::find(policy_); if(policy == Policy::invalid) return (errno=EINVAL,-1); policies[(FuseFunc::Enum::Type)*fusefunc] = policy; return 0; } int Config::set_category_policy(const string &category_, const string &policy_) { const Policy *policy; const Category *category; category = Category::find(category_); if(category == Category::invalid) return (errno=ENODATA,-1); policy = Policy::find(policy_); if(policy == Policy::invalid) return (errno=EINVAL,-1); for(int i = 0; i < FuseFunc::Enum::END; i++) { if(FuseFunc::fusefuncs[i] == (Category::Enum::Type)*category) policies[(FuseFunc::Enum::Type)FuseFunc::fusefuncs[i]] = policy; } return 0; } Config::CacheFiles::operator int() const { return _data; } Config::CacheFiles::operator std::string() const { switch(_data) { case OFF: return "off"; case PARTIAL: return "partial"; case FULL: return "full"; case AUTO_FULL: return "auto-full"; case LIBFUSE: return "libfuse"; case INVALID: break; } return ""; } Config::CacheFiles::CacheFiles() : _data(INVALID) { } Config::CacheFiles::CacheFiles(Config::CacheFiles::Enum data_) : _data(data_) { } bool Config::CacheFiles::valid() const { return (_data != INVALID); } Config::CacheFiles& Config::CacheFiles::operator=(const Config::CacheFiles::Enum data_) { _data = data_; return *this; } Config::CacheFiles& Config::CacheFiles::operator=(const std::string &data_) { if(data_ == "off") _data = OFF; else if(data_ == "partial") _data = PARTIAL; else if(data_ == "full") _data = FULL; else if(data_ == "auto-full") _data = AUTO_FULL; else if(data_ == "libfuse") _data = LIBFUSE; else _data = INVALID; return *this; } mergerfs-2.28.1/src/fuse_releasedir.hpp0000664000175000017500000000163313471033317016476 0ustar bilebile/* 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 releasedir(const char *fusepath_, fuse_file_info *ffi_); } mergerfs-2.28.1/src/fs_exists.hpp0000664000175000017500000000314713471033317015346 0ustar bilebile/* 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_base_stat.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 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.28.1/src/xattr.hpp0000664000175000017500000000234213471033317014475 0ustar bilebile/* 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.28.1/src/fuse_getxattr.cpp0000664000175000017500000003550413475343677016241 0ustar bilebile/* 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_base_getxattr.hpp" #include "fs_path.hpp" #include "fs_statvfs_cache.hpp" #include "rwlock.hpp" #include "str.hpp" #include "ugid.hpp" #include "version.hpp" #include #include #include #include #include #include #include #include static const char SECURITY_CAPABILITY[] = "security.capability"; using std::string; using std::vector; using std::set; 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 void getxattr_controlfile_fusefunc_policy(const Config &config_, const string &attr_, string &attrvalue_) { FuseFunc fusefunc; fusefunc = FuseFunc::find(attr_); if(fusefunc != FuseFunc::invalid) attrvalue_ = (std::string)*config_.policies[(FuseFunc::Enum::Type)*fusefunc]; } static void getxattr_controlfile_category_policy(const Config &config_, const string &attr_, string &attrvalue_) { Category cat; cat = Category::find(attr_); if(cat != Category::invalid) { vector policies; for(int i = FuseFunc::Enum::BEGIN; i < FuseFunc::Enum::END; i++) { if(cat == (Category::Enum::Type)*FuseFunc::fusefuncs[i]) policies.push_back(*config_.policies[i]); } std::sort(policies.begin(),policies.end()); policies.erase(std::unique(policies.begin(),policies.end()), policies.end()); attrvalue_ = str::join(policies,','); } } static void getxattr_controlfile_srcmounts(const Config &config_, string &attrvalue_) { attrvalue_ = config_.branches.to_string(); } static void getxattr_controlfile_branches(const Config &config_, string &attrvalue_) { attrvalue_ = config_.branches.to_string(true); } static void getxattr_controlfile_uint64_t(const uint64_t uint_, string &attrvalue_) { std::ostringstream os; os << uint_; attrvalue_ = os.str(); } static void getxattr_controlfile_double(const double d_, string &attrvalue_) { std::ostringstream os; os << d_; attrvalue_ = os.str(); } static void getxattr_controlfile_time_t(const time_t time, string &attrvalue) { std::ostringstream os; os << time; attrvalue = os.str(); } static void getxattr_controlfile_bool(const bool boolvalue, string &attrvalue) { attrvalue = (boolvalue ? "true" : "false"); } static void getxattr_controlfile_errno(const int errno_, string &attrvalue) { switch(errno_) { case 0: attrvalue = "passthrough"; break; case ENOATTR: attrvalue = "noattr"; break; case ENOSYS: attrvalue = "nosys"; break; default: attrvalue = "ERROR"; break; } } static void getxattr_controlfile_statfs(const Config::StatFS::Enum enum_, string &attrvalue_) { switch(enum_) { case Config::StatFS::BASE: attrvalue_ = "base"; break; case Config::StatFS::FULL: attrvalue_ = "full"; break; default: attrvalue_ = "ERROR"; break; } } static void getxattr_controlfile_statfsignore(const Config::StatFSIgnore::Enum enum_, string &attrvalue_) { switch(enum_) { case Config::StatFSIgnore::NONE: attrvalue_ = "none"; break; case Config::StatFSIgnore::RO: attrvalue_ = "ro"; break; case Config::StatFSIgnore::NC: attrvalue_ = "nc"; break; default: attrvalue_ = "ERROR"; break; } } static void getxattr_controlfile(const Config::CacheFiles &cache_files_, string &attrvalue_) { attrvalue_ = (string)cache_files_; } static void getxattr_controlfile(const uint16_t &uint16_, string &attrvalue_) { std::ostringstream os; os << uint16_; attrvalue_ = os.str(); } static void getxattr_controlfile_policies(const Config &config, string &attrvalue) { size_t i = Policy::Enum::begin(); attrvalue = (string)Policy::policies[i]; for(i++; i < Policy::Enum::end(); i++) attrvalue += ',' + (string)Policy::policies[i]; } static void getxattr_controlfile_version(string &attrvalue) { attrvalue = MERGERFS_VERSION; if(attrvalue.empty()) attrvalue = "unknown_possible_problem_with_build"; } static void getxattr_controlfile_pid(string &attrvalue) { int pid; char buf[32]; pid = getpid(); snprintf(buf,sizeof(buf),"%d",pid); attrvalue = buf; } static void getxattr_controlfile_cache_attr(string &attrvalue) { double d; d = fuse_config_get_attr_timeout(fuse_get_context()->fuse); l::getxattr_controlfile_double(d,attrvalue); } static void getxattr_controlfile_cache_entry(string &attrvalue) { double d; d = fuse_config_get_entry_timeout(fuse_get_context()->fuse); l::getxattr_controlfile_double(d,attrvalue); } static void getxattr_controlfile_cache_negative_entry(string &attrvalue) { double d; d = fuse_config_get_negative_entry_timeout(fuse_get_context()->fuse); l::getxattr_controlfile_double(d,attrvalue); } static int getxattr_controlfile(const Config &config, const char *attrname, char *buf, const size_t count) { size_t len; string attrvalue; vector attr; str::split(attr,attrname,'.'); if((attr[0] != "user") || (attr[1] != "mergerfs")) return -ENOATTR; switch(attr.size()) { case 3: if(attr[2] == "srcmounts") l::getxattr_controlfile_srcmounts(config,attrvalue); else if(attr[2] == "branches") l::getxattr_controlfile_branches(config,attrvalue); else if(attr[2] == "minfreespace") l::getxattr_controlfile_uint64_t(config.minfreespace,attrvalue); else if(attr[2] == "moveonenospc") l::getxattr_controlfile_bool(config.moveonenospc,attrvalue); else if(attr[2] == "dropcacheonclose") l::getxattr_controlfile_bool(config.dropcacheonclose,attrvalue); else if(attr[2] == "symlinkify") l::getxattr_controlfile_bool(config.symlinkify,attrvalue); else if(attr[2] == "symlinkify_timeout") l::getxattr_controlfile_time_t(config.symlinkify_timeout,attrvalue); else if(attr[2] == "nullrw") l::getxattr_controlfile_bool(config.nullrw,attrvalue); else if(attr[2] == "ignorepponrename") l::getxattr_controlfile_bool(config.ignorepponrename,attrvalue); else if(attr[2] == "security_capability") l::getxattr_controlfile_bool(config.security_capability,attrvalue); else if(attr[2] == "xattr") l::getxattr_controlfile_errno(config.xattr,attrvalue); else if(attr[2] == "link_cow") l::getxattr_controlfile_bool(config.link_cow,attrvalue); else if(attr[2] == "statfs") l::getxattr_controlfile_statfs(config.statfs,attrvalue); else if(attr[2] == "statfs_ignore") l::getxattr_controlfile_statfsignore(config.statfs_ignore,attrvalue); else if(attr[2] == "policies") l::getxattr_controlfile_policies(config,attrvalue); else if(attr[2] == "version") l::getxattr_controlfile_version(attrvalue); else if(attr[2] == "pid") l::getxattr_controlfile_pid(attrvalue); else if(attr[2] == "direct_io") l::getxattr_controlfile_bool(config.direct_io,attrvalue); else if(attr[2] == "posix_acl") l::getxattr_controlfile_bool(config.posix_acl,attrvalue); else if(attr[2] == "async_read") l::getxattr_controlfile_bool(config.async_read,attrvalue); else if(attr[2] == "fuse_msg_size") l::getxattr_controlfile(config.fuse_msg_size,attrvalue); break; case 4: if(attr[2] == "category") l::getxattr_controlfile_category_policy(config,attr[3],attrvalue); else if(attr[2] == "func") l::getxattr_controlfile_fusefunc_policy(config,attr[3],attrvalue); else if((attr[2] == "cache") && (attr[3] == "open")) l::getxattr_controlfile_uint64_t(config.open_cache.timeout,attrvalue); else if((attr[2] == "cache") && (attr[3] == "statfs")) l::getxattr_controlfile_uint64_t(fs::statvfs_cache_timeout(),attrvalue); else if((attr[2] == "cache") && (attr[3] == "attr")) l::getxattr_controlfile_cache_attr(attrvalue); else if((attr[2] == "cache") && (attr[3] == "entry")) l::getxattr_controlfile_cache_entry(attrvalue); else if((attr[2] == "cache") && (attr[3] == "negative_entry")) l::getxattr_controlfile_cache_negative_entry(attrvalue); else if((attr[2] == "cache") && (attr[3] == "symlinks")) l::getxattr_controlfile_bool(config.cache_symlinks,attrvalue); else if((attr[2] == "cache") && (attr[3] == "readdir")) l::getxattr_controlfile_bool(config.cache_readdir,attrvalue); else if((attr[2] == "cache") && (attr[3] == "files")) l::getxattr_controlfile(config.cache_files,attrvalue); break; } if(attrvalue.empty()) return -ENOATTR; len = attrvalue.size(); if(count == 0) return len; if(count < len) return -ERANGE; memcpy(buf,attrvalue.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 &branches_, const char *fusepath, char *buf, const size_t count) { string concated; vector paths; vector 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) { vector attr; str::split(attr,attrname,'.'); 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(Policy::Func::Search searchFunc, const Branches &branches_, const size_t minfreespace, const char *fusepath, const char *attrname, char *buf, const size_t count) { int rv; string fullpath; vector basepaths; rv = searchFunc(branches_,fusepath,minfreespace,basepaths); if(rv == -1) return -errno; fullpath = fs::path::make(basepaths[0],fusepath); if(str::isprefix(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) { const fuse_context *fc = fuse_get_context(); const Config &config = Config::get(fc); if(fusepath == config.controlfile) return l::getxattr_controlfile(config, attrname, buf, count); if((config.security_capability == false) && l::is_attrname_security_capability(attrname)) return -ENOATTR; if(config.xattr) return -config.xattr; const ugid::Set ugid(fc->uid,fc->gid); const rwlock::ReadGuard readlock(&config.branches_lock); return l::getxattr(config.getxattr, config.branches, config.minfreespace, fusepath, attrname, buf, count); } } mergerfs-2.28.1/src/fs_sendfile_linux.icpp0000664000175000017500000000201513367322163017200 0ustar bilebile/* 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 "errno.hpp" 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.28.1/src/fuse_destroy.cpp0000664000175000017500000000151213471033317016037 0ustar bilebile/* 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.28.1/src/fuse_symlink.cpp0000664000175000017500000000710113471033317016034 0ustar bilebile/* 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_base_symlink.hpp" #include "fs_clonepath.hpp" #include "fs_path.hpp" #include "rv.hpp" #include "rwlock.hpp" #include "ugid.hpp" #include #include #include #include using std::string; using std::vector; namespace l { static int symlink_loop_core(const string &newbasepath_, const char *oldpath_, const char *newpath_, const int error_) { int rv; string fullnewpath; fullnewpath = fs::path::make(&newbasepath_,newpath_); rv = fs::symlink(oldpath_,fullnewpath); return error::calc(rv,error_,errno); } static int symlink_loop(const string &existingpath_, const vector newbasepaths_, const char *oldpath_, const char *newpath_, const string &newdirpath_) { 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], oldpath_, newpath_, error); } return -error; } static int symlink(Policy::Func::Search searchFunc_, Policy::Func::Create createFunc_, const Branches &branches_, const uint64_t minfreespace_, const char *oldpath_, const char *newpath_) { int rv; string newdirpath; vector newbasepaths; vector existingpaths; newdirpath = fs::path::dirname(newpath_); rv = searchFunc_(branches_,newdirpath,minfreespace_,existingpaths); if(rv == -1) return -errno; rv = createFunc_(branches_,newdirpath,minfreespace_,newbasepaths); if(rv == -1) return -errno; return l::symlink_loop(*existingpaths[0],newbasepaths, oldpath_,newpath_,newdirpath); } } namespace FUSE { int symlink(const char *oldpath_, const char *newpath_) { const fuse_context *fc = fuse_get_context(); const Config &config = Config::get(fc); const ugid::Set ugid(fc->uid,fc->gid); const rwlock::ReadGuard readlock(&config.branches_lock); return l::symlink(config.getattr, config.symlink, config.branches, config.minfreespace, oldpath_, newpath_); } } mergerfs-2.28.1/src/fs_cow.cpp0000664000175000017500000000563413471033317014615 0ustar bilebile/* 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_clonefile.hpp" #include "fs_mktemp.hpp" #include "fs_base_close.hpp" #include "fs_base_open.hpp" #include "fs_base_rename.hpp" #include "fs_base_stat.hpp" #include "fs_base_unlink.hpp" #include #include #include #include #include #include using std::string; 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_) { return ((flags_ & O_RDWR) || (flags_ & 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(!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 cleanup_on_error(src_fd); rv = fs::clonefile(src_fd,dst_fd); if(rv == -1) return cleanup_on_error(src_fd,dst_fd,dst_fullpath); rv = fs::rename(dst_fullpath,src_fullpath_); if(rv == -1) return cleanup_on_error(src_fd,dst_fd,dst_fullpath); fs::close(src_fd); fs::close(dst_fd); return 0; } } } mergerfs-2.28.1/src/fuse_removexattr.cpp0000664000175000017500000000567113471033317016740 0ustar bilebile/* 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_base_removexattr.hpp" #include "fs_path.hpp" #include "rv.hpp" #include "rwlock.hpp" #include "ugid.hpp" #include #include #include using std::string; using std::vector; namespace l { static int removexattr_loop_core(const string *basepath_, const char *fusepath_, const char *attrname_, const int error_) { int rv; string fullpath; fullpath = fs::path::make(basepath_,fusepath_); rv = fs::lremovexattr(fullpath,attrname_); return error::calc(rv,error_,errno); } static int removexattr_loop(const vector &basepaths_, const char *fusepath_, const char *attrname_) { int error; error = -1; for(size_t i = 0, ei = basepaths_.size(); i != ei; i++) { error = l::removexattr_loop_core(basepaths_[i],fusepath_,attrname_,error); } return -error; } static int removexattr(Policy::Func::Action actionFunc_, const Branches &branches_, const uint64_t minfreespace_, const char *fusepath_, const char *attrname_) { int rv; vector basepaths; rv = actionFunc_(branches_,fusepath_,minfreespace_,basepaths); if(rv == -1) return -errno; return l::removexattr_loop(basepaths,fusepath_,attrname_); } } namespace FUSE { int removexattr(const char *fusepath_, const char *attrname_) { const fuse_context *fc = fuse_get_context(); const Config &config = Config::get(fc); if(fusepath_ == config.controlfile) return -ENOATTR; if(config.xattr) return -config.xattr; const ugid::Set ugid(fc->uid,fc->gid); const rwlock::ReadGuard readlock(&config.branches_lock); return l::removexattr(config.removexattr, config.branches, config.minfreespace, fusepath_, attrname_); } } mergerfs-2.28.1/src/fuse_fallocate.cpp0000664000175000017500000000301413471033317016277 0ustar bilebile/* 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_base_fallocate.hpp" #include 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 char *fusepath_, int mode_, off_t offset_, off_t len_, fuse_file_info *ffi_) { FileInfo *fi = reinterpret_cast(ffi_->fh); return l::fallocate(fi->fd, mode_, offset_, len_); } } mergerfs-2.28.1/src/fs_base_access.hpp0000664000175000017500000000267613471033317016270 0ustar bilebile/* 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 access(const int dirfd_, const std::string &path_, const int mode_, const int flags_) { return ::faccessat(dirfd_,path_.c_str(),mode_,flags_); } static inline int access(const std::string &path_, const int mode_, const int flags_) { return fs::access(AT_FDCWD,path_,mode_,flags_); } static inline int eaccess(const std::string &path_, const int mode_) { return fs::access(path_,mode_,AT_EACCESS); } } mergerfs-2.28.1/src/policy_rand.cpp0000664000175000017500000000254613471033317015637 0ustar bilebile/* 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 #include #include using std::string; using std::vector; int Policy::Func::rand(const Category::Enum::Type type, const Branches &branches_, const char *fusepath, const uint64_t minfreespace, vector &paths) { int rv; rv = Policy::Func::all(type,branches_,fusepath,minfreespace,paths); if(rv == 0) std::random_shuffle(paths.begin(),paths.end()); return rv; } mergerfs-2.28.1/src/fs_base_utime_utimensat.hpp0000664000175000017500000000240213471033317020226 0ustar bilebile/* 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 utime(const int dirfd, const std::string &path, const struct timespec times[2], const int flags) { return ::utimensat(dirfd,path.c_str(),times,flags); } static inline int futimens(const int fd_, const struct timespec ts_[2]) { return ::futimens(fd_,ts_); } } mergerfs-2.28.1/src/fuse_ftruncate.hpp0000664000175000017500000000175013471033317016352 0ustar bilebile/* 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 ftruncate(const char *fusepath_, off_t size_, fuse_file_info *ffi_); } mergerfs-2.28.1/src/fuse_prepare_hide.cpp0000664000175000017500000000221513471136565017007 0ustar bilebile/* 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; struct fuse_file_info 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.28.1/src/fs_base_close.hpp0000664000175000017500000000165013471033317016123 0ustar bilebile/* 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.28.1/src/fs_base_removexattr.hpp0000664000175000017500000000215513471033317017377 0ustar bilebile/* 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.28.1/src/fs_base_setxattr.hpp0000664000175000017500000000364413471033317016701 0ustar bilebile/* 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 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_); } 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 } } mergerfs-2.28.1/src/fuse_read.hpp0000664000175000017500000000224713471033317015274 0ustar bilebile/* 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 read(const char *fusepath_, char *buf_, size_t count_, off_t offset_, fuse_file_info *ffi_); int read_null(const char *fusepath_, char *buf_, size_t count_, off_t offset_, fuse_file_info *ffi_); } mergerfs-2.28.1/src/fuse_read_buf.hpp0000664000175000017500000000205713471033317016127 0ustar bilebile/* 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 FUSE { int read_buf(const char *fusepath_, struct fuse_bufvec **buf_, size_t size_, off_t offset_, fuse_file_info *ffi_); } mergerfs-2.28.1/src/fuse_init.hpp0000664000175000017500000000156113471033317015322 0ustar bilebile/* 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 { void * init(fuse_conn_info *conn_); } mergerfs-2.28.1/src/fuse_write.cpp0000664000175000017500000000642313471033317015506 0ustar bilebile/* 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_base_write.hpp" #include "fs_movefile.hpp" #include "rwlock.hpp" #include "ugid.hpp" #include #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 write(WriteFunc func_, const char *buf_, const size_t count_, const off_t offset_, fuse_file_info *ffi_) { int rv; FileInfo* fi; fi = reinterpret_cast(ffi_->fh); rv = func_(fi->fd,buf_,count_,offset_); if(l::out_of_space(-rv)) { const fuse_context *fc = fuse_get_context(); const Config &config = Config::get(fc); if(config.moveonenospc) { vector paths; const ugid::Set ugid(0,0); const rwlock::ReadGuard readlock(&config.branches_lock); config.branches.to_paths(paths); rv = fs::movefile(paths,fi->fusepath,count_,fi->fd); if(rv == -1) return -ENOSPC; rv = func_(fi->fd,buf_,count_,offset_); } } return rv; } } namespace FUSE { int write(const char *fusepath_, const char *buf_, size_t count_, off_t offset_, fuse_file_info *ffi_) { WriteFunc wf; wf = ((ffi_->direct_io) ? l::write_direct_io : l::write_regular); return l::write(wf,buf_,count_,offset_,ffi_); } int write_null(const char *fusepath_, const char *buf_, size_t count_, off_t offset_, fuse_file_info *ffi_) { return count_; } } mergerfs-2.28.1/src/policy_error.hpp0000664000175000017500000000261113467101456016047 0ustar bilebile/* 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.28.1/src/fuse_fsyncdir.cpp0000664000175000017500000000251613471033317016174 0ustar bilebile/* 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_base_fsync.hpp" #include #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 char *fusepath_, int isdatasync_, fuse_file_info *ffi_) { DirInfo *di = reinterpret_cast(ffi_->fh); return l::fsyncdir(di,isdatasync_); } } mergerfs-2.28.1/src/fuse_opendir.cpp0000664000175000017500000000225113471653145016016 0ustar bilebile/* 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 namespace FUSE { int opendir(const char *fusepath_, fuse_file_info *ffi_) { const Config &config = Config::get(); ffi_->fh = reinterpret_cast(new DirInfo(fusepath_)); if(config.cache_readdir) { ffi_->keep_cache = 1; ffi_->cache_readdir = 1; } return 0; } } mergerfs-2.28.1/src/fuse_write_buf.hpp0000664000175000017500000000230413471033317016341 0ustar bilebile/* 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 FUSE { int write_buf(const char *fusepath_, struct fuse_bufvec *buf_, off_t offset_, fuse_file_info *ffi_); int write_buf_null(const char *fusepath_, struct fuse_bufvec *buf_, off_t offset_, fuse_file_info *ffi_); } mergerfs-2.28.1/src/str.hpp0000664000175000017500000000367413467101456014161 0ustar bilebile/* 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 str { void split(std::vector &result, const char *str, const char delimiter); void split(std::vector &result, const std::string &str, const char delimiter); std::string join(const std::vector &vec, const size_t substridx, const char sep); std::string join(const std::vector &vec, 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 ends_with(const std::string &str_, const std::string &suffix_); } mergerfs-2.28.1/src/fs_base_read.hpp0000664000175000017500000000227013471033317015730 0ustar bilebile/* 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.28.1/src/fuse_create.hpp0000664000175000017500000000171313471033317015621 0ustar bilebile/* 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 FUSE { int create(const char *fusepath_, mode_t mode_, fuse_file_info *ffi_); } mergerfs-2.28.1/src/fs_base_listxattr.hpp0000664000175000017500000000310013471033317017044 0ustar bilebile/* 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_); } 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.28.1/src/option_parser.hpp0000664000175000017500000000163713471033317016225 0ustar bilebile/* 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 namespace options { void parse(fuse_args *args, Config *config); } mergerfs-2.28.1/src/fuse_mkdir.hpp0000664000175000017500000000156713471033317015473 0ustar bilebile/* 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.28.1/src/version.hpp0000664000175000017500000000007613477573310015032 0ustar bilebile#pragma once static const char MERGERFS_VERSION[] = "2.28.1"; mergerfs-2.28.1/src/fuse_unlink.cpp0000664000175000017500000000505513471033317015654 0ustar bilebile/* 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_base_unlink.hpp" #include "fs_path.hpp" #include "rv.hpp" #include "rwlock.hpp" #include "ugid.hpp" #include #include #include #include using std::string; using std::vector; 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 = -1; 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(Policy::Func::Action actionFunc_, const Branches &branches_, const uint64_t minfreespace_, const char *fusepath_) { int rv; vector basepaths; rv = actionFunc_(branches_,fusepath_,minfreespace_,basepaths); if(rv == -1) return -errno; return l::unlink_loop(basepaths,fusepath_); } } namespace FUSE { int unlink(const char *fusepath_) { const fuse_context *fc = fuse_get_context(); const Config &config = Config::get(fc); const ugid::Set ugid(fc->uid,fc->gid); const rwlock::ReadGuard readlock(&config.branches_lock); config.open_cache.erase(fusepath_); return l::unlink(config.unlink, config.branches, config.minfreespace, fusepath_); } } mergerfs-2.28.1/src/symlinkify.hpp0000664000175000017500000000247313427205466015545 0ustar bilebile/* 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.28.1/src/fs_base_ioctl.hpp0000664000175000017500000000251313471033317016127 0ustar bilebile/* 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.28.1/src/fuse_fsync.cpp0000664000175000017500000000254713471033317015501 0ustar bilebile/* 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_base_fsync.hpp" #include #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 char *fusepath_, int isdatasync_, fuse_file_info *ffi_) { FileInfo *fi = reinterpret_cast(ffi_->fh); return l::fsync(fi->fd,isdatasync_); } } mergerfs-2.28.1/src/fuse_mknod.cpp0000664000175000017500000001015613471033317015462 0ustar bilebile/* 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_base_mknod.hpp" #include "fs_clonepath.hpp" #include "fs_path.hpp" #include "rv.hpp" #include "rwlock.hpp" #include "ugid.hpp" #include #include #include using std::string; using std::vector; 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(Policy::Func::Search searchFunc_, Policy::Func::Create createFunc_, const Branches &branches_, const uint64_t minfreespace_, 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,minfreespace_,existingpaths); if(rv == -1) return -errno; rv = createFunc_(branches_,fusedirpath,minfreespace_,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_) { const fuse_context *fc = fuse_get_context(); const Config &config = Config::get(fc); const ugid::Set ugid(fc->uid,fc->gid); const rwlock::ReadGuard readlock(&config.branches_lock); return l::mknod(config.getattr, config.mknod, config.branches, config.minfreespace, fusepath_, mode_, fc->umask, rdev_); } } mergerfs-2.28.1/src/fuse_flock.cpp0000664000175000017500000000240313471033317015444 0ustar bilebile/* 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_base_flock.hpp" #include 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 char *fusepath_, fuse_file_info *ffi_, int op_) { FileInfo* fi = reinterpret_cast(ffi_->fh); return l::flock(fi->fd,op_); } } mergerfs-2.28.1/src/fs_mktemp.cpp0000664000175000017500000000314713467103610015316 0ustar bilebile/* 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_base_open.hpp" #include #include #include using std::string; static string generate_tmp_path(const string &base_) { string tmp; tmp = base_; tmp += '_'; for(int i = 0; i < 6; 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 = 10; 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.28.1/src/fs_clonefile.cpp0000664000175000017500000000527613471033317015767 0ustar bilebile/* 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_attr.hpp" #include "fs_base_chmod.hpp" #include "fs_base_chown.hpp" #include "fs_base_fadvise.hpp" #include "fs_base_fallocate.hpp" #include "fs_base_fchmod.hpp" #include "fs_base_fchown.hpp" #include "fs_base_ftruncate.hpp" #include "fs_base_stat.hpp" #include "fs_base_utime.hpp" #include "fs_copy_file_range.hpp" #include "fs_copyfile.hpp" #include "fs_ficlone.hpp" #include "fs_sendfile.hpp" #include "fs_xattr.hpp" static 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::copy_file_range(src_fd_,dst_fd_,count_); if(rv != -1) return rv; rv = fs::sendfile(src_fd_,dst_fd_,count_); if(rv != -1) return rv; return fs::copyfile(src_fd_,dst_fd_); } 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 = ::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) && !ignorable_error(errno)) return -1; rv = fs::xattr::copy(src_fd_,dst_fd_); if((rv == -1) && !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::futime(dst_fd_,src_st); if(rv == -1) return -1; return 0; } } mergerfs-2.28.1/src/fuse_chmod.hpp0000664000175000017500000000156713471033317015457 0ustar bilebile/* 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.28.1/src/fs_base_fadvise_posix.icpp0000664000175000017500000000201213467103610020015 0ustar bilebile/* 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 "errno.hpp" 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.28.1/src/fuse_link.cpp0000664000175000017500000002022513471033317015305 0ustar bilebile/* 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_base_link.hpp" #include "fs_clonepath.hpp" #include "fs_path.hpp" #include "rv.hpp" #include "rwlock.hpp" #include "ugid.hpp" #include #include #include using std::string; using std::vector; namespace l { static int link_create_path_core(const string &oldbasepath_, const string &newbasepath_, const char *oldfusepath_, const char *newfusepath_, 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); return error::calc(rv,error_,errno); } static int link_create_path_loop(const vector &oldbasepaths_, const string &newbasepath_, const char *oldfusepath_, const char *newfusepath_, const string &newfusedirpath_) { int rv; int error; error = -1; for(size_t i = 0, ei = oldbasepaths_.size(); i != ei; i++) { rv = fs::clonepath_as_root(newbasepath_,*oldbasepaths_[i],newfusedirpath_); if(rv == -1) error = error::calc(rv,error,errno); else error = l::link_create_path_core(*oldbasepaths_[i],newbasepath_, oldfusepath_,newfusepath_, error); } return -error; } static int link_create_path(Policy::Func::Search searchFunc_, Policy::Func::Action actionFunc_, const Branches &branches_, const uint64_t minfreespace_, const char *oldfusepath_, const char *newfusepath_) { int rv; string newfusedirpath; vector oldbasepaths; vector newbasepaths; rv = actionFunc_(branches_,oldfusepath_,minfreespace_,oldbasepaths); if(rv == -1) return -errno; newfusedirpath = fs::path::dirname(newfusepath_); rv = searchFunc_(branches_,newfusedirpath,minfreespace_,newbasepaths); if(rv == -1) return -errno; return l::link_create_path_loop(oldbasepaths,*newbasepaths[0], oldfusepath_,newfusepath_, newfusedirpath); } static int clonepath_if_would_create(Policy::Func::Search searchFunc_, Policy::Func::Create createFunc_, const Branches &branches_, const uint64_t minfreespace_, const string &oldbasepath_, const char *oldfusepath_, const char *newfusepath_) { int rv; string newfusedirpath; vector newbasepath; newfusedirpath = fs::path::dirname(newfusepath_); rv = createFunc_(branches_,newfusedirpath,minfreespace_,newbasepath); if(rv == -1) return -1; if(oldbasepath_ != *newbasepath[0]) return (errno=EXDEV,-1); rv = searchFunc_(branches_,newfusedirpath,minfreespace_,newbasepath); if(rv == -1) return -1; return fs::clonepath_as_root(*newbasepath[0],oldbasepath_,newfusedirpath); } static int link_preserve_path_core(Policy::Func::Search searchFunc_, Policy::Func::Create createFunc_, const Branches &branches_, const uint64_t minfreespace_, const string &oldbasepath_, const char *oldfusepath_, const char *newfusepath_, 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)) { rv = l::clonepath_if_would_create(searchFunc_,createFunc_, branches_,minfreespace_, oldbasepath_, oldfusepath_,newfusepath_); if(rv != -1) rv = fs::link(oldfullpath,newfullpath); } return error::calc(rv,error_,errno); } static int link_preserve_path_loop(Policy::Func::Search searchFunc_, Policy::Func::Create createFunc_, const Branches &branches_, const uint64_t minfreespace_, const char *oldfusepath_, const char *newfusepath_, const vector &oldbasepaths_) { int error; error = -1; for(size_t i = 0, ei = oldbasepaths_.size(); i != ei; i++) { error = l::link_preserve_path_core(searchFunc_,createFunc_, branches_,minfreespace_, *oldbasepaths_[i], oldfusepath_,newfusepath_, error); } return -error; } static int link_preserve_path(Policy::Func::Search searchFunc_, Policy::Func::Action actionFunc_, Policy::Func::Create createFunc_, const Branches &branches_, const uint64_t minfreespace_, const char *oldfusepath_, const char *newfusepath_) { int rv; vector oldbasepaths; rv = actionFunc_(branches_,oldfusepath_,minfreespace_,oldbasepaths); if(rv == -1) return -errno; return l::link_preserve_path_loop(searchFunc_,createFunc_, branches_,minfreespace_, oldfusepath_,newfusepath_, oldbasepaths); } } namespace FUSE { int link(const char *from_, const char *to_) { const fuse_context *fc = fuse_get_context(); const Config &config = Config::get(fc); const ugid::Set ugid(fc->uid,fc->gid); const rwlock::ReadGuard readlock(&config.branches_lock); if(config.create->path_preserving() && !config.ignorepponrename) return l::link_preserve_path(config.getattr, config.link, config.create, config.branches, config.minfreespace, from_, to_); return l::link_create_path(config.link, config.create, config.branches, config.minfreespace, from_, to_); } } mergerfs-2.28.1/src/fs.hpp0000664000175000017500000000301613471033317013742 0ustar bilebile/* 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 { using std::string; using std::vector; void findallfiles(const vector &basepaths_, const char *fusepath_, vector &paths_); int findonfs(const vector &basepaths_, const string &fusepath_, const int fd_, string &basepath_); void realpathize(vector &strs_); int getfl(const int fd_); int setfl(const int fd_, const mode_t mode_); int mfs(const vector &srcs_, const uint64_t minfreespace_, string &path_); } mergerfs-2.28.1/src/fuse_fchmod.cpp0000664000175000017500000000244713471033317015616 0ustar bilebile/* 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_base_fchmod.hpp" #include #include 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 struct fuse_file_info *ffi_, const mode_t mode_) { FileInfo *fi = reinterpret_cast(ffi_->fh); return l::fchmod(fi->fd,mode_); } } mergerfs-2.28.1/src/assert.hpp0000664000175000017500000000206613427205466014646 0ustar bilebile/* 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.28.1/src/fuse_ioctl.cpp0000664000175000017500000001222313471033317015461 0ustar bilebile/* 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_base_close.hpp" #include "fs_base_ioctl.hpp" #include "fs_base_open.hpp" #include "fs_path.hpp" #include "rwlock.hpp" #include "ugid.hpp" #include #include #include #include using std::string; using std::vector; #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 thats 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(fuse_file_info *ffi_, const uint32_t cmd_, void *data_, uint32_t *out_bufsz_) { FileInfo *fi = reinterpret_cast(ffi_->fh); return l::ioctl(fi->fd,cmd_,data_,out_bufsz_); } #ifndef O_NOATIME #define O_NOATIME 0 #endif static int ioctl_dir_base(Policy::Func::Search searchFunc_, const Branches &branches_, const uint64_t minfreespace_, const char *fusepath_, const uint32_t cmd_, void *data_, uint32_t *out_bufsz_) { int fd; int rv; string fullpath; vector basepaths; rv = searchFunc_(branches_,fusepath_,minfreespace_,basepaths); if(rv == -1) return -errno; fullpath = fs::path::make(basepaths[0],fusepath_); const int flags = O_RDONLY | O_NOATIME | O_NONBLOCK; fd = fs::open(fullpath,flags); if(fd == -1) return -errno; rv = l::ioctl(fd,cmd_,data_,out_bufsz_); fs::close(fd); return rv; } static int ioctl_dir(fuse_file_info *ffi_, const uint32_t cmd_, void *data_, uint32_t *out_bufsz_) { DirInfo *di = reinterpret_cast(ffi_->fh); const fuse_context *fc = fuse_get_context(); const Config &config = Config::get(fc); const ugid::Set ugid(fc->uid,fc->gid); const rwlock::ReadGuard readlock(&config.branches_lock); return l::ioctl_dir_base(config.open, config.branches, config.minfreespace, di->fusepath.c_str(), cmd_, data_, out_bufsz_); } } namespace FUSE { int ioctl(const char *fusepath_, int cmd_, void *arg_, fuse_file_info *ffi_, unsigned int flags_, void *data_, uint32_t *out_bufsz_) { 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.28.1/src/policy_eprand.cpp0000664000175000017500000000256213471033317016162 0ustar bilebile/* 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 #include #include using std::string; using std::vector; int Policy::Func::eprand(const Category::Enum::Type type, const Branches &branches_, const char *fusepath, const uint64_t minfreespace, vector &paths) { int rv; rv = Policy::Func::epall(type,branches_,fusepath,minfreespace,paths); if(rv == 0) std::random_shuffle(paths.begin(),paths.end()); return rv; } mergerfs-2.28.1/src/policy.hpp0000664000175000017500000001265513471033317014642 0ustar bilebile/* 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 "branch.hpp" #include "category.hpp" #include "fs.hpp" #include #include #include class Policy { public: struct Enum { enum Type { invalid = -1, BEGIN = 0, all = BEGIN, epall, epff, eplfs, eplus, epmfs, eprand, erofs, ff, lfs, lus, mfs, newest, rand, END }; static size_t begin() { return BEGIN; } static size_t end() { return END; } }; struct Func { typedef std::string string; typedef std::vector strvec; typedef std::vector cstrptrvec; typedef const string cstring; typedef const uint64_t cuint64_t; typedef const strvec cstrvec; typedef const Category::Enum::Type CType; typedef int (*Ptr)(CType,const Branches &,const char *,cuint64_t,cstrptrvec &); template class Base { public: Base(const Policy *p) : func(p->_func) {} int operator()(const Branches &b,const char *c,cuint64_t d,cstrptrvec &e) { return func(T,b,c,d,e); } int operator()(const Branches &b,const string &c,cuint64_t d,cstrptrvec &e) { return func(T,b,c.c_str(),d,e); } int operator()(const Branches &b,const char *c,cuint64_t d,string *e) { int rv; cstrptrvec v; rv = func(T,b,c,d,v); if(!v.empty()) *e = *v[0]; return rv; } private: const Ptr func; }; typedef Base Action; typedef Base Create; typedef Base Search; static int invalid(CType,const Branches&,const char *,cuint64_t,cstrptrvec&); static int all(CType,const Branches&,const char*,cuint64_t,cstrptrvec&); static int epall(CType,const Branches&,const char*,cuint64_t,cstrptrvec&); static int epff(CType,const Branches&,const char *,cuint64_t,cstrptrvec&); static int eplfs(CType,const Branches&,const char *,cuint64_t,cstrptrvec&); static int eplus(CType,const Branches&,const char *,cuint64_t,cstrptrvec&); static int epmfs(CType,const Branches&,const char *,cuint64_t,cstrptrvec&); static int eprand(CType,const Branches&,const char *,cuint64_t,cstrptrvec&); static int erofs(CType,const Branches&,const char *,cuint64_t,cstrptrvec&); static int ff(CType,const Branches&,const char *,cuint64_t,cstrptrvec&); static int lfs(CType,const Branches&,const char *,cuint64_t,cstrptrvec&); static int lus(CType,const Branches&,const char *,cuint64_t,cstrptrvec&); static int mfs(CType,const Branches&,const char *,cuint64_t,cstrptrvec&); static int newest(CType,const Branches&,const char *,cuint64_t,cstrptrvec&); static int rand(CType,const Branches&,const char *,cuint64_t,cstrptrvec&); }; private: Enum::Type _enum; std::string _str; Func::Ptr _func; bool _path_preserving; public: Policy() : _enum(invalid), _str(invalid), _func(invalid), _path_preserving(false) { } Policy(const Enum::Type enum_, const std::string &str_, const Func::Ptr func_, const bool path_preserving_) : _enum(enum_), _str(str_), _func(func_), _path_preserving(path_preserving_) { } bool path_preserving() const { return _path_preserving; } public: operator const Enum::Type() const { return _enum; } operator const std::string&() const { return _str; } operator const Func::Ptr() const { return _func; } operator const Policy*() const { return this; } bool operator==(const Enum::Type enum_) const { return _enum == enum_; } bool operator==(const std::string &str_) const { return _str == str_; } bool operator==(const Func::Ptr func_) const { return _func == func_; } bool operator!=(const Policy &r) const { return _enum != r._enum; } bool operator<(const Policy &r) const { return _enum < r._enum; } public: static const Policy &find(const std::string&); static const Policy &find(const Enum::Type); public: static const std::vector _policies_; static const Policy * const policies; static const Policy &invalid; static const Policy &all; static const Policy &epall; static const Policy &epff; static const Policy &eplfs; static const Policy ⩱ static const Policy &epmfs; static const Policy &eprand; static const Policy &erofs; static const Policy &ff; static const Policy &lfs; static const Policy &lus; static const Policy &mfs; static const Policy &newest; static const Policy &rand; }; mergerfs-2.28.1/src/category.hpp0000664000175000017500000000415213471033317015151 0ustar bilebile/* 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 class Category { public: struct Enum { enum Type { invalid = -1, BEGIN = 0, action = BEGIN, create, search, END }; }; private: Enum::Type _enum; std::string _str; public: Category() : _enum(invalid), _str(invalid) { } Category(const Enum::Type enum_, const std::string &str_) : _enum(enum_), _str(str_) { } public: operator const Enum::Type() const { return _enum; } operator const std::string&() const { return _str; } operator const Category*() const { return this; } bool operator==(const std::string &str_) const { return _str == str_; } bool operator==(const Enum::Type enum_) const { return _enum == enum_; } bool operator!=(const Category &r) const { return _enum != r._enum; } bool operator<(const Category &r) const { return _enum < r._enum; } public: static const Category &find(const std::string&); static const Category &find(const Enum::Type); public: static const std::vector _categories_; static const Category * const categories; static const Category &invalid; static const Category &action; static const Category &create; static const Category &search; }; mergerfs-2.28.1/src/fuse_prepare_hide.hpp0000664000175000017500000000164313471136565017020 0ustar bilebile/* 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.28.1/src/config.hpp0000664000175000017500000000743713475343677014633 0ustar bilebile/* 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 "branch.hpp" #include "fusefunc.hpp" #include "policy.hpp" #include "policy_cache.hpp" #include #include #include #include #include class Config { public: struct StatFS { enum Enum { BASE, FULL }; }; struct StatFSIgnore { enum Enum { NONE, RO, NC }; }; class CacheFiles { public: enum Enum { INVALID = -1, LIBFUSE, OFF, PARTIAL, FULL, AUTO_FULL }; CacheFiles(); CacheFiles(Enum); operator int() const; operator std::string() const; CacheFiles& operator=(const Enum); CacheFiles& operator=(const std::string&); bool valid() const; private: Enum _data; }; public: Config(); public: int set_func_policy(const std::string &fusefunc_, const std::string &policy_); int set_category_policy(const std::string &category_, const std::string &policy_); public: std::string fsname; std::string destmount; Branches branches; mutable pthread_rwlock_t branches_lock; uint64_t minfreespace; bool moveonenospc; bool direct_io; bool kernel_cache; bool auto_cache; bool dropcacheonclose; bool symlinkify; time_t symlinkify_timeout; bool nullrw; bool ignorepponrename; bool security_capability; bool link_cow; int xattr; StatFS::Enum statfs; StatFSIgnore::Enum statfs_ignore; bool posix_acl; bool cache_symlinks; bool cache_readdir; bool async_read; CacheFiles cache_files; uint16_t fuse_msg_size; public: const Policy *policies[FuseFunc::Enum::END]; const Policy *&access; const Policy *&chmod; const Policy *&chown; const Policy *&create; const Policy *&getattr; const Policy *&getxattr; const Policy *&link; const Policy *&listxattr; const Policy *&mkdir; const Policy *&mknod; const Policy *&open; const Policy *&readlink; const Policy *&removexattr; const Policy *&rename; const Policy *&rmdir; const Policy *&setxattr; const Policy *&symlink; const Policy *&truncate; const Policy *&unlink; const Policy *&utimens; public: mutable PolicyCache open_cache; public: const std::string controlfile; public: static const Config & get(void) { const fuse_context *fc = fuse_get_context(); return get(fc); } static const Config & get(const fuse_context *fc) { return *((Config*)fc->private_data); } static Config & get_writable(void) { return (*((Config*)fuse_get_context()->private_data)); } }; mergerfs-2.28.1/src/ugid.cpp0000664000175000017500000000211513427205466014263 0ustar bilebile/* 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.28.1/src/fs_base_getxattr.hpp0000664000175000017500000000443213471033317016661 0ustar bilebile/* 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 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_); } 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.28.1/src/policy_all.cpp0000664000175000017500000000435213471033317015460 0ustar bilebile/* 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 "policy_error.hpp" #include #include using std::string; using std::vector; namespace all { static int create(const Branches &branches_, const uint64_t minfreespace, vector &paths) { int rv; int error; fs::info_t info; const Branch *branch; error = ENOENT; for(size_t i = 0, ei = branches_.size(); i != ei; i++) { branch = &branches_[i]; 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 < minfreespace) error_and_continue(error,ENOSPC); paths.push_back(&branch->path); } if(paths.empty()) return (errno=error,-1); return 0; } } int Policy::Func::all(const Category::Enum::Type type, const Branches &branches_, const char *fusepath, const uint64_t minfreespace, vector &paths) { if(type == Category::Enum::create) return all::create(branches_,minfreespace,paths); return Policy::Func::epall(type,branches_,fusepath,minfreespace,paths); } mergerfs-2.28.1/src/fs_xattr.hpp0000664000175000017500000000361613427205466015201 0ustar bilebile/* 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.28.1/src/fs_sendfile.cpp0000664000175000017500000000157713367322163015624 0ustar bilebile/* 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__ # include "fs_sendfile_linux.icpp" #else # include "fs_sendfile_unsupported.icpp" #endif mergerfs-2.28.1/src/fs_ficlone_unsupported.icpp0000664000175000017500000000166513467101456020272 0ustar bilebile/* 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.28.1/src/fuse_fchmod.hpp0000664000175000017500000000170013471033317015612 0ustar bilebile/* 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 FUSE { int fchmod(const struct fuse_file_info *ffi_, const mode_t mode_); } mergerfs-2.28.1/src/fs_xattr.cpp0000664000175000017500000001570113467103610015162 0ustar bilebile/* 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_base_close.hpp" #include "fs_base_getxattr.hpp" #include "fs_base_listxattr.hpp" #include "fs_base_open.hpp" #include "fs_base_removexattr.hpp" #include "fs_base_setxattr.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 = list(fd,attrs); if(rv != -1) { string tmp(attrs.begin(),attrs.end()); str::split(attrvector,tmp,'\0'); } return rv; } int list(const string &path, vector &attrvector) { int rv; vector attrs; rv = list(path,attrs); if(rv != -1) { string tmp(attrs.begin(),attrs.end()); str::split(attrvector,tmp,'\0'); } return rv; } int list(const int fd, string &attrstr) { int rv; vector attrs; rv = 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 = 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 = 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 = list(fd,attrstr); if(rv == -1) return -1; { string key; istringstream ss(attrstr); while(getline(ss,key,'\0')) { string value; rv = get(fd,key,value); if(rv != -1) attrs[key] = value; } } return 0; } int get(const string &path, map &attrs) { int rv; string attrstr; rv = list(path,attrstr); if(rv == -1) return -1; { string key; istringstream ss(attrstr); while(getline(ss,key,'\0')) { string value; rv = 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.c_str(), 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 = 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; set(fd,attrs); return fs::close(fd); } int copy(const int fdin, const int fdout) { int rv; map attrs; rv = get(fdin,attrs); if(rv == -1) return -1; return set(fdout,attrs); } int copy(const string &from, const string &to) { int rv; map attrs; rv = get(from,attrs); if(rv == -1) return -1; return set(to,attrs); } } } mergerfs-2.28.1/src/fs_base_chmod.hpp0000664000175000017500000000303113471033317016103 0ustar bilebile/* 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_base_stat.hpp" #include #define MODE_BITS (S_ISUID|S_ISGID|S_ISVTX|S_IRWXU|S_IRWXG|S_IRWXO) namespace fs { static inline int chmod(const std::string &path_, const mode_t mode_) { return ::chmod(path_.c_str(),mode_); } static inline int chmod_check_on_error(const std::string &path_, const mode_t mode_) { int rv; rv = fs::chmod(path_,mode_); if(rv == -1) { int error; struct stat st; error = errno; rv = fs::stat(path_,&st); if(rv == -1) return -1; if((st.st_mode & MODE_BITS) != (mode_ & MODE_BITS)) return (errno=error,-1); } return 0; } } mergerfs-2.28.1/src/fs_movefile.cpp0000664000175000017500000000565413471033317015635 0ustar bilebile/* 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 #include #include #include "errno.hpp" #include "fs.hpp" #include "fs_base_open.hpp" #include "fs_base_close.hpp" #include "fs_base_mkstemp.hpp" #include "fs_base_rename.hpp" #include "fs_base_unlink.hpp" #include "fs_base_stat.hpp" #include "fs_clonefile.hpp" #include "fs_clonepath.hpp" #include "fs_path.hpp" using std::string; using std::vector; namespace fs { int movefile(const vector &basepaths, const string &fusepath, const size_t additional_size, int &origfd) { int rv; int fdin; int fdout; int fdin_flags; string fusedir; string fdin_path; string fdout_path; string fdout_temp; struct stat fdin_st; fdin = origfd; rv = fs::fstat(fdin,&fdin_st); if(rv == -1) return -1; fdin_flags = fs::getfl(fdin); if(rv == -1) return -1; rv = fs::findonfs(basepaths,fusepath,fdin,fdin_path); if(rv == -1) return -1; fdin_st.st_size += additional_size; rv = fs::mfs(basepaths,fdin_st.st_size,fdout_path); if(rv == -1) return -1; fusedir = fs::path::dirname(&fusepath); rv = fs::clonepath(fdin_path,fdout_path,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,fusepath); fdout_temp = fdout_path; fdout = fs::mkstemp(fdout_temp); if(fdout == -1) return -1; rv = fs::clonefile(fdin,fdout); if(rv == -1) goto cleanup; rv = fs::setfl(fdout,fdin_flags); if(rv == -1) goto cleanup; rv = fs::rename(fdout_temp,fdout_path); 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; } } mergerfs-2.28.1/src/fs_base_fadvise.cpp0000664000175000017500000000361613467103610016435 0ustar bilebile/* 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 # include "fs_base_fadvise_posix.icpp" #else # include "fs_base_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.28.1/src/dirinfo.hpp0000664000175000017500000000166513427205466015003 0ustar bilebile/* 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 class DirInfo { public: DirInfo(const char *fusepath_) : fusepath(fusepath_) { } public: std::string fusepath; }; mergerfs-2.28.1/src/fs_base_futimesat_osx.icpp0000664000175000017500000000375113367322163020064 0ustar bilebile/* 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 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 = getpath(dirfd,pathname,fullpath); if(rv == -1) return -1; return ::utimes(fullpath,times); } } mergerfs-2.28.1/src/fuse_readlink.hpp0000664000175000017500000000163213471033317016147 0ustar bilebile/* 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.28.1/src/fuse_removexattr.hpp0000664000175000017500000000160713471033317016740 0ustar bilebile/* 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.28.1/src/fusefunc.cpp0000664000175000017500000000733713471033317015155 0ustar bilebile/* 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 "buildvector.hpp" #include "category.hpp" #include "fusefunc.hpp" #define FUSEFUNC(X,Y) FuseFunc(FuseFunc::Enum::X,#X,Category::Enum::Y) const std::vector FuseFunc::_fusefuncs_ = buildvector (FUSEFUNC(invalid,invalid)) (FUSEFUNC(access,search)) (FUSEFUNC(chmod,action)) (FUSEFUNC(chown,action)) (FUSEFUNC(create,create)) (FUSEFUNC(getattr,search)) (FUSEFUNC(getxattr,search)) (FUSEFUNC(link,action)) (FUSEFUNC(listxattr,search)) (FUSEFUNC(mkdir,create)) (FUSEFUNC(mknod,create)) (FUSEFUNC(open,search)) (FUSEFUNC(readlink,search)) (FUSEFUNC(removexattr,action)) (FUSEFUNC(rename,action)) (FUSEFUNC(rmdir,action)) (FUSEFUNC(setxattr,action)) (FUSEFUNC(symlink,create)) (FUSEFUNC(truncate,action)) (FUSEFUNC(unlink,action)) (FUSEFUNC(utimens,action)) ; const FuseFunc * const FuseFunc::fusefuncs = &_fusefuncs_[1]; const FuseFunc &FuseFunc::invalid = FuseFunc::fusefuncs[FuseFunc::Enum::invalid]; const FuseFunc &FuseFunc::access = FuseFunc::fusefuncs[FuseFunc::Enum::access]; const FuseFunc &FuseFunc::chmod = FuseFunc::fusefuncs[FuseFunc::Enum::chmod]; const FuseFunc &FuseFunc::chown = FuseFunc::fusefuncs[FuseFunc::Enum::chown]; const FuseFunc &FuseFunc::create = FuseFunc::fusefuncs[FuseFunc::Enum::create]; const FuseFunc &FuseFunc::getattr = FuseFunc::fusefuncs[FuseFunc::Enum::getattr]; const FuseFunc &FuseFunc::getxattr = FuseFunc::fusefuncs[FuseFunc::Enum::getxattr]; const FuseFunc &FuseFunc::link = FuseFunc::fusefuncs[FuseFunc::Enum::link]; const FuseFunc &FuseFunc::listxattr = FuseFunc::fusefuncs[FuseFunc::Enum::listxattr]; const FuseFunc &FuseFunc::mkdir = FuseFunc::fusefuncs[FuseFunc::Enum::mkdir]; const FuseFunc &FuseFunc::mknod = FuseFunc::fusefuncs[FuseFunc::Enum::mknod]; const FuseFunc &FuseFunc::open = FuseFunc::fusefuncs[FuseFunc::Enum::open]; const FuseFunc &FuseFunc::readlink = FuseFunc::fusefuncs[FuseFunc::Enum::readlink]; const FuseFunc &FuseFunc::removexattr = FuseFunc::fusefuncs[FuseFunc::Enum::removexattr]; const FuseFunc &FuseFunc::rmdir = FuseFunc::fusefuncs[FuseFunc::Enum::rmdir]; const FuseFunc &FuseFunc::setxattr = FuseFunc::fusefuncs[FuseFunc::Enum::setxattr]; const FuseFunc &FuseFunc::symlink = FuseFunc::fusefuncs[FuseFunc::Enum::symlink]; const FuseFunc &FuseFunc::truncate = FuseFunc::fusefuncs[FuseFunc::Enum::truncate]; const FuseFunc &FuseFunc::unlink = FuseFunc::fusefuncs[FuseFunc::Enum::unlink]; const FuseFunc &FuseFunc::utimens = FuseFunc::fusefuncs[FuseFunc::Enum::utimens]; const FuseFunc& FuseFunc::find(const std::string &str) { for(int i = Enum::BEGIN; i != Enum::END; ++i) { if(fusefuncs[i] == str) return fusefuncs[i]; } return invalid; } const FuseFunc& FuseFunc::find(const FuseFunc::Enum::Type i) { if((i >= FuseFunc::Enum::BEGIN) && (i < FuseFunc::Enum::END)) return fusefuncs[i]; return invalid; } mergerfs-2.28.1/src/policy_epall.cpp0000664000175000017500000000747213471033317016013 0ustar bilebile/* 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.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 #include using std::string; using std::vector; namespace epall { static int create(const Branches &branches_, const char *fusepath, const uint64_t minfreespace, vector &paths) { int rv; int error; fs::info_t info; const Branch *branch; error = ENOENT; for(size_t i = 0, ei = branches_.size(); i != ei; i++) { branch = &branches_[i]; if(!fs::exists(branch->path,fusepath)) error_and_continue(error,ENOENT); 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 < 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 &branches_, const char *fusepath, vector &paths) { int rv; int error; bool readonly; const Branch *branch; error = ENOENT; for(size_t i = 0, ei = branches_.size(); i != ei; i++) { branch = &branches_[i]; if(!fs::exists(branch->path,fusepath)) error_and_continue(error,ENOENT); if(branch->ro()) error_and_continue(error,EROFS); 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 &branches_, const char *fusepath, vector &paths) { const Branch *branch; for(size_t i = 0, ei = branches_.size(); i != ei; i++) { branch = &branches_[i]; if(!fs::exists(branch->path,fusepath)) continue; paths.push_back(&branch->path); } if(paths.empty()) return (errno=ENOENT,-1); return 0; } } int Policy::Func::epall(const Category::Enum::Type type, const Branches &branches_, const char *fusepath, const uint64_t minfreespace, vector &paths) { switch(type) { case Category::Enum::create: return epall::create(branches_,fusepath,minfreespace,paths); case Category::Enum::action: return epall::action(branches_,fusepath,paths); case Category::Enum::search: default: return epall::search(branches_,fusepath,paths); } } mergerfs-2.28.1/src/fs_copyfile.hpp0000664000175000017500000000156713467101456015652 0ustar bilebile/* 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 copyfile(const int src_fd_, const int dst_fd_); } mergerfs-2.28.1/src/fuse_setxattr.hpp0000664000175000017500000000174613471033317016242 0ustar bilebile/* 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 attrvalsize_, int flags_); } mergerfs-2.28.1/src/fs_base_readdir.hpp0000664000175000017500000000166713471033317016440 0ustar bilebile/* 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.28.1/src/fuse_getattr.cpp0000664000175000017500000000615713471033317016032 0ustar bilebile/* 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_base_stat.hpp" #include "fs_inode.hpp" #include "fs_path.hpp" #include "rwlock.hpp" #include "symlinkify.hpp" #include "ugid.hpp" #include #include #include using std::string; using std::vector; namespace l { 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(Policy::Func::Search searchFunc_, const Branches &branches_, const uint64_t minfreespace_, const char *fusepath_, struct stat *st_, const bool symlinkify_, const time_t symlinkify_timeout_) { int rv; string fullpath; vector basepaths; rv = searchFunc_(branches_,fusepath_,minfreespace_,basepaths); if(rv == -1) return -errno; fullpath = fs::path::make(basepaths[0],fusepath_); rv = fs::lstat(fullpath,st_); if(rv == -1) return -errno; if(symlinkify_ && symlinkify::can_be_symlink(*st_,symlinkify_timeout_)) st_->st_mode = symlinkify::convert(st_->st_mode); fs::inode::recompute(st_); return 0; } } namespace FUSE { int getattr(const char *fusepath_, struct stat *st_) { const fuse_context *fc = fuse_get_context(); const Config &config = Config::get(fc); if(fusepath_ == config.controlfile) return l::getattr_controlfile(st_); const ugid::Set ugid(fc->uid,fc->gid); const rwlock::ReadGuard readlock(&config.branches_lock); return l::getattr(config.getattr, config.branches, config.minfreespace, fusepath_, st_, config.symlinkify, config.symlinkify_timeout); } } mergerfs-2.28.1/src/fuse_fallocate.hpp0000664000175000017500000000176013471033317016312 0ustar bilebile/* 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 fallocate(const char *fusepath_, int mode_, off_t offset_, off_t len_, fuse_file_info *ffi_); } mergerfs-2.28.1/src/fs_ficlone.cpp0000664000175000017500000000161413467101456015443 0ustar bilebile/* 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__ # include "fs_ficlone_linux.icpp" #else # include "fs_ficlone_unsupported.icpp" #endif mergerfs-2.28.1/src/fs_attr.cpp0000664000175000017500000000156713367322163015004 0ustar bilebile/* 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__ # include "fs_attr_linux.icpp" #else # include "fs_attr_unsupported.icpp" #endif mergerfs-2.28.1/src/fuse_mknod.hpp0000664000175000017500000000162313471033317015466 0ustar bilebile /* 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.28.1/src/fuse_write.hpp0000664000175000017500000000226113471033317015507 0ustar bilebile/* 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 write(const char *fusepath_, const char *buf_, size_t count_, off_t offset_, fuse_file_info *ffi_); int write_null(const char *fusepath_, const char *buf_, size_t count_, off_t offset_, fuse_file_info *ffi_); } mergerfs-2.28.1/src/fuse_release.hpp0000664000175000017500000000162513471033317016000 0ustar bilebile/* 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 release(const char *fusepath_, fuse_file_info *ffi_); } mergerfs-2.28.1/src/fs_base_fallocate_osx.icpp0000664000175000017500000000275113467103610020007 0ustar bilebile/* 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 "errno.hpp" 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 ::_fallocate_core(fd,offset,len); } } mergerfs-2.28.1/src/fs_clonepath.cpp0000664000175000017500000000761013471033317015776 0ustar bilebile/* 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 "errno.h" #include "fs_attr.hpp" #include "fs_base_chmod.hpp" #include "fs_base_chown.hpp" #include "fs_base_mkdir.hpp" #include "fs_base_stat.hpp" #include "fs_base_utime.hpp" #include "fs_clonepath.hpp" #include "fs_path.hpp" #include "fs_xattr.hpp" #include "ugid.hpp" using std::string; 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.empty()) { rv = fs::clonepath(fromsrc,tosrc,dirname); if(rv == -1) return -1; } frompath = fs::path::make(fromsrc,relative); rv = fs::stat(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) && !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) && !ignorable_error(errno)) return -1; rv = fs::lchown_check_on_error(topath,st); if(return_metadata_errors && (rv == -1)) return -1; rv = fs::utime(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.28.1/src/fuse_rmdir.hpp0000664000175000017500000000153413471033317015474 0ustar bilebile/* 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.28.1/src/fs_base_lseek.hpp0000664000175000017500000000201513471033317016115 0ustar bilebile/* 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_); } } mergerfs-2.28.1/src/fs_base_truncate.hpp0000664000175000017500000000224513471033317016644 0ustar bilebile/* 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.28.1/src/ugid_rwlock.icpp0000664000175000017500000000245713427205466016026 0ustar bilebile/* 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.28.1/src/statvfs_util.hpp0000664000175000017500000000233513471033317016064 0ustar bilebile/* 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 uint64_t spaceavail(const struct statvfs &st) { return (st.f_frsize * st.f_bavail); } static inline uint64_t spaceused(const struct statvfs &st) { return (st.f_frsize * (st.f_blocks - st.f_bavail)); } } mergerfs-2.28.1/src/policy_epmfs.cpp0000664000175000017500000001126613471033317016024 0ustar bilebile/* 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.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 #include #include using std::string; using std::vector; namespace epmfs { static int create(const Branches &branches_, const char *fusepath, const uint64_t minfreespace, vector &paths) { int rv; int error; uint64_t epmfs; fs::info_t info; const Branch *branch; const string *epmfsbasepath; error = ENOENT; epmfs = std::numeric_limits::min(); epmfsbasepath = NULL; for(size_t i = 0, ei = branches_.size(); i != ei; i++) { branch = &branches_[i]; if(!fs::exists(branch->path,fusepath)) error_and_continue(error,ENOENT); 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 < minfreespace) error_and_continue(error,ENOSPC); if(info.spaceavail < epmfs) continue; epmfs = info.spaceavail; epmfsbasepath = &branch->path; } if(epmfsbasepath == NULL) return (errno=error,-1); paths.push_back(epmfsbasepath); return 0; } static int action(const Branches &branches_, const char *fusepath, vector &paths) { int rv; int error; uint64_t epmfs; fs::info_t info; const Branch *branch; const string *epmfsbasepath; error = ENOENT; epmfs = std::numeric_limits::min(); epmfsbasepath = NULL; for(size_t i = 0, ei = branches_.size(); i != ei; i++) { branch = &branches_[i]; if(!fs::exists(branch->path,fusepath)) error_and_continue(error,ENOENT); if(branch->ro()) 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 < epmfs) continue; epmfs = info.spaceavail; epmfsbasepath = &branch->path; } if(epmfsbasepath == NULL) return (errno=error,-1); paths.push_back(epmfsbasepath); return 0; } static int search(const Branches &branches_, const char *fusepath, vector &paths) { int rv; uint64_t epmfs; uint64_t spaceavail; const Branch *branch; const string *epmfsbasepath; epmfs = 0; epmfsbasepath = NULL; for(size_t i = 0, ei = branches_.size(); i != ei; i++) { branch = &branches_[i]; 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; epmfsbasepath = &branch->path; } if(epmfsbasepath == NULL) return (errno=ENOENT,-1); paths.push_back(epmfsbasepath); return 0; } } int Policy::Func::epmfs(const Category::Enum::Type type, const Branches &branches_, const char *fusepath, const uint64_t minfreespace, vector &paths) { switch(type) { case Category::Enum::create: return epmfs::create(branches_,fusepath,minfreespace,paths); case Category::Enum::action: return epmfs::action(branches_,fusepath,paths); case Category::Enum::search: default: return epmfs::search(branches_,fusepath,paths); } } mergerfs-2.28.1/src/fs_sendfile_unsupported.icpp0000664000175000017500000000174013367322163020435 0ustar bilebile/* 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 "errno.hpp" namespace fs { ssize_t sendfile(const int fdin, const int fdout, const size_t count) { return (errno=EINVAL,-1); } } mergerfs-2.28.1/src/fuse_statfs.cpp0000664000175000017500000001200413471033317015650 0ustar bilebile/* 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_base_stat.hpp" #include "fs_base_statvfs.hpp" #include "fs_path.hpp" #include "rwlock.hpp" #include "statvfs_util.hpp" #include "ugid.hpp" #include #include #include #include #include #include using std::string; using std::map; using std::vector; typedef Config::StatFS StatFS; typedef Config::StatFSIgnore StatFSIgnore; 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::Enum ignore_, const Branch *branch_, const bool readonly_) { return ((((ignore_ == StatFSIgnore::RO) || readonly_) && (branch_->ro_or_nc())) || ((ignore_ == StatFSIgnore::NC) && (branch_->nc()))); } static int statfs(const Branches &branches_, const char *fusepath_, const StatFS::Enum mode_, const StatFSIgnore::Enum 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(size_t i = 0, ei = branches_.size(); i < ei; i++) { fullpath = ((mode_ == StatFS::FULL) ? fs::path::make(&branches_[i].path,fusepath_) : branches_[i].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_,&branches_[i],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_) { const fuse_context *fc = fuse_get_context(); const Config &config = Config::get(fc); const ugid::Set ugid(fc->uid,fc->gid); const rwlock::ReadGuard readlock(&config.branches_lock); return l::statfs(config.branches, fusepath_, config.statfs, config.statfs_ignore, st_); } } mergerfs-2.28.1/src/fs_copy_file_range.cpp0000664000175000017500000000163413467101456017153 0ustar bilebile/* 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__ # include "fs_copy_file_range_linux.icpp" #else # include "fs_copy_file_range_unsupported.icpp" #endif mergerfs-2.28.1/src/fuse_ftruncate.cpp0000664000175000017500000000244113471033317016343 0ustar bilebile/* 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_base_ftruncate.hpp" #include 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 char *fusepath_, off_t size_, fuse_file_info *ffi_) { FileInfo *fi = reinterpret_cast(ffi_->fh); return l::ftruncate(fi->fd,size_); } } mergerfs-2.28.1/src/fs_sendfile.hpp0000664000175000017500000000163313427205466015625 0ustar bilebile/* 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.28.1/src/fs_glob.hpp0000664000175000017500000000164613471033317014754 0ustar bilebile/* 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 namespace fs { void glob(const std::string &pattern_, std::vector &strs_); } mergerfs-2.28.1/src/fuse_open.cpp0000664000175000017500000000706013474604542015322 0ustar bilebile/* 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_base_open.hpp" #include "fs_cow.hpp" #include "fs_path.hpp" #include "policy_cache.hpp" #include "rwlock.hpp" #include "ugid.hpp" #include #include #include using std::string; using std::vector; typedef Config::CacheFiles CacheFiles; namespace l { static int open_core(const string &basepath_, const char *fusepath_, const int flags_, const bool link_cow_, 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) return -errno; *fh_ = reinterpret_cast(new FileInfo(fd,fusepath_)); return 0; } static int open(Policy::Func::Search searchFunc_, PolicyCache &cache, const Branches &branches_, const uint64_t minfreespace_, const char *fusepath_, const int flags_, const bool link_cow_, uint64_t *fh_) { int rv; string basepath; rv = cache(searchFunc_,branches_,fusepath_,minfreespace_,&basepath); if(rv == -1) return -errno; return l::open_core(basepath,fusepath_,flags_,link_cow_,fh_); } } namespace FUSE { int open(const char *fusepath_, fuse_file_info *ffi_) { const fuse_context *fc = fuse_get_context(); const Config &config = Config::get(fc); const ugid::Set ugid(fc->uid,fc->gid); const rwlock::ReadGuard readlock(&config.branches_lock); switch(config.cache_files) { case CacheFiles::LIBFUSE: ffi_->direct_io = config.direct_io; ffi_->keep_cache = config.kernel_cache; ffi_->auto_cache = config.auto_cache; break; case CacheFiles::OFF: ffi_->direct_io = 1; ffi_->keep_cache = 0; ffi_->auto_cache = 0; break; case CacheFiles::PARTIAL: ffi_->direct_io = 0; ffi_->keep_cache = 0; ffi_->auto_cache = 0; break; case CacheFiles::FULL: ffi_->direct_io = 0; ffi_->keep_cache = 1; ffi_->auto_cache = 0; break; case CacheFiles::AUTO_FULL: ffi_->direct_io = 0; ffi_->keep_cache = 0; ffi_->auto_cache = 1; break; } return l::open(config.open, config.open_cache, config.branches, config.minfreespace, fusepath_, ffi_->flags, config.link_cow, &ffi_->fh); } } mergerfs-2.28.1/src/fuse_ioctl.hpp0000664000175000017500000000206013471033317015464 0ustar bilebile/* 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 ioctl(const char *fusepath_, int cmd_, void *arg_, fuse_file_info *ffi_, unsigned int flags_, void *data_, uint32_t *out_bufsz_); } mergerfs-2.28.1/src/fs_base_remove.hpp0000664000175000017500000000207413471033317016314 0ustar bilebile/* 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.28.1/src/fs_acl.cpp0000664000175000017500000000233213467103610014553 0ustar bilebile/* 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 #include "fs_base_getxattr.hpp" #include "fs_path.hpp" 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.28.1/src/fs_base_readlink.hpp0000664000175000017500000000205613471033317016610 0ustar bilebile/* 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.28.1/src/fuse_fchown.hpp0000664000175000017500000000172413471033317015644 0ustar bilebile/* 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 fchown(const struct fuse_file_info *ffi_, uid_t uid_, gid_t gid_); } mergerfs-2.28.1/src/fs_movefile.hpp0000664000175000017500000000207213427205466015640 0ustar bilebile/* 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 { int movefile(const std::vector &basepaths, const std::string &fusepath, const size_t additional_size, int &origfd); } mergerfs-2.28.1/src/fuse_fchown.cpp0000664000175000017500000000256013471033317015636 0ustar bilebile/* 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_base_fchown.hpp" #include #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 struct fuse_file_info *ffi_, const uid_t uid_, const gid_t gid_) { FileInfo *fi = reinterpret_cast(ffi_->fh); return l::fchown(fi->fd,uid_,gid_); } } mergerfs-2.28.1/src/buildvector.hpp0000664000175000017500000000233313471033317015655 0ustar bilebile/* 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.28.1/src/fs_base_stat.hpp0000664000175000017500000000421713471033317015773 0ustar bilebile/* 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_); } 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_); } static inline int fstat(const int fd_, struct stat *st_) { return ::fstat(fd_,st_); } 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.28.1/src/fs_base_unlink.hpp0000664000175000017500000000205513467101456016323 0ustar bilebile/* 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.28.1/src/mergerfs.cpp0000664000175000017500000001253213475734103015147 0ustar bilebile/* 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 "fuse_access.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_mkdir.hpp" #include "fuse_mknod.hpp" #include "fuse_open.hpp" #include "fuse_opendir.hpp" #include "fuse_prepare_hide.hpp" #include "fuse_read.hpp" #include "fuse_read_buf.hpp" #include "fuse_readdir.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.hpp" #include "fuse_write_buf.hpp" #include #include #include #include namespace l { static void get_fuse_operations(struct fuse_operations &ops, const bool nullrw) { ops.flag_nullpath_ok = true; ops.flag_nopath = true; ops.flag_utime_omit_ok = true; ops.access = FUSE::access; ops.bmap = NULL; 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 = NULL; // 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.getdir = NULL; /* deprecated; use readdir */ ops.getxattr = FUSE::getxattr; ops.init = FUSE::init; ops.ioctl = FUSE::ioctl; ops.link = FUSE::link; ops.listxattr = FUSE::listxattr; ops.lock = NULL; ops.mkdir = FUSE::mkdir; ops.mknod = FUSE::mknod; ops.open = FUSE::open; ops.opendir = FUSE::opendir; ops.poll = NULL; ops.prepare_hide = FUSE::prepare_hide; ops.read = (nullrw ? FUSE::read_null : FUSE::read); ops.read_buf = (nullrw ? NULL : FUSE::read_buf); ops.readdir = FUSE::readdir; 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.utime = NULL; /* deprecated; use utimens() */ ops.utimens = FUSE::utimens; ops.write = (nullrw ? FUSE::write_null : FUSE::write); 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_) { fuse_args args; fuse_operations ops = {0}; Config config; args.argc = argc_; args.argv = argv_; args.allocated = 0; options::parse(&args,&config); l::setup_resources(); l::get_fuse_operations(ops,config.nullrw); return fuse_main(args.argc, args.argv, &ops, &config); } } int main(int argc, char **argv) { return l::main(argc,argv); } mergerfs-2.28.1/src/fuse_flock.hpp0000664000175000017500000000165613471033317015462 0ustar bilebile/* 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 flock(const char *fusepath_, fuse_file_info *ffi_, int op_); } mergerfs-2.28.1/src/fs_base_fadvise.hpp0000664000175000017500000000230513471033317016435 0ustar bilebile/* 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 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.28.1/src/rv.hpp0000664000175000017500000000200713427205466013767 0ustar bilebile/* 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 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; } } mergerfs-2.28.1/src/fs_info_t.hpp0000664000175000017500000000167113467101456015312 0ustar bilebile/* 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 { struct info_t { bool readonly; uint64_t spaceavail; uint64_t spaceused; }; } mergerfs-2.28.1/src/policy_cache.hpp0000664000175000017500000000277713471033317015771 0ustar bilebile/* 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 #include #include #include class PolicyCache { public: struct Value { Value(); uint64_t time; std::string path; }; public: PolicyCache(void); public: void erase(const char *fusepath_); void cleanup(const int prob_ = 1); void clear(void); public: int operator()(Policy::Func::Search &func_, const Branches &branches_, const char *fusepath_, const uint64_t minfreespace_, std::string *branch_); public: uint64_t timeout; private: pthread_mutex_t _lock; std::map _cache; }; mergerfs-2.28.1/src/fs_base_utime_generic.hpp0000664000175000017500000001521313471033317017635 0ustar bilebile/* 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_base_futimesat.hpp" #include "fs_base_stat.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 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) && (_timespec_invalid(ts_[0]) || _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(!_any_timespec_is_utime_omit(ts_)) return 0; rv = ::fstatat(dirfd,path.c_str(),&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(!_any_timespec_is_utime_omit(ts_)) return 0; rv = ::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(_any_timespec_is_utime_now(ts_)) return 0; rv = ::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(_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 = _set_utime_omit_to_current_value(dirfd,path,ts_,tv,flags); if(rv == -1) return -1; rv = _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(_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 = _set_utime_omit_to_current_value(fd,ts_,tv); if(rv == -1) return -1; rv = _set_utime_now_to_now(ts_,tv); if(rv == -1) return -1; return (tvp=tv,0); } namespace fs { static inline int utime(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(_flags_invalid(flags)) return (errno=EINVAL,-1); if(_timespec_invalid(ts_)) return (errno=EINVAL,-1); if(_should_ignore(ts_)) return 0; rv = _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.c_str(),tvp); if(_can_call_lutimes(dirfd,path,flags)) return ::lutimes(path.c_str(),tvp); return (errno=ENOTSUP,-1); } static inline int futimens(const int fd_, const struct timespec ts_[2]) { int rv; struct timeval tv[2]; struct timeval *tvp; if(_timespec_invalid(ts_)) return (errno=EINVAL,-1); if(_should_ignore(ts_)) return 0; rv = _convert_timespec_to_timeval(fd_,ts_,tv,tvp); if(rv == -1) return -1; return ::futimes(fd_,tvp); } } mergerfs-2.28.1/src/fs_base_rmdir.hpp0000664000175000017500000000205113471033317016127 0ustar bilebile/* 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.28.1/src/policy_erofs.cpp0000664000175000017500000000232013471033317016017 0ustar bilebile/* 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 #include using std::string; using std::vector; int Policy::Func::erofs(const Category::Enum::Type type, const Branches &branches_, const char *fusepath, const uint64_t minfreespace, vector &paths) { return (errno=EROFS,-1); } mergerfs-2.28.1/src/fuse_chown.hpp0000664000175000017500000000162013471033317015471 0ustar bilebile/* 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.28.1/src/fasthash.cpp0000664000175000017500000000503313467101456015134 0ustar bilebile/* The MIT License Copyright (C) 2012 Zilong Tan (eric.zltan@gmail.com) 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. */ #include "fasthash.h" // Compression function for Merkle-Damgard construction. // This function is generated using the framework provided. #define mix(h) ({ \ (h) ^= (h) >> 23; \ (h) *= 0x2127599bf4325c37ULL; \ (h) ^= (h) >> 47; }) uint64_t fasthash64(const void *buf, size_t len, uint64_t seed) { const uint64_t m = 0x880355f21e6d1965ULL; const uint64_t *pos = (const uint64_t *)buf; const uint64_t *end = pos + (len / 8); const unsigned char *pos2; uint64_t h = seed ^ (len * m); uint64_t v; while (pos != end) { v = *pos++; h ^= mix(v); h *= m; } pos2 = (const unsigned char*)pos; v = 0; switch (len & 7) { case 7: v ^= (uint64_t)pos2[6] << 48; case 6: v ^= (uint64_t)pos2[5] << 40; case 5: v ^= (uint64_t)pos2[4] << 32; case 4: v ^= (uint64_t)pos2[3] << 24; case 3: v ^= (uint64_t)pos2[2] << 16; case 2: v ^= (uint64_t)pos2[1] << 8; case 1: v ^= (uint64_t)pos2[0]; h ^= mix(v); h *= m; } return mix(h); } uint32_t fasthash32(const void *buf, size_t len, uint32_t seed) { // the following trick converts the 64-bit hashcode to Fermat // residue, which shall retain information from both the higher // and lower parts of hashcode. uint64_t h; h = fasthash64(buf, len, seed); return (h - (h >> 32)); } mergerfs-2.28.1/src/fuse_listxattr.hpp0000664000175000017500000000163613471033317016420 0ustar bilebile/* 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.28.1/src/endian.hpp0000664000175000017500000000171313471033317014572 0ustar bilebile/* 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.28.1/src/fuse_fsync.hpp0000664000175000017500000000166613471033317015507 0ustar bilebile/* 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 fsync(const char *fusepath_, int isdatasync_, fuse_file_info *ffi_); } mergerfs-2.28.1/src/fs_base_mkdir.hpp0000664000175000017500000000202113471033317016115 0ustar bilebile/* 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 mkdir(const std::string &path_, const mode_t mode_) { return ::mkdir(path_.c_str(),mode_); } } mergerfs-2.28.1/src/fuse_rename.cpp0000664000175000017500000002127613475734103015633 0ustar bilebile/* 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 "config.hpp" #include "errno.hpp" #include "fs_base_remove.hpp" #include "fs_base_rename.hpp" #include "fs_clonepath.hpp" #include "fs_path.hpp" #include "rv.hpp" #include "rwlock.hpp" #include "ugid.hpp" using std::string; using std::vector; using std::set; static bool member(const vector &haystack, const string &needle) { for(size_t i = 0, ei = haystack.size(); i != ei; i++) { if(*haystack[i] == needle) return true; } return false; } static void _remove(const vector &toremove) { for(size_t i = 0, ei = toremove.size(); i != ei; i++) fs::remove(toremove[i]); } static void _rename_create_path_core(const vector &oldbasepaths, const string &oldbasepath, const string &newbasepath, const char *oldfusepath, const char *newfusepath, const string &newfusedirpath, int &error, vector &tounlink) { int rv; bool ismember; string oldfullpath; string newfullpath; ismember = member(oldbasepaths,oldbasepath); if(ismember) { rv = fs::clonepath_as_root(newbasepath,oldbasepath,newfusedirpath); if(rv != -1) { oldfullpath = fs::path::make(oldbasepath,oldfusepath); newfullpath = fs::path::make(oldbasepath,newfusepath); rv = fs::rename(oldfullpath,newfullpath); } error = error::calc(rv,error,errno); if(rv == -1) tounlink.push_back(oldfullpath); } else { newfullpath = fs::path::make(oldbasepath,newfusepath); tounlink.push_back(newfullpath); } } static int _rename_create_path(Policy::Func::Search searchFunc, Policy::Func::Action actionFunc, const Branches &branches_, const uint64_t minfreespace, const char *oldfusepath, const char *newfusepath) { int rv; int error; string newfusedirpath; vector toremove; vector newbasepath; vector oldbasepaths; rv = actionFunc(branches_,oldfusepath,minfreespace,oldbasepaths); if(rv == -1) return -errno; newfusedirpath = fs::path::dirname(newfusepath); rv = searchFunc(branches_,newfusedirpath,minfreespace,newbasepath); if(rv == -1) return -errno; error = -1; for(size_t i = 0, ei = branches_.size(); i != ei; i++) { const string &oldbasepath = branches_[i].path; _rename_create_path_core(oldbasepaths, oldbasepath,*newbasepath[0], oldfusepath,newfusepath, newfusedirpath, error,toremove); } if(error == 0) _remove(toremove); return -error; } static int _clonepath(Policy::Func::Search searchFunc, const Branches &branches_, const uint64_t minfreespace, const string &dstbasepath, const string &fusedirpath) { int rv; vector srcbasepath; rv = searchFunc(branches_,fusedirpath,minfreespace,srcbasepath); if(rv == -1) return -errno; fs::clonepath_as_root(*srcbasepath[0],dstbasepath,fusedirpath); return 0; } static int _clonepath_if_would_create(Policy::Func::Search searchFunc, Policy::Func::Create createFunc, const Branches &branches_, const uint64_t minfreespace, const string &oldbasepath, const char *oldfusepath, const char *newfusepath) { int rv; string newfusedirpath; vector newbasepath; newfusedirpath = fs::path::dirname(newfusepath); rv = createFunc(branches_,newfusedirpath,minfreespace,newbasepath); if(rv == -1) return rv; if(oldbasepath == *newbasepath[0]) return _clonepath(searchFunc,branches_,minfreespace,oldbasepath,newfusedirpath); return (errno=EXDEV,-1); } static void _rename_preserve_path_core(Policy::Func::Search searchFunc, Policy::Func::Create createFunc, const Branches &branches_, const uint64_t minfreespace, const vector &oldbasepaths, const string &oldbasepath, const char *oldfusepath, const char *newfusepath, int &error, vector &toremove) { int rv; bool ismember; string newfullpath; newfullpath = fs::path::make(oldbasepath,newfusepath); ismember = member(oldbasepaths,oldbasepath); if(ismember) { string oldfullpath; oldfullpath = fs::path::make(oldbasepath,oldfusepath); rv = fs::rename(oldfullpath,newfullpath); if((rv == -1) && (errno == ENOENT)) { rv = _clonepath_if_would_create(searchFunc,createFunc, branches_,minfreespace, oldbasepath,oldfusepath,newfusepath); if(rv == 0) rv = fs::rename(oldfullpath,newfullpath); } error = error::calc(rv,error,errno); if(rv == -1) toremove.push_back(oldfullpath); } else { toremove.push_back(newfullpath); } } static int _rename_preserve_path(Policy::Func::Search searchFunc, Policy::Func::Action actionFunc, Policy::Func::Create createFunc, const Branches &branches_, const uint64_t minfreespace, const char *oldfusepath, const char *newfusepath) { int rv; int error; vector toremove; vector oldbasepaths; rv = actionFunc(branches_,oldfusepath,minfreespace,oldbasepaths); if(rv == -1) return -errno; error = -1; for(size_t i = 0, ei = branches_.size(); i != ei; i++) { const string &oldbasepath = branches_[i].path; _rename_preserve_path_core(searchFunc,createFunc, branches_,minfreespace, oldbasepaths,oldbasepath, oldfusepath,newfusepath, error,toremove); } if(error == 0) _remove(toremove); return -error; } namespace FUSE { int rename(const char *oldpath, const char *newpath) { const fuse_context *fc = fuse_get_context(); const Config &config = Config::get(fc); const ugid::Set ugid(fc->uid,fc->gid); const rwlock::ReadGuard readlock(&config.branches_lock); config.open_cache.erase(oldpath); if(config.create->path_preserving() && !config.ignorepponrename) return _rename_preserve_path(config.getattr, config.rename, config.create, config.branches, config.minfreespace, oldpath, newpath); return _rename_create_path(config.getattr, config.rename, config.branches, config.minfreespace, oldpath, newpath); } } mergerfs-2.28.1/src/fs_ficlone.hpp0000664000175000017500000000160413467101456015447 0ustar bilebile/* 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.28.1/src/fs_statvfs_cache.cpp0000664000175000017500000000550313471033317016635 0ustar bilebile/* 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_base_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.28.1/src/fuse_release.cpp0000664000175000017500000000322313471033317015767 0ustar bilebile/* 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_base_close.hpp" #include "fs_base_fadvise.hpp" #include #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 char *fusepath_, fuse_file_info *ffi_) { const Config &config = Config::get(); FileInfo *fi = reinterpret_cast(ffi_->fh); if(config.open_cache.timeout) config.open_cache.cleanup(10); return l::release(fi,config.dropcacheonclose); } } mergerfs-2.28.1/src/khash.h0000664000175000017500000005204713467101456014105 0ustar bilebile/* 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.28.1/src/fuse_create.cpp0000664000175000017500000001066013474604542015624 0ustar bilebile/* 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_base_open.hpp" #include "fs_clonepath.hpp" #include "fs_path.hpp" #include "rwlock.hpp" #include "ugid.hpp" #include #include #include using std::string; using std::vector; typedef Config::CacheFiles CacheFiles; namespace l { 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(Policy::Func::Search searchFunc_, Policy::Func::Create createFunc_, const Branches &branches_, const uint64_t minfreespace_, const char *fusepath_, const mode_t mode_, const mode_t umask_, const int flags_, uint64_t *fh_) { int rv; string fullpath; string fusedirpath; vector createpaths; vector existingpaths; fusedirpath = fs::path::dirname(fusepath_); rv = searchFunc_(branches_,fusedirpath,minfreespace_,existingpaths); if(rv == -1) return -errno; rv = createFunc_(branches_,fusedirpath,minfreespace_,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 *ffi_) { const fuse_context *fc = fuse_get_context(); const Config &config = Config::get(fc); const ugid::Set ugid(fc->uid,fc->gid); const rwlock::ReadGuard readlock(&config.branches_lock); switch(config.cache_files) { case CacheFiles::LIBFUSE: ffi_->direct_io = config.direct_io; ffi_->keep_cache = config.kernel_cache; ffi_->auto_cache = config.auto_cache; break; case CacheFiles::OFF: ffi_->direct_io = 1; ffi_->keep_cache = 0; ffi_->auto_cache = 0; break; case CacheFiles::PARTIAL: ffi_->direct_io = 0; ffi_->keep_cache = 0; ffi_->auto_cache = 0; break; case CacheFiles::FULL: ffi_->direct_io = 0; ffi_->keep_cache = 1; ffi_->auto_cache = 0; break; case CacheFiles::AUTO_FULL: ffi_->direct_io = 0; ffi_->keep_cache = 0; ffi_->auto_cache = 1; break; } return l::create(config.getattr, config.create, config.branches, config.minfreespace, fusepath_, mode_, fc->umask, ffi_->flags, &ffi_->fh); } } mergerfs-2.28.1/src/fs_base_chown.hpp0000664000175000017500000000407113471033317016134 0ustar bilebile/* 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_base_stat.hpp" #include #include #include #include namespace fs { static inline int chown(const std::string &path_, const uid_t uid_, const gid_t gid_) { return ::chown(path_.c_str(),uid_,gid_); } static inline int chown(const std::string &path_, const struct stat &st_) { return fs::chown(path_,st_.st_uid,st_.st_gid); } static inline int lchown(const std::string &path_, const uid_t uid_, const gid_t gid_) { return ::lchown(path_.c_str(),uid_,gid_); } static inline int lchown(const std::string &path_, const struct stat &st_) { return fs::lchown(path_,st_.st_uid,st_.st_gid); } 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.28.1/src/fasthash.h0000664000175000017500000000325613467101456014606 0ustar bilebile/* The MIT License Copyright (C) 2012 Zilong Tan (eric.zltan@gmail.com) 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 _FASTHASH_H #define _FASTHASH_H #include #include #ifdef __cplusplus extern "C" { #endif /** * fasthash32 - 32-bit implementation of fasthash * @buf: data buffer * @len: data size * @seed: the seed */ uint32_t fasthash32(const void *buf, size_t len, uint32_t seed); /** * fasthash64 - 64-bit implementation of fasthash * @buf: data buffer * @len: data size * @seed: the seed */ uint64_t fasthash64(const void *buf, size_t len, uint64_t seed); #ifdef __cplusplus } #endif #endif mergerfs-2.28.1/src/fuse_write_buf.cpp0000664000175000017500000000553013471033317016340 0ustar bilebile/* 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 "policy.hpp" #include "rwlock.hpp" #include "ugid.hpp" #include #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); } } namespace FUSE { int write_buf(const char *fusepath_, fuse_bufvec *src_, off_t offset_, fuse_file_info *ffi_) { int rv; FileInfo *fi = reinterpret_cast(ffi_->fh); rv = l::write_buf(fi->fd,src_,offset_); if(l::out_of_space(-rv)) { const fuse_context *fc = fuse_get_context(); const Config &config = Config::get(fc); if(config.moveonenospc) { size_t extra; vector paths; const ugid::Set ugid(0,0); const rwlock::ReadGuard readlock(&config.branches_lock); config.branches.to_paths(paths); extra = fuse_buf_size(src_); rv = fs::movefile(paths,fi->fusepath,extra,fi->fd); if(rv == -1) return -ENOSPC; rv = l::write_buf(fi->fd,src_,offset_); } } return rv; } int write_buf_null(const char *fusepath_, fuse_bufvec *src_, off_t offset_, fuse_file_info *ffi_) { return src_->buf[0].size; } } mergerfs-2.28.1/src/fs_path.cpp0000664000175000017500000000312013467103610014744 0ustar bilebile/* 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 #include "fs_path.hpp" 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_) { string rv; string::reverse_iterator i; string::reverse_iterator ei; rv = *path_; i = rv.rbegin(); ei = rv.rend(); while(*i == '/' && i != ei) i++; while(*i != '/' && i != ei) i++; while(*i == '/' && i != ei) i++; rv.erase(i.base(),rv.end()); return rv; } string basename(const string &path) { return path.substr(path.find_last_of('/')+1); } } } mergerfs-2.28.1/src/fuse_truncate.cpp0000664000175000017500000000542513471033317016202 0ustar bilebile/* 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_base_truncate.hpp" #include "fs_path.hpp" #include "rv.hpp" #include "rwlock.hpp" #include "ugid.hpp" #include #include #include #include #include using std::string; using std::vector; namespace l { static int truncate_loop_core(const string *basepath_, const char *fusepath_, const off_t size_, const int error_) { int rv; string fullpath; fullpath = fs::path::make(basepath_,fusepath_); rv = fs::truncate(fullpath,size_); return error::calc(rv,error_,errno); } static int truncate_loop(const vector &basepaths_, const char *fusepath_, const off_t size_) { int error; error = -1; for(size_t i = 0, ei = basepaths_.size(); i != ei; i++) { error = l::truncate_loop_core(basepaths_[i],fusepath_,size_,error); } return -error; } static int truncate(Policy::Func::Action actionFunc_, const Branches &branches_, const uint64_t minfreespace_, const char *fusepath_, const off_t size_) { int rv; vector basepaths; rv = actionFunc_(branches_,fusepath_,minfreespace_,basepaths); if(rv == -1) return -errno; return l::truncate_loop(basepaths,fusepath_,size_); } } namespace FUSE { int truncate(const char *fusepath_, off_t size_) { const fuse_context *fc = fuse_get_context(); const Config &config = Config::get(fc); const ugid::Set ugid(fc->uid,fc->gid); const rwlock::ReadGuard readlock(&config.branches_lock); return l::truncate(config.truncate, config.branches, config.minfreespace, fusepath_, size_); } } mergerfs-2.28.1/src/resources.hpp0000664000175000017500000000174513471033317015353 0ustar bilebile/* 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.28.1/src/ugid_linux.hpp0000664000175000017500000000502313427205466015510 0ustar bilebile/* 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.28.1/src/fs_base_fsync.hpp0000664000175000017500000000222113471033317016133 0ustar bilebile/* 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 fsync(const int fd_) { return ::fsync(fd_); } static inline int fdatasync(const int fd_) { #if _POSIX_SYNCHRONIZED_IO > 0 return ::fdatasync(fd_); #else return (errno=ENOSYS,-1); #endif } } mergerfs-2.28.1/src/fs_base_symlink.hpp0000664000175000017500000000260613471033317016506 0ustar bilebile/* 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 *oldpath_, const char *newpath_) { return ::symlink(oldpath_, newpath_); } static inline int symlink(const std::string &oldpath_, const std::string &newpath_) { return fs::symlink(oldpath_.c_str(), newpath_.c_str()); } static inline int symlink(const char *oldpath_, const std::string &newpath_) { return fs::symlink(oldpath_, newpath_.c_str()); } } mergerfs-2.28.1/src/fs_clonepath.hpp0000664000175000017500000000321413427205466016006 0ustar bilebile/* 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.28.1/src/fuse_fgetattr.cpp0000664000175000017500000000252313471033317016171 0ustar bilebile/* 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_base_stat.hpp" #include "fs_inode.hpp" #include namespace l { static int fgetattr(const int fd_, struct stat *st_) { int rv; rv = fs::fstat(fd_,st_); if(rv == -1) return -errno; fs::inode::recompute(st_); return 0; } } namespace FUSE { int fgetattr(const char *fusepath_, struct stat *st_, fuse_file_info *ffi_) { FileInfo *fi = reinterpret_cast(ffi_->fh); return l::fgetattr(fi->fd,st_); } } mergerfs-2.28.1/src/fs_base_flock.hpp0000664000175000017500000000172313471033317016115 0ustar bilebile/* 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.28.1/src/fuse_releasedir.cpp0000664000175000017500000000217313471033317016471 0ustar bilebile/* 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 namespace l { static int releasedir(DirInfo *di_) { delete di_; return 0; } } namespace FUSE { int releasedir(const char *fusepath_, fuse_file_info *ffi_) { DirInfo *di = reinterpret_cast(ffi_->fh); return l::releasedir(di); } } mergerfs-2.28.1/src/gidcache.hpp0000664000175000017500000000267713427205466015104 0ustar bilebile/* 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.28.1/src/fs_path.hpp0000664000175000017500000000347013471033317014762 0ustar bilebile/* 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 { using std::string; string dirname(const char *path_); string dirname(const string *path_); string basename(const string &path); static inline void append(string &base, const char *suffix) { base += suffix; } static inline void append(string &base, const string &suffix) { base += suffix; } static inline string make(const string &base_, const char *suffix_) { return (base_ + suffix_); } static inline string make(const string *base_, const char *suffix_) { return (*base_ + suffix_); } static inline string make(const string *base_, const string *suffix_) { return (*base_ + *suffix_); } static inline string make(const string &base_, const string &suffix_) { return (base_ + suffix_); } } }; mergerfs-2.28.1/src/fuse_chmod.cpp0000664000175000017500000000524113471033317015443 0ustar bilebile/* 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_base_chmod.hpp" #include "fs_path.hpp" #include "rv.hpp" #include "rwlock.hpp" #include "ugid.hpp" #include #include #include using std::string; using std::vector; namespace l { static int chmod_loop_core(const string *basepath_, const char *fusepath_, const mode_t mode_, const int error_) { int rv; string fullpath; fullpath = fs::path::make(basepath_,fusepath_); rv = fs::chmod(fullpath,mode_); return error::calc(rv,error_,errno); } static int chmod_loop(const vector &basepaths_, const char *fusepath_, const mode_t mode_) { int error; error = -1; for(size_t i = 0, ei = basepaths_.size(); i != ei; i++) { error = l::chmod_loop_core(basepaths_[i],fusepath_,mode_,error); } return -error; } static int chmod(Policy::Func::Action actionFunc_, const Branches &branches_, const uint64_t minfreespace_, const char *fusepath_, const mode_t mode_) { int rv; vector basepaths; rv = actionFunc_(branches_,fusepath_,minfreespace_,basepaths); if(rv == -1) return -errno; return l::chmod_loop(basepaths,fusepath_,mode_); } } namespace FUSE { int chmod(const char *fusepath_, mode_t mode_) { const fuse_context *fc = fuse_get_context(); const Config &config = Config::get(fc); const ugid::Set ugid(fc->uid,fc->gid); const rwlock::ReadGuard readlock(&config.branches_lock); return l::chmod(config.chmod, config.branches, config.minfreespace, fusepath_, mode_); } } mergerfs-2.28.1/src/str.cpp0000664000175000017500000000712313467103610014137 0ustar bilebile/* 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 using std::string; using std::vector; using std::istringstream; namespace str { void split(vector &result, const char *str, const char delimiter) { string part; istringstream ss(str); while(std::getline(ss,part,delimiter)) result.push_back(part); } void split(vector &result, const string &str, const char delimiter) { return split(result,str.c_str(),delimiter); } 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); } 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 ends_with(const string &str_, const string &suffix_) { if(suffix_.size() > str_.size()) return false; return std::equal(suffix_.rbegin(), suffix_.rend(), str_.rbegin()); } } mergerfs-2.28.1/src/fusefunc.hpp0000664000175000017500000000666513471033317015165 0ustar bilebile/* 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 "category.hpp" #include #include class FuseFunc { public: struct Enum { enum Type { invalid = -1, BEGIN = 0, access = BEGIN, chmod, chown, create, getattr, getxattr, link, listxattr, mkdir, mknod, open, readlink, removexattr, rename, rmdir, setxattr, symlink, truncate, unlink, utimens, END }; }; private: Enum::Type _enum; std::string _str; Category::Enum::Type _category; public: FuseFunc() : _enum(invalid), _str(invalid), _category(Category::Enum::invalid) { } FuseFunc(const Enum::Type enum_, const std::string &str_, const Category::Enum::Type category_) : _enum(enum_), _str(str_), _category(category_) { } public: operator const Enum::Type() const { return _enum; } operator const std::string&() const { return _str; } operator const Category::Enum::Type() const { return _category; } operator const FuseFunc*() const { return this; } bool operator==(const std::string &str_) const { return _str == str_; } bool operator==(const Enum::Type enum_) const { return _enum == enum_; } bool operator!=(const FuseFunc &r) const { return _enum != r._enum; } bool operator<(const FuseFunc &r) const { return _enum < r._enum; } public: static const FuseFunc &find(const std::string&); static const FuseFunc &find(const Enum::Type); public: static const std::vector _fusefuncs_; static const FuseFunc * const fusefuncs; static const FuseFunc &invalid; static const FuseFunc &access; static const FuseFunc &chmod; static const FuseFunc &chown; static const FuseFunc &create; static const FuseFunc &getattr; static const FuseFunc &getxattr; static const FuseFunc &link; static const FuseFunc &listxattr; static const FuseFunc &mkdir; static const FuseFunc &mknod; static const FuseFunc &open; static const FuseFunc &readlink; static const FuseFunc &removexattr; static const FuseFunc &rename; static const FuseFunc &rmdir; static const FuseFunc &setxattr; static const FuseFunc &symlink; static const FuseFunc &truncate; static const FuseFunc &unlink; static const FuseFunc &utimens; }; mergerfs-2.28.1/src/fuse_setxattr.cpp0000664000175000017500000003657013474604542016247 0ustar bilebile/* 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_base_setxattr.hpp" #include "fs_glob.hpp" #include "fs_path.hpp" #include "fs_statvfs_cache.hpp" #include "num.hpp" #include "rv.hpp" #include "rwlock.hpp" #include "str.hpp" #include "ugid.hpp" #include #include #include #include #include static const char SECURITY_CAPABILITY[] = "security.capability"; using std::string; using std::vector; namespace l { static bool is_attrname_security_capability(const char *attrname_) { return (strcmp(attrname_,SECURITY_CAPABILITY) == 0); } static void split_attrval(const string &attrval, string &instruction, string &values) { size_t offset; offset = attrval.find_first_of('/'); instruction = attrval.substr(0,offset); if(offset != string::npos) values = attrval.substr(offset); } static int setxattr_srcmounts(const string &attrval, const int flags, Branches &branches_, pthread_rwlock_t &branches_lock) { const rwlock::WriteGuard wrg(&branches_lock); string instruction; string values; if((flags & XATTR_CREATE) == XATTR_CREATE) return -EEXIST; l::split_attrval(attrval,instruction,values); if(instruction == "+") branches_.add_end(values); else if(instruction == "+<") branches_.add_begin(values); else if(instruction == "+>") branches_.add_end(values); else if(instruction == "-") branches_.erase_fnmatch(values); else if(instruction == "-<") branches_.erase_begin(); else if(instruction == "->") branches_.erase_end(); else if(instruction == "=") branches_.set(values); else if(instruction.empty()) branches_.set(values); else return -EINVAL; return 0; } static int setxattr_uint64_t(const string &attrval, const int flags, uint64_t &uint) { int rv; if((flags & XATTR_CREATE) == XATTR_CREATE) return -EEXIST; rv = num::to_uint64_t(attrval,uint); if(rv == -1) return -EINVAL; return 0; } static int setxattr_double(const string &attrval_, const int flags_, double *d_) { int rv; if((flags_ & XATTR_CREATE) == XATTR_CREATE) return -EEXIST; rv = num::to_double(attrval_,d_); if(rv == -1) return -EINVAL; return 0; } static int setxattr_time_t(const string &attrval, const int flags, time_t &time) { int rv; if((flags & XATTR_CREATE) == XATTR_CREATE) return -EEXIST; rv = num::to_time_t(attrval,time); if(rv == -1) return -EINVAL; return 0; } static int setxattr_bool(const string &attrval, const int flags, bool &value) { if((flags & XATTR_CREATE) == XATTR_CREATE) return -EEXIST; if(attrval == "false") value = false; else if(attrval == "true") value = true; else return -EINVAL; return 0; } static int setxattr_xattr(const string &attrval_, const int flags_, int &xattr_) { if((flags_ & XATTR_CREATE) == XATTR_CREATE) return -EEXIST; if(attrval_ == "passthrough") xattr_ = 0; else if(attrval_ == "noattr") xattr_ = ENOATTR; else if(attrval_ == "nosys") xattr_ = ENOSYS; else return -EINVAL; return 0; } static int setxattr_statfs(const string &attrval_, const int flags_, Config::StatFS::Enum &enum_) { if((flags_ & XATTR_CREATE) == XATTR_CREATE) return -EEXIST; if(attrval_ == "base") enum_ = Config::StatFS::BASE; else if(attrval_ == "full") enum_ = Config::StatFS::FULL; else return -EINVAL; return 0; } static int setxattr_statfsignore(const string &attrval_, const int flags_, Config::StatFSIgnore::Enum &enum_) { if((flags_ & XATTR_CREATE) == XATTR_CREATE) return -EEXIST; if(attrval_ == "none") enum_ = Config::StatFSIgnore::NONE; else if(attrval_ == "ro") enum_ = Config::StatFSIgnore::RO; else if(attrval_ == "nc") enum_ = Config::StatFSIgnore::NC; else return -EINVAL; return 0; } static int setxattr(const string &attrval_, const int flags_, Config::CacheFiles &cache_files_) { Config::CacheFiles tmp; if((flags_ & XATTR_CREATE) == XATTR_CREATE) return -EEXIST; tmp = attrval_; if(!tmp.valid()) return -EINVAL; cache_files_ = tmp; return 0; } static int setxattr_controlfile_func_policy(Config &config, const string &funcname, const string &attrval, const int flags) { int rv; if((flags & XATTR_CREATE) == XATTR_CREATE) return -EEXIST; if(funcname == "open") config.open_cache.clear(); rv = config.set_func_policy(funcname,attrval); if(rv == -1) return -errno; return 0; } static int setxattr_controlfile_category_policy(Config &config, const string &categoryname, const string &attrval, const int flags) { int rv; if((flags & XATTR_CREATE) == XATTR_CREATE) return -EEXIST; if(categoryname == "search") config.open_cache.clear(); rv = config.set_category_policy(categoryname,attrval); if(rv == -1) return -errno; return 0; } static int setxattr_statfs_timeout(const string &attrval_, const int flags_) { int rv; uint64_t timeout; rv = l::setxattr_uint64_t(attrval_,flags_,timeout); if(rv >= 0) fs::statvfs_cache_timeout(timeout); return rv; } static int setxattr_controlfile_cache_attr(const string &attrval_, const int flags_) { int rv; double d; rv = l::setxattr_double(attrval_,flags_,&d); if(rv >= 0) fuse_config_set_attr_timeout(fuse_get_context()->fuse,d); return rv; } static int setxattr_controlfile_cache_entry(const string &attrval_, const int flags_) { int rv; double d; rv = l::setxattr_double(attrval_,flags_,&d); if(rv >= 0) fuse_config_set_entry_timeout(fuse_get_context()->fuse,d); return rv; } static int setxattr_controlfile_cache_negative_entry(const string &attrval_, const int flags_) { int rv; double d; rv = l::setxattr_double(attrval_,flags_,&d); if(rv >= 0) fuse_config_set_negative_entry_timeout(fuse_get_context()->fuse,d); return rv; } static int setxattr_controlfile(Config &config, const string &attrname, const string &attrval, const int flags) { vector attr; str::split(attr,attrname,'.'); switch(attr.size()) { case 3: if(attr[2] == "srcmounts") return l::setxattr_srcmounts(attrval, flags, config.branches, config.branches_lock); else if(attr[2] == "branches") return l::setxattr_srcmounts(attrval, flags, config.branches, config.branches_lock); else if(attr[2] == "minfreespace") return l::setxattr_uint64_t(attrval, flags, config.minfreespace); else if(attr[2] == "moveonenospc") return l::setxattr_bool(attrval, flags, config.moveonenospc); else if(attr[2] == "dropcacheonclose") return l::setxattr_bool(attrval, flags, config.dropcacheonclose); else if(attr[2] == "symlinkify") return l::setxattr_bool(attrval, flags, config.symlinkify); else if(attr[2] == "symlinkify_timeout") return l::setxattr_time_t(attrval, flags, config.symlinkify_timeout); else if(attr[2] == "ignorepponrename") return l::setxattr_bool(attrval, flags, config.ignorepponrename); else if(attr[2] == "security_capability") return l::setxattr_bool(attrval, flags, config.security_capability); else if(attr[2] == "xattr") return l::setxattr_xattr(attrval, flags, config.xattr); else if(attr[2] == "link_cow") return l::setxattr_bool(attrval, flags, config.link_cow); else if(attr[2] == "statfs") return l::setxattr_statfs(attrval, flags, config.statfs); else if(attr[2] == "statfs_ignore") return l::setxattr_statfsignore(attrval, flags, config.statfs_ignore); else if(attr[2] == "direct_io") return l::setxattr_bool(attrval, flags, config.direct_io); break; case 4: if(attr[2] == "category") return l::setxattr_controlfile_category_policy(config, attr[3], attrval, flags); else if(attr[2] == "func") return l::setxattr_controlfile_func_policy(config, attr[3], attrval, flags); else if((attr[2] == "cache") && (attr[3] == "open")) return l::setxattr_uint64_t(attrval, flags, config.open_cache.timeout); else if((attr[2] == "cache") && (attr[3] == "statfs")) return l::setxattr_statfs_timeout(attrval,flags); else if((attr[2] == "cache") && (attr[3] == "attr")) return l::setxattr_controlfile_cache_attr(attrval,flags); else if((attr[2] == "cache") && (attr[3] == "entry")) return l::setxattr_controlfile_cache_entry(attrval,flags); else if((attr[2] == "cache") && (attr[3] == "negative_entry")) return l::setxattr_controlfile_cache_negative_entry(attrval,flags); else if((attr[2] == "cache") && (attr[3] == "readdir")) return l::setxattr_bool(attrval,flags,config.cache_readdir); else if((attr[2] == "cache") && (attr[3] == "files")) return l::setxattr(attrval,flags,config.cache_files); break; default: break; } return -EINVAL; } static int setxattr_loop_core(const string *basepath, const char *fusepath, const char *attrname, const char *attrval, const size_t attrvalsize, const int flags, const int error) { int rv; string fullpath; fullpath = fs::path::make(basepath,fusepath); rv = fs::lsetxattr(fullpath,attrname,attrval,attrvalsize,flags); return error::calc(rv,error,errno); } static int setxattr_loop(const vector &basepaths, const char *fusepath, const char *attrname, const char *attrval, const size_t attrvalsize, const int flags) { int error; error = -1; for(size_t i = 0, ei = basepaths.size(); i != ei; i++) { error = l::setxattr_loop_core(basepaths[i],fusepath, attrname,attrval,attrvalsize,flags, error); } return -error; } static int setxattr(Policy::Func::Action actionFunc, const Branches &branches_, const uint64_t minfreespace, const char *fusepath, const char *attrname, const char *attrval, const size_t attrvalsize, const int flags) { int rv; vector basepaths; rv = actionFunc(branches_,fusepath,minfreespace,basepaths); if(rv == -1) return -errno; return l::setxattr_loop(basepaths,fusepath,attrname,attrval,attrvalsize,flags); } } namespace FUSE { int setxattr(const char *fusepath, const char *attrname, const char *attrval, size_t attrvalsize, int flags) { const fuse_context *fc = fuse_get_context(); const Config &config = Config::get(fc); if(fusepath == config.controlfile) return l::setxattr_controlfile(Config::get_writable(), attrname, string(attrval,attrvalsize), flags); if((config.security_capability == false) && l::is_attrname_security_capability(attrname)) return -ENOATTR; if(config.xattr) return -config.xattr; const ugid::Set ugid(fc->uid,fc->gid); const rwlock::ReadGuard readlock(&config.branches_lock); return l::setxattr(config.setxattr, config.branches, config.minfreespace, fusepath, attrname, attrval, attrvalsize, flags); } } mergerfs-2.28.1/src/fuse_open.hpp0000664000175000017500000000161713471033317015322 0ustar bilebile/* 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 open(const char *fusepath_, fuse_file_info *ffi_); } mergerfs-2.28.1/src/fuse_fgetattr.hpp0000664000175000017500000000177113471033317016202 0ustar bilebile/* 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 FUSE { int fgetattr(const char *fusepath_, struct stat *st_, fuse_file_info *ffi_); } mergerfs-2.28.1/src/fs_inode.hpp0000664000175000017500000000234313471033317015122 0ustar bilebile/* 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 { namespace inode { static const uint64_t MAGIC = 0x7472617065786974; inline void recompute(struct stat *st_) { /* not ideal to do this at runtime but likely gets optimized out */ if(sizeof(st_->st_ino) == 4) st_->st_ino |= ((uint32_t)st_->st_dev << 16); else st_->st_ino |= ((uint64_t)st_->st_dev << 32); } } } mergerfs-2.28.1/src/fs_base_statvfs.hpp0000664000175000017500000000323113471033317016505 0ustar bilebile/* 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_base_close.hpp" #include "fs_base_open.hpp" #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.28.1/src/fuse_link.hpp0000664000175000017500000000155713471033317015321 0ustar bilebile/* 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 link(const char *from_, const char *to_); } mergerfs-2.28.1/src/fs_base_fchown.hpp0000664000175000017500000000326313471033317016304 0ustar bilebile/* 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_base_stat.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.28.1/src/fs_ficlone_linux.icpp0000664000175000017500000000206413467101456017033 0ustar bilebile/* 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_base_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.28.1/src/gidcache.cpp0000664000175000017500000000672713467103610015067 0ustar bilebile/* 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.28.1/src/fuse_flush.cpp0000664000175000017500000000243113471033317015470 0ustar bilebile/* 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_base_close.hpp" #include "fs_base_dup.hpp" #include 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 char *fusepath_, fuse_file_info *ffi_) { FileInfo *fi = reinterpret_cast(ffi_->fh); return l::flush(fi->fd); } } mergerfs-2.28.1/src/fuse_init.cpp0000664000175000017500000000504513475361401015320 0ustar bilebile/* 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 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_, bool *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 &c_) { if(l::capable(conn_,FUSE_CAP_MAX_PAGES)) { l::want(conn_,FUSE_CAP_MAX_PAGES); conn_->max_pages = c_.fuse_msg_size; } else { c_.fuse_msg_size = FUSE_DEFAULT_MAX_PAGES_PER_REQ; } } } namespace FUSE { void * init(fuse_conn_info *conn_) { Config &c = Config::get_writable(); ugid::init(); l::want_if_capable(conn_,FUSE_CAP_ASYNC_DIO); l::want_if_capable(conn_,FUSE_CAP_ASYNC_READ,&c.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,&c.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_POSIX_ACL,&c.posix_acl); l::want_if_capable_max_pages(conn_,c); return &c; } } mergerfs-2.28.1/src/fuse_getxattr.hpp0000664000175000017500000000167513471033317016227 0ustar bilebile/* 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.28.1/src/fs_base_fallocate_unsupported.icpp0000664000175000017500000000174313367322163021573 0ustar bilebile/* 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.28.1/src/num.hpp0000664000175000017500000000201713474062170014133 0ustar bilebile/* 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 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_); } mergerfs-2.28.1/src/fuse_opendir.hpp0000664000175000017500000000162513471033317016020 0ustar bilebile/* 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 opendir(const char *fusepath_, fuse_file_info *ffi_); } mergerfs-2.28.1/src/fs_base_futimesat.cpp0000664000175000017500000000162113367322163017014 0ustar bilebile/* 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__ # include "fs_base_futimesat_osx.icpp" #else # include "fs_base_futimesat_generic.icpp" #endif mergerfs-2.28.1/src/fs_base_utime.hpp0000664000175000017500000000322513471033317016141 0ustar bilebile/* 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 #ifdef __linux__ # include "fs_base_utime_utimensat.hpp" #elif __FreeBSD__ >= 11 # include "fs_base_utime_utimensat.hpp" #else # include "fs_base_utime_generic.hpp" #endif #include "fs_base_stat.hpp" namespace fs { static inline int utime(const std::string &path_, const struct stat &st_) { struct timespec times[2]; times[0] = *fs::stat_atime(&st_); times[1] = *fs::stat_mtime(&st_); return fs::utime(AT_FDCWD,path_,times,0); } static inline int futime(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); } static inline int lutime(const std::string &path_, const struct timespec times_[2]) { return fs::utime(AT_FDCWD,path_,times_,AT_SYMLINK_NOFOLLOW); } } mergerfs-2.28.1/src/fuse_listxattr.cpp0000664000175000017500000001047613475343677016436 0ustar bilebile/* 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 "buildvector.hpp" #include "category.hpp" #include "config.hpp" #include "errno.hpp" #include "fs_base_listxattr.hpp" #include "fs_path.hpp" #include "rwlock.hpp" #include "ugid.hpp" #include "xattr.hpp" #include #include #include #include using std::string; using std::vector; namespace l { static int listxattr_controlfile(char *list_, const size_t size_) { string xattrs; const vector strs = buildvector ("user.mergerfs.async_read") ("user.mergerfs.branches") ("user.mergerfs.cache.attr") ("user.mergerfs.cache.entry") ("user.mergerfs.cache.files") ("user.mergerfs.cache.negative_entry") ("user.mergerfs.cache.open") ("user.mergerfs.cache.readdir") ("user.mergerfs.cache.statfs") ("user.mergerfs.cache.symlinks") ("user.mergerfs.direct_io") ("user.mergerfs.dropcacheonclose") ("user.mergerfs.fuse_msg_size") ("user.mergerfs.ignorepponrename") ("user.mergerfs.link_cow") ("user.mergerfs.minfreespace") ("user.mergerfs.moveonenospc") ("user.mergerfs.nullrw") ("user.mergerfs.pid") ("user.mergerfs.policies") ("user.mergerfs.posix_acl") ("user.mergerfs.security_capability") ("user.mergerfs.srcmounts") ("user.mergerfs.statfs") ("user.mergerfs.statfs_ignore") ("user.mergerfs.symlinkify") ("user.mergerfs.symlinkify_timeout") ("user.mergerfs.version") ("user.mergerfs.xattr") ; xattrs.reserve(1024); for(size_t i = 0; i < strs.size(); i++) xattrs += (strs[i] + '\0'); for(size_t i = Category::Enum::BEGIN; i < Category::Enum::END; i++) xattrs += ("user.mergerfs.category." + (std::string)*Category::categories[i] + '\0'); for(size_t i = FuseFunc::Enum::BEGIN; i < FuseFunc::Enum::END; i++) xattrs += ("user.mergerfs.func." + (std::string)*FuseFunc::fusefuncs[i] + '\0'); 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(Policy::Func::Search searchFunc_, const Branches &branches_, const uint64_t minfreespace_, const char *fusepath_, char *list_, const size_t size_) { int rv; string fullpath; vector basepaths; rv = searchFunc_(branches_,fusepath_,minfreespace_,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_) { const fuse_context *fc = fuse_get_context(); const Config &config = Config::get(fc); if(fusepath_ == config.controlfile) return l::listxattr_controlfile(list_,size_); switch(config.xattr) { case 0: break; case ENOATTR: return 0; default: return -config.xattr; } const ugid::Set ugid(fc->uid,fc->gid); const rwlock::ReadGuard readlock(&config.branches_lock); return l::listxattr(config.listxattr, config.branches, config.minfreespace, fusepath_, list_, size_); } } mergerfs-2.28.1/src/hashset.hpp0000664000175000017500000000255113467101456015001 0ustar bilebile/* 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 "fasthash.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_) { int rv; uint64_t h; khint_t key; h = fasthash64(str_,strlen(str_),0x7472617065786974); key = kh_put(hashset,_set,h,&rv); if(rv == 0) return 0; kh_key(_set,key) = h; return rv; } inline int size(void) { return kh_size(_set); } private: khash_t(hashset) *_set; }; mergerfs-2.28.1/src/policy_newest.cpp0000664000175000017500000001127513471033317016217 0ustar bilebile/* 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.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 #include #include #include using std::string; using std::vector; namespace newest { static int create(const Branches &branches_, const char *fusepath, const uint64_t minfreespace, vector &paths) { int rv; int error; time_t newest; struct stat st; fs::info_t info; const Branch *branch; const string *newestbasepath; error = ENOENT; newest = std::numeric_limits::min(); newestbasepath = NULL; for(size_t i = 0, ei = branches_.size(); i != ei; i++) { branch = &branches_[i]; if(!fs::exists(branch->path,fusepath,&st)) error_and_continue(error,ENOENT); if(branch->ro_or_nc()) error_and_continue(error,EROFS); 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 < minfreespace) error_and_continue(error,ENOSPC); newest = st.st_mtime; newestbasepath = &branch->path; } if(newestbasepath == NULL) return (errno=error,-1); paths.push_back(newestbasepath); return 0; } static int action(const Branches &branches_, const char *fusepath, vector &paths) { int rv; int error; bool readonly; time_t newest; struct stat st; const Branch *branch; const string *newestbasepath; error = ENOENT; newest = std::numeric_limits::min(); newestbasepath = NULL; for(size_t i = 0, ei = branches_.size(); i != ei; i++) { branch = &branches_[i]; if(!fs::exists(branch->path,fusepath,&st)) error_and_continue(error,ENOENT); if(branch->ro()) error_and_continue(error,EROFS); 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; newestbasepath = &branch->path; } if(newestbasepath == NULL) return (errno=error,-1); paths.push_back(newestbasepath); return 0; } static int search(const Branches &branches_, const char *fusepath, vector &paths) { time_t newest; struct stat st; const Branch *branch; const string *newestbasepath; newest = std::numeric_limits::min(); newestbasepath = NULL; for(size_t i = 0, ei = branches_.size(); i != ei; i++) { branch = &branches_[i]; if(!fs::exists(branch->path,fusepath,&st)) continue; if(st.st_mtime < newest) continue; newest = st.st_mtime; newestbasepath = &branch->path; } if(newestbasepath == NULL) return (errno=ENOENT,-1); paths.push_back(newestbasepath); return 0; } } int Policy::Func::newest(const Category::Enum::Type type, const Branches &branches_, const char *fusepath, const uint64_t minfreespace, vector &paths) { switch(type) { case Category::Enum::create: return newest::create(branches_,fusepath,minfreespace,paths); case Category::Enum::action: return newest::action(branches_,fusepath,paths); case Category::Enum::search: default: return newest::search(branches_,fusepath,paths); } } mergerfs-2.28.1/src/fs_info.hpp0000664000175000017500000000167113467101456014767 0ustar bilebile/* 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.28.1/src/option_parser.cpp0000664000175000017500000004065313477572320016231 0ustar bilebile/* 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_glob.hpp" #include "fs_statvfs_cache.hpp" #include "num.hpp" #include "policy.hpp" #include "str.hpp" #include "version.hpp" #include #include #include #include #include #include #include #include #include #include #include using std::string; using std::vector; enum { MERGERFS_OPT_HELP, MERGERFS_OPT_VERSION }; static void set_option(fuse_args *args, const std::string &option_) { fuse_opt_add_arg(args,"-o"); fuse_opt_add_arg(args,option_.c_str()); } static void set_kv_option(fuse_args *args, const std::string &key, const std::string &value) { std::string option; option = key + '=' + value; set_option(args,option); } static void set_fsname(fuse_args *args_, Config *config_) { if(config_->fsname.empty()) { vector branches; config_->branches.to_paths(branches); if(branches.size() > 0) config_->fsname = str::remove_common_prefix_and_join(branches,':'); } set_kv_option(args_,"fsname",config_->fsname); } static void set_subtype(fuse_args *args) { set_kv_option(args,"subtype","mergerfs"); } static void set_default_options(fuse_args *args) { set_option(args,"default_permissions"); } static int parse_and_process(const std::string &value_, uint16_t &uint16_, uint16_t min_, uint16_t max_) { int rv; uint64_t uint64; rv = num::to_uint64_t(value_,uint64); if(rv == -1) return 1; if((uint64 > max_) || (uint64 < min_)) return 1; uint16_ = uint64; return 0; } static int parse_and_process(const std::string &value_, uint64_t &int_) { int rv; rv = num::to_uint64_t(value_,int_); if(rv == -1) return 1; return 0; } static int parse_and_process(const std::string &value, time_t &time) { int rv; rv = num::to_time_t(value,time); if(rv == -1) return 1; return 0; } static int parse_and_process(const std::string &value_, bool &boolean_) { if((value_ == "false") || (value_ == "0") || (value_ == "off")) boolean_ = false; else if((value_ == "true") || (value_ == "1") || (value_ == "on")) boolean_ = true; else return 1; return 0; } static int parse_and_process(const std::string &value_, std::string &str_) { str_ = value_; return 0; } static int parse_and_process(const std::string &value_, Config::CacheFiles &cache_files_) { Config::CacheFiles tmp; tmp = value_; if(!tmp.valid()) return 1; cache_files_ = tmp; return 0; } static int parse_and_process_errno(const std::string &value_, int &errno_) { if(value_ == "passthrough") errno_ = 0; else if(value_ == "nosys") errno_ = ENOSYS; else if(value_ == "noattr") errno_ = ENOATTR; else return 1; return 0; } static int parse_and_process_statfs(const std::string &value_, Config::StatFS::Enum &enum_) { if(value_ == "base") enum_ = Config::StatFS::BASE; else if(value_ == "full") enum_ = Config::StatFS::FULL; else return 1; return 0; } static int parse_and_process_statfsignore(const std::string &value_, Config::StatFSIgnore::Enum &enum_) { if(value_ == "none") enum_ = Config::StatFSIgnore::NONE; else if(value_ == "ro") enum_ = Config::StatFSIgnore::RO; else if(value_ == "nc") enum_ = Config::StatFSIgnore::NC; else return 1; return 0; } static int parse_and_process_statfs_cache(const std::string &value_) { int rv; uint64_t timeout; rv = num::to_uint64_t(value_,timeout); if(rv == -1) return 1; fs::statvfs_cache_timeout(timeout); return 0; } static int parse_and_process_cache(Config &config_, const string &func_, const string &value_, fuse_args *outargs) { if(func_ == "open") return parse_and_process(value_,config_.open_cache.timeout); else if(func_ == "statfs") return parse_and_process_statfs_cache(value_); else if(func_ == "entry") return (set_kv_option(outargs,"entry_timeout",value_),0); else if(func_ == "negative_entry") return (set_kv_option(outargs,"negative_timeout",value_),0); else if(func_ == "attr") return (set_kv_option(outargs,"attr_timeout",value_),0); else if(func_ == "symlinks") return parse_and_process(value_,config_.cache_symlinks); else if(func_ == "readdir") return parse_and_process(value_,config_.cache_readdir); else if(func_ == "files") return parse_and_process(value_,config_.cache_files); return 1; } static int parse_and_process_arg(Config &config, const std::string &arg) { if(arg == "defaults") return 0; else if(arg == "hard_remove") return 0; else if(arg == "direct_io") return (config.direct_io=true,0); else if(arg == "kernel_cache") return (config.kernel_cache=true,0); else if(arg == "auto_cache") return (config.auto_cache=true,0); else if(arg == "async_read") return (config.async_read=true,0); else if(arg == "sync_read") return (config.async_read=false,0); else if(arg == "atomic_o_trunc") return 0; else if(arg == "big_writes") return 0; return 1; } static int parse_and_process_kv_arg(Config &config, const std::string &key, const std::string &value, fuse_args *outargs) { int rv; std::vector keypart; rv = -1; str::split(keypart,key,'.'); if(keypart.size() == 2) { if(keypart[0] == "func") rv = config.set_func_policy(keypart[1],value); else if(keypart[0] == "category") rv = config.set_category_policy(keypart[1],value); else if(keypart[0] == "cache") rv = parse_and_process_cache(config,keypart[1],value,outargs); } else { if(key == "minfreespace") rv = parse_and_process(value,config.minfreespace); else if(key == "moveonenospc") rv = parse_and_process(value,config.moveonenospc); else if(key == "dropcacheonclose") rv = parse_and_process(value,config.dropcacheonclose); else if(key == "symlinkify") rv = parse_and_process(value,config.symlinkify); else if(key == "symlinkify_timeout") rv = parse_and_process(value,config.symlinkify_timeout); else if(key == "nullrw") rv = parse_and_process(value,config.nullrw); else if(key == "ignorepponrename") rv = parse_and_process(value,config.ignorepponrename); else if(key == "security_capability") rv = parse_and_process(value,config.security_capability); else if(key == "link_cow") rv = parse_and_process(value,config.link_cow); else if(key == "xattr") rv = parse_and_process_errno(value,config.xattr); else if(key == "statfs") rv = parse_and_process_statfs(value,config.statfs); else if(key == "statfs_ignore") rv = parse_and_process_statfsignore(value,config.statfs_ignore); else if(key == "fsname") rv = parse_and_process(value,config.fsname); else if(key == "posix_acl") rv = parse_and_process(value,config.posix_acl); else if(key == "direct_io") rv = parse_and_process(value,config.direct_io); else if(key == "kernel_cache") rv = parse_and_process(value,config.kernel_cache); else if(key == "auto_cache") rv = parse_and_process(value,config.auto_cache); else if(key == "async_read") rv = parse_and_process(value,config.async_read); else if(key == "max_write") rv = 0; else if(key == "fuse_msg_size") rv = parse_and_process(value,config.fuse_msg_size, 1, FUSE_MAX_MAX_PAGES); } if(rv == -1) rv = 1; return rv; } static int process_opt(Config &config, const std::string &arg, fuse_args *outargs) { int rv; std::vector argvalue; str::split(argvalue,arg,'='); switch(argvalue.size()) { case 1: rv = parse_and_process_arg(config,argvalue[0]); break; case 2: rv = parse_and_process_kv_arg(config,argvalue[0],argvalue[1],outargs); break; default: rv = 1; break; }; return rv; } static int process_branches(const char *arg, Config &config) { config.branches.set(arg); return 0; } static int process_destmounts(const char *arg, Config &config) { config.destmount = 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 func.=

Set function to policy

\n" " -o category.=

Set functions in category to

\n" " -o cache.open= 'open' policy cache timeout in seconds.\n" " default = 0 (disabled)\n" " -o cache.statfs= '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.symlinks=\n" " Enable kernel caching of symlinks (if supported)\n" " default = false\n" " -o cache.readdir=\n" " Enable kernel caching readdir (if supported)\n" " default = false\n" " -o cache.attr= File attribute cache timeout in seconds.\n" " default = 1\n" " -o cache.entry= File name lookup cache timeout in seconds.\n" " default = 1\n" " -o cache.negative_entry=\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 minfreespace= Minimum free space needed for certain policies.\n" " default = 4G\n" " -o moveonenospc= Try to move file to another drive when ENOSPC\n" " on write. default = false\n" " -o dropcacheonclose=\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= 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=\n" " Timeout in seconds before files turn to symlinks.\n" " default = 3600\n" " -o nullrw= Disables reads and writes. For benchmarking.\n" " default = false\n" " -o ignorepponrename=\n" " Ignore path preserving when performing renames\n" " and links. default = false\n" " -o link_cow= Delink/clone file on open to simulate CoW.\n" " default = false\n" " -o security_capability=\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= Enable POSIX ACL support. default = false\n" " -o async_read= 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) { int rv = 0; Config &config = *(Config*)data; switch(key) { case FUSE_OPT_KEY_OPT: rv = process_opt(config,arg,outargs); break; case FUSE_OPT_KEY_NONOPT: rv = config.branches.empty() ? process_branches(arg,config) : process_destmounts(arg,config); break; case MERGERFS_OPT_HELP: usage(); close(2); dup(1); fuse_opt_add_arg(outargs,"-ho"); break; case MERGERFS_OPT_VERSION: std::cout << "mergerfs version: " << (MERGERFS_VERSION[0] ? MERGERFS_VERSION : "unknown") << std::endl; fuse_opt_add_arg(outargs,"--version"); break; default: break; } return rv; } namespace options { void parse(fuse_args *args_, Config *config_) { 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_, config_, opts, ::option_processor); set_default_options(args_); set_fsname(args_,config_); set_subtype(args_); } } mergerfs-2.28.1/src/fuse_rmdir.cpp0000664000175000017500000000474113471033317015472 0ustar bilebile/* 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_base_rmdir.hpp" #include "fs_path.hpp" #include "rv.hpp" #include "rwlock.hpp" #include "ugid.hpp" #include #include #include using std::string; using std::vector; namespace l { static int rmdir_loop_core(const string *basepath_, const char *fusepath_, const int error_) { int rv; string fullpath; fullpath = fs::path::make(basepath_,fusepath_); rv = fs::rmdir(fullpath); return error::calc(rv,error_,errno); } static int rmdir_loop(const vector &basepaths_, const char *fusepath_) { int error; error = -1; for(size_t i = 0, ei = basepaths_.size(); i != ei; i++) { error = l::rmdir_loop_core(basepaths_[i],fusepath_,error); } return -error; } static int rmdir(Policy::Func::Action actionFunc_, const Branches &branches_, const uint64_t minfreespace_, const char *fusepath_) { int rv; vector basepaths; rv = actionFunc_(branches_,fusepath_,minfreespace_,basepaths); if(rv == -1) return -errno; return l::rmdir_loop(basepaths,fusepath_); } } namespace FUSE { int rmdir(const char *fusepath_) { const fuse_context *fc = fuse_get_context(); const Config &config = Config::get(fc); const ugid::Set ugid(fc->uid,fc->gid); const rwlock::ReadGuard readguard(&config.branches_lock); return l::rmdir(config.rmdir, config.branches, config.minfreespace, fusepath_); } } mergerfs-2.28.1/src/fs_base_open.hpp0000664000175000017500000000275713471033317015770 0ustar bilebile/* 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_); } } mergerfs-2.28.1/src/fs_base_fallocate.cpp0000664000175000017500000000207013467103610016737 0ustar bilebile/* 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_base_fallocate_linux.icpp" #elif _XOPEN_SOURCE >= 600 || _POSIX_C_SOURCE >= 200112L # include "fs_base_fallocate_posix.icpp" #elif __APPLE__ # include "fs_base_fallocate_osx.icpp" #else # include "fs_base_fallocate_unsupported.icpp" #endif mergerfs-2.28.1/src/fs_base_fchmod.hpp0000664000175000017500000000322613471033317016257 0ustar bilebile/* 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_base_stat.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.28.1/src/fuse_unlink.hpp0000664000175000017500000000153513471033317015660 0ustar bilebile/* 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.28.1/src/errno.hpp0000664000175000017500000000214313427205466014466 0ustar bilebile/* 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.28.1/src/fs_base_fallocate_linux.icpp0000664000175000017500000000200113467103610020321 0ustar bilebile/* 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 "errno.hpp" 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.28.1/src/policy_lus.cpp0000664000175000017500000000500113471033317015503 0ustar bilebile/* 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.hpp" #include "fs_exists.hpp" #include "fs_info.hpp" #include "fs_path.hpp" #include "policy.hpp" #include "policy_error.hpp" #include #include #include using std::string; using std::vector; namespace lus { static int create(const Branches &branches_, const uint64_t minfreespace, vector &paths) { int rv; int error; uint64_t lus; fs::info_t info; const Branch *branch; const string *lusbasepath; error = ENOENT; lus = std::numeric_limits::max(); lusbasepath = NULL; for(size_t i = 0, ei = branches_.size(); i != ei; i++) { branch = &branches_[i]; 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 < minfreespace) error_and_continue(error,ENOSPC); if(info.spaceused >= lus) continue; lus = info.spaceused; lusbasepath = &branch->path; } if(lusbasepath == NULL) return (errno=error,-1); paths.push_back(lusbasepath); return 0; } } int Policy::Func::lus(const Category::Enum::Type type, const Branches &branches_, const char *fusepath, const uint64_t minfreespace, vector &paths) { if(type == Category::Enum::create) return lus::create(branches_,minfreespace,paths); return Policy::Func::eplus(type,branches_,fusepath,minfreespace,paths); } mergerfs-2.28.1/src/fs_attr_unsupported.icpp0000664000175000017500000000211013367322163017606 0ustar bilebile/* 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 "errno.hpp" 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.28.1/src/fuse_readdir.cpp0000664000175000017500000000605313471033317015765 0ustar bilebile/* 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 "config.hpp" #include "dirinfo.hpp" #include "errno.hpp" #include "fs_base_closedir.hpp" #include "fs_base_dirfd.hpp" #include "fs_base_opendir.hpp" #include "fs_base_readdir.hpp" #include "fs_base_stat.hpp" #include "fs_devid.hpp" #include "fs_inode.hpp" #include "fs_path.hpp" #include "hashset.hpp" #include "rwlock.hpp" #include "ugid.hpp" #include #include #include using std::string; using std::vector; #define NO_OFFSET 0 namespace l { static int readdir(const Branches &branches_, const char *dirname_, void *buf_, const fuse_fill_dir_t filler_) { HashSet names; string basepath; struct stat st = {0}; for(size_t i = 0, ei = branches_.size(); i != ei; i++) { int rv; int dirfd; DIR *dh; basepath = fs::path::make(&branches_[i].path,dirname_); dh = fs::opendir(basepath); if(!dh) continue; dirfd = fs::dirfd(dh); st.st_dev = fs::devid(dirfd); if(st.st_dev == (dev_t)-1) st.st_dev = i; rv = 0; for(struct dirent *de = fs::readdir(dh); de && !rv; de = fs::readdir(dh)) { rv = names.put(de->d_name); if(rv == 0) continue; st.st_ino = de->d_ino; st.st_mode = DTTOIF(de->d_type); fs::inode::recompute(&st); rv = filler_(buf_,de->d_name,&st,NO_OFFSET); if(rv) return (fs::closedir(dh),-ENOMEM); } fs::closedir(dh); } return 0; } } namespace FUSE { int readdir(const char *fusepath_, void *buf_, fuse_fill_dir_t filler_, off_t offset_, fuse_file_info *ffi_) { DirInfo *di = reinterpret_cast(ffi_->fh); const fuse_context *fc = fuse_get_context(); const Config &config = Config::get(fc); const ugid::Set ugid(fc->uid,fc->gid); const rwlock::ReadGuard readlock(&config.branches_lock); return l::readdir(config.branches, di->fusepath.c_str(), buf_, filler_); } } mergerfs-2.28.1/src/fuse_access.hpp0000664000175000017500000000157113471033317015621 0ustar bilebile/* 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.28.1/src/fuse_truncate.hpp0000664000175000017500000000162513471033317016205 0ustar bilebile/* 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.28.1/src/fuse_getattr.hpp0000664000175000017500000000167613471033317016040 0ustar bilebile/* 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_); } mergerfs-2.28.1/src/fs_copyfile.cpp0000664000175000017500000000454713471033317015641 0ustar bilebile/* 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_base_stat.hpp" #include "fs_base_lseek.hpp" #include "fs_base_read.hpp" #include "fs_base_write.hpp" #include using std::vector; static int writen(const int fd_, const char *buf_, const size_t count_) { size_t nleft; ssize_t nwritten; nleft = count_; do { nwritten = fs::write(fd_,buf_,nleft); if((nwritten == -1) && (errno == EINTR)) continue; if(nwritten == -1) return -1; nleft -= nwritten; buf_ += nwritten; } while(nleft > 0); return count_; } static int copyfile(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 = writen(dst_fd_,&buf[0],nr); if(nw == -1) return -1; totalwritten += nw; } return totalwritten; } namespace fs { int copyfile(const int src_fd_, const int dst_fd_) { int rv; struct stat st; rv = fs::fstat(src_fd_,&st); if(rv == -1) return rv; return ::copyfile(src_fd_, dst_fd_, st.st_size, st.st_blksize); } }