#!/bin/bash
#
#  Copyright (C) 2002 Dell Computer Corporation <gary_lerhaupt@dell.com>
#
#    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 of the License, 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#
#  devlabel by Gary Lerhaupt <gary_lerhaupt@dell.com>
#  12/19/2002

function show_usage ()
{
    echo $"Usage: $0 [action] [options]" >&2
    echo $"      [action]  = { reload | status | add | remove | printid }" >&2
    echo $"      [options] = [-d device] [-s symlink] [-u UUID]" >&2
    echo $"                = [--partnum partition#] [--automount] [-q]" >&2
}

function parse_config_data()
{
    # This functions takes in 1 line of input from $CONFIG_FILE and parses it into the
    # proper format. It is able to tell if something is a UNIQUE_ID by the presence of a ":".
    # If the line is blank or is a comment, then $IGNORE_LINE is set.
    # If the SYMLINK starts with /dev/raw/raw, then $SYM_TYPE=rawdevice else symlink.
    # $1 = 1 full line of input from the rawdevices file.
    # Returns $SYMLINK $DEVICE $UNIQUE_ID $IGNORE_LINE $SYM_TYPE
    #
    SYMLINK=""
    DEVICE=""
    UNIQUE_ID=""
    IGNORE_LINE=""
    SYM_TYPE=""
    OPTIONS=""
    AUTOMOUNT=""
    
    # Get rid of carriage returns, if necessary
    line_arg=`echo "$1" | tr -d '\r'`

    # Initially test if it is a comment or a blank line
    if [ -z "$line_arg" ] || [ `echo "$line_arg" | grep -c "^ *#"` -gt 0 ]; then
	IGNORE_LINE=1
    else
	set -- $line_arg
	SYMLINK="$1"
	DEVICE="$2"
	UNIQUE_ID="$3"
	OPTIONS="$4"

	if [ `echo "$SYMLINK" | grep -c "/dev/raw/raw"` -gt 0 ]; then
	    SYM_TYPE="rawdevice"
	else
	    SYM_TYPE="symlink"
	fi
	
	if [ -z "$SYMLINK" ] || [ -z "$DEVICE" ] || [ -z "$UNIQUE_ID" ]; then
	    echo $"" >&2
	    echo $"Ignoring bad entry: $1." >&2
	    echo $"Expecting entry to have at least 3 parameters: <SYMLINK> <DEVICE> <UUID>" >&2
	    IGNORE_LINE=1
	fi

	if [ -n "$OPTIONS" ]; then
	    for config_option in `echo "$OPTIONS" | tr ',' '\n'`; do
		if [ `echo "$config_option" | tr A-Z a-z` == "automount" ]; then
		    AUTOMOUNT="true"
		fi
	    done
	fi
    fi
}

