| ... | ... | @@ -6,15 +6,25 @@ const native_os = builtin.os.tag; |
| 6 | 6 | const std = @import("../std.zig"); |
| 7 | 7 | const Allocator = std.mem.Allocator; |
| 8 | 8 | const assert = std.debug.assert; |
| 9 | | const testing = std.debug.testing; |
| 9 | const testing = std.testing; |
| 10 | 10 | const unicode = std.unicode; |
| 11 | 11 | const posix = std.posix; |
| 12 | 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 | 20 | block: Block, |
| 15 | 21 | |
| 16 | 22 | pub const Block = switch (native_os) { |
| 17 | 23 | .windows => []const u16, |
| 24 | .wasi => switch (builtin.link_libc) { |
| 25 | false => void, |
| 26 | true => [:null]const ?[*:0]const u8, |
| 27 | }, |
| 18 | 28 | else => [:null]const ?[*:0]const u8, |
| 19 | 29 | }; |
| 20 | 30 | |
| ... | ... | @@ -89,11 +99,11 @@ pub const Map = struct { |
| 89 | 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 | 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 | 107 | return m.array_hash_map.values(); |
| 98 | 108 | } |
| 99 | 109 | |
| ... | ... | @@ -214,10 +224,10 @@ pub const Map = struct { |
| 214 | 224 | |
| 215 | 225 | /// Creates a null-delimited environment variable block in the format |
| 216 | 226 | /// expected by POSIX, from a hash map plus options. |
| 217 | | pub fn createBlock( |
| 227 | pub fn createBlockPosix( |
| 218 | 228 | map: *const Map, |
| 219 | 229 | arena: Allocator, |
| 220 | | options: CreateBlockOptions, |
| 230 | options: CreateBlockPosixOptions, |
| 221 | 231 | ) Allocator.Error![:null]?[*:0]u8 { |
| 222 | 232 | const ZigProgressAction = enum { nothing, edit, delete, add }; |
| 223 | 233 | const zig_progress_action: ZigProgressAction = a: { |
| ... | ... | @@ -273,6 +283,46 @@ pub const Map = struct { |
| 273 | 283 | assert(i == envp_count); |
| 274 | 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 | 328 | pub const CreateMapError = error{ |
| ... | ... | @@ -380,162 +430,131 @@ pub fn createMap(env: Environ, allocator: Allocator) CreateMapError!Map { |
| 380 | 430 | } |
| 381 | 431 | } |
| 382 | 432 | |
| 383 | | test createMap { |
| 384 | | var env = try createMap(testing.allocator); |
| 385 | | defer env.deinit(); |
| 386 | | } |
| 387 | | |
| 388 | | pub const GetEnvVarOwnedError = error{ |
| 433 | pub const ContainsError = error{ |
| 389 | 434 | OutOfMemory, |
| 390 | | EnvironmentVariableNotFound, |
| 391 | | |
| 392 | | /// On Windows, environment variable keys provided by the user must be valid WTF-8. |
| 393 | | /// https://wtf-8.codeberg.page/ |
| 435 | /// On Windows, environment variable keys provided by the user must be |
| 436 | /// valid [WTF-8](https://wtf-8.codeberg.page/). This error is unreachable |
| 437 | /// if the key is statically known to be valid. |
| 394 | 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 | 444 | /// On Windows, if `key` is not valid [WTF-8](https://wtf-8.codeberg.page/), |
| 399 | 445 | /// then `error.InvalidWtf8` is returned. |
| 400 | | /// On Windows, the value is encoded as [WTF-8](https://wtf-8.codeberg.page/). |
| 401 | | /// On other platforms, the value is an opaque sequence of bytes with no particular encoding. |
| 402 | | pub fn getEnvVarOwned(allocator: Allocator, key: []const u8) GetEnvVarOwnedError![]u8 { |
| 403 | | if (native_os == .windows) { |
| 404 | | const result_w = blk: { |
| 405 | | var stack_alloc = std.heap.stackFallback(256 * @sizeOf(u16), allocator); |
| 406 | | const stack_allocator = stack_alloc.get(); |
| 407 | | const key_w = try unicode.wtf8ToWtf16LeAllocZ(stack_allocator, key); |
| 408 | | defer stack_allocator.free(key_w); |
| 446 | /// |
| 447 | /// See also: |
| 448 | /// * `createMap` |
| 449 | /// * `containsConstant` |
| 450 | /// * `containsUnempty` |
| 451 | pub fn contains(environ: Environ, gpa: Allocator, key: []const u8) ContainsError!bool { |
| 452 | var map = try createMap(environ, gpa); |
| 453 | defer map.deinit(); |
| 454 | return map.contains(key); |
| 455 | } |
| 409 | 456 | |
| 410 | | break :blk getenvW(key_w) orelse return error.EnvironmentVariableNotFound; |
| 411 | | }; |
| 412 | | // wtf16LeToWtf8Alloc can only fail with OutOfMemory |
| 413 | | return unicode.wtf16LeToWtf8Alloc(allocator, result_w); |
| 414 | | } else if (native_os == .wasi and !builtin.link_libc) { |
| 415 | | var envmap = createMap(allocator) catch return error.OutOfMemory; |
| 416 | | defer envmap.deinit(); |
| 417 | | const val = envmap.get(key) orelse return error.EnvironmentVariableNotFound; |
| 418 | | return allocator.dupe(u8, val); |
| 419 | | } else { |
| 420 | | const result = posix.getenv(key) orelse return error.EnvironmentVariableNotFound; |
| 421 | | return allocator.dupe(u8, result); |
| 422 | | } |
| 457 | /// On Windows, if `key` is not valid [WTF-8](https://wtf-8.codeberg.page/), |
| 458 | /// then `error.InvalidWtf8` is returned. |
| 459 | /// |
| 460 | /// See also: |
| 461 | /// * `createMap` |
| 462 | /// * `containsUnemptyConstant` |
| 463 | /// * `contains` |
| 464 | pub fn containsUnempty(environ: Environ, gpa: Allocator, key: []const u8) ContainsError!bool { |
| 465 | var map = try createMap(environ, gpa); |
| 466 | defer map.deinit(); |
| 467 | const value = map.get(key) orelse return false; |
| 468 | return value.len != 0; |
| 423 | 469 | } |
| 424 | 470 | |
| 425 | | /// On Windows, `key` must be valid WTF-8. |
| 426 | | pub inline fn hasEnvVarConstant(comptime key: []const u8) bool { |
| 471 | /// This function is unavailable on WASI without libc due to the memory |
| 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 | 481 | if (native_os == .windows) { |
| 428 | 482 | const key_w = comptime unicode.wtf8ToWtf16LeStringLiteral(key); |
| 429 | | return getenvW(key_w) != null; |
| 430 | | } else if (native_os == .wasi and !builtin.link_libc) { |
| 431 | | return false; |
| 483 | return getWindows(environ, key_w) != null; |
| 432 | 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. |
| 438 | | pub inline fn hasNonEmptyEnvVarConstant(comptime key: []const u8) bool { |
| 489 | /// This function is unavailable on WASI without libc due to the memory |
| 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 | 499 | if (native_os == .windows) { |
| 440 | 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 | 502 | return value.len != 0; |
| 443 | | } else if (native_os == .wasi and !builtin.link_libc) { |
| 444 | | return false; |
| 445 | 503 | } else { |
| 446 | | const value = posix.getenv(key) orelse return false; |
| 504 | const value = getPosix(environ, key) orelse return false; |
| 447 | 505 | return value.len != 0; |
| 448 | 506 | } |
| 449 | 507 | } |
| 450 | 508 | |
| 451 | | pub const ParseIntError = std.fmt.ParseIntError || error{EnvironmentVariableNotFound}; |
| 452 | | |
| 453 | | /// Parses an environment variable as an integer. |
| 509 | /// This function is unavailable on WASI without libc due to the memory |
| 510 | /// allocation requirement. |
| 454 | 511 | /// |
| 455 | | /// On Windows, `key` must be valid WTF-8. |
| 456 | | pub fn parseInt(io: std.Io, key: []const u8, comptime I: type, base: u8) ParseIntError!I { |
| 457 | | const text = io.environ(key) orelse return error.EnvironmentVariableNotFound; |
| 458 | | return std.fmt.parseInt(I, text, base); |
| 459 | | } |
| 460 | | |
| 461 | | pub const HasEnvVarError = error{ |
| 462 | | OutOfMemory, |
| 463 | | |
| 464 | | /// On Windows, environment variable keys provided by the user must be valid WTF-8. |
| 465 | | /// https://wtf-8.codeberg.page/ |
| 466 | | InvalidWtf8, |
| 467 | | }; |
| 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 | | } |
| 512 | /// See also: |
| 513 | /// * `getWindows` |
| 514 | /// * `createMap` |
| 515 | pub fn getPosix(environ: Environ, key: []const u8) ?[:0]const u8 { |
| 516 | if (mem.findScalar(u8, key, '=') != null) return null; |
| 517 | for (environ.block) |opt_line| { |
| 518 | const line = opt_line.?; |
| 519 | var line_i: usize = 0; |
| 520 | while (line[line_i] != 0) : (line_i += 1) { |
| 521 | if (line_i == key.len) break; |
| 522 | if (line[line_i] != key[line_i]) break; |
| 523 | } |
| 524 | if ((line_i != key.len) or (line[line_i] != '=')) continue; |
| 486 | 525 | |
| 487 | | /// On Windows, if `key` is not valid [WTF-8](https://wtf-8.codeberg.page/), |
| 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; |
| 526 | return mem.sliceTo(line + line_i + 1, 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. |
| 509 | | /// The returned slice points to memory in the PEB. |
| 531 | /// Windows-only. Get an environment variable with a null-terminated, WTF-16 |
| 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 | 537 | /// See also: |
| 514 | | /// * `std.posix.getenv` |
| 515 | 538 | /// * `createMap` |
| 516 | | /// * `getEnvVarOwned` |
| 517 | | /// * `hasEnvVarConstant` |
| 518 | | /// * `hasEnvVar` |
| 519 | | pub fn getenvW(key: [*:0]const u16) ?[:0]const u16 { |
| 520 | | if (native_os != .windows) { |
| 521 | | @compileError("Windows-only"); |
| 522 | | } |
| 539 | /// * `containsConstant` |
| 540 | /// * `contains` |
| 541 | pub fn getWindows(environ: Environ, key: [*:0]const u16) ?[:0]const u16 { |
| 542 | comptime assert(native_os == .windows); |
| 543 | |
| 544 | // '=' anywhere but the start makes this an invalid environment variable name. |
| 523 | 545 | const key_slice = mem.sliceTo(key, 0); |
| 524 | | // '=' anywhere but the start makes this an invalid environment variable name |
| 525 | | if (key_slice.len > 0 and std.mem.findScalar(u16, key_slice[1..], '=') != null) { |
| 526 | | return null; |
| 527 | | } |
| 528 | | const ptr = std.os.windows.peb().ProcessParameters.Environment; |
| 546 | if (key_slice.len > 0 and mem.findScalar(u16, key_slice[1..], '=') != null) return null; |
| 547 | |
| 529 | 548 | var i: usize = 0; |
| 530 | | while (ptr[i] != 0) { |
| 531 | | const key_value = mem.sliceTo(ptr[i..], 0); |
| 549 | while (environ.block[i] != 0) { |
| 550 | const key_value = mem.sliceTo(environ.block[i..], 0); |
| 532 | 551 | |
| 533 | 552 | // There are some special environment variables that start with =, |
| 534 | 553 | // so we need a special case to not treat = as a key/value separator |
| 535 | 554 | // if it's the first character. |
| 536 | 555 | // https://devblogs.microsoft.com/oldnewthing/20100506-00/?p=14133 |
| 537 | 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 | 558 | // This is enforced by CreateProcess. |
| 540 | 559 | // If violated, CreateProcess will fail with INVALID_PARAMETER. |
| 541 | 560 | unreachable; // must contain a = |
| ... | ... | @@ -552,25 +571,35 @@ pub fn getenvW(key: [*:0]const u16) ?[:0]const u16 { |
| 552 | 571 | return null; |
| 553 | 572 | } |
| 554 | 573 | |
| 555 | | test getEnvVarOwned { |
| 556 | | try testing.expectError( |
| 557 | | error.EnvironmentVariableNotFound, |
| 558 | | getEnvVarOwned(std.testing.allocator, "BADENV"), |
| 559 | | ); |
| 560 | | } |
| 561 | | |
| 562 | | test hasEnvVarConstant { |
| 563 | | if (native_os == .wasi and !builtin.link_libc) return error.SkipZigTest; |
| 564 | | |
| 565 | | try testing.expect(!hasEnvVarConstant("BADENV")); |
| 566 | | } |
| 574 | pub const GetAllocError = error{ |
| 575 | OutOfMemory, |
| 576 | EnvironmentVariableMissing, |
| 577 | /// On Windows, environment variable keys provided by the user must be |
| 578 | /// valid [WTF-8](https://wtf-8.codeberg.page/). This error is unreachable |
| 579 | /// if the key is statically known to be valid. |
| 580 | InvalidWtf8, |
| 581 | }; |
| 567 | 582 | |
| 568 | | test hasEnvVar { |
| 569 | | const has_env = try hasEnvVar(std.testing.allocator, "BADENV"); |
| 570 | | try testing.expect(!has_env); |
| 583 | /// Caller owns returned memory. |
| 584 | /// |
| 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 | 603 | /// `null` means to leave the `ZIG_PROGRESS` environment variable unmodified. |
| 575 | 604 | /// If non-null, negative means to remove the environment variable, and >= 0 |
| 576 | 605 | /// means to provide it with the given integer. |
| ... | ... | @@ -579,7 +608,11 @@ pub const CreateBlockOptions = struct { |
| 579 | 608 | |
| 580 | 609 | /// Creates a null-delimited environment variable block in the format expected |
| 581 | 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 | 616 | const contains_zig_progress = for (existing.block) |opt_line| { |
| 584 | 617 | if (mem.eql(u8, mem.sliceTo(opt_line.?, '='), "ZIG_PROGRESS")) break true; |
| 585 | 618 | } else false; |
| ... | ... | @@ -646,7 +679,7 @@ test "Map.createBlock" { |
| 646 | 679 | |
| 647 | 680 | var arena = std.heap.ArenaAllocator.init(allocator); |
| 648 | 681 | defer arena.deinit(); |
| 649 | | const environ = try envmap.createBlock(arena.allocator(), .{}); |
| 682 | const environ = try envmap.createBlockPosix(arena.allocator(), .{}); |
| 650 | 683 | |
| 651 | 684 | try testing.expectEqual(@as(usize, 5), environ.len); |
| 652 | 685 | |
| ... | ... | @@ -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 | 701 | test Map { |
| 709 | 702 | var env = Map.init(testing.allocator); |
| 710 | 703 | defer env.deinit(); |
| ... | ... | @@ -733,13 +726,14 @@ test Map { |
| 733 | 726 | var it = env.iterator(); |
| 734 | 727 | var count: Map.Size = 0; |
| 735 | 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 | 730 | try testing.expect(is_an_expected_name); |
| 738 | 731 | count += 1; |
| 739 | 732 | } |
| 740 | 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 | 737 | try testing.expect(env.get("SOMETHING_NEW") == null); |
| 744 | 738 | |
| 745 | 739 | try testing.expectEqual(@as(Map.Size, 1), env.count()); |
| ... | ... | @@ -751,7 +745,7 @@ test Map { |
| 751 | 745 | |
| 752 | 746 | // and WTF-8 that's not valid UTF-8 |
| 753 | 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 | 750 | defer testing.allocator.free(wtf8_with_surrogate_pair); |
| 757 | 751 | |
| ... | ... | @@ -759,3 +753,45 @@ test Map { |
| 759 | 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 | } |