gnu: ding: Use INVOKE.
[jackhill/guix/guix.git] / gnu / build / linux-modules.scm
CommitLineData
fcaa5f44 1;;; GNU Guix --- Functional package management for GNU
3c14e7e6 2;;; Copyright © 2014, 2016, 2018 Ludovic Courtès <ludo@gnu.org>
a5e13c3b 3;;; Copyright © 2017 Mathieu Othacehe <m.othacehe@gmail.com>
fcaa5f44
LC
4;;;
5;;; This file is part of GNU Guix.
6;;;
7;;; GNU Guix is free software; you can redistribute it and/or modify it
8;;; under the terms of the GNU General Public License as published by
9;;; the Free Software Foundation; either version 3 of the License, or (at
10;;; your option) any later version.
11;;;
12;;; GNU Guix is distributed in the hope that it will be useful, but
13;;; WITHOUT ANY WARRANTY; without even the implied warranty of
14;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15;;; GNU General Public License for more details.
16;;;
17;;; You should have received a copy of the GNU General Public License
18;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
19
20(define-module (gnu build linux-modules)
21 #:use-module (guix elf)
8661ad27 22 #:use-module (guix glob)
a5e13c3b 23 #:use-module (guix build syscalls)
fcd068e9 24 #:use-module ((guix build utils) #:select (find-files))
fcaa5f44
LC
25 #:use-module (rnrs io ports)
26 #:use-module (rnrs bytevectors)
27 #:use-module (srfi srfi-1)
28 #:use-module (srfi srfi-26)
29 #:use-module (ice-9 vlist)
30 #:use-module (ice-9 match)
8661ad27 31 #:use-module (ice-9 rdelim)
fcaa5f44
LC
32 #:export (dot-ko
33 ensure-dot-ko
8b2219d8 34 module-aliases
fcaa5f44 35 module-dependencies
1a5f4662 36 module-soft-dependencies
411959be 37 normalize-module-name
d2a1cf45 38 file-name->module-name
fcd068e9 39 find-module-file
fcaa5f44
LC
40 recursive-module-dependencies
41 modules-loaded
42 module-loaded?
43 load-linux-module*
44
8661ad27
LC
45 current-module-debugging-port
46
47 device-module-aliases
48 known-module-aliases
49 matching-modules))
fcaa5f44
LC
50
51;;; Commentary:
52;;;
53;;; Tools to deal with Linux kernel modules.
54;;;
55;;; Code:
56
57(define current-module-debugging-port
58 (make-parameter (%make-void-port "w")))
59
60(define (section-contents elf section)
61 "Return the contents of SECTION in ELF as a bytevector."
f43d2dcd
LC
62 (let ((contents (make-bytevector (elf-section-size section))))
63 (bytevector-copy! (elf-bytes elf) (elf-section-offset section)
fcaa5f44 64 contents 0
f43d2dcd 65 (elf-section-size section))
fcaa5f44
LC
66 contents))
67
68(define %not-nul
69 (char-set-complement (char-set #\nul)))
70
71(define (nul-separated-string->list str)
72 "Split STR at occurrences of the NUL character and return the resulting
73string list."
74 (string-tokenize str %not-nul))
75
76(define (key=value->pair str)
77 "Assuming STR has the form \"KEY=VALUE\", return a pair like (KEY
78. \"VALUE\")."
79 (let ((= (string-index str #\=)))
80 (cons (string->symbol (string-take str =))
81 (string-drop str (+ 1 =)))))
82
83(define (modinfo-section-contents file)
84 "Return the contents of the '.modinfo' section of FILE as a list of
85key/value pairs.."
86 (let* ((bv (call-with-input-file file get-bytevector-all))
87 (elf (parse-elf bv))
f43d2dcd
LC
88 (section (elf-section-by-name elf ".modinfo"))
89 (modinfo (section-contents elf section)))
fcaa5f44
LC
90 (map key=value->pair
91 (nul-separated-string->list (utf8->string modinfo)))))
92
93(define %not-comma
94 (char-set-complement (char-set #\,)))
95
96(define (module-dependencies file)
97 "Return the list of modules that FILE depends on. The returned list
98contains module names, not actual file names."
99 (let ((info (modinfo-section-contents file)))
100 (match (assq 'depends info)
101 (('depends . what)
102 (string-tokenize what %not-comma)))))
103
1a5f4662
DM
104(define not-softdep-whitespace
105 (char-set-complement (char-set #\space #\tab)))
106
107(define (module-soft-dependencies file)
108 "Return a list of (cons section soft-dependency) of module FILE."
109 ;; TEXT: "pre: baz blubb foo post: bax bar"
110 (define (parse-softdep text)
111 (let loop ((value '())
112 (tokens (string-tokenize text not-softdep-whitespace))
113 (section #f))
114 (match tokens
115 ((token rest ...)
116 (if (string=? (string-take-right token 1) ":") ; section
519be98c 117 (loop value rest (string-trim-both (string-drop-right token 1)))
1a5f4662
DM
118 (loop (cons (cons section token) value) rest section)))
119 (()
120 value))))
121
122 ;; Note: Multiple 'softdep sections are allowed.
123 (let ((info (modinfo-section-contents file)))
124 (concatenate
125 (filter-map (match-lambda
126 (('softdep . value)
127 (parse-softdep value))
128 (_ #f))
129 (modinfo-section-contents file)))))
130
8b2219d8
DM
131(define (module-aliases file)
132 "Return the list of aliases of module FILE."
133 (let ((info (modinfo-section-contents file)))
134 (filter-map (match-lambda
135 (('alias . value)
136 value)
137 (_ #f))
138 (modinfo-section-contents file))))
139
fcaa5f44
LC
140(define dot-ko
141 (cut string-append <> ".ko"))
142
143(define (ensure-dot-ko name)
144 "Return NAME with a '.ko' prefix appended, unless it already has it."
145 (if (string-suffix? ".ko" name)
146 name
147 (dot-ko name)))
148
5c7dd5ac
LC
149(define (normalize-module-name module)
150 "Return the \"canonical\" name for MODULE, replacing hyphens with
151underscores."
152 ;; See 'modname_normalize' in libkmod.
153 (string-map (lambda (chr)
154 (case chr
155 ((#\-) #\_)
156 (else chr)))
157 module))
158
7ba903b6 159(define (file-name->module-name file)
5c7dd5ac
LC
160 "Return the module name corresponding to FILE, stripping the trailing '.ko'
161and normalizing it."
162 (normalize-module-name (basename file ".ko")))
7ba903b6 163
fcd068e9
LC
164(define (find-module-file directory module)
165 "Lookup module NAME under DIRECTORY, and return its absolute file name.
166NAME can be a file name with or without '.ko', or it can be a module name.
4db7a9dc 167Raise an error if it could not be found.
fcd068e9
LC
168
169Module names can differ from file names in interesting ways; for instance,
170module names usually (always?) use underscores as the inter-word separator,
171whereas file names often, but not always, use hyphens. Examples:
172\"usb-storage.ko\", \"serpent_generic.ko\"."
173 (define names
174 ;; List of possible file names. XXX: It would of course be cleaner to
175 ;; have a database that maps module names to file names and vice versa,
176 ;; but everyone seems to be doing hacks like this one. Oh well!
177 (map ensure-dot-ko
178 (delete-duplicates
179 (list module
180 (normalize-module-name module)
181 (string-map (lambda (chr) ;converse of 'normalize-module-name'
182 (case chr
183 ((#\_) #\-)
184 (else chr)))
185 module)))))
186
187 (match (find-files directory
188 (lambda (file stat)
189 (member (basename file) names)))
190 ((file)
191 file)
192 (()
4db7a9dc 193 (error "kernel module not found" module directory))
fcd068e9
LC
194 ((_ ...)
195 (error "several modules by that name" module directory))))
196
fcaa5f44
LC
197(define* (recursive-module-dependencies files
198 #:key (lookup-module dot-ko))
199 "Return the topologically-sorted list of file names of the modules depended
200on by FILES, recursively. File names of modules are determined by applying
201LOOKUP-MODULE to the module name."
202 (let loop ((files files)
203 (result '())
204 (visited vlist-null))
205 (match files
206 (()
207 (delete-duplicates (reverse result)))
208 ((head . tail)
209 (let* ((visited? (vhash-assoc head visited))
210 (deps (if visited?
211 '()
212 (map lookup-module (module-dependencies head))))
213 (visited (if visited?
214 visited
215 (vhash-cons head #t visited))))
216 (loop (append deps tail)
217 (append result deps) visited))))))
218
219(define %not-newline
220 (char-set-complement (char-set #\newline)))
221
222(define (modules-loaded)
223 "Return the list of names of currently loaded Linux modules."
224 (let* ((contents (call-with-input-file "/proc/modules"
225 get-string-all))
226 (lines (string-tokenize contents %not-newline)))
227 (match (map string-tokenize lines)
228 (((modules . _) ...)
229 modules))))
230
7ba903b6
LC
231(define (module-black-list)
232 "Return the black list of modules that must not be loaded. This black list
233is specified using 'modprobe.blacklist=MODULE1,MODULE2,...' on the kernel
5c7dd5ac
LC
234command line; it is honored by libkmod for users that pass
235'KMOD_PROBE_APPLY_BLACKLIST', which includes 'modprobe --use-blacklist' and
236udev."
7ba903b6
LC
237 (define parameter
238 "modprobe.blacklist=")
239
240 (let ((command (call-with-input-file "/proc/cmdline"
241 get-string-all)))
242 (append-map (lambda (arg)
243 (if (string-prefix? parameter arg)
244 (string-tokenize (string-drop arg (string-length parameter))
245 %not-comma)
246 '()))
247 (string-tokenize command))))
248
fcaa5f44
LC
249(define (module-loaded? module)
250 "Return #t if MODULE is already loaded. MODULE must be a Linux module name,
251not a file name."
252 (member module (modules-loaded)))
253
254(define* (load-linux-module* file
255 #:key
256 (recursive? #t)
7ba903b6
LC
257 (lookup-module dot-ko)
258 (black-list (module-black-list)))
259 "Load Linux module from FILE, the name of a '.ko' file; return true on
260success, false otherwise. When RECURSIVE? is true, load its dependencies
261first (à la 'modprobe'.) The actual files containing modules depended on are
262obtained by calling LOOKUP-MODULE with the module name. Modules whose name
263appears in BLACK-LIST are not loaded."
7ba903b6
LC
264 (define (black-listed? module)
265 (let ((result (member module black-list)))
266 (when result
267 (format (current-module-debugging-port)
268 "not loading module '~a' because it's black-listed~%"
269 module))
270 result))
271
272 (define (load-dependencies file)
273 (let ((dependencies (module-dependencies file)))
675e81a0
LC
274 (every (cut load-linux-module* <>
275 #:lookup-module lookup-module
276 #:black-list black-list)
7ba903b6
LC
277 (map lookup-module dependencies))))
278
279 (and (not (black-listed? (file-name->module-name file)))
280 (or (not recursive?)
281 (load-dependencies file))
3c14e7e6 282 (let ((fd #f))
7ba903b6
LC
283 (format (current-module-debugging-port)
284 "loading Linux module from '~a'...~%" file)
285
286 (catch 'system-error
287 (lambda ()
3c14e7e6
LC
288 (set! fd (open-fdes file O_RDONLY))
289 (load-linux-module/fd fd)
290 (close-fdes fd)
291 #t)
7ba903b6
LC
292 (lambda args
293 ;; If this module was already loaded and we're in modprobe style, ignore
294 ;; the error.
3c14e7e6 295 (when fd (close-fdes fd))
7ba903b6
LC
296 (or (and recursive? (= EEXIST (system-error-errno args)))
297 (apply throw args)))))))
fcaa5f44 298
8661ad27
LC
299\f
300;;;
301;;; Device modules.
302;;;
303
304;; Copied from (guix utils). FIXME: Factorize.
305(define (readlink* file)
306 "Call 'readlink' until the result is not a symlink."
307 (define %max-symlink-depth 50)
308
309 (let loop ((file file)
310 (depth 0))
311 (define (absolute target)
312 (if (absolute-file-name? target)
313 target
314 (string-append (dirname file) "/" target)))
315
316 (if (>= depth %max-symlink-depth)
317 file
318 (call-with-values
319 (lambda ()
320 (catch 'system-error
321 (lambda ()
322 (values #t (readlink file)))
323 (lambda args
324 (let ((errno (system-error-errno args)))
325 (if (or (= errno EINVAL))
326 (values #f file)
327 (apply throw args))))))
328 (lambda (success? target)
329 (if success?
330 (loop (absolute target) (+ depth 1))
331 file))))))
332
333;; See 'major' and 'minor' in <sys/sysmacros.h>.
334
335(define (stat->device-major st)
336 (ash (logand #xfff00 (stat:rdev st)) -8))
337
338(define (stat->device-minor st)
339 (logand #xff (stat:rdev st)))
340
341(define %not-slash
342 (char-set-complement (char-set #\/)))
343
344(define (read-uevent port)
345 "Read a /sys 'uevent' file from PORT and return an alist where each car is a
346key such as 'MAJOR or 'DEVTYPE and each cdr is the corresponding value."
347 (let loop ((result '()))
348 (match (read-line port)
349 ((? eof-object?)
350 (reverse result))
351 (line
352 (loop (cons (key=value->pair line) result))))))
353
354(define (device-module-aliases device)
355 "Return the list of module aliases required by DEVICE, a /dev file name, as
356in this example:
357
358 (device-module-aliases \"/dev/sda\")
359 => (\"scsi:t-0x00\" \"pci:v00008086d00009D03sv0000103Csd000080FAbc01sc06i01\")
360
361The modules corresponding to these aliases can then be found using
362'matching-modules'."
363 ;; The approach is adapted from
364 ;; <https://unix.stackexchange.com/questions/97676/how-to-find-the-driver-module-associated-with-a-device-on-linux>.
365 (let* ((st (stat device))
366 (type (stat:type st))
367 (major (stat->device-major st))
368 (minor (stat->device-minor st))
369 (sys-name (string-append "/sys/dev/"
370 (case type
371 ((block-special) "block")
372 ((char-special) "char")
373 (else (symbol->string type)))
374 "/" (number->string major) ":"
375 (number->string minor)))
376 (directory (canonicalize-path (readlink* sys-name))))
377 (let loop ((components (string-tokenize directory %not-slash))
378 (aliases '()))
379 (match components
380 (("sys" "devices" _)
381 (reverse aliases))
382 ((head ... _)
383 (let ((uevent (string-append (string-join components "/" 'prefix)
384 "/uevent")))
385 (if (file-exists? uevent)
386 (let ((props (call-with-input-file uevent read-uevent)))
387 (match (assq-ref props 'MODALIAS)
388 (#f (loop head aliases))
389 (alias (loop head (cons alias aliases)))))
390 (loop head aliases))))))))
391
392(define (read-module-aliases port)
393 "Read from PORT data in the Linux 'modules.alias' file format. Return a
394list of alias/module pairs where each alias is a glob pattern as like the
395result of:
396
71e08fde 397 (string->compiled-sglob \"scsi:t-0x01*\")
8661ad27
LC
398
399and each module is a module name like \"snd_hda_intel\"."
400 (define (comment? str)
401 (string-prefix? "#" str))
402
403 (define (tokenize str)
404 ;; Lines have the form "alias ALIAS MODULE", where ALIAS can contain
405 ;; whitespace. This is why we don't use 'string-tokenize'.
406 (let* ((str (string-trim-both str))
407 (left (string-index str #\space))
408 (right (string-rindex str #\space)))
409 (list (string-take str left)
410 (string-trim-both (substring str left right))
411 (string-trim-both (string-drop str right)))))
412
413 (let loop ((aliases '()))
414 (match (read-line port)
415 ((? eof-object?)
416 (reverse aliases))
417 ((? comment?)
418 (loop aliases))
419 (line
420 (match (tokenize line)
421 (("alias" alias module)
71e08fde 422 (loop (alist-cons (string->compiled-sglob alias) module
8661ad27
LC
423 aliases)))
424 (() ;empty line
425 (loop aliases)))))))
426
a57df67b
LC
427(define (current-kernel-directory)
428 "Return the directory of the currently running Linux kernel."
8661ad27
LC
429 (string-append (or (getenv "LINUX_MODULE_DIRECTORY")
430 "/run/booted-system/kernel/lib/modules")
a57df67b
LC
431 "/" (utsname:release (uname))))
432
433(define (current-alias-file)
434 "Return the absolute file name of the default 'modules.alias' file."
435 (string-append (current-kernel-directory) "/modules.alias"))
8661ad27
LC
436
437(define* (known-module-aliases #:optional (alias-file (current-alias-file)))
438 "Return the list of alias/module pairs read from ALIAS-FILE. Each alias is
439actually a pattern."
440 (call-with-input-file alias-file read-module-aliases))
441
442(define* (matching-modules alias
443 #:optional (known-aliases (known-module-aliases)))
444 "Return the list of modules that match ALIAS according to KNOWN-ALIASES.
445ALIAS is a string like \"scsi:t-0x00\" as returned by
446'device-module-aliases'."
447 (filter-map (match-lambda
448 ((pattern . module)
449 (and (glob-match? pattern alias)
450 module)))
451 known-aliases))
452
fcaa5f44 453;;; linux-modules.scm ends here