function get_id()
{
    # This function determines the ID that is returned for the DEVICE
    # $1 = $DEVICE
    # Returns $ID
    #
   
    DEVICE="$1"
    bad_scsi=""
    device_partialname=`echo $DEVICE | sed 's/.*\([hs]d[a-z]\+\).*/\1/'`

    # Set scsi_unique_id or determine if its not SCSI
    scsi_unique_id=`/usr/bin/scsi_unique_id $DEVICE 2>/dev/null`
    if [ "$?" -gt 0 ]; then
	bad_scsi="TRUE"
    fi
    
    # Get SCSI model
    scsi_model=`echo "$scsi_unique_id" | grep "model:" | sed 's/model://' | tr -d " "`

    # Get IDE model
    ide_info_dir="/proc/ide/$device_partialname"
    if [ -d "$ide_info_dir" ]; then
	ide_model=`cat $ide_info_dir/model 2>/dev/null | tr -d " "`
    fi

    # Determine the UUID
    ID=""
    if [ -z "$ID" ]; then
	ID=`echo "$scsi_unique_id" | grep "page83 type3:" | sed 's/page83 type3: \(.*\)/\1/'`
	method="S83.3:"
    fi
    if [ -z "$ID" ]; then
	ID=`echo "$scsi_unique_id" | grep "page83 type2:" | sed 's/page83 type2: \(.*\)/\1/'`
	method="S83.2:"
    fi
    if [ -z "$ID" ]; then
	ID=`echo "$scsi_unique_id" | grep "page83 type1:" | sed 's/page83 type1: \(.*\)/\1/'`
	method="S83.1:"
    fi
    if [ -z "$ID" ]; then
	ID=`echo "$scsi_unique_id" | grep "page80:" | grep -v "No page80" | sed 's/page80: \(.*\)/\1/'`
	method="S80:"
    fi
    if [ -z "$ID" ]; then
	ID=`echo "$scsi_unique_id" | grep "page83 type0:" | sed 's/page83 type0: \(.*\)/\1/'`
	method="S83.0:"
    fi

    # Add the SCSI model to the ID
    if [ -n "$scsi_model" ] && [ -z "$bad_scsi" ]; then
	if [ -z "$ID" ]; then
	    method="S:"
	    ID="$scsi_model"
	else
	    ID="$ID$scsi_model"
	fi
    fi

    if [ -z "$ID" ] && [ -d $ide_info_dir ]; then
	ID=`cat $ide_info_dir/identify 2>/dev/null | tr "\n" " " | cut -d " " -f "11-20" | tr -d " "`
	
	ID="$ID$ide_model"
	method="I:"
    fi

    # if ID isn't blank, tag the method to it
    if [ -n "$ID" ]; then
	ID=$method$ID
    fi

}

function rotate_old_files()
{
    for old_file_number in 8 7 6 5 4 3 2 1 0; do
	new_file_number=$(($old_file_number + 1))
	mv -f $CONFIG_FILE.$old_file_number $CONFIG_FILE.$new_file_number 2>/dev/null
    done
    mv -f $CONFIG_FILE.old $CONFIG_FILE.0 2>/dev/null
}

function check_uniqueness()
{
    # This script checks a given device ID against the device_mappings array to determine if
    # it is a unique ID or not
    # $1 = the UUID to check against device_mappings

    IFS='
'
    if [ `echo "${device_mappings[*]}" | grep -c "$1$"` -eq 0 ]; then
	# Cannot find any devices with this ID
	unset IFS
	return 1
    fi
    if [ `echo "${device_mappings[*]}" | grep -c "$1$"` -ne 1 ]; then
	echo $"" >&2
	echo $"Uniqueness check failed.  The following devices have the same UUID:" >&2
	echo "${device_mappings[*]}" | grep $1 | sed 's/=.*//' >&2
	unset IFS
	return 2
    fi
    unset IFS
    return 0
}

function print_id()
{
    # Check that we have all the arguments
    if [ -z "$cli_device" ]; then
	echo $"" >&2
	echo $"Error! Invalid number of arguments passed." >&2
	echo $"Usage: printid [-d device]" >&2
	exit 1
    fi
    
    get_id $cli_device
    if [ -n "$ID" ]; then
	echo $ID
    else
	exit 1
    fi
}

function remove_entry()
{
    # Check that we have all the arguments
    if [ -z "$cli_symlink" ]; then
	echo $"" >&2
	echo $"Error! Invalid number of arguments passed." >&2
	echo $"Usage: remove [-s symlink]" >&2
	exit 1
    fi

    cp -f $CONFIG_FILE $CONFIG_FILE.old 2>/dev/null
    cat $CONFIG_FILE.old | grep -v "^$cli_symlink " > $CONFIG_FILE 2>/dev/null
    rotate_old_files

    # If there is a difference, report back
    if [ `diff $CONFIG_FILE $CONFIG_FILE.0 | grep -c $` -gt 0 ]; then
	if [ `echo "$cli_symlink" | grep -c "/dev/raw/raw"` -gt 0 ]; then
	    SYM_TYPE="rawdevice"
	    raw $cli_symlink 0 0
	    echo $"Unbound rawdevice $cli_symlink"
	else
	    SYM_TYPE="symlink"
	    rm -f $cli_symlink
	    echo $"Deleted symlink $cli_symlink"
	fi
	echo $"Removed $SYM_TYPE $cli_symlink from $CONFIG_FILE"
    else
	echo $"" >&2
	echo $"Unable to find $SYM_TYPE $cli_symlink in $CONFIG_FILE" >&2
	echo $"No changes made." >&2
	exit 1
    fi
}

