#!/usr/pkg/bin/runawk

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

############################################################

#env "LC_ALL=C"

#use "pkgsrc-dewey.awk"
#use "power_getopt.awk"
#use "psu_funcs.awk"
#use "has_prefix.awk"
#use "tmpfile.awk"
#use "xclose.awk"
#use "xgetline.awk"

############################################################
#.begin-str help
# pkg_lint_summary - does sanity check for summaries
# usage: pkg_lint_summary -h
#        pkg_lint_summary [OPTIONS] [files...]
# OPTIONS:
#   -h          display this help
#   -l          checks REQUIRES/PROVIDES consistency
#   -L          checks that libs from REQUIRES are present on filesystem
#   -d          checks that dependencies (DEPENDS) are satisfied
#   -D          checks that dependencies (BUILD_DEPENDS) are satisfied
#   -c          checks that summary for packages given on input do not CONFLICTS
#               with each other
#   -f          checks that there are no files common for several packages,
#               PLIST entries are expected in input
#   -u          checks unicity of PKGBASEs
#   -n          checks for missing CONFLICTS by anayling PLIST entries
#   -s          checks OPSYS, OS_VERSION and MACHINE_ARCH fields given on input
#               and warns if they differ from shown by "uname -srm"
#   =p prefix   specify prefix (e.g. /usr/pkg) which is used by -l
#.end-str
############################################################

BEGIN {
	if (getarg("h")){
		print_help()
		exitnow(0)
	}

	prefix = getarg("p", "/usr/pkg")

	opt_L = getarg("L")
	opt_l = getarg("l")
	opt_d = getarg("d")
	opt_D = getarg("D")
	opt_c = getarg("c")
	opt_f = getarg("f")
	opt_u = getarg("u")
	opt_n = getarg("n")
	opt_s = getarg("s")

	cnt = 0

	if (!opt_L && !opt_l && !opt_d && !opt_D && !opt_c && !opt_f && !opt_u && !opt_n && !opt_s){
		print "At least one of the following options should be applied:\n     -L, -l, -d, -D, -c, -f, -u, -n or -s" > "/dev/stderr"
		exitnow(1)
	}

	tmp_summary = tmpfile()
	if (opt_d || opt_D || opt_c || opt_n)
		printf "" > tmp_summary

	if (opt_s){
		"uname -srm" | getline
		host_os = $1
		sub(/[_-].*$/, "", os_ver)
		host_os_ver = $2
		host_arch = $3

		if (host_arch == "amd64")
			host_arch = "x86_64"
		else if (host_arch ~ /^i.86$/)
			host_arch = "i386"
	}

	ex = 0
}

/^PKGNAME=/{
	pkgname = substr($0, 9)
	pkgbase = pkgname2pkgbase(pkgname)
	pkgver  = pkgname2version(pkgname)
}

/^PKGPATH=/{
	pkgpath = substr($0, 9)
}

opt_u {
	if (/^ASSIGNMENTS=/){
		assigns = substr($0, 13)
	}else if (NF == 0){
		if (assigns != "")
			path = pkgpath ":" assigns
		else
			path = pkgpath

		if (pkgbase in pkgbase2path){
			pkgbase2path [pkgbase] = pkgbase2path [pkgbase] " " path
		}else{
			pkgbase2path [pkgbase] = path
		}
	}
}

opt_l || opt_L {
	if (/^REQUIRES=/){
		requires1 = substr($0, 10)
		requires1 = normalize_path(requires1)
		if (!(requires1 in provides2pkg))
			requires [++requires_cnt] = requires1
		if (opt_L) { # && !has_prefix(requires1, prefix)){
			dirname = requires1
			sub(/\/[^\/]*\/?$/, "", dirname)
			syslibdirs [dirname] = 1
		}
	}else if (opt_l && /^PROVIDES=/){
		provides1 = substr($0, 10)
		provides1 = normalize_path(provides1)
		provides2pkg [provides1] = 1
	}else if (NF == 0){
		pkg = (pkgpath " " pkgname)
		for (i=1; i <= requires_cnt; ++i){
#			print "r:", requires [i], pkg
			req = requires [i]
			reqd_libs [req] = 1
			reqd_libs2where [req, ++reqd_libs2cnt [req]] = pkg
		}

		requires_cnt = 0
	}
}

opt_d || opt_D || opt_c || opt_n {
	print > tmp_summary
}

