Browse Source

audit (and fix) file permissions and ownership. closes #148

pull/152/head
Antonio SJ Musumeci 9 years ago
parent
commit
f6d396c30d
  1. 9
      Makefile
  2. 7
      README.md
  3. 133
      tools/fsck.mergerfs

9
Makefile

@ -93,10 +93,12 @@ EXEC_PREFIX = $(PREFIX)
DATAROOTDIR = $(PREFIX)/share DATAROOTDIR = $(PREFIX)/share
DATADIR = $(DATAROOTDIR) DATADIR = $(DATAROOTDIR)
BINDIR = $(EXEC_PREFIX)/bin BINDIR = $(EXEC_PREFIX)/bin
SBINDIR = $(EXEC_PREFIX)/sbin
MANDIR = $(DATAROOTDIR)/man MANDIR = $(DATAROOTDIR)/man
MAN1DIR = $(MANDIR)/man1 MAN1DIR = $(MANDIR)/man1
INSTALLBINDIR = $(DESTDIR)$(BINDIR) INSTALLBINDIR = $(DESTDIR)$(BINDIR)
INSTALLSBINDIR = $(DESTDIR)$(SBINDIR)
INSTALLMAN1DIR = $(DESTDIR)$(MAN1DIR) INSTALLMAN1DIR = $(DESTDIR)$(MAN1DIR)
ifeq ($(XATTR_AVAILABLE),0) ifeq ($(XATTR_AVAILABLE),0)
@ -116,6 +118,8 @@ $(TARGET): src/version.hpp obj/obj-stamp $(OBJ)
clone: $(TARGET) clone: $(TARGET)
$(LN) -s $< $@ $(LN) -s $< $@
fsck.mergerfs: tools/fsck.mergerfs
changelog: changelog:
$(GIT2DEBCL) --name $(TARGET) > ChangeLog $(GIT2DEBCL) --name $(TARGET) > ChangeLog
@ -145,7 +149,7 @@ clean: rpm-clean
distclean: clean distclean: clean
$(GIT) clean -fd $(GIT) clean -fd
install: install-base install-clone install-man
install: install-base install-clone install-tools install-man
install-base: $(TARGET) install-base: $(TARGET)
$(INSTALL) -v -m 0755 -D "$(TARGET)" "$(INSTALLBINDIR)/$(TARGET)" $(INSTALL) -v -m 0755 -D "$(TARGET)" "$(INSTALLBINDIR)/$(TARGET)"
@ -153,6 +157,9 @@ install-base: $(TARGET)
install-clone: clone install-clone: clone
$(CP) -a $< "$(INSTALLBINDIR)/$<" $(CP) -a $< "$(INSTALLBINDIR)/$<"
install-tools: fsck.mergerfs
$(INSTALL) -v -m 0755 -D "tools/$<" "$(INSTALLSBINDIR)/$<"
install-man: $(MANPAGE) install-man: $(MANPAGE)
$(INSTALL) -v -m 0644 -D "$(MANPAGE)" "$(INSTALLMAN1DIR)/$(MANPAGE)" $(INSTALL) -v -m 0644 -D "$(MANPAGE)" "$(INSTALLMAN1DIR)/$(MANPAGE)"

7
README.md

