appfs-mkfs at [42a3efcd94]

File appfs-mkfs artifact d5deebecbf part of check-in 42a3efcd94


#! /usr/bin/env bash

#
# Copyright (c) 2014  Roy Keene
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
#

PATH="${PATH}:$(cd "$(dirname "$(which "$0")")" && pwd)"

if [ "$1" == '--cpio' ]; then
	shift

	mode='file'

	pkgfile="$1"
else
	mode='dir'

	pkgsdir="$1"
fi
appfsdir="$2"
sitekey="$3"
sitecert="$4"

if [ -n "${sitekey}" ]; then
	sitekey="$(readlink -f "${sitekey}")"
fi

if [ -n "${sitecert}" ]; then
	sitecert="$(readlink -f "${sitecert}")"
fi

if [ -z "${pkgsdir}" -a -z "${pkgfile}" ] || [ -z "${appfsdir}" ]; then
	echo 'Usage: appfs-mk {--cpio <pkgfile>|<pkgsdir>} <appfsdir> [<site-key> [<site-certificate>]]' >&2

	exit 1
fi

appfsdir="$(cd "${appfsdir}" && pwd)"
if [ -z "${appfsdir}" ]; then
	echo "Unable to find appfs directory." >&2

	exit 1
fi

mkdir -p "${appfsdir}/sha1"

function sha1() {
	local filename

	filename="$1"

	openssl sha1 "${filename}" | sed 's@.*= @@'
}

function emit_manifest() {
	find . -print0 | while IFS='' read -r -d $'\0' filename; do
		if [ "${filename}" = '.' ]; then
			continue
		fi

		filename="$(echo "${filename}" | sed 's@^\./@@' | head -n 1)"

		if [ ! -e "${filename}" ]; then
			continue
		fi

		if [ -h "${filename}" ]; then
			type='symlink'
		elif [ -d "${filename}" ]; then
			type='directory'
		elif [ -f "${filename}" ]; then
			type='file'
		else
			continue
		fi

		case "${type}" in
			directory)
				stat_format='%Y'
				extra_data=''
				;;
			symlink)
				stat_format='%Y'
				extra_data="$(readlink "${filename}")"
				;;
			file)
				if [ -x "${filename}" ]; then
					extra_data='x'
				else
					extra_data=''
				fi

				stat_format='%Y,%s'
				filename_hash="$(sha1 "${filename}")"
				extra_data="${extra_data},${filename_hash}"

				filename_intree="${appfsdir}/sha1/${filename_hash}"

				if [ ! -e "${filename_intree}" ]; then
					cat "${filename}" > "${filename_intree}.tmp"

					mv "${filename_intree}.tmp" "${filename_intree}"
				fi
				;;
		esac
		stat_data="$(stat --format="${stat_format}" "${filename}")"

		if [ -z "${extra_data}" ]; then
			echo "${type},${stat_data},${filename}"
		else
			echo "${type},${stat_data},${extra_data},${filename}"
		fi
	done
}

packagelistfile="${appfsdir}/sha1/${RANDOM}${RANDOM}${RANDOM}${RANDOM}${RANDOM}.tmp"
default_isLatest='0'

case "${mode}" in
	dir)
		cd "${pkgsdir}" || exit 1
		;;
	file)
		oldpackagelistfile="${appfsdir}/sha1/$(cat "${appfsdir}/index" | head -n 1 | cut -f 1 -d ',')"

		workdir="${appfsdir}/.workdir-${RANDOM}${RANDOM}${RANDOM}${RANDOM}"
		mkdir "${workdir}"

		cat "${pkgfile}" | ( cd "${workdir}" && cpio -imd ) || exit 1
		cd "${workdir}" || exit 1

		dirdate="$(find . -type f -printf '%TY%Tm%Td%TH%TM.%TS\n' -quit | cut -f 1-2 -d '.')"
		find . -type d -print0 | xargs -0 -- touch -t "${dirdate}"

		# If this archive contains exactly one package mark it as the latest version
		chk_package="$(echo *)"
		if [ -d "${chk_package}" ]; then
			default_isLatest='1'
		fi

		cat "${oldpackagelistfile}" 2>/dev/null | (
			if [ -d "${chk_package}" ]; then
				sed 's@^\('"{chk_package}"',.*\),1@\1,0@'
			else
				cat
			fi
		) > "${packagelistfile}"

		;;
esac

for package in *; do
	if [ ! -d "${package}" ]; then
		continue
	fi

	(
		cd "${package}" || exit 1

		for os_cpuArch in *; do
			os="$(echo "${os_cpuArch}" | cut -f 1 -d '-')"
			cpuArch="$(echo "${os_cpuArch}" | cut -f 2- -d '-')"

			(
				cd "${os_cpuArch}" || exit 1

				for version in *; do
					if [ -h "${version}" ]; then
						continue
					fi

					manifestfile="${appfsdir}/sha1/${RANDOM}${RANDOM}${RANDOM}${RANDOM}${RANDOM}.tmp"

					(
						cd "${version}" || exit 1

						echo "#manifestmetadata,${package},${os},${cpuArch},${version}"
						emit_manifest
					) > "${manifestfile}"

					manifestfile_hash="$(sha1 "${manifestfile}")"
					mv "${manifestfile}" "${appfsdir}/sha1/${manifestfile_hash}"

					# XXX:TODO: Determine if this is the latest version
					isLatest="${default_isLatest:-0}"

					echo "${package},${version},${os},${cpuArch},${manifestfile_hash},${isLatest}"
				done

			)
		done
	)
done >> "${packagelistfile}"

# Ensure package list file does not contain duplicate versions
cat "${packagelistfile}" | awk -F ',' '
	{
		package = $1;
		version = $2;
		os = $3;
		cpuArch = $4;
		hash = $5;
		isLatest = $6;

		key = package "," version "," os "," cpuArch;

		if (isLatest == "1") {
			keys_latest[key] = hash;
		}
		keys[key] = hash;
	}

	END{
		for (key in keys) {
			if (keys_latest[key] != "") {
				isLatest = "1";
				hash = keys_latest[key];
			} else {
				isLatest = "0";
				hash = keys[key];
			}

			print key "," hash "," isLatest;
		}
	}  
' | sort -u > "${packagelistfile}.new"
cat "${packagelistfile}.new" > "${packagelistfile}"
rm -f "${packagelistfile}.new"

packagelistfile_hash="$(sha1 "${packagelistfile}")"
mv "${packagelistfile}" "${appfsdir}/sha1/${packagelistfile_hash}"

if [ -n "$APPFS_SIGN_IN_PLACE" ]; then
	indexfile="${appfsdir}/index"
else
	indexfile="${appfsdir}/index.new"
fi

echo "${packagelistfile_hash},sha1" > "${indexfile}"

if [ -x "$(which 'appfs-cert' 2>/dev/null)" ]; then
	appfs-cert sign-site "${indexfile}" "${sitekey}" "${sitecert}"
fi

if [ -z "$APPFS_SIGN_IN_PLACE" ]; then
	mv "${indexfile}" "${appfsdir}/index"
fi

case "${mode}" in
	file)
		cd /
		rm -rf "${workdir}"
		;;
esac