diff --git a/lib/std/Io/File/MemoryMap.zig b/lib/std/Io/File/MemoryMap.zig index 2b917840a14409ce0008c63f47698aff4dee32ab..b3196aab3e420a95113822afd4bf99d584556d3e 100644 --- a/lib/std/Io/File/MemoryMap.zig +++ b/lib/std/Io/File/MemoryMap.zig @@ -95,6 +95,11 @@ pub const SetLengthError = error{ /// `options` is needed because the mapping may need to be destroyed and /// re-created. All the same options must be provided except for `len` which is /// the new length. +/// +/// This operation cannot be completed atomically on all operating systems. +/// When this function fails, the `MemoryMap` may be left in an unmapped state, +/// which can be detected by checking if `memory.len` is zero. In such case it +/// is safe to call `destroy` which will have no effect. pub fn setLength(mm: *MemoryMap, io: Io, options: CreateOptions) SetLengthError!void { return io.vtable.fileMemoryMapSetLength(io.userdata, mm, options); } diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index e0f5804abafe373c3dae01ed2df0585d3a6c0fae..db85f2e52c0a5f2c5bfeb6ddfaf15edc7c14e27b 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -16238,6 +16238,8 @@ fn createFileMap( try Thread.checkCancel(); var section = windows.INVALID_HANDLE_VALUE; + const section_size: windows.LARGE_INTEGER = @intCast(len); + const page = windows.PAGE.fromProtection(protection) orelse return error.AccessDenied; switch (windows.ntdll.NtCreateSection( §ion, .{ @@ -16251,8 +16253,8 @@ fn createFileMap( .STANDARD = .{ .RIGHTS = .REQUIRED }, }, null, - @constCast(&@as(i64, @intCast(len))), - .{ .READWRITE = true }, + §ion_size, + page, .{ .COMMIT = populate }, file.handle, )) { @@ -16260,6 +16262,7 @@ fn createFileMap( .FILE_LOCK_CONFLICT => return error.FileLockConflict, .INVALID_FILE_FOR_SECTION => return error.OperationUnsupported, .ACCESS_DENIED => return error.AccessDenied, + .SECTION_TOO_BIG => return error.SectionOversize, else => |status| return windows.unexpectedStatus(status), } var contents_ptr: ?[*]align(std.heap.page_size_min) u8 = null; @@ -16274,7 +16277,7 @@ fn createFileMap( &contents_len, .Unmap, .{}, - .{ .READWRITE = true }, + page, )) { .SUCCESS => {}, .CONFLICTING_ADDRESSES => return error.MappingAlreadyExists, @@ -16363,16 +16366,20 @@ fn fileMemoryMapDestroy(userdata: ?*anyopaque, mm: *File.MemoryMap) void { const memory = mm.memory; if (mm.section) |section| switch (native_os) { .windows => { + if (section == windows.INVALID_HANDLE_VALUE) return; _ = windows.ntdll.NtUnmapViewOfSection(windows.current_process, memory.ptr); windows.CloseHandle(section); }, .wasi => unreachable, - else => switch (posix.errno(posix.system.munmap(memory.ptr, memory.len))) { - .SUCCESS => {}, - else => |e| { - if (builtin.mode == .Debug) - std.log.err("failed to unmap {d} bytes at {*}: {t}", .{ memory.len, memory.ptr, e }); - }, + else => { + if (memory.len == 0) return; + switch (posix.errno(posix.system.munmap(memory.ptr, memory.len))) { + .SUCCESS => {}, + else => |e| { + if (builtin.mode == .Debug) + std.log.err("failed to unmap {d} bytes at {*}: {t}", .{ memory.len, memory.ptr, e }); + }, + } }, } else { const gpa = t.allocator; @@ -16394,47 +16401,16 @@ fn fileMemoryMapSetLength( const new_len = options.len; if (mm.section) |section| { - const aligned_old_len = alignment.forward(old_memory.len); - const aligned_new_len = alignment.forward(new_len); - if (aligned_new_len == aligned_old_len) { + if (alignment.forward(new_len) == alignment.forward(old_memory.len)) { mm.memory.len = new_len; return; } switch (native_os) { .windows => { - if (aligned_new_len > aligned_old_len) { - var new_section_size: windows.LARGE_INTEGER = @intCast(aligned_new_len); - switch (windows.ntdll.NtExtendSection(section, &new_section_size)) { - .SUCCESS => {}, - else => |status| return windows.unexpectedStatus(status), - } - assert(new_section_size == aligned_new_len); - mm.memory.len = new_len; - } else { - _ = windows.ntdll.NtUnmapViewOfSection(windows.current_process, old_memory.ptr); - windows.CloseHandle(section); - var contents_len = aligned_new_len; - var contents_ptr: ?[*]align(std.heap.page_size_min) u8 = null; - switch (windows.ntdll.NtMapViewOfSection( - section, - windows.current_process, - @ptrCast(&contents_ptr), - null, - 0, - null, - &contents_len, - .Unmap, - .{}, - .{ .READWRITE = true }, - )) { - .SUCCESS => {}, - .SECTION_PROTECTION => return error.PermissionDenied, - .INVALID_VIEW_SIZE => |status| return windows.statusBug(status), - else => |status| return windows.unexpectedStatus(status), - } - assert(contents_len == aligned_new_len); - mm.memory = contents_ptr.?[0..new_len]; - } + _ = windows.ntdll.NtUnmapViewOfSection(windows.current_process, old_memory.ptr); + windows.CloseHandle(section); + mm.section = windows.INVALID_HANDLE_VALUE; + mm.memory = &.{}; }, .wasi => unreachable, .linux => { @@ -16463,6 +16439,7 @@ fn fileMemoryMapSetLength( } }; mm.memory = new_memory; + return; }, else => { switch (posix.errno(posix.system.munmap(old_memory.ptr, old_memory.len))) { @@ -16475,20 +16452,21 @@ fn fileMemoryMapSetLength( return error.Unexpected; }, } - if (createFileMap(mm.file, options.protection, mm.offset, options.populate, new_len)) |result| { - mm.* = result; - return; - } else |err| switch (err) { - error.OperationUnsupported, - error.Unseekable, - error.SectionOversize, - error.MappingAlreadyExists, - error.FileLockConflict, - => return error.Unexpected, // It worked before on the same open file. - else => |e| return e, - } + mm.memory = &.{}; }, } + if (createFileMap(mm.file, options.protection, mm.offset, options.populate, new_len)) |result| { + mm.* = result; + return; + } else |err| switch (err) { + error.OperationUnsupported, + error.Unseekable, + error.SectionOversize, + error.MappingAlreadyExists, + error.FileLockConflict, + => return error.Unexpected, // It worked before on the same open file. + else => |e| return e, + } } else { const gpa = t.allocator; if (gpa.rawRemap(old_memory, alignment, new_len, @returnAddress())) |new_ptr| { diff --git a/lib/std/Io/test.zig b/lib/std/Io/test.zig index 0f4285b9bd92183cb9efa265e22ac2aec4042baa..6d06ebacaaefffcec8d1b4f377b38bb983a42f78 100644 --- a/lib/std/Io/test.zig +++ b/lib/std/Io/test.zig @@ -629,19 +629,20 @@ test "memory mapping" { try expectEqualStrings("this9is9my data123", updated_contents); { - var file = try tmp.dir.openFile(io, "blah.txt", .{ .mode = .read_only }); + var file = try tmp.dir.openFile(io, "blah.txt", .{ .mode = .read_write }); defer file.close(io); var mm = try file.createMemoryMap(io, .{ .len = "this9is9my".len, - .protection = .{ .read = true }, }); defer mm.destroy(io); try expectEqualStrings("this9is9my", mm.memory); // Cross a page boundary to require an actual remap. - try mm.setLength(io, .{ .len = std.heap.pageSize() * 2 }); + try mm.setLength(io, .{ + .len = std.heap.pageSize() * 2, + }); try mm.read(io); try expectEqualStrings("this9is9my data123\x00\x00", mm.memory[0.."this9is9my data123\x00\x00".len]); diff --git a/lib/std/os/windows.zig b/lib/std/os/windows.zig index 91f3cb6513fd6969b98dd0cc67b2e922268bde42..c94596de8e55be5c8d5689c5e4a53b815aafc7ae 100644 --- a/lib/std/os/windows.zig +++ b/lib/std/os/windows.zig @@ -2126,6 +2126,20 @@ pub const PAGE = packed struct(ULONG) { Reserved19: u12 = 0, REVERT_TO_FILE_MAP: bool = false, + + pub fn fromProtection(protection: std.process.MemoryProtection) ?PAGE { + // TODO https://github.com/ziglang/zig/issues/22214 + return switch (@as(u3, @bitCast(protection))) { + 0b000 => .{ .NOACCESS = true }, + 0b001 => .{ .READONLY = true }, + 0b010 => null, + 0b011 => .{ .READWRITE = true }, + 0b100 => .{ .EXECUTE = true }, + 0b101 => .{ .EXECUTE_READ = true }, + 0b110 => null, + 0b111 => .{ .EXECUTE_READWRITE = true }, + }; + } }; pub const MEM = struct { diff --git a/lib/std/process.zig b/lib/std/process.zig index 3f085037a30b5761687ba0195b96791f38049736..8c531830e54240c320ffab82f12db006bbdf47d6 100644 --- a/lib/std/process.zig +++ b/lib/std/process.zig @@ -1030,16 +1030,7 @@ pub fn protectMemory(memory: []align(std.heap.page_size_min) u8, protection: Mem var size = memory.len; // ntdll takes an extra level of indirection here var old: windows.PAGE = undefined; const current_process: windows.HANDLE = @ptrFromInt(@as(usize, @bitCast(@as(isize, -1)))); - const new: windows.PAGE = switch (@as(u3, @bitCast(protection))) { - 0b000 => .{ .NOACCESS = true }, - 0b001 => .{ .READONLY = true }, - 0b010 => return error.AccessDenied, // +w -r not allowed - 0b011 => .{ .READWRITE = true }, - 0b100 => .{ .EXECUTE = true }, - 0b101 => .{ .EXECUTE_READ = true }, - 0b110 => return error.AccessDenied, // +w -r not allowed - 0b111 => .{ .EXECUTE_READWRITE = true }, - }; + const new = windows.PAGE.fromProtection(protection) orelse return error.AccessDenied; switch (windows.ntdll.NtProtectVirtualMemory(current_process, @ptrCast(&addr), &size, new, &old)) { .SUCCESS => return, .INVALID_ADDRESS => return error.AccessDenied,