kiss/kiss

1957 lines
61 KiB
Plaintext
Raw Normal View History

2020-05-09 18:25:38 +00:00
#!/bin/sh
# shellcheck source=/dev/null
#
2021-07-22 21:34:24 +00:00
# Simple package manager written in POSIX shell for https://kisslinux.org
2019-08-19 17:07:50 +00:00
#
# The MIT License (MIT)
#
# Copyright (c) 2019-2021 Dylan Araps
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
2019-06-13 14:48:08 +00:00
2019-09-21 17:22:56 +00:00
log() {
printf '%b%s %b%s%b %s\n' \
2021-07-05 08:42:52 +00:00
"$c1" "${3:-->}" "${c3}${2:+$c2}" "$1" "$c3" "$2" >&2
2019-09-21 17:22:56 +00:00
}
war() {
log "$1" "$2" "${3:-WARNING}"
}
2019-06-13 14:48:08 +00:00
die() {
2020-05-07 11:46:50 +00:00
log "$1" "$2" "${3:-ERROR}"
2019-06-13 14:48:08 +00:00
exit 1
}
contains() {
# Check if a "string list" contains a word.
case " $1 " in *" $2 "*) return 0; esac; return 1
}
equ() {
2021-07-19 05:11:13 +00:00
# Check if a string is equal to enother.
# This replaces '[ "$var" = str ]' and '[ "$var" != str ]'.
case $1 in "$2") return 0 ;; *) return 1; esac
}
ok() {
2021-07-19 05:11:13 +00:00
# Check if a string is non-null.
2021-07-19 08:42:13 +00:00
# This replaces '[ "$var" ]', '[ -n "$var" ]'.
case $1 in '') return 1 ;; *) return 0; esac
}
2021-07-19 08:41:18 +00:00
null() {
2021-07-19 08:42:13 +00:00
# Check if a string is non-null.
# This replaces '[ -z "$var" ]'.
case $1 in '') return 0 ;; *) return 1; esac
2021-07-19 08:41:18 +00:00
}
tmp_file() {
# Create a uniquely named temporary file and store its absolute path
# in a variable (_tmp_file).
#
# To prevent subshell usage and to handle cases where multiple files
# are needed, this saves the last two temporary files to variables
# for access by the caller (allowing 3 files at once).
_tmp_file_pre_pre=$_tmp_file_pre
_tmp_file_pre=$_tmp_file
2021-07-16 17:12:52 +00:00
_tmp_file=$tmp_dir/$KISS_PID-$1-$2
2021-07-19 05:18:49 +00:00
: > "$_tmp_file" || die "$1" "Failed to create temporary file"
}
tmp_file_copy() {
# Create a uniquely named temporary file and make a duplicate of
# the file in '$3' if it exists.
tmp_file "$1" "$2"
! [ -f "$3" ] || cp -f "$3" "$_tmp_file"
}
2020-02-18 19:28:35 +00:00
prompt() {
2021-07-19 08:41:18 +00:00
null "$1" || log "$1"
2020-09-11 14:38:02 +00:00
2021-07-02 12:05:36 +00:00
log "Continue?: Press Enter to continue or Ctrl+C to abort"
2020-02-18 19:28:35 +00:00
# korn-shell does not exit on interrupt of read.
equ "$KISS_PROMPT" 0 || read -r _ || exit 1
2019-09-22 11:35:07 +00:00
}
mkcd() {
mkdir -p "$@" && cd "$1"
}
fnr() {
# Replace all occurrences of substrings with substrings. This
# function takes pairs of arguments iterating two at a time
# until everything has been replaced.
_fnr=$1
shift 1
while :; do case $_fnr-$# in
*"$1"*) _fnr=${_fnr%"$1"*}${2}${_fnr##*"$1"} ;;
*-2) break ;;
*) shift 2
esac done
}
am_owner() {
# Figure out if we need to change users to operate on
# a given file or directory.
inf=$(ls -ld "$1") ||
die "Failed to file information for '$1'"
2021-07-19 05:18:49 +00:00
# Split the ls output into fields.
2021-07-19 16:12:55 +00:00
read -r _ _ user _ <<EOF
$inf
EOF
2020-09-09 12:03:57 +00:00
2021-07-19 16:12:55 +00:00
equ "$LOGNAME/$user" "$user/$LOGNAME"
2020-01-27 08:06:56 +00:00
}
as_user() {
printf 'Using '%s' (to become %s)\n' "$cmd_su" "$user"
case ${cmd_su##*/} in
2021-07-19 18:22:49 +00:00
su) "$cmd_su" -c "$* <&3" "$user" 3<&0 </dev/tty ;;
*) "$cmd_su" -u "$user" -- "$@"
esac
}
2020-11-06 06:45:56 +00:00
pkg_owner() {
ok "$2" || { set +f; set -f -- "$1" "$sys_db"/*/manifest; }
2020-11-06 06:45:56 +00:00
_owns=$(grep -lxF "$@")
2021-07-17 13:12:31 +00:00
_owns=${_owns%/*}
_owns=${_owns##*/}
2020-11-06 06:45:56 +00:00
ok "$_owns"
2020-11-06 06:45:56 +00:00
}
resolve_path() {
_rpath=$KISS_ROOT/${1#/}
# Attempt to resolve symlinks by using 'cd'.
# If this fails, fallback to the file's parent
# directory.
cd -P "${_rpath%/*}" 2>/dev/null || PWD=${_rpath%/*}
# Final resolved path.
_rpath=${PWD#"$KISS_ROOT"}/${_rpath##*/}
cd "$OLDPWD"
}
run_hook() {
# Run all hooks in KISS_HOOK (a colon separated
# list of absolute file paths).
IFS=:
for hook in ${KISS_HOOK:-}; do case $hook in *?*)
"$hook" "$@" || die "$1 hook failed: '$hook'"
esac done
unset IFS
}
run_hook_pkg() {
# Run a hook from the package's database files.
if [ -x "$sys_db/$2/$1" ]; then
log "$2" "Running $1 hook"
"$sys_db/$2/$1"
elif [ -f "$sys_db/$2/$1" ]; then
war "$2" "skipping $1 hook: not executable"
fi
}
decompress() {
case $1 in
2021-07-12 00:57:48 +00:00
*.tbz|*.bz2) bzip2 -d ;;
*.lzma) lzma -dc ;;
*.lz) lzip -dc ;;
*.tar) cat ;;
*.tgz|*.gz) gzip -d ;;
*.xz|*.txz) xz -dcT0 ;;
*.zst) zstd -dc ;;
esac < "$1"
}
sh256() {
# Higher level sh256 function which filters out non-existent
# files (and also directories).
for f do shift
[ -d "$f" ] || [ ! -e "$f" ] || set -- "$@" "$f"
done
# Only call _sh256 if we have files to operate on.
for f do _sh256 "$@"; return 0; done
}
_sh256() {
# There's no standard utility to generate sha256 checksums.
# This is a simple wrapper around sha256sum, sha256, shasum,
# openssl, digest, ... which will use whatever is available.
#
# All utilities must match 'sha256sum' output.
#
# Example: '<checksum> <file>'
unset hash
2021-07-18 21:11:29 +00:00
# Set the arguments based on found sha256 utility.
case ${cmd_sha##*/} in
openssl) set -- dgst -sha256 -r "$@" ;;
sha256) set -- -r "$@" ;;
shasum) set -- -a 256 "$@" ;;
digest) set -- -a sha256 "$@" ;;
esac
IFS=$newline
# Generate checksums for all input files. This is a single
# call to the utility rather than one per file.
_hash=$("$cmd_sha" "$@") || die "Failed to generate checksums"
# Strip the filename from each element.
# '<checksum> ?<file>' -> '<checksum>'
for sum in $_hash; do
hash=$hash${hash:+"$newline"}${sum%% *}
done
2021-07-19 08:49:38 +00:00
printf '%s\n' "$hash"
2021-07-18 20:15:58 +00:00
unset IFS
}
2021-07-03 14:48:39 +00:00
pkg_find_version() {
2021-07-17 10:58:36 +00:00
ver_pre=$repo_ver
rel_pre=$repo_rel
pkg_find "$@"
2021-07-03 14:48:39 +00:00
2021-07-06 18:05:03 +00:00
read -r repo_ver repo_rel 2>/dev/null < "$repo_dir/version" ||
2021-07-03 14:48:39 +00:00
die "$1" "Failed to read version file ($repo_dir/version)"
ok "$repo_rel" ||
die "$1" "Release field not found in version file"
2021-07-19 18:13:06 +00:00
# This belongs somewhere else, for now it can live here.
[ -x "$repo_dir/build" ] ||
die "$pkg" "Build file not found or not executable"
2021-07-17 10:58:36 +00:00
}
pkg_find_version_split() {
pkg_find_version "$@"
# Split the version on '.+-_' to obtain individual components.
IFS=.+-_ read -r repo_major repo_minor repo_patch repo_ident <<EOF
$repo_ver
EOF
2021-07-03 14:48:39 +00:00
}
pkg_find() {
2021-07-17 11:55:59 +00:00
_pkg_find "$@" || die "'$1' not found"
}
_pkg_find() {
2020-05-21 07:38:07 +00:00
# Figure out which repository a package belongs to by searching for
# directories matching the package name in $KISS_PATH/*.
2021-07-05 09:22:52 +00:00
set -- "$1" "$2" "$3" "${4:-"$KISS_PATH"}"
IFS=:
2021-07-05 09:22:52 +00:00
# Iterate over KISS_PATH, grabbing all directories which match the query.
2021-07-04 13:02:45 +00:00
# Intentional.
# shellcheck disable=2086
for _find_path in $4 "${3:-$sys_db}"; do set +f
for _find_pkg in "$_find_path/"$1; do
test "${3:--d}" "$_find_pkg" && set -f -- "$@" "$_find_pkg"
2020-03-23 09:41:32 +00:00
done
done
2019-06-13 16:40:50 +00:00
2020-04-20 09:14:25 +00:00
unset IFS
2020-03-23 10:05:54 +00:00
# Show all search results if called from 'kiss search', else store the
2021-07-16 11:59:23 +00:00
# values in variables. If there are 4 arguments, no package has been found.
2021-07-14 07:04:18 +00:00
case $2-$# in
2021-07-17 11:55:59 +00:00
*-4) return 1 ;;
-*) repo_dir=$5 repo_name=${5##*/} ;;
2021-07-16 11:59:23 +00:00
*) shift 4; printf '%s\n' "$@"
2021-07-14 07:04:18 +00:00
esac
2019-06-13 16:40:50 +00:00
}
2021-07-17 10:58:36 +00:00
pkg_list_version() {
# List installed packages. As the format is files and directories, this
# just involves a simple for loop and file read.
2019-06-29 20:38:35 +00:00
# Optional arguments can be passed to check for specific packages. If no
# arguments are passed, list all.
ok "$1" || { set +f; set -f -- "$sys_db"/*; }
# Loop over each package and print its name and version.
for _list_pkg do
2021-07-17 10:58:36 +00:00
pkg_find_version "${_list_pkg##*/}" "" "" "$sys_db"
2021-07-03 23:07:36 +00:00
2021-07-17 10:58:36 +00:00
printf '%s\n' "$repo_name $repo_ver-$repo_rel"
done
2020-09-12 14:35:10 +00:00
}
2019-06-13 14:48:08 +00:00
pkg_cache() {
# Find the tarball of a package using a glob. Use the user's set compression
# method if found or first match of the below glob.
2021-07-03 14:48:39 +00:00
pkg_find_version "$1"
2021-07-16 12:04:23 +00:00
set +f -- "$bin_dir/$1@$repo_ver-$repo_rel.tar."
set -f -- "$1$KISS_COMPRESS" "$1"*
tar_file=$1
2021-07-17 08:46:20 +00:00
# If the first match does not exist, use the second. If neither exist,
# this function returns 1 and the caller handles the error.
[ -f "$1" ] || { tar_file=$2; [ -f "$2" ]; }
}
pkg_source_resolve() {
# Given a line of input from the sources file, return an absolute
# path to the source if it already exists, error if not.
2021-07-20 14:50:37 +00:00
ok "${2##\#*}" || { unset _res _des; return; }
unset _des
# Surround each replacement with substitutions to handled escaped markers.
# First substitution turns '\MARKER' into ' ' (can't appear in sources as
# they're already split on whitespace), second replaces 'MARKER' with its
# value and the third, turns ' ' into 'MARKER' (dropping \\).
fnr "${2%"${2##*[!/]}"}" \
\\VERSION \ VERSION "$repo_ver" \ VERSION \
\\RELEASE \ RELEASE "$repo_rel" \ RELEASE \
\\MAJOR \ MAJOR "$repo_major" \ MAJOR \
\\MINOR \ MINOR "$repo_minor" \ MINOR \
\\PATCH \ PATCH "$repo_patch" \ PATCH \
\\IDENT \ IDENT "$repo_ident" \ IDENT \
\\PACKAGE \ PACKAGE "$repo_name" \ PACKAGE
2021-07-19 09:25:44 +00:00
set -- "$1" "$_fnr" "${3%"${3##*[!/]}"}" "$4"
# Git repository.
2021-07-19 08:41:18 +00:00
if null "${2##git+*}"; then
_res=$2
# Remote source (cached).
elif [ -f "$src_dir/$1/${3:+"$3/"}${2##*/}" ]; then
_res=$src_dir/$1/${3:+"$3/"}${2##*/}
# Remote source.
2021-07-19 08:41:18 +00:00
elif null "${2##*://*}"; then
_res=url+$2
_des=$src_dir/$1/${3:+"$3/"}${2##*/}
# Local relative dir.
elif [ -d "$repo_dir/$2" ]; then
_res=$repo_dir/$2/.
# Local absolute dir.
elif [ -d "/${2##/}" ]; then
_res=/${2##/}/.
# Local relative file.
elif [ -f "$repo_dir/$2" ]; then
_res=$repo_dir/$2
# Local absolute file.
elif [ -f "/${2##/}" ]; then
_res=/${2##/}
else
die "$1" "No local file '$2'"
fi
2021-07-19 09:25:44 +00:00
ok "$4" || printf 'found %s\n' "$_res"
}
pkg_source() {
2020-05-21 07:38:07 +00:00
# Download any remote package sources. The existence of local files is
# also checked.
2021-07-17 10:58:36 +00:00
pkg_find_version_split "$1"
# Support packages without sources. Simply do nothing.
[ -f "$repo_dir/sources" ] || return 0
2021-07-17 11:31:06 +00:00
log "$1" "Reading sources"
2020-02-14 16:06:47 +00:00
while read -r src dest || ok "$src"; do
2021-07-05 08:23:10 +00:00
pkg_source_resolve "$1" "$src" "$dest" "$2"
2019-09-23 06:30:34 +00:00
# arg1: pre-source
# arg2: package name
# arg3: verbatim source
# arg4: resolved source
run_hook pre-source "$1" "$src" "$_fnr"
case $_res in url+*)
# Create directory structure in source cache. This prevents cache
# conflicts between identical sources with differing dests.
mkcd "${_des%/*}"
2020-02-11 15:33:38 +00:00
pkg_source_get "$_des" "${_res##url+}"
esac
# arg1: post-source
# arg2: package name
# arg3: verbatim source
# arg4: resolved source
run_hook post-source "$1" "$src" "${_des:-"$_res"}"
done < "$repo_dir/sources"
2019-06-13 14:48:08 +00:00
}
pkg_source_get() {
log "$repo_name" "Downloading $2"
# Set the arguments based on found download utility.
case ${cmd_get##*/} in
2021-07-20 16:38:13 +00:00
aria2c|axel) set -- -o "$@" ;;
curl) set -- -fLo "$@" ;;
wget|wget2) set -- -O "$@" ;;
esac
"$cmd_get" "$@" || {
rm -f "$2"
die "$repo_name" "Failed to download $3"
}
}
2021-07-17 12:32:33 +00:00
pkg_source_git() {
# This magic will shallow clone branches, commits or the
# regular repository. It correctly handles cases where a
# shallow clone is not possible.
log "$repo_name" "Cloning $1"
2021-07-17 12:32:33 +00:00
# Split the source into URL + OBJECT (branch or commit).
url=$1 com=${url##*[@#]} com=${com#${url%[#@]*}}
2021-07-17 12:32:33 +00:00
git init
git remote add origin "${url%[#@]*}"
git fetch -t --filter=tree:0 origin "$com" || git fetch -t
git -c advice.detachedHead=0 checkout "${com:-FETCH_HEAD}"
}
pkg_source_tar() {
2021-07-05 08:37:00 +00:00
# This is a portable shell implementation of GNU tar's
# '--strip-components 1'. Use of this function denotes a
# performance penalty.
tmp_file "$repo_name" tarball
tmp_file "$repo_name" tarball-manifest
decompress "$1" > "$_tmp_file_pre" ||
die "$repo_name" "Failed to decompress $1"
2021-07-05 08:37:00 +00:00
tar xf "$_tmp_file_pre" ||
2021-07-17 12:32:33 +00:00
die "$repo_name" "Failed to extract $1"
2021-07-05 08:37:00 +00:00
# The sort command filters out all duplicate top-level
# directories from the tarball's manifest. This is an optimization
# as we avoid looping (4000 times for Python(!)).
tar tf "$_tmp_file_pre" | sort -ut / -k1,1 > "$_tmp_file" ||
die "$repo_name" "Failed to extract manifest"
2021-07-05 08:37:00 +00:00
# Iterate over all directories in the first level of the
# tarball's manifest. Each directory is moved up a level.
while IFS=/ read -r dir _; do case ${dir#.} in *?*)
# Move the parent directory to prevent naming conflicts
# with the to-be-moved children.
2021-07-16 17:12:52 +00:00
mv -f "$dir" "$KISS_PID-$dir"
2021-07-05 08:37:00 +00:00
# Move all children up a directory level. If the mv command
# fails, fallback to copying the remainder of the files.
2021-07-05 08:37:00 +00:00
#
# We can't use '-exec {} +' with any arguments between
# the '{}' and '+' as this is not POSIX. We must also
# use '$0' and '$@' to reference all arguments.
2021-07-16 17:12:52 +00:00
find "$KISS_PID-$dir/." ! -name . -prune \
2021-07-05 08:37:00 +00:00
-exec sh -c 'mv -f "$0" "$@" .' {} + 2>/dev/null ||
2021-07-16 17:12:52 +00:00
find "$KISS_PID-$dir/." ! -name . -prune \
2021-07-05 08:37:00 +00:00
-exec sh -c 'cp -fRp "$0" "$@" .' {} +
# Remove the directory now that all files have been
# transferred out of it. This can't be a simple 'rmdir'
# as we may leave files in here if any were copied.
2021-07-16 17:12:52 +00:00
rm -rf "$KISS_PID-$dir"
esac done < "$_tmp_file"
# Remove the tarball now that we are done with it.
rm -f "$_tmp_file_pre"
2021-07-05 08:37:00 +00:00
}
2019-06-13 14:48:08 +00:00
pkg_extract() {
2020-05-21 07:38:07 +00:00
# Extract all source archives to the build directory and copy over any
2021-07-05 10:52:54 +00:00
# local repository files.
#
# NOTE: repo_dir comes from caller.
log "$1" "Extracting sources"
2019-06-29 20:38:35 +00:00
# arg1: pre-extract
# arg2: package name
# arg3: path to DESTDIR
run_hook pre-extract "$pkg" "$pkg_dir/$pkg"
while read -r src dest || ok "$src"; do
2021-07-05 08:23:10 +00:00
pkg_source_resolve "$1" "$src" "$dest" >/dev/null
# Create the source's directories if not null.
2021-07-19 08:41:18 +00:00
null "$_res" || mkcd "$mak_dir/$1/$dest"
2021-07-17 12:32:33 +00:00
case $_res in
git+*)
pkg_source_git "${_res##git+}"
;;
2020-01-16 21:45:12 +00:00
*.tar|*.tar.??|*.tar.???|*.tar.????|*.t?z)
pkg_source_tar "$_res"
;;
2019-09-23 06:30:34 +00:00
2021-07-17 12:32:33 +00:00
*?*)
cp -Rf "$_res" .
;;
esac
done < "$repo_dir/sources" || die "$1" "Failed to extract $_res"
2019-06-13 14:48:08 +00:00
}
2019-06-29 20:38:35 +00:00
pkg_depends() {
2020-07-07 21:51:40 +00:00
# Resolve all dependencies and generate an ordered list. The deepest
# dependencies are listed first and then the parents in reverse order.
contains "$deps" "$1" || {
2021-07-06 16:53:49 +00:00
# Filter out non-explicit, already installed packages.
2021-07-19 08:41:18 +00:00
null "$3" || ok "$2" || contains "$explicit" "$1" ||
2021-07-17 10:58:36 +00:00
! [ -d "$sys_db/$1" ] || return
# Detect circular dependencies and bail out.
# Looks for multiple repeating patterns of (dep dep_parent) (5 is max).
case " $4 " in
*" ${4##* } "*" $1 "\
*" ${4##* } "*" $1 "\
*" ${4##* } "*" $1 "\
*" ${4##* } "*" $1 "\
*" ${4##* } "*" $1 "\
*)
die "Circular dependency detected $1 <> ${4##* }"
esac
! pkg_find "$1" || ! [ -e "$repo_dir/depends" ] ||
2021-06-30 13:49:27 +00:00
# Recurse through the dependencies of the child packages.
while read -r dep dep_type || ok "$dep"; do
ok "${dep##\#*}" || continue
2021-07-19 18:27:05 +00:00
pkg_depends "$dep" '' "$3" "$4 $1" "$dep_type"
done < "$repo_dir/depends" || :
# Add parent to dependencies list.
if ! equ "$2" expl || { equ "$5" make && ! pkg_cache "$1"; }; then
deps="$deps $1"
fi
}
2019-06-29 20:38:35 +00:00
}
pkg_order() {
# Order a list of packages based on dependence and take into account
# pre-built tarballs if this is to be called from 'kiss i'.
unset order redro deps
case $2 in *?*)
log "Ordering arguments list based on dependence"
esac
for pkg do case $pkg in
2021-07-06 19:10:23 +00:00
/*@*.tar.*) deps="$deps $pkg" ;;
*@*.tar.*) deps="$deps $ppwd/$pkg" ;;
*/*) die "Not a package' ($pkg)" ;;
*) pkg_depends "$pkg" raw
esac done
# Filter the list, only keeping explicit packages. The purpose of these
# two loops is to order the argument list based on dependence.
2021-07-19 15:20:41 +00:00
for pkg in $deps; do case " $* " in *" $pkg "*|*" ${pkg##"$ppwd/"} "*)
order="$order $pkg"
redro="$pkg $redro"
esac done
unset deps
}
2019-06-13 14:48:08 +00:00
pkg_strip() {
2020-05-21 07:38:07 +00:00
# Strip package binaries and libraries. This saves space on the system as
# well as on the tarballs we ship for installation.
[ -f "$mak_dir/$pkg/nostrip" ] || equ "$KISS_STRIP" 0 && return
2019-06-13 14:48:08 +00:00
log "$1" "Stripping binaries and libraries"
2019-06-29 20:38:35 +00:00
2020-05-24 13:56:30 +00:00
# Strip only files matching the below ELF types. This uses 'od' to print
2020-05-24 16:42:44 +00:00
# the first 18 bytes of the file. This is the location of the ELF header
# (up to the ELF type) and contains the type information we need.
2020-05-24 13:56:30 +00:00
#
# Static libraries (.a) are in reality AR archives which contain ELF
2020-05-24 16:42:44 +00:00
# objects. We simply read from the same 18 bytes and assume that the AR
# header equates to an archive containing objects (.o).
2020-05-24 16:22:13 +00:00
#
2020-05-24 16:42:44 +00:00
# Example ELF output ('003' is ELF type):
2020-05-24 16:22:13 +00:00
# 0000000 177 E L F 002 001 001 \0 \0 \0 \0 \0 \0 \0 \0 \0
# 0000020 003 \0
# 0000022
#
# Example AR output (.a):
# 0000000 ! < a r c h > \n /
# 0000020
# 0000022
while read -r file; do case $file in
# Look only in these locations for files of interest (libraries,
# programs, etc). This includes all subdirectories. Old behavior
# would run od on all files (upwards of 4000 for Python).
*/sbin/?*|*/bin/?*|*/lib/?*|*/lib??/?*|*/lib???/?*|*/lib????/?*)
2020-05-25 15:37:57 +00:00
case $(od -A o -t c -N 18 "$file") in
2020-05-24 14:50:28 +00:00
# REL (object files (.o), static libraries (.a)).
*177*E*L*F*0000020\ 001\ *|*\!*\<*a*r*c*h*\>*)
2020-05-24 16:22:13 +00:00
strip -g -R .comment -R .note "$file"
;;
# EXEC (binaries), DYN (shared libraries).
# Shared libraries keep global symbols in a separate ELF section
# called '.dynsym'. '--strip-all/-s' does not touch the dynamic
# symbol entries which makes this safe to do.
2020-05-24 16:34:05 +00:00
*177*E*L*F*0000020\ 00[23]\ *)
2020-05-24 16:22:13 +00:00
strip -s -R .comment -R .note "$file"
;;
2020-05-24 13:56:30 +00:00
esac
esac done < "$pkg_dir/$1/$pkg_db/$1/manifest" 2>/dev/null || :
2019-06-13 14:48:08 +00:00
}
2020-11-06 06:45:56 +00:00
pkg_fix_deps() {
2020-05-21 07:38:07 +00:00
# Dynamically look for missing runtime dependencies by checking each
# binary and library with 'ldd'. This catches any extra libraries and or
# dependencies pulled in by the package's build suite.
2021-07-03 10:48:14 +00:00
log "$1" "looking for dependencies (using ${cmd_elf##*/})"
2021-07-17 09:39:28 +00:00
tmp_file_copy "$1" depends depends
tmp_file "$1" depends-fixed
2020-11-06 06:45:56 +00:00
set +f
set -f -- "$sys_db/"*/manifest
2020-03-23 10:40:38 +00:00
2021-07-17 09:37:50 +00:00
# False positive (not a write).
# shellcheck disable=2094
while read -r _file; do case $_file in
# Look only in these locations for files of interest (libraries,
# programs, etc). This includes all subdirectories. Old behavior
# would run ldd on all files (upwards of 4000 for Python).
*/sbin/?*|*/bin/?*|*/lib/?*|*/lib??/?*|*/lib???/?*|*/lib????/?*)
# The readelf mode requires ldd's output to resolve the library
# path for a given file. If ldd fails, silently skip the file.
2021-07-20 16:50:50 +00:00
ldd=$(ldd -- "$pkg_dir/$repo_name$_file" 2>/dev/null) || continue
# Attempt to get information from readelf. If this fails (or we
# are in ldd mode), do full ldd mode (which has the downside of
# listing dependencies of dependencies (and so on)).
2021-07-20 16:50:50 +00:00
elf=$("$cmd_elf" -d "$pkg_dir/$repo_name$_file" 2>/dev/null) || elf=$ldd
# Iterate over the output of readelf or ldd, extract file names,
# resolve their paths and finally, figure out their owner.
2021-07-19 15:20:41 +00:00
while read -r lib; do case $lib in *NEEDED*\[*\]|*'=>'*)
# readelf: 0x0000 (NEEDED) Shared library: [libjson-c.so.5]
lib=${lib##*\[}
lib=${lib%%\]*}
# Resolve library path.
# ldd: libjson-c.so.5 => /lib/libjson-c.so.5 ...
case $cmd_elf in
2021-07-20 16:50:50 +00:00
*readelf) lib=${ldd#*" $lib => "} ;;
*) lib=${lib##*=> } ;;
2020-09-25 19:38:05 +00:00
esac
lib=${lib%% *}
# Skip files owned by libc and POSIX.
case ${lib##*/} in
ld-* |\
lib[cm].so* |\
libcrypt.so* |\
libdl.so* |\
libmvec.so* |\
libpthread.so* |\
libresolv.so* |\
librt.so* |\
libtrace.so* |\
libutil.so* |\
libxnet.so* |\
ldd)
continue
esac
resolve_path "$lib"
# Skip file if owned by current package
! pkg_owner -e "$_rpath" manifest ||
continue
! pkg_owner -e "$_rpath" "$@" ||
2021-07-17 13:12:31 +00:00
printf '%s\n' "$_owns"
esac done <<EOF || :
2021-07-20 16:50:50 +00:00
$elf
EOF
esac done < manifest |
# Sort the depends file (including the existing depends file) and
# remove any duplicate entries. This can't take into account comments
# so they remain rather than being replaced.
sort -uk1,1 "$_tmp_file_pre" - > "$_tmp_file"
# If the depends file was modified, show a diff and replace it.
2021-07-16 12:14:36 +00:00
! [ -s "$_tmp_file" ] || {
diff -U 3 "$_tmp_file_pre" "$_tmp_file" 2>/dev/null || :
# Replace the existing depends file if one exists, otherwise this
# just moves the file to its final resting place.
mv -f "$_tmp_file" depends
# Generate a new manifest as we may be the creator of the depends
# file. This could otherwise be implemented by inserting a line
# at the correct place in the existing manifest.
2021-07-19 15:53:01 +00:00
pkg_manifest "${PWD##*/}" "$pkg_dir"
2021-07-16 12:14:36 +00:00
}
}
2021-07-05 10:26:16 +00:00
pkg_manifest() {
# Generate the package's manifest file. This is a list of each file
# and directory inside the package. The file is used when uninstalling
# packages, checking for package conflicts and for general debugging.
log "$1" "Generating manifest"
2021-07-19 15:53:01 +00:00
tmp_file "$1" manifest
2021-07-17 04:43:15 +00:00
# Create a list of all files and directories. Append '/' to the end of
# directories so they can be easily filtered out later. Also filter out
# all libtool .la files and charset.alias.
{
printf '%s\n' "$2/$1/$pkg_db/$1/manifest"
! [ -d "$2/$1/etc" ] ||
printf '%s\n' "$2/$1/$pkg_db/$1/etcsums"
find "$2/$1" ! -path "$2/$1" -type d -exec printf '%s/\n' {} + \
-o \( ! -type d -a ! -name \*.la -a ! -name charset.alias \) -print
2020-11-23 05:21:02 +00:00
2021-07-17 08:21:26 +00:00
# Sort the output in reverse. Directories appear after their contents.
} | sort -ur > "$_tmp_file"
2021-07-05 10:26:16 +00:00
2021-07-19 18:32:45 +00:00
# Remove the prefix from each line.
2021-07-19 15:53:01 +00:00
while read -r file; do
printf '%s\n' "${file#"$2/$1"}"
done < "$_tmp_file" > "$2/$1/$pkg_db/$1/manifest"
2021-07-05 10:26:16 +00:00
}
2019-06-13 14:48:08 +00:00
pkg_manifest_validate() {
2021-07-21 05:07:51 +00:00
# NOTE: _pkg comes from caller.
log "$_pkg" "Checking if manifest valid"
while read -r line; do
2021-07-21 05:07:51 +00:00
[ -e "$tar_dir/$_pkg$line" ] || [ -h "$tar_dir/$_pkg$line" ] || {
2021-07-03 23:39:40 +00:00
printf '%s\n' "$line"
set -- "$@" "$line"
}
2021-07-21 05:07:51 +00:00
done < "$pkg_db/$_pkg/manifest"
2021-07-03 23:39:40 +00:00
2021-07-21 05:07:51 +00:00
for f do
die "$_pkg" "manifest contains $# non-existent files"
done
}
pkg_manifest_replace() {
# Replace the matching line in the manifest with the desired replacement.
# This used to be a 'sed' call which turned out to be a little
# error-prone in some cases. This new method is a tad slower but ensures
# we never wipe the file due to a command error.
tmp_file "$1" "manifest-replace-${2##*/}"
while read -r line; do
! equ "$line" "$2" || line=$3
printf '%s\n' "$line"
done < "$sys_db/$1/manifest" | sort -r > "$_tmp_file"
mv -f "$_tmp_file" "$sys_db/$1/manifest"
}
pkg_etcsums() {
# Generate checksums for each configuration file in the package's /etc/
# directory for use in "smart" handling of these files.
log "$repo_name" "Generating etcsums"
# Minor optimization - skip packages without /etc/.
[ -d "$pkg_dir/$repo_name/etc" ] || return 0
2020-02-05 08:56:25 +00:00
# Create a list of all files in etc but do it in reverse.
while read -r etc; do case $etc in /etc/*[!/])
set -- "$pkg_dir/$repo_name/$etc" "$@"
esac done < manifest
sh256 "$@" > etcsums
}
2020-02-05 08:56:25 +00:00
2021-07-17 15:14:09 +00:00
pkg_tar() {
# Create a tarball from the built package's files. This tarball also
# contains the package's database entry.
2021-07-05 10:52:54 +00:00
#
# NOTE: repo_ comes from caller.
log "$1" "Creating tarball"
2021-07-17 13:44:32 +00:00
_tar_file=$bin_dir/$1@$repo_ver-$repo_rel.tar.$KISS_COMPRESS
# Use 'cd' to avoid needing tar's '-C' flag which may not be portable
# across implementations.
kiss: Various portability fixes. - Added POSIX shell implementation of the 'readlink' utility for use _only_ when the 'readlink' utility is not available. - Made tar usage more portable. All that is left now is the removal of --strip-components 1 for full (presumed) portability. - Swapped from sha256sum to shasum as it's more portable. This is still not a full solution. Here's a checklist of where we currently are: POSIX Core utilities (depends coreutils) - [x] sh (POSIX) - [x] find (POSIX) -type f, -type d, -exec {} [+;], -o, -print, ! - [x] ls (POSIX) -l, -d - [x] sed (POSIX) -n, s/<search>/<replace>/g, /<delete>/d - [x] grep (POSIX) -l, -F, -x, -f, -q, -v - [x] sort (POSIX) -r, -u, -k - [x] tee (POSIX) - [x] date (POSIX) - [x] mkdir (POSIX) -p - [x] rm (POSIX) -f, -r - [x] rmdir (POSIX) - [x] cp (POSIX) -f, -P, -p, -L, -R - [x] mv (POSIX) -f - [x] chown (POSIX) -h - [x] diff (POSIX) -U BSD utilities - [x] install (BSD, not POSIX) (still portable) -o, -g, -m, -d Misc - [x] readlink (NOT POSIX) (fallback shell implementation) - [x] su* (sudo, doas, su) (in order, optional) - [x] git (downloads from git) (must link to curl) Compiler/libc utilities (depends cc & libc) - [x] readelf (Part of compiler toolchain) (GNU, LLVM or elfutils) - [x] strip (Part of compiler toolchain) (GNU, LLVM or elfutils) - [x] ldd (Part of libc) Tarball compression - [ ] tar (must have --strip-components) (busybox, GNU, libarchive)) - [x] bzip2 (widely used) -d, -z - [x] xz (widely used) -d, -z, -c, -T - [x] gzip (widely used) -d, -6 - [x] zstd (optional) -d, -z, -c - [x] unzip (optional) - [ ] shasum (checksums) (NO standard. Portable across Linux/BSD)
2020-04-30 16:38:37 +00:00
cd "$pkg_dir/$1"
2020-04-24 08:04:05 +00:00
# Create a tarball from the contents of the built package.
2021-07-08 08:15:31 +00:00
tar cf - . | case $KISS_COMPRESS in
bz2) bzip2 -z ;;
gz) gzip -6 ;;
lzma) lzma -z ;;
lz) lzip -z ;;
2021-07-05 08:44:48 +00:00
xz) xz -zT0 ;;
zst) zstd -z ;;
esac > "$_tar_file"
2021-07-17 15:14:09 +00:00
cd "$OLDPWD"
log "$1" "Successfully created tarball"
2021-07-15 04:20:31 +00:00
# arg1: post-package
# arg2: package name
# arg3: path to tarball
run_hook post-package "$1" "$_tar_file"
2021-07-17 15:14:09 +00:00
}
2019-06-13 14:48:08 +00:00
pkg_build_all() {
# Build packages and turn them into packaged tarballs.
# Order the argument list and filter out duplicates.
2021-07-06 18:41:30 +00:00
# Mark packages passed on the command-line explicit.
# Also resolve dependencies for all explicit packages.
2020-09-09 13:52:18 +00:00
for pkg do
2021-07-19 18:13:06 +00:00
pkg_depends "$pkg" expl filter
explicit="$explicit $pkg "
2020-09-09 13:52:18 +00:00
done
2019-09-13 18:25:33 +00:00
# If this is an update, don't always build explicitly passsed packages
# and instead install pre-built binaries if they exist.
ok "$pkg_update" || explicit_build=$explicit
2019-09-13 20:52:15 +00:00
set --
2020-05-16 06:26:34 +00:00
# If an explicit package is a dependency of another explicit package,
2021-07-06 18:41:30 +00:00
# remove it from the explicit list.
2021-06-29 15:28:50 +00:00
for pkg in $explicit; do
contains "$deps" "$pkg" || set -- "$@" "$pkg"
2019-09-13 18:25:33 +00:00
done
explicit_cnt=$#
2021-07-06 16:53:49 +00:00
explicit=$*
log "Building: explicit: $*${deps:+, implicit: ${deps## }}"
2021-07-04 13:02:45 +00:00
# Intentional, globbing disabled.
2019-09-20 14:54:12 +00:00
# shellcheck disable=2046,2086
set -- $deps "$@"
# Ask for confirmation if extra packages need to be built.
2021-07-19 05:08:49 +00:00
equ "$#" "$explicit_cnt" || prompt
2019-07-11 06:14:17 +00:00
log "Checking for pre-built dependencies"
# Install any pre-built dependencies if they exist in the binary
# directory and are up to date.
2021-06-30 09:14:31 +00:00
for pkg in "$@"; do
if ! contains "$explicit_build" "$pkg" && pkg_cache "$pkg"; then
2021-07-07 20:10:07 +00:00
log "$pkg" "Found pre-built binary"
2021-07-03 15:23:51 +00:00
# Intended behavior.
# shellcheck disable=2030,2031
2021-07-03 15:12:40 +00:00
(export KISS_FORCE=1; args i "$tar_file")
2021-06-30 09:14:31 +00:00
else
set -- "$@" "$pkg"
fi
2020-03-21 11:54:48 +00:00
2021-06-30 09:14:31 +00:00
shift
2020-09-09 13:52:18 +00:00
done
2019-06-29 20:38:35 +00:00
for pkg do
pkg_source "$pkg"
! [ -f "$repo_dir/sources" ] || pkg_verify "$pkg"
done
2019-06-13 14:48:08 +00:00
2019-07-03 13:35:14 +00:00
# Finally build and create tarballs for all passed packages and
# dependencies.
2020-06-11 08:06:43 +00:00
for pkg do
2021-07-20 12:42:18 +00:00
log "$pkg" "Building package ($((_build_cur+=1))/$#)"
2020-01-03 07:10:41 +00:00
2021-07-17 10:58:36 +00:00
pkg_find_version_split "$pkg"
2021-07-16 15:54:10 +00:00
# arg1: queue-status
# arg2: package name
# arg3: number in queue
# arg4: total in queue
2021-07-20 12:42:18 +00:00
run_hook queue "$pkg" "$_build_cur" "$#"
2021-07-16 15:54:10 +00:00
! [ -f "$repo_dir/sources" ] || pkg_extract "$pkg"
2020-02-05 08:56:25 +00:00
pkg_build "$pkg"
2021-07-19 15:53:01 +00:00
pkg_manifest "$pkg" "$pkg_dir"
pkg_strip "$pkg"
cd "$pkg_dir/$pkg/$pkg_db/$pkg"
2020-11-06 07:00:23 +00:00
pkg_fix_deps "$pkg"
pkg_etcsums
pkg_tar "$pkg"
2019-06-26 16:27:36 +00:00
if ok "$pkg_update" || ! contains "$explicit" "$pkg"; then
log "$pkg" "Needed as a dependency or has an update, installing"
2021-07-03 15:23:51 +00:00
# Intended behavior.
# shellcheck disable=2030,2031
2021-07-03 15:12:40 +00:00
(export KISS_FORCE=1; args i "$pkg")
fi
done
2019-06-29 20:38:35 +00:00
2021-07-17 06:18:14 +00:00
case $pkg_update in '')
# Intentional, globbing disabled.
# shellcheck disable=2046,2086
2021-07-17 06:18:14 +00:00
! prompt "Install built packages? [$explicit]" || (args i $explicit)
esac
2019-06-29 20:38:35 +00:00
}
pkg_build() {
# Install built packages to a directory under the package name to
# avoid collisions with other packages.
mkcd "$mak_dir/$1" "$pkg_dir/$1/$pkg_db"
log "$1" "Starting build"
2021-07-15 04:20:31 +00:00
# arg1: pre-build
# arg2: package name
# arg3: path to build directory
run_hook pre-build "$1" "$mak_dir/$1"
# Attempt to create the log file early so any permissions errors are caught
# before the build starts. 'tee' is run in a pipe and POSIX shell has no
# pipe-fail causing confusing behavior when tee fails.
2021-07-16 17:12:52 +00:00
: > "$log_dir/$1-$time-$KISS_PID"
# Call the build script, log the output to the terminal and to a file.
# There's no PIPEFAIL in POSIX shell so we must resort to tricks like kill.
{
2021-07-16 12:21:35 +00:00
# Give the script a modified environment. Define toolchain program
# environment variables assuming a generic environment by default.
#
# Define DESTDIR, PREFIX and GOPATH to sane defaults as their use is
# mandatory in anything using autotools, meson, cmake, etc.
#
# Define KISS_ROOT as the sanitized value used internally by the
# package manager. This is safe to join with other paths.
2021-07-14 20:46:43 +00:00
AR="${AR:-ar}" \
CC="${CC:-cc}" \
CXX="${CXX:-c++}" \
2021-07-14 20:46:43 +00:00
NM="${NM:-nm}" \
RANLIB="${RANLIB:-ranlib}" \
DESTDIR="$pkg_dir/$1" \
2021-07-14 20:46:43 +00:00
GOPATH="$PWD/go" \
KISS_ROOT="$KISS_ROOT" \
\
"$repo_dir/build" "$pkg_dir/$1" "$repo_ver" 2>&1 || {
log "$1" "Build failed"
2021-07-16 17:12:52 +00:00
log "$1" "Log stored to $log_dir/$1-$time-$KISS_PID"
2021-07-15 04:20:31 +00:00
# arg1: build-fail
# arg2: package name
# arg3: path to build directory
run_hook build-fail "$pkg" "$mak_dir/$1"
pkg_clean
kill 0
}
2021-07-16 17:12:52 +00:00
} | tee "$log_dir/$1-$time-$KISS_PID"
# Delete the log file if the build succeeded to prevent the directory
# from filling very quickly with useless logs.
2021-07-19 05:08:49 +00:00
equ "$KISS_KEEPLOG" 1 || rm -f "$log_dir/$1-$time-$KISS_PID"
# Copy the repository files to the package directory.
cp -LRf "$repo_dir" "$pkg_dir/$1/$pkg_db/"
log "$1" "Successfully built package"
2021-07-15 04:20:31 +00:00
# arg1: post-build
# arg2: package name
# arg3: path to DESTDIR
run_hook post-build "$1" "$pkg_dir/$1"
}
2019-06-29 20:38:35 +00:00
pkg_checksums() {
# Generate checksums for packages.
#
# NOTE: repo_ comes from caller.
while read -r src dest || ok "$src"; do
pkg_source_resolve "$repo_name" "$src" "$dest" >/dev/null
case $_res in [!g][!i][!t][!+]*/*[!.])
set -- "$@" "$_res"
esac
done < "$repo_dir/sources"
# Only run _sh256 if we have elements.
for f do _sh256 "$@"; return 0; done
2020-09-14 19:26:58 +00:00
}
2020-02-08 18:36:58 +00:00
pkg_verify() {
2020-05-21 07:38:07 +00:00
# Verify all package checksums. This is achieved by generating a new set
# of checksums and then comparing those with the old set.
2021-07-07 08:16:25 +00:00
#
# NOTE: repo_dir comes from caller.
log "$repo_name" "Verifying sources"
# Generate a new set of checksums to compare against.
pkg_checksums >/dev/null
# Intentional, globbing disabled.
# shellcheck disable=2038,2086
set -- $hash
2020-05-16 06:26:34 +00:00
# Only read checksums if we generated some.
2021-07-19 08:41:18 +00:00
null "$1" ||
# Check that the first column (separated by whitespace) match in both
# checksum files. If any part of either file differs, mismatch. Abort.
while read -r chk _ || ok "$1"; do
printf '%s\n%s\n' "- ${chk:-missing}" "+ ${1:-no source}"
case $1-${chk:-null} in
2021-07-19 15:16:49 +00:00
"$chk-$1"|"$1-SKIP")
# We have a match.
;;
"$hash"-*|*)
die "$repo_name" "Checksum mismatch"
;;
esac
shift "$(($# != 0))"
done < "$repo_dir/checksums"
2020-02-08 18:36:58 +00:00
}
2019-06-29 20:38:35 +00:00
pkg_conflicts() {
# Check to see if a package conflicts with another.
# _pkg comes from the caller.
log "$_pkg" "Checking for package conflicts"
tmp_file "$_pkg" manifest-files
tmp_file "$_pkg" found-conflicts
2020-05-16 06:26:34 +00:00
# Filter the tarball's manifest and select only files. Resolve all
# symlinks in file paths as well.
2021-07-17 12:36:37 +00:00
while read -r file; do case $file in *[!/])
resolve_path "$file"
2021-07-17 12:36:37 +00:00
printf '%s\n' "$_rpath"
esac done < "$PWD/$pkg_db/$_pkg/manifest" > "$_tmp_file_pre"
2020-01-06 05:49:52 +00:00
cd "$tar_dir/$_pkg"
2020-05-16 06:26:34 +00:00
set +f
set -f "$sys_db"/*/manifest
2020-01-06 05:49:52 +00:00
# Remove the current package from the manifest list.
2021-07-20 09:20:50 +00:00
fnr " $* " " $sys_db/$_pkg/manifest " ""
2020-05-16 06:26:34 +00:00
# Intentional, globbing disabled.
# shellcheck disable=2046,2086
set -- $_fnr
2020-01-28 15:00:29 +00:00
# Return here if there is nothing to check conflicts against.
! equ "$#" 0 || return 0
# Store the list of found conflicts in a file as we'll be using the
# information multiple times. Storing things in the cache dir allows
# us to be lazy as they'll be automatically removed on script end.
grep -Fxf "$_tmp_file_pre" -- "$@" 2>/dev/null > "$_tmp_file" || :
# Enable alternatives automatically if it is safe to do so.
# This checks to see that the package that is about to be installed
# doesn't overwrite anything it shouldn't in '/var/db/kiss/installed'.
2021-07-17 13:28:50 +00:00
grep -q ":/var/db/kiss/installed/" "$_tmp_file" || safe=1
if ! equ "$KISS_CHOICE" 1 && equ "$safe" 1 && [ -s "$_tmp_file" ]; then
# This is a novel way of offering an "alternatives" system.
# It is entirely dynamic and all "choices" are created and
# destroyed on the fly.
#
# When a conflict is found between two packages, the file
# is moved to a directory called "choices" and its name
# changed to store its parent package and its intended
# location.
#
# The package's manifest is then updated to reflect this
# new location.
2020-01-28 12:07:08 +00:00
#
# The 'kiss alternatives' command parses this directory and
# offers you the CHOICE of *swapping* entries in this
# directory for those on the filesystem.
2020-01-28 12:07:08 +00:00
#
# The alternatives command does the same thing we do here,
# it rewrites manifests and moves files around to make
# this work.
#
# Pretty nifty huh?
2020-03-11 17:36:31 +00:00
while IFS=: read -r _ con; do
printf '%s\n' "Found conflict $con"
2020-01-28 12:07:08 +00:00
# Create the "choices" directory inside of the tarball.
# This directory will store the conflicting file.
2021-07-17 08:31:29 +00:00
mkdir -p "$PWD/$cho_db"
2020-01-28 12:07:08 +00:00
# Construct the file name of the "db" entry of the
# conflicting file. (pkg_name>usr>bin>ls)
fnr "$con" '/' '>'
2020-01-28 12:07:08 +00:00
# Move the conflicting file to the choices directory
# and name it according to the format above.
mv -f "$PWD$con" "$PWD/$cho_db/$_pkg$_fnr" 2>/dev/null || {
2020-02-11 10:04:44 +00:00
log "File must be in ${con%/*} and not a symlink to it"
log "This usually occurs when a binary is installed to"
2021-07-17 08:31:29 +00:00
die "/sbin instead of /usr/bin (example)"
2020-02-11 10:04:44 +00:00
}
done < "$_tmp_file"
log "$_pkg" "Converted all conflicts to choices (kiss a)"
2020-02-06 12:24:21 +00:00
# Rewrite the package's manifest to update its location
# to its new spot (and name) in the choices directory.
pkg_manifest "$_pkg" "$tar_dir"
elif [ -s "$_tmp_file" ]; then
log "Package '$_pkg' conflicts with another package" "" "!>"
log "Run 'KISS_CHOICE=1 kiss i $_pkg' to add conflicts" "" "!>"
2020-05-07 11:46:50 +00:00
die "as alternatives." "" "!>"
2020-01-28 15:00:29 +00:00
fi
2019-06-13 14:48:08 +00:00
}
2020-01-28 13:07:11 +00:00
pkg_swap() {
# Swap between package alternatives.
2021-07-17 10:58:36 +00:00
[ -d "$sys_db/$1" ] || die "'$1' not found"
2020-01-28 13:07:11 +00:00
fnr "$1$2" '/' '>'
2020-01-28 13:07:11 +00:00
[ -f "$sys_ch/$_fnr" ] || [ -h "$sys_ch/$_fnr" ] ||
2021-07-17 10:58:36 +00:00
die "Alternative '$1 ${2:-null}' doesn't exist"
if [ -f "$KISS_ROOT$2" ]; then
pkg_owner "/${2#/}" ||
2021-07-17 13:12:31 +00:00
die "File '$2' exists on filesystem but isn't owned"
2020-09-26 05:08:10 +00:00
log "Swapping '$2' from '$_owns' to '$1'"
2020-09-26 05:08:10 +00:00
# Convert the current owner to an alternative and rewrite its manifest
# file to reflect this.
cp -Pf "$KISS_ROOT$2" "$sys_ch/$_owns>${_fnr#*>}"
pkg_manifest_replace "$_owns" "$2" "/$cho_db/$_owns>${_fnr#*>}"
fi
# Convert the desired alternative to a real file and rewrite the manifest
# file to reflect this. The reverse of above.
mv -f "$sys_ch/$_fnr" "$KISS_ROOT/$2"
pkg_manifest_replace "$1" "/$cho_db/$_fnr" "$2"
2020-01-28 13:07:11 +00:00
}
file_rwx() {
# Convert the output of 'ls' (rwxrwx---) to octal. This is simply
# a 1-9 loop with the second digit being the value of the field.
2021-07-17 08:35:29 +00:00
#
# NOTE: This drops setgid/setuid permissions and does not include
# them in the conversion. This is intentional.
2021-07-17 08:40:51 +00:00
rwx=$(ls -ld "$1") oct='' o=0
for c in 14 22 31 44 52 61 74 82 91; do
rwx=${rwx#?}
case $rwx in
[rwx]*): "$((o+=${c#?}))" ;;
[st]*): "$((o+=1))" ;;
[ST]*) ;;
esac
2020-04-28 04:43:54 +00:00
2021-07-13 20:48:03 +00:00
case $((${c%?} % 3)) in 0)
oct=$oct$o o=0
esac
done
}
pkg_install_files() {
2021-07-17 14:49:43 +00:00
# Copy files and create directories (preserving permissions).
2021-07-17 14:51:17 +00:00
# The 'test $1' will run with '-z' for overwrite and '-e' for verify.
2021-07-17 14:49:43 +00:00
while { read -r file && _file=$KISS_ROOT$file; } do case $file in
*/)
# Skip directories if they already exist in the file system.
# (Think /usr/bin, /usr/lib, etc).
[ -d "$_file" ] || {
file_rwx "$2/${file#/}"
mkdir -m "$oct" "$_file"
}
;;
2020-04-22 06:37:46 +00:00
2021-07-17 14:49:43 +00:00
*)
# Skip directories and files which exist in verify mode.
[ -d "$_file" ] || ! test "$1" "$_file" ||
2021-07-17 14:49:43 +00:00
continue
case $file in /etc/*[!/])
# Handle /etc/ files in a special way (via a 3-way checksum) to
# determine how these files should be installed. Do we overwrite
# the existing file? Do we install it as $file.new to avoid
# deleting user configuration? etc.
#
# This is more or less similar to Arch Linux's Pacman with the
# user manually handling the .new files when and if they appear.
pkg_etc || continue
esac
if [ -h "$_file" ]; then
2021-07-17 14:49:43 +00:00
# Copy the file to the destination directory overwriting
# any existing file.
cp -fP "$2$file" "${_file%/*}/."
else
# Construct a temporary filename which is a) unique and
# b) identifiable as related to the package manager.
__tmp=${_file%/*}/__kiss-tmp-$_pkg-${file##*/}-$KISS_PID
# Copy the file to the destination directory with the
# temporary name created above.
cp -fP "$2$file" "$__tmp" &&
# Atomically move the temporary file to its final
# destination. The running processes will either get
# the old file or the new one.
mv -f "$__tmp" "$_file"
fi
esac || return 1; done
2020-04-22 06:18:12 +00:00
}
2020-05-21 07:55:29 +00:00
pkg_remove_files() {
# Remove a file list from the system. This function runs during package
# installation and package removal. Combining the removals in these two
# functions allows us to stop duplicating code.
while read -r file; do
case $file in /etc/?*[!/])
2021-07-05 12:10:48 +00:00
sh256 "$KISS_ROOT/$file" >/dev/null
2021-07-19 14:54:57 +00:00
read -r sum_pkg <&3 ||:
2021-07-19 14:35:39 +00:00
equ "$hash" "$sum_pkg" || {
printf 'Skipping %s (modified)\n' "$file"
continue
}
2021-07-19 14:54:57 +00:00
esac
_file=${KISS_ROOT:+"$KISS_ROOT/"}${file%%/}
2020-05-21 07:55:29 +00:00
# Queue all directory symlinks for later removal.
if [ -h "$_file" ] && [ -d "$_file" ]; then
case $file in /*/*/)
set -- "$@" "$_file"
esac
2020-05-21 07:55:29 +00:00
# Remove empty directories.
elif [ -d "$_file" ]; then
2021-07-03 22:20:19 +00:00
rmdir "$_file" 2>/dev/null || :
2020-05-21 07:55:29 +00:00
# Remove everything else.
else
rm -f "$_file"
2020-05-21 07:55:29 +00:00
fi
done
# Remove all broken directory symlinks.
for sym do
[ -e "$sym" ] || rm -f "$sym"
done
2020-05-21 07:55:29 +00:00
}
2021-07-17 14:43:58 +00:00
pkg_etc() {
sh256 "$tar_dir/$_pkg$file" "$KISS_ROOT$file" >/dev/null
2020-03-25 10:21:10 +00:00
2021-07-18 20:15:58 +00:00
sum_new=${hash%%"$newline"*}
sum_sys=${hash#*"$newline"}
2021-07-19 14:35:39 +00:00
read -r sum_old <&3 2>/dev/null ||:
2020-03-25 10:21:10 +00:00
2021-07-17 14:43:58 +00:00
# Compare the three checksums to determine what to do.
case ${sum_old:-null}${sum_sys:-null}${sum_new} in
# old = Y, sys = X, new = Y
"${sum_new}${sum_sys}${sum_old}")
return 1
2021-07-17 14:43:58 +00:00
;;
2021-07-17 14:43:58 +00:00
# old = X, sys = X, new = X
# old = X, sys = Y, new = Y
# old = X, sys = X, new = Y
"${sum_old}${sum_old}${sum_old}"|\
"${sum_old:-null}${sum_sys}${sum_sys}"|\
"${sum_sys}${sum_old}"*)
2021-07-17 14:43:58 +00:00
;;
2021-07-17 14:43:58 +00:00
# All other cases.
*)
war "$_pkg" "saving $file as $file.new"
_file=$_file.new
2021-07-17 14:43:58 +00:00
;;
esac
}
2020-03-25 10:21:10 +00:00
pkg_removable() {
# Check if a package is removable and die if it is not.
# A package is removable when it has no dependents.
log "$1" "Checking if package removable"
2021-07-03 15:23:51 +00:00
cd "$sys_db"
set +f
! grep -lFx -- "$1" */depends ||
die "$1" "Not removable, has dependents"
set -f
cd "$OLDPWD"
}
2019-06-13 14:48:08 +00:00
pkg_remove() {
2020-05-21 07:38:07 +00:00
# Remove a package and all of its files. The '/etc' directory is handled
# differently and configuration files are *not* overwritten.
2021-07-17 15:18:25 +00:00
[ -d "$sys_db/$1" ] || die "'$1' not installed"
# Intended behavior.
# shellcheck disable=2030,2031
equ "$KISS_FORCE" 1 || pkg_removable "$1"
2019-07-26 16:21:44 +00:00
# Block being able to abort the script with 'Ctrl+C' during removal.
2020-05-21 07:38:07 +00:00
# Removes all risk of the user aborting a package removal leaving an
# incomplete package installed.
2019-07-04 15:32:53 +00:00
trap '' INT
2021-07-15 04:20:31 +00:00
# arg1: pre-remove
# arg2: package name
# arg3: path to installed database
run_hook_pkg pre-remove "$1"
run_hook pre-remove "$1" "$sys_db/$1"
# Make a backup of any etcsums if they exist.
2021-07-16 09:24:06 +00:00
tmp_file_copy "$1" etcsums-copy "$sys_db/$1/etcsums"
2020-07-29 16:03:02 +00:00
log "$1" "Removing package"
2021-07-19 14:35:39 +00:00
pkg_remove_files < "$sys_db/$1/manifest" 3< "$_tmp_file"
2019-07-26 16:21:44 +00:00
# Reset 'trap' to its original value. Removal is done so
2019-07-04 15:32:53 +00:00
# we no longer need to block 'Ctrl+C'.
trap pkg_clean EXIT INT
log "$1" "Removed successfully"
2019-06-13 14:48:08 +00:00
}
pkg_installable() {
# Check if a package is removable and die if it is not.
# A package is removable when all of its dependencies
# are satisfied.
log "$1" "Checking if package installable"
2021-07-03 23:42:23 +00:00
# False positive.
# shellcheck disable=2094
! [ -f "$2" ] ||
while read -r dep dep_type || ok "$dep"; do
2021-07-19 07:25:15 +00:00
case "$dep $dep_type" in [!\#]?*\ )
2021-07-17 15:27:46 +00:00
! [ -d "$sys_db/$dep" ] || continue
printf '%s %s\n' "$dep" "$dep_type"
2021-07-17 15:40:34 +00:00
2021-07-17 15:27:46 +00:00
set -- "$1" "$2" "$(($3 + 1))"
esac
done < "$2"
case ${3:-0} in [1-9]*)
die "$1" "Package not installable, missing $3 package(s)"
esac
}
2019-06-29 20:38:35 +00:00
pkg_install() {
2020-04-24 08:04:05 +00:00
# Install a built package tarball.
#
# Package installation works similarly to the method used by Slackware in
# some of their tooling. It's not the obvious solution to the problem,
# however it is the best solution at this given time.
#
# When an installation is an update to an existing package, instead of
# removing the old version first we do something different.
#
# The new version is installed overwriting any files which it has in
# common with the previously installed version of the package.
#
# A "diff" is then generated between the old and new versions and contains
# any files existing in the old version but not the new version.
#
# The package manager then goes and removes these files which leaves us
# with the new package version in the file system and all traces of the
# old version gone.
#
# For good measure the package manager will then install the new package
# an additional time. This is to ensure that the above diff didn't contain
# anything incorrect.
#
# This is the better method as it is "seamless". An update to busybox won't
2021-07-07 08:45:21 +00:00
# create a window in which there is no access to all of its utilities.
2019-06-14 05:58:09 +00:00
# Install can also take the full path to a tarball. We don't need to check
# the repository if this is the case.
2021-07-06 19:07:12 +00:00
case $1 in
*.tar.*)
[ -f "$1" ] || die "File '$1' does not exist"
2020-09-27 08:57:52 +00:00
2021-07-06 19:07:12 +00:00
tar_file=$1
2021-07-17 11:55:59 +00:00
_pkg=${1##*/}
_pkg=${_pkg%@*}
2021-07-06 19:07:12 +00:00
;;
2019-06-29 20:38:35 +00:00
2021-07-06 19:07:12 +00:00
*)
pkg_cache "$1" || die "$1" "Not yet built"
2021-07-17 11:55:59 +00:00
_pkg=$1
2021-07-06 19:07:12 +00:00
;;
esac
2019-06-29 20:38:35 +00:00
2021-07-17 11:55:59 +00:00
mkcd "$tar_dir/$_pkg"
2019-06-29 20:38:35 +00:00
# The tarball is extracted to a temporary directory where its contents are
# then "installed" to the filesystem. Running this step as soon as possible
# allows us to also check the validity of the tarball and bail out early
# if needed.
2020-06-10 08:23:34 +00:00
decompress "$tar_file" | tar xf -
2019-07-04 14:43:10 +00:00
# Naively assume that the existence of a manifest file is all that
# determines a valid KISS package from an invalid one. This should be a
# fine assumption to make in 99.99% of cases.
2021-07-17 11:55:59 +00:00
[ -f "$PWD/$pkg_db/$_pkg/manifest" ] || die "Not a valid KISS package"
2021-07-19 05:08:49 +00:00
# Intended behavior.
# shellcheck disable=2030,2031
equ "$KISS_FORCE" 1 || {
2021-07-21 05:07:51 +00:00
pkg_manifest_validate
2021-07-17 11:55:59 +00:00
pkg_installable "$_pkg" "$PWD/$pkg_db/$_pkg/depends"
2020-03-26 13:00:29 +00:00
}
2019-07-05 06:34:06 +00:00
2021-07-15 04:20:31 +00:00
# arg1: pre-install
# arg2: package name
# arg3: path to extracted package
2021-07-17 11:55:59 +00:00
run_hook pre-install "$_pkg" "$PWD"
pkg_conflicts
2020-09-27 08:57:52 +00:00
2021-07-17 11:55:59 +00:00
log "$_pkg" "Installing package (${tar_file##*/})"
2020-01-28 12:07:08 +00:00
# If the package is already installed (and this is an upgrade) make a
# backup of the manifest and etcsums files.
2021-07-17 11:55:59 +00:00
tmp_file_copy "$_pkg" manifest-copy "$sys_db/$_pkg/manifest"
tmp_file_copy "$_pkg" etcsums-copy "$sys_db/$_pkg/etcsums"
tmp_file "$_pkg" manifest-diff
2021-07-17 11:55:59 +00:00
tar_man=$PWD/$pkg_db/$_pkg/manifest
# Generate a list of files which exist in the currently installed manifest
# but not in the newer (to be installed) manifest.
2021-07-16 07:07:04 +00:00
grep -vFxf "$tar_man" "$_tmp_file_pre_pre" > "$_tmp_file" 2>/dev/null ||:
# Reverse the manifest file so that we start shallow and go deeper as we
# iterate over each item. This is needed so that directories are created
# going down the tree.
2021-07-17 11:55:59 +00:00
tmp_file "$_pkg" manifest-reverse
sort "$tar_man" > "$_tmp_file"
# Block being able to abort the script with Ctrl+C during installation.
# Removes all risk of the user aborting a package installation leaving
# an incomplete package installed.
trap '' INT
2021-07-14 08:32:57 +00:00
if
# Install the package's files by iterating over its manifest.
pkg_install_files -z "$PWD" < "$_tmp_file" 3< "$_tmp_file_pre_pre" &&
2019-06-29 20:38:35 +00:00
2021-07-14 08:32:57 +00:00
# This is the aforementioned step removing any files from the old
# version of the package if the installation is an update. Each file
# type has to be specially handled to ensure no system breakage occurs.
2021-07-19 14:35:39 +00:00
pkg_remove_files < "$_tmp_file_pre" 3< "$_tmp_file_pre_pre" &&
2021-07-14 08:32:57 +00:00
# Install the package's files a second time to fix any mess caused by
# the above removal of the previous version of the package.
pkg_install_files -e "$PWD" < "$_tmp_file" 3< "$_tmp_file_pre_pre"
then
# Reset 'trap' to its original value. Installation is done so we no longer
# need to block 'Ctrl+C'.
trap pkg_clean EXIT INT
2021-07-15 04:20:31 +00:00
# arg1: post-install
# arg2: package name
# arg3: path to installed package database
2021-07-17 11:55:59 +00:00
run_hook_pkg post-install "$_pkg"
run_hook post-install "$_pkg" "$sys_db/$_pkg"
2021-07-17 11:55:59 +00:00
log "$_pkg" "Installed successfully"
else
pkg_clean
2021-07-17 11:55:59 +00:00
log "$_pkg" "Failed to install package." ERROR
die "$_pkg" "Filesystem now dirty, manual repair needed."
fi
2019-06-13 14:48:08 +00:00
}
2021-07-12 09:44:27 +00:00
pkg_update() {
2019-06-29 20:38:35 +00:00
# Check all installed packages for updates. So long as the installed
# version and the version in the repositories differ, it's considered
# an update.
log "Updating repositories"
# Create a list of all repositories.
2021-07-04 13:02:45 +00:00
# Intentional, globbing disabled.
# shellcheck disable=2046,2086
{ IFS=:; set -- $KISS_PATH; unset IFS; }
2020-05-21 07:38:07 +00:00
# Update each repository in '$KISS_PATH'.
2020-03-21 11:48:05 +00:00
for repo do
ok "$repo" || continue
2021-07-02 12:19:23 +00:00
[ -d "$repo" ] || {
log "$repo" " "
printf 'Skipping repository, not a directory\n'
continue
}
2021-07-02 12:32:30 +00:00
2020-02-06 12:06:51 +00:00
cd "$repo"
2019-08-14 09:58:14 +00:00
2021-07-02 12:32:30 +00:00
git remote >/dev/null 2>&1 || {
log "$repo" " "
printf 'Skipping git pull, not a repository\n'
continue
}
2019-08-14 09:58:14 +00:00
2021-07-02 12:32:30 +00:00
# Go to the repository's root directory.
git_root=$(git rev-parse --show-toplevel)
cd "${git_root:?"failed to find git root for '$PWD'"}"
2021-07-02 12:32:30 +00:00
# Go to the real root directory if this is a submodule.
git_root=$(git rev-parse --show-superproject-working-tree)
cd "${git_root:-"$PWD"}"
2021-07-02 12:32:30 +00:00
contains "$repos" "$PWD" || {
repos="$repos $PWD "
2021-07-15 04:20:31 +00:00
# arg1: pre-update
# env: PWD is path to repository
run_hook pre-update
2021-07-17 15:40:34 +00:00
# Display whether or not signature verification is enabled.
_sig=$(git config --get --default false merge.verifySignatures)
log "$PWD" "[verify: $_sig]"
if ! am_owner "$PWD"; then
log "$PWD" "Need '$user' to update"
as_user git pull
as_user git submodule update --remote --init -f
else
2021-07-02 12:32:30 +00:00
git pull
git submodule update --remote --init -f
fi
2021-07-15 04:23:06 +00:00
# arg1: post-update
# env: PWD is path to repository
run_hook post-update
2021-07-02 12:32:30 +00:00
}
done
log "Checking for new package versions"
2020-05-16 05:10:37 +00:00
set +f --
for pkg in "$sys_db/"*; do
2021-07-17 10:58:36 +00:00
pkg_find_version "${pkg##*/}" "" "" "$sys_db"
2021-07-03 14:48:39 +00:00
pkg_find_version "${pkg##*/}"
2019-06-29 20:38:35 +00:00
# Detect repository orphans (installed packages with no
# associated repository).
case $repo_dir in */var/db/kiss/installed/*)
2021-07-16 11:11:03 +00:00
_repo_orp="$_repo_orp$newline${pkg##*/}"
esac
# Compare installed packages to repository packages.
equ "$ver_pre-$rel_pre" "$repo_ver-$repo_rel" || {
2021-07-17 10:58:36 +00:00
printf '%s\n' "${pkg##*/} $ver_pre-$rel_pre ==> $repo_ver-$repo_rel"
set -- "$@" "${pkg##*/}"
2019-07-11 06:14:17 +00:00
}
2019-06-13 14:48:08 +00:00
done
2019-07-11 06:14:17 +00:00
log "Checking for orphaned repository packages"
case $_repo_orp in *?*)
war "Packages without repository$_repo_orp"
esac
2020-02-06 11:55:01 +00:00
set -f
pkg_update=1
2020-02-06 11:55:01 +00:00
case " $* " in
*" kiss "*)
log "Detected package manager update"
log "The package manager will be updated first"
2021-07-17 11:42:27 +00:00
prompt
pkg_build_all kiss
log "Updated the package manager"
log "Re-run 'kiss update' to update your system"
;;
" ")
log "Everything is up to date"
;;
*)
log "Packages to update: $*"
2021-07-17 11:42:27 +00:00
prompt
pkg_order "$@"
# Intentional, globbing disabled.
# shellcheck disable=2046,2086
pkg_build_all $order
log "Updated all packages"
;;
esac
2019-06-13 14:48:08 +00:00
}
2019-06-29 20:38:35 +00:00
pkg_clean() {
2020-05-21 07:38:07 +00:00
# Clean up on exit or error. This removes everything related to the build.
2021-07-17 11:15:09 +00:00
# If _KISS_LVL is (1) we are the top-level process - the entire cache will
# be removed. If _KISS_LVL is any other value, remove only the tar directory.
case ${KISS_DEBUG:-0}-${_KISS_LVL:-1} in
2021-07-17 11:15:09 +00:00
0-1) rm -rf "$proc" ;;
0-*) rm -rf "$tar_dir"
esac
2019-06-29 20:38:35 +00:00
}
2019-06-15 06:19:20 +00:00
2019-06-29 20:38:35 +00:00
args() {
2020-05-21 07:38:07 +00:00
# Parse script arguments manually. This is rather easy to do in our case
# since the first argument is always an "action" and the arguments that
# follow are all package names.
2019-08-19 18:45:19 +00:00
action=$1
2020-05-24 18:05:13 +00:00
shift "$(($# != 0))"
2019-08-19 18:45:19 +00:00
2021-07-03 15:46:09 +00:00
# Ensure that arguments do not contain invalid characters. Wildcards can
# not be used here as they would conflict with kiss extensions.
case $action in
a|alternatives)
2021-07-19 08:49:38 +00:00
case $1 in *\**|*\!*|*\[*|*\ *|*\]*|*/*|*"$newline"*)
die "Invalid argument: '!*[ ]/\\n' ($1)"
2021-07-03 15:46:09 +00:00
esac
;;
b|build|c|checksum|d|download|i|install|l|list|r|remove)
for _arg do case ${action%%"${action#?}"}-$_arg in
2021-07-19 08:49:38 +00:00
i-*\!*|i-*\**|i-*\[*|i-*\ *|i-*\]*|i-*"$newline"*)
die "Invalid argument: '!*[ ]\\n' ('$_arg')"
2021-07-06 17:41:25 +00:00
;;
2021-07-19 08:49:38 +00:00
[!i]-*\!*|[!i]-*\**|[!i]-*\[*|[!i]-*\ *|\
[!i]-*\]*|[!i]-*/*|[!i]-*"$newline"*)
die "Invalid argument: '!*[ ]/\\n' ('$_arg')"
2021-07-06 17:41:25 +00:00
;;
esac done
2021-07-19 08:49:38 +00:00
# When no arguments are given on the command-line, use the basename
# of the current directory as the package name and add the parent
# directory to the running process' KISS_PATH.
2021-07-16 12:38:24 +00:00
case ${action%%"${action#?}"}-$# in [!l]-0)
export KISS_PATH=${PWD%/*}:$KISS_PATH
set -- "${PWD##*/}"
esac
# Order the argument list based on dependence.
pkg_order "$@"
# Intentional, globbing disabled.
# shellcheck disable=2046,2086
set -- $order
2021-07-03 15:46:09 +00:00
;;
2020-05-18 08:44:24 +00:00
esac
# Need to increment _KISS_LVL here to ensure we don't wipe the cache
2021-07-16 09:27:48 +00:00
# early by non-asroot invocations.
export _KISS_LVL=$((_KISS_LVL + 1))
2021-07-16 09:27:48 +00:00
# Rerun the script as root with a fixed environment if needed. We sadly
# can't run singular functions as root so this is needed.
2021-07-19 05:08:49 +00:00
#
# Intended behavior.
# shellcheck disable=2030,2031
case $action in a|alternatives|i|install|r|remove)
if ok "$1" && ! am_owner "$KISS_ROOT/"; then
as_user env \
2021-07-19 18:22:49 +00:00
LOGNAME="$user" \
HOME="$HOME" \
XDG_CACHE_HOME="$XDG_CACHE_HOME" \
KISS_COMPRESS="$KISS_COMPRESS" \
KISS_PATH="$KISS_PATH" \
KISS_FORCE="$KISS_FORCE" \
KISS_ROOT="$KISS_ROOT" \
KISS_CHOICE="$KISS_CHOICE" \
KISS_COLOR="$KISS_COLOR" \
KISS_TMPDIR="$KISS_TMPDIR" \
2021-07-15 19:32:12 +00:00
KISS_PID="$KISS_PID" \
_KISS_LVL="$_KISS_LVL" \
"$0" "$action" "$@"
return
fi
2019-08-19 18:45:19 +00:00
esac
2019-06-29 20:38:35 +00:00
2020-05-21 07:38:07 +00:00
# Actions can be abbreviated to their first letter. This saves keystrokes
# once you memorize the commands.
2019-08-19 18:45:19 +00:00
case $action in
2020-01-28 12:16:01 +00:00
a|alternatives)
if equ "$1" -; then
while read -r pkg path; do
pkg_swap "$pkg" "$path"
done
elif ok "$1"; then
pkg_swap "$@"
else
# Go over each alternative and format the file
# name for listing. (pkg_name>usr>bin>ls)
set +f; for pkg in "$sys_ch/"*; do
fnr "${pkg##*/}" '>' '/'
printf '%s %s\n' "${_fnr%%/*}" "/${_fnr#*/}"
done
fi
2020-09-14 19:26:58 +00:00
;;
2020-09-14 19:26:58 +00:00
c|checksum)
for pkg do
2021-07-17 11:31:06 +00:00
pkg_source "$pkg" c
2020-04-28 03:26:36 +00:00
[ -f "$repo_dir/sources" ] || continue
2019-06-29 20:38:35 +00:00
pkg_checksums
2019-06-29 20:38:35 +00:00
2021-07-19 15:14:12 +00:00
if ok "$hash"; then
printf '%s\n' "$hash" > "$repo_dir/checksums"
log "$pkg" "Generated checksums"
else
log "$pkg" "No sources needing checksums"
fi
2020-09-12 14:40:05 +00:00
done
2019-06-29 20:38:35 +00:00
;;
2021-07-06 18:35:37 +00:00
i|install) for pkg do pkg_install "$pkg"; done ;;
b|build) pkg_build_all "${@:?No packages installed}" ;;
d|download) for pkg do pkg_source "$pkg"; done ;;
2021-07-17 10:58:36 +00:00
l|list) pkg_list_version "$@" ;;
r|remove) for pkg in $redro; do pkg_remove "$pkg"; done ;;
2021-07-17 10:58:36 +00:00
s|search) for pkg do pkg_find "$pkg" all; done ;;
2021-07-12 09:44:27 +00:00
u|update) pkg_update ;;
2021-07-22 12:57:46 +00:00
v|version) printf '5.5.18\n' ;;
2019-06-29 20:38:35 +00:00
2020-09-08 14:07:55 +00:00
'')
log 'kiss [a|b|c|d|i|l|r|s|u|v] [pkg]...'
log 'alternatives List and swap alternatives'
log 'build Build packages'
log 'checksum Generate checksums'
log 'download Download sources'
log 'install Install packages'
log 'list List installed packages'
log 'remove Remove packages'
log 'search Search for packages'
2020-05-16 16:00:44 +00:00
log 'update Update the system'
2020-09-08 14:07:55 +00:00
log 'version Package manager version'
printf '\nRun "kiss help-ext" to see all actions\n'
;;
help-ext)
log 'Installed extensions (kiss-* in PATH)'
2021-07-04 13:02:45 +00:00
# Intentional, globbing disabled.
# shellcheck disable=2046,2030,2031
2021-07-03 10:15:06 +00:00
set -- $(pkg_find kiss-\* all -x "$PATH")
# To align descriptions figure out which extension has the longest
# name by doing a simple 'name > max ? name : max' on the basename
# of the path with 'kiss-' stripped as well.
#
# This also removes any duplicates found in '$PATH', picking the
# first match.
for path do p=${path#*/kiss-}
case " $seen " in
*" $p "*) shift ;;
*) seen=" $seen $p " max=$((${#p} > max ? ${#p}+1 : max))
esac
2020-09-23 18:48:08 +00:00
done
2021-07-19 15:32:30 +00:00
IFS=\#$IFS
# Print each extension, grab its description from the second line
# in the file and align the output based on the above max.
for path do
2021-07-19 15:32:30 +00:00
# Open the extension as a file descriptor.
exec 3< "$path"
# Grab the second line in the extension.
{ read -r _ && read -r _ cmt; } <&3
printf "%b->%b %-${max}s %s\\n" \
"$c1" "$c3" "${path#*/kiss-}" "$cmt"
done >&2
2019-06-29 20:38:35 +00:00
;;
2019-07-24 22:33:12 +00:00
*)
2021-07-17 10:58:36 +00:00
pkg_find "kiss-$action*" "" -x "$PATH"
2021-07-03 10:15:06 +00:00
"$repo_dir" "$@"
;;
2019-06-13 14:48:08 +00:00
esac
}
create_tmp_dirs() {
# Root directory.
KISS_ROOT=${KISS_ROOT%"${KISS_ROOT##*[!/]}"}
# This allows for automatic setup of a KISS chroot and will
# do nothing on a normal system.
2021-07-03 22:20:19 +00:00
mkdir -p "$KISS_ROOT/" 2>/dev/null || :
# System package database.
sys_db=$KISS_ROOT/${pkg_db:=var/db/kiss/installed}
sys_ch=$KISS_ROOT/${cho_db:=var/db/kiss/choices}
# Top-level cache directory.
cac_dir=${XDG_CACHE_HOME:-"${HOME%"${HOME##*[!/]}"}/.cache"}
cac_dir=${cac_dir%"${cac_dir##*[!/]}"}/kiss
# Persistent cache directories.
src_dir=$cac_dir/sources
log_dir=$cac_dir/logs/${time%-*}
bin_dir=$cac_dir/bin
# Top-level Temporary cache directory.
proc=${KISS_TMPDIR:="$cac_dir/proc"}
2021-07-16 17:12:52 +00:00
proc=${proc%"${proc##*[!/]}"}/$KISS_PID
# Temporary cache directories.
mak_dir=$proc/build
pkg_dir=$proc/pkg
tar_dir=$proc/extract
tmp_dir=$proc/tmp
mkdir -p "$src_dir" "$log_dir" "$bin_dir" \
"$mak_dir" "$pkg_dir" "$tar_dir" "$tmp_dir"
}
2019-06-13 14:48:08 +00:00
main() {
2020-05-09 18:25:38 +00:00
# Globally disable globbing and enable exit-on-error.
set -ef
2021-07-05 08:42:52 +00:00
# Color can be disabled via the environment variable KISS_COLOR. Colors are
# also automatically disabled if output is being used in a pipe/redirection.
equ "$KISS_COLOR" 0 || ! [ -t 2 ] ||
2021-07-05 08:42:52 +00:00
c1='\033[1;33m' c2='\033[1;34m' c3='\033[m'
# Store the original working directory to ensure that relative paths
# passed by the user on the command-line properly resolve to locations
# in the filesystem.
ppwd=$PWD
2021-07-16 11:11:03 +00:00
# Never know when you're gonna need one of these.
newline="
"
2021-07-16 17:12:52 +00:00
# Defaults for environment variables.
2021-07-08 08:15:31 +00:00
: "${KISS_COMPRESS:=gz}"
2021-07-16 17:12:52 +00:00
: "${KISS_PID:=$$}"
2021-07-19 16:12:55 +00:00
: "${LOGNAME:?POSIX requires LOGNAME be set}"
2021-07-08 08:15:31 +00:00
2020-05-21 07:38:07 +00:00
# Figure out which 'sudo' command to use based on the user's choice or what
# is available on the system.
cmd_su=${KISS_SU:-"$(
command -v ssu ||
command -v sudo ||
command -v doas ||
command -v su
)"} || cmd_su=su
2020-09-25 19:38:05 +00:00
2020-11-06 06:45:56 +00:00
# Figure out which utility is available to dump elf information.
cmd_elf=${KISS_ELF:-"$(
2020-11-06 06:45:56 +00:00
command -v readelf ||
command -v eu-readelf ||
command -v llvm-readelf
2021-07-03 10:23:04 +00:00
)"} || cmd_elf=ldd
2020-11-06 06:45:56 +00:00
# Figure out which sha256 utility is available.
cmd_sha=${KISS_CHK:-"$(
command -v openssl ||
command -v sha256sum ||
command -v sha256 ||
command -v shasum ||
command -v digest
)"} || die "No sha256 utility found"
# Figure out which download utility is available.
cmd_get=${KISS_GET:-"$(
2021-07-20 16:38:13 +00:00
command -v aria2c ||
command -v axel ||
command -v curl ||
command -v wget ||
2021-07-20 16:39:06 +00:00
command -v wget2
2021-07-20 16:38:13 +00:00
)"} || die "No download utility found (aria2c, axel, curl, wget, wget2)"
2020-05-21 07:38:07 +00:00
# Store the date and time of script invocation to be used as the name of
# the log files the package manager creates uring builds.
time=$(date +%Y-%m-%d-%H:%M)
2020-01-28 08:08:15 +00:00
create_tmp_dirs
2021-07-17 08:39:55 +00:00
# Catch errors and ensure that build files and directories are cleaned
# up before we die. This occurs on 'Ctrl+C' as well as success and error.
trap pkg_clean EXIT INT
2019-06-13 14:48:08 +00:00
args "$@"
}
main "$@"