#!/bin/ash
#(c) Copyright Barry Kauler Nov. 2010. License GPL v3 /usr/share/doc/legal
#a complete rewrite of this script, aiming for extreme simplicity and multi-thread protection.
#101202 usb_storage may have claimed an interface that is needed by another module (dual-mode 3G modems).
#101202 more variability for random usleep.
#101202 /etc/modules/firmware.dep.inst.${KERNVER} now has names of firmware tarballs, not modules.
#101210 put files into /tmp/pup_event_backend (created by rc.sysinit).
#101210 certain modules are loaded explicitly by /usr/sbin/usb_modeswitch_dispatcher.
#101210 protect0, protect1, extra protection mechanisms. 101211: protect2.
#101218 PREFLIST (in /etc/rc.d/MODULESCONFIG) format can have multiple ':', ex: 8139cp:8139too:8139xx (last is most preferred).
#110508 do not allow argument-module if it is blacklisted (note blacklist.conf created in rc.sysinit).
#110508 modprobe -i to ignore "install" and "remove" commands in /etc/modprobe.d/*.conf.
#110509 avoid chance of a module not getting loaded when almost-simultaneous 'add' uevents.
#110516 no longer using setup_alsa_func. now using /etc/modprobe.d/alsa-base.conf (from Ubuntu) only.
#111106 support firmware directories.
#120823 rerwin: $FIRMPKG always written. 
#120823 rerwin: --use-blacklist to apply the blacklist commands in the configuration files (if any) to module names as well.
#120828 rerwin: --use-blacklist again.
#120908 rerwin: Change preference processing to ensure preferred module loaded first.
#120909 rerwin: Use temporary blacklisting for preferences to avoid interference and inconsistency.

export LANG=C
. /etc/rc.d/PUPSTATE
. /etc/rc.d/MODULESCONFIG

KERNVER="`uname -r`"

[ "$FASTBOOTMODULESLIST" != "" ] && exit 1 #fastboot, see /etc/rc.d/MODULESCONFIG.

#101121 rerwin: optional $1 supplied by triggered udev rule, see /etc/udev/rules.d/60-udev-modem.rules
RULEMODULE=""
[ $1 ] && RULEMODULE="$1"

SLEEPU=`echo -n ${$} | rev` #110509 ex: pid 3124 becomes 4213, more variability for sleep.

#setup_alsa_func() {
# if [ "`echo $MODULE | cut -c 1-4`" = "snd_" ];then
#  while [ 1 ];do
#   CNTSND=`find /tmp/pup_event_backend -maxdepth 1 -type f -name 'pup_event_alsa_cnt*' | wc -l`
#   touch /tmp/pup_event_backend/pup_event_alsa_cnt${$}
#   tPATTERN='^alias snd\-card\-'"${CNTSND} "
#   #logic is a bit rough, but Alsa Wizard has already claimed this card...
#   [ "`grep "$tPATTERN" /etc/modprobe.d/alsa.conf`" != "" ] && continue
#   # note, /etc/modprobe.d/alsa_card*.conf is removed in rc.sysinit.
#   echo "alias snd-card-$CNTSND $MODULEx" > /etc/modprobe.d/alsa_card${$}.conf
#   echo "alias sound-slot-$CNTSND $MODULEx" >> /etc/modprobe.d/alsa_card${$}.conf
#   #paranoid check for two instances of this script clashing...
#   CNTSNDchk=`find /tmp/pup_event_backend -maxdepth 1 -type f -name 'pup_event_alsa_cnt*' | wc -l`
#   CNTSNDdiff=$(($CNTSNDchk - $CNTSND))
#   if [ $CNTSNDdiff -gt 1 ];then
#    rm -f /tmp/pup_event_backend/pup_event_alsa_cnt${$}
#    rm -f /etc/modprobe.d/alsa_card${$}.conf
#    #usleep ${$} #quasi random sleep.
#    #usleep `echo -n ${$} | rev` #101202 more variability.
#    usleep $SLEEPU #110509
#    continue
#   fi
#   break
#  done
# fi
#}

