aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMartin Pärtel <martin.partel@gmail.com>2011-07-08 19:09:07 +0300
committerMartin Pärtel <martin.partel@gmail.com>2011-07-08 19:09:07 +0300
commit2135ad723dce9654f1844ec4f76f06c9e240be98 (patch)
tree31b94696aa7e3da71b823e8d856febb7ee245e5c
downloadbindfs-2135ad723dce9654f1844ec4f76f06c9e240be98.tar.gz
Initial commit to git.
-rw-r--r--.gitignore32
-rw-r--r--COPYING340
-rw-r--r--ChangeLog135
-rw-r--r--Makefile.am6
-rw-r--r--README41
-rw-r--r--TODO22
-rwxr-xr-xautogen.sh57
-rw-r--r--configure.ac42
-rw-r--r--src/Makefile.am11
-rw-r--r--src/bindfs.1338
-rw-r--r--src/bindfs.c1329
-rw-r--r--src/debug.h33
-rw-r--r--src/misc.c83
-rw-r--r--src/misc.h40
-rw-r--r--src/permchain.c325
-rw-r--r--src/permchain.h55
-rw-r--r--src/userinfo.c177
-rw-r--r--src/userinfo.h39
-rw-r--r--tests/Makefile.am2
-rwxr-xr-xtests/common.rb154
-rwxr-xr-xtests/test_bindfs.rb206
-rwxr-xr-xtests/test_concurrent.rb45
22 files changed, 3512 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..c1cf07c
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,32 @@
+# Autotools stuff
+Makefile.in
+Makefile
+
+/autom4te.cache
+/aclocal.m4
+/compile
+/configure
+/depcomp
+/install-sh
+/missing
+
+/config.log
+/config.sub
+/config.guess
+/config.status
+/config.h.in
+/config.h
+/libtool
+/ltmain.sh
+/stamp-h1
+
+# C stuff
+
+*.o
+.libs
+.deps
+
+# Products
+
+src/bindfs
+
diff --git a/COPYING b/COPYING
new file mode 100644
index 0000000..d60c31a
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,340 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+ 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Library General Public License instead.) You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must show them these terms so they know their
+rights.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+ 2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of Sections
+ 1 and 2 above on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable. However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+ 5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+ Gnomovision version 69, Copyright (C) year name of author
+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+ `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+ <signature of Ty Coon>, 1 April 1989
+ Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs. If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library. If this is what you want to do, use the GNU Library General
+Public License instead of this License.
diff --git a/ChangeLog b/ChangeLog
new file mode 100644
index 0000000..eabc0e3
--- /dev/null
+++ b/ChangeLog
@@ -0,0 +1,135 @@
+2010-08-07 Martin Pärtel <martin dot partel at gmail dot com>
+
+ * Improved --help and manpage.
+ * Disabled FUSE attribute cache when using mirroring to avoid
+ caching the owner of files when observed by a mirrored user.
+ * Added a testcase for the above.
+ * Released 1.9
+
+2010-01-17 Martin Pärtel <martin dot partel at gmail dot com>
+
+ * Added options to control the behavior of chown and chgrp.
+ * Released 1.8.4
+
+2009-03-28 Martin Pärtel <martin dot partel at gmail dot com>
+
+ * Added --ctime-from-mtime. Contributed by Shez.
+ * Added --chmod-allow-x.
+ * Released 1.8.3
+
+2008-12-14 Martin Pärtel <martin dot partel at gmail dot com>
+
+ * Converted ChangeLog to UTF-8.
+
+2008-12-13 Martin Pärtel <martin dot partel at gmail dot com>
+
+ * Specified that the license is GPL v2 or later in all source files
+ and in the README file.
+ * Released 1.8.2 with no functional changes.
+
+2008-12-12 Martin Pärtel <martin dot partel at gmail dot com>
+
+ * Made xattr-rw the default instead of xattr-ro,
+ which returned a "permission denied" that could mislead some programs.
+ * Released 1.8.1
+
+2008-08-17 Martin Pärtel <martin dot partel at gmail dot com>
+
+ * Fixed segfault in option parsing on platforms where
+ sizeof(int) != sizeof(long), such as amd64.
+ * Released 1.8
+
+2008-07-08 Martin Pärtel <martin dot partel at gmail dot com>
+
+ * Symlinks to absolute paths didn't work. Now they do.
+ Reported by rpfuller. Thanks!
+ * Ownership of symlinks weren't set. Now they are.
+ Again, reported by rpfuller. Thanks again!
+ * Released 1.7
+
+2008-06-26 Martin Pärtel <martin dot partel at gmail dot com>
+
+ * --create-as-* and --create-for-* weren't applied for mknod().
+ Bug report and patch by rpfuller. Thanks!
+ * Released 1.6.2
+
+2008-06-25 Martin Pärtel <martin dot partel at gmail dot com>
+
+ * Added copyright messages to each source file.
+ * Escaped man-page dashes, since unescaped dashes are treated as
+ hyphens.
+
+2008-06-19 Martin Pärtel <martin dot partel at gmail dot com>
+
+ * Fixed missing '=' signs in the man-page.
+
+2008-05-14 Martin Pärtel <martin dot partel at gmail dot com>
+
+ * If the mount source and destination directories are the same
+ then we no longer require that the directory be empty.
+ (-ononempty is added implicitly)
+ * Released 1.6.1
+
+2008-05-10 Martin Pärtel <martin dot partel at gmail dot com>
+
+ * Added --create-with-perms.
+ * Added a little automated test suite.
+ * Moved the project to code.google.com.
+ * Released 1.6
+
+2008-01-26 Martin Pärtel <martin dot partel at gmail dot com>
+
+ * Fixed an embarrassing segfault while parsing --mirror arguments.
+ Thanks to Stefan Kost for reporting it!
+ * Released 1.5
+
+2007-12-31 Martin Pärtel <martin dot partel at gmail dot com>
+
+ * Made compatible with Mac OS X with the help of Theocharis Athanasakis.
+ * Released 1.4.2
+
+2007-11-09 Martin Pärtel <martin dot partel at gmail dot com>
+
+ * Fixed a bug in userinfo.c that could prevent using numeric
+ user or group IDs when /etc/passwd or /etc/group have long records.
+ * Released 1.4.1
+
+2007-10-31 Martin Pärtel <martin dot partel at gmail dot com>
+
+ * Applied another patch from Joel Daniels to fix a bug that
+ occurred when /etc/passwd or /etc/group had long records.
+ Thanks!
+ * Released 1.4
+
+2007-07-17 Martin Pärtel <martin dot partel at gmail dot com>
+
+ * Renamed the new options added by Joel
+ to --create-for-user and --create-for-group.
+ * Made it an error to use --create-as-user as non-root.
+ * Released 1.3
+
+2007-07-17 Joel Daniels <jdaniel4 at uiuc dot e d u>
+
+ * Added the user_for_create and group_for_create options
+
+
+2007-03-24 Martin Pärtel <martin dot partel at gmail dot com>
+
+ * Fixed minor errors in man-page.
+ * Released 1.2.1
+
+2007-03-03 Martin Pärtel <martin dot partel at gmail dot com>
+
+ * Michael Roitzsch pointed out incorrect permission checks
+ for symlinks and fixed unlink() to correctly check for write permission
+ to the the directory (instead of the file). Thanks!
+ * Removed check_access() altogether. Will now rely on the kernel for
+ all permission checks by always enabling -o default_permissions.
+ * Released 1.2
+
+
+2007-01-14 Martin Pärtel <martin dot partel at gmail dot com>
+
+ * Changed -o no_allow_others to -o no_allow_other.
+ * Added a way to specify group members in -m and -M.
+ * Released 1.1
diff --git a/Makefile.am b/Makefile.am
new file mode 100644
index 0000000..623153e
--- /dev/null
+++ b/Makefile.am
@@ -0,0 +1,6 @@
+# not a GNU package. You can remove this line, if
+# have all needed files, that a GNU package needs
+AUTOMAKE_OPTIONS = foreign
+
+SUBDIRS = src tests
+
diff --git a/README b/README
new file mode 100644
index 0000000..d8f7956
--- /dev/null
+++ b/README
@@ -0,0 +1,41 @@
+
+bindfs - http://code.google.com/p/bindfs/
+
+-- Overview --
+
+bindfs is a FUSE filesystem for mirroring a directory to another
+directory, similarly to mount --bind. The permissions of the mirrored
+directory can be altered in various ways.
+
+Some things bindfs can be used for:
+ - Making a directory read-only.
+ - Making all executables non-executable.
+ - Sharing a directory with a list of users (or groups).
+ - Modifying permission bits using rules with chmod-like syntax.
+ - Changing the permissions with which files are created.
+
+Non-root users can use almost all features, but most interesting
+use-cases need user_allow_other to be defined in /etc/fuse.conf
+
+
+-- Installation --
+
+Make sure fuse 2.5.3 or above is installed (http://fuse.sf.net/).
+Then compile and install as usual:
+./configure
+make
+make install
+
+If you want the mounts made by non-root users to be visible to other users,
+you may have to add the line user_allow_other to /etc/fuse.conf.
+
+
+-- Usage --
+
+See the bindfs --help or the man-page for instructions and examples.
+
+
+-- License --
+
+GNU General Public License version 2 or any later version.
+See the file COPYING.
diff --git a/TODO b/TODO
new file mode 100644
index 0000000..257de9d
--- /dev/null
+++ b/TODO
@@ -0,0 +1,22 @@
+
+Major (i.e. probably not very soon):
+
+- Applying options to a subset of all files;
+ something like --if-file-matches '*.md5' { -u checksummer -p u+rw }
+ - This would make for some new useful options like --hide or --deny
+ - We could also have special xattrs like 'bindfs:perms' that
+ don't get propagated to the base directory but control bindfs behaviour
+ instead.
+ - All this leads to the thought of an integrated minilanguage.
+ Taken to the extreme, would this make bindfs almost yet another scripting
+ language binding for FUSE?
+ - Stackable/pluggable scripts? Any benefit over a remount?
+
+Minor:
+
+- Decide what to do with the fuse options uid=N and gid=N, or at least
+ mention them in the docs.
+
+- Look at capabilities instead of uid==0 when checking for special privileges.
+ Do this in a portable way and fall back to uid==0 if not available.
+
diff --git a/autogen.sh b/autogen.sh
new file mode 100755
index 0000000..3c50136
--- /dev/null
+++ b/autogen.sh
@@ -0,0 +1,57 @@
+#!/bin/bash
+
+# Enable environment variables to override tool commands.
+: ${AUTOCONF=autoconf}
+: ${AUTOHEADER=autoheader}
+: ${AUTOMAKE=automake}
+: ${ACLOCAL=aclocal}
+: ${LIBTOOLIZE=libtoolize}
+
+# Apple calls the GNU libtoolize "glibtoolize"
+if [[ ! -x `which "$LIBTOOLIZE"` ]]; then
+ LIBTOOLIZE=glibtoolize
+fi
+if [[ ! -x `which "$LIBTOOLIZE"` ]]; then
+ echo "Cannot find libtoolize"
+ exit 1
+fi
+
+# Add /usr/local/share/aclocal to aclocal's search path
+if [[ -d /usr/local/share/aclocal ]]; then
+ ACLOCAL="$ACLOCAL -I /usr/local/share/aclocal"
+fi
+
+rm -rf autom4te.cache
+rm -f aclocal.m4
+rm -f missing mkinstalldirs depcomp install-sh libtool
+
+echo "Running $ACLOCAL..."
+$ACLOCAL || exit 1
+
+echo "Running $AUTOHEADER..."
+$AUTOHEADER || exit 1
+
+echo "Running $AUTOCONF..."
+$AUTOCONF || exit 1
+
+echo "Running $LIBTOOLIZE..."
+$LIBTOOLIZE --automake --copy --force || exit 1
+
+echo "Running $AUTOMAKE..."
+$AUTOMAKE -a -c || exit 1
+
+if [ "$1" == "-d" ]; then
+ echo "Running configure --enable-debug"
+ echo
+ sleep 1s
+ ./configure --enable-debug
+elif [ -n "$1" ]; then
+ echo
+ echo "./configure $@"
+ ./configure $@
+else
+ echo
+ echo "autogen.sh completed successfully."
+ echo "Now run ./configure with the appropriate flags and then make."
+fi
+
diff --git a/configure.ac b/configure.ac
new file mode 100644
index 0000000..2e0974c
--- /dev/null
+++ b/configure.ac
@@ -0,0 +1,42 @@
+AC_INIT([bindfs],[1.9],[martin.partel@gmail.com])
+
+AM_INIT_AUTOMAKE
+AM_CONFIG_HEADER(config.h)
+
+AC_PROG_CC
+AC_LANG(C)
+AC_PROG_LIBTOOL
+
+# --enable and --with options
+AC_ARG_ENABLE([debug],
+ [AS_HELP_STRING([--enable-debug], [enable extra debug output])])
+AC_ARG_WITH([core-foundation],
+ AS_HELP_STRING([--with-core-foundation], [link against Core Foundation (OS X only) @<:@default=no@:>@]))
+
+
+if test x"$enable_debug" == "xyes" ; then
+ CFLAGS="${CFLAGS} -g -O0 -DMALLOC_CHECK_=2"
+ AC_DEFINE([BINDFS_DEBUG], [1], [Define to 1 to enable debugging messages])
+else
+ CFLAGS="${CFLAGS} -O2"
+fi
+
+if test x"$with_core_foundation" == "xyes" ; then
+ AC_MSG_NOTICE([Linking with Core Foundation])
+ LDFLAGS="${LDFLAGS} -framework CoreFoundation"
+fi
+
+CFLAGS="${CFLAGS} -Wall -D_REENTRANT -D_FILE_OFFSET_BITS=64 -DFUSE_USE_VERSION=25"
+
+# Check for xattrs
+AC_CHECK_FUNCS([setxattr getxattr listxattr removexattr])
+AC_CHECK_FUNCS([lsetxattr lgetxattr llistxattr lremovexattr])
+
+# Check for fuse
+PKG_CHECK_MODULES([fuse], [fuse >= 2.5.3])
+
+AC_CONFIG_FILES([Makefile \
+ src/Makefile \
+ tests/Makefile])
+AC_OUTPUT
+
diff --git a/src/Makefile.am b/src/Makefile.am
new file mode 100644
index 0000000..f123526
--- /dev/null
+++ b/src/Makefile.am
@@ -0,0 +1,11 @@
+## Process this file with automake to produce Makefile.in
+
+bin_PROGRAMS = bindfs
+
+noinst_HEADERS = debug.h permchain.h userinfo.h misc.h
+bindfs_SOURCES = bindfs.c permchain.c userinfo.c misc.c
+
+AM_CFLAGS = $(fuse_CFLAGS)
+bindfs_LDADD = $(fuse_LIBS)
+
+man_MANS = bindfs.1
diff --git a/src/bindfs.1 b/src/bindfs.1
new file mode 100644
index 0000000..92444a2
--- /dev/null
+++ b/src/bindfs.1
@@ -0,0 +1,338 @@
+.TH BINDFS 1
+
+
+.SH NAME
+bindfs \(hy mount \-\-bind in user\-space
+
+
+.SH SYNOPSIS
+\fBbindfs\fP [\fIoptions\fP]\fI dir mountpoint
+
+
+.SH DESCRIPTION
+A FUSE filesystem for mirroring the contents of a directory to another
+directory. Additionally, one can change the permissions
+of files in the mirrored directory.
+
+
+.SH OPTIONS
+.TP
+.B \-h, \-\-help
+Displays a help message and exits.
+
+.TP
+.B \-V, \-\-version
+Displays version information and exits.
+
+.TP
+.B \-u, \-\-user, \-\-owner=\fIuser\fP, \-o owner=...
+Makes all files owned by the specified user.
+Also causes chown on the mounted filesystem to always fail.
+
+.TP
+.B \-g, \-\-group=\fIgroup\fP, \-o group=...
+Makes all files owned by the specified group.
+Also causes chgrp on the mounted filesystem to always fail.
+
+.TP
+.B \-p, \-\-perms=\fIpermissions\fP, \-o perms=...
+Takes a comma\- or colon\-separated list of chmod\-like permission
+specifications to be applied to the permission bits in order.
+See \fB\%PERMISSION \%SPECIFICATION\fP below for details.
+
+This only affects how the permission bits of existing files are altered
+when shown in the mounted directory. You can use \-\-create\-with\-perms to
+change the permissions that newly created files get in the source directory.
+
+Note that, as usual, the root user isn't bound by the permissions set here.
+You can get a truly read-only mount by using \fB-r\fP.
+
+.TP
+.B \-m, \-\-mirror=\fIusers\fP, \-o mirror=...
+Takes a comma\- or colon\-separated list of users who will see themselves as
+the owners of all files. Users who are not listed here will still be able
+to access the mount if the permissions otherwise allow them to.
+
+You can also give a group name prefixed with an '@' to mirror all members of
+a group. This will not change which group the files are shown to have.
+
+.TP
+.B \-M, \-\-mirror\-only=\fIusers\fP, \-o mirror\-only=...
+Like \fB\-\-mirror\fP but disallows access for all other users (except root).
+
+.TP
+.B \-n, \-\-no\-allow\-other, \-o no\-allow\-other
+Does not add \fB\-o allow_other\fP to FUSE options.
+This causes the mount to be accessible only by the current user.
+
+
+.SH FILE CREATION POLICY
+New files and directories are created so they are owned by the mounter.
+bindfs can let this happen (the default for normal users),
+or it can try to change the owner to the uid/gid of the process that
+wants to create the file (the default for root). It is also possible to
+have bindfs try to change the owner to a particular user or group.
+
+.TP
+.B \-\-create\-as\-user, \-o create\-as\-user
+Tries to change the owner and group of new files and directories to the
+uid and gid of the caller. This can work only if the mounter is root.
+It is also the default behavior (mimicing mount \-\-bind) if the mounter is root.
+
+.TP
+.B \-\-create\-as\-mounter, \-o create\-as\-mounter
+All new files and directories will be owned by the mounter.
+This is the default behavior for non\-root mounters.
+
+.TP
+.B \-\-create\-for\-user=\fIuser\fP, \-o create\-for\-user=...
+Tries to change the owner of new files and directories to the user
+specified here. This can work only if the mounter is root. This
+option overrides the \-\-create\-as\-user and \-\-create\-as\-mounter options.
+
+.TP
+.B \-\-create\-for\-group=\fIgroup\fP, \-o create\-for\-group=...
+Tries to change the owning group of new files and directories to the
+group specified here. This can work only if the mounter is root. This
+option overrides the \-\-create\-as\-user and \-\-create\-as\-mounter options.
+
+.TP
+.B \-\-create\-with\-perms=\fIpermissions\fP, \-o create\-with\-perms=...
+Works like \-\-perms but is applied to the permission bits of new files
+get in the source directory.
+Normally the permissions of new files depend on the creating process's
+preferences and umask.
+This option can be used to modify those permissions or override
+them completely.
+See \fB\%PERMISSION \%SPECIFICATION\fP below for details.
+
+
+.SH CHOWN/CHGRP POLICY
+The behaviour on chown/chgrp calls can be changed. By default they are passed
+through to the source directory even if bindfs is set to show
+a fake owner/group. A chown/chgrp call will only succeed if the user has
+enough mirrored permissions to chmod the mirrored file AND
+the mounter has enough permissions to chmod the real file.
+
+.TP
+.B \-\-chown\-normal, \-o chown\-normal
+Tries to chown the underlying file. This is the default.
+
+.TP
+.B \-\-chown\-ignore, \-o chown\-ignore
+Lets chown succeed (if the user has enough mirrored permissions)
+but actually does nothing. A combined chown/chgrp is effectively turned
+into a chgrp-only request.
+
+.TP
+.B \-\-chown\-deny, \-o chown\-deny
+Makes chown always fail with a 'permission denied' error.
+A combined chown/chgrp request will fail as well.
+
+.TP
+.B \-\-chgrp\-normal, \-o chgrp\-normal
+Tries to chgrp the underlying file. This is the default.
+
+.TP
+.B \-\-chgrp\-ignore, \-o chgrp\-ignore
+Lets chgrp succeed (if the user has enough mirrored permissions)
+but actually does nothing. A combined chown/chgrp is effectively turned into a
+chown-only request.
+
+.TP
+.B \-\-chgrp\-deny, \-o chgrp\-deny
+Makes chgrp always fail with a 'permission denied' error.
+A combined chown/chgrp request will fail as well.
+
+
+.SH CHMOD POLICY
+Chmod calls are forwarded to the source directory by default.
+This may cause unexpected behaviour if bindfs is altering permission bits.
+
+.TP
+.B \-\-chmod\-normal, \-o chmod\-normal
+Tries to chmod the underlying file. This will succeed if the user has
+the appropriate mirrored permissions to chmod the mirrored file AND
+the mounter has enough permissions to chmod the real file.
+This is the default (in order to behave like mount \-\-bind by default).
+
+.TP
+.B \-\-chmod\-ignore, \-o chmod\-ignore
+Lets chmod succeed (if the user has enough mirrored permissions)
+but actually does nothing.
+
+.TP
+.B \-\-chmod\-deny, \-o chmod\-deny
+Makes chmod always fail with a 'permission denied' error.
+
+.TP
+.B \-\-chmod\-allow\-x, \-o chmod\-allow\-x
+Allows setting and clearing the executable attribute on files
+(but not directories). When used with \-\-chmod\-ignore,
+chmods will only affect execute bits on files and changes to other bits are
+discarded.
+With \-\-chmod\-deny, all chmods that would change any bits except
+excecute bits on files will still fail with a 'permission denied'.
+This option does nothing with \-\-chmod\-normal.
+
+
+.SH XATTR POLICY
+Extended attributes are mirrored by default,
+though not all underlying file systems support xattrs.
+
+.TP
+.B \-\-xattr\-none, \-o xattr\-none
+Disable extended attributes altogether. All operations will
+return 'Operation not supported'.
+
+.TP
+.B \-\-xattr\-ro, \-o xattr\-ro
+Let extended attributes be read\-only.
+
+.TP
+.B \-\-xattr\-rw, \-o xattr\-rw
+Let extended attributes be read\-write (the default).
+The read/write permissions are checked against the (possibly modified)
+file permissions inside the mount.
+
+
+.SH TIME-RELATED OPTIONS
+
+Recall that a unix file has three standard timestamps:
+\fBatime\fP (last access i.e. read time),
+\fBmtime\fP (last content modification time)
+\fBctime\fP (last content or metadata (inode) change time)
+
+It may sometimes be useful to alter these timestamps, but care should be taken
+not to cause programs (e.g. backup jobs) to miss important changes.
+
+.TP
+.B \-\-ctime\-from-mtime, \-o ctime\-from\-mtime
+Reads the ctime of each file and directory from its mtime.
+In other words, only content modifications (as opposed to metadata changes)
+will be reflected in a mirrored file's ctime.
+(The underlying file's ctime will still be updated normally.)
+
+
+.SH FUSE OPTIONS
+.TP
+.B \-o \fIoptions
+Fuse options.
+
+.TP
+.B \-r, \-o ro
+Make the mount strictly read-only.
+This even prevents root from writing to it.
+If this is all you need, then (since Linux 2.6.26) you can get a
+more efficent mount with \fBmount \-\-bind\fP and then \fBmount \-o remount,ro\fP.
+
+.TP
+.B \-d, \-o debug
+Enable debug output (implies \-f).
+
+.TP
+.B \-f
+Foreground operation.
+
+.TP
+.B \-s
+Disable multithreaded operation. bindfs should be thread-safe.
+
+
+.SH PERMISSION SPECIFICATION
+The \fB\-p\fP option takes a comma\- or colon\-separated list of either octal
+numeric permission bits or symbolic representations of permission bit
+operations.
+The symbolic representation is based on that of the \fBchmod\fP(1) command.
+setuid, setgid and sticky bits are ignored.
+
+This program extends the chmod symbolic representation with the following
+operands:
+
+`\fBD\fP' (right hand side)
+ Works like \fBX\fP but applies only to directories (not to executables).
+
+`\fBd\fP' and `\fBf\fP' (left hand side)
+ Makes this directive only apply to directories (d) or files (f).
+ e.g. \fBgd\-w\fP would remove the group write bit from all directories.
+
+`\fBu\fP', `\fBg\fP', `\fBo\fP' (right hand side)
+ Uses the user (u), group (g) or others (o) permission bits of
+ the original file.
+ e.g. \fBg=u\fP would copy the user's permission bits to the group.
+ \fBug+o\fP would add the others' permissions to the owner and group.
+
+
+.I Examples
+.TP
+.B o\-rwx
+Removes all permission bits from others.
+
+.TP
+.B g=rD
+Allows group to read all files and enter all directories, but nothing else.
+
+.TP
+.B 0644,a+X
+Sets permission bits to 0644 and adds the execute bit for everyone
+to all directories and executables.
+
+.TP
+.B og\-x:og+rD:u=rwX:g+rw
+Removes execute bit for others and group,
+adds read and directory execute for others and group,
+sets user permissions to read, write and execute directory/executable,
+adds read and write for group.
+
+
+.SH EXAMPLES
+.BR
+.TP
+.B bindfs \-u www \-g nogroup \-p 0000,u=rD ~/mywebsite ~/public_html/mysite
+
+Publishes a website in public_html so that only the 'www' user can
+read the site.
+
+.TP
+.B bindfs \-M foo,bar,1007,@mygroup \-p 0600,u+X dir mnt
+
+Gives access to 'foo', 'bar', the user with the UID 1007 as well as
+everyone in the group 'mygroup'. Sets the permission bits to 0600,
+thus giving the specified users read/write access,
+and adds the user execute bit for directories and executables.
+
+.TP
+.B bindfs \-ono\-allow\-other,perms=a\-w somedir somedir
+
+Makes a directory read\-only and accessable only by the current user.
+
+.TP
+.B bindfs#/home/bob/shared /var/www/shared/bob fuse perms=0000:u+rD 0 0
+
+An example \fI/etc/fstab\fP entry. Note that the colon must be used to
+separate arguments to perms, because the comma is an option separator in
+\fI/etc/fstab\fP.
+
+
+.SH NOTES
+
+Setuid and setgid bits have no effect inside the mount.
+This is a necessary security feature of FUSE.
+
+MacFuse caches file contents by default.
+This means that changes in source files are not always immediately visible under the mount point.
+\fB\-o nolocalcaches\fP can be used to disable the cache.
+
+.SH BUGS
+
+Please report to the issue tracker on the project home page at
+http://code.google.com/p/bindfs/
+
+
+.SH AUTHOR
+Martin P\[:a]rtel <martin dot partel at gmail dot com>
+
+
+.SH SEE ALSO
+\fBchmod\fP(1), \fBfusermount\fP(1)
+
diff --git a/src/bindfs.c b/src/bindfs.c
new file mode 100644
index 0000000..ff7b35f
--- /dev/null
+++ b/src/bindfs.c
@@ -0,0 +1,1329 @@
+/*
+ Copyright 2006,2007,2008,2009,2010 Martin Pärtel <martin.partel@gmail.com>
+
+ This file is part of bindfs.
+
+ bindfs is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 2 of the License, or
+ (at your option) any later version.
+
+ bindfs is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with bindfs. If not, see <http://www.gnu.org/licenses/>.
+
+
+ This file is based on fusexmp_fh.c from FUSE 2.5.3,
+ which had the following notice:
+ ---
+ FUSE: Filesystem in Userspace
+ Copyright (C) 2001-2006 Miklos Szeredi <miklos@szeredi.hu>
+
+ This program can be distributed under the terms of the GNU GPL.
+ See the file COPYING.
+ ---
+
+*/
+
+#include <config.h>
+
+/* For pread/pwrite */
+#define _XOPEN_SOURCE 500
+
+#include <stdlib.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <string.h>
+#include <ctype.h>
+#ifdef HAVE_SYS_TYPES_H
+#include <sys/types.h>
+#endif
+#ifdef HAVE_SYS_STAT_H
+#include <sys/stat.h>
+#endif
+#include <sys/statvfs.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <dirent.h>
+#include <errno.h>
+#include <getopt.h>
+#include <assert.h>
+#include <pwd.h>
+#include <grp.h>
+#ifdef HAVE_SETXATTR
+#include <sys/xattr.h>
+#endif
+
+#include <fuse.h>
+#include <fuse_opt.h>
+
+#include "debug.h"
+#include "permchain.h"
+#include "userinfo.h"
+#include "misc.h"
+
+/* SETTINGS */
+static struct settings {
+ const char *progname;
+ struct permchain *permchain; /* permission bit rules. see permchain.h */
+ uid_t new_uid; /* user-specified uid */
+ gid_t new_gid; /* user-specified gid */
+ uid_t create_for_uid;
+ gid_t create_for_gid;
+ const char *mntsrc;
+ const char *mntdest;
+ int mntsrc_fd;
+
+ enum CreatePolicy {
+ CREATE_AS_USER,
+ CREATE_AS_MOUNTER
+ } create_policy;
+
+ struct permchain *create_permchain; /* the --create-with-perms option */
+
+ enum ChownPolicy {
+ CHOWN_NORMAL,
+ CHOWN_IGNORE,
+ CHOWN_DENY
+ } chown_policy;
+
+ enum ChgrpPolicy {
+ CHGRP_NORMAL,
+ CHGRP_IGNORE,
+ CHGRP_DENY
+ } chgrp_policy;
+
+ enum ChmodPolicy {
+ CHMOD_NORMAL,
+ CHMOD_IGNORE,
+ CHMOD_DENY
+ } chmod_policy;
+
+ int chmod_allow_x;
+
+ enum XAttrPolicy {
+ XATTR_UNIMPLEMENTED,
+ XATTR_READ_ONLY,
+ XATTR_READ_WRITE
+ } xattr_policy;
+
+ int mirrored_users_only;
+ uid_t* mirrored_users;
+ int num_mirrored_users;
+ gid_t *mirrored_members;
+ int num_mirrored_members;
+
+ int ctime_from_mtime;
+} settings;
+
+
+
+/* PROTOTYPES */
+
+static int is_mirroring_enabled();
+
+/* Checks whether the uid is to be the mirrored owner of all files. */
+static int is_mirrored_user(uid_t uid);
+
+/* Processes the virtual path to a real path. Don't free() the result. */
+static const char *process_path(const char *path);
+
+/* The common parts of getattr and fgetattr */
+static int getattr_common(const char *path, struct stat *stbuf);
+
+
+/* FUSE callbacks */
+static void *bindfs_init();
+static void bindfs_destroy(void *private_data);
+static int bindfs_getattr(const char *path, struct stat *stbuf);
+static int bindfs_fgetattr(const char *path, struct stat *stbuf,
+ struct fuse_file_info *fi);
+static int bindfs_readlink(const char *path, char *buf, size_t size);
+static int bindfs_opendir(const char *path, struct fuse_file_info *fi);
+static inline DIR *get_dirp(struct fuse_file_info *fi);
+static int bindfs_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
+ off_t offset, struct fuse_file_info *fi);
+static int bindfs_releasedir(const char *path, struct fuse_file_info *fi);
+static int bindfs_mknod(const char *path, mode_t mode, dev_t rdev);
+static int bindfs_mkdir(const char *path, mode_t mode);
+static int bindfs_unlink(const char *path);
+static int bindfs_rmdir(const char *path);
+static int bindfs_symlink(const char *from, const char *to);
+static int bindfs_rename(const char *from, const char *to);
+static int bindfs_link(const char *from, const char *to);
+static int bindfs_chmod(const char *path, mode_t mode);
+static int bindfs_chown(const char *path, uid_t uid, gid_t gid);
+static int bindfs_truncate(const char *path, off_t size);
+static int bindfs_ftruncate(const char *path, off_t size,
+ struct fuse_file_info *fi);
+static int bindfs_utime(const char *path, struct utimbuf *buf);
+static int bindfs_create(const char *path, mode_t mode, struct fuse_file_info *fi);
+static int bindfs_open(const char *path, struct fuse_file_info *fi);
+static int bindfs_read(const char *path, char *buf, size_t size, off_t offset,
+ struct fuse_file_info *fi);
+static int bindfs_write(const char *path, const char *buf, size_t size,
+ off_t offset, struct fuse_file_info *fi);
+static int bindfs_statfs(const char *path, struct statvfs *stbuf);
+static int bindfs_release(const char *path, struct fuse_file_info *fi);
+static int bindfs_fsync(const char *path, int isdatasync,
+ struct fuse_file_info *fi);
+
+
+static void print_usage(const char *progname);
+static void atexit_func();
+static int process_option(void *data, const char *arg, int key,
+ struct fuse_args *outargs);
+
+static int is_mirroring_enabled()
+{
+ return settings.num_mirrored_users + settings.num_mirrored_members > 0;
+}
+
+static int is_mirrored_user(uid_t uid)
+{
+ int i;
+ for (i = 0; i < settings.num_mirrored_users; ++i) {
+ if (settings.mirrored_users[i] == uid)
+ break;
+ }
+ if (i < settings.num_mirrored_users) { /* found in mirrored_users */
+ return 1;
+ } else {
+ for (i = 0; i < settings.num_mirrored_members; ++i) {
+ if (user_belongs_to_group(uid, settings.mirrored_members[i]))
+ break;
+ }
+ if (i < settings.num_mirrored_members) /* found in mirrored_members */
+ return 1;
+ }
+ return 0;
+}
+
+
+static const char *process_path(const char *path)
+{
+ if (path == NULL) /* possible? */
+ return NULL;
+
+ while (*path == '/')
+ ++path;
+
+ if (*path == '\0')
+ return ".";
+ else
+ return path;
+}
+
+static int getattr_common(const char *procpath, struct stat *stbuf)
+{
+ struct fuse_context *fc = fuse_get_context();
+
+ /* Copy mtime (file content modification time)
+ to ctime (inode/status change time)
+ if the user asked for that */
+ if (settings.ctime_from_mtime)
+ stbuf->st_ctime = stbuf->st_mtime;
+
+ /* Report user-defined owner/group if specified */
+ if (settings.new_uid != -1)
+ stbuf->st_uid = settings.new_uid;
+ if (settings.new_gid != -1)
+ stbuf->st_gid = settings.new_gid;
+
+ /* Mirrored user? */
+ if (is_mirroring_enabled() && is_mirrored_user(fc->uid)) {
+ stbuf->st_uid = fc->uid;
+ } else if (settings.mirrored_users_only && fc->uid != 0) {
+ stbuf->st_mode &= ~0777; /* Deny all access if mirror-only and not root */
+ return 0;
+ }
+
+ if ((stbuf->st_mode & S_IFLNK) == S_IFLNK)
+ return 0; /* don't bother with symlink permissions -- they don't matter */
+
+ /* Apply user-defined permission bit modifications */
+ stbuf->st_mode = permchain_apply(settings.permchain, stbuf->st_mode);
+
+ /* Check that we can really do what we promise */
+ if (access(procpath, R_OK) == -1)
+ stbuf->st_mode &= ~0444;
+ if (access(procpath, W_OK) == -1)
+ stbuf->st_mode &= ~0222;
+ if (access(procpath, X_OK) == -1)
+ stbuf->st_mode &= ~0111;
+
+ return 0;
+}
+
+static void *bindfs_init()
+{
+ assert(settings.permchain != NULL);
+ assert(settings.mntsrc_fd > 0);
+
+ if (fchdir(settings.mntsrc_fd) != 0) {
+ fprintf(
+ stderr,
+ "Could not change working directory to '%s': %s\n",
+ settings.mntsrc,
+ strerror(errno)
+ );
+ fuse_exit(fuse_get_context()->fuse);
+ }
+
+ return NULL;
+}
+
+static void bindfs_destroy(void *private_data)
+{
+}
+
+static int bindfs_getattr(const char *path, struct stat *stbuf)
+{
+ path = process_path(path);
+
+ if (lstat(path, stbuf) == -1)
+ return -errno;
+ return getattr_common(path, stbuf);
+}
+
+static int bindfs_fgetattr(const char *path, struct stat *stbuf,
+ struct fuse_file_info *fi)
+{
+ path = process_path(path);
+
+ if (fstat(fi->fh, stbuf) == -1)
+ return -errno;
+ return getattr_common(path, stbuf);
+}
+
+static int bindfs_readlink(const char *path, char *buf, size_t size)
+{
+ int res;
+
+ path = process_path(path);
+
+ /* No need to check for access to the link itself, since symlink
+ permissions don't matter. Access to the path components of the symlink
+ are automatically queried by FUSE. */
+
+ res = readlink(path, buf, size - 1);
+ if (res == -1)
+ return -errno;
+
+ buf[res] = '\0';
+ return 0;
+}
+
+static int bindfs_opendir(const char *path, struct fuse_file_info *fi)
+{
+ DIR *dp;
+
+ path = process_path(path);
+
+ dp = opendir(path);
+ if (dp == NULL)
+ return -errno;
+
+ fi->fh = (unsigned long) dp;
+ return 0;
+}
+
+static inline DIR *get_dirp(struct fuse_file_info *fi)
+{
+ return (DIR *) (uintptr_t) fi->fh;
+}
+
+static int bindfs_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
+ off_t offset, struct fuse_file_info *fi)
+{
+ DIR *dp = get_dirp(fi);
+ struct dirent *de;
+
+ (void) path;
+ seekdir(dp, offset);
+ while ((de = readdir(dp)) != NULL) {
+ 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, telldir(dp)))
+ break;
+ }
+
+ return 0;
+}
+
+static int bindfs_releasedir(const char *path, struct fuse_file_info *fi)
+{
+ DIR *dp = get_dirp(fi);
+ (void) path;
+ closedir(dp);
+ return 0;
+}
+
+static int bindfs_mknod(const char *path, mode_t mode, dev_t rdev)
+{
+ int res;
+ struct fuse_context *fc;
+ uid_t file_owner = -1;
+ gid_t file_group = -1;
+
+ path = process_path(path);
+
+ mode = permchain_apply(settings.create_permchain, mode);
+
+ if (S_ISFIFO(mode))
+ res = mkfifo(path, mode);
+ else
+ res = mknod(path, mode, rdev);
+ if (res == -1)
+ return -errno;
+
+ if (settings.create_policy == CREATE_AS_USER) {
+ fc = fuse_get_context();
+ file_owner = fc->uid;
+ file_group = fc->gid;
+ }
+
+ if (settings.create_for_uid != -1)
+ file_owner = settings.create_for_uid;
+ if (settings.create_for_gid != -1)
+ file_group = settings.create_for_gid;
+
+ if ((file_owner != -1) || (file_group != -1)) {
+ if (chown(path, file_owner, file_group) == -1) {
+ DPRINTF("Failed to chown new device node (%d)", errno);
+ }
+ }
+
+ return 0;
+}
+
+static int bindfs_mkdir(const char *path, mode_t mode)
+{
+ int res;
+ struct fuse_context *fc;
+ uid_t file_owner = -1;
+ gid_t file_group = -1;
+
+ path = process_path(path);
+
+ mode |= S_IFDIR; /* tell permchain_apply this is a directory */
+ mode = permchain_apply(settings.create_permchain, mode);
+
+ res = mkdir(path, mode & 0777);
+ if (res == -1)
+ return -errno;
+
+ if (settings.create_policy == CREATE_AS_USER) {
+ fc = fuse_get_context();
+ file_owner = fc->uid;
+ file_group = fc->gid;
+ }
+
+ if (settings.create_for_uid != -1)
+ file_owner = settings.create_for_uid;
+ if (settings.create_for_gid != -1)
+ file_group = settings.create_for_gid;
+
+ if ((file_owner != -1) || (file_group != -1)) {
+ if (chown(path, file_owner, file_group) == -1) {
+ DPRINTF("Failed to chown new directory (%d)", errno);
+ }
+ }
+
+ return 0;
+}
+
+static int bindfs_unlink(const char *path)
+{
+ int res;
+
+ path = process_path(path);
+
+ res = unlink(path);
+ if (res == -1)
+ return -errno;
+
+ return 0;
+}
+
+static int bindfs_rmdir(const char *path)
+{
+ int res;
+
+ path = process_path(path);
+
+ res = rmdir(path);
+ if (res == -1)
+ return -errno;
+
+ return 0;
+}
+
+static int bindfs_symlink(const char *from, const char *to)
+{
+ int res;
+ struct fuse_context *fc;
+ uid_t file_owner = -1;
+ gid_t file_group = -1;
+
+ to = process_path(to);
+
+ res = symlink(from, to);
+ if (res == -1)
+ return -errno;
+
+ if (settings.create_policy == CREATE_AS_USER) {
+ fc = fuse_get_context();
+ file_owner = fc->uid;
+ file_group = fc->gid;
+ }
+
+ if (settings.create_for_uid != -1)
+ file_owner = settings.create_for_uid;
+ if (settings.create_for_gid != -1)
+ file_group = settings.create_for_gid;
+
+ if ((file_owner != -1) || (file_group != -1)) {
+ if (lchown(to, file_owner, file_group) == -1) {
+ DPRINTF("Failed to lchown new symlink (%d)", errno);
+ }
+ }
+
+ return 0;
+}
+
+static int bindfs_rename(const char *from, const char *to)
+{
+ int res;
+
+ from = process_path(from);
+ to = process_path(to);
+
+ res = rename(from, to);
+ if (res == -1)
+ return -errno;
+
+ return 0;
+}
+
+static int bindfs_link(const char *from, const char *to)
+{
+ int res;
+
+ from = process_path(from);
+ to = process_path(to);
+
+ res = link(from, to);
+ if (res == -1)
+ return -errno;
+
+ return 0;
+}
+
+static int bindfs_chmod(const char *path, mode_t mode)
+{
+ int file_execute_only = 0;
+ struct stat st;
+ mode_t diff = 0;
+
+ path = process_path(path);
+
+ if (settings.chmod_allow_x) {
+ /* Get the old permission bits and see which bits would change. */
+ if (lstat(path, &st) == -1)
+ return -errno;
+
+ if (S_ISREG(st.st_mode)) {
+ diff = (st.st_mode & 07777) ^ (mode & 07777);
+ file_execute_only = 1;
+ }
+ }
+
+ switch (settings.chmod_policy) {
+ case CHMOD_NORMAL:
+ if (chmod(path, mode) == -1)
+ return -errno;
+ return 0;
+ case CHMOD_IGNORE:
+ if (file_execute_only) {
+ diff &= 00111; /* See which execute bits were flipped.
+ Forget about other differences. */
+ if (chmod(path, st.st_mode ^ diff) == -1)
+ return -errno;
+ }
+ return 0;
+ case CHMOD_DENY:
+ if (file_execute_only) {
+ if ((diff & 07666) == 0) {
+ /* Only execute bits have changed, so we can allow this. */
+ if (chmod(path, mode) == -1)
+ return -errno;
+ return 0;
+ }
+ }
+ return -EPERM;
+ default:
+ assert(0);
+ }
+}
+
+static int bindfs_chown(const char *path, uid_t uid, gid_t gid)
+{
+ int res;
+
+ if (uid != -1) {
+ switch (settings.chown_policy) {
+ case CHOWN_NORMAL:
+ break;
+ case CHOWN_IGNORE:
+ uid = -1;
+ break;
+ case CHOWN_DENY:
+ return -EPERM;
+ }
+ }
+
+ if (gid != -1) {
+ switch (settings.chgrp_policy) {
+ case CHGRP_NORMAL:
+ break;
+ case CHGRP_IGNORE:
+ gid = -1;
+ break;
+ case CHGRP_DENY:
+ return -EPERM;
+ }
+ }
+
+ if (uid != -1 || gid != -1) {
+ path = process_path(path);
+ res = lchown(path, uid, gid);
+ if (res == -1)
+ return -errno;
+ }
+
+ return 0;
+}
+
+static int bindfs_truncate(const char *path, off_t size)
+{
+ int res;
+
+ path = process_path(path);
+
+ res = truncate(path, size);
+ if (res == -1)
+ return -errno;
+
+ return 0;
+}
+
+static int bindfs_ftruncate(const char *path, off_t size,
+ struct fuse_file_info *fi)
+{
+ int res;
+
+ (void) path;
+
+ res = ftruncate(fi->fh, size);
+ if (res == -1)
+ return -errno;
+
+ return 0;
+}
+
+static int bindfs_utime(const char *path, struct utimbuf *buf)
+{
+ int res;
+
+ path = process_path(path);
+
+ res = utime(path, buf);
+ if (res == -1)
+ return -errno;
+
+ return 0;
+}
+
+static int bindfs_create(const char *path, mode_t mode, struct fuse_file_info *fi)
+{
+ int fd;
+ struct fuse_context *fc;
+ uid_t file_owner = -1;
+ gid_t file_group = -1;
+
+ path = process_path(path);
+
+ mode |= S_IFREG; /* tell permchain_apply this is a regular file */
+ mode = permchain_apply(settings.create_permchain, mode);
+
+ fd = open(path, fi->flags, mode & 0777);
+ if (fd == -1)
+ return -errno;
+
+ if (settings.create_policy == CREATE_AS_USER) {
+ fc = fuse_get_context();
+ file_owner = fc->uid;
+ file_group = fc->gid;
+ }
+
+ if (settings.create_for_uid != -1)
+ file_owner = settings.create_for_uid;
+ if (settings.create_for_gid != -1)
+ file_group = settings.create_for_gid;
+
+ if ((file_owner != -1) || (file_group != -1)) {
+ if (chown(path, file_owner, file_group) == -1) {
+ DPRINTF("Failed to chown new file (%d)", errno);
+ }
+ }
+
+ fi->fh = fd;
+ return 0;
+}
+
+static int bindfs_open(const char *path, struct fuse_file_info *fi)
+{
+ int fd;
+
+ path = process_path(path);
+
+ fd = open(path, fi->flags);
+ if (fd == -1)
+ return -errno;
+
+ fi->fh = fd;
+ return 0;
+}
+
+static int bindfs_read(const char *path, char *buf, size_t size, off_t offset,
+ struct fuse_file_info *fi)
+{
+ int res;
+
+ (void) path;
+ res = pread(fi->fh, buf, size, offset);
+ if (res == -1)
+ res = -errno;
+
+ return res;
+}
+
+static int bindfs_write(const char *path, const char *buf, size_t size,
+ off_t offset, struct fuse_file_info *fi)
+{
+ int res;
+
+ (void) path;
+ res = pwrite(fi->fh, buf, size, offset);
+ if (res == -1)
+ res = -errno;
+
+ return res;
+}
+
+static int bindfs_statfs(const char *path, struct statvfs *stbuf)
+{
+ int res;
+
+ path = process_path(path);
+
+ res = statvfs(path, stbuf);
+ if (res == -1)
+ return -errno;
+
+ return 0;
+}
+
+static int bindfs_release(const char *path, struct fuse_file_info *fi)
+{
+ (void) path;
+ close(fi->fh);
+
+ return 0;
+}
+
+static int bindfs_fsync(const char *path, int isdatasync,
+ struct fuse_file_info *fi)
+{
+ int res;
+ (void) path;
+
+#ifndef HAVE_FDATASYNC
+ (void) isdatasync;
+#else
+ if (isdatasync)
+ res = fdatasync(fi->fh);
+ else
+#endif
+ res = fsync(fi->fh);
+ if (res == -1)
+ return -errno;
+
+ return 0;
+}
+
+#ifdef HAVE_SETXATTR
+/* If HAVE_L*XATTR is not defined, we assume Mac/BSD -style *xattr() */
+
+static int bindfs_setxattr(const char *path, const char *name, const char *value,
+ size_t size, int flags)
+{
+ DPRINTF("setxattr %s %s=%s", path, name, value);
+
+ if (settings.xattr_policy == XATTR_READ_ONLY)
+ return -EACCES;
+
+ /* fuse checks permissions for us */
+ path = process_path(path);
+#ifdef HAVE_LSETXATTR
+ if (lsetxattr(path, name, value, size, flags) == -1)
+#else
+ if (setxattr(path, name, value, size, 0, flags | XATTR_NOFOLLOW) == -1)
+#endif
+ return -errno;
+ return 0;
+}
+
+static int bindfs_getxattr(const char *path, const char *name, char *value,
+ size_t size)
+{
+ int res;
+
+ DPRINTF("getxattr %s %s", path, name);
+
+ path = process_path(path);
+ /* fuse checks permissions for us */
+#ifdef HAVE_LGETXATTR
+ res = lgetxattr(path, name, value, size);
+#else
+ res = getxattr(path, name, value, size, 0, XATTR_NOFOLLOW);
+#endif
+ if (res == -1)
+ return -errno;
+ return res;
+}
+
+static int bindfs_listxattr(const char *path, char *list, size_t size)
+{
+ int res;
+
+ DPRINTF("listxattr %s", path);
+
+ path = process_path(path);
+ /* fuse checks permissions for us */
+#ifdef HAVE_LLISTXATTR
+ res = llistxattr(path, list, size);
+#else
+ res = listxattr(path, list, size, XATTR_NOFOLLOW);
+#endif
+ if (res == -1)
+ return -errno;
+ return res;
+}
+
+static int bindfs_removexattr(const char *path, const char *name)
+{
+ DPRINTF("removexattr %s %s", path, name);
+
+ if (settings.xattr_policy == XATTR_READ_ONLY)
+ return -EACCES;
+
+ path = process_path(path);
+ /* fuse checks permissions for us */
+#ifdef HAVE_LREMOVEXATTR
+ if (lremovexattr(path, name) == -1)
+#else
+ if (removexattr(path, name, XATTR_NOFOLLOW) == -1)
+#endif
+ return -errno;
+ return 0;
+}
+#endif /* HAVE_SETXATTR */
+
+
+static struct fuse_operations bindfs_oper = {
+ .init = bindfs_init,
+ .destroy = bindfs_destroy,
+ .getattr = bindfs_getattr,
+ .fgetattr = bindfs_fgetattr,
+ /* no access() since we always use -o default_permissions */
+ .readlink = bindfs_readlink,
+ .opendir = bindfs_opendir,
+ .readdir = bindfs_readdir,
+ .releasedir = bindfs_releasedir,
+ .mknod = bindfs_mknod,
+ .mkdir = bindfs_mkdir,
+ .symlink = bindfs_symlink,
+ .unlink = bindfs_unlink,
+ .rmdir = bindfs_rmdir,
+ .rename = bindfs_rename,
+ .link = bindfs_link,
+ .chmod = bindfs_chmod,
+ .chown = bindfs_chown,
+ .truncate = bindfs_truncate,
+ .ftruncate = bindfs_ftruncate,
+ .utime = bindfs_utime,
+ .create = bindfs_create,
+ .open = bindfs_open,
+ .read = bindfs_read,
+ .write = bindfs_write,
+ .statfs = bindfs_statfs,
+ .release = bindfs_release,
+ .fsync = bindfs_fsync,
+#ifdef HAVE_SETXATTR
+ .setxattr = bindfs_setxattr,
+ .getxattr = bindfs_getxattr,
+ .listxattr = bindfs_listxattr,
+ .removexattr= bindfs_removexattr,
+#endif
+};
+
+static void print_usage(const char *progname)
+{
+ if (progname == NULL)
+ progname = "bindfs";
+
+ printf("\n"
+ "Usage: %s [options] dir mountpoint\n"
+ "Information:\n"
+ " -h --help Print this and exit.\n"
+ " -V --version Print version number and exit.\n"
+ "\n"
+ "Options:\n"
+ " -u --user, --owner Set file owner.\n"
+ " -g --group Set file group.\n"
+ " -m --mirror Comma-separated list of users who will see\n"
+ " themselves as the owners of all files.\n"
+ " -M --mirror-only Like --mirror but disallow access for\n"
+ " all other users.\n"
+ " -n --no-allow-other Do not add -o allow_other to fuse options.\n"
+ "\n"
+ "Permission bits:\n"
+ " -p --perms Specify permissions, similar to chmod\n"
+ " e.g. og-x,og+rD,u=rwX,g+rw or 0644,a+X\n"
+ "\n"
+ "File creation policy:\n"
+ " --create-as-user New files owned by creator (default for root). *\n"
+ " --create-as-mounter New files owned by fs mounter (default for users).\n"
+ " --create-for-user New files owned by specified user. *\n"
+ " --create-for-group New files owned by specified group. *\n"
+ " --create-with-perms Alter permissions of new files.\n"
+ "\n"
+ "Chown policy:\n"
+ " --chown-normal Try to chown the original files (the default).\n"
+ " --chown-ignore Have all chowns fail silently.\n"
+ " --chown-deny Have all chowns fail with 'permission denied'.\n"
+ "\n"
+ "Chgrp policy:\n"
+ " --chgrp-normal Try to chgrp the original files (the default).\n"
+ " --chgrp-ignore Have all chgrps fail silently.\n"
+ " --chgrp-deny Have all chgrps fail with 'permission denied'.\n"
+ "\n"
+ "Chmod policy:\n"
+ " --chmod-normal Try to chmod the original files (the default).\n"
+ " --chmod-ignore Have all chmods fail silently.\n"
+ " --chmod-deny Have all chmods fail with 'permission denied'.\n"
+ " --chmod-allow-x Allow changing file execute bits in any case.\n"
+ "\n"
+ "Extended attribute policy:\n"
+ " --xattr-none Do not implement xattr operations.\n"
+ " --xattr-ro Read-only xattr operations.\n"
+ " --xattr-rw Read-write xattr operations (the default).\n"
+ "\n"
+ "Time-related:\n"
+ " --ctime-from-mtime Read file properties' change time\n"
+ " from file content modification time.\n"
+ "\n"
+ "FUSE options:\n"
+ " -o opt[,opt,...] Mount options.\n"
+ " -r -o ro Mount strictly read-only.\n"
+ " -d -o debug Enable debug output (implies -f).\n"
+ " -f Foreground operation.\n"
+ " -s Disable multithreaded operation.\n"
+ "\n"
+ "(*: root only)\n"
+ "\n",
+ progname);
+}
+
+
+static void atexit_func()
+{
+ permchain_destroy(settings.permchain);
+ settings.permchain = NULL;
+ permchain_destroy(settings.create_permchain);
+ settings.create_permchain = NULL;
+ free(settings.mirrored_users);
+ settings.mirrored_users = NULL;
+ free(settings.mirrored_members);
+ settings.mirrored_members = NULL;
+}
+
+enum OptionKey {
+ OPTKEY_NONOPTION = -2,
+ OPTKEY_UNKNOWN = -1,
+ OPTKEY_HELP,
+ OPTKEY_VERSION,
+ OPTKEY_CREATE_AS_USER,
+ OPTKEY_CREATE_AS_MOUNTER,
+ OPTKEY_CHOWN_NORMAL,
+ OPTKEY_CHOWN_IGNORE,
+ OPTKEY_CHOWN_DENY,
+ OPTKEY_CHGRP_NORMAL,
+ OPTKEY_CHGRP_IGNORE,
+ OPTKEY_CHGRP_DENY,
+ OPTKEY_CHMOD_NORMAL,
+ OPTKEY_CHMOD_IGNORE,
+ OPTKEY_CHMOD_DENY,
+ OPTKEY_CHMOD_ALLOW_X,
+ OPTKEY_XATTR_NONE,
+ OPTKEY_XATTR_READ_ONLY,
+ OPTKEY_XATTR_READ_WRITE,
+ OPTKEY_CTIME_FROM_MTIME
+};
+
+static int process_option(void *data, const char *arg, int key,
+ struct fuse_args *outargs)
+{
+ switch ((enum OptionKey)key)
+ {
+ case OPTKEY_HELP:
+ print_usage(my_basename(settings.progname));
+ exit(0);
+
+ case OPTKEY_VERSION:
+ printf("%s\n", PACKAGE_STRING);
+ exit(0);
+
+ case OPTKEY_CREATE_AS_USER:
+ if (getuid() == 0) {
+ settings.create_policy = CREATE_AS_USER;
+ } else {
+ fprintf(stderr, "Error: You need to be root to use --create-as-user !\n");
+ return -1;
+ }
+ return 0;
+ case OPTKEY_CREATE_AS_MOUNTER:
+ settings.create_policy = CREATE_AS_MOUNTER;
+ return 0;
+
+ case OPTKEY_CHOWN_NORMAL:
+ settings.chown_policy = CHOWN_NORMAL;
+ return 0;
+ case OPTKEY_CHOWN_IGNORE:
+ settings.chown_policy = CHOWN_IGNORE;
+ return 0;
+ case OPTKEY_CHOWN_DENY:
+ settings.chown_policy = CHOWN_DENY;
+ return 0;
+
+ case OPTKEY_CHGRP_NORMAL:
+ settings.chgrp_policy = CHGRP_NORMAL;
+ return 0;
+ case OPTKEY_CHGRP_IGNORE:
+ settings.chgrp_policy = CHGRP_IGNORE;
+ return 0;
+ case OPTKEY_CHGRP_DENY:
+ settings.chgrp_policy = CHGRP_DENY;
+ return 0;
+
+ case OPTKEY_CHMOD_NORMAL:
+ settings.chmod_policy = CHMOD_NORMAL;
+ return 0;
+ case OPTKEY_CHMOD_IGNORE:
+ settings.chmod_policy = CHMOD_IGNORE;
+ return 0;
+ case OPTKEY_CHMOD_DENY:
+ settings.chmod_policy = CHMOD_DENY;
+ return 0;
+
+ case OPTKEY_CHMOD_ALLOW_X:
+ settings.chmod_allow_x = 1;
+ return 0;
+
+ case OPTKEY_XATTR_NONE:
+ settings.xattr_policy = XATTR_UNIMPLEMENTED;
+ return 0;
+ case OPTKEY_XATTR_READ_ONLY:
+ settings.xattr_policy = XATTR_READ_ONLY;
+ return 0;
+ case OPTKEY_XATTR_READ_WRITE:
+ settings.xattr_policy = XATTR_READ_WRITE;
+ return 0;
+
+ case OPTKEY_CTIME_FROM_MTIME:
+ settings.ctime_from_mtime = 1;
+ return 0;
+
+ case OPTKEY_NONOPTION:
+ if (!settings.mntsrc) {
+ settings.mntsrc = arg;
+ return 0;
+ } else if (!settings.mntdest) {
+ settings.mntdest = arg;
+ return 1; /* leave this argument for fuse_main */
+ } else {
+ fprintf(stderr, "Too many arguments given\n");
+ return -1;
+ }
+
+ default:
+ return 1;
+ }
+}
+
+
+int main(int argc, char *argv[])
+{
+ struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
+ /* Fuse's option parser will store things here. */
+ static struct OptionData {
+ char *user;
+ char *group;
+ char *perms;
+ char *mirror;
+ char *mirror_only;
+ char *create_for_user;
+ char *create_for_group;
+ char *create_with_perms;
+ int no_allow_other;
+ } od = {NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0};
+
+ #define OPT2(one, two, key) \
+ FUSE_OPT_KEY(one, key), \
+ FUSE_OPT_KEY(two, key)
+ #define OPT_OFFSET2(one, two, offset, key) \
+ {one, offsetof(struct OptionData, offset), key}, \
+ {two, offsetof(struct OptionData, offset), key}
+ #define OPT_OFFSET3(one, two, three, offset, key) \
+ {one, offsetof(struct OptionData, offset), key}, \
+ {two, offsetof(struct OptionData, offset), key}, \
+ {three, offsetof(struct OptionData, offset), key}
+ static const struct fuse_opt options[] = {
+ OPT2("-h", "--help", OPTKEY_HELP),
+ OPT2("-V", "--version", OPTKEY_VERSION),
+ OPT_OFFSET2("-u %s", "--user=%s", user, -1),
+ OPT_OFFSET2( "--owner=%s", "owner=%s", user, -1),
+ OPT_OFFSET3("-g %s", "--group=%s", "group=%s", group, -1),
+ OPT_OFFSET3("-p %s", "--perms=%s", "perms=%s", perms, -1),
+ OPT_OFFSET3("-m %s", "--mirror=%s", "mirror=%s", mirror, -1),
+ OPT_OFFSET3("-M %s", "--mirror-only=%s", "mirror-only=%s", mirror_only, -1),
+ OPT_OFFSET3("-n", "--no-allow-other", "no-allow-other", no_allow_other, -1),
+ OPT2("--create-as-user", "create-as-user", OPTKEY_CREATE_AS_USER),
+ OPT2("--create-as-mounter", "create-as-mounter", OPTKEY_CREATE_AS_MOUNTER),
+ OPT_OFFSET2("--create-for-user=%s", "create-for-user=%s", create_for_user, -1),
+ OPT_OFFSET2("--create-for-group=%s", "create-for-group=%s", create_for_group, -1),
+ OPT_OFFSET2("--create-with-perms=%s", "create-with-perms=%s", create_with_perms, -1),
+ OPT2("--chown-normal", "chown-normal", OPTKEY_CHOWN_NORMAL),
+ OPT2("--chown-ignore", "chown-ignore", OPTKEY_CHOWN_IGNORE),
+ OPT2("--chown-deny", "chown-deny", OPTKEY_CHOWN_DENY),
+ OPT2("--chgrp-normal", "chgrp-normal", OPTKEY_CHGRP_NORMAL),
+ OPT2("--chgrp-ignore", "chgrp-ignore", OPTKEY_CHGRP_IGNORE),
+ OPT2("--chgrp-deny", "chgrp-deny", OPTKEY_CHGRP_DENY),
+ OPT2("--chmod-normal", "chmod-normal", OPTKEY_CHMOD_NORMAL),
+ OPT2("--chmod-ignore", "chmod-ignore", OPTKEY_CHMOD_IGNORE),
+ OPT2("--chmod-deny", "chmod-deny", OPTKEY_CHMOD_DENY),
+ OPT2("--chmod-allow-x", "chmod-allow-x", OPTKEY_CHMOD_ALLOW_X),
+ OPT2("--xattr-none", "xattr-none", OPTKEY_XATTR_NONE),
+ OPT2("--xattr-ro", "xattr-ro", OPTKEY_XATTR_READ_ONLY),
+ OPT2("--xattr-rw", "xattr-rw", OPTKEY_XATTR_READ_WRITE),
+ OPT2("--ctime-from-mtime", "ctime-from-mtime", OPTKEY_CTIME_FROM_MTIME),
+ FUSE_OPT_END
+ };
+
+ /* General-purpose variables */
+ int i, j;
+ char *p, *tmpstr;
+
+ int fuse_main_return;
+
+
+ /* Initialize settings */
+ settings.progname = argv[0];
+ settings.permchain = permchain_create();
+ settings.new_uid = -1;
+ settings.new_gid = -1;
+ settings.create_for_uid = -1;
+ settings.create_for_gid = -1;
+ settings.mntsrc = NULL;
+ settings.mntdest = NULL;
+ settings.create_policy = (getuid() == 0) ? CREATE_AS_USER : CREATE_AS_MOUNTER;
+ settings.create_permchain = permchain_create();
+ settings.chown_policy = CHOWN_NORMAL;
+ settings.chgrp_policy = CHGRP_NORMAL;
+ settings.chmod_policy = CHMOD_NORMAL;
+ settings.chmod_allow_x = 0;
+ settings.xattr_policy = XATTR_READ_WRITE;
+ settings.mirrored_users_only = 0;
+ settings.mirrored_users = NULL;
+ settings.num_mirrored_users = 0;
+ settings.mirrored_members = NULL;
+ settings.num_mirrored_members = 0;
+ settings.ctime_from_mtime = 0;
+ atexit(&atexit_func);
+
+ /* Parse options */
+ if (fuse_opt_parse(&args, &od, options, &process_option) == -1)
+ return 1;
+
+ /* Check that a source directory and a mount point was given */
+ if (!settings.mntsrc || !settings.mntdest) {
+ print_usage(my_basename(argv[0]));
+ return 1;
+ }
+
+ /* Parse new owner and group */
+ if (od.user) {
+ if (!user_uid(od.user, &settings.new_uid)) {
+ fprintf(stderr, "Not a valid user ID: %s\n", od.user);
+ return 1;
+ }
+ }
+ if (od.group) {
+ if (!group_gid(od.group, &settings.new_gid)) {
+ fprintf(stderr, "Not a valid group ID: %s\n", od.group);
+ return 1;
+ }
+ }
+
+ /* Parse user and group for new creates */
+ if (od.create_for_user) {
+ if (getuid() != 0) {
+ fprintf(stderr, "Error: You need to be root to use --create-for-user !\n");
+ return 1;
+ }
+ if (!user_uid(od.create_for_user, &settings.create_for_uid)) {
+ fprintf(stderr, "Not a valid user ID: %s\n", od.create_for_user);
+ return 1;
+ }
+ }
+ if (od.create_for_group) {
+ if (getuid() != 0) {
+ fprintf(stderr, "Error: You need to be root to use --create-for-group !\n");
+ return 1;
+ }
+ if (!group_gid(od.create_for_group, &settings.create_for_gid)) {
+ fprintf(stderr, "Not a valid group ID: %s\n", od.create_for_group);
+ return 1;
+ }
+ }
+
+ /* Parse mirrored users and groups */
+ if (od.mirror && od.mirror_only) {
+ fprintf(stderr, "Cannot specify both -m|--mirror and -M|--mirror-only\n");
+ return 1;
+ }
+ if (od.mirror_only) {
+ settings.mirrored_users_only = 1;
+ od.mirror = od.mirror_only;
+ }
+ if (od.mirror) {
+ settings.num_mirrored_users = count_chars(od.mirror, ',') +
+ count_chars(od.mirror, ':') + 1;
+ settings.num_mirrored_members = ((*od.mirror == '@') ? 1 : 0) +
+ count_substrs(od.mirror, ",@") +
+ count_substrs(od.mirror, ":@");
+ settings.num_mirrored_users -= settings.num_mirrored_members;
+ settings.mirrored_users = malloc(settings.num_mirrored_users*sizeof(uid_t));
+ settings.mirrored_members = malloc(settings.num_mirrored_members*sizeof(gid_t));
+
+ i = 0; /* iterate over mirrored_users */
+ j = 0; /* iterate over mirrored_members */
+ p = od.mirror;
+ while (i < settings.num_mirrored_users || j < settings.num_mirrored_members) {
+ tmpstr = strdup_until(p, ",:");
+
+ if (*tmpstr == '@') { /* This is a group name */
+ if (!group_gid(tmpstr + 1, &settings.mirrored_members[j++])) {
+ fprintf(stderr, "Invalid group ID: '%s'\n", tmpstr + 1);
+ free(tmpstr);
+ return 1;
+ }
+ } else {
+ if (!user_uid(tmpstr, &settings.mirrored_users[i++])) {
+ fprintf(stderr, "Invalid user ID: '%s'\n", tmpstr);
+ free(tmpstr);
+ return 1;
+ }
+ }
+ free(tmpstr);
+
+ while (*p != '\0' && *p != ',' && *p != ':')
+ ++p;
+ if (*p != '\0')
+ ++p;
+ else {
+ /* Done. The counters should match. */
+ assert(i == settings.num_mirrored_users);
+ assert(j == settings.num_mirrored_members);
+ }
+ }
+ }
+
+ /* Parse permission bits */
+ if (od.perms) {
+ if (add_chmod_rules_to_permchain(od.perms, settings.permchain) != 0) {
+ fprintf(stderr, "Invalid permission specification: '%s'\n", od.perms);
+ return 1;
+ }
+ }
+ if (od.create_with_perms) {
+ if (add_chmod_rules_to_permchain(od.create_with_perms, settings.create_permchain) != 0) {
+ fprintf(stderr, "Invalid permission specification: '%s'\n", od.create_with_perms);
+ return 1;
+ }
+ }
+
+
+ /* Add default fuse options */
+ if (!od.no_allow_other) {
+ fuse_opt_add_arg(&args, "-oallow_other");
+ }
+
+ /* We want the kernel to do our access checks for us based on what getattr gives it. */
+ fuse_opt_add_arg(&args, "-odefault_permissions");
+
+ /* We need to disable the attribute cache whenever two users
+ can see different attributes. For now, only mirroring can do that. */
+ if (is_mirroring_enabled()) {
+ fuse_opt_add_arg(&args, "-oattr_timeout=0");
+ }
+
+ /* If the mount source and destination directories are the same
+ then don't require that the directory be empty. */
+ if (strcmp(settings.mntsrc, settings.mntdest) == 0)
+ fuse_opt_add_arg(&args, "-ononempty");
+
+ /* Open mount source for chrooting in bindfs_init */
+ settings.mntsrc_fd = open(settings.mntsrc, O_RDONLY);
+ if (settings.mntsrc_fd == -1) {
+ fprintf(stderr, "Could not open source directory\n");
+ return 1;
+ }
+
+ /* Ignore the umask of the mounter on file creation */
+ umask(0);
+
+ /* Remove xattr implementation if the user doesn't want it */
+ if (settings.xattr_policy == XATTR_UNIMPLEMENTED) {
+ bindfs_oper.setxattr = NULL;
+ bindfs_oper.getxattr = NULL;
+ bindfs_oper.listxattr = NULL;
+ bindfs_oper.removexattr = NULL;
+ }
+
+ fuse_main_return = fuse_main(args.argc, args.argv, &bindfs_oper);
+
+ fuse_opt_free_args(&args);
+ close(settings.mntsrc_fd);
+
+ return fuse_main_return;
+}
diff --git a/src/debug.h b/src/debug.h
new file mode 100644
index 0000000..1883101
--- /dev/null
+++ b/src/debug.h
@@ -0,0 +1,33 @@
+/*
+ Copyright 2006,2007,2008 Martin Pärtel <martin.partel@gmail.com>
+
+ This file is part of bindfs.
+
+ bindfs is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 2 of the License, or
+ (at your option) any later version.
+
+ bindfs is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with bindfs. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef INC_BINDFS_DEBUG_H
+#define INC_BINDFS_DEBUG_H
+
+#include <config.h>
+
+#if BINDFS_DEBUG
+#include <stdio.h>
+#define DPRINTF(fmt, ...) fprintf(stderr, "DEBUG: " fmt "\n", __VA_ARGS__)
+#else
+#define DPRINTF(...)
+#endif
+
+#endif
+
diff --git a/src/misc.c b/src/misc.c
new file mode 100644
index 0000000..0280f61
--- /dev/null
+++ b/src/misc.c
@@ -0,0 +1,83 @@
+/*
+ Copyright 2006,2007,2008 Martin Pärtel <martin.partel@gmail.com>
+
+ This file is part of bindfs.
+
+ bindfs is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 2 of the License, or
+ (at your option) any later version.
+
+ bindfs is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with bindfs. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "misc.h"
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+
+int count_chars(const char *s, char ch)
+{
+ int count = 0;
+ assert(s != NULL);
+ while (*s != '\0') {
+ if (*s == ch)
+ ++count;
+ ++s;
+ }
+ return count;
+}
+
+int count_substrs(const char *s, const char *sub)
+{
+ int count = 0;
+ int sublen = strlen(sub);
+ int left = strlen(s);
+
+ assert(s != NULL && sub != NULL);
+
+ while (left > sublen) {
+ if (strncmp(s, sub, sublen) == 0)
+ ++count;
+ --left;
+ ++s;
+ }
+
+ return count;
+}
+
+char *strdup_until(const char *s, const char *endchars)
+{
+ char *endloc = strpbrk(s, endchars);
+ char *ret;
+ if (!endloc) {
+ ret = malloc((strlen(s) + 1) * sizeof(char));
+ strcpy(ret, s);
+ return ret;
+ } else {
+ ret = malloc((endloc - s + 1) * sizeof(char));
+ memcpy(ret, s, (endloc - s) * sizeof(char));
+ ret[(endloc - s)] = '\0';
+ return ret;
+ }
+}
+
+const char *my_basename(const char *path)
+{
+ const char *p;
+
+ if (path == NULL)
+ return NULL;
+
+ p = strrchr(path, '/');
+ if (p != NULL)
+ return p + 1;
+ else
+ return path;
+}
diff --git a/src/misc.h b/src/misc.h
new file mode 100644
index 0000000..404c76a
--- /dev/null
+++ b/src/misc.h
@@ -0,0 +1,40 @@
+/*
+ Copyright 2006,2007,2008 Martin Pärtel <martin.partel@gmail.com>
+
+ This file is part of bindfs.
+
+ bindfs is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 2 of the License, or
+ (at your option) any later version.
+
+ bindfs is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with bindfs. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef INC_BINDFS_MISC_H
+#define INC_BINDFS_MISC_H
+
+/* Counts the number of times ch occurs in s. */
+int count_chars(const char *s, char ch);
+
+/* Counts the number of times sub occurs in s. */
+int count_substrs(const char *s, const char *sub);
+
+/* Creates a duplicate string of all the characters in s before
+ an end character is reached. */
+char *strdup_until(const char *s, const char *endchars);
+
+/* Returns a pointer to the first character after the
+ final slash of path, or path itself if it contains no slashes.
+ If the path ends with a slash, then the result is an empty
+ string.
+ Returns NULL if path is NULL. */
+const char *my_basename(const char *path);
+
+#endif
diff --git a/src/permchain.c b/src/permchain.c
new file mode 100644
index 0000000..6e99e7b
--- /dev/null
+++ b/src/permchain.c
@@ -0,0 +1,325 @@
+/*
+ Copyright 2006,2007,2008 Martin Pärtel <martin.partel@gmail.com>
+
+ This file is part of bindfs.
+
+ bindfs is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 2 of the License, or
+ (at your option) any later version.
+
+ bindfs is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with bindfs. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "permchain.h"
+#include <assert.h>
+#include <string.h>
+#include <ctype.h>
+#include "misc.h"
+#include "debug.h"
+
+/* constants for permchain->flags */
+#define PC_APPLY_FILES 1
+#define PC_APPLY_DIRS 2
+#define PC_FLAGS_DEFAULT ((PC_APPLY_FILES) | (PC_APPLY_DIRS))
+
+struct permchain {
+ mode_t mask; /* which permissions to apply to */
+ char op; /* one of '=', '+', '-', 'o' (octal) or '\0' */
+ union {
+ char operands[16]; /* a subset of rwxXstugo */
+ unsigned int octal;
+ };
+ int flags;
+ struct permchain *next;
+};
+
+struct permchain *permchain_create()
+{
+ struct permchain *pc = malloc(sizeof(struct permchain));
+ pc->mask = 0000;
+ pc->op = '\0';
+ memset(pc->operands, '\0', sizeof(pc->operands));
+ pc->next = NULL;
+ pc->flags = PC_FLAGS_DEFAULT;
+ return pc;
+}
+
+
+static int add_chmod_rule_to_permchain(const char *start, const char *end,
+ struct permchain *pc);
+static int add_octal_rule_to_permchain(const char *start, const char *end,
+ struct permchain *pc);
+static mode_t modebits_to_all(int perms); /* e.g. 5 -> 0555 */
+
+
+
+static int add_chmod_rule_to_permchain(const char *start, const char *end,
+ struct permchain *pc)
+{
+ int ret = -1;
+
+ int len = end - start;
+ char *buf = alloca((len + 1) * sizeof(char));
+ const char *p = buf;
+
+ enum {LHS, RHS} state = LHS;
+ struct permchain *newpc = permchain_create();
+ char *operands_ptr = newpc->operands;
+
+ newpc->flags = 0; /* Reset to PC_FLAGS_DEFAULT in the end if not modified */
+
+ memcpy(buf, start, len);
+ buf[len] = '\0';
+
+ while (*p != '\0') {
+ if (state == LHS) {
+ switch (*p) {
+ case 'u':
+ newpc->mask |= 0700;
+ break;
+ case 'g':
+ newpc->mask |= 0070;
+ break;
+ case 'o':
+ newpc->mask |= 0007;
+ break;
+ case 'a':
+ newpc->mask = 0777;
+ break;
+ case 'f':
+ newpc->flags |= PC_APPLY_FILES;
+ break;
+ case 'd':
+ newpc->flags |= PC_APPLY_DIRS;
+ break;
+ case '=':
+ case '+':
+ case '-':
+ if (p == buf) /* first char -> default to 'a' */
+ newpc->mask = 0777;
+ newpc->op = *p;
+ state = RHS;
+ break;
+ default:
+ goto error;
+ }
+ } else {
+ switch (*p) {
+ case 'r':
+ case 'w':
+ case 'x':
+ case 'X':
+ case 'D':
+ case 's':
+ case 't':
+ case 'u':
+ case 'g':
+ case 'o':
+ if (!strchr(newpc->operands, *p)) {
+ *(operands_ptr++) = *p;
+ }
+ break;
+ default:
+ goto error;
+ }
+ }
+
+ ++p;
+ }
+
+ ret = 0;
+error:
+ if (newpc->flags == 0)
+ newpc->flags = PC_FLAGS_DEFAULT;
+ if (ret == 0)
+ permchain_cat(pc, newpc);
+ else
+ permchain_destroy(newpc);
+ return ret;
+}
+
+static int add_octal_rule_to_permchain(const char *start, const char *end,
+ struct permchain *pc)
+{
+ struct permchain *newpc = permchain_create();
+ long mode = strtol(start, NULL, 8);
+
+ if (mode < 0 || mode > 0777) {
+ permchain_destroy(newpc);
+ return -1;
+ }
+
+ newpc->mask = 0777;
+ newpc->op = 'o';
+ newpc->octal = mode;
+
+ permchain_cat(pc, newpc);
+ return 0;
+}
+
+int add_chmod_rules_to_permchain(const char *rule, struct permchain *pc)
+{
+ int ret = -1;
+ const char *start, *end;
+ struct permchain *newpc = permchain_create();
+
+ assert(rule != 0);
+
+ end = start = rule;
+
+ while (*end != '\0') {
+ /* find delimiter or end of list */
+ while (*end != ',' && *end != ':' && *end != '\0')
+ ++end;
+
+ if (start == end) /* empty rule */
+ goto error;
+
+ assert(start < end);
+
+ if (isdigit(*start)) {
+ if (add_octal_rule_to_permchain(start, end, newpc) != 0)
+ goto error;
+
+ } else {
+ if (add_chmod_rule_to_permchain(start, end, newpc) != 0)
+ goto error;
+ }
+
+ if (*end == ',' || *end == ':')
+ start = ++end;
+ }
+
+ ret = 0;
+error:
+ if (ret == 0)
+ permchain_cat(pc, newpc);
+ else
+ permchain_destroy(newpc);
+ return ret;
+}
+
+void permchain_cat(struct permchain *left, struct permchain *right)
+{
+ while (left->next != NULL)
+ left = left->next;
+ left->next = right;
+}
+
+mode_t modebits_to_all(int perms)
+{
+ mode_t m = perms;
+ m |= perms << 3;
+ m |= perms << 6;
+ return m;
+}
+
+mode_t permchain_apply(struct permchain *pc, mode_t tgtmode)
+{
+ mode_t original_mode = tgtmode;
+ mode_t mode = 0000;
+ const char *p;
+
+ while (pc != NULL) {
+ #if BINDFS_DEBUG
+ if (pc->op == 'o')
+ DPRINTF("STAT MODE: %o, op = %c %o", tgtmode, pc->op, pc->octal);
+ else
+ DPRINTF("STAT MODE: %o, op = %c%s", tgtmode, pc->op, pc->operands);
+ #endif
+
+ if (pc->op == '\0') {
+ pc = pc->next;
+ continue;
+ }
+
+ if ((S_ISDIR(tgtmode) && !(pc->flags & PC_APPLY_DIRS))
+ ||
+ (!S_ISDIR(tgtmode) && !(pc->flags & PC_APPLY_FILES))) {
+
+ pc = pc->next;
+ continue;
+ }
+
+ if (pc->op == '=' || pc->op == '+' || pc->op == '-') {
+
+ mode = 0000;
+
+ for (p = pc->operands; *p != '\0'; ++p) {
+ switch (*p) {
+ case 'r':
+ mode |= 0444;
+ break;
+ case 'w':
+ mode |= 0222;
+ break;
+ case 'x':
+ mode |= 0111;
+ break;
+ case 'X':
+ if (S_ISDIR(original_mode) || ((original_mode & 0111) != 0))
+ mode |= 0111;
+ break;
+ case 'D':
+ if (S_ISDIR(original_mode))
+ mode |= 0111;
+ break;
+ case 's':
+ case 't':
+ /* ignored */
+ break;
+ case 'u':
+ mode |= modebits_to_all((original_mode & 0700) >> 6);
+ break;
+ case 'g':
+ mode |= modebits_to_all((original_mode & 0070) >> 3);
+ break;
+ case 'o':
+ mode |= modebits_to_all(original_mode & 0007);
+ break;
+ default:
+ assert(0);
+ }
+ }
+ mode &= pc->mask;
+ }
+
+ switch (pc->op) {
+ case '=':
+ tgtmode = (tgtmode & ~pc->mask) | mode;
+ break;
+ case '+':
+ tgtmode |= mode;
+ break;
+ case '-':
+ tgtmode &= ~0777 | ~mode;
+ break;
+ case 'o':
+ tgtmode = (tgtmode & ~0777) | pc->octal;
+ break;
+ default:
+ assert(0);
+ }
+ pc = pc->next;
+ DPRINTF(" =>: %o", tgtmode);
+ }
+ return tgtmode;
+}
+
+void permchain_destroy(struct permchain *pc)
+{
+ struct permchain *next;
+ while (pc) {
+ next = pc->next;
+ free(pc);
+ pc = next;
+ }
+}
+
diff --git a/src/permchain.h b/src/permchain.h
new file mode 100644
index 0000000..0f268f0
--- /dev/null
+++ b/src/permchain.h
@@ -0,0 +1,55 @@
+/*
+ Copyright 2006,2007,2008 Martin Pärtel <martin.partel@gmail.com>
+
+ This file is part of bindfs.
+
+ bindfs is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 2 of the License, or
+ (at your option) any later version.
+
+ bindfs is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with bindfs. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef INC_BINDFS_PERMCHAIN_H
+#define INC_BINDFS_PERMCHAIN_H
+
+
+#include <config.h>
+
+#define _GNU_SOURCE
+
+#include <stdlib.h>
+#ifdef HAVE_SYS_TYPES_H
+#include <sys/types.h>
+#endif
+#ifdef HAVE_SYS_STAT_H
+#include <sys/stat.h>
+#endif
+#include <unistd.h>
+
+struct permchain;
+
+struct permchain *permchain_create();
+
+/* Parses chmod arguments like 0777, a=rX, og-rwx etc.
+ Multiple rules may be given, separated with commas or colons.
+ Unlike the ordinary chmod command, the octal specification may be
+ present in a comma/colon-separated list.
+ Returns 0 on success. On failure, pc will not be modified. */
+int add_chmod_rules_to_permchain(const char *rule, struct permchain *pc);
+
+/* Links 'right' to the end of 'left'. Don't destroy 'right' after this. */
+void permchain_cat(struct permchain *left, struct permchain *right);
+
+mode_t permchain_apply(struct permchain *pc, mode_t tgtmode);
+
+void permchain_destroy(struct permchain *pc);
+
+#endif
diff --git a/src/userinfo.c b/src/userinfo.c
new file mode 100644
index 0000000..df0b6e6
--- /dev/null
+++ b/src/userinfo.c
@@ -0,0 +1,177 @@
+/*
+ Copyright 2006,2007,2008 Martin Pärtel <martin.partel@gmail.com>
+
+ This file is part of bindfs.
+
+ bindfs is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 2 of the License, or
+ (at your option) any later version.
+
+ bindfs is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with bindfs. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "userinfo.h"
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+
+int user_uid(const char *username, uid_t *ret)
+{
+ struct passwd pwbuf, *pwbufp = NULL;
+ char *buf;
+ size_t buflen;
+ int res;
+
+ uid_t uid;
+ char *endptr;
+
+ /* Check whether the string is a numerical UID */
+ uid = strtol(username, &endptr, 10);
+ if (*endptr == '\0') {
+ buflen = 1024;
+ buf = malloc(buflen);
+ res = getpwuid_r(uid, &pwbuf, buf, buflen, &pwbufp);
+ if (res != 0) {
+ if (res != ERANGE) { /* don't care if buffer was too small */
+ free(buf);
+ return 0;
+ }
+ }
+ free(buf);
+ *ret = uid;
+ return 1;
+ }
+
+ /* Process user name */
+ buflen = 1024;
+ buf = malloc(buflen);
+
+ res = getpwnam_r(username, &pwbuf, buf, buflen, &pwbufp);
+ while(res == ERANGE) {
+ buflen *= 2;
+ buf = realloc(buf, buflen);
+ res = getpwnam_r(username, &pwbuf, buf, buflen, &pwbufp);
+ }
+
+ if (pwbufp == NULL) {
+ free(buf);
+ return 0;
+ }
+
+ *ret = pwbuf.pw_uid;
+ free(buf);
+ return 1;
+}
+
+
+int group_gid(const char *groupname, gid_t *ret)
+{
+ struct group gbuf, *gbufp = NULL;
+ char *buf;
+ size_t buflen;
+ int res;
+
+ gid_t gid;
+ char *endptr;
+
+ /* Check whether the string is a numerical GID */
+ gid = strtol(groupname, &endptr, 10);
+ if (*endptr == '\0') {
+ buflen = 1024;
+ buf = malloc(buflen);
+ res = getgrgid_r(gid, &gbuf, buf, buflen, &gbufp);
+ if (res != 0) {
+ if (res != ERANGE) { /* don't care if buffer was too small */
+ free(buf);
+ return 0;
+ }
+ }
+ free(buf);
+ *ret = gid;
+ return 1;
+ }
+
+ /* Process group name */
+ buflen = 1024;
+ buf = malloc(buflen);
+
+ res = getgrnam_r(groupname, &gbuf, buf, buflen, &gbufp);
+ while(res == ERANGE) {
+ buflen *= 2;
+ buf = realloc(buf, buflen);
+ res = getgrnam_r(groupname, &gbuf, buf, buflen, &gbufp);
+ }
+
+ if (gbufp == NULL) {
+ free(buf);
+ return 0;
+ }
+
+ *ret = gbuf.gr_gid;
+ free(buf);
+ return 1;
+}
+
+
+int user_belongs_to_group(uid_t uid, gid_t gid)
+{
+ struct passwd pwbuf, *pwbufp = NULL;
+ struct group grbuf, *grbufp = NULL;
+ char *buf;
+ size_t buflen;
+
+ int member;
+ uid_t member_uid;
+
+ int res;
+
+ buflen = 1024;
+ buf = malloc(buflen);
+
+ res = getpwuid_r(uid, &pwbuf, buf, buflen, &pwbufp);
+ while(res == ERANGE) {
+ buflen *= 2;
+ buf = realloc(buf, buflen);
+ res = getpwuid_r(uid, &pwbuf, buf, buflen, &pwbufp);
+ }
+
+ if (pwbufp == NULL)
+ goto no;
+
+ if (gid == pwbuf.pw_gid)
+ goto yes;
+
+ /* we reuse the same buf because we don't need the passwd info any more */
+ res = getgrgid_r(gid, &grbuf, buf, buflen, &grbufp);
+ while(res == ERANGE) {
+ buflen *= 2;
+ buf = realloc(buf, buflen);
+ res = getgrgid_r(gid, &grbuf, buf, buflen, &grbufp);
+ }
+
+ if (grbufp == NULL)
+ goto no;
+
+ for (member = 0; grbuf.gr_mem[member] != NULL; ++member) {
+ if (user_uid(grbuf.gr_mem[member], &member_uid))
+ if (member_uid == uid)
+ goto yes;
+ }
+
+ goto no;
+
+yes:
+ free(buf);
+ return 1;
+no:
+ free(buf);
+ return 0;
+}
diff --git a/src/userinfo.h b/src/userinfo.h
new file mode 100644
index 0000000..26cdb00
--- /dev/null
+++ b/src/userinfo.h
@@ -0,0 +1,39 @@
+/*
+ Copyright 2006,2007,2008 Martin Pärtel <martin.partel@gmail.com>
+
+ This file is part of bindfs.
+
+ bindfs is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 2 of the License, or
+ (at your option) any later version.
+
+ bindfs is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with bindfs. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef INC_BINDFS_USERINFO_H
+#define INC_BINDFS_USERINFO_H
+
+#include <config.h>
+
+#ifdef HAVE_SYS_TYPES_H
+#include <sys/types.h>
+#endif
+#include <pwd.h>
+#include <grp.h>
+
+/* Misc. reentrant helpers for handling user data.
+ Return non-zero on success/true and 0 on failure/false. */
+
+int user_uid(const char *username, uid_t *ret);
+int group_gid(const char *groupname, gid_t *ret);
+
+int user_belongs_to_group(uid_t uid, gid_t gid);
+
+#endif
diff --git a/tests/Makefile.am b/tests/Makefile.am
new file mode 100644
index 0000000..79b8dee
--- /dev/null
+++ b/tests/Makefile.am
@@ -0,0 +1,2 @@
+
+TESTS = test_bindfs.rb
diff --git a/tests/common.rb b/tests/common.rb
new file mode 100755
index 0000000..f15110c
--- /dev/null
+++ b/tests/common.rb
@@ -0,0 +1,154 @@
+#!/usr/bin/env ruby
+#
+# Copyright 2006,2007,2008,2009,2010 Martin Pärtel <martin.partel@gmail.com>
+#
+# This file is part of bindfs.
+#
+# bindfs is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 2 of the License, or
+# (at your option) any later version.
+#
+# bindfs is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with bindfs. If not, see <http://www.gnu.org/licenses/>.
+#
+
+require 'fileutils.rb'
+include FileUtils
+
+# Set the default umask for all tests
+File.umask 0022
+
+EXECUTABLE_PATH = '../src/bindfs'
+TESTDIR_NAME = 'tmp_test_bindfs'
+
+# If set to an array of test names, only those will be run
+$only_these_tests = nil
+
+# Prepares a test environment with a mounted directory
+def testenv(bindfs_args, &block)
+
+ testcase_title = bindfs_args
+
+ return unless $only_these_tests == nil or $only_these_tests.member? testcase_title
+
+ puts "--- #{testcase_title} ---"
+ puts "[ #{bindfs_args} ]"
+
+ begin
+ Dir.mkdir TESTDIR_NAME
+ rescue Exception => ex
+ $stderr.puts "ERROR creating testdir at #{TESTDIR_NAME}"
+ $stderr.puts ex
+ exit! 1
+ end
+
+ begin
+ Dir.chdir TESTDIR_NAME
+ Dir.mkdir 'src'
+ Dir.mkdir 'mnt'
+ rescue Exception => ex
+ $stderr.puts "ERROR preparing testdir at #{TESTDIR_NAME}"
+ $stderr.puts ex
+ exit! 1
+ end
+
+ bindfs_pid = nil
+ begin
+ cmd = "../#{EXECUTABLE_PATH} #{bindfs_args} src mnt"
+ bindfs_pid = Process.fork do
+ exec cmd
+ exit! 127
+ end
+ rescue Exception => ex
+ $stderr.puts "ERROR running bindfs"
+ $stderr.puts ex
+ Dir.chdir '..'
+ system("rm -Rf #{TESTDIR_NAME}")
+ exit! 1
+ end
+
+ # Wait for bindfs to daemonize itself
+ Process.wait bindfs_pid
+
+ # TODO: check that mounting was successful
+
+ testcase_ok = true
+ begin
+ yield
+ rescue Exception => ex
+ $stderr.puts "ERROR: testcase `#{testcase_title}' failed"
+ $stderr.puts ex
+ $stderr.puts ex.backtrace
+ testcase_ok = false
+ end
+
+ begin
+ unless system(umount_cmd + ' mnt')
+ raise Exception.new(umount_cmd + " failed with status #{$?}")
+ end
+ rescue Exception => ex
+ $stderr.puts "ERROR: failed to umount"
+ $stderr.puts ex
+ $stderr.puts ex.backtrace
+ testcase_ok = false
+ end
+
+ begin
+ Dir.chdir '..'
+ rescue Exception => ex
+ $stderr.puts "ERROR: failed to exit test env"
+ $stderr.puts ex
+ $stderr.puts ex.backtrace
+ exit! 1
+ end
+
+ unless system "rm -Rf #{TESTDIR_NAME}"
+ $stderr.puts "ERROR: failed to clear test directory"
+ exit! 1
+ end
+
+ if testcase_ok
+ puts "OK"
+ else
+ exit! 1
+ end
+end
+
+# Like testenv but skips the test if not running as root
+def root_testenv(bindfs_args, &block)
+ if Process.uid == 0
+ testenv(bindfs_args, &block)
+ else
+ puts "--- #{bindfs_args} ---"
+ puts "[ #{bindfs_args} ]"
+ puts "SKIP (requires root)"
+ end
+end
+
+def umount_cmd
+ if `which fusermount`.strip.empty?
+ then 'umount'
+ else 'fusermount -uz'
+ end
+end
+
+def assert
+ raise Exception.new('test failed') unless yield
+end
+
+def assert_exception(ex)
+ begin
+ yield
+ rescue ex
+ return
+ end
+ raise Exception.new('expected exception ' + ex.to_s)
+end
+
+
diff --git a/tests/test_bindfs.rb b/tests/test_bindfs.rb
new file mode 100755
index 0000000..8d5b18f
--- /dev/null
+++ b/tests/test_bindfs.rb
@@ -0,0 +1,206 @@
+#!/usr/bin/env ruby
+#
+# Copyright 2006,2007,2008,2009,2010 Martin Pärtel <martin.partel@gmail.com>
+#
+# This file is part of bindfs.
+#
+# bindfs is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 2 of the License, or
+# (at your option) any later version.
+#
+# bindfs is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with bindfs. If not, see <http://www.gnu.org/licenses/>.
+#
+
+require 'common.rb'
+
+include Errno
+
+# FileUtils.chown turned out to be quite buggy in Ruby 1.8.7,
+# so we'll use File.chown instead.
+def chown(user, group, list)
+ user = Etc.getpwnam(user).uid if user.is_a? String
+ group = Etc.getgrnam(group).gid if group.is_a? String
+
+ list = [list] unless list.is_a? Array
+ for file in list
+ File.chown(user, group, file)
+ end
+end
+
+# Treat parameters as test names and run only those
+$only_these_tests = ARGV unless ARGV.empty?
+
+# Some useful shorthands
+$nobody_uid = nobody_uid = Etc.getpwnam('nobody').uid
+$nogroup_gid = nogroup_gid = Etc.getgrnam('nogroup').gid
+
+
+testenv("") do
+ assert { File.basename(pwd) == TESTDIR_NAME }
+end
+
+testenv("-u nobody -g nogroup") do
+ touch('src/file')
+
+ assert { File.stat('mnt/file').uid == nobody_uid }
+ assert { File.stat('mnt/file').gid == nogroup_gid }
+end
+
+testenv("-p 0600:u+D") do
+ touch('src/file')
+ chmod(0777, 'src/file')
+
+ assert { File.stat('mnt/file').mode & 0777 == 0600 }
+end
+
+testenv("--chmod-deny") do
+ touch('src/file')
+
+ assert_exception(EPERM) { chmod(0777, 'mnt/file') }
+end
+
+testenv("-u nobody -m #{Process.uid} -p 0600,u+D") do
+ touch('src/file')
+
+ assert { File.stat('mnt/file').uid == Process.uid }
+end
+
+testenv("--create-with-perms=og=r:ogd+x") do
+ touch('src/file')
+ mkdir('src/dir')
+
+ assert { File.stat('mnt/file').mode & 0077 == 0044 }
+ assert { File.stat('mnt/dir').mode & 0077 == 0055 }
+end
+
+testenv("--ctime-from-mtime") do
+ sf = 'src/file'
+ mf = 'mnt/file'
+
+ touch(sf)
+ sleep(1.1)
+ chmod(0777, mf)
+
+ assert { File.stat(mf).ctime == File.stat(mf).mtime }
+ assert { File.stat(sf).ctime > File.stat(sf).mtime }
+
+end
+
+# Define expectation for changing [uid, gid, both]
+# for each combination of chown/chgrp flags.
+chown_chgrp_test_cases = {
+ :chown_normal => {
+ :chgrp_normal => [:uid, :gid, :both],
+ :chgrp_ignore => [:uid, nil, :uid],
+ :chgrp_deny => [:uid, EPERM, EPERM]
+ },
+ :chown_ignore => {
+ :chgrp_normal => [nil, :gid, :gid],
+ :chgrp_ignore => [nil, nil, nil],
+ :chgrp_deny => [nil, EPERM, EPERM]
+ },
+ :chown_deny => {
+ :chgrp_normal => [EPERM, :gid, EPERM],
+ :chgrp_ignore => [EPERM, nil, EPERM],
+ :chgrp_deny => [EPERM, EPERM, EPERM]
+ }
+}
+
+def run_chown_chgrp_test_case(chown_flag, chgrp_flag, expectations)
+ flags = [chown_flag, chgrp_flag].map do |flag|
+ '--' + flag.to_s.sub('_', '-')
+ end.join ' '
+
+ srcfile = 'src/file'
+ mntfile = 'mnt/file'
+ tests = [
+ lambda { chown('nobody', nil, mntfile) },
+ lambda { chown(nil, 'nogroup', mntfile) },
+ lambda { chown('nobody', 'nogroup', mntfile) }
+ ]
+
+ for testcase, expect in tests.zip expectations
+ root_testenv(flags) do
+ touch(srcfile)
+ if expect.respond_to? :exception
+ assert_exception(expect) { testcase.call }
+ else
+ testcase.call
+ uid = File.stat(srcfile).uid
+ gid = File.stat(srcfile).gid
+
+ case expect
+ when :uid
+ assert { uid == $nobody_uid }
+ assert { gid != $nogroup_gid }
+ when :gid
+ assert { uid != $nobody_uid }
+ assert { gid == $nogroup_gid }
+ when :both
+ assert { uid == $nobody_uid }
+ assert { gid == $nogroup_gid }
+ when nil
+ assert { uid != $nobody_uid }
+ assert { gid != $nogroup_gid }
+ end
+ end
+ end
+ end
+end
+
+chown_chgrp_test_cases.each do |chown_flag, more|
+ more.each do |chgrp_flag, expectations|
+ run_chown_chgrp_test_case(chown_flag, chgrp_flag, expectations)
+ end
+end
+
+root_testenv("--chown-deny") do
+ touch('src/file')
+
+ assert_exception(EPERM) { chown('nobody', nil, 'mnt/file') }
+ assert_exception(EPERM) { chown('nobody', 'nogroup', 'mnt/file') }
+ chown(nil, 'nogroup', 'mnt/file')
+end
+
+testenv("--chmod-allow-x --chmod-ignore") do
+ touch('src/file')
+
+ chmod(01700, 'src/file') # sticky bit set
+
+ chmod(00077, 'mnt/file') # should change x bits; should not unset sticky bit
+ assert { File.stat('src/file').mode & 07777 == 01611 }
+
+
+ mkdir('src/dir')
+ chmod(0700, 'src/dir')
+ chmod(0077, 'mnt/dir') # bits on dir should not change
+ assert { File.stat('src/dir').mode & 0777 == 0700 }
+end
+
+testenv("--chmod-deny --chmod-allow-x") do
+ touch('src/file')
+
+ chmod(0700, 'src/file')
+
+ chmod(0700, 'mnt/file') # no-op chmod should work
+
+ assert_exception(EPERM) { chmod(0777, 'mnt/file') }
+ assert_exception(EPERM) { chmod(0000, 'mnt/file') }
+ assert_exception(EPERM) { chmod(01700, 'mnt/file') } # sticky bit
+
+ chmod(0611, 'mnt/file') # chmod that only changes x-bits should work
+ assert { File.stat('src/file').mode & 07777 == 00611 }
+
+
+ mkdir('src/dir')
+ chmod(0700, 'src/dir')
+ assert_exception(EPERM) { chmod(0700, 'mnt/dir') } # chmod on dir should not work
+end
+
diff --git a/tests/test_concurrent.rb b/tests/test_concurrent.rb
new file mode 100755
index 0000000..f07c031
--- /dev/null
+++ b/tests/test_concurrent.rb
@@ -0,0 +1,45 @@
+#!/usr/bin/env ruby
+#
+# Copyright 2006,2007,2008,2009,2010 Martin Pärtel <martin.partel@gmail.com>
+#
+# This file is part of bindfs.
+#
+# bindfs is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 2 of the License, or
+# (at your option) any later version.
+#
+# bindfs is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with bindfs. If not, see <http://www.gnu.org/licenses/>.
+#
+require 'common.rb'
+
+raise "Please run this as root" unless Process.uid == 0
+
+raise "Give two users as parameters" unless ARGV.length == 2
+
+user1 = ARGV[0]
+user2 = ARGV[1]
+
+raise "Give two _different_ users as parameters" unless user1 != user2
+
+testenv "--mirror=#{user1},#{user2}" do
+ touch('src/file')
+
+ count = 0
+ 10.times do |i|
+ out1 = `su #{user1} -c "stat --format=%U mnt/file"`
+ out2 = `su #{user2} -c "stat --format=%U mnt/file"`
+
+ out1.strip!
+ out2.strip!
+ puts "#{i+1}: #{out1} #{out2}"
+ raise "FAIL: #{user1} saw #{out1} on iter #{i}" unless out1 == user1
+ raise "FAIL: #{user2} saw #{out2} on iter #{i}" unless out2 == user2
+ end
+end