authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-01-14 18:23:40-08:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-01-15 14:18:20-08:00
logbed7bc37c43afce335610867aedbcd506ade65f4
treee424a2954fb6f8de3bbe07ada4504cc03053a1b1
parenta70e0061579a4365da0092554470ffeadb4594a4

std.File.MemoryMap updates

- change offset to u64 - make len non-optional - make write take a file_size parameter - std.Io.Threaded: introduce disable_memory_mapping flag to force it to take the fallback path. Additionally: - introduce BlockSize to File.Stat. On Windows, based on cached call to NtQuerySystemInformation. On unsupported OS's, set to 1. - support File.NLink on Windows. this was available the whole time, we just didn't see the field at first. - remove EBADF / INVALID_HANDLE from reading/writing file error sets

7 files changed, 131 insertions(+), 67 deletions(-)

lib/std/Io.zig+1-1
...@@ -658,7 +658,7 @@ pub const VTable = struct {...@@ -658,7 +658,7 @@ pub const VTable = struct {
658 fileMemoryMapDestroy: *const fn (?*anyopaque, *File.MemoryMap) void,658 fileMemoryMapDestroy: *const fn (?*anyopaque, *File.MemoryMap) void,
659 fileMemoryMapSetLength: *const fn (?*anyopaque, *File.MemoryMap, n: usize) File.MemoryMap.SetLengthError!void,659 fileMemoryMapSetLength: *const fn (?*anyopaque, *File.MemoryMap, n: usize) File.MemoryMap.SetLengthError!void,
660 fileMemoryMapRead: *const fn (?*anyopaque, *File.MemoryMap) File.ReadPositionalError!void,660 fileMemoryMapRead: *const fn (?*anyopaque, *File.MemoryMap) File.ReadPositionalError!void,
661 fileMemoryMapWrite: *const fn (?*anyopaque, *File.MemoryMap) File.WritePositionalError!void,661 fileMemoryMapWrite: *const fn (?*anyopaque, *File.MemoryMap, file_size: u64) File.WritePositionalError!void,
662662
663 processExecutableOpen: *const fn (?*anyopaque, File.OpenFlags) std.process.OpenExecutableError!File,663 processExecutableOpen: *const fn (?*anyopaque, File.OpenFlags) std.process.OpenExecutableError!File,
664 processExecutablePath: *const fn (?*anyopaque, buffer: []u8) std.process.ExecutablePathError!usize,664 processExecutablePath: *const fn (?*anyopaque, buffer: []u8) std.process.ExecutablePathError!usize,
lib/std/Io/File.zig+9
...@@ -22,6 +22,7 @@ pub const INode = std.posix.ino_t;...@@ -22,6 +22,7 @@ pub const INode = std.posix.ino_t;
22pub const NLink = std.posix.nlink_t;22pub const NLink = std.posix.nlink_t;
23pub const Uid = std.posix.uid_t;23pub const Uid = std.posix.uid_t;
24pub const Gid = std.posix.gid_t;24pub const Gid = std.posix.gid_t;
25pub const BlockSize = u32;
2526
26pub const Kind = enum {27pub const Kind = enum {
27 block_device,28 block_device,
...@@ -65,6 +66,14 @@ pub const Stat = struct {...@@ -65,6 +66,14 @@ pub const Stat = struct {
65 mtime: Io.Timestamp,66 mtime: Io.Timestamp,
66 /// 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.
67 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. When creating a
72 /// `MemoryMap`, the mapping length must be a multiple of this value.
73 ///
74 /// On Windows, this is whichever is larger: PageSize or
75 /// AllocationGranularity.
76 block_size: BlockSize,
68};77};
6978
70pub fn stdout() File {79pub fn stdout() File {
lib/std/Io/File/MemoryMap.zig+31-16
...@@ -10,10 +10,11 @@ const File = Io.File;...@@ -10,10 +10,11 @@ const File = Io.File;
10const Allocator = std.mem.Allocator;10const Allocator = std.mem.Allocator;
1111
12file: File,12file: File,
13/// Byte index inside `file` where `memory` starts.13/// Byte index inside `file` where `memory` starts. Page-aligned.
14offset: usize,14offset: u64,
15/// Memory that may or may not remain consistent with file contents. Use `read`15/// Memory that may or may not remain consistent with file contents. Use `read`
16/// and `write` to ensure synchronization points.16/// and `write` to ensure synchronization points. No minimum alignment on the
17/// pointer is guaranteed, but the length is page-aligned.
17memory: []u8,18memory: []u8,
18/// Tells whether it is memory-mapped or file operations. On Windows this also19/// Tells whether it is memory-mapped or file operations. On Windows this also
19/// has a section handle.20/// has a section handle.
...@@ -22,10 +23,10 @@ section: ?Section,...@@ -22,10 +23,10 @@ section: ?Section,
22pub const Section = if (is_windows) std.os.windows.HANDLE else void;23pub const Section = if (is_windows) std.os.windows.HANDLE else void;
2324
24pub const CreateError = error{25pub const CreateError = error{
25 /// A file descriptor refers to a non-regular file. Or a file mapping was requested,26 /// One of the following:
26 /// but the file descriptor is not open for reading. Or `MAP.SHARED` was requested27 /// * The `File.Kind` is not `file`.
27 /// and `PROT_WRITE` is set, but the file descriptor is not open in `RDWR` mode.28 /// * The file is not open for reading and read access protections enabled.
28 /// Or `PROT_WRITE` is set, but the file is append-only.29 /// * The file is not open for writing and write access protections enabled.
29 AccessDenied,30 AccessDenied,
30 /// The `prot` argument asks for `PROT_EXEC` but the mapped area belongs to a file on31 /// 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 /// a filesystem that was mounted no-exec.
...@@ -36,6 +37,12 @@ pub const CreateError = error{...@@ -36,6 +37,12 @@ pub const CreateError = error{
36} || Allocator.Error || File.ReadPositionalError;37} || Allocator.Error || File.ReadPositionalError;
3738
38pub const CreateOptions = struct {39pub const CreateOptions = struct {
40 /// Size of the mapping, in bytes. If this is longer than the file size, it
41 /// will be filled with zeroes.
42 ///
43 /// Asserted to be a multiple of page size which can be obtained via
44 /// `std.heap.pageSize`.
45 len: usize,
39 /// When this has read set to false, bytes that are not modified before a46 /// When this has read set to false, bytes that are not modified before a
40 /// sync may have the original file contents, or may be set to zero.47 /// sync may have the original file contents, or may be set to zero.
41 protection: std.process.MemoryProtection = .{ .read = true, .write = true },48 protection: std.process.MemoryProtection = .{ .read = true, .write = true },
...@@ -45,12 +52,9 @@ pub const CreateOptions = struct {...@@ -45,12 +52,9 @@ pub const CreateOptions = struct {
45 undefined_contents: bool = false,52 undefined_contents: bool = false,
46 /// Prefault the pages.53 /// Prefault the pages.
47 populate: bool = true,54 populate: bool = true,
48 /// Byte index of file to start from.55 /// Asserted to be a multiple of page size which can be obtained via
56 /// `std.heap.pageSize`.
49 offset: u64 = 0,57 offset: u64 = 0,
50 /// `null` indicates to map the entire file. If mapping the entire file is
51 /// desired and the file size is known, it is more efficient to populate
52 /// the value here.
53 len: ?usize = null,
54};58};
5559
56/// To release the resources associated with the returned `MemoryMap`, call60/// To release the resources associated with the returned `MemoryMap`, call
...@@ -73,8 +77,15 @@ pub const SetLengthError = error{...@@ -73,8 +77,15 @@ pub const SetLengthError = error{
73/// of the file after calling this is unspecified until `write` is called.77/// of the file after calling this is unspecified until `write` is called.
74///78///
75/// May change the pointer address of `memory`.79/// May change the pointer address of `memory`.
76pub fn setLength(mm: *MemoryMap, io: Io, n: usize) File.SetLengthError!void {80pub fn setLength(
77 return io.vtable.fileMemoryMapSetLength(io.userdata, mm, n);81 mm: *MemoryMap,
82 io: Io,
83 /// New size of the mapping, in bytes. If this is longer than the file
84 /// size, it will be filled with zeroes. Asserted to be a multiple of page
85 /// size which can be obtained with `std.heap.pageSize`.
86 new_length: usize,
87) File.SetLengthError!void {
88 return io.vtable.fileMemoryMapSetLength(io.userdata, mm, new_length);
78}89}
7990
80/// Synchronizes the contents of `memory` from `file`.91/// Synchronizes the contents of `memory` from `file`.
...@@ -83,6 +94,10 @@ pub fn read(mm: *MemoryMap, io: Io) File.ReadPositionalError!void {...@@ -83,6 +94,10 @@ pub fn read(mm: *MemoryMap, io: Io) File.ReadPositionalError!void {
83}94}
8495
85/// Synchronizes the contents of `memory` to `file`.96/// Synchronizes the contents of `memory` to `file`.
86pub fn write(mm: *MemoryMap, io: Io) File.WritePositionalError!void {97///
87 return io.vtable.fileMemoryMapWrite(io.userdata, mm);98/// Size of the mapping may be longer than the file size, so the `file_size`
99/// argument is used to avoid writing too many bytes. If `file_size` is not
100/// handy, use `File.length` to get it.
101pub fn write(mm: *MemoryMap, io: Io, file_size: u64) File.WritePositionalError!void {
102 return io.vtable.fileMemoryMapWrite(io.userdata, mm, file_size);
88}103}
lib/std/Io/Threaded.zig+79-32
...@@ -57,6 +57,7 @@ use_sendfile: UseSendfile = .default,...@@ -57,6 +57,7 @@ use_sendfile: UseSendfile = .default,
57use_copy_file_range: UseCopyFileRange = .default,57use_copy_file_range: UseCopyFileRange = .default,
58use_fcopyfile: UseFcopyfile = .default,58use_fcopyfile: UseFcopyfile = .default,
59use_fchmodat2: UseFchmodat2 = .default,59use_fchmodat2: UseFchmodat2 = .default,
60disable_memory_mapping: bool,
6061
61stderr_writer: File.Writer = .{62stderr_writer: File.Writer = .{
62 .io = undefined,63 .io = undefined,
...@@ -75,6 +76,13 @@ random_file: RandomFile = .{},...@@ -75,6 +76,13 @@ random_file: RandomFile = .{},
7576
76csprng: Csprng = .{},77csprng: Csprng = .{},
7778
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
78pub const Csprng = struct {86pub const Csprng = struct {
79 rng: std.Random.DefaultCsprng = .{87 rng: std.Random.DefaultCsprng = .{
80 .state = undefined,88 .state = undefined,
...@@ -1220,6 +1228,8 @@ pub const InitOptions = struct {...@@ -1220,6 +1228,8 @@ pub const InitOptions = struct {
1220 /// * `processExecutablePath` on OpenBSD and Haiku (observes "PATH").1228 /// * `processExecutablePath` on OpenBSD and Haiku (observes "PATH").
1221 /// * `processSpawn`, `processSpawnPath`, `processReplace`, `processReplacePath`1229 /// * `processSpawn`, `processSpawnPath`, `processReplace`, `processReplacePath`
1222 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,
1223};1233};
12241234
1225/// Related:1235/// Related:
...@@ -1247,6 +1257,7 @@ pub fn init(...@@ -1247,6 +1257,7 @@ pub fn init(
1247 .argv0 = options.argv0,1257 .argv0 = options.argv0,
1248 .environ = .{ .process_environ = options.environ },1258 .environ = .{ .process_environ = options.environ },
1249 .worker_threads = init_single_threaded.worker_threads,1259 .worker_threads = init_single_threaded.worker_threads,
1260 .disable_memory_mapping = options.disable_memory_mapping,
1250 };1261 };
12511262
1252 const cpu_count = std.Thread.getCpuCount();1263 const cpu_count = std.Thread.getCpuCount();
...@@ -1263,6 +1274,7 @@ pub fn init(...@@ -1263,6 +1274,7 @@ pub fn init(
1263 .argv0 = options.argv0,1274 .argv0 = options.argv0,
1264 .environ = .{ .process_environ = options.environ },1275 .environ = .{ .process_environ = options.environ },
1265 .worker_threads = .init(null),1276 .worker_threads = .init(null),
1277 .disable_memory_mapping = options.disable_memory_mapping,
1266 };1278 };
12671279
1268 if (posix.Sigaction != void) {1280 if (posix.Sigaction != void) {
...@@ -1299,6 +1311,7 @@ pub const init_single_threaded: Threaded = .{...@@ -1299,6 +1311,7 @@ pub const init_single_threaded: Threaded = .{
1299 .argv0 = .empty,1311 .argv0 = .empty,
1300 .environ = .{},1312 .environ = .{},
1301 .worker_threads = .init(null),1313 .worker_threads = .init(null),
1314 .disable_memory_mapping = false,
1302};1315};
13031316
1304var global_single_threaded_instance: Threaded = .init_single_threaded;1317var global_single_threaded_instance: Threaded = .init_single_threaded;
...@@ -2935,7 +2948,11 @@ fn fileStatLinux(userdata: ?*anyopaque, file: File) File.StatError!File.Stat {...@@ -2935,7 +2948,11 @@ fn fileStatLinux(userdata: ?*anyopaque, file: File) File.StatError!File.Stat {
29352948
2936fn fileStatWindows(userdata: ?*anyopaque, file: File) File.StatError!File.Stat {2949fn fileStatWindows(userdata: ?*anyopaque, file: File) File.StatError!File.Stat {
2937 const t: *Threaded = @ptrCast(@alignCast(userdata));2950 const t: *Threaded = @ptrCast(@alignCast(userdata));
2938 _ = 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;
29392956
2940 var io_status_block: windows.IO_STATUS_BLOCK = undefined;2957 var io_status_block: windows.IO_STATUS_BLOCK = undefined;
2941 var info: windows.FILE.ALL_INFORMATION = undefined;2958 var info: windows.FILE.ALL_INFORMATION = undefined;
...@@ -2997,10 +3014,31 @@ fn fileStatWindows(userdata: ?*anyopaque, file: File) File.StatError!File.Stat {...@@ -2997,10 +3014,31 @@ fn fileStatWindows(userdata: ?*anyopaque, file: File) File.StatError!File.Stat {
2997 .atime = windows.fromSysTime(info.BasicInformation.LastAccessTime),3014 .atime = windows.fromSysTime(info.BasicInformation.LastAccessTime),
2998 .mtime = windows.fromSysTime(info.BasicInformation.LastWriteTime),3015 .mtime = windows.fromSysTime(info.BasicInformation.LastWriteTime),
2999 .ctime = windows.fromSysTime(info.BasicInformation.ChangeTime),3016 .ctime = windows.fromSysTime(info.BasicInformation.ChangeTime),
3000 .nlink = 0,3017 .nlink = info.StandardInformation.NumberOfLinks,
3018 .block_size = block_size,
3001 };3019 };
3002}3020}
30033021
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
3004fn fileStatWasi(userdata: ?*anyopaque, file: File) File.StatError!File.Stat {3042fn fileStatWasi(userdata: ?*anyopaque, file: File) File.StatError!File.Stat {
3005 if (builtin.link_libc) return fileStatPosix(userdata, file);3043 if (builtin.link_libc) return fileStatPosix(userdata, file);
30063044
...@@ -8008,10 +8046,10 @@ fn fileReadStreamingWindows(userdata: ?*anyopaque, file: File, data: []const []u...@@ -8008,10 +8046,10 @@ fn fileReadStreamingWindows(userdata: ?*anyopaque, file: File, data: []const []u
8008 syscall.finish();8046 syscall.finish();
8009 return 0;8047 return 0;
8010 },8048 },
8011 .NETNAME_DELETED => return syscall.fail(error.ConnectionResetByPeer),8049 .NETNAME_DELETED => if (is_debug) unreachable else return error.Unexpected,
8012 .LOCK_VIOLATION => return syscall.fail(error.LockViolation),8050 .LOCK_VIOLATION => return syscall.fail(error.LockViolation),
8013 .ACCESS_DENIED => return syscall.fail(error.AccessDenied),8051 .ACCESS_DENIED => return syscall.fail(error.AccessDenied),
8014 .INVALID_HANDLE => return syscall.fail(error.NotOpenForReading),8052 .INVALID_HANDLE => if (is_debug) unreachable else return error.Unexpected,
8015 // TODO: Determine if INVALID_FUNCTION is possible in more scenarios than just passing8053 // TODO: Determine if INVALID_FUNCTION is possible in more scenarios than just passing
8016 // a handle to a directory.8054 // a handle to a directory.
8017 .INVALID_FUNCTION => return syscall.fail(error.IsDir),8055 .INVALID_FUNCTION => return syscall.fail(error.IsDir),
...@@ -8158,10 +8196,10 @@ fn fileReadPositionalWindows(userdata: ?*anyopaque, file: File, data: []const []...@@ -8158,10 +8196,10 @@ fn fileReadPositionalWindows(userdata: ?*anyopaque, file: File, data: []const []
8158 syscall.finish();8196 syscall.finish();
8159 return 0;8197 return 0;
8160 },8198 },
8161 .NETNAME_DELETED => return syscall.fail(error.ConnectionResetByPeer),8199 .NETNAME_DELETED => if (is_debug) unreachable else return error.Unexpected,
8162 .LOCK_VIOLATION => return syscall.fail(error.LockViolation),8200 .LOCK_VIOLATION => return syscall.fail(error.LockViolation),
8163 .ACCESS_DENIED => return syscall.fail(error.AccessDenied),8201 .ACCESS_DENIED => return syscall.fail(error.AccessDenied),
8164 .INVALID_HANDLE => return syscall.fail(error.NotOpenForReading),8202 .INVALID_HANDLE => if (is_debug) unreachable else return error.Unexpected,
8165 // 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
8166 // a handle to a directory.8204 // a handle to a directory.
8167 .INVALID_FUNCTION => return syscall.fail(error.IsDir),8205 .INVALID_FUNCTION => return syscall.fail(error.IsDir),
...@@ -8842,7 +8880,7 @@ fn writeFilePositionalWindows(...@@ -8842,7 +8880,7 @@ fn writeFilePositionalWindows(
8842 .NOT_ENOUGH_MEMORY => return syscall.fail(error.SystemResources),8880 .NOT_ENOUGH_MEMORY => return syscall.fail(error.SystemResources),
8843 .NOT_ENOUGH_QUOTA => return syscall.fail(error.SystemResources),8881 .NOT_ENOUGH_QUOTA => return syscall.fail(error.SystemResources),
8844 .NO_DATA => return syscall.fail(error.BrokenPipe),8882 .NO_DATA => return syscall.fail(error.BrokenPipe),
8845 .INVALID_HANDLE => return syscall.fail(error.NotOpenForWriting),8883 .INVALID_HANDLE => if (is_debug) unreachable else return error.Unexpected, // use after free
8846 .LOCK_VIOLATION => return syscall.fail(error.LockViolation),8884 .LOCK_VIOLATION => return syscall.fail(error.LockViolation),
8847 .ACCESS_DENIED => return syscall.fail(error.AccessDenied),8885 .ACCESS_DENIED => return syscall.fail(error.AccessDenied),
8848 .WORKING_SET_QUOTA => return syscall.fail(error.SystemResources),8886 .WORKING_SET_QUOTA => return syscall.fail(error.SystemResources),
...@@ -12470,6 +12508,7 @@ const linux_statx_request: std.os.linux.STATX = .{...@@ -12470,6 +12508,7 @@ const linux_statx_request: std.os.linux.STATX = .{
12470 .INO = true,12508 .INO = true,
12471 .SIZE = true,12509 .SIZE = true,
12472 .NLINK = true,12510 .NLINK = true,
12511 .BLOCKS = true,
12473};12512};
1247412513
12475const linux_statx_check: std.os.linux.STATX = .{12514const linux_statx_check: std.os.linux.STATX = .{
...@@ -12481,6 +12520,7 @@ const linux_statx_check: std.os.linux.STATX = .{...@@ -12481,6 +12520,7 @@ const linux_statx_check: std.os.linux.STATX = .{
12481 .INO = true,12520 .INO = true,
12482 .SIZE = true,12521 .SIZE = true,
12483 .NLINK = true,12522 .NLINK = true,
12523 .BLOCKS = false,
12484};12524};
1248512525
12486fn statFromLinux(stx: *const std.os.linux.Statx) Io.UnexpectedError!File.Stat {12526fn statFromLinux(stx: *const std.os.linux.Statx) Io.UnexpectedError!File.Stat {
...@@ -12499,6 +12539,7 @@ fn statFromLinux(stx: *const std.os.linux.Statx) Io.UnexpectedError!File.Stat {...@@ -12499,6 +12539,7 @@ fn statFromLinux(stx: *const std.os.linux.Statx) Io.UnexpectedError!File.Stat {
12499 },12539 },
12500 .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) },
12501 .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,
12502 };12543 };
12503}12544}
1250412545
...@@ -12547,6 +12588,7 @@ fn statFromPosix(st: *const posix.Stat) File.Stat {...@@ -12547,6 +12588,7 @@ fn statFromPosix(st: *const posix.Stat) File.Stat {
12547 .atime = timestampFromPosix(&atime),12588 .atime = timestampFromPosix(&atime),
12548 .mtime = timestampFromPosix(&mtime),12589 .mtime = timestampFromPosix(&mtime),
12549 .ctime = timestampFromPosix(&ctime),12590 .ctime = timestampFromPosix(&ctime),
12591 .block_size = st.blksize,
12550 };12592 };
12551}12593}
1255212594
...@@ -12568,6 +12610,7 @@ fn statFromWasi(st: *const std.os.wasi.filestat_t) File.Stat {...@@ -12568,6 +12610,7 @@ fn statFromWasi(st: *const std.os.wasi.filestat_t) File.Stat {
12568 .atime = .fromNanoseconds(st.atim),12610 .atime = .fromNanoseconds(st.atim),
12569 .mtime = .fromNanoseconds(st.mtim),12611 .mtime = .fromNanoseconds(st.mtim),
12570 .ctime = .fromNanoseconds(st.ctim),12612 .ctime = .fromNanoseconds(st.ctim),
12613 .block_size = 1,
12571 };12614 };
12572}12615}
1257312616
...@@ -16127,28 +16170,29 @@ fn fileMemoryMapCreate(...@@ -16127,28 +16170,29 @@ fn fileMemoryMapCreate(
16127) File.MemoryMap.CreateError!File.MemoryMap {16170) File.MemoryMap.CreateError!File.MemoryMap {
16128 const t: *Threaded = @ptrCast(@alignCast(userdata));16171 const t: *Threaded = @ptrCast(@alignCast(userdata));
16129 const offset = options.offset;16172 const offset = options.offset;
16173 const len = options.len;
1613016174
16131 const page_size = std.heap.pageSize();16175 assert(std.mem.isAligned(len, std.heap.page_size_min));
16132 const aligned_len: usize = options.len.?; // TODO query if necessary
1613316176
16134 if (createFileMap(file, options.protection, offset, options.populate, aligned_len)) |result| {16177 if (!t.disable_memory_mapping) {
16135 return result;16178 if (createFileMap(file, options.protection, offset, options.populate, len)) |result| {
16136 } else |err| switch (err) {16179 return result;
16137 error.Unseekable, error.Canceled => |e| return e,16180 } else |err| switch (err) {
16138 else => {16181 error.Unseekable, error.Canceled, error.AccessDenied => |e| return e,
16139 if (builtin.mode == .Debug)16182 else => {
16140 std.log.warn("memory mapping failed with {t}, falling back to file operations", .{err});16183 if (builtin.mode == .Debug)
16141 },16184 std.log.warn("memory mapping failed with {t}, falling back to file operations", .{err});
16185 },
16186 }
16142 }16187 }
1614316188
16144 const gpa = t.allocator;16189 const gpa = t.allocator;
16145 const alignment: Alignment = .fromByteUnits(page_size);
16146 const memory = m: {16190 const memory = m: {
16147 const ptr = gpa.rawAlloc(aligned_len, alignment, @returnAddress()) orelse16191 const ptr = gpa.rawAlloc(len, .@"1", @returnAddress()) orelse
16148 return error.OutOfMemory;16192 return error.OutOfMemory;
16149 break :m ptr[0..aligned_len];16193 break :m ptr[0..len];
16150 };16194 };
16151 errdefer gpa.rawFree(memory, alignment, @returnAddress());16195 errdefer gpa.rawFree(memory, .@"1", @returnAddress());
1615216196
16153 if (!options.undefined_contents) try mmSyncRead(file, memory, offset);16197 if (!options.undefined_contents) try mmSyncRead(file, memory, offset);
1615416198
...@@ -16180,12 +16224,13 @@ const CreateFileMapError = error{...@@ -16180,12 +16224,13 @@ const CreateFileMapError = error{
16180 OutOfMemory,16224 OutOfMemory,
16181 MappingAlreadyExists,16225 MappingAlreadyExists,
16182 Unseekable,16226 Unseekable,
16227 FileLockConflict,
16183} || Io.Cancelable || Io.UnexpectedError;16228} || Io.Cancelable || Io.UnexpectedError;
1618416229
16185fn createFileMap(16230fn createFileMap(
16186 file: File,16231 file: File,
16187 protection: std.process.MemoryProtection,16232 protection: std.process.MemoryProtection,
16188 offset: usize,16233 offset: u64,
16189 populate: bool,16234 populate: bool,
16190 aligned_len: usize,16235 aligned_len: usize,
16191) CreateFileMapError!File.MemoryMap {16236) CreateFileMapError!File.MemoryMap {
...@@ -16212,7 +16257,7 @@ fn createFileMap(...@@ -16212,7 +16257,7 @@ fn createFileMap(
16212 file.handle,16257 file.handle,
16213 )) {16258 )) {
16214 .SUCCESS => {},16259 .SUCCESS => {},
16215 .FILE_LOCK_CONFLICT => return error.FileLocked,16260 .FILE_LOCK_CONFLICT => return error.FileLockConflict,
16216 .INVALID_FILE_FOR_SECTION => return error.OperationUnsupported,16261 .INVALID_FILE_FOR_SECTION => return error.OperationUnsupported,
16217 else => |status| return windows.unexpectedStatus(status),16262 else => |status| return windows.unexpectedStatus(status),
16218 }16263 }
...@@ -16318,9 +16363,7 @@ fn fileMemoryMapDestroy(userdata: ?*anyopaque, mm: *File.MemoryMap) void {...@@ -16318,9 +16363,7 @@ fn fileMemoryMapDestroy(userdata: ?*anyopaque, mm: *File.MemoryMap) void {
16318 }16363 }
16319 } else {16364 } else {
16320 const gpa = t.allocator;16365 const gpa = t.allocator;
16321 const page_size = std.heap.pageSize();16366 gpa.rawFree(memory, .@"1", @returnAddress());
16322 const alignment: Alignment = .fromByteUnits(page_size);
16323 gpa.rawFree(memory, alignment, @returnAddress());
16324 }16367 }
16325 mm.* = undefined;16368 mm.* = undefined;
16326}16369}
...@@ -16331,6 +16374,7 @@ fn fileMemoryMapSetLength(...@@ -16331,6 +16374,7 @@ fn fileMemoryMapSetLength(
16331 new_len: usize,16374 new_len: usize,
16332) File.MemoryMap.SetLengthError!void {16375) File.MemoryMap.SetLengthError!void {
16333 const t: *Threaded = @ptrCast(@alignCast(userdata));16376 const t: *Threaded = @ptrCast(@alignCast(userdata));
16377 assert(std.mem.isAligned(new_len, std.heap.page_size_min));
16334 if (mm.section) |section| switch (native_os) {16378 if (mm.section) |section| switch (native_os) {
16335 .windows => {16379 .windows => {
16336 _ = section;16380 _ = section;
...@@ -16366,12 +16410,10 @@ fn fileMemoryMapSetLength(...@@ -16366,12 +16410,10 @@ fn fileMemoryMapSetLength(
16366 },16410 },
16367 } else {16411 } else {
16368 const gpa = t.allocator;16412 const gpa = t.allocator;
16369 const page_size = std.heap.pageSize();16413 if (gpa.rawRemap(mm.memory, .@"1", new_len, @returnAddress())) |new_ptr| {
16370 const alignment: Alignment = .fromByteUnits(page_size);
16371 if (gpa.rawRemap(mm.memory, alignment, new_len, @returnAddress())) |new_ptr| {
16372 mm.memory = new_ptr[0..new_len];16414 mm.memory = new_ptr[0..new_len];
16373 } else {16415 } else {
16374 const new_ptr = gpa.rawAlloc(new_len, alignment, @returnAddress()) orelse16416 const new_ptr = gpa.rawAlloc(new_len, .@"1", @returnAddress()) orelse
16375 return error.OutOfMemory;16417 return error.OutOfMemory;
16376 const copy_len = @min(new_len, mm.memory.len);16418 const copy_len = @min(new_len, mm.memory.len);
16377 @memcpy(new_ptr[0..copy_len], mm.memory[0..copy_len]);16419 @memcpy(new_ptr[0..copy_len], mm.memory[0..copy_len]);
...@@ -16387,11 +16429,16 @@ fn fileMemoryMapRead(userdata: ?*anyopaque, mm: *File.MemoryMap) File.ReadPositi...@@ -16387,11 +16429,16 @@ fn fileMemoryMapRead(userdata: ?*anyopaque, mm: *File.MemoryMap) File.ReadPositi
16387 return mmSyncRead(mm.file, mm.memory, mm.offset);16429 return mmSyncRead(mm.file, mm.memory, mm.offset);
16388}16430}
1638916431
16390fn fileMemoryMapWrite(userdata: ?*anyopaque, mm: *File.MemoryMap) File.WritePositionalError!void {16432fn fileMemoryMapWrite(
16433 userdata: ?*anyopaque,
16434 mm: *File.MemoryMap,
16435 file_size: u64,
16436) File.WritePositionalError!void {
16391 const t: *Threaded = @ptrCast(@alignCast(userdata));16437 const t: *Threaded = @ptrCast(@alignCast(userdata));
16392 _ = t;16438 _ = t;
16393 if (mm.section != null) return;16439 if (mm.section != null) return;
16394 return mmSyncWrite(mm.file, mm.memory, mm.offset);16440 const offset = mm.offset;
16441 return mmSyncWrite(mm.file, mm.memory[0..@intCast(file_size - offset)], offset);
16395}16442}
1639616443
16397fn mmSyncRead(file: File, memory: []u8, offset: u64) File.ReadPositionalError!void {16444fn mmSyncRead(file: File, memory: []u8, offset: u64) File.ReadPositionalError!void {
lib/std/Io/test.zig+9-17
...@@ -605,31 +605,23 @@ test "memory mapping" {...@@ -605,31 +605,23 @@ test "memory mapping" {
605 });605 });
606606
607 {607 {
608 var file = try tmp.dir.openFile(io, "blah.txt", .{});608 var file = try tmp.dir.openFile(io, "blah.txt", .{ .mode = .read_write });
609 defer file.close(io);609 defer file.close(io);
610610
611 var mm = try file.createMemoryMap(io, .{});611 const stat = try file.stat(io);
612 const aligned_len = std.mem.alignForward(usize, @intCast(stat.size), std.heap.pageSize());
613
614 var mm = try file.createMemoryMap(io, .{ .len = aligned_len });
612 defer mm.destroy(io);615 defer mm.destroy(io);
613616
614 try expectEqualStrings("this is my data123", mm.memory);617 try expectEqualStrings("this is my data123", std.mem.sliceTo(mm.memory, 0));
615 mm.memory[5] = '9';618 mm.memory[4] = '9';
616 mm.memory[8] = '9';619 mm.memory[7] = '9';
617620
618 try mm.write(io);621 try mm.write(io, stat.size);
619 }622 }
620623
621 var buffer: [100]u8 = undefined;624 var buffer: [100]u8 = undefined;
622 const updated_contents = try tmp.dir.readFile(io, "blah.txt", &buffer);625 const updated_contents = try tmp.dir.readFile(io, "blah.txt", &buffer);
623 try expectEqualStrings("this9is9my data123", updated_contents);626 try expectEqualStrings("this9is9my data123", updated_contents);
624
625 var file = try tmp.dir.openFile(io, "blah.txt", .{});
626 defer file.close(io);
627
628 var mm = try file.createMemoryMap(io, .{
629 .protection = .{ .read = true },
630 .offset = 2,
631 });
632 defer mm.destroy(io);
633
634 try expectEqualStrings("is9is9my data123", mm.memory);
635}627}
lib/std/c.zig+1-1
...@@ -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,
lib/std/posix.zig+1
...@@ -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;