#!/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.


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 = '-'

	# 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 = "6.15.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())
