#!/bin/sh

# Copyright (c) 2011-2024, Aleksey Cheusov <vle@gmx.net>
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
#     * Redistributions of source code must retain the above copyright
#       notice, this list of conditions and the following disclaimer.
#     * Redistributions in binary form must reproduce the above
#       copyright notice, this list of conditions and the following
#       disclaimer in the documentation and/or other materials provided
#       with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

set -e
export LC_ALL=C

######################################################################
sysconf=${NIH_SYSCONFDIR-/usr/pkg/etc}/nih.conf
userconf="$HOME/.nihrc"

dfltconfdir=${NIH_DFLTCONFDIR-/usr/pkg/lib/nih}

: ${PKG_INFO_CMD:=/usr/pkg/sbin/pkg_info}
: ${PKG_ADMIN_CMD:=/usr/pkg/sbin/pkg_admin}
: ${PKG_DELETE_CMD:=/usr/pkg/sbin/pkg_delete}
: ${PKG_UPDATE_PLAN_CMD:=pkg_update_plan}
: ${PKG_ADD_CMD:=/usr/pkg/sbin/pkg_add}
: ${PKG_DBDIR:=/usr/pkg/pkgdb}
: ${SUFX:=.tgz}

pkgdb="${PKG_DBDIR}/pkgdb.byfile.db"

######################################################################
errmsg (){
    # $1 -- error message
    printf "%s\n" "$1" 1>&2
}

if ! test -r "$pkgdb"; then
    errmsg "Cannot find pkgdb."
    exit 1
fi

# loading config files
if test -r "$userconf"; then
    conffile="$userconf"
elif test -r "$sysconf"; then
    conffile="$sysconf"
else
    cat <<EOF
Neither $sysconf nor $userconf are readable.
Sorry, I cannot help.
EOF
    exit 1
fi

. "$dfltconfdir/nih.default.conf"
. "$conffile"

: ${PKG_TINY_BIN_SUMMARY:=${PKG_SUMMARY_DIR}/pkg_tiny_bin_summary.txt}
: ${PKG_TINY_INST_SUMMARY:=${PKG_SUMMARY_DIR}/pkg_tiny_inst_summary.txt}

######################################################################
verbose (){
    printf "%s\n" "$1" 1>&2
}

usage (){
    cat 1>&2 <<'EOF'
Usage:
  pkgnih [global options] command [command options] [--] [command args]

Global options:
 nih -h                 - Display help message
 nih -V                 - Display nih version
 nih -y                 - Answer 'Yes' to all questions
 nih -Y <question_id>   - Answer 'Yes' to question_id question.
 nih -s <summary>       - pkg_summary(5) file

 nih -D                 - enable debugging

Commands:
 nih <command> -h    - Display help message for a <command>
 nih help            - Display help message
 nih refresh         - Download pkg_summary/SHA512 files
                       or update pkg_src_summary file
 nih install
   nih update        - Install or update packages
 nih uninstall
   nih delete
   nih remove        - Uninstall packages
 nih verify          - Verify packages integrity
 nih status          - Show the status of installed packages
 nih search          - Search on packages
 nih audit           - Check the installed packages for vulnerabilities
 nih info            - Show information about package
 nih meta            - Output available or installed meta packages
 nih leaf            - Output or remove leaf packages
 nih list            - List packages
 nih mark            - Mark packages
 nih deps            - Show dependencies
 nih clean-cache     - Clean-up cache directory with binaries
 nih history         - Output a history of changes
EOF
}

######################################################################
# ensure that pkg_bin_summary.txt exists and refresh it
set_pkg_summary() {
    if test -z "$PKG_SUMMARY"; then
	PKG_SUMMARY="$PKG_SUMMARY_DIR/pkg_bin_summary.txt"
    fi
}

fetch_ftp (){
    ${NIH_FTP_CMD-/usr/bin/ftp} -o "$_base" -V -q1800 $FTP_OPTIONS "$1"
}

fetch_wget (){
    ${NIH_WGET_CMD-wget} -O "$_base" --timeout=1800 --progress=bar $WGET_OPTIONS "$1"
}

fetch_cp (){
    _base=`basename $1`
    rm -f "$_base"
    if ! ln "$1" . 2>/dev/null; then
	cp "$1" .
    fi
}

fetch_uni (){
    # $1 -- URL or file
    if test -n "${1##/*}"; then
	_base=`basename $1`
	rm -f "$_base"

	fetch "$@"
    elif test "${1%/*}" != `pwd`; then
	_base=`basename $1`
	rm -f "$_base"

	fetch_cp "$@"
    fi
}

remove_comments (){
    # remove empty lines and lines started with #; trim spaces
    # "$@" --file
    runawk -f trim.awk -e '!/^#/ && NF > 0 {print trim_lrc($0)}' "$@"
}

create_cachedir (){
    mkdir -p "$CACHEDIR"
}

set_yes_var () {
    # $1 -- yesno id
    eval "yes_$1=1"
    nih_opts="$nih_opts -Y$1"
}

on_exit () {
    if test -n "$debug"; then
	errmsg "temporary directory: $tmp_dir"
    else
	rm -rf "$tmp_dir"
    fi
}

######################################################################
######################################################################
######################################################################
# functions
show_summary_PNC (){
    # output:
    #    PKGPATH (PKGNAME) <LF>
    #        COMMENT <LF>
    awk '/^PKGNAME=/ {pkgname=substr($0, 9)}
         /^PKGPATH=/ {pkgpath=substr($0, 9)}
         /^COMMENT=/ {comment=substr($0, 9)}
         NF == 0 {printf "%s\t%s\t%s\n", pkgpath, pkgname, comment}' "$@" |
    sort |
    awk -F'\t' '{printf "%s (%s)\n     %s\n", $1, $2, $3}'
}

show_summary_NPC (){
    # output:
    #    PKGNAME (PKGPATH) <LF>
    #        COMMENT <LF>
    awk '/^PKGNAME=/ {pkgname=substr($0, 9)}
         /^PKGPATH=/ {pkgpath=substr($0, 9)}
         /^COMMENT=/ {comment=substr($0, 9)}
         NF == 0 {printf "%s\t%s\t%s\n", pkgname, pkgpath, comment}' "$@" |
    sort |
    awk -F'\t' '{printf "%s (%s)\n     %s\n", $1, $2, $3}'
}

show_summary_BC (){
    # output: PKGBASE     COMMENT <LF>
    awk '/^PKGNAME=/ {pkgbase=substr($0, 9); sub(/-[0-9].*$/, "", pkgbase)}
         /^COMMENT=/ {comment=substr($0, 9)}
         NF == 0 {printf "%-19s %s\n", pkgbase, comment}' "$@" |
    sort
}

show_summary_NC (){
    # output: PKGNAME     COMMENT <LF>
    awk '/^PKGNAME=/ {pkgname=substr($0, 9)}
         /^COMMENT=/ {comment=substr($0, 9)}
         NF == 0 {printf "%-19s %s\n", pkgname, comment}' "$@" |
    sort
}

show_summary_N (){
    # output: PKGNAME
    awk '/^PKGNAME=/ {printf "%s\n", substr($0, 9)}' "$@" | sort | tr '
' ' ' | wrap 70
}

show_summary_NP (){
    # output: PKGNAME(PKGPATH)
    awk '/^PKGNAME=/ {pkgname=substr($0, 9)}
         /^PKGPATH=/ {pkgpath=substr($0, 9)}
         NF == 0 {printf "%s (%s)  ", pkgname, pkgpath}
	END {printf "\n"}' "$@" | sort | wrap 70
}

wrap (){ # fmt(1) is not portable accross different OSes
    #$1 -- width
    w=$1
    shift
    awk -v w="$w" '
	{
	    for (i=1; i<=NF; ++i) {
		sz += length($i)+1
		if (i != 1 && sz > w){
		    sz = length($i)+1
		    printf "\n"
		}
		printf "%s ", $i
	    }
	} END {printf "\n"}' "$@"
}

indent (){
    # $@ -- files
    awk '{print "    " $0}' "$@"
}

