commit 5a7dc4b0fae62c8b7ec798043c93e4d90704a638 (tree)
parent 63f345a75afdf4f956b136c06776f109f5c567af
Author: Andrew Kelley <andrew@ziglang.org>
Date: Wed, 14 Jan 2026 14:41:31 -0800
std.Io: introduce File.MemoryMap
by defining the pointer contents to only be synchronized after explicit
sync points, makes it legal to have a fallback implementation based on
file operations while still supporting a handful of use cases for memory
mapping.
furthermore, it makes it legal for evented I/O implementations to use
evented file I/O for the sync points rather than memory mapping.
not yet done:
- implement checking the length when options.len is null
- some windows impl work
- some wasi impl work
- unit tests
- integration with compiler
Diffstat:
9 files changed, 597 insertions(+), 83 deletions(-)
diff --git a/lib/std/Io.zig b/lib/std/Io.zig
@@ -9,6 +9,7 @@
//! * concurrent queues
//! * wait groups and select
//! * mutexes, futexes, events, and conditions
+//! * memory mapped files
//! This interface allows programmers to write optimal, reusable code while
//! participating in these operations.
const Io = @This();
@@ -653,6 +654,12 @@ pub const VTable = struct {
fileRealPath: *const fn (?*anyopaque, File, out_buffer: []u8) File.RealPathError!usize,
fileHardLink: *const fn (?*anyopaque, File, Dir, []const u8, File.HardLinkOptions) File.HardLinkError!void,
+ fileMemoryMapCreate: *const fn (?*anyopaque, File, File.MemoryMap.CreateOptions) File.MemoryMap.CreateError!File.MemoryMap,
+ fileMemoryMapDestroy: *const fn (?*anyopaque, *File.MemoryMap) void,
+ fileMemoryMapSetLength: *const fn (?*anyopaque, *File.MemoryMap, n: usize) File.MemoryMap.SetLengthError!void,
+ fileMemoryMapRead: *const fn (?*anyopaque, *File.MemoryMap) File.ReadPositionalError!void,
+ fileMemoryMapWrite: *const fn (?*anyopaque, *File.MemoryMap) File.WritePositionalError!void,
+
processExecutableOpen: *const fn (?*anyopaque, File.OpenFlags) std.process.OpenExecutableError!File,
processExecutablePath: *const fn (?*anyopaque, buffer: []u8) std.process.ExecutablePathError!usize,
lockStderr: *const fn (?*anyopaque, ?Terminal.Mode) Cancelable!LockedStderr,
diff --git a/lib/std/Io/File.zig b/lib/std/Io/File.zig
@@ -14,6 +14,8 @@ handle: Handle,
pub const Reader = @import("File/Reader.zig");
pub const Writer = @import("File/Writer.zig");
pub const Atomic = @import("File/Atomic.zig");
+/// Memory intended to remain consistent with file contents.
+pub const MemoryMap = @import("File/MemoryMap.zig");
pub const Handle = std.posix.fd_t;
pub const INode = std.posix.ino_t;
@@ -529,7 +531,25 @@ pub fn readStreaming(file: File, io: Io, buffer: []const []u8) Reader.Error!usiz
return io.vtable.fileReadStreaming(io.userdata, file, buffer);
}
-pub const ReadPositionalError = Reader.Error || error{Unseekable};
+pub const ReadPositionalError = error{
+ InputOutput,
+ SystemResources,
+ /// Trying to read a directory file descriptor as if it were a file.
+ IsDir,
+ BrokenPipe,
+ /// Non-blocking has been enabled, and reading from the file descriptor
+ /// would block.
+ WouldBlock,
+ /// In WASI, this error occurs when the file descriptor does
+ /// not hold the required rights to read from it.
+ AccessDenied,
+ /// Unable to read file due to lock. Depending on the `Io` implementation,
+ /// reading from a locked file may return this error, or may ignore the
+ /// lock.
+ LockViolation,
+ /// This file cannot be read positionally.
+ Unseekable,
+} || Io.Cancelable || Io.UnexpectedError;
/// Returns 0 on stream end or if `buffer` has no space available for data.
///
@@ -539,7 +559,31 @@ pub fn readPositional(file: File, io: Io, buffer: []const []u8, offset: u64) Rea
return io.vtable.fileReadPositional(io.userdata, file, buffer, offset);
}
-pub const WritePositionalError = Writer.Error || error{Unseekable};
+pub const WritePositionalError = error{
+ DiskQuota,
+ FileTooBig,
+ InputOutput,
+ NoSpaceLeft,
+ DeviceBusy,
+ /// File descriptor does not hold the required rights to write to it.
+ AccessDenied,
+ PermissionDenied,
+ /// File is an unconnected socket, or closed its read end.
+ BrokenPipe,
+ /// Insufficient kernel memory to read from in_fd.
+ SystemResources,
+ /// The process cannot access the file because another process has locked
+ /// a portion of the file. Windows-only.
+ LockViolation,
+ /// Non-blocking has been enabled and this operation would block.
+ WouldBlock,
+ /// This error occurs when a device gets disconnected before or mid-flush
+ /// while it's being written to - errno(6): No such device or address.
+ NoDevice,
+ FileBusy,
+ /// This file cannot be written positionally.
+ Unseekable,
+} || Io.Cancelable || Io.UnexpectedError;
/// See also:
/// * `writer`
@@ -744,4 +788,5 @@ test {
_ = Reader;
_ = Writer;
_ = Atomic;
+ _ = MemoryMap;
}
diff --git a/lib/std/Io/File/MemoryMap.zig b/lib/std/Io/File/MemoryMap.zig
@@ -0,0 +1,79 @@
+const MemoryMap = @This();
+
+const builtin = @import("builtin");
+const native_os = builtin.os.tag;
+const is_windows = native_os == .windows;
+
+const std = @import("../../std.zig");
+const Io = std.Io;
+const File = Io.File;
+const Allocator = std.mem.Allocator;
+
+file: File,
+/// Byte index inside `file` where `memory` starts.
+offset: usize,
+/// Memory that may or may not remain consistent with file contents. Use `read`
+/// and `write` to ensure synchronization points.
+memory: []u8,
+/// Tells whether it is memory-mapped or file operations. On Windows this also
+/// has a section handle.
+section: ?Section,
+
+pub const Section = if (is_windows) std.os.windows.HANDLE else void;
+
+pub const CreateError = error{
+ /// A file descriptor refers to a non-regular file. Or a file mapping was requested,
+ /// but the file descriptor is not open for reading. Or `MAP.SHARED` was requested
+ /// and `PROT_WRITE` is set, but the file descriptor is not open in `RDWR` mode.
+ /// Or `PROT_WRITE` is set, but the file is append-only.
+ AccessDenied,
+ /// The `prot` argument asks for `PROT_EXEC` but the mapped area belongs to a file on
+ /// a filesystem that was mounted no-exec.
+ PermissionDenied,
+ LockedMemoryLimitExceeded,
+ ProcessFdQuotaExceeded,
+ SystemFdQuotaExceeded,
+} || Allocator.Error || File.ReadPositionalError;
+
+pub const CreateOptions = struct {
+ protection: std.process.MemoryProtection = .{ .read = true, .write = true },
+ populate: bool = true,
+ /// Byte index of file to start from.
+ offset: u64 = 0,
+ /// `null` indicates to map the entire file. If mapping the entire file is
+ /// desired and the file size is known, it is more efficient to populate
+ /// the value here.
+ len: ?usize = null,
+};
+
+pub fn create(io: Io, file: File, options: CreateOptions) CreateError!MemoryMap {
+ return io.vtable.fileMemoryMapCreate(io.userdata, file, options);
+}
+
+/// If `write` is not called before this function, changes to `memory` may or may
+/// not be synchronized to `file`.
+pub fn destroy(mm: *MemoryMap, io: Io) void {
+ io.vtable.fileMemoryMapDestroy(io.userdata, mm);
+}
+
+pub const SetLengthError = error{
+ LockedMemoryLimitExceeded,
+} || Allocator.Error || File.SetLengthError;
+
+/// Change the size of the mapping. This does not sync the contents. The size
+/// of the file after calling this is unspecified until `write` is called.
+///
+/// May change the pointer address of `memory`.
+pub fn setLength(mm: *MemoryMap, io: Io, n: usize) File.SetLengthError!void {
+ return io.vtable.fileMemoryMapSetLength(io.userdata, mm, n);
+}
+
+/// Synchronizes the contents of `memory` from `file`.
+pub fn read(mm: *MemoryMap, io: Io) File.ReadPositionalError!void {
+ return io.vtable.fileMemoryMapRead(io.userdata, mm);
+}
+
+/// Synchronizes the contents of `memory` to `file`.
+pub fn write(mm: *MemoryMap, io: Io) File.WritePositionalError!void {
+ return io.vtable.fileMemoryMapWrite(io.userdata, mm);
+}
diff --git a/lib/std/Io/File/Reader.zig b/lib/std/Io/File/Reader.zig
@@ -29,13 +29,11 @@ interface: Io.Reader,
pub const Error = error{
InputOutput,
SystemResources,
+ /// Trying to read a directory file descriptor as if it were a file.
IsDir,
BrokenPipe,
ConnectionResetByPeer,
Timeout,
- /// In WASI, EBADF is mapped to this error because it is returned when
- /// trying to read a directory file descriptor as if it were a file.
- NotOpenForReading,
SocketUnconnected,
/// Non-blocking has been enabled, and reading from the file descriptor
/// would block.
diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig
@@ -22,6 +22,12 @@ const windows = std.os.windows;
const ws2_32 = std.os.windows.ws2_32;
/// Thread-safe.
+///
+/// Used for:
+/// * allocating `Io.Future` and `Io.Group` closures.
+/// * formatting spawning child processes
+/// * scanning environment variables on some targets
+/// * memory-mapping when mmap or equivalent is not available
allocator: Allocator,
mutex: std.Thread.Mutex = .{},
cond: std.Thread.Condition = .{},
@@ -1490,6 +1496,12 @@ pub fn io(t: *Threaded) Io {
.fileRealPath = fileRealPath,
.fileHardLink = fileHardLink,
+ .fileMemoryMapCreate = fileMemoryMapCreate,
+ .fileMemoryMapDestroy = fileMemoryMapDestroy,
+ .fileMemoryMapSetLength = fileMemoryMapSetLength,
+ .fileMemoryMapRead = fileMemoryMapRead,
+ .fileMemoryMapWrite = fileMemoryMapWrite,
+
.processExecutableOpen = processExecutableOpen,
.processExecutablePath = processExecutablePath,
.lockStderr = lockStderr,
@@ -1642,6 +1654,12 @@ pub fn ioBasic(t: *Threaded) Io {
.fileRealPath = fileRealPath,
.fileHardLink = fileHardLink,
+ .fileMemoryMapCreate = fileMemoryMapCreate,
+ .fileMemoryMapDestroy = fileMemoryMapDestroy,
+ .fileMemoryMapSetLength = fileMemoryMapSetLength,
+ .fileMemoryMapRead = fileMemoryMapRead,
+ .fileMemoryMapWrite = fileMemoryMapWrite,
+
.processExecutableOpen = processExecutableOpen,
.processExecutablePath = processExecutablePath,
.lockStderr = lockStderr,
@@ -1733,15 +1751,24 @@ const have_wait4 = switch (native_os) {
else => false,
};
+const have_mmap = switch (native_os) {
+ .wasi, .windows => false,
+ else => true,
+};
+
const open_sym = if (posix.lfs64_abi) posix.system.open64 else posix.system.open;
const openat_sym = if (posix.lfs64_abi) posix.system.openat64 else posix.system.openat;
const fstat_sym = if (posix.lfs64_abi) posix.system.fstat64 else posix.system.fstat;
const fstatat_sym = if (posix.lfs64_abi) posix.system.fstatat64 else posix.system.fstatat;
const lseek_sym = if (posix.lfs64_abi) posix.system.lseek64 else posix.system.lseek;
const preadv_sym = if (posix.lfs64_abi) posix.system.preadv64 else posix.system.preadv;
+const pread_sym = if (posix.lfs64_abi) posix.system.pread64 else posix.system.pread;
const ftruncate_sym = if (posix.lfs64_abi) posix.system.ftruncate64 else posix.system.ftruncate;
const pwritev_sym = if (posix.lfs64_abi) posix.system.pwritev64 else posix.system.pwritev;
+const pwrite_sym = if (posix.lfs64_abi) posix.system.pwrite64 else posix.system.pwrite;
const sendfile_sym = if (posix.lfs64_abi) posix.system.sendfile64 else posix.system.sendfile;
+const mmap_sym = if (posix.lfs64_abi) posix.system.mmap64 else posix.system.mmap;
+
const linux_copy_file_range_use_c = std.c.versionCheck(if (builtin.abi.isAndroid()) .{
.major = 34,
.minor = 0,
@@ -8028,27 +8055,22 @@ fn fileReadPositionalPosix(userdata: ?*anyopaque, file: File, data: []const []u8
try syscall.checkCancel();
continue;
},
- else => |e| {
- syscall.finish();
- switch (e) {
- .INVAL => |err| return errnoBug(err),
- .FAULT => |err| return errnoBug(err),
- .AGAIN => |err| return errnoBug(err),
- .BADF => return error.NotOpenForReading, // File operation on directory.
- .IO => return error.InputOutput,
- .ISDIR => return error.IsDir,
- .NOBUFS => return error.SystemResources,
- .NOMEM => return error.SystemResources,
- .NOTCONN => return error.SocketUnconnected,
- .CONNRESET => return error.ConnectionResetByPeer,
- .TIMEDOUT => return error.Timeout,
- .NXIO => return error.Unseekable,
- .SPIPE => return error.Unseekable,
- .OVERFLOW => return error.Unseekable,
- .NOTCAPABLE => return error.AccessDenied,
- else => |err| return posix.unexpectedErrno(err),
- }
- },
+ .NOTCONN => |err| return syscall.errnoBug(err), // not a socket
+ .CONNRESET => |err| return syscall.errnoBug(err), // not a socket
+ .BADF => |err| return syscall.errnoBug(err), // use after free
+ .INVAL => |err| return syscall.errnoBug(err),
+ .FAULT => |err| return syscall.errnoBug(err), // segmentation fault
+ .AGAIN => |err| return syscall.errnoBug(err),
+ .IO => return syscall.fail(error.InputOutput),
+ .ISDIR => return syscall.fail(error.IsDir),
+ .NOBUFS => return syscall.fail(error.SystemResources),
+ .NOMEM => return syscall.fail(error.SystemResources),
+ .TIMEDOUT => return syscall.fail(error.Timeout),
+ .NXIO => return syscall.fail(error.Unseekable),
+ .SPIPE => return syscall.fail(error.Unseekable),
+ .OVERFLOW => return syscall.fail(error.Unseekable),
+ .NOTCAPABLE => return syscall.fail(error.AccessDenied),
+ else => |err| return syscall.unexpectedErrno(err),
}
}
}
@@ -8061,33 +8083,28 @@ fn fileReadPositionalPosix(userdata: ?*anyopaque, file: File, data: []const []u8
syscall.finish();
return @bitCast(rc);
},
- .INTR => {
+ .INTR, .TIMEDOUT => {
try syscall.checkCancel();
continue;
},
- else => |e| {
+ .NXIO => return syscall.fail(error.Unseekable),
+ .SPIPE => return syscall.fail(error.Unseekable),
+ .OVERFLOW => return syscall.fail(error.Unseekable),
+ .NOBUFS => return syscall.fail(error.SystemResources),
+ .NOMEM => return syscall.fail(error.SystemResources),
+ .AGAIN => return syscall.fail(error.WouldBlock),
+ .IO => return syscall.fail(error.InputOutput),
+ .ISDIR => return syscall.fail(error.IsDir),
+ .NOTCONN => |err| return syscall.errnoBug(err), // not a socket
+ .CONNRESET => |err| return syscall.errnoBug(err), // not a socket
+ .INVAL => |err| return syscall.errnoBug(err),
+ .FAULT => |err| return syscall.errnoBug(err),
+ .BADF => |err| {
syscall.finish();
- switch (e) {
- .INVAL => |err| return errnoBug(err),
- .FAULT => |err| return errnoBug(err),
- .AGAIN => return error.WouldBlock,
- .BADF => |err| {
- if (native_os == .wasi) return error.NotOpenForReading; // File operation on directory.
- return errnoBug(err); // File descriptor used after closed.
- },
- .IO => return error.InputOutput,
- .ISDIR => return error.IsDir,
- .NOBUFS => return error.SystemResources,
- .NOMEM => return error.SystemResources,
- .NOTCONN => return error.SocketUnconnected,
- .CONNRESET => return error.ConnectionResetByPeer,
- .TIMEDOUT => return error.Timeout,
- .NXIO => return error.Unseekable,
- .SPIPE => return error.Unseekable,
- .OVERFLOW => return error.Unseekable,
- else => |err| return posix.unexpectedErrno(err),
- }
+ if (native_os == .wasi) return error.IsDir; // File operation on directory.
+ return errnoBug(err); // File descriptor used after closed.
},
+ else => |err| return syscall.unexpectedErrno(err),
}
}
}
@@ -8770,29 +8787,24 @@ fn fileWritePositional(
try syscall.checkCancel();
continue;
},
- else => |e| {
- syscall.finish();
- switch (e) {
- .INVAL => |err| return errnoBug(err),
- .FAULT => |err| return errnoBug(err),
- .AGAIN => return error.WouldBlock,
- .BADF => return error.NotOpenForWriting, // Usually a race condition.
- .DESTADDRREQ => |err| return errnoBug(err), // `connect` was never called.
- .DQUOT => return error.DiskQuota,
- .FBIG => return error.FileTooBig,
- .IO => return error.InputOutput,
- .NOSPC => return error.NoSpaceLeft,
- .PERM => return error.PermissionDenied,
- .PIPE => return error.BrokenPipe,
- .CONNRESET => |err| return errnoBug(err), // Not a socket handle.
- .BUSY => return error.DeviceBusy,
- .TXTBSY => return error.FileBusy,
- .NXIO => return error.Unseekable,
- .SPIPE => return error.Unseekable,
- .OVERFLOW => return error.Unseekable,
- else => |err| return posix.unexpectedErrno(err),
- }
- },
+ .INVAL => |err| return syscall.errnoBug(err),
+ .FAULT => |err| return syscall.errnoBug(err),
+ .DESTADDRREQ => |err| return syscall.errnoBug(err), // `connect` was never called.
+ .CONNRESET => |err| return syscall.errnoBug(err), // Not a socket handle.
+ .BADF => |err| return syscall.errnoBug(err), // use after free
+ .AGAIN => return syscall.fail(error.WouldBlock),
+ .DQUOT => return syscall.fail(error.DiskQuota),
+ .FBIG => return syscall.fail(error.FileTooBig),
+ .IO => return syscall.fail(error.InputOutput),
+ .NOSPC => return syscall.fail(error.NoSpaceLeft),
+ .PERM => return syscall.fail(error.PermissionDenied),
+ .PIPE => return syscall.fail(error.BrokenPipe),
+ .BUSY => return syscall.fail(error.DeviceBusy),
+ .TXTBSY => return syscall.fail(error.FileBusy),
+ .NXIO => return syscall.fail(error.Unseekable),
+ .SPIPE => return syscall.fail(error.Unseekable),
+ .OVERFLOW => return syscall.fail(error.Unseekable),
+ else => |err| return syscall.unexpectedErrno(err),
}
}
}
@@ -16107,3 +16119,381 @@ pub fn chdir(dir_path: []const u8) ChdirError!void {
else => |err| return syscall.unexpectedErrno(err),
};
}
+
+fn fileMemoryMapCreate(
+ userdata: ?*anyopaque,
+ file: File,
+ options: File.MemoryMap.CreateOptions,
+) File.MemoryMap.CreateError!File.MemoryMap {
+ const t: *Threaded = @ptrCast(@alignCast(userdata));
+ const offset = options.offset;
+
+ const page_size = std.heap.pageSize();
+ const aligned_len: usize = options.len.?; // TODO query if necessary
+
+ if (createFileMap(file, options.protection, offset, options.populate, aligned_len)) |result| {
+ return result;
+ } else |err| switch (err) {
+ error.Unseekable, error.Canceled => |e| return e,
+ else => {
+ if (builtin.mode == .Debug)
+ std.log.warn("memory mapping failed with {t}, falling back to file operations", .{err});
+ },
+ }
+
+ const gpa = t.allocator;
+ const alignment: Alignment = .fromByteUnits(page_size);
+ const memory = m: {
+ const ptr = gpa.rawAlloc(aligned_len, alignment, @returnAddress()) orelse
+ return error.OutOfMemory;
+ break :m ptr[0..aligned_len];
+ };
+ errdefer gpa.rawFree(memory, alignment, @returnAddress());
+
+ // If the mapping does not have read permissions, no need to populate the contents.
+ if (options.protection.read) try mmSyncRead(file, memory, offset);
+
+ return .{
+ .file = file,
+ .offset = offset,
+ .memory = memory,
+ .section = null,
+ };
+}
+
+const CreateFileMapError = error{
+ /// MaximumSize is greater than the system-defined maximum for sections, or
+ /// greater than the specified file and the section is not writable.
+ SectionOversize,
+ /// A file descriptor refers to a non-regular file. Or a file mapping was requested,
+ /// but the file descriptor is not open for reading. Or `MAP.SHARED` was requested
+ /// and `PROT_WRITE` is set, but the file descriptor is not open in `RDWR` mode.
+ /// Or `PROT_WRITE` is set, but the file is append-only.
+ AccessDenied,
+ /// The `prot` argument asks for `PROT_EXEC` but the mapped area belongs to a file on
+ /// a filesystem that was mounted no-exec.
+ PermissionDenied,
+ FileBusy,
+ LockedMemoryLimitExceeded,
+ OperationUnsupported,
+ ProcessFdQuotaExceeded,
+ SystemFdQuotaExceeded,
+ OutOfMemory,
+ MappingAlreadyExists,
+ Unseekable,
+} || Io.Cancelable || Io.UnexpectedError;
+
+fn createFileMap(
+ file: File,
+ protection: std.process.MemoryProtection,
+ offset: usize,
+ populate: bool,
+ aligned_len: usize,
+) CreateFileMapError!File.MemoryMap {
+ if (is_windows) {
+ try Thread.checkCancel();
+
+ var section = windows.INVALID_HANDLE_VALUE;
+ switch (windows.ntdll.NtCreateSection(
+ §ion,
+ .{
+ .SPECIFIC = .{ .SECTION = .{
+ .QUERY = true,
+ .MAP_WRITE = protection.write,
+ .MAP_READ = protection.read,
+ .MAP_EXECUTE = protection.execute,
+ .EXTEND_SIZE = true,
+ } },
+ .STANDARD = .{ .RIGHTS = .REQUIRED },
+ },
+ null,
+ @constCast(&@as(i64, @intCast(aligned_len))),
+ .{ .READWRITE = true },
+ .{ .COMMIT = populate },
+ file.handle,
+ )) {
+ .SUCCESS => {},
+ .FILE_LOCK_CONFLICT => return error.FileLocked,
+ .INVALID_FILE_FOR_SECTION => return error.OperationUnsupported,
+ else => |status| return windows.unexpectedStatus(status),
+ }
+ const current_process: windows.HANDLE = @ptrFromInt(@as(usize, @bitCast(@as(isize, -1))));
+ var contents_ptr: ?[*]u8 = null;
+ var contents_len = aligned_len;
+ switch (windows.ntdll.NtMapViewOfSection(
+ section,
+ current_process,
+ @ptrCast(&contents_ptr),
+ null,
+ 0,
+ null,
+ &contents_len,
+ .Unmap,
+ .{},
+ .{ .READWRITE = true },
+ )) {
+ .SUCCESS => {},
+ .CONFLICTING_ADDRESSES => return error.MappingAlreadyExists,
+ .SECTION_PROTECTION => return error.PermissionDenied,
+ else => |status| return windows.unexpectedStatus(status),
+ }
+ return .{
+ .file = file,
+ .offset = offset,
+ .memory = contents_ptr.?[0..contents_len],
+ .section = section,
+ };
+ } else if (have_mmap) {
+ const prot: posix.PROT = .{
+ .READ = protection.read,
+ .WRITE = protection.write,
+ .EXEC = protection.execute,
+ };
+ const flags: posix.MAP = .{
+ .TYPE = if (native_os == .linux) .SHARED_VALIDATE else .SHARED,
+ .POPULATE = populate,
+ };
+
+ const contents = while (true) {
+ const syscall: Syscall = try .start();
+ const casted_offset = std.math.cast(i64, offset) orelse return error.Unseekable;
+ const rc = mmap_sym(null, aligned_len, prot, flags, file.handle, casted_offset);
+ syscall.finish();
+ const err: posix.E = if (builtin.link_libc) e: {
+ if (rc != std.c.MAP_FAILED) {
+ break @as([*]u8, @ptrCast(@alignCast(rc)))[0..aligned_len];
+ }
+ break :e @enumFromInt(posix.system._errno().*);
+ } else e: {
+ const err = posix.errno(rc);
+ if (err == .SUCCESS) {
+ break @as([*]u8, @ptrFromInt(rc))[0..aligned_len];
+ }
+ break :e err;
+ };
+ switch (err) {
+ .SUCCESS => unreachable,
+ .INTR => continue,
+ .ACCES => return error.AccessDenied,
+ .AGAIN => return error.LockedMemoryLimitExceeded,
+ .EXIST => return error.MappingAlreadyExists,
+ .MFILE => return error.ProcessFdQuotaExceeded,
+ .NFILE => return error.SystemFdQuotaExceeded,
+ .NODEV => return error.OperationUnsupported,
+ .NOMEM => return error.OutOfMemory,
+ .PERM => return error.PermissionDenied,
+ .TXTBSY => return error.FileBusy,
+ .OVERFLOW => return error.Unseekable,
+ .BADF => return errnoBug(err), // Always a race condition.
+ .INVAL => return errnoBug(err), // Invalid parameters to mmap()
+ else => return posix.unexpectedErrno(err),
+ }
+ };
+ return .{
+ .file = file,
+ .offset = offset,
+ .memory = contents,
+ .section = {},
+ };
+ }
+
+ return error.OperationUnsupported;
+}
+
+fn fileMemoryMapDestroy(userdata: ?*anyopaque, mm: *File.MemoryMap) void {
+ const t: *Threaded = @ptrCast(@alignCast(userdata));
+ const memory = mm.memory;
+ if (mm.section) |section| {
+ if (is_windows) {
+ const current_process: windows.HANDLE = @ptrFromInt(@as(usize, @bitCast(@as(isize, -1))));
+ _ = windows.ntdll.NtUnmapViewOfSection(current_process, memory.ptr);
+ windows.CloseHandle(section);
+ } 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 {
+ const gpa = t.allocator;
+ const page_size = std.heap.pageSize();
+ const alignment: Alignment = .fromByteUnits(page_size);
+ gpa.rawFree(memory, alignment, @returnAddress());
+ }
+ mm.* = undefined;
+}
+
+fn fileMemoryMapSetLength(
+ userdata: ?*anyopaque,
+ mm: *File.MemoryMap,
+ new_len: usize,
+) File.MemoryMap.SetLengthError!void {
+ const t: *Threaded = @ptrCast(@alignCast(userdata));
+ if (mm.section) |section| switch (native_os) {
+ .windows => {
+ _ = section;
+ @panic("TODO");
+ },
+ .wasi => unreachable,
+ else => {
+ const flags: posix.MREMAP = .{ .MAYMOVE = true };
+ const addr_hint: ?[*]const u8 = null;
+ const new_memory = while (true) {
+ const syscall: Syscall = try .start();
+ const rc = posix.system.mremap(mm.memory.ptr, mm.memory.len, new_len, flags, addr_hint);
+ syscall.finish();
+ const err: posix.E = if (builtin.link_libc) e: {
+ if (rc != std.c.MAP_FAILED) break @as([*]u8, @ptrCast(@alignCast(rc)))[0..new_len];
+ break :e @enumFromInt(posix.system._errno().*);
+ } else e: {
+ const err = posix.errno(rc);
+ if (err == .SUCCESS) break @as([*]u8, @ptrFromInt(rc))[0..new_len];
+ break :e err;
+ };
+ switch (err) {
+ .SUCCESS => unreachable,
+ .INTR => continue,
+ .AGAIN => return error.LockedMemoryLimitExceeded,
+ .NOMEM => return error.OutOfMemory,
+ .INVAL => return errnoBug(err),
+ .FAULT => return errnoBug(err),
+ else => return posix.unexpectedErrno(err),
+ }
+ };
+ mm.memory = new_memory;
+ },
+ } else {
+ const gpa = t.allocator;
+ const page_size = std.heap.pageSize();
+ const alignment: Alignment = .fromByteUnits(page_size);
+ if (gpa.rawRemap(mm.memory, alignment, new_len, @returnAddress())) |new_ptr| {
+ mm.memory = new_ptr[0..new_len];
+ } else {
+ const new_ptr = gpa.rawAlloc(new_len, alignment, @returnAddress()) orelse
+ return error.OutOfMemory;
+ const copy_len = @min(new_len, mm.memory.len);
+ @memcpy(new_ptr[0..copy_len], mm.memory[0..copy_len]);
+ mm.memory = new_ptr[0..new_len];
+ }
+ }
+}
+
+fn fileMemoryMapRead(userdata: ?*anyopaque, mm: *File.MemoryMap) File.ReadPositionalError!void {
+ const t: *Threaded = @ptrCast(@alignCast(userdata));
+ _ = t;
+ if (mm.section != null) return;
+ return mmSyncRead(mm.file, mm.memory, mm.offset);
+}
+
+fn fileMemoryMapWrite(userdata: ?*anyopaque, mm: *File.MemoryMap) File.WritePositionalError!void {
+ const t: *Threaded = @ptrCast(@alignCast(userdata));
+ _ = t;
+ if (mm.section != null) return;
+ return mmSyncWrite(mm.file, mm.memory, mm.offset);
+}
+
+fn mmSyncRead(file: File, memory: []u8, offset: u64) File.ReadPositionalError!void {
+ switch (native_os) {
+ .windows => @panic("TODO"),
+ .wasi => @panic("TODO"),
+ else => {
+ var i: usize = 0;
+ const syscall: Syscall = try .start();
+ while (true) {
+ const buf = memory[i..];
+ if (buf.len == 0) {
+ syscall.finish();
+ break;
+ }
+ const rc = pread_sym(file.handle, buf.ptr, buf.len, @intCast(offset + i));
+ switch (posix.errno(rc)) {
+ .SUCCESS => {
+ const n: usize = @intCast(rc);
+ if (n == 0) {
+ syscall.finish();
+ @memset(memory[i..], 0);
+ break;
+ }
+ i += n;
+ try syscall.checkCancel();
+ continue;
+ },
+ .INTR, .TIMEDOUT => {
+ try syscall.checkCancel();
+ continue;
+ },
+ .NXIO => return syscall.fail(error.Unseekable),
+ .SPIPE => return syscall.fail(error.Unseekable),
+ .OVERFLOW => return syscall.fail(error.Unseekable),
+ .NOBUFS => return syscall.fail(error.SystemResources),
+ .NOMEM => return syscall.fail(error.SystemResources),
+ .AGAIN => return syscall.fail(error.WouldBlock),
+ .IO => return syscall.fail(error.InputOutput),
+ .ISDIR => return syscall.fail(error.IsDir),
+ .NOTCONN => |err| return syscall.errnoBug(err), // not a socket
+ .CONNRESET => |err| return syscall.errnoBug(err), // not a socket
+ .INVAL => |err| return syscall.errnoBug(err),
+ .FAULT => |err| return syscall.errnoBug(err),
+ .BADF => |err| {
+ syscall.finish();
+ if (native_os == .wasi) return error.IsDir; // File operation on directory.
+ return errnoBug(err); // File descriptor used after closed.
+ },
+ else => |err| return syscall.unexpectedErrno(err),
+ }
+ }
+ },
+ }
+}
+
+fn mmSyncWrite(file: File, memory: []u8, offset: u64) File.WritePositionalError!void {
+ switch (native_os) {
+ .windows => @panic("TODO"),
+ .wasi => @panic("TODO"),
+ else => {
+ var i: usize = 0;
+ const syscall: Syscall = try .start();
+ while (true) {
+ const buf = memory[i..];
+ if (buf.len == 0) {
+ syscall.finish();
+ break;
+ }
+ const rc = pwrite_sym(file.handle, buf.ptr, buf.len, @intCast(offset));
+ switch (posix.errno(rc)) {
+ .SUCCESS => {
+ const n: usize = @bitCast(rc);
+ i += n;
+ try syscall.checkCancel();
+ continue;
+ },
+ .INTR => {
+ try syscall.checkCancel();
+ continue;
+ },
+ .INVAL => |err| return syscall.errnoBug(err),
+ .FAULT => |err| return syscall.errnoBug(err),
+ .DESTADDRREQ => |err| return syscall.errnoBug(err), // not a socket
+ .CONNRESET => |err| return syscall.errnoBug(err), // not a socket
+ .BADF => |err| return syscall.errnoBug(err), // use after free
+ .AGAIN => return syscall.fail(error.WouldBlock),
+ .DQUOT => return syscall.fail(error.DiskQuota),
+ .FBIG => return syscall.fail(error.FileTooBig),
+ .IO => return syscall.fail(error.InputOutput),
+ .NOSPC => return syscall.fail(error.NoSpaceLeft),
+ .PERM => return syscall.fail(error.PermissionDenied),
+ .PIPE => return syscall.fail(error.BrokenPipe),
+ .BUSY => return syscall.fail(error.DeviceBusy),
+ .TXTBSY => return syscall.fail(error.FileBusy),
+ .NXIO => return syscall.fail(error.Unseekable),
+ .SPIPE => return syscall.fail(error.Unseekable),
+ .OVERFLOW => return syscall.fail(error.Unseekable),
+ else => |err| return syscall.unexpectedErrno(err),
+ }
+ }
+ },
+ }
+}
diff --git a/lib/std/posix.zig b/lib/std/posix.zig
@@ -433,7 +433,7 @@ pub fn read(fd: fd_t, buf: []u8) ReadError!usize {
.FAULT => unreachable,
.AGAIN => return error.WouldBlock,
.CANCELED => return error.Canceled,
- .BADF => return error.NotOpenForReading, // Can be a race condition.
+ .BADF => return error.Unexpected, // use after free
.IO => return error.InputOutput,
.ISDIR => return error.IsDir,
.NOBUFS => return error.SystemResources,
diff --git a/lib/std/process.zig b/lib/std/process.zig
@@ -1018,22 +1018,19 @@ pub const ProtectMemoryError = error{
OutOfMemory,
} || Io.UnexpectedError;
-pub const ProtectMemoryOptions = packed struct(u3) {
+pub const MemoryProtection = packed struct(u3) {
read: bool = false,
write: bool = false,
execute: bool = false,
};
-pub fn protectMemory(
- memory: []align(std.heap.page_size_min) u8,
- options: ProtectMemoryOptions,
-) ProtectMemoryError!void {
+pub fn protectMemory(memory: []align(std.heap.page_size_min) u8, protection: MemoryProtection) ProtectMemoryError!void {
if (native_os == .windows) {
var addr = memory.ptr; // ntdll takes an extra level of indirection here
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(options))) {
+ const new: windows.PAGE = switch (@as(u3, @bitCast(protection))) {
0b000 => .{ .NOACCESS = true },
0b001 => .{ .READONLY = true },
0b010 => return error.AccessDenied, // +w -r not allowed
@@ -1050,9 +1047,9 @@ pub fn protectMemory(
}
} else if (posix.PROT != void) {
const flags: posix.PROT = .{
- .READ = options.read,
- .WRITE = options.write,
- .EXEC = options.execute,
+ .READ = protection.read,
+ .WRITE = protection.write,
+ .EXEC = protection.execute,
};
switch (posix.errno(posix.system.mprotect(memory.ptr, memory.len, flags))) {
.SUCCESS => return,
diff --git a/lib/std/zig/system.zig b/lib/std/zig/system.zig
@@ -421,7 +421,6 @@ pub fn resolveTargetQuery(io: Io, query: Target.Query) DetectError!Target {
error.BrokenPipe => return error.Unexpected,
error.ConnectionResetByPeer => return error.Unexpected,
error.Timeout => return error.Unexpected,
- error.NotOpenForReading => return error.Unexpected,
error.SocketUnconnected => return error.Unexpected,
error.AccessDenied,
diff --git a/src/link/MappedFile.zig b/src/link/MappedFile.zig
@@ -1,4 +1,3 @@
-/// TODO add a mapped file abstraction to std.Io
const MappedFile = @This();
const builtin = @import("builtin");