authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-01-15 14:10:09-08:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-01-15 14:18:21-08:00
log7b21fd7244ed1b0459b542346e96a20e91138f69
treef5a448d213c0176b5a20cd03e59a9f69765c2af7
parentd2585e68a79b8e43166e797d6adc33282acac046

std.Io.Threaded: fix memory mapping on windows

- set protection flags properly - handle when mapping fails after unmapping

5 files changed, 59 insertions(+), 70 deletions(-)

lib/std/Io/File/MemoryMap.zig+5
...@@ -95,6 +95,11 @@ pub const SetLengthError = error{...@@ -95,6 +95,11 @@ pub const SetLengthError = error{
95/// `options` is needed because the mapping may need to be destroyed and95/// `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 is96/// re-created. All the same options must be provided except for `len` which is
97/// the new length.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.
98pub fn setLength(mm: *MemoryMap, io: Io, options: CreateOptions) SetLengthError!void {103pub fn setLength(mm: *MemoryMap, io: Io, options: CreateOptions) SetLengthError!void {
99 return io.vtable.fileMemoryMapSetLength(io.userdata, mm, options);104 return io.vtable.fileMemoryMapSetLength(io.userdata, mm, options);
100}105}
lib/std/Io/Threaded.zig+35-57
...@@ -16238,6 +16238,8 @@ fn createFileMap(...@@ -16238,6 +16238,8 @@ fn createFileMap(
16238 try Thread.checkCancel();16238 try Thread.checkCancel();
1623916239
16240 var section = windows.INVALID_HANDLE_VALUE;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;
16241 switch (windows.ntdll.NtCreateSection(16243 switch (windows.ntdll.NtCreateSection(
16242 &section,16244 &section,
16243 .{16245 .{
...@@ -16251,8 +16253,8 @@ fn createFileMap(...@@ -16251,8 +16253,8 @@ fn createFileMap(
16251 .STANDARD = .{ .RIGHTS = .REQUIRED },16253 .STANDARD = .{ .RIGHTS = .REQUIRED },
16252 },16254 },
16253 null,16255 null,
16254 @constCast(&@as(i64, @intCast(len))),16256 &section_size,
16255 .{ .READWRITE = true },16257 page,
16256 .{ .COMMIT = populate },16258 .{ .COMMIT = populate },
16257 file.handle,16259 file.handle,
16258 )) {16260 )) {
...@@ -16260,6 +16262,7 @@ fn createFileMap(...@@ -16260,6 +16262,7 @@ fn createFileMap(
16260 .FILE_LOCK_CONFLICT => return error.FileLockConflict,16262 .FILE_LOCK_CONFLICT => return error.FileLockConflict,
16261 .INVALID_FILE_FOR_SECTION => return error.OperationUnsupported,16263 .INVALID_FILE_FOR_SECTION => return error.OperationUnsupported,
16262 .ACCESS_DENIED => return error.AccessDenied,16264 .ACCESS_DENIED => return error.AccessDenied,
16265 .SECTION_TOO_BIG => return error.SectionOversize,
16263 else => |status| return windows.unexpectedStatus(status),16266 else => |status| return windows.unexpectedStatus(status),
16264 }16267 }
16265 var contents_ptr: ?[*]align(std.heap.page_size_min) u8 = null;16268 var contents_ptr: ?[*]align(std.heap.page_size_min) u8 = null;
...@@ -16274,7 +16277,7 @@ fn createFileMap(...@@ -16274,7 +16277,7 @@ fn createFileMap(
16274 &contents_len,16277 &contents_len,
16275 .Unmap,16278 .Unmap,
16276 .{},16279 .{},
16277 .{ .READWRITE = true },16280 page,
16278 )) {16281 )) {
16279 .SUCCESS => {},16282 .SUCCESS => {},
16280 .CONFLICTING_ADDRESSES => return error.MappingAlreadyExists,16283 .CONFLICTING_ADDRESSES => return error.MappingAlreadyExists,
...@@ -16363,16 +16366,20 @@ fn fileMemoryMapDestroy(userdata: ?*anyopaque, mm: *File.MemoryMap) void {...@@ -16363,16 +16366,20 @@ fn fileMemoryMapDestroy(userdata: ?*anyopaque, mm: *File.MemoryMap) void {
16363 const memory = mm.memory;16366 const memory = mm.memory;
16364 if (mm.section) |section| switch (native_os) {16367 if (mm.section) |section| switch (native_os) {
16365 .windows => {16368 .windows => {
16369 if (section == windows.INVALID_HANDLE_VALUE) return;
16366 _ = windows.ntdll.NtUnmapViewOfSection(windows.current_process, memory.ptr);16370 _ = windows.ntdll.NtUnmapViewOfSection(windows.current_process, memory.ptr);
16367 windows.CloseHandle(section);16371 windows.CloseHandle(section);
16368 },16372 },
16369 .wasi => unreachable,16373 .wasi => unreachable,
16370 else => switch (posix.errno(posix.system.munmap(memory.ptr, memory.len))) {16374 else => {
16371 .SUCCESS => {},16375 if (memory.len == 0) return;
16372 else => |e| {16376 switch (posix.errno(posix.system.munmap(memory.ptr, memory.len))) {
16373 if (builtin.mode == .Debug)16377 .SUCCESS => {},
16374 std.log.err("failed to unmap {d} bytes at {*}: {t}", .{ memory.len, memory.ptr, e });16378 else => |e| {
16375 },16379 if (builtin.mode == .Debug)
16380 std.log.err("failed to unmap {d} bytes at {*}: {t}", .{ memory.len, memory.ptr, e });
16381 },
16382 }
16376 },16383 },
16377 } else {16384 } else {
16378 const gpa = t.allocator;16385 const gpa = t.allocator;
...@@ -16394,47 +16401,16 @@ fn fileMemoryMapSetLength(...@@ -16394,47 +16401,16 @@ fn fileMemoryMapSetLength(
16394 const new_len = options.len;16401 const new_len = options.len;
1639516402
16396 if (mm.section) |section| {16403 if (mm.section) |section| {
16397 const aligned_old_len = alignment.forward(old_memory.len);16404 if (alignment.forward(new_len) == alignment.forward(old_memory.len)) {
16398 const aligned_new_len = alignment.forward(new_len);
16399 if (aligned_new_len == aligned_old_len) {
16400 mm.memory.len = new_len;16405 mm.memory.len = new_len;
16401 return;16406 return;
16402 }16407 }
16403 switch (native_os) {16408 switch (native_os) {
16404 .windows => {16409 .windows => {
16405 if (aligned_new_len > aligned_old_len) {16410 _ = windows.ntdll.NtUnmapViewOfSection(windows.current_process, old_memory.ptr);
16406 var new_section_size: windows.LARGE_INTEGER = @intCast(aligned_new_len);16411 windows.CloseHandle(section);
16407 switch (windows.ntdll.NtExtendSection(section, &new_section_size)) {16412 mm.section = windows.INVALID_HANDLE_VALUE;
16408 .SUCCESS => {},16413 mm.memory = &.{};
16409 else => |status| return windows.unexpectedStatus(status),
16410 }
16411 assert(new_section_size == aligned_new_len);
16412 mm.memory.len = new_len;
16413 } else {
16414 _ = windows.ntdll.NtUnmapViewOfSection(windows.current_process, old_memory.ptr);
16415 windows.CloseHandle(section);
16416 var contents_len = aligned_new_len;
16417 var contents_ptr: ?[*]align(std.heap.page_size_min) u8 = null;
16418 switch (windows.ntdll.NtMapViewOfSection(
16419 section,
16420 windows.current_process,
16421 @ptrCast(&contents_ptr),
16422 null,
16423 0,
16424 null,
16425 &contents_len,
16426 .Unmap,
16427 .{},
16428 .{ .READWRITE = true },
16429 )) {
16430 .SUCCESS => {},
16431 .SECTION_PROTECTION => return error.PermissionDenied,
16432 .INVALID_VIEW_SIZE => |status| return windows.statusBug(status),
16433 else => |status| return windows.unexpectedStatus(status),
16434 }
16435 assert(contents_len == aligned_new_len);
16436 mm.memory = contents_ptr.?[0..new_len];
16437 }
16438 },16414 },
16439 .wasi => unreachable,16415 .wasi => unreachable,
16440 .linux => {16416 .linux => {
...@@ -16463,6 +16439,7 @@ fn fileMemoryMapSetLength(...@@ -16463,6 +16439,7 @@ fn fileMemoryMapSetLength(
16463 }16439 }
16464 };16440 };
16465 mm.memory = new_memory;16441 mm.memory = new_memory;
16442 return;
16466 },16443 },
16467 else => {16444 else => {
16468 switch (posix.errno(posix.system.munmap(old_memory.ptr, old_memory.len))) {16445 switch (posix.errno(posix.system.munmap(old_memory.ptr, old_memory.len))) {
...@@ -16475,20 +16452,21 @@ fn fileMemoryMapSetLength(...@@ -16475,20 +16452,21 @@ fn fileMemoryMapSetLength(
16475 return error.Unexpected;16452 return error.Unexpected;
16476 },16453 },
16477 }16454 }
16478 if (createFileMap(mm.file, options.protection, mm.offset, options.populate, new_len)) |result| {16455 mm.memory = &.{};
16479 mm.* = result;
16480 return;
16481 } else |err| switch (err) {
16482 error.OperationUnsupported,
16483 error.Unseekable,
16484 error.SectionOversize,
16485 error.MappingAlreadyExists,
16486 error.FileLockConflict,
16487 => return error.Unexpected, // It worked before on the same open file.
16488 else => |e| return e,
16489 }
16490 },16456 },
16491 }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 }
16492 } else {16470 } else {
16493 const gpa = t.allocator;16471 const gpa = t.allocator;
16494 if (gpa.rawRemap(old_memory, alignment, new_len, @returnAddress())) |new_ptr| {16472 if (gpa.rawRemap(old_memory, alignment, new_len, @returnAddress())) |new_ptr| {
lib/std/Io/test.zig+4-3
...@@ -629,19 +629,20 @@ test "memory mapping" {...@@ -629,19 +629,20 @@ test "memory mapping" {
629 try expectEqualStrings("this9is9my data123", updated_contents);629 try expectEqualStrings("this9is9my data123", updated_contents);
630630
631 {631 {
632 var file = try tmp.dir.openFile(io, "blah.txt", .{ .mode = .read_only });632 var file = try tmp.dir.openFile(io, "blah.txt", .{ .mode = .read_write });
633 defer file.close(io);633 defer file.close(io);
634634
635 var mm = try file.createMemoryMap(io, .{635 var mm = try file.createMemoryMap(io, .{
636 .len = "this9is9my".len,636 .len = "this9is9my".len,
637 .protection = .{ .read = true },
638 });637 });
639 defer mm.destroy(io);638 defer mm.destroy(io);
640639
641 try expectEqualStrings("this9is9my", mm.memory);640 try expectEqualStrings("this9is9my", mm.memory);
642641
643 // Cross a page boundary to require an actual remap.642 // Cross a page boundary to require an actual remap.
644 try mm.setLength(io, .{ .len = std.heap.pageSize() * 2 });643 try mm.setLength(io, .{
644 .len = std.heap.pageSize() * 2,
645 });
645 try mm.read(io);646 try mm.read(io);
646647
647 try expectEqualStrings("this9is9my data123\x00\x00", mm.memory[0.."this9is9my data123\x00\x00".len]);648 try expectEqualStrings("this9is9my data123\x00\x00", mm.memory[0.."this9is9my data123\x00\x00".len]);
lib/std/os/windows.zig+14
...@@ -2126,6 +2126,20 @@ pub const PAGE = packed struct(ULONG) {...@@ -2126,6 +2126,20 @@ pub const PAGE = packed struct(ULONG) {
2126 Reserved19: u12 = 0,2126 Reserved19: u12 = 0,
21272127
2128 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 }
2129};2143};
21302144
2131pub const MEM = struct {2145pub const MEM = struct {
lib/std/process.zig+1-10
...@@ -1030,16 +1030,7 @@ pub fn protectMemory(memory: []align(std.heap.page_size_min) u8, protection: Mem...@@ -1030,16 +1030,7 @@ pub fn protectMemory(memory: []align(std.heap.page_size_min) u8, protection: Mem
1030 var size = memory.len; // ntdll takes an extra level of indirection here1030 var size = memory.len; // ntdll takes an extra level of indirection here
1031 var old: windows.PAGE = undefined;1031 var old: windows.PAGE = undefined;
1032 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))));
1033 const new: windows.PAGE = switch (@as(u3, @bitCast(protection))) {1033 const new = windows.PAGE.fromProtection(protection) orelse return error.AccessDenied;
1034 0b000 => .{ .NOACCESS = true },
1035 0b001 => .{ .READONLY = true },
1036 0b010 => return error.AccessDenied, // +w -r not allowed
1037 0b011 => .{ .READWRITE = true },
1038 0b100 => .{ .EXECUTE = true },
1039 0b101 => .{ .EXECUTE_READ = true },
1040 0b110 => return error.AccessDenied, // +w -r not allowed
1041 0b111 => .{ .EXECUTE_READWRITE = true },
1042 };
1043 switch (windows.ntdll.NtProtectVirtualMemory(current_process, @ptrCast(&addr), &size, new, &old)) {1034 switch (windows.ntdll.NtProtectVirtualMemory(current_process, @ptrCast(&addr), &size, new, &old)) {
1044 .SUCCESS => return,1035 .SUCCESS => return,
1045 .INVALID_ADDRESS => return error.AccessDenied,1036 .INVALID_ADDRESS => return error.AccessDenied,