cleanup_db (){
    verbose 'Updating pkgdb...'
    $PKG_ADMIN_CMD rebuild-tree
}

yesno (){
    # $1 - id
    # $2 - question
    while true; do
	printf '%s (y/N)? ' "$2"
	set +e # workaround for buggy DragonFlyBSD/FreeBSD /bin/sh
	if eval 'test -n "$yes_'"$1\""; then
	    verbose Yes
	    set -e # workaround for buggy DragonFlyBSD/FreeBSD /bin/sh
	    return 0
	fi
	set -e # workaround for buggy DragonFlyBSD/FreeBSD /bin/sh

	read answer
	answer=${answer:-no}
	case "$answer" in
	    y|Y|yes|Yes|YES)
		return 0;;
	    *)
		return 1;;
	esac
    done
}

get_date (){
    date +%Y%m%d_%H%M
}

summary2binary (){
    # input: summary
    # output:
    #    PKGNAME1 FILE_NAME1
    #    ...
    #    PKGNAMEn FILE_NAMEn
    awk -v SUFX="$SUFX" '
    /^PKGNAME=/ {pkg1 = substr($0, 9) SUFX}
    /^FILE_NAME=/ {pkg2 = substr($0, 11)}
    NF == 0 {
	print pkg1, pkg2
	pkg1 = pkg2 = ""
    }' "$@"
}

# saves list of installed packages
save_installed_packages (){
    if test -n "$NO_INSTALLED_COPY"; then
	return
    fi

    # was inst_summary already saved?
    if test -n "$summary_aleady_saved"; then
	return
    fi
    summary_aleady_saved=1

    #
    installed_fn="$HISTORYDIR"/installed_`get_date`
    counter=''
    while test -f "${installed_fn}$counter.bz2"; do
	counter=${counter-0}
	counter=$(($counter+1))
    done

    refresh_inst_summary

    installed_fn="${installed_fn}$counter.bz2"

    grep -E '^$|^(PKGNAME|PKGPATH|automatic|try_out|keep|BUILD_DATE)=' \
	"$PKG_INST_SUMMARY" |
	bzip2 > "$installed_fn".bz2
    mv "$installed_fn".bz2 "$installed_fn"
}

cat_bin_summary (){ # TODO: adapt for rpmdb
    set_pkg_summary
    summary_exists "$PKG_SUMMARY" ''
    cat "$PKG_SUMMARY"
}

cat_inst_summary (){ # TODO: adapt for rpmdb
    refresh_inst_summary
    cat "$PKG_INST_SUMMARY"
}

cat_src_summary (){
    summary_exists "$PKG_SRC_SUMMARY" '-p'
    cat "$PKG_SRC_SUMMARY"
}

######################################################################
######################################################################
######################################################################
# refresh
usage_refresh (){
    cat <<'EOF'
nih refresh [options]

   Refresh remote package lists.

   options:
      -h     display this help message
      -b     download pkg_summary(5) and SHA512 (the default)
      -p     update pkg_src_summary file from pkgsrc tree
      -i     update pkg_inst_summay file
      -P     the same as -p but pkg_src_summary file
             is regenerated from scratch

nih.conf:
   REPOSITORY      - URL or file name for pkg_summary.gz/pkg_bin_summary.txt
   PKG_SUMMARY     - path to pkg_bin_summary.txt, set this variable if you use
                     local pkg_summary, otherwise keep it unset
   fetch function  - download command
EOF
}

fetch_summary_file (){
    # $1 - repository
    # $2 - extension
    verbose "Downloading $1/All/pkg_summary.$2... "
    rm -f pkg_summary.$2
    if fetch_uni "$1/All/pkg_summary.$2"; then
	verbose done
	case "$2" in
	    gz)  gzip -dc  pkg_summary.gz  > pkg_bin_summary.txt.tmp || return 1;;
	    bz2) bzip2 -dc pkg_summary.bz2 > pkg_bin_summary.txt.tmp || return 1;;
	    *)   errmsg 'Unknown ext'; exit 1;;
	esac
	mv pkg_bin_summary.txt.tmp pkg_bin_summary.txt
    else
	return 1
    fi
}

fetch_sha512_file (){
    # $1 - repository
    # $2 - extension
    if test -n "$IGNORE_CKSUMS"; then
	return 0;
    fi

    verbose "Downloading $1/SHA512.$2... "
    rm -f SHA512.$2
    if fetch_uni "$1/SHA512.$2"; then
	verbose done
	case "$2" in
	    gz)   gzip  -dc SHA512.$2 > SHA512.txt.tmp || return 1;;
	    bz2)  bzip2 -dc SHA512.$2 > SHA512.txt.tmp || return 1;;
	    *)    errmsg 'Unknown ext'; exit 1;;
	esac
	mv SHA512.txt.tmp SHA512.txt
    else
	return 1
    fi
}

refresh_bin_summary (){ # TODO: adapt for rpmdb
    cd "$tmp_dir"

    rm -f new_summary.txt
    for repo in $REPOSITORY; do
	if ! fetch_summary_file "$repo" gz && ! fetch_summary_file "$repo" bz2; then
	    errmsg 'pkg_summary(5) file cannot be downloaded.'
	    exit 1
	fi

	if ! grep -l '^FILE_CKSUM=' pkg_bin_summary.txt > /dev/null; then
	    if ! fetch_sha512_file "$repo" bz2 && ! fetch_sha512_file "$repo" gz; then
		errmsg 'SHA512 file cannot be downloaded.'
		exit 1
	    fi
	    pkg_cksum2summary -c SHA512.txt pkg_bin_summary.txt > pkg_bin_summary.txt.tmp
	    mv pkg_bin_summary.txt.tmp pkg_bin_summary.txt
	    rm -f SHA512.txt SHA512.bz2 SHA512.gz
	fi
	sed 's,^\(FILE_NAME= *\)\(.*\),\1'"$repo"'/All/\2,' pkg_bin_summary.txt >> new_summary.txt
	rm pkg_bin_summary.txt
    done
    cd "$PKG_SUMMARY_DIR"
    pkg_uniq_summary -Fpb "$tmp_dir"/new_summary.txt > pkg_bin_summary.txt.tmp
    mv pkg_bin_summary.txt.tmp pkg_bin_summary.txt
    rm "$tmp_dir"/new_summary.txt

    make_tiny_summary pkg_bin_summary.txt > "$PKG_TINY_BIN_SUMMARY.tmp"
    mv "$PKG_TINY_BIN_SUMMARY.tmp" "$PKG_TINY_BIN_SUMMARY"

    if test -z "$NO_SUMMARY_COPY"; then
	date_sufx=`get_date`
	cp pkg_bin_summary.txt pkg_bin_summary_${date_sufx}.txt
    fi
}

refresh_src_summary (){ # TODO: adapt for rpmdb
    verbose 'Updating pkg_src_summary. This may take a while...'
    all_pkgs="$PKG_SRC_SUMMARY_DIR/pkgsrc_packages"
    pkg_list_all_pkgs -a "$PKGSRC_ADDSUBDIRS" > "$all_pkgs"
    pkg_update_src_summary -d "$PKG_SRC_SUMMARY_DIR" -f "$all_pkgs" -v
}

refresh_inst_summary (){ # TODO: adapt for rpmdb
    if test -r "$PKG_INST_SUMMARY" &&
	test "$pkgdb" -ot "$PKG_INST_SUMMARY"
    then
	:
    else
	verbose 'Updating pkg_inst_summary...'
	pkg_bin_summary -a PLIST,automatic,try_out,keep,COMMENT \
	    > "$PKG_INST_SUMMARY".tmp
	mv "$PKG_INST_SUMMARY".tmp "$PKG_INST_SUMMARY"

	make_tiny_summary "$PKG_INST_SUMMARY" > "$PKG_TINY_INST_SUMMARY.tmp"
	mv "$PKG_TINY_INST_SUMMARY.tmp" "$PKG_TINY_INST_SUMMARY"
    fi
}

