#!/usr/local/bin/cbsd
#v12.1.7
CBSDMODULE="sys"
MYARG="basedir dstdir filelist"
MYOPTARG="chaselibs excludedir mtree prunelist verbose"
MYDESC="Copy files by index file from basedir to dstdir"
ADDHELP="

${H3_COLOR}Description${N0_COLOR}:

 Copy files by index file to \$dstdir. Used by jail2iso script and
 populate basejail from host.

${H3_COLOR}Options${N0_COLOR}:

 ${N2_COLOR}basedir=${N0_COLOR}    - <path> copy files from this place, source dir.
 ${N2_COLOR}chaselibs=${N0_COLOR}  - check/add libs via ldd.
 ${N2_COLOR}dstdir=${N0_COLOR}     - <path> copy files to this place, destination dir.
 ${N2_COLOR}excludedir=${N0_COLOR} - skip manage files from this directories list
               (use pipe as delimer, e.g: /dev|/root.
 ${N2_COLOR}filelist=${N0_COLOR}   - (.xz|.tgz)-archived or source with file list to copy.
 ${N2_COLOR}mtree=${N0_COLOR}      - create hier by mtree first, default=0 (no).
 ${N2_COLOR}prunelist=${N0_COLOR}  - source with file list to exclude.
 ${N2_COLOR}verbose=${N0_COLOR}    - mode verbose output.

${H3_COLOR}Examples${N0_COLOR}

 # mkdir /tmp/min /tmp/full
 # cbsd copy-binlib basedir=/ dstdir=/tmp/min filelist=/usr/local/cbsd/share/FreeBSD-filemin_14.txt.xz
 # cbsd copy-binlib basedir=/ dstdir=/tmp/full filelist=/usr/local/cbsd/share/FreeBSD-filebases_14.txt.xz

"

. ${subrdir}/nc.subr

verbose=0
chaselibs=0

. ${cbsdinit}

[ -z "${mtree}" ] && mtree=0

[ -n "${excludedir}" ] && excludedir=$( echo ${excludedir} | ${TR_CMD} '|' ' ' )

BASE_DIR="${basedir}"
FILES="${filelist}"
DST_DIR="${dstdir}"

make_mtree()
{
	[ -f ${BASE_DIR}/etc/mtree/BSD.root.dist ] && ${MTREE_CMD} -deU -f ${BASE_DIR}/etc/mtree/BSD.root.dist -p ${DST_DIR} >/dev/null
	[ -f ${BASE_DIR}/etc/mtree/BSD.usr.dist ] && ${MTREE_CMD} -deU -f ${BASE_DIR}/etc/mtree/BSD.usr.dist -p ${DST_DIR}/usr >/dev/null
	[ -f ${BASE_DIR}/etc/mtree/BSD.var.dist ] && ${MTREE_CMD} -deU -f ${BASE_DIR}/etc/mtree/BSD.var.dist -p ${DST_DIR}/var >/dev/null
	[ -f ${BASE_DIR}/etc/mtree/BIND.chroot.dist ] && ${MTREE_CMD} -deU -f ${BASE_DIR}/etc/mtree/BIND.chroot.dist -p ${DST_DIR}/var/named >/dev/null
	[ -f ${BASE_DIR}/etc/mtree/BSD.sendmail.dist ] && ${MTREE_CMD} -deU -f ${BASE_DIR}/etc/mtree/BSD.sendmail.dist -p ${DST_DIR} >/dev/null
	[ -f ${BASE_DIR}/etc/mtree/BSD.include.dist ] && ${MTREE_CMD} -deU -f ${BASE_DIR}/etc/mtree/BSD.include.dist -p ${DST_DIR}/usr/include >/dev/null
	[ ! -d "${DST_DIR}/usr/tests" ] && ${MKDIR_CMD} -p "${DST_DIR}/usr/tests"
	[ -f ${BASE_DIR}/etc/mtree/BSD.tests.dist ] && ${MTREE_CMD} -deU -f ${BASE_DIR}/etc/mtree/BSD.tests.dist -p ${DST_DIR}/usr/tests >/dev/null
}

make_libmap()
{
	A=$( ${MKTEMP_CMD} ${tmpdir}/libtxt.XXX )
	B=$( ${MKTEMP_CMD} ${tmpdir}/libtxtsort.XXX )
	TRAP="${TRAP} ${RM_CMD} -f ${A} ${B};"
	trap "${TRAP}" HUP INT ABRT BUS TERM EXIT

	${XZCAT_CMD} ${FILES} | while read line; do
		[ -z "${line}" ] && continue
		case ":${line}" in
			:#*)
				continue
				;;
		esac
		[ -r "${BASE_DIR}${line}" ] && ${LDD_CMD} -f "%p\n" ${BASE_DIR}${line} >> $A 2>/dev/null
	done
	${SORT_CMD} -u ${A} > ${B}
	${RM_CMD} -f ${A}
}

copy_binlib()
{
	local _dotnum=0
	local _prefix
	local _strlen
	local _skip
	local _ret

	# pass one: copy files
	${XZCAT_CMD} ${FILES} | while read line; do
		[ -z "${line}" ] && continue

		_prefix=$( substr --pos=0 --len=1 --str="${line}" )
		_hard_link=0

		case "${_prefix}" in
			"#")
				continue
				;;
			"%")
				_all_files=
				_hard_link=1
				# collect all hard-links in variables
				for i in ${line}; do
					[ "${i}" = "%" ] && continue
					_all_files="${_all_files} ${i}"
				done
				;;
			*)
				_all_files="${line}"
				;;
		esac

		_skip=0

		for i in ${_all_files}; do
			if [ -n "${excludedir}" ]; then
				for skipdir in ${excludedir}; do
					_strlen=$( strlen ${skipdir} )
					_test_for_skip=$( substr --pos=0 --len=${_strlen} --str="${i}" )
					if [ "${skipdir}" = "${_test_for_skip}" ]; then
						[ ${verbose} -eq 1 ] && echo "** skip list dir: ${skipdir}"
						_skip=1
						continue
					fi
				done
			fi

			if [ ! -r "${BASE_DIR}${i}" ]; then
				[ ${verbose} -eq 1 ] && ${ECHO} "\n${N1_COLOR}Notice: Exist in index, but not found in ${N2_COLOR}${BASE_DIR}: ${N1_COLOR}${i}${N0_COLOR}\n"
				continue
			fi

			[ ${verbose} -eq 1 ] && echo "copying ${BASE_DIR}${i} to ${DST_DIR}${D}"
		done

		[ ${_skip} -eq 1 ] && continue

		if [ ${_hard_link} -eq 0 ]; then
			D=$( ${CHROOT_CMD} ${BASE_DIR} ${DIRNAME_CMD} ${line} )
			[ ! -d "{DST_DIR}${D}" ] && ${MKDIR_CMD} -p ${DST_DIR}${D}
			if [ -r ${BASE_DIR}${line} -o -h ${BASE_DIR}${line} ]; then
				if [ ! -r ${DST_DIR}${D} ]; then
					${CP_CMD} -a ${BASE_DIR}${line} ${DST_DIR}${D}
					_ret=$?
					if [ ${_ret} -ne 0 ]; then
						echo "error: ${CP_CMD} -a ${BASE_DIR}${line} ${DST_DIR}${D}"
					fi
				else
					#echo "already exist: ${CP_CMD} -a ${BASE_DIR}${line} ${DST_DIR}${D}"
				fi
			else
				${ECHO} "${N1_COLOR}No such file from index in source, skipp: ${N2_COLOR}${BASE_DIR}${line}${N0_COLOR}" 1>&2
			fi
		else
			_hard_count=0
			_hard_dst=		# store destination for hard links

			for i in ${_all_files}; do
				D=$( ${CHROOT_CMD} ${BASE_DIR} ${DIRNAME_CMD} ${i} )
				[ ! -d "{DST_DIR}${D}" ] && ${MKDIR_CMD} -p ${DST_DIR}${D}

				if [ ${_hard_count} -eq 0 ]; then
					_hard_dst="${DST_DIR}${i}"
					${CP_CMD} -a ${BASE_DIR}${i} ${DST_DIR}${D}
					_hard_count=1
				else
					_hard_src="${DST_DIR}${i}"
					cd ${DST_DIR}${D} && ${LN_CMD} -f ${_hard_dst} ${_hard_src}
				fi
			done
		fi

		_dotnum=$(( _dotnum + 1 ))
		if [ ${_dotnum} -gt 100 -a ${verbose} -eq 0 ]; then
			printf "." 1>&2
			_dotnum=0
		fi
	done

	[ ${chaselibs} -eq 0 ] && return 0
	_dotnum=0

	# necessary libs
	${CAT_CMD} ${B} | while read line; do
		[ -z "${line}" ] && continue
		[ -f "${DST_DIR}${line}" ] && continue
		[ ! -r "${BASE_DIR}${line}" -a ${verbose} -eq 1 ] && ${ECHO} "\n${N1_COLOR}Notice: exist in index, but not found in ${N2_COLOR}${BASE_DIR}: ${N1_COLOR}${line}${N0_COLOR}\n" && continue
		D=$( ${CHROOT_CMD} ${BASE_DIR} ${DIRNAME_CMD} ${line} )
		[ ! -d "${DST_DIR}${D}" ] && ${MKDIR_CMD} -p ${DST_DIR}${D}
		[ ${verbose} -eq 1 ] && echo "${CP_CMD} -a ${BASE_DIR}${line} ${DST_DIR}${D}"
		${RSYNC_CMD} -a --hard-links --acls --xattrs --devices --numeric-ids --recursive --partial ${BASE_DIR}${line} ${DST_DIR}${D}
		_dotnum=$(( _dotnum + 1 ))
		if [ ${_dotnum} -gt 100 -a ${verbose} -eq 0 ]; then
			printf "." 1>&2
			_dotnum=0
		fi

		_dotnum=$(( _dotnum + 1 ))
	done

	${RM_CMD} -f ${B}
}

prunelist()
{
	[ ! -f "${prunelist}" -o -z "${prunelist}" ] && return 0		# no prune
	[ -z "${1}" ] && return 0 # sanity

	${ECHO} "${N1_COLOR}Prune file by list: ${N2_COLOR}${prunelist}${N0_COLOR}"

	for FILE in $( ${CAT_CMD} ${prunelist} ); do
		[ -z "${FILE}" ] && continue
		case ":${FILE}" in
			:#* | :)
				continue
				;;
		esac
		${RM_CMD} -rf ${1}/${FILE} 2>/dev/null
	done
}

[ ! -d "${BASE_DIR}" ] && err 1 "No such ${BASE_DIR}
[ ! -r "${FILES}" ] && err 1 "No such ${FILES}

if [ -d "${DST_DIR}" ]; then
	${CHFLAGS_CMD} -R noschg ${DST_DIR}
	${RM_CMD} -rf ${DST_DIR}
fi

${MKDIR_CMD} -p ${DST_DIR}

[ ${mtree} -eq 1 ] && make_mtree
make_libmap
copy_binlib
prunelist

exit 0
