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 @@...@@ -9,6 +9,7 @@
9//! * concurrent queues9//! * concurrent queues
10//! * wait groups and select10//! * wait groups and select
11//! * mutexes, futexes, events, and conditions11//! * mutexes, futexes, events, and conditions
12//! * memory mapped files
12//! This interface allows programmers to write optimal, reusable code while13//! This interface allows programmers to write optimal, reusable code while
13//! participating in these operations.14//! participating in these operations.
14const Io = @This();15const Io = @This();
...@@ -653,6 +654,12 @@ pub const VTable = struct {...@@ -653,6 +654,12 @@ pub const VTable = struct {
653 fileRealPath: *const fn (?*anyopaque, File, out_buffer: []u8) File.RealPathError!usize,654 fileRealPath: *const fn (?*anyopaque, File, out_buffer: []u8) File.RealPathError!usize,
654 fileHardLink: *const fn (?*anyopaque, File, Dir, []const u8, File.HardLinkOptions) File.HardLinkError!void,655 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
656 processExecutableOpen: *const fn (?*anyopaque, File.OpenFlags) std.process.OpenExecutableError!File,663 processExecutableOpen: *const fn (?*anyopaque, File.OpenFlags) std.process.OpenExecutableError!File,
657 processExecutablePath: *const fn (?*anyopaque, buffer: []u8) std.process.ExecutablePathError!usize,664 processExecutablePath: *const fn (?*anyopaque, buffer: []u8) std.process.ExecutablePathError!usize,
658 lockStderr: *const fn (?*anyopaque, ?Terminal.Mode) Cancelable!LockedStderr,665 lockStderr: *const fn (?*anyopaque, ?Terminal.Mode) Cancelable!LockedStderr,
lib/std/Io/File.zig+47-2
...@@ -14,6 +14,8 @@ handle: Handle,...@@ -14,6 +14,8 @@ handle: Handle,
14pub const Reader = @import("File/Reader.zig");14pub const Reader = @import("File/Reader.zig");
15pub const Writer = @import("File/Writer.zig");15pub const Writer = @import("File/Writer.zig");
16pub const Atomic = @import("File/Atomic.zig");16pub const Atomic = @import("File/Atomic.zig");
17/// Memory intended to remain consistent with file contents.
18pub const MemoryMap = @import("File/MemoryMap.zig");
1719
18pub const Handle = std.posix.fd_t;20pub const Handle = std.posix.fd_t;
19pub const INode = std.posix.ino_t;21pub const INode = std.posix.ino_t;
...@@ -529,7 +531,25 @@ pub fn readStreaming(file: File, io: Io, buffer: []const []u8) Reader.Error!usiz...@@ -529,7 +531,25 @@ pub fn readStreaming(file: File, io: Io, buffer: []const []u8) Reader.Error!usiz
529 return io.vtable.fileReadStreaming(io.userdata, file, buffer);531 return io.vtable.fileReadStreaming(io.userdata, file, buffer);
530}532}
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
534/// Returns 0 on stream end or if `buffer` has no space available for data.554/// Returns 0 on stream end or if `buffer` has no space available for data.
535///555///
...@@ -539,7 +559,31 @@ pub fn readPositional(file: File, io: Io, buffer: []const []u8, offset: u64) Rea...@@ -539,7 +559,31 @@ pub fn readPositional(file: File, io: Io, buffer: []const []u8, offset: u64) Rea
539 return io.vtable.fileReadPositional(io.userdata, file, buffer, offset);559 return io.vtable.fileReadPositional(io.userdata, file, buffer, offset);
540}560}
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
544/// See also:588/// See also:
545/// * `writer`589/// * `writer`
...@@ -744,4 +788,5 @@ test {...@@ -744,4 +788,5 @@ test {
744 _ = Reader;788 _ = Reader;
745 _ = Writer;789 _ = Writer;
746 _ = Atomic;790 _ = Atomic;
791 _ = MemoryMap;
747}792}
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,...@@ -29,13 +29,11 @@ interface: Io.Reader,
29pub const Error = error{29pub const Error = error{
30 InputOutput,30 InputOutput,
31 SystemResources,31 SystemResources,
32 /// Trying to read a directory file descriptor as if it were a file.
32 IsDir,33 IsDir,
33 BrokenPipe,34 BrokenPipe,
34 ConnectionResetByPeer,35 ConnectionResetByPeer,
35 Timeout,36 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,
39 SocketUnconnected,37 SocketUnconnected,
40 /// Non-blocking has been enabled, and reading from the file descriptor38 /// Non-blocking has been enabled, and reading from the file descriptor
41 /// would block.39 /// would block.
lib/std/Io/Threaded.zig+456-66
...@@ -22,6 +22,12 @@ const windows = std.os.windows;...@@ -22,6 +22,12 @@ const windows = std.os.windows;
22const ws2_32 = std.os.windows.ws2_32;22const ws2_32 = std.os.windows.ws2_32;
2323
24/// Thread-safe.24/// 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
25allocator: Allocator,31allocator: Allocator,
26mutex: std.Thread.Mutex = .{},32mutex: std.Thread.Mutex = .{},
27cond: std.Thread.Condition = .{},33cond: std.Thread.Condition = .{},
...@@ -1490,6 +1496,12 @@ pub fn io(t: *Threaded) Io {...@@ -1490,6 +1496,12 @@ pub fn io(t: *Threaded) Io {
1490 .fileRealPath = fileRealPath,1496 .fileRealPath = fileRealPath,
1491 .fileHardLink = fileHardLink,1497 .fileHardLink = fileHardLink,
14921498
1499 .fileMemoryMapCreate = fileMemoryMapCreate,
1500 .fileMemoryMapDestroy = fileMemoryMapDestroy,
1501 .fileMemoryMapSetLength = fileMemoryMapSetLength,
1502 .fileMemoryMapRead = fileMemoryMapRead,
1503 .fileMemoryMapWrite = fileMemoryMapWrite,
1504
1493 .processExecutableOpen = processExecutableOpen,1505 .processExecutableOpen = processExecutableOpen,
1494 .processExecutablePath = processExecutablePath,1506 .processExecutablePath = processExecutablePath,
1495 .lockStderr = lockStderr,1507 .lockStderr = lockStderr,
...@@ -1642,6 +1654,12 @@ pub fn ioBasic(t: *Threaded) Io {...@@ -1642,6 +1654,12 @@ pub fn ioBasic(t: *Threaded) Io {
1642 .fileRealPath = fileRealPath,1654 .fileRealPath = fileRealPath,
1643 .fileHardLink = fileHardLink,1655 .fileHardLink = fileHardLink,
16441656
1657 .fileMemoryMapCreate = fileMemoryMapCreate,
1658 .fileMemoryMapDestroy = fileMemoryMapDestroy,
1659 .fileMemoryMapSetLength = fileMemoryMapSetLength,
1660 .fileMemoryMapRead = fileMemoryMapRead,
1661 .fileMemoryMapWrite = fileMemoryMapWrite,
1662
1645 .processExecutableOpen = processExecutableOpen,1663 .processExecutableOpen = processExecutableOpen,
1646 .processExecutablePath = processExecutablePath,1664 .processExecutablePath = processExecutablePath,
1647 .lockStderr = lockStderr,1665 .lockStderr = lockStderr,
...@@ -1733,15 +1751,24 @@ const have_wait4 = switch (native_os) {...@@ -1733,15 +1751,24 @@ const have_wait4 = switch (native_os) {
1733 else => false,1751 else => false,
1734};1752};
17351753
1754const have_mmap = switch (native_os) {
1755 .wasi, .windows => false,
1756 else => true,
1757};
1758
1736const open_sym = if (posix.lfs64_abi) posix.system.open64 else posix.system.open;1759const open_sym = if (posix.lfs64_abi) posix.system.open64 else posix.system.open;
1737const openat_sym = if (posix.lfs64_abi) posix.system.openat64 else posix.system.openat;1760const openat_sym = if (posix.lfs64_abi) posix.system.openat64 else posix.system.openat;
1738const fstat_sym = if (posix.lfs64_abi) posix.system.fstat64 else posix.system.fstat;1761const fstat_sym = if (posix.lfs64_abi) posix.system.fstat64 else posix.system.fstat;
1739const fstatat_sym = if (posix.lfs64_abi) posix.system.fstatat64 else posix.system.fstatat;1762const fstatat_sym = if (posix.lfs64_abi) posix.system.fstatat64 else posix.system.fstatat;
1740const lseek_sym = if (posix.lfs64_abi) posix.system.lseek64 else posix.system.lseek;1763const lseek_sym = if (posix.lfs64_abi) posix.system.lseek64 else posix.system.lseek;
1741const preadv_sym = if (posix.lfs64_abi) posix.system.preadv64 else posix.system.preadv;1764const 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;
1742const ftruncate_sym = if (posix.lfs64_abi) posix.system.ftruncate64 else posix.system.ftruncate;1766const ftruncate_sym = if (posix.lfs64_abi) posix.system.ftruncate64 else posix.system.ftruncate;
1743const pwritev_sym = if (posix.lfs64_abi) posix.system.pwritev64 else posix.system.pwritev;1767const 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;
1744const sendfile_sym = if (posix.lfs64_abi) posix.system.sendfile64 else posix.system.sendfile;1769const 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
1745const linux_copy_file_range_use_c = std.c.versionCheck(if (builtin.abi.isAndroid()) .{1772const linux_copy_file_range_use_c = std.c.versionCheck(if (builtin.abi.isAndroid()) .{
1746 .major = 34,1773 .major = 34,
1747 .minor = 0,1774 .minor = 0,
...@@ -8028,27 +8055,22 @@ fn fileReadPositionalPosix(userdata: ?*anyopaque, file: File, data: []const []u8...@@ -8028,27 +8055,22 @@ fn fileReadPositionalPosix(userdata: ?*anyopaque, file: File, data: []const []u8
8028 try syscall.checkCancel();8055 try syscall.checkCancel();
8029 continue;8056 continue;
8030 },8057 },
8031 else => |e| {8058 .NOTCONN => |err| return syscall.errnoBug(err), // not a socket
8032 syscall.finish();8059 .CONNRESET => |err| return syscall.errnoBug(err), // not a socket
8033 switch (e) {8060 .BADF => |err| return syscall.errnoBug(err), // use after free
8034 .INVAL => |err| return errnoBug(err),8061 .INVAL => |err| return syscall.errnoBug(err),
8035 .FAULT => |err| return errnoBug(err),8062 .FAULT => |err| return syscall.errnoBug(err), // segmentation fault
8036 .AGAIN => |err| return errnoBug(err),8063 .AGAIN => |err| return syscall.errnoBug(err),
8037 .BADF => return error.NotOpenForReading, // File operation on directory.8064 .IO => return syscall.fail(error.InputOutput),
8038 .IO => return error.InputOutput,8065 .ISDIR => return syscall.fail(error.IsDir),
8039 .ISDIR => return error.IsDir,8066 .NOBUFS => return syscall.fail(error.SystemResources),
8040 .NOBUFS => return error.SystemResources,8067 .NOMEM => return syscall.fail(error.SystemResources),
8041 .NOMEM => return error.SystemResources,8068 .TIMEDOUT => return syscall.fail(error.Timeout),
8042 .NOTCONN => return error.SocketUnconnected,8069 .NXIO => return syscall.fail(error.Unseekable),
8043 .CONNRESET => return error.ConnectionResetByPeer,8070 .SPIPE => return syscall.fail(error.Unseekable),
8044 .TIMEDOUT => return error.Timeout,8071 .OVERFLOW => return syscall.fail(error.Unseekable),
8045 .NXIO => return error.Unseekable,8072 .NOTCAPABLE => return syscall.fail(error.AccessDenied),
8046 .SPIPE => return error.Unseekable,8073 else => |err| return syscall.unexpectedErrno(err),
8047 .OVERFLOW => return error.Unseekable,
8048 .NOTCAPABLE => return error.AccessDenied,
8049 else => |err| return posix.unexpectedErrno(err),
8050 }
8051 },
8052 }8074 }
8053 }8075 }
8054 }8076 }
...@@ -8061,33 +8083,28 @@ fn fileReadPositionalPosix(userdata: ?*anyopaque, file: File, data: []const []u8...@@ -8061,33 +8083,28 @@ fn fileReadPositionalPosix(userdata: ?*anyopaque, file: File, data: []const []u8
8061 syscall.finish();8083 syscall.finish();
8062 return @bitCast(rc);8084 return @bitCast(rc);
8063 },8085 },
8064 .INTR => {8086 .INTR, .TIMEDOUT => {
8065 try syscall.checkCancel();8087 try syscall.checkCancel();
8066 continue;8088 continue;
8067 },8089 },
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| {
8069 syscall.finish();8103 syscall.finish();
8070 switch (e) {8104 if (native_os == .wasi) return error.IsDir; // File operation on directory.
8071 .INVAL => |err| return errnoBug(err),8105 return errnoBug(err); // File descriptor used after closed.
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 }
8090 },8106 },
8107 else => |err| return syscall.unexpectedErrno(err),
8091 }8108 }
8092 }8109 }
8093}8110}
...@@ -8770,29 +8787,24 @@ fn fileWritePositional(...@@ -8770,29 +8787,24 @@ fn fileWritePositional(
8770 try syscall.checkCancel();8787 try syscall.checkCancel();
8771 continue;8788 continue;
8772 },8789 },
8773 else => |e| {8790 .INVAL => |err| return syscall.errnoBug(err),
8774 syscall.finish();8791 .FAULT => |err| return syscall.errnoBug(err),
8775 switch (e) {8792 .DESTADDRREQ => |err| return syscall.errnoBug(err), // `connect` was never called.
8776 .INVAL => |err| return errnoBug(err),8793 .CONNRESET => |err| return syscall.errnoBug(err), // Not a socket handle.
8777 .FAULT => |err| return errnoBug(err),8794 .BADF => |err| return syscall.errnoBug(err), // use after free
8778 .AGAIN => return error.WouldBlock,8795 .AGAIN => return syscall.fail(error.WouldBlock),
8779 .BADF => return error.NotOpenForWriting, // Usually a race condition.8796 .DQUOT => return syscall.fail(error.DiskQuota),
8780 .DESTADDRREQ => |err| return errnoBug(err), // `connect` was never called.8797 .FBIG => return syscall.fail(error.FileTooBig),
8781 .DQUOT => return error.DiskQuota,8798 .IO => return syscall.fail(error.InputOutput),
8782 .FBIG => return error.FileTooBig,8799 .NOSPC => return syscall.fail(error.NoSpaceLeft),
8783 .IO => return error.InputOutput,8800 .PERM => return syscall.fail(error.PermissionDenied),
8784 .NOSPC => return error.NoSpaceLeft,8801 .PIPE => return syscall.fail(error.BrokenPipe),
8785 .PERM => return error.PermissionDenied,8802 .BUSY => return syscall.fail(error.DeviceBusy),
8786 .PIPE => return error.BrokenPipe,8803 .TXTBSY => return syscall.fail(error.FileBusy),
8787 .CONNRESET => |err| return errnoBug(err), // Not a socket handle.8804 .NXIO => return syscall.fail(error.Unseekable),
8788 .BUSY => return error.DeviceBusy,8805 .SPIPE => return syscall.fail(error.Unseekable),
8789 .TXTBSY => return error.FileBusy,8806 .OVERFLOW => return syscall.fail(error.Unseekable),
8790 .NXIO => return error.Unseekable,8807 else => |err| return syscall.unexpectedErrno(err),
8791 .SPIPE => return error.Unseekable,
8792 .OVERFLOW => return error.Unseekable,
8793 else => |err| return posix.unexpectedErrno(err),
8794 }
8795 },
8796 }8808 }
8797 }8809 }
8798}8810}
...@@ -16107,3 +16119,381 @@ pub fn chdir(dir_path: []const u8) ChdirError!void {...@@ -16107,3 +16119,381 @@ pub fn chdir(dir_path: []const u8) ChdirError!void {
16107 else => |err| return syscall.unexpectedErrno(err),16119 else => |err| return syscall.unexpectedErrno(err),
16108 };16120 };
16109}16121}
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 {...@@ -433,7 +433,7 @@ pub fn read(fd: fd_t, buf: []u8) ReadError!usize {
433 .FAULT => unreachable,433 .FAULT => unreachable,
434 .AGAIN => return error.WouldBlock,434 .AGAIN => return error.WouldBlock,
435 .CANCELED => return error.Canceled,435 .CANCELED => return error.Canceled,
436 .BADF => return error.NotOpenForReading, // Can be a race condition.436 .BADF => return error.Unexpected, // use after free
437 .IO => return error.InputOutput,437 .IO => return error.InputOutput,
438 .ISDIR => return error.IsDir,438 .ISDIR => return error.IsDir,
439 .NOBUFS => return error.SystemResources,439 .NOBUFS => return error.SystemResources,
lib/std/process.zig+6-9
...@@ -1018,22 +1018,19 @@ pub const ProtectMemoryError = error{...@@ -1018,22 +1018,19 @@ pub const ProtectMemoryError = error{
1018 OutOfMemory,1018 OutOfMemory,
1019} || Io.UnexpectedError;1019} || Io.UnexpectedError;
10201020
1021pub const ProtectMemoryOptions = packed struct(u3) {1021pub const MemoryProtection = packed struct(u3) {
1022 read: bool = false,1022 read: bool = false,
1023 write: bool = false,1023 write: bool = false,
1024 execute: bool = false,1024 execute: bool = false,
1025};1025};
10261026
1027pub fn protectMemory(1027pub fn protectMemory(memory: []align(std.heap.page_size_min) u8, protection: MemoryProtection) ProtectMemoryError!void {
1028 memory: []align(std.heap.page_size_min) u8,
1029 options: ProtectMemoryOptions,
1030) ProtectMemoryError!void {
1031 if (native_os == .windows) {1028 if (native_os == .windows) {
1032 var addr = memory.ptr; // ntdll takes an extra level of indirection here1029 var addr = memory.ptr; // ntdll takes an extra level of indirection here
1033 var size = memory.len; // ntdll takes an extra level of indirection here1030 var size = memory.len; // ntdll takes an extra level of indirection here
1034 var old: windows.PAGE = undefined;1031 var old: windows.PAGE = undefined;
1035 const current_process: windows.HANDLE = @ptrFromInt(@as(usize, @bitCast(@as(isize, -1))));1032 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))) {
1037 0b000 => .{ .NOACCESS = true },1034 0b000 => .{ .NOACCESS = true },
1038 0b001 => .{ .READONLY = true },1035 0b001 => .{ .READONLY = true },
1039 0b010 => return error.AccessDenied, // +w -r not allowed1036 0b010 => return error.AccessDenied, // +w -r not allowed
...@@ -1050,9 +1047,9 @@ pub fn protectMemory(...@@ -1050,9 +1047,9 @@ pub fn protectMemory(
1050 }1047 }
1051 } else if (posix.PROT != void) {1048 } else if (posix.PROT != void) {
1052 const flags: posix.PROT = .{1049 const flags: posix.PROT = .{
1053 .READ = options.read,1050 .READ = protection.read,
1054 .WRITE = options.write,1051 .WRITE = protection.write,
1055 .EXEC = options.execute,1052 .EXEC = protection.execute,
1056 };1053 };
1057 switch (posix.errno(posix.system.mprotect(memory.ptr, memory.len, flags))) {1054 switch (posix.errno(posix.system.mprotect(memory.ptr, memory.len, flags))) {
1058 .SUCCESS => return,1055 .SUCCESS => return,
lib/std/zig/system.zig-1
...@@ -421,7 +421,6 @@ pub fn resolveTargetQuery(io: Io, query: Target.Query) DetectError!Target {...@@ -421,7 +421,6 @@ pub fn resolveTargetQuery(io: Io, query: Target.Query) DetectError!Target {
421 error.BrokenPipe => return error.Unexpected,421 error.BrokenPipe => return error.Unexpected,
422 error.ConnectionResetByPeer => return error.Unexpected,422 error.ConnectionResetByPeer => return error.Unexpected,
423 error.Timeout => return error.Unexpected,423 error.Timeout => return error.Unexpected,
424 error.NotOpenForReading => return error.Unexpected,
425 error.SocketUnconnected => return error.Unexpected,424 error.SocketUnconnected => return error.Unexpected,
426425
427 error.AccessDenied,426 error.AccessDenied,
src/link/MappedFile.zig-1
...@@ -1,4 +1,3 @@...@@ -1,4 +1,3 @@
1/// TODO add a mapped file abstraction to std.Io
2const MappedFile = @This();1const MappedFile = @This();
32
4const builtin = @import("builtin");3const builtin = @import("builtin");