| ... | @@ -6,15 +6,25 @@ const native_os = builtin.os.tag; | ... | @@ -6,15 +6,25 @@ const native_os = builtin.os.tag; |
| 6 | const std = @import("../std.zig"); | 6 | const std = @import("../std.zig"); |
| 7 | const Allocator = std.mem.Allocator; | 7 | const Allocator = std.mem.Allocator; |
| 8 | const assert = std.debug.assert; | 8 | const assert = std.debug.assert; |
| 9 | const testing = std.debug.testing; | 9 | const testing = std.testing; |
| 10 | const unicode = std.unicode; | 10 | const unicode = std.unicode; |
| 11 | const posix = std.posix; | 11 | const posix = std.posix; |
| 12 | const mem = std.mem; | 12 | const mem = std.mem; |
| 13 | | 13 | |
| | 14 | /// Unmodified, unprocessed data provided by the operating system. |
| | 15 | /// |
| | 16 | /// On Windows this might point to memory in the PEB. |
| | 17 | /// |
| | 18 | /// On WASI without libc, this is void because the environment has to be |
| | 19 | /// queried and heap-allocated at runtime. |
| 14 | block: Block, | 20 | block: Block, |
| 15 | | 21 | |
| 16 | pub const Block = switch (native_os) { | 22 | pub const Block = switch (native_os) { |
| 17 | .windows => []const u16, | 23 | .windows => []const u16, |
| | 24 | .wasi => switch (builtin.link_libc) { |
| | 25 | false => void, |
| | 26 | true => [:null]const ?[*:0]const u8, |
| | 27 | }, |
| 18 | else => [:null]const ?[*:0]const u8, | 28 | else => [:null]const ?[*:0]const u8, |
| 19 | }; | 29 | }; |
| 20 | | 30 | |
| ... | @@ -89,11 +99,11 @@ pub const Map = struct { | ... | @@ -89,11 +99,11 @@ pub const Map = struct { |
| 89 | self.* = undefined; | 99 | self.* = undefined; |
| 90 | } | 100 | } |
| 91 | | 101 | |
| 92 | pub fn keys(m: *Map) [][]const u8 { | 102 | pub fn keys(m: *const Map) [][]const u8 { |
| 93 | return m.array_hash_map.keys(); | 103 | return m.array_hash_map.keys(); |
| 94 | } | 104 | } |
| 95 | | 105 | |
| 96 | pub fn values(m: *Map) [][]const u8 { | 106 | pub fn values(m: *const Map) [][]const u8 { |
| 97 | return m.array_hash_map.values(); | 107 | return m.array_hash_map.values(); |
| 98 | } | 108 | } |
| 99 | | 109 | |
| ... | @@ -214,10 +224,10 @@ pub const Map = struct { | ... | @@ -214,10 +224,10 @@ pub const Map = struct { |
| 214 | | 224 | |
| 215 | /// Creates a null-delimited environment variable block in the format | 225 | /// Creates a null-delimited environment variable block in the format |
| 216 | /// expected by POSIX, from a hash map plus options. | 226 | /// expected by POSIX, from a hash map plus options. |
| 217 | pub fn createBlock( | 227 | pub fn createBlockPosix( |
| 218 | map: *const Map, | 228 | map: *const Map, |
| 219 | arena: Allocator, | 229 | arena: Allocator, |
| 220 | options: CreateBlockOptions, | 230 | options: CreateBlockPosixOptions, |
| 221 | ) Allocator.Error![:null]?[*:0]u8 { | 231 | ) Allocator.Error![:null]?[*:0]u8 { |
| 222 | const ZigProgressAction = enum { nothing, edit, delete, add }; | 232 | const ZigProgressAction = enum { nothing, edit, delete, add }; |
| 223 | const zig_progress_action: ZigProgressAction = a: { | 233 | const zig_progress_action: ZigProgressAction = a: { |
| ... | @@ -273,6 +283,46 @@ pub const Map = struct { | ... | @@ -273,6 +283,46 @@ pub const Map = struct { |
| 273 | assert(i == envp_count); | 283 | assert(i == envp_count); |
| 274 | return envp_buf; | 284 | return envp_buf; |
| 275 | } | 285 | } |
| | 286 | |
| | 287 | /// Caller must free result. |
| | 288 | pub fn createBlockWindows(map: *const Map, gpa: Allocator) Allocator.Error![]u16 { |
| | 289 | // count bytes needed |
| | 290 | const max_chars_needed = x: { |
| | 291 | // Only need 2 trailing NUL code units for an empty environment |
| | 292 | var max_chars_needed: usize = if (map.count() == 0) 2 else 1; |
| | 293 | var it = map.iterator(); |
| | 294 | while (it.next()) |pair| { |
| | 295 | // +1 for '=' |
| | 296 | // +1 for null byte |
| | 297 | max_chars_needed += pair.key_ptr.len + pair.value_ptr.len + 2; |
| | 298 | } |
| | 299 | break :x max_chars_needed; |
| | 300 | }; |
| | 301 | const result = try gpa.alloc(u16, max_chars_needed); |
| | 302 | errdefer gpa.free(result); |
| | 303 | |
| | 304 | var it = map.iterator(); |
| | 305 | var i: usize = 0; |
| | 306 | while (it.next()) |pair| { |
| | 307 | i += try unicode.wtf8ToWtf16Le(result[i..], pair.key_ptr.*); |
| | 308 | result[i] = '='; |
| | 309 | i += 1; |
| | 310 | i += try unicode.wtf8ToWtf16Le(result[i..], pair.value_ptr.*); |
| | 311 | result[i] = 0; |
| | 312 | i += 1; |
| | 313 | } |
| | 314 | result[i] = 0; |
| | 315 | i += 1; |
| | 316 | // An empty environment is a special case that requires a redundant |
| | 317 | // NUL terminator. CreateProcess will read the second code unit even |
| | 318 | // though theoretically the first should be enough to recognize that the |
| | 319 | // environment is empty (see https://nullprogram.com/blog/2023/08/23/) |
| | 320 | if (map.count() == 0) { |
| | 321 | result[i] = 0; |
| | 322 | i += 1; |
| | 323 | } |
| | 324 | return try gpa.realloc(result, i); |
| | 325 | } |
| 276 | }; | 326 | }; |
| 277 | | 327 | |
| 278 | pub const CreateMapError = error{ | 328 | pub const CreateMapError = error{ |
| ... | @@ -380,162 +430,131 @@ pub fn createMap(env: Environ, allocator: Allocator) CreateMapError!Map { | ... | @@ -380,162 +430,131 @@ pub fn createMap(env: Environ, allocator: Allocator) CreateMapError!Map { |
| 380 | } | 430 | } |
| 381 | } | 431 | } |
| 382 | | 432 | |
| 383 | test createMap { | 433 | pub const ContainsError = error{ |
| 384 | var env = try createMap(testing.allocator); | | |
| 385 | defer env.deinit(); | | |
| 386 | } | | |
| 387 | | | |
| 388 | pub const GetEnvVarOwnedError = error{ | | |
| 389 | OutOfMemory, | 434 | OutOfMemory, |
| 390 | EnvironmentVariableNotFound, | 435 | /// On Windows, environment variable keys provided by the user must be |
| 391 | | 436 | /// valid [WTF-8](https://wtf-8.codeberg.page/). This error is unreachable |
| 392 | /// On Windows, environment variable keys provided by the user must be valid WTF-8. | 437 | /// if the key is statically known to be valid. |
| 393 | /// https://wtf-8.codeberg.page/ | | |
| 394 | InvalidWtf8, | 438 | InvalidWtf8, |
| | 439 | /// WASI-only. `environ_sizes_get` or `environ_get` failed for an |
| | 440 | /// unexpected reason. |
| | 441 | Unexpected, |
| 395 | }; | 442 | }; |
| 396 | | 443 | |
| 397 | /// Caller must free returned memory. | | |
| 398 | /// On Windows, if `key` is not valid [WTF-8](https://wtf-8.codeberg.page/), | 444 | /// On Windows, if `key` is not valid [WTF-8](https://wtf-8.codeberg.page/), |
| 399 | /// then `error.InvalidWtf8` is returned. | 445 | /// then `error.InvalidWtf8` is returned. |
| 400 | /// On Windows, the value is encoded as [WTF-8](https://wtf-8.codeberg.page/). | 446 | /// |
| 401 | /// On other platforms, the value is an opaque sequence of bytes with no particular encoding. | 447 | /// See also: |
| 402 | pub fn getEnvVarOwned(allocator: Allocator, key: []const u8) GetEnvVarOwnedError![]u8 { | 448 | /// * `createMap` |
| 403 | if (native_os == .windows) { | 449 | /// * `containsConstant` |
| 404 | const result_w = blk: { | 450 | /// * `containsUnempty` |
| 405 | var stack_alloc = std.heap.stackFallback(256 * @sizeOf(u16), allocator); | 451 | pub fn contains(environ: Environ, gpa: Allocator, key: []const u8) ContainsError!bool { |
| 406 | const stack_allocator = stack_alloc.get(); | 452 | var map = try createMap(environ, gpa); |
| 407 | const key_w = try unicode.wtf8ToWtf16LeAllocZ(stack_allocator, key); | 453 | defer map.deinit(); |
| 408 | defer stack_allocator.free(key_w); | 454 | return map.contains(key); |
| | 455 | } |
| 409 | | 456 | |
| 410 | break :blk getenvW(key_w) orelse return error.EnvironmentVariableNotFound; | 457 | /// On Windows, if `key` is not valid [WTF-8](https://wtf-8.codeberg.page/), |
| 411 | }; | 458 | /// then `error.InvalidWtf8` is returned. |
| 412 | // wtf16LeToWtf8Alloc can only fail with OutOfMemory | 459 | /// |
| 413 | return unicode.wtf16LeToWtf8Alloc(allocator, result_w); | 460 | /// See also: |
| 414 | } else if (native_os == .wasi and !builtin.link_libc) { | 461 | /// * `createMap` |
| 415 | var envmap = createMap(allocator) catch return error.OutOfMemory; | 462 | /// * `containsUnemptyConstant` |
| 416 | defer envmap.deinit(); | 463 | /// * `contains` |
| 417 | const val = envmap.get(key) orelse return error.EnvironmentVariableNotFound; | 464 | pub fn containsUnempty(environ: Environ, gpa: Allocator, key: []const u8) ContainsError!bool { |
| 418 | return allocator.dupe(u8, val); | 465 | var map = try createMap(environ, gpa); |
| 419 | } else { | 466 | defer map.deinit(); |
| 420 | const result = posix.getenv(key) orelse return error.EnvironmentVariableNotFound; | 467 | const value = map.get(key) orelse return false; |
| 421 | return allocator.dupe(u8, result); | 468 | return value.len != 0; |
| 422 | } | | |
| 423 | } | 469 | } |
| 424 | | 470 | |
| 425 | /// On Windows, `key` must be valid WTF-8. | 471 | /// This function is unavailable on WASI without libc due to the memory |
| 426 | pub inline fn hasEnvVarConstant(comptime key: []const u8) bool { | 472 | /// allocation requirement. |
| | 473 | /// |
| | 474 | /// On Windows, `key` must be valid [WTF-8](https://wtf-8.codeberg.page/), |
| | 475 | /// |
| | 476 | /// See also: |
| | 477 | /// * `contains` |
| | 478 | /// * `containsUnemptyConstant` |
| | 479 | /// * `createMap` |
| | 480 | pub inline fn containsConstant(environ: Environ, comptime key: []const u8) bool { |
| 427 | if (native_os == .windows) { | 481 | if (native_os == .windows) { |
| 428 | const key_w = comptime unicode.wtf8ToWtf16LeStringLiteral(key); | 482 | const key_w = comptime unicode.wtf8ToWtf16LeStringLiteral(key); |
| 429 | return getenvW(key_w) != null; | 483 | return getWindows(environ, key_w) != null; |
| 430 | } else if (native_os == .wasi and !builtin.link_libc) { | | |
| 431 | return false; | | |
| 432 | } else { | 484 | } else { |
| 433 | return posix.getenv(key) != null; | 485 | return getPosix(environ, key) != null; |
| 434 | } | 486 | } |
| 435 | } | 487 | } |
| 436 | | 488 | |
| 437 | /// On Windows, `key` must be valid WTF-8. | 489 | /// This function is unavailable on WASI without libc due to the memory |
| 438 | pub inline fn hasNonEmptyEnvVarConstant(comptime key: []const u8) bool { | 490 | /// allocation requirement. |
| | 491 | /// |
| | 492 | /// On Windows, `key` must be valid [WTF-8](https://wtf-8.codeberg.page/), |
| | 493 | /// |
| | 494 | /// See also: |
| | 495 | /// * `containsUnempty` |
| | 496 | /// * `containsConstant` |
| | 497 | /// * `createMap` |
| | 498 | pub inline fn containsUnemptyConstant(environ: Environ, comptime key: []const u8) bool { |
| 439 | if (native_os == .windows) { | 499 | if (native_os == .windows) { |
| 440 | const key_w = comptime unicode.wtf8ToWtf16LeStringLiteral(key); | 500 | const key_w = comptime unicode.wtf8ToWtf16LeStringLiteral(key); |
| 441 | const value = getenvW(key_w) orelse return false; | 501 | const value = getWindows(environ, key_w) orelse return false; |
| 442 | return value.len != 0; | 502 | return value.len != 0; |
| 443 | } else if (native_os == .wasi and !builtin.link_libc) { | | |
| 444 | return false; | | |
| 445 | } else { | 503 | } else { |
| 446 | const value = posix.getenv(key) orelse return false; | 504 | const value = getPosix(environ, key) orelse return false; |
| 447 | return value.len != 0; | 505 | return value.len != 0; |
| 448 | } | 506 | } |
| 449 | } | 507 | } |
| 450 | | 508 | |
| 451 | pub const ParseIntError = std.fmt.ParseIntError || error{EnvironmentVariableNotFound}; | 509 | /// This function is unavailable on WASI without libc due to the memory |
| 452 | | 510 | /// allocation requirement. |
| 453 | /// Parses an environment variable as an integer. | | |
| 454 | /// | 511 | /// |
| 455 | /// On Windows, `key` must be valid WTF-8. | 512 | /// See also: |
| 456 | pub fn parseInt(io: std.Io, key: []const u8, comptime I: type, base: u8) ParseIntError!I { | 513 | /// * `getWindows` |
| 457 | const text = io.environ(key) orelse return error.EnvironmentVariableNotFound; | 514 | /// * `createMap` |
| 458 | return std.fmt.parseInt(I, text, base); | 515 | pub fn getPosix(environ: Environ, key: []const u8) ?[:0]const u8 { |
| 459 | } | 516 | if (mem.findScalar(u8, key, '=') != null) return null; |
| 460 | | 517 | for (environ.block) |opt_line| { |
| 461 | pub const HasEnvVarError = error{ | 518 | const line = opt_line.?; |
| 462 | OutOfMemory, | 519 | var line_i: usize = 0; |
| 463 | | 520 | while (line[line_i] != 0) : (line_i += 1) { |
| 464 | /// On Windows, environment variable keys provided by the user must be valid WTF-8. | 521 | if (line_i == key.len) break; |
| 465 | /// https://wtf-8.codeberg.page/ | 522 | if (line[line_i] != key[line_i]) break; |
| 466 | InvalidWtf8, | 523 | } |
| 467 | }; | 524 | if ((line_i != key.len) or (line[line_i] != '=')) continue; |
| 468 | | | |
| 469 | /// On Windows, if `key` is not valid [WTF-8](https://wtf-8.codeberg.page/), | | |
| 470 | /// then `error.InvalidWtf8` is returned. | | |
| 471 | pub fn hasEnvVar(allocator: Allocator, key: []const u8) HasEnvVarError!bool { | | |
| 472 | if (native_os == .windows) { | | |
| 473 | var stack_alloc = std.heap.stackFallback(256 * @sizeOf(u16), allocator); | | |
| 474 | const stack_allocator = stack_alloc.get(); | | |
| 475 | const key_w = try unicode.wtf8ToWtf16LeAllocZ(stack_allocator, key); | | |
| 476 | defer stack_allocator.free(key_w); | | |
| 477 | return getenvW(key_w) != null; | | |
| 478 | } else if (native_os == .wasi and !builtin.link_libc) { | | |
| 479 | var envmap = createMap(allocator) catch return error.OutOfMemory; | | |
| 480 | defer envmap.deinit(); | | |
| 481 | return envmap.getPtr(key) != null; | | |
| 482 | } else { | | |
| 483 | return posix.getenv(key) != null; | | |
| 484 | } | | |
| 485 | } | | |
| 486 | | 525 | |
| 487 | /// On Windows, if `key` is not valid [WTF-8](https://wtf-8.codeberg.page/), | 526 | return mem.sliceTo(line + line_i + 1, 0); |
| 488 | /// then `error.InvalidWtf8` is returned. | | |
| 489 | pub fn hasNonEmptyEnvVar(allocator: Allocator, key: []const u8) HasEnvVarError!bool { | | |
| 490 | if (native_os == .windows) { | | |
| 491 | var stack_alloc = std.heap.stackFallback(256 * @sizeOf(u16), allocator); | | |
| 492 | const stack_allocator = stack_alloc.get(); | | |
| 493 | const key_w = try unicode.wtf8ToWtf16LeAllocZ(stack_allocator, key); | | |
| 494 | defer stack_allocator.free(key_w); | | |
| 495 | const value = getenvW(key_w) orelse return false; | | |
| 496 | return value.len != 0; | | |
| 497 | } else if (native_os == .wasi and !builtin.link_libc) { | | |
| 498 | var envmap = createMap(allocator) catch return error.OutOfMemory; | | |
| 499 | defer envmap.deinit(); | | |
| 500 | const value = envmap.getPtr(key) orelse return false; | | |
| 501 | return value.len != 0; | | |
| 502 | } else { | | |
| 503 | const value = posix.getenv(key) orelse return false; | | |
| 504 | return value.len != 0; | | |
| 505 | } | 527 | } |
| | 528 | return null; |
| 506 | } | 529 | } |
| 507 | | 530 | |
| 508 | /// Windows-only. Get an environment variable with a null-terminated, WTF-16 encoded name. | 531 | /// Windows-only. Get an environment variable with a null-terminated, WTF-16 |
| 509 | /// The returned slice points to memory in the PEB. | 532 | /// encoded name. |
| 510 | /// | 533 | /// |
| 511 | /// This function performs a Unicode-aware case-insensitive lookup using RtlEqualUnicodeString. | 534 | /// This function performs a Unicode-aware case-insensitive lookup using |
| | 535 | /// RtlEqualUnicodeString. |
| 512 | /// | 536 | /// |
| 513 | /// See also: | 537 | /// See also: |
| 514 | /// * `std.posix.getenv` | | |
| 515 | /// * `createMap` | 538 | /// * `createMap` |
| 516 | /// * `getEnvVarOwned` | 539 | /// * `containsConstant` |
| 517 | /// * `hasEnvVarConstant` | 540 | /// * `contains` |
| 518 | /// * `hasEnvVar` | 541 | pub fn getWindows(environ: Environ, key: [*:0]const u16) ?[:0]const u16 { |
| 519 | pub fn getenvW(key: [*:0]const u16) ?[:0]const u16 { | 542 | comptime assert(native_os == .windows); |
| 520 | if (native_os != .windows) { | 543 | |
| 521 | @compileError("Windows-only"); | 544 | // '=' anywhere but the start makes this an invalid environment variable name. |
| 522 | } | | |
| 523 | const key_slice = mem.sliceTo(key, 0); | 545 | const key_slice = mem.sliceTo(key, 0); |
| 524 | // '=' anywhere but the start makes this an invalid environment variable name | 546 | if (key_slice.len > 0 and mem.findScalar(u16, key_slice[1..], '=') != null) return null; |
| 525 | if (key_slice.len > 0 and std.mem.findScalar(u16, key_slice[1..], '=') != null) { | 547 | |
| 526 | return null; | | |
| 527 | } | | |
| 528 | const ptr = std.os.windows.peb().ProcessParameters.Environment; | | |
| 529 | var i: usize = 0; | 548 | var i: usize = 0; |
| 530 | while (ptr[i] != 0) { | 549 | while (environ.block[i] != 0) { |
| 531 | const key_value = mem.sliceTo(ptr[i..], 0); | 550 | const key_value = mem.sliceTo(environ.block[i..], 0); |
| 532 | | 551 | |
| 533 | // There are some special environment variables that start with =, | 552 | // There are some special environment variables that start with =, |
| 534 | // so we need a special case to not treat = as a key/value separator | 553 | // so we need a special case to not treat = as a key/value separator |
| 535 | // if it's the first character. | 554 | // if it's the first character. |
| 536 | // https://devblogs.microsoft.com/oldnewthing/20100506-00/?p=14133 | 555 | // https://devblogs.microsoft.com/oldnewthing/20100506-00/?p=14133 |
| 537 | const equal_search_start: usize = if (key_value[0] == '=') 1 else 0; | 556 | const equal_search_start: usize = if (key_value[0] == '=') 1 else 0; |
| 538 | const equal_index = std.mem.findScalarPos(u16, key_value, equal_search_start, '=') orelse { | 557 | const equal_index = mem.findScalarPos(u16, key_value, equal_search_start, '=') orelse { |
| 539 | // This is enforced by CreateProcess. | 558 | // This is enforced by CreateProcess. |
| 540 | // If violated, CreateProcess will fail with INVALID_PARAMETER. | 559 | // If violated, CreateProcess will fail with INVALID_PARAMETER. |
| 541 | unreachable; // must contain a = | 560 | unreachable; // must contain a = |
| ... | @@ -552,25 +571,35 @@ pub fn getenvW(key: [*:0]const u16) ?[:0]const u16 { | ... | @@ -552,25 +571,35 @@ pub fn getenvW(key: [*:0]const u16) ?[:0]const u16 { |
| 552 | return null; | 571 | return null; |
| 553 | } | 572 | } |
| 554 | | 573 | |
| 555 | test getEnvVarOwned { | 574 | pub const GetAllocError = error{ |
| 556 | try testing.expectError( | 575 | OutOfMemory, |
| 557 | error.EnvironmentVariableNotFound, | 576 | EnvironmentVariableMissing, |
| 558 | getEnvVarOwned(std.testing.allocator, "BADENV"), | 577 | /// On Windows, environment variable keys provided by the user must be |
| 559 | ); | 578 | /// valid [WTF-8](https://wtf-8.codeberg.page/). This error is unreachable |
| 560 | } | 579 | /// if the key is statically known to be valid. |
| 561 | | 580 | InvalidWtf8, |
| 562 | test hasEnvVarConstant { | 581 | }; |
| 563 | if (native_os == .wasi and !builtin.link_libc) return error.SkipZigTest; | | |
| 564 | | | |
| 565 | try testing.expect(!hasEnvVarConstant("BADENV")); | | |
| 566 | } | | |
| 567 | | 582 | |
| 568 | test hasEnvVar { | 583 | /// Caller owns returned memory. |
| 569 | const has_env = try hasEnvVar(std.testing.allocator, "BADENV"); | 584 | /// |
| 570 | try testing.expect(!has_env); | 585 | /// On Windows: |
| | 586 | /// * If `key` is not valid [WTF-8](https://wtf-8.codeberg.page/), then |
| | 587 | /// `error.InvalidWtf8` is returned. |
| | 588 | /// * The returned value is encoded as [WTF-8](https://wtf-8.codeberg.page/). |
| | 589 | /// |
| | 590 | /// On other platforms, the value is an opaque sequence of bytes with no |
| | 591 | /// particular encoding. |
| | 592 | /// |
| | 593 | /// See also: |
| | 594 | /// * `createMap` |
| | 595 | pub fn getAlloc(environ: Environ, gpa: Allocator, key: []const u8) GetAllocError![]u8 { |
| | 596 | var map = createMap(environ, gpa) catch return error.OutOfMemory; |
| | 597 | defer map.deinit(); |
| | 598 | const val = map.get(key) orelse return error.EnvironmentVariableMissing; |
| | 599 | return gpa.dupe(u8, val); |
| 571 | } | 600 | } |
| 572 | | 601 | |
| 573 | pub const CreateBlockOptions = struct { | 602 | pub const CreateBlockPosixOptions = struct { |
| 574 | /// `null` means to leave the `ZIG_PROGRESS` environment variable unmodified. | 603 | /// `null` means to leave the `ZIG_PROGRESS` environment variable unmodified. |
| 575 | /// If non-null, negative means to remove the environment variable, and >= 0 | 604 | /// If non-null, negative means to remove the environment variable, and >= 0 |
| 576 | /// means to provide it with the given integer. | 605 | /// means to provide it with the given integer. |
| ... | @@ -579,7 +608,11 @@ pub const CreateBlockOptions = struct { | ... | @@ -579,7 +608,11 @@ pub const CreateBlockOptions = struct { |
| 579 | | 608 | |
| 580 | /// Creates a null-delimited environment variable block in the format expected | 609 | /// Creates a null-delimited environment variable block in the format expected |
| 581 | /// by POSIX, from a different one. | 610 | /// by POSIX, from a different one. |
| 582 | pub fn createBlock(existing: Environ, arena: Allocator, options: CreateBlockOptions) Allocator.Error![:null]?[*:0]u8 { | 611 | pub fn createBlockPosix( |
| | 612 | existing: Environ, |
| | 613 | arena: Allocator, |
| | 614 | options: CreateBlockPosixOptions, |
| | 615 | ) Allocator.Error![:null]?[*:0]u8 { |
| 583 | const contains_zig_progress = for (existing.block) |opt_line| { | 616 | const contains_zig_progress = for (existing.block) |opt_line| { |
| 584 | if (mem.eql(u8, mem.sliceTo(opt_line.?, '='), "ZIG_PROGRESS")) break true; | 617 | if (mem.eql(u8, mem.sliceTo(opt_line.?, '='), "ZIG_PROGRESS")) break true; |
| 585 | } else false; | 618 | } else false; |
| ... | @@ -646,7 +679,7 @@ test "Map.createBlock" { | ... | @@ -646,7 +679,7 @@ test "Map.createBlock" { |
| 646 | | 679 | |
| 647 | var arena = std.heap.ArenaAllocator.init(allocator); | 680 | var arena = std.heap.ArenaAllocator.init(allocator); |
| 648 | defer arena.deinit(); | 681 | defer arena.deinit(); |
| 649 | const environ = try envmap.createBlock(arena.allocator(), .{}); | 682 | const environ = try envmap.createBlockPosix(arena.allocator(), .{}); |
| 650 | | 683 | |
| 651 | try testing.expectEqual(@as(usize, 5), environ.len); | 684 | try testing.expectEqual(@as(usize, 5), environ.len); |
| 652 | | 685 | |
| ... | @@ -665,46 +698,6 @@ test "Map.createBlock" { | ... | @@ -665,46 +698,6 @@ test "Map.createBlock" { |
| 665 | } | 698 | } |
| 666 | } | 699 | } |
| 667 | | 700 | |
| 668 | /// Caller must free result. | | |
| 669 | pub fn createWindowsEnvBlock(allocator: mem.Allocator, env_map: *const Map) ![]u16 { | | |
| 670 | // count bytes needed | | |
| 671 | const max_chars_needed = x: { | | |
| 672 | // Only need 2 trailing NUL code units for an empty environment | | |
| 673 | var max_chars_needed: usize = if (env_map.count() == 0) 2 else 1; | | |
| 674 | var it = env_map.iterator(); | | |
| 675 | while (it.next()) |pair| { | | |
| 676 | // +1 for '=' | | |
| 677 | // +1 for null byte | | |
| 678 | max_chars_needed += pair.key_ptr.len + pair.value_ptr.len + 2; | | |
| 679 | } | | |
| 680 | break :x max_chars_needed; | | |
| 681 | }; | | |
| 682 | const result = try allocator.alloc(u16, max_chars_needed); | | |
| 683 | errdefer allocator.free(result); | | |
| 684 | | | |
| 685 | var it = env_map.iterator(); | | |
| 686 | var i: usize = 0; | | |
| 687 | while (it.next()) |pair| { | | |
| 688 | i += try unicode.wtf8ToWtf16Le(result[i..], pair.key_ptr.*); | | |
| 689 | result[i] = '='; | | |
| 690 | i += 1; | | |
| 691 | i += try unicode.wtf8ToWtf16Le(result[i..], pair.value_ptr.*); | | |
| 692 | result[i] = 0; | | |
| 693 | i += 1; | | |
| 694 | } | | |
| 695 | result[i] = 0; | | |
| 696 | i += 1; | | |
| 697 | // An empty environment is a special case that requires a redundant | | |
| 698 | // NUL terminator. CreateProcess will read the second code unit even | | |
| 699 | // though theoretically the first should be enough to recognize that the | | |
| 700 | // environment is empty (see https://nullprogram.com/blog/2023/08/23/) | | |
| 701 | if (env_map.count() == 0) { | | |
| 702 | result[i] = 0; | | |
| 703 | i += 1; | | |
| 704 | } | | |
| 705 | return try allocator.realloc(result, i); | | |
| 706 | } | | |
| 707 | | | |
| 708 | test Map { | 701 | test Map { |
| 709 | var env = Map.init(testing.allocator); | 702 | var env = Map.init(testing.allocator); |
| 710 | defer env.deinit(); | 703 | defer env.deinit(); |
| ... | @@ -733,13 +726,14 @@ test Map { | ... | @@ -733,13 +726,14 @@ test Map { |
| 733 | var it = env.iterator(); | 726 | var it = env.iterator(); |
| 734 | var count: Map.Size = 0; | 727 | var count: Map.Size = 0; |
| 735 | while (it.next()) |entry| { | 728 | while (it.next()) |entry| { |
| 736 | const is_an_expected_name = std.mem.eql(u8, "SOMETHING_NEW", entry.key_ptr.*) or std.mem.eql(u8, "SOMETHING_NEW_AND_LONGER", entry.key_ptr.*); | 729 | const is_an_expected_name = mem.eql(u8, "SOMETHING_NEW", entry.key_ptr.*) or mem.eql(u8, "SOMETHING_NEW_AND_LONGER", entry.key_ptr.*); |
| 737 | try testing.expect(is_an_expected_name); | 730 | try testing.expect(is_an_expected_name); |
| 738 | count += 1; | 731 | count += 1; |
| 739 | } | 732 | } |
| 740 | try testing.expectEqual(@as(Map.Size, 2), count); | 733 | try testing.expectEqual(@as(Map.Size, 2), count); |
| 741 | | 734 | |
| 742 | env.remove("SOMETHING_NEW"); | 735 | try testing.expect(env.swapRemove("SOMETHING_NEW")); |
| | 736 | try testing.expect(!env.swapRemove("SOMETHING_NEW")); |
| 743 | try testing.expect(env.get("SOMETHING_NEW") == null); | 737 | try testing.expect(env.get("SOMETHING_NEW") == null); |
| 744 | | 738 | |
| 745 | try testing.expectEqual(@as(Map.Size, 1), env.count()); | 739 | try testing.expectEqual(@as(Map.Size, 1), env.count()); |
| ... | @@ -751,7 +745,7 @@ test Map { | ... | @@ -751,7 +745,7 @@ test Map { |
| 751 | | 745 | |
| 752 | // and WTF-8 that's not valid UTF-8 | 746 | // and WTF-8 that's not valid UTF-8 |
| 753 | const wtf8_with_surrogate_pair = try unicode.wtf16LeToWtf8Alloc(testing.allocator, &[_]u16{ | 747 | const wtf8_with_surrogate_pair = try unicode.wtf16LeToWtf8Alloc(testing.allocator, &[_]u16{ |
| 754 | std.mem.nativeToLittle(u16, 0xD83D), // unpaired high surrogate | 748 | mem.nativeToLittle(u16, 0xD83D), // unpaired high surrogate |
| 755 | }); | 749 | }); |
| 756 | defer testing.allocator.free(wtf8_with_surrogate_pair); | 750 | defer testing.allocator.free(wtf8_with_surrogate_pair); |
| 757 | | 751 | |
| ... | @@ -759,3 +753,45 @@ test Map { | ... | @@ -759,3 +753,45 @@ test Map { |
| 759 | try testing.expectEqualSlices(u8, wtf8_with_surrogate_pair, env.get(wtf8_with_surrogate_pair).?); | 753 | try testing.expectEqualSlices(u8, wtf8_with_surrogate_pair, env.get(wtf8_with_surrogate_pair).?); |
| 760 | } | 754 | } |
| 761 | } | 755 | } |
| | 756 | |
| | 757 | test "convert from Environ to Map and back again" { |
| | 758 | const gpa = testing.allocator; |
| | 759 | |
| | 760 | var map: Map = .init(gpa); |
| | 761 | defer map.deinit(); |
| | 762 | try map.put("FOO", "BAR"); |
| | 763 | try map.put("A", ""); |
| | 764 | try map.put("", "B"); |
| | 765 | |
| | 766 | var arena_allocator = std.heap.ArenaAllocator.init(gpa); |
| | 767 | defer arena_allocator.deinit(); |
| | 768 | const arena = arena_allocator.allocator(); |
| | 769 | |
| | 770 | const environ: Environ = switch (native_os) { |
| | 771 | .windows => .{ .block = try map.createBlockWindows(arena) }, |
| | 772 | .wasi => if (!builtin.libc) return error.SkipZigTest, |
| | 773 | else => .{ .block = try map.createBlockPosix(arena, .{}) }, |
| | 774 | }; |
| | 775 | |
| | 776 | try testing.expectEqual(true, environ.contains(gpa, "FOO")); |
| | 777 | try testing.expectEqual(false, environ.contains(gpa, "BAR")); |
| | 778 | try testing.expectEqual(true, environ.contains(gpa, "A")); |
| | 779 | try testing.expectEqual(true, environ.containsConstant("A")); |
| | 780 | try testing.expectEqual(false, environ.containsUnempty(gpa, "A")); |
| | 781 | try testing.expectEqual(false, environ.containsUnemptyConstant("A")); |
| | 782 | try testing.expectEqual(true, environ.contains(gpa, "")); |
| | 783 | try testing.expectEqual(false, environ.contains(gpa, "B")); |
| | 784 | |
| | 785 | try testing.expectError(error.EnvironmentVariableMissing, environ.getAlloc(gpa, "BOGUS")); |
| | 786 | { |
| | 787 | const value = try environ.getAlloc(gpa, "FOO"); |
| | 788 | defer gpa.free(value); |
| | 789 | try testing.expectEqualStrings("BAR", value); |
| | 790 | } |
| | 791 | |
| | 792 | var map2 = try environ.createMap(gpa); |
| | 793 | defer map2.deinit(); |
| | 794 | |
| | 795 | try testing.expectEqualSlices([]const u8, map.keys(), map2.keys()); |
| | 796 | try testing.expectEqualSlices([]const u8, map.values(), map2.values()); |
| | 797 | } |