authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-01-14 14:41:31-08:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-01-15 14:18:20-08:00
log5a7dc4b0fae62c8b7ec798043c93e4d90704a638
treeb5e546447555e5b4a2bcd308af49d43b49f0b2b0
parent63f345a75afdf4f956b136c06776f109f5c567af

std.Io: introduce File.MemoryMap

by defining the pointer contents to only be synchronized after explicit sync points, makes it legal to have a fallback implementation based on file operations while still supporting a handful of use cases for memory mapping. furthermore, it makes it legal for evented I/O implementations to use evented file I/O for the sync points rather than memory mapping. not yet done: - implement checking the length when options.len is null - some windows impl work - some wasi impl work - unit tests - integration with compiler

9 files changed, 597 insertions(+), 83 deletions(-)

lib/std/Io.zig+7
......@@ -9,6 +9,7 @@
99//! * concurrent queues
1010//! * wait groups and select
1111//! * mutexes, futexes, events, and conditions
12//! * memory mapped files
1213//! This interface allows programmers to write optimal, reusable code while
1314//! participating in these operations.
1415const Io = @This();
......@@ -653,6 +654,12 @@ pub const VTable = struct {
653654 fileRealPath: *const fn (?*anyopaque, File, out_buffer: []u8) File.RealPathError!usize,
654655 fileHardLink: *const fn (?*anyopaque, File, Dir, []const u8, File.HardLinkOptions) File.HardLinkError!void,
655656
657 fileMemoryMapCreate: *const fn (?*anyopaque, File, File.MemoryMap.CreateOptions) File.MemoryMap.CreateError!File.MemoryMap,
658 fileMemoryMapDestroy: *const fn (?*anyopaque, *File.MemoryMap) void,
659 fileMemoryMapSetLength: *const fn (?*anyopaque, *File.MemoryMap, n: usize) File.MemoryMap.SetLengthError!void,
660 fileMemoryMapRead: *const fn (?*anyopaque, *File.MemoryMap) File.ReadPositionalError!void,
661 fileMemoryMapWrite: *const fn (?*anyopaque, *File.MemoryMap) File.WritePositionalError!void,
662
656663 processExecutableOpen: *const fn (?*anyopaque, File.OpenFlags) std.process.OpenExecutableError!File,
657664 processExecutablePath: *const fn (?*anyopaque, buffer: []u8) std.process.ExecutablePathError!usize,
658665 lockStderr: *const fn (?*anyopaque, ?Terminal.Mode) Cancelable!LockedStderr,
lib/std/Io/File.zig+47-2
......@@ -14,6 +14,8 @@ handle: Handle,
1414pub const Reader = @import("File/Reader.zig");
1515pub const Writer = @import("File/Writer.zig");
1616pub const Atomic = @import("File/Atomic.zig");
17/// Memory intended to remain consistent with file contents.
18pub const MemoryMap = @import("File/MemoryMap.zig");
1719
1820pub const Handle = std.posix.fd_t;
1921pub const INode = std.posix.ino_t;
......@@ -529,7 +531,25 @@ pub fn readStreaming(file: File, io: Io, buffer: []const []u8) Reader.Error!usiz
529531 return io.vtable.fileReadStreaming(io.userdata, file, buffer);
530532}
531533
532pub const ReadPositionalError = Reader.Error || error{Unseekable};
534pub const ReadPositionalError = error{
535 InputOutput,
536 SystemResources,
537 /// Trying to read a directory file descriptor as if it were a file.
538 IsDir,
539 BrokenPipe,
540 /// Non-blocking has been enabled, and reading from the file descriptor
541 /// would block.
542 WouldBlock,
543 /// In WASI, this error occurs when the file descriptor does
544 /// not hold the required rights to read from it.
545 AccessDenied,
546 /// Unable to read file due to lock. Depending on the `Io` implementation,
547 /// reading from a locked file may return this error, or may ignore the
548 /// lock.
549 LockViolation,
550 /// This file cannot be read positionally.
551 Unseekable,
552} || Io.Cancelable || Io.UnexpectedError;
533553
534554/// Returns 0 on stream end or if `buffer` has no space available for data.
535555///
......@@ -539,7 +559,31 @@ pub fn readPositional(file: File, io: Io, buffer: []const []u8, offset: u64) Rea
539559 return io.vtable.fileReadPositional(io.userdata, file, buffer, offset);
540560}
541561
542pub const WritePositionalError = Writer.Error || error{Unseekable};
562pub const WritePositionalError = error{
563 DiskQuota,
564 FileTooBig,
565 InputOutput,
566 NoSpaceLeft,
567 DeviceBusy,
568 /// File descriptor does not hold the required rights to write to it.
569 AccessDenied,
570 PermissionDenied,
571 /// File is an unconnected socket, or closed its read end.
572 BrokenPipe,
573 /// Insufficient kernel memory to read from in_fd.
574 SystemResources,
575 /// The process cannot access the file because another process has locked
576 /// a portion of the file. Windows-only.
577 LockViolation,
578 /// Non-blocking has been enabled and this operation would block.
579 WouldBlock,
580 /// This error occurs when a device gets disconnected before or mid-flush
581 /// while it's being written to - errno(6): No such device or address.
582 NoDevice,
583 FileBusy,
584 /// This file cannot be written positionally.
585 Unseekable,
586} || Io.Cancelable || Io.UnexpectedError;
543587
544588/// See also:
545589/// * `writer`
......@@ -744,4 +788,5 @@ test {
744788 _ = Reader;
745789 _ = Writer;
746790 _ = Atomic;
791 _ = MemoryMap;
747792}
lib/std/Io/File/MemoryMap.zig created+79
......@@ -0,0 +1,79 @@
1const MemoryMap = @This();
2
3const builtin = @import("builtin");
4const native_os = builtin.os.tag;
5const is_windows = native_os == .windows;
6
7const std = @import("../../std.zig");
8const Io = std.Io;
9const File = Io.File;
10const Allocator = std.mem.Allocator;
11
12file: File,
13/// Byte index inside `file` where `memory` starts.
14offset: usize,
15/// Memory that may or may not remain consistent with file contents. Use `read`
16/// and `write` to ensure synchronization points.
17memory: []u8,
18/// Tells whether it is memory-mapped or file operations. On Windows this also
19/// has a section handle.
20section: ?Section,
21
22pub const Section = if (is_windows) std.os.windows.HANDLE else void;
23
24pub const CreateError = error{
25 /// A file descriptor refers to a non-regular file. Or a file mapping was requested,
26 /// but the file descriptor is not open for reading. Or `MAP.SHARED` was requested
27 /// and `PROT_WRITE` is set, but the file descriptor is not open in `RDWR` mode.
28 /// Or `PROT_WRITE` is set, but the file is append-only.
29 AccessDenied,
30 /// The `prot` argument asks for `PROT_EXEC` but the mapped area belongs to a file on
31 /// a filesystem that was mounted no-exec.
32 PermissionDenied,
33 LockedMemoryLimitExceeded,
34 ProcessFdQuotaExceeded,
35 SystemFdQuotaExceeded,
36} || Allocator.Error || File.ReadPositionalError;
37
38pub const CreateOptions = struct {
39 protection: std.process.MemoryProtection = .{ .read = true, .write = true },
40 populate: bool = true,
41 /// Byte index of file to start from.
42 offset: u64 = 0,
43 /// `null` indicates to map the entire file. If mapping the entire file is
44 /// desired and the file size is known, it is more efficient to populate
45 /// the value here.
46 len: ?usize = null,
47};
48
49pub fn create(io: Io, file: File, options: CreateOptions) CreateError!MemoryMap {
50 return io.vtable.fileMemoryMapCreate(io.userdata, file, options);
51}
52
53/// If `write` is not called before this function, changes to `memory` may or may
54/// not be synchronized to `file`.
55pub fn destroy(mm: *MemoryMap, io: Io) void {
56 io.vtable.fileMemoryMapDestroy(io.userdata, mm);
57}
58
59pub const SetLengthError = error{
60 LockedMemoryLimitExceeded,
61} || Allocator.Error || File.SetLengthError;
62
63/// Change the size of the mapping. This does not sync the contents. The size
64/// of the file after calling this is unspecified until `write` is called.
65///
66/// May change the pointer address of `memory`.
67pub fn setLength(mm: *MemoryMap, io: Io, n: usize) File.SetLengthError!void {
68 return io.vtable.fileMemoryMapSetLength(io.userdata, mm, n);
69}
70
71/// Synchronizes the contents of `memory` from `file`.
72pub fn read(mm: *MemoryMap, io: Io) File.ReadPositionalError!void {
73 return io.vtable.fileMemoryMapRead(io.userdata, mm);
74}
75
76/// Synchronizes the contents of `memory` to `file`.
77pub fn write(mm: *MemoryMap, io: Io) File.WritePositionalError!void {
78 return io.vtable.fileMemoryMapWrite(io.userdata, mm);
79}
lib/std/Io/File/Reader.zig+1-3
......@@ -29,13 +29,11 @@ interface: Io.Reader,
2929pub const Error = error{
3030 InputOutput,
3131 SystemResources,
32 /// Trying to read a directory file descriptor as if it were a file.
3233 IsDir,
3334 BrokenPipe,
3435 ConnectionResetByPeer,
3536 Timeout,
36 /// In WASI, EBADF is mapped to this error because it is returned when
37 /// trying to read a directory file descriptor as if it were a file.
38 NotOpenForReading,
3937 SocketUnconnected,
4038 /// Non-blocking has been enabled, and reading from the file descriptor
4139 /// would block.
lib/std/Io/Threaded.zig+456-66
......@@ -22,6 +22,12 @@ const windows = std.os.windows;
2222const ws2_32 = std.os.windows.ws2_32;
2323
2424/// Thread-safe.
25///
26/// Used for:
27/// * allocating `Io.Future` and `Io.Group` closures.
28/// * formatting spawning child processes
29/// * scanning environment variables on some targets
30/// * memory-mapping when mmap or equivalent is not available
2531allocator: Allocator,
2632mutex: std.Thread.Mutex = .{},
2733cond: std.Thread.Condition = .{},
......@@ -1490,6 +1496,12 @@ pub fn io(t: *Threaded) Io {
14901496 .fileRealPath = fileRealPath,
14911497 .fileHardLink = fileHardLink,
14921498
1499 .fileMemoryMapCreate = fileMemoryMapCreate,
1500 .fileMemoryMapDestroy = fileMemoryMapDestroy,
1501 .fileMemoryMapSetLength = fileMemoryMapSetLength,
1502 .fileMemoryMapRead = fileMemoryMapRead,
1503 .fileMemoryMapWrite = fileMemoryMapWrite,
1504
14931505 .processExecutableOpen = processExecutableOpen,
14941506 .processExecutablePath = processExecutablePath,
14951507 .lockStderr = lockStderr,
......@@ -1642,6 +1654,12 @@ pub fn ioBasic(t: *Threaded) Io {
16421654 .fileRealPath = fileRealPath,
16431655 .fileHardLink = fileHardLink,
16441656
1657 .fileMemoryMapCreate = fileMemoryMapCreate,
1658 .fileMemoryMapDestroy = fileMemoryMapDestroy,
1659 .fileMemoryMapSetLength = fileMemoryMapSetLength,
1660 .fileMemoryMapRead = fileMemoryMapRead,
1661 .fileMemoryMapWrite = fileMemoryMapWrite,
1662
16451663 .processExecutableOpen = processExecutableOpen,
16461664 .processExecutablePath = processExecutablePath,
16471665 .lockStderr = lockStderr,
......@@ -1733,15 +1751,24 @@ const have_wait4 = switch (native_os) {
17331751 else => false,
17341752};
17351753
1754const have_mmap = switch (native_os) {
1755 .wasi, .windows => false,
1756 else => true,
1757};
1758
17361759const open_sym = if (posix.lfs64_abi) posix.system.open64 else posix.system.open;
17371760const openat_sym = if (posix.lfs64_abi) posix.system.openat64 else posix.system.openat;
17381761const fstat_sym = if (posix.lfs64_abi) posix.system.fstat64 else posix.system.fstat;
17391762const fstatat_sym = if (posix.lfs64_abi) posix.system.fstatat64 else posix.system.fstatat;
17401763const lseek_sym = if (posix.lfs64_abi) posix.system.lseek64 else posix.system.lseek;
17411764const preadv_sym = if (posix.lfs64_abi) posix.system.preadv64 else posix.system.preadv;
1765const pread_sym = if (posix.lfs64_abi) posix.system.pread64 else posix.system.pread;
17421766const ftruncate_sym = if (posix.lfs64_abi) posix.system.ftruncate64 else posix.system.ftruncate;
17431767const pwritev_sym = if (posix.lfs64_abi) posix.system.pwritev64 else posix.system.pwritev;
1768const pwrite_sym = if (posix.lfs64_abi) posix.system.pwrite64 else posix.system.pwrite;
17441769const sendfile_sym = if (posix.lfs64_abi) posix.system.sendfile64 else posix.system.sendfile;
1770const mmap_sym = if (posix.lfs64_abi) posix.system.mmap64 else posix.system.mmap;
1771
17451772const linux_copy_file_range_use_c = std.c.versionCheck(if (builtin.abi.isAndroid()) .{
17461773 .major = 34,
17471774 .minor = 0,
......@@ -8028,27 +8055,22 @@ fn fileReadPositionalPosix(userdata: ?*anyopaque, file: File, data: []const []u8
80288055 try syscall.checkCancel();
80298056 continue;
80308057 },
8031 else => |e| {
8032 syscall.finish();
8033 switch (e) {
8034 .INVAL => |err| return errnoBug(err),
8035 .FAULT => |err| return errnoBug(err),
8036 .AGAIN => |err| return errnoBug(err),
8037 .BADF => return error.NotOpenForReading, // File operation on directory.
8038 .IO => return error.InputOutput,
8039 .ISDIR => return error.IsDir,
8040 .NOBUFS => return error.SystemResources,
8041 .NOMEM => return error.SystemResources,
8042 .NOTCONN => return error.SocketUnconnected,
8043 .CONNRESET => return error.ConnectionResetByPeer,
8044 .TIMEDOUT => return error.Timeout,
8045 .NXIO => return error.Unseekable,
8046 .SPIPE => return error.Unseekable,
8047 .OVERFLOW => return error.Unseekable,
8048 .NOTCAPABLE => return error.AccessDenied,
8049 else => |err| return posix.unexpectedErrno(err),
8050 }
8051 },
8058 .NOTCONN => |err| return syscall.errnoBug(err), // not a socket
8059 .CONNRESET => |err| return syscall.errnoBug(err), // not a socket
8060 .BADF => |err| return syscall.errnoBug(err), // use after free
8061 .INVAL => |err| return syscall.errnoBug(err),
8062 .FAULT => |err| return syscall.errnoBug(err), // segmentation fault
8063 .AGAIN => |err| return syscall.errnoBug(err),
8064 .IO => return syscall.fail(error.InputOutput),
8065 .ISDIR => return syscall.fail(error.IsDir),
8066 .NOBUFS => return syscall.fail(error.SystemResources),
8067 .NOMEM => return syscall.fail(error.SystemResources),
8068 .TIMEDOUT => return syscall.fail(error.Timeout),
8069 .NXIO => return syscall.fail(error.Unseekable),
8070 .SPIPE => return syscall.fail(error.Unseekable),
8071 .OVERFLOW => return syscall.fail(error.Unseekable),
8072 .NOTCAPABLE => return syscall.fail(error.AccessDenied),
8073 else => |err| return syscall.unexpectedErrno(err),
80528074 }
80538075 }
80548076 }
......@@ -8061,33 +8083,28 @@ fn fileReadPositionalPosix(userdata: ?*anyopaque, file: File, data: []const []u8
80618083 syscall.finish();
80628084 return @bitCast(rc);
80638085 },
8064 .INTR => {
8086 .INTR, .TIMEDOUT => {
80658087 try syscall.checkCancel();
80668088 continue;
80678089 },
8068 else => |e| {
8090 .NXIO => return syscall.fail(error.Unseekable),
8091 .SPIPE => return syscall.fail(error.Unseekable),
8092 .OVERFLOW => return syscall.fail(error.Unseekable),
8093 .NOBUFS => return syscall.fail(error.SystemResources),
8094 .NOMEM => return syscall.fail(error.SystemResources),
8095 .AGAIN => return syscall.fail(error.WouldBlock),
8096 .IO => return syscall.fail(error.InputOutput),
8097 .ISDIR => return syscall.fail(error.IsDir),
8098 .NOTCONN => |err| return syscall.errnoBug(err), // not a socket
8099 .CONNRESET => |err| return syscall.errnoBug(err), // not a socket
8100 .INVAL => |err| return syscall.errnoBug(err),
8101 .FAULT => |err| return syscall.errnoBug(err),
8102 .BADF => |err| {
80698103 syscall.finish();
8070 switch (e) {
8071 .INVAL => |err| return errnoBug(err),
8072 .FAULT => |err| return errnoBug(err),
8073 .AGAIN => return error.WouldBlock,
8074 .BADF => |err| {
8075 if (native_os == .wasi) return error.NotOpenForReading; // File operation on directory.
8076 return errnoBug(err); // File descriptor used after closed.
8077 },
8078 .IO => return error.InputOutput,
8079 .ISDIR => return error.IsDir,
8080 .NOBUFS => return error.SystemResources,
8081 .NOMEM => return error.SystemResources,
8082 .NOTCONN => return error.SocketUnconnected,
8083 .CONNRESET => return error.ConnectionResetByPeer,
8084 .TIMEDOUT => return error.Timeout,
8085 .NXIO => return error.Unseekable,
8086 .SPIPE => return error.Unseekable,
8087 .OVERFLOW => return error.Unseekable,
8088 else => |err| return posix.unexpectedErrno(err),
8089 }
8104 if (native_os == .wasi) return error.IsDir; // File operation on directory.
8105 return errnoBug(err); // File descriptor used after closed.
80908106 },
8107 else => |err| return syscall.unexpectedErrno(err),
80918108 }
80928109 }
80938110}
......@@ -8770,29 +8787,24 @@ fn fileWritePositional(
87708787 try syscall.checkCancel();
87718788 continue;
87728789 },
8773 else => |e| {
8774 syscall.finish();
8775 switch (e) {
8776 .INVAL => |err| return errnoBug(err),
8777 .FAULT => |err| return errnoBug(err),
8778 .AGAIN => return error.WouldBlock,
8779 .BADF => return error.NotOpenForWriting, // Usually a race condition.
8780 .DESTADDRREQ => |err| return errnoBug(err), // `connect` was never called.
8781 .DQUOT => return error.DiskQuota,
8782 .FBIG => return error.FileTooBig,
8783 .IO => return error.InputOutput,
8784 .NOSPC => return error.NoSpaceLeft,
8785 .PERM => return error.PermissionDenied,
8786 .PIPE => return error.BrokenPipe,
8787 .CONNRESET => |err| return errnoBug(err), // Not a socket handle.
8788 .BUSY => return error.DeviceBusy,
8789 .TXTBSY => return error.FileBusy,
8790 .NXIO => return error.Unseekable,
8791 .SPIPE => return error.Unseekable,
8792 .OVERFLOW => return error.Unseekable,
8793 else => |err| return posix.unexpectedErrno(err),
8794 }
8795 },
8790 .INVAL => |err| return syscall.errnoBug(err),
8791 .FAULT => |err| return syscall.errnoBug(err),
8792 .DESTADDRREQ => |err| return syscall.errnoBug(err), // `connect` was never called.
8793 .CONNRESET => |err| return syscall.errnoBug(err), // Not a socket handle.
8794 .BADF => |err| return syscall.errnoBug(err), // use after free
8795 .AGAIN => return syscall.fail(error.WouldBlock),
8796 .DQUOT => return syscall.fail(error.DiskQuota),
8797 .FBIG => return syscall.fail(error.FileTooBig),
8798 .IO => return syscall.fail(error.InputOutput),
8799 .NOSPC => return syscall.fail(error.NoSpaceLeft),
8800 .PERM => return syscall.fail(error.PermissionDenied),
8801 .PIPE => return syscall.fail(error.BrokenPipe),
8802 .BUSY => return syscall.fail(error.DeviceBusy),
8803 .TXTBSY => return syscall.fail(error.FileBusy),
8804 .NXIO => return syscall.fail(error.Unseekable),
8805 .SPIPE => return syscall.fail(error.Unseekable),
8806 .OVERFLOW => return syscall.fail(error.Unseekable),
8807 else => |err| return syscall.unexpectedErrno(err),
87968808 }
87978809 }
87988810}
......@@ -16107,3 +16119,381 @@ pub fn chdir(dir_path: []const u8) ChdirError!void {
1610716119 else => |err| return syscall.unexpectedErrno(err),
1610816120 };
1610916121}
16122
16123fn fileMemoryMapCreate(
16124 userdata: ?*anyopaque,
16125 file: File,
16126 options: File.MemoryMap.CreateOptions,
16127) File.MemoryMap.CreateError!File.MemoryMap {
16128 const t: *Threaded = @ptrCast(@alignCast(userdata));
16129 const offset = options.offset;
16130
16131 const page_size = std.heap.pageSize();
16132 const aligned_len: usize = options.len.?; // TODO query if necessary
16133
16134 if (createFileMap(file, options.protection, offset, options.populate, aligned_len)) |result| {
16135 return result;
16136 } else |err| switch (err) {
16137 error.Unseekable, error.Canceled => |e| return e,
16138 else => {
16139 if (builtin.mode == .Debug)
16140 std.log.warn("memory mapping failed with {t}, falling back to file operations", .{err});
16141 },
16142 }
16143
16144 const gpa = t.allocator;
16145 const alignment: Alignment = .fromByteUnits(page_size);
16146 const memory = m: {
16147 const ptr = gpa.rawAlloc(aligned_len, alignment, @returnAddress()) orelse
16148 return error.OutOfMemory;
16149 break :m ptr[0..aligned_len];
16150 };
16151 errdefer gpa.rawFree(memory, alignment, @returnAddress());
16152
16153 // If the mapping does not have read permissions, no need to populate the contents.
16154 if (options.protection.read) try mmSyncRead(file, memory, offset);
16155
16156 return .{
16157 .file = file,
16158 .offset = offset,
16159 .memory = memory,
16160 .section = null,
16161 };
16162}
16163
16164const CreateFileMapError = error{
16165 /// MaximumSize is greater than the system-defined maximum for sections, or
16166 /// greater than the specified file and the section is not writable.
16167 SectionOversize,
16168 /// A file descriptor refers to a non-regular file. Or a file mapping was requested,
16169 /// but the file descriptor is not open for reading. Or `MAP.SHARED` was requested
16170 /// and `PROT_WRITE` is set, but the file descriptor is not open in `RDWR` mode.
16171 /// Or `PROT_WRITE` is set, but the file is append-only.
16172 AccessDenied,
16173 /// The `prot` argument asks for `PROT_EXEC` but the mapped area belongs to a file on
16174 /// a filesystem that was mounted no-exec.
16175 PermissionDenied,
16176 FileBusy,
16177 LockedMemoryLimitExceeded,
16178 OperationUnsupported,
16179 ProcessFdQuotaExceeded,
16180 SystemFdQuotaExceeded,
16181 OutOfMemory,
16182 MappingAlreadyExists,
16183 Unseekable,
16184} || Io.Cancelable || Io.UnexpectedError;
16185
16186fn createFileMap(
16187 file: File,
16188 protection: std.process.MemoryProtection,
16189 offset: usize,
16190 populate: bool,
16191 aligned_len: usize,
16192) CreateFileMapError!File.MemoryMap {
16193 if (is_windows) {
16194 try Thread.checkCancel();
16195
16196 var section = windows.INVALID_HANDLE_VALUE;
16197 switch (windows.ntdll.NtCreateSection(
16198 &section,
16199 .{
16200 .SPECIFIC = .{ .SECTION = .{
16201 .QUERY = true,
16202 .MAP_WRITE = protection.write,
16203 .MAP_READ = protection.read,
16204 .MAP_EXECUTE = protection.execute,
16205 .EXTEND_SIZE = true,
16206 } },
16207 .STANDARD = .{ .RIGHTS = .REQUIRED },
16208 },
16209 null,
16210 @constCast(&@as(i64, @intCast(aligned_len))),
16211 .{ .READWRITE = true },
16212 .{ .COMMIT = populate },
16213 file.handle,
16214 )) {
16215 .SUCCESS => {},
16216 .FILE_LOCK_CONFLICT => return error.FileLocked,
16217 .INVALID_FILE_FOR_SECTION => return error.OperationUnsupported,
16218 else => |status| return windows.unexpectedStatus(status),
16219 }
16220 const current_process: windows.HANDLE = @ptrFromInt(@as(usize, @bitCast(@as(isize, -1))));
16221 var contents_ptr: ?[*]u8 = null;
16222 var contents_len = aligned_len;
16223 switch (windows.ntdll.NtMapViewOfSection(
16224 section,
16225 current_process,
16226 @ptrCast(&contents_ptr),
16227 null,
16228 0,
16229 null,
16230 &contents_len,
16231 .Unmap,
16232 .{},
16233 .{ .READWRITE = true },
16234 )) {
16235 .SUCCESS => {},
16236 .CONFLICTING_ADDRESSES => return error.MappingAlreadyExists,
16237 .SECTION_PROTECTION => return error.PermissionDenied,
16238 else => |status| return windows.unexpectedStatus(status),
16239 }
16240 return .{
16241 .file = file,
16242 .offset = offset,
16243 .memory = contents_ptr.?[0..contents_len],
16244 .section = section,
16245 };
16246 } else if (have_mmap) {
16247 const prot: posix.PROT = .{
16248 .READ = protection.read,
16249 .WRITE = protection.write,
16250 .EXEC = protection.execute,
16251 };
16252 const flags: posix.MAP = .{
16253 .TYPE = if (native_os == .linux) .SHARED_VALIDATE else .SHARED,
16254 .POPULATE = populate,
16255 };
16256
16257 const contents = while (true) {
16258 const syscall: Syscall = try .start();
16259 const casted_offset = std.math.cast(i64, offset) orelse return error.Unseekable;
16260 const rc = mmap_sym(null, aligned_len, prot, flags, file.handle, casted_offset);
16261 syscall.finish();
16262 const err: posix.E = if (builtin.link_libc) e: {
16263 if (rc != std.c.MAP_FAILED) {
16264 break @as([*]u8, @ptrCast(@alignCast(rc)))[0..aligned_len];
16265 }
16266 break :e @enumFromInt(posix.system._errno().*);
16267 } else e: {
16268 const err = posix.errno(rc);
16269 if (err == .SUCCESS) {
16270 break @as([*]u8, @ptrFromInt(rc))[0..aligned_len];
16271 }
16272 break :e err;
16273 };
16274 switch (err) {
16275 .SUCCESS => unreachable,
16276 .INTR => continue,
16277 .ACCES => return error.AccessDenied,
16278 .AGAIN => return error.LockedMemoryLimitExceeded,
16279 .EXIST => return error.MappingAlreadyExists,
16280 .MFILE => return error.ProcessFdQuotaExceeded,
16281 .NFILE => return error.SystemFdQuotaExceeded,
16282 .NODEV => return error.OperationUnsupported,
16283 .NOMEM => return error.OutOfMemory,
16284 .PERM => return error.PermissionDenied,
16285 .TXTBSY => return error.FileBusy,
16286 .OVERFLOW => return error.Unseekable,
16287 .BADF => return errnoBug(err), // Always a race condition.
16288 .INVAL => return errnoBug(err), // Invalid parameters to mmap()
16289 else => return posix.unexpectedErrno(err),
16290 }
16291 };
16292 return .{
16293 .file = file,
16294 .offset = offset,
16295 .memory = contents,
16296 .section = {},
16297 };
16298 }
16299
16300 return error.OperationUnsupported;
16301}
16302
16303fn fileMemoryMapDestroy(userdata: ?*anyopaque, mm: *File.MemoryMap) void {
16304 const t: *Threaded = @ptrCast(@alignCast(userdata));
16305 const memory = mm.memory;
16306 if (mm.section) |section| {
16307 if (is_windows) {
16308 const current_process: windows.HANDLE = @ptrFromInt(@as(usize, @bitCast(@as(isize, -1))));
16309 _ = windows.ntdll.NtUnmapViewOfSection(current_process, memory.ptr);
16310 windows.CloseHandle(section);
16311 } else {
16312 switch (posix.errno(posix.system.munmap(memory.ptr, memory.len))) {
16313 .SUCCESS => {},
16314 else => |e| {
16315 if (builtin.mode == .Debug)
16316 std.log.err("failed to unmap {d} bytes at {*}: {t}", .{ memory.len, memory.ptr, e });
16317 },
16318 }
16319 }
16320 } else {
16321 const gpa = t.allocator;
16322 const page_size = std.heap.pageSize();
16323 const alignment: Alignment = .fromByteUnits(page_size);
16324 gpa.rawFree(memory, alignment, @returnAddress());
16325 }
16326 mm.* = undefined;
16327}
16328
16329fn fileMemoryMapSetLength(
16330 userdata: ?*anyopaque,
16331 mm: *File.MemoryMap,
16332 new_len: usize,
16333) File.MemoryMap.SetLengthError!void {
16334 const t: *Threaded = @ptrCast(@alignCast(userdata));
16335 if (mm.section) |section| switch (native_os) {
16336 .windows => {
16337 _ = section;
16338 @panic("TODO");
16339 },
16340 .wasi => unreachable,
16341 else => {
16342 const flags: posix.MREMAP = .{ .MAYMOVE = true };
16343 const addr_hint: ?[*]const u8 = null;
16344 const new_memory = while (true) {
16345 const syscall: Syscall = try .start();
16346 const rc = posix.system.mremap(mm.memory.ptr, mm.memory.len, new_len, flags, addr_hint);
16347 syscall.finish();
16348 const err: posix.E = if (builtin.link_libc) e: {
16349 if (rc != std.c.MAP_FAILED) break @as([*]u8, @ptrCast(@alignCast(rc)))[0..new_len];
16350 break :e @enumFromInt(posix.system._errno().*);
16351 } else e: {
16352 const err = posix.errno(rc);
16353 if (err == .SUCCESS) break @as([*]u8, @ptrFromInt(rc))[0..new_len];
16354 break :e err;
16355 };
16356 switch (err) {
16357 .SUCCESS => unreachable,
16358 .INTR => continue,
16359 .AGAIN => return error.LockedMemoryLimitExceeded,
16360 .NOMEM => return error.OutOfMemory,
16361 .INVAL => return errnoBug(err),
16362 .FAULT => return errnoBug(err),
16363 else => return posix.unexpectedErrno(err),
16364 }
16365 };
16366 mm.memory = new_memory;
16367 },
16368 } else {
16369 const gpa = t.allocator;
16370 const page_size = std.heap.pageSize();
16371 const alignment: Alignment = .fromByteUnits(page_size);
16372 if (gpa.rawRemap(mm.memory, alignment, new_len, @returnAddress())) |new_ptr| {
16373 mm.memory = new_ptr[0..new_len];
16374 } else {
16375 const new_ptr = gpa.rawAlloc(new_len, alignment, @returnAddress()) orelse
16376 return error.OutOfMemory;
16377 const copy_len = @min(new_len, mm.memory.len);
16378 @memcpy(new_ptr[0..copy_len], mm.memory[0..copy_len]);
16379 mm.memory = new_ptr[0..new_len];
16380 }
16381 }
16382}
16383
16384fn fileMemoryMapRead(userdata: ?*anyopaque, mm: *File.MemoryMap) File.ReadPositionalError!void {
16385 const t: *Threaded = @ptrCast(@alignCast(userdata));
16386 _ = t;
16387 if (mm.section != null) return;
16388 return mmSyncRead(mm.file, mm.memory, mm.offset);
16389}
16390
16391fn fileMemoryMapWrite(userdata: ?*anyopaque, mm: *File.MemoryMap) File.WritePositionalError!void {
16392 const t: *Threaded = @ptrCast(@alignCast(userdata));
16393 _ = t;
16394 if (mm.section != null) return;
16395 return mmSyncWrite(mm.file, mm.memory, mm.offset);
16396}
16397
16398fn mmSyncRead(file: File, memory: []u8, offset: u64) File.ReadPositionalError!void {
16399 switch (native_os) {
16400 .windows => @panic("TODO"),
16401 .wasi => @panic("TODO"),
16402 else => {
16403 var i: usize = 0;
16404 const syscall: Syscall = try .start();
16405 while (true) {
16406 const buf = memory[i..];
16407 if (buf.len == 0) {
16408 syscall.finish();
16409 break;
16410 }
16411 const rc = pread_sym(file.handle, buf.ptr, buf.len, @intCast(offset + i));
16412 switch (posix.errno(rc)) {
16413 .SUCCESS => {
16414 const n: usize = @intCast(rc);
16415 if (n == 0) {
16416 syscall.finish();
16417 @memset(memory[i..], 0);
16418 break;
16419 }
16420 i += n;
16421 try syscall.checkCancel();
16422 continue;
16423 },
16424 .INTR, .TIMEDOUT => {
16425 try syscall.checkCancel();
16426 continue;
16427 },
16428 .NXIO => return syscall.fail(error.Unseekable),
16429 .SPIPE => return syscall.fail(error.Unseekable),
16430 .OVERFLOW => return syscall.fail(error.Unseekable),
16431 .NOBUFS => return syscall.fail(error.SystemResources),
16432 .NOMEM => return syscall.fail(error.SystemResources),
16433 .AGAIN => return syscall.fail(error.WouldBlock),
16434 .IO => return syscall.fail(error.InputOutput),
16435 .ISDIR => return syscall.fail(error.IsDir),
16436 .NOTCONN => |err| return syscall.errnoBug(err), // not a socket
16437 .CONNRESET => |err| return syscall.errnoBug(err), // not a socket
16438 .INVAL => |err| return syscall.errnoBug(err),
16439 .FAULT => |err| return syscall.errnoBug(err),
16440 .BADF => |err| {
16441 syscall.finish();
16442 if (native_os == .wasi) return error.IsDir; // File operation on directory.
16443 return errnoBug(err); // File descriptor used after closed.
16444 },
16445 else => |err| return syscall.unexpectedErrno(err),
16446 }
16447 }
16448 },
16449 }
16450}
16451
16452fn mmSyncWrite(file: File, memory: []u8, offset: u64) File.WritePositionalError!void {
16453 switch (native_os) {
16454 .windows => @panic("TODO"),
16455 .wasi => @panic("TODO"),
16456 else => {
16457 var i: usize = 0;
16458 const syscall: Syscall = try .start();
16459 while (true) {
16460 const buf = memory[i..];
16461 if (buf.len == 0) {
16462 syscall.finish();
16463 break;
16464 }
16465 const rc = pwrite_sym(file.handle, buf.ptr, buf.len, @intCast(offset));
16466 switch (posix.errno(rc)) {
16467 .SUCCESS => {
16468 const n: usize = @bitCast(rc);
16469 i += n;
16470 try syscall.checkCancel();
16471 continue;
16472 },
16473 .INTR => {
16474 try syscall.checkCancel();
16475 continue;
16476 },
16477 .INVAL => |err| return syscall.errnoBug(err),
16478 .FAULT => |err| return syscall.errnoBug(err),
16479 .DESTADDRREQ => |err| return syscall.errnoBug(err), // not a socket
16480 .CONNRESET => |err| return syscall.errnoBug(err), // not a socket
16481 .BADF => |err| return syscall.errnoBug(err), // use after free
16482 .AGAIN => return syscall.fail(error.WouldBlock),
16483 .DQUOT => return syscall.fail(error.DiskQuota),
16484 .FBIG => return syscall.fail(error.FileTooBig),
16485 .IO => return syscall.fail(error.InputOutput),
16486 .NOSPC => return syscall.fail(error.NoSpaceLeft),
16487 .PERM => return syscall.fail(error.PermissionDenied),
16488 .PIPE => return syscall.fail(error.BrokenPipe),
16489 .BUSY => return syscall.fail(error.DeviceBusy),
16490 .TXTBSY => return syscall.fail(error.FileBusy),
16491 .NXIO => return syscall.fail(error.Unseekable),
16492 .SPIPE => return syscall.fail(error.Unseekable),
16493 .OVERFLOW => return syscall.fail(error.Unseekable),
16494 else => |err| return syscall.unexpectedErrno(err),
16495 }
16496 }
16497 },
16498 }
16499}
lib/std/posix.zig+1-1
......@@ -433,7 +433,7 @@ pub fn read(fd: fd_t, buf: []u8) ReadError!usize {
433433 .FAULT => unreachable,
434434 .AGAIN => return error.WouldBlock,
435435 .CANCELED => return error.Canceled,
436 .BADF => return error.NotOpenForReading, // Can be a race condition.
436 .BADF => return error.Unexpected, // use after free
437437 .IO => return error.InputOutput,
438438 .ISDIR => return error.IsDir,
439439 .NOBUFS => return error.SystemResources,
lib/std/process.zig+6-9
......@@ -1018,22 +1018,19 @@ pub const ProtectMemoryError = error{
10181018 OutOfMemory,
10191019} || Io.UnexpectedError;
10201020
1021pub const ProtectMemoryOptions = packed struct(u3) {
1021pub const MemoryProtection = packed struct(u3) {
10221022 read: bool = false,
10231023 write: bool = false,
10241024 execute: bool = false,
10251025};
10261026
1027pub fn protectMemory(
1028 memory: []align(std.heap.page_size_min) u8,
1029 options: ProtectMemoryOptions,
1030) ProtectMemoryError!void {
1027pub fn protectMemory(memory: []align(std.heap.page_size_min) u8, protection: MemoryProtection) ProtectMemoryError!void {
10311028 if (native_os == .windows) {
10321029 var addr = memory.ptr; // ntdll takes an extra level of indirection here
10331030 var size = memory.len; // ntdll takes an extra level of indirection here
10341031 var old: windows.PAGE = undefined;
10351032 const current_process: windows.HANDLE = @ptrFromInt(@as(usize, @bitCast(@as(isize, -1))));
1036 const new: windows.PAGE = switch (@as(u3, @bitCast(options))) {
1033 const new: windows.PAGE = switch (@as(u3, @bitCast(protection))) {
10371034 0b000 => .{ .NOACCESS = true },
10381035 0b001 => .{ .READONLY = true },
10391036 0b010 => return error.AccessDenied, // +w -r not allowed
......@@ -1050,9 +1047,9 @@ pub fn protectMemory(
10501047 }
10511048 } else if (posix.PROT != void) {
10521049 const flags: posix.PROT = .{
1053 .READ = options.read,
1054 .WRITE = options.write,
1055 .EXEC = options.execute,
1050 .READ = protection.read,
1051 .WRITE = protection.write,
1052 .EXEC = protection.execute,
10561053 };
10571054 switch (posix.errno(posix.system.mprotect(memory.ptr, memory.len, flags))) {
10581055 .SUCCESS => return,
lib/std/zig/system.zig-1
......@@ -421,7 +421,6 @@ pub fn resolveTargetQuery(io: Io, query: Target.Query) DetectError!Target {
421421 error.BrokenPipe => return error.Unexpected,
422422 error.ConnectionResetByPeer => return error.Unexpected,
423423 error.Timeout => return error.Unexpected,
424 error.NotOpenForReading => return error.Unexpected,
425424 error.SocketUnconnected => return error.Unexpected,
426425
427426 error.AccessDenied,
src/link/MappedFile.zig-1
......@@ -1,4 +1,3 @@
1/// TODO add a mapped file abstraction to std.Io
21const MappedFile = @This();
32
43const builtin = @import("builtin");