mirror of https://github.com/trapexit/mergerfs.git
				
				
			
			You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							133 lines
						
					
					
						
							4.2 KiB
						
					
					
				
			
		
		
		
			
			
			
		
		
	
	
							133 lines
						
					
					
						
							4.2 KiB
						
					
					
				| #!/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()
 |