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

 Filelist support for hardlink, e.g. valid line's:

 %% /rescue/cat
 %% /sbin/swapon /sbin/swapctl /sbin/swapoff

${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 plain text 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 chaselibs=1 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}"

strip_path()
{
	local _path

	_path=$( echo "${1}" | ${TR_CMD} -s "/" )
	echo -n "${_path}"
}

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()
{
	local _source_path
	local 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

	${CAT_FILES_CMD} ${FILES} | while read line; do
		[ -z "${line}" ] && continue
		case ":${line}" in
			:#*)
				continue
				;;
		esac
		_source_path=$( strip_path ${BASE_DIR}/${line} )
		if [ -r "${_source_path}" ]; then
			[ ${verbose} -eq 1 ] && echo "${LDD_CMD} -f \"%p\n\" ${_source_path}"
			case "${platform}" in
				Linux)
					${LDD_CMD} ${_source_path} | ${GREP_CMD} '/' | ${AWK_CMD} '{printf $1" "$3"\n"}'  | while read _a _b; do
						_prefix=$( echo ${_a} | ${CUT_CMD} -c1-1 )
						[ "${_prefix}" = "/" ] && echo "${_a}"
						_prefix=$( echo ${_b} | ${CUT_CMD} -c1-1 )
						[ "${_prefix}" = "/" ] && echo "${_b}"
					done >> ${A}
					;;
				*)
					${LDD_CMD} -f "%p\n" ${_source_path} >> ${A} 2>/dev/null
					;;
			esac
		fi
	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
	${CAT_FILES_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
				#echo "ALL: ${_all_files}"
				;;
			*)
				_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

			_source_path=$( strip_path ${BASE_DIR}/${i} )

#			if [ ! -r "${_source_path}" ]; 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

			_destination_path=$( strip_path ${DST_DIR}/${D} )
			[ ${verbose} -eq 1 ] && echo "binlib: copying ${_source_path} to ${_destination_path}"
		done

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

		if [ ${_hard_link} -eq 0 ]; then
			D=$( ${CHROOT_CMD} ${BASE_DIR} ${DIRNAME_CMD} ${line} )
			_destination_path=$( strip_path ${DST_DIR}/${D} )

			[ ! -d "{_destination_path}" ] && ${MKDIR_CMD} -p ${_destination_path}
			_dstfile=$( ${BASENAME_CMD} ${line} )
			_source_path=$( strip_path ${BASE_DIR}/${line} )

			if [ -r ${_source_path} -o -h ${_source_path} ]; then
				_destination_path_file=$( strip_path ${_destination_path}/${_dstfile} )
				if [ ! -r ${_destination_path_file} ]; then

					if [ -h ${_source_path} ]; then
						_source_readlink=$( ${READLINK_CMD} ${_source_path} )

						_prefix=$( substr --pos=0 --len=1 --str="${_source_readlink}" )
						if [ "${_prefix}" = "/" ]; then
							#D=$( ${CHROOT_CMD} ${BASE_DIR} ${DIRNAME_CMD} ${_source_readlink} )
							_destination_path_readlink=$( strip_path ${DST_DIR}/${_source_readlink} )
							_destination_path_readlink=$( ${DIRNAME_CMD} ${DST_DIR}/${_source_readlink} )
							S="${BASE_DIR}"
						else
							# relative link, same dir
							S=$( ${DIRNAME_CMD} ${_source_path} )
							_destination_path_readlink=$( strip_path ${DST_DIR}/${S} )
						fi
						[ ! -d "{_destination_path_readlink}" ] && ${MKDIR_CMD} -p ${_destination_path_readlink}

						[ ${verbose} -eq 1 ] && echo "binlib: source is symlink: ${_source_path} -> ${_source_readlink}"

						_source_readlink=$( strip_path ${S}/${_source_readlink} )

						[ ${verbose} -eq 1 ] && echo "binlib: sym:  -> ${CP_CMD} -a ${_source_readlink} ${_destination_path_readlink}"
						${CP_CMD} -a ${_source_readlink} ${_destination_path_readlink}
					else
						_source_readlink=
					fi


					[ ${verbose} -eq 1 ] && echo " -> ${CP_CMD} -a ${_source_path} ${_destination_path_file}"
					${CP_CMD} -a ${_source_path} ${_destination_path_file}
					_ret=$?
					if [ ${_ret} -ne 0 ]; then
						echo "error: ${CP_CMD} -a ${_source_path} ${_destination_path_file}"
					fi
				else
					true
					#echo "already exist: ${_destination_path_file}"
				fi
			else
				${ECHO} "${W1_COLOR}No such file from index in source, skipp: ${N2_COLOR}${_source_path}${N0_COLOR}" 1>&2
			fi
		else
			# reset hard count
			_hard_count=0
			_hard_dst=		# store destination for hard links

			for i in ${_all_files}; do
				[ ${verbose} -eq 1 ] && echo "hardlink >[${i}]"
				D=$( ${CHROOT_CMD} ${BASE_DIR} ${DIRNAME_CMD} ${i} )
				_destination_path=$( strip_path ${DST_DIR}/${D} )

				[ ! -d "{_destination_path}" ] && ${MKDIR_CMD} -p ${_destination_path}
				_dstfile=$( ${BASENAME_CMD} ${i} )
				_source_path=$( strip_path ${BASE_DIR}/${i} )

				if [ ${_hard_count} -eq 0 ]; then
					_hard_dst=$( strip_path ${DST_DIR}/${i} )

					[ ${verbose} -eq 1 ] && echo "hrdlnk -> ${CP_CMD} -a ${_source_path} ${_hard_dst}"
					${CP_CMD} -a ${_source_path} ${_hard_dst}
					_hard_count=1
				else
					_hard_src=$( strip_path ${DST_DIR}/${i} )
					_destination_path_dir=$( normalize_path ${DST_DIR}/${D} )
					[ ${verbose} -eq 1 ] && echo "hrdlnk -> cd ${_destination_path_dir} && ${LN_CMD} -f ${_hard_dst} ${_hard_src}"
					cd ${_destination_path_dir} && ${LN_CMD} -f ${_hard_dst} ${_hard_src}
				fi
			done
		fi

		# dup ^^ ??
