#!/bin/sh
#-*-mode:  sh -*-

# Copyright (c) 2010-2014, 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.

############################################################
# user settable variables

PKG_SRC_SUMMARY_CMD=${PKG_SRC_SUMMARY_CMD-pkg_micro_src_summary}
PKG_SUMMARY=${PKG_SUMMARY-/usr/pkgsrc/packages/All/pkg_summary.txt}

############################################################
set -e

. pipestatus

export LC_ALL=C

############################################################
PKG_INFO_CMD="${PKG_INFO_CMD-/usr/pkg/sbin/pkg_info}"
PKG_DELETE_CMD="${PKG_DELETE_CMD-/usr/pkg/sbin/pkg_delete}"
PKG_ADMIN_CMD="${PKG_ADMIN_CMD-/usr/pkg/sbin/pkg_admin}"

############################################################
usage (){
    cat 1>&2 <<EOF
pkg_status - checks the status of installed packages, for example,
compares installed packages with pkgsrc tree or pkg_summary

usage:
  pkg_status -h
  pkg_status -V
  pkg_status -s [-ruaTAqDp0] [ [--] pkg_info_args ]
  pkg_status -b [-ruaTAqDBN] [ [--] pkg_info_args ]
  pkg_status -k [-mULdS]     [ [--] pkg_info_args or pkg_admin_args ]

OPTIONS:
  -h         display this help message
  -V         display version
  -r         raw output
  -D         for debugging purposes, temp files are not removed
OPTIONS common for -s and -b:
  -s         compare against pkgsrc source tree
  -b         compare against pkg_summary(5) database,
             a path to which is stored in PKG_SUMMARY environment variable
  -u         analyse packages marked as installed by user (the default, see -a)
  -a         analyse all installed packages (see -u)
  -A         by default up-to-date packages are skipped,
             with -A they are output too
  -q         No noisy reminder about output format
  -T         Output U near comparison result for packages installed by user
OPTIONS for -b:
  -B         consider packages different if their BUILD_DATE differ
OPTIONS for -s:
  -p <file>  get PKGPATHs from <file> (instead of pkg_info -QPKGPATH).
  -0         use all available CPUs for obtaining pkg_src_summary.
OPTIONS for -k:
  -m         check checksums of installed files (pkg_admin check)
  -U         check for REQUIRES/PROVIDES coherence (pkg_lint_summary -l)
  -L         check existence of files listed in REQUIRES (pkg_lint_summary -L)
  -d         check for presense of dependencies (pkg_lint_summary -d)
  -S         check for OS_VERSION of installed packages (pkg_lint_summary -s)
Environment:
   PKG_STATUS_OPTIONS - additional options passed to pkg_status
   PKG_SUMMARY      - path to pkg_summary.{txt,gx,bz2},
                      it defaults to /usr/pkgsrc/packages/All/pkg_summary.txt
   PKG_SRC_SUMMARY_CMD - command for generating micro summary,
                      it defaults to pkg_micro_src_summary,
                      reasonable (but slower) alternative is pkg_src_summary.
   PSS_SLAVES       - If you want to use all available CPUs, i.e. run several
                      subprocesses in parallel, set this variable to +<cpu_cnt>.
                      For details, see pkg_src_summary(1). On NetBSD, FreeBSD,
                      OpenBSD, DragonFlyBSD and Linux PSS_SLAVES defaults
                      a number of available CPUs and therefore significantly
                      speeds up 'pkg_status -s'.
   PSS_TRANSPORT    - See pkg_src_summary(1)
   PSS_PPERS        - See pkg_src_summary(1)
Examples:
   pkg_status -h
   pkg_status -V

   pkg_status -s
   pkg_status -saq0
   env PSS_SLAVES=+8 pkg_status -sra
   pkg_status -s pkg_install bmake bootstrap-mk-files
   pkg_status -s -- -K ~/pkg_distbb/var/db/pkg -a
   pkg_status -srA -- pkg_summary-utils
   env PKG_STATUS_OPTIONS=-0 pkg_status -sp /etc/interesting_pkgs.conf

   pkg_status -b
   pkg_status -bA
   pkg_status -bar

   pkg_status -lRn

   pkg_status -km
   pkg_status -kUdS
EOF
}