function add_entry()
{
    # Disable for s390 and s390x. Needs to done fairly different.
    if [ "`uname -m`" = "s390" -o "`uname -m`" = "s390x" ]; then
        echo $"" >&2
        echo $"Fdisk is not available for s390 or s390x." >&2
        echo $"Aborting device check." >&2
        exit 4
    fi

    # Check that the right arguments were passed
    if [ -z "$cli_symlink" ] || [ -z "$cli_device" ] && [ -z "$cli_uuid" ]; then
	echo $"" >&2
	echo $"Error! Invalid number of parameters passed." >&2
	echo $"Usage: add [-s symlink] [-d device]" >&2
	echo $"   or: add [-s rawdevice] [-d device]" >&2
	echo $"   or: add [-s symlink] [-u UUID]" >&2
	echo $"   or: add [-s rawdevice] [-u UUID]" >&2
	exit 1
    fi

    # Determine the $SYM_TYPE
    if [ `echo "$cli_symlink" | grep -c "/dev/raw/raw"` -gt 0 ]; then
	SYM_TYPE="rawdevice"
    else
	SYM_TYPE="symlink"
    fi

    # Make sure symlink begins with "/"
    if [ `echo "$cli_symlink" | grep -c "^/"` -eq 0 ]; then
	echo $"" >&2
	echo $"Error! Symlink does not begin with '/'".
	echo $"Symlinks in devlabel must be absolute pathnames."
	exit 11
    fi
    
    # Check that $cli_symlink does not exist
    if [ -e "$cli_symlink" ] && [ "$SYM_TYPE" == "symlink" ]; then
	echo $"" >&2
	echo $"The file $cli_symlink already exists." >&2
	echo $"Failure. Could not create a $SYM_TYPE." >&2
	exit 3
    fi

    # Check that a symlink named $cli_symlink is not already in CONFIG_FILE
    if [ `grep -c "^$cli_symlink " $CONFIG_FILE` -gt 0 ]; then
	echo $"" >&2
	echo $"The $SYM_TYPE $cli_symlink is already in $CONFIG_FILE." >&2
	echo $"Failure. Could not add $SYM_TYPE." >&2
	exit 10
    fi

    # If $cli_uuid is set, find out what device it references
    if [ -n "$cli_uuid" ]; then
	check_uniqueness "$cli_uuid"
	if [ "$?" -ne 0 ]; then
	    echo $"" >&2
	    echo $"Could not add a symlink to the device with this UUID." >&2
	    echo $"Either no device could be found or multiple devices have this ID." >&2
	    exit 12
	fi
	cli_device=`echo "${device_mappings[*]}" | sed 's/.*\(\/dev\/[hs]d[a-z]\+\)='$cli_uuid'.*/\1/'`
	if [ -n "$cli_partnum" ]; then
	    cli_device="$cli_device$cli_partnum"
	fi
    fi
    
    # Check that $cli_device returns a UUID
    device_prefix=`echo $cli_device | sed 's/\(.*[hs]d[a-z]\+\)[0-9]\+/\1/'`
    get_id $cli_device

    # Check that the device even exists
    if [ `fdisk -l $device_prefix 2>/dev/null | awk '{print $1}' | grep -c "^$cli_device$"` -eq 0 ] && [ `fdisk -l $device_prefix 2>/dev/null | awk '{print $2}' | grep -c "^$cli_device:$"` -eq 0 ]; then
	echo $"" >&2
	echo $"$cli_device does not exist." >&2
	echo $"Failure.  Since this device does not exist, it did not return an identifier." >&2
	exit 4
    fi

    # Check for a valid ID
    if [ -z "$ID" ]; then
	echo $"" >&2
	echo $"$cli_device did not return a UUID." >&2
	echo $"Failure.  Could not find a UUID for $cli_device." >&2
	exit 5
    else
	# Check that device gives a unique ID
	check_uniqueness "$ID"
	return_code="$?"
	if [ "$return_code" -eq 1 ]; then
	    echo $"" >&2
	    echo $"Failure." >&2
	    echo $"The device you are trying to add to devlabel does not show up in" >&2
	    echo $"/proc/partitions." >&2
	    exit 9
	elif [ "$return_code" -gt 1 ]; then
	    echo $"" >&2
	    echo $"Failure." >&2
	    echo $"The device UUID for $cli_device is identical to other devices on your system." >&2
	    echo $"Because of this, you cannot use devlabel with this device." >&2
	    exit 6
	else
	    # If symlink, do symlink things else do raw things 
	    if [ "$SYM_TYPE" == "symlink" ]; then
		ln -fs $cli_device $cli_symlink
		if [ "$?" -ne 0 ]; then
		    echo $"Failure.  Unable to create symlink $cli_symlink -> $cli_device." >&2
		    exit 7
		fi
		echo $"Created symlink $cli_symlink -> $cli_device"
	    elif [ "$SYM_TYPE" == "rawdevice" ]; then
		raw $cli_symlink $cli_device
		if [ "$?" -ne 0 ]; then
		    echo $"Failure.  Unable to create rawlink $cli_symlink -> $cli_device." >&2
		    exit 8
		fi
		echo $"Created rawmapping $cli_symlink -> $cli_device"
	    fi

	    # Format the options, if any
	    options_to_write=""
	    if [ -n "$cli_automount" ]; then
		options_to_write="automount"
	    fi
	    
	    echo "$cli_symlink $cli_device $ID $options_to_write" >> $CONFIG_FILE
	    echo $"Added $cli_symlink to $CONFIG_FILE"
	fi
    fi
}

