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

  1. #!/usr/bin/python
  2. # The MIT License (MIT)
  3. # Copyright (c) 2014 Antonio SJ Musumeci <trapexit@spawn.link>
  4. # Permission is hereby granted, free of charge, to any person obtaining a copy
  5. # of this software and associated documentation files (the "Software"), to deal
  6. # in the Software without restriction, including without limitation the rights
  7. # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  8. # copies of the Software, and to permit persons to whom the Software is
  9. # furnished to do so, subject to the following conditions:
  10. # The above copyright notice and this permission notice shall be included in
  11. # all copies or substantial portions of the Software.
  12. # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  13. # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  14. # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  15. # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  16. # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  17. # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  18. # THE SOFTWARE.
  19. import argparse
  20. import os
  21. import xattr
  22. import errno
  23. def main():
  24. parser = argparse.ArgumentParser(description='audit a mergerfs mount for inconsistencies')
  25. parser.add_argument('device',type=str,help='device')
  26. parser.add_argument('-v','--verbose',action='store_true',help='print details of audit item')
  27. parser.add_argument('-f','--fix',choices=['manual','newest'],help='fix policy')
  28. args = parser.parse_args()
  29. if args.fix:
  30. args.verbose = True
  31. if args.fix == 'manual':
  32. fix = manual_fix
  33. elif args.fix == 'newest':
  34. fix = newest_fix
  35. else:
  36. fix = noop_fix
  37. try:
  38. controlfile = os.path.join(args.device,".mergerfs")
  39. version = xattr.getxattr(controlfile,"user.mergerfs.version")
  40. for (dirname,dirnames,filenames) in os.walk(args.device):
  41. fulldirpath = os.path.join(args.device,dirname)
  42. check_consistancy(fulldirpath,args.verbose,fix)
  43. for filename in filenames:
  44. fullpath = os.path.join(fulldirpath,filename)
  45. check_consistancy(fullpath,args.verbose,fix)
  46. except IOError as e:
  47. if e.errno == errno.ENOENT:
  48. print("%s is not a mergerfs device" % args.device)
  49. else:
  50. print("IOError: %s" % e.strerror)
  51. def check_consistancy(fullpath,verbose,fix):
  52. paths = xattr.getxattr(fullpath,"user.mergerfs.allpaths").split('\0')
  53. if len(paths) > 1:
  54. stats = [os.stat(path) for path in paths]
  55. if stats_different(stats):
  56. print("mismatch: %s" % fullpath)
  57. if verbose:
  58. print_stats(paths,stats)
  59. fix(paths,stats)
  60. def noop_fix(paths,stats):
  61. pass
  62. def manual_fix(paths,stats):
  63. done = False
  64. while not done:
  65. try:
  66. value = input('Which is correct?: ')
  67. setstat(stats[value % len(paths)],paths)
  68. done = True
  69. except NameError:
  70. print("Input error: enter a value between 0 and %d" % (len(paths)-1))
  71. except Exception as e:
  72. print("%s" % e)
  73. done = True
  74. def newest_fix(paths,stats):
  75. stats.sort(key=lambda stat: stat.st_mtime)
  76. try:
  77. setstat(stats[-1],paths)
  78. except Exception as e:
  79. print("%s" % e)
  80. def setstat(stat,paths):
  81. for path in paths:
  82. try:
  83. os.chmod(path,stat.st_mode)
  84. os.chown(path,stat.st_uid,stat.st_gid);
  85. print("setting %s > uid: %d gid: %d mode: %o" %
  86. (path,stat.st_uid,stat.st_gid,stat.st_mode))
  87. except Exception as e:
  88. print("%s" % e)
  89. def stats_different(stats):
  90. base = stats[0]
  91. for stat in stats:
  92. if ((stat.st_mode == base.st_mode) and
  93. (stat.st_uid == base.st_uid) and
  94. (stat.st_gid == base.st_gid)):
  95. continue
  96. return True
  97. return False
  98. def print_stats(Files,Stats):
  99. for i in xrange(0,len(Files)):
  100. print("- %i: %s > uid: %s; gid: %s; mode: %o" %
  101. (i,Files[i],Stats[i].st_uid,Stats[i].st_gid,Stats[i].st_mode))
  102. if __name__ == "__main__":
  103. main()