do_refresh() {
    func=refresh_bin_summary

    while getopts hbipP f; do
	case "$f" in
	    '?')
		exit 1;;
	    h)
		usage_refresh
		exit 0;;
	    b)
		func=refresh_bin_summary;;
	    p)
		func=refresh_src_summary;;
	    i)
		func=refresh_inst_summary;;
	    P)
		rm -f "$PKG_SRC_SUMMARY"
		func=refresh_src_summary;;
	esac
    done
    shift `expr $OPTIND - 1`

    if test refresh_bin_summary = "$func" -a -n "$PKG_SUMMARY"; then
	cat 1>&2 <<EOF
Variable PKG_SUMMARY is set in configuration file.
Nothing to do.
EOF
	exit 1
    fi

    $func
}

summary_exists (){
    # $1 -- summary file
    # $2 -- option for "nih refresh"
    if ! test -s "$1"; then
	cat 1>&2 <<EOF
'$1' file doesn't exist or is empty.
Perhaps you need to run 'nih refresh $2'
EOF
	exit 1
    fi
}

make_tiny_summary (){
    # remove unnecessary fields and add PKGPATH with "fake" category if it is absent
    awk '
! /^$|^(PKGNAME|PKGPATH|COMMENT|automatic|try_out|CONFLICTS|DEPENDS|BUILD_DATE|PROVIDES|REQUIRES|FILE_NAME)=/ {next}
/^PKGPATH=/ {pkgpath_presents = 1}
/^PKGNAME=/ {pkgname = substr($0, 9)}
NF == 0 {
        if (!pkgpath_presents) {
                sub(/-[^-]*$/, "", pkgname)
                print "PKGPATH=fake/" pkgname
                pkgname = ""; pkgpath_presents = 0
        }
        print ""; next
}
{ print }
' "$@"
}

refresh_tiny_bin_summary (){
    set_pkg_summary
    summary_exists "$PKG_SUMMARY" ''
    if test "$PKG_SUMMARY" -nt "$PKG_TINY_BIN_SUMMARY"; then
	PKG_TINY_BIN_SUMMARY="$tmp_dir/tiny_summary"
	make_tiny_summary "$PKG_SUMMARY" > "$PKG_TINY_BIN_SUMMARY".tmp
	mv "$PKG_TINY_BIN_SUMMARY".tmp "$PKG_TINY_BIN_SUMMARY"
    fi
}

######################################################################
######################################################################
######################################################################
# install | update
usage_install (){
    cat <<'EOF'
nih install -h
nih install [options] <packages>

      Install or update specified packages. These commands are the same.

   options:
      -h     display this help message

      -n     don't actually install a package, just report the steps that
             would be taken if it were.

      -r     update specified packages and their dependencies
      -R     update specified packages and their dependent packages

      -a <val>    set PKG_PATH and run pkg_add(8) directly
             val=1 ==> PKG_PATH=$CACHEDIR/All
             val=2 ==> PKG_PATH=$REPOSITORY/All

      -B     consider packages different if their BUILD_DATE differ

      -l     do not remove auto-installed leaf packages
      -L     remove auto-installed leaf packages (the default)

      -p     disable REQUIRES/PROVIDES consistency check
      -P     enable REQUIRES/PROVIDES consistency check (the default)

      -v     disable verifying pkgdb before installation
      -V     enable verifying pkgdb before installation (the default)

      -t     install packages and mark them as "try out"

      -d     download binaries only, do not update installed packages
      -k     continue downloading packages after errors are encountered
      -w     if requested package is not found, write a warning message to stderr
             and continue the installation.

      -f <file>     take packages for update/install from the specified file

      -D     For debugging purposes

Install, uninstall or update specified packages.
If no packages are specified
full update is done. Packages with "keep" flag (see 'nih mark -k' and
'nih list -k' for details) are not updated.
Packages may be specified as
PKGBASE, PKGNAME, PKGPATH or a filename, optionally with one of the following suffixes:
 "-" (tells to uninstall the package),
 "--" (tells to uninstall the package with all dependent packages),
 "+" (tells to install/update the package and mark it as auto-installed), or
 "_" (tells to install/update the package without altering auto-installed flag).
 "/" (tells not to update nor uninstall the package if it is possible).
When installing, packages without a suffix are marked as installed by user.
Automatically installed leaf packages are removed automatically.
If "_" is specified as a package, all packages
for which update is available will be updated.

Examples:
   nih update
   nih update -dk
   nih install mawk lang/gawk
   nih install perl
   nih install GConf+ xproto+
   nih install gmake- autoconf- automake- libtool-base-
   nih install textproc/dict-client wip/dict-client-
   nih install -t xfce4 fvwm openbox ctwm wmii fluxbox sawfish
   nih install -a1 firefox
   nih install -a2 -- -UA xulrunner
   nih install emacs-- emacs/
EOF
}

check_cksums (){ # TODO: adapt for rpmdb
    # $1 - foobar-1.2.3.tgz
    if test -n "$IGNORE_CKSUMS"; then
	return
    fi

    printf '    ckecksum... ' 1>&2
    expected_sha512=`pkg_grep_summary -t suffix FILE_NAME "/$1" < "$PKG_SUMMARY_DIR/pkg_bin_summary.txt" |
	sed -n 's,^FILE_CKSUM= *sha512  *,,p'`
    if test -z "$expected_sha512"; then
	verbose 'skipped'
	return 0
    fi

    curr_sha512=`digest sha512 "$1" | awk '{print $4}'`
    if test "$expected_sha512" = "$curr_sha512"; then
	verbose 'ok'
	return 0
    else
	verbose 'failed'
	return 1
    fi
}

download_pkgs (){ # TODO: adapt for rpmdb
    # stdin -- list of (pkg url) packages to download
    (
	ex=0
	while read pkg url; do
	    cd "$CACHEDIR/All"
	    verbose "  $pkg"
	    if ! test -f "$pkg" || ! check_cksums "$pkg"; then
		printf '    downloading... '
		if ! fetch_uni "$url"; then
		    ex=1
		    verbose 'failed'
		    if test -z "$force_download"; then
			exit 1
		    fi
		    continue
		fi
		verbose 'ok'

		if test -n "$check_second_cksum" && ! check_cksums "$pkg"; then
		    ex=1
		    verbose 'Try to update pkg_summary(5) file (nih refresh)'
		    if test -z "$force_download"; then
			exit 1
		    fi
		    continue
		fi
	    fi
	done
	exit $ex
    )
}

grep_lines (){
    # $1 - file with lines
    # input: stdin
    # output: lines present in $1 and stdin
    awk 'FILENAME != "-" { hash [$0] = 0; next }
	($0 in hash) {print $0 }' "$1" -
}

rev_lines (){
    # Reverse input lines
    # $@ - files
    awk '{ line [NR] = $0 }
    END { for (i=NR; i >= 1; --i) print line [i] }' "$@"
}

replace_with_fake (){ # TODO: adapt for rpmdb
    # $1 - pkgbase
    # $2 - version
    deps=`$PKG_INFO_CMD -X "$1" | sed -n 's/^DEPENDS=//p'`
    path=`$PKG_INFO_CMD -X "$1" | sed -n 's/^PKGPATH=//p'`
    /usr/pkg/libexec/nih/pkg_create_fake -n "$1-$2" -p "$path" -d "$deps" "$fake_pkg_fn"
    $PKG_ADD_CMD -UD "$fake_pkg_fn"
    $PKG_ADMIN_CMD set fake=yes "$1-$2"
}

######################################################################
######################################################################
######################################################################
# install

is_pkg_summary_required() {
    for pkg in "$@"; do
	printf '%s\n' "$pkg"
    done | grep -vq -- '-$'

    return $?
}