@ -272,11 +272,16 @@ A B C
/mnt/b/full/path/to/A /mnt/b/full/path/to/A
``` ```
# TOOLING
* /usr/sbin/fsck.mergerfs: Provides permissions and ownership auditing and the ability to fix them.
# TIPS / NOTES # TIPS / NOTES
* If you don't see some directories / files you expect in a merged point be sure the user has permission to all the underlying directories. If `/drive0/a` has is owned by `root:root` with ACLs set to `0700` and `/drive1/a` is `root:root` and `0755` you'll see only `/drive1/a`.
* If you don't see some directories / files you expect in a merged point be sure the user has permission to all the underlying directories. If `/drive0/a` has is owned by `root:root` with ACLs set to `0700` and `/drive1/a` is `root:root` and `0755` you'll see only `/drive1/a`. Use `fsck.mergerfs` to audit the drive for out of sync permissions.
* Since POSIX gives you only error or success on calls its difficult to determine the proper behavior when applying the behavior to multiple targets. Generally if something succeeds when reading it returns the data it can. If something fails when making an action we continue on and return the last error. * Since POSIX gives you only error or success on calls its difficult to determine the proper behavior when applying the behavior to multiple targets. Generally if something succeeds when reading it returns the data it can. If something fails when making an action we continue on and return the last error.
* The recommended options are **defaults,allow_other**. The **allow_other** is to allow users who are not the one which executed mergerfs access to the mountpoint. **defaults** is described above and should offer the best performance. It's possible that if you're running on an older platform the **splice** features aren't available and could error. In that case simply use the other options manually. * The recommended options are **defaults,allow_other**. The **allow_other** is to allow users who are not the one which executed mergerfs access to the mountpoint. **defaults** is described above and should offer the best performance. It's possible that if you're running on an older platform the **splice** features aren't available and could error. In that case simply use the other options manually.
* If write performance is valued more than read it may be useful to enable **direct_io**.
* Remember that some policies mixed with some functions may result in strange behaviors. Not that some of these behaviors and race conditions couldn't happen outside **mergerfs** but that they are far more likely to occur on account of attempt to merge together multiple sources of data which could be out of sync due to the different policies. * Remember that some policies mixed with some functions may result in strange behaviors. Not that some of these behaviors and race conditions couldn't happen outside **mergerfs** but that they are far more likely to occur on account of attempt to merge together multiple sources of data which could be out of sync due to the different policies.
* An example: [Kodi](http://kodi.tv) and [Plex](http://plex.tv) can apparently use directory [mtime](http://linux.die.net/man/2/stat) to more efficiently determine whether or not to scan for new content rather than simply performing a full scan. If using the current default **getattr** policy of **ff** its possible **Kodi** will miss an update on account of it returning the first directory found's **stat** info and its a later directory on another mount which had the **mtime** recently updated. To fix this you will want to set **func.getattr=newest**. Remember though that this is just **stat**. If the file is later **open**'ed or **unlink**'ed and the policy is different for those then a completely different file or directory could be acted on. * An example: [Kodi](http://kodi.tv) and [Plex](http://plex.tv) can apparently use directory [mtime](http://linux.die.net/man/2/stat) to more efficiently determine whether or not to scan for new content rather than simply performing a full scan. If using the current default **getattr** policy of **ff** its possible **Kodi** will miss an update on account of it returning the first directory found's **stat** info and its a later directory on another mount which had the **mtime** recently updated. To fix this you will want to set **func.getattr=newest**. Remember though that this is just **stat**. If the file is later **open**'ed or **unlink**'ed and the policy is different for those then a completely different file or directory could be acted on.
* Due to previously mentioned issues its generally best to set **category** wide policies rather than individual **func**'s. This will help limit the confusion of tools such as [rsync](http://linux.die.net/man/1/rsync). * Due to previously mentioned issues its generally best to set **category** wide policies rather than individual **func**'s. This will help limit the confusion of tools such as [rsync](http://linux.die.net/man/1/rsync).

133
tools/fsck.mergerfs

@ -0,0 +1,133 @@
#!/usr/bin/python
# The MIT License (MIT)
# Copyright (c) 2014 Antonio SJ Musumeci <trapexit@spawn.link>
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
import argparse
import os
import xattr
import errno
def main():
parser = argparse.ArgumentParser(description='audit a mergerfs mount for inconsistencies')
parser.add_argument('device',type=str,help='device')
parser.add_argument('-v','--verbose',action='store_true',help='print details of audit item')
parser.add_argument('-f','--fix',choices=['manual','newest'],help='fix policy')
args = parser.parse_args()
if args.fix:
args.verbose = True
if args.fix == 'manual':
fix = manual_fix
elif args.fix == 'newest':
fix = newest_fix
else:
fix = noop_fix
try:
controlfile = os.path.join(args.device,".mergerfs")
version = xattr.getxattr(controlfile,"user.mergerfs.version")
for (dirname,dirnames,filenames) in os.walk(args.device):
fulldirpath = os.path.join(args.device,dirname)
check_consistancy(fulldirpath,args.verbose,fix)
for filename in filenames:
fullpath = os.path.join(fulldirpath,filename)
check_consistancy(fullpath,args.verbose,fix)
except IOError as e:
if e.errno == errno.ENOENT:
print("%s is not a mergerfs device" % args.device)
else:
print("IOError: %s" % e.strerror)
def check_consistancy(fullpath,verbose,fix):
paths = xattr.getxattr(fullpath,"user.mergerfs.allpaths").split('\0')
if len(paths) > 1:
stats = [os.stat(path) for path in paths]
if stats_different(stats):
print("mismatch: %s" % fullpath)
if verbose:
print_stats(paths,stats)
fix(paths,stats)
def noop_fix(paths,stats):
pass
def manual_fix(paths,stats):
done = False
while not done:
try:
value = input('Which is correct?: ')
setstat(stats[value % len(paths)],paths)
done = True
except NameError:
print("Input error: enter a value between 0 and %d" % (len(paths)-1))
except Exception as e:
print("%s" % e)
done = True
def newest_fix(paths,stats):
stats.sort(key=lambda stat: stat.st_mtime)
try:
setstat(stats[-1],paths)
except Exception as e:
print("%s" % e)
def setstat(stat,paths):
for path in paths:
try:
os.chmod(path,stat.st_mode)
os.chown(path,stat.st_uid,stat.st_gid);
print("setting %s > uid: %d gid: %d mode: %o" %
(path,stat.st_uid,stat.st_gid,stat.st_mode))
except Exception as e:
print("%s" % e)
def stats_different(stats):
base = stats[0]
for stat in stats:
if ((stat.st_mode == base.st_mode) and
(stat.st_uid == base.st_uid) and
(stat.st_gid == base.st_gid)):
continue
return True
return False
def print_stats(Files,Stats):
for i in xrange(0,len(Files)):
print("- %i: %s > uid: %s; gid: %s; mode: %o" %
(i,Files[i],Stats[i].st_uid,Stats[i].st_gid,Stats[i].st_mode))
if __name__ == "__main__":
main()
Loading…
Cancel
Save