#! /bin/sh
#  fix-udev-rules -- fixes up after badly broken iscan udev rules
#  Copyright (C) 2009  SEIKO EPSON Corporation

#  This file is part of "Image Scan! for Linux".
#  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 FITNESS
#  FOR A PARTICULAR PURPOSE or MERCHANTABILITY.
#  See the GNU General Public License for more details.
#
#  You should have received a verbatim copy of the GNU General Public
#  License along with this program; if not, write to:
#
#      Free Software Foundation, Inc.
#      59 Temple Place, Suite 330
#      Boston, MA  02111-1307  USA


#  Shows script usage documentation and exits the program with the
#  optional status passed as its first argument.

usage () {
    cat <<EOF
'`basename $0`' fixes up after badly broken iscan udev rules

Usage: $0 [OPTIONS]

Image Scan! for Linux version 2.16.0 through 2.19.x shipped with a
script that generated broken '*iscan.rules' files when executed with
'--mode=udev'.  These rules files may end up in the system's 'initrd'
images and, when used with 'udev' versions later than 125, cause the
system to no longer boot.

The following options are supported:

  -h, --help           display this message and exit
  -v, --version        display program version and exit
  -c, --check-only     only performs a check, does not attempt to fix
  -f, --fix-initrd     also try to fix affected initrd images
  -q, --quiet          suppress output if there is nothing to fix

You can check your system for signs of this problem by running this
script with the '--check-only' option.  In this mode, it will only
report whether your system is affected.  In case it is affected, you
can attempt to repair the damage with:

  `basename $0`

Note that this requires 'root' privileges and does not try to fix bad
'initrd' images.  In order to fix any bad 'initrd' images you need to
pass the '--fix-initrd' option.  Although we have been very careful in
trying to fix such images, there is a chance that you cannot boot with
a "fixed" image.

Images created with 'update-initramfs' are updated with the same tool,
meaning that backup images are created as per your system's settings.

Other images are backed up to a file with a .`basename $0`.bak
extension just in case.

EOF
    exit $1
}

#  Output a version blurb and exit conditionally.

version () {
    do_cont=$1

    cat <<EOF
`basename $0` (Image Scan! for Linux) 2.20.0
Copyright (C) 2009  SEIKO EPSON Corporation
This is free software.  You may redistribute copies of it under the terms
of the GNU General Public License <http://www.gnu.org/licenses/gpl.html>.
EOF

    test xno = x$do_cont && exit 0
}

mesg () {
    echo `basename $0`: $*
}

#  Set initial values of global variables.

DO_HELP=no
DO_VERS=no
CHECK_ONLY=no
FIX_INITRD=no
QUIET=no

#  Process command line options and arguments.

parsed_opts=`getopt \
    --options hvcfq \
    --longopt help,version,check-only,fix-initrd,quiet \
    -- "$@"`

if test 0 != $? \
    ; then
    mesg "error: invalid command line" >&2
    usage 2
fi

eval set -- "$parsed_opts"

while test x-- != x"$1" \
    ; do
    case "$1" in		# `getopt` quotes option arguments
	-h|--help)       DO_HELP=yes; shift;;
	-v|--version)    DO_VERS=yes; shift;;
	-c|--check-only) CHECK_ONLY=yes; shift;;
	-f|--fix-initrd) FIX_INITRD=yes; shift;;
	-q|--quiet)      QUIET=yes; shift;;
	*)
	    mesg "internal error: inconsistent option spec handling" >&2
	    exit 3
	    ;;
    esac
done
shift				# past the '--' marker


#  Sanity checking and option handling.

test 0 -ne $# && usage 1

test xno != x$DO_VERS && version $DO_HELP
test xno != x$DO_HELP && usage 0

#  Override FIX_INITRD if CHECK_ONLY requested.

if test xyes = x$CHECK_ONLY \
    ; then
    if test xyes = x$FIX_INITRD \
	; then
	mesg "warning: --check-only requested, overriding --fix-initrd" >&2
    fi
    FIX_INITRD=no
fi

#  Check for root permissions.

if test 0 != `id -u` \
    ; then
    if test xyes != x$CHECK_ONLY \
	; then
	mesg "warning: you need 'root' permissions to fix things" >&2
	exit 1
    fi
fi


#  Disable the cause of this mess: make-policy-file.
#  The make-policy-file script is fixed in 2.20.0.  Before 2.16.0
#  there was no such script.

if `type dpkg-query >/dev/null 2>&1` \
    ; then
    file="`dpkg-query -L iscan 2>/dev/null | grep make-policy-file`"
    version="`dpkg-query -W -f='${Version}' iscan 2>/dev/null`"
    dpkg --compare-versions "$version" lt 2.20.0 || file=