firmware_tarball_func() {
 fPATTERN='[:,]'"${MODULE}"'\.ko|[:,]'"${MODULEx}"'\.ko'
 FIRMPKG="`cat /etc/modules/firmware.dep.${KERNVER}  | grep -v '^#' | grep ':' | grep -E "$fPATTERN" | cut -f 1 -d ':' | head -n 1`"
 if [ "$FIRMPKG" != "" ];then
  iPATTERN='^'"${FIRMPKG}"'$'
  if [ "`grep "$iPATTERN" /etc/modules/firmware.dep.inst.${KERNVER}`" = "" ];then
   FLAGFIRM='no'
   if [ -d /lib/modules/all-firmware/${FIRMPKG} ];then #111106 support firmware directories.
    cp -a -f --remove-destination /lib/modules/all-firmware/${FIRMPKG}/* /
    FLAGFIRM='yes'
   else
    if [ -f /lib/modules/all-firmware/${FIRMPKG}.tar.gz ];then
     tar -z -x --strip=1 --directory=/ -f /lib/modules/all-firmware/${FIRMPKG}.tar.gz > /dev/null 2>&1
     FLAGFIRM='yes'
    fi
   fi
   if [ "$FLAGFIRM" = "yes" ];then
    #execute any post-install script...
    if [ -f /pinstall.${FIRMPKG}.sh ];then
     BRKCNT=0
     while [ 1 ];do #serialise execution of pinstall scripts...
      PINSTALLCNT=`find / -maxdepth 1 -type f -name 'pinstall.*.sh' | wc -l`
      [ $PINSTALLCNT -eq 1 ] && break
      usleep ${SLEEPU}0 #110509
      BRKCNT=$(($BRKCNT + 1))
      [ $BRKCNT -gt 5 ] && break
     done
     cd /
     /pinstall.${FIRMPKG}.sh >/dev/null 2>&1
     rm -f /pinstall.${FIRMPKG}.sh >/dev/null 2>&1
    fi
    echo "$FIRMPKG" >> /etc/modules/firmware.dep.inst.${KERNVER} #101202 120823 moved.
   fi
  fi
 fi
}

#120908...
write_preference_log() {
 echo "$(date +%H:%M:%S.%N | cut -c 1-12) ${$} $MODULE - $1" >> /tmp/pup_event_backend/preferences.log
}

#101210 remove. multiple entries with same modalias may be required, for example 3G modem that needs to load usb-storage and option on same interface...
##101210 just in case this script called more than once with the same modalias...
#echo "M=${MODALIAS} " > /tmp/pup_event_backend/protect0-${$}
#mREGEX='M='"$MODALIAS"' '
#[ `cat /tmp/pup_event_backend/protect0-* | grep "$mREGEX" | wc -l` -gt 1 ] && exit

#note, no longer using /tmp/pup_event_modprobe.conf (which was created in rc.sysinit)...
#110508 -i to ignore "install" and "remove" commands in /etc/modprobe.d/*.conf...
#120823 rerwin: --use-blacklist (or -b) to apply the blacklist commands in the configuration files (if any) to module names as well...
MODULE="`/sbin/modprobe -i --use-blacklist --show-depends $MODALIAS 2>/dev/null | tail -n 1 | rev | cut -f 1 -d '/' | rev | cut -f 1 -d '.' | tr '\-' '_'`"
if [ "$RULEMODULE" != "" ];then
 if [ "$MODULE" = "" -o "$MODULE" = "usb_storage" -o "$MODULE" = "snd_hda_intel" ];then
  #110508 do not allow argument-module if it is blacklisted (note blacklist.conf created in rc.sysinit)...
  [ "`cat /etc/modprobe.d/blacklist*.conf | grep -w "${RULEMODULE}"`" = "" ] && MODULE="$RULEMODULE" #101121 rerwin: Use module from argument
 fi
fi
[ "$MODULE" = "" ] && exit 1

#101202 usb_storage may have claimed an interface that is needed by another module (dual-mode 3G modems)...
if [ "$MODULE" = "usb_storage" ];then
 if [ "`lsmod | grep '^usb_storage '`" != "" ];then
  if [ ! -f /etc/modprobe.d/blacklist-usb_storage.conf ];then
   echo 'blacklist usb_storage' > /etc/modprobe.d/blacklist-usb_storage.conf
   #120828 rerwin: --use-blacklist (or -b) to apply the blacklist commands in the configuration files (if any) to module names as well...
   MODULE="`/sbin/modprobe -i --use-blacklist --show-depends $MODALIAS 2>/dev/null | tail -n 1 | rev | cut -f 1 -d '/' | rev | cut -f 1 -d '.' | tr '\-' '_'`" #110508 120828
   [ "$MODULE" = "" ] && exit 1
  fi
 fi
fi

case $MODALIAS in
 pci:*)
  #######/etc/rc.d/MODULESCONFIG overrides######
  #may need to do substitution for specific pci interface...
  VENDOR='0x'"`echo -n "$MODALIAS" | cut -f 2 -d 'v' | cut -b 1-8 | tr [A-Z] [a-z]`"
  DEVICE='0x'"`echo -n "$MODALIAS" | cut -f 2 -d 'v' | cut -b 10-17 | tr [A-Z] [a-z]`"
  aPATTERN="$VENDOR $DEVICE"
  REPLACEMENT="`echo "$PCI_OVERRIDES" | grep "$aPATTERN" | cut -f 1 -d ' '`"
  [ "$REPLACEMENT" = "(none)" ] && exit 1
  #101121 rerwin: RULEMODULE: replace only if not already forced by udev rule...
  [ "$REPLACEMENT" != "" ] && [ "$RULEMODULE" = "" ] && MODULE="$REPLACEMENT"
  #####BAD HACKS SECTION#####
  if [ "$MODULE" = "mwave" ];then
   #only install firmware tarball, do not load module (firmware script does it).
   firmware_tarball_func
   exit 1
  fi
 ;;
esac

#101218 bugfix, improve...
#preferences list, ex rt2500usb and rt73usb both hits, then choose latter...
modPATTERN='^'"$MODULE"':'
PREFHIT="`echo -n "$PREFLIST" | tr ' ' '\n' | grep "$modPATTERN" | head -n 1`" #120908
if [ "$PREFHIT" != "" ];then
 origMODULE="$MODULE" #120908
 cat /etc/modprobe.d/* 2>/dev/null | grep -o '^blacklist  *[^ ]*' | tr -s ' ' > /tmp/pup_event_backend/blacklist-$$.conf #120909
 PREFMODS="`echo -n "$PREFHIT" | cut -f 2-9 -d ':' | tr ':' ' '`"
 for PREFMOD in $PREFMODS #format can have multiple ':', ex: 8139cp:8139too:8139xx (last is most preferred).
 do
  echo "blacklist $MODULE" >> /tmp/pup_event_backend/blacklist-$$.conf #120909
  #120828 rerwin: --use-blacklist (or -b) to apply the blacklist commands in the configuration files (if any) to module names as well...
  xMODULE="`/sbin/modprobe -i --use-blacklist --config /tmp/pup_event_backend/blacklist-$$.conf --show-depends $MODALIAS 2>/dev/null | tail -n 1 | rev | cut -f 1 -d '/' | rev | cut -f 1 -d '.' | tr '\-' '_'`" #110508 120828 120909
  [ "$xMODULE" = "$PREFMOD" ] && MODULE="$xMODULE"
 done
 rm -f /tmp/pup_event_backend/blacklist-$$.conf #120909
 [ "$MODULE" = "$origMODULE" ] \
  && sleep 1 \
  && write_preference_log "Retained ($PREFHIT)" \
  || write_preference_log "Substituted for module $origMODULE ($PREFHIT)" #120908
fi

#module already loaded, exit...
mREGEX='MODULE='"$MODULE"' DEVPATH'
if cat /tmp/pup_event_backend/pup_event_module_devpath_log* 2>/dev/null | grep "${mREGEX}" ;then exit ;fi

##101210 there may be virtual-simultaneous executions of this script to load the same module...
#echo "M=${MODULE} " > /tmp/pup_event_backend/protect1-${$}
#mREGEX='M='"$MODULE"' '
#[ `cat /tmp/pup_event_backend/protect1-* | grep "$mREGEX" | wc -l` -gt 1 ] && exit
##...note: leaving out the above protect1 does not break anything, although it might potentially
##   do so. Putting it in reduced execution past this point (on my laptop) from 54 to 25 times.
##101211 it is (remotely) possible simultaneous execution on a multi-core cpu could get past above, so...
#usleep `echo -n ${$} | rev`0 #ex: 4123 would become 32140 microseconds.
#echo "M=${MODULE} " > /tmp/pup_event_backend/protect2-${$}
#[ `cat /tmp/pup_event_backend/protect2-* | grep "$mREGEX" | wc -l` -gt 1 ] && exit

#110509 there may be almost-simultaneous executions of this script to load the same module...
touch /tmp/pup_event_backend/lock1-${$} #start lock region.
mREGEX=" ${MODULE} "
echo "${$} ${MODULE} " > /tmp/pup_event_backend/protect1-${$}
for NUM in 1 2
do
 SIMULT="`cat /tmp/pup_event_backend/protect1-* | grep "${mREGEX}"`"
 [ $NUM -eq 1 ] && usleep ${SLEEPU}
 if [ `echo "$SIMULT" | wc -l` -gt 1 ];then
  [ $NUM -eq 2 ] && usleep ${SLEEPU}
  #random sleep above means that this process will kill the others before they can kill this one...
  for ONEPID in `echo -n "$SIMULT" | cut -f 1 -d ' ' | tr '\n' ' '`
  do
   [ $ONEPID -eq ${$} ] && continue
   [ -f /tmp/pup_event_backend/lock1-${ONEPID} ] && kill $ONEPID #other process within lock region.
   [ $? -eq 0 ] && rm -f /tmp/pup_event_backend/protect1-${ONEPID} && rm -f /tmp/pup_event_backend/lock1-${ONEPID}
  done
 fi
done
#if another process is beyond this point (loading same module), then exit here...
rm -f /tmp/pup_event_backend/lock1-${$} && \
 [ `cat /tmp/pup_event_backend/protect1-* | grep "$mREGEX" | wc -l` -gt 1 ] && \
 rm -f /tmp/pup_event_backend/protect1-${$} && exit

MODULEx="`echo -n "$MODULE" | tr '_' '-'`"

#101210 certain modules are loaded explicitly by /usr/sbin/usb_modeswitch_dispatcher.
#note, see also /usr/sbin/pupdial_init_hotpluggable, which can replay uevents and trigger usb_modeswitch_dispatcher.
#i did put code in here to exit if comes here with MODULE==option (for example), but there does not seem
#to be any clash, and for 3G modems that do not require switching (and hence usb_modeswitch does not run),
#then it is necessary for the module to be loaded here.

#110516 no longer using setup_alsa_func...
#setup_alsa_func
firmware_tarball_func

#log to file. rc.sysinit needs this info to find out if any modaliases missed (also above)...
echo "MODULE=$MODULE DEVPATH=$DEVPATH MODALIAS=$MODALIAS" >> /tmp/pup_event_backend/pup_event_module_devpath_log${$}

cd /sbin #v408 rerwin thinks this is needed for slamr module.

#120908 Wait for any preference processing, then ensure a preferred module is loaded first and any active conflicting modules afterward...
if [ "$(ls /tmp/pup_event_backend/preference_processing_active* 2>/dev/null)" != "" ];then
 write_preference_log "Began waiting for preference processing"
 BRKCNT=0
 while [ $BRKCNT -lt 20 ]; do
  sleep 0.1
  BRKCNT=$(($BRKCNT + 1))
  [ "$(ls /tmp/pup_event_backend/preference_processing_active* 2>/dev/null)" = "" ] && break
 done
 write_preference_log "Resumed loading after preference processing"
fi
NONPREFS="$(echo -n "$PREFLIST" | tr ' ' '\n' | grep -w -o ".*:${MODULE}$" | sed -e 's/:[^:]*$//' | tr : '\n')"
[ "$NONPREFS" = "" ] \
 || [ "$(lsmod | sed 's/ .*//' | grep -E "$(echo $MODULE $NONPREFS | tr ' ' '|')" | tail -n 1 | sed "s/^${MODULE}$//")" = "" ] \
 && exec /sbin/modprobe $MODULE

touch /tmp/pup_event_backend/preference_processing_active-${$}
write_preference_log "Began preference reload processing"
NPRELOADS=""
ALLDEPENDERS=""
for ONEMODULE in $NONPREFS;do
 LOADEDNP="$(lsmod | grep "^$ONEMODULE ")"
 if [ "$LOADEDNP" = "" ];then
  LOADEDNP="$(grep -o "^MODULE=${ONEMODULE} " /tmp/pup_event_backend/pup_event_module_devpath_log* | grep -o '[^=]*$')"
  DEPENDERS=""
 else
  DEPENDERS="$(echo -n $LOADEDNP | tr -s ' ' | cut -f 4 -d ' ' | tr -d - | tr , ' ' | tr '\n' ' ' | sed 's/^  *$//')"
 fi
 if [ "$LOADEDNP" != "" ];then
  if [ "$DEPENDERS" != "" ];then
   /sbin/modprobe -r -i -q $DEPENDERS
   wait
   ALLDEPENDERS="${ALLDEPENDERS}${DEPENDERS}"
  fi
  /sbin/modprobe -r -i -q $ONEMODULE
  wait
  NPRELOADS="${ONEMODULE} ${NPRELOADS}" #reverse order
 fi
done
for ONEMODULE in $MODULE $NPRELOADS $ALLDEPENDERS;do
 lsmod | grep -q -w "^$ONEMODULE" \
  && [ "$ONEMODULE" != "$MODULE" ] \
  && write_preference_log "Unload failed for module: ${ONEMODULE}"
 /sbin/modprobe $ONEMODULE
sleep 0.01 #precaution
done
write_preference_log "Reloaded module(s): ${NPRELOADS}${ALLDEPENDERS}"
rm -f /tmp/pup_event_backend/preference_processing_active-${$}
#120908 end

###END###
