authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-01-16 03:59:31+01:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-01-16 03:59:31+01:00
log84261222b47c698d42682de1ce6931e01dc0b649
treef5a448d213c0176b5a20cd03e59a9f69765c2af7
parent63f345a75afdf4f956b136c06776f109f5c567af
parent7b21fd7244ed1b0459b542346e96a20e91138f69

Merge pull request 'introduce std.Io.File.MemoryMap API' (#30840) from mmap into master

Reviewed-on: https://codeberg.org/ziglang/zig/pulls/30840

17 files changed, 1014 insertions(+), 128 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, File.MemoryMap.CreateOptions) 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+60-2
...@@ -14,12 +14,15 @@ handle: Handle,...@@ -14,12 +14,15 @@ 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;
20pub const NLink = std.posix.nlink_t;22pub const NLink = std.posix.nlink_t;
21pub const Uid = std.posix.uid_t;23pub const Uid = std.posix.uid_t;
22pub const Gid = std.posix.gid_t;24pub const Gid = std.posix.gid_t;
25pub const BlockSize = u32;
2326
24pub const Kind = enum {27pub const Kind = enum {
25 block_device,28 block_device,
...@@ -63,6 +66,10 @@ pub const Stat = struct {...@@ -63,6 +66,10 @@ pub const Stat = struct {
63 mtime: Io.Timestamp,66 mtime: Io.Timestamp,
64 /// Last status/metadata change time in nanoseconds, relative to UTC 1970-01-01.67 /// Last status/metadata change time in nanoseconds, relative to UTC 1970-01-01.
65 ctime: Io.Timestamp,68 ctime: Io.Timestamp,
69 /// Smallest chunk length in bytes appropriate for optimal I/O. This will
70 /// be set to `1` for operating systems or file systems that do not
71 /// recognize this concept. Not always a power of two.
72 block_size: BlockSize,
66};73};
6774
68pub fn stdout() File {75pub fn stdout() File {
...@@ -529,7 +536,27 @@ pub fn readStreaming(file: File, io: Io, buffer: []const []u8) Reader.Error!usiz...@@ -529,7 +536,27 @@ pub fn readStreaming(file: File, io: Io, buffer: []const []u8) Reader.Error!usiz
529 return io.vtable.fileReadStreaming(io.userdata, file, buffer);536 return io.vtable.fileReadStreaming(io.userdata, file, buffer);
530}537}
531538
532pub const ReadPositionalError = Reader.Error || error{Unseekable};539pub const ReadPositionalError = error{
540 InputOutput,
541 SystemResources,
542 /// Trying to read a directory file descriptor as if it were a file.
543 IsDir,
544 BrokenPipe,
545 /// Non-blocking has been enabled, and reading from the file descriptor
546 /// would block.
547 WouldBlock,
548 /// In WASI, this error occurs when the file descriptor does
549 /// not hold the required rights to read from it.
550 AccessDenied,
551 /// Unable to read file due to lock. Depending on the `Io` implementation,
552 /// reading from a locked file may return this error, or may ignore the
553 /// lock.
554 LockViolation,
555 /// This file cannot be read positionally.
556 Unseekable,
557 /// File was not opened with read capability.
558 NotOpenForReading,
559} || Io.Cancelable || Io.UnexpectedError;
533560
534/// Returns 0 on stream end or if `buffer` has no space available for data.561/// Returns 0 on stream end or if `buffer` has no space available for data.
535///562///
...@@ -539,7 +566,33 @@ pub fn readPositional(file: File, io: Io, buffer: []const []u8, offset: u64) Rea...@@ -539,7 +566,33 @@ pub fn readPositional(file: File, io: Io, buffer: []const []u8, offset: u64) Rea
539 return io.vtable.fileReadPositional(io.userdata, file, buffer, offset);566 return io.vtable.fileReadPositional(io.userdata, file, buffer, offset);
540}567}
541568
542pub const WritePositionalError = Writer.Error || error{Unseekable};569pub const WritePositionalError = error{
570 DiskQuota,
571 FileTooBig,
572 InputOutput,
573 NoSpaceLeft,
574 DeviceBusy,
575 /// File descriptor does not hold the required rights to write to it.
576 AccessDenied,
577 PermissionDenied,
578 /// File is an unconnected socket, or closed its read end.
579 BrokenPipe,
580 /// Insufficient kernel memory to read from in_fd.
581 SystemResources,
582 /// The process cannot access the file because another process has locked
583 /// a portion of the file. Windows-only.
584 LockViolation,
585 /// Non-blocking has been enabled and this operation would block.
586 WouldBlock,
587 /// This error occurs when a device gets disconnected before or mid-flush
588 /// while it's being written to - errno(6): No such device or address.
589 NoDevice,
590 FileBusy,
591 /// This file cannot be written positionally.
592 Unseekable,
593 /// File was not opened with write capability.
594 NotOpenForWriting,
595} || Io.Cancelable || Io.UnexpectedError;
543596
544/// See also:597/// See also:
545/// * `writer`598/// * `writer`
...@@ -740,8 +793,13 @@ pub fn hardLink(...@@ -740,8 +793,13 @@ pub fn hardLink(
740 return io.vtable.fileHardLink(io.userdata, file, new_dir, new_sub_path, options);793 return io.vtable.fileHardLink(io.userdata, file, new_dir, new_sub_path, options);
741}794}
742795
796pub fn createMemoryMap(file: File, io: Io, options: MemoryMap.CreateOptions) MemoryMap.CreateError!MemoryMap {
797 return .create(io, file, options);
798}
799
743test {800test {
744 _ = Reader;801 _ = Reader;
745 _ = Writer;802 _ = Writer;
746 _ = Atomic;803 _ = Atomic;
804 _ = MemoryMap;
747}805}
lib/std/Io/File/MemoryMap.zig created+119
...@@ -0,0 +1,119 @@
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. Page-aligned.
14offset: u64,
15/// Memory that may or may not remain consistent with file contents. Use `read`
16/// and `write` to ensure synchronization points. Length has no alignment
17/// requirement.
18memory: []align(std.heap.page_size_min) u8,
19/// Tells whether it is memory-mapped or file operations. On Windows this also
20/// has a section handle.
21section: ?Section,
22
23pub const Section = if (is_windows) std.os.windows.HANDLE else void;
24
25pub const CreateError = error{
26 /// One of the following:
27 /// * The `File.Kind` is not `file`.
28 /// * The file is not open for reading and read access protections enabled.
29 /// * The file is not open for writing and write access protections enabled.
30 AccessDenied,
31 /// The `prot` argument asks for `PROT_EXEC` but the mapped area belongs to a file on
32 /// a filesystem that was mounted no-exec.
33 PermissionDenied,
34 LockedMemoryLimitExceeded,
35 ProcessFdQuotaExceeded,
36 SystemFdQuotaExceeded,
37} || Allocator.Error || File.ReadPositionalError;
38
39pub const CreateOptions = struct {
40 /// Size of the mapping, in bytes. If this is longer than the file size,
41 /// `memory` beyond the file end will be filled with zeroes and it is
42 /// unspecified whether, after calling `write`, the file length will be
43 /// set to `len` or remain unchanged.
44 ///
45 /// This value has no minimum alignment requirement, but may gain
46 /// efficiency benefits from being a multiple of `File.Stat.block_size`.
47 len: usize,
48 /// When this has read set to false, bytes that are not modified before a
49 /// sync may have the original file contents, or may be set to zero.
50 protection: std.process.MemoryProtection = .{ .read = true, .write = true },
51 /// If set to `true`, allows bytes observed before calling `read` to be
52 /// undefined, and bytes unwritten before calling `write` to write
53 /// undefined memory to the file.
54 undefined_contents: bool = false,
55 /// Prefault the pages. If this option is unsupported, it is silently
56 /// ignored. Aside from custom Io implementations, this option is only
57 /// supported on Linux.
58 populate: bool = true,
59 /// Asserted to be a multiple of page size which can be obtained via
60 /// `std.heap.pageSize`.
61 offset: u64 = 0,
62};
63
64/// To release the resources associated with the returned `MemoryMap`, call
65/// `destroy`.
66pub fn create(io: Io, file: File, options: CreateOptions) CreateError!MemoryMap {
67 return io.vtable.fileMemoryMapCreate(io.userdata, file, options);
68}
69
70/// If `write` is not called before this function, changes to `memory` may or may
71/// not be synchronized to `file`.
72pub fn destroy(mm: *MemoryMap, io: Io) void {
73 io.vtable.fileMemoryMapDestroy(io.userdata, mm);
74}
75
76pub const SetLengthError = error{
77 /// One of the following:
78 /// * The `File.Kind` is not `file`.
79 /// * The file is not open for reading and read access protections enabled.
80 /// * The file is not open for writing and write access protections enabled.
81 AccessDenied,
82 /// The `prot` argument asks for `PROT_EXEC` but the mapped area belongs to a file on
83 /// a filesystem that was mounted no-exec.
84 PermissionDenied,
85 LockedMemoryLimitExceeded,
86 ProcessFdQuotaExceeded,
87 SystemFdQuotaExceeded,
88} || Allocator.Error || File.SetLengthError;
89
90/// Change the size of the mapping. This does not sync the contents. The size
91/// of the file after calling this is unspecified until `write` is called.
92///
93/// May change the pointer address of `memory`.
94///
95/// `options` is needed because the mapping may need to be destroyed and
96/// re-created. All the same options must be provided except for `len` which is
97/// the new length.
98///
99/// This operation cannot be completed atomically on all operating systems.
100/// When this function fails, the `MemoryMap` may be left in an unmapped state,
101/// which can be detected by checking if `memory.len` is zero. In such case it
102/// is safe to call `destroy` which will have no effect.
103pub fn setLength(mm: *MemoryMap, io: Io, options: CreateOptions) SetLengthError!void {
104 return io.vtable.fileMemoryMapSetLength(io.userdata, mm, options);
105}
106
107/// Synchronizes the contents of `memory` from `file`.
108pub fn read(mm: *MemoryMap, io: Io) File.ReadPositionalError!void {
109 return io.vtable.fileMemoryMapRead(io.userdata, mm);
110}
111
112/// Synchronizes the contents of `memory` to `file`.
113///
114/// If `memory.len` is greater than file size, the bytes beyond the end of the
115/// file may be dropped, or they may be written, extending the size of the
116/// file.
117pub fn write(mm: *MemoryMap, io: Io) File.WritePositionalError!void {
118 return io.vtable.fileMemoryMapWrite(io.userdata, mm);
119}
lib/std/Io/File/Reader.zig+2-3
...@@ -29,12 +29,11 @@ interface: Io.Reader,...@@ -29,12 +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 /// File was not opened with read capability.
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,37 NotOpenForReading,
39 SocketUnconnected,38 SocketUnconnected,
40 /// Non-blocking has been enabled, and reading from the file descriptor39 /// Non-blocking has been enabled, and reading from the file descriptor
lib/std/Io/Threaded.zig+667-86
...@@ -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 = .{},
...@@ -51,6 +57,7 @@ use_sendfile: UseSendfile = .default,...@@ -51,6 +57,7 @@ use_sendfile: UseSendfile = .default,
51use_copy_file_range: UseCopyFileRange = .default,57use_copy_file_range: UseCopyFileRange = .default,
52use_fcopyfile: UseFcopyfile = .default,58use_fcopyfile: UseFcopyfile = .default,
53use_fchmodat2: UseFchmodat2 = .default,59use_fchmodat2: UseFchmodat2 = .default,
60disable_memory_mapping: bool,
5461
55stderr_writer: File.Writer = .{62stderr_writer: File.Writer = .{
56 .io = undefined,63 .io = undefined,
...@@ -69,6 +76,13 @@ random_file: RandomFile = .{},...@@ -69,6 +76,13 @@ random_file: RandomFile = .{},
6976
70csprng: Csprng = .{},77csprng: Csprng = .{},
7178
79system_basic_information: SystemBasicInformation = .{},
80
81const SystemBasicInformation = if (!is_windows) struct {} else struct {
82 buffer: windows.SYSTEM_BASIC_INFORMATION = undefined,
83 initialized: std.atomic.Value(bool) = .{ .raw = false },
84};
85
72pub const Csprng = struct {86pub const Csprng = struct {
73 rng: std.Random.DefaultCsprng = .{87 rng: std.Random.DefaultCsprng = .{
74 .state = undefined,88 .state = undefined,
...@@ -1214,6 +1228,8 @@ pub const InitOptions = struct {...@@ -1214,6 +1228,8 @@ pub const InitOptions = struct {
1214 /// * `processExecutablePath` on OpenBSD and Haiku (observes "PATH").1228 /// * `processExecutablePath` on OpenBSD and Haiku (observes "PATH").
1215 /// * `processSpawn`, `processSpawnPath`, `processReplace`, `processReplacePath`1229 /// * `processSpawn`, `processSpawnPath`, `processReplace`, `processReplacePath`
1216 environ: process.Environ,1230 environ: process.Environ,
1231 /// If set to `true`, `File.MemoryMap` APIs will always take the fallback path.
1232 disable_memory_mapping: bool = false,
1217};1233};
12181234
1219/// Related:1235/// Related:
...@@ -1241,6 +1257,7 @@ pub fn init(...@@ -1241,6 +1257,7 @@ pub fn init(
1241 .argv0 = options.argv0,1257 .argv0 = options.argv0,
1242 .environ = .{ .process_environ = options.environ },1258 .environ = .{ .process_environ = options.environ },
1243 .worker_threads = init_single_threaded.worker_threads,1259 .worker_threads = init_single_threaded.worker_threads,
1260 .disable_memory_mapping = options.disable_memory_mapping,
1244 };1261 };
12451262
1246 const cpu_count = std.Thread.getCpuCount();1263 const cpu_count = std.Thread.getCpuCount();
...@@ -1257,6 +1274,7 @@ pub fn init(...@@ -1257,6 +1274,7 @@ pub fn init(
1257 .argv0 = options.argv0,1274 .argv0 = options.argv0,
1258 .environ = .{ .process_environ = options.environ },1275 .environ = .{ .process_environ = options.environ },
1259 .worker_threads = .init(null),1276 .worker_threads = .init(null),
1277 .disable_memory_mapping = options.disable_memory_mapping,
1260 };1278 };
12611279
1262 if (posix.Sigaction != void) {1280 if (posix.Sigaction != void) {
...@@ -1293,6 +1311,7 @@ pub const init_single_threaded: Threaded = .{...@@ -1293,6 +1311,7 @@ pub const init_single_threaded: Threaded = .{
1293 .argv0 = .empty,1311 .argv0 = .empty,
1294 .environ = .{},1312 .environ = .{},
1295 .worker_threads = .init(null),1313 .worker_threads = .init(null),
1314 .disable_memory_mapping = false,
1296};1315};
12971316
1298var global_single_threaded_instance: Threaded = .init_single_threaded;1317var global_single_threaded_instance: Threaded = .init_single_threaded;
...@@ -1490,6 +1509,12 @@ pub fn io(t: *Threaded) Io {...@@ -1490,6 +1509,12 @@ pub fn io(t: *Threaded) Io {
1490 .fileRealPath = fileRealPath,1509 .fileRealPath = fileRealPath,
1491 .fileHardLink = fileHardLink,1510 .fileHardLink = fileHardLink,
14921511
1512 .fileMemoryMapCreate = fileMemoryMapCreate,
1513 .fileMemoryMapDestroy = fileMemoryMapDestroy,
1514 .fileMemoryMapSetLength = fileMemoryMapSetLength,
1515 .fileMemoryMapRead = fileMemoryMapRead,
1516 .fileMemoryMapWrite = fileMemoryMapWrite,
1517
1493 .processExecutableOpen = processExecutableOpen,1518 .processExecutableOpen = processExecutableOpen,
1494 .processExecutablePath = processExecutablePath,1519 .processExecutablePath = processExecutablePath,
1495 .lockStderr = lockStderr,1520 .lockStderr = lockStderr,
...@@ -1642,6 +1667,12 @@ pub fn ioBasic(t: *Threaded) Io {...@@ -1642,6 +1667,12 @@ pub fn ioBasic(t: *Threaded) Io {
1642 .fileRealPath = fileRealPath,1667 .fileRealPath = fileRealPath,
1643 .fileHardLink = fileHardLink,1668 .fileHardLink = fileHardLink,
16441669
1670 .fileMemoryMapCreate = fileMemoryMapCreate,
1671 .fileMemoryMapDestroy = fileMemoryMapDestroy,
1672 .fileMemoryMapSetLength = fileMemoryMapSetLength,
1673 .fileMemoryMapRead = fileMemoryMapRead,
1674 .fileMemoryMapWrite = fileMemoryMapWrite,
1675
1645 .processExecutableOpen = processExecutableOpen,1676 .processExecutableOpen = processExecutableOpen,
1646 .processExecutablePath = processExecutablePath,1677 .processExecutablePath = processExecutablePath,
1647 .lockStderr = lockStderr,1678 .lockStderr = lockStderr,
...@@ -1733,15 +1764,24 @@ const have_wait4 = switch (native_os) {...@@ -1733,15 +1764,24 @@ const have_wait4 = switch (native_os) {
1733 else => false,1764 else => false,
1734};1765};
17351766
1767const have_mmap = switch (native_os) {
1768 .wasi, .windows => false,
1769 else => true,
1770};
1771
1736const open_sym = if (posix.lfs64_abi) posix.system.open64 else posix.system.open;1772const 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;1773const 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;1774const 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;1775const 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;1776const 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;1777const preadv_sym = if (posix.lfs64_abi) posix.system.preadv64 else posix.system.preadv;
1778const 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;1779const 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;1780const pwritev_sym = if (posix.lfs64_abi) posix.system.pwritev64 else posix.system.pwritev;
1781const 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;1782const sendfile_sym = if (posix.lfs64_abi) posix.system.sendfile64 else posix.system.sendfile;
1783const mmap_sym = if (posix.lfs64_abi) posix.system.mmap64 else posix.system.mmap;
1784
1745const linux_copy_file_range_use_c = std.c.versionCheck(if (builtin.abi.isAndroid()) .{1785const linux_copy_file_range_use_c = std.c.versionCheck(if (builtin.abi.isAndroid()) .{
1746 .major = 34,1786 .major = 34,
1747 .minor = 0,1787 .minor = 0,
...@@ -2908,7 +2948,11 @@ fn fileStatLinux(userdata: ?*anyopaque, file: File) File.StatError!File.Stat {...@@ -2908,7 +2948,11 @@ fn fileStatLinux(userdata: ?*anyopaque, file: File) File.StatError!File.Stat {
29082948
2909fn fileStatWindows(userdata: ?*anyopaque, file: File) File.StatError!File.Stat {2949fn fileStatWindows(userdata: ?*anyopaque, file: File) File.StatError!File.Stat {
2910 const t: *Threaded = @ptrCast(@alignCast(userdata));2950 const t: *Threaded = @ptrCast(@alignCast(userdata));
2911 _ = t;2951
2952 const block_size: u32 = if (t.systemBasicInformation()) |sbi|
2953 @intCast(@max(sbi.PageSize, sbi.AllocationGranularity))
2954 else
2955 std.heap.page_size_max;
29122956
2913 var io_status_block: windows.IO_STATUS_BLOCK = undefined;2957 var io_status_block: windows.IO_STATUS_BLOCK = undefined;
2914 var info: windows.FILE.ALL_INFORMATION = undefined;2958 var info: windows.FILE.ALL_INFORMATION = undefined;
...@@ -2970,10 +3014,31 @@ fn fileStatWindows(userdata: ?*anyopaque, file: File) File.StatError!File.Stat {...@@ -2970,10 +3014,31 @@ fn fileStatWindows(userdata: ?*anyopaque, file: File) File.StatError!File.Stat {
2970 .atime = windows.fromSysTime(info.BasicInformation.LastAccessTime),3014 .atime = windows.fromSysTime(info.BasicInformation.LastAccessTime),
2971 .mtime = windows.fromSysTime(info.BasicInformation.LastWriteTime),3015 .mtime = windows.fromSysTime(info.BasicInformation.LastWriteTime),
2972 .ctime = windows.fromSysTime(info.BasicInformation.ChangeTime),3016 .ctime = windows.fromSysTime(info.BasicInformation.ChangeTime),
2973 .nlink = 0,3017 .nlink = info.StandardInformation.NumberOfLinks,
3018 .block_size = block_size,
2974 };3019 };
2975}3020}
29763021
3022fn systemBasicInformation(t: *Threaded) ?*const windows.SYSTEM_BASIC_INFORMATION {
3023 if (!t.system_basic_information.initialized.load(.acquire)) {
3024 t.mutex.lock();
3025 defer t.mutex.unlock();
3026
3027 switch (windows.ntdll.NtQuerySystemInformation(
3028 .SystemBasicInformation,
3029 &t.system_basic_information.buffer,
3030 @sizeOf(windows.SYSTEM_BASIC_INFORMATION),
3031 null,
3032 )) {
3033 .SUCCESS => {},
3034 else => return null,
3035 }
3036
3037 t.system_basic_information.initialized.store(true, .release);
3038 }
3039 return &t.system_basic_information.buffer;
3040}
3041
2977fn fileStatWasi(userdata: ?*anyopaque, file: File) File.StatError!File.Stat {3042fn fileStatWasi(userdata: ?*anyopaque, file: File) File.StatError!File.Stat {
2978 if (builtin.link_libc) return fileStatPosix(userdata, file);3043 if (builtin.link_libc) return fileStatPosix(userdata, file);
29793044
...@@ -7889,7 +7954,7 @@ fn fileReadStreamingPosix(userdata: ?*anyopaque, file: File, data: []const []u8)...@@ -7889,7 +7954,7 @@ fn fileReadStreamingPosix(userdata: ?*anyopaque, file: File, data: []const []u8)
7889 syscall.finish();7954 syscall.finish();
7890 return nread;7955 return nread;
7891 },7956 },
7892 .INTR => {7957 .INTR, .TIMEDOUT => {
7893 try syscall.checkCancel();7958 try syscall.checkCancel();
7894 continue;7959 continue;
7895 },7960 },
...@@ -7898,14 +7963,13 @@ fn fileReadStreamingPosix(userdata: ?*anyopaque, file: File, data: []const []u8)...@@ -7898,14 +7963,13 @@ fn fileReadStreamingPosix(userdata: ?*anyopaque, file: File, data: []const []u8)
7898 switch (e) {7963 switch (e) {
7899 .INVAL => |err| return errnoBug(err),7964 .INVAL => |err| return errnoBug(err),
7900 .FAULT => |err| return errnoBug(err),7965 .FAULT => |err| return errnoBug(err),
7901 .BADF => return error.NotOpenForReading, // File operation on directory.7966 .BADF => return error.IsDir, // File operation on directory.
7902 .IO => return error.InputOutput,7967 .IO => return error.InputOutput,
7903 .ISDIR => return error.IsDir,7968 .ISDIR => return error.IsDir,
7904 .NOBUFS => return error.SystemResources,7969 .NOBUFS => return error.SystemResources,
7905 .NOMEM => return error.SystemResources,7970 .NOMEM => return error.SystemResources,
7906 .NOTCONN => return error.SocketUnconnected,7971 .NOTCONN => return error.SocketUnconnected,
7907 .CONNRESET => return error.ConnectionResetByPeer,7972 .CONNRESET => return error.ConnectionResetByPeer,
7908 .TIMEDOUT => return error.Timeout,
7909 .NOTCAPABLE => return error.AccessDenied,7973 .NOTCAPABLE => return error.AccessDenied,
7910 else => |err| return posix.unexpectedErrno(err),7974 else => |err| return posix.unexpectedErrno(err),
7911 }7975 }
...@@ -7922,7 +7986,7 @@ fn fileReadStreamingPosix(userdata: ?*anyopaque, file: File, data: []const []u8)...@@ -7922,7 +7986,7 @@ fn fileReadStreamingPosix(userdata: ?*anyopaque, file: File, data: []const []u8)
7922 syscall.finish();7986 syscall.finish();
7923 return @intCast(rc);7987 return @intCast(rc);
7924 },7988 },
7925 .INTR => {7989 .INTR, .TIMEDOUT => {
7926 try syscall.checkCancel();7990 try syscall.checkCancel();
7927 continue;7991 continue;
7928 },7992 },
...@@ -7932,9 +7996,9 @@ fn fileReadStreamingPosix(userdata: ?*anyopaque, file: File, data: []const []u8)...@@ -7932,9 +7996,9 @@ fn fileReadStreamingPosix(userdata: ?*anyopaque, file: File, data: []const []u8)
7932 .INVAL => |err| return errnoBug(err),7996 .INVAL => |err| return errnoBug(err),
7933 .FAULT => |err| return errnoBug(err),7997 .FAULT => |err| return errnoBug(err),
7934 .AGAIN => return error.WouldBlock,7998 .AGAIN => return error.WouldBlock,
7935 .BADF => |err| {7999 .BADF => {
7936 if (native_os == .wasi) return error.NotOpenForReading; // File operation on directory.8000 if (native_os == .wasi) return error.IsDir; // File operation on directory.
7937 return errnoBug(err); // File descriptor used after closed.8001 return error.NotOpenForReading;
7938 },8002 },
7939 .IO => return error.InputOutput,8003 .IO => return error.InputOutput,
7940 .ISDIR => return error.IsDir,8004 .ISDIR => return error.IsDir,
...@@ -7942,7 +8006,6 @@ fn fileReadStreamingPosix(userdata: ?*anyopaque, file: File, data: []const []u8)...@@ -7942,7 +8006,6 @@ fn fileReadStreamingPosix(userdata: ?*anyopaque, file: File, data: []const []u8)
7942 .NOMEM => return error.SystemResources,8006 .NOMEM => return error.SystemResources,
7943 .NOTCONN => return error.SocketUnconnected,8007 .NOTCONN => return error.SocketUnconnected,
7944 .CONNRESET => return error.ConnectionResetByPeer,8008 .CONNRESET => return error.ConnectionResetByPeer,
7945 .TIMEDOUT => return error.Timeout,
7946 else => |err| return posix.unexpectedErrno(err),8009 else => |err| return posix.unexpectedErrno(err),
7947 }8010 }
7948 },8011 },
...@@ -7981,10 +8044,10 @@ fn fileReadStreamingWindows(userdata: ?*anyopaque, file: File, data: []const []u...@@ -7981,10 +8044,10 @@ fn fileReadStreamingWindows(userdata: ?*anyopaque, file: File, data: []const []u
7981 syscall.finish();8044 syscall.finish();
7982 return 0;8045 return 0;
7983 },8046 },
7984 .NETNAME_DELETED => return syscall.fail(error.ConnectionResetByPeer),8047 .NETNAME_DELETED => if (is_debug) unreachable else return error.Unexpected,
7985 .LOCK_VIOLATION => return syscall.fail(error.LockViolation),8048 .LOCK_VIOLATION => return syscall.fail(error.LockViolation),
7986 .ACCESS_DENIED => return syscall.fail(error.AccessDenied),8049 .ACCESS_DENIED => return syscall.fail(error.AccessDenied),
7987 .INVALID_HANDLE => return syscall.fail(error.NotOpenForReading),8050 .INVALID_HANDLE => if (is_debug) unreachable else return error.Unexpected,
7988 // TODO: Determine if INVALID_FUNCTION is possible in more scenarios than just passing8051 // TODO: Determine if INVALID_FUNCTION is possible in more scenarios than just passing
7989 // a handle to a directory.8052 // a handle to a directory.
7990 .INVALID_FUNCTION => return syscall.fail(error.IsDir),8053 .INVALID_FUNCTION => return syscall.fail(error.IsDir),
...@@ -8024,31 +8087,25 @@ fn fileReadPositionalPosix(userdata: ?*anyopaque, file: File, data: []const []u8...@@ -8024,31 +8087,25 @@ fn fileReadPositionalPosix(userdata: ?*anyopaque, file: File, data: []const []u8
8024 syscall.finish();8087 syscall.finish();
8025 return nread;8088 return nread;
8026 },8089 },
8027 .INTR => {8090 .INTR, .TIMEDOUT => {
8028 try syscall.checkCancel();8091 try syscall.checkCancel();
8029 continue;8092 continue;
8030 },8093 },
8031 else => |e| {8094 .NOTCONN => |err| return syscall.errnoBug(err), // not a socket
8032 syscall.finish();8095 .CONNRESET => |err| return syscall.errnoBug(err), // not a socket
8033 switch (e) {8096 .INVAL => |err| return syscall.errnoBug(err),
8034 .INVAL => |err| return errnoBug(err),8097 .FAULT => |err| return syscall.errnoBug(err), // segmentation fault
8035 .FAULT => |err| return errnoBug(err),8098 .AGAIN => |err| return syscall.errnoBug(err),
8036 .AGAIN => |err| return errnoBug(err),8099 .IO => return syscall.fail(error.InputOutput),
8037 .BADF => return error.NotOpenForReading, // File operation on directory.8100 .ISDIR => return syscall.fail(error.IsDir),
8038 .IO => return error.InputOutput,8101 .BADF => return syscall.fail(error.IsDir),
8039 .ISDIR => return error.IsDir,8102 .NOBUFS => return syscall.fail(error.SystemResources),
8040 .NOBUFS => return error.SystemResources,8103 .NOMEM => return syscall.fail(error.SystemResources),
8041 .NOMEM => return error.SystemResources,8104 .NXIO => return syscall.fail(error.Unseekable),
8042 .NOTCONN => return error.SocketUnconnected,8105 .SPIPE => return syscall.fail(error.Unseekable),
8043 .CONNRESET => return error.ConnectionResetByPeer,8106 .OVERFLOW => return syscall.fail(error.Unseekable),
8044 .TIMEDOUT => return error.Timeout,8107 .NOTCAPABLE => return syscall.fail(error.AccessDenied),
8045 .NXIO => return error.Unseekable,8108 else => |err| return syscall.unexpectedErrno(err),
8046 .SPIPE => return error.Unseekable,
8047 .OVERFLOW => return error.Unseekable,
8048 .NOTCAPABLE => return error.AccessDenied,
8049 else => |err| return posix.unexpectedErrno(err),
8050 }
8051 },
8052 }8109 }
8053 }8110 }
8054 }8111 }
...@@ -8061,33 +8118,28 @@ fn fileReadPositionalPosix(userdata: ?*anyopaque, file: File, data: []const []u8...@@ -8061,33 +8118,28 @@ fn fileReadPositionalPosix(userdata: ?*anyopaque, file: File, data: []const []u8
8061 syscall.finish();8118 syscall.finish();
8062 return @bitCast(rc);8119 return @bitCast(rc);
8063 },8120 },
8064 .INTR => {8121 .INTR, .TIMEDOUT => {
8065 try syscall.checkCancel();8122 try syscall.checkCancel();
8066 continue;8123 continue;
8067 },8124 },
8068 else => |e| {8125 .NXIO => return syscall.fail(error.Unseekable),
8126 .SPIPE => return syscall.fail(error.Unseekable),
8127 .OVERFLOW => return syscall.fail(error.Unseekable),
8128 .NOBUFS => return syscall.fail(error.SystemResources),
8129 .NOMEM => return syscall.fail(error.SystemResources),
8130 .AGAIN => return syscall.fail(error.WouldBlock),
8131 .IO => return syscall.fail(error.InputOutput),
8132 .ISDIR => return syscall.fail(error.IsDir),
8133 .NOTCONN => |err| return syscall.errnoBug(err), // not a socket
8134 .CONNRESET => |err| return syscall.errnoBug(err), // not a socket
8135 .INVAL => |err| return syscall.errnoBug(err),
8136 .FAULT => |err| return syscall.errnoBug(err),
8137 .BADF => {
8069 syscall.finish();8138 syscall.finish();
8070 switch (e) {8139 if (native_os == .wasi) return error.IsDir; // File operation on directory.
8071 .INVAL => |err| return errnoBug(err),8140 return error.NotOpenForReading;
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 },8141 },
8142 else => |err| return syscall.unexpectedErrno(err),
8091 }8143 }
8092 }8144 }
8093}8145}
...@@ -8101,14 +8153,17 @@ fn fileReadPositionalWindows(userdata: ?*anyopaque, file: File, data: []const []...@@ -8101,14 +8153,17 @@ fn fileReadPositionalWindows(userdata: ?*anyopaque, file: File, data: []const []
8101 const t: *Threaded = @ptrCast(@alignCast(userdata));8153 const t: *Threaded = @ptrCast(@alignCast(userdata));
8102 _ = t;8154 _ = t;
81038155
8104 const DWORD = windows.DWORD;
8105
8106 var index: usize = 0;8156 var index: usize = 0;
8107 while (index < data.len and data[index].len == 0) index += 1;8157 while (index < data.len and data[index].len == 0) index += 1;
8108 if (index == data.len) return 0;8158 if (index == data.len) return 0;
8109 const buffer = data[index];8159 const buffer = data[index];
8110 const want_read_count: DWORD = @min(std.math.maxInt(DWORD), buffer.len);
81118160
8161 return readFilePositionalWindows(file, buffer, offset);
8162}
8163
8164fn readFilePositionalWindows(file: File, buffer: []u8, offset: u64) File.ReadPositionalError!usize {
8165 const DWORD = windows.DWORD;
8166 const want_read_count: DWORD = @min(std.math.maxInt(DWORD), buffer.len);
8112 var overlapped: windows.OVERLAPPED = .{8167 var overlapped: windows.OVERLAPPED = .{
8113 .Internal = 0,8168 .Internal = 0,
8114 .InternalHigh = 0,8169 .InternalHigh = 0,
...@@ -8141,10 +8196,10 @@ fn fileReadPositionalWindows(userdata: ?*anyopaque, file: File, data: []const []...@@ -8141,10 +8196,10 @@ fn fileReadPositionalWindows(userdata: ?*anyopaque, file: File, data: []const []
8141 syscall.finish();8196 syscall.finish();
8142 return 0;8197 return 0;
8143 },8198 },
8144 .NETNAME_DELETED => return syscall.fail(error.ConnectionResetByPeer),8199 .NETNAME_DELETED => if (is_debug) unreachable else return error.Unexpected,
8145 .LOCK_VIOLATION => return syscall.fail(error.LockViolation),8200 .LOCK_VIOLATION => return syscall.fail(error.LockViolation),
8146 .ACCESS_DENIED => return syscall.fail(error.AccessDenied),8201 .ACCESS_DENIED => return syscall.fail(error.AccessDenied),
8147 .INVALID_HANDLE => return syscall.fail(error.NotOpenForReading),8202 .INVALID_HANDLE => if (is_debug) unreachable else return error.Unexpected,
8148 // TODO: Determine if INVALID_FUNCTION is possible in more scenarios than just passing8203 // TODO: Determine if INVALID_FUNCTION is possible in more scenarios than just passing
8149 // a handle to a directory.8204 // a handle to a directory.
8150 .INVALID_FUNCTION => return syscall.fail(error.IsDir),8205 .INVALID_FUNCTION => return syscall.fail(error.IsDir),
...@@ -8739,7 +8794,7 @@ fn fileWritePositional(...@@ -8739,7 +8794,7 @@ fn fileWritePositional(
8739 .INVAL => |err| return errnoBug(err),8794 .INVAL => |err| return errnoBug(err),
8740 .FAULT => |err| return errnoBug(err),8795 .FAULT => |err| return errnoBug(err),
8741 .AGAIN => |err| return errnoBug(err),8796 .AGAIN => |err| return errnoBug(err),
8742 .BADF => return error.NotOpenForWriting, // can be a race condition.8797 .BADF => return error.NotOpenForWriting,
8743 .DESTADDRREQ => |err| return errnoBug(err), // `connect` was never called.8798 .DESTADDRREQ => |err| return errnoBug(err), // `connect` was never called.
8744 .DQUOT => return error.DiskQuota,8799 .DQUOT => return error.DiskQuota,
8745 .FBIG => return error.FileTooBig,8800 .FBIG => return error.FileTooBig,
...@@ -8770,29 +8825,24 @@ fn fileWritePositional(...@@ -8770,29 +8825,24 @@ fn fileWritePositional(
8770 try syscall.checkCancel();8825 try syscall.checkCancel();
8771 continue;8826 continue;
8772 },8827 },
8773 else => |e| {8828 .INVAL => |err| return syscall.errnoBug(err),
8774 syscall.finish();8829 .FAULT => |err| return syscall.errnoBug(err),
8775 switch (e) {8830 .DESTADDRREQ => |err| return syscall.errnoBug(err), // `connect` was never called.
8776 .INVAL => |err| return errnoBug(err),8831 .CONNRESET => |err| return syscall.errnoBug(err), // Not a socket handle.
8777 .FAULT => |err| return errnoBug(err),8832 .BADF => return syscall.fail(error.NotOpenForWriting),
8778 .AGAIN => return error.WouldBlock,8833 .AGAIN => return syscall.fail(error.WouldBlock),
8779 .BADF => return error.NotOpenForWriting, // Usually a race condition.8834 .DQUOT => return syscall.fail(error.DiskQuota),
8780 .DESTADDRREQ => |err| return errnoBug(err), // `connect` was never called.8835 .FBIG => return syscall.fail(error.FileTooBig),
8781 .DQUOT => return error.DiskQuota,8836 .IO => return syscall.fail(error.InputOutput),
8782 .FBIG => return error.FileTooBig,8837 .NOSPC => return syscall.fail(error.NoSpaceLeft),
8783 .IO => return error.InputOutput,8838 .PERM => return syscall.fail(error.PermissionDenied),
8784 .NOSPC => return error.NoSpaceLeft,8839 .PIPE => return syscall.fail(error.BrokenPipe),
8785 .PERM => return error.PermissionDenied,8840 .BUSY => return syscall.fail(error.DeviceBusy),
8786 .PIPE => return error.BrokenPipe,8841 .TXTBSY => return syscall.fail(error.FileBusy),
8787 .CONNRESET => |err| return errnoBug(err), // Not a socket handle.8842 .NXIO => return syscall.fail(error.Unseekable),
8788 .BUSY => return error.DeviceBusy,8843 .SPIPE => return syscall.fail(error.Unseekable),
8789 .TXTBSY => return error.FileBusy,8844 .OVERFLOW => return syscall.fail(error.Unseekable),
8790 .NXIO => return error.Unseekable,8845 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 }8846 }
8797 }8847 }
8798}8848}
...@@ -8830,7 +8880,7 @@ fn writeFilePositionalWindows(...@@ -8830,7 +8880,7 @@ fn writeFilePositionalWindows(
8830 .NOT_ENOUGH_MEMORY => return syscall.fail(error.SystemResources),8880 .NOT_ENOUGH_MEMORY => return syscall.fail(error.SystemResources),
8831 .NOT_ENOUGH_QUOTA => return syscall.fail(error.SystemResources),8881 .NOT_ENOUGH_QUOTA => return syscall.fail(error.SystemResources),
8832 .NO_DATA => return syscall.fail(error.BrokenPipe),8882 .NO_DATA => return syscall.fail(error.BrokenPipe),
8833 .INVALID_HANDLE => return syscall.fail(error.NotOpenForWriting),8883 .INVALID_HANDLE => if (is_debug) unreachable else return error.Unexpected, // use after free
8834 .LOCK_VIOLATION => return syscall.fail(error.LockViolation),8884 .LOCK_VIOLATION => return syscall.fail(error.LockViolation),
8835 .ACCESS_DENIED => return syscall.fail(error.AccessDenied),8885 .ACCESS_DENIED => return syscall.fail(error.AccessDenied),
8836 .WORKING_SET_QUOTA => return syscall.fail(error.SystemResources),8886 .WORKING_SET_QUOTA => return syscall.fail(error.SystemResources),
...@@ -12458,6 +12508,7 @@ const linux_statx_request: std.os.linux.STATX = .{...@@ -12458,6 +12508,7 @@ const linux_statx_request: std.os.linux.STATX = .{
12458 .INO = true,12508 .INO = true,
12459 .SIZE = true,12509 .SIZE = true,
12460 .NLINK = true,12510 .NLINK = true,
12511 .BLOCKS = true,
12461};12512};
1246212513
12463const linux_statx_check: std.os.linux.STATX = .{12514const linux_statx_check: std.os.linux.STATX = .{
...@@ -12469,6 +12520,7 @@ const linux_statx_check: std.os.linux.STATX = .{...@@ -12469,6 +12520,7 @@ const linux_statx_check: std.os.linux.STATX = .{
12469 .INO = true,12520 .INO = true,
12470 .SIZE = true,12521 .SIZE = true,
12471 .NLINK = true,12522 .NLINK = true,
12523 .BLOCKS = false,
12472};12524};
1247312525
12474fn statFromLinux(stx: *const std.os.linux.Statx) Io.UnexpectedError!File.Stat {12526fn statFromLinux(stx: *const std.os.linux.Statx) Io.UnexpectedError!File.Stat {
...@@ -12487,6 +12539,7 @@ fn statFromLinux(stx: *const std.os.linux.Statx) Io.UnexpectedError!File.Stat {...@@ -12487,6 +12539,7 @@ fn statFromLinux(stx: *const std.os.linux.Statx) Io.UnexpectedError!File.Stat {
12487 },12539 },
12488 .mtime = .{ .nanoseconds = @intCast(@as(i128, stx.mtime.sec) * std.time.ns_per_s + stx.mtime.nsec) },12540 .mtime = .{ .nanoseconds = @intCast(@as(i128, stx.mtime.sec) * std.time.ns_per_s + stx.mtime.nsec) },
12489 .ctime = .{ .nanoseconds = @intCast(@as(i128, stx.ctime.sec) * std.time.ns_per_s + stx.ctime.nsec) },12541 .ctime = .{ .nanoseconds = @intCast(@as(i128, stx.ctime.sec) * std.time.ns_per_s + stx.ctime.nsec) },
12542 .block_size = if (stx.mask.BLOCKS) stx.blksize else 1,
12490 };12543 };
12491}12544}
1249212545
...@@ -12535,6 +12588,7 @@ fn statFromPosix(st: *const posix.Stat) File.Stat {...@@ -12535,6 +12588,7 @@ fn statFromPosix(st: *const posix.Stat) File.Stat {
12535 .atime = timestampFromPosix(&atime),12588 .atime = timestampFromPosix(&atime),
12536 .mtime = timestampFromPosix(&mtime),12589 .mtime = timestampFromPosix(&mtime),
12537 .ctime = timestampFromPosix(&ctime),12590 .ctime = timestampFromPosix(&ctime),
12591 .block_size = @intCast(st.blksize),
12538 };12592 };
12539}12593}
1254012594
...@@ -12556,6 +12610,7 @@ fn statFromWasi(st: *const std.os.wasi.filestat_t) File.Stat {...@@ -12556,6 +12610,7 @@ fn statFromWasi(st: *const std.os.wasi.filestat_t) File.Stat {
12556 .atime = .fromNanoseconds(st.atim),12610 .atime = .fromNanoseconds(st.atim),
12557 .mtime = .fromNanoseconds(st.mtim),12611 .mtime = .fromNanoseconds(st.mtim),
12558 .ctime = .fromNanoseconds(st.ctim),12612 .ctime = .fromNanoseconds(st.ctim),
12613 .block_size = 1,
12559 };12614 };
12560}12615}
1256112616
...@@ -16107,3 +16162,529 @@ pub fn chdir(dir_path: []const u8) ChdirError!void {...@@ -16107,3 +16162,529 @@ pub fn chdir(dir_path: []const u8) ChdirError!void {
16107 else => |err| return syscall.unexpectedErrno(err),16162 else => |err| return syscall.unexpectedErrno(err),
16108 };16163 };
16109}16164}
16165
16166fn fileMemoryMapCreate(
16167 userdata: ?*anyopaque,
16168 file: File,
16169 options: File.MemoryMap.CreateOptions,
16170) File.MemoryMap.CreateError!File.MemoryMap {
16171 const t: *Threaded = @ptrCast(@alignCast(userdata));
16172 const offset = options.offset;
16173 const len = options.len;
16174
16175 if (!t.disable_memory_mapping) {
16176 if (createFileMap(file, options.protection, offset, options.populate, len)) |result| {
16177 return result;
16178 } else |err| switch (err) {
16179 error.Unseekable, error.Canceled, error.AccessDenied => |e| return e,
16180 error.OperationUnsupported => {},
16181 else => {
16182 if (builtin.mode == .Debug)
16183 std.log.warn("memory mapping failed with {t}, falling back to file operations", .{err});
16184 },
16185 }
16186 }
16187
16188 const gpa = t.allocator;
16189 const page_size = std.heap.pageSize();
16190 const alignment: Alignment = .fromByteUnits(page_size);
16191 const memory = m: {
16192 const ptr = gpa.rawAlloc(len, alignment, @returnAddress()) orelse return error.OutOfMemory;
16193 break :m ptr[0..len];
16194 };
16195 errdefer gpa.rawFree(memory, alignment, @returnAddress());
16196
16197 if (!options.undefined_contents) try mmSyncRead(file, memory, offset);
16198
16199 return .{
16200 .file = file,
16201 .offset = offset,
16202 .memory = @alignCast(memory),
16203 .section = null,
16204 };
16205}
16206
16207const CreateFileMapError = error{
16208 /// MaximumSize is greater than the system-defined maximum for sections, or
16209 /// greater than the specified file and the section is not writable.
16210 SectionOversize,
16211 /// A file descriptor refers to a non-regular file. Or a file mapping was requested,
16212 /// but the file descriptor is not open for reading. Or `MAP.SHARED` was requested
16213 /// and `PROT_WRITE` is set, but the file descriptor is not open in `RDWR` mode.
16214 /// Or `PROT_WRITE` is set, but the file is append-only.
16215 AccessDenied,
16216 /// The `prot` argument asks for `PROT_EXEC` but the mapped area belongs to a file on
16217 /// a filesystem that was mounted no-exec.
16218 PermissionDenied,
16219 FileBusy,
16220 LockedMemoryLimitExceeded,
16221 OperationUnsupported,
16222 ProcessFdQuotaExceeded,
16223 SystemFdQuotaExceeded,
16224 OutOfMemory,
16225 MappingAlreadyExists,
16226 Unseekable,
16227 FileLockConflict,
16228} || Io.Cancelable || Io.UnexpectedError;
16229
16230fn createFileMap(
16231 file: File,
16232 protection: std.process.MemoryProtection,
16233 offset: u64,
16234 populate: bool,
16235 len: usize,
16236) CreateFileMapError!File.MemoryMap {
16237 if (is_windows) {
16238 try Thread.checkCancel();
16239
16240 var section = windows.INVALID_HANDLE_VALUE;
16241 const section_size: windows.LARGE_INTEGER = @intCast(len);
16242 const page = windows.PAGE.fromProtection(protection) orelse return error.AccessDenied;
16243 switch (windows.ntdll.NtCreateSection(
16244 &section,
16245 .{
16246 .SPECIFIC = .{ .SECTION = .{
16247 .QUERY = true,
16248 .MAP_WRITE = protection.write,
16249 .MAP_READ = protection.read,
16250 .MAP_EXECUTE = protection.execute,
16251 .EXTEND_SIZE = true,
16252 } },
16253 .STANDARD = .{ .RIGHTS = .REQUIRED },
16254 },
16255 null,
16256 &section_size,
16257 page,
16258 .{ .COMMIT = populate },
16259 file.handle,
16260 )) {
16261 .SUCCESS => {},
16262 .FILE_LOCK_CONFLICT => return error.FileLockConflict,
16263 .INVALID_FILE_FOR_SECTION => return error.OperationUnsupported,
16264 .ACCESS_DENIED => return error.AccessDenied,
16265 .SECTION_TOO_BIG => return error.SectionOversize,
16266 else => |status| return windows.unexpectedStatus(status),
16267 }
16268 var contents_ptr: ?[*]align(std.heap.page_size_min) u8 = null;
16269 var contents_len = len;
16270 switch (windows.ntdll.NtMapViewOfSection(
16271 section,
16272 windows.current_process,
16273 @ptrCast(&contents_ptr),
16274 null,
16275 0,
16276 null,
16277 &contents_len,
16278 .Unmap,
16279 .{},
16280 page,
16281 )) {
16282 .SUCCESS => {},
16283 .CONFLICTING_ADDRESSES => return error.MappingAlreadyExists,
16284 .SECTION_PROTECTION => return error.PermissionDenied,
16285 .ACCESS_DENIED => return error.AccessDenied,
16286 .INVALID_VIEW_SIZE => |status| return windows.statusBug(status),
16287 else => |status| return windows.unexpectedStatus(status),
16288 }
16289 if (builtin.mode == .Debug) {
16290 const page_size = std.heap.pageSize();
16291 const alignment: Alignment = .fromByteUnits(page_size);
16292 assert(contents_len == alignment.forward(len));
16293 }
16294 return .{
16295 .file = file,
16296 .offset = offset,
16297 .memory = contents_ptr.?[0..len],
16298 .section = section,
16299 };
16300 } else if (have_mmap) {
16301 const prot: posix.PROT = .{
16302 .READ = protection.read,
16303 .WRITE = protection.write,
16304 .EXEC = protection.execute,
16305 };
16306 const flags: posix.MAP = switch (native_os) {
16307 .linux => .{
16308 .TYPE = .SHARED_VALIDATE,
16309 .POPULATE = populate,
16310 },
16311 else => .{
16312 .TYPE = .SHARED,
16313 },
16314 };
16315
16316 const page_align = std.heap.page_size_min;
16317
16318 const contents = while (true) {
16319 const syscall: Syscall = try .start();
16320 const casted_offset = std.math.cast(i64, offset) orelse return error.Unseekable;
16321 const rc = mmap_sym(null, len, prot, flags, file.handle, casted_offset);
16322 syscall.finish();
16323 const err: posix.E = if (builtin.link_libc) e: {
16324 if (rc != std.c.MAP_FAILED) {
16325 break @as([*]align(page_align) u8, @ptrCast(@alignCast(rc)))[0..len];
16326 }
16327 break :e @enumFromInt(posix.system._errno().*);
16328 } else e: {
16329 const err = posix.errno(rc);
16330 if (err == .SUCCESS) {
16331 break @as([*]align(page_align) u8, @ptrFromInt(rc))[0..len];
16332 }
16333 break :e err;
16334 };
16335 switch (err) {
16336 .SUCCESS => unreachable,
16337 .INTR => continue,
16338 .ACCES => return error.AccessDenied,
16339 .AGAIN => return error.LockedMemoryLimitExceeded,
16340 .EXIST => return error.MappingAlreadyExists,
16341 .MFILE => return error.ProcessFdQuotaExceeded,
16342 .NFILE => return error.SystemFdQuotaExceeded,
16343 .NODEV => return error.OperationUnsupported,
16344 .NOMEM => return error.OutOfMemory,
16345 .PERM => return error.PermissionDenied,
16346 .TXTBSY => return error.FileBusy,
16347 .OVERFLOW => return error.Unseekable,
16348 .BADF => return errnoBug(err), // Always a race condition.
16349 .INVAL => return errnoBug(err), // Invalid parameters to mmap()
16350 else => return posix.unexpectedErrno(err),
16351 }
16352 };
16353 return .{
16354 .file = file,
16355 .offset = offset,
16356 .memory = contents,
16357 .section = {},
16358 };
16359 }
16360
16361 return error.OperationUnsupported;
16362}
16363
16364fn fileMemoryMapDestroy(userdata: ?*anyopaque, mm: *File.MemoryMap) void {
16365 const t: *Threaded = @ptrCast(@alignCast(userdata));
16366 const memory = mm.memory;
16367 if (mm.section) |section| switch (native_os) {
16368 .windows => {
16369 if (section == windows.INVALID_HANDLE_VALUE) return;
16370 _ = windows.ntdll.NtUnmapViewOfSection(windows.current_process, memory.ptr);
16371 windows.CloseHandle(section);
16372 },
16373 .wasi => unreachable,
16374 else => {
16375 if (memory.len == 0) return;
16376 switch (posix.errno(posix.system.munmap(memory.ptr, memory.len))) {
16377 .SUCCESS => {},
16378 else => |e| {
16379 if (builtin.mode == .Debug)
16380 std.log.err("failed to unmap {d} bytes at {*}: {t}", .{ memory.len, memory.ptr, e });
16381 },
16382 }
16383 },
16384 } else {
16385 const gpa = t.allocator;
16386 gpa.rawFree(memory, .fromByteUnits(std.heap.pageSize()), @returnAddress());
16387 }
16388 mm.* = undefined;
16389}
16390
16391fn fileMemoryMapSetLength(
16392 userdata: ?*anyopaque,
16393 mm: *File.MemoryMap,
16394 options: File.MemoryMap.CreateOptions,
16395) File.MemoryMap.SetLengthError!void {
16396 const t: *Threaded = @ptrCast(@alignCast(userdata));
16397 const page_size = std.heap.pageSize();
16398 const alignment: Alignment = .fromByteUnits(page_size);
16399 const page_align = std.heap.page_size_min;
16400 const old_memory = mm.memory;
16401 const new_len = options.len;
16402
16403 if (mm.section) |section| {
16404 if (alignment.forward(new_len) == alignment.forward(old_memory.len)) {
16405 mm.memory.len = new_len;
16406 return;
16407 }
16408 switch (native_os) {
16409 .windows => {
16410 _ = windows.ntdll.NtUnmapViewOfSection(windows.current_process, old_memory.ptr);
16411 windows.CloseHandle(section);
16412 mm.section = windows.INVALID_HANDLE_VALUE;
16413 mm.memory = &.{};
16414 },
16415 .wasi => unreachable,
16416 .linux => {
16417 const flags: posix.MREMAP = .{ .MAYMOVE = true };
16418 const addr_hint: ?[*]const u8 = null;
16419 const new_memory = while (true) {
16420 const syscall: Syscall = try .start();
16421 const rc = posix.system.mremap(old_memory.ptr, old_memory.len, new_len, flags, addr_hint);
16422 syscall.finish();
16423 const err: posix.E = if (builtin.link_libc) e: {
16424 if (rc != std.c.MAP_FAILED) break @as([*]align(page_align) u8, @ptrCast(@alignCast(rc)))[0..new_len];
16425 break :e @enumFromInt(posix.system._errno().*);
16426 } else e: {
16427 const err = posix.errno(rc);
16428 if (err == .SUCCESS) break @as([*]align(page_align) u8, @ptrFromInt(rc))[0..new_len];
16429 break :e err;
16430 };
16431 switch (err) {
16432 .SUCCESS => unreachable,
16433 .INTR => continue,
16434 .AGAIN => return error.LockedMemoryLimitExceeded,
16435 .NOMEM => return error.OutOfMemory,
16436 .INVAL => return errnoBug(err),
16437 .FAULT => return errnoBug(err),
16438 else => return posix.unexpectedErrno(err),
16439 }
16440 };
16441 mm.memory = new_memory;
16442 return;
16443 },
16444 else => {
16445 switch (posix.errno(posix.system.munmap(old_memory.ptr, old_memory.len))) {
16446 .SUCCESS => {},
16447 else => |e| {
16448 if (builtin.mode == .Debug) std.log.err("failed to unmap {d} bytes at {*}: {t}", .{
16449 old_memory.len, old_memory.ptr, e,
16450 });
16451 // munmap must be infallible, or we cannot design reliable software.
16452 return error.Unexpected;
16453 },
16454 }
16455 mm.memory = &.{};
16456 },
16457 }
16458 if (createFileMap(mm.file, options.protection, mm.offset, options.populate, new_len)) |result| {
16459 mm.* = result;
16460 return;
16461 } else |err| switch (err) {
16462 error.OperationUnsupported,
16463 error.Unseekable,
16464 error.SectionOversize,
16465 error.MappingAlreadyExists,
16466 error.FileLockConflict,
16467 => return error.Unexpected, // It worked before on the same open file.
16468 else => |e| return e,
16469 }
16470 } else {
16471 const gpa = t.allocator;
16472 if (gpa.rawRemap(old_memory, alignment, new_len, @returnAddress())) |new_ptr| {
16473 mm.memory = @alignCast(new_ptr[0..new_len]);
16474 } else {
16475 const new_ptr: [*]align(page_align) u8 = @alignCast(
16476 gpa.rawAlloc(new_len, alignment, @returnAddress()) orelse return error.OutOfMemory,
16477 );
16478 const copy_len = @min(new_len, old_memory.len);
16479 @memcpy(new_ptr[0..copy_len], old_memory[0..copy_len]);
16480 mm.memory = new_ptr[0..new_len];
16481 gpa.rawFree(old_memory, alignment, @returnAddress());
16482 }
16483 }
16484}
16485
16486fn fileMemoryMapRead(userdata: ?*anyopaque, mm: *File.MemoryMap) File.ReadPositionalError!void {
16487 const t: *Threaded = @ptrCast(@alignCast(userdata));
16488 _ = t;
16489 const section = mm.section orelse return mmSyncRead(mm.file, mm.memory, mm.offset);
16490 _ = section;
16491}
16492
16493fn fileMemoryMapWrite(userdata: ?*anyopaque, mm: *File.MemoryMap) File.WritePositionalError!void {
16494 const t: *Threaded = @ptrCast(@alignCast(userdata));
16495 _ = t;
16496 const section = mm.section orelse return mmSyncWrite(mm.file, mm.memory, mm.offset);
16497 _ = section;
16498}
16499
16500fn mmSyncRead(file: File, memory: []u8, offset: u64) File.ReadPositionalError!void {
16501 if (is_windows) {
16502 var i: usize = 0;
16503 while (true) {
16504 const buf = memory[i..];
16505 if (buf.len == 0) break;
16506 const n = try readFilePositionalWindows(file, buf, offset + i);
16507 if (n == 0) {
16508 @memset(memory[i..], 0);
16509 break;
16510 }
16511 i += n;
16512 }
16513 } else if (native_os == .wasi and !builtin.link_libc) {
16514 var i: usize = 0;
16515 const syscall: Syscall = try .start();
16516 while (true) {
16517 const buf = memory[i..];
16518 if (buf.len == 0) {
16519 syscall.finish();
16520 break;
16521 }
16522 var n: usize = undefined;
16523 const vec: std.os.wasi.iovec_t = .{ .base = buf.ptr, .len = buf.len };
16524 switch (std.os.wasi.fd_pread(file.handle, (&vec)[0..1], 1, offset + i, &n)) {
16525 .SUCCESS => {
16526 if (n == 0) {
16527 syscall.finish();
16528 @memset(memory[i..], 0);
16529 break;
16530 }
16531 i += n;
16532 try syscall.checkCancel();
16533 continue;
16534 },
16535 .INTR, .TIMEDOUT => {
16536 try syscall.checkCancel();
16537 continue;
16538 },
16539 .NOTCONN => |err| return syscall.errnoBug(err), // not a socket
16540 .CONNRESET => |err| return syscall.errnoBug(err), // not a socket
16541 .BADF => |err| return syscall.errnoBug(err), // use after free
16542 .INVAL => |err| return syscall.errnoBug(err),
16543 .FAULT => |err| return syscall.errnoBug(err), // segmentation fault
16544 .AGAIN => |err| return syscall.errnoBug(err),
16545 .IO => return syscall.fail(error.InputOutput),
16546 .ISDIR => return syscall.fail(error.IsDir),
16547 .NOBUFS => return syscall.fail(error.SystemResources),
16548 .NOMEM => return syscall.fail(error.SystemResources),
16549 .NXIO => return syscall.fail(error.Unseekable),
16550 .SPIPE => return syscall.fail(error.Unseekable),
16551 .OVERFLOW => return syscall.fail(error.Unseekable),
16552 .NOTCAPABLE => return syscall.fail(error.AccessDenied),
16553 else => |err| return syscall.unexpectedErrno(err),
16554 }
16555 }
16556 } else {
16557 var i: usize = 0;
16558 const syscall: Syscall = try .start();
16559 while (true) {
16560 const buf = memory[i..];
16561 if (buf.len == 0) {
16562 syscall.finish();
16563 break;
16564 }
16565 const rc = pread_sym(file.handle, buf.ptr, buf.len, @intCast(offset + i));
16566 switch (posix.errno(rc)) {
16567 .SUCCESS => {
16568 const n: usize = @intCast(rc);
16569 if (n == 0) {
16570 syscall.finish();
16571 @memset(memory[i..], 0);
16572 break;
16573 }
16574 i += n;
16575 try syscall.checkCancel();
16576 continue;
16577 },
16578 .INTR, .TIMEDOUT => {
16579 try syscall.checkCancel();
16580 continue;
16581 },
16582 .NXIO => return syscall.fail(error.Unseekable),
16583 .SPIPE => return syscall.fail(error.Unseekable),
16584 .OVERFLOW => return syscall.fail(error.Unseekable),
16585 .NOBUFS => return syscall.fail(error.SystemResources),
16586 .NOMEM => return syscall.fail(error.SystemResources),
16587 .AGAIN => return syscall.fail(error.WouldBlock),
16588 .IO => return syscall.fail(error.InputOutput),
16589 .ISDIR => return syscall.fail(error.IsDir),
16590 .NOTCONN => |err| return syscall.errnoBug(err), // not a socket
16591 .CONNRESET => |err| return syscall.errnoBug(err), // not a socket
16592 .INVAL => |err| return syscall.errnoBug(err),
16593 .FAULT => |err| return syscall.errnoBug(err),
16594 .BADF => |err| return syscall.errnoBug(err), // use after free
16595 else => |err| return syscall.unexpectedErrno(err),
16596 }
16597 }
16598 }
16599}
16600
16601fn mmSyncWrite(file: File, memory: []u8, offset: u64) File.WritePositionalError!void {
16602 if (is_windows) {
16603 var i: usize = 0;
16604 while (true) {
16605 const buf = memory[i..];
16606 if (buf.len == 0) break;
16607 i += try writeFilePositionalWindows(file.handle, memory[i..], offset + i);
16608 }
16609 } else if (native_os == .wasi and !builtin.link_libc) {
16610 var i: usize = 0;
16611 var n: usize = undefined;
16612 const syscall: Syscall = try .start();
16613 while (true) {
16614 const buf = memory[i..];
16615 if (buf.len == 0) {
16616 syscall.finish();
16617 break;
16618 }
16619 const iovec: std.os.wasi.ciovec_t = .{ .base = buf.ptr, .len = buf.len };
16620 switch (std.os.wasi.fd_pwrite(file.handle, (&iovec)[0..1], 1, offset + i, &n)) {
16621 .SUCCESS => {
16622 i += n;
16623 try syscall.checkCancel();
16624 continue;
16625 },
16626 .INTR => {
16627 try syscall.checkCancel();
16628 continue;
16629 },
16630 .DQUOT => return syscall.fail(error.DiskQuota),
16631 .FBIG => return syscall.fail(error.FileTooBig),
16632 .IO => return syscall.fail(error.InputOutput),
16633 .NOSPC => return syscall.fail(error.NoSpaceLeft),
16634 .PERM => return syscall.fail(error.PermissionDenied),
16635 .PIPE => return syscall.fail(error.BrokenPipe),
16636 .NOTCAPABLE => return syscall.fail(error.AccessDenied),
16637 .NXIO => return syscall.fail(error.Unseekable),
16638 .SPIPE => return syscall.fail(error.Unseekable),
16639 .OVERFLOW => return syscall.fail(error.Unseekable),
16640 .INVAL => |err| return syscall.errnoBug(err),
16641 .FAULT => |err| return syscall.errnoBug(err),
16642 .AGAIN => |err| return syscall.errnoBug(err),
16643 .BADF => |err| return syscall.errnoBug(err), // use after free
16644 .DESTADDRREQ => |err| return syscall.errnoBug(err), // not a socket
16645 else => |err| return syscall.unexpectedErrno(err),
16646 }
16647 }
16648 } else {
16649 var i: usize = 0;
16650 const syscall: Syscall = try .start();
16651 while (true) {
16652 const buf = memory[i..];
16653 if (buf.len == 0) {
16654 syscall.finish();
16655 break;
16656 }
16657 const rc = pwrite_sym(file.handle, buf.ptr, buf.len, @intCast(offset + i));
16658 switch (posix.errno(rc)) {
16659 .SUCCESS => {
16660 const n: usize = @bitCast(rc);
16661 i += n;
16662 try syscall.checkCancel();
16663 continue;
16664 },
16665 .INTR => {
16666 try syscall.checkCancel();
16667 continue;
16668 },
16669 .INVAL => |err| return syscall.errnoBug(err),
16670 .FAULT => |err| return syscall.errnoBug(err),
16671 .DESTADDRREQ => |err| return syscall.errnoBug(err), // not a socket
16672 .CONNRESET => |err| return syscall.errnoBug(err), // not a socket
16673 .BADF => return syscall.fail(error.NotOpenForWriting),
16674 .AGAIN => return syscall.fail(error.WouldBlock),
16675 .DQUOT => return syscall.fail(error.DiskQuota),
16676 .FBIG => return syscall.fail(error.FileTooBig),
16677 .IO => return syscall.fail(error.InputOutput),
16678 .NOSPC => return syscall.fail(error.NoSpaceLeft),
16679 .PERM => return syscall.fail(error.PermissionDenied),
16680 .PIPE => return syscall.fail(error.BrokenPipe),
16681 .BUSY => return syscall.fail(error.DeviceBusy),
16682 .TXTBSY => return syscall.fail(error.FileBusy),
16683 .NXIO => return syscall.fail(error.Unseekable),
16684 .SPIPE => return syscall.fail(error.Unseekable),
16685 .OVERFLOW => return syscall.fail(error.Unseekable),
16686 else => |err| return syscall.unexpectedErrno(err),
16687 }
16688 }
16689 }
16690}
lib/std/Io/Threaded/test.zig+62
...@@ -204,3 +204,65 @@ test "cancel blocked read from pipe" {...@@ -204,3 +204,65 @@ test "cancel blocked read from pipe" {
204 try io.sleep(.fromMilliseconds(10), .awake);204 try io.sleep(.fromMilliseconds(10), .awake);
205 try future.cancel(io);205 try future.cancel(io);
206}206}
207
208test "memory mapping fallback" {
209 if (builtin.os.tag == .wasi and builtin.link_libc) {
210 // https://github.com/ziglang/zig/issues/20747 (open fd does not have write permission)
211 return error.SkipZigTest;
212 }
213
214 var threaded: std.Io.Threaded = .init(std.testing.allocator, .{
215 .argv0 = .empty,
216 .environ = .empty,
217 .disable_memory_mapping = true,
218 });
219 defer threaded.deinit();
220 const io = threaded.io();
221
222 var tmp = testing.tmpDir(.{});
223 defer tmp.cleanup();
224
225 try tmp.dir.writeFile(io, .{
226 .sub_path = "blah.txt",
227 .data = "this is my data123",
228 });
229
230 {
231 var file = try tmp.dir.openFile(io, "blah.txt", .{ .mode = .read_write });
232 defer file.close(io);
233
234 // The `Io.File.MemoryMap` API does not specify what happens if we supply a
235 // length greater than file size, but this is testing specifically std.Io.Threaded
236 // with disable_memory_mapping = true.
237 var mm = try file.createMemoryMap(io, .{ .len = "this is my data123".len + 3 });
238 defer mm.destroy(io);
239
240 try testing.expectEqualStrings("this is my data123\x00\x00\x00", mm.memory);
241 mm.memory[4] = '9';
242 mm.memory[7] = '9';
243
244 try mm.write(io);
245 }
246
247 var buffer: [100]u8 = undefined;
248 const updated_contents = try tmp.dir.readFile(io, "blah.txt", &buffer);
249 try testing.expectEqualStrings("this9is9my data123\x00\x00\x00", updated_contents);
250
251 {
252 var file = try tmp.dir.openFile(io, "blah.txt", .{ .mode = .read_only });
253 defer file.close(io);
254
255 var mm = try file.createMemoryMap(io, .{
256 .len = "this9is9my".len,
257 .protection = .{ .read = true },
258 });
259 defer mm.destroy(io);
260
261 try testing.expectEqualStrings("this9is9my", mm.memory);
262
263 try mm.setLength(io, .{ .len = "this9is9my data123".len });
264 try mm.read(io);
265
266 try testing.expectEqualStrings("this9is9my data123", mm.memory);
267 }
268}
lib/std/Io/test.zig+56
...@@ -592,3 +592,59 @@ test "randomSecure" {...@@ -592,3 +592,59 @@ test "randomSecure" {
592 // that two sets of 50 bytes were equal.592 // that two sets of 50 bytes were equal.
593 try expect(!mem.eql(u8, &buf_a, &buf_b));593 try expect(!mem.eql(u8, &buf_a, &buf_b));
594}594}
595
596test "memory mapping" {
597 if (builtin.cpu.arch == .hexagon) return error.SkipZigTest; // mmap returned EINVAL
598 if (builtin.os.tag == .wasi and builtin.link_libc) {
599 // https://github.com/ziglang/zig/issues/20747 (open fd does not have write permission)
600 return error.SkipZigTest;
601 }
602
603 const io = testing.io;
604
605 var tmp = tmpDir(.{});
606 defer tmp.cleanup();
607
608 try tmp.dir.writeFile(io, .{
609 .sub_path = "blah.txt",
610 .data = "this is my data123",
611 });
612
613 {
614 var file = try tmp.dir.openFile(io, "blah.txt", .{ .mode = .read_write });
615 defer file.close(io);
616
617 var mm = try file.createMemoryMap(io, .{ .len = "this is my data123".len });
618 defer mm.destroy(io);
619
620 try expectEqualStrings("this is my data123", mm.memory);
621 mm.memory[4] = '9';
622 mm.memory[7] = '9';
623
624 try mm.write(io);
625 }
626
627 var buffer: [100]u8 = undefined;
628 const updated_contents = try tmp.dir.readFile(io, "blah.txt", &buffer);
629 try expectEqualStrings("this9is9my data123", updated_contents);
630
631 {
632 var file = try tmp.dir.openFile(io, "blah.txt", .{ .mode = .read_write });
633 defer file.close(io);
634
635 var mm = try file.createMemoryMap(io, .{
636 .len = "this9is9my".len,
637 });
638 defer mm.destroy(io);
639
640 try expectEqualStrings("this9is9my", mm.memory);
641
642 // Cross a page boundary to require an actual remap.
643 try mm.setLength(io, .{
644 .len = std.heap.pageSize() * 2,
645 });
646 try mm.read(io);
647
648 try expectEqualStrings("this9is9my data123\x00\x00", mm.memory[0.."this9is9my data123\x00\x00".len]);
649 }
650}
lib/std/c.zig+2-2
...@@ -212,7 +212,7 @@ pub const nlink_t = switch (native_os) {...@@ -212,7 +212,7 @@ pub const nlink_t = switch (native_os) {
212 .wasi => c_ulonglong,212 .wasi => c_ulonglong,
213 // https://github.com/SerenityOS/serenity/blob/b98f537f117b341788023ab82e0c11ca9ae29a57/Kernel/API/POSIX/sys/types.h#L45213 // https://github.com/SerenityOS/serenity/blob/b98f537f117b341788023ab82e0c11ca9ae29a57/Kernel/API/POSIX/sys/types.h#L45
214 .freebsd, .serenity => u64,214 .freebsd, .serenity => u64,
215 .openbsd, .netbsd, .dragonfly, .illumos => u32,215 .openbsd, .netbsd, .dragonfly, .illumos, .windows => u32,
216 .haiku => i32,216 .haiku => i32,
217 .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos => u16,217 .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos => u16,
218 else => u0,218 else => u0,
...@@ -10334,7 +10334,7 @@ pub extern "c" fn getgrgid(gid: gid_t) ?*group;...@@ -10334,7 +10334,7 @@ pub extern "c" fn getgrgid(gid: gid_t) ?*group;
10334pub extern "c" fn getgrgid_r(gid: gid_t, grp: *group, buf: [*]u8, buflen: usize, result: *?*group) c_int;10334pub extern "c" fn getgrgid_r(gid: gid_t, grp: *group, buf: [*]u8, buflen: usize, result: *?*group) c_int;
10335pub extern "c" fn getrlimit64(resource: rlimit_resource, rlim: *rlimit) c_int;10335pub extern "c" fn getrlimit64(resource: rlimit_resource, rlim: *rlimit) c_int;
10336pub extern "c" fn lseek64(fd: fd_t, offset: i64, whence: c_int) i64;10336pub extern "c" fn lseek64(fd: fd_t, offset: i64, whence: c_int) i64;
10337pub extern "c" fn mmap64(addr: ?*align(page_size) anyopaque, len: usize, prot: PROT, flags: c_uint, fd: fd_t, offset: i64) *anyopaque;10337pub extern "c" fn mmap64(addr: ?*align(page_size) anyopaque, len: usize, prot: PROT, flags: MAP, fd: fd_t, offset: i64) *anyopaque;
10338pub extern "c" fn open64(path: [*:0]const u8, oflag: O, ...) c_int;10338pub extern "c" fn open64(path: [*:0]const u8, oflag: O, ...) c_int;
10339pub extern "c" fn openat64(fd: c_int, path: [*:0]const u8, oflag: O, ...) c_int;10339pub extern "c" fn openat64(fd: c_int, path: [*:0]const u8, oflag: O, ...) c_int;
10340pub extern "c" fn pread64(fd: fd_t, buf: [*]u8, nbyte: usize, offset: i64) isize;10340pub extern "c" fn pread64(fd: fd_t, buf: [*]u8, nbyte: usize, offset: i64) isize;
lib/std/fs/test.zig+2-11
...@@ -827,11 +827,6 @@ test "file operations on directories" {...@@ -827,11 +827,6 @@ test "file operations on directories" {
827 const buf = try ctx.dir.readFileAlloc(io, test_dir_name, testing.allocator, .unlimited);827 const buf = try ctx.dir.readFileAlloc(io, test_dir_name, testing.allocator, .unlimited);
828 testing.allocator.free(buf);828 testing.allocator.free(buf);
829 },829 },
830 .wasi => {
831 // WASI return EBADF, which gets mapped to NotOpenForReading.
832 // See https://github.com/bytecodealliance/wasmtime/issues/1935
833 try expectError(error.NotOpenForReading, ctx.dir.readFileAlloc(io, test_dir_name, testing.allocator, .unlimited));
834 },
835 else => {830 else => {
836 try expectError(error.IsDir, ctx.dir.readFileAlloc(io, test_dir_name, testing.allocator, .unlimited));831 try expectError(error.IsDir, ctx.dir.readFileAlloc(io, test_dir_name, testing.allocator, .unlimited));
837 },832 },
...@@ -851,13 +846,9 @@ test "file operations on directories" {...@@ -851,13 +846,9 @@ test "file operations on directories" {
851 defer handle.close(io);846 defer handle.close(io);
852847
853 // Reading from the handle should fail848 // Reading from the handle should fail
854 const expected_err = switch (native_os) {
855 .wasi => error.NotOpenForReading,
856 else => error.IsDir,
857 };
858 var buf: [1]u8 = undefined;849 var buf: [1]u8 = undefined;
859 try expectError(expected_err, handle.readStreaming(io, &.{&buf}));850 try expectError(error.IsDir, handle.readStreaming(io, &.{&buf}));
860 try expectError(expected_err, handle.readPositional(io, &.{&buf}, 0));851 try expectError(error.IsDir, handle.readPositional(io, &.{&buf}, 0));
861 }852 }
862 try expectError(error.IsDir, ctx.dir.openFile(io, test_dir_name, .{ .allow_directory = false, .mode = .read_only }));853 try expectError(error.IsDir, ctx.dir.openFile(io, test_dir_name, .{ .allow_directory = false, .mode = .read_only }));
863854
lib/std/heap.zig+3-2
...@@ -53,8 +53,9 @@ pub var next_mmap_addr_hint: ?[*]align(page_size_min) u8 = null;...@@ -53,8 +53,9 @@ pub var next_mmap_addr_hint: ?[*]align(page_size_min) u8 = null;
53///53///
54/// On many systems, the actual page size can only be determined at runtime54/// On many systems, the actual page size can only be determined at runtime
55/// with `pageSize`.55/// with `pageSize`.
56pub const page_size_min: usize = std.options.page_size_min orelse (page_size_min_default orelse56pub const page_size_min: usize = std.options.page_size_min orelse (page_size_min_default orelse 1);
57 @compileError(@tagName(builtin.cpu.arch) ++ "-" ++ @tagName(builtin.os.tag) ++ " has unknown page_size_min; populate std.options.page_size_min"));57//`orelse 1` is a workaround for https://codeberg.org/ziglang/zig/issues/30842
58//@compileError(@tagName(builtin.cpu.arch) ++ "-" ++ @tagName(builtin.os.tag) ++ " has unknown page_size_min; populate std.options.page_size_min"));
5859
59/// comptime-known maximum page size of the target.60/// comptime-known maximum page size of the target.
60///61///
lib/std/os/windows.zig+16
...@@ -28,6 +28,8 @@ pub const ws2_32 = @import("windows/ws2_32.zig");...@@ -28,6 +28,8 @@ pub const ws2_32 = @import("windows/ws2_32.zig");
28pub const crypt32 = @import("windows/crypt32.zig");28pub const crypt32 = @import("windows/crypt32.zig");
29pub const nls = @import("windows/nls.zig");29pub const nls = @import("windows/nls.zig");
3030
31pub const current_process: HANDLE = @ptrFromInt(@as(usize, @bitCast(@as(isize, -1))));
32
31pub const FILE = struct {33pub const FILE = struct {
32 // ref: km/ntddk.h34 // ref: km/ntddk.h
3335
...@@ -2124,6 +2126,20 @@ pub const PAGE = packed struct(ULONG) {...@@ -2124,6 +2126,20 @@ pub const PAGE = packed struct(ULONG) {
2124 Reserved19: u12 = 0,2126 Reserved19: u12 = 0,
21252127
2126 REVERT_TO_FILE_MAP: bool = false,2128 REVERT_TO_FILE_MAP: bool = false,
2129
2130 pub fn fromProtection(protection: std.process.MemoryProtection) ?PAGE {
2131 // TODO https://github.com/ziglang/zig/issues/22214
2132 return switch (@as(u3, @bitCast(protection))) {
2133 0b000 => .{ .NOACCESS = true },
2134 0b001 => .{ .READONLY = true },
2135 0b010 => null,
2136 0b011 => .{ .READWRITE = true },
2137 0b100 => .{ .EXECUTE = true },
2138 0b101 => .{ .EXECUTE_READ = true },
2139 0b110 => null,
2140 0b111 => .{ .EXECUTE_READWRITE = true },
2141 };
2142 }
2127};2143};
21282144
2129pub const MEM = struct {2145pub const MEM = struct {
lib/std/os/windows/ntdll.zig+5
...@@ -253,6 +253,11 @@ pub extern "ntdll" fn NtCreateSection(...@@ -253,6 +253,11 @@ pub extern "ntdll" fn NtCreateSection(
253 FileHandle: ?HANDLE,253 FileHandle: ?HANDLE,
254) callconv(.winapi) NTSTATUS;254) callconv(.winapi) NTSTATUS;
255255
256pub extern "ntdll" fn NtExtendSection(
257 SectionHandle: HANDLE,
258 NewSectionSize: *LARGE_INTEGER,
259) callconv(.winapi) NTSTATUS;
260
256pub extern "ntdll" fn NtAllocateVirtualMemory(261pub extern "ntdll" fn NtAllocateVirtualMemory(
257 ProcessHandle: HANDLE,262 ProcessHandle: HANDLE,
258 BaseAddress: *PVOID,263 BaseAddress: *PVOID,
lib/std/posix.zig+3-2
...@@ -55,6 +55,7 @@ else switch (native_os) {...@@ -55,6 +55,7 @@ else switch (native_os) {
55 pub const gid_t = void;55 pub const gid_t = void;
56 pub const mode_t = u0;56 pub const mode_t = u0;
57 pub const nlink_t = u0;57 pub const nlink_t = u0;
58 pub const blksize_t = void;
58 pub const ino_t = void;59 pub const ino_t = void;
59 pub const IFNAMESIZE = {};60 pub const IFNAMESIZE = {};
60 pub const SIG = void;61 pub const SIG = void;
...@@ -433,14 +434,14 @@ pub fn read(fd: fd_t, buf: []u8) ReadError!usize {...@@ -433,14 +434,14 @@ pub fn read(fd: fd_t, buf: []u8) ReadError!usize {
433 .FAULT => unreachable,434 .FAULT => unreachable,
434 .AGAIN => return error.WouldBlock,435 .AGAIN => return error.WouldBlock,
435 .CANCELED => return error.Canceled,436 .CANCELED => return error.Canceled,
436 .BADF => return error.NotOpenForReading, // Can be a race condition.437 .BADF => return error.Unexpected, // use after free
437 .IO => return error.InputOutput,438 .IO => return error.InputOutput,
438 .ISDIR => return error.IsDir,439 .ISDIR => return error.IsDir,
439 .NOBUFS => return error.SystemResources,440 .NOBUFS => return error.SystemResources,
440 .NOMEM => return error.SystemResources,441 .NOMEM => return error.SystemResources,
441 .NOTCONN => return error.SocketUnconnected,442 .NOTCONN => return error.SocketUnconnected,
442 .CONNRESET => return error.ConnectionResetByPeer,443 .CONNRESET => return error.ConnectionResetByPeer,
443 .TIMEDOUT => return error.Timeout,444 .TIMEDOUT => return error.Unexpected,
444 else => |err| return unexpectedErrno(err),445 else => |err| return unexpectedErrno(err),
445 }446 }
446 }447 }
lib/std/process.zig+6-18
...@@ -1018,31 +1018,19 @@ pub const ProtectMemoryError = error{...@@ -1018,31 +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.fromProtection(protection) orelse return error.AccessDenied;
1037 0b000 => .{ .NOACCESS = true },
1038 0b001 => .{ .READONLY = true },
1039 0b010 => return error.AccessDenied, // +w -r not allowed
1040 0b011 => .{ .READWRITE = true },
1041 0b100 => .{ .EXECUTE = true },
1042 0b101 => .{ .EXECUTE_READ = true },
1043 0b110 => return error.AccessDenied, // +w -r not allowed
1044 0b111 => .{ .EXECUTE_READWRITE = true },
1045 };
1046 switch (windows.ntdll.NtProtectVirtualMemory(current_process, @ptrCast(&addr), &size, new, &old)) {1034 switch (windows.ntdll.NtProtectVirtualMemory(current_process, @ptrCast(&addr), &size, new, &old)) {
1047 .SUCCESS => return,1035 .SUCCESS => return,
1048 .INVALID_ADDRESS => return error.AccessDenied,1036 .INVALID_ADDRESS => return error.AccessDenied,
...@@ -1050,9 +1038,9 @@ pub fn protectMemory(...@@ -1050,9 +1038,9 @@ pub fn protectMemory(
1050 }1038 }
1051 } else if (posix.PROT != void) {1039 } else if (posix.PROT != void) {
1052 const flags: posix.PROT = .{1040 const flags: posix.PROT = .{
1053 .READ = options.read,1041 .READ = protection.read,
1054 .WRITE = options.write,1042 .WRITE = protection.write,
1055 .EXEC = options.execute,1043 .EXEC = protection.execute,
1056 };1044 };
1057 switch (posix.errno(posix.system.mprotect(memory.ptr, memory.len, flags))) {1045 switch (posix.errno(posix.system.mprotect(memory.ptr, memory.len, flags))) {
1058 .SUCCESS => return,1046 .SUCCESS => return,
lib/std/zig/system.zig-1
...@@ -420,7 +420,6 @@ pub fn resolveTargetQuery(io: Io, query: Target.Query) DetectError!Target {...@@ -420,7 +420,6 @@ pub fn resolveTargetQuery(io: Io, query: Target.Query) DetectError!Target {
420 error.WouldBlock => return error.Unexpected,420 error.WouldBlock => return error.Unexpected,
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,
424 error.NotOpenForReading => return error.Unexpected,423 error.NotOpenForReading => return error.Unexpected,
425 error.SocketUnconnected => return error.Unexpected,424 error.SocketUnconnected => return error.Unexpected,
426425
src/link/Dwarf.zig+4
...@@ -49,6 +49,10 @@ pub const UpdateError = error{...@@ -49,6 +49,10 @@ pub const UpdateError = error{
49 Underflow,49 Underflow,
50 UnexpectedEndOfFile,50 UnexpectedEndOfFile,
51 NonResizable,51 NonResizable,
52 /// TODO why is this in the error set?
53 ConnectionResetByPeer,
54 /// TODO why is this in the error set?
55 SocketUnconnected,
52} ||56} ||
53 codegen.GenerateSymbolError ||57 codegen.GenerateSymbolError ||
54 Io.File.OpenError ||58 Io.File.OpenError ||
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");