#!/bin/sh # ipkg - the itsy package management system # # Copyright (C) 2001 Carl D. Worth # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2, or (at your option) # any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. set -e # By default do not do globbing. Any command wanting globbing should # enable it first and disable it afterwards. set -o noglob if [ -z "$IPKG_CONF_DIR" ]; then IPKG_CONF_DIR=/etc fi . $IPKG_CONF_DIR/ipkg.conf IPKG_STATE_DIR=$IPKG_ROOT/usr/lib/ipkg IPKG_INFO_DIR=$IPKG_STATE_DIR/info IPKG_PENDING_DIR=$IPKG_STATE_DIR/pending IPKG_TMP=$IPKG_ROOT/tmp/ipkg IPKG_STATUS_FIELDS='\(Package\|Status\|Version\|Conffiles\)' # Proxy Support if [ -n "$IPKG_PROXY_HTTP" ]; then export http_proxy="$IPKG_PROXY_HTTP" fi if [ -n "$IPKG_PROXY_FTP" ]; then export ftp_proxy="$IPKG_PROXY_FTP" fi ipkg_usage() { cat< Download and install (and dependencies) install Install package remove Remove package Informational Commands: list List available packages and descriptions files List all files belonging to search Search for a packaging providing info [pkg []] Display all/some info fields for or all status [pkg []] Display all/some status fields for or all depends Print uninstalled package dependencies for EOT exit 1 } ipkg_dir_part() { local dir=`echo $1 | sed -ne 's/\(.*\/\).*/\1/p'` if [ -z "$dir" ]; then dir="./" fi echo $dir } ipkg_file_part() { echo $1 | sed 's/.*\///' } ipkg_protect_slashes() { sed -e 's/\//\\\//g' } ipkg_download() { local src=$1 local dest=$2 local src_file=`ipkg_file_part $src` local dest_dir=`ipkg_dir_part $dest` if [ -z "$dest_dir" ]; then dest_dir="." fi local dest_file=`ipkg_file_part $dest` if [ -z "$dest_file" ]; then dest_file=$src_file fi # Proxy support local proxyuser="" local proxypassword="" local proxyoption="" if [ -n "$IPKG_PROXY_USERNAME" ]; then proxyuser="--proxy-user=\"$IPKG_PROXY_USERNAME\"" proxypassword="--proxy-passwd=\"$IPKG_PROXY_PASSWORD\"" fi if [ -n "$IPKG_PROXY_HTTP" -o -n "$IPKG_PROXY_FTP" ]; then proxyoption="--proxy=on" fi echo "Downloading $src ..." rm -f $dest_dir/$dest_file if ! wget --passive-ftp -nd $proxyoption $proxyuser $proxypassword -P $dest_dir $src; then echo "ipkg_download: ERROR: Failed to retrieve $src, returning $err" return 1 fi if [ "$src_file" != "$dest_file" ]; then mv $dest_dir/$src_file $dest_dir/$dest_file fi echo "Done." return 0 } ipkg_update() { if [ ! -e "$IPKG_STATE_DIR" ]; then mkdir -p $IPKG_STATE_DIR fi if ! ipkg_download $IPKG_SOURCE/Packages $IPKG_STATE_DIR/available; then return 1 fi echo "Updated list of available packages in $IPKG_STATE_DIR/available" return 0 } ipkg_list() { ipkg_require_available || return 1 # black magic... sed -ne " /^Package:/{ s/^Package:[[:space:]]*\<\([a-z0-9.+-]*$1[a-z0-9.+-]*\).*/\1/ h } /^Description:/{ s/^Description:[[:space:]]*\(.*\)/\1/ H g s/\\ / - / p } " $IPKG_STATE_DIR/available } ipkg_extract_paragraph() { local pkg=$1 sed -ne "/Package:[[:space:]]*$pkg[[:space:]]*\$/,/^\$/p" } ipkg_extract_field() { local field=$1 # blacker magic... sed -ne " : TOP /^$field:/{ p n b FIELD } d : FIELD /^$/b TOP /^[^[:space:]]/b TOP p n b FIELD " } ipkg_extract_value() { sed -e "s/^[^:]*:[[:space:]]*//" } ipkg_require_available() { if [ ! -f "$IPKG_STATE_DIR/available" ]; then echo "ERROR: File not found: $IPKG_STATE_DIR/available" >&2 echo " You probably want to run \`ipkg update'" >&2 return 1 fi return 0 } ipkg_require_status() { [ -f "$IPKG_STATE_DIR/status" ] || touch $IPKG_STATE_DIR/status } ipkg_info() { ipkg_require_available || return 1 case $# in 0) cat $IPKG_STATE_DIR/available ;; 1) ipkg_extract_paragraph $1 < $IPKG_STATE_DIR/available ;; *) ipkg_extract_paragraph $1 < $IPKG_STATE_DIR/available | ipkg_extract_field $2 ;; esac } ipkg_status() { ipkg_require_status case $# in 0) cat $IPKG_STATE_DIR/status ;; 1) ipkg_extract_paragraph $1 < $IPKG_STATE_DIR/status ;; *) ipkg_extract_paragraph $1 < $IPKG_STATE_DIR/status | ipkg_extract_field $2 ;; esac } ipkg_status_matching() { ipkg_require_status sed -ne " : TOP /^Package:/{ s/^Package:[[:space:]]*// s/[[:space:]]*$// h } /$1/{ g p b NEXT } d : NEXT /^$/b TOP n b NEXT " < $IPKG_STATE_DIR/status } ipkg_status_installed() { pkg=$1 ipkg_status $pkg Status | grep -q "Status: install ok installed" } ipkg_status_mentioned() { pkg=$1 [ -n "`ipkg_status $pkg Status`" ] } ipkg_files() { pkg=$1 cat $IPKG_INFO_DIR/$pkg.list } ipkg_search() { file=$1 set +o noglob grep -H $file $IPKG_INFO_DIR/*.list | sed 's/^.*\/\(.*\)\.list:/\1: /' set -o noglob } ipkg_status_remove() { [ $# -lt 1 ] && return 1 local pkg=$1 ipkg_require_status sed -ne "/Package:[[:space:]]*$pkg[[:space:]]*\$/,/^\$/!p" < $IPKG_STATE_DIR/status > $IPKG_STATE_DIR/status.new mv $IPKG_STATE_DIR/status.new $IPKG_STATE_DIR/status } ipkg_status_update() { [ $# -lt 1 ] && return 1 local pkg=$1 ipkg_status_remove $pkg ipkg_extract_field "$IPKG_STATUS_FIELDS" >> $IPKG_STATE_DIR/status echo "" >> $IPKG_STATE_DIR/status } ipkg_depends() { ipkg_require_available || return 1 new_pkgs="$*" all_deps= while [ -n "$new_pkgs" ]; do all_deps="$all_deps $new_pkgs" new_deps= for pkg in $new_pkgs; do if echo $pkg | grep -q '[^a-z0-9.+-]'; then echo "ipkg_depends: ERROR: Package name $pkg contains illegal characters (should be [a-z0-9.+-])" >&2 return 1 fi # TODO: Fix this. For now I am ignoring versions and alternations in dependencies. new_deps="$new_deps "`ipkg_info $pkg '\(Pre-\)\?Depends' | ipkg_extract_value | sed -e 's/([^)]*)//g s/\(|[[:space:]]*[a-z0-9.+-]\+[[:space:]]*\)\+//g s/,/ /g s/ \+/ /g'` done new_deps=`echo $new_deps | sed -e 's/[[:space:]]\+/\\ /g' | sort | uniq` maybe_new_pkgs= for pkg in $new_deps; do if ! ipkg_status_installed $pkg; then maybe_new_pkgs="$maybe_new_pkgs $pkg" fi done new_pkgs= for pkg in $maybe_new_pkgs; do if ! echo $all_deps | grep -q "\<$pkg\>"; then if ! grep -q "^Package:[[:space:]]*$pkg[[:space:]]*\$" $IPKG_STATE_DIR/available; then echo "ipkg_depends: Warning: $pkg mentioned in dependency but no package found in $IPKG_STATE_DIR/available" >&2 else new_pkgs="$new_pkgs $pkg" fi fi done done echo $all_deps } ipkg_get_install() { pkgs=`ipkg_depends $*` mkdir -p $IPKG_INFO_DIR for pkg in $pkgs; do if ! ipkg_status_mentioned $pkg; then echo "Package: $pkg Status: install ok not-installed" | ipkg_status_update $pkg fi done for pkg in $pkgs; do filename=`ipkg_info $pkg Filename | ipkg_extract_value` if [ -z "$filename" ]; then echo "ipkg_get_install: ERROR: Cannot find package $pkg in $IPKG_STATE_DIR/available." echo "ipkg_get_install: Check the spelling and maybe run \`ipkg update'." ipkg_status_remove $pkg return 1; fi [ -e "$IPKG_TMP" ] || mkdir -p $IPKG_TMP echo "" tmp_pkg_file="$IPKG_TMP/"`ipkg_file_part $filename` if ! ipkg_download $IPKG_SOURCE/$filename $tmp_pkg_file; then echo "ipkg_get_install: Perhaps you need to run \`ipkg update'?" return 1 fi if ! ipkg_install_file $tmp_pkg_file; then echo "ipkg_get_install: ERROR: Failed to install $tmp_pkg_file" echo "ipkg_get_install: I'll leave it there for you to try a manual installation" return 1 fi rm $tmp_pkg_file done } ipkg_install_file() { filename=$1 if [ ! -f "$filename" ]; then echo "ipkg_install_file: ERROR: File $filename not found" return 1 fi pkg=`ipkg_file_part $filename | sed 's/\([a-z0-9.+-]\+\)_.*/\1/'` # Check dependencies depends=`ipkg_depends $pkg | sed -e "s/\<$pkg\>//"` # Don't worry about deps that are scheduled for installation missing_deps= for dep in $depends; do if ! ipkg_status $dep | grep -q 'Status:[[:space:]]install'; then missing_deps="$missing_deps $dep" fi done # TODO: We need to allow a force here if [ ! -z "$missing_deps" ]; then echo "ipkg_install_file: ERROR: $pkg depends on the following uninstalled programs: $missing_deps" echo "ipkg_install_file: You may want to use \`ipkg install' to install these." return 1 fi mkdir -p $IPKG_TMP/$pkg/control mkdir -p $IPKG_TMP/$pkg/data mkdir -p $IPKG_INFO_DIR if ! tar -xzOf $filename ./control.tar.gz | tar -xzf - -C $IPKG_TMP/$pkg/control; then echo "ipkg_install_file: ERROR unpacking control.tar.gz from $filename" return 1 fi if [ "$IPKG_ROOT" != "/" ]; then if [ -x "$IPKG_TMP/$pkg/control/preinst" -o -x "$IPKG_TMP/$pkg/control/postinst" ]; then echo "Cannot run {pre|post}inst scripts when IPKG_ROOT != \"/\"" echo "Copying $filename to $IPKG_PENDING_DIR for later installation." echo "Package: $pkg Status: install ok pending" | ipkg_status_update $pkg mkdir -p $IPKG_PENDING_DIR cp $filename $IPKG_PENDING_DIR rm -r $IPKG_TMP/$pkg/control rm -r $IPKG_TMP/$pkg/data rmdir $IPKG_TMP/$pkg return 0 fi fi echo -n "Unpacking $pkg..." set +o noglob for file in $IPKG_TMP/$pkg/control/*; do base_file=`ipkg_file_part $file` mv $file $IPKG_INFO_DIR/$pkg.$base_file done set -o noglob rm -r $IPKG_TMP/$pkg/control if ! tar -xzOf $filename ./data.tar.gz | tar -xzf - -C $IPKG_TMP/$pkg/data; then echo "ipkg_install_file: ERROR unpacking data.tar.gz from $filename" return 1 fi echo "Done." echo -n "Configuring $pkg..." if [ -x "$IPKG_INFO_DIR/$pkg.preinst" ]; then if ! $IPKG_INFO_DIR/$pkg.preinst; then echo "$IPKG_INFO_DIR/$pkg.preinst failed. Aborting installation of $pkg" rm -rf $IPKG_TMP/$pkg/data rmdir $IPKG_TMP/$pkg return 1 fi fi old_conffiles=`ipkg_status $pkg Conffiles | ipkg_extract_value` new_conffiles= if [ -f "$IPKG_INFO_DIR/$pkg.conffiles" ]; then for conffile in `cat $IPKG_INFO_DIR/$pkg.conffiles`; do if [ -f "$IPKG_ROOT/$conffile" ] && ! echo " $old_conffiles " | grep -q " $conffile "`md5sum $IPKG_ROOT/$conffile | sed 's/ .*//'`; then while true; do echo -n "Configuration file \`$conffile' ==> File on system created by you or by a script. ==> File also in package provided by package maintainer. What would you like to do about it ? Your options are: Y or I : install the package maintainer's version N or O : keep your currently-installed version D : show the differences between the versions (if diff is installed) The default action is to keep your current version. *** `ipkg_file_part $conffile` (Y/I/N/O/D) [default=N] ? " read response case "$response" in [YyIi] | [Yy][Ee][Ss]) md5sum=`md5sum $IPKG_TMP/$pkg/data/$conffile | sed 's/ .*//'` new_conffiles="$new_conffiles $conffile $md5sum" break ;; [Dd]) echo " diff -u $IPKG_ROOT/$conffile $IPKG_TMP/$pkg/data/$conffile" diff -u $IPKG_ROOT/$conffile $IPKG_TMP/$pkg/data/$conffile || true echo "[Press ENTER to continue]" read junk ;; *) new_conffiles="$new_conffiles $conffile " rm $IPKG_TMP/$pkg/data/$conffile break ;; esac done else md5sum=`md5sum $IPKG_TMP/$pkg/data/$conffile | sed 's/ .*//'` new_conffiles="$new_conffiles $conffile $md5sum" fi done fi owd=`pwd` (cd $IPKG_TMP/$pkg/data/; tar cf - . | (cd $owd; cd $IPKG_ROOT; tar xf -)) rm -rf $IPKG_TMP/$pkg/data rmdir $IPKG_TMP/$pkg tar -xzOf $filename ./data.tar.gz | tar tzf - | sed -e 's/^\.//' > $IPKG_INFO_DIR/$pkg.list if [ -x "$IPKG_INFO_DIR/$pkg.postinst" ]; then $IPKG_INFO_DIR/$pkg.postinst fi if [ -n "$new_conffiles" ]; then new_conffiles='Conffiles: '`echo $new_conffiles | ipkg_protect_slashes` fi sed -e "s/\(Package:.*\)/\1\\ Status: install ok installed\\ ${new_conffiles}/" $IPKG_INFO_DIR/$pkg.control | ipkg_status_update $pkg rm -f $IPKG_INFO_DIR/$pkg.control rm -f $IPKG_INFO_DIR/$pkg.conffiles rm -f $IPKG_INFO_DIR/$pkg.preinst rm -f $IPKG_INFO_DIR/$pkg.postinst echo "Done." } ipkg_install() { while [ $# -gt 0 ]; do pkg=$1 shift case "$pkg" in http://* | ftp://*) tmp_pkg_file="$IPKG_TMP/"`ipkg_file_part $pkg` if ipkg_download $pkg $tmp_pkg_file; then ipkg_install_file $tmp_pkg_file rm $tmp_pkg_file fi ;; *.ipk) if [ -f "$pkg" ]; then ipkg_install_file $pkg else echo "File not found $pkg" >&2 fi ;; *) ipkg_get_install $pkg ;; esac done } ipkg_install_pending() { [ "$IPKG_ROOT" != "/" ] && return 0 if [ -d "$IPKG_PENDING_DIR" ]; then set +o noglob pending=`ls -1d $IPKG_PENDING_DIR/*.ipk 2> /dev/null` || true set -o noglob if [ -n "$pending" ]; then echo "The following packages in $IPKG_PENDING_DIR will now be installed:" echo $pending for filename in $pending; do if ipkg_install_file $filename; then rm $filename fi done fi fi return 0 } ipkg_install_wanted() { wanted=`ipkg_status_matching 'Status:[[:space:]]*install.*not-installed'` if [ -n "$wanted" ]; then echo "The following packages were previously requested but never installed:" echo $wanted ipkg_install $wanted fi return 0 } ipkg_upgrade_pkg() { while [ $# -gt 0 ]; do pkg=$1 shift inst_ver=`ipkg_status $pkg Version | ipkg_extract_value` avail_ver=`ipkg_info $pkg Version | ipkg_extract_value` if [ -z "$inst_ver" ]; then echo "Package $pkg does not appear to be installed" return 0 fi if [ -z "$avail_ver" ]; then echo "Assuming locally installed package $pkg ($inst_ver) is up to date" return 0 fi if [ "$inst_ver" != "$avail_ver" ]; then echo "Upgrading $pkg from $inst_ver to $avail_ver" ipkg_install $pkg else echo "Package $pkg ($inst_ver) is up to date" fi done } ipkg_upgrade() { if [ $# -lt 1 ]; then inst_pkgs=`ipkg_status_matching 'Status:.*[[:space:]]installed'` for pkg in $inst_pkgs; do ipkg_upgrade_pkg $pkg done else ipkg_upgrade_pkg $* fi } ipkg_remove_pkg() { pkg=$1 if ! ipkg_status_installed $pkg; then echo "ipkg_remove: Package $pkg does not appear to be installed." if ipkg_status_mentioned $pkg; then echo "Purging mention of $pkg from the ipkg database" ipkg_status_remove $pkg fi return 1 fi echo "ipkg_remove: Removing $pkg... " files=`cat $IPKG_INFO_DIR/$pkg.list` if [ -x "$IPKG_INFO_DIR/$pkg.prerm" ]; then if [ "$IPKG_ROOT" = "/" ]; then $IPKG_INFO_DIR/$pkg.prerm else echo "ipkg_remove: WARNING: skipping $pkg.prerm since IPKG_ROOT $IPKG_ROOT is not /" fi fi conffiles=`ipkg_status $pkg Conffiles | ipkg_extract_value` dirs_to_remove= for file in $files; do if [ -d "$IPKG_ROOT/$file" ]; then dirs_to_remove="$dirs_to_remove $IPKG_ROOT/$file" else if echo " $conffiles " | grep -q " $file "; then if echo " $conffiles " | grep -q " $file "`md5sum $IPKG_ROOT/$file | sed 's/ .*//'`; then rm -f $IPKG_ROOT/$file fi else rm -f $IPKG_ROOT/$file fi fi done removed_a_dir=t while [ -n "$removed_a_dir" ]; do removed_a_dir= new_dirs_to_remove= for dir in $dirs_to_remove; do if rmdir $dir >/dev/null 2>&1; then removed_a_dir=t else new_dirs_to_remove="$new_dirs_to_remove $dir" fi done dirs_to_remove=$new_dirs_to_remove done if [ -n "$dirs_to_remove" ]; then echo "ipkg_remove: Warning: Not removing the following directories since they are not empty:" >&2 echo "$dirs_to_remove" | sed -e 's/\/[/]\+/\//g' >&2 fi if [ -x "$IPKG_INFO_DIR/$pkg.postrm" ]; then if [ "$IPKG_ROOT" = "/" ]; then $IPKG_INFO_DIR/$pkg.postrm else echo "ipkg_remove: WARNING: skipping $pkg.postrm since IPKG_ROOT $IPKG_ROOT is not /" fi fi ipkg_status_remove $pkg set +o noglob rm -f $IPKG_INFO_DIR/$pkg.* set -o noglob echo "Done." } ipkg_remove() { while [ $# -gt 0 ]; do pkg=$1 shift ipkg_remove_pkg $pkg done } ########### # ipkg main ########### cmd=$1 shift >/dev/null 2>&1 || ipkg_usage case "$cmd" in update|upgrade|list|info|status|install_pending) ;; install|depends|remove|files|search) if [ $# -lt 1 ]; then echo "ERROR: ipkg $cmd requires an argument" ipkg_usage fi ;; *) echo "ERROR: unknown sub-command \`$cmd'" ipkg_usage ;; esac # Only install pending if we have an interactive sub-command case "$cmd" in upgrade|install) ipkg_install_pending ipkg_install_wanted ;; esac ipkg_$cmd $*