master
1const std = @import("std.zig");
2const builtin = @import("builtin");
3const fs = std.fs;
4const mem = std.mem;
5const math = std.math;
6const Allocator = mem.Allocator;
7const assert = std.debug.assert;
8const testing = std.testing;
9const native_os = builtin.os.tag;
10const posix = std.posix;
11const windows = std.os.windows;
12const unicode = std.unicode;
13
14pub const Child = @import("process/Child.zig");
15pub const abort = posix.abort;
16pub const exit = posix.exit;
17pub const changeCurDir = posix.chdir;
18pub const changeCurDirZ = posix.chdirZ;
19
20pub const GetCwdError = posix.GetCwdError;
21
22/// The result is a slice of `out_buffer`, from index `0`.
23/// On Windows, the result is encoded as [WTF-8](https://wtf-8.codeberg.page/).
24/// On other platforms, the result is an opaque sequence of bytes with no particular encoding.
25pub fn getCwd(out_buffer: []u8) GetCwdError![]u8 {
26 return posix.getcwd(out_buffer);
27}
28
29// Same as GetCwdError, minus error.NameTooLong + Allocator.Error
30pub const GetCwdAllocError = Allocator.Error || error{CurrentWorkingDirectoryUnlinked} || posix.UnexpectedError;
31
32/// Caller must free the returned memory.
33/// On Windows, the result is encoded as [WTF-8](https://wtf-8.codeberg.page/).
34/// On other platforms, the result is an opaque sequence of bytes with no particular encoding.
35pub fn getCwdAlloc(allocator: Allocator) GetCwdAllocError![]u8 {
36 // The use of max_path_bytes here is just a heuristic: most paths will fit
37 // in stack_buf, avoiding an extra allocation in the common case.
38 var stack_buf: [fs.max_path_bytes]u8 = undefined;
39 var heap_buf: ?[]u8 = null;
40 defer if (heap_buf) |buf| allocator.free(buf);
41
42 var current_buf: []u8 = &stack_buf;
43 while (true) {
44 if (posix.getcwd(current_buf)) |slice| {
45 return allocator.dupe(u8, slice);
46 } else |err| switch (err) {
47 error.NameTooLong => {
48 // The path is too long to fit in stack_buf. Allocate geometrically
49 // increasing buffers until we find one that works
50 const new_capacity = current_buf.len * 2;
51 if (heap_buf) |buf| allocator.free(buf);
52 current_buf = try allocator.alloc(u8, new_capacity);
53 heap_buf = current_buf;
54 },
55 else => |e| return e,
56 }
57 }
58}
59
60test getCwdAlloc {
61 if (native_os == .wasi) return error.SkipZigTest;
62
63 const cwd = try getCwdAlloc(testing.allocator);
64 testing.allocator.free(cwd);
65}
66
67pub const EnvMap = struct {
68 hash_map: HashMap,
69
70 const HashMap = std.HashMap(
71 []const u8,
72 []const u8,
73 EnvNameHashContext,
74 std.hash_map.default_max_load_percentage,
75 );
76
77 pub const Size = HashMap.Size;
78
79 pub const EnvNameHashContext = struct {
80 fn upcase(c: u21) u21 {
81 if (c <= std.math.maxInt(u16))
82 return windows.ntdll.RtlUpcaseUnicodeChar(@as(u16, @intCast(c)));
83 return c;
84 }
85
86 pub fn hash(self: @This(), s: []const u8) u64 {
87 _ = self;
88 if (native_os == .windows) {
89 var h = std.hash.Wyhash.init(0);
90 var it = unicode.Wtf8View.initUnchecked(s).iterator();
91 while (it.nextCodepoint()) |cp| {
92 const cp_upper = upcase(cp);
93 h.update(&[_]u8{
94 @as(u8, @intCast((cp_upper >> 16) & 0xff)),
95 @as(u8, @intCast((cp_upper >> 8) & 0xff)),
96 @as(u8, @intCast((cp_upper >> 0) & 0xff)),
97 });
98 }
99 return h.final();
100 }
101 return std.hash_map.hashString(s);
102 }
103
104 pub fn eql(self: @This(), a: []const u8, b: []const u8) bool {
105 _ = self;
106 if (native_os == .windows) {
107 var it_a = unicode.Wtf8View.initUnchecked(a).iterator();
108 var it_b = unicode.Wtf8View.initUnchecked(b).iterator();
109 while (true) {
110 const c_a = it_a.nextCodepoint() orelse break;
111 const c_b = it_b.nextCodepoint() orelse return false;
112 if (upcase(c_a) != upcase(c_b))
113 return false;
114 }
115 return if (it_b.nextCodepoint()) |_| false else true;
116 }
117 return std.hash_map.eqlString(a, b);
118 }
119 };
120
121 /// Create a EnvMap backed by a specific allocator.
122 /// That allocator will be used for both backing allocations
123 /// and string deduplication.
124 pub fn init(allocator: Allocator) EnvMap {
125 return EnvMap{ .hash_map = HashMap.init(allocator) };
126 }
127
128 /// Free the backing storage of the map, as well as all
129 /// of the stored keys and values.
130 pub fn deinit(self: *EnvMap) void {
131 var it = self.hash_map.iterator();
132 while (it.next()) |entry| {
133 self.free(entry.key_ptr.*);
134 self.free(entry.value_ptr.*);
135 }
136
137 self.hash_map.deinit();
138 }
139
140 /// Same as `put` but the key and value become owned by the EnvMap rather
141 /// than being copied.
142 /// If `putMove` fails, the ownership of key and value does not transfer.
143 /// On Windows `key` must be a valid [WTF-8](https://wtf-8.codeberg.page/) string.
144 pub fn putMove(self: *EnvMap, key: []u8, value: []u8) !void {
145 assert(unicode.wtf8ValidateSlice(key));
146 const get_or_put = try self.hash_map.getOrPut(key);
147 if (get_or_put.found_existing) {
148 self.free(get_or_put.key_ptr.*);
149 self.free(get_or_put.value_ptr.*);
150 get_or_put.key_ptr.* = key;
151 }
152 get_or_put.value_ptr.* = value;
153 }
154
155 /// `key` and `value` are copied into the EnvMap.
156 /// On Windows `key` must be a valid [WTF-8](https://wtf-8.codeberg.page/) string.
157 pub fn put(self: *EnvMap, key: []const u8, value: []const u8) !void {
158 assert(unicode.wtf8ValidateSlice(key));
159 const value_copy = try self.copy(value);
160 errdefer self.free(value_copy);
161 const get_or_put = try self.hash_map.getOrPut(key);
162 if (get_or_put.found_existing) {
163 self.free(get_or_put.value_ptr.*);
164 } else {
165 get_or_put.key_ptr.* = self.copy(key) catch |err| {
166 _ = self.hash_map.remove(key);
167 return err;
168 };
169 }
170 get_or_put.value_ptr.* = value_copy;
171 }
172
173 /// Find the address of the value associated with a key.
174 /// The returned pointer is invalidated if the map resizes.
175 /// On Windows `key` must be a valid [WTF-8](https://wtf-8.codeberg.page/) string.
176 pub fn getPtr(self: EnvMap, key: []const u8) ?*[]const u8 {
177 assert(unicode.wtf8ValidateSlice(key));
178 return self.hash_map.getPtr(key);
179 }
180
181 /// Return the map's copy of the value associated with
182 /// a key. The returned string is invalidated if this
183 /// key is removed from the map.
184 /// On Windows `key` must be a valid [WTF-8](https://wtf-8.codeberg.page/) string.
185 pub fn get(self: EnvMap, key: []const u8) ?[]const u8 {
186 assert(unicode.wtf8ValidateSlice(key));
187 return self.hash_map.get(key);
188 }
189
190 /// Removes the item from the map and frees its value.
191 /// This invalidates the value returned by get() for this key.
192 /// On Windows `key` must be a valid [WTF-8](https://wtf-8.codeberg.page/) string.
193 pub fn remove(self: *EnvMap, key: []const u8) void {
194 assert(unicode.wtf8ValidateSlice(key));
195 const kv = self.hash_map.fetchRemove(key) orelse return;
196 self.free(kv.key);
197 self.free(kv.value);
198 }
199
200 /// Returns the number of KV pairs stored in the map.
201 pub fn count(self: EnvMap) HashMap.Size {
202 return self.hash_map.count();
203 }
204
205 /// Returns an iterator over entries in the map.
206 pub fn iterator(self: *const EnvMap) HashMap.Iterator {
207 return self.hash_map.iterator();
208 }
209
210 /// Returns a full copy of `em` allocated with `gpa`, which is not necessarily
211 /// the same allocator used to allocate `em`.
212 pub fn clone(em: *const EnvMap, gpa: Allocator) Allocator.Error!EnvMap {
213 var new: EnvMap = .init(gpa);
214 errdefer new.deinit();
215 // Since we need to dupe the keys and values, the only way for error handling to not be a
216 // nightmare is to add keys to an empty map one-by-one. This could be avoided if this
217 // abstraction were a bit less... OOP-esque.
218 try new.hash_map.ensureUnusedCapacity(em.hash_map.count());
219 var it = em.hash_map.iterator();
220 while (it.next()) |entry| {
221 try new.put(entry.key_ptr.*, entry.value_ptr.*);
222 }
223 return new;
224 }
225
226 fn free(self: EnvMap, value: []const u8) void {
227 self.hash_map.allocator.free(value);
228 }
229
230 fn copy(self: EnvMap, value: []const u8) ![]u8 {
231 return self.hash_map.allocator.dupe(u8, value);
232 }
233};
234
235test EnvMap {
236 var env = EnvMap.init(testing.allocator);
237 defer env.deinit();
238
239 try env.put("SOMETHING_NEW", "hello");
240 try testing.expectEqualStrings("hello", env.get("SOMETHING_NEW").?);
241 try testing.expectEqual(@as(EnvMap.Size, 1), env.count());
242
243 // overwrite
244 try env.put("SOMETHING_NEW", "something");
245 try testing.expectEqualStrings("something", env.get("SOMETHING_NEW").?);
246 try testing.expectEqual(@as(EnvMap.Size, 1), env.count());
247
248 // a new longer name to test the Windows-specific conversion buffer
249 try env.put("SOMETHING_NEW_AND_LONGER", "1");
250 try testing.expectEqualStrings("1", env.get("SOMETHING_NEW_AND_LONGER").?);
251 try testing.expectEqual(@as(EnvMap.Size, 2), env.count());
252
253 // case insensitivity on Windows only
254 if (native_os == .windows) {
255 try testing.expectEqualStrings("1", env.get("something_New_aNd_LONGER").?);
256 } else {
257 try testing.expect(null == env.get("something_New_aNd_LONGER"));
258 }
259
260 var it = env.iterator();
261 var count: EnvMap.Size = 0;
262 while (it.next()) |entry| {
263 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.*);
264 try testing.expect(is_an_expected_name);
265 count += 1;
266 }
267 try testing.expectEqual(@as(EnvMap.Size, 2), count);
268
269 env.remove("SOMETHING_NEW");
270 try testing.expect(env.get("SOMETHING_NEW") == null);
271
272 try testing.expectEqual(@as(EnvMap.Size, 1), env.count());
273
274 if (native_os == .windows) {
275 // test Unicode case-insensitivity on Windows
276 try env.put("КИРиллИЦА", "something else");
277 try testing.expectEqualStrings("something else", env.get("кириллица").?);
278
279 // and WTF-8 that's not valid UTF-8
280 const wtf8_with_surrogate_pair = try unicode.wtf16LeToWtf8Alloc(testing.allocator, &[_]u16{
281 std.mem.nativeToLittle(u16, 0xD83D), // unpaired high surrogate
282 });
283 defer testing.allocator.free(wtf8_with_surrogate_pair);
284
285 try env.put(wtf8_with_surrogate_pair, wtf8_with_surrogate_pair);
286 try testing.expectEqualSlices(u8, wtf8_with_surrogate_pair, env.get(wtf8_with_surrogate_pair).?);
287 }
288}
289
290pub const GetEnvMapError = error{
291 OutOfMemory,
292 /// WASI-only. `environ_sizes_get` or `environ_get`
293 /// failed for an unexpected reason.
294 Unexpected,
295};
296
297/// Returns a snapshot of the environment variables of the current process.
298/// Any modifications to the resulting EnvMap will not be reflected in the environment, and
299/// likewise, any future modifications to the environment will not be reflected in the EnvMap.
300/// Caller owns resulting `EnvMap` and should call its `deinit` fn when done.
301pub fn getEnvMap(allocator: Allocator) GetEnvMapError!EnvMap {
302 var result = EnvMap.init(allocator);
303 errdefer result.deinit();
304
305 if (native_os == .windows) {
306 const ptr = windows.peb().ProcessParameters.Environment;
307
308 var i: usize = 0;
309 while (ptr[i] != 0) {
310 const key_start = i;
311
312 // There are some special environment variables that start with =,
313 // so we need a special case to not treat = as a key/value separator
314 // if it's the first character.
315 // https://devblogs.microsoft.com/oldnewthing/20100506-00/?p=14133
316 if (ptr[key_start] == '=') i += 1;
317
318 while (ptr[i] != 0 and ptr[i] != '=') : (i += 1) {}
319 const key_w = ptr[key_start..i];
320 const key = try unicode.wtf16LeToWtf8Alloc(allocator, key_w);
321 errdefer allocator.free(key);
322
323 if (ptr[i] == '=') i += 1;
324
325 const value_start = i;
326 while (ptr[i] != 0) : (i += 1) {}
327 const value_w = ptr[value_start..i];
328 const value = try unicode.wtf16LeToWtf8Alloc(allocator, value_w);
329 errdefer allocator.free(value);
330
331 i += 1; // skip over null byte
332
333 try result.putMove(key, value);
334 }
335 return result;
336 } else if (native_os == .wasi and !builtin.link_libc) {
337 var environ_count: usize = undefined;
338 var environ_buf_size: usize = undefined;
339
340 const environ_sizes_get_ret = std.os.wasi.environ_sizes_get(&environ_count, &environ_buf_size);
341 if (environ_sizes_get_ret != .SUCCESS) {
342 return posix.unexpectedErrno(environ_sizes_get_ret);
343 }
344
345 if (environ_count == 0) {
346 return result;
347 }
348
349 const environ = try allocator.alloc([*:0]u8, environ_count);
350 defer allocator.free(environ);
351 const environ_buf = try allocator.alloc(u8, environ_buf_size);
352 defer allocator.free(environ_buf);
353
354 const environ_get_ret = std.os.wasi.environ_get(environ.ptr, environ_buf.ptr);
355 if (environ_get_ret != .SUCCESS) {
356 return posix.unexpectedErrno(environ_get_ret);
357 }
358
359 for (environ) |env| {
360 const pair = mem.sliceTo(env, 0);
361 var parts = mem.splitScalar(u8, pair, '=');
362 const key = parts.first();
363 const value = parts.rest();
364 try result.put(key, value);
365 }
366 return result;
367 } else if (builtin.link_libc) {
368 var ptr = std.c.environ;
369 while (ptr[0]) |line| : (ptr += 1) {
370 var line_i: usize = 0;
371 while (line[line_i] != 0 and line[line_i] != '=') : (line_i += 1) {}
372 const key = line[0..line_i];
373
374 var end_i: usize = line_i;
375 while (line[end_i] != 0) : (end_i += 1) {}
376 const value = line[line_i + 1 .. end_i];
377
378 try result.put(key, value);
379 }
380 return result;
381 } else {
382 for (std.os.environ) |line| {
383 var line_i: usize = 0;
384 while (line[line_i] != 0 and line[line_i] != '=') : (line_i += 1) {}
385 const key = line[0..line_i];
386
387 var end_i: usize = line_i;
388 while (line[end_i] != 0) : (end_i += 1) {}
389 const value = line[line_i + 1 .. end_i];
390
391 try result.put(key, value);
392 }
393 return result;
394 }
395}
396
397test getEnvMap {
398 var env = try getEnvMap(testing.allocator);
399 defer env.deinit();
400}
401
402pub const GetEnvVarOwnedError = error{
403 OutOfMemory,
404 EnvironmentVariableNotFound,
405
406 /// On Windows, environment variable keys provided by the user must be valid WTF-8.
407 /// https://wtf-8.codeberg.page/
408 InvalidWtf8,
409};
410
411/// Caller must free returned memory.
412/// On Windows, if `key` is not valid [WTF-8](https://wtf-8.codeberg.page/),
413/// then `error.InvalidWtf8` is returned.
414/// On Windows, the value is encoded as [WTF-8](https://wtf-8.codeberg.page/).
415/// On other platforms, the value is an opaque sequence of bytes with no particular encoding.
416pub fn getEnvVarOwned(allocator: Allocator, key: []const u8) GetEnvVarOwnedError![]u8 {
417 if (native_os == .windows) {
418 const result_w = blk: {
419 var stack_alloc = std.heap.stackFallback(256 * @sizeOf(u16), allocator);
420 const stack_allocator = stack_alloc.get();
421 const key_w = try unicode.wtf8ToWtf16LeAllocZ(stack_allocator, key);
422 defer stack_allocator.free(key_w);
423
424 break :blk getenvW(key_w) orelse return error.EnvironmentVariableNotFound;
425 };
426 // wtf16LeToWtf8Alloc can only fail with OutOfMemory
427 return unicode.wtf16LeToWtf8Alloc(allocator, result_w);
428 } else if (native_os == .wasi and !builtin.link_libc) {
429 var envmap = getEnvMap(allocator) catch return error.OutOfMemory;
430 defer envmap.deinit();
431 const val = envmap.get(key) orelse return error.EnvironmentVariableNotFound;
432 return allocator.dupe(u8, val);
433 } else {
434 const result = posix.getenv(key) orelse return error.EnvironmentVariableNotFound;
435 return allocator.dupe(u8, result);
436 }
437}
438
439/// On Windows, `key` must be valid WTF-8.
440pub fn hasEnvVarConstant(comptime key: []const u8) bool {
441 if (native_os == .windows) {
442 const key_w = comptime unicode.wtf8ToWtf16LeStringLiteral(key);
443 return getenvW(key_w) != null;
444 } else if (native_os == .wasi and !builtin.link_libc) {
445 @compileError("hasEnvVarConstant is not supported for WASI without libc");
446 } else {
447 return posix.getenv(key) != null;
448 }
449}
450
451/// On Windows, `key` must be valid WTF-8.
452pub fn hasNonEmptyEnvVarConstant(comptime key: []const u8) bool {
453 if (native_os == .windows) {
454 const key_w = comptime unicode.wtf8ToWtf16LeStringLiteral(key);
455 const value = getenvW(key_w) orelse return false;
456 return value.len != 0;
457 } else if (native_os == .wasi and !builtin.link_libc) {
458 @compileError("hasNonEmptyEnvVarConstant is not supported for WASI without libc");
459 } else {
460 const value = posix.getenv(key) orelse return false;
461 return value.len != 0;
462 }
463}
464
465pub const ParseEnvVarIntError = std.fmt.ParseIntError || error{EnvironmentVariableNotFound};
466
467/// Parses an environment variable as an integer.
468///
469/// Since the key is comptime-known, no allocation is needed.
470///
471/// On Windows, `key` must be valid WTF-8.
472pub fn parseEnvVarInt(comptime key: []const u8, comptime I: type, base: u8) ParseEnvVarIntError!I {
473 if (native_os == .windows) {
474 const key_w = comptime std.unicode.wtf8ToWtf16LeStringLiteral(key);
475 const text = getenvW(key_w) orelse return error.EnvironmentVariableNotFound;
476 return std.fmt.parseIntWithGenericCharacter(I, u16, text, base);
477 } else if (native_os == .wasi and !builtin.link_libc) {
478 @compileError("parseEnvVarInt is not supported for WASI without libc");
479 } else {
480 const text = posix.getenv(key) orelse return error.EnvironmentVariableNotFound;
481 return std.fmt.parseInt(I, text, base);
482 }
483}
484
485pub const HasEnvVarError = error{
486 OutOfMemory,
487
488 /// On Windows, environment variable keys provided by the user must be valid WTF-8.
489 /// https://wtf-8.codeberg.page/
490 InvalidWtf8,
491};
492
493/// On Windows, if `key` is not valid [WTF-8](https://wtf-8.codeberg.page/),
494/// then `error.InvalidWtf8` is returned.
495pub fn hasEnvVar(allocator: Allocator, key: []const u8) HasEnvVarError!bool {
496 if (native_os == .windows) {
497 var stack_alloc = std.heap.stackFallback(256 * @sizeOf(u16), allocator);
498 const stack_allocator = stack_alloc.get();
499 const key_w = try unicode.wtf8ToWtf16LeAllocZ(stack_allocator, key);
500 defer stack_allocator.free(key_w);
501 return getenvW(key_w) != null;
502 } else if (native_os == .wasi and !builtin.link_libc) {
503 var envmap = getEnvMap(allocator) catch return error.OutOfMemory;
504 defer envmap.deinit();
505 return envmap.getPtr(key) != null;
506 } else {
507 return posix.getenv(key) != null;
508 }
509}
510
511/// On Windows, if `key` is not valid [WTF-8](https://wtf-8.codeberg.page/),
512/// then `error.InvalidWtf8` is returned.
513pub fn hasNonEmptyEnvVar(allocator: Allocator, key: []const u8) HasEnvVarError!bool {
514 if (native_os == .windows) {
515 var stack_alloc = std.heap.stackFallback(256 * @sizeOf(u16), allocator);
516 const stack_allocator = stack_alloc.get();
517 const key_w = try unicode.wtf8ToWtf16LeAllocZ(stack_allocator, key);
518 defer stack_allocator.free(key_w);
519 const value = getenvW(key_w) orelse return false;
520 return value.len != 0;
521 } else if (native_os == .wasi and !builtin.link_libc) {
522 var envmap = getEnvMap(allocator) catch return error.OutOfMemory;
523 defer envmap.deinit();
524 const value = envmap.getPtr(key) orelse return false;
525 return value.len != 0;
526 } else {
527 const value = posix.getenv(key) orelse return false;
528 return value.len != 0;
529 }
530}
531
532/// Windows-only. Get an environment variable with a null-terminated, WTF-16 encoded name.
533/// The returned slice points to memory in the PEB.
534///
535/// This function performs a Unicode-aware case-insensitive lookup using RtlEqualUnicodeString.
536///
537/// See also:
538/// * `std.posix.getenv`
539/// * `getEnvMap`
540/// * `getEnvVarOwned`
541/// * `hasEnvVarConstant`
542/// * `hasEnvVar`
543pub fn getenvW(key: [*:0]const u16) ?[:0]const u16 {
544 if (native_os != .windows) {
545 @compileError("Windows-only");
546 }
547 const key_slice = mem.sliceTo(key, 0);
548 // '=' anywhere but the start makes this an invalid environment variable name
549 if (key_slice.len > 0 and std.mem.indexOfScalar(u16, key_slice[1..], '=') != null) {
550 return null;
551 }
552 const ptr = windows.peb().ProcessParameters.Environment;
553 var i: usize = 0;
554 while (ptr[i] != 0) {
555 const key_value = mem.sliceTo(ptr[i..], 0);
556
557 // There are some special environment variables that start with =,
558 // so we need a special case to not treat = as a key/value separator
559 // if it's the first character.
560 // https://devblogs.microsoft.com/oldnewthing/20100506-00/?p=14133
561 const equal_search_start: usize = if (key_value[0] == '=') 1 else 0;
562 const equal_index = std.mem.indexOfScalarPos(u16, key_value, equal_search_start, '=') orelse {
563 // This is enforced by CreateProcess.
564 // If violated, CreateProcess will fail with INVALID_PARAMETER.
565 unreachable; // must contain a =
566 };
567
568 const this_key = key_value[0..equal_index];
569 if (windows.eqlIgnoreCaseWtf16(key_slice, this_key)) {
570 return key_value[equal_index + 1 ..];
571 }
572
573 // skip past the NUL terminator
574 i += key_value.len + 1;
575 }
576 return null;
577}
578
579test getEnvVarOwned {
580 try testing.expectError(
581 error.EnvironmentVariableNotFound,
582 getEnvVarOwned(std.testing.allocator, "BADENV"),
583 );
584}
585
586test hasEnvVarConstant {
587 if (native_os == .wasi and !builtin.link_libc) return error.SkipZigTest;
588
589 try testing.expect(!hasEnvVarConstant("BADENV"));
590}
591
592test hasEnvVar {
593 const has_env = try hasEnvVar(std.testing.allocator, "BADENV");
594 try testing.expect(!has_env);
595}
596
597pub const ArgIteratorPosix = struct {
598 index: usize,
599 count: usize,
600
601 pub const InitError = error{};
602
603 pub fn init() ArgIteratorPosix {
604 return ArgIteratorPosix{
605 .index = 0,
606 .count = std.os.argv.len,
607 };
608 }
609
610 pub fn next(self: *ArgIteratorPosix) ?[:0]const u8 {
611 if (self.index == self.count) return null;
612
613 const s = std.os.argv[self.index];
614 self.index += 1;
615 return mem.sliceTo(s, 0);
616 }
617
618 pub fn skip(self: *ArgIteratorPosix) bool {
619 if (self.index == self.count) return false;
620
621 self.index += 1;
622 return true;
623 }
624};
625
626pub const ArgIteratorWasi = struct {
627 allocator: Allocator,
628 index: usize,
629 args: [][:0]u8,
630
631 pub const InitError = error{OutOfMemory} || posix.UnexpectedError;
632
633 /// You must call deinit to free the internal buffer of the
634 /// iterator after you are done.
635 pub fn init(allocator: Allocator) InitError!ArgIteratorWasi {
636 const fetched_args = try ArgIteratorWasi.internalInit(allocator);
637 return ArgIteratorWasi{
638 .allocator = allocator,
639 .index = 0,
640 .args = fetched_args,
641 };
642 }
643
644 fn internalInit(allocator: Allocator) InitError![][:0]u8 {
645 var count: usize = undefined;
646 var buf_size: usize = undefined;
647
648 switch (std.os.wasi.args_sizes_get(&count, &buf_size)) {
649 .SUCCESS => {},
650 else => |err| return posix.unexpectedErrno(err),
651 }
652
653 if (count == 0) {
654 return &[_][:0]u8{};
655 }
656
657 const argv = try allocator.alloc([*:0]u8, count);
658 defer allocator.free(argv);
659
660 const argv_buf = try allocator.alloc(u8, buf_size);
661
662 switch (std.os.wasi.args_get(argv.ptr, argv_buf.ptr)) {
663 .SUCCESS => {},
664 else => |err| return posix.unexpectedErrno(err),
665 }
666
667 var result_args = try allocator.alloc([:0]u8, count);
668 var i: usize = 0;
669 while (i < count) : (i += 1) {
670 result_args[i] = mem.sliceTo(argv[i], 0);
671 }
672
673 return result_args;
674 }
675
676 pub fn next(self: *ArgIteratorWasi) ?[:0]const u8 {
677 if (self.index == self.args.len) return null;
678
679 const arg = self.args[self.index];
680 self.index += 1;
681 return arg;
682 }
683
684 pub fn skip(self: *ArgIteratorWasi) bool {
685 if (self.index == self.args.len) return false;
686
687 self.index += 1;
688 return true;
689 }
690
691 /// Call to free the internal buffer of the iterator.
692 pub fn deinit(self: *ArgIteratorWasi) void {
693 const last_item = self.args[self.args.len - 1];
694 const last_byte_addr = @intFromPtr(last_item.ptr) + last_item.len + 1; // null terminated
695 const first_item_ptr = self.args[0].ptr;
696 const len = last_byte_addr - @intFromPtr(first_item_ptr);
697 self.allocator.free(first_item_ptr[0..len]);
698 self.allocator.free(self.args);
699 }
700};
701
702/// Iterator that implements the Windows command-line parsing algorithm.
703/// The implementation is intended to be compatible with the post-2008 C runtime,
704/// but is *not* intended to be compatible with `CommandLineToArgvW` since
705/// `CommandLineToArgvW` uses the pre-2008 parsing rules.
706///
707/// This iterator faithfully implements the parsing behavior observed from the C runtime with
708/// one exception: if the command-line string is empty, the iterator will immediately complete
709/// without returning any arguments (whereas the C runtime will return a single argument
710/// representing the name of the current executable).
711///
712/// The essential parts of the algorithm are described in Microsoft's documentation:
713///
714/// - https://learn.microsoft.com/en-us/cpp/cpp/main-function-command-line-args?view=msvc-170#parsing-c-command-line-arguments
715///
716/// David Deley explains some additional undocumented quirks in great detail:
717///
718/// - https://daviddeley.com/autohotkey/parameters/parameters.htm#WINCRULES
719pub const ArgIteratorWindows = struct {
720 allocator: Allocator,
721 /// Encoded as WTF-16 LE.
722 cmd_line: []const u16,
723 index: usize = 0,
724 /// Owned by the iterator. Long enough to hold contiguous NUL-terminated slices
725 /// of each argument encoded as WTF-8.
726 buffer: []u8,
727 start: usize = 0,
728 end: usize = 0,
729
730 pub const InitError = error{OutOfMemory};
731
732 /// `cmd_line_w` *must* be a WTF16-LE-encoded string.
733 ///
734 /// The iterator stores and uses `cmd_line_w`, so its memory must be valid for
735 /// at least as long as the returned ArgIteratorWindows.
736 pub fn init(allocator: Allocator, cmd_line_w: []const u16) InitError!ArgIteratorWindows {
737 const wtf8_len = unicode.calcWtf8Len(cmd_line_w);
738
739 // This buffer must be large enough to contain contiguous NUL-terminated slices
740 // of each argument.
741 // - During parsing, the length of a parsed argument will always be equal to
742 // to less than its unparsed length
743 // - The first argument needs one extra byte of space allocated for its NUL
744 // terminator, but for each subsequent argument the necessary whitespace
745 // between arguments guarantees room for their NUL terminator(s).
746 const buffer = try allocator.alloc(u8, wtf8_len + 1);
747 errdefer allocator.free(buffer);
748
749 return .{
750 .allocator = allocator,
751 .cmd_line = cmd_line_w,
752 .buffer = buffer,
753 };
754 }
755
756 /// Returns the next argument and advances the iterator. Returns `null` if at the end of the
757 /// command-line string. The iterator owns the returned slice.
758 /// The result is encoded as [WTF-8](https://wtf-8.codeberg.page/).
759 pub fn next(self: *ArgIteratorWindows) ?[:0]const u8 {
760 return self.nextWithStrategy(next_strategy);
761 }
762
763 /// Skips the next argument and advances the iterator. Returns `true` if an argument was
764 /// skipped, `false` if at the end of the command-line string.
765 pub fn skip(self: *ArgIteratorWindows) bool {
766 return self.nextWithStrategy(skip_strategy);
767 }
768
769 const next_strategy = struct {
770 const T = ?[:0]const u8;
771
772 const eof = null;
773
774 /// Returns '\' if any backslashes are emitted, otherwise returns `last_emitted_code_unit`.
775 fn emitBackslashes(self: *ArgIteratorWindows, count: usize, last_emitted_code_unit: ?u16) ?u16 {
776 for (0..count) |_| {
777 self.buffer[self.end] = '\\';
778 self.end += 1;
779 }
780 return if (count != 0) '\\' else last_emitted_code_unit;
781 }
782
783 /// If `last_emitted_code_unit` and `code_unit` form a surrogate pair, then
784 /// the previously emitted high surrogate is overwritten by the codepoint encoded
785 /// by the surrogate pair, and `null` is returned.
786 /// Otherwise, `code_unit` is emitted and returned.
787 fn emitCharacter(self: *ArgIteratorWindows, code_unit: u16, last_emitted_code_unit: ?u16) ?u16 {
788 // Because we are emitting WTF-8, we need to
789 // check to see if we've emitted two consecutive surrogate
790 // codepoints that form a valid surrogate pair in order
791 // to ensure that we're always emitting well-formed WTF-8
792 // (https://wtf-8.codeberg.page/#concatenating).
793 //
794 // If we do have a valid surrogate pair, we need to emit
795 // the UTF-8 sequence for the codepoint that they encode
796 // instead of the WTF-8 encoding for the two surrogate pairs
797 // separately.
798 //
799 // This is relevant when dealing with a WTF-16 encoded
800 // command line like this:
801 // "<0xD801>"<0xDC37>
802 // which would get parsed and converted to WTF-8 as:
803 // <0xED><0xA0><0x81><0xED><0xB0><0xB7>
804 // but instead, we need to recognize the surrogate pair
805 // and emit the codepoint it encodes, which in this
806 // example is U+10437 (𐐷), which is encoded in UTF-8 as:
807 // <0xF0><0x90><0x90><0xB7>
808 if (last_emitted_code_unit != null and
809 std.unicode.utf16IsLowSurrogate(code_unit) and
810 std.unicode.utf16IsHighSurrogate(last_emitted_code_unit.?))
811 {
812 const codepoint = std.unicode.utf16DecodeSurrogatePair(&.{ last_emitted_code_unit.?, code_unit }) catch unreachable;
813
814 // Unpaired surrogate is 3 bytes long
815 const dest = self.buffer[self.end - 3 ..];
816 const len = unicode.utf8Encode(codepoint, dest) catch unreachable;
817 // All codepoints that require a surrogate pair (> U+FFFF) are encoded as 4 bytes
818 assert(len == 4);
819 self.end += 1;
820 return null;
821 }
822
823 const wtf8_len = std.unicode.wtf8Encode(code_unit, self.buffer[self.end..]) catch unreachable;
824 self.end += wtf8_len;
825 return code_unit;
826 }
827
828 fn yieldArg(self: *ArgIteratorWindows) [:0]const u8 {
829 self.buffer[self.end] = 0;
830 const arg = self.buffer[self.start..self.end :0];
831 self.end += 1;
832 self.start = self.end;
833 return arg;
834 }
835 };
836
837 const skip_strategy = struct {
838 const T = bool;
839
840 const eof = false;
841
842 fn emitBackslashes(_: *ArgIteratorWindows, _: usize, last_emitted_code_unit: ?u16) ?u16 {
843 return last_emitted_code_unit;
844 }
845
846 fn emitCharacter(_: *ArgIteratorWindows, _: u16, last_emitted_code_unit: ?u16) ?u16 {
847 return last_emitted_code_unit;
848 }
849
850 fn yieldArg(_: *ArgIteratorWindows) bool {
851 return true;
852 }
853 };
854
855 fn nextWithStrategy(self: *ArgIteratorWindows, comptime strategy: type) strategy.T {
856 var last_emitted_code_unit: ?u16 = null;
857 // The first argument (the executable name) uses different parsing rules.
858 if (self.index == 0) {
859 if (self.cmd_line.len == 0 or self.cmd_line[0] == 0) {
860 // Immediately complete the iterator.
861 // The C runtime would return the name of the current executable here.
862 return strategy.eof;
863 }
864
865 var inside_quotes = false;
866 while (true) : (self.index += 1) {
867 const char = if (self.index != self.cmd_line.len)
868 mem.littleToNative(u16, self.cmd_line[self.index])
869 else
870 0;
871 switch (char) {
872 0 => {
873 return strategy.yieldArg(self);
874 },
875 '"' => {
876 inside_quotes = !inside_quotes;
877 },
878 ' ', '\t' => {
879 if (inside_quotes) {
880 last_emitted_code_unit = strategy.emitCharacter(self, char, last_emitted_code_unit);
881 } else {
882 self.index += 1;
883 return strategy.yieldArg(self);
884 }
885 },
886 else => {
887 last_emitted_code_unit = strategy.emitCharacter(self, char, last_emitted_code_unit);
888 },
889 }
890 }
891 }
892
893 // Skip spaces and tabs. The iterator completes if we reach the end of the string here.
894 while (true) : (self.index += 1) {
895 const char = if (self.index != self.cmd_line.len)
896 mem.littleToNative(u16, self.cmd_line[self.index])
897 else
898 0;
899 switch (char) {
900 0 => return strategy.eof,
901 ' ', '\t' => continue,
902 else => break,
903 }
904 }
905
906 // Parsing rules for subsequent arguments:
907 //
908 // - The end of the string always terminates the current argument.
909 // - When not in 'inside_quotes' mode, a space or tab terminates the current argument.
910 // - 2n backslashes followed by a quote emit n backslashes (note: n can be zero).
911 // If in 'inside_quotes' and the quote is immediately followed by a second quote,
912 // one quote is emitted and the other is skipped, otherwise, the quote is skipped
913 // and 'inside_quotes' is toggled.
914 // - 2n + 1 backslashes followed by a quote emit n backslashes followed by a quote.
915 // - n backslashes not followed by a quote emit n backslashes.
916 var backslash_count: usize = 0;
917 var inside_quotes = false;
918 while (true) : (self.index += 1) {
919 const char = if (self.index != self.cmd_line.len)
920 mem.littleToNative(u16, self.cmd_line[self.index])
921 else
922 0;
923 switch (char) {
924 0 => {
925 last_emitted_code_unit = strategy.emitBackslashes(self, backslash_count, last_emitted_code_unit);
926 return strategy.yieldArg(self);
927 },
928 ' ', '\t' => {
929 last_emitted_code_unit = strategy.emitBackslashes(self, backslash_count, last_emitted_code_unit);
930 backslash_count = 0;
931 if (inside_quotes) {
932 last_emitted_code_unit = strategy.emitCharacter(self, char, last_emitted_code_unit);
933 } else return strategy.yieldArg(self);
934 },
935 '"' => {
936 const char_is_escaped_quote = backslash_count % 2 != 0;
937 last_emitted_code_unit = strategy.emitBackslashes(self, backslash_count / 2, last_emitted_code_unit);
938 backslash_count = 0;
939 if (char_is_escaped_quote) {
940 last_emitted_code_unit = strategy.emitCharacter(self, '"', last_emitted_code_unit);
941 } else {
942 if (inside_quotes and
943 self.index + 1 != self.cmd_line.len and
944 mem.littleToNative(u16, self.cmd_line[self.index + 1]) == '"')
945 {
946 last_emitted_code_unit = strategy.emitCharacter(self, '"', last_emitted_code_unit);
947 self.index += 1;
948 } else {
949 inside_quotes = !inside_quotes;
950 }
951 }
952 },
953 '\\' => {
954 backslash_count += 1;
955 },
956 else => {
957 last_emitted_code_unit = strategy.emitBackslashes(self, backslash_count, last_emitted_code_unit);
958 backslash_count = 0;
959 last_emitted_code_unit = strategy.emitCharacter(self, char, last_emitted_code_unit);
960 },
961 }
962 }
963 }
964
965 /// Frees the iterator's copy of the command-line string and all previously returned
966 /// argument slices.
967 pub fn deinit(self: *ArgIteratorWindows) void {
968 self.allocator.free(self.buffer);
969 }
970};
971
972/// Optional parameters for `ArgIteratorGeneral`
973pub const ArgIteratorGeneralOptions = struct {
974 comments: bool = false,
975 single_quotes: bool = false,
976};
977
978/// A general Iterator to parse a string into a set of arguments
979pub fn ArgIteratorGeneral(comptime options: ArgIteratorGeneralOptions) type {
980 return struct {
981 allocator: Allocator,
982 index: usize = 0,
983 cmd_line: []const u8,
984
985 /// Should the cmd_line field be free'd (using the allocator) on deinit()?
986 free_cmd_line_on_deinit: bool,
987
988 /// buffer MUST be long enough to hold the cmd_line plus a null terminator.
989 /// buffer will we free'd (using the allocator) on deinit()
990 buffer: []u8,
991 start: usize = 0,
992 end: usize = 0,
993
994 pub const Self = @This();
995
996 pub const InitError = error{OutOfMemory};
997
998 /// cmd_line_utf8 MUST remain valid and constant while using this instance
999 pub fn init(allocator: Allocator, cmd_line_utf8: []const u8) InitError!Self {
1000 const buffer = try allocator.alloc(u8, cmd_line_utf8.len + 1);
1001 errdefer allocator.free(buffer);
1002
1003 return Self{
1004 .allocator = allocator,
1005 .cmd_line = cmd_line_utf8,
1006 .free_cmd_line_on_deinit = false,
1007 .buffer = buffer,
1008 };
1009 }
1010
1011 /// cmd_line_utf8 will be free'd (with the allocator) on deinit()
1012 pub fn initTakeOwnership(allocator: Allocator, cmd_line_utf8: []const u8) InitError!Self {
1013 const buffer = try allocator.alloc(u8, cmd_line_utf8.len + 1);
1014 errdefer allocator.free(buffer);
1015
1016 return Self{
1017 .allocator = allocator,
1018 .cmd_line = cmd_line_utf8,
1019 .free_cmd_line_on_deinit = true,
1020 .buffer = buffer,
1021 };
1022 }
1023
1024 // Skips over whitespace in the cmd_line.
1025 // Returns false if the terminating sentinel is reached, true otherwise.
1026 // Also skips over comments (if supported).
1027 fn skipWhitespace(self: *Self) bool {
1028 while (true) : (self.index += 1) {
1029 const character = if (self.index != self.cmd_line.len) self.cmd_line[self.index] else 0;
1030 switch (character) {
1031 0 => return false,
1032 ' ', '\t', '\r', '\n' => continue,
1033 '#' => {
1034 if (options.comments) {
1035 while (true) : (self.index += 1) {
1036 switch (self.cmd_line[self.index]) {
1037 '\n' => break,
1038 0 => return false,
1039 else => continue,
1040 }
1041 }
1042 continue;
1043 } else {
1044 break;
1045 }
1046 },
1047 else => break,
1048 }
1049 }
1050 return true;
1051 }
1052
1053 pub fn skip(self: *Self) bool {
1054 if (!self.skipWhitespace()) {
1055 return false;
1056 }
1057
1058 var backslash_count: usize = 0;
1059 var in_quote = false;
1060 while (true) : (self.index += 1) {
1061 const character = if (self.index != self.cmd_line.len) self.cmd_line[self.index] else 0;
1062 switch (character) {
1063 0 => return true,
1064 '"', '\'' => {
1065 if (!options.single_quotes and character == '\'') {
1066 backslash_count = 0;
1067 continue;
1068 }
1069 const quote_is_real = backslash_count % 2 == 0;
1070 if (quote_is_real) {
1071 in_quote = !in_quote;
1072 }
1073 },
1074 '\\' => {
1075 backslash_count += 1;
1076 },
1077 ' ', '\t', '\r', '\n' => {
1078 if (!in_quote) {
1079 return true;
1080 }
1081 backslash_count = 0;
1082 },
1083 else => {
1084 backslash_count = 0;
1085 continue;
1086 },
1087 }
1088 }
1089 }
1090
1091 /// Returns a slice of the internal buffer that contains the next argument.
1092 /// Returns null when it reaches the end.
1093 pub fn next(self: *Self) ?[:0]const u8 {
1094 if (!self.skipWhitespace()) {
1095 return null;
1096 }
1097
1098 var backslash_count: usize = 0;
1099 var in_quote = false;
1100 while (true) : (self.index += 1) {
1101 const character = if (self.index != self.cmd_line.len) self.cmd_line[self.index] else 0;
1102 switch (character) {
1103 0 => {
1104 self.emitBackslashes(backslash_count);
1105 self.buffer[self.end] = 0;
1106 const token = self.buffer[self.start..self.end :0];
1107 self.end += 1;
1108 self.start = self.end;
1109 return token;
1110 },
1111 '"', '\'' => {
1112 if (!options.single_quotes and character == '\'') {
1113 self.emitBackslashes(backslash_count);
1114 backslash_count = 0;
1115 self.emitCharacter(character);
1116 continue;
1117 }
1118 const quote_is_real = backslash_count % 2 == 0;
1119 self.emitBackslashes(backslash_count / 2);
1120 backslash_count = 0;
1121
1122 if (quote_is_real) {
1123 in_quote = !in_quote;
1124 } else {
1125 self.emitCharacter('"');
1126 }
1127 },
1128 '\\' => {
1129 backslash_count += 1;
1130 },
1131 ' ', '\t', '\r', '\n' => {
1132 self.emitBackslashes(backslash_count);
1133 backslash_count = 0;
1134 if (in_quote) {
1135 self.emitCharacter(character);
1136 } else {
1137 self.buffer[self.end] = 0;
1138 const token = self.buffer[self.start..self.end :0];
1139 self.end += 1;
1140 self.start = self.end;
1141 return token;
1142 }
1143 },
1144 else => {
1145 self.emitBackslashes(backslash_count);
1146 backslash_count = 0;
1147 self.emitCharacter(character);
1148 },
1149 }
1150 }
1151 }
1152
1153 fn emitBackslashes(self: *Self, emit_count: usize) void {
1154 var i: usize = 0;
1155 while (i < emit_count) : (i += 1) {
1156 self.emitCharacter('\\');
1157 }
1158 }
1159
1160 fn emitCharacter(self: *Self, char: u8) void {
1161 self.buffer[self.end] = char;
1162 self.end += 1;
1163 }
1164
1165 /// Call to free the internal buffer of the iterator.
1166 pub fn deinit(self: *Self) void {
1167 self.allocator.free(self.buffer);
1168
1169 if (self.free_cmd_line_on_deinit) {
1170 self.allocator.free(self.cmd_line);
1171 }
1172 }
1173 };
1174}
1175
1176/// Cross-platform command line argument iterator.
1177pub const ArgIterator = struct {
1178 const InnerType = switch (native_os) {
1179 .windows => ArgIteratorWindows,
1180 .wasi => if (builtin.link_libc) ArgIteratorPosix else ArgIteratorWasi,
1181 else => ArgIteratorPosix,
1182 };
1183
1184 inner: InnerType,
1185
1186 /// Initialize the args iterator. Consider using initWithAllocator() instead
1187 /// for cross-platform compatibility.
1188 pub fn init() ArgIterator {
1189 if (native_os == .wasi) {
1190 @compileError("In WASI, use initWithAllocator instead.");
1191 }
1192 if (native_os == .windows) {
1193 @compileError("In Windows, use initWithAllocator instead.");
1194 }
1195
1196 return ArgIterator{ .inner = InnerType.init() };
1197 }
1198
1199 pub const InitError = InnerType.InitError;
1200
1201 /// You must deinitialize iterator's internal buffers by calling `deinit` when done.
1202 pub fn initWithAllocator(allocator: Allocator) InitError!ArgIterator {
1203 if (native_os == .wasi and !builtin.link_libc) {
1204 return ArgIterator{ .inner = try InnerType.init(allocator) };
1205 }
1206 if (native_os == .windows) {
1207 const cmd_line = std.os.windows.peb().ProcessParameters.CommandLine;
1208 const cmd_line_w = cmd_line.Buffer.?[0 .. cmd_line.Length / 2];
1209 return ArgIterator{ .inner = try InnerType.init(allocator, cmd_line_w) };
1210 }
1211
1212 return ArgIterator{ .inner = InnerType.init() };
1213 }
1214
1215 /// Get the next argument. Returns 'null' if we are at the end.
1216 /// Returned slice is pointing to the iterator's internal buffer.
1217 /// On Windows, the result is encoded as [WTF-8](https://wtf-8.codeberg.page/).
1218 /// On other platforms, the result is an opaque sequence of bytes with no particular encoding.
1219 pub fn next(self: *ArgIterator) ?([:0]const u8) {
1220 return self.inner.next();
1221 }
1222
1223 /// Parse past 1 argument without capturing it.
1224 /// Returns `true` if skipped an arg, `false` if we are at the end.
1225 pub fn skip(self: *ArgIterator) bool {
1226 return self.inner.skip();
1227 }
1228
1229 /// Call this to free the iterator's internal buffer if the iterator
1230 /// was created with `initWithAllocator` function.
1231 pub fn deinit(self: *ArgIterator) void {
1232 // Unless we're targeting WASI or Windows, this is a no-op.
1233 if (native_os == .wasi and !builtin.link_libc) {
1234 self.inner.deinit();
1235 }
1236
1237 if (native_os == .windows) {
1238 self.inner.deinit();
1239 }
1240 }
1241};
1242
1243/// Holds the command-line arguments, with the program name as the first entry.
1244/// Use argsWithAllocator() for cross-platform code.
1245pub fn args() ArgIterator {
1246 return ArgIterator.init();
1247}
1248
1249/// You must deinitialize iterator's internal buffers by calling `deinit` when done.
1250pub fn argsWithAllocator(allocator: Allocator) ArgIterator.InitError!ArgIterator {
1251 return ArgIterator.initWithAllocator(allocator);
1252}
1253
1254/// Caller must call argsFree on result.
1255/// On Windows, the result is encoded as [WTF-8](https://wtf-8.codeberg.page/).
1256/// On other platforms, the result is an opaque sequence of bytes with no particular encoding.
1257pub fn argsAlloc(allocator: Allocator) ![][:0]u8 {
1258 // TODO refactor to only make 1 allocation.
1259 var it = try argsWithAllocator(allocator);
1260 defer it.deinit();
1261
1262 var contents = std.array_list.Managed(u8).init(allocator);
1263 defer contents.deinit();
1264
1265 var slice_list = std.array_list.Managed(usize).init(allocator);
1266 defer slice_list.deinit();
1267
1268 while (it.next()) |arg| {
1269 try contents.appendSlice(arg[0 .. arg.len + 1]);
1270 try slice_list.append(arg.len);
1271 }
1272
1273 const contents_slice = contents.items;
1274 const slice_sizes = slice_list.items;
1275 const slice_list_bytes = try math.mul(usize, @sizeOf([]u8), slice_sizes.len);
1276 const total_bytes = try math.add(usize, slice_list_bytes, contents_slice.len);
1277 const buf = try allocator.alignedAlloc(u8, .of([]u8), total_bytes);
1278 errdefer allocator.free(buf);
1279
1280 const result_slice_list = mem.bytesAsSlice([:0]u8, buf[0..slice_list_bytes]);
1281 const result_contents = buf[slice_list_bytes..];
1282 @memcpy(result_contents[0..contents_slice.len], contents_slice);
1283
1284 var contents_index: usize = 0;
1285 for (slice_sizes, 0..) |len, i| {
1286 const new_index = contents_index + len;
1287 result_slice_list[i] = result_contents[contents_index..new_index :0];
1288 contents_index = new_index + 1;
1289 }
1290
1291 return result_slice_list;
1292}
1293
1294pub fn argsFree(allocator: Allocator, args_alloc: []const [:0]u8) void {
1295 var total_bytes: usize = 0;
1296 for (args_alloc) |arg| {
1297 total_bytes += @sizeOf([]u8) + arg.len + 1;
1298 }
1299 const unaligned_allocated_buf = @as([*]const u8, @ptrCast(args_alloc.ptr))[0..total_bytes];
1300 const aligned_allocated_buf: []align(@alignOf([]u8)) const u8 = @alignCast(unaligned_allocated_buf);
1301 return allocator.free(aligned_allocated_buf);
1302}
1303
1304test ArgIteratorWindows {
1305 const t = testArgIteratorWindows;
1306
1307 try t(
1308 \\"C:\Program Files\zig\zig.exe" run .\src\main.zig -target x86_64-windows-gnu -O ReleaseSafe -- --emoji=🗿 --eval="new Regex(\"Dwayne \\\"The Rock\\\" Johnson\")"
1309 , &.{
1310 \\C:\Program Files\zig\zig.exe
1311 ,
1312 \\run
1313 ,
1314 \\.\src\main.zig
1315 ,
1316 \\-target
1317 ,
1318 \\x86_64-windows-gnu
1319 ,
1320 \\-O
1321 ,
1322 \\ReleaseSafe
1323 ,
1324 \\--
1325 ,
1326 \\--emoji=🗿
1327 ,
1328 \\--eval=new Regex("Dwayne \"The Rock\" Johnson")
1329 ,
1330 });
1331
1332 // Empty
1333 try t("", &.{});
1334
1335 // Separators
1336 try t("aa bb cc", &.{ "aa", "bb", "cc" });
1337 try t("aa\tbb\tcc", &.{ "aa", "bb", "cc" });
1338 try t("aa\nbb\ncc", &.{"aa\nbb\ncc"});
1339 try t("aa\r\nbb\r\ncc", &.{"aa\r\nbb\r\ncc"});
1340 try t("aa\rbb\rcc", &.{"aa\rbb\rcc"});
1341 try t("aa\x07bb\x07cc", &.{"aa\x07bb\x07cc"});
1342 try t("aa\x7Fbb\x7Fcc", &.{"aa\x7Fbb\x7Fcc"});
1343 try t("aa🦎bb🦎cc", &.{"aa🦎bb🦎cc"});
1344
1345 // Leading/trailing whitespace
1346 try t(" ", &.{""});
1347 try t(" aa bb ", &.{ "", "aa", "bb" });
1348 try t("\t\t", &.{""});
1349 try t("\t\taa\t\tbb\t\t", &.{ "", "aa", "bb" });
1350 try t("\n\n", &.{"\n\n"});
1351 try t("\n\naa\n\nbb\n\n", &.{"\n\naa\n\nbb\n\n"});
1352
1353 // Executable name with quotes/backslashes
1354 try t("\"aa bb\tcc\ndd\"", &.{"aa bb\tcc\ndd"});
1355 try t("\"", &.{""});
1356 try t("\"\"", &.{""});
1357 try t("\"\"\"", &.{""});
1358 try t("\"\"\"\"", &.{""});
1359 try t("\"\"\"\"\"", &.{""});
1360 try t("aa\"bb\"cc\"dd", &.{"aabbccdd"});
1361 try t("aa\"bb cc\"dd", &.{"aabb ccdd"});
1362 try t("\"aa\\\"bb\"", &.{"aa\\bb"});
1363 try t("\"aa\\\\\"", &.{"aa\\\\"});
1364 try t("aa\\\"bb", &.{"aa\\bb"});
1365 try t("aa\\\\\"bb", &.{"aa\\\\bb"});
1366
1367 // Arguments with quotes/backslashes
1368 try t(". \"aa bb\tcc\ndd\"", &.{ ".", "aa bb\tcc\ndd" });
1369 try t(". aa\" \"bb\"\t\"cc\"\n\"dd\"", &.{ ".", "aa bb\tcc\ndd" });
1370 try t(". ", &.{"."});
1371 try t(". \"", &.{ ".", "" });
1372 try t(". \"\"", &.{ ".", "" });
1373 try t(". \"\"\"", &.{ ".", "\"" });
1374 try t(". \"\"\"\"", &.{ ".", "\"" });
1375 try t(". \"\"\"\"\"", &.{ ".", "\"\"" });
1376 try t(". \"\"\"\"\"\"", &.{ ".", "\"\"" });
1377 try t(". \" \"", &.{ ".", " " });
1378 try t(". \" \"\"", &.{ ".", " \"" });
1379 try t(". \" \"\"\"", &.{ ".", " \"" });
1380 try t(". \" \"\"\"\"", &.{ ".", " \"\"" });
1381 try t(". \" \"\"\"\"\"", &.{ ".", " \"\"" });
1382 try t(". \" \"\"\"\"\"\"", &.{ ".", " \"\"\"" });
1383 try t(". \\\"", &.{ ".", "\"" });
1384 try t(". \\\"\"", &.{ ".", "\"" });
1385 try t(". \\\"\"\"", &.{ ".", "\"" });
1386 try t(". \\\"\"\"\"", &.{ ".", "\"\"" });
1387 try t(". \\\"\"\"\"\"", &.{ ".", "\"\"" });
1388 try t(". \\\"\"\"\"\"\"", &.{ ".", "\"\"\"" });
1389 try t(". \" \\\"", &.{ ".", " \"" });
1390 try t(". \" \\\"\"", &.{ ".", " \"" });
1391 try t(". \" \\\"\"\"", &.{ ".", " \"\"" });
1392 try t(". \" \\\"\"\"\"", &.{ ".", " \"\"" });
1393 try t(". \" \\\"\"\"\"\"", &.{ ".", " \"\"\"" });
1394 try t(". \" \\\"\"\"\"\"\"", &.{ ".", " \"\"\"" });
1395 try t(". aa\\bb\\\\cc\\\\\\dd", &.{ ".", "aa\\bb\\\\cc\\\\\\dd" });
1396 try t(". \\\\\\\"aa bb\"", &.{ ".", "\\\"aa", "bb" });
1397 try t(". \\\\\\\\\"aa bb\"", &.{ ".", "\\\\aa bb" });
1398
1399 // From https://learn.microsoft.com/en-us/cpp/cpp/main-function-command-line-args#results-of-parsing-command-lines
1400 try t(
1401 \\foo.exe "abc" d e
1402 , &.{ "foo.exe", "abc", "d", "e" });
1403 try t(
1404 \\foo.exe a\\b d"e f"g h
1405 , &.{ "foo.exe", "a\\\\b", "de fg", "h" });
1406 try t(
1407 \\foo.exe a\\\"b c d
1408 , &.{ "foo.exe", "a\\\"b", "c", "d" });
1409 try t(
1410 \\foo.exe a\\\\"b c" d e
1411 , &.{ "foo.exe", "a\\\\b c", "d", "e" });
1412 try t(
1413 \\foo.exe a"b"" c d
1414 , &.{ "foo.exe", "ab\" c d" });
1415
1416 // From https://daviddeley.com/autohotkey/parameters/parameters.htm#WINCRULESEX
1417 try t("foo.exe CallMeIshmael", &.{ "foo.exe", "CallMeIshmael" });
1418 try t("foo.exe \"Call Me Ishmael\"", &.{ "foo.exe", "Call Me Ishmael" });
1419 try t("foo.exe Cal\"l Me I\"shmael", &.{ "foo.exe", "Call Me Ishmael" });
1420 try t("foo.exe CallMe\\\"Ishmael", &.{ "foo.exe", "CallMe\"Ishmael" });
1421 try t("foo.exe \"CallMe\\\"Ishmael\"", &.{ "foo.exe", "CallMe\"Ishmael" });
1422 try t("foo.exe \"Call Me Ishmael\\\\\"", &.{ "foo.exe", "Call Me Ishmael\\" });
1423 try t("foo.exe \"CallMe\\\\\\\"Ishmael\"", &.{ "foo.exe", "CallMe\\\"Ishmael" });
1424 try t("foo.exe a\\\\\\b", &.{ "foo.exe", "a\\\\\\b" });
1425 try t("foo.exe \"a\\\\\\b\"", &.{ "foo.exe", "a\\\\\\b" });
1426
1427 // Surrogate pair encoding of 𐐷 separated by quotes.
1428 // Encoded as WTF-16:
1429 // "<0xD801>"<0xDC37>
1430 // Encoded as WTF-8:
1431 // "<0xED><0xA0><0x81>"<0xED><0xB0><0xB7>
1432 // During parsing, the quotes drop out and the surrogate pair
1433 // should end up encoded as its normal UTF-8 representation.
1434 try t("foo.exe \"\xed\xa0\x81\"\xed\xb0\xb7", &.{ "foo.exe", "𐐷" });
1435}
1436
1437fn testArgIteratorWindows(cmd_line: []const u8, expected_args: []const []const u8) !void {
1438 const cmd_line_w = try unicode.wtf8ToWtf16LeAllocZ(testing.allocator, cmd_line);
1439 defer testing.allocator.free(cmd_line_w);
1440
1441 // next
1442 {
1443 var it = try ArgIteratorWindows.init(testing.allocator, cmd_line_w);
1444 defer it.deinit();
1445
1446 for (expected_args) |expected| {
1447 if (it.next()) |actual| {
1448 try testing.expectEqualStrings(expected, actual);
1449 } else {
1450 return error.TestUnexpectedResult;
1451 }
1452 }
1453 try testing.expect(it.next() == null);
1454 }
1455
1456 // skip
1457 {
1458 var it = try ArgIteratorWindows.init(testing.allocator, cmd_line_w);
1459 defer it.deinit();
1460
1461 for (0..expected_args.len) |_| {
1462 try testing.expect(it.skip());
1463 }
1464 try testing.expect(!it.skip());
1465 }
1466}
1467
1468test "general arg parsing" {
1469 try testGeneralCmdLine("a b\tc d", &.{ "a", "b", "c", "d" });
1470 try testGeneralCmdLine("\"abc\" d e", &.{ "abc", "d", "e" });
1471 try testGeneralCmdLine("a\\\\\\b d\"e f\"g h", &.{ "a\\\\\\b", "de fg", "h" });
1472 try testGeneralCmdLine("a\\\\\\\"b c d", &.{ "a\\\"b", "c", "d" });
1473 try testGeneralCmdLine("a\\\\\\\\\"b c\" d e", &.{ "a\\\\b c", "d", "e" });
1474 try testGeneralCmdLine("a b\tc \"d f", &.{ "a", "b", "c", "d f" });
1475 try testGeneralCmdLine("j k l\\", &.{ "j", "k", "l\\" });
1476 try testGeneralCmdLine("\"\" x y z\\\\", &.{ "", "x", "y", "z\\\\" });
1477
1478 try testGeneralCmdLine("\".\\..\\zig-cache\\build\" \"bin\\zig.exe\" \".\\..\" \".\\..\\zig-cache\" \"--help\"", &.{
1479 ".\\..\\zig-cache\\build",
1480 "bin\\zig.exe",
1481 ".\\..",
1482 ".\\..\\zig-cache",
1483 "--help",
1484 });
1485
1486 try testGeneralCmdLine(
1487 \\ 'foo' "bar"
1488 , &.{ "'foo'", "bar" });
1489}
1490
1491fn testGeneralCmdLine(input_cmd_line: []const u8, expected_args: []const []const u8) !void {
1492 var it = try ArgIteratorGeneral(.{}).init(std.testing.allocator, input_cmd_line);
1493 defer it.deinit();
1494 for (expected_args) |expected_arg| {
1495 const arg = it.next().?;
1496 try testing.expectEqualStrings(expected_arg, arg);
1497 }
1498 try testing.expect(it.next() == null);
1499}
1500
1501test "response file arg parsing" {
1502 try testResponseFileCmdLine(
1503 \\a b
1504 \\c d\
1505 , &.{ "a", "b", "c", "d\\" });
1506 try testResponseFileCmdLine("a b c d\\", &.{ "a", "b", "c", "d\\" });
1507
1508 try testResponseFileCmdLine(
1509 \\j
1510 \\ k l # this is a comment \\ \\\ \\\\ "none" "\\" "\\\"
1511 \\ "m" #another comment
1512 \\
1513 , &.{ "j", "k", "l", "m" });
1514
1515 try testResponseFileCmdLine(
1516 \\ "" q ""
1517 \\ "r s # t" "u\" v" #another comment
1518 \\
1519 , &.{ "", "q", "", "r s # t", "u\" v" });
1520
1521 try testResponseFileCmdLine(
1522 \\ -l"advapi32" a# b#c d#
1523 \\e\\\
1524 , &.{ "-ladvapi32", "a#", "b#c", "d#", "e\\\\\\" });
1525
1526 try testResponseFileCmdLine(
1527 \\ 'foo' "bar"
1528 , &.{ "foo", "bar" });
1529}
1530
1531fn testResponseFileCmdLine(input_cmd_line: []const u8, expected_args: []const []const u8) !void {
1532 var it = try ArgIteratorGeneral(.{ .comments = true, .single_quotes = true })
1533 .init(std.testing.allocator, input_cmd_line);
1534 defer it.deinit();
1535 for (expected_args) |expected_arg| {
1536 const arg = it.next().?;
1537 try testing.expectEqualStrings(expected_arg, arg);
1538 }
1539 try testing.expect(it.next() == null);
1540}
1541
1542pub const UserInfo = struct {
1543 uid: posix.uid_t,
1544 gid: posix.gid_t,
1545};
1546
1547/// POSIX function which gets a uid from username.
1548pub fn getUserInfo(name: []const u8) !UserInfo {
1549 return switch (native_os) {
1550 .linux,
1551 .driverkit,
1552 .ios,
1553 .maccatalyst,
1554 .macos,
1555 .tvos,
1556 .visionos,
1557 .watchos,
1558 .freebsd,
1559 .netbsd,
1560 .openbsd,
1561 .haiku,
1562 .illumos,
1563 .serenity,
1564 => posixGetUserInfo(name),
1565 else => @compileError("Unsupported OS"),
1566 };
1567}
1568
1569/// TODO this reads /etc/passwd. But sometimes the user/id mapping is in something else
1570/// like NIS, AD, etc. See `man nss` or look at an strace for `id myuser`.
1571pub fn posixGetUserInfo(name: []const u8) !UserInfo {
1572 const file = try std.fs.openFileAbsolute("/etc/passwd", .{});
1573 defer file.close();
1574 var buffer: [4096]u8 = undefined;
1575 var file_reader = file.reader(&buffer);
1576 return posixGetUserInfoPasswdStream(name, &file_reader.interface) catch |err| switch (err) {
1577 error.ReadFailed => return file_reader.err.?,
1578 error.EndOfStream => return error.UserNotFound,
1579 error.CorruptPasswordFile => return error.CorruptPasswordFile,
1580 };
1581}
1582
1583fn posixGetUserInfoPasswdStream(name: []const u8, reader: *std.Io.Reader) !UserInfo {
1584 const State = enum {
1585 start,
1586 wait_for_next_line,
1587 skip_password,
1588 read_user_id,
1589 read_group_id,
1590 };
1591
1592 var name_index: usize = 0;
1593 var uid: posix.uid_t = 0;
1594 var gid: posix.gid_t = 0;
1595
1596 sw: switch (State.start) {
1597 .start => switch (try reader.takeByte()) {
1598 ':' => {
1599 if (name_index == name.len) {
1600 continue :sw .skip_password;
1601 } else {
1602 continue :sw .wait_for_next_line;
1603 }
1604 },
1605 '\n' => return error.CorruptPasswordFile,
1606 else => |byte| {
1607 if (name_index == name.len or name[name_index] != byte) {
1608 continue :sw .wait_for_next_line;
1609 }
1610 name_index += 1;
1611 continue :sw .start;
1612 },
1613 },
1614 .wait_for_next_line => switch (try reader.takeByte()) {
1615 '\n' => {
1616 name_index = 0;
1617 continue :sw .start;
1618 },
1619 else => continue :sw .wait_for_next_line,
1620 },
1621 .skip_password => switch (try reader.takeByte()) {
1622 '\n' => return error.CorruptPasswordFile,
1623 ':' => {
1624 continue :sw .read_user_id;
1625 },
1626 else => continue :sw .skip_password,
1627 },
1628 .read_user_id => switch (try reader.takeByte()) {
1629 ':' => {
1630 continue :sw .read_group_id;
1631 },
1632 '\n' => return error.CorruptPasswordFile,
1633 else => |byte| {
1634 const digit = switch (byte) {
1635 '0'...'9' => byte - '0',
1636 else => return error.CorruptPasswordFile,
1637 };
1638 {
1639 const ov = @mulWithOverflow(uid, 10);
1640 if (ov[1] != 0) return error.CorruptPasswordFile;
1641 uid = ov[0];
1642 }
1643 {
1644 const ov = @addWithOverflow(uid, digit);
1645 if (ov[1] != 0) return error.CorruptPasswordFile;
1646 uid = ov[0];
1647 }
1648 continue :sw .read_user_id;
1649 },
1650 },
1651 .read_group_id => switch (try reader.takeByte()) {
1652 '\n', ':' => return .{
1653 .uid = uid,
1654 .gid = gid,
1655 },
1656 else => |byte| {
1657 const digit = switch (byte) {
1658 '0'...'9' => byte - '0',
1659 else => return error.CorruptPasswordFile,
1660 };
1661 {
1662 const ov = @mulWithOverflow(gid, 10);
1663 if (ov[1] != 0) return error.CorruptPasswordFile;
1664 gid = ov[0];
1665 }
1666 {
1667 const ov = @addWithOverflow(gid, digit);
1668 if (ov[1] != 0) return error.CorruptPasswordFile;
1669 gid = ov[0];
1670 }
1671 continue :sw .read_group_id;
1672 },
1673 },
1674 }
1675 comptime unreachable;
1676}
1677
1678pub fn getBaseAddress() usize {
1679 switch (native_os) {
1680 .linux => {
1681 const phdrs = std.posix.getSelfPhdrs();
1682 var base: usize = 0;
1683 for (phdrs) |phdr| switch (phdr.type) {
1684 .LOAD => return base + phdr.vaddr,
1685 .PHDR => base = @intFromPtr(phdrs.ptr) - phdr.vaddr,
1686 else => {},
1687 } else unreachable;
1688 },
1689 .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos => {
1690 return @intFromPtr(&std.c._mh_execute_header);
1691 },
1692 .windows => return @intFromPtr(windows.kernel32.GetModuleHandleW(null)),
1693 else => @compileError("Unsupported OS"),
1694 }
1695}
1696
1697/// Tells whether calling the `execv` or `execve` functions will be a compile error.
1698pub const can_execv = switch (native_os) {
1699 .windows, .haiku, .wasi => false,
1700 else => true,
1701};
1702
1703/// Tells whether spawning child processes is supported (e.g. via Child)
1704pub const can_spawn = switch (native_os) {
1705 .wasi, .ios, .tvos, .visionos, .watchos => false,
1706 else => true,
1707};
1708
1709pub const ExecvError = std.posix.ExecveError || error{OutOfMemory};
1710
1711/// Replaces the current process image with the executed process.
1712/// This function must allocate memory to add a null terminating bytes on path and each arg.
1713/// It must also convert to KEY=VALUE\0 format for environment variables, and include null
1714/// pointers after the args and after the environment variables.
1715/// `argv[0]` is the executable path.
1716/// This function also uses the PATH environment variable to get the full path to the executable.
1717/// Due to the heap-allocation, it is illegal to call this function in a fork() child.
1718/// For that use case, use the `std.posix` functions directly.
1719pub fn execv(allocator: Allocator, argv: []const []const u8) ExecvError {
1720 return execve(allocator, argv, null);
1721}
1722
1723/// Replaces the current process image with the executed process.
1724/// This function must allocate memory to add a null terminating bytes on path and each arg.
1725/// It must also convert to KEY=VALUE\0 format for environment variables, and include null
1726/// pointers after the args and after the environment variables.
1727/// `argv[0]` is the executable path.
1728/// This function also uses the PATH environment variable to get the full path to the executable.
1729/// Due to the heap-allocation, it is illegal to call this function in a fork() child.
1730/// For that use case, use the `std.posix` functions directly.
1731pub fn execve(
1732 allocator: Allocator,
1733 argv: []const []const u8,
1734 env_map: ?*const EnvMap,
1735) ExecvError {
1736 if (!can_execv) @compileError("The target OS does not support execv");
1737
1738 var arena_allocator = std.heap.ArenaAllocator.init(allocator);
1739 defer arena_allocator.deinit();
1740 const arena = arena_allocator.allocator();
1741
1742 const argv_buf = try arena.allocSentinel(?[*:0]const u8, argv.len, null);
1743 for (argv, 0..) |arg, i| argv_buf[i] = (try arena.dupeZ(u8, arg)).ptr;
1744
1745 const envp = m: {
1746 if (env_map) |m| {
1747 const envp_buf = try createNullDelimitedEnvMap(arena, m);
1748 break :m envp_buf.ptr;
1749 } else if (builtin.link_libc) {
1750 break :m std.c.environ;
1751 } else if (builtin.output_mode == .Exe) {
1752 // Then we have Zig start code and this works.
1753 // TODO type-safety for null-termination of `os.environ`.
1754 break :m @as([*:null]const ?[*:0]const u8, @ptrCast(std.os.environ.ptr));
1755 } else {
1756 // TODO come up with a solution for this.
1757 @compileError("missing std lib enhancement: std.process.execv implementation has no way to collect the environment variables to forward to the child process");
1758 }
1759 };
1760
1761 return posix.execvpeZ_expandArg0(.no_expand, argv_buf.ptr[0].?, argv_buf.ptr, envp);
1762}
1763
1764pub const TotalSystemMemoryError = error{
1765 UnknownTotalSystemMemory,
1766};
1767
1768/// Returns the total system memory, in bytes as a u64.
1769/// We return a u64 instead of usize due to PAE on ARM
1770/// and Linux's /proc/meminfo reporting more memory when
1771/// using QEMU user mode emulation.
1772pub fn totalSystemMemory() TotalSystemMemoryError!u64 {
1773 switch (native_os) {
1774 .linux => {
1775 var info: std.os.linux.Sysinfo = undefined;
1776 const result: usize = std.os.linux.sysinfo(&info);
1777 if (std.os.linux.errno(result) != .SUCCESS) {
1778 return error.UnknownTotalSystemMemory;
1779 }
1780 // Promote to u64 to avoid overflow on systems where info.totalram is a 32-bit usize
1781 return @as(u64, info.totalram) * info.mem_unit;
1782 },
1783 .freebsd => {
1784 var physmem: c_ulong = undefined;
1785 var len: usize = @sizeOf(c_ulong);
1786 posix.sysctlbynameZ("hw.physmem", &physmem, &len, null, 0) catch |err| switch (err) {
1787 error.UnknownName => unreachable,
1788 else => return error.UnknownTotalSystemMemory,
1789 };
1790 return @as(u64, @intCast(physmem));
1791 },
1792 // whole Darwin family
1793 .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos => {
1794 // "hw.memsize" returns uint64_t
1795 var physmem: u64 = undefined;
1796 var len: usize = @sizeOf(u64);
1797 posix.sysctlbynameZ("hw.memsize", &physmem, &len, null, 0) catch |err| switch (err) {
1798 error.PermissionDenied => unreachable, // only when setting values,
1799 error.SystemResources => unreachable, // memory already on the stack
1800 error.UnknownName => unreachable, // constant, known good value
1801 else => return error.UnknownTotalSystemMemory,
1802 };
1803 return physmem;
1804 },
1805 .openbsd => {
1806 const mib: [2]c_int = [_]c_int{
1807 posix.CTL.HW,
1808 posix.HW.PHYSMEM64,
1809 };
1810 var physmem: i64 = undefined;
1811 var len: usize = @sizeOf(@TypeOf(physmem));
1812 posix.sysctl(&mib, &physmem, &len, null, 0) catch |err| switch (err) {
1813 error.NameTooLong => unreachable, // constant, known good value
1814 error.PermissionDenied => unreachable, // only when setting values,
1815 error.SystemResources => unreachable, // memory already on the stack
1816 error.UnknownName => unreachable, // constant, known good value
1817 else => return error.UnknownTotalSystemMemory,
1818 };
1819 assert(physmem >= 0);
1820 return @as(u64, @bitCast(physmem));
1821 },
1822 .windows => {
1823 var sbi: windows.SYSTEM_BASIC_INFORMATION = undefined;
1824 const rc = windows.ntdll.NtQuerySystemInformation(
1825 .SystemBasicInformation,
1826 &sbi,
1827 @sizeOf(windows.SYSTEM_BASIC_INFORMATION),
1828 null,
1829 );
1830 if (rc != .SUCCESS) {
1831 return error.UnknownTotalSystemMemory;
1832 }
1833 return @as(u64, sbi.NumberOfPhysicalPages) * sbi.PageSize;
1834 },
1835 else => return error.UnknownTotalSystemMemory,
1836 }
1837}
1838
1839/// Indicate that we are now terminating with a successful exit code.
1840/// In debug builds, this is a no-op, so that the calling code's
1841/// cleanup mechanisms are tested and so that external tools that
1842/// check for resource leaks can be accurate. In release builds, this
1843/// calls exit(0), and does not return.
1844pub fn cleanExit() void {
1845 if (builtin.mode == .Debug) {
1846 return;
1847 } else {
1848 std.debug.lockStdErr();
1849 exit(0);
1850 }
1851}
1852
1853/// Raise the open file descriptor limit.
1854///
1855/// On some systems, this raises the limit before seeing ProcessFdQuotaExceeded
1856/// errors. On other systems, this does nothing.
1857pub fn raiseFileDescriptorLimit() void {
1858 const have_rlimit = posix.rlimit_resource != void;
1859 if (!have_rlimit) return;
1860
1861 var lim = posix.getrlimit(.NOFILE) catch return; // Oh well; we tried.
1862 if (native_os.isDarwin()) {
1863 // On Darwin, `NOFILE` is bounded by a hardcoded value `OPEN_MAX`.
1864 // According to the man pages for setrlimit():
1865 // setrlimit() now returns with errno set to EINVAL in places that historically succeeded.
1866 // It no longer accepts "rlim_cur = RLIM.INFINITY" for RLIM.NOFILE.
1867 // Use "rlim_cur = min(OPEN_MAX, rlim_max)".
1868 lim.max = @min(std.c.OPEN_MAX, lim.max);
1869 }
1870 if (lim.cur == lim.max) return;
1871
1872 // Do a binary search for the limit.
1873 var min: posix.rlim_t = lim.cur;
1874 var max: posix.rlim_t = 1 << 20;
1875 // But if there's a defined upper bound, don't search, just set it.
1876 if (lim.max != posix.RLIM.INFINITY) {
1877 min = lim.max;
1878 max = lim.max;
1879 }
1880
1881 while (true) {
1882 lim.cur = min + @divTrunc(max - min, 2); // on freebsd rlim_t is signed
1883 if (posix.setrlimit(.NOFILE, lim)) |_| {
1884 min = lim.cur;
1885 } else |_| {
1886 max = lim.cur;
1887 }
1888 if (min + 1 >= max) break;
1889 }
1890}
1891
1892test raiseFileDescriptorLimit {
1893 raiseFileDescriptorLimit();
1894}
1895
1896pub const CreateEnvironOptions = struct {
1897 /// `null` means to leave the `ZIG_PROGRESS` environment variable unmodified.
1898 /// If non-null, negative means to remove the environment variable, and >= 0
1899 /// means to provide it with the given integer.
1900 zig_progress_fd: ?i32 = null,
1901};
1902
1903/// Creates a null-delimited environment variable block in the format
1904/// expected by POSIX, from a hash map plus options.
1905pub fn createEnvironFromMap(
1906 arena: Allocator,
1907 map: *const EnvMap,
1908 options: CreateEnvironOptions,
1909) Allocator.Error![:null]?[*:0]u8 {
1910 const ZigProgressAction = enum { nothing, edit, delete, add };
1911 const zig_progress_action: ZigProgressAction = a: {
1912 const fd = options.zig_progress_fd orelse break :a .nothing;
1913 const contains = map.get("ZIG_PROGRESS") != null;
1914 if (fd >= 0) {
1915 break :a if (contains) .edit else .add;
1916 } else {
1917 if (contains) break :a .delete;
1918 }
1919 break :a .nothing;
1920 };
1921
1922 const envp_count: usize = c: {
1923 var count: usize = map.count();
1924 switch (zig_progress_action) {
1925 .add => count += 1,
1926 .delete => count -= 1,
1927 .nothing, .edit => {},
1928 }
1929 break :c count;
1930 };
1931
1932 const envp_buf = try arena.allocSentinel(?[*:0]u8, envp_count, null);
1933 var i: usize = 0;
1934
1935 if (zig_progress_action == .add) {
1936 envp_buf[i] = try std.fmt.allocPrintSentinel(arena, "ZIG_PROGRESS={d}", .{options.zig_progress_fd.?}, 0);
1937 i += 1;
1938 }
1939
1940 {
1941 var it = map.iterator();
1942 while (it.next()) |pair| {
1943 if (mem.eql(u8, pair.key_ptr.*, "ZIG_PROGRESS")) switch (zig_progress_action) {
1944 .add => unreachable,
1945 .delete => continue,
1946 .edit => {
1947 envp_buf[i] = try std.fmt.allocPrintSentinel(arena, "{s}={d}", .{
1948 pair.key_ptr.*, options.zig_progress_fd.?,
1949 }, 0);
1950 i += 1;
1951 continue;
1952 },
1953 .nothing => {},
1954 };
1955
1956 envp_buf[i] = try std.fmt.allocPrintSentinel(arena, "{s}={s}", .{ pair.key_ptr.*, pair.value_ptr.* }, 0);
1957 i += 1;
1958 }
1959 }
1960
1961 assert(i == envp_count);
1962 return envp_buf;
1963}
1964
1965/// Creates a null-delimited environment variable block in the format
1966/// expected by POSIX, from a hash map plus options.
1967pub fn createEnvironFromExisting(
1968 arena: Allocator,
1969 existing: [*:null]const ?[*:0]const u8,
1970 options: CreateEnvironOptions,
1971) Allocator.Error![:null]?[*:0]u8 {
1972 const existing_count, const contains_zig_progress = c: {
1973 var count: usize = 0;
1974 var contains = false;
1975 while (existing[count]) |line| : (count += 1) {
1976 contains = contains or mem.eql(u8, mem.sliceTo(line, '='), "ZIG_PROGRESS");
1977 }
1978 break :c .{ count, contains };
1979 };
1980 const ZigProgressAction = enum { nothing, edit, delete, add };
1981 const zig_progress_action: ZigProgressAction = a: {
1982 const fd = options.zig_progress_fd orelse break :a .nothing;
1983 if (fd >= 0) {
1984 break :a if (contains_zig_progress) .edit else .add;
1985 } else {
1986 if (contains_zig_progress) break :a .delete;
1987 }
1988 break :a .nothing;
1989 };
1990
1991 const envp_count: usize = c: {
1992 var count: usize = existing_count;
1993 switch (zig_progress_action) {
1994 .add => count += 1,
1995 .delete => count -= 1,
1996 .nothing, .edit => {},
1997 }
1998 break :c count;
1999 };
2000
2001 const envp_buf = try arena.allocSentinel(?[*:0]u8, envp_count, null);
2002 var i: usize = 0;
2003 var existing_index: usize = 0;
2004
2005 if (zig_progress_action == .add) {
2006 envp_buf[i] = try std.fmt.allocPrintSentinel(arena, "ZIG_PROGRESS={d}", .{options.zig_progress_fd.?}, 0);
2007 i += 1;
2008 }
2009
2010 while (existing[existing_index]) |line| : (existing_index += 1) {
2011 if (mem.eql(u8, mem.sliceTo(line, '='), "ZIG_PROGRESS")) switch (zig_progress_action) {
2012 .add => unreachable,
2013 .delete => continue,
2014 .edit => {
2015 envp_buf[i] = try std.fmt.allocPrintSentinel(arena, "ZIG_PROGRESS={d}", .{options.zig_progress_fd.?}, 0);
2016 i += 1;
2017 continue;
2018 },
2019 .nothing => {},
2020 };
2021 envp_buf[i] = try arena.dupeZ(u8, mem.span(line));
2022 i += 1;
2023 }
2024
2025 assert(i == envp_count);
2026 return envp_buf;
2027}
2028
2029pub fn createNullDelimitedEnvMap(arena: mem.Allocator, env_map: *const EnvMap) Allocator.Error![:null]?[*:0]u8 {
2030 return createEnvironFromMap(arena, env_map, .{});
2031}
2032
2033test createNullDelimitedEnvMap {
2034 const allocator = testing.allocator;
2035 var envmap = EnvMap.init(allocator);
2036 defer envmap.deinit();
2037
2038 try envmap.put("HOME", "/home/ifreund");
2039 try envmap.put("WAYLAND_DISPLAY", "wayland-1");
2040 try envmap.put("DISPLAY", ":1");
2041 try envmap.put("DEBUGINFOD_URLS", " ");
2042 try envmap.put("XCURSOR_SIZE", "24");
2043
2044 var arena = std.heap.ArenaAllocator.init(allocator);
2045 defer arena.deinit();
2046 const environ = try createNullDelimitedEnvMap(arena.allocator(), &envmap);
2047
2048 try testing.expectEqual(@as(usize, 5), environ.len);
2049
2050 inline for (.{
2051 "HOME=/home/ifreund",
2052 "WAYLAND_DISPLAY=wayland-1",
2053 "DISPLAY=:1",
2054 "DEBUGINFOD_URLS= ",
2055 "XCURSOR_SIZE=24",
2056 }) |target| {
2057 for (environ) |variable| {
2058 if (mem.eql(u8, mem.span(variable orelse continue), target)) break;
2059 } else {
2060 try testing.expect(false); // Environment variable not found
2061 }
2062 }
2063}
2064
2065/// Caller must free result.
2066pub fn createWindowsEnvBlock(allocator: mem.Allocator, env_map: *const EnvMap) ![]u16 {
2067 // count bytes needed
2068 const max_chars_needed = x: {
2069 // Only need 2 trailing NUL code units for an empty environment
2070 var max_chars_needed: usize = if (env_map.count() == 0) 2 else 1;
2071 var it = env_map.iterator();
2072 while (it.next()) |pair| {
2073 // +1 for '='
2074 // +1 for null byte
2075 max_chars_needed += pair.key_ptr.len + pair.value_ptr.len + 2;
2076 }
2077 break :x max_chars_needed;
2078 };
2079 const result = try allocator.alloc(u16, max_chars_needed);
2080 errdefer allocator.free(result);
2081
2082 var it = env_map.iterator();
2083 var i: usize = 0;
2084 while (it.next()) |pair| {
2085 i += try unicode.wtf8ToWtf16Le(result[i..], pair.key_ptr.*);
2086 result[i] = '=';
2087 i += 1;
2088 i += try unicode.wtf8ToWtf16Le(result[i..], pair.value_ptr.*);
2089 result[i] = 0;
2090 i += 1;
2091 }
2092 result[i] = 0;
2093 i += 1;
2094 // An empty environment is a special case that requires a redundant
2095 // NUL terminator. CreateProcess will read the second code unit even
2096 // though theoretically the first should be enough to recognize that the
2097 // environment is empty (see https://nullprogram.com/blog/2023/08/23/)
2098 if (env_map.count() == 0) {
2099 result[i] = 0;
2100 i += 1;
2101 }
2102 return try allocator.realloc(result, i);
2103}
2104
2105/// Logs an error and then terminates the process with exit code 1.
2106pub fn fatal(comptime format: []const u8, format_arguments: anytype) noreturn {
2107 std.log.err(format, format_arguments);
2108 exit(1);
2109}