archrelease: copy trunk to extra-x86_64
[arch-packages.git] / dkms / trunk / hook.sh
blob6c685ee35dbd8861d7d4daf92d41c0a789bd3835
1 #!/bin/bash
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
21 run() {
22 echo "==> $*"
23 "$@" > /dev/null
24 local ret=$?
25 (( $ret )) && echo "==> WARNING: \`$*' exited $ret"
26 return $ret
29 # check whether the dependencies of a module are installed
30 # $1: module name
31 # $2: module version
32 # $3: kernel version
33 check_dependency() { (
34 source "$source_tree/$1-$2/dkms.conf"
35 local mod lines line
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
40 done
41 exit 1
42 done
43 exit 0
44 ) }
46 # check whether the modules should be built with this kernel version
47 # $1: module name
48 # $2: module version
49 # $3: 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
56 all_kver() {
57 pushd "$install_tree" >/dev/null
58 local path
59 for path in */build/; do
60 echo "${path%%/*}"
61 done
62 popd >/dev/null
65 # list all module name/version for a specific kernel version
66 # $1: kernel version
67 all_nv_from_kver() {
68 local path
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]}"
73 done
76 # list of modules/version for installed/built kernel version
77 # $1: kernel version
78 built_nv_from_kver() {
79 local line
80 dkms status -k "$1" | while read -r line; do
81 if [[ "$line" =~ ^([^/]+/[^,]+)", $1, "[^:]+": "(built|installed) ]]; then
82 echo "${BASH_REMATCH[1]}"
84 done
87 # list installed or built kernel version for a specific module version
88 # $1 : module name/module version
89 built_kver_from_nv() {
90 local line
91 dkms status "$1" | while read -r line; do
92 if [[ "$line" =~ ^"$1, "([^,]+)", "[^:]+": "(built|installed) ]]; then
93 echo "${BASH_REMATCH[1]}"
95 done
98 # install registered modules
99 dkms_install() {
100 # list of modules to build for a specific kernel
101 local -A tobuild=()
103 # add new/updated modules for all kernels to the build list
104 local nv kver
105 for nv in "${!DKMS_MODULES[@]}"; do
106 for kver in $(all_kver); do
107 tobuild["$nv/$kver"]=''
108 done
109 done
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"]=''
114 done
115 done
117 # list of kver which requires depmod refresh
118 local -A depmods=()
120 # let's build and install
121 local nvk mod mver
122 local -i retry=1
123 while (( $retry > 0 )); do
124 retry=0
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
132 unset tobuild[$nvk]
133 continue
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.")
137 unset tobuild[$nvk]
138 continue
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.")
142 unset tobuild[$nvk]
143 continue
144 # postpone modules with missing dependencies
145 elif ! check_dependency "$mod" "$mver" "$kver"; then
146 continue
148 # give it a try dkms
149 run dkms install --no-depmod "$mod/$mver" -k "$kver"
150 if (( $? == 0 )); then
151 # register kernel version for later depmod
152 depmods[$kver]=''
154 unset tobuild[$nvk]
155 # maybe this module was a dep of another, so we retry
156 retry=1
157 done
158 done
159 # run depmod later for performance improvments
160 if (( $DKMS_DEPMOD )); then
161 for kver in "${!depmods[@]}"; do
162 run depmod "$kver"
163 done
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.")
171 done
174 # remove registered modules
175 # run depmod once per kernel for performance improvments
176 dkms_remove() {
177 local nv kver
178 local -A depmods=()
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
186 depmods[$kver]=''
187 else
188 ERROR_MESSAGES+=("Failed to remove module $nv for kernel $kver.")
190 done
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.")
196 done
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
203 depmods[$kver]=''
204 else
205 ERROR_MESSAGES+=("Failed to remove module $nv for kernel $kver.")
207 done
208 done
209 # run depmod later for performance improvments
210 if (( $DKMS_DEPMOD )); then
211 for kver in "${!depmods[@]}"; do
212 run depmod "$kver"
213 done
217 # display hook usage and exit $1 (default 1)
218 usage() {
219 cat << EOF >&2
220 usage: ${0##*/} <options> install|remove
221 options: -D Do not run depmod
223 exit ${1:-1}
226 # emulated program entry point
227 main() {
228 [[ "$DKMS_ALPM_HOOK_DEBUG" ]] && set -x
230 # prevent each dkms call from failing with authorization errors
231 if (( EUID )); then
232 echo 'You must be root to use this hook' >&2
233 return 1
236 # parse command line options
237 declare -i DKMS_DEPMOD=1
238 local opt
239 while getopts 'hD' opt; do
240 case $opt in
241 D) DKMS_DEPMOD=0;;
242 *) usage;;
243 esac
244 done
245 shift $((OPTIND - 1))
246 (( $# != 1 )) && usage
248 # parse command action to early exit
249 case "$1" in
250 install|remove) declare -r DKMS_ACTION="$1";;
251 *) usage;;
252 esac
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
261 local path
262 for path in "$source_tree" "$install_tree"; do
263 if [[ ! -d "$path" ]]; then
264 echo "==> Missing mandatory directory: $path. Exiting!" >&2
265 return 1
267 done
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]}"]=''
290 done
292 dkms_$DKMS_ACTION
294 # display errors at the end, to maximize readers
295 local msg
296 for msg in "${ERROR_MESSAGES[@]}"; do
297 echo "==> ERROR: $msg" >&2
298 done
300 return 0
303 main "$@"