pax_global_header00006660000000000000000000000064124554400240014512gustar00rootroot0000000000000052 comment=8176353f9c6ac04eb72cb8cea166e8c2c229ffa9 unionfs-fuse-1.0/000077500000000000000000000000001245544002400137735ustar00rootroot00000000000000unionfs-fuse-1.0/.gitignore000066400000000000000000000011631245544002400157640ustar00rootroot00000000000000# unionfs specific unionfs.log unionfs unionfsctl # build directory build/* BUILD/* # codelite project files *.project # vi tmp files *.swp # build directory build/* # vi tmp files *.swp # Autofoo entries *.o *.la *.lo *.pyc *.ko *.a .libs .deps *.cache .cvsignore compile configure.status configure.lineno depcomp libtool ltconfig libltdl py-compile # BEAM Entries *.beam parser-messages MISC_ERRORS cscope.files cscope.out updates logs # OS and Editor Artifacts .DS_Store *.diff *~ # Misc TAGS tags .gres.* *.orig .gdb_history # kdevelop *.kdevelop* *.kdevses Doxyfile # Subversion foo .svn # diff rejects *.rej unionfs-fuse-1.0/CMakeLists.txt000066400000000000000000000023221245544002400165320ustar00rootroot00000000000000project(unionfs-fuse C) cmake_minimum_required(VERSION 2.0) INCLUDE (CheckIncludeFiles) # Set a default build type for single-configuration # CMake generators if no build type is set. IF(NOT CMAKE_CONFIGURATION_TYPES AND NOT CMAKE_BUILD_TYPE) SET(CMAKE_BUILD_TYPE RelWithDebInfo) ENDIF(NOT CMAKE_CONFIGURATION_TYPES AND NOT CMAKE_BUILD_TYPE) # Select flags. SET(CMAKE_C_FLAGS "-pipe -W -Wall -DFORTIFY_SOURCE=2") SET(CMAKE_C_FLAGS_RELWITHDEBINFO "-O2 -g") SET(CMAKE_C_FLAGS_RELEASE "-O2") SET(CMAKE_C_FLAGS_DEBUG "-O0 -g -DDEBUG") if (UNIX AND APPLE) include_directories("/usr/local/include/osxfuse/fuse") endif() add_definitions(-D_FILE_OFFSET_BITS=64 -DFUSE_USE_VERSION=26) option(WITH_XATTR "Enable support for extended attributes" OFF) # .h include files IF (WITH_XATTR) CHECK_INCLUDE_FILES("sys/xattr.h" HAVE_LIBC_XATTR) CHECK_INCLUDE_FILES("attr/xattr.h" HAVE_LIBATTR_XATTR) IF (HAVE_LIBC_XATTR) add_definitions(-DLIBC_XATTR) ELSEIF(HAVE_LIBATTR_XATTR) add_definitions(-DLIBATTR_XATTR) ENDIF() IF (NOT HAVE_LIBC_XATTR AND NOT HAVE_LIBATTR_XATTR) add_definitions(-DDISABLE_XATTR) ENDIF() ELSE (WITH_XATTR) add_definitions(-DDISABLE_XATTR) ENDIF (WITH_XATTR) add_subdirectory(src) add_subdirectory(man) unionfs-fuse-1.0/CREDITS000066400000000000000000000026041245544002400150150ustar00rootroot00000000000000This is the list of people who have contributed to unionfs-fuse: Maxim Konushikhin R. Ramkumar John Cobb Bernd Schubert Samuel Gelineau Mattias Wadman Lucas C. Villa Real Frank Vanderhallen Jason Cassell (a.k.a. BlueSloth) Raphael Geissert Jason Long Yann E. Morin Alexander Kolesen Pierre Jean Schweitzer Sebastian Pipping Nicolas Dely Julian Andres Klode Mike Kazantsev B Howe Thomas Petazzoni Likai Liu Ben Gardiner Hashtable routines taken from: Christopher Clark Copyright (c) 2002, 2004, Christopher Clark http://www.cl.cam.ac.uk/~cwc22/hashtable/ Hash algorithm taken from Arash Partow (http://www.partow.net/programming/hashfunctions/index.html) but rewritten from scratch due to incompatible license. COW routines taken from: OpenBSD Copyright (c) 1991, 1993, 1994 The Regents of the University of California. All rights reserved. unionfs-fuse-1.0/LICENSE000066400000000000000000000030421245544002400147770ustar00rootroot00000000000000Unionfs-fuse is provided under the terms of the new BSD license. Copyright (c) 2008, Radek Podgorny, Bernd Schubert All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * 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. * Neither the name of the original author; nor the names of any contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER 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. unionfs-fuse-1.0/Makefile000066400000000000000000000007551245544002400154420ustar00rootroot00000000000000PREFIX=/usr/local BINDIR=/bin SBINDIR=/sbin build: $(MAKE) -C src/ clean: $(MAKE) -C src/ clean install: build install -d $(DESTDIR)$(PREFIX)$(BINDIR) install -d $(DESTDIR)$(PREFIX)$(SBINDIR) install -d $(DESTDIR)$(PREFIX)/share/man/man8 install -m 0755 src/unionfs $(DESTDIR)$(PREFIX)$(BINDIR) install -m 0755 src/unionfsctl $(DESTDIR)$(PREFIX)$(BINDIR) install -m 0755 mount.unionfs $(DESTDIR)$(PREFIX)$(SBINDIR) install -m 0644 man/unionfs.8 $(DESTDIR)$(PREFIX)/share/man/man8/ unionfs-fuse-1.0/NEWS000066400000000000000000000045401245544002400144750ustar00rootroot000000000000001.0 - renamed binary to unionfs-fuse - add uniofs-fuse-ctl utility to control unionfs-fuse over ioctl - better apple support - dropped stats - better test suite - probably lots of other things, it's been a while since last release ;-) 0.26 - use BUILD_PATH instead of snprintf, which will return -ENAMETOOLONG if the path is too long instead of simply using a wrong path - new options "-o relaxed_permissions" and "-o hide_meta_dir" - included the debian directory - Properly check on rmdir() if sub-branches are also empty - Always compile with debug code, but only enable debugging on request - New option -o debug_file - Lots of bugs fixed in BUILD_PATH() - Improved search of white-out files - Properly fix Debian Bug#509516. - More debug output. - Add syslog support without the risk of possible dead locks - Use fuse big-writes (and reads) if available 0.25 - Alternate way to specify branches - Minor fixes 0.24 - Support for liveCDs / live USB sticks (-o chroot) - Build-in support to change the maximum number of open files - Added recursive directory COW. - relative paths: Critical bug fix, had been completely broken in 0.23 0.23 - remove to_user() and to_root() calls for autorization and use fuse build-in default_permissions checks - bugfix: rename created wrong whiteout type 0.22 - Fix a bug reported by Jens Hoelldampf , in 0.21 cow didn't work for pathes. 0.21 - Fix a segmentation fault when COW was enabled, but no rw-branch was specified. - Proper handling when ro-branches are on top of rw-branches. - Disabled syslog entries for now, since it might cause deadlocks. - Fixed a license issue with the elfhash, since the CPL is not compatible with the BSD license. - Fix a bug when stat() was called instead of lstat(). - Many internal code changes (renaming of "root" to "branch"). - Better directory structure. - Add this NEWS file. - Add a man page. 0.20 - Fix a critical bug introduced in 0.19 when we disabled threadding. 0.19 - Several copy-on-write fixes (readdir, unlink, rmdir, rename). - Whiteout files are now located in branch/.unionfs/ subdirectories. - Disable threadding, since our current permissions-model is not compatible with the NPTL implementation and the Posix-Thread model in general. - Add supplementary group support. 0.18 - First release with copy-on-write (COW) support. - Many many internal code changes. unionfs-fuse-1.0/README.md000066400000000000000000000016621245544002400152570ustar00rootroot00000000000000unionfs-fuse ============ This is my effort to create a unionfs filesystem implementation which is way more flexible than the current in-kernel unionfs solution. I'm open to patches, suggestions, whatever... The preferred way is the mailing list at unionfs-fuse@googlegroups.com or see http://groups.google.com/group/unionfs-fuse. Why choose this stuff --------------------- * The filesystem has to be mounted after the roots are mounted when using the standard module. With unionfs-fuse, you can mount the roots later and their contents will appear seamlesly * You get caching which speeds things up a lot for free * Advanced features like copy-on-write and more Why NOT choose it ----------------- * Compared to kernel-space solution we need lots of useless context switches which makes kernel-only solution clear speed-winner (well, actually I've made some tests and the hard-drives seem to be the bottleneck so the speed is fine, too) unionfs-fuse-1.0/debian/000077500000000000000000000000001245544002400152155ustar00rootroot00000000000000unionfs-fuse-1.0/debian/README.source000066400000000000000000000001721245544002400173740ustar00rootroot00000000000000This package make use of quilt to sligtly modify it for Debian. See more detail read /usr/share/doc/quilt/README.source unionfs-fuse-1.0/debian/changelog000066400000000000000000000141111245544002400170650ustar00rootroot00000000000000unionfs-fuse (0.25~hg.20110220-1) unstable; urgency=low * new hg version * Update debian/copyright to directly include the original BSD 3-Clause License -- Bernd Schubert Mon, 21 Feb 2011 00:03:28 +0100 unionfs-fuse (0.25~hg.20100602-2) unstable; urgency=low * Set upload to unstable -- Bernd Schubert Sun, 06 Jun 2010 16:49:24 +0200 unionfs-fuse (0.25~hg.20100602-1) karmic; urgency=low * Pull from my 0.25 branch http://podgorny.cz/~bernd/hg/hgwebdir.cgi/0.25 * Check the NEWS file for details * Improve the fix for Debian bug 509516 -- Bernd Schubert Wed, 02 Jun 2010 00:25:51 +0200 unionfs-fuse (0.25~hg.20100315-1) unstable; urgency=low * new hg version -- Bernd Schubert Sun, 16 May 2010 21:35:29 +0200 unionfs-fuse (0.24-1) unstable; urgency=low * New upstream release * Add utimens.patch * Also close 509516, although the solution is not perfect yet (closes: #509516) * Switch to dpkg-source 3.0 (quilt) format -- Bernd Schubert Wed, 26 May 2010 16:42:13 +0200 unionfs-fuse (0.23.hg.20100315-1) unstable; urgency=low * Another update from my branch to merge with Radek and to update the test script -- Bernd Schubert Mon, 15 Mar 2010 00:02:08 +0100 unionfs-fuse (0.23.hg.20090831-1) unstable; urgency=low * another pull from my branch * fix relative pathes (closes: 541614) * inlcude patch from Goswin (closes: 533403) -- Bernd Schubert Mon, 31 Aug 2009 00:29:46 +0200 unionfs-fuse (0.23.hg.20090611-1) unstable; urgency=low * another pull from my branch * change my maintainer mail address -- Bernd Schubert Thu, 11 Jun 2009 20:42:41 +0200 unionfs-fuse (0.23.hg.20090601-2) unstable; urgency=low * set DH_COMPAT to 7 * fix several lintian packaging errors -- Bernd Schubert Tue, 02 Jun 2009 00:18:25 +0200 unionfs-fuse (0.23.hg.20090601-1) unstable; urgency=low * new upstream release * pull from my branch to include everything that is supposed to be in 0.24 (http://podgorny.cz/~bernd/hg/hgwebdir.cgi/radek-trunk-bernd-merge) * (closes: #496794) * (closes: #530214) * (closes: #511157) * new option "-o statfs_omit_ro" (closes: #511446) * man page updated if "-o cow" (is not specified closes: #511047) * partly fixes bug #509516, I leave that bug open, since only fixed for one rw branch -- Bernd Schubert Mon, 01 Jun 2009 23:12:42 +0200 unionfs-fuse (0.21-3) unstable; urgency=high * fixes critical buffer overflow on using relativ pathes * slightly improve the man page to tell people without -ocow not everything might work as expected * change binary path from /usr/sbin to /usr/bin * (closes: #511995) * (closes: #511158) -- Bernd Schubert Sat, 24 Jan 2009 00:10:44 +0100 unionfs-fuse (0.21-2) unstable; urgency=high * fix a critical bug: creating new files in directories existing only in read-only branches failed, since the directory path wasn't copied to the rw-branch * (closes: #495380) -- Bernd Schubert Mon, 11 Aug 2008 09:22:42 +0000 unionfs-fuse (0.21-1) unstable; urgency=low * new upstream release -- Bernd Schubert Sat, 26 Jul 2008 18:28:39 +0200 unionfs-fuse (0.20-5) unstable; urgency=low * Another pull from my branch, fixes sereveral bugs, among it a critical deadlock * Installing new NEWS file and examples/ directory with dh_docs * This is actually the 0.21 release (also to be released this weekend). -- Bernd Schubert Sat, 26 Jul 2008 14:03:50 +0200 unionfs-fuse (0.20-4) unstable; urgency=low * as by suggestion of Kapil: - remove debian/copyright.in - install the CREDITS file with dh_installdocs -- Bernd Schubert Thu, 26 Jun 2008 21:02:11 +0200 unionfs-fuse (0.20-3) unstable; urgency=low * another hg pull from http://hg.podgorny.cz/unionfs-fuse (fixes the elfhash CPL incompatibility problem) * Changes to the debian/copyright files as suggested by Kapil Hari Paranjape and Richard Laager -- Bernd Schubert Thu, 26 Jun 2008 13:22:14 +0200 unionfs-fuse (0.20-2) unstable; urgency=low * add debian/watch file * fix the copyright file generation -- Bernd Schubert Mon, 23 Jun 2008 22:50:03 +0200 unionfs-fuse (0.20-1) unstable; urgency=low * Ouch, already so many internal releases and I never noticed the version string was wrong. -- Bernd Schubert Fri, 20 Jun 2008 21:13:18 +0000 unionfs-fuse (0.9.20-2) unstable; urgency=low * correct the section to misc -- Bernd Schubert Fri, 20 Jun 2008 12:25:54 +0000 unionfs-fuse (0.9.20-1) unstable; urgency=low * new upstream release * additional hg pull from http://podgorny.cz/~bernd/hg/hgwebdir.cgi/radek-trunk-bernd-merge * preparing official debian upload * switch to cmake based build system * (closes: #481490) -- Bernd Schubert Thu, 19 Jun 2008 17:37:15 +0200 unionfs-fuse (0.9.19-hg20080403-ql2) unstable; urgency=low * convert to debian quilt series * add fd.patch -- Bernd Schubert Tue, 29 Apr 2008 13:56:06 +0200 unionfs-fuse (0.9.19-hg20080403-ql1) unstable; urgency=low * rename() bugfixes * slight rmdir() and readdir() bugfixes * Update the version to 0.9.19-hg -- Bernd Schubert Thu, 3 Apr 2008 17:32:29 +0200 unionfs-fuse (0.9.18hg.ql2) unstable; urgency=low * fix dependency of unionfs-fuse and build-dependency -- Bernd Schubert Thu, 13 Mar 2008 13:49:45 +0100 unionfs-fuse (0.9.18hg-1) unstable; urgency=low * update to hg version + fixes -- Bernd Schubert Wed, 12 Mar 2008 16:54:57 +0100 unionfs-fuse (0.9.18-1) unstable; urgency=low * Initial Release. -- Bernd Schubert Wed, 12 Sep 2007 19:22:07 +0200 unionfs-fuse-1.0/debian/compat000066400000000000000000000000021245544002400164130ustar00rootroot000000000000007 unionfs-fuse-1.0/debian/control000066400000000000000000000006551245544002400166260ustar00rootroot00000000000000Source: unionfs-fuse Section: misc Priority: optional Maintainer: Bernd Schubert Build-Depends: debhelper (>= 7.0.0), libfuse-dev, quilt (>= 0.40), cmake Standards-Version: 3.8.4 Package: unionfs-fuse Architecture: any Depends: ${shlibs:Depends}, ${misc:Depends}, fuse-utils Description: Fuse implementation of unionfs This is another unionfs implementation using filesystem in userspace (fuse). unionfs-fuse-1.0/debian/copyright000066400000000000000000000104411245544002400171500ustar00rootroot00000000000000This package was debianized by Bernd Schubert on Wed, 12 Sep 2007 19:22:07 +0200. It was downloaded from http://podgorny.cz/moin/UnionFsFuse The brief summary of the copyright information for the files in this package is given below. The summary is followed by the detailed license texts. Files: * Copyright: Copyright 2008, Radek Podgorny, Bernd Schubert License: other The complete text of this license can be found at the end of this file in the paragraph "Modified BSD 3-Clause" Files: debian/* Copyright: Copyright 2008, Bernd Schubert License: other The complete text of this license can be found at the end of this file in the paragraph "Modified BSD 3-Clause" Files: hashtable*.[ch] Copyright: Copyright 2002, 2004, Christopher Clark License: other The complete text of this license can be found at the end of this file in the paragraph "Modified BSD 3-Clause" Files: cow_utils.[ch] Copyright: Copyright 1991, 1993, 1994, The Regents of the University of California. License: BSD-3 The complete text of this license can be found at the end of this file in the paragraph "Original BSD 3-Clause License:" The detailed License Texts ========================== Modified BSD 3-Clause: ---------------------- (taken from LICENSE file in source package) Copyright All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * 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. * Neither the name of the original author; nor the names of any contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER 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. Original BSD 3-Clause License: ------------------------------ Copyright (c) The Regents of the University of California. 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. 3. Neither the name of the University nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS 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. unionfs-fuse-1.0/debian/dirs000066400000000000000000000000111245544002400160710ustar00rootroot00000000000000usr/sbin unionfs-fuse-1.0/debian/rules000077500000000000000000000037471245544002400163100ustar00rootroot00000000000000#!/usr/bin/make -f # -*- makefile -*- # Sample debian/rules that uses debhelper. # This file was originally written by Joey Hess and Craig Small. # As a special exception, when this file is copied by dh-make into a # dh-make output file, you may use that output file without restriction. # This special exception was added by Craig Small in version 0.37 of dh-make. # Uncomment this to turn on verbose mode. export DH_VERBOSE=1 QUILT_STAMPFN := patch-stamp include /usr/share/quilt/quilt.make CFLAGS = -pipe -W -Wall -g ifneq (,$(findstring noopt,$(DEB_BUILD_OPTIONS))) CFLAGS += -O0 else CFLAGS += -O2 endif configure: configure-stamp configure-stamp: dh_testdir # Add here commands to configure the package. mkdir -p debian/build cd debian/build \ && cmake ../../ -DCMAKE_INSTALL_PREFIX=/usr \ -DCMAKE_C_FLAGS="$(CFLAGS)" \ -DCMAKE_SKIP_RPATH=TRUE touch configure-stamp build: build-stamp build-stamp: $(QUILT_STAMPFN) configure-stamp dh_testdir # Add here commands to compile the package. cd debian/build && make touch build-stamp clean: unpatch dh_testdir dh_testroot rm -f build-stamp configure-stamp $(QUILT_STAMPFN) # Add here commands to clean up after the build process. rm -fr debian/build rm -f debian/files dh_clean install: build dh_testdir dh_testroot dh_prep dh_installdirs # Add here commands to install the package into debian/unionfsfuse. cd debian/build && make DESTDIR=../../debian/unionfs-fuse install # Build architecture-independent files here. binary-indep: # We have nothing to do by default. # Build architecture-dependent files here. binary-arch: build install dh_testdir dh_testroot dh_installchangelogs dh_installdocs CREDITS NEWS examples/ dh_installexamples # dh_install dh_installman dh_link dh_strip dh_compress dh_fixperms dh_installdeb dh_shlibdeps dh_gencontrol dh_md5sums dh_builddeb binary: binary-indep binary-arch .PHONY: build clean binary-indep binary-arch binary install configure unionfs-fuse-1.0/debian/watch000066400000000000000000000001201245544002400162370ustar00rootroot00000000000000version=3 http://podgorny.cz/unionfs-fuse/releases/unionfs-fuse-(.*)\.tar\.bz2 unionfs-fuse-1.0/examples/000077500000000000000000000000001245544002400156115ustar00rootroot00000000000000unionfs-fuse-1.0/examples/S01a-unionfs-live-cd.sh000077500000000000000000000011071245544002400216530ustar00rootroot00000000000000#!/bin/sh # Copyright: Bernd Schubert # BSD license, see LICENSE file for details FUSE_OPT="-o allow_other,use_ino,suid,dev,nonempty" CHROOT_PATH="/tmp/unionfs" UNION_OPT="-ocow,chroot=$CHROOT_PATH,max_files=32768" UBIN=/usr/bin/unionfs-fuse mount -t proc proc /proc mount -t tmpfs tmpfs /tmp mkdir -p $CHROOT_PATH/root mkdir -p $CHROOT_PATH/rw mkdir -p /tmp/union mount --bind / $CHROOT_PATH/root $UBIN $FUSE_OPT $UNION_OPT /rw=RW:/root=RO /tmp/union mount -t proc proc /tmp/union/proc cd /tmp/union mkdir oldroot pivot_root . oldroot init q unionfs-fuse-1.0/examples/rc.local.omit-pid.sh000077500000000000000000000011271245544002400213670ustar00rootroot00000000000000#!/bin/sh -e # # rc.local # # This script is executed at the end of each multiuser runlevel. # Make sure that the script will "exit 0" on success or any other # value on error. # # In order to enable or disable this script just change the execution # bits. # # By default this script does nothing. # dont't kill unionfs-fuse dir=/lib/init/rw/sendsigs.omit.d [ ! -d $dir ] || omitdir=$dir dir=/var/run/sendsigs.omit.d [ ! -d $dir ] || omitdir=$dir if [ -n "$omitdir" ]; then for i in `ps ax |grep unionfs | grep -v grep | awk '{print $1}'`; do echo $i >${omitdir}/unionfs.$i; done fi exit 0 unionfs-fuse-1.0/examples/unionfs-fuse-nfs-root000077500000000000000000000065671245544002400217430ustar00rootroot00000000000000#!/bin/sh -x # This is an example script for diskless-booted systems over NFS. # In order to make the script working properly, you need to adjust # the parameters below and you also aready need to have booted # your root environment over NFS, either by an initrd/initramfs # or by your specific OS NFS-boot method (e.g. see # ${path_to_linux_sources}/Documentation/nfsroot.txt). # Furtheremore, this script expectes your NFS-server to export # /unionfs/hosts (MUST be read-write) and # /unionfs/groups/default (read-only is sufficient). # You then also MUST have the directory /unionfs/host and # /unionfs/groups/default with these # subdirectories: bin etc lib lib32 root sbin usr var # # All of this is only an example, of course, and may be easily changed. # # On my systems this script is then executed as the very first init-script, # thus before any other init script is executed. On debian/ubuntu this # can be done by copying this script to /etc/init.d/unionfs-fuse and then # by creating a link /etc/rcS.d/S01a_unionfs-fuse -> ../init.d/unionfs-fuse. # # NOTE: It is generally advisable to monitor the very first boot process of # a client using a serial cable, SOL (ipmi) or serial netconsole. # # LICENSE: new BSD license, see LICENSE file for details. # Copyright: Bernd Schubert PATH=/bin:/sbin:/usr/bin:/usr/sbin:/opt/local/bin:/opt/local/sbin NET="192.168.1." NFS_SERV="192.168.55.86" NFS_OPT="-otcp,nfsvers=3,port=2049,rsize=8192,wsize=8192,nolock" FUSE_OPT="-o default_permissions -o allow_other -o use_ino -o nonempty -o suid" UNION_OPT="-o cow -o noinitgroups" UPATH="/unionfs" UBIN="unionfs-fuse" IP=`ip addr show |grep $NET | sed -e s'/^.*inet //' |sed -e s'/\/24 brd.*$//'` [ -z "$IP" ] && exit 0 if [ "$IP" = "$NFS_SERV" ]; then # The nfs server shall not create a union of itself, abort! echo "This system is the nfs-server, I won't mount myself!" exit 1 fi # allow more open files, reminder: everything in /etc and /var is opened by # ${UPATH} e.g. with fontconfig caching the default of 1024 open file can be # too small ulimit -n 16384 mount -n -t proc proc /proc mount -n -t sysfs sysfs /sys mount -t tmpfs -o size=8192 tmpfs /tmp modprobe fuse # for the portmapper we need localhost ifconfig lo 127.0.0.1 /etc/init.d/portmap start # we cannot convice fuse not to write to /etc/mtab, but /etc/mtab is still # write protected, so a workaround #touch /${UPATH}/tmp/mtab #mount -n --bind /${UPATH}/tmp/mtab /etc/mtab # test if hosts dir is already there and if not create it mount -n -t nfs $NFS_OPT ${NFS_SERV}:/${UPATH}/hosts /${UPATH}/host # make sure the directories really do exist mkdir -p /${UPATH}/host/${IP} mkdir -p /${UPATH}/host/${IP} umount -n /${UPATH}/host # the client specific files mount -n -t nfs $NFS_OPT ${NFS_SERV}:/$UPATH/groups/default /$UPATH/group mount -n -t nfs $NFS_OPT ${NFS_SERV}:/${UPATH}/hosts/${IP} /${UPATH}/host mkdir -p /${UPATH}/host/root mount -n -omode=0655 -t tmpfs tmpfs /${UPATH}/host/root unionmount() { dir=$1 mkdir -p /${UPATH}/host/$dir mount --bind /$dir /$UPATH/common/$dir host="/${UPATH}/host/${dir}=RW" group="/${UPATH}/group/${dir}=RO" common="/$UPATH/common/${dir}=RO" $UBIN $FUSE_OPT $UNION_OPT ${host}:${group}:${common} /$UPATH/union/$dir mount --bind /$UPATH/union/$dir /$dir } for i in etc var lib lib32 bin sbin usr root; do unionmount $i done # re-read inittab init q umount /tmp unionfs-fuse-1.0/man/000077500000000000000000000000001245544002400145465ustar00rootroot00000000000000unionfs-fuse-1.0/man/CMakeLists.txt000066400000000000000000000000651245544002400173070ustar00rootroot00000000000000INSTALL(FILES unionfs.8 DESTINATION share/man/man8/) unionfs-fuse-1.0/man/unionfs.8000066400000000000000000000132531245544002400163240ustar00rootroot00000000000000.de Vb \" Begin verbatim text .ft CW .nf .ne \\$1 .. .de Ve \" End verbatim text .ft R .fi .. .TH "unionfs" "8" "2015" "unionfs-fuse 1.0" "" .SH "NAME" unionfs\-fuse \- A userspace unionfs implementation .SH "SYNOPSIS" .B unionfs\-fuse \fB[\-o \fIoption1\fP \-o \fIoption2\fP ... \-o \fIoptionN\fP ]\fR \fBtop_branch:lower_branch:...:lowest_branch \fR \fBmount_point\fR .SH "DESCRIPTION" \fBunionfs\fR overlays several directory into one single mount point. .PP It first tries to access the file on the top branch and if the file does not exist there, it continues on lower level branches. If the user tries to modify a file on a lower level read\-only branch the file is copied to to a higher level read\-write branch if the \fBcopy\-on\-write (cow) \fR mode was enabled. .SH "OPTIONS" Below is a summary of unionfs options .TP \fB\-o chroot=path Path to chroot into. By using this option unionfs may be used for live CDs or live USB sticks, etc. So it can serve "/" as filesystem. If you do not specify this option and try to use it for "/" it will deadlock on calling 'pivot_root'. If you do set this option, you also need to specify the branches relativly to the given chroot directory. See examples/S01a-unionfs-live-cd.sh for an example. .TP \fB\-o cow Enable copy\-on\-write .TP \fB\-o hide_meta_files In our unionfs root path we have a .unionfs directory that includes metadata, such as hidden (deleted) files. This options make this directory invisible from readdir(), so for example "ls -la /union_root/" will not show it. However, this directory is still there and "cd .unionfs" or "ls -l .unionfs" still work. Also, libfuse will create .fuse_hidden* files, if a file is open, but will be deleted. Those fuse meta files also will be invisble. This option is especially usufull for package builders. .TP \fB\-d Enable debugging for unionfs and libfuse. Useful for developers if the code if the code does not behave as expected. Debug information will be written to stderr and a debug file (./unionfs_debug.log by default). .TP \fB\-o debug_file=file Write unionfs debug information into that file. .TP \fB\-o max_files=number Maximum number of open files. Most system have a default of 1024 open files per process. For example if unionfs serves "/" applications like KDE or GNOME might have much more open files, which will make the unionfs process to exceed this limit. Suggested for "/" is >16000 or even >32000 files. If this limit exceeds unionfs will not be able to open further files. .TP \fB\-o noinitgroups Since version 0.23 without any effect, just left over for compatibility. Might be removed in future versions. .TP \fB\-o relaxed_permissions Usually we automatically add the libfuse option "-odefault_permissions" so that libfuse takes over permission checks. However, if running not as root (so as uid =! 0 and gid != 0), permissions of the underlying filesystem are already sufficient. In order to prevent from severe security issues, this option is not allowed if running as root. .TP \fB\-o statfs_omit_ro By default blocks of all branches are counted in statfs() calls (e.g. by 'df'). On setting this option read-only branches will be omitted for the summary of blocks. This may sound weird but it actually fixes "wrong" percentage of free space. .TP .SH "Options to libfuse" There are several further options available, which don't directly apply to unionfs, but to libfuse. Please run "unionfs --help" to see these. We already set the "-o default-permissions" options on our own. .SH "EXAMPLES" .Vb 5 \& unionfs \-o cow,max_files=32768 \e \& -o allow_other,use_ino,suid,dev,nonempty \e \& /u/host/etc=RW:/u/group/etc=RO:/u/common/etc=RO \e \& /u/union/etc .Ve .SH "Meta data" Like other filesystems unionfs also needs to store meta data. Well, presently only information about deleted files and directories need to be stored, but in future releases more information might be required, e.g. inode-numbers for persistent inode information. Meta data information are saved and looked for in the .unionfs/ directories of each branch-root. So in the example above, these are /u/host/etc/.unionfs, /u/group/etc/.unionfs and /u/common/etc/.unionfs. Within these directories a complete directory structure may be found. Example: If the admin decides to delete the file /etc/test/testfile, which only exists in /u/unionfs/etc/test/testfile, unionfs can't delete this file, since it is on a read-only branch. So instead the whiteout file /u/host/etc/.unionfs/test/testfile_HIDDEN~ will be created. So on accessing the union filesystem, test/testfile will not be visible. Please also note that whiteout files/directories will only hide the files in lower level branches. So for example whiteouts in the group directory (/u/group/etc/.unionfs of the example above) will only hide file of the common branch (/u/common/etc), but not these of the group and host branches. Especially for diskless-booted environments it is rather useful for the admin to create whiteout files him/her-self. For example one should blacklist network re-initializations, /etc/mtab, /etc/nologin of the server and several cron-scripts. This can be easily achieved by creating whiteout files for these scripts in the group meta directory. .SH "KNOWN ISSUES" .Vb 5 \&1) Another issue is that presently there is no support for read-only branches when copy-on-write is disabled, thus, -ocow is NOT specified! Support for that might be added in later releases. .Ve .SH "AUTHORS" .B unionfs\-fuse Original implemention by Radek Podgorny .SH "COPYRIGHT" Radek Podgorny , Bernd Schubert .SH "THANKS" Many thanks to the author of the FUSE filesystem Miklos Szeredi. unionfs-fuse-1.0/mount.unionfs000077500000000000000000000002041245544002400165370ustar00rootroot00000000000000#!/bin/bash label=$1 mpoint=$2 shift 3 dirs=${@##*,dirs=} dirs=${dirs/,/:} mount.fuse "unionfs#$dirs" "$mpoint" -o ${@%%,dirs=*} unionfs-fuse-1.0/src/000077500000000000000000000000001245544002400145625ustar00rootroot00000000000000unionfs-fuse-1.0/src/CMakeLists.txt000066400000000000000000000011361245544002400173230ustar00rootroot00000000000000set(HASHTABLE_SRCS hashtable.c hashtable_itr.c) set(UNIONFS_SRCS unionfs.c opts.c debug.c findbranch.c readdir.c general.c unlink.c cow.c cow_utils.c string.c rmdir.c usyslog.c) set(UNIONFSCTL_SRCS unionfsctl.c) add_executable(unionfs ${UNIONFS_SRCS} ${HASHTABLE_SRCS}) if (UNIX AND NOT APPLE) target_link_libraries(unionfs fuse pthread rt) else() target_link_libraries(unionfs fuse pthread) endif() add_executable(unionfsctl ${UNIONFSCTL_SRCS}) INSTALL(PROGRAMS ${CMAKE_CURRENT_BINARY_DIR}/unionfs DESTINATION bin) INSTALL(PROGRAMS ${CMAKE_CURRENT_BINARY_DIR}/unionfsctl DESTINATION bin) unionfs-fuse-1.0/src/Makefile000066400000000000000000000016101245544002400162200ustar00rootroot00000000000000CFLAGS += -Wall CPPFLAGS += $(shell pkg-config --cflags fuse) CPPFLAGS += -DFUSE_USE_VERSION=26 CPPFLAGS += -DLIBC_XATTR # glibc nowadays includes xattr # CPPFLAGS += -DLIBATTR_XATTR # define this to libattr xattr include # CPPFLAGS += -DDISABLE_XATTR # disable xattr support # CPPFLAGS += -DDISABLE_AT # disable *at function support LDFLAGS += LIB = $(shell pkg-config --libs fuse) -lpthread HASHTABLE_OBJ = hashtable.o hashtable_itr.o UNIONFS_OBJ = unionfs.o opts.o debug.o findbranch.o readdir.o \ general.o unlink.o rmdir.o cow.o cow_utils.o string.o \ usyslog.o UNIONFSCTL_OBJ = unionfsctl.o all: unionfs unionfsctl unionfs: $(UNIONFS_OBJ) $(HASHTABLE_OBJ) uioctl.h version.h $(CC) $(LDFLAGS) -o $@ $(UNIONFS_OBJ) $(HASHTABLE_OBJ) $(LIB) unionfsctl: $(UNIONFSCTL_OBJ) uioctl.h version.h $(CC) $(LDFLAGS) -o $@ $(UNIONFSCTL_OBJ) clean: rm -f unionfs rm -f unionfsctl rm -f *.o unionfs-fuse-1.0/src/conf.h000066400000000000000000000014071245544002400156620ustar00rootroot00000000000000/* * License: BSD-style license * Copyright: Bernd Schubert * */ #ifndef CONF_H_ #define CONF_H_ #ifdef _XOPEN_SOURCE // *at support, such as openat, utimensat, etc (see man 2 openat) #include #include #if !defined (DISABLE_AT) && (_XOPEN_SOURCE >= 700 && _POSIX_C_SOURCE >= 200809L) \ && defined (AT_SYMLINK_NOFOLLOW) #define UNIONFS_HAVE_AT #endif #endif // _XOPEN_SOURCE // xattr support #if !defined (DISABLE_XATTR) #if defined (LIBC_XATTR) #include #elif defined (LIBATTR_XATTR) #include #else #error // neither libc attr nor libattr xattr defined #endif #if defined (XATTR_CREATE) && defined (XATTR_REPLACE) #define HAVE_XATTR #endif #endif #endif // CONF_H_ unionfs-fuse-1.0/src/cow.c000066400000000000000000000121511245544002400155160ustar00rootroot00000000000000/* * C Implementation: cow * * Copy-on-write functions * * License: BSD-style license * Copyright: Radek Podgorny , * Bernd Schubert */ #include #include #include #include #include #include #include #include #include "opts.h" #include "findbranch.h" #include "general.h" #include "cow.h" #include "cow_utils.h" #include "string.h" #include "debug.h" #include "usyslog.h" /** * Actually create the directory here. */ static int do_create(const char *path, int nbranch_ro, int nbranch_rw) { DBG("%s\n", path); char dirp[PATHLEN_MAX]; // dir path to create sprintf(dirp, "%s%s", uopt.branches[nbranch_rw].path, path); struct stat buf; int res = stat(dirp, &buf); if (res != -1) RETURN(0); // already exists if (nbranch_ro == nbranch_rw) { // special case nbranch_ro = nbranch_rw, this is if we a create // unionfs meta directories, so not directly on cow operations buf.st_mode = S_IRWXU | S_IRWXG; } else { // data from the ro-branch char o_dirp[PATHLEN_MAX]; // the pathname we want to copy sprintf(o_dirp, "%s%s", uopt.branches[nbranch_ro].path, path); res = stat(o_dirp, &buf); if (res == -1) RETURN(1); // lower level branch removed in the mean time? } res = mkdir(dirp, buf.st_mode); if (res == -1) { USYSLOG(LOG_DAEMON, "Creating %s failed: \n", dirp); RETURN(1); } if (nbranch_ro == nbranch_rw) RETURN(0); // the special case again if (setfile(dirp, &buf)) RETURN(1); // directory already removed by another process? // TODO: time, but its values are modified by the next dir/file creation steps? RETURN(0); } /** * l_nbranch (lower nbranch than nbranch) is write protected, create the dir path on * nbranch for an other COW operation. */ int path_create(const char *path, int nbranch_ro, int nbranch_rw) { DBG("%s\n", path); if (!uopt.cow_enabled) RETURN(0); char p[PATHLEN_MAX]; if (BUILD_PATH(p, uopt.branches[nbranch_rw].path, path)) RETURN(-ENAMETOOLONG); struct stat st; if (!stat(p, &st)) { // path does already exists, no need to create it RETURN(0); } char *walk = (char *)path; // first slashes, e.g. we have path = /dir1/dir2/, will set walk = dir1/dir2/ while (*walk != '\0' && *walk == '/') walk++; do { // walk over the directory name, walk will now be /dir2 while (*walk != '\0' && *walk != '/') walk++; // +1 due to \0, which gets added automatically snprintf(p, (walk - path) + 1, "%s", path); // walk - path = strlen(/dir1) int res = do_create(p, nbranch_ro, nbranch_rw); if (res) RETURN(res); // creating the directory failed // as above the do loop, walk over the next slashes, walk = dir2/ while (*walk != '\0' && *walk == '/') walk++; } while (*walk != '\0'); RETURN(0); } /** * Same as path_create(), but ignore the last segment in path, * i.e. it might be a filename. */ int path_create_cutlast(const char *path, int nbranch_ro, int nbranch_rw) { DBG("%s\n", path); char *dname = u_dirname(path); if (dname == NULL) RETURN(-ENOMEM); int ret = path_create(dname, nbranch_ro, nbranch_rw); free(dname); RETURN(ret); } /** * initiate the cow-copy action */ int cow_cp(const char *path, int branch_ro, int branch_rw) { DBG("%s\n", path); // create the path to the file path_create_cutlast(path, branch_ro, branch_rw); char from[PATHLEN_MAX], to[PATHLEN_MAX]; if (BUILD_PATH(from, uopt.branches[branch_ro].path, path)) RETURN(-ENAMETOOLONG); if (BUILD_PATH(to, uopt.branches[branch_rw].path, path)) RETURN(-ENAMETOOLONG); setlocale(LC_ALL, ""); struct cow cow; cow.uid = getuid(); // Copy the umask for explicit mode setting. cow.umask = umask(0); umask(cow.umask); cow.from_path = from; cow.to_path = to; struct stat buf; lstat(cow.from_path, &buf); cow.stat = &buf; int res; switch (buf.st_mode & S_IFMT) { case S_IFLNK: res = copy_link(&cow); break; case S_IFDIR: res = copy_directory(path, branch_ro, branch_rw); break; case S_IFBLK: case S_IFCHR: res = copy_special(&cow); break; case S_IFIFO: res = copy_fifo(&cow); break; case S_IFSOCK: USYSLOG(LOG_WARNING, "COW of sockets not supported: %s\n", cow.from_path); RETURN(1); default: res = copy_file(&cow); } RETURN(res); } /** * copy a directory between branches (includes all contents of the directory) */ int copy_directory(const char *path, int branch_ro, int branch_rw) { DBG("%s\n", path); /* create the directory on the destination branch */ int res = path_create(path, branch_ro, branch_rw); if (res != 0) { RETURN(res); } /* determine path to source directory on read-only branch */ char from[PATHLEN_MAX]; if (BUILD_PATH(from, uopt.branches[branch_ro].path, path)) RETURN(1); DIR *dp = opendir(from); if (dp == NULL) RETURN(1); struct dirent *de; while ((de = readdir(dp)) != NULL) { if (strcmp(de->d_name, ".") == 0 || strcmp(de->d_name, "..") == 0) continue; char member[PATHLEN_MAX]; if (BUILD_PATH(member, path, "/", de->d_name)) { res = 1; break; } res = cow_cp(member, branch_ro, branch_rw); if (res != 0) break; } closedir(dp); RETURN(res); } unionfs-fuse-1.0/src/cow.h000066400000000000000000000007231245544002400155250ustar00rootroot00000000000000/* * License: BSD-style license * Copyright: Radek Podgorny , * Bernd Schubert */ #ifndef COW_H #define COW_H #include int cow_cp(const char *path, int branch_ro, int branch_rw); int path_create(const char *path, int nbranch_ro, int nbranch_rw); int path_create_cutlast(const char *path, int nbranch_ro, int nbranch_rw); int copy_directory(const char *path, int branch_ro, int branch_rw); #endif unionfs-fuse-1.0/src/cow_utils.c000066400000000000000000000167611245544002400167510ustar00rootroot00000000000000/*- * Copyright (c) 1991, 1993, 1994 * The Regents of the University of California. 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. * 3. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS 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. * * Bernd Schubert : * This file was taken from OpenBSD and modified to fit the unionfs requirements. */ #include #include #include #include #include #include #include #include #include #include #include "unionfs.h" #include "cow_utils.h" #include "debug.h" #include "general.h" #include "usyslog.h" // BSD seems to know S_ISTXT itself #ifndef S_ISTXT #define S_ISTXT S_ISVTX #endif /** * set the stat() data of a file **/ int setfile(const char *path, struct stat *fs) { DBG("%s\n", path); struct utimbuf ut; int rval = 0; fs->st_mode &= S_ISUID | S_ISGID | S_ISTXT | S_IRWXU | S_IRWXG | S_IRWXO; ut.actime = fs->st_atime; ut.modtime = fs->st_mtime; if (utime(path, &ut)) { USYSLOG(LOG_WARNING, "utimes: %s", path); rval = 1; } /* * Changing the ownership probably won't succeed, unless we're root * or POSIX_CHOWN_RESTRICTED is not set. Set uid/gid before setting * the mode; current BSD behavior is to remove all setuid bits on * chown. If chown fails, lose setuid/setgid bits. */ if (chown(path, fs->st_uid, fs->st_gid)) { if (errno != EPERM) { USYSLOG(LOG_WARNING, "chown: %s", path); rval = 1; } fs->st_mode &= ~(S_ISTXT | S_ISUID | S_ISGID); } if (chmod(path, fs->st_mode)) { USYSLOG(LOG_WARNING, "chown: %s", path); rval = 1; } #ifdef HAVE_CHFLAGS /* * XXX * NFS doesn't support chflags; ignore errors unless there's reason * to believe we're losing bits. (Note, this still won't be right * if the server supports flags and we were trying to *remove* flags * on a file that we copied, i.e., that we didn't create.) */ errno = 0; if (chflags(path, fs->st_flags)) { if (errno != EOPNOTSUPP || fs->st_flags != 0) { USYSLOG(LOG_WARNING, "chflags: %s", path); rval = 1; } RETURN(rval); } #endif RETURN(rval); } /** * set the stat() data of a link **/ static int setlink(const char *path, struct stat *fs) { DBG("%s\n", path); if (lchown(path, fs->st_uid, fs->st_gid)) { if (errno != EPERM) { USYSLOG(LOG_WARNING, "lchown: %s", path); RETURN(1); } } RETURN(0); } /** * copy an ordinary file with all of its stat() data **/ int copy_file(struct cow *cow) { DBG("from %s to %s\n", cow->from_path, cow->to_path); static char buf[MAXBSIZE]; struct stat to_stat, *fs; int from_fd, rcount, to_fd, wcount; int rval = 0; #ifdef VM_AND_BUFFER_CACHE_SYNCHRONIZED char *p; #endif if ((from_fd = open(cow->from_path, O_RDONLY, 0)) == -1) { USYSLOG(LOG_WARNING, "%s", cow->from_path); RETURN(1); } fs = cow->stat; to_fd = open(cow->to_path, O_WRONLY | O_TRUNC | O_CREAT, fs->st_mode & ~(S_ISTXT | S_ISUID | S_ISGID)); if (to_fd == -1) { USYSLOG(LOG_WARNING, "%s", cow->to_path); (void)close(from_fd); RETURN(1); } /* * Mmap and write if less than 8M (the limit is so we don't totally * trash memory on big files. This is really a minor hack, but it * wins some CPU back. */ #ifdef VM_AND_BUFFER_CACHE_SYNCHRONIZED if (fs->st_size > 0 && fs->st_size <= 8 * 1048576) { if ((p = mmap(NULL, (size_t)fs->st_size, PROT_READ, MAP_FILE|MAP_SHARED, from_fd, (off_t)0)) == MAP_FAILED) { USYSLOG(LOG_WARNING, "mmap: %s", cow->from_path); rval = 1; } else { madvise(p, fs->st_size, MADV_SEQUENTIAL); if (write(to_fd, p, fs->st_size) != fs->st_size) { USYSLOG(LOG_WARNING, "%s", cow->to_path); rval = 1; } /* Some systems don't unmap on close(2). */ if (munmap(p, fs->st_size) < 0) { USYSLOG(LOG_WARNING, "%s", cow->from_path); rval = 1; } } } else #endif { while ((rcount = read(from_fd, buf, MAXBSIZE)) > 0) { wcount = write(to_fd, buf, rcount); if (rcount != wcount || wcount == -1) { USYSLOG(LOG_WARNING, "%s", cow->to_path); rval = 1; break; } } if (rcount < 0) { USYSLOG(LOG_WARNING, "copy failed: %s", cow->from_path); rval = 1; } } if (rval == 1) { (void)close(from_fd); (void)close(to_fd); RETURN(1); } if (setfile(cow->to_path, cow->stat)) rval = 1; /* * If the source was setuid or setgid, lose the bits unless the * copy is owned by the same user and group. */ #define RETAINBITS \ (S_ISUID | S_ISGID | S_ISVTX | S_IRWXU | S_IRWXG | S_IRWXO) else if (fs->st_mode & (S_ISUID | S_ISGID) && fs->st_uid == cow->uid) { if (fstat(to_fd, &to_stat)) { USYSLOG(LOG_WARNING, "%s", cow->to_path); rval = 1; } else if (fs->st_gid == to_stat.st_gid && fchmod(to_fd, fs->st_mode & RETAINBITS & ~cow->umask)) { USYSLOG(LOG_WARNING, "%s", cow->to_path); rval = 1; } } (void)close(from_fd); if (close(to_fd)) { USYSLOG(LOG_WARNING, "%s", cow->to_path); rval = 1; } RETURN(rval); } /** * copy a link, actually we recreate the link and only copy its stat() data. */ int copy_link(struct cow *cow) { DBG("from %s to %s\n", cow->from_path, cow->to_path); int len; char link[PATHLEN_MAX]; if ((len = readlink(cow->from_path, link, sizeof(link)-1)) == -1) { USYSLOG(LOG_WARNING, "readlink: %s", cow->from_path); RETURN(1); } link[len] = '\0'; if (symlink(link, cow->to_path)) { USYSLOG(LOG_WARNING, "symlink: %s", link); RETURN(1); } RETURN(setlink(cow->to_path, cow->stat)); } /** * copy a fifo, actually we recreate the fifo and only copy * its stat() data **/ int copy_fifo(struct cow *cow) { DBG("from %s to %s\n", cow->from_path, cow->to_path); if (mkfifo(cow->to_path, cow->stat->st_mode)) { USYSLOG(LOG_WARNING, "mkfifo: %s", cow->to_path); RETURN(1); } RETURN(setfile(cow->to_path, cow->stat)); } /** * copy a special file, actually we recreate this file and only copy * its stat() data */ int copy_special(struct cow *cow) { DBG("from %s to %s\n", cow->from_path, cow->to_path); if (mknod(cow->to_path, cow->stat->st_mode, cow->stat->st_rdev)) { USYSLOG(LOG_WARNING, "mknod: %s", cow->to_path); RETURN(1); } RETURN(setfile(cow->to_path, cow->stat)); } unionfs-fuse-1.0/src/cow_utils.h000066400000000000000000000010671245544002400167470ustar00rootroot00000000000000/* * License: BSD-style license * Copyright: Radek Podgorny , * Bernd Schubert */ #ifndef COW_UTILS_H #define COW_UTILS_H #define VM_AND_BUFFER_CACHE_SYNCHRONIZED #define MAXBSIZE 4096 struct cow { mode_t umask; uid_t uid; // source file char *from_path; struct stat *stat; // destination file char *to_path; }; int setfile(const char *path, struct stat *fs); int copy_special(struct cow *cow); int copy_fifo(struct cow *cow); int copy_link(struct cow *cow); int copy_file(struct cow *cow); #endif unionfs-fuse-1.0/src/debug.c000066400000000000000000000025561245544002400160240ustar00rootroot00000000000000/* * License: BSD-style license * Copyright: Radek Podgorny , * Bernd Schubert */ #include #include #include #include #include "opts.h" #include "debug.h" static char default_debug_path[] = "./unionfs_debug.log"; static pthread_rwlock_t file_lock = PTHREAD_RWLOCK_INITIALIZER; FILE* dbgfile = NULL; int debug_init(void) { FILE* old_dbgfile = dbgfile; pthread_rwlock_wrlock(&file_lock); // LOCK file pthread_rwlock_rdlock(&uopt.dbgpath_lock); // LOCK string char *dbgpath = uopt.dbgpath; if (!dbgpath) dbgpath = default_debug_path; printf("Debug mode, log will be written to %s\n", dbgpath); dbgfile = fopen(dbgpath, "w"); int res = 0; if (!dbgfile) { printf("Failed to open %s for writing: %s.\nAborting!\n", dbgpath, strerror(errno)); dbgfile = old_dbgfile; // we can re-use the old file res = 2; } else if (old_dbgfile) fclose(old_dbgfile); setlinebuf(dbgfile); // line buffering pthread_rwlock_unlock(&uopt.dbgpath_lock); // UNLOCK string pthread_rwlock_unlock(&file_lock); // UNLOCK file return res; } /** * Read-lock dbgfile and return it. */ FILE* get_dbgfile(void) { pthread_rwlock_rdlock(&file_lock); return dbgfile; } /** * Unlock dbgfile */ void put_dbgfile(void) { pthread_rwlock_unlock(&file_lock); } unionfs-fuse-1.0/src/debug.h000066400000000000000000000021701245544002400160210ustar00rootroot00000000000000/* * License: BSD-style license * Copyright: Radek Podgorny , * Bernd Schubert , * Bernd Schubert * * * Details about finding branches: * We begin at the top-level branch to look for a file or directory, in * the code usually called "path". If path was not found, we check for a * whiteout-file/directory. If we find a whiteout, we won't check further * in lower level branches. If neither path nor the corresponding whiteout * have been found, we do the test the next branch and so on. * If a file was found, but it is on a read-only branch and a read-write * branch was requested we return EACCESS. On the other hand we ignore * directories on read-only branches, since the directory in the higher * level branch doesn't prevent the user can later on see the file on the * lower level branch - so no problem to create path in the lower level * branch. * It also really important the files in higher level branches have * priority, since this is the reason, we can't write to file in a * lower level branch, when another file already exists in a higher * level ro-branch - on further access to the file the unmodified * file in the ro-branch will visible. * TODO Terminology: * In the code below we have functions like find_lowest_rw_branch(). * IMHO this is rather mis-leading, since it actually will find the * top-level rw-branch. The naming of the function is due to the fact * we begin to cound branches with 0, so 0 is actually the top-level * branch with highest file serving priority. */ #include #include #include #include #include #include #include "unionfs.h" #include "opts.h" #include "general.h" #include "cow.h" #include "findbranch.h" #include "string.h" #include "debug.h" #include "usyslog.h" /** * Find a branch that has "path". Return the branch number. */ static int find_branch(const char *path, searchflag_t flag) { DBG("%s\n", path); int i = 0; for (i = 0; i < uopt.nbranches; i++) { char p[PATHLEN_MAX]; if (BUILD_PATH(p, uopt.branches[i].path, path)) RETURN(-ENAMETOOLONG); struct stat stbuf; int res = lstat(p, &stbuf); DBG("%s: res = %d\n", p, res); if (res == 0) { // path was found switch (flag) { case RWRO: // any path we found is fine RETURN(i); case RWONLY: // we need a rw-branch if (uopt.branches[i].rw) RETURN(i); break; default: USYSLOG(LOG_ERR, "%s: Unknown flag %d\n", __func__, flag); } } // check check for a hide file, checking first here is the magic to hide files *below* this level res = path_hidden(path, i); if (res > 0) { // So no path, but whiteout found. No need to search in further branches errno = ENOENT; RETURN(-1); } else if (res < 0) { errno = res; // error RETURN(-1); } } errno = ENOENT; RETURN(-1); } /** * Find a ro or rw branch. */ int find_rorw_branch(const char *path) { DBG("%s\n", path); int res = find_branch(path, RWRO); RETURN(res); } /** * Find a writable branch. If file does not exist, we check for * the parent directory. * @path - the path to find or to copy (with last element cut off) * @ rw_hint - the rw branch to copy to, set to -1 to autodetect it */ int __find_rw_branch_cutlast(const char *path, int rw_hint) { int branch = find_rw_branch_cow(path); DBG("branch = %d\n", branch); if (branch >= 0 || (branch < 0 && errno != ENOENT)) RETURN(branch); DBG("Check for parent directory\n"); // So path does not exist, now again, but with dirname only. // We MUST NOT call find_rw_branch_cow() // since this function // doesn't work properly for directories. char *dname = u_dirname(path); if (dname == NULL) { errno = ENOMEM; RETURN(-1); } branch = find_rorw_branch(dname); DBG("branch = %d\n", branch); // No branch found, so path does nowhere exist, error if (branch < 0) goto out; // Reminder rw_hint == -1 -> autodetect, we do not care which branch it is if (uopt.branches[branch].rw && (rw_hint == -1 || branch == rw_hint)) goto out; if (!uopt.cow_enabled) { // So path exists, but is not writable. branch = -1; errno = EACCES; goto out; } int branch_rw; // since it is a directory, any rw-branch is fine if (rw_hint == -1) branch_rw = find_lowest_rw_branch(uopt.nbranches); else branch_rw = rw_hint; DBG("branch_rw = %d\n", branch_rw); // no writable branch found, we must return an error if (branch_rw < 0) { branch = -1; errno = EACCES; goto out; } if (path_create(dname, branch, branch_rw) == 0) branch = branch_rw; // path successfully copied out: free(dname); RETURN(branch); } /** * Call __find_rw_branch_cutlast() */ int find_rw_branch_cutlast(const char *path) { int rw_hint = -1; // autodetect rw_branch int res = __find_rw_branch_cutlast(path, rw_hint); RETURN(res); } /** * copy-on-write * Find path in a union branch and if this branch is read-only, * copy the file to a read-write branch. * NOTE: Don't call this to copy directories. Use path_create() for that! * It will definitely fail, when a ro-branch is on top of a rw-branch * and a directory is to be copied from ro- to rw-branch. */ int find_rw_branch_cow(const char *path) { DBG("%s\n", path); int branch_rorw = find_rorw_branch(path); // not found anywhere if (branch_rorw < 0) RETURN(-1); // the found branch is writable, good! if (uopt.branches[branch_rorw].rw) RETURN(branch_rorw); // cow is disabled and branch is not writable, so deny write permission if (!uopt.cow_enabled) { errno = EACCES; RETURN(-1); } int branch_rw = find_lowest_rw_branch(branch_rorw); if (branch_rw < 0) { // no writable branch found errno = EACCES; RETURN(-1); } if (cow_cp(path, branch_rorw, branch_rw)) RETURN(-1); // remove a file that might hide the copied file remove_hidden(path, branch_rw); RETURN(branch_rw); } /** * Find lowest possible writable branch but only lower than branch_ro. */ int find_lowest_rw_branch(int branch_ro) { DBG_IN(); int i = 0; for (i = 0; i < branch_ro; i++) { if (uopt.branches[i].rw) RETURN(i); // found it it. } RETURN(-1); } unionfs-fuse-1.0/src/findbranch.h000066400000000000000000000007351245544002400170360ustar00rootroot00000000000000/* * License: BSD-style license * Copyright: Radek Podgorny , * Bernd Schubert */ #ifndef FINDBRANCH_H #define FINDBRANCH_H typedef enum searchflag { RWRO, RWONLY } searchflag_t; int find_rorw_branch(const char *path); int find_lowest_rw_branch(int branch_ro); int find_rw_branch_cutlast(const char *path); int __find_rw_branch_cutlast(const char *path, int rw_hint); int find_rw_branch_cow(const char *path); #endif unionfs-fuse-1.0/src/general.c000066400000000000000000000123211245544002400163420ustar00rootroot00000000000000/* * C Implementation: general * * Description: General functions, not directly related to file system operations * * original implementation by Radek Podgorny * * License: BSD-style license * Copyright: Radek Podgorny , * Bernd Schubert * */ #include #include #include #include #include #include #include #include #include #include #include #include "unionfs.h" #include "opts.h" #include "string.h" #include "cow.h" #include "findbranch.h" #include "general.h" #include "debug.h" #include "usyslog.h" /** * Check if a file or directory with the hidden flag exists. */ static int filedir_hidden(const char *path) { // cow mode disabled, no need for hidden files if (!uopt.cow_enabled) RETURN(false); char p[PATHLEN_MAX]; if (strlen(path) + strlen(HIDETAG) > PATHLEN_MAX) RETURN(-ENAMETOOLONG); snprintf(p, PATHLEN_MAX, "%s%s", path, HIDETAG); DBG("%s\n", p); struct stat stbuf; int res = lstat(p, &stbuf); if (res == 0) RETURN(1); RETURN(0); } /** * check if any dir or file within path is hidden */ int path_hidden(const char *path, int branch) { DBG("%s\n", path); if (!uopt.cow_enabled) RETURN(false); char whiteoutpath[PATHLEN_MAX]; if (BUILD_PATH(whiteoutpath, uopt.branches[branch].path, METADIR, path)) RETURN(false); // -1 as we MUST not end on the next path element char *walk = whiteoutpath + uopt.branches[branch].path_len + strlen(METADIR) - 1; // first slashes, e.g. we have path = /dir1/dir2/, will set walk = dir1/dir2/ while (*walk != '\0' && *walk == '/') walk++; do { // walk over the directory name, walk will now be /dir2 while (*walk != '\0' && *walk != '/') walk++; // +1 due to \0, which gets added automatically char p[PATHLEN_MAX]; // walk - path = strlen(/dir1) snprintf(p, (walk - whiteoutpath) + 1, "%s", whiteoutpath); int res = filedir_hidden(p); if (res) RETURN(res); // path is hidden or error // as above the do loop, walk over the next slashes, walk = dir2/ while (*walk != '\0' && *walk == '/') walk++; } while (*walk != '\0'); RETURN(0); } /** * Remove a hide-file in all branches up to maxbranch * If maxbranch == -1, try to delete it in all branches. */ int remove_hidden(const char *path, int maxbranch) { DBG("%s\n", path); if (!uopt.cow_enabled) RETURN(0); if (maxbranch == -1) maxbranch = uopt.nbranches; int i; for (i = 0; i <= maxbranch; i++) { char p[PATHLEN_MAX]; if (BUILD_PATH(p, uopt.branches[i].path, METADIR, path)) RETURN(-ENAMETOOLONG); if (strlen(p) + strlen(HIDETAG) > PATHLEN_MAX) RETURN(-ENAMETOOLONG); strcat(p, HIDETAG); // TODO check length switch (path_is_dir(p)) { case IS_FILE: unlink(p); break; case IS_DIR: rmdir(p); break; case NOT_EXISTING: continue; } } RETURN(0); } /** * check if path is a directory * * return proper types given by filetype_t */ filetype_t path_is_dir(const char *path) { DBG("%s\n", path); struct stat buf; if (lstat(path, &buf) == -1) RETURN(NOT_EXISTING); if (S_ISDIR(buf.st_mode)) RETURN(IS_DIR); RETURN(IS_FILE); } /** * Create a file or directory that hides path below branch_rw */ static int do_create_whiteout(const char *path, int branch_rw, enum whiteout mode) { DBG("%s\n", path); char metapath[PATHLEN_MAX]; if (BUILD_PATH(metapath, METADIR, path)) RETURN(-1); // p MUST be without path to branch prefix here! 2 x branch_rw is correct here! // this creates e.g. branch/.unionfs/some_directory path_create_cutlast(metapath, branch_rw, branch_rw); char p[PATHLEN_MAX]; if (BUILD_PATH(p, uopt.branches[branch_rw].path, metapath)) RETURN(-1); strcat(p, HIDETAG); // TODO check length int res; if (mode == WHITEOUT_FILE) { res = open(p, O_WRONLY | O_CREAT, S_IRUSR | S_IWUSR); if (res == -1) RETURN(-1); res = close(res); } else { res = mkdir(p, S_IRWXU); if (res) USYSLOG(LOG_ERR, "Creating %s failed: %s\n", p, strerror(errno)); } RETURN(res); } /** * Create a file that hides path below branch_rw */ int hide_file(const char *path, int branch_rw) { DBG("%s\n", path); int res = do_create_whiteout(path, branch_rw, WHITEOUT_FILE); RETURN(res); } /** * Create a directory that hides path below branch_rw */ int hide_dir(const char *path, int branch_rw) { DBG("%s\n", path); int res = do_create_whiteout(path, branch_rw, WHITEOUT_DIR); RETURN(res); } /** * This is called *after* unlink() or rmdir(), create a whiteout file * if the same file/dir does exist in a lower branch */ int maybe_whiteout(const char *path, int branch_rw, enum whiteout mode) { DBG("%s\n", path); // we are not interested in the branch itself, only if it exists at all if (find_rorw_branch(path) != -1) { int res = do_create_whiteout(path, branch_rw, mode); RETURN(res); } RETURN(0); } /** * Set file owner of after an operation, which created a file. */ int set_owner(const char *path) { struct fuse_context *ctx = fuse_get_context(); if (ctx->uid != 0 && ctx->gid != 0) { int res = lchown(path, ctx->uid, ctx->gid); if (res) { USYSLOG(LOG_WARNING, ":%s: Setting the correct file owner failed: %s !\n", __func__, strerror(errno)); RETURN(-errno); } } RETURN(0); } unionfs-fuse-1.0/src/general.h000066400000000000000000000012471245544002400163540ustar00rootroot00000000000000/* * License: BSD-style license * Copyright: Radek Podgorny , * Bernd Schubert */ #ifndef GENERAL_H #define GENERAL_H #include enum whiteout { WHITEOUT_FILE, WHITEOUT_DIR }; typedef enum filetype { NOT_EXISTING=-1, IS_DIR=0, IS_FILE=1, } filetype_t; int path_hidden(const char *path, int branch); int remove_hidden(const char *path, int maxbranch); int hide_file(const char *path, int branch_rw); int hide_dir(const char *path, int branch_rw); filetype_t path_is_dir (const char *path); int maybe_whiteout(const char *path, int branch_rw, enum whiteout mode); int set_owner(const char *path); #endif unionfs-fuse-1.0/src/hashtable.c000066400000000000000000000223111245544002400166600ustar00rootroot00000000000000/* Copyright (C) 2004 Christopher Clark */ #include "hashtable.h" #include "hashtable_private.h" #include #include #include /* Credit for primes table: Aaron Krowne http://br.endernet.org/~akrowne/ http://planetmath.org/encyclopedia/GoodHashTablePrimes.html */ static const unsigned int primes[] = { 53, 97, 193, 389, 769, 1543, 3079, 6151, 12289, 24593, 49157, 98317, 196613, 393241, 786433, 1572869, 3145739, 6291469, 12582917, 25165843, 50331653, 100663319, 201326611, 402653189, 805306457, 1610612741 }; const unsigned int prime_table_length = sizeof(primes)/sizeof(primes[0]); const float max_load_factor = 0.65; #define TOL 1e-6 // float tolerance /** * my_ceil - calculate ceil value * Using C ceil() from math.h with -lm requires to link this library only for * this simple function. As it is not performance relevent for the hash table * we use our own * implementation. * */ static int my_ceil(float x) { int y = (int) x; if (y - x > TOL) return y; else return y + 1; } /*****************************************************************************/ struct hashtable * create_hashtable(unsigned int minsize, unsigned int (*hashf) (void*), int (*eqf) (void*,void*)) { struct hashtable *h; unsigned int pindex, size = primes[0]; /* Check requested hashtable isn't too large */ if (minsize > (1u << 30)) return NULL; /* Enforce size as prime */ for (pindex=0; pindex < prime_table_length; pindex++) { if (primes[pindex] > minsize) { size = primes[pindex]; break; } } h = (struct hashtable *)malloc(sizeof(struct hashtable)); if (NULL == h) return NULL; /*oom*/ h->table = (struct entry **)malloc(sizeof(struct entry*) * size); if (NULL == h->table) { free(h); return NULL; } /*oom*/ memset(h->table, 0, size * sizeof(struct entry *)); h->tablelength = size; h->primeindex = pindex; h->entrycount = 0; h->hashfn = hashf; h->eqfn = eqf; h->loadlimit = my_ceil(size * max_load_factor); return h; } /*****************************************************************************/ unsigned int hash(struct hashtable *h, void *k) { /* Aim to protect against poor hash functions by adding logic here * - logic taken from java 1.4 hashtable source */ unsigned int i = h->hashfn(k); i += ~(i << 9); i ^= ((i >> 14) | (i << 18)); /* >>> */ i += (i << 4); i ^= ((i >> 10) | (i << 22)); /* >>> */ return i; } /*****************************************************************************/ static int hashtable_expand(struct hashtable *h) { /* Double the size of the table to accomodate more entries */ struct entry **newtable; struct entry *e; struct entry **pE; unsigned int newsize, i, index; /* Check we're not hitting max capacity */ if (h->primeindex == (prime_table_length - 1)) return 0; newsize = primes[++(h->primeindex)]; newtable = (struct entry **)malloc(sizeof(struct entry*) * newsize); if (NULL != newtable) { memset(newtable, 0, newsize * sizeof(struct entry *)); /* This algorithm is not 'stable'. ie. it reverses the list * when it transfers entries between the tables */ for (i = 0; i < h->tablelength; i++) { while (NULL != (e = h->table[i])) { h->table[i] = e->next; index = indexFor(newsize,e->h); e->next = newtable[index]; newtable[index] = e; } } free(h->table); h->table = newtable; } /* Plan B: realloc instead */ else { newtable = (struct entry **) realloc(h->table, newsize * sizeof(struct entry *)); if (NULL == newtable) { (h->primeindex)--; return 0; } h->table = newtable; memset(newtable[h->tablelength], 0, newsize - h->tablelength); for (i = 0; i < h->tablelength; i++) { for (pE = &(newtable[i]), e = *pE; e != NULL; e = *pE) { index = indexFor(newsize,e->h); if (index == i) { pE = &(e->next); } else { *pE = e->next; e->next = newtable[index]; newtable[index] = e; } } } } h->tablelength = newsize; h->loadlimit = my_ceil(newsize * max_load_factor); return -1; } /*****************************************************************************/ unsigned int hashtable_count(struct hashtable *h) { return h->entrycount; } /*****************************************************************************/ int hashtable_insert(struct hashtable *h, void *k, void *v) { /* This method allows duplicate keys - but they shouldn't be used */ unsigned int index; struct entry *e; if (++(h->entrycount) > h->loadlimit) { /* Ignore the return value. If expand fails, we should * still try cramming just this value into the existing table * -- we may not have memory for a larger table, but one more * element may be ok. Next time we insert, we'll try expanding again.*/ hashtable_expand(h); } e = (struct entry *)malloc(sizeof(struct entry)); if (NULL == e) { --(h->entrycount); return 0; } /*oom*/ e->h = hash(h,k); index = indexFor(h->tablelength,e->h); e->k = k; e->v = v; e->next = h->table[index]; h->table[index] = e; return -1; } /*****************************************************************************/ void * /* returns value associated with key */ hashtable_search(struct hashtable *h, void *k) { struct entry *e; unsigned int hashvalue, index; hashvalue = hash(h,k); index = indexFor(h->tablelength,hashvalue); e = h->table[index]; while (NULL != e) { /* Check hash value to short circuit heavier comparison */ if ((hashvalue == e->h) && (h->eqfn(k, e->k))) return e->v; e = e->next; } return NULL; } /*****************************************************************************/ void * /* returns value associated with key */ hashtable_remove(struct hashtable *h, void *k) { /* TODO: consider compacting the table when the load factor drops enough, * or provide a 'compact' method. */ struct entry *e; struct entry **pE; void *v; unsigned int hashvalue, index; hashvalue = hash(h,k); index = indexFor(h->tablelength,hash(h,k)); pE = &(h->table[index]); e = *pE; while (NULL != e) { /* Check hash value to short circuit heavier comparison */ if ((hashvalue == e->h) && (h->eqfn(k, e->k))) { *pE = e->next; h->entrycount--; v = e->v; freekey(e->k); free(e); return v; } pE = &(e->next); e = e->next; } return NULL; } /*****************************************************************************/ /* destroy */ void hashtable_destroy(struct hashtable *h, int free_values) { unsigned int i; struct entry *e, *f; struct entry **table = h->table; if (free_values) { for (i = 0; i < h->tablelength; i++) { e = table[i]; while (NULL != e) { f = e; e = e->next; freekey(f->k); free(f->v); free(f); } } } else { for (i = 0; i < h->tablelength; i++) { e = table[i]; while (NULL != e) { f = e; e = e->next; freekey(f->k); free(f); } } } free(h->table); free(h); } /* * Copyright (c) 2002, Christopher Clark * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * * 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. * * * Neither the name of the original author; nor the names of any contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER * 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. */ unionfs-fuse-1.0/src/hashtable.h000066400000000000000000000163511245544002400166740ustar00rootroot00000000000000/* Copyright (C) 2002 Christopher Clark */ #ifndef __HASHTABLE_CWC22_H__ #define __HASHTABLE_CWC22_H__ struct hashtable; /* Example of use: * * struct hashtable *h; * struct some_key *k; * struct some_value *v; * * static unsigned int hash_from_key_fn( void *k ); * static int keys_equal_fn ( void *key1, void *key2 ); * * h = create_hashtable(16, hash_from_key_fn, keys_equal_fn); * k = (struct some_key *) malloc(sizeof(struct some_key)); * v = (struct some_value *) malloc(sizeof(struct some_value)); * * (initialise k and v to suitable values) * * if (! hashtable_insert(h,k,v) ) * { exit(-1); } * * if (NULL == (found = hashtable_search(h,k) )) * { printf("not found!"); } * * if (NULL == (found = hashtable_remove(h,k) )) * { printf("Not found\n"); } * */ /* Macros may be used to define type-safe(r) hashtable access functions, with * methods specialized to take known key and value types as parameters. * * Example: * * Insert this at the start of your file: * * DEFINE_HASHTABLE_INSERT(insert_some, struct some_key, struct some_value); * DEFINE_HASHTABLE_SEARCH(search_some, struct some_key, struct some_value); * DEFINE_HASHTABLE_REMOVE(remove_some, struct some_key, struct some_value); * * This defines the functions 'insert_some', 'search_some' and 'remove_some'. * These operate just like hashtable_insert etc., with the same parameters, * but their function signatures have 'struct some_key *' rather than * 'void *', and hence can generate compile time errors if your program is * supplying incorrect data as a key (and similarly for value). * * Note that the hash and key equality functions passed to create_hashtable * still take 'void *' parameters instead of 'some key *'. This shouldn't be * a difficult issue as they're only defined and passed once, and the other * functions will ensure that only valid keys are supplied to them. * * The cost for this checking is increased code size and runtime overhead * - if performance is important, it may be worth switching back to the * unsafe methods once your program has been debugged with the safe methods. * This just requires switching to some simple alternative defines - eg: * #define insert_some hashtable_insert * */ /***************************************************************************** * create_hashtable * @name create_hashtable * @param minsize minimum initial size of hashtable * @param hashfunction function for hashing keys * @param key_eq_fn function for determining key equality * @return newly created hashtable or NULL on failure */ struct hashtable * create_hashtable(unsigned int minsize, unsigned int (*hashfunction) (void*), int (*key_eq_fn) (void*,void*)); /***************************************************************************** * hashtable_insert * @name hashtable_insert * @param h the hashtable to insert into * @param k the key - hashtable claims ownership and will free on removal * @param v the value - does not claim ownership * @return non-zero for successful insertion * * This function will cause the table to expand if the insertion would take * the ratio of entries to table size over the maximum load factor. * * This function does not check for repeated insertions with a duplicate key. * The value returned when using a duplicate key is undefined -- when * the hashtable changes size, the order of retrieval of duplicate key * entries is reversed. * If in doubt, remove before insert. */ int hashtable_insert(struct hashtable *h, void *k, void *v); #define DEFINE_HASHTABLE_INSERT(fnname, keytype, valuetype) \ int fnname (struct hashtable *h, keytype *k, valuetype *v) \ { \ return hashtable_insert(h,k,v); \ } /***************************************************************************** * hashtable_search * @name hashtable_search * @param h the hashtable to search * @param k the key to search for - does not claim ownership * @return the value associated with the key, or NULL if none found */ void * hashtable_search(struct hashtable *h, void *k); #define DEFINE_HASHTABLE_SEARCH(fnname, keytype, valuetype) \ valuetype * fnname (struct hashtable *h, keytype *k) \ { \ return (valuetype *) (hashtable_search(h,k)); \ } /***************************************************************************** * hashtable_remove * @name hashtable_remove * @param h the hashtable to remove the item from * @param k the key to search for - does not claim ownership * @return the value associated with the key, or NULL if none found */ void * /* returns value */ hashtable_remove(struct hashtable *h, void *k); #define DEFINE_HASHTABLE_REMOVE(fnname, keytype, valuetype) \ valuetype * fnname (struct hashtable *h, keytype *k) \ { \ return (valuetype *) (hashtable_remove(h,k)); \ } /***************************************************************************** * hashtable_count * @name hashtable_count * @param h the hashtable * @return the number of items stored in the hashtable */ unsigned int hashtable_count(struct hashtable *h); /***************************************************************************** * hashtable_destroy * @name hashtable_destroy * @param h the hashtable * @param free_values whether to call 'free' on the remaining values */ void hashtable_destroy(struct hashtable *h, int free_values); #endif /* __HASHTABLE_CWC22_H__ */ /* * Copyright (c) 2002, Christopher Clark * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * * 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. * * * Neither the name of the original author; nor the names of any contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER * 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. */ unionfs-fuse-1.0/src/hashtable_itr.c000066400000000000000000000124201245544002400175360ustar00rootroot00000000000000/* Copyright (C) 2002, 2004 Christopher Clark */ #include "hashtable.h" #include "hashtable_private.h" #include "hashtable_itr.h" #include /* defines NULL */ /*****************************************************************************/ /* hashtable_iterator - iterator constructor */ struct hashtable_itr * hashtable_iterator(struct hashtable *h) { unsigned int i, tablelength; struct hashtable_itr *itr = (struct hashtable_itr *) malloc(sizeof(struct hashtable_itr)); if (NULL == itr) return NULL; itr->h = h; itr->e = NULL; itr->parent = NULL; tablelength = h->tablelength; itr->index = tablelength; if (0 == h->entrycount) return itr; for (i = 0; i < tablelength; i++) { if (NULL != h->table[i]) { itr->e = h->table[i]; itr->index = i; break; } } return itr; } /*****************************************************************************/ /* advance - advance the iterator to the next element * returns zero if advanced to end of table */ int hashtable_iterator_advance(struct hashtable_itr *itr) { unsigned int j,tablelength; struct entry **table; struct entry *next; if (NULL == itr->e) return 0; /* stupidity check */ next = itr->e->next; if (NULL != next) { itr->parent = itr->e; itr->e = next; return -1; } tablelength = itr->h->tablelength; itr->parent = NULL; if (tablelength <= (j = ++(itr->index))) { itr->e = NULL; return 0; } table = itr->h->table; while (NULL == (next = table[j])) { if (++j >= tablelength) { itr->index = tablelength; itr->e = NULL; return 0; } } itr->index = j; itr->e = next; return -1; } /*****************************************************************************/ /* remove - remove the entry at the current iterator position * and advance the iterator, if there is a successive * element. * If you want the value, read it before you remove: * beware memory leaks if you don't. * Returns zero if end of iteration. */ int hashtable_iterator_remove(struct hashtable_itr *itr) { struct entry *remember_e, *remember_parent; int ret; /* Do the removal */ if (NULL == (itr->parent)) { /* element is head of a chain */ itr->h->table[itr->index] = itr->e->next; } else { /* element is mid-chain */ itr->parent->next = itr->e->next; } /* itr->e is now outside the hashtable */ remember_e = itr->e; itr->h->entrycount--; freekey(remember_e->k); /* Advance the iterator, correcting the parent */ remember_parent = itr->parent; ret = hashtable_iterator_advance(itr); if (itr->parent == remember_e) { itr->parent = remember_parent; } free(remember_e); return ret; } /*****************************************************************************/ int /* returns zero if not found */ hashtable_iterator_search(struct hashtable_itr *itr, struct hashtable *h, void *k) { struct entry *e, *parent; unsigned int hashvalue, index; hashvalue = hash(h,k); index = indexFor(h->tablelength,hashvalue); e = h->table[index]; parent = NULL; while (NULL != e) { /* Check hash value to short circuit heavier comparison */ if ((hashvalue == e->h) && (h->eqfn(k, e->k))) { itr->index = index; itr->e = e; itr->parent = parent; itr->h = h; return -1; } parent = e; e = e->next; } return 0; } /* * Copyright (c) 2002, 2004, Christopher Clark * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * * 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. * * * Neither the name of the original author; nor the names of any contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER * 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. */ unionfs-fuse-1.0/src/hashtable_itr.h000066400000000000000000000077661245544002400175640ustar00rootroot00000000000000/* Copyright (C) 2002, 2004 Christopher Clark */ #ifndef __HASHTABLE_ITR_CWC22__ #define __HASHTABLE_ITR_CWC22__ #include "hashtable.h" #include "hashtable_private.h" /* needed to enable inlining */ /*****************************************************************************/ /* This struct is only concrete here to allow the inlining of two of the * accessor functions. */ struct hashtable_itr { struct hashtable *h; struct entry *e; struct entry *parent; unsigned int index; }; /*****************************************************************************/ /* hashtable_iterator */ struct hashtable_itr * hashtable_iterator(struct hashtable *h); /*****************************************************************************/ /* hashtable_iterator_key * - return the value of the (key,value) pair at the current position */ extern inline void * hashtable_iterator_key(struct hashtable_itr *i) { return i->e->k; } /*****************************************************************************/ /* value - return the value of the (key,value) pair at the current position */ extern inline void * hashtable_iterator_value(struct hashtable_itr *i) { return i->e->v; } /*****************************************************************************/ /* advance - advance the iterator to the next element * returns zero if advanced to end of table */ int hashtable_iterator_advance(struct hashtable_itr *itr); /*****************************************************************************/ /* remove - remove current element and advance the iterator to the next element * NB: if you need the value to free it, read it before * removing. ie: beware memory leaks! * returns zero if advanced to end of table */ int hashtable_iterator_remove(struct hashtable_itr *itr); /*****************************************************************************/ /* search - overwrite the supplied iterator, to point to the entry * matching the supplied key. h points to the hashtable to be searched. * returns zero if not found. */ int hashtable_iterator_search(struct hashtable_itr *itr, struct hashtable *h, void *k); #define DEFINE_HASHTABLE_ITERATOR_SEARCH(fnname, keytype) \ int fnname (struct hashtable_itr *i, struct hashtable *h, keytype *k) \ { \ return (hashtable_iterator_search(i,h,k)); \ } #endif /* __HASHTABLE_ITR_CWC22__*/ /* * Copyright (c) 2002, 2004, Christopher Clark * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * * 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. * * * Neither the name of the original author; nor the names of any contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER * 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. */ unionfs-fuse-1.0/src/hashtable_private.h000066400000000000000000000056211245544002400204240ustar00rootroot00000000000000/* Copyright (C) 2002, 2004 Christopher Clark */ #ifndef __HASHTABLE_PRIVATE_CWC22_H__ #define __HASHTABLE_PRIVATE_CWC22_H__ #include "hashtable.h" /*****************************************************************************/ struct entry { void *k, *v; unsigned int h; struct entry *next; }; struct hashtable { unsigned int tablelength; struct entry **table; unsigned int entrycount; unsigned int loadlimit; unsigned int primeindex; unsigned int (*hashfn) (void *k); int (*eqfn) (void *k1, void *k2); }; /*****************************************************************************/ unsigned int hash(struct hashtable *h, void *k); /*****************************************************************************/ /* indexFor */ static inline unsigned int indexFor(unsigned int tablelength, unsigned int hashvalue) { return (hashvalue % tablelength); }; /* Only works if tablelength == 2^N */ /*static inline unsigned int indexFor(unsigned int tablelength, unsigned int hashvalue) { return (hashvalue & (tablelength - 1u)); } */ /*****************************************************************************/ #define freekey(X) free(X) /*define freekey(X) ; */ /*****************************************************************************/ #endif /* __HASHTABLE_PRIVATE_CWC22_H__*/ /* * Copyright (c) 2002, Christopher Clark * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * * 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. * * * Neither the name of the original author; nor the names of any contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER * 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. */ unionfs-fuse-1.0/src/opts.c000066400000000000000000000245251245544002400157230ustar00rootroot00000000000000/* * C Implementation: opts.c * * Option parser * * License: BSD-style license * Copyright: Radek Podgorny , * Bernd Schubert */ #include #include #include #include #include #include #include #include #include "conf.h" #include "opts.h" #include "version.h" #include "string.h" /** * Set debug path */ void set_debug_path(char *new_path, int len) { pthread_rwlock_wrlock(&uopt.dbgpath_lock); // LOCK path if (uopt.dbgpath) free(uopt.dbgpath); uopt.dbgpath = strndup(new_path, len); pthread_rwlock_unlock(&uopt.dbgpath_lock); // UNLOCK path } /** * Check if a debug path is set */ static bool get_has_debug_path(void) { pthread_rwlock_rdlock(&uopt.dbgpath_lock); // LOCK path bool has_debug_path = (uopt.dbgpath) ? true : false; pthread_rwlock_unlock(&uopt.dbgpath_lock); // UNLOCK path return has_debug_path; } /** * Enable or disable internal debugging */ bool set_debug_onoff(int value) { bool res = false; if (value) { bool has_debug_path = get_has_debug_path(); if (has_debug_path) { uopt.debug = 1; res = true; } } else { uopt.debug = 0; res = true; } return res; } /** * Set the maximum number of open files */ int set_max_open_files(const char *arg) { struct rlimit rlim; unsigned long max_files; if (sscanf(arg, "max_files=%ld\n", &max_files) != 1) { fprintf(stderr, "%s Converting %s to number failed, aborting!\n", __func__, arg); exit(1); } rlim.rlim_cur = max_files; rlim.rlim_max = max_files; if (setrlimit(RLIMIT_NOFILE, &rlim)) { fprintf(stderr, "%s: Setting the maximum number of files failed: %s\n", __func__, strerror(errno)); exit(1); } return 0; } uopt_t uopt; void uopt_init() { memset(&uopt, 0, sizeof(uopt_t)); // initialize options with zeros first pthread_rwlock_init(&uopt.dbgpath_lock, NULL); } /** * Take a relative path as argument and return the absolute path by using the * current working directory. The return string is malloc'ed with this function. */ char *make_absolute(char *relpath) { // Already an absolute path if (*relpath == '/') return relpath; char cwd[PATHLEN_MAX]; if (!getcwd(cwd, PATHLEN_MAX)) { perror("Unable to get current working directory"); return NULL; } size_t cwdlen = strlen(cwd); if (!cwdlen) { fprintf(stderr, "Zero-sized length of CWD!\n"); return NULL; } // 2 due to: +1 for '/' between cwd and relpath // +1 for trailing '/' int abslen = cwdlen + strlen(relpath) + 2; if (abslen > PATHLEN_MAX) { fprintf(stderr, "Absolute path too long!\n"); return NULL; } char *abspath = malloc(abslen); if (abspath == NULL) { fprintf(stderr, "%s: malloc failed\n", __func__); exit(1); // still at early stage, we can abort } // the ending required slash is added later by add_trailing_slash() snprintf(abspath, abslen, "%s/%s", cwd, relpath); return abspath; } /** * Add a trailing slash at the end of a branch. So functions using this * path don't have to care about this slash themselves. **/ char *add_trailing_slash(char *path) { int len = strlen(path); if (path[len - 1] == '/') { return path; // no need to add a slash, already there } path = realloc(path, len + 2); // +1 for '/' and +1 for '\0' if (!path) { fprintf(stderr, "%s: realloc() failed, aborting\n", __func__); exit(1); // still very early stage, we can abort here } strcat(path, "/"); return path; } /** * Add a given branch and its options to the array of available branches. * example branch string "branch1=RO" or "/path/path2=RW" */ static void add_branch(char *branch) { uopt.branches = realloc(uopt.branches, (uopt.nbranches+1) * sizeof(branch_entry_t)); if (uopt.branches == NULL) { fprintf(stderr, "%s: realloc failed\n", __func__); exit(1); // still at early stage, we can't abort } char *res; char **ptr = (char **)&branch; res = strsep(ptr, "="); if (!res) return; // for string manipulations it is important to copy the string, otherwise // make_absolute() and add_trailing_slash() will corrupt our input (parse string) uopt.branches[uopt.nbranches].path = strdup(res); uopt.branches[uopt.nbranches].rw = 0; res = strsep(ptr, "="); if (res) { if (strcasecmp(res, "rw") == 0) { uopt.branches[uopt.nbranches].rw = 1; } else if (strcasecmp(res, "ro") == 0) { // no action needed here } else { fprintf(stderr, "Failed to parse RO/RW flag, setting RO.\n"); // no action needed here either } } uopt.nbranches++; } /** * These options define our branch paths. * example arg string: "branch1=RW:branch2=RO:branch3=RO" */ static int parse_branches(const char *arg) { // the last argument is our mountpoint, don't take it as branch! if (uopt.nbranches) return 0; // We don't free the buf as parts of it may go to branches char *buf = strdup(arg); char **ptr = (char **)&buf; char *branch; while ((branch = strsep(ptr, ROOT_SEP)) != NULL) { if (strlen(branch) == 0) continue; add_branch(branch); } free(branch); free(buf); return uopt.nbranches; } /** * get_opt_str - get the parameter string * @arg - option argument * @opt_name - option name, used for error messages * fuse passes arguments with the argument prefix, e.g. * "-o chroot=/path/to/chroot/" will give us "chroot=/path/to/chroot/" * and we need to cut off the "chroot=" part * NOTE: If the user specifies a relative path of the branches * to the chroot, it is absolutely required * -o chroot=path is provided before specifying the braches! */ static char * get_opt_str(const char *arg, char *opt_name) { char *str = index(arg, '='); if (!str) { fprintf(stderr, "-o %s parameter not properly specified, aborting!\n", opt_name); exit(1); // still early phase, we can abort } if (strlen(str) < 3) { fprintf(stderr, "%s path has not sufficient characters, aborting!\n", opt_name); exit(1); } str++; // just jump over the '=' // copy of the given parameter, just in case something messes around // with command line parameters later on str = strdup(str); if (!str) { fprintf(stderr, "strdup failed: %s Aborting!\n", strerror(errno)); exit(1); } return str; } static void print_help(const char *progname) { printf( "unionfs-fuse version "VERSION"\n" "by Radek Podgorny \n" "\n" "Usage: %s [options] branch[=RO/RW][:branch...] mountpoint\n" "The first argument is a colon separated list of directories to merge\n" "When neither RO nor RW is specified, selection defaults to RO.\n" "\n" "general options:\n" " -d Enable debug output\n" " -o opt,[opt...] mount options\n" " -h --help print help\n" " -V --version print version\n" "\n" "UnionFS options:\n" " -o chroot=path chroot into this path. Use this if you \n" " want to have a union of \"/\" \n" " -o cow enable copy-on-write\n" " mountpoint\n" " -o debug_file file to write debug information into\n" " -o dirs=branch[=RO/RW][:branch...]\n" " alternate way to specify directories to merge\n" " -o hide_meta_files \".unionfs\" is a secret directory not\n" " visible by readdir(), and so are\n" " .fuse_hidden* files\n" " -o max_files=number Increase the maximum number of open files\n" " -o relaxed_permissions Disable permissions checks, but only if\n" " running neither as UID=0 or GID=0\n" " -o statfs_omit_ro do not count blocks of ro-branches\n" "\n", progname); } /** * This method is to post-process options once we know all of them */ void unionfs_post_opts(void) { // chdir to the given chroot, we if (uopt.chroot) { int res = chdir(uopt.chroot); if (res) { fprintf(stderr, "Chdir to %s failed: %s ! Aborting!\n", uopt.chroot, strerror(errno)); exit(1); } } // Make the pathes absolute and add trailing slashes int i; for (i = 0; i 0) return 0; uopt.retval = 1; return 1; case KEY_DIRS: // skip the "dirs=" res = parse_branches(arg+5); if (res > 0) return 0; uopt.retval = 1; return 1; case KEY_CHROOT: uopt.chroot = get_opt_str(arg, "chroot"); return 0; case KEY_COW: uopt.cow_enabled = true; return 0; case KEY_DEBUG_FILE: uopt.dbgpath = get_opt_str(arg, "debug_file"); uopt.debug = true; return 0; case KEY_HELP: print_help(outargs->argv[0]); fuse_opt_add_arg(outargs, "-ho"); uopt.doexit = 1; return 0; case KEY_HIDE_META_FILES: case KEY_HIDE_METADIR: uopt.hide_meta_files = true; return 0; case KEY_MAX_FILES: set_max_open_files(arg); return 0; case KEY_NOINITGROUPS: // option only for compatibility with older versions return 0; case KEY_STATFS_OMIT_RO: uopt.statfs_omit_ro = true; return 0; case KEY_RELAXED_PERMISSIONS: uopt.relaxed_permissions = true; return 0; case KEY_VERSION: printf("unionfs-fuse version: "VERSION"\n"); #ifdef HAVE_XATTR printf("(compiled with xattr support)\n"); #endif uopt.doexit = 1; return 1; default: uopt.retval = 1; return 1; } } unionfs-fuse-1.0/src/opts.h000066400000000000000000000020771245544002400157260ustar00rootroot00000000000000/* * License: BSD-style license * Copyright: Radek Podgorny , * Bernd Schubert */ #ifndef OPTS_H #define OPTS_H #include #include #include "unionfs.h" #define ROOT_SEP ":" typedef struct { int nbranches; branch_entry_t *branches; bool cow_enabled; bool statfs_omit_ro; int doexit; int retval; char *chroot; // chroot we might go into bool debug; // enable debugging char *dbgpath; // debug file we write debug information into pthread_rwlock_t dbgpath_lock; // locks dbgpath bool hide_meta_files; bool relaxed_permissions; } uopt_t; enum { KEY_CHROOT, KEY_COW, KEY_DEBUG_FILE, KEY_DIRS, KEY_HELP, KEY_HIDE_META_FILES, KEY_HIDE_METADIR, KEY_MAX_FILES, KEY_NOINITGROUPS, KEY_RELAXED_PERMISSIONS, KEY_STATFS_OMIT_RO, KEY_VERSION }; extern uopt_t uopt; void set_debug_path(char *new_path, int strlen); bool set_debug_onoff(int value); void uopt_init(); int unionfs_opt_proc(void *data, const char *arg, int key, struct fuse_args *outargs); void unionfs_post_opts(); #endif unionfs-fuse-1.0/src/readdir.c000066400000000000000000000134551245544002400163500ustar00rootroot00000000000000/* * Description: find a file in a branch * * License: BSD-style license * * original implementation by Radek Podgorny * * License: BSD-style license * Copyright: Radek Podgorny , * Bernd Schubert * * * */ #include #include #include #include #include #include #include #include #include #include "unionfs.h" #include "opts.h" #include "debug.h" #include "hashtable.h" #include "general.h" #include "string.h" /** * Hide metadata. As is causes a slight slowndown this is optional * */ static bool hide_meta_files(int branch, const char *path, struct dirent *de) { if (uopt.hide_meta_files == false) RETURN(false); fprintf(stderr, "uopt.branches[branch].path = %s path = %s\n", uopt.branches[branch].path, path); fprintf(stderr, "METANAME = %s, de->d_name = %s\n", METANAME, de->d_name); // TODO Would it be faster to add hash comparison? // HIDE out .unionfs directory if (strcmp(uopt.branches[branch].path, path) == 0 && strcmp(METANAME, de->d_name) == 0) { RETURN(true); } // HIDE fuse META files if (strncmp(FUSE_META_FILE, de->d_name, FUSE_META_LENGTH) == 0) RETURN(true); RETURN(false); } /** * Check if fname has a hiding tag and return its status. * Also, add this file and to the hiding hash table. * Warning: If fname has the tag, fname gets modified. */ static bool is_hiding(struct hashtable *hides, char *fname) { DBG("%s\n", fname); char *tag; tag = whiteout_tag(fname); if (tag) { // even more important, ignore the file without the tag! // hint: tag is a pointer to the flag-suffix within de->d_name *tag = '\0'; // this modifies fname! // add to hides (only if not there already) if (!hashtable_search(hides, fname)) { hashtable_insert(hides, strdup(fname), malloc(1)); } RETURN(true); } RETURN(false); } /** * Read whiteout files */ static void read_whiteouts(const char *path, struct hashtable *whiteouts, int branch) { DBG("%s\n", path); char p[PATHLEN_MAX]; if (BUILD_PATH(p, uopt.branches[branch].path, METADIR, path)) return; DIR *dp = opendir(p); if (dp == NULL) return; struct dirent *de; while ((de = readdir(dp)) != NULL) { is_hiding(whiteouts, de->d_name); } closedir(dp); } /** * unionfs-fuse readdir function */ int unionfs_readdir(const char *path, void *buf, fuse_fill_dir_t filler, off_t offset, struct fuse_file_info *fi) { DBG("%s\n", path); (void)offset; (void)fi; int i = 0; int rc = 0; // we will store already added files here to handle same file names across different branches struct hashtable *files = create_hashtable(16, string_hash, string_equal); struct hashtable *whiteouts = NULL; if (uopt.cow_enabled) whiteouts = create_hashtable(16, string_hash, string_equal); bool subdir_hidden = false; for (i = 0; i < uopt.nbranches; i++) { if (subdir_hidden) break; char p[PATHLEN_MAX]; if (BUILD_PATH(p, uopt.branches[i].path, path)) { rc = -ENAMETOOLONG; goto out; } // check if branches below this branch are hidden int res = path_hidden(path, i); if (res < 0) { rc = res; // error goto out; } if (res > 0) subdir_hidden = true; DIR *dp = opendir(p); if (dp == NULL) { if (uopt.cow_enabled) read_whiteouts(path, whiteouts, i); continue; } struct dirent *de; while ((de = readdir(dp)) != NULL) { // already added in some other branch if (hashtable_search(files, de->d_name) != NULL) continue; // check if we need file hiding if (uopt.cow_enabled) { // file should be hidden from the user if (hashtable_search(whiteouts, de->d_name) != NULL) continue; } if (hide_meta_files(i, p, de) == true) continue; // fill with something dummy, we're interested in key existence only hashtable_insert(files, strdup(de->d_name), malloc(1)); struct stat st; memset(&st, 0, sizeof(st)); st.st_ino = de->d_ino; st.st_mode = de->d_type << 12; if (filler(buf, de->d_name, &st, 0)) break; } closedir(dp); if (uopt.cow_enabled) read_whiteouts(path, whiteouts, i); } out: hashtable_destroy(files, 1); if (uopt.cow_enabled) hashtable_destroy(whiteouts, 1); RETURN(rc); } /** * check if a directory on all paths is empty * return 0 if empty, 1 if not and negative value on error * * TODO: This shares lots of code with unionfs_readdir(), can we merge * both functions? */ int dir_not_empty(const char *path) { DBG("%s\n", path); int i = 0; int rc = 0; int not_empty = 0; struct hashtable *whiteouts = NULL; if (uopt.cow_enabled) whiteouts = create_hashtable(16, string_hash, string_equal); bool subdir_hidden = false; for (i = 0; i < uopt.nbranches; i++) { if (subdir_hidden) break; char p[PATHLEN_MAX]; if (BUILD_PATH(p, uopt.branches[i].path, path)) { rc = -ENAMETOOLONG; goto out; } // check if branches below this branch are hidden int res = path_hidden(path, i); if (res < 0) { rc = res; // error goto out; } if (res > 0) subdir_hidden = true; DIR *dp = opendir(p); if (dp == NULL) { if (uopt.cow_enabled) read_whiteouts(path, whiteouts, i); continue; } struct dirent *de; while ((de = readdir(dp)) != NULL) { // Ignore . and .. if ((strcmp(de->d_name, ".") == 0) || (strcmp(de->d_name, "..") == 0)) continue; // check if we need file hiding if (uopt.cow_enabled) { // file should be hidden from the user if (hashtable_search(whiteouts, de->d_name) != NULL) continue; } if (hide_meta_files(i, p, de) == true) continue; // When we arrive here, a valid entry was found not_empty = 1; closedir(dp); goto out; } closedir(dp); if (uopt.cow_enabled) read_whiteouts(path, whiteouts, i); } out: if (uopt.cow_enabled) hashtable_destroy(whiteouts, 1); if (rc) RETURN(rc); RETURN(not_empty); } unionfs-fuse-1.0/src/readdir.h000066400000000000000000000003301245544002400163410ustar00rootroot00000000000000#ifndef READDIR_H #define READDIR_H #include int unionfs_readdir(const char *path, void *buf, fuse_fill_dir_t filler, off_t offset, struct fuse_file_info *fi); int dir_not_empty(const char *path); #endif unionfs-fuse-1.0/src/rmdir.c000066400000000000000000000046531245544002400160530ustar00rootroot00000000000000/* * C Implementation: rmdir * * Description: unionfs rmdir() call * If the directory to remove exists in a lower branch, create a * file with a tag informing other functions that the file * is hidden. * * original implementation by Radek Podgorny * * License: BSD-style license * Copyright: Radek Podgorny , * Bernd Schubert * */ #include #include #include #include #include #include #include #include #include #include "unionfs.h" #include "opts.h" #include "debug.h" #include "cow.h" #include "general.h" #include "findbranch.h" #include "string.h" #include "readdir.h" #include "usyslog.h" /** * If the branch that has the directory to be removed is in read-write mode, * we can really delete the file. */ static int rmdir_rw(const char *path, int branch_rw) { DBG("%s\n", path); char p[PATHLEN_MAX]; if (BUILD_PATH(p, uopt.branches[branch_rw].path, path)) return ENAMETOOLONG; int res = rmdir(p); if (res == -1) return errno; return 0; } /** * If the branch that has the directory to be removed is in read-only mode, * we create a file with a HIDE tag in an upper level branch. * To other fuse functions this tag means, not to expose the * lower level directory. */ static int rmdir_ro(const char *path, int branch_ro) { DBG("%s\n", path); // find a writable branch above branch_ro int branch_rw = find_lowest_rw_branch(branch_ro); if (branch_rw < 0) return -EACCES; DBG("Calling hide_dir\n"); if (hide_dir(path, branch_rw) == -1) { switch (errno) { case (EEXIST): case (ENOTDIR): case (ENOTEMPTY): // catch errors not allowed for rmdir() USYSLOG (LOG_ERR, "%s: Creating the whiteout failed: %s\n", __func__, strerror(errno)); errno = EFAULT; } return errno; } return 0; } /** * rmdir() call */ int unionfs_rmdir(const char *path) { DBG("%s\n", path); if (dir_not_empty(path)) return -ENOTEMPTY; int i = find_rorw_branch(path); if (i == -1) return -errno; int res; if (!uopt.branches[i].rw) { // read-only branch if (!uopt.cow_enabled) res = EROFS; else res = rmdir_ro(path, i); } else { // read-write branch res = rmdir_rw(path, i); if (res == 0) { // No need to be root, whiteouts are created as root! maybe_whiteout(path, i, WHITEOUT_DIR); } } return -res; } unionfs-fuse-1.0/src/rmdir.h000066400000000000000000000003271245544002400160520ustar00rootroot00000000000000/* * License: BSD-style license * Copyright: Radek Podgorny , * Bernd Schubert */ #ifndef RMDIR_H #define RMDIR_H int unionfs_rmdir(const char *path); #endif unionfs-fuse-1.0/src/string.c000066400000000000000000000116701245544002400162410ustar00rootroot00000000000000/* * C Implementation: string * * Description: General string functions, not directly related to file system operations * * original implementation by Radek Podgorny * * License: BSD-style license * Copyright: Radek Podgorny , * Bernd Schubert * */ #include #include #include #include #include #include #include #include "unionfs.h" #include "opts.h" #include "debug.h" #include "general.h" #include "usyslog.h" /** * Check if the given fname suffixes the hide tag */ char *whiteout_tag(const char *fname) { DBG("%s\n", fname); char *tag = strstr(fname, HIDETAG); // check if fname has tag, fname is not only the tag, file name ends with the tag // TODO: static strlen(HIDETAG) if (tag && tag != fname && strlen(tag) == strlen(HIDETAG)) { return tag; } return NULL; } /** * copy one or more char arrays into dest and check for maximum size * * arguments: maximal string length and one or more char* string arrays * * check if the sum of the strings is larger than PATHLEN_MAX * * This function requires a NULL as last argument! * * path already MUST have been allocated! */ int build_path(char *path, int max_len, const char *callfunc, int line, ...) { va_list ap; // argument pointer int len = 0; char *str_ptr = path; (void)str_ptr; // please the compile to avoid warning in non-debug mode (void)line; (void)callfunc; path[0] = '\0'; // that way can easily strcat even the first element va_start(ap, line); while (1) { char *str = va_arg (ap, char *); // the next path element if (!str) break; /* Prevent '//' in pathes, if len > 0 we are not in the first * loop-run. This is rather ugly, but I don't see another way to * make sure there really is a '/'. By simply cutting off * the initial '/' of the added string, we could run into a bug * and would not have a '/' between path elements at all * if somewhere else a directory slash has been forgotten... */ if (len > 0) { // walk to the end of path while (*path != '\0') path++; // we are on '\0', now go back to the last char path--; if (*path == '/') { int count = len; // count makes sure nobody tricked us and gave // slashes as first path only... while (*path == '/' && count > 1) { // possibly there are several slashes... // But we want only one slash path--; count--; } // now we are *before* '/', walk to slash again path++; // eventually we walk over the slashes of the // next string while (*str == '/') str++; } else if (*str != '/') { // neither path ends with a slash, nor str // starts with a slash, prevent a wrong path strcat(path, "/"); len++; } } va_end(ap); len += strlen(str); // +1 for final \0 not counted by strlen if (len + 1 > max_len) { USYSLOG (LOG_WARNING, "%s():%d Path too long \n", callfunc, line); errno = ENAMETOOLONG; RETURN(-errno); } strcat (path, str); } if (len == 0) { USYSLOG(LOG_ERR, "from: %s():%d : No argument given?\n", callfunc, line); errno = EIO; RETURN(-errno); } DBG("from: %s():%d path: %s\n", callfunc, line, str_ptr); RETURN(0); } /** * dirname() in libc might not be thread-save, at least the man page states * "may return pointers to statically allocated memory", so we need our own * implementation */ char *u_dirname(const char *path) { DBG("%s\n", path); char *ret = strdup(path); if (ret == NULL) { USYSLOG(LOG_WARNING, "strdup failed, probably out of memory!\n"); return ret; } char *ri = strrchr(ret, '/'); if (ri != NULL) { *ri = '\0'; // '/' found, so a full path } else { strcpy(ret, "."); // '/' not found, so path is only a file } return ret; } /** * general elf hash (32-bit) function * * Algorithm taken from URL: http://www.partow.net/programming/hashfunctions/index.html, * but rewritten from scratch due to incompatible license. * * str needs to NULL terminated */ static unsigned int elfhash(const char *str) { DBG("%s\n", str); unsigned int hash = 0; while (*str) { hash = (hash << 4) + (*str); // hash * 16 + c // 0xF is 1111 in dual system, so highbyte is the highest byte of hash (which is 32bit == 4 Byte) unsigned int highbyte = hash & 0xF0000000UL; if (highbyte != 0) hash ^= (highbyte >> 24); // example (if the condition is met): // hash = 10110000000000000000000010100000 // highbyte = 10110000000000000000000000000000 // (highbyte >> 24) = 00000000000000000000000010110000 // after XOR: hash = 10110000000000000000000000010000 hash &= ~highbyte; // ~highbyte = 01001111111111111111111111111111 // after AND: hash = 00000000000000000000000000010000 str++; } return hash; } /** * Just a hash wrapper function, this way we can easily exchange the default * hash algorith. */ unsigned int string_hash(void *s) { return elfhash(s); } unionfs-fuse-1.0/src/string.h000066400000000000000000000020451245544002400162420ustar00rootroot00000000000000/* * License: BSD-style license * Copyright: Radek Podgorny , * Bernd Schubert */ #ifndef UNIONFS_STRING_H #define UNIONFS_STRING_H #include char *whiteout_tag(const char *fname); int build_path(char *dest, int max_len, const char *callfunc, int line, ...); char *u_dirname(const char *path); unsigned int string_hash(void *s); /** * A wrapper for build_path(). In build_path() we test if the given number of strings does exceed * a maximum string length. Since there is no way in C to determine the given number of arguments, we * simply add NULL here. */ #define BUILD_PATH(dest, ...) build_path(dest, PATHLEN_MAX, __func__, __LINE__, __VA_ARGS__, NULL) /** * Test if two strings are eqal. * Return 1 if the strings are equal and 0 if they are different. */ // This is left in the header file bacause gcc is too stupid to inline across object files static inline int string_equal(void *s1, void *s2) { if (strcmp(s1, s2) == 0) return 1; return 0; } #endif // UNIONFS_STRING_H unionfs-fuse-1.0/src/uioctl.h000066400000000000000000000007141245544002400162340ustar00rootroot00000000000000/* * License: BSD-style license * Copyright: Bernd Schubert * */ #ifndef UIOCTL_H_ #define UIOCTL_H_ #include #include "unionfs.h" enum unionfs_ioctls { UNIONFS_ONOFF_DEBUG = _IOW('E', 0, int), UNIONFS_SET_DEBUG_FILE = _IOW('E', 1, char[PATHLEN_MAX]), UNIONFS_STATS_BYTES_READ = _IOW('E', 2, void), UNIONFS_STATS_BYTES_WRITTEN = _IOW('E', 3, void), } unionfs_ioctls_t; #endif // UIOCTL_H_ unionfs-fuse-1.0/src/unionfs.c000066400000000000000000000536541245544002400164240ustar00rootroot00000000000000/* * * This is offered under a BSD-style license. This means you can use the code for whatever you * desire in any way you may want but you MUST NOT forget to give me appropriate credits when * spreading your work which is based on mine. Something like "original implementation by Radek * Podgorny" should be fine. * * License: BSD-style license * Copyright: Radek Podgorny , * Bernd Schubert */ #if defined __linux__ // For pread()/pwrite()/utimensat() #define _XOPEN_SOURCE 700 // For chroot #define _BSD_SOURCE // this is deprecated since glibc 2.20 but let's keep it for a while #define _DEFAULT_SOURCE 1 #endif #include #if __APPLE__ #include #else #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #ifdef linux #include #else #include #endif #ifdef HAVE_XATTR #include #endif #include "unionfs.h" #include "opts.h" #include "debug.h" #include "findbranch.h" #include "general.h" #include "unlink.h" #include "rmdir.h" #include "readdir.h" #include "cow.h" #include "string.h" #include "usyslog.h" #include "conf.h" #include "uioctl.h" static struct fuse_opt unionfs_opts[] = { FUSE_OPT_KEY("chroot=%s,", KEY_CHROOT), FUSE_OPT_KEY("cow", KEY_COW), FUSE_OPT_KEY("debug_file=%s", KEY_DEBUG_FILE), FUSE_OPT_KEY("dirs=%s", KEY_DIRS), FUSE_OPT_KEY("--help", KEY_HELP), FUSE_OPT_KEY("-h", KEY_HELP), FUSE_OPT_KEY("hide_meta_dir", KEY_HIDE_METADIR), FUSE_OPT_KEY("hide_meta_files", KEY_HIDE_META_FILES), FUSE_OPT_KEY("max_files=%s", KEY_MAX_FILES), FUSE_OPT_KEY("noinitgroups", KEY_NOINITGROUPS), FUSE_OPT_KEY("relaxed_permissions", KEY_RELAXED_PERMISSIONS), FUSE_OPT_KEY("statfs_omit_ro", KEY_STATFS_OMIT_RO), FUSE_OPT_KEY("--version", KEY_VERSION), FUSE_OPT_KEY("-V", KEY_VERSION), FUSE_OPT_END }; static int unionfs_chmod(const char *path, mode_t mode) { DBG("%s\n", path); int i = find_rw_branch_cow(path); if (i == -1) RETURN(-errno); char p[PATHLEN_MAX]; if (BUILD_PATH(p, uopt.branches[i].path, path)) RETURN(-ENAMETOOLONG); int res = chmod(p, mode); if (res == -1) RETURN(-errno); RETURN(0); } static int unionfs_chown(const char *path, uid_t uid, gid_t gid) { DBG("%s\n", path); int i = find_rw_branch_cow(path); if (i == -1) RETURN(-errno); char p[PATHLEN_MAX]; if (BUILD_PATH(p, uopt.branches[i].path, path)) RETURN(-ENAMETOOLONG); int res = lchown(p, uid, gid); if (res == -1) RETURN(-errno); RETURN(0); } /** * unionfs implementation of the create call * libfuse will call this to create regular files */ static int unionfs_create(const char *path, mode_t mode, struct fuse_file_info *fi) { DBG("%s\n", path); int i = find_rw_branch_cutlast(path); if (i == -1) RETURN(-errno); char p[PATHLEN_MAX]; if (BUILD_PATH(p, uopt.branches[i].path, path)) RETURN(-ENAMETOOLONG); // NOTE: We should do: // Create the file with mode=0 first, otherwise we might create // a file as root + x-bit + suid bit set, which might be used for // security racing! int res = open(p, fi->flags, 0); if (res == -1) RETURN(-errno); set_owner(p); // no error check, since creating the file succeeded // NOW, that the file has the proper owner we may set the requested mode fchmod(res, mode); fi->fh = res; remove_hidden(path, i); DBG("fd = %" PRIx64 "\n", fi->fh); RETURN(0); } /** * flush may be called multiple times for an open file, this must not really * close the file. This is important if used on a network filesystem like NFS * which flush the data/metadata on close() */ static int unionfs_flush(const char *path, struct fuse_file_info *fi) { DBG("fd = %"PRIx64"\n", fi->fh); int fd = dup(fi->fh); if (fd == -1) { // What to do now? if (fsync(fi->fh) == -1) RETURN(-EIO); RETURN(-errno); } int res = close(fd); if (res == -1) RETURN(-errno); RETURN(0); } /** * Just a stub. This method is optional and can safely be left unimplemented */ static int unionfs_fsync(const char *path, int isdatasync, struct fuse_file_info *fi) { DBG("fd = %"PRIx64"\n", fi->fh); int res; if (isdatasync) { #if _POSIX_SYNCHRONIZED_IO + 0 > 0 res = fdatasync(fi->fh); #else res = fsync(fi->fh); #endif } else { res = fsync(fi->fh); } if (res == -1) RETURN(-errno); RETURN(0); } static int unionfs_getattr(const char *path, struct stat *stbuf) { DBG("%s\n", path); int i = find_rorw_branch(path); if (i == -1) RETURN(-errno); char p[PATHLEN_MAX]; if (BUILD_PATH(p, uopt.branches[i].path, path)) RETURN(-ENAMETOOLONG); int res = lstat(p, stbuf); if (res == -1) RETURN(-errno); /* This is a workaround for broken gnu find implementations. Actually, * n_links is not defined at all for directories by posix. However, it * seems to be common for filesystems to set it to one if the actual value * is unknown. Since nlink_t is unsigned and since these broken implementations * always substract 2 (for . and ..) this will cause an underflow, setting * it to max(nlink_t). */ if (S_ISDIR(stbuf->st_mode)) stbuf->st_nlink = 1; RETURN(0); } /** * init method * called before first access to the filesystem */ static void * unionfs_init(struct fuse_conn_info *conn) { // just to prevent the compiler complaining about unused variables (void) conn->max_readahead; // we only now (from unionfs_init) may go into the chroot, since otherwise // fuse_main() will fail to open /dev/fuse and to call mount if (uopt.chroot) { int res = chroot(uopt.chroot); if (res) { USYSLOG(LOG_WARNING, "Chdir to %s failed: %s ! Aborting!\n", uopt.chroot, strerror(errno)); exit(1); } } #ifdef FUSE_CAP_IOCTL_DIR if (conn->capable & FUSE_CAP_IOCTL_DIR) conn->want |= FUSE_CAP_IOCTL_DIR; #endif return NULL; } static int unionfs_link(const char *from, const char *to) { DBG("from %s to %s\n", from, to); // hardlinks do not work across different filesystems so we need a copy of from first int i = find_rw_branch_cow(from); if (i == -1) RETURN(-errno); int j = __find_rw_branch_cutlast(to, i); if (j == -1) RETURN(-errno); DBG("from branch: %d to branch: %d\n", i, j); char f[PATHLEN_MAX], t[PATHLEN_MAX]; if (BUILD_PATH(f, uopt.branches[i].path, from)) RETURN(-ENAMETOOLONG); if (BUILD_PATH(t, uopt.branches[j].path, to)) RETURN(-ENAMETOOLONG); int res = link(f, t); if (res == -1) RETURN(-errno); // no need for set_owner(), since owner and permissions are copied over by link() remove_hidden(to, i); // remove hide file (if any) RETURN(0); } static int unionfs_ioctl(const char *path, int cmd, void *arg, struct fuse_file_info *fi, unsigned int flags, void *data) { (void) path; (void) arg; // avoid compiler warning (void) fi; // avoid compiler warning fprintf(stderr, "Got ioctl: %d\n", cmd); if (flags & FUSE_IOCTL_COMPAT) return -ENOSYS; switch (cmd) { case UNIONFS_ONOFF_DEBUG: { int on_off = *((int *) data); // unionfs-ctl gives the opposite value, so !! bool setRes = set_debug_onoff(!!on_off); if (!setRes) return -EINVAL; return 0; } case UNIONFS_SET_DEBUG_FILE: { char *debug_path = (char *) data; set_debug_path(debug_path, _IOC_SIZE(cmd)); debug_init(); return 0; } default: USYSLOG(LOG_ERR, "Unknown ioctl: %d", cmd); return -EINVAL; break; } return 0; } /** * unionfs mkdir() implementation * * NOTE: Never delete whiteouts directories here, since this will just * make already hidden sub-branches visible again. */ static int unionfs_mkdir(const char *path, mode_t mode) { DBG("%s\n", path); int i = find_rw_branch_cutlast(path); if (i == -1) RETURN(-errno); char p[PATHLEN_MAX]; if (BUILD_PATH(p, uopt.branches[i].path, path)) RETURN(-ENAMETOOLONG); int res = mkdir(p, 0); if (res == -1) RETURN(-errno); set_owner(p); // no error check, since creating the file succeeded // NOW, that the file has the proper owner we may set the requested mode chmod(p, mode); RETURN(0); } static int unionfs_mknod(const char *path, mode_t mode, dev_t rdev) { DBG("%s\n", path); int i = find_rw_branch_cutlast(path); if (i == -1) RETURN(-errno); char p[PATHLEN_MAX]; if (BUILD_PATH(p, uopt.branches[i].path, path)) RETURN(-ENAMETOOLONG); int file_type = mode & S_IFMT; int file_perm = mode & (S_PROT_MASK); int res = -1; if ((file_type) == S_IFREG) { // under FreeBSD, only the super-user can create ordinary files using mknod // Actually this workaround should not be required any more // since we now have the unionfs_create() method // So can we remove it? USYSLOG (LOG_INFO, "deprecated mknod workaround, tell the unionfs-fuse authors if you see this!\n"); res = creat(p, 0); if (res > 0 && close(res) == -1) USYSLOG(LOG_WARNING, "Warning, cannot close file\n"); } else { res = mknod(p, file_type, rdev); } if (res == -1) RETURN(-errno); set_owner(p); // no error check, since creating the file succeeded // NOW, that the file has the proper owner we may set the requested mode chmod(p, file_perm); remove_hidden(path, i); RETURN(0); } static int unionfs_open(const char *path, struct fuse_file_info *fi) { DBG("%s\n", path); int i; if (fi->flags & (O_WRONLY | O_RDWR)) { i = find_rw_branch_cutlast(path); } else { i = find_rorw_branch(path); } if (i == -1) RETURN(-errno); char p[PATHLEN_MAX]; if (BUILD_PATH(p, uopt.branches[i].path, path)) RETURN(-ENAMETOOLONG); int fd = open(p, fi->flags); if (fd == -1) RETURN(-errno); if (fi->flags & (O_WRONLY | O_RDWR)) { // There might have been a hide file, but since we successfully // wrote to the real file, a hide file must not exist anymore remove_hidden(path, i); } // This makes exec() fail //fi->direct_io = 1; fi->fh = (unsigned long)fd; DBG("fd = %"PRIx64"\n", fi->fh); RETURN(0); } static int unionfs_read(const char *path, char *buf, size_t size, off_t offset, struct fuse_file_info *fi) { DBG("fd = %"PRIx64"\n", fi->fh); int res = pread(fi->fh, buf, size, offset); if (res == -1) RETURN(-errno); RETURN(res); } static int unionfs_readlink(const char *path, char *buf, size_t size) { DBG("%s\n", path); int i = find_rorw_branch(path); if (i == -1) RETURN(-errno); char p[PATHLEN_MAX]; if (BUILD_PATH(p, uopt.branches[i].path, path)) RETURN(-ENAMETOOLONG); int res = readlink(p, buf, size - 1); if (res == -1) RETURN(-errno); buf[res] = '\0'; RETURN(0); } static int unionfs_release(const char *path, struct fuse_file_info *fi) { DBG("fd = %"PRIx64"\n", fi->fh); int res = close(fi->fh); if (res == -1) RETURN(-errno); RETURN(0); } /** * unionfs rename function * TODO: If we rename a directory on a read-only branch, we need to copy over * all files to the renamed directory on the read-write branch. */ static int unionfs_rename(const char *from, const char *to) { DBG("from %s to %s\n", from, to); bool is_dir = false; // is 'from' a file or directory int j = find_rw_branch_cutlast(to); if (j == -1) RETURN(-errno); int i = find_rorw_branch(from); if (i == -1) RETURN(-errno); if (!uopt.branches[i].rw) { i = find_rw_branch_cow(from); if (i == -1) RETURN(-errno); } if (i != j) { USYSLOG(LOG_ERR, "%s: from and to are on different writable branches %d vs %d, which" "is not supported yet.\n", __func__, i, j); RETURN(-EXDEV); } char f[PATHLEN_MAX], t[PATHLEN_MAX]; if (BUILD_PATH(f, uopt.branches[i].path, from)) RETURN(-ENAMETOOLONG); if (BUILD_PATH(t, uopt.branches[i].path, to)) RETURN(-ENAMETOOLONG); filetype_t ftype = path_is_dir(f); if (ftype == NOT_EXISTING) RETURN(-ENOENT); else if (ftype == IS_DIR) is_dir = true; int res; if (!uopt.branches[i].rw) { // since original file is on a read-only branch, we copied the from file to a writable branch, // but since we will rename from, we also need to hide the from file on the read-only branch if (is_dir) res = hide_dir(from, i); else res = hide_file(from, i); if (res) RETURN(-errno); } res = rename(f, t); if (res == -1) { int err = errno; // unlink() might overwrite errno // if from was on a read-only branch we copied it, but now rename failed so we need to delete it if (!uopt.branches[i].rw) { if (unlink(f)) USYSLOG(LOG_ERR, "%s: cow of %s succeeded, but rename() failed and now " "also unlink() failed\n", __func__, from); if (remove_hidden(from, i)) USYSLOG(LOG_ERR, "%s: cow of %s succeeded, but rename() failed and now " "also removing the whiteout failed\n", __func__, from); } RETURN(-err); } if (uopt.branches[i].rw) { // A lower branch still *might* have a file called 'from', we need to delete this. // We only need to do this if we have been on a rw-branch, since we created // a whiteout for read-only branches anyway. if (is_dir) maybe_whiteout(from, i, WHITEOUT_DIR); else maybe_whiteout(from, i, WHITEOUT_FILE); } remove_hidden(to, i); // remove hide file (if any) RETURN(0); } /** * Wrapper function to convert the result of statfs() to statvfs() * libfuse uses statvfs, since it conforms to POSIX. Unfortunately, * glibc's statvfs parses /proc/mounts, which then results in reading * the filesystem itself again - which would result in a deadlock. * TODO: BSD/MacOSX */ static int statvfs_local(const char *path, struct statvfs *stbuf) { #ifdef linux /* glibc's statvfs walks /proc/mounts and stats entries found there * in order to extract their mount flags, which may deadlock if they * are mounted under the unionfs. As a result, we have to do this * ourselves. */ struct statfs stfs; int res = statfs(path, &stfs); if (res == -1) RETURN(res); memset(stbuf, 0, sizeof(*stbuf)); stbuf->f_bsize = stfs.f_bsize; if (stfs.f_frsize) { stbuf->f_frsize = stfs.f_frsize; } else { stbuf->f_frsize = stfs.f_bsize; } stbuf->f_blocks = stfs.f_blocks; stbuf->f_bfree = stfs.f_bfree; stbuf->f_bavail = stfs.f_bavail; stbuf->f_files = stfs.f_files; stbuf->f_ffree = stfs.f_ffree; stbuf->f_favail = stfs.f_ffree; /* nobody knows */ /* We don't worry about flags, exactly because this would * require reading /proc/mounts, and avoiding that and the * resulting deadlocks is exactly what we're trying to avoid * by doing this rather than using statvfs. */ stbuf->f_flag = 0; stbuf->f_namemax = stfs.f_namelen; RETURN(0); #else RETURN(statvfs(path, stbuf)); #endif } /** * statvs implementation * * Note: We do not set the fsid, as fuse ignores it anyway. */ static int unionfs_statfs(const char *path, struct statvfs *stbuf) { (void)path; DBG("%s\n", path); int first = 1; dev_t devno[uopt.nbranches]; int retVal = 0; int i = 0; for (i = 0; i < uopt.nbranches; i++) { struct statvfs stb; int res = statvfs_local(uopt.branches[i].path, &stb); if (res == -1) { retVal = -errno; break; } struct stat st; res = stat(uopt.branches[i].path, &st); if (res == -1) { retVal = -errno; break; } devno[i] = st.st_dev; if (first) { memcpy(stbuf, &stb, sizeof(*stbuf)); first = 0; stbuf->f_fsid = stb.f_fsid << 8; continue; } // Eliminate same devices int j = 0; for (j = 0; j < i; j ++) { if (st.st_dev == devno[j]) break; } if (j == i) { // Filesystem can have different block sizes -> normalize to first's block size double ratio = (double)stb.f_bsize / (double)stbuf->f_bsize; if (uopt.branches[i].rw) { stbuf->f_blocks += stb.f_blocks * ratio; stbuf->f_bfree += stb.f_bfree * ratio; stbuf->f_bavail += stb.f_bavail * ratio; stbuf->f_files += stb.f_files; stbuf->f_ffree += stb.f_ffree; stbuf->f_favail += stb.f_favail; } else if (!uopt.statfs_omit_ro) { // omitting the RO branches is not correct regarding // the block counts but it actually fixes the // percentage of free space. so, let the user decide. stbuf->f_blocks += stb.f_blocks * ratio; stbuf->f_files += stb.f_files; } if (!(stb.f_flag & ST_RDONLY)) stbuf->f_flag &= ~ST_RDONLY; if (!(stb.f_flag & ST_NOSUID)) stbuf->f_flag &= ~ST_NOSUID; if (stb.f_namemax < stbuf->f_namemax) stbuf->f_namemax = stb.f_namemax; } } RETURN(retVal); } static int unionfs_symlink(const char *from, const char *to) { DBG("from %s to %s\n", from, to); int i = find_rw_branch_cutlast(to); if (i == -1) RETURN(-errno); char t[PATHLEN_MAX]; if (BUILD_PATH(t, uopt.branches[i].path, to)) RETURN(-ENAMETOOLONG); int res = symlink(from, t); if (res == -1) RETURN(-errno); set_owner(t); // no error check, since creating the file succeeded remove_hidden(to, i); // remove hide file (if any) RETURN(0); } static int unionfs_truncate(const char *path, off_t size) { DBG("%s\n", path); int i = find_rw_branch_cow(path); if (i == -1) RETURN(-errno); char p[PATHLEN_MAX]; if (BUILD_PATH(p, uopt.branches[i].path, path)) RETURN(-ENAMETOOLONG); int res = truncate(p, size); if (res == -1) RETURN(-errno); RETURN(0); } static int unionfs_utimens(const char *path, const struct timespec ts[2]) { DBG("%s\n", path); int i = find_rw_branch_cow(path); if (i == -1) RETURN(-errno); char p[PATHLEN_MAX]; if (BUILD_PATH(p, uopt.branches[i].path, path)) RETURN(-ENAMETOOLONG); #ifdef UNIONFS_HAVE_AT int res = utimensat(0, p, ts, AT_SYMLINK_NOFOLLOW); #else struct timeval tv[2]; tv[0].tv_sec = ts[0].tv_sec; tv[0].tv_usec = ts[0].tv_nsec * 1000; tv[1].tv_sec = ts[1].tv_sec; tv[1].tv_usec = ts[1].tv_nsec * 1000; int res = utimes(p, tv); #endif if (res == -1) RETURN(-errno); RETURN(0); } static int unionfs_write(const char *path, const char *buf, size_t size, off_t offset, struct fuse_file_info *fi) { (void)path; DBG("fd = %"PRIx64"\n", fi->fh); int res = pwrite(fi->fh, buf, size, offset); if (res == -1) RETURN(-errno); RETURN(res); } #ifdef HAVE_XATTR #if __APPLE__ static int unionfs_getxattr(const char *path, const char *name, char *value, size_t size, uint32_t position) { #else static int unionfs_getxattr(const char *path, const char *name, char *value, size_t size) { #endif DBG("%s\n", path); int i = find_rorw_branch(path); if (i == -1) RETURN(-errno); char p[PATHLEN_MAX]; if (BUILD_PATH(p, uopt.branches[i].path, path)) RETURN(-ENAMETOOLONG); #if __APPLE__ int res = getxattr(p, name, value, size, position, XATTR_NOFOLLOW); #else int res = lgetxattr(p, name, value, size); #endif if (res == -1) RETURN(-errno); RETURN(res); } static int unionfs_listxattr(const char *path, char *list, size_t size) { DBG("%s\n", path); int i = find_rorw_branch(path); if (i == -1) RETURN(-errno); char p[PATHLEN_MAX]; if (BUILD_PATH(p, uopt.branches[i].path, path)) RETURN(-ENAMETOOLONG); #if __APPLE__ int res = listxattr(p, list, size, XATTR_NOFOLLOW); #else int res = llistxattr(p, list, size); #endif if (res == -1) RETURN(-errno); RETURN(res); } static int unionfs_removexattr(const char *path, const char *name) { DBG("%s\n", path); int i = find_rw_branch_cow(path); if (i == -1) RETURN(-errno); char p[PATHLEN_MAX]; if (BUILD_PATH(p, uopt.branches[i].path, path)) RETURN(-ENAMETOOLONG); #if __APPLE__ int res = removexattr(p, name, XATTR_NOFOLLOW); #else int res = lremovexattr(p, name); #endif if (res == -1) RETURN(-errno); RETURN(res); } #if __APPLE__ static int unionfs_setxattr(const char *path, const char *name, const char *value, size_t size, int flags, uint32_t position) { #else static int unionfs_setxattr(const char *path, const char *name, const char *value, size_t size, int flags) { #endif DBG("%s\n", path); int i = find_rw_branch_cow(path); if (i == -1) RETURN(-errno); char p[PATHLEN_MAX]; if (BUILD_PATH(p, uopt.branches[i].path, path)) RETURN(-ENAMETOOLONG); #if __APPLE__ int res = setxattr(p, name, value, size, position, flags | XATTR_NOFOLLOW); #else int res = lsetxattr(p, name, value, size, flags); #endif if (res == -1) RETURN(-errno); RETURN(res); } #endif // HAVE_XATTR static struct fuse_operations unionfs_oper = { .chmod = unionfs_chmod, .chown = unionfs_chown, .create = unionfs_create, .flush = unionfs_flush, .fsync = unionfs_fsync, .getattr = unionfs_getattr, .init = unionfs_init, .ioctl = unionfs_ioctl, .link = unionfs_link, .mkdir = unionfs_mkdir, .mknod = unionfs_mknod, .open = unionfs_open, .read = unionfs_read, .readlink = unionfs_readlink, .readdir = unionfs_readdir, .release = unionfs_release, .rename = unionfs_rename, .rmdir = unionfs_rmdir, .statfs = unionfs_statfs, .symlink = unionfs_symlink, .truncate = unionfs_truncate, .unlink = unionfs_unlink, .utimens = unionfs_utimens, .write = unionfs_write, #ifdef HAVE_XATTR .getxattr = unionfs_getxattr, .listxattr = unionfs_listxattr, .removexattr = unionfs_removexattr, .setxattr = unionfs_setxattr, #endif }; int main(int argc, char *argv[]) { struct fuse_args args = FUSE_ARGS_INIT(argc, argv); init_syslog(); uopt_init(); if (fuse_opt_parse(&args, NULL, unionfs_opts, unionfs_opt_proc) == -1) RETURN(1); if (uopt.debug) debug_init(); if (!uopt.doexit) { if (uopt.nbranches == 0) { printf("You need to specify at least one branch!\n"); RETURN(1); } } // enable fuse permission checks, we need to set this, even we we are // not root, since we don't have our own access() function int uid = getuid(); int gid = getgid(); bool default_permissions = true; if (uid != 0 && gid != 0 && uopt.relaxed_permissions) { default_permissions = false; } else if (uopt.relaxed_permissions) { // protec the user of a very critical security issue fprintf(stderr, "Relaxed permissions disallowed for root!\n"); exit(1); } if (default_permissions) { if (fuse_opt_add_arg(&args, "-odefault_permissions")) { fprintf(stderr, "Severe failure, can't enable permssion checks, aborting!\n"); exit(1); } } unionfs_post_opts(); #ifdef FUSE_CAP_BIG_WRITES /* libfuse > 0.8 supports large IO, also for reads, to increase performance * We support any IO sizes, so lets enable that option */ if (fuse_opt_add_arg(&args, "-obig_writes")) { fprintf(stderr, "Failed to enable big writes!\n"); exit(1); } #endif umask(0); int res = fuse_main(args.argc, args.argv, &unionfs_oper, NULL); RETURN(uopt.doexit ? uopt.retval : res); } unionfs-fuse-1.0/src/unionfs.h000066400000000000000000000013331245544002400164140ustar00rootroot00000000000000/* * License: BSD-style license * Copyright: Radek Podgorny , * Bernd Schubert */ #ifndef UNIONFS_H #define UNIONFS_H #define PATHLEN_MAX 1024 #define HIDETAG "_HIDDEN~" #define METANAME ".unionfs-fuse" #define METADIR (METANAME "/") // string concetanation! // fuse meta files, we might want to hide those #define FUSE_META_FILE ".fuse_hidden" #define FUSE_META_LENGTH 12 // file access protection mask #define S_PROT_MASK (S_ISUID| S_ISGID | S_ISVTX | S_IRWXU | S_IRWXG | S_IRWXO) typedef struct { char *path; int path_len; // strlen(path) int fd; // used to prevent accidental umounts of path unsigned char rw; // the writable flag } branch_entry_t; #endif unionfs-fuse-1.0/src/unionfsctl.c000066400000000000000000000047461245544002400171250ustar00rootroot00000000000000#include #include #include #include #include #include #include #include "uioctl.h" static void print_help(char* progname) { fprintf(stderr, "Usage:\n"); fprintf(stderr, " %s [] [file-path] \n", progname); fprintf(stderr, "\n"); fprintf(stderr, " List of parameters\n"); fprintf(stderr, " -p \n"); fprintf(stderr, " -d \n"); fprintf(stderr, " Enable or disable debugging.\n"); fprintf(stderr, "\n"); fprintf(stderr, "Example: "); fprintf(stderr, " %s -p /tmp/unionfs-fuse.log -d on /mnt/unionfs/union\n", progname); fprintf(stderr, "\n"); fprintf(stderr, "\n"); } int main(int argc, char **argv) { char *progname = basename(argv[0]); if (argc < 3) { print_help(progname); exit(1); } // file_name is the last argument const char *file_name = argv[argc - 1]; const int fd = open(file_name, O_RDONLY ); if (fd == -1) { fprintf(stderr, "Failed to open file: %s: %s\n\n", file_name, strerror(errno) ); exit(1); } argc--; int opt; const char* argument_param; int debug_on_off; int ioctl_res; while ((opt = getopt(argc, argv, "d:p:")) != -1) { switch (opt) { case 'p': argument_param = optarg; if (strlen(argument_param) < 1) { fprintf(stderr, "Not a valid debug path given!\n"); print_help(progname); exit(1); } if (strlen(argument_param) > PATHLEN_MAX) { fprintf(stderr, "Debug path too long!\n"); exit(1); } ioctl_res = ioctl(fd, UNIONFS_SET_DEBUG_FILE, argument_param); if (ioctl_res == -1) { fprintf(stderr, "debug-file ioctl failed: %s\n", strerror(errno) ); exit(1); } break; case 'd': argument_param = optarg; if (strlen(argument_param) < 1) { fprintf(stderr, "invalid \"-d %s\" option given, valid is" "-d on/off!\n", argument_param); exit(1); } if (strncmp(argument_param, "on", 2) == 0) debug_on_off = 1; else if ((strncmp(argument_param, "off", 3) == 0) ) debug_on_off = 0; else { fprintf(stderr, "invalid \"-d %s\" option given, valid is " "\"-d on/off\"!\n", argument_param); exit(1); } ioctl_res = ioctl(fd, UNIONFS_ONOFF_DEBUG, &debug_on_off); if (ioctl_res == -1) { fprintf(stderr, "debug-on/off ioctl failed: %s\n", strerror(errno) ); exit(1); } break; default: fprintf(stderr, "Unhandled option %c given.\n", opt); break; } } return 0; } unionfs-fuse-1.0/src/unlink.c000066400000000000000000000042271245544002400162330ustar00rootroot00000000000000/* * C Implementation: unlink * * Description: unionfs unlink() call * If the file to unlink exists in a lower branch, create a * file with a tag informing other functions that the file * is hidden. * * original implementation by Radek Podgorny * * License: BSD-style license * Copyright: Radek Podgorny , * Bernd Schubert * */ #include #include #include #include #include #include #include #include "unionfs.h" #include "opts.h" #include "debug.h" #include "cow.h" #include "general.h" #include "findbranch.h" #include "string.h" /** * If the branch that has the file to be unlinked is in read-only mode, * we create a file with a HIDE tag in an upper level branch. * To other fuse functions this tag means, not to expose the * lower level file. */ static int unlink_ro(const char *path, int branch_ro) { DBG("%s\n", path); // find a writable branch above branch_ro int branch_rw = find_lowest_rw_branch(branch_ro); if (branch_rw < 0) RETURN(EACCES); if (hide_file(path, branch_rw) == -1) { // creating the file with the hide tag failed // TODO: open() error messages are not optimal on unlink() RETURN(errno); } RETURN(0); } /** * If the branch that has the file to be unlinked is in read-write mode, * we can really delete the file. */ static int unlink_rw(const char *path, int branch_rw) { DBG("%s\n", path); char p[PATHLEN_MAX]; if (BUILD_PATH(p, uopt.branches[branch_rw].path, path)) RETURN(ENAMETOOLONG); int res = unlink(p); if (res == -1) RETURN(errno); RETURN(0); } /** * unlink() call */ int unionfs_unlink(const char *path) { DBG("%s\n", path); int i = find_rorw_branch(path); if (i == -1) RETURN(errno); int res; if (!uopt.branches[i].rw) { // read-only branch if (!uopt.cow_enabled) { res = EROFS; } else { res = unlink_ro(path, i); } } else { // read-write branch res = unlink_rw(path, i); if (res == 0) { // No need to be root, whiteouts are created as root! maybe_whiteout(path, i, WHITEOUT_FILE); } } RETURN(-res); } unionfs-fuse-1.0/src/unlink.h000066400000000000000000000003321245544002400162310ustar00rootroot00000000000000/* * License: BSD-style license * Copyright: Radek Podgorny , * Bernd Schubert */ #ifndef UNLINK_H #define UNLINK_H int unionfs_unlink(const char *path); #endif unionfs-fuse-1.0/src/usyslog.c000066400000000000000000000170251245544002400164400ustar00rootroot00000000000000/* * License: BSD-style license * Copyright: Bernd Schubert * * Details: * Log files might be located on our own filesystem. If we then want to log * a message to syslog, we would need to log to ourself, which easily ends up * in a deadlock. Initializing openlog() using the flags * LOG_NDELAY | LOG_NOWAIT should prevent that, but real live has shown that * this does not work reliable and systems 'crashed' just because we * tried to log a harmless message. * So this file introduces a syslog thread and a syslog buffer. usyslog() * calls write without a risk to deadlock into the syslog buffer (chained * list) and then the seperate syslog_thread call syslog(). That way our * our filesystem thread(s) cannot stall from syslog() calls. */ #include #include #include #include #include #if __APPLE__ #include #else #include #endif #include #include #include "usyslog.h" #include "debug.h" static ulogs_t *free_head, *free_bottom; // free chained list log entries static ulogs_t *used_head = NULL, *used_bottom = NULL; //used chained list pointers static pthread_mutex_t list_lock; // locks the entire chained list static pthread_cond_t cond_message; // used to wake up the syslog thread // Only used for debugging, protected by list_lock static int free_entries; static int used_entries = 0; //#define USYSLOG_DEBUG #ifdef USYSLOG_DEBUG static void verify_lists() { pthread_mutex_lock(&list_lock); ulogs_t *entry = free_head; int free_count = -1; bool first_free = true; while (entry) { if (first_free) { first_free = false; free_count = 1; } else free_count++; entry = entry->next; } if (free_count != free_entries && free_entries != 0) DBG("usyslog list error detected: number of free entries inconsistent!" " %d vs. %d", free_count, free_entries); entry = used_head; int used_count = -1; bool first_used = true; while (entry) { if (first_used) { first_used = false; used_count = 1; } else used_count++; entry = entry->next; } if (used_count != used_entries && used_entries != 0) DBG("usyslog list error detected: number of used entries inconsistent!" " (used: %d vs. %d) (free: %d vs. %d) \n", used_count, used_entries, free_count, free_entries); pthread_mutex_unlock(&list_lock); } #else #define verify_lists() #endif /** * Walks the chained used-list and calls syslog() */ static void do_syslog(void) { pthread_mutex_lock(&list_lock); // we MUST ensure not to keep that forever ulogs_t *log_entry = used_head; while (log_entry) { pthread_mutex_t *entry_lock = &log_entry->lock; int res = pthread_mutex_trylock(entry_lock); if (res) { if (res != EBUSY) DBG("Entirely unexpected locking error %s\n", strerror(res)); // If something goes wrong with the log_entry we do not // block the critical list_lock forever! // EBUSY might come up rarely, if we race with usyslog() pthread_mutex_unlock(&list_lock); sleep(1); pthread_mutex_lock(&list_lock); log_entry = used_head; continue; } pthread_mutex_unlock(&list_lock); // This syslog call and so this lock might block, so be // carefull on using locks! The filesystem IO thread // *MUST* only try to lock it using pthread_mutex_trylock() syslog(log_entry->priority, "%s", log_entry->message); log_entry->used = false; // NOTE: The list is only locked now, after syslog() succeeded! pthread_mutex_lock(&list_lock); ulogs_t *next_entry = log_entry->next; // just to save the pointer used_head = log_entry->next; if (!used_head) used_bottom = NULL; // no used entries left if (free_bottom) free_bottom->next = log_entry; free_bottom = log_entry; free_bottom->next = NULL; if (!free_head) free_head = log_entry; free_entries++; used_entries--; pthread_mutex_unlock(&list_lock); // unlock ist ASAP log_entry = next_entry; pthread_mutex_unlock(entry_lock); } verify_lists(); } /** * syslog backgroung thread that tries to to empty the syslog buffer */ static void * syslog_thread(void *arg) { // FIXME: What is a better way to prevent a compiler warning about // unused variable 'arg' int tinfo = *((int *) arg); if (tinfo == 0 && tinfo == 1) printf("Starting thread %d", tinfo); pthread_mutex_t sleep_mutex; pthread_mutex_init(&sleep_mutex, NULL); pthread_mutex_lock(&sleep_mutex); while (1) { pthread_cond_wait(&cond_message, &sleep_mutex); do_syslog(); } return NULL; } /** * usyslog - function to be called if something shall be logged to syslog */ void usyslog(int priority, const char *format, ...) { int res; ulogs_t *log; // Lock the entire list first, which means the syslog thread MUST NOT // lock it if there is any chance it might be locked forever. pthread_mutex_lock(&list_lock); // Some sanity checks. If we fail here, we will leak a log entry, // but will not lock up. if (free_head == NULL) { DBG("All syslog entries already busy\n"); pthread_mutex_unlock(&list_lock); return; } log = free_head; free_head = log->next; res = pthread_mutex_trylock(&log->lock); if (res == EBUSY) { // huh, that never should happen! DBG("Critical log error, log entry is BUSY, but should not\n"); pthread_mutex_unlock(&list_lock); return; } else if (res) { // huh, that never should happen either! DBG("Never should happen, can get lock: %s\n", strerror(res)); pthread_mutex_unlock(&list_lock); return; } if (log->used) { // huh, that never should happen either! DBG("Never should happen, entry is busy, but should not!\n"); pthread_mutex_unlock(&log->lock); pthread_mutex_unlock(&list_lock); return; } if (!used_head) used_head = used_bottom = log; else { used_bottom->next = log; used_bottom = log; } if (log->next) { // from free_list to end of used_list, so next is NULL now log->next = NULL; } else { // so the last entry in free_list free_bottom = NULL; } free_entries--; used_entries++; // Everything below is log entry related, so we can free the list_lock pthread_mutex_unlock(&list_lock); va_list ap; va_start(ap, format); vsnprintf(log->message, MAX_MSG_SIZE, format, ap); log->priority = priority; log->used = 1; pthread_mutex_unlock(&log->lock); pthread_cond_signal(&cond_message); // wake up the syslog thread } /** * Initialize syslogs */ void init_syslog(void) { openlog("unionfs-fuse: ", LOG_CONS | LOG_NDELAY | LOG_NOWAIT | LOG_PID, LOG_DAEMON); pthread_mutex_init(&list_lock, NULL); pthread_cond_init(&cond_message, NULL); pthread_t thread; pthread_attr_t attr; int t_arg = 0; // thread argument, not required for us int i; ulogs_t *log, *last = NULL; for (i = 0; i < MAX_SYSLOG_MESSAGES; i++) { log = malloc(sizeof(ulogs_t)); if (log == NULL) { fprintf(stderr, "\nLog initialization failed: %s\n", strerror(errno)); fprintf(stderr, "Aborting!\n"); // Still initialazation phase, we can abort. exit (1); } log->used = false; pthread_mutex_init(&log->lock, NULL); if (last) { last->next = log; } else { // so the very first entry free_head = log; } last = log; } last->next = NULL; free_bottom = last; free_entries = MAX_SYSLOG_MESSAGES; pthread_attr_init(&attr); pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); int res = pthread_create(&thread, &attr, syslog_thread, (void *) &t_arg); if (res != 0) { fprintf(stderr, "Failed to initialize the syslog threads: %s\n", strerror(res)); exit(1); } } unionfs-fuse-1.0/src/usyslog.h000066400000000000000000000014751245544002400164470ustar00rootroot00000000000000/* * License: BSD-style license * Copyright: Bernd Schubert */ #include #include #define MAX_SYSLOG_MESSAGES 32 // max number of buffered syslog messages #define MAX_MSG_SIZE 256 // max string length for syslog messages /* chained buffer list of syslog entries */ typedef struct ulogs { int priority; // first argument for syslog() char message[MAX_MSG_SIZE]; // 2nd argument for syslog() bool used; // is this entry used? pthread_mutex_t lock; // lock a single entry struct ulogs *next; // pointer to the next entry } ulogs_t; void init_syslog(void); void usyslog(int priority, const char *format, ...); #define USYSLOG(priority, format, ...) \ do { \ DBG(format, ##__VA_ARGS__); \ usyslog(priority, format, ##__VA_ARGS__); \ } while (0); unionfs-fuse-1.0/src/version.h000066400000000000000000000002051245544002400164150ustar00rootroot00000000000000/* * License: BSD-style license * Copyright: Radek Podgorny , */ #ifndef _VERSION_H #define VERSION "1.0" #endif unionfs-fuse-1.0/test.py000077500000000000000000000141631245544002400153340ustar00rootroot00000000000000#!/usr/bin/python3 import unittest import subprocess import os import shutil import time def call(cmd): return subprocess.check_output(cmd, shell=True) #enddef def write_to_file(fn, data): with open(fn, 'w') as f: f.write(data) #endwith #enddef def read_from_file(fn): with open(fn, 'r') as f: return f.read() #endwith #enddef class Common: def setUp(self): self._dirs = ['ro1', 'ro2', 'rw1', 'rw2'] for d in self._dirs: os.mkdir(d) write_to_file('%s/%s_file' % (d, d), d) write_to_file('%s/common_file' % d, d) #endfor write_to_file('ro1/ro_common_file', 'ro1') write_to_file('ro2/ro_common_file', 'ro2') write_to_file('rw1/rw_common_file', 'rw1') write_to_file('rw2/rw_common_file', 'rw2') os.mkdir('union') #enddef def tearDown(self): # TODO: investigate the following # the sleep seems to be needed for some users or else the umount fails # anyway, everything works fine on my system, so why wait? ;-) # if it fails for someone, let's find the race and fix it! #time.sleep(1) call('fusermount -u union') for d in self._dirs: shutil.rmtree(d) #endfor shutil.rmtree('union') #endef #endclass class UnionFS_RO_RO_TestCase(Common, unittest.TestCase): def setUp(self): super().setUp() call('src/unionfs -o cow ro1=ro:ro2=ro union') #enddef def test_listing(self): lst = ['ro1_file', 'ro2_file', 'ro_common_file', 'common_file'] self.assertEqual(set(lst), set(os.listdir('union'))) #enddef def test_overlay_order(self): self.assertEqual(read_from_file('union/common_file'), 'ro1') #enddef def test_write(self): with self.assertRaises(PermissionError): write_to_file('union/ro1_file', 'something') #endwith with self.assertRaises(PermissionError): write_to_file('union/ro2_file', 'something') #endwith with self.assertRaises(PermissionError): write_to_file('union/ro_common_file', 'something') #endwith with self.assertRaises(PermissionError): write_to_file('union/common_file', 'something') #endwith #enddef def test_delete(self): with self.assertRaises(PermissionError): os.remove('union/ro1_file') #endwith with self.assertRaises(PermissionError): os.remove('union/ro2_file') #endwith with self.assertRaises(PermissionError): os.remove('union/ro_common_file') #endwith with self.assertRaises(PermissionError): os.remove('union/common_file') #endwith #enddef #endclass class UnionFS_RW_RO_TestCase(Common, unittest.TestCase): def setUp(self): super().setUp() call('src/unionfs rw1=rw:ro1=ro union') #enddef def test_listing(self): lst = ['ro1_file', 'rw1_file', 'ro_common_file', 'rw_common_file', 'common_file'] self.assertEqual(set(lst), set(os.listdir('union'))) #enddef def test_delete(self): # TODO: shouldn't this be PermissionError? with self.assertRaisesRegex(OSError, '[Errno 30]'): os.remove('union/ro1_file') #endwith #enddef def test_write(self): # TODO: shouldn't this be the same as above? with self.assertRaises(PermissionError): write_to_file('union/ro1_file', 'something') #endwith #enddef #endclass class UnionFS_RW_RO_COW_TestCase(Common, unittest.TestCase): def setUp(self): super().setUp() call('src/unionfs -o cow rw1=rw:ro1=ro union') #enddef def test_listing(self): lst = ['ro1_file', 'rw1_file', 'ro_common_file', 'rw_common_file', 'common_file'] self.assertEqual(set(lst), set(os.listdir('union'))) #enddef def test_whiteout(self): os.remove('union/ro1_file') self.assertNotIn('ro1_file', os.listdir('union')) self.assertIn('ro1_file', os.listdir('ro1')) #enddef def test_cow(self): write_to_file('union/ro1_file', 'something') self.assertEqual(read_from_file('union/ro1_file'), 'something') self.assertEqual(read_from_file('ro1/ro1_file'), 'ro1') self.assertEqual(read_from_file('rw1/ro1_file'), 'something') #enddef def test_cow_and_whiteout(self): write_to_file('union/ro1_file', 'something') os.remove('union/ro1_file') self.assertFalse(os.path.isfile('union/ro_file')) self.assertFalse(os.path.isfile('rw1/ro_file')) self.assertEqual(read_from_file('ro1/ro1_file'), 'ro1') #enddef #endclass class UnionFS_RO_RW_TestCase(Common, unittest.TestCase): def setUp(self): super().setUp() call('src/unionfs ro1=ro:rw1=rw union') #enddef def test_listing(self): lst = ['ro1_file', 'rw1_file', 'ro_common_file', 'rw_common_file', 'common_file'] self.assertEqual(set(lst), set(os.listdir('union'))) #enddef def test_delete(self): # TODO: shouldn't this be a PermissionError? with self.assertRaisesRegex(OSError, '[Errno 30]'): os.remove('union/ro1_file') #endwith #enddef def test_write(self): # TODO: shouldn't this be the same error as for the delete? with self.assertRaises(PermissionError): write_to_file('union/ro1_file', 'something') #endwith #enddef #endclass class UnionFS_RO_RW_COW_TestCase(Common, unittest.TestCase): def setUp(self): super().setUp() call('src/unionfs -o cow ro1=ro:rw1=rw union') #enddef def test_listing(self): lst = ['ro1_file', 'rw1_file', 'ro_common_file', 'rw_common_file', 'common_file'] self.assertEqual(set(lst), set(os.listdir('union'))) #enddef def test_delete(self): # TODO: shouldn't this be and OSError (see above) with self.assertRaises(PermissionError): os.remove('union/ro1_file') #endwith #enddef def test_write(self): with self.assertRaises(PermissionError): write_to_file('union/ro1_file', 'something') #endwith #enddef #endclass class IOCTL_TestCase(Common, unittest.TestCase): def setUp(self): super().setUp() call('src/unionfs rw1=rw:ro1=ro union') #enddef def test_debug(self): # TODO: this is not safe, use some temporary filename or something if os.path.exists('/tmp/test_debug.log'): os.remove('/tmp/test_debug.log') #endif call('src/unionfsctl -p /tmp/test_debug.log -d on union') self.assertTrue(os.path.isfile('/tmp/test_debug.log')) os.remove('/tmp/test_debug.log') #enddef def test_wrong_args(self): # TODO: also check the return code? with self.assertRaises(subprocess.CalledProcessError): call('src/unionfsctl -xxxx 2>/dev/null') #endwith #enddef #endclass if __name__ == '__main__': unittest.main() #endif unionfs-fuse-1.0/test.sh000077500000000000000000000035511245544002400153150ustar00rootroot00000000000000#!/bin/bash set -v set -e rm -rf original union working-copy mkdir original union working-copy original/play-dir original/del-dir echo v1 > original/file echo v1 > original/play-with-me echo v1 > original/delete-me src/unionfs -d -o cow working-copy=rw:original=ro union >unionfs.log 2>&1 & trap 'if [ "$(ls union)" ]; then fusermount -u union; fi; rm -rf union original working-copy' EXIT sleep 1 [ "$(cat union/file)" = "v1" ] echo "v2" > union/file [ "$(cat union/file)" = "v2" ] echo "v2" > union/play-with-me [ "$(cat union/play-with-me)" = "v2" ] [ -f union/play-with-me ] rm union/play-with-me [ ! -f union/play-with-me ] [ -f union/delete-me ] rm union/delete-me [ ! -f union/delete-me ] [ "$(ls union/play-dir)" = "" ] echo "fool" > union/play-dir/foo [ "$(ls union/play-dir)" = "foo" ] rm union/play-dir/foo [ "$(ls union/play-dir)" = "" ] [ -d union/play-dir ] rmdir union/play-dir [ ! -d union/play-dir ] [ -d union/del-dir ] rmdir union/del-dir [ ! -d union/del-dir ] ! echo v1 > union/del-dir/foo [ ! -d union/del-dir ] mkdir union/del-dir [ ! -f union/del-dir/foo ] echo v1 > union/del-dir/foo [ -f union/del-dir/foo ] rm union/del-dir/foo [ -d union/del-dir ] rmdir union/del-dir [ ! -d union/del-dir ] # rmdir() test set +e set +v rc=0 mkdir original/testdir touch original/testdir/testfile mkdir working-copy/testdir rmdir union/testdir 2>/dev/null if [ $? -eq 0 ]; then echo "rmdir succeeded, although it must not" rc=$(($rc + $?)) fi rm union/testdir/testfile rc=$(($rc + $?)) rmdir union/testdir/ rc=$(($rc + $?)) if [ $rc -ne 0 ]; then echo "rmdir test failed" exit 1 else echo "rmdir test passed" fi set -e fusermount -u union [ "$(cat original/file)" = "v1" ] [ "$(cat original/play-with-me)" = "v1" ] [ "$(cat original/delete-me)" = "v1" ] [ -d original/play-dir ] [ -d original/del-dir ] [ "$(cat working-copy/file)" = "v2" ] echo "ALL TEST PASSED"