#!/bin/bash
#==============================================================================
# Copyright IBM Corp. 2003, 2006.
#
# lsdasd ($Revision: 1.16.2.2 $)
#
# Script to generate /proc/dasd/devices output for kernels beyond 2.4.
# (Designed to work with 2.4 as well.)
#
# Author(s): S. Bader <shbader@de.ibm.com>
#
# This file is part of s390-tools
#
# s390-tools 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 of the License, or
# (at your option) any later version.
#
# s390-tools 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 s390-tools; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#==============================================================================
CMD=$(basename $0)

case $(uname -r|cut -d. -f1-2) in
	2.1|2.2|2.3|2.4)
		SYSFS=false
		;;
	*)
		SYSFS=true
		if [ "$(cat /proc/filesystems|grep sysfs)" = "" ]; then
			SYSFS=false
		fi
		SYSFSDIR=$(cat /proc/mounts|awk '$3=="sysfs"{print $2; exit}')
		if [ "$SYSFSDIR" = "" ]; then
			SYSFS=false
		fi

		;;
esac

#------------------------------------------------------------------------------
# Print usage
#------------------------------------------------------------------------------
function PrintUsage() {
	cat <<-EOD
		Usage: $(basename $0) <options> [<device>]

		<options> ::=
		 	-a|--offline
		 		Include devices that are currently offline.
		 	-h|--help
		 		Print this text and exit.
		 	-s|--short
		 		Strip leading 0.0. from bus IDs (only 2.5
		 		and later).
		 	-v|--verbose
		 		For compatibility/future use. Currently ignored.
		 	--version
		 		Show tools and command version.

		<device> ::= <devno>|<bus ID>
		 	Limit output to one device which is given as a
		 	<devno> for kernels before 2.5 and as a bus ID
		 	for kernels 2.5 and later.
	EOD
}

function PrintVersion()
{
	local TVER=$(echo '$Revision: 1.16.2.2 $'|awk '{print $2}')
	cat <<-EOD
	$CMD: version %S390_TOOLS_VERSION% ($TVER).
	Copyright IBM Corp. 2003, 2006.
	EOD
}

#------------------------------------------------------------------------------
# Helper function to check a device string.
#------------------------------------------------------------------------------
function CheckDeviceString() {
	local X

	if $SYSFS; then
		X=$(
			echo "$1" |
			awk --posix -F. '
				function PrintBusID(css, grp, devno) {
					while(length(devno) < 4)
						devno = "0" devno
					print css "." grp "." devno
				}
				NF == 1 && $1 ~ /^[0-9a-fA-F]{1,4}$/ {
					PrintBusID("0","0", $1)
					next
				}
				NF != 3 || $1 !~ /^[0-9a-fA-F]{1,2}$/ {
					next
				}
				$2 !~ /^[0-9a-fA-F]{1,2}$/ {
					next
				}
				$3 !~ /^[0-9a-fA-F]{1,4}$/ {
					next
				}
				{
					PrintBusID($1, $2, $3)
				}
			'
		)
	else
		X=$(
			echo "$1" |
			awk --posix '/^(0[xX])?[0-9a-fA-F]{1,4}$/{
				sub(/^0[xX]/,"")
				print tolower($0)
			}'
		)
	fi
	if [ "$X" != "" ]; then
		echo $X
		return 0
	fi

	return 1
}

