summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMaxim Cournoyer <maxim@guixotic.coop>2025-10-21 23:22:24 +0900
committerMaxim Cournoyer <maxim@guixotic.coop>2025-11-19 09:42:11 +0900
commitfdc13e85a6d95de6d5fb1dee2c7374618510700f (patch)
tree1ddaf4e2a91579f41db948ecb72f6078229dc8b6
parent0368fbf2053ec9126c1f9a0e7b00e1d1ea833765 (diff)
syscalls: Add mmap support.
* guix/build/syscalls.scm (protection, protection-set, mmap-flag) (mmap-flag-set, %mmap-guardian, %unmapped-bytevectors): New variables. (hurd?, protection-symbol->value, protection-set->value) (mmap-flag-symbol->value, mmap-flag-set->value, pump-mmap-guardian) (%map-failed, %mmap, mmap, %munmap, munmap, %msync, msync): New procedures. * guix/build/io.scm: New file. * Makefile.am: Register it. * tests/syscalls.scm: ("mmap", "file->bytevector, reading") ("file->bytevector, writing"): New tests. Change-Id: I19ec687899eda635559e91200dd8d98669b0e35f
-rw-r--r--Makefile.am1
-rw-r--r--guix/build/io.scm56
-rw-r--r--guix/build/syscalls.scm154
-rw-r--r--tests/syscalls.scm36
4 files changed, 244 insertions, 3 deletions
diff --git a/Makefile.am b/Makefile.am
index 459f3f4b6b7..1fa00ccbd96 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -267,6 +267,7 @@ MODULES = \
267 guix/build/kconfig.scm \ 267 guix/build/kconfig.scm \
268 guix/build/linux-module-build-system.scm \ 268 guix/build/linux-module-build-system.scm \
269 guix/build/store-copy.scm \ 269 guix/build/store-copy.scm \
270 guix/build/io.scm \
270 guix/build/json.scm \ 271 guix/build/json.scm \
271 guix/build/pack.scm \ 272 guix/build/pack.scm \
272 guix/build/utils.scm \ 273 guix/build/utils.scm \
diff --git a/guix/build/io.scm b/guix/build/io.scm
new file mode 100644
index 00000000000..edb9a565adf
--- /dev/null
+++ b/guix/build/io.scm
@@ -0,0 +1,56 @@
1;;; GNU Guix --- Functional package management for GNU
2;;; Copyright © 2025 Maxim Cournoyer <maxim@guixotic.coop>
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
19(define-module (guix build io)
20 #:use-module (guix build syscalls)
21 #:use-module (ice-9 format)
22 #:use-module (rnrs bytevectors)
23 #:use-module (rnrs enums)
24 #:use-module (rnrs io ports)
25 #:use-module (system foreign)
26 #:export (file->bytevector)
27 ;; For convenience.
28 #:re-export (protection
29 protection-set
30 mmap-flag
31 mmap-flag-set))
32
33;;;
34;;; Memory mapped files.
35;;;
36
37(define* (file->bytevector file #:key
38 (protections (protection-set read))
39 (flags (if (enum-set-member? (protection write)
40 protections)
41 (mmap-flag-set shared)
42 (mmap-flag-set private)))
43 (offset 0))
44 "Return a bytevector object that is backed by a memory mapped FILE. This
45avoids eagerly copying the full file contents into memory, instead letting the
46kernel lazily page it in on demand. The underlying memory map is
47automatically unmapped when the bytevector is no longer referenced. Refer to
48the documentation of `mmap' for details about the accepted arguments."
49 (let* ((mode (format #f "rb~:[~;+~]"
50 (and (enum-set-member? (protection write) protections)
51 (enum-set-member? (mmap-flag shared) flags))))
52 (port (open-file file mode)))
53 (call-with-port port
54 (lambda (port)
55 (mmap (fileno port) (- (stat:size (stat file)) offset)
56 #:protections protections #:flags flags #:offset offset)))))
diff --git a/guix/build/syscalls.scm b/guix/build/syscalls.scm
index d40b1ae5d93..0ffa9e70f77 100644
--- a/guix/build/syscalls.scm
+++ b/guix/build/syscalls.scm
@@ -31,6 +31,7 @@
31 #:use-module (system foreign) 31 #:use-module (system foreign)
32 #:use-module (system base target) 32 #:use-module (system base target)
33 #:use-module (rnrs bytevectors) 33 #:use-module (rnrs bytevectors)
34 #:use-module (rnrs enums)
34 #:autoload (ice-9 binary-ports) (get-bytevector-n) 35 #:autoload (ice-9 binary-ports) (get-bytevector-n)
35 #:use-module (srfi srfi-1) 36 #:use-module (srfi srfi-1)
36 #:use-module (srfi srfi-9) 37 #:use-module (srfi srfi-9)
@@ -42,8 +43,18 @@
42 #:use-module (ice-9 regex) 43 #:use-module (ice-9 regex)
43 #:use-module (ice-9 match) 44 #:use-module (ice-9 match)
44 #:use-module (ice-9 ftw) 45 #:use-module (ice-9 ftw)
45 #:use-module (ice-9 threads) 46 #:export (protection
46 #:export (MS_RDONLY 47 protection-set
48 mmap-flag
49 mmap-flag-set
50 mmap
51
52 MS_ASYNC
53 MS_INVALIDATE
54 MS_SYNC
55 msync
56
57 MS_RDONLY
47 MS_NOSUID 58 MS_NOSUID
48 MS_NODEV 59 MS_NODEV
49 MS_NOEXEC 60 MS_NOEXEC
@@ -1108,6 +1119,145 @@ backend device."
1108 1119
1109 1120
1110;;; 1121;;;
1122;;; Memory maps.
1123;;;
1124
1125;;; Constants from <sys/mman.h>. Enums are used given the actual values vary
1126;;; between Linux and the Hurd, hence must be lazily resolved at the time of
1127;;; use (runtime).
1128(define-enumeration protection
1129 (none ;page can not be accessed
1130 read ;page can be read
1131 write ;page can be written
1132 exec) ;page can be executed
1133 protection-set)
1134
1135(define-enumeration mmap-flag
1136 (shared ;share changes with other processes
1137 private) ;private copy-on-write mapping
1138 mmap-flag-set)
1139
1140(define (hurd?)
1141 (string=? "GNU" (utsname:sysname (uname))))
1142
1143(define (protection-symbol->value s)
1144 ;; The values for the Hurd are taken from glibc's bits/mman.h, while those
1145 ;; for Linux from include/uapi/asm-generic/mman-common.h.
1146 (let ((hurd? (hurd?)))
1147 (cond
1148 ((eq? (protection none) s)
1149 #x0)
1150 ((eq? (protection read) s)
1151 (if hurd? #x4 #x1))
1152 ((eq? (protection write) s)
1153 #x2)
1154 ((eq? (protection exec) s)
1155 (if hurd? #x1 #x4))
1156 (else (error "unexpected protection symbol" s)))))
1157
1158(define (protection-set->value protections)
1159 "Take PROTECTIONS, a set of protection set, and compute the platform-specific
1160value for use with `mmap'."
1161 (unless (enum-set-subset? protections (enum-set-universe (protection-set)))
1162 (error "invalid mmap protection value; expected a protection enum set"
1163 protections))
1164 (apply logior (map protection-symbol->value (enum-set->list protections))))
1165
1166(define (mmap-flag-symbol->value s)
1167 ;; The values for the Hurd are taken from glibc's bits/mman.h, while those
1168 ;; for Linux from include/uapi/linux/mman.h.
1169 (let ((hurd? (hurd?)))
1170 (cond
1171 ((eq? (mmap-flag private) s)
1172 (if hurd? #x0 #x2))
1173 ((eq? (mmap-flag shared) s)
1174 (if hurd? #x10 #x1))
1175 (else (error "unexpected mmap-flag symbol" s)))))
1176
1177(define (mmap-flag-set->value flags)
1178 (unless (enum-set-subset? flags (enum-set-universe (mmap-flag-set)))
1179 (error "invalid mmap flags value; expected a mmap-flag enum set"
1180 flags))
1181 (apply logior (map mmap-flag-symbol->value (enum-set->list flags))))
1182
1183(define (%map-failed) ;mmap failure sentinel
1184 (if (= 8 (sizeof '*))
1185 #xffffffffffffffff ;64-bit
1186 #xffffffff)) ;32-bit
1187
1188(define %mmap
1189 (syscall->procedure '* "mmap" (list '* size_t int int int long)))
1190
1191(define %mmap-guardian
1192 (make-guardian))
1193
1194(define (pump-mmap-guardian)
1195 (let ((bv (%mmap-guardian)))
1196 (when bv
1197 (munmap bv)
1198 (pump-mmap-guardian))))
1199
1200(add-hook! after-gc-hook pump-mmap-guardian)
1201
1202(define* (mmap fd len #:key
1203 (protections (protection-set read))
1204 (flags (if (enum-set-member? (protection write) protections)
1205 (mmap-flag-set shared)
1206 (mmap-flag-set private)))
1207 (offset 0))
1208 "Return a bytevector to a memory-mapped region of length LEN bytes
1209for the open file descriptor FD. The mapping is created with the given memory
1210PROTECTIONS and FLAGS, which are PROTECTION and MMAP-FLAGS enum sets,
1211respectively. These values are internally converted to the correct values and
1212bitwise OR'd, and determine whether updates are visible to other processes
1213and/or carried through to the underlying file. Raise a 'system-error'
1214exception on error. The memory is automatically unmapped with `munmap' when
1215the bytevector object is no longer referenced."
1216 (let*-values (((protections*) (protection-set->value protections))
1217 ((flags*) (mmap-flag-set->value flags))
1218 ((ptr err) (%mmap %null-pointer len protections* flags*
1219 fd offset)))
1220 (when (= (%map-failed) (pointer-address ptr))
1221 (throw 'system-error "mmap" "mmap ~S with len ~S: ~A"
1222 (list fd len (strerror err))
1223 (list err)))
1224 (let ((bv (pointer->bytevector ptr len)))
1225 (%mmap-guardian bv)
1226 bv)))
1227
1228(define %munmap
1229 (syscall->procedure int "munmap" (list '* size_t)))
1230
1231(define (munmap bv)
1232 "Unmap the memory region described by BV, a bytevector object."
1233 (let*-values (((ptr) (bytevector->pointer bv))
1234 ((len) (bytevector-length bv))
1235 ((ret err) (%munmap ptr len)))
1236 (unless (zero? ret)
1237 (throw 'system-error "munmap" "munmap ~S with len ~S: ~A"
1238 (list ptr len (strerror err))
1239 (list err)))))
1240
1241(define MS_ASYNC 1) ;sync memory asynchronously
1242(define MS_INVALIDATE 2) ;invalidate the caches
1243(define MS_SYNC 4) ;synchronous memory sync
1244
1245(define %msync
1246 (syscall->procedure int "msync" (list '* size_t int)))
1247
1248(define* (msync bv #:key (flags MS_SYNC))
1249 "Flush changes made to the in-core copy of a file that was mapped into memory
1250using `mmap' back to the file system."
1251 (let*-values (((ptr) (bytevector->pointer bv))
1252 ((len) (bytevector-length bv))
1253 ((ret err) (%msync ptr len flags)))
1254 (unless (zero? ret)
1255 (throw 'system-error "msync" "msync ~S with len ~S: ~A"
1256 (list ptr len (strerror err))
1257 (list err)))))
1258
1259
1260;;;
1111;;; Random. 1261;;; Random.
1112;;; 1262;;;
1113 1263
diff --git a/tests/syscalls.scm b/tests/syscalls.scm
index a0483e68f08..bebc3aaa72d 100644
--- a/tests/syscalls.scm
+++ b/tests/syscalls.scm
@@ -22,8 +22,11 @@
22 22
23(define-module (test-syscalls) 23(define-module (test-syscalls)
24 #:use-module (guix utils) 24 #:use-module (guix utils)
25 #:use-module (guix build io)
25 #:use-module (guix build syscalls) 26 #:use-module (guix build syscalls)
27 #:use-module (guix build utils)
26 #:use-module (gnu build linux-container) 28 #:use-module (gnu build linux-container)
29 #:use-module (rnrs bytevectors)
27 #:use-module (srfi srfi-1) 30 #:use-module (srfi srfi-1)
28 #:use-module (srfi srfi-26) 31 #:use-module (srfi srfi-26)
29 #:use-module (srfi srfi-64) 32 #:use-module (srfi srfi-64)
@@ -31,7 +34,7 @@
31 #:use-module (system foreign) 34 #:use-module (system foreign)
32 #:use-module ((ice-9 ftw) #:select (scandir)) 35 #:use-module ((ice-9 ftw) #:select (scandir))
33 #:use-module (ice-9 match) 36 #:use-module (ice-9 match)
34 #:use-module (ice-9 threads)) 37 #:use-module (ice-9 textual-ports))
35 38
36;; Test the (guix build syscalls) module, although there's not much that can 39;; Test the (guix build syscalls) module, although there's not much that can
37;; actually be tested without being root. 40;; actually be tested without being root.
@@ -735,6 +738,37 @@
735 (member (system-error-errno args) 738 (member (system-error-errno args)
736 (list EPERM ENOSYS))))) 739 (list EPERM ENOSYS)))))
737 740
741(test-assert "mmap"
742 (begin
743 (call-with-output-file temp-file
744 (lambda (p)
745 (display "abcdefghij")))
746 (mmap (open-fdes temp-file O_RDONLY) 5)))
747
748(test-equal "file->bytevector, reading"
749 #\6
750 (begin
751 (call-with-output-file temp-file
752 (lambda (p)
753 (display "0123456789\n" p)))
754 (sync)
755 (integer->char
756 (bytevector-u8-ref (file->bytevector temp-file) 6))))
757
758(test-equal "file->bytevector, writing"
759 "0000000700"
760 (begin
761 (call-with-output-file temp-file
762 (lambda (p)
763 (display "0000000000" p)))
764 (sync)
765 (let ((bv (file->bytevector temp-file
766 #:protections (protection-set write))))
767
768 (bytevector-u8-set! bv 7 (char->integer #\7))
769 (msync bv)) ;ensure the file gets written
770 (call-with-input-file temp-file get-string-all)))
771
738(test-end) 772(test-end)
739 773
740(false-if-exception (delete-file temp-file)) 774(false-if-exception (delete-file temp-file))