#!/usr/bin/python3 # SPDX-License-Identifier: GPL-2.0-or-later # Copyright (c) 2018-2024 Oracle. All rights reserved. # # Author: Darrick J. Wong # Walk a filesystem tree to generate a protofile for mkfs. if __name__ == '__main__': if True: import gettext # set up gettext before main so that we can set up _(). gettext.bindtextdomain("xfsprogs", "/usr/share/locale") gettext.textdomain("xfsprogs") _ = gettext.gettext else: def _(a): return a import os import argparse import sys import stat def emit_proto_header(): '''Emit the protofile header.''' print('/') print('0 0') def stat_to_str(statbuf): '''Convert a stat buffer to a proto string.''' if stat.S_ISREG(statbuf.st_mode): type = '-' elif stat.S_ISCHR(statbuf.st_mode): type = 'c' elif stat.S_ISBLK(statbuf.st_mode): type = 'b' elif stat.S_ISFIFO(statbuf.st_mode): type = 'p' elif stat.S_ISDIR(statbuf.st_mode): type = 'd' elif stat.S_ISLNK(statbuf.st_mode): type = 'l' if statbuf.st_mode & stat.S_ISUID: suid = 'u' else: suid = '-' if statbuf.st_mode & stat.S_ISGID: sgid = 'g' else: sgid = '-' perms = stat.S_IMODE(statbuf.st_mode) return '%s%s%s%03o %d %d' % (type, suid, sgid, perms, statbuf.st_uid, \ statbuf.st_gid) def stat_to_extra(statbuf, fullpath): '''Compute the extras column for a protofile.''' if stat.S_ISREG(statbuf.st_mode): return ' %s' % fullpath elif stat.S_ISCHR(statbuf.st_mode) or stat.S_ISBLK(statbuf.st_mode): return ' %d %d' % (os.major(statbuf.st_rdev), os.minor(statbuf.st_rdev)) elif stat.S_ISLNK(statbuf.st_mode): return ' %s' % os.readlink(fullpath) return '' def max_fname_len(s1): '''Return the length of the longest string in s1.''' ret = 0 for s in s1: if len(s) > ret: ret = len(s) return ret def walk_tree(path, depth): '''Walk the directory tree rooted by path.''' dirs = [] files = [] for fname in os.listdir(path): fullpath = os.path.join(path, fname) sb = os.lstat(fullpath) if stat.S_ISDIR(sb.st_mode): dirs.append(fname) continue elif stat.S_ISSOCK(sb.st_mode): continue else: files.append(fname) for fname in files: if ' ' in fname: msg = _("Spaces not allowed in file names.") raise ValueError(f'{fname}: {msg}') for fname in dirs: if ' ' in fname: msg = _("Spaces not allowed in subdirectory names.") raise Exception(f'{fname}: {msg}') fname_width = max_fname_len(files) for fname in files: fullpath = os.path.join(path, fname) sb = os.lstat(fullpath) extra = stat_to_extra(sb, fullpath) print('%*s%-*s %s%s' % (depth, ' ', fname_width, fname, \ stat_to_str(sb), extra)) for fname in dirs: fullpath = os.path.join(path, fname) sb = os.lstat(fullpath) extra = stat_to_extra(sb, fullpath) print('%*s%s %s' % (depth, ' ', fname, \ stat_to_str(sb))) walk_tree(fullpath, depth + 1) if depth > 1: print('%*s$' % (depth - 1, ' ')) def main(): parser = argparse.ArgumentParser( \ description = _("Generate mkfs.xfs protofile for a directory tree.")) parser.add_argument('paths', metavar = _('paths'), type = str, \ nargs = '*', help = _('Directory paths to walk.')) parser.add_argument("-V", help = _("Report version and exit."), \ action = "store_true") args = parser.parse_args() if args.V: msg = _("xfs_protofile version") pkgver = "6.14.0" print(f"{msg} {pkgver}") sys.exit(0) emit_proto_header() if len(args.paths) == 0: print('d--755 0 0') print('$') else: # Copy the first argument's stat to the rootdir statbuf = os.stat(args.paths[0]) if not stat.S_ISDIR(statbuf.st_mode): raise NotADirectoryError(path) print(stat_to_str(statbuf)) # All files under each path go in the root dir, recursively for path in args.paths: print(': Descending path %s' % path) try: walk_tree(path, 1) except Exception as e: print(e, file = sys.stderr) return 1 print('$') return 0 if __name__ == '__main__': sys.exit(main())