blob: 40d06ddb44aa300b6801ec451ffe915e288969f6 [file] [log] [blame]
#!/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())