#		for i in ${_all_files}; do
#			_source_path=$( strip_path ${BASE_DIR}/${i} )
#			_destination_path=$( strip_path ${DST_DIR}/${D} )
#			[ ${verbose} -eq 1 ] && echo " --> ${CP_CMD} -a ${_source_path} ${_destination_path}"
#			${CP_CMD} -a ${_source_path} ${_destination_path}
#		done

		_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

	[ ! -r ${B} ] && return 0

	# necessary libs
	${CAT_CMD} ${B} | while read line; do
		[ -z "${line}" ] && continue
		_prefix=$( substr --pos=0 --len=1 --str="${line}" )
		[ "${_prefix}" != "/" ] && continue
		_destination_path=$( strip_path ${DST_DIR}/${line} )
		[ ${verbose} -eq 1 ] && echo "deps libs: ${_destination_path}"
		[ -f "${_destinatione_path}" ] && continue

		_source_path=$( strip_path ${BASE_DIR}/${line} )

		[ ! -r "${_source_path}" -a ${verbose} -eq 1 ] && ${ECHO} "\n${W1_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} )

		_destination_dir=$( strip_path ${DST_DIR}/${D} )

		[ ! -d "${_destination_dir}" ] && ${MKDIR_CMD} -p ${_destination_dir}
		[ ${verbose} -eq 1 ] && echo "${RSYNC_CMD} -L -a ${_source_path} ${_destination_dir}"
		${RSYNC_CMD} -L -a --hard-links --acls --xattrs --devices --numeric-ids --recursive --partial ${_source_path} ${_destination_dir}
		_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}

ext_list=$( ${BASENAME_CMD} ${FILES} )
ext=${ext_list##*\.}

case "${ext}" in
	xz|tgz|txz|gz)
		CAT_FILES_CMD="${XZCAT_CMD}"
		;;
	*)
		CAT_FILES_CMD="${CAT_CMD}"
		;;
esac

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

[ ! -d ${DST_DIR} ] && ${MKDIR_CMD} -p ${DST_DIR}

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

exit 0
