mirror of https://github.com/trapexit/mergerfs.git
Browse Source
Merge pull request #152 from trapexit/fsck
Merge pull request #152 from trapexit/fsck
audit (and fix) file permissions and ownership. closes #148pull/155/head
Antonio SJ Musumeci
9 years ago
3 changed files with 147 additions and 2 deletions
-
9Makefile
-
7README.md
-
133tools/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() |
Write
Preview
Loading…
Cancel
Save
Reference in new issue