|
|
#!/usr/bin/python
# Copyright (c) 2016, Antonio SJ Musumeci <trapexit@spawn.link>
# Permission to use, copy, modify, and/or distribute this software for any # purpose with or without fee is hereby granted, provided that the above # copyright notice and this permission notice appear in all copies.
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
import 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()
|