diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/cmd.sh | 126 | ||||
-rw-r--r-- | src/control.sh | 136 | ||||
-rw-r--r-- | src/db.sh | 293 | ||||
-rw-r--r-- | src/dir.sh | 74 | ||||
-rw-r--r-- | src/garbage.sh | 76 | ||||
-rw-r--r-- | src/include.sh | 181 | ||||
-rw-r--r-- | src/index.sh | 200 | ||||
-rw-r--r-- | src/locale.sh | 88 | ||||
-rw-r--r-- | src/output.sh | 68 | ||||
-rw-r--r-- | src/remove.sh | 92 | ||||
-rw-r--r-- | src/suite.sh | 105 |
11 files changed, 1439 insertions, 0 deletions
diff --git a/src/cmd.sh b/src/cmd.sh new file mode 100644 index 0000000..d05e7eb --- /dev/null +++ b/src/cmd.sh @@ -0,0 +1,126 @@ +# pro-archman +# lib/cmd.sh +# Command-related functions +# +# Copyright (C) 2013 Patrick "P. J." McDermott +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +[ "x${_CMD_SM+set}" = 'xset' ] && return 0 +_CMD_SM=1 + +use output +use locale + +cmds= + +load_cmds() +{ + local cmd= + + for cmd in ${PKGLIBCMD}; do + cmd="${cmd##*/}" + cmd="${cmd%.sm}" + cmds="${cmds}${cmd}${LF}" + use "cmd/${cmd}" + done +} + +print_opt_summaries() +{ + local padding= + local opt= + local opt_out= + local summary= + + padding="$(printf '%24s' '')" + for opt in $(printf '%s' "${OPTSTRING}" | sed 's/\([a-zA-Z0-9]\)/ \1/g') + do + if [ ${#opt} -eq 1 ]; then + # No argument expected. + opt_out="-${opt}" + else + # Argument expected. + opt="${opt%?}" + opt_out="-${opt} $(get_msg "opt_${opt}_arg")" + fi + if [ ${#opt_out} -gt 20 ]; then + printf ' %s\n%24s' "${opt_out}" '' + else + printf ' %-20s ' "${opt_out}" + fi + summary="$(get_msg "opt_${opt}_summary")" + printf '%s\n' "${summary}" | fold -s -w 56 | \ + sed "2,\$s/^/${padding}/;" + done +} + +print_cmd_summaries() +{ + local padding= + local cmd= + local cmd_clean= + local summary= + + padding="$(printf '%24s' '')" + for cmd in ${PKGLIBCMD}; do + cmd="${cmd##*/}" + cmd="${cmd%.sm}" + if [ ${#cmd} -gt 20 ]; then + printf ' %s\n%24s' "${cmd}" '' + else + printf ' %-20s ' "${cmd}" + fi + cmd_clean="$(printf '%s' "${cmd}" | \ + tr '[A-Z]' '[a-z]' | tr -C '[a-z0-9_]' '_')" + summary="$(get_msg "cmd_${cmd_clean}_summary")" + printf '%s\n' "${summary}" | fold -s -w 56 | \ + sed "2,\$s/^/${padding}/;" + done +} + +print_cmd_usage() +{ + local cmd="${1}" + local cmd_clean= + local usage= + + cmd_clean="$(printf '%s' "${cmd}" | \ + tr '[A-Z]' '[a-z]' | tr -C '[a-z0-9_]' '_')" + usage="$(get_msg "cmd_${cmd_clean}_usage")" + + printf "$(get_msg 'cmd_usage')\n" "${0}" "${cmd}" "${usage}" +} + +is_cmd() +{ + local cmd="${1}" + + [ "x$(printf '%s' "${cmds}" | grep "^${cmd}$")" = "x${cmd}" ] +} + +run_cmd() +{ + local cmd="${1}" + local cmd_clean= + shift + + cmd_clean="$(printf '%s' "${cmd}" | \ + tr '[A-Z]' '[a-z]' | tr -C '[a-z0-9_]' '_')" + if is_cmd "${cmd}"; then + "cmd_${cmd_clean}_main" "${@}" + else + error 1 "$(get_msg 'cmd_not_found')" "${cmd}" + fi +} diff --git a/src/control.sh b/src/control.sh new file mode 100644 index 0000000..6907b3d --- /dev/null +++ b/src/control.sh @@ -0,0 +1,136 @@ +# pro-archman +# lib/control.sh +# Functions for parsing control files. +# +# Copyright (C) 2012, 2013 Patrick "P. J." McDermott +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +[ "x${_CONTROL_SM+set}" = 'xset' ] && return 0 +_CONTROL_SM=1 + +use output +use locale + +control_file= +control_line_nr= + +parse_control() +{ + local field_cb="${2}" + local req_fields="${3}" + local opt_fields="${4}" + local all_fields= + local got_fields= + local line= + local name= + local value= + + control_file="${1}" + control_line_nr=0 + + req_fields="$(printf '%s\n' ${req_fields})" + opt_fields="$(printf '%s\n' ${opt_fields})" + all_fields="${LF}${req_fields}${LF}${opt_fields}${LF}" + got_fields="${LF}" + + while IFS='' read -r line; do + control_line_nr=$(($control_line_nr + 1)) + if [ "x$(echo ${line})" = 'x' ]; then + parse_control_error 'control_empty_line' + elif [ "x${line#\#}" != "x${line}" ]; then + # Comment. + : + elif [ "x${line# }" = "x${line}" ]; then + # "Name: Value" line. + if [ "x${name}" != 'x' ]; then + if ! "${field_cb}" "${name}" "${value}"; then + return 0 + fi + fi + name="${line%%:*}" + value="${line#*:}" + value="${value# }" + if [ "x${name}" = 'x' -o "x${name}" = "x${line}" ]; then + parse_control_error 'control_bad_nv' + continue + fi + if [ "x${req_fields}" != 'x' ]; then + if [ "x${all_fields%${LF}${name}${LF}*}" = \ + "x${all_fields}" ]; then + # Unknown field. + parse_control_error \ + 'control_unknown_field' \ + "${name}" + else + # Remove field from list of required + # fields. + req_fields="$(printf '%s' \ + "${req_fields}" | \ + grep -Fv "${name}")" + fi + fi + if [ "x${got_fields%${LF}${name}${LF}*}" != \ + "x${got_fields}" ]; then + # Duplicate field. + parse_control_error 'control_duplicate_field' \ + "${name}" + else + got_fields="${got_fields}${name}${LF}" + fi + else + # Continuation line. + if [ "x${name}" = 'x' ]; then + # Expecting a "Name: Value" line. + parse_control_error 'control_found_continuation' + continue + fi + value="${value}${LF}${line# }" + fi + done <<-EOF + $(cat "${control_file}") + EOF + + if [ "x${name}" != 'x' ]; then + if ! "${field_cb}" "${name}" "${value}"; then + return 0 + fi + fi + + if [ "x${req_fields}" != 'x' ]; then + req_fields="$(printf "%s$(get_msg 'list_item_separator')" \ + ${req_fields})" + parse_control_error 'control_missing_fields' "${req_fields}" + fi + + return 0 +} + +parse_control_error() +{ + local msgid="${1}" + local file_info= + shift 1 + + if [ ${control_line_nr} -eq 0 ]; then + file_info="$(printf '%20s:' "${control_file}")" + else + file_info="$(printf '%20s(l%d):' "${control_file}" \ + "${control_line_nr}")" + fi + + warn "${file_info} $(get_msg "${msgid}")" "${@}" + + return 0 +} diff --git a/src/db.sh b/src/db.sh new file mode 100644 index 0000000..e2f8fb7 --- /dev/null +++ b/src/db.sh @@ -0,0 +1,293 @@ +# pro-archman +# lib/db.sh +# Functions for querying and modifying the database +# +# Copyright (C) 2013 Patrick "P. J." McDermott +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +[ "x${_DB_SM+set}" = 'xset' ] && return 0 +_DB_SM=1 + +use dir + +# +# Functions for the suites indices +# + +db_get_srcver() +{ + local chan="${1}" + local dist="${2}" + local source="${3}" + local dir= + + dir="${base_dir}/feeds/${chan}/${dist}/.db" + dir="${dir}/$(hash_name "${source}")/${source}" + if [ -f "${dir}/srcver" ]; then + cat "${dir}/srcver" + fi + + return 0 +} + +db_set_srcver() +{ + local chan="${1}" + local dist="${2}" + local source="${3}" + local srcver="${4}" + local dir= + + dir="${base_dir}/feeds/${chan}/${dist}/.db" + dir="${dir}/$(hash_name "${source}")/${source}" + mkdir -p "${dir}" + printf '%s\n' "${srcver}" >"${dir}/srcver" + + return 0 +} + +db_del_srcver() +{ + local chan="${1}" + local dist="${2}" + local source="${3}" + local dir= + + dir="${base_dir}/feeds/${chan}/${dist}/.db" + dir="${dir}/$(hash_name "${source}")/${source}" + rm -f "${dir}/srcver" + # Remove ".../.db/<hash>/<source>". + rmdir "${dir}" + # Try to remove ".../.db/<hash>" and ".../.db". + for dir in "${dir%/*}" "${dir%/*/*}"; do + try_rmdir "${dir}" || break + done + + return 0 +} + +db_get_binver() +{ + local chan="${1}" + local dist="${2}" + local arch="${3}" + local plat="${4}" + local source="${5}" + local dir= + + dir="${base_dir}/feeds/${chan}/${dist}/.db" + dir="${dir}/$(hash_name "${source}")/${source}/${arch}_${plat}" + if [ -f "${dir}/binver" ]; then + cat "${dir}/binver" + fi + + return 0 +} + +db_set_binver() +{ + local chan="${1}" + local dist="${2}" + local arch="${3}" + local plat="${4}" + local source="${5}" + local binver="${6}" + local dir= + + dir="${base_dir}/feeds/${chan}/${dist}/.db" + dir="${dir}/$(hash_name "${source}")/${source}/${arch}_${plat}" + mkdir -p "${dir}" + printf '%s\n' "${binver}" >"${dir}/binver" + + return 0 +} + +db_del_binver() +{ + local chan="${1}" + local dist="${2}" + local arch="${3}" + local plat="${4}" + local source="${5}" + local dir= + + dir="${base_dir}/feeds/${chan}/${dist}/.db" + dir="${dir}/$(hash_name "${source}")/${source}/${arch}_${plat}" + rm -f "${dir}/binver" + # Remove ".../.db/<hash>/<source>/<arch>_<plat>". + rmdir "${dir}" + + return 0 +} + +db_foreach_source() +{ + local chan="${1}" + local dist="${2}" + local cb="${3}" + local dir= + + shift 3 + + dir="${base_dir}/feeds/${chan}/${dist}/.db" + # For each hash: + for dir in "${dir}/"*/; do + if [ ! -d "${dir}" ]; then + continue + fi + # For each source: + for dir in "${dir}/"*/; do + if [ ! -d "${dir}" ]; then + continue + fi + dir="${dir%/}" + dir="${dir##*/}" + "${cb}" "${chan}" "${dist}" "${dir}" "${@}" + done + done + + return 0 +} + +db_get_archplats() +{ + local chan="${1}" + local dist="${2}" + local source="${3}" + local dir= + + dir="${base_dir}/feeds/${chan}/${dist}/.db" + dir="${dir}/$(hash_name "${source}")/${source}" + for dir in "${dir}/"*_*/; do + if [ ! -d "${dir}" ]; then + continue + fi + dir="${dir%/}" + dir="${dir##*/}" + printf '%s %s\n' "${dir%%_*}" "${dir#*_}" + done + + return 0 +} + +# +# Functions for the pool indices +# + +db_get_packages() +{ + local arch="${1}" + local plat="${2}" + local source="${3}" + local binver="${4}" + local dir= + + dir="${base_dir}/pool/$(hash_name "${source}")/${source}/.db" + dir="${dir}/${binver}_${arch}_${plat}" + if [ -f "${dir}/packages" ]; then + cat "${dir}/packages" + fi + + return 0 +} + +db_add_package() +{ + local arch="${1}" + local plat="${2}" + local source="${3}" + local binver="${4}" + local size="${5}" + local sect="${6}" + local pkg="${7}" + local dir= + + dir="${base_dir}/pool/$(hash_name "${source}")/${source}/.db" + dir="${dir}/${binver}_${arch}_${plat}" + mkdir -p "${dir}" + printf '%s %s %s\n' "${size}" "${sect}" "${pkg}" >>"${dir}/packages" + + return 0 +} + +db_del_packages() +{ + local arch="${1}" + local plat="${2}" + local source="${3}" + local binver="${4}" + local dir= + + dir="${base_dir}/pool/$(hash_name "${source}")/${source}/.db" + dir="${dir}/${binver}_${arch}_${plat}" + rm -f "${dir}/packages" + # Remove "pool/<hash>/<source>/.db/<binver>_<arch>_<plat>". + rmdir "${dir}" + # Try to remove "pool/<hash>/<source>/.db". + try_rmdir "${dir%/*}" + + return 0 +} + +db_inc_references() +{ + local arch="${1}" + local plat="${2}" + local source="${3}" + local binver="${4}" + local dir= + local refs= + + dir="${base_dir}/pool/$(hash_name "${source}")/${source}/.db" + dir="${dir}/${binver}_${arch}_${plat}" + if [ -f "${dir}/references" ]; then + refs="$(cat "${dir}/references")" + refs=$(($refs + 1)) + else + refs=1 + mkdir -p "${dir}" + fi + printf '%d\n' "${refs}" >"${dir}/references" + printf '%d\n' "${refs}" + + return 0 +} + +db_dec_references() +{ + local arch="${1}" + local plat="${2}" + local source="${3}" + local binver="${4}" + local dir= + local refs= + + dir="${base_dir}/pool/$(hash_name "${source}")/${source}/.db" + dir="${dir}/${binver}_${arch}_${plat}" + if [ -f "${dir}/references" ]; then + refs="$(cat "${dir}/references")" + refs=$(($refs - 1)) + else + refs=0 + fi + if [ ${refs} -eq 0 ]; then + rm -f "${dir}/references" + else + printf '%d\n' "${refs}" >"${dir}/references" + fi + printf '%d\n' "${refs}" + + return 0 +} diff --git a/src/dir.sh b/src/dir.sh new file mode 100644 index 0000000..0b866c0 --- /dev/null +++ b/src/dir.sh @@ -0,0 +1,74 @@ +# pro-archman +# lib/dir.sh +# Miscellaneous directory-related functions +# +# Copyright (C) 2013 Patrick "P. J." McDermott +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +[ "x${_DIR_SM+set}" = 'xset' ] && return 0 +_DIR_SM=1 + +hash_name() +{ + local name="${1}" + local hash= + + if [ "x${name}" != "x${name#lib?}" ]; then + hash="$(printf '%s\n' "${name}" | sed 's/^\(lib.\).*$/\1/')" + elif [ "x${name}" != "x${name#src-?}" ]; then + hash="$(printf '%s\n' "${name}" | sed 's/^\(src-.\).*$/\1/')" + else + hash="$(printf '%s\n' "${name}" | sed 's/^\(.\).*$/\1/')" + fi + printf '%s\n' "${hash}" + + return 0 +} + +dir_is_empty() +{ + local dir="${1}" + local ret= + local dirent= + + ret=0 + + # Patterns to match all dirents except "." and "..": + # * dirents whose names don't start with "." + # .[!.] dirents whose names start with ".", are two characters long, + # and aren't ".." + # .??* dirents whose names start with "." and are three or more + # characters long + for dirent in "${dir}/"* "${dir}/".[!.] "${dir}/".[!.] "${dir}/".??*; do + if [ -e "${dirent}" ]; then + ret=1 + break + fi + done + + return ${ret} +} + +try_rmdir() +{ + local dir="${1}" + + if dir_is_empty "${dir}"; then + rmdir "${dir}" + return ${?} + else + return 1 + fi +} diff --git a/src/garbage.sh b/src/garbage.sh new file mode 100644 index 0000000..a6f1a3d --- /dev/null +++ b/src/garbage.sh @@ -0,0 +1,76 @@ +# pro-archman +# lib/garbage.sh +# Functions for garbage collection +# +# Copyright (C) 2013 Patrick "P. J." McDermott +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +[ "x${_GARBAGE_SM+set}" = 'xset' ] && return 0 +_GARBAGE_SM=1 + +use locale +use output +use dir + +collect_garbage() +{ + local cur_time= + local garbage= + local gar_time= + local dirs= + local file= + + info_v "$(get_msg 'collecting_garbage')" + + # NB: The %s format conversion specifier is not specified by POSIX, but + # it is supported by the GNU and BusyBox implementations of date. + cur_time=$(date '+%s') + + for garbage in "${base_dir}/.db/garbage/"*; do + if [ ! -f "${garbage}" ]; then + continue + fi + gar_time=${garbage##*/} + if [ ${cur_time} -lt ${gar_time} ]; then + break + fi + while read -r dirs file; do + info_v "$(get_msg 'collecting_garbage_file')" "${file}" + rm -f "${base_dir}/${file}" + while [ ${dirs} -gt 0 ]; do + file="${file%/*}" + try_rmdir "${base_dir}/${file}" + dirs=$(($dirs - 1)) + done + done <"${garbage}" + rm -f "${garbage}" + done +} + +mark_pool_garbage() +{ + local file="${1}" + local time= + + info_v "$(get_msg 'marking_garbage_file')" "${file}" + + # NB: The %s format conversion specifier is not specified by POSIX, but + # it is supported by the GNU and BusyBox implementations of date. + time=$(date '+%s') + time=$(($time + $conf_pool_gc_delay)) + + mkdir -p "${base_dir}/.db/garbage" + printf '2 %s\n' "${file}" >>"${base_dir}/.db/garbage/${time}" +} diff --git a/src/include.sh b/src/include.sh new file mode 100644 index 0000000..42ccf24 --- /dev/null +++ b/src/include.sh @@ -0,0 +1,181 @@ +# pro-archman +# lib/include.sh +# Functions for including changes +# +# Copyright (C) 2013, 2014 Patrick "P. J." McDermott +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +[ "x${_INCLUDE_SM+set}" = 'xset' ] && return 0 +_INCLUDE_SM=1 + +use control +use db +use locale +use output + +_INCLUDE_CHANGES_FIELDS='Format Source Binary Version Architecture Platform +Distribution Maintainer Changed-By Date Description Changes Files' + +_include_format= +_include_source= +_include_version= +_include_distribution= +_include_files= + +include_changes() +{ + local changes="${1}" + local chan= + local dist= + local source= + local srcver= + local script= + local bvaps= + local binver= + local arch= + local plat= + local old_ver= + local files= + local size= + local sect= + local file= + local pkg= + local pool_file= + + parse_control "${changes}" _include_changes_field \ + "${_INCLUDE_CHANGES_FIELDS}" '' + if [ "x${_include_format}" != 'x1.0' ]; then + error 2 "$(get_msg 'include_unknown_changes_format')" \ + "${changes}" "${_include_format}" + fi + chan="${conf_incoming_channel}" + dist="${_include_distribution}" + source="${_include_source}" + srcver="${_include_version}" + + info "$(get_msg 'include_including')" "${source}" "${srcver}" \ + "${chan}" "${dist}" + + if [ "x${_include_files}" = 'x' ]; then + warn "$(get_msg 'include_no_files')" + return 0 + fi + + # List of (binver, arch, plat) tuples to be checked later. + script='s/[0-9][0-9]* [^ ][^ ]* [^_]*_\([^_]*\)' + script="${script}"'_\([^_]*\)_\([^_]*\)\.opk/\1 \2 \3/p' + bvaps="$(printf '%s\n' "${_include_files}" | \ + sed -n "${script}" | LC_COLLATE='C' sort | uniq)" + + # Pre-inclusion database sanity checks and updates: check for an + # existing version of the package in the suite. + old_ver="$(db_get_srcver "${chan}" "${dist}" "${source}")" + if [ "x${old_ver}" = 'x' ]; then + # New package. + db_set_srcver "${chan}" "${dist}" "${source}" "${srcver}" + elif [ "x${old_ver}" != "x${_include_version}" ]; then + # New source version. Remove the old source package from the + # suite. + remove_source_from_suite "${chan}" "${dist}" "${source}" + db_set_srcver "${chan}" "${dist}" "${source}" "${srcver}" + else + # Same source version. Hopefully different binary version, + # architecture, and/or platform. Make sure such "bvap" tuples + # are new. + while read -r binver arch plat; do + old_ver="$(db_get_binver "${chan}" "${dist}" \ + "${arch}" "${plat}" "${source}")" + if [ "x${old_ver}" = "x${binver}" ]; then + error 2 "$(get_msg 'include_bvap_exists')" \ + "${binver}" "${arch}" "${plat}" + fi + done <<-EOF + ${bvaps} + EOF + fi + + # For each (binver, arch, plat) tuple in the package changes: + # * Remove old binary packages of the same arch and plat from the + # suite. + # * Set the new binary version of packages of the arch and plat in the + # suite. + # * Set the reference count for the tuple to 1. + # This is done separately from the next loop because it must be done + # exactly once for each bvap tuple. The next loop can hit any given + # bvap tuple multiple times. + while read -r binver arch plat; do + old_ver="$(db_get_binver "${chan}" "${dist}" \ + "${arch}" "${plat}" "${source}")" + if [ "x${old_ver}" != 'x' ]; then + remove_packages_from_suite_archplat "${chan}" \ + "${dist}" "${arch}" "${plat}" "${source}" + fi + db_set_binver "${chan}" "${dist}" "${arch}" "${plat}" \ + "${source}" "${binver}" + db_inc_references "${arch}" "${plat}" "${source}" "${binver}" \ + >/dev/null + done <<-EOF + ${bvaps} + EOF + + # Include each binary package. + files='' + while read -r size sect file; do + if [ "x${file##[ ]}" = 'x' ]; then + continue + fi + IFS='_' read -r pkg binver arch plat <<-EOF + ${file%.opk} + EOF + db_add_package "${arch}" "${plat}" "${source}" "${binver}" \ + "${size}" "${sect}" "${pkg}" + pool_file="pool/$(hash_name "${source}")/${source}" + pool_file="${pool_file}/${pkg}_${binver}_${arch}_${plat}.opk" + file="$(dirname "${changes}")/${file}" + files="${files} ${file}" + cp -p "${file}" "${base_dir}/${pool_file}" + feed_add_package "${chan}" "${dist}" "${arch}" "${plat}" \ + "${sect}" "${pkg}" "${size}" "${pool_file}" + done <<-EOF + ${_include_files} + EOF + + printf '%s\n' ${files} + return 0 +} + +_include_changes_field() +{ + local name="${1}" + local value="${2}" + + case "${name}" in + 'Format') + _include_format="${value}" + ;; + 'Source') + _include_source="${value}" + ;; + 'Version') + _include_version="${value}" + ;; + 'Distribution') + _include_distribution="${value}" + ;; + 'Files') + _include_files="${value}" + ;; + esac +} diff --git a/src/index.sh b/src/index.sh new file mode 100644 index 0000000..8749897 --- /dev/null +++ b/src/index.sh @@ -0,0 +1,200 @@ +# pro-archman +# lib/index.sh +# Functions for working with package feed indices +# +# Copyright (C) 2013 Patrick "P. J." McDermott +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +[ "x${_INDEX_SM+set}" = 'xset' ] && return 0 +_INDEX_SM=1 + +use locale +use output +use dir + +feed_add_package() +{ + local chan="${1}" + local dist="${2}" + local arch="${3}" + local plat="${4}" + local sect="${5}" + local pkg="${6}" + local size="${7}" + local file="${8}" + local pkg_hash= + local feed_hash_idx= + local old_dir= + + info_v "$(get_msg 'feed_adding')" "${pkg}" \ + "${chan}" "${dist}" "${arch}" "${plat}" "${sect}" + + pkg_hash="$(hash_name "${pkg}")" + + # Add package metadata to feed hash index. + feed_hash_idx="${base_dir}/feeds/${chan}/${dist}/${arch}/${plat}" + feed_hash_idx="${feed_hash_idx}/${sect}/.db/${pkg_hash}" + mkdir -p "${feed_hash_idx}/info" + tar -xzOf "${base_dir}/${file}" 'control.tar.gz' | \ + tar -xzO './control' >"${feed_hash_idx}/info/${pkg}.control" + printf 'Filename: %s\nSize: %s\nMD5sum: %s\nSHA256sum: %s\n\n' \ + "../../../../../../${file}" "${size}" \ + "$(md5sum "${base_dir}/${file}" | sed 's/ .*$//')" \ + "$(sha256sum "${base_dir}/${file}" | sed 's/ .*$//')" \ + >>"${feed_hash_idx}/info/${pkg}.control" + + # Mark feed index fragment as outdated. + old_dir="${base_dir}/feeds/.db/${chan}_${dist}/${arch}_${plat}" + old_dir="${old_dir}/${sect}" + mkdir -p "${old_dir}" + >"${old_dir}/${pkg_hash}" + + return 0 +} + +feed_remove_package() +{ + local chan="${1}" + local dist="${2}" + local arch="${3}" + local plat="${4}" + local sect="${5}" + local pkg="${6}" + local pkg_hash= + local feed_hash_idx= + local old_dir= + + info_v "$(get_msg 'feed_removing')" "${pkg}" \ + "${chan}" "${dist}" "${arch}" "${plat}" "${sect}" + + pkg_hash="$(hash_name "${pkg}")" + + # Remove package metadata from feed hash index. + feed_hash_idx="${base_dir}/feeds/${chan}/${dist}/${arch}/${plat}" + feed_hash_idx="${feed_hash_idx}/${sect}/.db/${pkg_hash}" + rm -f "${feed_hash_idx}/info/${pkg}.control" + try_rmdir "${feed_hash_idx}/info" + + # Mark feed index fragment as outdated. + old_dir="${base_dir}/feeds/.db/${chan}_${dist}/${arch}_${plat}" + old_dir="${old_dir}/${sect}" + mkdir -p "${old_dir}" + >"${old_dir}/${pkg_hash}" + + return 0 +} + +update_feeds() +{ + local suite_dirent= + local chan= + local dist= + local suite= + local archplat_dirent= + local arch= + local plat= + local archplat= + local sect_dirent= + local sect= + local manifest_entry= + local hash_dirent= + local idx= + + info_v "$(get_msg 'updating_feeds')" + + # For each suite: + for suite_dirent in "${base_dir}/feeds/.db/"*_*/; do + if [ ! -d "${suite_dirent}" ]; then + continue + fi + chan="${suite_dirent%/}" + chan="${chan##*/}" + dist="${chan##*_}" + chan="${chan%_*}" + suite="${base_dir}/feeds/${chan}/${dist}" + exec 3>"${suite}/Manifest~" + # For each archplat: + for archplat_dirent in "${suite_dirent}/"*_*/; do + if [ ! -d "${archplat_dirent}" ]; then + continue + fi + arch="${archplat_dirent%/}" + arch="${arch##*/}" + plat="${arch##*_}" + arch="${arch%_*}" + archplat="${suite}/${arch}/${plat}" + # For each section: + for sect_dirent in "${archplat_dirent}/"*/; do + if [ ! -d "${sect_dirent}" ]; then + continue + fi + sect="${sect_dirent%/}" + sect="${sect##*/}" + info_v "$(get_msg 'updating_feed')" \ + "${chan}" "${dist}" \ + "${arch}" "${plat}" "${sect}" + manifest_entry="${arch}/${plat}/${sect}" + sect="${archplat}/${sect}" + # For each package name hash: + for hash_dirent in "${sect_dirent}/"*; do + if [ ! -f "${hash_dirent}" ]; then + continue + fi + idx="${sect}/.db/${hash_dirent##*/}" + # Ensure there are still packages here. + if [ -d "${idx}/info" ]; then + cat "${idx}/info/"*.control \ + >"${idx}/Packages" + else + rm -f "${idx}/Packages" + rmdir "${idx}" + fi + rm -f "${hash_dirent}" + done + # Ensure there are still packages here. + if ! try_rmdir "${sect}/.db"; then + cat "${sect}/.db/"*/Packages \ + >"${sect}/Packages~" + mv "${sect}/Packages~" \ + "${sect}/Packages" + if ${conf_gzip}; then + gzip -9c "${sect}/Packages" \ + >"${sect}/Packages.gz" + fi + printf '%s\n' "${manifest_entry}" >&3 + else + rm -f "${sect}/Packages" \ + "${sect}/Packages.gz" + fi + rmdir "${sect_dirent}" + try_rmdir "${sect}" + done + rmdir "${archplat_dirent}" + try_rmdir "${archplat}" + try_rmdir "${archplat%/*}" + done + rmdir "${suite_dirent}" + exec 3>&- + if [ -s "${suite}/Manifest~" ]; then + mv "${suite}/Manifest~" "${suite}/Manifest" + else + rm -f "${suite}/Manifest~" "${suite}/Manifest" + rmdir "${suite}" + rmdir "${suite%/*}" + fi + done + + return 0 +} diff --git a/src/locale.sh b/src/locale.sh new file mode 100644 index 0000000..25a7758 --- /dev/null +++ b/src/locale.sh @@ -0,0 +1,88 @@ +# pro-archman +# lib/locale.sh +# Locale functions +# +# Copyright (C) 2012, 2013 Patrick "P. J." McDermott +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +[ "x${_LOCALE_SM+set}" = 'xset' ] && return 0 +_LOCALE_SM=1 + +LOCALEDIR='@@LOCALEDIR@@' +DEFAULT_LOCALE='en_US' +TEXT_DOMAIN='pro_archman' + +load_locale() +{ + local localedir= + + # Make sure LC_MESSAGES is set. + if [ "x${LC_MESSAGES+set}" != 'xset' ]; then + if [ "x${LC_ALL+set}" = 'xset' ]; then + LC_MESSAGES="${LC_ALL}" + elif [ "x${LANG+set}" = 'xset' ]; then + LC_MESSAGES="${LANG}" + else + LC_MESSAGES="${DEFAULT_LOCALE}" + fi + fi + + if [ "${ARCHMAN_LOCALEDIR+set}" = 'set' ]; then + localedir="${ARCHMAN_LOCALEDIR:-.}" + else + localedir="${LOCALEDIR}" + fi + + # Try to load the locale. + if ! _try_load_locale "${localedir}" \ + "${LC_MESSAGES%.*}"; then + if ! _try_load_locale "${localedir}" \ + "${LC_MESSAGES%_*}"; then + if ! _try_load_locale "${localedir}" \ + "${DEFAULT_LOCALE}"; then + warn 'Cannot load locale' + return 1 + fi + fi + fi + + return 0 +} + +get_msg() +{ + local msgid="${1}" + + eval "printf '%s' \"\${msg_${TEXT_DOMAIN}_${msgid}}\"" + + return 0 +} + +_try_load_locale() +{ + local localedir="${1}" + local locale="${2}" + local ms= + + for ms in "${localedir}/${locale}/LC_MESSAGES/${TEXT_DOMAIN}.ms" \ + "${localedir}/${locale}.ms"; do + if [ -f "${ms}" ]; then + . "${ms}" + return 0 + fi + done + + return 1 +} diff --git a/src/output.sh b/src/output.sh new file mode 100644 index 0000000..d3d150f --- /dev/null +++ b/src/output.sh @@ -0,0 +1,68 @@ +# pro-archman +# lib/output.sh +# Functions for printing messages +# +# Copyright (C) 2013 Patrick "P. J." McDermott +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +[ "x${_OUTPUT_SM+set}" = 'xset' ] && return 0 +_OUTPUT_SM=1 + +error() +{ + local status=${1} + local fmt="${2}" + shift 2 + + printf '%s: Error: ' "${0##*/}" >&2 + printf "${fmt}\n" "${@}" >&2 + + # In a subshell, this will have no effect, so the shell's exit status + # will be 128+SIGINT. Meh. + exit_status=${status} + kill -s INT ${$} +} + +warn() +{ + local fmt="${1}" + shift 1 + + printf '%s: Warning: ' "${0##*/}" >&2 + printf "${fmt}\n" "${@}" >&2 + + return 0 +} + +info() +{ + local fmt="${1}" + shift 1 + + printf '%s: ' "${0##*/}" >&2 + printf "${fmt}\n" "${@}" >&2 + + return 0 +} + +info_v() +{ + if ${opt_v:-false} || ${conf_verbose:-false}; then + info "${@}" + return ${?} + fi + + return 0 +} diff --git a/src/remove.sh b/src/remove.sh new file mode 100644 index 0000000..a3a06dd --- /dev/null +++ b/src/remove.sh @@ -0,0 +1,92 @@ +# pro-archman +# lib/remove.sh +# Functions for removing packges +# +# Copyright (C) 2013 Patrick "P. J." McDermott +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +[ "x${_REMOVE_SM+set}" = 'xset' ] && return 0 +_REMOVE_SM=1 + +use locale +use output +use db + +remove_source_from_suite() +{ + local chan="${1}" + local dist="${2}" + local source="${3}" + local arch= + local plat= + + info "$(get_msg 'removing_from_suite')" \ + "${source}" "${chan}" "${dist}" + + while read -r arch plat; do + remove_packages_from_suite_archplat \ + "${chan}" "${dist}" "${arch}" "${plat}" "${source}" + done <<-EOF + $(db_get_archplats "${chan}" "${dist}" "${source}") + EOF + + db_del_srcver "${chan}" "${dist}" "${source}" + + return 0 +} + +remove_packages_from_suite_archplat() +{ + local chan="${1}" + local dist="${2}" + local arch="${3}" + local plat="${4}" + local source="${5}" + local binver= + local refs= + local size= + local sect= + local pkg= + local file= + + info_v "$(get_msg 'removing_from_suite_archplat')" \ + "${source}" "${chan}" "${dist}" "${arch}" "${plat}" + + binver="$(db_get_binver "${chan}" "${dist}" "${arch}" "${plat}" \ + "${source}")" + if [ "x${binver}" = 'x' ]; then + return 0 + fi + db_del_binver "${chan}" "${dist}" "${arch}" "${plat}" "${source}" + refs=$(db_dec_references "${arch}" "${plat}" "${source}" "${binver}") + + while read -r size sect pkg; do + feed_remove_package "${chan}" "${dist}" "${arch}" "${plat}" \ + "${sect}" "${pkg}" + if [ ${refs} -eq 0 ]; then + file="pool/$(hash_name "${source}")/${source}" + file="${file}/${pkg}_${binver}_${arch}_${plat}.opk" + mark_pool_garbage "${file}" + fi + done <<-EOF + $(db_get_packages "${arch}" "${plat}" "${source}" "${binver}") + EOF + + if [ ${refs} -eq 0 ]; then + db_del_packages "${arch}" "${plat}" "${source}" "${binver}" + fi + + return 0 +} diff --git a/src/suite.sh b/src/suite.sh new file mode 100644 index 0000000..4de39ef --- /dev/null +++ b/src/suite.sh @@ -0,0 +1,105 @@ +# pro-archman +# lib/suite.sh +# Functions for working with suites +# +# Copyright (C) 2013 Patrick "P. J." McDermott +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +[ "x${_SUITE_SM+set}" = 'xset' ] && return 0 +_SUITE_SM=1 + +use db +use dir +use remove +use locale +use output + +copy_suite() +{ + local src_chan="${1}" + local src_dist="${2}" + local dst_chan="${3}" + local dst_dist="${4}" + + info "$(get_msg 'suite_copying')" \ + "${src_chan}" "${src_dist}" "${dst_chan}" "${dst_dist}" + + remove_suite "${dst_chan}" "${dst_dist}" + + db_foreach_source "${src_chan}" "${src_dist}" _suite_copy_source \ + "${dst_chan}" "${dst_dist}" +} + +remove_suite() +{ + local chan="${1}" + local dist="${2}" + + info "$(get_msg 'suite_removing')" \ + "${chan}" "${dist}" + + db_foreach_source "${chan}" "${dist}" _suite_remove_source +} + +_suite_copy_source() +{ + local src_chan="${1}" + local src_dist="${2}" + local source="${3}" + local dst_chan="${4}" + local dst_dist="${5}" + local srcver= + local arch= + local plat= + local binver= + local size= + local sect= + local pkg= + local pool_file= + + srcver="$(db_get_srcver "${chan}" "${dist}" "${source}")" + db_set_srcver "${dst_chan}" "${dst_dist}" "${source}" "${srcver}" + + while read -r arch plat; do + binver="$(db_get_binver "${src_chan}" "${src_dist}" \ + "${arch}" "${plat}" "${source}")" + db_set_binver "${dst_chan}" "${dst_dist}" "${arch}" "${plat}" \ + "${source}" "${binver}" + db_inc_references "${arch}" "${plat}" "${source}" "${binver}" \ + >/dev/null + while read -r size sect pkg; do + pool_file="pool/$(hash_name "${source}")/${source}" + pool_file="${pool_file}/${pkg}_${binver}" + pool_file="${pool_file}_${arch}_${plat}.opk" + feed_add_package "${dst_chan}" "${dst_dist}" \ + "${arch}" "${plat}" \ + "${sect}" "${pkg}" "${size}" "${pool_file}" + done <<-EOF + $(db_get_packages "${arch}" "${plat}" \ + "${source}" "${binver}") + EOF + done <<-EOF + $(db_get_archplats "${src_chan}" "${src_dist}" "${source}") + EOF +} + +_suite_remove_source() +{ + local chan="${1}" + local dist="${2}" + local source="${3}" + + remove_source_from_suite "${chan}" "${dist}" "${source}" +} |