File: //volume1/@appstore/synocli-kernel/bin/synocli-kernelmodule.sh
#!/bin/bash
#########################################################################
# Written by: th0ma7@gmail.com
# Part of SynoCommunity Developpers
#
# 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 3 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, see <https://www.gnu.org/licenses/>.
#########################################################################
#------------------------------------------------
# Make sure an argument was passed
#------------------------------------------------
usage() {
echo
printf '%10s %s\n' "Usage :" "$0 [-s|--spk <package>] [<insmod,start|rmmod,stop|reload,restart|status>] module1.ko module2.ko ..."
printf '%20s %s\n' "Optional :" "[-c|--config <file>:<option1>,<option2>,...]"
printf '%20s %s\n' "" "[-u|--udev <file>]"
echo
printf '%40s %s\n' "[-s|--spk <package>] : " "SynoCommunity package name containing kernel modules"
printf '%40s %s\n' "[<insmod|rmmod|reload|status>] : " "Action to be performed"
printf '%40s %s\n' "[-h|--help] : " "Print this help"
printf '%40s %s\n' "[-v|--verbose] : " "Verbose mode"
echo
printf '%10s %s\n' "" "Examples :"
printf '%20s %s\n' "" "$0 --spk synokernel-cdrom --verbose cdrom sr_mod status"
printf '%20s %s\n' "" "$0 --spk synokernel-cdrom --config synokernel-cdrom.cfg:default status"
printf '%20s %s\n' "" "$0 --spk synokernel-usbserial --udev 60-synokernel-usbserial.rules usbserial ch341 cp210x status"
printf '%20s %s\n' "" "$0 --spk synokernel-usbserial --config synokernel-usbserial.cfg:ch341,cp210x status"
echo
}
#------------------------------------------------
# Print detailed information for debugging
#------------------------------------------------
verbose() {
printf '%20s %s\n' "" "$0 (verbose)"
printf '%60s %s\n' "SynoCommunity kernel driver package name (SPK)" "[${SPK}]"
printf '%60s %s\n' "SynoCommunity configuration file (SPK_CFG)" "[${SPK_CFG}]"
printf '%60s %s\n' "SynoCommunity configuration path (SPK_CFG_PATH)" "[${SPK_CFG_PATH}]"
printf '%60s %s\n' "SynoCommunity configuration option (SPK_CFG_OPT)" "[${SPK_CFG_OPT}]"
printf '%60s %s\n' "Synology NAS arch (ARCH)" "[${ARCH}]"
printf '%60s %s\n' "Synology DSM version (DSM_VERSION)" "[${DSM_VERSION}]"
printf '%60s %s\n' "Running kernel version (KVER)" "[${KVER}]"
printf '%60s %s\n' "Module action insmod|rmmod|reload|status (ACTION)" "[${ACTION}]"
printf '%60s %s\n' "Kernel modules path (MPATH)" "[${MPATH}]"
printf '%60s %s\n' "Full kernel modules path (KPATH)" "[${KPATH}]"
printf '%60s %s\n' "Device firmware path (FPATH)" "[${FPATH}]"
printf '%60s %s\n' "udev rules.d path (UPATH)" "[${UPATH}]"
printf '%60s %s\n' "udev rules.d file (URULE)" "[${URULE}]"
printf '%60s %s\n' "Kernel objects list (KO_LIST)" "[${KO_LIST}]"
printf '%60s %s\n' "Kernel objects found (KO_PATH)" "[${KO_PATH}]"
}
#------------------------------------------------
# Get all kernel modules from configuration file
# based on passed configuration option
#------------------------------------------------
get_ko_list() {
ko_list_cfg=""
if [ "${SPK_CFG}" ]; then
# Check that configuration exists (if requested)
if [ ! -f ${SPK_CFG_PATH}/${SPK_CFG} ]; then
usage
echo -ne "\nERROR: Configuration file [${SPK_CFG_PATH}/${SPK_CFG}] does not exist or inaccessible...\n\n"
exit 1
fi
# Always include default first
ko_list_cfg=$(sed -n "s/^default:\(.*\)/\1/p" ${SPK_CFG_PATH}/${SPK_CFG})
if [ ! "${ko_list_cfg}" ]; then
usage
echo -ne "\nERROR: Configuration option [default] not found in file [${SPK_CFG_PATH}/${SPK_CFG}]...\n\n"
exit 1
fi
IFS=","
for config in ${SPK_CFG_OPT}
do
ko_list_cfg_tmp=$(sed -n "s/^${config}:\(.*\)/\1/p" ${SPK_CFG_PATH}/${SPK_CFG})
if [ ! "${ko_list_cfg}" ]; then
usage
echo -ne "\nERROR: Configuration option [${config}:] not found in file [${SPK_CFG_PATH}/${SPK_CFG}]...\n\n"
exit 1
fi
ko_list_cfg+="${ko_list_cfg_tmp} "
done
IFS=" "
fi
# Return merged module list from config file and
# parameters passed as arguments but keep its order,
# starting with the config file followed by args
echo ${ko_list_cfg} ${KO_LIST_ARG} | awk '{for (i=1;i<=NF;i++) if (!a[$i]++) printf("%s%s",$i,FS)}{printf("\n")}' | xargs
}
#------------------------------------------------
# Modules can be passed as:
# <module>
# <module>.ko
# /<path>/<module>*
# The following find the right module as needed
#------------------------------------------------
ko_path_match() {
ko_find=""
ko_path=""
ko_missing=""
for ko in $KO_LIST
do
# first check if the name
# matches to a path
if [ ! "$(echo $ko | grep '/')" ]; then
# Ensure to add .ko if needed
[ ! "$(echo $ko | grep '.ko$')" ] && ko=$ko.ko
# Replace any '-' or '_' by '[-_]
ko=$(echo ${ko} | sed -r 's/[-_]/[-_]/g')
# Find full module kernel object path
ko_find=$(find $KPATH -name $ko)
[ ! "$ko_find" ] && ko_missing+="$ko "
fi
ko_path+="${ko_find##*${KVER}} "
done
# Return missing modules in case of error
# else return the list of found modules
[ ! "${ko_missing}" ] \
&& echo "$ko_path" | xargs \
|| echo "missing: ${ko_missing}" | xargs
}
# exit if no parameters passed
[ $# -eq 0 ] && usage && exit 1
# must be root to load/unload kernel modules
if [ ! "$(id -un)" = "root" ]; then
verbose && usage
echo
echo "ERROR: Must have root or sudo priviledges..."
echo
exit 1
fi
###
### Global variables
###
SPK="" # SynoCommunity kernel driver package name
SPK_CFG="" # SynoCommunity configuration file
SPK_CFG_OPT="" # SynoCommunity configuration option
SPK_CFG_PATH="" # SynoCommunity configuration path
ACTION="" # Module action insmod|rmmod|reload|status
VERBOSE="FALSE" # Set verbose mode
HELP="FALSE" # Print help
URULE="FALSE" # Set udev rules to false by default
while [ $# -gt 0 ]
do
case $1 in
-s|--spk ) shift 1
SPK=$1;;
-c|--config ) shift 1
SPK_CFG=$(echo $1 | cut -f1 -d:)
SPK_CFG_OPT=$(echo $1 | cut -f2 -d:);;
-u|--udev ) shift 1
URULE=$(echo $1 | cut -f1 -d:);;
-h|--help ) HELP="TRUE";;
insmod|rmmod|reload|start|stop|restart|status ) ACTION=$1;;
-v|--verbose ) VERBOSE="TRUE";;
* ) KO_LIST_ARG+="$1 ";;
esac
shift 1;
done
###
### Other global variables
###
ARCH=$(uname -a | awk '{print $NF}' | cut -f2 -d_) # Synology NAS arch
DSM_VERSION=$(sed -n 's/^productversion="\(.*\)"/\1/p' /etc/VERSION) # Synology DSM version
FPATH_SYS="/sys/module/firmware_class/parameters/path" # System module firmware path file index
KVER=$(uname -r | awk -F. '{print $1 "." $2 "." $3}') # Running kernel version
FPATH="" # Device firmware path
KPATH="" # Full kernel modules path
MPATH="" # Kernel modules path
KO_LIST="" # List of kernel objects to enable|disable (includes config+args)
KO_LIST_ARG=$(echo ${KO_LIST_ARG} | xargs) # List of kernel objects passed in argument
KO_PATH="" # List of found kernel objects (*.ko) with path (includes config+args)
SYNOLOG_PATH=/var/log/packages # Default log output file
SYNOLOG=${SYNOLOG_PATH}/synocli-kernelmodule.log # Default log output file
# If SPK is set reassign variables
if [ -n "${SPK}" ]; then
SPK_CFG_PATH="/var/packages/${SPK}/target/etc"
FPATH="/var/packages/${SPK}/target/lib/firmware"
MPATH="/var/packages/${SPK}/target/lib/modules"
UPATH="/var/packages/${SPK}/target/rules.d"
KPATH="${MPATH}/${ARCH}-${DSM_VERSION}/${KVER}"
SYNOLOG="${SYNOLOG_PATH}/synocli-kernelmodule-${SPK}.log"
fi
# All output to SYNOLOG, STDOUT to the screen
exec > >(tee -a ${SYNOLOG}) 2> >(tee -a ${SYNOLOG} >/dev/null)
# Get list of kernel objects from
# both the configuration file and
# arguments passed on cmd line
KO_LIST=$(get_ko_list)
# Find resulting kernel object (*.ko) full path
KO_PATH=$(ko_path_match)
# exit if modules are missing/not found
if [ "$(echo $KO_PATH | cut -f1 -d:)" = "missing" ]; then
verbose && usage
echo
echo "ERROR: Missing kernel modules: [$(echo $KO_PATH | cut -f2 -d: | xargs)]"
echo
exit 1
fi
# If verbose is set print default arguments
[ ${VERBOSE} = "TRUE" ] && verbose
[ ${HELP} = "TRUE" ] && usage && exit 0
# If module path does not exists, exit
if [ ! -d ${MPATH} ]; then
usage
echo -ne "\nERROR: Module path [${MPATH}] does not exist or inaccessible...\n\n"
exit 1
fi
# Set kernel module .ko object base path
# If does not exists, exit
if [ ! -d ${KPATH} ]; then
usage
echo -ne "\nERROR: Kernel modules base path [${KPATH}] does not exist or inaccessible...\n\n"
exit 1
fi
# Check that udev rules file exist
if [ ! ${URULE} "FALSE" -a ! -f ${UPATH}/${URULE} ]; then
usage
echo -ne "\nERROR: udev rules.d file [${UPATH}/${URULE}] does not exist or inaccessible...\n\n"
exit 1
fi
# load the requested modules
load ()
{
error=0
if [ "{URULE}" ]; then
echo -ne "\t[enable] optional udev rules...\n"
printf '%40s %-34s' "" "[$URULE]"
ln -s ${UPATH}/${URULE} /lib/udev/rules.d/${URULE}
udevadm control --reload-rules
if [ $? -eq 0 ]; then
echo -ne " OK\n"
else
error=1
echo -ne " N/A\n"
fi
fi
# Add firmware path to running kernel
if [ -d "${FPATH}" ]; then
echo -ne "\t[loading] optional firmware path...\n"
printf '%65s' "[${FPATH}]"
echo "${FPATH}" > ${FPATH_SYS}
if [ $? -eq 0 ]; then
echo -ne " OK\n"
else
error=1
echo -ne " N/A\n"
fi
fi
echo -ne "\t[insmod] kernel modules...\n"
for ko in $KO_PATH
do
module=$(echo "${ko}" | sed -e 's/.*\///' -e 's/-/_/' -e 's/\.ko//')
printf '%40s %-35s' $ko "[$module]"
status=$(lsmod | grep "^$module ")
if [ $? -eq 0 -a "status" ]; then
echo "Already Loaded"
else
if [ -f $KPATH/$ko ]; then
insmod $KPATH/$ko
[ $? -eq 0 ] && echo "OK" || echo "ERROR"
else
echo "ERROR: Module $KPATH/$ko not found!"
error=1
fi
fi
done
return $error
}
# unload the requested modules in a reversed order
unload ()
{
error=0
# Unload drivers in reverse order
echo -ne "\t[rmmod] kernel modules...\n"
for item in $KO_PATH; do echo $item; done | tac | while read ko
do
module=$(echo "${ko}" | sed -e 's/.*\///' -e 's/-/_/g' -e 's/\.ko//')
printf '%40s %-35s' $ko "[$module]"
status=$(lsmod | grep "^$module ")
if [ $? -eq 0 -a "status" ]; then
rmmod $module
echo -ne "N/A\n"
else
echo -ne "ERROR\n"
error=1
fi
done
# Remove firmware path to running kernel
if [ -d "${FPATH}" ]; then
echo -ne "\t[unloading] optional firmware path...\n"
echo "" > ${FPATH_SYS}
error=$?
fi
# Remove udev rules
if [ "{URULE}" ]; then
echo -ne "\t[remove] optional udev rules...\n"
printf '%40s %-34s' "" "[$URULE]"
rm -f /lib/udev/rules.d/${URULE}
udevadm control --reload-rules
if [ $? -eq 0 ]; then
echo -ne " N/A\n"
else
error=1
echo -ne " ERROR\n"
fi
fi
return $error
}
# Provide a status of the loaded modules
status ()
{
error=0
echo -ne "\t[status] kernel modules...\n"
for ko in $KO_PATH
do
module=$(echo "${ko}" | sed -e 's/.*\///' -e 's/-/_/g' -e 's/\.ko//')
printf '%40s %-35s' $ko "[$module]"
status=$(lsmod | grep "^$module ")
if [ $? -eq 0 -a "status" ]; then
echo -ne "OK\n"
else
error=1
echo -ne "N/A\n"
fi
done
# Validate option firmware path
if [ -d "${FPATH}" ]; then
echo -ne "\t[status] of optional firmware path...\n"
printf '%65s' "[${FPATH}]"
grep -q ${FPATH} ${FPATH_SYS}
if [ $? -eq 0 ]; then
echo -ne " OK\n"
else
error=1
echo -ne " N/A\n"
fi
fi
# Validate udev rules (not much can be done)
if [ "{URULE}" ]; then
echo -ne "\t[status] of optional udev rules...\n"
printf '%40s %-34s' "" "[$URULE]"
if [ -h /lib/udev/rules.d/${URULE} ]; then
echo -ne " OK\n"
else
error=1
echo -ne " N/A\n"
fi
fi
return $error
}
case $ACTION in
insmod|start)
if status; then
echo ${SPK} is already running
exit 0
else
echo Starting ${SPK} ...
load
exit $?
fi
;;
rmmod|stop)
if status; then
echo Stopping ${SPK} ...
unload
exit $?
else
echo ${SPK} is not running
exit 0
fi
;;
reload|restart)
echo -ne "---\nReloading ${SPK} package...\n"
unload
load
exit $?
;;
status)
echo -ne "---\nStatus of ${SPK} package...\n"
if status; then
echo ${SPK} is running
exit 0
else
echo ${SPK} is not running
exit 1
fi
;;
*) usage
exit 1
;;
esac