summary refs log tree commit diff
diff options
context:
space:
mode:
authorDanny Milosavljevic <dannym@scratchpost.org>2017-12-16 00:16:40 +0100
committerDanny Milosavljevic <dannym@scratchpost.org>2017-12-16 07:33:09 +0100
commit97817e7f18bd3bd95152e46239ad96928678e944 (patch)
treeb53456638df011edb833b0ad3b193dcf2f9ccbbd
parent5985d01bd84a10439c5c0418bf2979aaa8a08b1a (diff)
downloadguix-97817e7f18bd3bd95152e46239ad96928678e944.tar.gz
linux-boot: Add make-static-device-nodes.
* gnu/build/linux-boot.scm (make-static-device-nodes): New variable.
(<device-node>): New variable.
(read-static-device-nodes): New variable.
(report-system-error): New variable.
(catch-system-error): New variable.
(create-device-node): New variable.
(mkdir-p*): New variable.

Co-Authored-By: Ludovic Courtès <ludo@gnu.org>
-rw-r--r--gnu/build/linux-boot.scm107
1 files changed, 107 insertions, 0 deletions
diff --git a/gnu/build/linux-boot.scm b/gnu/build/linux-boot.scm
index 2547f1e0af..4dd740174e 100644
--- a/gnu/build/linux-boot.scm
+++ b/gnu/build/linux-boot.scm
@@ -22,8 +22,11 @@
   #:use-module (system repl error-handling)
   #:autoload   (system repl repl) (start-repl)
   #:use-module (srfi srfi-1)
+  #:use-module (srfi srfi-9)
   #:use-module (srfi srfi-26)
   #:use-module (ice-9 match)
+  #:use-module (ice-9 rdelim)
+  #:use-module (ice-9 regex)
   #:use-module (ice-9 ftw)
   #:use-module (guix build utils)
   #:use-module ((guix build syscalls)
@@ -35,6 +38,7 @@
             linux-command-line
             find-long-option
             make-essential-device-nodes
+            make-static-device-nodes
             configure-qemu-networking
 
             bind-mount
@@ -105,6 +109,109 @@ with the given MAJOR number, starting with MINOR."
              'block-special #o644 (device-number major (+ minor i)))
       (loop (+ i 1)))))
 
+;; Representation of a /dev node.
+(define-record-type <device-node>
+  (device-node name type major minor module)
+  device-node?
+  (name device-node-name)
+  (type device-node-type)
+  (major device-node-major)
+  (minor device-node-minor)
+  (module device-node-module))
+
+(define (read-static-device-nodes port)
+  "Read from PORT a list of <device-node> written in the format used by
+/lib/modules/*/*.devname files."
+  (let loop ((line (read-line port)))
+    (if (eof-object? line)
+      '()
+      (match (string-split line #\space)
+       (((? (cut string-prefix? "#" <>)) _ ...)
+        (loop (read-line port)))
+       ((module-name device-name device-spec)
+        (let* ((device-parts
+               (string-match "([bc])([0-9][0-9]*):([0-9][0-9]*)"
+                             device-spec))
+               (type-string (match:substring device-parts 1))
+               (type (match type-string
+                      ("c" 'char-special)
+                      ("b" 'block-special)))
+               (major-string (match:substring device-parts 2))
+               (major (string->number major-string 10))
+               (minor-string (match:substring device-parts 3))
+               (minor (string->number minor-string 10)))
+          (cons (device-node device-name type major minor module-name)
+                (loop (read-line port)))))
+       (_
+        (begin
+          (format (current-error-port)
+                  "read-static-device-nodes: ignored devname line '~a'~%" line)
+          (loop (read-line port))))))))
+
+(define* (mkdir-p* dir #:optional (mode #o755))
+  "This is a variant of 'mkdir-p' that works around
+<http://bugs.gnu.org/24659> by passing MODE explicitly in each 'mkdir' call."
+  (define absolute?
+    (string-prefix? "/" dir))
+
+  (define not-slash
+    (char-set-complement (char-set #\/)))
+
+  (let loop ((components (string-tokenize dir not-slash))
+             (root       (if absolute?
+                             ""
+                             ".")))
+    (match components
+      ((head tail ...)
+       (let ((path (string-append root "/" head)))
+         (catch 'system-error
+           (lambda ()
+             (mkdir path mode)
+             (loop tail path))
+           (lambda args
+             (if (= EEXIST (system-error-errno args))
+                 (loop tail path)
+                 (apply throw args))))))
+      (() #t))))
+
+(define (report-system-error name . args)
+  "Report a system error for the file NAME."
+  (let ((errno (system-error-errno args)))
+    (format (current-error-port) "could not create '~a': ~a~%" name
+            (strerror errno))))
+
+;; Catch a system-error, log it and don't die from it.
+(define-syntax-rule (catch-system-error name exp)
+  (catch 'system-error
+    (lambda ()
+      exp)
+    (lambda args
+      (apply report-system-error name args))))
+
+;; Create a device node like the <device-node> passed here on the filesystem.
+(define create-device-node
+  (match-lambda
+    (($ <device-node> xname type major minor module)
+     (let ((name (string-append "/dev/" xname)))
+       (mkdir-p* (dirname name))
+       (catch-system-error name
+         (mknod name type #o600 (device-number major minor)))))))
+
+(define* (make-static-device-nodes linux-release-module-directory)
+  "Create static device nodes required by the given Linux release.
+This is required in order to solve a chicken-or-egg problem:
+The Linux kernel has a feature to autoload modules when a device is first
+accessed.
+And udev has a feature to set the permissions of static nodes correctly
+when it is starting up and also to automatically create nodes when hardware
+is hotplugged. That leaves universal device files which are not linked to
+one specific hardware device. These we have to create."
+  (let ((devname-name (string-append linux-release-module-directory "/"
+                                     "modules.devname")))
+    (for-each create-device-node
+              (call-with-input-file devname-name
+                                    read-static-device-nodes))))
+
 (define* (make-essential-device-nodes #:key (root "/"))
   "Make essential device nodes under ROOT/dev."
   ;; The hand-made devtmpfs/udev!