do_install() {
    verify_pkgdb=1
    remove_leaves=1
    reqprov_check=1

#    cmd="$1"
    while getopts a:BcCdDeEf:hklLnpPrRtvVwW f; do
	case "$f" in
	    '?')
		exit 1;;
	    n)
		pkg_opts='-n';;
	    h)
		usage_install
		exit 0;;
	    d)
		download_only=1;;
	    k)
		export force_download=1;;
	    B)
		update_plan_opts="$update_plan_opts -B";;
	    c)
		unset check_second_cksum || true;;
	    C)
		check_second_cksum=1;;
	    w)
		update_plan_opts="$update_plan_opts -w";;
	    W)
		update_plan_opts="$update_plan_opts -W";;
	    t)
		update_plan_opts="$update_plan_opts -t";;
	    l)
		unset remove_leaves || true;;
	    L)
		remove_leaves=1;;
	    p)
		unset reqprov_check || true;;
	    P)
		reqprov_check=1;;
	    e)
		unset plist_check || true;;
	    E)
		plist_check=1;;
	    v)
		unset verify_pkgdb || true;;
	    V)
		verify_pkgdb=1;;
	    D)
		debug=1;;
	    a)
		pkg_add_local="$OPTARG";;
	    r)
		update_plan_opts="$update_plan_opts -r";;
	    R)
		update_plan_opts="$update_plan_opts -R";;
	    f)
		packages_fn="$OPTARG";;
	    *)
		opts="${opts}$f";;
	esac
    done
    shift `expr $OPTIND - 1`

    # -V
    if test -n "$verify_pkgdb"; then
	verbose 'Verifying pkgdb'
	pkg_status -k -lLdc | awk '/^.:/ {$0 = "   " $0} {print "  ", $0}'
    fi

    # -p
    if test -z "$reqprov_check"; then
	update_plan_opts="$update_plan_opts -p"
    fi

    # -l
    if test -z "$remove_leaves"; then
	update_plan_opts="$update_plan_opts -l"
    fi

    # -a
    if test -n "$pkg_add_local"; then
	verbose "Installing packages $*"
	save_installed_packages
	case "$pkg_add_local" in
	    1)
		pkg_paths="$CACHEDIR";;
	    2)
		pkg_paths="$REPOSITORY";;
	    *)
		errmsg 'Bad argument for -a option'
		errmsg 'See nih install -h for details'
		exit 1;;
	esac
	ex=0
	for path in $pkg_paths; do
	    verbose "Trying $PKG_PATH repository"
	    export PKG_PATH="$path/All"
	    if $PKG_ADD_CMD "$@"; then
		break
	    else
		ex=$?
	    fi
	done
	unset PKG_PATH || true
	exit $ex
    fi

    #
    if is_pkg_summary_required "$@"; then
	refresh_tiny_bin_summary
	export PKG_SUMMARY="$PKG_TINY_BIN_SUMMARY"
    else
	export PKG_SUMMARY=/dev/null
    fi
    refresh_inst_summary

    installed_summary="$tmp_dir/installed_summary"
    grep -E '^$|^(PROVIDES|REQUIRES|PKGNAME|PKGPATH|COMMENT|automatic|CONFLICTS|DEPENDS|BUILD_DATE|try_out|keep|PLIST)=' \
	"$PKG_INST_SUMMARY" > "$installed_summary"

    cmp_summary="$tmp_dir/cmp_summary"

    update_pkgs_fn="$tmp_dir/pkgs_specs.txt"
    printf '' > "$update_pkgs_fn"
    if test $# -eq 0; then
	if test -n "$packages_fn"; then
	    remove_comments "$packages_fn" > "$update_pkgs_fn"
	else
	    echo _ > "$update_pkgs_fn"
	fi
    else
	for i; do
	    if echo "$i" | grep '^/' > /dev/null; then
		suffix=`echo "$i" | sed -n 's|^.*\([-+_]\)$|\1|p'`
		if test -n "$suffix"; then
		    fn=`echo "$i" | sed 's|^\(.*\)[-+_]$|\1|'`
		else
		    fn="$i"
		fi
		pkg_info -X "$fn" |
		sed "s ^FILE_NAME=.*$ FILE_NAME=$fn " |
		tee "$PKG_TINY_BIN_SUMMARY".tmp |
		awk -v suffix="$suffix" '
		     /^PKGPATH=/ {p=substr($0, 9)}
		     /^PKGNAME=/ {n=substr($0, 9)}
		     END {print p ";" n suffix}' >> "$update_pkgs_fn"
		cat "$PKG_TINY_BIN_SUMMARY" >> "$PKG_TINY_BIN_SUMMARY".tmp
		pkg_uniq_summary "$PKG_TINY_BIN_SUMMARY".tmp > "$PKG_TINY_BIN_SUMMARY"
	    else
		printf '%s\n' "$i" >> "$update_pkgs_fn"
	    fi
	done
    fi

    ##################################################################
    ######################### update plan ############################
    verbose "Generating an update plan..."
    plan_summary="$tmp_dir/plan_summary"
    printf '' > "$plan_summary"
    if ! test -s "$update_pkgs_fn"; then
	verbose 'Everything is up-to-date.'
	exit 0
    fi

    if test -n "$debug"; then
	debug_opt=-v
    fi

    set_pkg_summary
    if $PKG_UPDATE_PLAN_CMD -iv $update_plan_opts $debug_opt \
	"$installed_summary" "$PKG_SUMMARY" \
	> "$plan_summary" < "$update_pkgs_fn"
    then
	:
    else
	ex=$?
	cat "$plan_summary" 1>&2
	exit $ex
    fi

    pkg_cmp_summary -a force_update "$installed_summary" "$plan_summary" |
    grep -v ^= > "$cmp_summary" || true

    if ! test -s "$cmp_summary" > /dev/null; then
	verbose 'Nothing to be done'
	exit
    fi

    printf '%s\n' '--------------------------------------------------------'

    rem_pkgs=`awk '$1 == "-" {print $2}' "$cmp_summary"`
    rem_pkgs=`echo $rem_pkgs` # remove NL characters
    rem_summary="$tmp_dir/rem_summary"
    pkg_grep_summary -t strlist PKGBASE "$rem_pkgs" \
	< "$installed_summary" > "$rem_summary"

    inst_all_pkgs_fn="$tmp_dir/inst_all_pkgs"
    inst_all_summary="$tmp_dir/inst_all_summary"

    awk '$1 ~ /^[<>!]$/ {print $2 "-" $4}
	 $1 ~ /^[+]$/   {print $2 "-" $3}' "$cmp_summary" |
    sort -u > "$inst_all_pkgs_fn"

    pkg_grep_summary -t strfile PKGNAME "$inst_all_pkgs_fn" \
	< "$plan_summary" > "$inst_all_summary"

    inst_auto_summary="$tmp_dir/inst_auto_summary"
    pkg_grep_summary -v -e automatic < "$inst_all_summary" > "$inst_auto_summary"

    inst_user_summary="$tmp_dir/inst_user_summary"
    pkg_grep_summary    -e automatic < "$inst_all_summary" > "$inst_user_summary"

    # rem
    if test -s "$rem_summary"; then
	verbose 'The following packages will be removed'
	summary2packages_to_be_removed "$rem_summary"
    fi

    # install
    if test -s "$inst_user_summary"; then
	verbose 'The following packages will be installed'
	summary2packages_to_be_installed "$inst_user_summary"
    fi

    # install as a depdendency
    if test -s "$inst_auto_summary"; then
	verbose 'The following packages will be installed as auto-installed'
	summary2packages_to_be_installed_as_dep "$inst_auto_summary"
    fi

    #
    if ! yesno install 'Proceed'; then
	exit 0
    fi

    # creating cache dir
    if test -s "$inst_user_summary" -o -s "$inst_auto_summary"; then
	create_cachedir
	cd "$CACHEDIR"
    fi

    ##################################################################
    ########################## installing ############################
    summary2binary "$inst_user_summary" "$inst_auto_summary" |
    awk 'NR==1 {print "Downloading binary packages..." > "/dev/stderr"} {print}' |
    download_pkgs

    if test -n "$download_only"; then
	exit 0
    fi

    #
    rem_pkgs="$tmp_dir/extra_rem"
    sed -n 's/^PKGNAME=//p' "$rem_summary" > "$rem_pkgs"

    # Ensure that packages that will be be installed do not have
    # common files
    if test -n "$plist_check" -a -s "$inst_all_pkgs_fn"; then
	verbose "Analysing PLIST..."
