From 43dc70c0136ddb3cfe02fb91a0d3ae8095859f08 Mon Sep 17 00:00:00 2001 From: id628 Date: Mon, 26 Feb 2024 17:37:38 -0500 Subject: [PATCH] Create mergerfs.findfile This script tells you which drive holds the file you're looking for. It also warns you if there are multiple copies in different drives. --- src/mergerfs.findfile | 105 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 105 insertions(+) create mode 100644 src/mergerfs.findfile diff --git a/src/mergerfs.findfile b/src/mergerfs.findfile new file mode 100644 index 0000000..75d8ec8 --- /dev/null +++ b/src/mergerfs.findfile @@ -0,0 +1,105 @@ +#!/usr/bin/env python3 +import argparse +import ctypes +import errno +import glob +import os +import sys +import stat + +_libc = ctypes.CDLL("libc.so.6", use_errno=True) +_lgetxattr = _libc.lgetxattr +_lgetxattr.argtypes = [ctypes.c_char_p, ctypes.c_char_p, ctypes.c_void_p, ctypes.c_size_t] + +def lgetxattr(path, name): + if type(path) == str: + path = path.encode(errors='backslashreplace') + if type(name) == str: + name = name.encode(errors='backslashreplace') + length = 64 + while True: + buf = ctypes.create_string_buffer(length) + res = _lgetxattr(path, name, buf, ctypes.c_size_t(length)) + if res >= 0: + return buf.raw[0:res] + else: + err = ctypes.get_errno() + if err == errno.ERANGE: + length *= 2 + elif err == errno.ENODATA: + return None + else: + raise IOError(err, os.strerror(err), path) + +def xattr_basepath(fullpath): + basepath = lgetxattr(fullpath, 'user.mergerfs.basepath') + if basepath is not None: + return basepath.decode(errors='backslashreplace') + return None + +def xattr_relpath(fullpath): + relpath = lgetxattr(fullpath, 'user.mergerfs.relpath') + if relpath is not None: + return relpath.decode(errors='backslashreplace') + return None + +def mergerfs_srcmounts(ctrlfile): + srcmounts_raw = lgetxattr(ctrlfile, 'user.mergerfs.srcmounts') + if srcmounts_raw is not None: + srcmounts = srcmounts_raw.decode(errors='backslashreplace').split(':') + expanded_srcmounts = [] + for srcmount in srcmounts: + expanded_srcmounts.extend(glob.glob(srcmount)) + return expanded_srcmounts + return [] + +def mergerfs_control_file(path): + while path != os.path.sep: + ctrlfile = os.path.join(path, '.mergerfs') + if os.path.exists(ctrlfile): + return ctrlfile + path = os.path.abspath(os.path.join(path, os.pardir)) + return None + +def parse_arguments(): + parser = argparse.ArgumentParser(description='Find file location in mergerfs and handle duplicates with deletion option.') + parser.add_argument('file_path', type=str, help='File path to check') + parser.add_argument('-d', '--delete', action='store_true', help='Generate delete commands for duplicates') + parser.add_argument('-v', '--verbose', action='store_true', help='Verbose mode to show searching process') + return parser.parse_args() + +if __name__ == "__main__": + args = parse_arguments() + + file_path = args.file_path + relpath = xattr_relpath(file_path) + ctrlfile = mergerfs_control_file(file_path) + if ctrlfile is None: + print("# Error: Could not find the .mergerfs control file.") + sys.exit(1) + + srcmounts = mergerfs_srcmounts(ctrlfile) + basepath = xattr_basepath(file_path) + found_files = [] + + if basepath and relpath: + first_found = True + for srcmount in srcmounts: + potential_path = os.path.join(srcmount, relpath.lstrip('/')) + if args.verbose: + print(f"{'# ' if args.delete else ''}Searching {srcmount}") + if os.path.exists(potential_path): + found_files.append(potential_path) + escaped_path = potential_path.replace('"', '\\"') + if first_found: + print(f"{'# ' if args.delete else ''}Full physical path of the file: {escaped_path}") + first_found = False + else: + if args.delete: + print(f'rm -vf "{escaped_path}"') + elif not args.delete: + print(f"{'# ' if args.delete else ''}***Duplicate: {escaped_path}") + else: + if not args.delete: + print("Could not find the full physical path for the file or the file is not part of a mergerfs pool.") +