#------------------------------------------------------------------------------
# Generat list of DASDs with SYSFS.
#------------------------------------------------------------------------------
function SysfsListDASDs() {
	
	(
		LIST=$(
			find $1/block -type l -path '*dasd*' \
				-printf '%h:%l\n' 2>/dev/null
		)
		for i in $LIST; do
			
			NAME=$(basename $(echo $i|cut -d: -f1))
			BUSID=$(basename $(echo $i|cut -d: -f2))
			if [ -b /dev/$NAME ]; then
				SSIZE=$(blockdev --getss /dev/$NAME 2>/dev/null)
			else
				SSIZE="NONODE"
			fi
			echo "$NAME $BUSID $SSIZE"
		done
		echo "----"
		DIR=$1/bus/ccw/drivers
		find $DIR/dasd-fba  -follow -name discipline -maxdepth 2 \
			-printf '%h\n' 2>/dev/null
		find $DIR/dasd-eckd -follow -name discipline -maxdepth 2 \
			-printf '%h\n' 2>/dev/null
	) | awk --posix -v SYSFSDIR="$1" '
	function Read(file) {
		getline x <file
		close(file)
		return x
	}
	function Hex2Dec(h) {
		number=0

		for(i=1; i<=length(h); i++) {
			d = index("0123456789abcdefABCDEF", substr(h,i,1))
			if(d == 0) {
				break
			}
			number = number * 16 + ((d>16) ? d-7 : d-1)
		}
		print number
	}
	BEGIN{
		OFFLINE = ("'$OFFLINE'" == "true") ? 1 : 0
		SECTION = 0
	}
	$1 == "----"{
		SECTION++
		next
	}
	SECTION == 0{
		NAME[$2]  = $1
		SSIZE[$2] = $3
		next
	}
	SECTION == 1{
		DEVDIR = $0
		BUSID = DEVDIR
		sub(/^.*\//, "", BUSID)
		BLKDEVDIR = SYSFSDIR "/block/" NAME[BUSID]
		DISCIPLINE = Read($0 "/discipline")

		ONLINE = Read(DEVDIR "/online")
		if (ONLINE == "0" || ONLINE == "no") {
			if (OFFLINE)
				print "0:0:" BUSID "(none) : offline"
			next
		}

		if (ONLINE == "yes") {
			X = Read(DEVDIR "/block/dev")
			MAJOR = Hex2Dec(substr(X, 1, 2))
			MINOR = Hex2Dec(substr(X, 3, 2))
		} else {
			split(Read(BLKDEVDIR "/dev"), A, ":")
			MAJOR = A[1]
			MINOR = A[2]
		}

		READONLY = Read(DEVDIR "/readonly")
		SIZE     = Read(BLKDEVDIR "/size")

		if (SSIZE[BUSID] == "NONODE") {
			printf("%s:%s:%s(%-4s) at (%3i:%3i) is %-7s%4s: " \
				"[device node missing]\n",
				length(NAME[BUSID]), NAME[BUSID],
				BUSID,
				DISCIPLINE,
				MAJOR,
				MINOR,
				NAME[BUSID],
				(READONLY > 0) ? "(ro)" : "")
				next
		}
		if (SSIZE[BUSID] == "" || SIZE == 0) {
			printf("%s:%s:%s(%-4s) at (%3i:%3i) is %-7s%4s: n/f\n",
				length(NAME[BUSID]), NAME[BUSID],
				BUSID,
				DISCIPLINE,
				MAJOR,
				MINOR,
				NAME[BUSID],
				(READONLY > 0) ? "(ro)" : "")
				next
		}

		printf("%s:%s:%s(%-4s) at (%3i:%3i) is %-7s%4s: ",
			length(NAME[BUSID]), NAME[BUSID],
			BUSID,
			DISCIPLINE,
			MAJOR,
			MINOR,
			NAME[BUSID],
			(READONLY > 0) ? "(ro)" : "")
		printf("active at blocksize %i, %i blocks, %i MB\n",
			SSIZE[BUSID],
			SIZE / (SSIZE[BUSID] / 512),
			SIZE / 2048)
	}
	' | sort -t: -k1n -k2 | cut -d: -f3-

	return 0
}

#------------------------------------------------------------------------------
# generate output for given DASD devices
#------------------------------------------------------------------------------
function generateOutput() {
    if $SYSFS; then
	DASDLISTALL=$(SysfsListDASDs $SYSFSDIR)
	if [ "$1" != "" ]; then
	    if [ "$DASDLIST" == "" ]; then 
		DASDLIST=$(echo "$DASDLISTALL" | awk -F\( '$1=="'$1'"{print}')
	    else
		DASDLIST=$DASDLIST$'\x0a'$(echo "$DASDLISTALL" |
		    awk -F\( '$1=="'$1'"{print}')
	    fi
	else
	    DASDLIST=$DASDLISTALL
	fi
    else
	if [ ! -r /proc/dasd/devices ]; then
	    echo "ERROR: neither proc nor sysfs interface found!" >&2
	    exit 1
	fi
	if $OFFLINE; then
	    DASDLIST=$(
		lscss -t 3390 | awk '
				FNR>2 && $5 != "yes"{
					print tolower($1) "(none) : offline"
				}
			'
		cat /proc/dasd/devices
	    )
	else
	    DASDLIST=$(cat /proc/dasd/devices)
	fi
    fi
    return 0
}


SHORTID=false
OFFLINE=false
VERBOSE=false
#------------------------------------------------------------------------------
# Evaluating command line options
#------------------------------------------------------------------------------
while [ $# -gt 0 ]; do
	case $1 in
		--help|-h)
			PrintUsage
			exit 1
			;;
		--verbose|-v)
			VERBOSE=true
			;;
		--offline|-a)
			OFFLINE=true
			;;
		--short|-s)
			SHORTID=true
			;;
		--version)
			PrintVersion
			exit 0
			;;
		-*)
			echo "$CMD: Invalid option $1"
			echo "Try 'lsdasd --help' for more information."
			exit 1
			;;
		*)
			DEV="$(CheckDeviceString $1)"
			if [ "$DEV" = "" ]; then
				echo "$CMD: ERROR: $1 no device format"
				echo "<$DEV>"
				exit 1
			fi
			DEVLIST="$DEVLIST $DEV"
			;;
	esac
	shift
done

#check if devices were given or print all
if [ "$DEVLIST" != "" ]; then
    for DEV in $DEVLIST; do
	generateOutput $DEV
    done
else
    generateOutput
fi

if $SHORTID; then
    DASDLIST=$(
	echo "$DASDLIST" |
	awk -F. '
			$1 == "0" && $2 == "0"{
				sub(/^0\.0\./,"")
				print
			}
		'
    )
fi

if [ "$DASDLIST" != "" ]; then
	echo "$DASDLIST"
fi

exit 0
