#!/usr/bin/env python3 """ Traversing directories and archives with AVFS/fuse """ import os import os.path import subprocess import stat avfs_sys_mount = os.fsencode(os.environ['HOME'] + '/.avfs') avfs_started = False def sys_name(fpath): global avfs_started if not avfs_started: subprocess.check_call(['mountavfs']) avfs_started = True return avfs_sys_mount + os.path.abspath(fpath) def exists(fpath): return os.path.exists(sys_name(fpath)) def isdir(fpath): return os.path.isdir(sys_name(fpath)) orig_open = open def open(fpath, *pargs, **kwargs): return orig_open(sys_name(fpath), *pargs, **kwargs) # AVFS has its own automatic view selection using file extensions, but it # includes plugins (like #patch) that will lead us into an infinite loop # if we try to do a directory traversal. Also, there are a few # extensions we want to add. avfscmds = { (b'.gz', b'#ugz'), (b'.tgz', b'#ugz#utar'), (b'.tar.bz2', b'#ubz2#utar'), (b'.bz2', b'#ubz2'), (b'.bz', b'#ubz2'), (b'.tbz2', b'#ubz2#utar'), (b'.tbz', b'#ubz2#utar'), (b'.Z', b'#uz'), (b'.tpz', b'#uz#utar'), (b'.tz', b'#uz#utar'), (b'.taz', b'#uz#utar'), (b'.a', b'#uar'), (b'.deb', b'#uar'), (b'.tar', b'#utar'), (b'.gem', b'#utar'), # Add upstream (b'.rar', b'#urar'), (b'.sfx', b'#urar'), (b'.zip', b'#uzip'), (b'.jar', b'#uzip'), (b'.ear', b'#uzip'), (b'.war', b'#uzip'), (b'.nupkg', b'#uzip'), # Add upstream (b'.whl', b'#uzip'), # Add upstream (b'.7z', b'#u7z'), (b'.zoo', b'#uzoo'), (b'.lha', b'#ulha'), (b'.lhz', b'#ulha'), (b'.arj', b'#uarj'), (b'.cpio', b'#ucpio'), (b'.rpm', b'#rpm'), (b'.tar.xz', b'#uxze#utar'), (b'.txz', b'#uxze#utar'), (b'.xz', b'#uxze'), (b'.lzma', b'#uxze'), (b'.vsix', b'#uzip'), # Add upstream } def guesscmd(filename): for ext, cmd in avfscmds: if filename.endswith(ext): return cmd + guesscmd(filename[:-len(ext)]) return b'' def get_lstat_mode(filename): """ Get the st_mode stat value of the given file, or None if the stat fails (doesn't exist, I/O error) """ try: return os.lstat(filename).st_mode except: return None def find(path, excludes): """ Recursively list all files under path, including files in archives supported by AVFS. """ sys_path = sys_name(path) name = os.path.basename(path) mode = get_lstat_mode(sys_path) if mode is None: return if name in excludes: return # If AVFS provides a cooked view of the file, substitute the view # for the original file. if stat.S_ISREG(mode): cmd = guesscmd(name) if cmd: cmd_mode = get_lstat_mode(sys_path + cmd) if cmd_mode is not None: path = path + cmd sys_path = sys_path + cmd name = name + cmd mode = cmd_mode if stat.S_ISDIR(mode): for entry in os.listdir(sys_path): yield from find(path + b'/' + entry, excludes) elif stat.S_ISREG(mode): yield path if __name__ == "__main__": import sys for f in find(os.fsencode(sys.argv[1]), {b'.git'}): print(f.decode('utf-8', 'replace'))