#	plist_summary="$tmp_dir/plist_summary"
	toinst_plist_summary="$tmp_dir/toinst_plist_summary"
	inst_plist_summary="$tmp_dir/inst_plist_summary"
	conflicts_fn="$tmp_dir/conflicts"

	# packages to install
	awk '{print $0 '"\"$SUFX\"}" "$inst_all_pkgs_fn" |
	xargs pkg_bin_summary -e -f PKGNAME,PKGPATH,PLIST > "$toinst_plist_summary"

	# installed packages
	pkg_grep_summary -f PKGNAME,PKGPATH,PLIST -v \
	    -t strfile PKGNAME "$rem_pkgs" < "$installed_summary" |
	sed 's/^PKGPATH=/PKGPATH=nih-installed-/' > "$inst_plist_summary"

	#
	pkg_uniq_summary -Fb "$toinst_plist_summary" "$inst_plist_summary" |
	pkg_lint_summary -f

	#
	pkg_lint_summary -f "$toinst_plist_summary" \
	    "$inst_plist_summary" > "$conflicts_fn" || true

	repl_fake_fn="$tmp_dir/repl_fake"
	/usr/pkg/libexec/nih/analyse_conflicts "$conflicts_fn" |
	sort -u > "$repl_fake_fn"

	while read p; do
	    ver=`pkg_grep_summary -f PKGNAME -s PKGBASE "$p" < "$inst_plist_summary" | sed -n 's/^PKGNAME=.*-//p'`
	    verbose "Replacing $p with fake package $p-$ver..."
	    test -n "$ver"
	    save_installed_packages
	    replace_with_fake "$p" "$ver"
	done < "$repl_fake_fn"
    fi

    # installation
    save_installed_packages

    if test -s "$rem_summary"; then
	pkg_summary2deps -drt "$installed_summary" | tsort |
	grep_lines "$rem_pkgs" - | rev_lines |
	while read pkg; do
	    verbose "Removing package $pkg..."
	    $PKG_DELETE_CMD -f $pkg_opts "$pkg"
	done
    fi

    pkg1_fn="$tmp_dir/pkg1"

    pkg_summary2deps -ndtr "$plan_summary" | tsort |
    grep_lines "$inst_all_pkgs_fn" |
    while read pkg; do
	cat "$inst_auto_summary" "$inst_user_summary" |
	pkg_grep_summary -r -s PKGNAME "$pkg" > "$pkg1_fn"

	fn_pkg=`summary2binary "$pkg1_fn" | awk '{sub(/^[^\/.].*\//, "", $2); print $2}'`

	if grep ^automatic= "$pkg1_fn" > /dev/null; then
	    verbose "Installing package $pkg as a dependency..."
	    if ! $PKG_ADD_CMD $pkg_opts -UDA "$fn_pkg"; then
		cleanup_db; exit 1
	    fi
	elif test -n "$fn_pkg"; then
	    verbose "Installing package $pkg..."
	    if $PKG_ADD_CMD $pkg_opts -UD "$fn_pkg" &&
	       $PKG_ADMIN_CMD unset automatic "$pkg"
	    then
		:
	    else
		cleanup_db; exit 1
	    fi
	fi

	if grep try_out "$pkg1_fn" > /dev/null; then
	    verbose "Marking package $pkg as 'try_out'..."
	    if ! $PKG_ADMIN_CMD set try_out=yes "$pkg"; then
		cleanup_db; exit 1
	    fi
	fi
    done
}

######################################################################
######################################################################
######################################################################
# uninstall | remove | delete
usage_uninstall (){
    cat <<'EOF'
nih uninstall -h
nih uninstall [options] <packages>

      Remove specified packages, dependent packages and automatically installed
      leaf packages. These three command are the same.

   options:
      -h     display this help message

      -n     don't actually deinstall a package, just report the steps that
             would be taken if it were.

      -l     do not remove auto-installed leaf packages
      -L     remove auto-installed leaf packages (the default)

      -p     disable REQUIRES/PROVIDES consistency check
      -P     enable REQUIRES/PROVIDES consistency check (the default)

      -t     remove "try out" packages
Examples:
   nih uninstall libgnome python26 kdelibs4
EOF
}

do_uninstall() {
    while getopts hnlLpPt f; do
	case "$f" in
	    '?')
		exit 1;;
	    n|l|L|p|P)
		pkg_delete_opts="$pkg_delete_opts -$f";;
	    h)
		usage_uninstall
		exit 0;;
	    t)
		try_out=1;;
	    *)
		opts="${opts}$f";;
	esac
    done
    shift `expr $OPTIND - 1`

    if test -n "$try_out"; then
	tryout_pkgs="$tmp_dir/tryout"
	refresh_inst_summary
	pkg_grep_summary -s try_out yes < "$PKG_INST_SUMMARY" |
	awk '/^PKGNAME=/ {print substr($0, 9) "--"}' > "$tryout_pkgs"
	plan=`cat "$tryout_pkgs"`
    fi

    for i in "$@"; do
	plan="$plan $i--"
    done

    $0 $nih_opts install $pkg_delete_opts $plan
}

######################################################################
######################################################################
######################################################################
# verify
usage_verify (){
    cat <<'EOF'
nih verify -h
nih verify -d|-c|-l|-L
nih verify -m|-s [PKGBASEs]

      Verify installed or listed packages.
      By default all installed packages are verified.

   options:
       -h     display this help message
       -m     check the checksums of installed files
       -l|-U  check the REQUIRES/PROVIDES coherence
       -L     check the existence of library files listed in REQUIRES
       -d     check the presence of dependencies
       -c     check the conflicts
       -s     compare `uname -r` and OS_VERSION from PKGBASEs
Examples:
   nih verify -dclL
   nih verify -m
   nih verify -m 'lib*' gawk
   nih verify -s
EOF
}

do_verify() {
    opts='k'
    while getopts hmUlLdcs f; do
	case "$f" in
	    '?')
		exit 1;;
	    h)
		usage_verify
		exit 0;;
	    l)
		opts="${opts}U";;
	    s)
		opts="${opts}S";;
	    *)
		opts="${opts}$f";;
	esac
    done
    shift `expr $OPTIND - 1`

    if test k = "$opts"; then
	opts="kdU"
    fi

    pkg_status "-$opts" "$@"
}

######################################################################
######################################################################
######################################################################
# status
usage_status (){
    cat <<'EOF'
nih status -h
nih status [-b] [-a|-u] [-r|-q|-A|-B] [PKGBASEs]
nih status -p           [-r|-q|-A|-B] [PKGBASEs]

      Compare installed packages with pkg_bin_summary.txt (default) or
      pkgsrc tree.

   options:
      -h     display this help message

      -b     compare installed packages against pkg_bin_summary.txt (default)
      -p     compare installed packages against pkgsrc tree

      -u     analyse packages installed by user (the default, see -a)
      -a     analyse all installed packages (see -u)

      -r     raw output (pkg_summary format)
      -B     consider packages different if their BUILD_DATE differ
      -A     by default up-to-date packages are skipped,
             with -A they are output too
      -q     no noisy reminder about output format

Examples:
   nih status -a
   nih status -uq
   nih status -pqr pkglint
EOF
}

do_status() {
    opts='b'
    while getopts hbpruaqAB f; do
	case "$f" in
	    '?')
		exit 1;;
	    h)
		usage_status
		exit 0;;
	    a)
		opts="${opts}aT";;
	    p)
		opts="${opts}s";;
	    *)
		opts="${opts}$f";;
	esac
    done
    shift `expr $OPTIND - 1`

    if echo "$opts" | grep 'b[^s]*$' > /dev/null; then
	refresh_tiny_bin_summary
	export PKG_SUMMARY="$PKG_TINY_BIN_SUMMARY"
    fi

    pkg_status "-$opts" "$@"
}

