gnu: Lower initrd makers from packages to monadic procedures.
[jackhill/guix/guix.git] / gnu / system / linux-initrd.scm
CommitLineData
f09d925b 1;;; GNU Guix --- Functional package management for GNU
b0dd47a8 2;;; Copyright © 2013, 2014 Ludovic Courtès <ludo@gnu.org>
f09d925b
LC
3;;;
4;;; This file is part of GNU Guix.
5;;;
6;;; GNU Guix is free software; you can redistribute it and/or modify it
7;;; under the terms of the GNU General Public License as published by
8;;; the Free Software Foundation; either version 3 of the License, or (at
9;;; your option) any later version.
10;;;
11;;; GNU Guix is distributed in the hope that it will be useful, but
12;;; 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.
15;;;
16;;; You should have received a copy of the GNU General Public License
17;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
18
735c6dd7
LC
19(define-module (gnu system linux-initrd)
20 #:use-module (guix monads)
f09d925b 21 #:use-module (guix utils)
f09d925b
LC
22 #:use-module (gnu packages cpio)
23 #:use-module (gnu packages compression)
24 #:use-module (gnu packages linux)
f989fa39 25 #:use-module (gnu packages guile)
f09d925b
LC
26 #:use-module ((gnu packages make-bootstrap)
27 #:select (%guile-static-stripped))
735c6dd7
LC
28 #:export (expression->initrd
29 qemu-initrd
30 gnu-system-initrd))
f09d925b
LC
31
32\f
33;;; Commentary:
34;;;
35;;; Tools to build initial RAM disks (initrd's) for Linux-Libre, and in
36;;; particular initrd's that run Guile.
37;;;
38;;; Code:
39
40
41(define* (expression->initrd exp
42 #:key
43 (guile %guile-static-stripped)
44 (cpio cpio)
45 (gzip gzip)
46 (name "guile-initrd")
47 (system (%current-system))
f989fa39 48 (modules '())
f09d925b
LC
49 (linux #f)
50 (linux-modules '()))
51 "Return a package that contains a Linux initrd (a gzipped cpio archive)
52containing GUILE and that evaluates EXP upon booting. LINUX-MODULES is a list
f989fa39
LC
53of `.ko' file names to be copied from LINUX into the initrd. MODULES is a
54list of Guile module names to be embedded in the initrd."
f09d925b
LC
55
56 ;; General Linux overview in `Documentation/early-userspace/README' and
57 ;; `Documentation/filesystems/ramfs-rootfs-initramfs.txt'.
58
59 (define builder
60 `(begin
61 (use-modules (guix build utils)
62 (ice-9 pretty-print)
63 (ice-9 popen)
64 (ice-9 match)
65 (ice-9 ftw)
66 (srfi srfi-26)
67 (system base compile)
68 (rnrs bytevectors)
69 ((system foreign) #:select (sizeof)))
70
f989fa39
LC
71 (let ((guile (assoc-ref %build-inputs "guile"))
72 (cpio (string-append (assoc-ref %build-inputs "cpio")
73 "/bin/cpio"))
74 (gzip (string-append (assoc-ref %build-inputs "gzip")
75 "/bin/gzip"))
76 (modules (assoc-ref %build-inputs "modules"))
77 (gos (assoc-ref %build-inputs "modules/compiled"))
78 (scm-dir (string-append "share/guile/" (effective-version)))
79 (go-dir (format #f ".cache/guile/ccache/~a-~a-~a-~a"
80 (effective-version)
81 (if (eq? (native-endianness) (endianness little))
82 "LE"
83 "BE")
84 (sizeof '*)
85 (effective-version)))
86 (out (assoc-ref %outputs "out")))
f09d925b
LC
87 (mkdir out)
88 (mkdir "contents")
89 (with-directory-excursion "contents"
90 (copy-recursively guile ".")
91 (call-with-output-file "init"
92 (lambda (p)
93 (format p "#!/bin/guile -ds~%!#~%" guile)
94 (pretty-print ',exp p)))
95 (chmod "init" #o555)
96 (chmod "bin/guile" #o555)
97
f989fa39
LC
98 ;; Copy Guile modules.
99 (chmod scm-dir #o777)
100 (copy-recursively modules scm-dir
101 #:follow-symlinks? #t)
102 (copy-recursively gos (string-append "lib/guile/"
103 (effective-version) "/ccache")
104 #:follow-symlinks? #t)
105
f09d925b 106 ;; Compile `init'.
f989fa39
LC
107 (mkdir-p go-dir)
108 (set! %load-path (cons modules %load-path))
109 (set! %load-compiled-path (cons gos %load-compiled-path))
110 (compile-file "init"
111 #:opts %auto-compilation-options
112 #:output-file (string-append go-dir "/init.go"))
f09d925b 113
f989fa39 114 ;; Copy Linux modules.
f09d925b
LC
115 (let* ((linux (assoc-ref %build-inputs "linux"))
116 (module-dir (and linux
117 (string-append linux "/lib/modules"))))
118 (mkdir "modules")
119 ,@(map (lambda (module)
120 `(match (find-files module-dir ,module)
121 ((file)
122 (format #t "copying '~a'...~%" file)
123 (copy-file file (string-append "modules/"
124 ,module)))
125 (()
126 (error "module not found" ,module module-dir))
127 ((_ ...)
128 (error "several modules by that name"
129 ,module module-dir))))
130 linux-modules))
131
132 ;; Reset the timestamps of all the files that will make it in the
133 ;; initrd.
134 (for-each (cut utime <> 0 0 0 0)
135 (find-files "." ".*"))
136
137 (system* cpio "--version")
138 (let ((pipe (open-pipe* OPEN_WRITE cpio "-o"
139 "-O" (string-append out "/initrd")
140 "-H" "newc" "--null")))
141 (define print0
142 (let ((len (string-length "./")))
143 (lambda (file)
144 (format pipe "~a\0" (string-drop file len)))))
145
146 ;; Note: as per `ramfs-rootfs-initramfs.txt', always add
147 ;; directory entries before the files that are inside of it: "The
148 ;; Linux kernel cpio extractor won't create files in a directory
149 ;; that doesn't exist, so the directory entries must go before
150 ;; the files that go in those directories."
151 (file-system-fold (const #t)
152 (lambda (file stat result) ; leaf
153 (print0 file))
154 (lambda (dir stat result) ; down
155 (unless (string=? dir ".")
156 (print0 dir)))
157 (const #f) ; up
158 (const #f) ; skip
159 (const #f)
160 #f
161 ".")
162
163 (and (zero? (close-pipe pipe))
164 (with-directory-excursion out
165 (and (zero? (system* gzip "--best" "initrd"))
166 (rename-file "initrd.gz" "initrd")))))))))
167
735c6dd7
LC
168 (mlet* %store-monad
169 ((source (imported-modules modules))
170 (compiled (compiled-modules modules))
171 (inputs (lower-inputs
172 `(("guile" ,guile)
173 ("cpio" ,cpio)
174 ("gzip" ,gzip)
175 ("modules" ,source)
176 ("modules/compiled" ,compiled)
177 ,@(if linux
178 `(("linux" ,linux))
179 '())))))
180 (derivation-expression name builder
181 #:modules '((guix build utils))
182 #:inputs inputs)))
183
184(define (qemu-initrd)
185 "Return a monadic derivation that builds an initrd for use in a QEMU guest
186where the store is shared with the host."
f09d925b
LC
187 (expression->initrd
188 '(begin
88840f02 189 (use-modules (srfi srfi-1)
f09d925b
LC
190 (srfi srfi-26)
191 (ice-9 match)
88840f02 192 ((system base compile) #:select (compile-file))
89bf140b 193 (guix build utils)
88840f02 194 (guix build linux-initrd))
f09d925b 195
88840f02 196 (display "Welcome, this is GNU's early boot Guile.\n")
f09d925b
LC
197 (display "Use '--repl' for an initrd REPL.\n\n")
198
88840f02
LC
199 (mount-essential-file-systems)
200 (let* ((args (linux-command-line))
f09d925b
LC
201 (option (lambda (opt)
202 (let ((opt (string-append opt "=")))
203 (and=> (find (cut string-prefix? opt <>)
204 args)
205 (lambda (arg)
206 (substring arg (+ 1 (string-index arg #\=))))))))
207 (to-load (option "--load"))
208 (root (option "--root")))
209
210 (when (member "--repl" args)
211 ((@ (system repl repl) start-repl)))
212
88840f02
LC
213 (display "loading CIFS and companion modules...\n")
214 (for-each (compose load-linux-module*
215 (cut string-append "/modules/" <>))
216 (list "md4.ko" "ecb.ko" "cifs.ko"))
f09d925b 217
88840f02
LC
218 (unless (configure-qemu-networking)
219 (display "network interface is DOWN\n"))
f09d925b 220
d91712ee
LC
221 ;; Make /dev nodes.
222 (make-essential-device-nodes)
b48d21b2 223
f09d925b
LC
224 ;; Prepare the real root file system under /root.
225 (unless (file-exists? "/root")
226 (mkdir "/root"))
227 (if root
228 (mount root "/root" "ext3")
229 (mount "none" "/root" "tmpfs"))
88840f02
LC
230 (mount-essential-file-systems #:root "/root")
231
b0dd47a8 232 (mkdir-p "/root/xchg")
89bf140b 233 (mkdir-p "/root/nix/store")
f09d925b 234
7c1d8146
LC
235 (unless (file-exists? "/root/dev")
236 (mkdir "/root/dev")
237 (make-essential-device-nodes #:root "/root"))
f09d925b
LC
238
239 ;; Mount the host's store and exchange directory.
88840f02
LC
240 (mount-qemu-smb-share "/store" "/root/nix/store")
241 (mount-qemu-smb-share "/xchg" "/root/xchg")
f09d925b 242
89bf140b
LC
243 ;; Copy the directories that contain .scm and .go files so that the
244 ;; child process in the chroot can load modules (we would bind-mount
245 ;; them but for some reason that fails with EINVAL -- XXX).
b0dd47a8
LC
246 (mkdir-p "/root/share")
247 (mkdir-p "/root/lib")
89bf140b
LC
248 (mount "none" "/root/share" "tmpfs")
249 (mount "none" "/root/lib" "tmpfs")
250 (copy-recursively "/share" "/root/share"
251 #:log (%make-void-port "w"))
252 (copy-recursively "/lib" "/root/lib"
253 #:log (%make-void-port "w"))
254
255
f09d925b 256 (if to-load
b0dd47a8
LC
257 (letrec ((resolve
258 (lambda (file)
259 ;; If FILE is a symlink to an absolute file name,
260 ;; resolve it as if we were under /root.
261 (let ((st (lstat file)))
262 (if (eq? 'symlink (stat:type st))
263 (let ((target (readlink file)))
264 (resolve (string-append "/root" target)))
265 file)))))
f09d925b 266 (format #t "loading boot file '~a'...\n" to-load)
b0dd47a8 267 (compile-file (resolve (string-append "/root/" to-load))
f09d925b
LC
268 #:output-file "/root/loader.go"
269 #:opts %auto-compilation-options)
270 (match (primitive-fork)
271 (0
272 (chroot "/root")
89bf140b
LC
273 (load-compiled "/loader.go")
274
275 ;; TODO: Remove /lib, /share, and /loader.go.
276 )
f09d925b
LC
277 (pid
278 (format #t "boot file loaded under PID ~a~%" pid)
279 (let ((status (waitpid pid)))
280 (reboot)))))
281 (begin
282 (display "no boot file passed via '--load'\n")
283 (display "entering a warm and cozy REPL\n")
284 ((@ (system repl repl) start-repl))))))
285 #:name "qemu-initrd"
89bf140b
LC
286 #:modules '((guix build utils)
287 (guix build linux-initrd))
f09d925b
LC
288 #:linux linux-libre
289 #:linux-modules '("cifs.ko" "md4.ko" "ecb.ko")))
290
735c6dd7
LC
291(define (gnu-system-initrd)
292 "Initrd for the GNU system itself, with nothing QEMU-specific."
1b89a66e
LC
293 (expression->initrd
294 '(begin
295 (use-modules (srfi srfi-1)
296 (srfi srfi-26)
297 (ice-9 match)
298 (guix build utils)
299 (guix build linux-initrd))
300
301 (display "Welcome, this is GNU's early boot Guile.\n")
302 (display "Use '--repl' for an initrd REPL.\n\n")
303
304 (mount-essential-file-systems)
305 (let* ((args (linux-command-line))
306 (option (lambda (opt)
307 (let ((opt (string-append opt "=")))
308 (and=> (find (cut string-prefix? opt <>)
309 args)
310 (lambda (arg)
311 (substring arg (+ 1 (string-index arg #\=))))))))
312 (to-load (option "--load"))
313 (root (option "--root")))
314
315 (when (member "--repl" args)
316 ((@ (system repl repl) start-repl)))
317
318 ;; Make /dev nodes.
319 (make-essential-device-nodes)
320
321 ;; Prepare the real root file system under /root.
fbd1c3e9 322 (mkdir-p "/root")
1b89a66e
LC
323 (if root
324 ;; Assume ROOT has a usable /dev tree.
325 (mount root "/root" "ext3")
326 (begin
327 (mount "none" "/root" "tmpfs")
328 (make-essential-device-nodes #:root "/root")))
329
330 (mount-essential-file-systems #:root "/root")
331
fbd1c3e9
LC
332 (mkdir-p "/root/tmp")
333 (mount "none" "/root/tmp" "tmpfs")
334
1b89a66e
LC
335 ;; XXX: We don't copy our fellow Guile modules to /root (see
336 ;; 'qemu-initrd'), so if TO-LOAD tries to load a module (which can
337 ;; happen if it throws, to display the exception!), then we're
338 ;; screwed. Hopefully TO-LOAD is a simple expression that just does
339 ;; '(execlp ...)'.
340
341 (if to-load
342 (begin
343 (format #t "loading '~a'...\n" to-load)
344 (chroot "/root")
345 (primitive-load to-load)
346 (format (current-error-port)
e0ba5fe5
LC
347 "boot program '~a' terminated, rebooting~%"
348 to-load)
1b89a66e
LC
349 (sleep 2)
350 (reboot))
351 (begin
b0dd47a8 352 (display "no init file passed via '--load'\n")
1b89a66e
LC
353 (display "entering a warm and cozy REPL\n")
354 ((@ (system repl repl) start-repl))))))
355 #:name "qemu-system-initrd"
356 #:modules '((guix build linux-initrd)
357 (guix build utils))
358 #:linux linux-libre))
359
f09d925b 360;;; linux-initrd.scm ends here