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.

125 lines
3.8 KiB

  1. #!/usr/bin/python
  2. # Copyright (c) 2016, Antonio SJ Musumeci <trapexit@spawn.link>
  3. # Permission to use, copy, modify, and/or distribute this software for any
  4. # purpose with or without fee is hereby granted, provided that the above
  5. # copyright notice and this permission notice appear in all copies.
  6. # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
  7. # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
  8. # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
  9. # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
  10. # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
  11. # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
  12. # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  13. import argparse
  14. import os
  15. import xattr
  16. import errno
  17. def main():
  18. parser = argparse.ArgumentParser(description='audit a mergerfs mount for inconsistencies')
  19. parser.add_argument('device',type=str,help='device')
  20. parser.add_argument('-v','--verbose',action='store_true',help='print details of audit item')
  21. parser.add_argument('-f','--fix',choices=['manual','newest'],help='fix policy')
  22. args = parser.parse_args()
  23. if args.fix:
  24. args.verbose = True
  25. if args.fix == 'manual':
  26. fix = manual_fix
  27. elif args.fix == 'newest':
  28. fix = newest_fix
  29. else:
  30. fix = noop_fix
  31. try:
  32. controlfile = os.path.join(args.device,".mergerfs")
  33. version = xattr.getxattr(controlfile,"user.mergerfs.version")
  34. for (dirname,dirnames,filenames) in os.walk(args.device):
  35. fulldirpath = os.path.join(args.device,dirname)
  36. check_consistancy(fulldirpath,args.verbose,fix)
  37. for filename in filenames:
  38. fullpath = os.path.join(fulldirpath,filename)
  39. check_consistancy(fullpath,args.verbose,fix)
  40. except IOError as e:
  41. if e.errno == errno.ENOENT:
  42. print("%s is not a mergerfs device" % args.device)
  43. else:
  44. print("IOError: %s" % e.strerror)
  45. def check_consistancy(fullpath,verbose,fix):
  46. paths = xattr.getxattr(fullpath,"user.mergerfs.allpaths").split('\0')
  47. if len(paths) > 1:
  48. stats = [os.stat(path) for path in paths]
  49. if stats_different(stats):
  50. print("mismatch: %s" % fullpath)
  51. if verbose:
  52. print_stats(paths,stats)
  53. fix(paths,stats)
  54. def noop_fix(paths,stats):
  55. pass
  56. def manual_fix(paths,stats):
  57. done = False
  58. while not done:
  59. try:
  60. value = input('Which is correct?: ')
  61. setstat(stats[value % len(paths)],paths)
  62. done = True
  63. except NameError:
  64. print("Input error: enter a value between 0 and %d" % (len(paths)-1))
  65. except Exception as e:
  66. print("%s" % e)
  67. done = True
  68. def newest_fix(paths,stats):
  69. stats.sort(key=lambda stat: stat.st_mtime)
  70. try:
  71. setstat(stats[-1],paths)
  72. except Exception as e:
  73. print("%s" % e)
  74. def setstat(stat,paths):
  75. for path in paths:
  76. try:
  77. os.chmod(path,stat.st_mode)
  78. os.chown(path,stat.st_uid,stat.st_gid);
  79. print("setting %s > uid: %d gid: %d mode: %o" %
  80. (path,stat.st_uid,stat.st_gid,stat.st_mode))
  81. except Exception as e:
  82. print("%s" % e)
  83. def stats_different(stats):
  84. base = stats[0]
  85. for stat in stats:
  86. if ((stat.st_mode == base.st_mode) and
  87. (stat.st_uid == base.st_uid) and
  88. (stat.st_gid == base.st_gid)):
  89. continue
  90. return True
  91. return False
  92. def print_stats(Files,Stats):
  93. for i in xrange(0,len(Files)):
  94. print("- %i: %s > uid: %s; gid: %s; mode: %o" %
  95. (i,Files[i],Stats[i].st_uid,Stats[i].st_gid,Stats[i].st_mode))
  96. if __name__ == "__main__":
  97. main()