######################################################################
######################################################################
######################################################################
# search
usage_search (){
    cat <<'EOF'
nih search [options] [args]

      Search for packages

   options:
      -h    display this help message

      -b    search in binary repository (pkg_summary(5), the default)
      -o    search in pkg_online database
      -i    search in installed packages
      -p    search in pkg_src_summary file

      -s    output search strategies
      -f    output search fields

      -1    display 1-line information about packages (default)
      -3    display short information about packages
      -9    display full information about packages
      -l    display PKGNAME, PKGPATH and file list

      -r    raw summaries are output
      -q    quiet mode, do not print "No matches found" to stderr

See pkg_digger(1) for details.

Examples:
   nih search -h
   nih search -s
   nih search -f
   nih search DEPENDS:substring:libmaa
   nih search PKGNAME:prefix:dict
   nih search COMMENT:word:dns PKGPATH:prefix:net
   nih search c:kw:'dns server'
   nih search n:p:lua
   nih search FILE_SIZE:awk:'fvalue+0 > 100000000'
   nih search -9 lang/mawk
   nih search -os
   nih search -of
   nih search -o oberon
   nih search -o dns server
   nih search -o3 dictionary -spell -japanese -chinese -korean
   nih search -o -q9r spreadsheet
EOF
}

# info
usage_info (){
    cat <<'EOF'
nih info [options] PKGBASE [PKGBASEs...]

      Show information about packages

   options:
      -h    display this help message

      -b    search in binary repository (pkg_summary(5), the default)
      -o    search in pkg_online database
      -i    search in installed packages
      -p    search in pkg_src_summary file

      -1|-3|-9|-l      the same as in 'nih search', the default is -3

      -r    raw summaries are output
      -q    quiet mode, do not print "No matches found" to stderr

See pkg_digger(1) for details.

Examples:
   nih info -h
   nih info runawk
   nih info -i runawk
   nih info -o oberon
EOF
}

# meta
usage_meta (){
    cat <<'EOF'
nih meta [options]

      List meta packages

   options:
      -h    display this help message

      -b    search in binary repository (pkg_summary(5), the default)
      -o    search in pkg_online database
      -i    search in installed packages
      -p    search in pkg_src_summary file

      -1|-3|-9     the same as in 'nih search' but works only with -r,
                   the default is -1.

      -r    raw summaries are output
      -q    quiet mode, do not print "No matches found" to stderr

See pkg_digger(1) for details.

Examples:
   nih meta -h
   nih meta
   nih meta -ir
   nih meta -o
EOF
}

do_search_info_meta() {
    op="$1"
    shift

    set_pkg_summary
    export PKG_DIGGER_BACKEND=pkg_digger_summary
    export PKG_DIGGER_SUMMARY="$PKG_SUMMARY"

    _summary2filelist=cat
    _summary2meta_packages=summary2meta_packages

    case "$op" in
	meta) set -- $NIH_META_OPTS "$@";;
	info) set -- $NIH_INFO_OPTS "$@";;
	search) set -- $NIH_SEARCH_OPTS "$@";;
    esac

    nih_refresh_opt=''

    while getopts hboiIpsf139rql f; do
	case "$f" in
	    '?')
		exit 1;;
	    b)
		export PKG_DIGGER_BACKEND=pkg_digger_summary
		export PKG_DIGGER_SUMMARY="$PKG_SUMMARY";;
	    o)
		export PKG_DIGGER_BACKEND=pkg_online_client;;
	    i|I)
		export PKG_DIGGER_BACKEND=pkg_digger_summary #pkg_digger_installed
		export PKG_DIGGER_SUMMARY="$PKG_INST_SUMMARY";;
	    p)
		nih_refresh_opt='-p'
		export PKG_DIGGER_BACKEND=pkg_digger_summary
		export PKG_DIGGER_SUMMARY="$PKG_SRC_SUMMARY";;
	    l)
		_summary2filelist=summary2filelist
		opts="${opts}r9";;
	    r)
		_summary2meta_packages=cat
		opts="${opts}r";;
	    h)
		usage_$op
		exit 0;;
	    *) # -1 -3 -9 -s -f -q -l
		opts="${opts}$f";;
	esac
    done
    shift `expr $OPTIND - 1`

    if test "$PKG_DIGGER_SUMMARY" = "$PKG_INST_SUMMARY"; then
	refresh_inst_summary
    fi
    if test "$PKG_DIGGER_BACKEND" = 'pkg_digger_summary'; then
	summary_exists "$PKG_DIGGER_SUMMARY" "$nih_refresh_opt"
    fi

    case "$op" in
	info)
	    if test $# -eq 0; then
		verbose 'No packages were specified'
		usage_info
		exit 1
	    fi

	    for i in "$@"; do
		pkg_digger -3 ${opts+-$opts} PKGBASE:exact:"$i"
	    done | $_summary2filelist;;
	meta)
	    pkg_digger -1r ${opts+-$opts} CATEGORIES:word:meta-pkgs |
	    $_summary2meta_packages;;
	*)
	    pkg_digger ${opts+-$opts} "$@" | $_summary2filelist;;
    esac
}

######################################################################
######################################################################
######################################################################
# leaf
usage_leaf (){
    cat <<'EOF'
nih leaf [options]

      Output or remove automatically installed leaf packages,
      by default list of leaves is output.

   options:
      -h    display this help message

      -a    output all leaf packages
      -u    output leaf packages installed by user
      -A    output auto-installed leaf packages (the default)

      -R    remove auto-installed leaf packages
      -n    pass -n to pkg_delete
      -r    raw output (pkg_summary format)

Examples:
   nih leaf
   nih leaf -a
   nih leaf -Rn
EOF
}

do_leaf() {
    opts=''
    pkg_delete_opts=''

    leaves_opts='-a'
    while getopts hauARnr f; do
	case "$f" in
	    '?')
		exit 1;;
	    h)
		usage_leaf
		exit 0;;
	    A)
		unset remove || true
		leaves_opts='-a';;
	    a)
		unset remove || true
		leaves_opts='';;
	    u)
		unset remove || true
		leaves_opts='-u';;
	    R)
		opts='-r'
		remove=1;;
	    n)
		pkg_delete_opts=-n;;
	    r)
		raw=1;;
	esac
    done
    shift `expr $OPTIND - 1`

    refresh_inst_summary

    verbose "Finding leaf packages..."

    leaves_fn="$tmp_dir/leaves"
    pkg_summary2leaves $leaves_opts "$PKG_INST_SUMMARY" > "$leaves_fn"

    if test -n "$remove"; then
	pkgnames=`sed -n 's/^PKGNAME=//p' "$leaves_fn"`
	$0 $nih_opts remove $pkg_delete_opts $pkgnames
    elif test -z "$raw"; then
	summary2leaf_packages "$leaves_fn"
    else
	cat "$leaves_fn"
    fi
}

######################################################################
######################################################################
######################################################################
# list
usage_list (){
    cat <<'EOF'
nih list [options]

      Output available or installed packages.

   options:
      -h       display this help message

      -b       output packages from binary repository (the default)
      -p       output packages from pkg_src_summary file
      -i       output installed packages

      -a       output all installed packages (implies -i)
      -u       output packages installed by user (implies -i)
      -t       output installed packages marked as "try out" (implies -i)
      -k       output installed packages marked as "keep" (implies -i)
      -f       output installed packages marked as "fake" (implies -i)

      -r       output raw summaries

Examples:
   nih list -h
   nih list
   nih list -t
   nih list -ur
EOF
}