(opt_n || opt_f) && /^PLIST=/ {
	plist [++plist_cnt] = substr($0, 7)
}

opt_s {
	if (/^OS_VERSION=/){
		os_ver = substr($0, 12)
		sub(/[_-].*$/, "", os_ver)
	}else if (/^OPSYS=/){
		os = substr($0, 7)
	}else if (/^MACHINE_ARCH=/){
		arch = substr($0, 14)
	}
}

NF == 0 {
	for (f in plist){
		p = plist [f]
		if (p in all_plist)
			all_plist [p] = all_plist [p] " " pkgpath ";" pkgname
		else
			all_plist [p] = pkgpath ";" pkgname
	}

	# opt_s
	if (opt_s){
		if (host_os != os || host_os_ver != os_ver || host_arch != arch){
			print "s: mismatch", pkgpath, pkgname, os, os_ver, arch
			ex = 1
		}
	}

	delete plist
	pkgname = pkgpath = pkgver = assigns = os = os_ver = arch = ""
}

function print_lib_notfound (prefix, lib,                        i){
	for (i=1; i <= reqd_libs2cnt [lib]; ++i){
		print prefix ": not_found " lib, reqd_libs2where [lib, i]
	}
}

function normalize_path (path){
	gsub(/\/\/+/, "/", path)
	gsub(/[^\/]+\/[.][.]/, "", path)
	gsub(/\/[.]/, "", path)
	gsub(/\/$/, "", path)
	return path
}

END {
	# -L
	if (opt_L){
		for (libdir in syslibdirs){
			pipe = "ls -1 '" libdir "' 2>/dev/null"
			while((pipe | getline lib) > 0){
				syslibs [libdir "/" lib] = 0
			}
			close(pipe)
		}

		for (p in reqd_libs){
			if (!(p in syslibs)){
				print_lib_notfound("L", p)
				ex = 1
			}
		}
	}

	# -l
	if (opt_l){
		for (p in reqd_libs){
			if (has_prefix(p, prefix) && !(p in provides2pkg)){
				print_lib_notfound("l", p)
				ex = 1
			}
		}
	}

	# -d -D -c -n
	if (opt_d || opt_D || opt_c || opt_n){
		if (ex)
			fflush()

		xclose(tmp_summary)

		if (opt_d) opts = "d"
		if (opt_D) opts = opts "D"
		if (opt_c || opt_n) opts = opts "c"

		failed_deps_fn = tmpfile()
		cmd = "pkg_summary2deps -Xnls" opts " " tmp_summary " 2>" failed_deps_fn " > /dev/null" 
		if (system(cmd))
			ex = 1

		if (opt_d || opt_D || opt_c)
			system("cat " failed_deps_fn)
	}

	# -n
	if (opt_n){
		for (f in all_plist){
			cnt = split(all_plist [f], arr)
			if (cnt > 1){
				for (i=1; i < cnt; ++i){
					for (j=i+1; j <= cnt; ++j){
						base1 = pkgpana2pkgbase(arr [i])
						base2 = pkgpana2pkgbase(arr [j])
						if (base1 != base2){
							conflicts [arr [i], arr [j]] = f
						}
					}
				}
			}
		}

		while (xgetline0(failed_deps_fn)){
			if ($1 != "c:")
				continue

			pana1 = ($4 ";" $5)
			pana2 = ($7 ";" $8)

			delete conflicts [pana1, pana2]
			delete conflicts [pana2, pana1]
		}

		for (pair in conflicts){
			idx = index(pair, SUBSEP)
			pana1 = substr(pair, 1, idx-1)
			sub(/;/, " ", pana1)
			pana2 = substr(pair, idx+1)
			sub(/;/, " ", pana2)
			print "n: conflict " conflicts [pair] " <- " pana1, pana2
			ex = 1
		}
	}

	# -u
	if (opt_u){
		for (pkgbase in pkgbase2path){
			if (index(pkgbase2path [pkgbase], " ")){
				printf "u: unicity %s <- %s\n", pkgbase, pkgbase2path [pkgbase]
				ex = 1
			}
		}
	}

	# -f
	if (opt_f){
		for (f in all_plist){
			if (index(all_plist [f], " ") > 0){
				pana = all_plist [f]
				gsub(/;/, " ", pana)
				printf "f: conflict %s %s\n", f, pana
				ex = 1
			}
		}
	}

	exitnow(ex)
}
