mergerfs/tools/fsck.mergerfs
2015-10-06 00:37:03 -04:00

134 lines
4.2 KiB
Python
Executable File

#!/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()