do_list() {
    _cat_summary=cat_bin_summary
    _summary4view=summary2available_packages
    _grep_summary=cat

    while getopts hbauiIrtkfp f; do
	case "$f" in
	    '?')
		exit 1;;
	    h)
		usage_list
		exit 0;;
	    b)
		_summary4view=summary2available_packages
		_cat_summary=cat_bin_summary
		_grep_summary=cat;;
	    a|i|I)
		_summary4view=summary2installed_packages
		_cat_summary=cat_inst_summary
		_grep_summary=cat;;
	    p)
		_summary4view=summary2available_packages
		_cat_summary=cat_src_summary
		_grep_summary=cat;;
	    t)
		_summary4view=summary2installed_packages
		_cat_summary=cat_inst_summary
		_grep_summary='pkg_grep_summary -s try_out yes';;
	    k)
		_summary4view=summary2installed_packages
		_cat_summary=cat_inst_summary
		_grep_summary='pkg_grep_summary -s keep yes';;
	    f)
		_summary4view=summary2installed_packages
		_cat_summary=cat_inst_summary
		_grep_summary="pkg_grep_summary -s COMMENT 'fake package'";;
	    u)
		_summary4view=summary2installed_packages
		_cat_summary=cat_inst_summary
		_grep_summary='pkg_grep_summary -e automatic';;
	    r)
		_summary4view=cat;;
	esac
    done
    shift `expr $OPTIND - 1`

    $_cat_summary | { eval $_grep_summary; } | $_summary4view
}

######################################################################
######################################################################
######################################################################
# mark
usage_mark (){
    cat <<'EOF'
nih mark [options] PKGBASEs

      Mark packages

   options:
      -h       display this help message

      -n       don't actually install a package, just report the steps that
               would be taken if it were.

      -u/-U    mark/unmark packages as "installed by user"
      -a/-A    mark/unmark packages as "installed as a dependency"
      -t/-T    mark/unmark packages as "try out"
      -k/-K    mark/unmark packages as "keep"

NOTES:
   -a and -U are synonyms
   -u and -A are synonyms
Examples:
   nih mark -a libmaa
   nih mark -u ffmpeg                    # we use ffmpeg(1)
   nih mark -t wmii openbox fluxfox sawfish xfce4
   nih mark -Tu ctwm                     # my primary window manager
   nih mark -k lua-mode ruby-mode dictem # manually built for emacs-22
EOF
}

do_mark() {
    opts='l'
    while getopts aAhkKntTuU f; do
	case "$f" in
	    '?')
		exit 1;;
	    h)
		usage_mark
		exit 0;;
	    u|A)
		mode_u=1;;
	    a|U)
		mode_a=1;;
	    t)
		mode_t=1;;
	    T)
		mode_T=1;;
	    k)
		mode_k=1;;
	    K)
		mode_K=1;;
	    n)
		exec_prefix='verbose Running command:';;
	    *)
		opts="${opts}$f";;
	esac
    done
    shift `expr $OPTIND - 1`

    if test $# -eq 0; then
	errmsg 'No packages were specified'
	exit 1
    fi

    if test -z "$mode_u$mode_a$mode_t$mode_T$mode_k$mode_K"; then
	pkg_bin_summary -e -a try_out,keep,automatic "$@" |
	summary2markers
	exit 0
    elif test "$mode_u$mode_a$mode_t$mode_T$mode_k$mode_K" != 1; then
	errmsg 'Only one option is allowed'
	exit 1
    elif test -n "$mode_a"; then
	cmd='set automatic=yes'
    elif test -n "$mode_u"; then
	cmd='unset automatic'
    elif test -n "$mode_t"; then
	cmd='set try_out=yes'
    elif test -n "$mode_T"; then
	cmd='unset try_out'
    elif test -n "$mode_k"; then
	cmd='set keep=yes'
    elif test -n "$mode_K"; then
	cmd='unset keep'
    fi

    save_installed_packages
    $exec_prefix touch $pkgdb
    $exec_prefix $PKG_ADMIN_CMD $cmd "$@"
}

######################################################################
######################################################################
######################################################################
# deps
usage_deps (){
    cat <<'EOF'
nih deps [options] [pkgs...]

      Output a dependency graph of installed packages or remote summary.

   options:
      -h    display this help message

      -b    analyse packages from binary repository (the default)
      -p    analyse packages from pkg_src_summary file
      -i    analyse installed packages

      -r    output dependencies, by default packages
            that depend on "pkgs" are output.

      -l    output list of packages instead of graph
      -B    output PKGBASE (the default)
      -P    output PKGPATH
      -N    output PKGNAME

      -t    output dependencies in tsort(1) compatible format
If both -P and -N are applied, PKGPATH;PKGNAME is output,
if both -P and -B are applied, PKGPATH;PKGBASE is output.

Examples:
   nih deps
   nih deps -P
   nih deps -PB
   nih deps glib2
   nih deps -r glib2
   nih deps -ri pkgnih
   nih deps -lBP 'devel/glib2;glib2' 'devel/libmaa;libmaa'
   nih deps -P devel/glib2 devel/libmaa
EOF
}

do_deps() {
    sb_opts=''
    pn_opts=''
    _cat_summary=cat_bin_summary
    while getopts hrlBNPtiIbp1 f; do
	case "$f" in
	    '?')
		exit 1;;
	    h)
		usage_deps
		exit 0;;
	    r)
		sb_opts="$sb_opts -r";;
	    l)
		sb_opts="$sb_opts -n";;
	    t)
		pn_addon_opts="$pn_addon_opts -t"
		sb_opts="$sb_opts -t";;
	    1)
		sb_opts="$sb_opts -1";;
	    B)
		pn_opts="$pn_opts -n";;
	    N)
		pn_opts="$pn_opts -nr";;
	    P)
		pn_opts="$pn_opts -p";;
	    i|I)
		_cat_summary=cat_inst_summary;;
	    b)
		_cat_summary=cat_bin_summary;;
	    p)
		_cat_summary=cat_src_summary;;
	esac
    done
    shift `expr $OPTIND - 1`

    pn_opts="${pn_opts:--n}${pn_addon_opts}"

    $_cat_summary |
    pkg_summary2deps -d $pn_opts 2>/dev/null |
    if test $# -eq 0; then
	sort -u
    else
	pkg_subgraph_deps $sb_opts -p "$*" | sort -u
    fi
}

######################################################################
######################################################################
######################################################################
# clean-cache
usage_clean_cache (){
    cat <<'EOF'
nih clean-cache [options] [pkgs...]

      Delete binaries in cache directory. By default all binaries
      except those of installed packages are removed.

   options:
      -h    display this help message
      -n    output files to be removed
      -a    remove all binaries
      -i    binaries for packages listed in /var/cache/nih/summary/installed_*
            are not removed.

Examples:
   nih clean-cache
   nih clean-cache -n
   nih clean-cache -a
EOF
}

do_clean_cache() {
    while getopts hnai f; do
	case "$f" in
	    '?')
		exit 1;;
	    h)
		usage_clean_cache
		exit 0;;
	    n)
		noop=1;;
	    a)
		rem_all=1;;
	    i)
		keep_installed=1;;
	esac
    done
    shift `expr $OPTIND - 1`

    create_cachedir
    cd "$CACHEDIR"

    present=$tmp_dir/present
    installed=$tmp_dir/installed
    del=$tmp_dir/del

    if test -n "$rem_all"; then
	printf '' > "$installed"
    else
	{
	    pkg_info -Xa

	    if test -n "$keep_installed"; then
		find "$HISTORYDIR" -name 'installed_*.txt' | xargs cat
		find "$HISTORYDIR" -name 'installed_*.bz2' | xargs bzip2 -dc
	    fi
	} | summary2binary | awk '{print $1}' | sort -u > "$installed"
    fi

    ls -1 | grep "$SUFX"'$' | sort > "$present"

    comm -13 "$installed" "$present" > "$del"
    if test -s "$del"; then
	verbose 'The following files will be deleted'
	indent "$del"
	if test -n "$noop" || ! yesno remove_pkgs 'Proceed'; then
	    exit 0
	fi

	xargs rm -f < "$del"
    else
	verbose 'No binaries to remove'
	exit 0
    fi
}

