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{
9595/// `options` is needed because the mapping may need to be destroyed and
9696/// re-created. All the same options must be provided except for `len` which is
9797/// 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.
98103pub fn setLength(mm: *MemoryMap, io: Io, options: CreateOptions) SetLengthError!void {
99104 return io.vtable.fileMemoryMapSetLength(io.userdata, mm, options);
100105}
lib/std/Io/Threaded.zig+35-57
......@@ -16238,6 +16238,8 @@ fn createFileMap(
1623816238 try Thread.checkCancel();
1623916239
1624016240 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;
1624116243 switch (windows.ntdll.NtCreateSection(
1624216244 &section,
1624316245 .{
......@@ -16251,8 +16253,8 @@ fn createFileMap(
1625116253 .STANDARD = .{ .RIGHTS = .REQUIRED },
1625216254 },
1625316255 null,
16254 @constCast(&@as(i64, @intCast(len))),
16255 .{ .READWRITE = true },
16256 &section_size,
16257 page,
1625616258 .{ .COMMIT = populate },
1625716259 file.handle,
1625816260 )) {
......@@ -16260,6 +16262,7 @@ fn createFileMap(
1626016262 .FILE_LOCK_CONFLICT => return error.FileLockConflict,
1626116263 .INVALID_FILE_FOR_SECTION => return error.OperationUnsupported,
1626216264 .ACCESS_DENIED => return error.AccessDenied,
16265 .SECTION_TOO_BIG => return error.SectionOversize,
1626316266 else => |status| return windows.unexpectedStatus(status),
1626416267 }
1626516268 var contents_ptr: ?[*]align(std.heap.page_size_min) u8 = null;
......@@ -16274,7 +16277,7 @@ fn createFileMap(
1627416277 &contents_len,
1627516278 .Unmap,
1627616279 .{},
16277 .{ .READWRITE = true },
16280 page,
1627816281 )) {
1627916282 .SUCCESS => {},
1628016283 .CONFLICTING_ADDRESSES => return error.MappingAlreadyExists,
......@@ -16363,16 +16366,20 @@ fn fileMemoryMapDestroy(userdata: ?*anyopaque, mm: *File.MemoryMap) void {
1636316366 const memory = mm.memory;
1636416367 if (mm.section) |section| switch (native_os) {
1636516368 .windows => {
16369 if (section == windows.INVALID_HANDLE_VALUE) return;
1636616370 _ = windows.ntdll.NtUnmapViewOfSection(windows.current_process, memory.ptr);
1636716371 windows.CloseHandle(section);
1636816372 },
1636916373 .wasi => unreachable,
16370 else => switch (posix.errno(posix.system.munmap(memory.ptr, memory.len))) {
16371 .SUCCESS => {},
16372 else => |e| {
16373 if (builtin.mode == .Debug)
16374 std.log.err("failed to unmap {d} bytes at {*}: {t}", .{ memory.len, memory.ptr, e });
16375 },
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 }
1637616383 },
1637716384 } else {
1637816385 const gpa = t.allocator;
......@@ -16394,47 +16401,16 @@ fn fileMemoryMapSetLength(
1639416401 const new_len = options.len;
1639516402
1639616403 if (mm.section) |section| {
16397 const aligned_old_len = alignment.forward(old_memory.len);
16398 const aligned_new_len = alignment.forward(new_len);
16399 if (aligned_new_len == aligned_old_len) {
16404 if (alignment.forward(new_len) == alignment.forward(old_memory.len)) {
1640016405 mm.memory.len = new_len;
1640116406 return;
1640216407 }
1640316408 switch (native_os) {
1640416409 .windows => {
16405 if (aligned_new_len > aligned_old_len) {
16406 var new_section_size: windows.LARGE_INTEGER = @intCast(aligned_new_len);
16407 switch (windows.ntdll.NtExtendSection(section, &new_section_size)) {
16408 .SUCCESS => {},
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 }
16410 _ = windows.ntdll.NtUnmapViewOfSection(windows.current_process, old_memory.ptr);
16411 windows.CloseHandle(section);
16412 mm.section = windows.INVALID_HANDLE_VALUE;
16413 mm.memory = &.{};
1643816414 },
1643916415 .wasi => unreachable,
1644016416 .linux => {
......@@ -16463,6 +16439,7 @@ fn fileMemoryMapSetLength(
1646316439 }
1646416440 };
1646516441 mm.memory = new_memory;
16442 return;
1646616443 },
1646716444 else => {
1646816445 switch (posix.errno(posix.system.munmap(old_memory.ptr, old_memory.len))) {
......@@ -16475,20 +16452,21 @@ fn fileMemoryMapSetLength(
1647516452 return error.Unexpected;
1647616453 },
1647716454 }
16478 if (createFileMap(mm.file, options.protection, mm.offset, options.populate, new_len)) |result| {
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 }
16455 mm.memory = &.{};
1649016456 },
1649116457 }
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 }
1649216470 } else {
1649316471 const gpa = t.allocator;
1649416472 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" {
629629 try expectEqualStrings("this9is9my data123", updated_contents);
630630
631631 {
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 });
633633 defer file.close(io);
634634
635635 var mm = try file.createMemoryMap(io, .{
636636 .len = "this9is9my".len,
637 .protection = .{ .read = true },
638637 });
639638 defer mm.destroy(io);
640639
641640 try expectEqualStrings("this9is9my", mm.memory);
642641
643642 // 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 });
645646 try mm.read(io);
646647
647648 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) {
21262126 Reserved19: u12 = 0,
21272127
21282128 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 }
21292143};
21302144
21312145pub 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
10301030 var size = memory.len; // ntdll takes an extra level of indirection here
10311031 var old: windows.PAGE = undefined;
10321032 const current_process: windows.HANDLE = @ptrFromInt(@as(usize, @bitCast(@as(isize, -1))));
1033 const new: windows.PAGE = switch (@as(u3, @bitCast(protection))) {
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 };
1033 const new = windows.PAGE.fromProtection(protection) orelse return error.AccessDenied;
10431034 switch (windows.ntdll.NtProtectVirtualMemory(current_process, @ptrCast(&addr), &size, new, &old)) {
10441035 .SUCCESS => return,
10451036 .INVALID_ADDRESS => return error.AccessDenied,