mergerfs.ctl 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275
  1. #!/usr/bin/env python3
  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 sys
  16. def find_mergerfs():
  17. rv = []
  18. with open('/proc/self/mountinfo','r') as f:
  19. for line in f:
  20. values = line.split()
  21. mountroot, mountpoint = values[3:5]
  22. separator = values.index('-', 6)
  23. fstype = values[separator + 1]
  24. if fstype == 'fuse.mergerfs' and mountroot == '/':
  25. rv.append(mountpoint.encode().decode('unicode_escape'))
  26. return rv
  27. def ask_about_path(paths):
  28. prompt = 'Available mergerfs mounts:\n'
  29. for i in range(0,len(paths)):
  30. prompt += ' {0}: {1}\n'.format(i,paths[i])
  31. prompt += 'Choose which mount to act on: '
  32. path = input(prompt)
  33. return paths[int(path)]
  34. def device2mount(device):
  35. with open('/proc/mounts','r') as f:
  36. for line in f:
  37. columns = line.split()
  38. if columns[0] == device:
  39. return columns[1]
  40. with open('/etc/fstab','r') as f:
  41. for line in f:
  42. columns = line.split()
  43. try:
  44. if columns[0] == device:
  45. return columns[1]
  46. realpath = os.path.realpath(columns[0])
  47. if realpath == device:
  48. return columns[1]
  49. except:
  50. pass
  51. return None
  52. def control_file(path):
  53. return os.path.join(path,'.mergerfs')
  54. def add_srcmount(ctrlfile,srcmount):
  55. key = b'user.mergerfs.srcmounts'
  56. value = b'+' + srcmount.encode()
  57. try:
  58. os.setxattr(ctrlfile,key,value)
  59. except Exception as e:
  60. print(e)
  61. def remove_srcmount(ctrlfile,srcmount):
  62. key = b'user.mergerfs.srcmounts'
  63. value = b'-' + srcmount.encode()
  64. try:
  65. os.setxattr(ctrlfile,key,value)
  66. except Exception as e:
  67. print(e)
  68. def normalize_key(key):
  69. if type(key) == bytes:
  70. if key.startswith(b'user.mergerfs.'):
  71. return key
  72. return b'user.mergerfs.' + key
  73. elif type(key) == str:
  74. if key.startswith('user.mergerfs.'):
  75. return key
  76. return 'user.mergerfs.' + key
  77. def print_mergerfs_info(fspaths):
  78. for fspath in fspaths:
  79. ctrlfile = control_file(fspath)
  80. version = os.getxattr(ctrlfile,'user.mergerfs.version')
  81. pid = os.getxattr(ctrlfile,'user.mergerfs.pid')
  82. srcmounts = os.getxattr(ctrlfile,'user.mergerfs.srcmounts')
  83. output = ('- mount: {0}\n'
  84. ' version: {1}\n'
  85. ' pid: {2}\n'
  86. ' srcmounts:\n'
  87. ' - ').format(fspath,
  88. version.decode(),
  89. pid.decode())
  90. srcmounts = srcmounts.decode().split(':')
  91. output += '\n - '.join(srcmounts)
  92. print(output)
  93. def build_arg_parser():
  94. desc = 'a tool for runtime manipulation of mergerfs'
  95. parser = argparse.ArgumentParser(description=desc)
  96. subparsers = parser.add_subparsers(dest='command')
  97. parser.add_argument('-m','--mount',
  98. type=str,
  99. help='mergerfs mount to act on')
  100. addopt = subparsers.add_parser('add')
  101. addopt.add_argument('type',choices=['path','device'])
  102. addopt.add_argument('path',type=str)
  103. addopt.set_defaults(func=cmd_add)
  104. removeopt = subparsers.add_parser('remove')
  105. removeopt.add_argument('type',choices=['path','device'])
  106. removeopt.add_argument('path',type=str)
  107. removeopt.set_defaults(func=cmd_remove)
  108. listopt = subparsers.add_parser('list')
  109. listopt.add_argument('type',choices=['options','values'])
  110. listopt.set_defaults(func=cmd_list)
  111. getopt = subparsers.add_parser('get')
  112. getopt.add_argument('option',type=str,nargs='+')
  113. getopt.set_defaults(func=cmd_get)
  114. setopt = subparsers.add_parser('set')
  115. setopt.add_argument('option',type=str)
  116. setopt.add_argument('value',type=str)
  117. setopt.set_defaults(func=cmd_set)
  118. infoopt = subparsers.add_parser('info')
  119. infoopt.set_defaults(func=cmd_info)
  120. return parser
  121. def cmd_add(fspaths,args):
  122. if args.type == 'device':
  123. return cmd_add_device(fspaths,args)
  124. elif args.type == 'path':
  125. return cmd_add_path(fspaths,args)
  126. def cmd_add_device(fspaths,args):
  127. for fspath in fspaths:
  128. ctrlfile = control_file(fspath)
  129. mount = device2mount(args.path)
  130. if mount:
  131. add_srcmount(ctrlfile,mount)
  132. else:
  133. print('{0} not found'.format(args.path))
  134. def cmd_add_path(fspaths,args):
  135. for fspath in fspaths:
  136. ctrlfile = control_file(fspath)
  137. add_srcmount(ctrlfile,args.path)
  138. def cmd_remove(fspaths,args):
  139. if args.type == 'device':
  140. return cmd_remove_device(fspaths,args)
  141. elif args.type == 'path':
  142. return cmd_remove_path(fspaths,args)
  143. def cmd_remove_device(fspaths,args):
  144. for fspath in fspaths:
  145. ctrlfile = control_file(fspath)
  146. mount = device2mount(args.path)
  147. if mount:
  148. remove_srcmount(ctrlfile,mount)
  149. else:
  150. print('{0} not found'.format(args.path.decode()))
  151. def cmd_remove_path(fspaths,args):
  152. for fspath in fspaths:
  153. ctrlfile = control_file(fspath)
  154. remove_srcmount(ctrlfile,args.path)
  155. def cmd_list(fspaths,args):
  156. if args.type == 'values':
  157. return cmd_list_values(fspaths,args)
  158. if args.type == 'options':
  159. return cmd_list_options(fspaths,args)
  160. def cmd_list_options(fspaths,args):
  161. for fspath in fspaths:
  162. ctrlfile = control_file(fspath)
  163. keys = os.listxattr(ctrlfile)
  164. output = ('- mount: {0}\n'
  165. ' options:\n').format(fspath)
  166. for key in keys:
  167. output += ' - {0}\n'.format(key)
  168. print(output,end='')
  169. def cmd_list_values(fspaths,args):
  170. for fspath in fspaths:
  171. ctrlfile = control_file(fspath)
  172. keys = os.listxattr(ctrlfile)
  173. output = ('- mount: {0}\n'
  174. ' options:\n').format(fspath)
  175. for key in keys:
  176. value = os.getxattr(ctrlfile,key)
  177. output += ' {0}: {1}\n'.format(key,value.decode())
  178. print(output,end='')
  179. def cmd_get(fspaths,args):
  180. for fspath in fspaths:
  181. ctrlfile = control_file(fspath)
  182. print('- mount: {0}'.format(fspath))
  183. for key in args.option:
  184. key = normalize_key(key)
  185. value = os.getxattr(ctrlfile,key).decode()
  186. print(' {0}: {1}'.format(key,value))
  187. def cmd_set(fspaths,args):
  188. for fspath in fspaths:
  189. ctrlfile = control_file(fspath)
  190. key = normalize_key(args.option)
  191. value = args.value.encode()
  192. try:
  193. os.setxattr(ctrlfile,key,value)
  194. except Exception as e:
  195. print(e)
  196. def cmd_info(fspaths,args):
  197. print_mergerfs_info(fspaths)
  198. def print_and_exit(string,rv):
  199. print(string)
  200. sys.exit(rv)
  201. def main():
  202. parser = build_arg_parser()
  203. args = parser.parse_args()
  204. fspaths = find_mergerfs()
  205. if args.mount and args.mount in fspaths:
  206. fspaths = [args.mount]
  207. elif not args.mount and not fspaths:
  208. print_and_exit('no mergerfs mounts found',1)
  209. elif args.mount and args.mount not in fspaths:
  210. print_and_exit('{0} is not a mergerfs mount'.format(args.mount),1)
  211. if hasattr(args, 'func'):
  212. args.func(fspaths,args)
  213. else:
  214. parser.print_help()
  215. sys.exit(0)
  216. if __name__ == "__main__":
  217. main()