######################################################################
######################################################################
######################################################################
# audit
usage_audit (){
    cat <<'EOF'
nih audit [options] [pkgs...]

      Download a new pkg-vulnerabilities file and check the listed
      installed packages for vulnerabilities. If no package is given,
      check all installed packages.

   options:
      -h    display this help message
      -d    download a new pkg-vulnerabilities file,
            do not check packages
      -c    check the packages for vulnerabilities
            using existing pkg-vulnerabilities file

Examples:
   nih audit -h
   nih audit
   nih audit -d
   nih audit -c 'ffmpeg*'
EOF
}

do_audit() {
    check=1
    download=1

    while getopts hdc f; do
	case "$f" in
	    '?')
		exit 1;;
	    h)
		usage_audit
		exit 0;;
	    d)
		check=;;
	    c)
		download=;;
	esac
    done
    shift `expr $OPTIND - 1`

    if test -n "$download"; then
	verbose 'Downloading new pkg-vulnerabilities file'
	$PKG_ADMIN_CMD fetch-pkg-vulnerabilities -u
    fi
    if test -n "$check"; then
	verbose 'Checking packages for vulnerabilities'
	$PKG_ADMIN_CMD audit "$@"
    fi
}

######################################################################
######################################################################
######################################################################
# history
usage_history (){
    cat <<'EOF'
nih history [options] [pkgs...]

      Output installed, removed or updated packages last nih sessions
      using saved ~/.nih/installed_*.txt files.

   options:
      -h           display this help message
      -B           consider packages different if their BUILD_DATE differ
      -p <pager>   output history to the specified "more"-like pager program
Examples:
   nih history -h
   nih history
   nih history -B
EOF
}

do_history_show() {
    # $1 -- summary_new filename
    # $2 -- summary_old filename
    pkg_cmp_summary -EA automatic,try_out,keep $cmp_summary_opts -p \
		    "$1" "$2" |
	awk '$1 != "="' | /usr/pkg/libexec/nih/cmp2hr
}

do_history() {
    nih_pager='cat'
    while getopts Bhp: f; do
	case "$f" in
	    '?')
		exit 1;;
	    h)
		usage_history
		exit 0;;
	    B)
		cmp_summary_opts=-b;;
	    p)
		nih_pager="$OPTARG";;
	esac
    done
    shift `expr $OPTIND - 1`

    if test -n "$NO_INSTALLED_COPY"; then
	verbose "Warning: NO_INSTALLED_COPY is set in config file!"
    fi

    refresh_inst_summary

    last_fn="$PKG_INST_SUMMARY"
    last=current

    unpacked1_fn="$tmp_dir/unpacked1.txt"
    unpacked2_fn="$tmp_dir/unpacked2.txt"

    ( cd "$HISTORYDIR"; ls -1r installed_????????_????.* 2>/dev/null; ) |
	{
	    while read curr_fn; do
		curr=`echo $curr_fn | awk 'match($0, /[0-9].*[0-9]/) {
		    print substr($0, RSTART, 4) "-" substr($0, RSTART+4, 2) "-" substr($0, RSTART+6, 2) " " substr($0, RSTART+9, 2) ":" substr($0, RSTART+11, 2)}'`
		verbose "$curr vs. $last" 2>&1

		if echo "$curr_fn" | grep '[.]bz2' > /dev/null; then
		    if test "$last_fn" = "$unpacked2_fn"; then
			tmp_fn="$unpacked1_fn"
		    else
			tmp_fn="$unpacked2_fn"
		    fi
		    bzip2 -dc "$HISTORYDIR/$curr_fn" > "$tmp_fn"
		    curr_fn="$tmp_fn"
		fi

		do_history_show "$curr_fn" "$last_fn"

		last="$curr"
		last_fn="$curr_fn"
	    done
	    verbose "0 A.D. vs. $last" 2>&1
	    do_history_show /dev/null "$last_fn"
	} |
    eval "$nih_pager"
}

######################################################################
######################################################################
######################################################################
# main code

check_for_obsolete_dotnih(){
    if test -d "$HOME"/.nih; then
	cat <<EOF
$HOME/.nih directory is obsolete.
  Move installed_*.* files to $HISTORYDIR directory.
  Move pkg_src_summary_* files to $PKG_SRC_SUMMARY_DIR directory.
  Move other summary_* files to $PKG_SUMMARY_DIR directory.
  Move downloaded packages to $CACHEDIR directory.
EOF
	exit 1
    fi
}

check_for_obsolete_dotnih

# handling global options
process_options (){
    alt_getopt \
	h     'usage; exit 0' \
	V     'verbose nih-0.16.0; exit 0' \
	y     'set_yes_var install; set_yes_var remove_pkgs' \
	=Y    'set_yes_var ' \
	D     'debug=1' \
	=s    'summary=' \
	-- $NIH_OPTS "$@"
}

PKG_INST_SUMMARY="$PKG_SUMMARY_DIR/pkg_inst_summary.txt"
PKG_SRC_SUMMARY="$PKG_SRC_SUMMARY_DIR/pkg_src_summary.txt"

export PKGSRCDIR PSS_SLAVES PSS_OPTIONS

cmds=`process_options "$@"`
eval "$cmds"

. /usr/pkg/libexec/psu/sig_handler.sh
tmp_dir=`mktemp -d /tmp/nih.XXXXXX`
test -n "$tmp_dir" || exit 1

fake_pkg_fn="$tmp_dir/fake.tgz"

# update PKG_INST_SUMMARY for unprivileged user
if ! touch "$PKG_INST_SUMMARY.tmp" 2>/dev/null; then
    PKG_INST_SUMMARY="$tmp_dir/pkg_inst_summary.tmp"
fi

# update PKG_TINY_INST_SUMMARY for unprivileged user
if ! touch "$PKG_TINY_INST_SUMMARY.tmp" 2>/dev/null; then
    PKG_TINY_INST_SUMMARY="$tmp_dir/pkg_tiny_inst_summary.tmp"
fi

#
if test $# -eq 0; then
    usage
    exit 1
fi

# -s
if test -n "$summary"; then
    case "$summary" in
	*.gz)
	    PKG_SUMMARY="$tmp_dir/pkg_bin_summary.txt"
	    gzip -dc "$summary" > "$PKG_SUMMARY";;
	*.bz2)
	    PKG_SUMMARY="$tmp_dir/pkg_bin_summary.txt"
	    bzip2 -dc "$summary" > "$PKG_SUMMARY";;
	*)
	    PKG_SUMMARY="$summary"
    esac
fi

case "$1" in
    help)
	usage
	exit 0;;
    refresh)
	shift
	set -- $NIH_REFRESH_OPTS "$@"

	do_refresh "$@"

	exit 0;;
    install|update)
	shift
	set -- $NIH_INSTALL_OPTS "$@"

	do_install "$@"

	exit 0;;
    uninstall|remove|delete)
	shift
	set -- $NIH_UNINSTALL_OPTS "$@"

	do_uninstall "$@"

	exit 0;;
    verify)
	shift
	set -- $NIH_VERIFY_OPTS "$@"

	do_verify "$@"

	exit 0;;
    status)
	shift
	set -- $NIH_STATUS_OPTS "$@"

	do_status "$@"

	exit 0;;
    search|info|meta)
	do_search_info_meta "$@"
	exit 0;;
    leaf)
	shift
	set -- $NIH_LEAF_OPTS "$@"

	do_leaf "$@"

	exit 0;;
    list)
	shift
	set -- $NIH_LIST_OPTS "$@"

	do_list "$@"

	exit 0;;
    mark)
	shift
	set -- $NIH_MARK_OPTS "$@"

	do_mark "$@"

	exit 0;;
    deps)
	shift
	set -- $NIH_DEPS_OPTS "$@"

	do_deps "$@"

	exit 0;;
    clean-cache)
	shift
	set -- $NIH_CLEAN_CACHE_OPTS "$@"

	do_clean_cache "$@"

	exit 0;;
    audit)
	shift
	set -- $NIH_AUDIT_OPTS "$@"

	do_audit "$@"

	exit 0;;
    history)
	shift
	set -- $NIH_HISTORY_OPTS "$@"

	do_history "$@"

	exit 0;;
    *)
	errmsg "Unknown command '$1', see 'nih -h' for more information"
	exit 2
esac
