123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275 |
- #!/usr/bin/env python3
- # Copyright (c) 2016, Antonio SJ Musumeci <trapexit@spawn.link>
- # Permission to use, copy, modify, and/or distribute this software for any
- # purpose with or without fee is hereby granted, provided that the above
- # copyright notice and this permission notice appear in all copies.
- # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- import argparse
- import os
- import sys
- def find_mergerfs():
- rv = []
- with open('/proc/self/mountinfo','r') as f:
- for line in f:
- values = line.split()
- mountroot, mountpoint = values[3:5]
- separator = values.index('-', 6)
- fstype = values[separator + 1]
- if fstype == 'fuse.mergerfs' and mountroot == '/':
- rv.append(mountpoint.encode().decode('unicode_escape'))
- return rv
- def ask_about_path(paths):
- prompt = 'Available mergerfs mounts:\n'
- for i in range(0,len(paths)):
- prompt += ' {0}: {1}\n'.format(i,paths[i])
- prompt += 'Choose which mount to act on: '
- path = input(prompt)
- return paths[int(path)]
- def device2mount(device):
- with open('/proc/mounts','r') as f:
- for line in f:
- columns = line.split()
- if columns[0] == device:
- return columns[1]
- with open('/etc/fstab','r') as f:
- for line in f:
- columns = line.split()
- try:
- if columns[0] == device:
- return columns[1]
- realpath = os.path.realpath(columns[0])
- if realpath == device:
- return columns[1]
- except:
- pass
- return None
- def control_file(path):
- return os.path.join(path,'.mergerfs')
- def add_srcmount(ctrlfile,srcmount):
- key = b'user.mergerfs.srcmounts'
- value = b'+' + srcmount.encode()
- try:
- os.setxattr(ctrlfile,key,value)
- except Exception as e:
- print(e)
- def remove_srcmount(ctrlfile,srcmount):
- key = b'user.mergerfs.srcmounts'
- value = b'-' + srcmount.encode()
- try:
- os.setxattr(ctrlfile,key,value)
- except Exception as e:
- print(e)
- def normalize_key(key):
- if type(key) == bytes:
- if key.startswith(b'user.mergerfs.'):
- return key
- return b'user.mergerfs.' + key
- elif type(key) == str:
- if key.startswith('user.mergerfs.'):
- return key
- return 'user.mergerfs.' + key
- def print_mergerfs_info(fspaths):
- for fspath in fspaths:
- ctrlfile = control_file(fspath)
- version = os.getxattr(ctrlfile,'user.mergerfs.version')
- pid = os.getxattr(ctrlfile,'user.mergerfs.pid')
- srcmounts = os.getxattr(ctrlfile,'user.mergerfs.srcmounts')
- output = ('- mount: {0}\n'
- ' version: {1}\n'
- ' pid: {2}\n'
- ' srcmounts:\n'
- ' - ').format(fspath,
- version.decode(),
- pid.decode())
- srcmounts = srcmounts.decode().split(':')
- output += '\n - '.join(srcmounts)
- print(output)
- def build_arg_parser():
- desc = 'a tool for runtime manipulation of mergerfs'
- parser = argparse.ArgumentParser(description=desc)
- subparsers = parser.add_subparsers(dest='command')
- parser.add_argument('-m','--mount',
- type=str,
- help='mergerfs mount to act on')
- addopt = subparsers.add_parser('add')
- addopt.add_argument('type',choices=['path','device'])
- addopt.add_argument('path',type=str)
- addopt.set_defaults(func=cmd_add)
- removeopt = subparsers.add_parser('remove')
- removeopt.add_argument('type',choices=['path','device'])
- removeopt.add_argument('path',type=str)
- removeopt.set_defaults(func=cmd_remove)
- listopt = subparsers.add_parser('list')
- listopt.add_argument('type',choices=['options','values'])
- listopt.set_defaults(func=cmd_list)
- getopt = subparsers.add_parser('get')
- getopt.add_argument('option',type=str,nargs='+')
- getopt.set_defaults(func=cmd_get)
- setopt = subparsers.add_parser('set')
- setopt.add_argument('option',type=str)
- setopt.add_argument('value',type=str)
- setopt.set_defaults(func=cmd_set)
- infoopt = subparsers.add_parser('info')
- infoopt.set_defaults(func=cmd_info)
- return parser
- def cmd_add(fspaths,args):
- if args.type == 'device':
- return cmd_add_device(fspaths,args)
- elif args.type == 'path':
- return cmd_add_path(fspaths,args)
- def cmd_add_device(fspaths,args):
- for fspath in fspaths:
- ctrlfile = control_file(fspath)
- mount = device2mount(args.path)
- if mount:
- add_srcmount(ctrlfile,mount)
- else:
- print('{0} not found'.format(args.path))
- def cmd_add_path(fspaths,args):
- for fspath in fspaths:
- ctrlfile = control_file(fspath)
- add_srcmount(ctrlfile,args.path)
- def cmd_remove(fspaths,args):
- if args.type == 'device':
- return cmd_remove_device(fspaths,args)
- elif args.type == 'path':
- return cmd_remove_path(fspaths,args)
- def cmd_remove_device(fspaths,args):
- for fspath in fspaths:
- ctrlfile = control_file(fspath)
- mount = device2mount(args.path)
- if mount:
- remove_srcmount(ctrlfile,mount)
- else:
- print('{0} not found'.format(args.path.decode()))
- def cmd_remove_path(fspaths,args):
- for fspath in fspaths:
- ctrlfile = control_file(fspath)
- remove_srcmount(ctrlfile,args.path)
- def cmd_list(fspaths,args):
- if args.type == 'values':
- return cmd_list_values(fspaths,args)
- if args.type == 'options':
- return cmd_list_options(fspaths,args)
- def cmd_list_options(fspaths,args):
- for fspath in fspaths:
- ctrlfile = control_file(fspath)
- keys = os.listxattr(ctrlfile)
- output = ('- mount: {0}\n'
- ' options:\n').format(fspath)
- for key in keys:
- output += ' - {0}\n'.format(key)
- print(output,end='')
- def cmd_list_values(fspaths,args):
- for fspath in fspaths:
- ctrlfile = control_file(fspath)
- keys = os.listxattr(ctrlfile)
- output = ('- mount: {0}\n'
- ' options:\n').format(fspath)
- for key in keys:
- value = os.getxattr(ctrlfile,key)
- output += ' {0}: {1}\n'.format(key,value.decode())
- print(output,end='')
- def cmd_get(fspaths,args):
- for fspath in fspaths:
- ctrlfile = control_file(fspath)
- print('- mount: {0}'.format(fspath))
- for key in args.option:
- key = normalize_key(key)
- value = os.getxattr(ctrlfile,key).decode()
- print(' {0}: {1}'.format(key,value))
- def cmd_set(fspaths,args):
- for fspath in fspaths:
- ctrlfile = control_file(fspath)
- key = normalize_key(args.option)
- value = args.value.encode()
- try:
- os.setxattr(ctrlfile,key,value)
- except Exception as e:
- print(e)
- def cmd_info(fspaths,args):
- print_mergerfs_info(fspaths)
- def print_and_exit(string,rv):
- print(string)
- sys.exit(rv)
- def main():
- parser = build_arg_parser()
- args = parser.parse_args()
- fspaths = find_mergerfs()
- if args.mount and args.mount in fspaths:
- fspaths = [args.mount]
- elif not args.mount and not fspaths:
- print_and_exit('no mergerfs mounts found',1)
- elif args.mount and args.mount not in fspaths:
- print_and_exit('{0} is not a mergerfs mount'.format(args.mount),1)
- if hasattr(args, 'func'):
- args.func(fspaths,args)
- else:
- parser.print_help()
- sys.exit(0)
- if __name__ == "__main__":
- main()
|