pkg_cmp_opts='-p'

opt_u=
opt_a=

process_args (){
    while getopts hVsbrAuaQqDp:0kmULcdltRnTBS f; do
	case $f in
	    h)    usage; exit 0;;
	    V)    echo 'pkg_status 0.15.2'; exit 0;;
	    s)    mode_src=1; mode_bin=;;
	    b)    mode_bin=1; mode_src=;;
	    r)    submode_raw=1;;
	    A)    submode_all=1;;
	    u)    unset opt_a; opt_u=1;;
	    a)    unset opt_u; opt_a=1;;
	    T)    pkg_cmp_opts="$pkg_cmp_opts";;
	    B)    pkg_cmp_opts="$pkg_cmp_opts -b";;
	    Q)    quiet=1; echo '-Q is deprecated, please use -q' 1>&2;;
	    q)    quiet=1;;
	    D)    debug=1;;
	    p)    pkgpaths_fn=$OPTARG;;
	    0)    all_cpus=1;;
	    k)    mode_checks=1;;
	    m)    check_checksums=1;;
	    U)    check_reqs_provs=1;;
	    L)    check_reqs_exist=1;;
	    c)    check_conflicts=-c;;
	    d)    check_deps=-d;;
	    S)    os_ver=1;;
	    '?')  exit 1;;
	esac
    done
}

process_args $PKG_STATUS_OPTIONS
process_args "$@"
shift `expr $OPTIND - 1`

######################################################################
#
bsd_xargs (){
    if read a; then
	{ printf '%s\n' "$a"; cat; } | xargs "$@"
    fi
}

# Adjust PSS_SLAVES
if test -n "$all_cpus"; then
    case `uname -s` in
	NetBSD|FreeBSD|OpenBSD|DragonFly)
	    PSS_SLAVES=+`sysctl hw.ncpu | awk '/hw.ncpu/ {print $3}'`;;
	Linux)
	    PSS_SLAVES=+`cat /proc/cpuinfo | awk '/processor/ {++cnt} END {print cnt}'`;;
    esac
    export PSS_SLAVES
fi

if test "$PSS_SLAVES" = +1 -o "$PSS_SLAVES" = ''; then
    unset PSS_SLAVES || true
fi

######################################################################
# top-level options

# temporarty directory
tmp_dir=`mktemp -d /tmp/pkg_status.XXXXXX`
if test -z "$debug"; then
    trap "rm -rf $tmp_dir" 0 1 2 15
else
    echo "Temp directory: $tmp_dir" 1>&2
fi

test "$tmp_dir" || exit 1

#
if test -z "${mode_src}${mode_bin}${mode_leaves}${mode_checks}"; then
    echo 'Either of the following options must be applied: -s, -b, -k or -l' 1>&2
    exit 1
fi

######################################################################
# -k
if test -n "$mode_checks"; then
    if test -n "$check_checksums"; then
	echo 'Checksums:'
	runpipe_base \
	    $PKG_ADMIN_CMD -qv check "$@" '|' \
	    grep 'fails .* checksum'
	if test "$pipestatus_1" -ne 0 -o "$pipestatus_2" -ne 1; then
	    ex=1
	fi
    fi

    if test -n "$os_ver$check_reqs_provs$check_reqs_exist$check_deps$check_conflicts"
    then
	if test $# -eq 0; then
	    $PKG_INFO_CMD -X -a
	else
	    $PKG_INFO_CMD -X "$@"
	fi > $tmp_dir/bin_summary.txt
    fi

    if test -n "$check_reqs_provs"; then
	echo 'REQUIRES/PROVIDES coherence:'
	pkg_lint_summary -l $tmp_dir/bin_summary.txt || ex=1
    fi

    if test -n "$check_reqs_exist"; then
	echo 'REQUIRES vs. filesystem:'
	pkg_lint_summary -L $tmp_dir/bin_summary.txt || ex=1
    fi

    if test -n "$check_deps$check_conflicts"; then
	echo 'Missing dependencies and conflicts:'
	pkg_lint_summary $check_deps $check_conflicts $tmp_dir/bin_summary.txt || ex=1
    fi

    if test -n "$os_ver"; then
	echo 'OS_VERSION:'
	runpipe_base pkg_lint_summary -s $tmp_dir/bin_summary.txt '|' \
	awk '$1 == "s:" {print $1, $2, $3, $4, $6}'
	if test "$pipestatus_1" -ne 0; then
	    ex=1
	fi
    fi

    exit $ex
