diff options
| author | Maxim Cournoyer <maxim@guixotic.coop> | 2025-10-21 23:22:24 +0900 |
|---|---|---|
| committer | Maxim Cournoyer <maxim@guixotic.coop> | 2025-11-19 09:42:11 +0900 |
| commit | fdc13e85a6d95de6d5fb1dee2c7374618510700f (patch) | |
| tree | 1ddaf4e2a91579f41db948ecb72f6078229dc8b6 | |
| parent | 0368fbf2053ec9126c1f9a0e7b00e1d1ea833765 (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.am | 1 | ||||
| -rw-r--r-- | guix/build/io.scm | 56 | ||||
| -rw-r--r-- | guix/build/syscalls.scm | 154 | ||||
| -rw-r--r-- | tests/syscalls.scm | 36 |
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 | ||
| 45 | avoids eagerly copying the full file contents into memory, instead letting the | ||
| 46 | kernel lazily page it in on demand. The underlying memory map is | ||
| 47 | automatically unmapped when the bytevector is no longer referenced. Refer to | ||
| 48 | the 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 | ||
| 1160 | value 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 | ||
| 1209 | for the open file descriptor FD. The mapping is created with the given memory | ||
| 1210 | PROTECTIONS and FLAGS, which are PROTECTION and MMAP-FLAGS enum sets, | ||
| 1211 | respectively. These values are internally converted to the correct values and | ||
| 1212 | bitwise OR'd, and determine whether updates are visible to other processes | ||
| 1213 | and/or carried through to the underlying file. Raise a 'system-error' | ||
| 1214 | exception on error. The memory is automatically unmapped with `munmap' when | ||
| 1215 | the 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 | ||
| 1250 | using `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)) |
