4 # Copyright © 2018-2021, Sébastien Luttringer
6 # This program is free software; you can redistribute it and/or
7 # modify it under the terms of the GNU General Public License
8 # as published by the Free Software Foundation; either version 2
9 # of the License, or (at your option) any later version.
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 # display what to run and run it quietly
25 (( $ret )) && echo "==> WARNING: \`$*' exited $ret"
29 # check whether the dependencies of a module are installed
33 check_dependency
() { (
34 source "$source_tree/$1-$2/dkms.conf"
36 for mod
in "${BUILD_DEPENDS[@]}"; do
37 mapfile lines
< <(dkms status
-m "$mod" -k "$3")
38 for line
in "${lines[@]}"; do
39 [[ "$line" =~
"$mod/"[^
,]+", $3, "[^
:]+': installed' ]] && break 2
46 # check whether the modules should be built with this kernel version
50 check_buildexclusive
() {
51 local BUILD_EXCLUSIVE_KERNEL
=$
(source "$source_tree/$1-$2/dkms.conf"; printf '%s\n' "$BUILD_EXCLUSIVE_KERNEL")
52 [[ "$3" =~
$BUILD_EXCLUSIVE_KERNEL ]]
55 # list all kernel versions
57 pushd "$install_tree" >/dev
/null
59 for path
in */build
/; do
65 # list all module name/version for a specific kernel version
69 for path
in "$source_tree"/*-*/dkms.conf
; do
70 if [[ -f "$path" && "$path" =~ ^
$source_tree/([^
/]+)-([^
/]+)/dkms\.conf$
]]; then
71 echo "${BASH_REMATCH[1]}/${BASH_REMATCH[2]}"
76 # list of modules/version for installed/built kernel version
78 built_nv_from_kver
() {
80 dkms status
-k "$1" |
while read -r line
; do
81 if [[ "$line" =~ ^
([^
/]+/[^
,]+)", $1, "[^
:]+": "(built|installed
) ]]; then
82 echo "${BASH_REMATCH[1]}"
87 # list installed or built kernel version for a specific module version
88 # $1 : module name/module version
89 built_kver_from_nv
() {
91 dkms status
"$1" |
while read -r line
; do
92 if [[ "$line" =~ ^
"$1, "([^
,]+)", "[^
:]+": "(built|installed
) ]]; then
93 echo "${BASH_REMATCH[1]}"
98 # install registered modules
100 # list of modules to build for a specific kernel
103 # add new/updated modules for all kernels to the build list
105 for nv
in "${!DKMS_MODULES[@]}"; do
106 for kver
in $
(all_kver
); do
107 tobuild
["$nv/$kver"]=''
110 # add modules for new/updated kernels to the build list
111 for kver
in "${!KERNEL_VERSIONS[@]}"; do
112 for nv
in $
(all_nv_from_kver
"$kver"); do
113 tobuild
["$nv/$kver"]=''
117 # list of kver which requires depmod refresh
120 # let's build and install
123 while (( $retry > 0 )); do
125 for nvk
in "${!tobuild[@]}"; do
126 [[ "$nvk" =~
([^
/]+)/([^
/]+)/(.
+) ]] ||
continue
127 mod
="${BASH_REMATCH[1]}"
128 mver
="${BASH_REMATCH[2]}"
129 kver
="${BASH_REMATCH[3]}"
130 # do not build excluded modules
131 if ! check_buildexclusive
"$mod" "$mver" "$kver"; then
134 # skip modules with missing kernel headers
135 elif [[ ! -d "$install_tree/$kver/build/include" ]]; then
136 ERROR_MESSAGES
+=("Missing $kver kernel headers for module $mod/$mver.")
139 # skip modules with missing kernel package
140 elif [[ ! -d "$install_tree/$kver/kernel" ]]; then
141 ERROR_MESSAGES
+=("Missing $kver kernel modules tree for module $mod/$mver.")
144 # postpone modules with missing dependencies
145 elif ! check_dependency
"$mod" "$mver" "$kver"; then
149 run dkms
install --no-depmod "$mod/$mver" -k "$kver"
150 if (( $?
== 0 )); then
151 # register kernel version for later depmod
155 # maybe this module was a dep of another, so we retry
159 # run depmod later for performance improvments
160 if (( $DKMS_DEPMOD )); then
161 for kver
in "${!depmods[@]}"; do
165 # add errors messages for missing dependencies modules
166 for nvk
in "${!tobuild[@]}"; do
167 [[ "$nvk" =~
([^
/]+/[^
/]+)/(.
+) ]] ||
continue
168 nv
="${BASH_REMATCH[1]}"
169 kver
="${BASH_REMATCH[2]}"
170 ERROR_MESSAGES
+=("Missing dependencies to install module $nv for kernel $kver.")
174 # remove registered modules
175 # run depmod once per kernel for performance improvments
179 # remove full modules first
180 for nv
in "${!DKMS_MODULES[@]}"; do
181 # try to remove modules one by one to keep the depmod optimization
182 for kver
in $
(built_kver_from_nv
"$nv"); do
183 run dkms remove
--no-depmod "$nv" -k "$kver"
184 if (( $?
== 0 )); then
185 # register kernel version for later depmod
188 ERROR_MESSAGES
+=("Failed to remove module $nv for kernel $kver.")
191 # ensure module removal (even if only added)
192 if [[ $
(dkms status
"$nv") ]]; then
193 run dkms remove
"$nv"
194 (( $?
== 0 )) || ERROR_MESSAGES
+=("Failed to remove module $nv.")
197 # remove modules for a specific kernel version
198 for kver
in "${!KERNEL_VERSIONS[@]}"; do
199 for nv
in $
(built_nv_from_kver
"$kver"); do
200 run dkms remove
--no-depmod "$nv" -k "$kver"
201 if (( $?
== 0 )); then
202 # register kernel version for later depmod
205 ERROR_MESSAGES
+=("Failed to remove module $nv for kernel $kver.")
209 # run depmod later for performance improvments
210 if (( $DKMS_DEPMOD )); then
211 for kver
in "${!depmods[@]}"; do
217 # display hook usage and exit $1 (default 1)
220 usage: ${0##*/} <options> install|remove
221 options: -D Do not run depmod
226 # emulated program entry point
228 [[ "$DKMS_ALPM_HOOK_DEBUG" ]] && set -x
230 # prevent each dkms call from failing with authorization errors
232 echo 'You must be root to use this hook' >&2
236 # parse command line options
237 declare -i DKMS_DEPMOD
=1
239 while getopts 'hD' opt
; do
245 shift $
((OPTIND
- 1))
246 (( $# != 1 )) && usage
248 # parse command action to early exit
250 install|remove
) declare -r DKMS_ACTION
="$1";;
254 # dkms path from framework config
255 # note: the alpm hooks which trigger this script use static path
256 source_tree
='/usr/src'
257 install_tree
='/usr/lib/modules'
258 source /etc
/dkms
/framework.conf
260 # check source_tree and install_tree exists
262 for path
in "$source_tree" "$install_tree"; do
263 if [[ ! -d "$path" ]]; then
264 echo "==> Missing mandatory directory: $path. Exiting!" >&2
269 # global storage for changed DKMS modules
270 # we use associate arrays to prevent duplication
271 # the key is <module name>/<module version>/<kernel version>
272 declare -A DKMS_MODULES
274 # global storage for changed linux kernels
275 # we use associate arrays to prevent duplication
276 declare -A KERNEL_VERSIONS
278 # global storage for error messages
279 declare -a ERROR_MESSAGES
281 # parse stdin paths to guess what we should install/remove
282 while read -r path
; do
283 if [[ "/$path" =~ ^
$source_tree/([^
/]+)-([^
/]+)/dkms\.conf$
]]; then
284 # we match file updates on dkms modules sources
285 DKMS_MODULES
["${BASH_REMATCH[1]}/${BASH_REMATCH[2]}"]=''
286 elif [[ "/$path" =~ ^
$install_tree/([^
/]+)/ ]]; then
287 # we match file updates on kernels install/removal
288 KERNEL_VERSIONS
["${BASH_REMATCH[1]}"]=''
294 # display errors at the end, to maximize readers
296 for msg
in "${ERROR_MESSAGES[@]}"; do
297 echo "==> ERROR: $msg" >&2