#!/bin/bash

# lscfg - display configuration data
#
# This file is part of the Linux lsvpd package.

# (C) Copyright IBM Corp. 2002, 2003, 2004

# Maintained by  Martin Schwenke

# Parts originally written by Todd Inglett.

# 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.
 
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
    
# $Id: lscfg.in,v 1.64 2004/06/07 05:59:21 martins Exp $

# Note /bin/bash is needed to ensure presence of printf.

# Depends on find and sort.  Both of these could come from busybox to
# avoid needing /usr.

# DESIGN NOTE:
#
# The current implementation finds all of the linux,vpd files,
# concatenates them, and runs xlatevpd on the result.  There's a
# temptation to attempt a more efficient translation of individual
# linux,vpd files.  However, each file can contain multiple VPD
# chunks, so thinks would probably not get any simpler.

PATH="/lib/lsvpd:/sbin:${PATH}:/usr/sbin:/usr/local/sbin" ; export PATH

. "/lib/lsvpd/lsvpd-functions.bash"

shopt -s nullglob

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

do_setup ()
{
    local d i

    for d in common query lscfg ; do
	for i in /lib/lsvpd/${d}.d/* ; do
	    . "$i"
	done
    done
}

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

usage ()
{
    cat <<EOF 1>&2
usage: $0 [options]
options: -h      print this usage message
         -v      Display VPD when available.
         -p      Display system-specific device information.
	 -l name Displays device information for the named device.
	 -d db   Directory db contains database, instead of default.
	 -z tgz  File tgz contains database archive (overrides -d).
EOF
    exit 1
}



# Options handling
dovpd=false
systemspecific=false
tgz=""

options=':'
options="${options}h"
options="${options}v"
options="${options}p"
options="${options}l:"
options="${options}t:"
options="${options}d:"
options="${options}z:"
while getopts ${options} opt
do
    case ${opt}
    in
	h ) usage
	    ;;
        v ) dovpd=true
            ;;
        p ) systemspecific=true
            ;;
        l ) name="$OPTARG"
            ;;
        d ) db="$OPTARG"
	    if [ ! -d "$db" ] ; then
		echo "no such directory: \"${db}\"" 1>&2
		echo 1>&2
		usage
	    fi
            ;;
        z ) tgz="$OPTARG"
	    if [ ! -f "$tgz" ] ; then
		echo "no such file: \"${tgz}\"" 1>&2
		echo 1>&2
		usage
	    fi
	    ;;
        \?) echo "unknown option \`${OPTARG}'" 1>&2
	    echo 1>&2
	    usage
            ;;
    esac
done
shift $((${OPTIND} - 1))

[ -n "$1" ] && usage

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

nl="
"

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

print_global_header ()
{
    echo -n "INSTALLED RESOURCE LIST"
    if $dovpd ; then
	echo " WITH VPD"
    else
	echo
    fi
    echo
    echo "The following resources are installed on the machine."
    if $dovpd ; then
	echo
    else
	echo "+/- = Added or deleted from Resource List."
	echo "*   = Diagnostic support not available."
	echo
    fi
    
    local arch=$(get_architecture)
    echo "  Model Architecture: ${arch}"
    local cpus=$(get_cpus)
    echo "  Model Implementation: ${cpus}, PCI Bus"
    echo
}

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

# A lot of work for a little output.

disk_size ()
{
    local disk="$1"

    local blocks

    local f="${db_proc_dir}/partitions"
    [ -r "$f" ] && \
	blocks=$(sed -n -e "s@.* \([0-9][0-9]*\) ${disk}\( .*\)*\$@\1@p" "$f")

    if [ -n "$blocks" ] ; then
	local mb=$((${blocks} * 1024 / 100000000 * 100))
	[ $mb -gt 0 ] && echo "${mb} MB"
    fi
}

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

fill_field () 
{
    local val="$1"
    local break="$2"
    local width="$3"
    local leader_1="$4"
    local leader_n="$5"

    local p="$leader_1"

    # Trailing spaces in $val threaten to kill us!  Also compact whitespace.
    set -o noglob
    val=$(echo -n $val)
    set +o noglob

    local out=""

    while [ ${#val} -gt $width ] ; do
	# Is there a space at about column $width that we can break at?
        local t="${val:0:$(($width + 2))}"
	local start="${t%[${break}]*}"
	if [ "$start" != "$t" ] ; then
	    local tt="${t//[^${break}]/}"
	    local sep="${tt: -1:1}"
	    [ "$sep" = " " ] && sep=""
	    out="${out}${p}${start}${sep}${nl}"
	    val="${t##*[${break}]}${val:$(($width + 2))}"
	else
	    # Is there a space anywhere?
	    start="${val%%[${break}]*}"
	    if [ "$start" != "$val" ] ; then
		local tt="${val//[^${break}]/}"
		local sep="${tt:0:1}"
		[ "$sep" = " " ] && sep=""
		out="${out}${p}${start}${sep}${nl}"
		val="${val#*[${break}]}"
	    else
		# Can't be broken.
		break
	    fi
	fi
	p="$leader_n"
    done

    echo "${out}${p}${val}"
}

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

# Filling information.
lscfg_wrap_column=78

lscfg_header_fmt="%s %-16s %-21s "
lscfg_header_t=$(printf "$lscfg_header_fmt" "x" "x" "x")
lscfg_ds_column=${#lscfg_header_t}
lscfg_ds_lead_spaces=$(printf "%${lscfg_ds_column}s" "")

lscfg_vpd_leading_spaces=8
lscfg_vpd_label_len=28
lscfg_vpd_leader=$(printf \
    "%$(($lscfg_vpd_label_len + $lscfg_vpd_leading_spaces))s" \
    "")
lscfg_vpd_field_width=$(($lscfg_wrap_column - \
    $lscfg_vpd_label_len - \
    $lscfg_vpd_leading_spaces))

#---------------------------------------------------------------------

render_linux_vpd ()
{
    local x="$1"

    local vpd_subdirs
    vpd_subdirs_list_hook "${x%/linux,vpd}"
    local vpd_dir
    for vpd_dir in $vpd_subdirs ; do
	local ax=$(vpd_field_get "$vpd_dir" "AX")
	[ -n "$ax" ] && ax="${ax#/dev/}"
	local yl=$(vpd_field_get "$vpd_dir" "YL")

	if should_render "$ax" "$yl" ; then
	    local ds=$(vpd_field_get "$vpd_dir" "DS")
	    render_header "$ax" "$yl" "$ds"
	    $dovpd && render_vpd "$vpd_dir" "$yl"
	fi
    done
}

should_render ()
{
    local ax="$1"
    local yl="$2"

    [ -n "$ax" ] || return 1
	
    # FIXME: This needs a case for locations that start with 'U'.
    case "$yl" in
	[!U]*[-/][FLMVY]*)
	    # Here we match things that don't get displayed in the
	    # non-system-specific section.  Some of them get knocked
	    # out earlier because they are in the top node.
	    return 1
	    ;;
	*)
	    if [ -n "$name" ] ; then
		case "$ax" in
		    ($name) return 0 ;;
		    (*)     return 1 ;;
		esac
	    else
		return 0
	    fi
    esac
}

render_header ()
{
    local ax="$1"
    local yl="$2"
    local ds="$3"

    local prefix
    if $dovpd || $systemspecific || [ -n "$name" ] ; then
	prefix=" "
    else
	# Hardcode this for now.
	prefix="+"
    fi

    case "$ax" in
	[hs]d[a-z]*)
	    local size=$(disk_size "$ax")
	    [ -n "$size" ] && ds="${ds} (${size})"
	    ;;
    esac

    # Filling code.  $lscfg_ds_column is the column where the DS field
    # should start.  If the previous part of the header spills past
    # that column, DS goes on the next line.  Independently of that,
    # DS is split at a space if it goes past $lscfg_wrap_column
    # (although this is done in fill_field).
    local header=$(printf "$lscfg_header_fmt" "$prefix" "$ax" "$yl")
    echo -n "$header"

    local p=""
    if [ ${#header} -gt $lscfg_ds_column ] ; then
	echo
	p="$lscfg_ds_lead_spaces"
    fi

    fill_field "$ds" " " $(($lscfg_wrap_column - $lscfg_ds_column)) "$p" "$lscfg_ds_lead_spaces" 
}

render_vpd ()
{
    local vpd_dir="$1"
    local yl="$2"

    local vpd_fields
    vpd_fields_list_hook "$vpd_dir"

    [ -n "$vpd_fields" ] && echo

    local k
    for k in $vpd_fields ; do
	if [ -r "${vpd_dir}/${k}" ] ; then
	    case "$k" in
		(DS|AX) : ;; # Already done in subdir header.
		(YL) render_vpd_line "$k" "$yl" ;;
		*)  local v=$(vpd_field_get "$vpd_dir" "$k")
		    render_vpd_line "${k%.*}" "$v"
		    ;;
	    esac
	fi
    done
	
    [ -n "$vpd_fields" ] && echo
}

render_vpd_line ()
{
    local k="$1"
    local v="$2"

    local label=""

    case "$k" in
	DS|AX|FC) 
	    # Special fields, not printed.
	    :
	    ;;

	# Individual fields.
	BR) label="Brand"                      ;;
	CC) label="Customer Card ID Number"    ;;
	CE) label="CCIN Extender"              ;;
	CI) label="CEC ID"                     ;;
	DD) label="Device Driver Level"        ;;
	DG) label="Diagnostic Level"           ;;
	EC) label="EC Level"                   ;;
	FN) label="FRU Number"                 ;;
	FU) label="Function Number"            ;;
	LI) label="Load ID"                    ;;
	LL) label="Loadable Microcode Level"   ;;
	MF) label="Manufacturer"               ;;
	MN) label="Manufacture ID"             ;;
	MP) label="Module Plug Count"          ;;
	NA) label="Network Address"            ;;
	NN) label="Node Name"                  ;;
	PA) label="Op Panel Installed"         ;;
	PN) label="Part Number"                ;;
	RD) label="Rack ID"                    ;;
	RL) label="ROS Level and ID"           ;;
	RM) label="Alterable ROM Level"        ;;
	SE) label="Machine/Cabinet Serial No"  ;;
	SI) label="Subsystem Vendor/Device ID" ;;
	SN) label="Serial Number"              ;;
	SZ) label="Size"                       ;;
	TM) label="Machine Type and Model"     ;;
	VI) label="Vendor ID/Device ID"        ;;
	
	# Grouped fields.
	R?) label="Product Specific.($k)"      ;;
	U?) label="User Specific.($k)"         ;;
	Y?) label="Device Specific.($k)"       ;;
	Z?) label="Device Specific.($k)"       ;;

	VK|CD|MD|DC|PL)
	    # Suppressed fields (CD should already be appended to DS)
	    :
	    ;;

	*) : "$k" ;; #echo "Unknown key ${k}" 1>&2 ;;
    esac

    [ -n "$label" ] && render_vpd_value "$k" "$v" "$label"
}

render_vpd_value ()
{
    local key="$1"
    local val="$2"
    local lab="$3"

    local dots=$(printf "%$(($lscfg_vpd_label_len - ${#lab}))s" "")
    dots="${dots// /.}"

    local break=""
    case "$key" in
	TM) break=" "  ;;
	YL) break="-/" ;;
	Z?) break=" ," ;;
    esac
    
    printf "%${lscfg_vpd_leading_spaces}s%s%s" "" "$lab" "$dots"

    if [ -n "$break" ] ; then
	fill_field "$val" \
	    "$break" $lscfg_vpd_field_width "" "$lscfg_vpd_leader"
    else
	echo "$val"
    fi
}

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

if [ -n "$tgz" ] ; then
    unpack_device_tree_archive "$tgz"
fi

do_setup
ensure_db

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

run_dynamic_vpd_hooks

[ -z "$name" ] && print_global_header
do_pre_items

list_linux_vpd |
while read vpd ; do
    render_linux_vpd "$vpd"
done

do_post_items

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

# PLATFORM SPECIFIC

if $systemspecific ; then
    do_platform_specific_section
fi
