#! /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 |} [ []]' >&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