#!/bin/bash
#
# devlabel by Gary Lerhaupt <gary_lerhaupt@dell.com>
# 8/20/2002

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=""
    
    # 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"

	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 3 parameters: <SYMLINK> <DEVICE> <UUID>" >&2
	    IGNORE_LINE=1
	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 | egrep -o [hs]d[a-z]+`
    
    # Get SCSI model
    scsi_model=`scsi_unique_id $DEVICE 2>/dev/null | 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

    # Find out if scsi_unique_id sees device
    scsi_unique_id $DEVICE 1>/dev/null 2>/dev/null
    if [ "$?" -gt 0 ]; then
	bad_scsi="TRUE"
    fi

    # Determine the UUID
    ID=""
    if [ -z "$ID" ]; then
	ID=`scsi_unique_id $DEVICE 2>/dev/null | grep "page83 type3:" | sed 's/page83 type3: \(.*\)/\1/'`
	method="S83.3:"
    fi
    if [ -z "$ID" ]; then
	ID=`scsi_unique_id $DEVICE 2>/dev/null | grep "page83 type2:" | sed 's/page83 type2: \(.*\)/\1/'`
	method="S83.2:"
    fi
    if [ -z "$ID" ]; then
	ID=`scsi_unique_id $DEVICE 2>/dev/null | grep "page83 type1:" | sed 's/page83 type1: \(.*\)/\1/'`
	method="S83.1:"
    fi
    if [ -z "$ID" ]; then
	ID=`scsi_unique_id $DEVICE 2>/dev/null | grep "page80:" | grep -v "No page80" | sed 's/page80: \(.*\)/\1/'`
	method="S80:"
    fi
    if [ -z "$ID" ]; then
	ID=`scsi_unique_id $DEVICE 2>/dev/null | 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 remove_entry()
{
    # This script deletes a symlink entry from $CONFIG_FILE
    # $1 = the name of the symlink

    # Check that we have a $1
    if [ -z "$1" ] || [ -n "$2" ]; then
	echo $"" >&2
	echo $"Invalid number of parameters passed." >&2
	echo $"Usage: remove <symlink>" >&2
	exit 1
    fi

    cp -f $CONFIG_FILE $CONFIG_FILE.old 2>/dev/null
    cat $CONFIG_FILE.old | grep -v "^$1 " > $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 "$1" | grep -c "/dev/raw/raw"` -gt 0 ]; then
	    SYM_TYPE="rawdevice"
	    raw $1 0 0
	    echo $"Unbound rawdevice $1"
	else
	    SYM_TYPE="symlink"
	    rm -f $1
	    echo $"Deleted symlink $1"
	fi
	echo $"Removed $SYM_TYPE $1 from $CONFIG_FILE"
    else
	echo $"" >&2
	echo $"Unable to find symlink $1 in $CONFIG_FILE" >&2
	echo $"No changes made." >&2
	exit 1
    fi
}

function add_entry()
{
    # This script adds the entry to the $CONFIG_FILE
    # $1 = the name of the symlink
    # $2 = the device to be symlinked

    # Check that we have a $1 and $2
    if [ -z "$1" ] || [ -z "$2" ]; then
	echo $"" >&2
	echo $"Invalid number of parameters passed." >&2
	echo $"Usage: add <symlink> <device>" >&2
	echo $"or Usage: add <rawdevice> <device>" >&2
	exit 1
    fi

    # Determine the $SYM_TYPE
    if [ `echo "$1" | grep -c "/dev/raw/raw"` -gt 0 ]; then
	SYM_TYPE="rawdevice"
    else
	SYM_TYPE="symlink"
    fi
    
    # Check that $1 does not exist
    if [ -e "$1" ] && [ "$SYM_TYPE" == "symlink" ]; then
	echo $"" >&2
	echo $"The file $1 already exists." >&2
	echo $"Failure. Could not create a symlink." >&2
	exit 3
    fi

    # Check that a symlink named $1 is not already in CONFIG_FILE
    if [ `grep -c "^$1 " $CONFIG_FILE` -gt 0 ]; then
	echo $"" >&2
	echo $"The symlink $1 is already in $CONFIG_FILE." >&2
	echo $"Failure. Could not add symlink." >&2
	exit 10
    fi
    
    # Check that $2 returns a UUID
    device_prefix=`echo $2 | sed 's/\(.*[hs]d[a-z]\+\)[0-9]\+/\1/'`
    get_id $2

    # Check that the device even exists
    if [ `fdisk -l "$device_prefix" | grep -c "$2 "` -eq 0 ] && [ -z "$ID" ]; then
	echo $"" >&2
	echo $"$2 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 $"$2 did not return a UUID." >&2
	echo $"Failure.  Could not find a UUID for $2." >&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 $2 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 $2 $1
		if [ "$?" -ne 0 ]; then
		    echo $"Failure.  Unable to create symlink $2 -> $1." >&2
		    exit 7
		fi
		echo $"Created symlink $1 -> $2"
	    elif [ "$SYM_TYPE" == "rawdevice" ]; then
		raw $1 $2
		if [ "$?" -ne 0 ]; then
		    echo $"Failure.  Unable to create rawlink $2 -> $1." >&2
		    exit 8
		fi
		echo $"Created rawmapping $1 -> $2"
	    fi
	    echo "$1 $2 $ID" >> $CONFIG_FILE
	    echo $"Added $1 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 symlink $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
		elif [ "$SYM_TYPE" == "rawdevice" ]; then
		    echo $"RAW: $SYMLINK -> $DEVICE"
		    raw $SYMLINK $DEVICE 
		fi

	        # Write to the newfile
		newfile[${#newfile[*]}]="$SYMLINK $DEVICE $UNIQUE_ID"
	    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()
{
    # 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 [ -e $SYMLINK ]; then
			raw -q $SYMLINK
		    else
			echo "The $SYM_TYPE $SYMLINK does not exist!.  It cannot be bound!"
		    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
declare -a device_mappings
for device_to_check  in `cat /proc/partitions | grep " [hs]d[a-z]* " | sed 's/.* \([hs]d[a-z]\+\) .*/\1/'`; do
    get_id /dev/$device_to_check 
    device_mappings[${#device_mappings[*]}]="/dev/$device_to_check=$ID"
done

# See how we were called.
case "$1" in
  start)
	/usr/bin/logger -t devlabel "devlabel service started/restarted"
        startup
        ;;

  status)
        USERID=`id -u`
        if [ "$USERID" -eq 0 ]; then 
          show_status
        else
          echo $"You need to be root to use this command!" >&2
        fi
        ;;

  add)
	add_entry $2 $3
	;;

  remove)
	remove_entry $2
	;;

  restart|reload)
        $0 start
        ;;

  *)
        echo $"Usage: $0 { start | status | restart | add | remove }" >&2
        exit 1
esac

exit 0