else
    file="`rpm -ql iscan 2>/dev/null | grep make-policy-file`"
    version="`rpm -qi --qf '%{VERSION}' iscan 2>/dev/null`"
    case "$version" in
	2.1[6789].*)
	    ;;
	*)			# not afflicted
            file=
	    ;;
    esac
fi
if test -n "$file" && test -x "$file" \
    ; then
    if test xyes = x$CHECK_ONLY \
	; then
	mesg "need to remove execute permissions on $file" >&2
    else
	chmod 0664 "$file"
	mesg "removed execute permissions on $file" >&2
    fi
elif test xno = x$QUIET \
      ; then
    mesg "no bad make-policy-file script found" >&2
fi


#  Check, and optionally fix, iscan's generated udev rules files.

for dir in \
    /etc/udev/rules.d \
    /lib/udev/rules.d \
    /dev/.udev/rules.d \
    ; do
    rules="$rules `ls $dir/*iscan.rules 2>/dev/null`"
done

found=
for file in $rules \
    ; do
    start=`sed -n '/{idProduct}=/=' $file | tail -n 1`
    if test -n "`sed -n \"$start,\\\${ /^LABEL/{ /_begin/p } }\" $file`" \
	; then
	found=true
	if test xyes = x$CHECK_ONLY \
	    ; then
	    mesg "need to fix udev rules file: $file" >&2
	else
	    sed -i "$start,\${ /^LABEL/s/_begin/_end/ }" $file
	    mesg "fixed $file" >&2
	fi
    fi
done
if test xtrue != x$found \
    ; then
    if test xno = x$QUIET \
        ; then
        mesg "no bad udev rules files found" >&2
    fi
fi


#  Helper function that tries to regenerate an initrd image.

rebuild_initrd () {

    img=$1
    shift
    rules="$@"

    #  Use update-initramfs if present but only for managed images.

    if `type update-initramfs >/dev/null 2>&1` \
	; then

	case $img in
	    /boot/initrd.img-*)
		version="`echo $img | sed 's/[^-]*-//'`"
		;;
	    /boot/initrd-*.img)
		version="`basename $img .img | sed 's/[^-]*-//'`"
		;;
	    /boot/initrd-*)
		version="`echo $1 | sed 's/[^-]*-//'`"
		;;
	    *)
		mesg "internal error: unhandled initrd glob" >&2
		mesg "skipping $img" >&2
		return 1
		;;
	esac

	if test -e /var/lib/initramfs-tools/$version \
	    ; then
	    update-initramfs -u -k $version
	    return $?
	fi
    fi

    #  Still here?  Need to fix manually.
    version="`echo $img | sed 's/[^0-9]*\([0-9][-.0-9]*[0-9]\).*/\1/'`"

    pkgs=
    if `type dpkg-query >/dev/null 2>&1`; then
      pkgs=`dpkg-query -W -f='${Package}\n' | grep $version`
    elif `type rpm >/dev/null 2>&1`; then
      pkgs=`rpm -qa | grep $version`
    fi

    pkgs=`echo $pkgs | grep -v module | grep -v firmware`

    mesg "$img must be fixed manually."
    cat <<EOF
The simplest way to do this is to update or re-install the corresponding
kernel package. Possible candidate packages include:
EOF
    if test x = x"$pkgs"; then
      echo "None found."
    else
      echo $pkgs | tr ' ' '\n'
    fi
}


#  Check, and optionally fix, initrd images.
#  Note, we cannot fix the initrd images before the rules files have
#  been fixed.

list=
for pattern in \
    /boot/initrd.img-* \
    /boot/initrd-* \
    ; do
    if `ls $pattern > /dev/null 2>&1`; then
      list="$list `ls $pattern`"
    fi
done

found=
for img in $list; do
    test -n "`echo $img | sed -n '/\.bak$/p'`" && continue
    test ! -r $img && continue
    rules="`gzip -d -c $img | cpio --quiet --list \
		| sed -n '/iscan\.rules$/p'`"
    for file in $rules \
	; do
	tmp=`mktemp`
	gzip -d -c $img | cpio --quiet --extract --to-stdout $file > $tmp
	start=`sed -n '/{idProduct}=/=' $tmp | tail -n 1`
	if test -n "`sed -n \"$start,\\\${ /^LABEL/{ /_begin/p } }\" $tmp`" \
	    ; then
	    rm -f $tmp
	    found=true
	    if test xyes != x$FIX_INITRD \
		; then
		mesg "need to fix initrd file: $img" >&2
	    else
		rebuild_initrd $img $rules
	    fi
	    continue 2
	else
	    rm -f $tmp
	fi
    done
done
if test xtrue != x$found \
    ; then
    if test xno = x$QUIET \
        ; then
        mesg "no bad initrd images found" >&2
    fi
fi


exit 0
