summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/archplat.sh144
-rw-r--r--src/block.sh86
-rw-r--r--src/cmd.sh122
-rw-r--r--src/control.sh147
-rw-r--r--src/deps.sh156
-rw-r--r--src/dir.sh56
-rw-r--r--src/fd.sh92
-rw-r--r--src/feed.sh137
-rw-r--r--src/getopt.sh52
-rw-r--r--src/install.sh266
-rw-r--r--src/local.mk23
-rw-r--r--src/locale.sh84
-rw-r--r--src/mutex.sh54
-rw-r--r--src/opkg.sh29
-rw-r--r--src/output.sh56
-rw-r--r--src/package.sh62
-rw-r--r--src/pkg.sh51
-rw-r--r--src/profile.sh165
-rw-r--r--src/rand.sh42
-rw-r--r--src/session.sh210
-rw-r--r--src/substvars.sh103
-rw-r--r--src/vardata.sh52
22 files changed, 2188 insertions, 1 deletions
diff --git a/src/archplat.sh b/src/archplat.sh
new file mode 100644
index 0000000..b8324a0
--- /dev/null
+++ b/src/archplat.sh
@@ -0,0 +1,144 @@
+# Functions for matching architectures and platforms
+#
+# Copyright (C) 2012 Patrick "P. J." McDermott
+#
+# This file is part of the ProteanOS Development Kit.
+#
+# The ProteanOS Development Kit 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.
+#
+# The ProteanOS Development Kit 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 the ProteanOS Development Kit. If not, see
+# <http://www.gnu.org/licenses/>.
+
+match_arch()
+{
+ local arch="${1}"
+ local arch_field="${2}"
+ local a=
+ local a_ere=
+
+ for a in ${arch_field}; do
+
+ # "all" == "all"
+ if [ "${arch}" = 'all' ]; then
+ if [ "${a}" = 'all' ]; then
+ return 0
+ else
+ continue
+ fi
+ fi
+
+ # "foo-bar-baz" == "any"
+ if [ "${a}" = 'any' ]; then
+ return 0
+ fi
+
+ # Generate an ERE to match hyphenated architectures.
+ a_ere="$(printf '%s\n' "${a}" | sed \
+ 's/^any-/[^-]+-/;s/-any-/-[^-]+-/g;s/-any$/-[^-]+/g')"
+
+ # "foo-bar-baz" == "foo-any-any"
+ if echo "${arch}" | grep -E "${a_ere}" >/dev/null 2>&1; then
+ return 0
+ fi
+
+ done
+
+ # Nothing matched.
+ return 1
+}
+
+match_plat()
+{
+ local plat="${1}"
+ local plat_field="${2}"
+ local p=
+
+ for p in ${plat_field}; do
+ if [ "x${plat}" = 'xall' ]; then
+ if [ "x${p}" = 'xall' ]; then
+ return 0
+ else
+ continue
+ fi
+ fi
+ if [ "x${p}" = 'xany' ]; then
+ return 0
+ fi
+ if [ "x${p}" = "x${plat}" ]; then
+ return 0
+ fi
+ done
+
+ return 1
+}
+
+arch_is_concerned()
+{
+ local host_arch="${1}"
+ local arches="${2}"
+ local arch=
+ local not_arch=
+ local seen_arch=
+
+ if [ "x${arches}" = 'x' ]; then
+ return 0
+ else
+ seen_arch=1
+ for arch in ${arches}; do
+ not_arch="${arch#!}"
+ if [ "x${not_arch}" != "x${arch}" ]; then
+ if match_arch "${host_arch}" "${not_arch}"
+ then
+ seen_arch=1
+ break
+ elif [ "x${host_arch}" != 'xall' ]; then
+ seen_arch=0
+ fi
+ elif match_arch "${host_arch}" "${arch}"; then
+ seen_arch=0
+ break
+ fi
+ done
+ return ${seen_arch}
+ fi
+}
+
+plat_is_concerned()
+{
+ local host_plat="${1}"
+ local plats="${2}"
+ local plat=
+ local not_plat=
+ local seen_plat=
+
+ if [ "x${plats}" = 'x' ]; then
+ return 0
+ else
+ seen_plat=1
+ for plat in ${plats}; do
+ not_plat="${plat#!}"
+ if [ "x${not_plat}" != "x${plat}" ]; then
+ if match_plat "${host_plat}" "${not_plat}"
+ then
+ seen_plat=1
+ break
+ elif [ "x${host_plat}" != 'xall' ]; then
+ seen_plat=0
+ fi
+ elif match_plat "${host_plat}" "${plat}"; then
+ seen_plat=0
+ break
+ fi
+ done
+ return ${seen_plat}
+ fi
+}
diff --git a/src/block.sh b/src/block.sh
new file mode 100644
index 0000000..f056305
--- /dev/null
+++ b/src/block.sh
@@ -0,0 +1,86 @@
+# Functions for mounting and unmounting block devices
+#
+# Copyright (C) 2015 Patrick "P. J." McDermott
+#
+# This file is part of the ProteanOS Development Kit.
+#
+# The ProteanOS Development Kit 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.
+#
+# The ProteanOS Development Kit 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 the ProteanOS Development Kit. If not, see
+# <http://www.gnu.org/licenses/>.
+
+is_block()
+{
+ local dev="${1}"
+
+ [ -b "${dev}" ]
+ return ${?}
+}
+
+check_block()
+{
+ local dev="${1}"
+
+ if ! [ -b "${dev}" ]; then
+ error 2 "$(get_msg 'block_device_invalid')" "${dev}"
+ fi
+}
+
+block_mount()
+{
+ local dev="${1}"
+ local dir=
+
+ check_block "${dev}"
+
+ rand
+ dir="$(get_vardata_dir 'mount')/block-$(printf '%010d' ${rand_x})"
+ if ! mkdir "${dir}"; then
+ error 2 "$(get_msg 'block_mkdir_fail')" "${dir}"
+ fi
+
+ if ! mount "${dev}" "${dir}"; then
+ rmdir "${dir}"
+ error 2 "$(get_msg 'block_mount_fail')" "${dev}"
+ fi
+
+ printf '%s' "${dir}"
+ return 0
+}
+
+block_umount()
+{
+ local dir="${1}"
+ local i=
+ local timed_out=
+
+ i=0
+ timed_out=false
+ while ! umount "${dir}"; do
+ i=$(($i + 1))
+ if [ ${i} -eq 10 ]; then
+ timed_out=true
+ break
+ fi
+ sleep 1
+ done
+
+ if ${timed_out}; then
+ error 2 "$(get_msg 'block_umount_fail')" "${dir}"
+ fi
+
+ if ! rmdir "${dir}"; then
+ error 2 "$(get_msg 'block_rmdir_fail')" "${dir}"
+ fi
+
+ return 0
+}
diff --git a/src/cmd.sh b/src/cmd.sh
new file mode 100644
index 0000000..d4e7824
--- /dev/null
+++ b/src/cmd.sh
@@ -0,0 +1,122 @@
+# Command-related functions
+#
+# Copyright (C) 2013 Patrick "P. J." McDermott
+#
+# This file is part of the ProteanOS Development Kit.
+#
+# The ProteanOS Development Kit 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.
+#
+# The ProteanOS Development Kit 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 the ProteanOS Development Kit. If not, see
+# <http://www.gnu.org/licenses/>.
+
+cmds=' '
+running_cmd=
+running_cmd_clean=
+
+register_cmd()
+{
+ local cmd="${1}"
+ shift 1
+
+ cmds="${cmds}${cmd} "
+ return 0
+}
+
+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 ${cmds}; do
+ 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\n' ${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
+ running_cmd="${cmd}"
+ running_cmd_clean="${cmd_clean}"
+ "cmd_${cmd_clean}_main" "${@}"
+ running_cmd=''
+ running_cmd_clean=''
+ 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..b25d00b
--- /dev/null
+++ b/src/control.sh
@@ -0,0 +1,147 @@
+# Functions for parsing control files.
+#
+# Copyright (C) 2012, 2013 Patrick "P. J." McDermott
+#
+# This file is part of the ProteanOS Development Kit.
+#
+# The ProteanOS Development Kit 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.
+#
+# The ProteanOS Development Kit 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 the ProteanOS Development Kit. If not, see
+# <http://www.gnu.org/licenses/>.
+
+control_file=
+control_line_nr=
+
+parse_control()
+{
+ local field_cb="${2}"
+ local paragraph_cb="${3}"
+ local req_fields="${4}"
+ local in_paragraph=
+ local line=
+ local para_req_fields=
+ local got_fields=
+ local name=
+ local value=
+
+ control_file="${1}"
+ control_line_nr=0
+
+ in_paragraph='false'
+
+ while IFS='' read -r line; do
+ control_line_nr=$(($control_line_nr + 1))
+ if [ "x$(echo ${line})" = 'x' ]; then
+ # Paragraph end.
+ if ${in_paragraph}; then
+ # The first line is blank to consolidate
+ # initialization code (see heredocument below).
+ in_paragraph='false'
+ if [ "x${name}" != 'x' ]; then
+ if ! "${field_cb}" "${name}" "${value}"
+ then
+ return 0
+ fi
+ fi
+ if ! "${paragraph_cb}"; then
+ return 0
+ fi
+ if [ "x${para_req_fields}" != 'x' ]; then
+ para_req_fields="$(printf "%s$(get_msg \
+ 'list_item_separator')" \
+ ${para_req_fields})"
+ parse_control_error \
+ 'control_missing_fields' \
+ "${para_req_fields}"
+ fi
+ fi
+ para_req_fields="$(printf '%s\n' ${req_fields})"
+ got_fields="${LF}"
+ name=''
+ value=''
+ elif [ "x${line#\#}" != "x${line}" ]; then
+ # Comment.
+ in_paragraph='true'
+ elif [ "x${line# }" = "x${line}" ]; then
+ # "Name: Value" line.
+ in_paragraph='true'
+ 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${para_req_fields}" != 'x' ]; then
+ # Remove field from list of required fields.
+ para_req_fields="$(printf '%s' "${para_req_fields}" | \
+ grep -Fv "${name}")"
+ 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.
+ in_paragraph='true'
+ 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
+ # The first blank line above triggers the paragraph end code to
+ # consolidate initialization code.
+ # The second blank line above is needed because the command substitution
+ # removes any trailing newlines.
+
+ if [ "x${name}" != 'x' ]; then
+ if ! "${field_cb}" "${name}" "${value}"; then
+ return 0
+ fi
+ 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/deps.sh b/src/deps.sh
new file mode 100644
index 0000000..862caff
--- /dev/null
+++ b/src/deps.sh
@@ -0,0 +1,156 @@
+# Functions for parsing dependency field values
+#
+# Copyright (C) 2012, 2014 Patrick "P. J." McDermott
+#
+# This file is part of the ProteanOS Development Kit.
+#
+# The ProteanOS Development Kit 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.
+#
+# The ProteanOS Development Kit 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 the ProteanOS Development Kit. If not, see
+# <http://www.gnu.org/licenses/>.
+
+parse_dep()
+{
+ local dep="${1}"
+ local host_arch="${2}"
+ local host_plat="${3}"
+ local pkgarchqual=
+ local pkg=
+ local archqual=
+ local relver=
+ local rel=
+ local ver=
+ local arches=
+ local plats=
+
+ dep="$(printf '%s\n' "${dep}" | sed -n '
+ H; # Store each input line in hold space.
+ ${ # On the last line:
+ g; # Restore everything to pattern space.
+ s/[\t\n]/ /g; # Replace tabs & newlines with spaces.
+ s/ *\([(\[<]\) */ \1/g; # One space before "(", "[", and "<".
+ s/ *\([)\]>]\) */\1 /g; # One space after "(", "[", and "<".
+ s/ *\(<[<=]\) */\1 /g; # One space after "<<" and "<=".
+ s/ *\(>[>=]\) */\1 /g; # One space after ">>" and ">=".
+ s/ *\(=\) */\1 /g; # One space after "=".
+ s/^ *//; # Remove leading spaces.
+ s/ *$//; # Remove trailing spaces.
+ s/ */ /g; # Remove duplicate spaces.
+ p; # Print the pattern space.
+ }; # End.
+ ')"
+
+ # dep is now of the form:
+ # pkg[:archqual] [(rel ver)] [\[arches\]] [<plats>]
+
+ pkgarchqual="${dep%% *}"
+ dep=" ${dep#* }"
+ pkg="${pkgarchqual%:*}"
+ if [ "x${pkg}" != "x${pkgarchqual}" ]; then
+ archqual="${pkgarchqual##*:}"
+ fi
+
+ if [ "x${dep# (*)}" != "x${dep}" ]; then
+ relver="${dep# (}"
+ relver="${relver%%)*}"
+ dep="${dep# (*)}"
+ rel="${relver% *}"
+ ver="${relver##* }"
+ fi
+
+ if [ "x${dep# \[*\]}" != "x${dep}" ]; then
+ arches="${dep# \[}"
+ arches="${arches%%\]*}"
+ dep="${dep# \[*\]}"
+ fi
+
+ if [ "x${dep# <*>}" != "x${dep}" ]; then
+ plats="${dep# <}"
+ plats="${plats%%>*}"
+ dep="${dep# <*>}"
+ fi
+
+ if [ "x${host_arch}" != 'x' ] && ! arch_is_concerned \
+ "${host_arch}" "${arches}"; then
+ return 0
+ fi
+ if [ "x${host_plat}" != 'x' ] && ! plat_is_concerned \
+ "${host_plat}" "${plats}"; then
+ return 0
+ fi
+
+ printf '%s' "${pkg}"
+ [ "x${archqual}" != 'x' ] && printf ':%s' "${archqual}"
+ [ "x${ver}" != 'x' ] && printf ' (%s %s)' "${rel}" "${ver}"
+ [ "x${host_arch}" = 'x' ] && [ "x${arches}" != 'x' ] && \
+ printf ' [%s]' "${arches}"
+ [ "x${host_plat}" = 'x' ] && [ "x${plats}" != 'x' ] && \
+ printf ' <%s>' "${plats}"
+ printf '\n'
+
+ return 0
+}
+
+reduce_deps()
+{
+ local deps="${1}"
+ local union="${2}"
+ local host_arch="${3}"
+ local host_plat="${4}"
+ local dep_and=
+ local dep_or=
+ local dep_list=
+ local dep_or_list=
+ local dep=
+
+ dep_list=''
+ IFS=','
+ for dep_and in ${deps}; do
+ unset IFS
+ if ! ${union}; then
+ dep_or_list=''
+ IFS='|'
+ for dep_or in ${dep_and}; do
+ unset IFS
+ dep="$(parse_dep "${dep_or}" \
+ "${host_arch}" "${host_plat}")"
+ if [ "x${dep}" != 'x' ]; then
+ if [ "x${dep_or_list}" != 'x' ]; then
+ dep_or_list="${dep_or_list} | "
+ fi
+ dep_or_list="${dep_or_list}${dep}"
+ fi
+ done
+ unset IFS
+ if [ "x${dep_or_list}" != 'x' ]; then
+ if [ "x${dep_list}" != 'x' ]; then
+ dep_list="${dep_list}, "
+ fi
+ dep_list="${dep_list}${dep_or_list}"
+ fi
+ else
+ dep="$(parse_dep "${dep_and}" \
+ "${host_arch}" "${host_plat}")"
+ if [ "x${dep}" != 'x' ]; then
+ if [ "x${dep_list}" != 'x' ]; then
+ dep_list="${dep_list}, "
+ fi
+ dep_list="${dep_list}${dep}"
+ fi
+ fi
+ done
+ unset IFS
+
+ printf '%s\n' "${dep_list}"
+
+ return 0
+}
diff --git a/src/dir.sh b/src/dir.sh
new file mode 100644
index 0000000..338d8fe
--- /dev/null
+++ b/src/dir.sh
@@ -0,0 +1,56 @@
+# Miscellaneous directory-related functions
+#
+# Copyright (C) 2013 Patrick "P. J." McDermott
+#
+# This file is part of the ProteanOS Development Kit.
+#
+# The ProteanOS Development Kit 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.
+#
+# The ProteanOS Development Kit 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 the ProteanOS Development Kit. If not, see
+# <http://www.gnu.org/licenses/>.
+
+dir_is_empty()
+{
+ local dir="${1}"
+ local ret=
+ local dirent=
+ local exclude=
+ local exclusion=
+ shift 1
+
+ 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
+ continue
+ fi
+ exclude=false
+ for exclusion in "${@}"; do
+ if [ "x${dirent##*/}" = "x${exclusion}" ]; then
+ exclude=true
+ break
+ fi
+ done
+ if ! ${exclude}; then
+ ret=1
+ break
+ fi
+ done
+
+ return ${ret}
+}
diff --git a/src/fd.sh b/src/fd.sh
new file mode 100644
index 0000000..0d5a50e
--- /dev/null
+++ b/src/fd.sh
@@ -0,0 +1,92 @@
+# Functions for opening and closing file descriptors
+#
+# Copyright (C) 2013 Patrick "P. J." McDermott
+#
+# This file is part of the ProteanOS Development Kit.
+#
+# The ProteanOS Development Kit 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.
+#
+# The ProteanOS Development Kit 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 the ProteanOS Development Kit. If not, see
+# <http://www.gnu.org/licenses/>.
+
+_FD_MIN=3
+# Shells and the file descriptors they reserve for the user:
+# * Debian Almquist Shell
+# - The shell doesn't understand any file descriptor greater than 9.
+# - File descriptors starting at 10 are used for I/O redirection.
+# * BusyBox ash
+# - File descriptors starting at 10 are used for job control and I/O
+# redirection.
+# * GNU Bash:
+# - GNU Readline uses file descriptor 255 in interactive shells.
+# - File descriptors starting at 10 are used for I/O redirection.
+# * MirOS Korn Shell
+# - File descriptors starting at either 10 or 24 are used for the shell's
+# tty_fd and shl_dbg_fd.
+# * Solaris ksh
+# - The shell doesn't understand any file descriptor greater than 9.
+_FD_MAX=9
+
+FD=
+
+fopen()
+{
+ local path="${1}"
+ local mode="${2}"
+ local i=
+ local fd=
+
+ case "${mode}" in
+ 'r') mode='<' ;;
+ 'w') mode='>' ;;
+ 'a') mode='>>';;
+ 'rw') mode='<>';;
+ *) return 125;;
+ esac
+
+ # Find first available file descriptor.
+ i=${_FD_MIN}
+ while [ ${i} -le ${_FD_MAX} ]; do
+ if [ "x$(eval echo "\${_fd_${i}+set}")" != 'xset' ]; then
+ fd=${i}
+ break
+ fi
+ i=$(($i + 1))
+ done
+ if [ "x${fd:+set}" != 'xset' ]; then
+ error 2 "$(get_msg 'emfile')"
+ return
+ fi
+
+ if eval "exec ${fd}${mode}'${path}'"; then
+ eval "_fd_${fd}='${mode}${path}'"
+ FD="${fd}"
+ return 0
+ else
+ error 2 "$(get_msg 'cant_fopen')"
+ fi
+}
+
+fclose()
+{
+ local fd="${1}"
+
+ # Make sure the file descriptor is open.
+ if [ "x$(eval echo "\${_fd_${fd}+set}")" != 'xset' ]; then
+ error 2 "$(get_msg 'ebadf')"
+ fi
+
+ eval "exec ${fd}>&-"
+ unset "_fd_${fd}"
+
+ return 0
+}
diff --git a/src/feed.sh b/src/feed.sh
new file mode 100644
index 0000000..4288c2a
--- /dev/null
+++ b/src/feed.sh
@@ -0,0 +1,137 @@
+# Functions for handling feed index files
+#
+# Copyright (C) 2013, 2014 Patrick "P. J." McDermott
+#
+# This file is part of the ProteanOS Development Kit.
+#
+# The ProteanOS Development Kit 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.
+#
+# The ProteanOS Development Kit 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 the ProteanOS Development Kit. If not, see
+# <http://www.gnu.org/licenses/>.
+
+feed_dep_fields=
+feed_pkg_cb=
+feed_deps_cb=
+feed_fname_cb=
+feed_md5sum_cb=
+feed_sha256sum_cb=
+feed_pkgs=
+feed_pkg_include=
+feed_pkg=
+feed_deps=
+
+feed_download()
+{
+ local feed_index="${1}"
+ local use_gzip="${2}"
+ local gzip=
+
+ if ${use_gzip}; then
+ gzip=gunzip
+ else
+ gzip=cat
+ fi
+
+ wget -q -O - "${feed_index}" | ${gzip}
+
+ return 0
+}
+
+feed_find_pkgs()
+{
+ local feed_idx="${1}"
+ local dep_fields="${2}"
+ local pkg_cb="${3}"
+ local deps_cb="${4}"
+ local fname_cb="${5}"
+ local md5sum_cb="${6}"
+ local sha256sum_cb="${7}"
+
+ feed_dep_fields=" $(printf '%s ' ${dep_fields} | tr 'A-Z' 'a-z')"
+ feed_pkg_cb="${pkg_cb}"
+ feed_deps_cb="${deps_cb}"
+ feed_fname_cb="${fname_cb}"
+ feed_md5sum_cb="${md5sum_cb}"
+ feed_sha256sum_cb="${sha256sum_cb}"
+
+ feed_pkgs=''
+ feed_pkg_include='false'
+ feed_pkg=''
+ feed_deps=''
+
+ parse_control "${feed_idx}" feed_field_cb feed_para_cb 'Package'
+
+ printf '%s\n' "${feed_pkgs# }"
+
+ return 0
+}
+
+feed_field_cb()
+{
+ local name="${1}"
+ local value="${2}"
+
+ name="$(printf '%s\n' "${name}" | tr 'A-Z' 'a-z')"
+
+ if [ "x${name}" = 'xpackage' ]; then
+ feed_pkg="${value}"
+ elif [ "x${name}" = 'xfilename' ]; then
+ ${feed_fname_cb} "${feed_pkg}" "${value}"
+ elif [ "x${name}" = 'xmd5sum' ]; then
+ ${feed_md5sum_cb} "${feed_pkg}" "${value}"
+ elif [ "x${name}" = 'xsha256sum' ]; then
+ ${feed_sha256sum_cb} "${feed_pkg}" "${value}"
+ elif [ "x${feed_dep_fields#* ${name} }" != "x${feed_dep_fields}" ]; then
+ feed_deps="${feed_deps}${value}, "
+ fi
+
+ if ${feed_pkg_cb} ${name} ${value}; then
+ feed_pkg_include='true'
+ fi
+
+ return 0
+}
+
+feed_para_cb()
+{
+ local new_deps=
+ local dep=
+
+ if ${feed_pkg_include}; then
+ feed_pkgs="${feed_pkgs} ${feed_pkg}"
+ fi
+
+ new_deps=''
+ IFS=','
+ for dep in ${feed_deps%, }; do
+ unset IFS
+ # Trim off versions and disjunctions.
+ dep="${dep%%(*}"
+ dep="${dep%%|*}"
+ # Trim whitespace.
+ dep="$(printf '%s\n' "${dep}" | sed 's/^ *//; s/ *$//')"
+ # In practice, this would suffice for control files generated by
+ # opkbuild:
+ # dep="${dep#[ ${HT}${LF}]}"
+ # dep="${dep%[ ${HT}${LF}]}"
+ new_deps="${new_deps} ${dep}"
+ done
+ unset IFS
+
+ ${feed_deps_cb} "${feed_pkg}" "${new_deps# }"
+
+ feed_pkg_include='false'
+ feed_pkg=''
+ feed_deps=''
+
+ return 0
+}
diff --git a/src/getopt.sh b/src/getopt.sh
new file mode 100644
index 0000000..07769e7
--- /dev/null
+++ b/src/getopt.sh
@@ -0,0 +1,52 @@
+# Functions for parsing command-line options
+#
+# Copyright (C) 2013 Patrick "P. J." McDermott
+#
+# This file is part of the ProteanOS Development Kit.
+#
+# The ProteanOS Development Kit 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.
+#
+# The ProteanOS Development Kit 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 the ProteanOS Development Kit. If not, see
+# <http://www.gnu.org/licenses/>.
+
+get_options()
+{
+ local optstring=
+ local prefix=
+ local opt=
+
+ if [ "x${running_cmd_clean}" = 'x' ]; then
+ optstring="${OPTSTRING}"
+ prefix='opt_'
+ else
+ eval "optstring=\"\${cmd_${running_cmd_clean}_optstring}\""
+ prefix="cmd_${running_cmd_clean}_opt_"
+ fi
+
+ unset OPTARG
+ while getopts "${optstring}" opt; do
+ if [ "x${opt}" = 'x?' ]; then
+ return 1
+ fi
+ case "${optstring}" in
+ *"${opt}:"*)
+ eval "${prefix}${opt}=\"\${OPTARG}\""
+ ;;
+ *)
+ eval "${prefix}${opt}=true"
+ ;;
+ esac
+ unset OPTARG
+ done
+
+ return 0
+}
diff --git a/src/install.sh b/src/install.sh
new file mode 100644
index 0000000..e1e93df
--- /dev/null
+++ b/src/install.sh
@@ -0,0 +1,266 @@
+# Functions for installing systems
+#
+# Copyright (C) 2013, 2014 Patrick "P. J." McDermott
+#
+# This file is part of the ProteanOS Development Kit.
+#
+# The ProteanOS Development Kit 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.
+#
+# The ProteanOS Development Kit 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 the ProteanOS Development Kit. If not, see
+# <http://www.gnu.org/licenses/>.
+
+install_deps=
+install_fnames=
+install_md5sums=
+install_sha256sums=
+install_feed_url=
+
+install_system()
+{
+ local mirror="${1}"
+ local suite="${2}"
+ local arch="${3}"
+ local plat="${4}"
+ local chroot="${5}"
+ local foreign="${6}"
+
+ if [ "x${arch}" = 'x' ]; then
+ arch="$(profile_detect_arch)"
+ fi
+ if [ "x${plat}" = 'x' ]; then
+ plat="$(profile_default_plat)"
+ fi
+ if [ "x${mirror}" = 'x' ]; then
+ mirror="$(profile_select_mirror)"
+ fi
+
+ if ! profile_validate_archplat "${mirror}" "${arch}" "${plat}" \
+ "${suite}"; then
+ error 2 "$(get_msg 'install_bad_archplat')" \
+ "${arch}" "${plat}"
+ fi
+
+ info "$(get_msg 'install_selected_arch')" "${arch}"
+ info "$(get_msg 'install_selected_plat')" "${plat}"
+ info "$(get_msg 'install_selected_mirror')" "${mirror}"
+
+ info "$(get_msg 'install_setting_up_chroot')"
+ if [ -d "${chroot}" ] && ! dir_is_empty "${chroot}" 'lost+found'; then
+ error 2 "$(get_msg 'install_chroot_dir_exists')" "${chroot}"
+ fi
+ if ! mkdir -p "${chroot}/.prokit" "${chroot}/prokit"; then
+ error 2 "$(get_msg 'install_mkdir_chroot_fail')" "${chroot}"
+ fi
+ >"${chroot}/prokit/installing"
+
+ info "$(get_msg 'install_find_pkgs')"
+ install_find_pkgs "${mirror}" "${suite}" "${arch}" "${plat}" \
+ "${chroot}"
+ install_get_pkgs "${chroot}"
+
+ info "$(get_msg 'install_configuring')"
+ if ! ${foreign}; then
+ profile_configure_system_native "${chroot}" "${arch}" "${plat}"
+ else
+ profile_configure_system_foreign "${chroot}" "${arch}" "${plat}"
+ fi
+
+ rm "${chroot}/prokit/installing"
+}
+
+install_find_pkgs()
+{
+ local mirror="${1}"
+ local suite="${2}"
+ local arch="${3}"
+ local plat="${4}"
+ local chroot="${5}"
+ local opkg_conf_fd=
+ local pkgs_fd=
+ local opt=
+ local feed_idx=
+ local feed=
+ local gzip=
+ local pkgs=
+
+ mkdir -p "${chroot}/etc/opkg" "${chroot}/var/lib/opkg/lists"
+ fopen "${chroot}/etc/opkg/opkg.conf" 'w'
+ opkg_conf_fd=${FD}
+
+ install_deps=
+ install_fnames=
+ install_md5sums=
+ install_sha256sums=
+
+ fopen "${chroot}/.prokit/packages" 'w'
+ pkgs_fd=${FD}
+
+ while read -r opt feed_idx feed; do
+ case "${opt}" in
+ 'src') gzip=false;;
+ 'src/gz') gzip=true;;
+ *) continue;;
+ esac
+ printf '%s %s %s\n' "${opt}" "${feed_idx}" \
+ "${feed%/Packages*}" >&${opkg_conf_fd}
+ feed_download "${feed}" ${gzip} \
+ >"${chroot}/var/lib/opkg/lists/${feed_idx}"
+ install_feed_url="${feed%/*}"
+ feed_find_pkgs "${chroot}/var/lib/opkg/lists/${feed_idx}" \
+ "$(profile_dep_fields)" profile_include_pkg \
+ install_deps_cb install_fname_cb \
+ install_md5sum_cb install_sha256sum_cb \
+ >&${pkgs_fd}
+ done <<-EOF
+ $(profile_feeds "${mirror}" "${arch}" "${plat}" "${suite}")
+ EOF
+
+ printf '\ndest root /\n' >&${opkg_conf_fd}
+ printf 'arch %s 1\n' 'all' "${arch}" 'src' >&${opkg_conf_fd}
+ fclose ${opkg_conf_fd}
+ fclose ${pkgs_fd}
+
+ pkgs="$(resolve_deps "$(cat "${chroot}/.prokit/packages")" \
+ "${install_deps}" | xargs printf '%s\n' | sort -u)"
+ printf '%s\n' "${pkgs}" >"${chroot}/.prokit/packages"
+}
+
+install_deps_cb()
+{
+ local pkg="${1}"
+ local deps="${2}"
+
+ install_deps="${install_deps}${pkg}: ${deps}${LF}"
+}
+
+install_fname_cb()
+{
+ local pkg="${1}"
+ local fname="${2}"
+
+ install_fnames="$(printf '%s\n%s %s/%s' "${install_fnames}" \
+ "${pkg}" "${install_feed_url}" "${fname}")"
+}
+
+install_md5sum_cb()
+{
+ local pkg="${1}"
+ local md5sum="${2}"
+
+ install_md5sums="$(printf '%s\n%s %s' "${install_md5sums}" \
+ "${pkg}" "${md5sum}")"
+}
+
+install_sha256sum_cb()
+{
+ local pkg="${1}"
+ local sha256sum="${2}"
+
+ install_sha256sums="$(printf '%s\n%s %s' \
+ "${install_sha256sums}" "${pkg}" "${sha256sum}")"
+}
+
+install_get_pkgs()
+{
+ local chroot="${1}"
+ local status_fd=
+ local pkg=
+ local fname=
+ local md5sum=
+ local sha256sum=
+ local file=
+ local control=
+ local field=
+ local printed=
+
+ mkdir -p "${chroot}/var/cache/opkg/archives" "${chroot}/tmp/opkg" \
+ "${chroot}/var/lib/opkg/info"
+
+ fopen "${chroot}/var/lib/opkg/status" 'w'
+ status_fd=${FD}
+
+ for pkg in $(cat "${chroot}/.prokit/packages"); do
+ info "$(get_msg 'install_downloading_pkg')" "${pkg}"
+ fname="$(printf '%s\n' "${install_fnames}" | \
+ sed -n "s/^${pkg} //p")"
+ md5sum="$(printf '%s\n' "${install_md5sums}" | \
+ sed -n "s/^${pkg} //p")"
+ sha256sum="$(printf '%s\n' "${install_sha256sums}" | \
+ sed -n "s/^${pkg} //p")"
+ wget -q -O "${chroot}/var/cache/opkg/archives/${fname##*/}" \
+ "${fname}" || \
+ error 2 "$(get_msg 'install_downloading_pkg_fail')"
+ fname="var/cache/opkg/archives/${fname##*/}"
+ if [ "x${md5sum}" != 'x' ]; then
+ printf '%s %s\n' \
+ "${md5sum}" "${chroot}/${fname}" | \
+ md5sum -c >/dev/null 2>&1 || \
+ error 2 "$(get_msg 'install_checksum_fail')"
+ fi
+ if [ "x${sha256sum}" != 'x' ]; then
+ printf '%s %s\n' \
+ "${sha256sum}" "${chroot}/${fname}" | \
+ sha256sum -c >/dev/null 2>&1 || \
+ error 2 "$(get_msg 'install_checksum_fail')"
+ fi
+
+ info "$(get_msg 'install_unpacking_pkg')" "${pkg}"
+ mkdir "${chroot}/tmp/opkg/${pkg}"
+ (
+ cd "${chroot}"
+ tar -xzOf "${fname}" data.tar.gz \
+ >"tmp/opkg/${pkg}/data.tar.gz"
+ tar -xzf "tmp/opkg/${pkg}/data.tar.gz"
+ cd "tmp/opkg/${pkg}"
+ tar -xzOf "../../../${fname}" control.tar.gz | \
+ tar -xz
+ )
+ tar -tzf "${chroot}/tmp/opkg/${pkg}/data.tar.gz" | \
+ sed 's/^\.//' >"${chroot}/var/lib/opkg/info/${pkg}.list"
+ rm -f "${chroot}/tmp/opkg/${pkg}/data.tar.gz"
+ for file in "${chroot}/tmp/opkg/${pkg}/"*; do
+ mv "${file}" \
+ "${chroot}/var/lib/opkg/info/${pkg}.${file##*/}"
+ done
+ rmdir "${chroot}/tmp/opkg/${pkg}"
+
+ # Write status file.
+ control="${chroot}/var/lib/opkg/info/${pkg}.control"
+ for field in Package Version Depends Recommends Suggests \
+ Provides Replaces Conflicts; do
+ grep "^${field}: " "${control}" >&${status_fd}
+ done
+ printf 'Status: install ok unpacked\n' >&${status_fd}
+ for field in Essential Architecture; do
+ grep "^${field}: " "${control}" >&${status_fd}
+ done
+ if [ -r "${chroot}/var/lib/opkg/info/${pkg}.conffiles" ]; then
+ printed=false
+ while read -r file; do
+ ${printed} || printf 'Conffiles:\n' \
+ >&${status_fd}
+ printf ' %s %s\n' "${file}" "$(md5sum \
+ "${chroot}/${file}" | cut -d' ' -f1)" \
+ >&${status_fd}
+ printed=true
+ done <"${chroot}/var/lib/opkg/info/${pkg}.conffiles"
+ fi
+ printf 'Installed-Time: %s\n\n' "$(date '+%s')" >&${status_fd}
+
+ rm -f "${chroot}/${fname}"
+ done
+
+ rm "${chroot}/.prokit/packages"
+ rmdir "${chroot}/.prokit"
+
+ fclose ${status_fd}
+}
diff --git a/src/local.mk b/src/local.mk
index 980b6ac..6133821 100644
--- a/src/local.mk
+++ b/src/local.mk
@@ -1,2 +1,23 @@
prokit_SOURCES += \
- src/main.sh
+ src/main.sh \
+ src/output.sh \
+ src/locale.sh \
+ src/getopt.sh \
+ src/fd.sh \
+ src/dir.sh \
+ src/vardata.sh \
+ src/archplat.sh \
+ src/deps.sh \
+ src/substvars.sh \
+ src/control.sh \
+ src/feed.sh \
+ src/pkg.sh \
+ src/mutex.sh \
+ src/session.sh \
+ src/block.sh \
+ src/opkg.sh \
+ src/rand.sh \
+ src/cmd.sh \
+ src/profile.sh \
+ src/package.sh \
+ src/install.sh
diff --git a/src/locale.sh b/src/locale.sh
new file mode 100644
index 0000000..76d2d9b
--- /dev/null
+++ b/src/locale.sh
@@ -0,0 +1,84 @@
+# Locale functions
+#
+# Copyright (C) 2012, 2013 Patrick "P. J." McDermott
+#
+# This file is part of the ProteanOS Development Kit.
+#
+# The ProteanOS Development Kit 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.
+#
+# The ProteanOS Development Kit 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 the ProteanOS Development Kit. If not, see
+# <http://www.gnu.org/licenses/>.
+
+DEFAULT_LOCALE='en_US'
+
+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 ${in_place}; then
+ localedir="${builddir}/locale"
+ 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_${TEXTDOMAIN}_${msgid}}\""
+
+ return 0
+}
+
+_try_load_locale()
+{
+ local localedir="${1}"
+ local locale="${2}"
+ local ms=
+
+ for ms in "${localedir}/${locale}/LC_MESSAGES/${TEXTDOMAIN}.ms" \
+ "${localedir}/${locale}.ms"; do
+ if [ -f "${ms}" ]; then
+ . "${ms}"
+ return 0
+ fi
+ done
+
+ return 1
+}
diff --git a/src/mutex.sh b/src/mutex.sh
new file mode 100644
index 0000000..cd4a365
--- /dev/null
+++ b/src/mutex.sh
@@ -0,0 +1,54 @@
+# Functions for locking and unlocking mutex objects
+#
+# Copyright (C) 2014 Patrick "P. J." McDermott
+#
+# This file is part of the ProteanOS Development Kit.
+#
+# The ProteanOS Development Kit 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.
+#
+# The ProteanOS Development Kit 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 the ProteanOS Development Kit. If not, see
+# <http://www.gnu.org/licenses/>.
+
+mutex_trylock()
+{
+ local mutex="${1}"
+
+ (set -C; printf '%d\n' "${$}" >"${mutex}") 2>/dev/null
+}
+
+mutex_timedlock()
+{
+ local mutex="${1}"
+ local timeout="${2}"
+
+ while ! mutex_trylock "${mutex}"; do
+ [ ${timeout} -eq 0 ] && return 1
+ timeout=$(($timeout - 1))
+ sleep 1
+ done
+
+ return 0
+}
+
+mutex_unlock()
+{
+ local mutex="${1}"
+
+ rm "${mutex}"
+}
+
+mutex_is_unlockable()
+{
+ local mutex="${1}"
+
+ [ "x$(cat "${mutex}" 2>/dev/null)" = "x${$}" ]
+}
diff --git a/src/opkg.sh b/src/opkg.sh
new file mode 100644
index 0000000..9e5c3c4
--- /dev/null
+++ b/src/opkg.sh
@@ -0,0 +1,29 @@
+# Functions for running the package manager
+#
+# Copyright (C) 2014 Patrick "P. J." McDermott
+#
+# This file is part of the ProteanOS Development Kit.
+#
+# The ProteanOS Development Kit 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.
+#
+# The ProteanOS Development Kit 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 the ProteanOS Development Kit. If not, see
+# <http://www.gnu.org/licenses/>.
+
+opkg_install_all()
+{
+ local root="${1}"
+
+ session_begin "${root}" . : true
+ session_exec /bin/sh -c \
+ 'opkg install $(opkg list-installed | cut -d " " -f 1)'
+ session_end
+}
diff --git a/src/output.sh b/src/output.sh
new file mode 100644
index 0000000..62d2095
--- /dev/null
+++ b/src/output.sh
@@ -0,0 +1,56 @@
+# Functions for printing messages
+#
+# Copyright (C) 2013 Patrick "P. J." McDermott
+#
+# This file is part of the ProteanOS Development Kit.
+#
+# The ProteanOS Development Kit 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.
+#
+# The ProteanOS Development Kit 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 the ProteanOS Development Kit. If not, see
+# <http://www.gnu.org/licenses/>.
+
+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
+}
diff --git a/src/package.sh b/src/package.sh
new file mode 100644
index 0000000..3175202
--- /dev/null
+++ b/src/package.sh
@@ -0,0 +1,62 @@
+# Interface for handling source package metadata
+#
+# Copyright (C) 2014 Patrick "P. J." McDermott
+#
+# This file is part of the ProteanOS Development Kit.
+#
+# The ProteanOS Development Kit 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.
+#
+# The ProteanOS Development Kit 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 the ProteanOS Development Kit. If not, see
+# <http://www.gnu.org/licenses/>.
+
+package_dir=
+package_format=
+
+package_init()
+{
+ local dir="${1}"
+
+ package_dir="${dir}"
+
+ package_format=''
+ if [ -r "${package_dir}/format" ]; then
+ case "$(cat "${package_dir}/format")" in
+ 2.*)
+ package_format=2
+ ;;
+ esac
+ fi
+
+ if [ "x${package_format}" = 'x' ]; then
+ error 2 "$(get_msg 'package_format_unknown')"
+ fi
+
+ use "package/${package_format}"
+
+ return 0
+}
+
+package_get_build_deps()
+{
+ local arch="${1}"
+ local plat="${2}"
+
+ "package_${package_format}_get_build_deps" "${arch}" "${plat}"
+}
+
+package_set_substvars()
+{
+ local arch="${1}"
+ local plat="${2}"
+
+ "package_${package_format}_set_substvars" "${arch}" "${plat}"
+}
diff --git a/src/pkg.sh b/src/pkg.sh
new file mode 100644
index 0000000..ee4aa25
--- /dev/null
+++ b/src/pkg.sh
@@ -0,0 +1,51 @@
+# Functions for handling packages and their dependencies
+#
+# Copyright (C) 2013, 2014 Patrick "P. J." McDermott
+#
+# This file is part of the ProteanOS Development Kit.
+#
+# The ProteanOS Development Kit 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.
+#
+# The ProteanOS Development Kit 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 the ProteanOS Development Kit. If not, see
+# <http://www.gnu.org/licenses/>.
+
+resolve_deps()
+{
+ local new_pkgs="${1}"
+ local deps="${2}"
+ local all_deps=
+ local new_deps=
+ local pkg=
+
+ all_deps=''
+ new_pkgs="${new_pkgs} "
+ while [ "x${new_pkgs}" != 'x' ]; do
+ all_deps="${all_deps}${new_pkgs}"
+ new_deps=''
+ for pkg in ${new_pkgs}; do
+ case "${pkg}" in
+ *[!a-z0-9+.-]*) continue;;
+ esac
+ new_deps="${new_deps} $(printf '%s' "${deps}" | \
+ sed -n "s/^${pkg}: *//p")"
+ done
+ new_deps="$(printf '%s\n' ${new_deps} | sort -u)"
+ new_pkgs=''
+ for pkg in ${new_deps}; do
+ if [ "x${all_deps# ${pkg} }" = "x${all_deps}" ]; then
+ new_pkgs="${new_pkgs}${pkg} "
+ fi
+ done
+ done
+
+ printf '%s\n' "${all_deps% }"
+}
diff --git a/src/profile.sh b/src/profile.sh
new file mode 100644
index 0000000..f64a0d8
--- /dev/null
+++ b/src/profile.sh
@@ -0,0 +1,165 @@
+# Profile-related functions
+#
+# Copyright (C) 2014 Patrick "P. J." McDermott
+#
+# This file is part of the ProteanOS Development Kit.
+#
+# The ProteanOS Development Kit 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.
+#
+# The ProteanOS Development Kit 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 the ProteanOS Development Kit. If not, see
+# <http://www.gnu.org/licenses/>.
+
+profiles=' '
+profile=
+
+register_profile()
+{
+ local profile="${1}"
+ shift 1
+
+ profiles="${profiles}${profile} "
+ return 0
+}
+
+is_profile()
+{
+ local prof="${1}"
+
+ [ "x${profiles# ${prof} }" != "x${profiles}" ]
+}
+
+profile_set()
+{
+ local prof="${1}"
+
+ if is_profile "${prof}"; then
+ profile="${prof}"
+ use "profile/${profile}"
+ else
+ error 1 "$(get_msg 'profile_not_found')" "${prof}"
+ fi
+
+ return 0
+}
+
+profile_normalize_suite()
+{
+ local suite="${1}"
+
+ "prof_${profile}_normalize_suite" "${suite}"
+}
+
+profile_detect_arch()
+{
+ "prof_${profile}_detect_arch"
+}
+
+profile_default_plat()
+{
+ "prof_${profile}_default_plat"
+}
+
+profile_select_mirror()
+{
+ "prof_${profile}_select_mirror"
+}
+
+profile_validate_archplat()
+{
+ local mirror="${1}"
+ local arch="${2}"
+ local plat="${3}"
+ local suite="${4}"
+
+ "prof_${profile}_validate_archplat" "${mirror}" "${arch}" "${plat}" \
+ "${suite}"
+}
+
+profile_feeds()
+{
+ local mirror="${1}"
+ local arch="${2}"
+ local plat="${3}"
+ local suite="${4}"
+
+ "prof_${profile}_feeds" "${mirror}" "${arch}" "${plat}" "${suite}"
+}
+
+profile_dep_fields()
+{
+ "prof_${profile}_dep_fields"
+}
+
+profile_include_pkg()
+{
+ local name="${1}"
+ local value="${2}"
+
+ "prof_${profile}_include_pkg" "${name}" "${value}"
+}
+
+profile_get_fstab()
+{
+ local arch="${1}"
+ local plat="${2}"
+
+ "prof_${profile}_get_fstab" "${arch}" "${plat}"
+}
+
+profile_file_systems_mounted()
+{
+ local root="${1}"
+ local arch="${1}"
+ local plat="${2}"
+
+ "prof_${profile}_file_systems_mounted" "${root}" "${arch}" "${plat}"
+}
+
+profile_bind_mount()
+{
+ local arch="${1}"
+ local plat="${2}"
+ local olddir="${3}"
+ local newdir="${4}"
+
+ "prof_${profile}_bind_mount" "${arch}" "${plat}" \
+ "${olddir}" "${newdir}"
+}
+
+profile_bind_umount()
+{
+ local arch="${1}"
+ local plat="${2}"
+ local olddir="${3}"
+ local newdir="${4}"
+
+ "prof_${profile}_bind_umount" "${arch}" "${plat}" \
+ "${olddir}" "${newdir}"
+}
+
+profile_configure_system_native()
+{
+ local root="${1}"
+ local arch="${2}"
+ local plat="${3}"
+
+ "prof_${profile}_configure_system_native" "${root}" "${arch}" "${plat}"
+}
+
+profile_configure_system_foreign()
+{
+ local root="${1}"
+ local arch="${2}"
+ local plat="${3}"
+
+ "prof_${profile}_configure_system_foreign" "${root}" "${arch}" "${plat}"
+}
diff --git a/src/rand.sh b/src/rand.sh
new file mode 100644
index 0000000..872d3fb
--- /dev/null
+++ b/src/rand.sh
@@ -0,0 +1,42 @@
+# Linear congruential pseudorandom number generator
+#
+# Copyright (C) 2014 Patrick "P. J." McDermott
+#
+# This file is part of the ProteanOS Development Kit.
+#
+# The ProteanOS Development Kit 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.
+#
+# The ProteanOS Development Kit 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 the ProteanOS Development Kit. If not, see
+# <http://www.gnu.org/licenses/>.
+
+rand_x=1
+
+srand()
+{
+ local x="${1}"
+
+ case "${x}" in
+ *[!0-9]*)
+ warn "$(get_msg 'rand_bad_x')"
+ return 1
+ ;;
+ esac
+
+ rand_x=${x}
+}
+
+rand()
+{
+ # Increment, multiplier, and modulus values are those used in glibc.
+ rand_x=$((1103515245 * $rand_x + 12345))
+ rand_x=$(($rand_x % 4294967296))
+}
diff --git a/src/session.sh b/src/session.sh
new file mode 100644
index 0000000..6453fae
--- /dev/null
+++ b/src/session.sh
@@ -0,0 +1,210 @@
+# Functions for managing prokit sessions
+#
+# Copyright (C) 2014 Patrick "P. J." McDermott
+#
+# This file is part of the ProteanOS Development Kit.
+#
+# The ProteanOS Development Kit 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.
+#
+# The ProteanOS Development Kit 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 the ProteanOS Development Kit. If not, see
+# <http://www.gnu.org/licenses/>.
+
+session_id=
+session_root=
+session_arch=
+session_plat=
+session_mountdir=
+session_atexit=
+session_sigs=
+
+session_begin()
+{
+ local root="${1}"
+ local mountdir="${2}"
+ local atexit="${3}"
+ local installing="${4}"
+
+ rand
+ session_id=${rand_x}
+ session_root="${root}"
+ session_arch="$(cat "${root}/etc/proteanos_arch")"
+ session_plat="$(cat "${root}/etc/proteanos_plat")"
+ session_mountdir="${mountdir}"
+ session_atexit="${atexit}"
+
+ [ -d "${session_root}/prokit" ] || mkdir "${session_root}/prokit"
+ if [ -f "${session_root}/prokit/installing" ] && ! ${installing}; then
+ error 2 "$(get_msg 'install_running')"
+ fi
+
+ session_set_sigs
+
+ if ! mutex_timedlock "${session_root}/prokit/sessions.lock" 5; then
+ error 2 "$(get_msg 'cant_lock_sessions')"
+ fi
+
+ # Check for a sessions pool.
+ if [ -d "${session_root}/prokit/sessions" ]; then
+ if ! profile_file_systems_mounted "${session_root}" \
+ "${session_arch}" "${session_plat}"; then
+ # If a sessions pool exists but the file systems aren't
+ # mounted, clean up the old sessions and mount the file
+ # systems.
+ rmdir "${session_root}/prokit/sessions/"*
+ session_mount
+ fi
+ else
+ # If the sessions pool doesn't exist, create it and mount the
+ # file systems.
+ mkdir "${session_root}/prokit/sessions"
+ session_mount
+ fi
+
+ # Register the session.
+ mkdir "${session_root}/prokit/sessions/${session_id}"
+ mkdir "${session_root}/prokit/sessions/${session_id}/wd"
+ profile_bind_mount "${session_arch}" "${session_plat}" \
+ "${session_mountdir}" \
+ "${session_root}/prokit/sessions/${session_id}/wd"
+
+ mutex_unlock "${session_root}/prokit/sessions.lock"
+}
+
+session_end()
+{
+ trap : ${session_sigs}
+
+ ${session_atexit}
+
+ # Unregister the session.
+ profile_bind_umount "${session_arch}" "${session_plat}" \
+ "${session_mountdir}" \
+ "${session_root}/prokit/sessions/${session_id}/wd"
+ rmdir "${session_root}/prokit/sessions/${session_id}/wd"
+ rmdir "${session_root}/prokit/sessions/${session_id}"
+
+ if ! mutex_is_unlockable "${session_root}/prokit/sessions.lock"; then
+ if ! mutex_timedlock "${session_root}/prokit/sessions.lock" 5
+ then
+ error 2 "$(get_msg 'cant_lock_sessions')"
+ fi
+ fi
+
+ # Clear the sessions pool. If there are no more sessions, unmount the
+ # file systems.
+ if rmdir "${session_root}/prokit/sessions" 2>/dev/null; then
+ session_umount
+ fi
+
+ mutex_unlock "${session_root}/prokit/sessions.lock"
+
+ trap - ${session_sigs}
+}
+
+session_id()
+{
+ printf '%d\n' ${session_id}
+ return 0
+}
+
+session_dir()
+{
+ printf '/prokit/sessions/%d\n' ${session_id}
+ return 0
+}
+
+session_exec()
+{
+ local args=
+ local session_dir=
+
+ args=''
+ for arg in "${@}"; do
+ arg="'$(printf '%s\n' "${arg}" | sed "s/'/'\\\\''/g")'"
+ args="${args} ${arg}"
+ done
+ session_dir="/prokit/sessions/${session_id}/wd"
+ chroot "${session_root}" /bin/sh -c "cd ${session_dir}; ${args}"
+}
+
+session_mount()
+{
+ local fs=
+ local dir=
+ local fstype=
+ local options=
+
+ while read fs dir fstype options; do
+ [ "x${dir}" = 'x' ] && continue
+ mount -t "${fstype}" -o "${options}" "${fs}" \
+ "${session_root}/${dir}"
+ done <<-EOF
+ $(profile_get_fstab "${session_arch}" "${session_plat}")
+ EOF
+}
+
+session_umount()
+{
+ local fs=
+ local dir=
+ local fstype=
+ local options=
+
+ while read fs dir fstype options; do
+ [ "x${dir}" = 'x' ] && continue
+ # umount sometimes complains that the /dev file system is busy.
+ # Here's a kludge to try to handle that. We better make sure
+ # bind mounts get unmounted; otherwise, `rm -Rf ${root}` can be
+ # painful.
+ while ! umount "${session_root}/${dir}"; do
+ sleep 1
+ done
+ done <<-EOF
+ $(profile_get_fstab "${session_arch}" "${session_plat}" | \
+ sed -n '1!G;h;$p')
+ EOF
+}
+
+session_set_sigs()
+{
+ local i=
+ local sig=
+
+ # We need the signal *number* in the signal handler. The only portable
+ # and easy way to get the number of a named signal is to search for it
+ # as in the following loop hack.
+ i=0
+ session_sigs=''
+ while [ ${i} -lt 127 ]; do
+ i=$(($i + 1))
+ sig="$(kill -l ${i} 2>/dev/null)" || continue
+ case "${sig}" in
+ 'HUP' | 'INT' | 'QUIT' | 'ABRT' | 'ALRM' | 'TERM')
+ session_sigs="${session_sigs} ${i}"
+ trap "session_handle_sig ${i}" ${i}
+ ;;
+ esac
+ done
+}
+
+session_handle_sig()
+{
+ local sig="${1}"
+
+ session_end
+
+ if [ "x${exit_status:+set}" = 'xset' ]; then
+ exit ${exit_status}
+ else
+ exit $((128 + $sig))
+ fi
+}
diff --git a/src/substvars.sh b/src/substvars.sh
new file mode 100644
index 0000000..c2bc3f8
--- /dev/null
+++ b/src/substvars.sh
@@ -0,0 +1,103 @@
+# Functions for setting and substituting variables
+#
+# Copyright (C) 2012, 2014 Patrick "P. J." McDermott
+#
+# This file is part of the ProteanOS Development Kit.
+#
+# The ProteanOS Development Kit 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.
+#
+# The ProteanOS Development Kit 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 the ProteanOS Development Kit. If not, see
+# <http://www.gnu.org/licenses/>.
+
+substvars_max_depth=50
+
+set_substvar()
+{
+ local name="${1}"
+ local value="${2}"
+
+ # Convert variable name to lower case and validate.
+ name="$(printf '%s\n' "${name}" | tr 'A-Z-' 'a-z_')"
+ case "${name:- }" in
+ *[!a-z0-9_]*)
+ warn "$(get_msg 'substvar_invalid')" "${name}"
+ return 1
+ ;;
+ esac
+
+ # Trim leading and trailing newline characters from value.
+ value="$(printf '%s\n' "${value}" | sed -n '
+ H; # Store each input line in the hold space.
+ ${ # At the last line of input:
+ g; # Restore the hold space into the pattern space.
+ s/^[\n]*//; # Remove leading newline characters.
+ s/[\n]*$//; # Remove trailing newline characters.
+ p; # Print the results.
+ };
+ ')"
+
+ # Escape single quotes in value.
+ value="$(printf '%s\n' "${value}" | sed "s/'/'\\\\''/g")"
+
+ eval "substvar_${name}='${value}'"
+
+ return 0
+}
+
+substvars()
+{
+ local string="${1}"
+ local depth=
+ local lhs=
+ local name=
+ local rhs=
+ local old_rhs=
+ local value=
+
+ # Logic inspired by that of dpkg's Dpkg::Substvars::substvars()
+ # subroutine.
+
+ depth=0
+
+ while :; do
+ lhs="${string%%\$\{*}"
+ if [ ${#lhs} -eq ${#string} ]; then
+ # No "${" was found.
+ break
+ fi
+ string="${string#*\$\{}"
+ name="${string%%\}*}"
+ rhs="${string#*\}}"
+
+ if [ ${#rhs} -lt ${#old_rhs} ]; then
+ # Reset the nesting counter if we've advanced the right
+ # side of the matched space.
+ depth=0
+ fi
+ if [ ${depth} -ge ${substvars_max_depth} ]; then
+ # Warn of possible recursion.
+ warn "$(get_msg 'substvar_deep_nesting')"
+ return 1
+ fi
+ old_rhs="${rhs}"
+
+ # Perform the substitution.
+ name="$(printf '%s\n' "${name}" | tr 'A-Z-' 'a-z_')"
+ value="$(eval "printf '%s\n' \"\${substvar_${name}}\"")"
+ string="${lhs}${value}${rhs}"
+ depth=$(($depth + 1))
+ done
+
+ printf '%s\n' "${string}"
+
+ return 0
+}
diff --git a/src/vardata.sh b/src/vardata.sh
new file mode 100644
index 0000000..0c68d67
--- /dev/null
+++ b/src/vardata.sh
@@ -0,0 +1,52 @@
+# Functions for getting variable/runtime data
+#
+# Copyright (C) 2015 Patrick "P. J." McDermott
+#
+# This file is part of the ProteanOS Development Kit.
+#
+# The ProteanOS Development Kit 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.
+#
+# The ProteanOS Development Kit 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 the ProteanOS Development Kit. If not, see
+# <http://www.gnu.org/licenses/>.
+
+VARDATA_DIRS='mount'
+
+vardatadir=
+
+init_vardata()
+{
+ local dir=
+
+ if ${in_place}; then
+ vardatadir="${builddir}/var"
+ for dir in ${VARDATA_DIRS}; do
+ mkdir -p "${vardatadir}/${dir}"
+ done
+ else
+ vardatadir="${PKGLOCALSTATEDIR}"
+ fi
+
+ return 0
+}
+
+get_vardata_dir()
+{
+ local dir="${1}"
+
+ case " ${VARDATA_DIRS} " in
+ *" ${dir} "*)
+ printf '%s/%s' "${vardatadir}" "${dir}"
+ ;;
+ esac
+
+ return 0
+}