function startup()
{
    # Declare necessary variables
    declare -a newfile
    write_file=""

    # Parse up the CONFIG_FILE and do symlinkings
    while read line; do

	dont_link=""
 	parse_config_data "$line"
	device_prefix=`echo $DEVICE | sed 's/\(.*[hs]d[a-z]\+\)[0-9]\+/\1/'`
	device_suffix=`echo $DEVICE | sed 's/.*[hs]d[a-z]\+\([0-9]\+\)/\1/'`
	get_id $DEVICE

	# Check if its a comment or blank line
	if [ -n "$IGNORE_LINE" ]; then
	    newfile[${#newfile[*]}]=$line

	else
	    # Make sure that the UUID is unique before making the symlink
	    check_uniqueness $UNIQUE_ID
	    return_code="$?"
	    if [ "$return_code" -eq 1 ]; then
		newfile[${#newfile[*]}]="$line"
		dont_link="TRUE"
		if [ "$SYM_TYPE" == "symlink" ]; then
		    rm -f $SYMLINK
		fi
		/usr/bin/logger -t devlabel -p syslog.warning "The $SYM_TYPE $SYMLINK -> $DEVICE is being ignored in $CONFIG_FILE because the correct device cannot be found."
		echo $""
		echo $"The device $DEVICE no longer seems to exist.  Because of this, the"
		echo $"$SYM_TYPE $SYMLINK -> $DEVICE will not be available. The reference"
		echo $"to this $SYM_TYPE in $CONFIG_FILE will be ignored."
	    elif [ "$return_code" -gt 1 ]; then
		newfile[${#newfile[*]}]=$line
		dont_link="TRUE"
		if [ "$SYM_TYPE" == "symlink" ]; then
		    rm -f $SYMLINK
		fi
		/usr/bin/logger -t devlabel -p syslog.warning "The $SYM_TYPE $SYMLINK -> $DEVICE is being ignored in $CONFIG_FILE due to uniqueness issues."
		echo $""
		echo $"The UUID associated with $SYMLINK is no longer absolutely unique."
		echo $"The $SYM_TYPE $SYMLINK -> $DEVICE will not be available."
		echo $"The reference to this $SYM_TYPE in $CONFIG_FILE will be ignored."

	    elif [ "$ID" != "$UNIQUE_ID" ]; then
		# Determine the new DEVICE name by searching for the UNIQUE_ID amongst all devices
	        new_device_prefix=`echo "${device_mappings[*]}" | sed 's/.*\(\/dev\/[hs]d[a-z]\+\)='$UNIQUE_ID'.*/\1/'`
	        if [ "$new_device_prefix" == "$device_prefix" ] && [ -z "$ID" ]; then
		    newfile[${#newfile[*]}]="$line"
		    dont_link="TRUE"
		    if [ "$SYM_TYPE" == "symlink" ]; then
			rm -f $SYMLINK
		    fi
		    /usr/bin/logger -t devlabel -p syslog.warning "The $SYM_TYPE $SYMLINK -> $DEVICE is being ignored in $CONFIG_FILE because it cannot be found."
		    echo $""
		    echo $"The device $DEVICE no longer seems to exist.  Because of this, the"
		    echo $"$SYM_TYPE $SYMLINK -> $DEVICE will not be available. The reference"
		    echo $"to this $SYM_TYPE in $CONFIG_FILE will be ignored."
		else
		    write_file="TRUE"
		    /usr/bin/logger -t devlabel -p syslog.notice "The device $DEVICE is now known as $new_device_prefix$device_suffix. $SYMLINK now points to the new name."
		    echo $""
		    echo $"Device name inconsistency detected for $SYM_TYPE $SYMLINK!"
		    echo $"The device $DEVICE is now $new_device_prefix$device_suffix."
		    echo $"The $SYM_TYPE $SYMLINK will now point to the new device name."
		    DEVICE="$new_device_prefix$device_suffix"
		fi
	    fi
	    
	    # If dont_link has not been set, do your symlinking/raw
	    if [ -z "$dont_link" ]; then  
		if [ "$SYM_TYPE" == "symlink" ]; then
		    echo $"SYMLINK: $SYMLINK -> $DEVICE"
		    ln -fs $DEVICE $SYMLINK

		    # Automount the device if AUTOMOUNT is set and its in /etc/fstab
		    mountpoint=`grep "^$SYMLINK " /etc/fstab -m 1 | awk '{print $2}'`
		    if [ -n "$AUTOMOUNT" ] && [ -n "$mountpoint" ] && [ `mount | grep -c "^$SYMLINK "` -eq 0 ]; then
			mount $mountpoint
		    fi
		elif [ "$SYM_TYPE" == "rawdevice" ]; then
		    echo $"RAW: $SYMLINK -> $DEVICE"
		    raw $SYMLINK $DEVICE 
		fi

	        # Write to the newfile
		newfile[${#newfile[*]}]="$SYMLINK $DEVICE $UNIQUE_ID $OPTIONS"
	    fi
	fi
    done < $CONFIG_FILE

    # If any device names changed, rewrite the CONFIG_FILE, otherwise, don't touch
    if [ -n "$write_file" ]; then
	cp -f $CONFIG_FILE $CONFIG_FILE.old 2>/dev/null
	rotate_old_files
	IFS='
'
	# Save the changes
	echo -e "${newfile[*]}" > $CONFIG_FILE 2>/dev/null
	unset IFS
    fi
}

function show_status()
{
    # Set raw -qa variable
    raw_listing=`raw -qa`

    # Parse up the CONFIG_FILE
    while read line; do
 	parse_config_data "$line"
 
 	if [ -z "$IGNORE_LINE" ]; then
	    check_uniqueness $UNIQUE_ID
	    if [ "$?" -ne 0 ]; then
		echo $"$SYMLINK->$DEVICE ignored. Cannot confirm UUID. No link will exist!" >&2
	    else
		if [ "$SYM_TYPE" == "symlink" ]; then
		    if [ -e $SYMLINK ]; then
			ls -o $SYMLINK
		    else
			echo "The $SYM_TYPE $SYMLINK does not exist.  Restart devlabel to create it!"
		    fi
		elif [ "$SYM_TYPE" == "rawdevice" ]; then
		    if [ `echo "$raw_listing" | grep -c "$SYMLINK:"` -gt 0 ]; then
			echo "$SYMLINK --[RAW]--> $DEVICE (bound to: `echo "$raw_listing" | grep "$SYMLINK:" | sed 's/.*bound to \(.*\)/\1/'`)"
		    else
			echo "The $SYM_TYPE $SYMLINK is currently unbound.  Restart devlabel to bind it!"
		    fi
		fi
	    fi
	fi
	   
    done < $CONFIG_FILE
}

####
#### Program Starts Here
####

unset IFS

# Source function library.
. /etc/init.d/functions

TEXTDOMAIN=initscripts
CONFIG_FILE=/etc/sysconfig/devlabel

[ -f /usr/bin/raw ] || exit 0
[ -f $CONFIG_FILE ] || exit 0

PATH=/usr/bin:/bin:/usr/sbin:/sbin

# Create an array of current device mappings
# Note that the only thing devlabel can currently handle are devices in /proc/partition of format hd[a-z]* or sd[a-z]*
declare -a device_mappings
for device_to_check  in `cat /proc/partitions | awk '{print $4}' | grep [hs]d[a-z] | grep -v [0-9]`; do
    get_id /dev/$device_to_check 
    device_mappings[${#device_mappings[*]}]="/dev/$device_to_check=$ID"
done

cli_device=""
cli_uuid=""
cli_symlink=""
cli_automount=""

# Parse command line arguments
action_flag=""
while [ $# -gt 0 ]; do
    case $1 in
	--device*|-d)
	    if echo $1 | grep '=' >/dev/null ; then
		cli_device=`echo $1 | sed 's/^.*=//'`
            else
                cli_device="$2"
                shift
            fi
            ;;
	--symlink*|-s)
	    if echo $1 | grep '=' >/dev/null ; then
		cli_symlink=`echo $1 | sed 's/^.*=//'`
            else
                cli_symlink="$2"
                shift
            fi
            ;;
	--uuid*|-u)
	    if echo $1 | grep '=' >/dev/null ; then
		cli_uuid=`echo $1 | sed 's/^.*=//'`
            else
                cli_uuid="$2"
                shift
            fi
            ;;
	--partnum*|-p)
	    if echo $1 | grep '=' >/dev/null ; then
		cli_partnum=`echo $1 | sed 's/^.*=//'`
            else
                cli_partnum="$2"
                shift
            fi
	    if [ -n "$cli_partnum" ] && [ `echo "$cli_partnum" | grep -c "^[0-9]"` -ne 1 ]; then
		echo $"" >&2
		echo "Error! Partition number is not a number">&2
		show_usage
		exit 2
	    fi
            ;;
	--automount)
	    cli_automount="true"
	    ;;
	--quiet|-q)
	    exec >/dev/null 2>&1
	    ;;
	-*|--*)
	    echo $"" >&2
	    echo $"Error!  Unknown option: $1" >&2
	    show_usage
	    exit 3
	    ;;
	*)
	    if [ -n "$action_flag" ]; then
		echo $"" >&2
		echo $"Error!  Multiple actions specified: $action, $1" >&2
		show_usage
		exit 4
	    fi
	    action_flag="set"
	    action="$1"
	    ;;
    esac
    shift
done

# Make sure they're root
if [ `id -u` -ne 0 ]; then
    echo $"You must be root to use this command." >&2
    exit 1
fi

# Run the specified action
case "$action" in
    start|restart|reload)
	/usr/bin/logger -t devlabel "devlabel service started/restarted"
        startup
	;;
    status)
	show_status
	;;
    add)
	add_entry
	;;
    remove)
	remove_entry
	;;
    printid)
	print_id
	;;
    "")
	echo "" >&2
	echo $"Error! No action was specified.">&2
	show_usage
	;;
    *)
	echo "" >&2
	echo $"Error! Unknown action specified: $action" >&2
	show_usage
	;;
esac