fi

######################################################################
# -s|-b

###
if test -n "$submode_raw"; then
    raw2hr (){
	cat
    }
else
    raw2hr (){
	/usr/pkg/libexec/nih/cmp2hr -1
    }
fi

###
if test -n "$submode_all"; then
    skip_uptodate (){
	awk '/^[+] /, NF == 0 {next} {print}'
    }
else
    skip_uptodate (){
	awk '/^[=+] /, NF == 0 {next} {print}'
    }
fi

###
if test -z "$pkgpaths_fn"; then
    pkgpaths_fn=$tmp_dir/pkgs.txt
    generate_pkgpaths (){
	$PKG_INFO_CMD -Q PKGPATH $pkg_info_opts "$@" > "$pkgpaths_fn"
    }
else
    generate_pkgpaths (){
    	true;
    }
fi

###
src_summary_fn=$tmp_dir/src_summary.txt
bin_summary_fn=$tmp_dir/bin_summary.txt
ext_summary_fn=$tmp_dir/ext_summary.txt
res_fn=$tmp_dir/result.txt

show_output_format (){
    if test -z "$quiet" -a -z "$submode_raw"; then
	cat <<EOF
Format of the output:
SIGN PKGPATH PKGBASE VERSION1 [VERSION2]

     SIGN:      newer version is available
            >   only older version is available
            =   the same version is available
            -   package disappeared or was renamed
            +   package has been renamed (also see '-' sign)
            !   packages are different
          [0-9] package is registered at least twice (problem?)
     VERSION1: version of installed package,
     VERSION2: (if any) - version of available package
**********************************************************************
EOF
    fi
}

output_results (){
    if test -s "$res_fn"; then
	show_output_format
	cat "$res_fn"
    else
	echo 'Everything is up-to-date'
    fi
}

# -s|-b
if test -n "$mode_src$mode_bin"; then
    if test $# -ne 0; then
	:
    elif test -z "$opt_u$opt_a" -o -n "$opt_u"; then
	pkg_info_opts=-u
    else
	pkg_info_opts=-a
    fi

    if test -n "$opt_a" -o $# -ne 0; then
	pkg_cmp_opts="$pkg_cmp_opts -Oautomatic,try_out"
    else
	pkg_cmp_opts="$pkg_cmp_opts -Otry_out"
    fi
fi

########################################
# -s
if test -n "$mode_src"; then
    pkg_bin_summary -f PKGPATH,PKGNAME,automatic,try_out -- $pkg_info_opts "$@" \
	> "$bin_summary_fn"
    generate_pkgpaths "$@"

    $PKG_SRC_SUMMARY_CMD < "$pkgpaths_fn" > "$src_summary_fn"
    runpipe0 \
	pkg_cmp_summary $pkg_cmp_opts \
	    "$bin_summary_fn" "$src_summary_fn" '|' \
	skip_uptodate '|' \
	raw2hr > "$res_fn"

    output_results
    exit 0
fi

########################################
# -b
cat_ext_summary (){
    case "$PKG_SUMMARY" in
	*.gz)
	    gzip -dc "$PKG_SUMMARY";;
	*.bz2)
	    bzip2 -dc "$PKG_SUMMARY";;
	*)
	    cat "$PKG_SUMMARY";;
    esac
}

if test -n "$mode_bin"; then
    pkg_bin_summary -f PKGPATH,PKGNAME,automatic,try_out,BUILD_DATE -- $pkg_info_opts "$@" \
	> "$bin_summary_fn"
    cat_ext_summary | pkg_refresh_summary > "$ext_summary_fn"
    runpipe0 \
	pkg_cmp_summary $pkg_cmp_opts \
	    "$bin_summary_fn" "$ext_summary_fn" '|' \
	skip_uptodate '|' \
	raw2hr > "$res_fn"

    output_results
    exit 0
fi
