commit 2135ad723dce9654f1844ec4f76f06c9e240be98 Author: Martin Pärtel Date: Fri Jul 8 19:09:07 2011 +0300 Initial commit to git. 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. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 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. + + , 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 + + * 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 + + * Added options to control the behavior of chown and chgrp. + * Released 1.8.4 + +2009-03-28 Martin Pärtel + + * Added --ctime-from-mtime. Contributed by Shez. + * Added --chmod-allow-x. + * Released 1.8.3 + +2008-12-14 Martin Pärtel + + * Converted ChangeLog to UTF-8. + +2008-12-13 Martin Pärtel + + * 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 + + * 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 + + * Fixed segfault in option parsing on platforms where + sizeof(int) != sizeof(long), such as amd64. + * Released 1.8 + +2008-07-08 Martin Pärtel + + * 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 + + * --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 + + * Added copyright messages to each source file. + * Escaped man-page dashes, since unescaped dashes are treated as + hyphens. + +2008-06-19 Martin Pärtel + + * Fixed missing '=' signs in the man-page. + +2008-05-14 Martin Pärtel + + * 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 + + * 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 + + * Fixed an embarrassing segfault while parsing --mirror arguments. + Thanks to Stefan Kost for reporting it! + * Released 1.5 + +2007-12-31 Martin Pärtel + + * Made compatible with Mac OS X with the help of Theocharis Athanasakis. + * Released 1.4.2 + +2007-11-09 Martin Pärtel + + * 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 + + * 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 + + * 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 + + * Added the user_for_create and group_for_create options + + +2007-03-24 Martin Pärtel + + * Fixed minor errors in man-page. + * Released 1.2.1 + +2007-03-03 Martin Pärtel + + * 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 + + * 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 + + +.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 + + 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 . + + + 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 + + This program can be distributed under the terms of the GNU GPL. + See the file COPYING. + --- + +*/ + +#include + +/* For pread/pwrite */ +#define _XOPEN_SOURCE 500 + +#include +#include +#include +#include +#include +#ifdef HAVE_SYS_TYPES_H +#include +#endif +#ifdef HAVE_SYS_STAT_H +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef HAVE_SETXATTR +#include +#endif + +#include +#include + +#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 + + 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 . +*/ + +#ifndef INC_BINDFS_DEBUG_H +#define INC_BINDFS_DEBUG_H + +#include + +#if BINDFS_DEBUG +#include +#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 + + 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 . +*/ + +#include "misc.h" +#include +#include +#include + +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 + + 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 . +*/ + +#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 + + 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 . +*/ + +#include "permchain.h" +#include +#include +#include +#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 + + 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 . +*/ + +#ifndef INC_BINDFS_PERMCHAIN_H +#define INC_BINDFS_PERMCHAIN_H + + +#include + +#define _GNU_SOURCE + +#include +#ifdef HAVE_SYS_TYPES_H +#include +#endif +#ifdef HAVE_SYS_STAT_H +#include +#endif +#include + +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 + + 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 . +*/ + +#include "userinfo.h" +#include +#include +#include + + +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 + + 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 . +*/ + +#ifndef INC_BINDFS_USERINFO_H +#define INC_BINDFS_USERINFO_H + +#include + +#ifdef HAVE_SYS_TYPES_H +#include +#endif +#include +#include + +/* 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 +# +# 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 . +# + +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 +# +# 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 . +# + +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 +# +# 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 . +# +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