summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/cmd.sh126
-rw-r--r--src/control.sh136
-rw-r--r--src/db.sh293
-rw-r--r--src/dir.sh74
-rw-r--r--src/garbage.sh76
-rw-r--r--src/include.sh181
-rw-r--r--src/index.sh200
-rw-r--r--src/locale.sh88
-rw-r--r--src/output.sh68
-rw-r--r--src/remove.sh92
-rw-r--r--src/suite.sh105
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}"
+}