| #!/usr/bin/python3 |
| |
| # SPDX-License-Identifier: GPL-2.0-or-later |
| # Copyright (c) 2018-2024 Oracle. All rights reserved. |
| # |
| # Author: Darrick J. Wong <djwong@kernel.org> |
| |
| # Walk a filesystem tree to generate a protofile for mkfs. |
| |
| @INIT_GETTEXT@ |
| 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 = '-' |
| |
| # We already register suid in the proto string, no need |
| # to also represent it into the octet |
| perms = stat.S_IMODE(statbuf.st_mode) & 0o777 |
| |
| 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 = "@pkg_version@" |
| 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()) |