mirror of
https://codeberg.org/ziglang/zig.git
synced 2026-03-08 01:04:43 +01:00
Merge pull request 'std.Progress: implement inter-process progress reporting for windows' (#31113) from threaded-win-cleanup into master
Reviewed-on: https://codeberg.org/ziglang/zig/pulls/31113 Reviewed-by: Andrew Kelley <andrew@ziglang.org>
This commit is contained in:
commit
fcef9905ae
17 changed files with 1447 additions and 1559 deletions
|
|
@ -1498,6 +1498,7 @@ fn generateLangRef(b: *std.Build) std.Build.LazyPath {
|
|||
defer dir.close(io);
|
||||
|
||||
var wf = b.addWriteFiles();
|
||||
b.step("test-docs", "Test code snippets from the docs").dependOn(&wf.step);
|
||||
|
||||
var it = dir.iterateAssumeFirstIteration();
|
||||
while (it.next(io) catch @panic("failed to read dir")) |entry| {
|
||||
|
|
|
|||
|
|
@ -386,10 +386,14 @@ pub const ZigProcess = struct {
|
|||
child: std.process.Child,
|
||||
multi_reader_buffer: Io.File.MultiReader.Buffer(2),
|
||||
multi_reader: Io.File.MultiReader,
|
||||
progress_ipc_fd: if (std.Progress.have_ipc) ?std.posix.fd_t else void,
|
||||
progress_ipc_index: ?if (std.Progress.have_ipc) std.Progress.Ipc.Index else noreturn,
|
||||
|
||||
pub const StreamEnum = enum { stdout, stderr };
|
||||
|
||||
pub fn saveState(zp: *ZigProcess, prog_node: std.Progress.Node) void {
|
||||
zp.progress_ipc_index = if (std.Progress.have_ipc) prog_node.takeIpcIndex() else null;
|
||||
}
|
||||
|
||||
pub fn deinit(zp: *ZigProcess, io: Io) void {
|
||||
zp.child.kill(io);
|
||||
zp.multi_reader.deinit();
|
||||
|
|
@ -417,7 +421,14 @@ pub fn evalZigProcess(
|
|||
|
||||
if (s.getZigProcess()) |zp| update: {
|
||||
assert(watch);
|
||||
if (std.Progress.have_ipc) if (zp.progress_ipc_fd) |fd| prog_node.setIpcFd(fd);
|
||||
if (zp.progress_ipc_index) |ipc_index| prog_node.setIpcIndex(ipc_index);
|
||||
zp.progress_ipc_index = null;
|
||||
var exited = false;
|
||||
defer if (exited) {
|
||||
s.cast(Compile).?.zig_process = null;
|
||||
zp.deinit(io);
|
||||
gpa.destroy(zp);
|
||||
} else zp.saveState(prog_node);
|
||||
const result = zigProcessUpdate(s, zp, watch, web_server, gpa) catch |err| switch (err) {
|
||||
error.BrokenPipe, error.EndOfStream => |reason| {
|
||||
std.log.info("{s} restart required: {t}", .{ argv[0], reason });
|
||||
|
|
@ -426,7 +437,7 @@ pub fn evalZigProcess(
|
|||
return s.fail("unable to wait for {s}: {t}", .{ argv[0], e });
|
||||
};
|
||||
_ = term;
|
||||
s.clearZigProcess(gpa);
|
||||
exited = true;
|
||||
break :update;
|
||||
},
|
||||
else => |e| return e,
|
||||
|
|
@ -442,7 +453,7 @@ pub fn evalZigProcess(
|
|||
return s.fail("unable to wait for {s}: {t}", .{ argv[0], e });
|
||||
};
|
||||
s.result_peak_rss = zp.child.resource_usage_statistics.getMaxRss() orelse 0;
|
||||
s.clearZigProcess(gpa);
|
||||
exited = true;
|
||||
try handleChildProcessTerm(s, term);
|
||||
return error.MakeFailed;
|
||||
}
|
||||
|
|
@ -467,19 +478,16 @@ pub fn evalZigProcess(
|
|||
.progress_node = prog_node,
|
||||
}) catch |err| return s.fail("failed to spawn zig compiler {s}: {t}", .{ argv[0], err });
|
||||
|
||||
zp.* = .{
|
||||
.child = zp.child,
|
||||
.multi_reader_buffer = undefined,
|
||||
.multi_reader = undefined,
|
||||
.progress_ipc_fd = if (std.Progress.have_ipc) prog_node.getIpcFd() else {},
|
||||
};
|
||||
zp.multi_reader.init(gpa, io, zp.multi_reader_buffer.toStreams(), &.{
|
||||
zp.child.stdout.?, zp.child.stderr.?,
|
||||
});
|
||||
if (watch) s.setZigProcess(zp);
|
||||
if (watch) s.cast(Compile).?.zig_process = zp;
|
||||
defer if (!watch) zp.deinit(io);
|
||||
|
||||
const result = try zigProcessUpdate(s, zp, watch, web_server, gpa);
|
||||
const result = result: {
|
||||
defer if (watch) zp.saveState(prog_node);
|
||||
break :result try zigProcessUpdate(s, zp, watch, web_server, gpa);
|
||||
};
|
||||
|
||||
if (!watch) {
|
||||
// Send EOF to stdin.
|
||||
|
|
@ -670,26 +678,6 @@ pub fn getZigProcess(s: *Step) ?*ZigProcess {
|
|||
};
|
||||
}
|
||||
|
||||
fn setZigProcess(s: *Step, zp: *ZigProcess) void {
|
||||
switch (s.id) {
|
||||
.compile => s.cast(Compile).?.zig_process = zp,
|
||||
else => unreachable,
|
||||
}
|
||||
}
|
||||
|
||||
fn clearZigProcess(s: *Step, gpa: Allocator) void {
|
||||
switch (s.id) {
|
||||
.compile => {
|
||||
const compile = s.cast(Compile).?;
|
||||
if (compile.zig_process) |zp| {
|
||||
gpa.destroy(zp);
|
||||
compile.zig_process = null;
|
||||
}
|
||||
},
|
||||
else => unreachable,
|
||||
}
|
||||
}
|
||||
|
||||
fn sendMessage(io: Io, file: Io.File, tag: std.zig.Client.Message.Tag) !void {
|
||||
const header: std.zig.Client.Message.Header = .{
|
||||
.tag = tag,
|
||||
|
|
|
|||
|
|
@ -366,15 +366,7 @@ const Os = switch (builtin.os.tag) {
|
|||
.MaximumLength = @intCast(path_len_bytes),
|
||||
.Buffer = @constCast(sub_path_w.span().ptr),
|
||||
};
|
||||
var attr = windows.OBJECT_ATTRIBUTES{
|
||||
.Length = @sizeOf(windows.OBJECT_ATTRIBUTES),
|
||||
.RootDirectory = if (std.fs.path.isAbsoluteWindowsW(sub_path_w.span())) null else root_fd,
|
||||
.Attributes = .{},
|
||||
.ObjectName = &nt_name,
|
||||
.SecurityDescriptor = null,
|
||||
.SecurityQualityOfService = null,
|
||||
};
|
||||
var io: windows.IO_STATUS_BLOCK = undefined;
|
||||
var iosb: windows.IO_STATUS_BLOCK = undefined;
|
||||
|
||||
switch (windows.ntdll.NtCreateFile(
|
||||
&dir_handle,
|
||||
|
|
@ -385,14 +377,18 @@ const Os = switch (builtin.os.tag) {
|
|||
.STANDARD = .{ .SYNCHRONIZE = true },
|
||||
.GENERIC = .{ .READ = true },
|
||||
},
|
||||
&attr,
|
||||
&io,
|
||||
&.{
|
||||
.RootDirectory = if (std.fs.path.isAbsoluteWindowsW(sub_path_w.span())) null else root_fd,
|
||||
.ObjectName = &nt_name,
|
||||
},
|
||||
&iosb,
|
||||
null,
|
||||
.{},
|
||||
.VALID_FLAGS,
|
||||
.OPEN,
|
||||
.{
|
||||
.DIRECTORY_FILE = true,
|
||||
.IO = .ASYNCHRONOUS,
|
||||
.OPEN_FOR_BACKUP_INTENT = true,
|
||||
},
|
||||
null,
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -181,13 +181,17 @@ test "cancel blocked read from pipe" {
|
|||
var write_end: Io.File = undefined;
|
||||
switch (builtin.target.os.tag) {
|
||||
.wasi => return error.SkipZigTest,
|
||||
.windows => try std.os.windows.CreatePipe(&read_end.handle, &write_end.handle, &.{
|
||||
.nLength = @sizeOf(std.os.windows.SECURITY_ATTRIBUTES),
|
||||
.lpSecurityDescriptor = null,
|
||||
.bInheritHandle = std.os.windows.FALSE,
|
||||
}),
|
||||
.windows => {
|
||||
const pipe = try threaded.windowsCreatePipe(.{
|
||||
.server = .{ .mode = .{ .IO = .SYNCHRONOUS_NONALERT } },
|
||||
.client = .{ .mode = .{ .IO = .SYNCHRONOUS_NONALERT } },
|
||||
.inbound = true,
|
||||
});
|
||||
read_end = .{ .handle = pipe[0], .flags = .{ .nonblocking = false } };
|
||||
write_end = .{ .handle = pipe[1], .flags = .{ .nonblocking = false } };
|
||||
},
|
||||
else => {
|
||||
const pipe = try std.Io.Threaded.pipe2(.{});
|
||||
const pipe = try std.Io.Threaded.pipe2(.{ .CLOEXEC = true });
|
||||
read_end = .{ .handle = pipe[0], .flags = .{ .nonblocking = false } };
|
||||
write_end = .{ .handle = pipe[1], .flags = .{ .nonblocking = false } };
|
||||
},
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -598,7 +598,11 @@ const WindowsThreadImpl = struct {
|
|||
}
|
||||
|
||||
fn join(self: Impl) void {
|
||||
windows.WaitForSingleObjectEx(self.thread.thread_handle, windows.INFINITE, false) catch unreachable;
|
||||
const infinite_timeout: windows.LARGE_INTEGER = std.math.minInt(windows.LARGE_INTEGER);
|
||||
switch (windows.ntdll.NtWaitForSingleObject(self.thread.thread_handle, windows.FALSE, &infinite_timeout)) {
|
||||
windows.NTSTATUS.WAIT_0 => {},
|
||||
else => |status| windows.unexpectedStatus(status) catch unreachable,
|
||||
}
|
||||
windows.CloseHandle(self.thread.thread_handle);
|
||||
assert(self.thread.completion.load(.seq_cst) == .completed);
|
||||
self.thread.free();
|
||||
|
|
|
|||
|
|
@ -452,12 +452,23 @@ pub fn dupe(allocator: Allocator, comptime T: type, m: []const T) Error![]T {
|
|||
return new_buf;
|
||||
}
|
||||
|
||||
/// Deprecated in favor of `dupeSentinel`
|
||||
/// Copies `m` to newly allocated memory, with a null-terminated element. Caller owns the memory.
|
||||
pub fn dupeZ(allocator: Allocator, comptime T: type, m: []const T) Error![:0]T {
|
||||
return allocator.dupeSentinel(T, m, 0);
|
||||
}
|
||||
|
||||
/// Copies `m` to newly allocated memory, with a null-terminated element. Caller owns the memory.
|
||||
pub fn dupeSentinel(
|
||||
allocator: Allocator,
|
||||
comptime T: type,
|
||||
m: []const T,
|
||||
comptime sentinel: T,
|
||||
) Error![:sentinel]T {
|
||||
const new_buf = try allocator.alloc(T, m.len + 1);
|
||||
@memcpy(new_buf[0..m.len], m);
|
||||
new_buf[m.len] = 0;
|
||||
return new_buf[0..m.len :0];
|
||||
new_buf[m.len] = sentinel;
|
||||
return new_buf[0..m.len :sentinel];
|
||||
}
|
||||
|
||||
/// An allocator that always fails to allocate.
|
||||
|
|
|
|||
|
|
@ -1848,6 +1848,24 @@ pub const F = struct {
|
|||
pub const RDLCK = if (is_sparc) 1 else 0;
|
||||
pub const WRLCK = if (is_sparc) 2 else 1;
|
||||
pub const UNLCK = if (is_sparc) 3 else 2;
|
||||
|
||||
pub const LINUX_SPECIFIC_BASE = 1024;
|
||||
|
||||
pub const SETLEASE = LINUX_SPECIFIC_BASE + 0;
|
||||
pub const GETLEASE = LINUX_SPECIFIC_BASE + 1;
|
||||
pub const NOTIFY = LINUX_SPECIFIC_BASE + 2;
|
||||
pub const DUPFD_QUERY = LINUX_SPECIFIC_BASE + 3;
|
||||
pub const CREATED_QUERY = LINUX_SPECIFIC_BASE + 4;
|
||||
pub const CANCELLK = LINUX_SPECIFIC_BASE + 5;
|
||||
pub const DUPFD_CLOEXEC = LINUX_SPECIFIC_BASE + 6;
|
||||
pub const SETPIPE_SZ = LINUX_SPECIFIC_BASE + 7;
|
||||
pub const GETPIPE_SZ = LINUX_SPECIFIC_BASE + 8;
|
||||
pub const ADD_SEALS = LINUX_SPECIFIC_BASE + 9;
|
||||
pub const GET_SEALS = LINUX_SPECIFIC_BASE + 10;
|
||||
pub const GET_RW_HINT = LINUX_SPECIFIC_BASE + 11;
|
||||
pub const SET_RW_HINT = LINUX_SPECIFIC_BASE + 12;
|
||||
pub const GET_FILE_RW_HINT = LINUX_SPECIFIC_BASE + 13;
|
||||
pub const SET_FILE_RW_HINT = LINUX_SPECIFIC_BASE + 14;
|
||||
};
|
||||
|
||||
pub const F_OWNER = enum(i32) {
|
||||
|
|
|
|||
|
|
@ -521,7 +521,7 @@ pub const FILE = struct {
|
|||
_,
|
||||
|
||||
pub const VALID_FLAGS: @This() = @enumFromInt(0b11);
|
||||
} = .ASYNCHRONOUS,
|
||||
},
|
||||
/// The file being opened must not be a directory file or this call
|
||||
/// fails. The file object being opened can represent a data file, a
|
||||
/// logical, virtual, or physical device, or a volume.
|
||||
|
|
@ -2324,12 +2324,12 @@ pub fn GetProcessHeap() ?*HEAP {
|
|||
// ref: um/winternl.h
|
||||
|
||||
pub const OBJECT_ATTRIBUTES = extern struct {
|
||||
Length: ULONG,
|
||||
RootDirectory: ?HANDLE,
|
||||
ObjectName: ?*UNICODE_STRING,
|
||||
Attributes: ATTRIBUTES,
|
||||
SecurityDescriptor: ?*anyopaque,
|
||||
SecurityQualityOfService: ?*anyopaque,
|
||||
Length: ULONG = @sizeOf(OBJECT_ATTRIBUTES),
|
||||
RootDirectory: ?HANDLE = null,
|
||||
ObjectName: ?*UNICODE_STRING = @constCast(&UNICODE_STRING.empty),
|
||||
Attributes: ATTRIBUTES = .{},
|
||||
SecurityDescriptor: ?*anyopaque = null,
|
||||
SecurityQualityOfService: ?*anyopaque = null,
|
||||
|
||||
// Valid values for the Attributes field
|
||||
pub const ATTRIBUTES = packed struct(ULONG) {
|
||||
|
|
@ -2420,14 +2420,10 @@ pub fn OpenFile(sub_path_w: []const u16, options: OpenFileOptions) OpenError!HAN
|
|||
.Buffer = @constCast(sub_path_w.ptr),
|
||||
};
|
||||
const attr: OBJECT_ATTRIBUTES = .{
|
||||
.Length = @sizeOf(OBJECT_ATTRIBUTES),
|
||||
.RootDirectory = if (std.fs.path.isAbsoluteWindowsWtf16(sub_path_w)) null else options.dir,
|
||||
.Attributes = .{
|
||||
.INHERIT = if (options.sa) |sa| sa.bInheritHandle != FALSE else false,
|
||||
},
|
||||
.Attributes = .{ .INHERIT = if (options.sa) |sa| sa.bInheritHandle != FALSE else false },
|
||||
.ObjectName = &nt_name,
|
||||
.SecurityDescriptor = if (options.sa) |ptr| ptr.lpSecurityDescriptor else null,
|
||||
.SecurityQualityOfService = null,
|
||||
};
|
||||
var io: IO_STATUS_BLOCK = undefined;
|
||||
while (true) {
|
||||
|
|
@ -2475,7 +2471,8 @@ pub fn OpenFile(sub_path_w: []const u16, options: OpenFileOptions) OpenError!HAN
|
|||
// call has failed. There is not really a sane way to handle
|
||||
// this other than retrying the creation after the OS finishes
|
||||
// the deletion.
|
||||
_ = kernel32.SleepEx(1, TRUE);
|
||||
const delay_one_ms: LARGE_INTEGER = -(std.time.ns_per_ms / 100);
|
||||
_ = ntdll.NtDelayExecution(TRUE, &delay_one_ms);
|
||||
continue;
|
||||
},
|
||||
.VIRUS_INFECTED, .VIRUS_DELETED => return error.AntivirusInterference,
|
||||
|
|
@ -2506,151 +2503,6 @@ pub fn GetCurrentThreadId() DWORD {
|
|||
pub fn GetLastError() Win32Error {
|
||||
return @enumFromInt(teb().LastErrorValue);
|
||||
}
|
||||
|
||||
pub const CreatePipeError = error{ Unexpected, SystemResources };
|
||||
|
||||
var npfs: ?HANDLE = null;
|
||||
|
||||
/// A Zig wrapper around `NtCreateNamedPipeFile` and `NtCreateFile` syscalls.
|
||||
/// It implements similar behavior to `CreatePipe` and is meant to serve
|
||||
/// as a direct substitute for that call.
|
||||
pub fn CreatePipe(rd: *HANDLE, wr: *HANDLE, sattr: *const SECURITY_ATTRIBUTES) CreatePipeError!void {
|
||||
// Up to NT 5.2 (Windows XP/Server 2003), `CreatePipe` would generate a pipe similar to:
|
||||
//
|
||||
// \??\pipe\Win32Pipes.{pid}.{count}
|
||||
//
|
||||
// where `pid` is the process id and count is a incrementing counter.
|
||||
// The implementation was changed after NT 6.0 (Vista) to open a handle to the Named Pipe File System
|
||||
// and use that as the root directory for `NtCreateNamedPipeFile`.
|
||||
// This object is visible under the NPFS but has no filename attached to it.
|
||||
//
|
||||
// This implementation replicates how `CreatePipe` works in modern Windows versions.
|
||||
const opt_dev_handle = @atomicLoad(?HANDLE, &npfs, .seq_cst);
|
||||
const dev_handle = opt_dev_handle orelse blk: {
|
||||
const str = std.unicode.utf8ToUtf16LeStringLiteral("\\Device\\NamedPipe\\");
|
||||
const len: u16 = @truncate(str.len * @sizeOf(u16));
|
||||
const name: UNICODE_STRING = .{
|
||||
.Length = len,
|
||||
.MaximumLength = len,
|
||||
.Buffer = @ptrCast(@constCast(str)),
|
||||
};
|
||||
const attrs: OBJECT_ATTRIBUTES = .{
|
||||
.ObjectName = @constCast(&name),
|
||||
.Length = @sizeOf(OBJECT_ATTRIBUTES),
|
||||
.RootDirectory = null,
|
||||
.Attributes = .{},
|
||||
.SecurityDescriptor = null,
|
||||
.SecurityQualityOfService = null,
|
||||
};
|
||||
|
||||
var iosb: IO_STATUS_BLOCK = undefined;
|
||||
var handle: HANDLE = undefined;
|
||||
switch (ntdll.NtCreateFile(
|
||||
&handle,
|
||||
.{
|
||||
.STANDARD = .{ .SYNCHRONIZE = true },
|
||||
.GENERIC = .{ .READ = true },
|
||||
},
|
||||
@constCast(&attrs),
|
||||
&iosb,
|
||||
null,
|
||||
.{},
|
||||
.VALID_FLAGS,
|
||||
.OPEN,
|
||||
.{ .IO = .SYNCHRONOUS_NONALERT },
|
||||
null,
|
||||
0,
|
||||
)) {
|
||||
.SUCCESS => {},
|
||||
// Judging from the ReactOS sources this is technically possible.
|
||||
.INSUFFICIENT_RESOURCES => return error.SystemResources,
|
||||
.INVALID_PARAMETER => unreachable,
|
||||
else => |e| return unexpectedStatus(e),
|
||||
}
|
||||
if (@cmpxchgStrong(?HANDLE, &npfs, null, handle, .seq_cst, .seq_cst)) |xchg| {
|
||||
CloseHandle(handle);
|
||||
break :blk xchg.?;
|
||||
} else break :blk handle;
|
||||
};
|
||||
|
||||
const name: UNICODE_STRING = .{ .Buffer = null, .Length = 0, .MaximumLength = 0 };
|
||||
var attrs: OBJECT_ATTRIBUTES = .{
|
||||
.ObjectName = @constCast(&name),
|
||||
.Length = @sizeOf(OBJECT_ATTRIBUTES),
|
||||
.RootDirectory = dev_handle,
|
||||
.Attributes = .{ .INHERIT = sattr.bInheritHandle != FALSE },
|
||||
.SecurityDescriptor = sattr.lpSecurityDescriptor,
|
||||
.SecurityQualityOfService = null,
|
||||
};
|
||||
|
||||
// 120 second relative timeout in 100ns units.
|
||||
const default_timeout: LARGE_INTEGER = (-120 * std.time.ns_per_s) / 100;
|
||||
var iosb: IO_STATUS_BLOCK = undefined;
|
||||
var read: HANDLE = undefined;
|
||||
switch (ntdll.NtCreateNamedPipeFile(
|
||||
&read,
|
||||
.{
|
||||
.SPECIFIC = .{ .FILE_PIPE = .{
|
||||
.WRITE_ATTRIBUTES = true,
|
||||
} },
|
||||
.STANDARD = .{ .SYNCHRONIZE = true },
|
||||
.GENERIC = .{ .READ = true },
|
||||
},
|
||||
&attrs,
|
||||
&iosb,
|
||||
.{ .READ = true, .WRITE = true },
|
||||
.CREATE,
|
||||
.{ .IO = .SYNCHRONOUS_NONALERT },
|
||||
.{ .TYPE = .BYTE_STREAM },
|
||||
.{ .MODE = .BYTE_STREAM },
|
||||
.{ .OPERATION = .QUEUE },
|
||||
1,
|
||||
4096,
|
||||
4096,
|
||||
@constCast(&default_timeout),
|
||||
)) {
|
||||
.SUCCESS => {},
|
||||
.INVALID_PARAMETER => unreachable,
|
||||
.INSUFFICIENT_RESOURCES => return error.SystemResources,
|
||||
else => |e| return unexpectedStatus(e),
|
||||
}
|
||||
errdefer CloseHandle(read);
|
||||
|
||||
attrs.RootDirectory = read;
|
||||
|
||||
var write: HANDLE = undefined;
|
||||
switch (ntdll.NtCreateFile(
|
||||
&write,
|
||||
.{
|
||||
.SPECIFIC = .{ .FILE_PIPE = .{
|
||||
.READ_ATTRIBUTES = true,
|
||||
} },
|
||||
.STANDARD = .{ .SYNCHRONIZE = true },
|
||||
.GENERIC = .{ .WRITE = true },
|
||||
},
|
||||
&attrs,
|
||||
&iosb,
|
||||
null,
|
||||
.{},
|
||||
.VALID_FLAGS,
|
||||
.OPEN,
|
||||
.{
|
||||
.IO = .SYNCHRONOUS_NONALERT,
|
||||
.NON_DIRECTORY_FILE = true,
|
||||
},
|
||||
null,
|
||||
0,
|
||||
)) {
|
||||
.SUCCESS => {},
|
||||
.INVALID_PARAMETER => unreachable,
|
||||
.INSUFFICIENT_RESOURCES => return error.SystemResources,
|
||||
else => |e| return unexpectedStatus(e),
|
||||
}
|
||||
|
||||
rd.* = read;
|
||||
wr.* = write;
|
||||
}
|
||||
|
||||
/// A Zig wrapper around `NtDeviceIoControlFile` and `NtFsControlFile` syscalls.
|
||||
/// It implements similar behavior to `DeviceIoControl` and is meant to serve
|
||||
/// as a direct substitute for that call.
|
||||
|
|
@ -2707,66 +2559,6 @@ pub fn GetOverlappedResult(h: HANDLE, overlapped: *OVERLAPPED, wait: bool) !DWOR
|
|||
return bytes;
|
||||
}
|
||||
|
||||
pub const SetHandleInformationError = error{Unexpected};
|
||||
|
||||
pub fn SetHandleInformation(h: HANDLE, mask: DWORD, flags: DWORD) SetHandleInformationError!void {
|
||||
if (kernel32.SetHandleInformation(h, mask, flags) == 0) {
|
||||
switch (GetLastError()) {
|
||||
else => |err| return unexpectedError(err),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub const WaitForSingleObjectError = error{
|
||||
WaitAbandoned,
|
||||
WaitTimeOut,
|
||||
Unexpected,
|
||||
};
|
||||
|
||||
pub fn WaitForSingleObject(handle: HANDLE, milliseconds: DWORD) WaitForSingleObjectError!void {
|
||||
return WaitForSingleObjectEx(handle, milliseconds, false);
|
||||
}
|
||||
|
||||
pub fn WaitForSingleObjectEx(handle: HANDLE, milliseconds: DWORD, alertable: bool) WaitForSingleObjectError!void {
|
||||
switch (kernel32.WaitForSingleObjectEx(handle, milliseconds, @intFromBool(alertable))) {
|
||||
WAIT_ABANDONED => return error.WaitAbandoned,
|
||||
WAIT_OBJECT_0 => return,
|
||||
WAIT_TIMEOUT => return error.WaitTimeOut,
|
||||
WAIT_FAILED => switch (GetLastError()) {
|
||||
else => |err| return unexpectedError(err),
|
||||
},
|
||||
else => return error.Unexpected,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn WaitForMultipleObjectsEx(handles: []const HANDLE, waitAll: bool, milliseconds: DWORD, alertable: bool) !u32 {
|
||||
assert(handles.len > 0 and handles.len <= MAXIMUM_WAIT_OBJECTS);
|
||||
const nCount: DWORD = @as(DWORD, @intCast(handles.len));
|
||||
switch (kernel32.WaitForMultipleObjectsEx(
|
||||
nCount,
|
||||
handles.ptr,
|
||||
@intFromBool(waitAll),
|
||||
milliseconds,
|
||||
@intFromBool(alertable),
|
||||
)) {
|
||||
WAIT_OBJECT_0...WAIT_OBJECT_0 + MAXIMUM_WAIT_OBJECTS => |n| {
|
||||
const handle_index = n - WAIT_OBJECT_0;
|
||||
assert(handle_index < nCount);
|
||||
return handle_index;
|
||||
},
|
||||
WAIT_ABANDONED_0...WAIT_ABANDONED_0 + MAXIMUM_WAIT_OBJECTS => |n| {
|
||||
const handle_index = n - WAIT_ABANDONED_0;
|
||||
assert(handle_index < nCount);
|
||||
return error.WaitAbandoned;
|
||||
},
|
||||
WAIT_TIMEOUT => return error.WaitTimeOut,
|
||||
WAIT_FAILED => switch (GetLastError()) {
|
||||
else => |err| return unexpectedError(err),
|
||||
},
|
||||
else => return error.Unexpected,
|
||||
}
|
||||
}
|
||||
|
||||
pub const CreateIoCompletionPortError = error{Unexpected};
|
||||
|
||||
pub fn CreateIoCompletionPort(
|
||||
|
|
@ -2878,21 +2670,6 @@ pub fn CloseHandle(hObject: HANDLE) void {
|
|||
assert(ntdll.NtClose(hObject) == .SUCCESS);
|
||||
}
|
||||
|
||||
pub const GetStdHandleError = error{
|
||||
NoStandardHandleAttached,
|
||||
Unexpected,
|
||||
};
|
||||
|
||||
pub fn GetStdHandle(handle_id: DWORD) GetStdHandleError!HANDLE {
|
||||
const handle = kernel32.GetStdHandle(handle_id) orelse return error.NoStandardHandleAttached;
|
||||
if (handle == INVALID_HANDLE_VALUE) {
|
||||
switch (GetLastError()) {
|
||||
else => |err| return unexpectedError(err),
|
||||
}
|
||||
}
|
||||
return handle;
|
||||
}
|
||||
|
||||
pub const QueryObjectNameError = error{
|
||||
AccessDenied,
|
||||
InvalidHandle,
|
||||
|
|
@ -3545,6 +3322,12 @@ pub fn nanoSecondsToFileTime(ns: Io.Timestamp) FILETIME {
|
|||
};
|
||||
}
|
||||
|
||||
/// Use RtlUpcaseUnicodeChar on Windows when not in comptime to avoid including a
|
||||
/// redundant copy of the uppercase data.
|
||||
pub inline fn toUpperWtf16(c: u16) u16 {
|
||||
return (if (builtin.os.tag != .windows or @inComptime()) nls.upcaseW else ntdll.RtlUpcaseUnicodeChar)(c);
|
||||
}
|
||||
|
||||
/// Compares two WTF16 strings using the equivalent functionality of
|
||||
/// `RtlEqualUnicodeString` (with case insensitive comparison enabled).
|
||||
/// This function can be called on any target.
|
||||
|
|
@ -3598,19 +3381,12 @@ pub fn eqlIgnoreCaseWtf8(a: []const u8, b: []const u8) bool {
|
|||
var a_wtf8_it = std.unicode.Wtf8View.initUnchecked(a).iterator();
|
||||
var b_wtf8_it = std.unicode.Wtf8View.initUnchecked(b).iterator();
|
||||
|
||||
// Use RtlUpcaseUnicodeChar on Windows when not in comptime to avoid including a
|
||||
// redundant copy of the uppercase data.
|
||||
const upcaseImpl = switch (builtin.os.tag) {
|
||||
.windows => if (@inComptime()) nls.upcaseW else ntdll.RtlUpcaseUnicodeChar,
|
||||
else => nls.upcaseW,
|
||||
};
|
||||
|
||||
while (true) {
|
||||
const a_cp = a_wtf8_it.nextCodepoint() orelse break;
|
||||
const b_cp = b_wtf8_it.nextCodepoint() orelse return false;
|
||||
|
||||
if (a_cp <= maxInt(u16) and b_cp <= maxInt(u16)) {
|
||||
if (a_cp != b_cp and upcaseImpl(@intCast(a_cp)) != upcaseImpl(@intCast(b_cp))) {
|
||||
if (a_cp != b_cp and toUpperWtf16(@intCast(a_cp)) != toUpperWtf16(@intCast(b_cp))) {
|
||||
return false;
|
||||
}
|
||||
} else if (a_cp != b_cp) {
|
||||
|
|
@ -4098,15 +3874,6 @@ pub const Win32Error = @import("windows/win32error.zig").Win32Error;
|
|||
pub const LANG = @import("windows/lang.zig");
|
||||
pub const SUBLANG = @import("windows/sublang.zig");
|
||||
|
||||
/// The standard input device. Initially, this is the console input buffer, CONIN$.
|
||||
pub const STD_INPUT_HANDLE = maxInt(DWORD) - 10 + 1;
|
||||
|
||||
/// The standard output device. Initially, this is the active console screen buffer, CONOUT$.
|
||||
pub const STD_OUTPUT_HANDLE = maxInt(DWORD) - 11 + 1;
|
||||
|
||||
/// The standard error device. Initially, this is the active console screen buffer, CONOUT$.
|
||||
pub const STD_ERROR_HANDLE = maxInt(DWORD) - 12 + 1;
|
||||
|
||||
pub const BOOL = c_int;
|
||||
pub const BOOLEAN = BYTE;
|
||||
pub const BYTE = u8;
|
||||
|
|
@ -5244,6 +5011,8 @@ pub const UNICODE_STRING = extern struct {
|
|||
Length: c_ushort,
|
||||
MaximumLength: c_ushort,
|
||||
Buffer: ?[*]WCHAR,
|
||||
|
||||
pub const empty: UNICODE_STRING = .{ .Length = 0, .MaximumLength = 0, .Buffer = null };
|
||||
};
|
||||
|
||||
pub const ACTIVATION_CONTEXT_DATA = opaque {};
|
||||
|
|
|
|||
|
|
@ -12,8 +12,6 @@ const FILETIME = windows.FILETIME;
|
|||
const HANDLE = windows.HANDLE;
|
||||
const HANDLER_ROUTINE = windows.HANDLER_ROUTINE;
|
||||
const HMODULE = windows.HMODULE;
|
||||
const INIT_ONCE = windows.INIT_ONCE;
|
||||
const INIT_ONCE_FN = windows.INIT_ONCE_FN;
|
||||
const LARGE_INTEGER = windows.LARGE_INTEGER;
|
||||
const LPCSTR = windows.LPCSTR;
|
||||
const LPCVOID = windows.LPCVOID;
|
||||
|
|
@ -24,7 +22,6 @@ const LPWSTR = windows.LPWSTR;
|
|||
const MODULEENTRY32 = windows.MODULEENTRY32;
|
||||
const OVERLAPPED = windows.OVERLAPPED;
|
||||
const OVERLAPPED_ENTRY = windows.OVERLAPPED_ENTRY;
|
||||
const PMEMORY_BASIC_INFORMATION = windows.PMEMORY_BASIC_INFORMATION;
|
||||
const PROCESS_INFORMATION = windows.PROCESS_INFORMATION;
|
||||
const SECURITY_ATTRIBUTES = windows.SECURITY_ATTRIBUTES;
|
||||
const SIZE_T = windows.SIZE_T;
|
||||
|
|
@ -37,7 +34,6 @@ const ULONG = windows.ULONG;
|
|||
const ULONG_PTR = windows.ULONG_PTR;
|
||||
const va_list = windows.va_list;
|
||||
const WCHAR = windows.WCHAR;
|
||||
const WIN32_FIND_DATAW = windows.WIN32_FIND_DATAW;
|
||||
const Win32Error = windows.Win32Error;
|
||||
const WORD = windows.WORD;
|
||||
|
||||
|
|
@ -59,39 +55,6 @@ pub extern "kernel32" fn CancelIo(
|
|||
hFile: HANDLE,
|
||||
) callconv(.winapi) BOOL;
|
||||
|
||||
// TODO: Wrapper around NtCancelIoFileEx.
|
||||
pub extern "kernel32" fn CancelIoEx(
|
||||
hFile: HANDLE,
|
||||
lpOverlapped: ?*OVERLAPPED,
|
||||
) callconv(.winapi) BOOL;
|
||||
|
||||
pub extern "kernel32" fn CreateFileW(
|
||||
lpFileName: LPCWSTR,
|
||||
dwDesiredAccess: ACCESS_MASK,
|
||||
dwShareMode: DWORD,
|
||||
lpSecurityAttributes: ?*SECURITY_ATTRIBUTES,
|
||||
dwCreationDisposition: DWORD,
|
||||
dwFlagsAndAttributes: DWORD,
|
||||
hTemplateFile: ?HANDLE,
|
||||
) callconv(.winapi) HANDLE;
|
||||
|
||||
// TODO A bunch of logic around NtCreateNamedPipe
|
||||
pub extern "kernel32" fn CreateNamedPipeW(
|
||||
lpName: LPCWSTR,
|
||||
dwOpenMode: DWORD,
|
||||
dwPipeMode: DWORD,
|
||||
nMaxInstances: DWORD,
|
||||
nOutBufferSize: DWORD,
|
||||
nInBufferSize: DWORD,
|
||||
nDefaultTimeOut: DWORD,
|
||||
lpSecurityAttributes: ?*const SECURITY_ATTRIBUTES,
|
||||
) callconv(.winapi) HANDLE;
|
||||
|
||||
// TODO: Matches `STD_*_HANDLE` to peb().ProcessParameters.Standard*
|
||||
pub extern "kernel32" fn GetStdHandle(
|
||||
nStdHandle: DWORD,
|
||||
) callconv(.winapi) ?HANDLE;
|
||||
|
||||
// TODO: Wrapper around NtSetInformationFile + `FILE_POSITION_INFORMATION`.
|
||||
// `FILE_STANDARD_INFORMATION` is also used if dwMoveMethod is `FILE_END`
|
||||
pub extern "kernel32" fn SetFilePointerEx(
|
||||
|
|
@ -117,11 +80,6 @@ pub extern "kernel32" fn WriteFile(
|
|||
in_out_lpOverlapped: ?*OVERLAPPED,
|
||||
) callconv(.winapi) BOOL;
|
||||
|
||||
// TODO: Wrapper around GetStdHandle + NtFlushBuffersFile.
|
||||
pub extern "kernel32" fn FlushFileBuffers(
|
||||
hFile: HANDLE,
|
||||
) callconv(.winapi) BOOL;
|
||||
|
||||
// TODO: Wrapper around NtSetInformationFile + `FILE_IO_COMPLETION_NOTIFICATION_INFORMATION`.
|
||||
pub extern "kernel32" fn SetFileCompletionNotificationModes(
|
||||
FileHandle: HANDLE,
|
||||
|
|
@ -143,24 +101,6 @@ pub extern "kernel32" fn GetSystemDirectoryW(
|
|||
|
||||
// I/O - Kernel Objects
|
||||
|
||||
// TODO: Wrapper around GetStdHandle + NtDuplicateObject.
|
||||
pub extern "kernel32" fn DuplicateHandle(
|
||||
hSourceProcessHandle: HANDLE,
|
||||
hSourceHandle: HANDLE,
|
||||
hTargetProcessHandle: HANDLE,
|
||||
lpTargetHandle: *HANDLE,
|
||||
dwDesiredAccess: ACCESS_MASK,
|
||||
bInheritHandle: BOOL,
|
||||
dwOptions: DWORD,
|
||||
) callconv(.winapi) BOOL;
|
||||
|
||||
// TODO: Wrapper around GetStdHandle + NtQueryObject + NtSetInformationObject with .ObjectHandleFlagInformation.
|
||||
pub extern "kernel32" fn SetHandleInformation(
|
||||
hObject: HANDLE,
|
||||
dwMask: DWORD,
|
||||
dwFlags: DWORD,
|
||||
) callconv(.winapi) BOOL;
|
||||
|
||||
// TODO: Wrapper around NtRemoveIoCompletion.
|
||||
pub extern "kernel32" fn GetQueuedCompletionStatus(
|
||||
CompletionPort: HANDLE,
|
||||
|
|
@ -210,37 +150,6 @@ pub extern "kernel32" fn TerminateProcess(
|
|||
uExitCode: UINT,
|
||||
) callconv(.winapi) BOOL;
|
||||
|
||||
// TODO: WaitForSingleObjectEx with bAlertable=false.
|
||||
pub extern "kernel32" fn WaitForSingleObject(
|
||||
hHandle: HANDLE,
|
||||
dwMilliseconds: DWORD,
|
||||
) callconv(.winapi) DWORD;
|
||||
|
||||
// TODO: Wrapper for GetStdHandle + NtWaitForSingleObject.
|
||||
// Sets up an activation context before calling NtWaitForSingleObject.
|
||||
pub extern "kernel32" fn WaitForSingleObjectEx(
|
||||
hHandle: HANDLE,
|
||||
dwMilliseconds: DWORD,
|
||||
bAlertable: BOOL,
|
||||
) callconv(.winapi) DWORD;
|
||||
|
||||
// TODO: WaitForMultipleObjectsEx with alertable=false
|
||||
pub extern "kernel32" fn WaitForMultipleObjects(
|
||||
nCount: DWORD,
|
||||
lpHandle: [*]const HANDLE,
|
||||
bWaitAll: BOOL,
|
||||
dwMilliseconds: DWORD,
|
||||
) callconv(.winapi) DWORD;
|
||||
|
||||
// TODO: Wrapper around NtWaitForMultipleObjects.
|
||||
pub extern "kernel32" fn WaitForMultipleObjectsEx(
|
||||
nCount: DWORD,
|
||||
lpHandle: [*]const HANDLE,
|
||||
bWaitAll: BOOL,
|
||||
dwMilliseconds: DWORD,
|
||||
bAlertable: BOOL,
|
||||
) callconv(.winapi) DWORD;
|
||||
|
||||
// Process Management
|
||||
|
||||
pub extern "kernel32" fn CreateProcessW(
|
||||
|
|
@ -256,12 +165,6 @@ pub extern "kernel32" fn CreateProcessW(
|
|||
lpProcessInformation: *PROCESS_INFORMATION,
|
||||
) callconv(.winapi) BOOL;
|
||||
|
||||
// TODO: implement via ntdll instead
|
||||
pub extern "kernel32" fn SleepEx(
|
||||
dwMilliseconds: DWORD,
|
||||
bAlertable: BOOL,
|
||||
) callconv(.winapi) DWORD;
|
||||
|
||||
// TODO: Wrapper around NtQueryInformationProcess with `PROCESS_BASIC_INFORMATION`.
|
||||
pub extern "kernel32" fn GetExitCodeProcess(
|
||||
hProcess: HANDLE,
|
||||
|
|
@ -436,14 +339,3 @@ pub extern "kernel32" fn FormatMessageW(
|
|||
|
||||
// TODO: Getter for teb().LastErrorValue.
|
||||
pub extern "kernel32" fn GetLastError() callconv(.winapi) Win32Error;
|
||||
|
||||
// TODO: Wrapper around RtlSetLastWin32Error.
|
||||
pub extern "kernel32" fn SetLastError(
|
||||
dwErrCode: Win32Error,
|
||||
) callconv(.winapi) void;
|
||||
|
||||
// Everything Else
|
||||
|
||||
pub extern "kernel32" fn GetSystemInfo(
|
||||
lpSystemInfo: *SYSTEM_INFO,
|
||||
) callconv(.winapi) void;
|
||||
|
|
|
|||
|
|
@ -407,6 +407,11 @@ pub extern "ntdll" fn NtCreateNamedPipeFile(
|
|||
DefaultTimeout: ?*const LARGE_INTEGER,
|
||||
) callconv(.winapi) NTSTATUS;
|
||||
|
||||
pub extern "ntdll" fn NtFlushBuffersFile(
|
||||
FileHandle: HANDLE,
|
||||
IoStatusBlock: *IO_STATUS_BLOCK,
|
||||
) callconv(.winapi) NTSTATUS;
|
||||
|
||||
pub extern "ntdll" fn NtMapViewOfSection(
|
||||
SectionHandle: HANDLE,
|
||||
ProcessHandle: HANDLE,
|
||||
|
|
@ -590,7 +595,7 @@ pub extern "ntdll" fn NtOpenThread(
|
|||
|
||||
pub extern "ntdll" fn NtCancelSynchronousIoFile(
|
||||
ThreadHandle: HANDLE,
|
||||
RequestToCancel: ?*IO_STATUS_BLOCK,
|
||||
IoRequestToCancel: ?*IO_STATUS_BLOCK,
|
||||
IoStatusBlock: *IO_STATUS_BLOCK,
|
||||
) callconv(.winapi) NTSTATUS;
|
||||
|
||||
|
|
@ -606,13 +611,13 @@ pub extern "ntdll" fn NtDelayExecution(
|
|||
DelayInterval: *const LARGE_INTEGER,
|
||||
) callconv(.winapi) NTSTATUS;
|
||||
|
||||
pub extern "ntdll" fn NtCancelIoFile(
|
||||
FileHandle: HANDLE,
|
||||
IoStatusBlock: *IO_STATUS_BLOCK,
|
||||
) callconv(.winapi) NTSTATUS;
|
||||
|
||||
pub extern "ntdll" fn NtCancelIoFileEx(
|
||||
FileHandle: HANDLE,
|
||||
IoRequestToCancel: *const IO_STATUS_BLOCK,
|
||||
IoStatusBlock: *IO_STATUS_BLOCK,
|
||||
) callconv(.winapi) NTSTATUS;
|
||||
|
||||
pub extern "ntdll" fn NtCancelIoFile(
|
||||
FileHandle: HANDLE,
|
||||
IoStatusBlock: *IO_STATUS_BLOCK,
|
||||
) callconv(.winapi) NTSTATUS;
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ const builtin = @import("builtin");
|
|||
const native_os = builtin.os.tag;
|
||||
|
||||
const std = @import("../std.zig");
|
||||
const Allocator = std.mem.Allocator;
|
||||
const Allocator = mem.Allocator;
|
||||
const assert = std.debug.assert;
|
||||
const testing = std.testing;
|
||||
const unicode = std.unicode;
|
||||
|
|
@ -14,12 +14,7 @@ const mem = std.mem;
|
|||
/// Unmodified, unprocessed data provided by the operating system.
|
||||
block: Block,
|
||||
|
||||
pub const empty: Environ = .{
|
||||
.block = switch (Block) {
|
||||
void => {},
|
||||
else => &.{},
|
||||
},
|
||||
};
|
||||
pub const empty: Environ = .{ .block = .empty };
|
||||
|
||||
/// On WASI without libc, this is `void` because the environment has to be
|
||||
/// queried and heap-allocated at runtime.
|
||||
|
|
@ -28,13 +23,65 @@ pub const empty: Environ = .{
|
|||
/// is modified, so a long-lived pointer cannot be used. Therefore, on this
|
||||
/// operating system `void` is also used.
|
||||
pub const Block = switch (native_os) {
|
||||
.windows => void,
|
||||
.windows => GlobalBlock,
|
||||
.wasi => switch (builtin.link_libc) {
|
||||
false => void,
|
||||
true => [:null]const ?[*:0]const u8,
|
||||
false => GlobalBlock,
|
||||
true => PosixBlock,
|
||||
},
|
||||
.freestanding, .other => void,
|
||||
else => [:null]const ?[*:0]const u8,
|
||||
.freestanding, .other => GlobalBlock,
|
||||
else => PosixBlock,
|
||||
};
|
||||
|
||||
pub const GlobalBlock = struct {
|
||||
use_global: bool,
|
||||
|
||||
pub const empty: GlobalBlock = .{ .use_global = false };
|
||||
pub const global: GlobalBlock = .{ .use_global = true };
|
||||
|
||||
pub fn deinit(_: GlobalBlock, _: Allocator) void {}
|
||||
};
|
||||
|
||||
pub const PosixBlock = struct {
|
||||
slice: [:null]const ?[*:0]const u8,
|
||||
|
||||
pub const empty: PosixBlock = .{ .slice = &.{} };
|
||||
|
||||
pub fn deinit(block: PosixBlock, gpa: Allocator) void {
|
||||
for (block.slice) |entry| gpa.free(mem.span(entry.?));
|
||||
gpa.free(block.slice);
|
||||
}
|
||||
|
||||
pub const View = struct {
|
||||
slice: []const [*:0]const u8,
|
||||
|
||||
pub fn isEmpty(v: View) bool {
|
||||
return v.slice.len == 0;
|
||||
}
|
||||
};
|
||||
pub fn view(block: PosixBlock) View {
|
||||
return .{ .slice = @ptrCast(block.slice) };
|
||||
}
|
||||
};
|
||||
|
||||
pub const WindowsBlock = struct {
|
||||
slice: [:0]const u16,
|
||||
|
||||
pub const empty: WindowsBlock = .{ .slice = &.{0} };
|
||||
|
||||
pub fn deinit(block: WindowsBlock, gpa: Allocator) void {
|
||||
gpa.free(block.slice);
|
||||
}
|
||||
|
||||
pub const View = struct {
|
||||
ptr: [*:0]const u16,
|
||||
|
||||
pub fn isEmpty(v: View) bool {
|
||||
return v.ptr[0] == 0;
|
||||
}
|
||||
};
|
||||
pub fn view(block: WindowsBlock) View {
|
||||
return .{ .ptr = block.slice.ptr };
|
||||
}
|
||||
};
|
||||
|
||||
pub const Map = struct {
|
||||
|
|
@ -46,47 +93,60 @@ pub const Map = struct {
|
|||
pub const Size = usize;
|
||||
|
||||
pub const EnvNameHashContext = struct {
|
||||
fn upcase(c: u21) u21 {
|
||||
if (c <= std.math.maxInt(u16))
|
||||
return std.os.windows.ntdll.RtlUpcaseUnicodeChar(@as(u16, @intCast(c)));
|
||||
return c;
|
||||
}
|
||||
|
||||
pub fn hash(self: @This(), s: []const u8) u32 {
|
||||
_ = self;
|
||||
if (native_os == .windows) {
|
||||
var h = std.hash.Wyhash.init(0);
|
||||
var it = unicode.Wtf8View.initUnchecked(s).iterator();
|
||||
while (it.nextCodepoint()) |cp| {
|
||||
const cp_upper = upcase(cp);
|
||||
h.update(&[_]u8{
|
||||
@as(u8, @intCast((cp_upper >> 16) & 0xff)),
|
||||
@as(u8, @intCast((cp_upper >> 8) & 0xff)),
|
||||
@as(u8, @intCast((cp_upper >> 0) & 0xff)),
|
||||
});
|
||||
}
|
||||
return @truncate(h.final());
|
||||
switch (native_os) {
|
||||
else => return std.array_hash_map.hashString(s),
|
||||
.windows => {
|
||||
var h = std.hash.Wyhash.init(0);
|
||||
var it = unicode.Wtf8View.initUnchecked(s).iterator();
|
||||
while (it.nextCodepoint()) |cp| {
|
||||
const cp_upper = if (std.math.cast(u16, cp)) |wtf16|
|
||||
std.os.windows.toUpperWtf16(wtf16)
|
||||
else
|
||||
cp;
|
||||
h.update(&[_]u8{
|
||||
@truncate(cp_upper >> 0),
|
||||
@truncate(cp_upper >> 8),
|
||||
@truncate(cp_upper >> 16),
|
||||
});
|
||||
}
|
||||
return @truncate(h.final());
|
||||
},
|
||||
}
|
||||
return std.array_hash_map.hashString(s);
|
||||
}
|
||||
|
||||
pub fn eql(self: @This(), a: []const u8, b: []const u8, b_index: usize) bool {
|
||||
_ = self;
|
||||
_ = b_index;
|
||||
if (native_os == .windows) {
|
||||
var it_a = unicode.Wtf8View.initUnchecked(a).iterator();
|
||||
var it_b = unicode.Wtf8View.initUnchecked(b).iterator();
|
||||
while (true) {
|
||||
const c_a = it_a.nextCodepoint() orelse break;
|
||||
const c_b = it_b.nextCodepoint() orelse return false;
|
||||
if (upcase(c_a) != upcase(c_b))
|
||||
return false;
|
||||
}
|
||||
return if (it_b.nextCodepoint()) |_| false else true;
|
||||
}
|
||||
return std.array_hash_map.eqlString(a, b);
|
||||
return eqlKeys(a, b);
|
||||
}
|
||||
};
|
||||
fn eqlKeys(a: []const u8, b: []const u8) bool {
|
||||
return switch (native_os) {
|
||||
else => std.array_hash_map.eqlString(a, b),
|
||||
.windows => std.os.windows.eqlIgnoreCaseWtf8(a, b),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn validateKey(key: []const u8) bool {
|
||||
switch (native_os) {
|
||||
else => return key.len > 0 and mem.findAny(u8, key, &.{ 0, '=' }) == null,
|
||||
.windows => {
|
||||
if (!unicode.wtf8ValidateSlice(key)) return false;
|
||||
var it = unicode.Wtf8View.initUnchecked(key).iterator();
|
||||
switch (it.nextCodepoint() orelse return false) {
|
||||
0 => return false,
|
||||
else => {},
|
||||
}
|
||||
while (it.nextCodepoint()) |cp| switch (cp) {
|
||||
0, '=' => return false,
|
||||
else => {},
|
||||
};
|
||||
return true;
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a Map backed by a specific allocator.
|
||||
/// That allocator will be used for both backing allocations
|
||||
|
|
@ -99,30 +159,71 @@ pub const Map = struct {
|
|||
/// of the stored keys and values.
|
||||
pub fn deinit(self: *Map) void {
|
||||
const gpa = self.allocator;
|
||||
var it = self.array_hash_map.iterator();
|
||||
while (it.next()) |entry| {
|
||||
gpa.free(entry.key_ptr.*);
|
||||
gpa.free(entry.value_ptr.*);
|
||||
}
|
||||
for (self.keys()) |key| gpa.free(key);
|
||||
for (self.values()) |value| gpa.free(value);
|
||||
self.array_hash_map.deinit(gpa);
|
||||
self.* = undefined;
|
||||
}
|
||||
|
||||
pub fn keys(m: *const Map) [][]const u8 {
|
||||
return m.array_hash_map.keys();
|
||||
pub fn keys(map: *const Map) [][]const u8 {
|
||||
return map.array_hash_map.keys();
|
||||
}
|
||||
|
||||
pub fn values(m: *const Map) [][]const u8 {
|
||||
return m.array_hash_map.values();
|
||||
pub fn values(map: *const Map) [][]const u8 {
|
||||
return map.array_hash_map.values();
|
||||
}
|
||||
|
||||
pub fn putPosixBlock(map: *Map, view: PosixBlock.View) Allocator.Error!void {
|
||||
for (view.slice) |entry| {
|
||||
var entry_i: usize = 0;
|
||||
while (entry[entry_i] != 0 and entry[entry_i] != '=') : (entry_i += 1) {}
|
||||
const key = entry[0..entry_i];
|
||||
|
||||
var end_i: usize = entry_i;
|
||||
while (entry[end_i] != 0) : (end_i += 1) {}
|
||||
const value = entry[entry_i + 1 .. end_i];
|
||||
|
||||
try map.put(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn putWindowsBlock(map: *Map, view: WindowsBlock.View) Allocator.Error!void {
|
||||
var i: usize = 0;
|
||||
while (view.ptr[i] != 0) {
|
||||
const key_start = i;
|
||||
|
||||
// There are some special environment variables that start with =,
|
||||
// so we need a special case to not treat = as a key/value separator
|
||||
// if it's the first character.
|
||||
// https://devblogs.microsoft.com/oldnewthing/20100506-00/?p=14133
|
||||
if (view.ptr[key_start] == '=') i += 1;
|
||||
|
||||
while (view.ptr[i] != 0 and view.ptr[i] != '=') : (i += 1) {}
|
||||
const key_w = view.ptr[key_start..i];
|
||||
const key = try unicode.wtf16LeToWtf8Alloc(map.allocator, key_w);
|
||||
errdefer map.allocator.free(key);
|
||||
|
||||
if (view.ptr[i] == '=') i += 1;
|
||||
|
||||
const value_start = i;
|
||||
while (view.ptr[i] != 0) : (i += 1) {}
|
||||
const value_w = view.ptr[value_start..i];
|
||||
const value = try unicode.wtf16LeToWtf8Alloc(map.allocator, value_w);
|
||||
errdefer map.allocator.free(value);
|
||||
|
||||
i += 1; // skip over null byte
|
||||
|
||||
try map.putMove(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
/// Same as `put` but the key and value become owned by the Map rather
|
||||
/// than being copied.
|
||||
/// If `putMove` fails, the ownership of key and value does not transfer.
|
||||
/// On Windows `key` must be a valid [WTF-8](https://wtf-8.codeberg.page/) string.
|
||||
pub fn putMove(self: *Map, key: []u8, value: []u8) !void {
|
||||
pub fn putMove(self: *Map, key: []u8, value: []u8) Allocator.Error!void {
|
||||
assert(validateKey(key));
|
||||
const gpa = self.allocator;
|
||||
assert(unicode.wtf8ValidateSlice(key));
|
||||
const get_or_put = try self.array_hash_map.getOrPut(gpa, key);
|
||||
if (get_or_put.found_existing) {
|
||||
gpa.free(get_or_put.key_ptr.*);
|
||||
|
|
@ -134,8 +235,8 @@ pub const Map = struct {
|
|||
|
||||
/// `key` and `value` are copied into the Map.
|
||||
/// On Windows `key` must be a valid [WTF-8](https://wtf-8.codeberg.page/) string.
|
||||
pub fn put(self: *Map, key: []const u8, value: []const u8) !void {
|
||||
assert(unicode.wtf8ValidateSlice(key));
|
||||
pub fn put(self: *Map, key: []const u8, value: []const u8) Allocator.Error!void {
|
||||
assert(validateKey(key));
|
||||
const gpa = self.allocator;
|
||||
const value_copy = try gpa.dupe(u8, value);
|
||||
errdefer gpa.free(value_copy);
|
||||
|
|
@ -155,7 +256,7 @@ pub const Map = struct {
|
|||
/// The returned pointer is invalidated if the map resizes.
|
||||
/// On Windows `key` must be a valid [WTF-8](https://wtf-8.codeberg.page/) string.
|
||||
pub fn getPtr(self: Map, key: []const u8) ?*[]const u8 {
|
||||
assert(unicode.wtf8ValidateSlice(key));
|
||||
assert(validateKey(key));
|
||||
return self.array_hash_map.getPtr(key);
|
||||
}
|
||||
|
||||
|
|
@ -164,11 +265,12 @@ pub const Map = struct {
|
|||
/// key is removed from the map.
|
||||
/// On Windows `key` must be a valid [WTF-8](https://wtf-8.codeberg.page/) string.
|
||||
pub fn get(self: Map, key: []const u8) ?[]const u8 {
|
||||
assert(unicode.wtf8ValidateSlice(key));
|
||||
assert(validateKey(key));
|
||||
return self.array_hash_map.get(key);
|
||||
}
|
||||
|
||||
pub fn contains(m: *const Map, key: []const u8) bool {
|
||||
assert(validateKey(key));
|
||||
return m.array_hash_map.contains(key);
|
||||
}
|
||||
|
||||
|
|
@ -181,7 +283,7 @@ pub const Map = struct {
|
|||
/// This invalidates the value returned by get() for this key.
|
||||
/// On Windows `key` must be a valid [WTF-8](https://wtf-8.codeberg.page/) string.
|
||||
pub fn swapRemove(self: *Map, key: []const u8) bool {
|
||||
assert(unicode.wtf8ValidateSlice(key));
|
||||
assert(validateKey(key));
|
||||
const kv = self.array_hash_map.fetchSwapRemove(key) orelse return false;
|
||||
const gpa = self.allocator;
|
||||
gpa.free(kv.key);
|
||||
|
|
@ -198,7 +300,7 @@ pub const Map = struct {
|
|||
/// This invalidates the value returned by get() for this key.
|
||||
/// On Windows `key` must be a valid [WTF-8](https://wtf-8.codeberg.page/) string.
|
||||
pub fn orderedRemove(self: *Map, key: []const u8) bool {
|
||||
assert(unicode.wtf8ValidateSlice(key));
|
||||
assert(validateKey(key));
|
||||
const kv = self.array_hash_map.fetchOrderedRemove(key) orelse return false;
|
||||
const gpa = self.allocator;
|
||||
gpa.free(kv.key);
|
||||
|
|
@ -233,105 +335,120 @@ pub const Map = struct {
|
|||
|
||||
/// Creates a null-delimited environment variable block in the format
|
||||
/// expected by POSIX, from a hash map plus options.
|
||||
pub fn createBlockPosix(
|
||||
pub fn createPosixBlock(
|
||||
map: *const Map,
|
||||
arena: Allocator,
|
||||
options: CreateBlockPosixOptions,
|
||||
) Allocator.Error![:null]?[*:0]u8 {
|
||||
gpa: Allocator,
|
||||
options: CreatePosixBlockOptions,
|
||||
) Allocator.Error!PosixBlock {
|
||||
const ZigProgressAction = enum { nothing, edit, delete, add };
|
||||
const zig_progress_action: ZigProgressAction = a: {
|
||||
const fd = options.zig_progress_fd orelse break :a .nothing;
|
||||
const exists = map.get("ZIG_PROGRESS") != null;
|
||||
const zig_progress_action: ZigProgressAction = action: {
|
||||
const fd = options.zig_progress_fd orelse break :action .nothing;
|
||||
const exists = map.contains("ZIG_PROGRESS");
|
||||
if (fd >= 0) {
|
||||
break :a if (exists) .edit else .add;
|
||||
break :action if (exists) .edit else .add;
|
||||
} else {
|
||||
if (exists) break :a .delete;
|
||||
if (exists) break :action .delete;
|
||||
}
|
||||
break :a .nothing;
|
||||
break :action .nothing;
|
||||
};
|
||||
|
||||
const envp_count: usize = c: {
|
||||
var c: usize = map.count();
|
||||
const envp = try gpa.allocSentinel(?[*:0]u8, len: {
|
||||
var len: usize = map.count();
|
||||
switch (zig_progress_action) {
|
||||
.add => c += 1,
|
||||
.delete => c -= 1,
|
||||
.add => len += 1,
|
||||
.delete => len -= 1,
|
||||
.nothing, .edit => {},
|
||||
}
|
||||
break :c c;
|
||||
};
|
||||
|
||||
const envp_buf = try arena.allocSentinel(?[*:0]u8, envp_count, null);
|
||||
var i: usize = 0;
|
||||
break :len len;
|
||||
}, null);
|
||||
var envp_len: usize = 0;
|
||||
errdefer {
|
||||
envp[envp_len] = null;
|
||||
PosixBlock.deinit(.{ .slice = envp[0..envp_len :null] }, gpa);
|
||||
}
|
||||
|
||||
if (zig_progress_action == .add) {
|
||||
envp_buf[i] = try std.fmt.allocPrintSentinel(arena, "ZIG_PROGRESS={d}", .{options.zig_progress_fd.?}, 0);
|
||||
i += 1;
|
||||
envp[envp_len] = try std.fmt.allocPrintSentinel(gpa, "ZIG_PROGRESS={d}", .{options.zig_progress_fd.?}, 0);
|
||||
envp_len += 1;
|
||||
}
|
||||
|
||||
{
|
||||
var it = map.iterator();
|
||||
while (it.next()) |pair| {
|
||||
if (mem.eql(u8, pair.key_ptr.*, "ZIG_PROGRESS")) switch (zig_progress_action) {
|
||||
.add => unreachable,
|
||||
.delete => continue,
|
||||
.edit => {
|
||||
envp_buf[i] = try std.fmt.allocPrintSentinel(arena, "{s}={d}", .{
|
||||
pair.key_ptr.*, options.zig_progress_fd.?,
|
||||
}, 0);
|
||||
i += 1;
|
||||
continue;
|
||||
},
|
||||
.nothing => {},
|
||||
};
|
||||
for (map.keys(), map.values()) |key, value| {
|
||||
if (mem.eql(u8, key, "ZIG_PROGRESS")) switch (zig_progress_action) {
|
||||
.add => unreachable,
|
||||
.delete => continue,
|
||||
.edit => {
|
||||
envp[envp_len] = try std.fmt.allocPrintSentinel(gpa, "{s}={d}", .{
|
||||
key, options.zig_progress_fd.?,
|
||||
}, 0);
|
||||
envp_len += 1;
|
||||
continue;
|
||||
},
|
||||
.nothing => {},
|
||||
};
|
||||
|
||||
envp_buf[i] = try std.fmt.allocPrintSentinel(arena, "{s}={s}", .{ pair.key_ptr.*, pair.value_ptr.* }, 0);
|
||||
i += 1;
|
||||
}
|
||||
envp[envp_len] = try std.fmt.allocPrintSentinel(gpa, "{s}={s}", .{ key, value }, 0);
|
||||
envp_len += 1;
|
||||
}
|
||||
|
||||
assert(i == envp_count);
|
||||
return envp_buf;
|
||||
assert(envp_len == envp.len);
|
||||
return .{ .slice = envp };
|
||||
}
|
||||
|
||||
/// Caller owns result.
|
||||
pub fn createBlockWindows(map: *const Map, gpa: Allocator) error{ OutOfMemory, InvalidWtf8 }![:0]u16 {
|
||||
pub fn createWindowsBlock(
|
||||
map: *const Map,
|
||||
gpa: Allocator,
|
||||
options: CreateWindowsBlockOptions,
|
||||
) error{ OutOfMemory, InvalidWtf8 }!WindowsBlock {
|
||||
// count bytes needed
|
||||
const max_chars_needed = x: {
|
||||
// Only need 2 trailing NUL code units for an empty environment
|
||||
var max_chars_needed: usize = if (map.count() == 0) 2 else 1;
|
||||
var it = map.iterator();
|
||||
while (it.next()) |pair| {
|
||||
// +1 for '='
|
||||
// +1 for null byte
|
||||
max_chars_needed += pair.key_ptr.len + pair.value_ptr.len + 2;
|
||||
const max_chars_needed = max_chars_needed: {
|
||||
var max_chars_needed: usize = "\x00".len;
|
||||
if (options.zig_progress_handle) |handle| if (handle != std.os.windows.INVALID_HANDLE_VALUE) {
|
||||
max_chars_needed += std.fmt.count("ZIG_PROGRESS={d}\x00", .{@intFromPtr(handle)});
|
||||
};
|
||||
for (map.keys(), map.values()) |key, value| {
|
||||
if (options.zig_progress_handle != null and eqlKeys(key, "ZIG_PROGRESS")) continue;
|
||||
max_chars_needed += key.len + "=".len + value.len + "\x00".len;
|
||||
}
|
||||
break :x max_chars_needed;
|
||||
break :max_chars_needed @max("\x00\x00".len, max_chars_needed);
|
||||
};
|
||||
const result = try gpa.alloc(u16, max_chars_needed);
|
||||
errdefer gpa.free(result);
|
||||
const block = try gpa.alloc(u16, max_chars_needed);
|
||||
errdefer gpa.free(block);
|
||||
|
||||
var it = map.iterator();
|
||||
var i: usize = 0;
|
||||
while (it.next()) |pair| {
|
||||
i += try unicode.wtf8ToWtf16Le(result[i..], pair.key_ptr.*);
|
||||
result[i] = '=';
|
||||
if (options.zig_progress_handle) |handle| if (handle != std.os.windows.INVALID_HANDLE_VALUE) {
|
||||
@memcpy(
|
||||
block[i..][0.."ZIG_PROGRESS=".len],
|
||||
&[_]u16{ 'Z', 'I', 'G', '_', 'P', 'R', 'O', 'G', 'R', 'E', 'S', 'S', '=' },
|
||||
);
|
||||
i += "ZIG_PROGRESS=".len;
|
||||
var value_buf: [std.fmt.count("{d}", .{std.math.maxInt(usize)})]u8 = undefined;
|
||||
const value = std.fmt.bufPrint(&value_buf, "{d}", .{@intFromPtr(handle)}) catch unreachable;
|
||||
for (block[i..][0..value.len], value) |*r, v| r.* = v;
|
||||
i += value.len;
|
||||
block[i] = 0;
|
||||
i += 1;
|
||||
i += try unicode.wtf8ToWtf16Le(result[i..], pair.value_ptr.*);
|
||||
result[i] = 0;
|
||||
};
|
||||
for (map.keys(), map.values()) |key, value| {
|
||||
if (options.zig_progress_handle != null and eqlKeys(key, "ZIG_PROGRESS")) continue;
|
||||
i += try unicode.wtf8ToWtf16Le(block[i..], key);
|
||||
block[i] = '=';
|
||||
i += 1;
|
||||
i += try unicode.wtf8ToWtf16Le(block[i..], value);
|
||||
block[i] = 0;
|
||||
i += 1;
|
||||
}
|
||||
result[i] = 0;
|
||||
i += 1;
|
||||
// An empty environment is a special case that requires a redundant
|
||||
// NUL terminator. CreateProcess will read the second code unit even
|
||||
// though theoretically the first should be enough to recognize that the
|
||||
// environment is empty (see https://nullprogram.com/blog/2023/08/23/)
|
||||
if (map.count() == 0) {
|
||||
result[i] = 0;
|
||||
for (0..2) |_| {
|
||||
block[i] = 0;
|
||||
i += 1;
|
||||
}
|
||||
const reallocated = try gpa.realloc(result, i);
|
||||
return reallocated[0 .. i - 1 :0];
|
||||
if (i >= 2) break;
|
||||
} else unreachable;
|
||||
const reallocated = try gpa.realloc(block, i);
|
||||
return .{ .slice = reallocated[0 .. i - 1 :0] };
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -344,13 +461,18 @@ pub const CreateMapError = error{
|
|||
|
||||
/// Allocates a `Map` and copies environment block into it.
|
||||
pub fn createMap(env: Environ, allocator: Allocator) CreateMapError!Map {
|
||||
if (native_os == .windows)
|
||||
return createMapWide(std.os.windows.peb().ProcessParameters.Environment, allocator);
|
||||
var map = Map.init(allocator);
|
||||
errdefer map.deinit();
|
||||
if (native_os == .windows) empty: {
|
||||
if (!env.block.use_global) break :empty;
|
||||
|
||||
var result = Map.init(allocator);
|
||||
errdefer result.deinit();
|
||||
const peb = std.os.windows.peb();
|
||||
assert(std.os.windows.ntdll.RtlEnterCriticalSection(peb.FastPebLock) == .SUCCESS);
|
||||
defer assert(std.os.windows.ntdll.RtlLeaveCriticalSection(peb.FastPebLock) == .SUCCESS);
|
||||
try map.putWindowsBlock(.{ .ptr = peb.ProcessParameters.Environment });
|
||||
} else if (native_os == .wasi and !builtin.link_libc) empty: {
|
||||
if (!env.block.use_global) break :empty;
|
||||
|
||||
if (native_os == .wasi and !builtin.link_libc) {
|
||||
var environ_count: usize = undefined;
|
||||
var environ_buf_size: usize = undefined;
|
||||
|
||||
|
|
@ -360,7 +482,7 @@ pub fn createMap(env: Environ, allocator: Allocator) CreateMapError!Map {
|
|||
}
|
||||
|
||||
if (environ_count == 0) {
|
||||
return result;
|
||||
return map;
|
||||
}
|
||||
|
||||
const environ = try allocator.alloc([*:0]u8, environ_count);
|
||||
|
|
@ -373,63 +495,9 @@ pub fn createMap(env: Environ, allocator: Allocator) CreateMapError!Map {
|
|||
return posix.unexpectedErrno(environ_get_ret);
|
||||
}
|
||||
|
||||
for (environ) |line| {
|
||||
const pair = mem.sliceTo(line, 0);
|
||||
var parts = mem.splitScalar(u8, pair, '=');
|
||||
const key = parts.first();
|
||||
const value = parts.rest();
|
||||
try result.put(key, value);
|
||||
}
|
||||
return result;
|
||||
} else {
|
||||
for (env.block) |opt_line| {
|
||||
const line = opt_line.?;
|
||||
var line_i: usize = 0;
|
||||
while (line[line_i] != 0 and line[line_i] != '=') : (line_i += 1) {}
|
||||
const key = line[0..line_i];
|
||||
|
||||
var end_i: usize = line_i;
|
||||
while (line[end_i] != 0) : (end_i += 1) {}
|
||||
const value = line[line_i + 1 .. end_i];
|
||||
|
||||
try result.put(key, value);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn createMapWide(ptr: [*:0]u16, gpa: Allocator) CreateMapError!Map {
|
||||
var result = Map.init(gpa);
|
||||
errdefer result.deinit();
|
||||
|
||||
var i: usize = 0;
|
||||
while (ptr[i] != 0) {
|
||||
const key_start = i;
|
||||
|
||||
// There are some special environment variables that start with =,
|
||||
// so we need a special case to not treat = as a key/value separator
|
||||
// if it's the first character.
|
||||
// https://devblogs.microsoft.com/oldnewthing/20100506-00/?p=14133
|
||||
if (ptr[key_start] == '=') i += 1;
|
||||
|
||||
while (ptr[i] != 0 and ptr[i] != '=') : (i += 1) {}
|
||||
const key_w = ptr[key_start..i];
|
||||
const key = try unicode.wtf16LeToWtf8Alloc(gpa, key_w);
|
||||
errdefer gpa.free(key);
|
||||
|
||||
if (ptr[i] == '=') i += 1;
|
||||
|
||||
const value_start = i;
|
||||
while (ptr[i] != 0) : (i += 1) {}
|
||||
const value_w = ptr[value_start..i];
|
||||
const value = try unicode.wtf16LeToWtf8Alloc(gpa, value_w);
|
||||
errdefer gpa.free(value);
|
||||
|
||||
i += 1; // skip over null byte
|
||||
|
||||
try result.putMove(key, value);
|
||||
}
|
||||
return result;
|
||||
try map.putPosixBlock(.{ .slice = environ });
|
||||
} else try map.putPosixBlock(env.block.view());
|
||||
return map;
|
||||
}
|
||||
|
||||
pub const ContainsError = error{
|
||||
|
|
@ -451,6 +519,7 @@ pub const ContainsError = error{
|
|||
/// * `containsConstant`
|
||||
/// * `containsUnempty`
|
||||
pub fn contains(environ: Environ, gpa: Allocator, key: []const u8) ContainsError!bool {
|
||||
if (native_os == .windows and !unicode.wtf8ValidateSlice(key)) return error.InvalidWtf8;
|
||||
var map = try createMap(environ, gpa);
|
||||
defer map.deinit();
|
||||
return map.contains(key);
|
||||
|
|
@ -464,6 +533,7 @@ pub fn contains(environ: Environ, gpa: Allocator, key: []const u8) ContainsError
|
|||
/// * `containsUnemptyConstant`
|
||||
/// * `contains`
|
||||
pub fn containsUnempty(environ: Environ, gpa: Allocator, key: []const u8) ContainsError!bool {
|
||||
if (native_os == .windows and !unicode.wtf8ValidateSlice(key)) return error.InvalidWtf8;
|
||||
var map = try createMap(environ, gpa);
|
||||
defer map.deinit();
|
||||
const value = map.get(key) orelse return false;
|
||||
|
|
@ -516,16 +586,15 @@ pub inline fn containsUnemptyConstant(environ: Environ, comptime key: []const u8
|
|||
/// * `createMap`
|
||||
pub fn getPosix(environ: Environ, key: []const u8) ?[:0]const u8 {
|
||||
if (mem.findScalar(u8, key, '=') != null) return null;
|
||||
for (environ.block) |opt_line| {
|
||||
const line = opt_line.?;
|
||||
var line_i: usize = 0;
|
||||
while (line[line_i] != 0) : (line_i += 1) {
|
||||
if (line_i == key.len) break;
|
||||
if (line[line_i] != key[line_i]) break;
|
||||
for (environ.block.view().slice) |entry| {
|
||||
var entry_i: usize = 0;
|
||||
while (entry[entry_i] != 0) : (entry_i += 1) {
|
||||
if (entry_i == key.len) break;
|
||||
if (entry[entry_i] != key[entry_i]) break;
|
||||
}
|
||||
if ((line_i != key.len) or (line[line_i] != '=')) continue;
|
||||
if ((entry_i != key.len) or (entry[entry_i] != '=')) continue;
|
||||
|
||||
return mem.sliceTo(line + line_i + 1, 0);
|
||||
return mem.sliceTo(entry + entry_i + 1, 0);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
|
@ -541,14 +610,16 @@ pub fn getPosix(environ: Environ, key: []const u8) ?[:0]const u8 {
|
|||
/// * `containsConstant`
|
||||
/// * `contains`
|
||||
pub fn getWindows(environ: Environ, key: [*:0]const u16) ?[:0]const u16 {
|
||||
comptime assert(native_os == .windows);
|
||||
comptime assert(@TypeOf(environ.block) == void);
|
||||
|
||||
// '=' anywhere but the start makes this an invalid environment variable name.
|
||||
const key_slice = mem.sliceTo(key, 0);
|
||||
if (key_slice.len > 0 and mem.findScalar(u16, key_slice[1..], '=') != null) return null;
|
||||
assert(key_slice.len > 0 and mem.findScalar(u16, key_slice[1..], '=') == null);
|
||||
|
||||
const ptr = std.os.windows.peb().ProcessParameters.Environment;
|
||||
if (!environ.block.use_global) return null;
|
||||
|
||||
const peb = std.os.windows.peb();
|
||||
assert(std.os.windows.ntdll.RtlEnterCriticalSection(peb.FastPebLock) == .SUCCESS);
|
||||
defer assert(std.os.windows.ntdll.RtlLeaveCriticalSection(peb.FastPebLock) == .SUCCESS);
|
||||
const ptr = peb.ProcessParameters.Environment;
|
||||
|
||||
var i: usize = 0;
|
||||
while (ptr[i] != 0) {
|
||||
|
|
@ -558,8 +629,7 @@ pub fn getWindows(environ: Environ, key: [*:0]const u16) ?[:0]const u16 {
|
|||
// so we need a special case to not treat = as a key/value separator
|
||||
// if it's the first character.
|
||||
// https://devblogs.microsoft.com/oldnewthing/20100506-00/?p=14133
|
||||
const equal_search_start: usize = if (key_value[0] == '=') 1 else 0;
|
||||
const equal_index = mem.findScalarPos(u16, key_value, equal_search_start, '=') orelse {
|
||||
const equal_index = mem.findScalarPos(u16, key_value, 1, '=') orelse {
|
||||
// This is enforced by CreateProcess.
|
||||
// If violated, CreateProcess will fail with INVALID_PARAMETER.
|
||||
unreachable; // must contain a =
|
||||
|
|
@ -598,13 +668,14 @@ pub const GetAllocError = error{
|
|||
/// See also:
|
||||
/// * `createMap`
|
||||
pub fn getAlloc(environ: Environ, gpa: Allocator, key: []const u8) GetAllocError![]u8 {
|
||||
if (native_os == .windows and !unicode.wtf8ValidateSlice(key)) return error.InvalidWtf8;
|
||||
var map = createMap(environ, gpa) catch return error.OutOfMemory;
|
||||
defer map.deinit();
|
||||
const val = map.get(key) orelse return error.EnvironmentVariableMissing;
|
||||
return gpa.dupe(u8, val);
|
||||
}
|
||||
|
||||
pub const CreateBlockPosixOptions = struct {
|
||||
pub const CreatePosixBlockOptions = struct {
|
||||
/// `null` means to leave the `ZIG_PROGRESS` environment variable unmodified.
|
||||
/// If non-null, negative means to remove the environment variable, and >= 0
|
||||
/// means to provide it with the given integer.
|
||||
|
|
@ -613,67 +684,147 @@ pub const CreateBlockPosixOptions = struct {
|
|||
|
||||
/// Creates a null-delimited environment variable block in the format expected
|
||||
/// by POSIX, from a different one.
|
||||
pub fn createBlockPosix(
|
||||
pub fn createPosixBlock(
|
||||
existing: Environ,
|
||||
arena: Allocator,
|
||||
options: CreateBlockPosixOptions,
|
||||
) Allocator.Error![:null]?[*:0]u8 {
|
||||
const contains_zig_progress = for (existing.block) |opt_line| {
|
||||
if (mem.eql(u8, mem.sliceTo(opt_line.?, '='), "ZIG_PROGRESS")) break true;
|
||||
gpa: Allocator,
|
||||
options: CreatePosixBlockOptions,
|
||||
) Allocator.Error!PosixBlock {
|
||||
const contains_zig_progress = for (existing.block.view().slice) |entry| {
|
||||
if (mem.eql(u8, mem.sliceTo(entry, '='), "ZIG_PROGRESS")) break true;
|
||||
} else false;
|
||||
|
||||
const ZigProgressAction = enum { nothing, edit, delete, add };
|
||||
const zig_progress_action: ZigProgressAction = a: {
|
||||
const fd = options.zig_progress_fd orelse break :a .nothing;
|
||||
const zig_progress_action: ZigProgressAction = action: {
|
||||
const fd = options.zig_progress_fd orelse break :action .nothing;
|
||||
if (fd >= 0) {
|
||||
break :a if (contains_zig_progress) .edit else .add;
|
||||
break :action if (contains_zig_progress) .edit else .add;
|
||||
} else {
|
||||
if (contains_zig_progress) break :a .delete;
|
||||
if (contains_zig_progress) break :action .delete;
|
||||
}
|
||||
break :a .nothing;
|
||||
break :action .nothing;
|
||||
};
|
||||
|
||||
const envp_count: usize = c: {
|
||||
var count: usize = existing.block.len;
|
||||
const envp = try gpa.allocSentinel(?[*:0]u8, len: {
|
||||
var len: usize = existing.block.slice.len;
|
||||
switch (zig_progress_action) {
|
||||
.add => count += 1,
|
||||
.delete => count -= 1,
|
||||
.add => len += 1,
|
||||
.delete => len -= 1,
|
||||
.nothing, .edit => {},
|
||||
}
|
||||
break :c count;
|
||||
};
|
||||
|
||||
const envp_buf = try arena.allocSentinel(?[*:0]u8, envp_count, null);
|
||||
var i: usize = 0;
|
||||
var existing_index: usize = 0;
|
||||
|
||||
break :len len;
|
||||
}, null);
|
||||
var envp_len: usize = 0;
|
||||
errdefer {
|
||||
envp[envp_len] = null;
|
||||
PosixBlock.deinit(.{ .slice = envp[0..envp_len :null] }, gpa);
|
||||
}
|
||||
if (zig_progress_action == .add) {
|
||||
envp_buf[i] = try std.fmt.allocPrintSentinel(arena, "ZIG_PROGRESS={d}", .{options.zig_progress_fd.?}, 0);
|
||||
i += 1;
|
||||
envp[envp_len] = try std.fmt.allocPrintSentinel(gpa, "ZIG_PROGRESS={d}", .{options.zig_progress_fd.?}, 0);
|
||||
envp_len += 1;
|
||||
}
|
||||
|
||||
while (existing.block[existing_index]) |line| : (existing_index += 1) {
|
||||
if (mem.eql(u8, mem.sliceTo(line, '='), "ZIG_PROGRESS")) switch (zig_progress_action) {
|
||||
var existing_index: usize = 0;
|
||||
while (existing.block.slice[existing_index]) |entry| : (existing_index += 1) {
|
||||
if (mem.eql(u8, mem.sliceTo(entry, '='), "ZIG_PROGRESS")) switch (zig_progress_action) {
|
||||
.add => unreachable,
|
||||
.delete => continue,
|
||||
.edit => {
|
||||
envp_buf[i] = try std.fmt.allocPrintSentinel(arena, "ZIG_PROGRESS={d}", .{options.zig_progress_fd.?}, 0);
|
||||
i += 1;
|
||||
envp[envp_len] = try std.fmt.allocPrintSentinel(gpa, "ZIG_PROGRESS={d}", .{options.zig_progress_fd.?}, 0);
|
||||
envp_len += 1;
|
||||
continue;
|
||||
},
|
||||
.nothing => {},
|
||||
};
|
||||
envp_buf[i] = try arena.dupeZ(u8, mem.span(line));
|
||||
i += 1;
|
||||
envp[envp_len] = try gpa.dupeZ(u8, mem.span(entry));
|
||||
envp_len += 1;
|
||||
}
|
||||
|
||||
assert(i == envp_count);
|
||||
return envp_buf;
|
||||
assert(envp_len == envp.len);
|
||||
return .{ .slice = envp };
|
||||
}
|
||||
|
||||
test "Map.createBlock" {
|
||||
const allocator = testing.allocator;
|
||||
var envmap = Map.init(allocator);
|
||||
pub const CreateWindowsBlockOptions = struct {
|
||||
/// `null` means to leave the `ZIG_PROGRESS` environment variable unmodified.
|
||||
/// If non-null, `std.os.windows.INVALID_HANDLE_VALUE` means to remove the
|
||||
/// environment variable, otherwise provide it with the given handle as an integer.
|
||||
zig_progress_handle: ?std.os.windows.HANDLE = null,
|
||||
};
|
||||
|
||||
/// Creates a null-delimited environment variable block in the format expected
|
||||
/// by POSIX, from a different one.
|
||||
pub fn createWindowsBlock(
|
||||
existing: Environ,
|
||||
gpa: Allocator,
|
||||
options: CreateWindowsBlockOptions,
|
||||
) Allocator.Error!WindowsBlock {
|
||||
if (!existing.block.use_global) return .{
|
||||
.slice = try gpa.dupeSentinel(u16, WindowsBlock.empty.slice, 0),
|
||||
};
|
||||
const peb = std.os.windows.peb();
|
||||
assert(std.os.windows.ntdll.RtlEnterCriticalSection(peb.FastPebLock) == .SUCCESS);
|
||||
defer assert(std.os.windows.ntdll.RtlLeaveCriticalSection(peb.FastPebLock) == .SUCCESS);
|
||||
const existing_block = peb.ProcessParameters.Environment;
|
||||
var ranges: [2]struct { start: usize, end: usize } = undefined;
|
||||
var ranges_len: usize = 0;
|
||||
ranges[ranges_len].start = 0;
|
||||
const zig_progress_key = [_]u16{ 'Z', 'I', 'G', '_', 'P', 'R', 'O', 'G', 'R', 'E', 'S', 'S', '=' };
|
||||
const needed_len = needed_len: {
|
||||
var needed_len: usize = "\x00".len;
|
||||
if (options.zig_progress_handle) |handle| if (handle != std.os.windows.INVALID_HANDLE_VALUE) {
|
||||
needed_len += std.fmt.count("ZIG_PROGRESS={d}\x00", .{@intFromPtr(handle)});
|
||||
};
|
||||
var i: usize = 0;
|
||||
while (existing_block[i] != 0) {
|
||||
const start = i;
|
||||
const entry = mem.sliceTo(existing_block[start..], 0);
|
||||
i += entry.len + "\x00".len;
|
||||
if (options.zig_progress_handle != null and entry.len >= zig_progress_key.len and
|
||||
std.os.windows.eqlIgnoreCaseWtf16(entry[0..zig_progress_key.len], &zig_progress_key))
|
||||
{
|
||||
ranges[ranges_len].end = start;
|
||||
ranges_len += 1;
|
||||
ranges[ranges_len].start = i;
|
||||
} else needed_len += entry.len + "\x00".len;
|
||||
}
|
||||
ranges[ranges_len].end = i;
|
||||
ranges_len += 1;
|
||||
break :needed_len @max("\x00\x00".len, needed_len);
|
||||
};
|
||||
const block = try gpa.alloc(u16, needed_len);
|
||||
errdefer gpa.free(block);
|
||||
var i: usize = 0;
|
||||
if (options.zig_progress_handle) |handle| if (handle != std.os.windows.INVALID_HANDLE_VALUE) {
|
||||
@memcpy(block[i..][0..zig_progress_key.len], &zig_progress_key);
|
||||
i += zig_progress_key.len;
|
||||
var value_buf: [std.fmt.count("{d}", .{std.math.maxInt(usize)})]u8 = undefined;
|
||||
const value = std.fmt.bufPrint(&value_buf, "{d}", .{@intFromPtr(handle)}) catch unreachable;
|
||||
for (block[i..][0..value.len], value) |*r, v| r.* = v;
|
||||
i += value.len;
|
||||
block[i] = 0;
|
||||
i += 1;
|
||||
};
|
||||
for (ranges[0..ranges_len]) |range| {
|
||||
const range_len = range.end - range.start;
|
||||
@memcpy(block[i..][0..range_len], existing_block[range.start..range.end]);
|
||||
i += range_len;
|
||||
}
|
||||
// An empty environment is a special case that requires a redundant
|
||||
// NUL terminator. CreateProcess will read the second code unit even
|
||||
// though theoretically the first should be enough to recognize that the
|
||||
// environment is empty (see https://nullprogram.com/blog/2023/08/23/)
|
||||
for (0..2) |_| {
|
||||
block[i] = 0;
|
||||
i += 1;
|
||||
if (i >= 2) break;
|
||||
} else unreachable;
|
||||
assert(i == block.len);
|
||||
return .{ .slice = block[0 .. i - 1 :0] };
|
||||
}
|
||||
|
||||
test "Map.createPosixBlock" {
|
||||
const gpa = testing.allocator;
|
||||
|
||||
var envmap = Map.init(gpa);
|
||||
defer envmap.deinit();
|
||||
|
||||
try envmap.put("HOME", "/home/ifreund");
|
||||
|
|
@ -682,29 +833,24 @@ test "Map.createBlock" {
|
|||
try envmap.put("DEBUGINFOD_URLS", " ");
|
||||
try envmap.put("XCURSOR_SIZE", "24");
|
||||
|
||||
var arena = std.heap.ArenaAllocator.init(allocator);
|
||||
defer arena.deinit();
|
||||
const environ = try envmap.createBlockPosix(arena.allocator(), .{});
|
||||
const block = try envmap.createPosixBlock(gpa, .{});
|
||||
defer block.deinit(gpa);
|
||||
|
||||
try testing.expectEqual(@as(usize, 5), environ.len);
|
||||
try testing.expectEqual(@as(usize, 5), block.slice.len);
|
||||
|
||||
inline for (.{
|
||||
for (&[_][]const u8{
|
||||
"HOME=/home/ifreund",
|
||||
"WAYLAND_DISPLAY=wayland-1",
|
||||
"DISPLAY=:1",
|
||||
"DEBUGINFOD_URLS= ",
|
||||
"XCURSOR_SIZE=24",
|
||||
}) |target| {
|
||||
for (environ) |variable| {
|
||||
if (mem.eql(u8, mem.span(variable orelse continue), target)) break;
|
||||
} else {
|
||||
try testing.expect(false); // Environment variable not found
|
||||
}
|
||||
}
|
||||
}, block.slice) |expected, actual| try testing.expectEqualStrings(expected, mem.span(actual.?));
|
||||
}
|
||||
|
||||
test Map {
|
||||
var env = Map.init(testing.allocator);
|
||||
const gpa = testing.allocator;
|
||||
|
||||
var env: Map = .init(gpa);
|
||||
defer env.deinit();
|
||||
|
||||
try env.put("SOMETHING_NEW", "hello");
|
||||
|
|
@ -740,6 +886,7 @@ test Map {
|
|||
try testing.expect(env.swapRemove("SOMETHING_NEW"));
|
||||
try testing.expect(!env.swapRemove("SOMETHING_NEW"));
|
||||
try testing.expect(env.get("SOMETHING_NEW") == null);
|
||||
try testing.expect(!env.contains("SOMETHING_NEW"));
|
||||
|
||||
try testing.expectEqual(@as(Map.Size, 1), env.count());
|
||||
|
||||
|
|
@ -749,10 +896,10 @@ test Map {
|
|||
try testing.expectEqualStrings("something else", env.get("кириллица").?);
|
||||
|
||||
// and WTF-8 that's not valid UTF-8
|
||||
const wtf8_with_surrogate_pair = try unicode.wtf16LeToWtf8Alloc(testing.allocator, &[_]u16{
|
||||
const wtf8_with_surrogate_pair = try unicode.wtf16LeToWtf8Alloc(gpa, &[_]u16{
|
||||
mem.nativeToLittle(u16, 0xD83D), // unpaired high surrogate
|
||||
});
|
||||
defer testing.allocator.free(wtf8_with_surrogate_pair);
|
||||
defer gpa.free(wtf8_with_surrogate_pair);
|
||||
|
||||
try env.put(wtf8_with_surrogate_pair, wtf8_with_surrogate_pair);
|
||||
try testing.expectEqualSlices(u8, wtf8_with_surrogate_pair, env.get(wtf8_with_surrogate_pair).?);
|
||||
|
|
@ -769,13 +916,9 @@ test "convert from Environ to Map and back again" {
|
|||
defer map.deinit();
|
||||
try map.put("FOO", "BAR");
|
||||
try map.put("A", "");
|
||||
try map.put("", "B");
|
||||
|
||||
var arena_allocator = std.heap.ArenaAllocator.init(gpa);
|
||||
defer arena_allocator.deinit();
|
||||
const arena = arena_allocator.allocator();
|
||||
|
||||
const environ: Environ = .{ .block = try map.createBlockPosix(arena, .{}) };
|
||||
const environ: Environ = .{ .block = try map.createPosixBlock(gpa, .{}) };
|
||||
defer environ.block.deinit(gpa);
|
||||
|
||||
try testing.expectEqual(true, environ.contains(gpa, "FOO"));
|
||||
try testing.expectEqual(false, environ.contains(gpa, "BAR"));
|
||||
|
|
@ -783,7 +926,6 @@ test "convert from Environ to Map and back again" {
|
|||
try testing.expectEqual(true, environ.containsConstant("A"));
|
||||
try testing.expectEqual(false, environ.containsUnempty(gpa, "A"));
|
||||
try testing.expectEqual(false, environ.containsUnemptyConstant("A"));
|
||||
try testing.expectEqual(true, environ.contains(gpa, ""));
|
||||
try testing.expectEqual(false, environ.contains(gpa, "B"));
|
||||
|
||||
try testing.expectError(error.EnvironmentVariableMissing, environ.getAlloc(gpa, "BOGUS"));
|
||||
|
|
@ -800,23 +942,47 @@ test "convert from Environ to Map and back again" {
|
|||
try testing.expectEqualDeep(map.values(), map2.values());
|
||||
}
|
||||
|
||||
test createMapWide {
|
||||
if (builtin.cpu.arch.endian() == .big) return error.SkipZigTest; // TODO
|
||||
test "Map.putPosixBlock" {
|
||||
const gpa = testing.allocator;
|
||||
|
||||
var map: Map = .init(gpa);
|
||||
defer map.deinit();
|
||||
|
||||
try map.put("FOO", "BAR");
|
||||
try map.put("A", "");
|
||||
try map.put("ZIG_PROGRESS", "unchanged");
|
||||
|
||||
const block = try map.createPosixBlock(gpa, .{});
|
||||
defer block.deinit(gpa);
|
||||
|
||||
var map2: Map = .init(gpa);
|
||||
defer map2.deinit();
|
||||
try map2.putPosixBlock(block.view());
|
||||
|
||||
try testing.expectEqualDeep(&[_][]const u8{ "FOO", "A", "ZIG_PROGRESS" }, map2.keys());
|
||||
try testing.expectEqualDeep(&[_][]const u8{ "BAR", "", "unchanged" }, map2.values());
|
||||
}
|
||||
|
||||
test "Map.putWindowsBlock" {
|
||||
if (native_os != .windows) return;
|
||||
|
||||
const gpa = testing.allocator;
|
||||
|
||||
var map: Map = .init(gpa);
|
||||
defer map.deinit();
|
||||
|
||||
try map.put("FOO", "BAR");
|
||||
try map.put("A", "");
|
||||
try map.put("", "B");
|
||||
try map.put("=B", "");
|
||||
try map.put("ZIG_PROGRESS", "unchanged");
|
||||
|
||||
const environ: [:0]u16 = try map.createBlockWindows(gpa);
|
||||
defer gpa.free(environ);
|
||||
const block = try map.createWindowsBlock(gpa, .{});
|
||||
defer block.deinit(gpa);
|
||||
|
||||
var map2 = try createMapWide(environ, gpa);
|
||||
var map2: Map = .init(gpa);
|
||||
defer map2.deinit();
|
||||
try map2.putWindowsBlock(block.view());
|
||||
|
||||
try testing.expectEqualDeep(&[_][]const u8{ "FOO", "A", "=B" }, map2.keys());
|
||||
try testing.expectEqualDeep(&[_][]const u8{ "BAR", "", "" }, map2.values());
|
||||
try testing.expectEqualDeep(&[_][]const u8{ "FOO", "A", "=B", "ZIG_PROGRESS" }, map2.keys());
|
||||
try testing.expectEqualDeep(&[_][]const u8{ "BAR", "", "", "unchanged" }, map2.values());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -90,15 +90,15 @@ fn _DllMainCRTStartup(
|
|||
fn wasm_freestanding_start() callconv(.c) void {
|
||||
// This is marked inline because for some reason LLVM in
|
||||
// release mode fails to inline it, and we want fewer call frames in stack traces.
|
||||
_ = @call(.always_inline, callMain, .{ {}, {} });
|
||||
_ = @call(.always_inline, callMain, .{ {}, std.process.Environ.Block.global });
|
||||
}
|
||||
|
||||
fn startWasi() callconv(.c) void {
|
||||
// The function call is marked inline because for some reason LLVM in
|
||||
// release mode fails to inline it, and we want fewer call frames in stack traces.
|
||||
switch (builtin.wasi_exec_model) {
|
||||
.reactor => _ = @call(.always_inline, callMain, .{ {}, {} }),
|
||||
.command => std.os.wasi.proc_exit(@call(.always_inline, callMain, .{ {}, {} })),
|
||||
.reactor => _ = @call(.always_inline, callMain, .{ {}, std.process.Environ.Block.global }),
|
||||
.command => std.os.wasi.proc_exit(@call(.always_inline, callMain, .{ {}, std.process.Environ.Block.global })),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -476,7 +476,7 @@ fn WinStartup() callconv(.withStackAlign(.c, 1)) noreturn {
|
|||
const cmd_line = std.os.windows.peb().ProcessParameters.CommandLine;
|
||||
const cmd_line_w = cmd_line.Buffer.?[0..@divExact(cmd_line.Length, 2)];
|
||||
|
||||
std.os.windows.ntdll.RtlExitUserProcess(callMain(cmd_line_w, {}));
|
||||
std.os.windows.ntdll.RtlExitUserProcess(callMain(cmd_line_w, .global));
|
||||
}
|
||||
|
||||
fn wWinMainCRTStartup() callconv(.withStackAlign(.c, 1)) noreturn {
|
||||
|
|
@ -620,13 +620,14 @@ fn expandStackSize(phdrs: []elf.Phdr) void {
|
|||
}
|
||||
|
||||
inline fn callMainWithArgs(argc: usize, argv: [*][*:0]u8, envp: [:null]?[*:0]u8) u8 {
|
||||
const env_block: std.process.Environ.Block = .{ .slice = envp };
|
||||
if (std.Options.debug_threaded_io) |t| {
|
||||
if (@sizeOf(std.Io.Threaded.Argv0) != 0) t.argv0.value = argv[0];
|
||||
t.environ = .{ .process_environ = .{ .block = envp } };
|
||||
t.environ = .{ .process_environ = .{ .block = env_block } };
|
||||
}
|
||||
std.Thread.maybeAttachSignalStack();
|
||||
std.debug.maybeEnableSegfaultHandler();
|
||||
return callMain(argv[0..argc], envp);
|
||||
return callMain(argv[0..argc], env_block);
|
||||
}
|
||||
|
||||
fn main(c_argc: c_int, c_argv: [*][*:0]c_char, c_envp: [*:null]?[*:0]c_char) callconv(.c) c_int {
|
||||
|
|
@ -648,7 +649,7 @@ fn main(c_argc: c_int, c_argv: [*][*:0]c_char, c_envp: [*:null]?[*:0]c_char) cal
|
|||
std.debug.maybeEnableSegfaultHandler();
|
||||
const cmd_line = std.os.windows.peb().ProcessParameters.CommandLine;
|
||||
const cmd_line_w = cmd_line.Buffer.?[0..@divExact(cmd_line.Length, 2)];
|
||||
return callMain(cmd_line_w, {});
|
||||
return callMain(cmd_line_w, .global);
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
|
|
@ -661,7 +662,7 @@ fn mainWithoutEnv(c_argc: c_int, c_argv: [*][*:0]c_char) callconv(.c) c_int {
|
|||
if (@sizeOf(std.Io.Threaded.Argv0) != 0) {
|
||||
if (std.Options.debug_threaded_io) |t| t.argv0.value = argv[0];
|
||||
}
|
||||
return callMain(argv, &.{});
|
||||
return callMain(argv, .empty);
|
||||
}
|
||||
|
||||
/// General error message for a malformed return type
|
||||
|
|
|
|||
|
|
@ -12,14 +12,10 @@ pub fn main(init: std.process.Init) !void {
|
|||
// containsUnempty
|
||||
{
|
||||
try std.testing.expect(try environ.containsUnempty(allocator, "FOO"));
|
||||
try std.testing.expect(!(try environ.containsUnempty(allocator, "FOO=")));
|
||||
try std.testing.expect(!(try environ.containsUnempty(allocator, "FO")));
|
||||
try std.testing.expect(!(try environ.containsUnempty(allocator, "FOOO")));
|
||||
if (builtin.os.tag == .windows) {
|
||||
try std.testing.expect(try environ.containsUnempty(allocator, "foo"));
|
||||
}
|
||||
try std.testing.expect(try environ.containsUnempty(allocator, "EQUALS"));
|
||||
try std.testing.expect(!(try environ.containsUnempty(allocator, "EQUALS=ABC")));
|
||||
try std.testing.expect(try environ.containsUnempty(allocator, "КИРиллИЦА"));
|
||||
if (builtin.os.tag == .windows) {
|
||||
try std.testing.expect(try environ.containsUnempty(allocator, "кирИЛЛица"));
|
||||
|
|
@ -35,14 +31,10 @@ pub fn main(init: std.process.Init) !void {
|
|||
// containsUnemptyConstant
|
||||
{
|
||||
try std.testing.expect(environ.containsUnemptyConstant("FOO"));
|
||||
try std.testing.expect(!environ.containsUnemptyConstant("FOO="));
|
||||
try std.testing.expect(!environ.containsUnemptyConstant("FO"));
|
||||
try std.testing.expect(!environ.containsUnemptyConstant("FOOO"));
|
||||
if (builtin.os.tag == .windows) {
|
||||
try std.testing.expect(environ.containsUnemptyConstant("foo"));
|
||||
}
|
||||
try std.testing.expect(environ.containsUnemptyConstant("EQUALS"));
|
||||
try std.testing.expect(!environ.containsUnemptyConstant("EQUALS=ABC"));
|
||||
try std.testing.expect(environ.containsUnemptyConstant("КИРиллИЦА"));
|
||||
if (builtin.os.tag == .windows) {
|
||||
try std.testing.expect(environ.containsUnemptyConstant("кирИЛЛица"));
|
||||
|
|
@ -58,14 +50,10 @@ pub fn main(init: std.process.Init) !void {
|
|||
// contains
|
||||
{
|
||||
try std.testing.expect(try environ.contains(allocator, "FOO"));
|
||||
try std.testing.expect(!(try environ.contains(allocator, "FOO=")));
|
||||
try std.testing.expect(!(try environ.contains(allocator, "FO")));
|
||||
try std.testing.expect(!(try environ.contains(allocator, "FOOO")));
|
||||
if (builtin.os.tag == .windows) {
|
||||
try std.testing.expect(try environ.contains(allocator, "foo"));
|
||||
}
|
||||
try std.testing.expect(try environ.contains(allocator, "EQUALS"));
|
||||
try std.testing.expect(!(try environ.contains(allocator, "EQUALS=ABC")));
|
||||
try std.testing.expect(try environ.contains(allocator, "КИРиллИЦА"));
|
||||
if (builtin.os.tag == .windows) {
|
||||
try std.testing.expect(try environ.contains(allocator, "кирИЛЛица"));
|
||||
|
|
@ -81,14 +69,10 @@ pub fn main(init: std.process.Init) !void {
|
|||
// containsConstant
|
||||
{
|
||||
try std.testing.expect(environ.containsConstant("FOO"));
|
||||
try std.testing.expect(!environ.containsConstant("FOO="));
|
||||
try std.testing.expect(!environ.containsConstant("FO"));
|
||||
try std.testing.expect(!environ.containsConstant("FOOO"));
|
||||
if (builtin.os.tag == .windows) {
|
||||
try std.testing.expect(environ.containsConstant("foo"));
|
||||
}
|
||||
try std.testing.expect(environ.containsConstant("EQUALS"));
|
||||
try std.testing.expect(!environ.containsConstant("EQUALS=ABC"));
|
||||
try std.testing.expect(environ.containsConstant("КИРиллИЦА"));
|
||||
if (builtin.os.tag == .windows) {
|
||||
try std.testing.expect(environ.containsConstant("кирИЛЛица"));
|
||||
|
|
@ -104,14 +88,10 @@ pub fn main(init: std.process.Init) !void {
|
|||
// getAlloc
|
||||
{
|
||||
try std.testing.expectEqualSlices(u8, "123", try environ.getAlloc(arena, "FOO"));
|
||||
try std.testing.expectError(error.EnvironmentVariableMissing, environ.getAlloc(arena, "FOO="));
|
||||
try std.testing.expectError(error.EnvironmentVariableMissing, environ.getAlloc(arena, "FO"));
|
||||
try std.testing.expectError(error.EnvironmentVariableMissing, environ.getAlloc(arena, "FOOO"));
|
||||
if (builtin.os.tag == .windows) {
|
||||
try std.testing.expectEqualSlices(u8, "123", try environ.getAlloc(arena, "foo"));
|
||||
}
|
||||
try std.testing.expectEqualSlices(u8, "ABC=123", try environ.getAlloc(arena, "EQUALS"));
|
||||
try std.testing.expectError(error.EnvironmentVariableMissing, environ.getAlloc(arena, "EQUALS=ABC"));
|
||||
try std.testing.expectEqualSlices(u8, "non-ascii አማርኛ \u{10FFFF}", try environ.getAlloc(arena, "КИРиллИЦА"));
|
||||
if (builtin.os.tag == .windows) {
|
||||
try std.testing.expectEqualSlices(u8, "non-ascii አማርኛ \u{10FFFF}", try environ.getAlloc(arena, "кирИЛЛица"));
|
||||
|
|
@ -130,13 +110,10 @@ pub fn main(init: std.process.Init) !void {
|
|||
defer environ_map.deinit();
|
||||
|
||||
try std.testing.expectEqualSlices(u8, "123", environ_map.get("FOO").?);
|
||||
try std.testing.expectEqual(null, environ_map.get("FO"));
|
||||
try std.testing.expectEqual(null, environ_map.get("FOOO"));
|
||||
if (builtin.os.tag == .windows) {
|
||||
try std.testing.expectEqualSlices(u8, "123", environ_map.get("foo").?);
|
||||
}
|
||||
try std.testing.expectEqualSlices(u8, "ABC=123", environ_map.get("EQUALS").?);
|
||||
try std.testing.expectEqual(null, environ_map.get("EQUALS=ABC"));
|
||||
try std.testing.expectEqualSlices(u8, "non-ascii አማርኛ \u{10FFFF}", environ_map.get("КИРиллИЦА").?);
|
||||
if (builtin.os.tag == .windows) {
|
||||
try std.testing.expectEqualSlices(u8, "non-ascii አማርኛ \u{10FFFF}", environ_map.get("кирИЛЛица").?);
|
||||
|
|
|
|||
|
|
@ -125,7 +125,7 @@ fn spawnVerify(verify_path: [:0]const u16, cmd_line: [:0]const u16) !windows.DWO
|
|||
.lpReserved2 = null,
|
||||
.hStdInput = null,
|
||||
.hStdOutput = null,
|
||||
.hStdError = windows.GetStdHandle(windows.STD_ERROR_HANDLE) catch null,
|
||||
.hStdError = windows.peb().ProcessParameters.hStdError,
|
||||
};
|
||||
var proc_info: windows.PROCESS_INFORMATION = undefined;
|
||||
|
||||
|
|
@ -149,7 +149,12 @@ fn spawnVerify(verify_path: [:0]const u16, cmd_line: [:0]const u16) !windows.DWO
|
|||
break :spawn proc_info.hProcess;
|
||||
};
|
||||
defer windows.CloseHandle(child_proc);
|
||||
try windows.WaitForSingleObjectEx(child_proc, windows.INFINITE, false);
|
||||
const infinite_timeout: windows.LARGE_INTEGER = std.math.minInt(windows.LARGE_INTEGER);
|
||||
switch (windows.ntdll.NtWaitForSingleObject(child_proc, windows.FALSE, &infinite_timeout)) {
|
||||
windows.NTSTATUS.WAIT_0 => {},
|
||||
.TIMEOUT => return error.WaitTimeOut,
|
||||
else => |status| return windows.unexpectedStatus(status),
|
||||
}
|
||||
|
||||
var exit_code: windows.DWORD = undefined;
|
||||
if (windows.kernel32.GetExitCodeProcess(child_proc, &exit_code) == 0) {
|
||||
|
|
|
|||
|
|
@ -233,12 +233,13 @@ fn testExecWithCwdInner(gpa: Allocator, io: Io, command: []const u8, cwd: std.pr
|
|||
}
|
||||
|
||||
fn renameExe(dir: Io.Dir, io: Io, old_sub_path: []const u8, new_sub_path: []const u8) !void {
|
||||
var attempt: u5 = 0;
|
||||
var attempt: u5 = 10;
|
||||
while (true) break dir.rename(old_sub_path, dir, new_sub_path, io) catch |err| switch (err) {
|
||||
error.AccessDenied => {
|
||||
if (attempt == 13) return error.AccessDenied;
|
||||
if (attempt == 26) return error.AccessDenied;
|
||||
// give the kernel a chance to finish closing the executable handle
|
||||
_ = std.os.windows.kernel32.SleepEx(@as(u32, 1) << attempt >> 1, std.os.windows.FALSE);
|
||||
const interval = @as(std.os.windows.LARGE_INTEGER, -1) << attempt;
|
||||
_ = std.os.windows.ntdll.NtDelayExecution(std.os.windows.FALSE, &interval);
|
||||
attempt += 1;
|
||||
continue;
|
||||
},
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue