#! /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;
latestKey = package "," os "," cpuArch;
key = package "," version "," os "," cpuArch;
if (isLatest == "1") {
keys_latest[latestKey] = hash;
}
keys[key] = hash;
}
END{
for (key in keys) {
hash = keys[key];
split(key, keyParts, /,/);
latestKey = keyParts[1] "," keyParts[3] "," keyParts[4];
if (keys_latest[latestKey] == hash) {
isLatest = "1";
} else {
isLatest = "0";
}
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