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
This commit is contained in:
Andrew Kelley 2026-01-14 14:41:31 -08:00
parent 63f345a75a
commit 5a7dc4b0fa
9 changed files with 597 additions and 83 deletions

View file

@ -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,

View file

@ -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;
}

View file

@ -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);
}

View file

@ -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.

View file

@ -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(
&section,
.{
.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),
}
}
},
}
}

View file

@ -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,

View file

@ -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,

View file

@ -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,

View file

@ -1,4 +1,3 @@
/// TODO add a mapped file abstraction to std.Io
const MappedFile = @This();
const builtin = @import("builtin");