2019-05-24 18:27:18 -04:00
const std = @import ( " std.zig " ) ;
2021-10-04 23:47:27 -07:00
const builtin = @import ( " builtin " ) ;
2019-05-25 13:07:44 -04:00
const os = std . os ;
2019-05-26 23:35:26 -04:00
const fs = std . fs ;
2019-05-24 18:27:18 -04:00
const mem = std . mem ;
2019-05-26 13:17:34 -04:00
const math = std . math ;
2019-05-24 18:27:18 -04:00
const Allocator = mem . Allocator ;
const assert = std . debug . assert ;
const testing = std . testing ;
2020-12-26 13:50:26 -07:00
const child_process = @import ( " child_process.zig " ) ;
2019-05-24 18:27:18 -04:00
2023-03-02 22:56:52 -07:00
pub const Child = child_process . ChildProcess ;
2019-05-25 13:07:44 -04:00
pub const abort = os . abort ;
pub const exit = os . exit ;
2019-05-26 13:17:34 -04:00
pub const changeCurDir = os . chdir ;
pub const changeCurDirC = os . chdirC ;
2019-05-24 18:27:18 -04:00
2019-05-26 23:35:26 -04:00
/// The result is a slice of `out_buffer`, from index `0`.
2020-03-27 14:28:48 -05:00
pub fn getCwd ( out_buffer : [ ] u8 ) ! [ ] u8 {
2019-05-26 23:35:26 -04:00
return os . getcwd ( out_buffer ) ;
}
/// Caller must free the returned memory.
2021-10-29 00:37:25 +01:00
pub fn getCwdAlloc ( allocator : Allocator ) ! [ ] u8 {
2020-03-28 00:12:40 -05:00
// The use of MAX_PATH_BYTES here is just a heuristic: most paths will fit
// in stack_buf, avoiding an extra allocation in the common case.
var stack_buf : [ fs . MAX_PATH_BYTES ] u8 = undefined ;
var heap_buf : ? [ ] u8 = null ;
defer if ( heap_buf ) | buf | allocator . free ( buf ) ;
var current_buf : [ ] u8 = & stack_buf ;
while ( true ) {
if ( os . getcwd ( current_buf ) ) | slice | {
2020-07-04 13:44:28 +02:00
return allocator . dupe ( u8 , slice ) ;
2020-05-29 16:39:47 -04:00
} else | err | switch ( err ) {
2020-03-28 00:12:40 -05:00
error . NameTooLong = > {
// The path is too long to fit in stack_buf. Allocate geometrically
// increasing buffers until we find one that works
const new_capacity = current_buf . len * 2 ;
if ( heap_buf ) | buf | allocator . free ( buf ) ;
current_buf = try allocator . alloc ( u8 , new_capacity ) ;
heap_buf = current_buf ;
} ,
2020-05-29 16:39:47 -04:00
else = > | e | return e ,
2020-03-28 00:12:40 -05:00
}
}
2019-05-26 23:35:26 -04:00
}
2023-10-06 21:29:08 -07:00
test getCwdAlloc {
Add/fix missing WASI functionality to pass libstd tests
This rather large commit adds/fixes missing WASI functionality
in `libstd` needed to pass the `libstd` tests. As such, now by
default tests targeting `wasm32-wasi` target are enabled in
`test/tests.zig` module. However, they can be disabled by passing
the `-Dskip-wasi=true` flag when invoking the `zig build test`
command. When the flag is set to `false`, i.e., when WASI tests are
included, `wasmtime` with `--dir=.` is used as the default testing
command.
Since the majority of `libstd` tests were relying on `fs.cwd()`
call to get current working directory handle wrapped in `Dir`
struct, in order to make the tests WASI-friendly, `fs.cwd()`
call was replaced with `testing.getTestDir()` function which
resolved to either `fs.cwd()` for non-WASI targets, or tries to
fetch the preopen list from the WASI runtime and extract a
preopen for '.' path.
The summary of changes introduced by this commit:
* implement `Dir.makeDir` and `Dir.openDir` targeting WASI
* implement `Dir.deleteFile` and `Dir.deleteDir` targeting WASI
* fix `os.close` and map errors in `unlinkat`
* move WASI-specific `mkdirat` and `unlinkat` from `std.fs.wasi`
to `std.os` module
* implement `lseek_{SET, CUR, END}` targeting WASI
* implement `futimens` targeting WASI
* implement `ftruncate` targeting WASI
* implement `readv`, `writev`, `pread{v}`, `pwrite{v}` targeting WASI
* make sure ANSI escape codes are _not_ used in stderr or stdout
in WASI, as WASI always sanitizes stderr, and sanitizes stdout if
fd is a TTY
* fix specifying WASI rights when opening/creating files/dirs
* tweak `AtomicFile` to be WASI-compatible
* implement `os.renameatWasi` for WASI-compliant `os.renameat` function
* implement sleep() targeting WASI
* fix `process.getEnvMap` targeting WASI
2020-05-05 17:23:49 +02:00
if ( builtin . os . tag == . wasi ) return error . SkipZigTest ;
2020-01-31 19:06:50 -06:00
const cwd = try getCwdAlloc ( testing . allocator ) ;
testing . allocator . free ( cwd ) ;
2019-05-26 23:35:26 -04:00
}
2022-02-04 12:08:38 -07:00
pub const EnvMap = struct {
hash_map : HashMap ,
const HashMap = std . HashMap (
[ ] const u8 ,
[ ] const u8 ,
EnvNameHashContext ,
std . hash_map . default_max_load_percentage ,
) ;
2022-02-04 23:42:10 -07:00
pub const Size = HashMap . Size ;
2022-02-04 12:08:38 -07:00
pub const EnvNameHashContext = struct {
2022-02-04 22:36:24 -07:00
fn upcase ( c : u21 ) u21 {
if ( c <= std . math . maxInt ( u16 ) )
2023-06-22 18:46:56 +01:00
return std . os . windows . ntdll . RtlUpcaseUnicodeChar ( @as ( u16 , @intCast ( c ) ) ) ;
2022-02-04 22:36:24 -07:00
return c ;
}
2022-02-04 12:08:38 -07:00
pub fn hash ( self : @This ( ) , s : [ ] const u8 ) u64 {
_ = self ;
if ( builtin . os . tag == . windows ) {
2022-02-04 23:42:10 -07:00
var h = std . hash . Wyhash . init ( 0 ) ;
var it = std . unicode . Utf8View . initUnchecked ( s ) . iterator ( ) ;
2022-02-04 22:36:24 -07:00
while ( it . nextCodepoint ( ) ) | cp | {
const cp_upper = upcase ( cp ) ;
h . update ( & [ _ ] u8 {
2023-06-22 18:46:56 +01:00
@as ( u8 , @intCast ( ( cp_upper >> 16 ) & 0xff ) ) ,
@as ( u8 , @intCast ( ( cp_upper >> 8 ) & 0xff ) ) ,
@as ( u8 , @intCast ( ( cp_upper >> 0 ) & 0xff ) ) ,
2022-02-04 22:36:24 -07:00
} ) ;
2022-02-04 12:08:38 -07:00
}
return h . final ( ) ;
}
return std . hash_map . hashString ( s ) ;
}
2022-02-04 22:36:24 -07:00
2022-02-04 12:08:38 -07:00
pub fn eql ( self : @This ( ) , a : [ ] const u8 , b : [ ] const u8 ) bool {
_ = self ;
if ( builtin . os . tag == . windows ) {
2022-02-04 23:42:10 -07:00
var it_a = std . unicode . Utf8View . initUnchecked ( a ) . iterator ( ) ;
var it_b = std . unicode . Utf8View . initUnchecked ( b ) . iterator ( ) ;
2022-02-04 22:36:24 -07:00
while ( true ) {
const c_a = it_a . nextCodepoint ( ) orelse break ;
const c_b = it_b . nextCodepoint ( ) orelse return false ;
if ( upcase ( c_a ) != upcase ( c_b ) )
return false ;
}
2022-02-06 23:30:06 -07:00
return if ( it_b . nextCodepoint ( ) ) | _ | false else true ;
2022-02-04 12:08:38 -07:00
}
return std . hash_map . eqlString ( a , b ) ;
}
2022-01-16 20:11:08 -08:00
} ;
2022-02-04 12:08:38 -07:00
/// Create a EnvMap backed by a specific allocator.
/// That allocator will be used for both backing allocations
/// and string deduplication.
pub fn init ( allocator : Allocator ) EnvMap {
return EnvMap { . hash_map = HashMap . init ( allocator ) } ;
2022-01-16 20:11:08 -08:00
}
2022-02-04 12:08:38 -07:00
/// Free the backing storage of the map, as well as all
/// of the stored keys and values.
pub fn deinit ( self : * EnvMap ) void {
var it = self . hash_map . iterator ( ) ;
2022-01-16 20:11:08 -08:00
while ( it . next ( ) ) | entry | {
2022-02-04 12:08:38 -07:00
self . free ( entry . key_ptr .* ) ;
self . free ( entry . value_ptr .* ) ;
2022-01-16 20:11:08 -08:00
}
2022-02-04 12:08:38 -07:00
self . hash_map . deinit ( ) ;
2022-01-16 20:11:08 -08:00
}
2022-02-04 12:08:38 -07:00
/// Same as `put` but the key and value become owned by the EnvMap rather
/// than being copied.
/// If `putMove` fails, the ownership of key and value does not transfer.
2022-02-18 14:58:13 -07:00
/// On Windows `key` must be a valid UTF-8 string.
2022-02-04 12:08:38 -07:00
pub fn putMove ( self : * EnvMap , key : [ ] u8 , value : [ ] u8 ) ! void {
const get_or_put = try self . hash_map . getOrPut ( key ) ;
if ( get_or_put . found_existing ) {
self . free ( get_or_put . key_ptr .* ) ;
self . free ( get_or_put . value_ptr .* ) ;
get_or_put . key_ptr .* = key ;
2022-01-16 20:11:08 -08:00
}
2022-02-04 12:08:38 -07:00
get_or_put . value_ptr .* = value ;
}
/// `key` and `value` are copied into the EnvMap.
2022-02-18 14:58:13 -07:00
/// On Windows `key` must be a valid UTF-8 string.
2022-02-04 12:08:38 -07:00
pub fn put ( self : * EnvMap , key : [ ] const u8 , value : [ ] const u8 ) ! void {
const value_copy = try self . copy ( value ) ;
errdefer self . free ( value_copy ) ;
const get_or_put = try self . hash_map . getOrPut ( key ) ;
if ( get_or_put . found_existing ) {
self . free ( get_or_put . value_ptr .* ) ;
} else {
get_or_put . key_ptr .* = self . copy ( key ) catch | err | {
_ = self . hash_map . remove ( key ) ;
return err ;
2022-01-16 20:11:08 -08:00
} ;
}
2022-02-04 12:08:38 -07:00
get_or_put . value_ptr .* = value_copy ;
2022-01-16 20:11:08 -08:00
}
2022-02-04 12:08:38 -07:00
/// Find the address of the value associated with a key.
/// The returned pointer is invalidated if the map resizes.
2022-02-18 14:58:13 -07:00
/// On Windows `key` must be a valid UTF-8 string.
2022-02-04 12:08:38 -07:00
pub fn getPtr ( self : EnvMap , key : [ ] const u8 ) ? * [ ] const u8 {
return self . hash_map . getPtr ( key ) ;
2022-01-16 20:11:08 -08:00
}
2022-02-04 12:08:38 -07:00
/// Return the map's copy of the value associated with
/// a key. The returned string is invalidated if this
/// key is removed from the map.
2022-02-18 14:58:13 -07:00
/// On Windows `key` must be a valid UTF-8 string.
2022-02-04 12:08:38 -07:00
pub fn get ( self : EnvMap , key : [ ] const u8 ) ? [ ] const u8 {
return self . hash_map . get ( key ) ;
2022-01-16 20:11:08 -08:00
}
2022-02-04 12:08:38 -07:00
/// Removes the item from the map and frees its value.
/// This invalidates the value returned by get() for this key.
2022-02-18 14:58:13 -07:00
/// On Windows `key` must be a valid UTF-8 string.
2022-02-04 12:08:38 -07:00
pub fn remove ( self : * EnvMap , key : [ ] const u8 ) void {
const kv = self . hash_map . fetchRemove ( key ) orelse return ;
self . free ( kv . key ) ;
self . free ( kv . value ) ;
2022-01-16 20:11:08 -08:00
}
2022-02-04 12:08:38 -07:00
/// Returns the number of KV pairs stored in the map.
pub fn count ( self : EnvMap ) HashMap . Size {
return self . hash_map . count ( ) ;
2022-01-16 20:11:08 -08:00
}
2022-02-04 12:08:38 -07:00
/// Returns an iterator over entries in the map.
pub fn iterator ( self : * const EnvMap ) HashMap . Iterator {
return self . hash_map . iterator ( ) ;
2022-01-16 20:11:08 -08:00
}
2022-02-04 12:08:38 -07:00
fn free ( self : EnvMap , value : [ ] const u8 ) void {
self . hash_map . allocator . free ( value ) ;
2022-01-16 20:11:08 -08:00
}
2022-02-04 12:08:38 -07:00
fn copy ( self : EnvMap , value : [ ] const u8 ) ! [ ] u8 {
return self . hash_map . allocator . dupe ( u8 , value ) ;
2022-01-16 20:11:08 -08:00
}
} ;
test " EnvMap " {
var env = EnvMap . init ( testing . allocator ) ;
defer env . deinit ( ) ;
try env . put ( " SOMETHING_NEW " , " hello " ) ;
try testing . expectEqualStrings ( " hello " , env . get ( " SOMETHING_NEW " ) .? ) ;
try testing . expectEqual ( @as ( EnvMap . Size , 1 ) , env . count ( ) ) ;
// overwrite
try env . put ( " SOMETHING_NEW " , " something " ) ;
try testing . expectEqualStrings ( " something " , env . get ( " SOMETHING_NEW " ) .? ) ;
try testing . expectEqual ( @as ( EnvMap . Size , 1 ) , env . count ( ) ) ;
// a new longer name to test the Windows-specific conversion buffer
try env . put ( " SOMETHING_NEW_AND_LONGER " , " 1 " ) ;
try testing . expectEqualStrings ( " 1 " , env . get ( " SOMETHING_NEW_AND_LONGER " ) .? ) ;
try testing . expectEqual ( @as ( EnvMap . Size , 2 ) , env . count ( ) ) ;
// case insensitivity on Windows only
if ( builtin . os . tag == . windows ) {
try testing . expectEqualStrings ( " 1 " , env . get ( " something_New_aNd_LONGER " ) .? ) ;
} else {
try testing . expect ( null == env . get ( " something_New_aNd_LONGER " ) ) ;
}
var it = env . iterator ( ) ;
var count : EnvMap . Size = 0 ;
while ( it . next ( ) ) | entry | {
2022-02-04 23:42:10 -07:00
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 .* ) ;
2022-01-16 20:11:08 -08:00
try testing . expect ( is_an_expected_name ) ;
count += 1 ;
}
try testing . expectEqual ( @as ( EnvMap . Size , 2 ) , count ) ;
env . remove ( " SOMETHING_NEW " ) ;
try testing . expect ( env . get ( " SOMETHING_NEW " ) == null ) ;
try testing . expectEqual ( @as ( EnvMap . Size , 1 ) , env . count ( ) ) ;
2022-02-06 23:30:06 -07:00
// test Unicode case-insensitivity on Windows
if ( builtin . os . tag == . windows ) {
try env . put ( " КИРиллИЦА " , " something else " ) ;
try testing . expectEqualStrings ( " something else " , env . get ( " кириллица " ) .? ) ;
}
2022-01-16 20:11:08 -08:00
}
/// Returns a snapshot of the environment variables of the current process.
/// Any modifications to the resulting EnvMap will not be not reflected in the environment, and
/// likewise, any future modifications to the environment will not be reflected in the EnvMap.
/// Caller owns resulting `EnvMap` and should call its `deinit` fn when done.
pub fn getEnvMap ( allocator : Allocator ) ! EnvMap {
var result = EnvMap . init ( allocator ) ;
2019-05-24 18:27:18 -04:00
errdefer result . deinit ( ) ;
2020-02-25 01:52:27 -05:00
if ( builtin . os . tag == . windows ) {
2020-02-22 17:49:52 -05:00
const ptr = os . windows . peb ( ) . ProcessParameters . Environment ;
2019-05-24 18:27:18 -04:00
var i : usize = 0 ;
2020-02-22 17:35:36 -05:00
while ( ptr [ i ] != 0 ) {
2019-05-24 18:27:18 -04:00
const key_start = i ;
2022-01-16 20:11:08 -08:00
// There are some special environment variables that start with =,
// so we need a special case to not treat = as a key/value separator
// if it's the first character.
// https://devblogs.microsoft.com/oldnewthing/20100506-00/?p=14133
if ( ptr [ key_start ] == '=' ) i += 1 ;
2019-05-24 18:27:18 -04:00
while ( ptr [ i ] != 0 and ptr [ i ] != '=' ) : ( i += 1 ) { }
const key_w = ptr [ key_start .. i ] ;
2022-02-04 23:35:22 -07:00
const key = try std . unicode . utf16leToUtf8Alloc ( allocator , key_w ) ;
errdefer allocator . free ( key ) ;
2019-05-24 18:27:18 -04:00
if ( ptr [ i ] == '=' ) i += 1 ;
const value_start = i ;
while ( ptr [ i ] != 0 ) : ( i += 1 ) { }
const value_w = ptr [ value_start .. i ] ;
2022-02-04 23:35:22 -07:00
const value = try std . unicode . utf16leToUtf8Alloc ( allocator , value_w ) ;
errdefer allocator . free ( value ) ;
2019-05-24 18:27:18 -04:00
2022-01-16 20:11:08 -08:00
i += 1 ; // skip over null byte
2022-02-04 23:35:22 -07:00
try result . putMove ( key , value ) ;
}
2020-02-22 17:35:36 -05:00
return result ;
2021-07-27 08:59:34 +09:00
} else if ( builtin . os . tag == . wasi and ! builtin . link_libc ) {
2019-05-24 18:27:18 -04:00
var environ_count : usize = undefined ;
var environ_buf_size : usize = undefined ;
2019-05-25 13:07:44 -04:00
const environ_sizes_get_ret = os . wasi . environ_sizes_get ( & environ_count , & environ_buf_size ) ;
2021-08-23 17:06:56 -07:00
if ( environ_sizes_get_ret != . SUCCESS ) {
2019-05-25 13:07:44 -04:00
return os . unexpectedErrno ( environ_sizes_get_ret ) ;
2019-05-24 18:27:18 -04:00
}
2023-01-19 13:50:23 +08:00
if ( environ_count == 0 ) {
return result ;
}
2023-11-10 05:27:17 +00:00
const environ = try allocator . alloc ( [ * : 0 ] u8 , environ_count ) ;
2019-05-24 18:27:18 -04:00
defer allocator . free ( environ ) ;
2023-11-10 05:27:17 +00:00
const environ_buf = try allocator . alloc ( u8 , environ_buf_size ) ;
2019-05-24 18:27:18 -04:00
defer allocator . free ( environ_buf ) ;
2019-05-25 13:07:44 -04:00
const environ_get_ret = os . wasi . environ_get ( environ . ptr , environ_buf . ptr ) ;
2021-08-23 17:06:56 -07:00
if ( environ_get_ret != . SUCCESS ) {
2019-05-25 13:07:44 -04:00
return os . unexpectedErrno ( environ_get_ret ) ;
2019-05-24 18:27:18 -04:00
}
for ( environ ) | env | {
2021-11-30 00:13:07 -07:00
const pair = mem . sliceTo ( env , 0 ) ;
2023-05-04 18:15:50 -07:00
var parts = mem . splitScalar ( u8 , pair , '=' ) ;
2022-07-25 21:04:30 +02:00
const key = parts . first ( ) ;
2022-12-29 17:38:19 -08:00
const value = parts . rest ( ) ;
2021-06-03 15:39:26 -05:00
try result . put ( key , value ) ;
2019-05-24 18:27:18 -04:00
}
return result ;
2020-02-22 15:59:13 -05:00
} else if ( builtin . link_libc ) {
var ptr = std . c . environ ;
2022-06-30 17:22:16 +03:00
while ( ptr [ 0 ] ) | line | : ( ptr += 1 ) {
2020-02-22 15:59:13 -05:00
var line_i : usize = 0 ;
while ( line [ line_i ] != 0 and line [ line_i ] != '=' ) : ( line_i += 1 ) { }
const key = line [ 0 .. line_i ] ;
var end_i : usize = line_i ;
while ( line [ end_i ] != 0 ) : ( end_i += 1 ) { }
const value = line [ line_i + 1 .. end_i ] ;
2021-06-03 15:39:26 -05:00
try result . put ( key , value ) ;
2020-02-22 15:59:13 -05:00
}
return result ;
2019-05-24 18:27:18 -04:00
} else {
2020-02-22 15:59:13 -05:00
for ( os . environ ) | line | {
2019-05-24 18:27:18 -04:00
var line_i : usize = 0 ;
2020-02-22 15:59:13 -05:00
while ( line [ line_i ] != 0 and line [ line_i ] != '=' ) : ( line_i += 1 ) { }
const key = line [ 0 .. line_i ] ;
2019-05-24 18:27:18 -04:00
var end_i : usize = line_i ;
2020-02-22 15:59:13 -05:00
while ( line [ end_i ] != 0 ) : ( end_i += 1 ) { }
const value = line [ line_i + 1 .. end_i ] ;
2019-05-24 18:27:18 -04:00
2021-06-03 15:39:26 -05:00
try result . put ( key , value ) ;
2019-05-24 18:27:18 -04:00
}
return result ;
}
}
2022-01-16 20:11:08 -08:00
test " getEnvMap " {
var env = try getEnvMap ( testing . allocator ) ;
2019-05-24 18:27:18 -04:00
defer env . deinit ( ) ;
}
pub const GetEnvVarOwnedError = error {
OutOfMemory ,
EnvironmentVariableNotFound ,
/// See https://github.com/ziglang/zig/issues/1774
InvalidUtf8 ,
} ;
/// Caller must free returned memory.
2022-11-10 14:00:55 -07:00
pub fn getEnvVarOwned ( allocator : Allocator , key : [ ] const u8 ) GetEnvVarOwnedError ! [ ] u8 {
2020-02-25 01:52:27 -05:00
if ( builtin . os . tag == . windows ) {
2020-02-22 17:35:36 -05:00
const result_w = blk : {
const key_w = try std . unicode . utf8ToUtf16LeWithNull ( allocator , key ) ;
defer allocator . free ( key_w ) ;
break : blk std . os . getenvW ( key_w ) orelse return error . EnvironmentVariableNotFound ;
} ;
return std . unicode . utf16leToUtf8Alloc ( allocator , result_w ) catch | err | switch ( err ) {
error . DanglingSurrogateHalf = > return error . InvalidUtf8 ,
error . ExpectedSecondSurrogateHalf = > return error . InvalidUtf8 ,
error . UnexpectedSecondSurrogateHalf = > return error . InvalidUtf8 ,
else = > | e | return e ,
} ;
2023-01-06 08:40:16 -08:00
} else if ( builtin . os . tag == . wasi and ! builtin . link_libc ) {
var envmap = getEnvMap ( allocator ) catch return error . OutOfMemory ;
defer envmap . deinit ( ) ;
const val = envmap . get ( key ) orelse return error . EnvironmentVariableNotFound ;
return allocator . dupe ( u8 , val ) ;
2019-05-24 18:27:18 -04:00
} else {
2019-05-26 13:17:34 -04:00
const result = os . getenv ( key ) orelse return error . EnvironmentVariableNotFound ;
2020-07-04 13:44:28 +02:00
return allocator . dupe ( u8 , result ) ;
2019-05-24 18:27:18 -04:00
}
}
2021-06-21 13:47:38 -05:00
pub fn hasEnvVarConstant ( comptime key : [ ] const u8 ) bool {
if ( builtin . os . tag == . windows ) {
const key_w = comptime std . unicode . utf8ToUtf16LeStringLiteral ( key ) ;
return std . os . getenvW ( key_w ) != null ;
2023-01-06 08:40:16 -08:00
} else if ( builtin . os . tag == . wasi and ! builtin . link_libc ) {
@compileError ( " hasEnvVarConstant is not supported for WASI without libc " ) ;
2021-06-21 13:47:38 -05:00
} else {
return os . getenv ( key ) != null ;
}
}
2021-10-29 00:37:25 +01:00
pub fn hasEnvVar ( allocator : Allocator , key : [ ] const u8 ) error { OutOfMemory } ! bool {
2021-06-21 13:47:38 -05:00
if ( builtin . os . tag == . windows ) {
var stack_alloc = std . heap . stackFallback ( 256 * @sizeOf ( u16 ) , allocator ) ;
2021-10-29 00:37:25 +01:00
const key_w = try std . unicode . utf8ToUtf16LeWithNull ( stack_alloc . get ( ) , key ) ;
2021-06-21 13:47:38 -05:00
defer stack_alloc . allocator . free ( key_w ) ;
return std . os . getenvW ( key_w ) != null ;
2023-01-06 08:40:16 -08:00
} else if ( builtin . os . tag == . wasi and ! builtin . link_libc ) {
var envmap = getEnvMap ( allocator ) catch return error . OutOfMemory ;
defer envmap . deinit ( ) ;
return envmap . getPtr ( key ) != null ;
2021-06-21 13:47:38 -05:00
} else {
return os . getenv ( key ) != null ;
}
}
2019-05-24 18:27:18 -04:00
test " os.getEnvVarOwned " {
2023-11-10 05:27:17 +00:00
const ga = std . testing . allocator ;
2021-05-04 20:47:26 +03:00
try testing . expectError ( error . EnvironmentVariableNotFound , getEnvVarOwned ( ga , " BADENV " ) ) ;
2019-05-24 18:27:18 -04:00
}
pub const ArgIteratorPosix = struct {
index : usize ,
count : usize ,
2022-01-30 11:27:52 -08:00
pub const InitError = error { } ;
2019-05-24 18:27:18 -04:00
pub fn init ( ) ArgIteratorPosix {
return ArgIteratorPosix {
. index = 0 ,
2019-05-26 13:17:34 -04:00
. count = os . argv . len ,
2019-05-24 18:27:18 -04:00
} ;
}
2020-10-22 17:52:48 -04:00
pub fn next ( self : * ArgIteratorPosix ) ? [ : 0 ] const u8 {
2019-05-24 18:27:18 -04:00
if ( self . index == self . count ) return null ;
2019-05-26 13:17:34 -04:00
const s = os . argv [ self . index ] ;
2019-05-24 18:27:18 -04:00
self . index += 1 ;
2021-11-30 00:13:07 -07:00
return mem . sliceTo ( s , 0 ) ;
2019-05-24 18:27:18 -04:00
}
pub fn skip ( self : * ArgIteratorPosix ) bool {
if ( self . index == self . count ) return false ;
self . index += 1 ;
return true ;
}
} ;
2020-05-20 19:42:15 +02:00
pub const ArgIteratorWasi = struct {
2022-11-10 14:00:55 -07:00
allocator : Allocator ,
2020-05-20 19:42:15 +02:00
index : usize ,
2020-10-22 17:52:48 -04:00
args : [ ] [ : 0 ] u8 ,
2020-05-20 19:42:15 +02:00
pub const InitError = error { OutOfMemory } || os . UnexpectedError ;
/// You must call deinit to free the internal buffer of the
/// iterator after you are done.
2022-11-10 14:00:55 -07:00
pub fn init ( allocator : Allocator ) InitError ! ArgIteratorWasi {
2020-05-20 19:42:15 +02:00
const fetched_args = try ArgIteratorWasi . internalInit ( allocator ) ;
return ArgIteratorWasi {
. allocator = allocator ,
. index = 0 ,
. args = fetched_args ,
} ;
}
2022-11-10 14:00:55 -07:00
fn internalInit ( allocator : Allocator ) InitError ! [ ] [ : 0 ] u8 {
2020-05-20 19:42:15 +02:00
const w = os . wasi ;
var count : usize = undefined ;
var buf_size : usize = undefined ;
switch ( w . args_sizes_get ( & count , & buf_size ) ) {
2021-08-23 17:06:56 -07:00
. SUCCESS = > { } ,
2020-05-20 19:42:15 +02:00
else = > | err | return os . unexpectedErrno ( err ) ,
}
2023-01-19 13:50:23 +08:00
if ( count == 0 ) {
return & [ _ ] [ : 0 ] u8 { } ;
}
2023-11-10 05:27:17 +00:00
const argv = try allocator . alloc ( [ * : 0 ] u8 , count ) ;
2020-05-20 19:42:15 +02:00
defer allocator . free ( argv ) ;
2023-11-10 05:27:17 +00:00
const argv_buf = try allocator . alloc ( u8 , buf_size ) ;
2020-05-20 19:42:15 +02:00
switch ( w . args_get ( argv . ptr , argv_buf . ptr ) ) {
2021-08-23 17:06:56 -07:00
. SUCCESS = > { } ,
2020-05-20 19:42:15 +02:00
else = > | err | return os . unexpectedErrno ( err ) ,
}
2020-10-22 17:52:48 -04:00
var result_args = try allocator . alloc ( [ : 0 ] u8 , count ) ;
2020-05-20 19:42:15 +02:00
var i : usize = 0 ;
while ( i < count ) : ( i += 1 ) {
2021-11-30 00:13:07 -07:00
result_args [ i ] = mem . sliceTo ( argv [ i ] , 0 ) ;
2020-05-20 19:42:15 +02:00
}
return result_args ;
}
2020-10-22 17:52:48 -04:00
pub fn next ( self : * ArgIteratorWasi ) ? [ : 0 ] const u8 {
2020-05-20 19:42:15 +02:00
if ( self . index == self . args . len ) return null ;
const arg = self . args [ self . index ] ;
self . index += 1 ;
return arg ;
}
pub fn skip ( self : * ArgIteratorWasi ) bool {
if ( self . index == self . args . len ) return false ;
self . index += 1 ;
return true ;
}
/// Call to free the internal buffer of the iterator.
pub fn deinit ( self : * ArgIteratorWasi ) void {
const last_item = self . args [ self . args . len - 1 ] ;
2023-06-15 13:14:16 +06:00
const last_byte_addr = @intFromPtr ( last_item . ptr ) + last_item . len + 1 ; // null terminated
2020-05-20 19:42:15 +02:00
const first_item_ptr = self . args [ 0 ] . ptr ;
2023-06-15 13:14:16 +06:00
const len = last_byte_addr - @intFromPtr ( first_item_ptr ) ;
2020-05-20 19:42:15 +02:00
self . allocator . free ( first_item_ptr [ 0 .. len ] ) ;
self . allocator . free ( self . args ) ;
}
} ;
2023-12-18 22:55:46 +01:00
/// Iterator that implements the Windows command-line parsing algorithm.
///
/// This iterator faithfully implements the parsing behavior observed in `CommandLineToArgvW` with
/// one exception: if the command-line string is empty, the iterator will immediately complete
/// without returning any arguments (whereas `CommandLineArgvW` will return a single argument
/// representing the name of the current executable).
pub const ArgIteratorWindows = struct {
allocator : Allocator ,
/// Owned by the iterator.
cmd_line : [ ] const u8 ,
index : usize = 0 ,
/// Owned by the iterator. Long enough to hold the entire `cmd_line` plus a null terminator.
buffer : [ ] u8 ,
start : usize = 0 ,
end : usize = 0 ,
pub const InitError = error { OutOfMemory , InvalidCmdLine } ;
/// `cmd_line_w` *must* be an UTF16-LE-encoded string.
///
/// The iterator makes a copy of `cmd_line_w` converted UTF-8 and keeps it; it does *not* take
/// ownership of `cmd_line_w`.
pub fn init ( allocator : Allocator , cmd_line_w : [ * : 0 ] const u16 ) InitError ! ArgIteratorWindows {
const cmd_line = std . unicode . utf16leToUtf8Alloc ( allocator , mem . sliceTo ( cmd_line_w , 0 ) ) catch | err | switch ( err ) {
error . DanglingSurrogateHalf ,
error . ExpectedSecondSurrogateHalf ,
error . UnexpectedSecondSurrogateHalf ,
= > return error . InvalidCmdLine ,
error . OutOfMemory = > return error . OutOfMemory ,
} ;
errdefer allocator . free ( cmd_line ) ;
const buffer = try allocator . alloc ( u8 , cmd_line . len + 1 ) ;
errdefer allocator . free ( buffer ) ;
return . {
. allocator = allocator ,
. cmd_line = cmd_line ,
. buffer = buffer ,
} ;
}
/// Returns the next argument and advances the iterator. Returns `null` if at the end of the
/// command-line string. The iterator owns the returned slice.
pub fn next ( self : * ArgIteratorWindows ) ? [ : 0 ] const u8 {
return self . nextWithStrategy ( next_strategy ) ;
}
/// Skips the next argument and advances the iterator. Returns `true` if an argument was
/// skipped, `false` if at the end of the command-line string.
pub fn skip ( self : * ArgIteratorWindows ) bool {
return self . nextWithStrategy ( skip_strategy ) ;
}
const next_strategy = struct {
const T = ? [ : 0 ] const u8 ;
const eof = null ;
fn emitBackslashes ( self : * ArgIteratorWindows , count : usize ) void {
for ( 0 .. count ) | _ | emitCharacter ( self , '\\' ) ;
}
fn emitCharacter ( self : * ArgIteratorWindows , char : u8 ) void {
self . buffer [ self . end ] = char ;
self . end += 1 ;
}
fn yieldArg ( self : * ArgIteratorWindows ) [ : 0 ] const u8 {
self . buffer [ self . end ] = 0 ;
const arg = self . buffer [ self . start .. self . end : 0 ] ;
self . end += 1 ;
self . start = self . end ;
return arg ;
}
} ;
const skip_strategy = struct {
const T = bool ;
const eof = false ;
fn emitBackslashes ( _ : * ArgIteratorWindows , _ : usize ) void { }
fn emitCharacter ( _ : * ArgIteratorWindows , _ : u8 ) void { }
fn yieldArg ( _ : * ArgIteratorWindows ) bool {
return true ;
}
} ;
// The essential parts of the algorithm are described in Microsoft's documentation:
//
// - <https://learn.microsoft.com/en-us/cpp/cpp/main-function-command-line-args?view=msvc-170#parsing-c-command-line-arguments>
// - <https://learn.microsoft.com/en-us/windows/win32/api/shellapi/nf-shellapi-commandlinetoargvw>
//
// David Deley explains some additional undocumented quirks in great detail:
//
// - <https://daviddeley.com/autohotkey/parameters/parameters.htm#WINCRULES>
//
// Code points <= U+0020 terminating an unquoted first argument was discovered independently by
// testing and observing the behavior of 'CommandLineToArgvW' on Windows 10.
fn nextWithStrategy ( self : * ArgIteratorWindows , comptime strategy : type ) strategy . T {
// The first argument (the executable name) uses different parsing rules.
if ( self . index == 0 ) {
var char = if ( self . cmd_line . len != 0 ) self . cmd_line [ 0 ] else 0 ;
switch ( char ) {
0 = > {
// Immediately complete the iterator.
// 'CommandLineToArgvW' would return the name of the current executable here.
return strategy . eof ;
} ,
'"' = > {
// If the first character is a quote, read everything until the next quote (then
// skip that quote), or until the end of the string.
self . index += 1 ;
while ( true ) : ( self . index += 1 ) {
char = if ( self . index != self . cmd_line . len ) self . cmd_line [ self . index ] else 0 ;
switch ( char ) {
0 = > {
return strategy . yieldArg ( self ) ;
} ,
'"' = > {
self . index += 1 ;
return strategy . yieldArg ( self ) ;
} ,
else = > {
strategy . emitCharacter ( self , char ) ;
} ,
}
}
} ,
else = > {
// Otherwise, read everything until the next space or ASCII control character
// (not including DEL) (then skip that character), or until the end of the
// string. This means that if the command-line string starts with one of these
// characters, the first returned argument will be the empty string.
while ( true ) : ( self . index += 1 ) {
char = if ( self . index != self . cmd_line . len ) self . cmd_line [ self . index ] else 0 ;
switch ( char ) {
0 = > {
return strategy . yieldArg ( self ) ;
} ,
'\x01' .. . ' ' = > {
self . index += 1 ;
return strategy . yieldArg ( self ) ;
} ,
else = > {
strategy . emitCharacter ( self , char ) ;
} ,
}
}
} ,
}
}
// Skip spaces and tabs. The iterator completes if we reach the end of the string here.
while ( true ) : ( self . index += 1 ) {
const char = if ( self . index != self . cmd_line . len ) self . cmd_line [ self . index ] else 0 ;
switch ( char ) {
0 = > return strategy . eof ,
' ' , '\t' = > continue ,
else = > break ,
}
}
// Parsing rules for subsequent arguments:
//
// - The end of the string always terminates the current argument.
// - When not in 'inside_quotes' mode, a space or tab terminates the current argument.
// - 2n backslashes followed by a quote emit n backslashes. If in 'inside_quotes' and the
// quote is immediately followed by a second quote, one quote is emitted and the other is
// skipped, otherwise, the quote is skipped. Finally, 'inside_quotes' is toggled.
// - 2n + 1 backslashes followed by a quote emit n backslashes followed by a quote.
// - n backslashes not followed by a quote emit n backslashes.
var backslash_count : usize = 0 ;
var inside_quotes = false ;
while ( true ) : ( self . index += 1 ) {
const char = if ( self . index != self . cmd_line . len ) self . cmd_line [ self . index ] else 0 ;
switch ( char ) {
0 = > {
strategy . emitBackslashes ( self , backslash_count ) ;
return strategy . yieldArg ( self ) ;
} ,
' ' , '\t' = > {
strategy . emitBackslashes ( self , backslash_count ) ;
backslash_count = 0 ;
if ( inside_quotes )
strategy . emitCharacter ( self , char )
else
return strategy . yieldArg ( self ) ;
} ,
'"' = > {
const char_is_escaped_quote = backslash_count % 2 != 0 ;
strategy . emitBackslashes ( self , backslash_count / 2 ) ;
backslash_count = 0 ;
if ( char_is_escaped_quote ) {
strategy . emitCharacter ( self , '"' ) ;
} else {
if ( inside_quotes and
self . index + 1 != self . cmd_line . len and
self . cmd_line [ self . index + 1 ] == '"' )
{
strategy . emitCharacter ( self , '"' ) ;
self . index += 1 ;
}
inside_quotes = ! inside_quotes ;
}
} ,
'\\' = > {
backslash_count += 1 ;
} ,
else = > {
strategy . emitBackslashes ( self , backslash_count ) ;
backslash_count = 0 ;
strategy . emitCharacter ( self , char ) ;
} ,
}
}
}
/// Frees the iterator's copy of the command-line string and all previously returned
/// argument slices.
pub fn deinit ( self : * ArgIteratorWindows ) void {
self . allocator . free ( self . buffer ) ;
self . allocator . free ( self . cmd_line ) ;
}
} ;
2022-01-30 11:27:52 -08:00
/// Optional parameters for `ArgIteratorGeneral`
pub const ArgIteratorGeneralOptions = struct {
2022-02-04 19:55:32 +02:00
comments : bool = false ,
single_quotes : bool = false ,
2022-01-30 11:27:52 -08:00
} ;
2019-05-24 18:27:18 -04:00
2022-01-30 11:27:52 -08:00
/// A general Iterator to parse a string into a set of arguments
pub fn ArgIteratorGeneral ( comptime options : ArgIteratorGeneralOptions ) type {
return struct {
allocator : Allocator ,
index : usize = 0 ,
cmd_line : [ ] const u8 ,
/// Should the cmd_line field be free'd (using the allocator) on deinit()?
free_cmd_line_on_deinit : bool ,
/// buffer MUST be long enough to hold the cmd_line plus a null terminator.
/// buffer will we free'd (using the allocator) on deinit()
buffer : [ ] u8 ,
start : usize = 0 ,
end : usize = 0 ,
pub const Self = @This ( ) ;
pub const InitError = error { OutOfMemory } ;
pub const InitUtf16leError = error { OutOfMemory , InvalidCmdLine } ;
/// cmd_line_utf8 MUST remain valid and constant while using this instance
pub fn init ( allocator : Allocator , cmd_line_utf8 : [ ] const u8 ) InitError ! Self {
2023-11-10 05:27:17 +00:00
const buffer = try allocator . alloc ( u8 , cmd_line_utf8 . len + 1 ) ;
2022-01-30 11:27:52 -08:00
errdefer allocator . free ( buffer ) ;
return Self {
. allocator = allocator ,
. cmd_line = cmd_line_utf8 ,
. free_cmd_line_on_deinit = false ,
. buffer = buffer ,
} ;
}
2019-05-24 18:27:18 -04:00
2022-01-30 11:27:52 -08:00
/// cmd_line_utf8 will be free'd (with the allocator) on deinit()
pub fn initTakeOwnership ( allocator : Allocator , cmd_line_utf8 : [ ] const u8 ) InitError ! Self {
2023-11-10 05:27:17 +00:00
const buffer = try allocator . alloc ( u8 , cmd_line_utf8 . len + 1 ) ;
2022-01-30 11:27:52 -08:00
errdefer allocator . free ( buffer ) ;
return Self {
. allocator = allocator ,
. cmd_line = cmd_line_utf8 ,
. free_cmd_line_on_deinit = true ,
. buffer = buffer ,
} ;
}
2019-05-24 18:27:18 -04:00
2022-01-30 11:27:52 -08:00
/// cmd_line_utf16le MUST be encoded UTF16-LE, and is converted to UTF-8 in an internal buffer
pub fn initUtf16le ( allocator : Allocator , cmd_line_utf16le : [ * : 0 ] const u16 ) InitUtf16leError ! Self {
2023-11-10 05:27:17 +00:00
const utf16le_slice = mem . sliceTo ( cmd_line_utf16le , 0 ) ;
const cmd_line = std . unicode . utf16leToUtf8Alloc ( allocator , utf16le_slice ) catch | err | switch ( err ) {
2022-01-30 11:27:52 -08:00
error . ExpectedSecondSurrogateHalf ,
error . DanglingSurrogateHalf ,
error . UnexpectedSecondSurrogateHalf ,
= > return error . InvalidCmdLine ,
error . OutOfMemory = > return error . OutOfMemory ,
} ;
errdefer allocator . free ( cmd_line ) ;
2023-11-10 05:27:17 +00:00
const buffer = try allocator . alloc ( u8 , cmd_line . len + 1 ) ;
2022-01-30 11:27:52 -08:00
errdefer allocator . free ( buffer ) ;
return Self {
. allocator = allocator ,
. cmd_line = cmd_line ,
. free_cmd_line_on_deinit = true ,
. buffer = buffer ,
} ;
}
2020-11-30 10:47:01 -08:00
2022-01-30 11:27:52 -08:00
// Skips over whitespace in the cmd_line.
// Returns false if the terminating sentinel is reached, true otherwise.
// Also skips over comments (if supported).
fn skipWhitespace ( self : * Self ) bool {
while ( true ) : ( self . index += 1 ) {
const character = if ( self . index != self . cmd_line . len ) self . cmd_line [ self . index ] else 0 ;
switch ( character ) {
0 = > return false ,
' ' , '\t' , '\r' , '\n' = > continue ,
'#' = > {
2022-02-04 19:55:32 +02:00
if ( options . comments ) {
2022-01-30 11:27:52 -08:00
while ( true ) : ( self . index += 1 ) {
switch ( self . cmd_line [ self . index ] ) {
'\n' = > break ,
0 = > return false ,
else = > continue ,
}
}
continue ;
} else {
break ;
}
} ,
else = > break ,
}
2019-05-24 18:27:18 -04:00
}
2022-01-30 11:27:52 -08:00
return true ;
2019-05-24 18:27:18 -04:00
}
2022-01-30 11:27:52 -08:00
pub fn skip ( self : * Self ) bool {
if ( ! self . skipWhitespace ( ) ) {
return false ;
}
2019-05-24 18:27:18 -04:00
2022-01-30 11:27:52 -08:00
var backslash_count : usize = 0 ;
var in_quote = false ;
while ( true ) : ( self . index += 1 ) {
const character = if ( self . index != self . cmd_line . len ) self . cmd_line [ self . index ] else 0 ;
switch ( character ) {
0 = > return true ,
2022-02-04 19:55:32 +02:00
'"' , '\'' = > {
if ( ! options . single_quotes and character == '\'' ) {
backslash_count = 0 ;
continue ;
}
2022-01-30 11:27:52 -08:00
const quote_is_real = backslash_count % 2 == 0 ;
if ( quote_is_real ) {
in_quote = ! in_quote ;
}
} ,
'\\' = > {
backslash_count += 1 ;
} ,
' ' , '\t' , '\r' , '\n' = > {
if ( ! in_quote ) {
return true ;
}
backslash_count = 0 ;
} ,
else = > {
backslash_count = 0 ;
continue ;
} ,
}
2019-05-24 18:27:18 -04:00
}
}
2022-01-30 11:27:52 -08:00
/// Returns a slice of the internal buffer that contains the next argument.
/// Returns null when it reaches the end.
pub fn next ( self : * Self ) ? [ : 0 ] const u8 {
if ( ! self . skipWhitespace ( ) ) {
return null ;
}
var backslash_count : usize = 0 ;
var in_quote = false ;
while ( true ) : ( self . index += 1 ) {
const character = if ( self . index != self . cmd_line . len ) self . cmd_line [ self . index ] else 0 ;
switch ( character ) {
0 = > {
self . emitBackslashes ( backslash_count ) ;
self . buffer [ self . end ] = 0 ;
2023-11-10 05:27:17 +00:00
const token = self . buffer [ self . start .. self . end : 0 ] ;
2022-01-30 11:27:52 -08:00
self . end += 1 ;
self . start = self . end ;
return token ;
} ,
2022-02-04 19:55:32 +02:00
'"' , '\'' = > {
if ( ! options . single_quotes and character == '\'' ) {
self . emitBackslashes ( backslash_count ) ;
backslash_count = 0 ;
self . emitCharacter ( character ) ;
continue ;
}
2022-01-30 11:27:52 -08:00
const quote_is_real = backslash_count % 2 == 0 ;
self . emitBackslashes ( backslash_count / 2 ) ;
backslash_count = 0 ;
if ( quote_is_real ) {
in_quote = ! in_quote ;
} else {
self . emitCharacter ( '"' ) ;
}
} ,
'\\' = > {
backslash_count += 1 ;
} ,
' ' , '\t' , '\r' , '\n' = > {
self . emitBackslashes ( backslash_count ) ;
backslash_count = 0 ;
if ( in_quote ) {
self . emitCharacter ( character ) ;
} else {
self . buffer [ self . end ] = 0 ;
2023-11-10 05:27:17 +00:00
const token = self . buffer [ self . start .. self . end : 0 ] ;
2022-01-30 11:27:52 -08:00
self . end += 1 ;
self . start = self . end ;
return token ;
}
} ,
else = > {
self . emitBackslashes ( backslash_count ) ;
backslash_count = 0 ;
self . emitCharacter ( character ) ;
} ,
}
2019-05-24 18:27:18 -04:00
}
}
2022-01-30 11:27:52 -08:00
fn emitBackslashes ( self : * Self , emit_count : usize ) void {
var i : usize = 0 ;
while ( i < emit_count ) : ( i += 1 ) {
self . emitCharacter ( '\\' ) ;
2019-05-24 18:27:18 -04:00
}
}
2022-01-30 11:27:52 -08:00
fn emitCharacter ( self : * Self , char : u8 ) void {
self . buffer [ self . end ] = char ;
self . end += 1 ;
}
2020-11-30 10:47:01 -08:00
2022-01-30 11:27:52 -08:00
/// Call to free the internal buffer of the iterator.
pub fn deinit ( self : * Self ) void {
self . allocator . free ( self . buffer ) ;
if ( self . free_cmd_line_on_deinit ) {
self . allocator . free ( self . cmd_line ) ;
}
2019-05-24 18:27:18 -04:00
}
2022-01-30 11:27:52 -08:00
} ;
}
2019-05-24 18:27:18 -04:00
2022-01-30 11:27:52 -08:00
/// Cross-platform command line argument iterator.
2019-05-24 18:27:18 -04:00
pub const ArgIterator = struct {
2020-05-20 19:42:15 +02:00
const InnerType = switch ( builtin . os . tag ) {
2023-12-18 22:55:46 +01:00
. windows = > ArgIteratorWindows ,
2021-07-27 08:59:34 +09:00
. wasi = > if ( builtin . link_libc ) ArgIteratorPosix else ArgIteratorWasi ,
2020-05-20 19:42:15 +02:00
else = > ArgIteratorPosix ,
} ;
2019-05-24 18:27:18 -04:00
inner : InnerType ,
2022-01-30 11:27:52 -08:00
/// Initialize the args iterator. Consider using initWithAllocator() instead
/// for cross-platform compatibility.
2019-05-24 18:27:18 -04:00
pub fn init ( ) ArgIterator {
2020-02-25 01:52:27 -05:00
if ( builtin . os . tag == . wasi ) {
2020-05-29 08:40:32 +02:00
@compileError ( " In WASI, use initWithAllocator instead. " ) ;
2019-05-24 18:27:18 -04:00
}
2022-01-30 11:27:52 -08:00
if ( builtin . os . tag == . windows ) {
@compileError ( " In Windows, use initWithAllocator instead. " ) ;
}
2019-05-24 18:27:18 -04:00
return ArgIterator { . inner = InnerType . init ( ) } ;
}
2023-12-18 22:55:46 +01:00
pub const InitError = InnerType . InitError ;
2020-05-20 19:42:15 +02:00
2020-05-29 08:40:32 +02:00
/// You must deinitialize iterator's internal buffers by calling `deinit` when done.
2022-11-10 14:00:55 -07:00
pub fn initWithAllocator ( allocator : Allocator ) InitError ! ArgIterator {
2021-07-27 08:59:34 +09:00
if ( builtin . os . tag == . wasi and ! builtin . link_libc ) {
2020-05-29 08:40:32 +02:00
return ArgIterator { . inner = try InnerType . init ( allocator ) } ;
}
2020-02-25 01:52:27 -05:00
if ( builtin . os . tag == . windows ) {
2022-01-30 11:27:52 -08:00
const cmd_line_w = os . windows . kernel32 . GetCommandLineW ( ) ;
2023-12-18 22:55:46 +01:00
return ArgIterator { . inner = try InnerType . init ( allocator , cmd_line_w ) } ;
2019-05-24 18:27:18 -04:00
}
2022-01-30 11:27:52 -08:00
return ArgIterator { . inner = InnerType . init ( ) } ;
2019-05-24 18:27:18 -04:00
}
2022-01-30 11:27:52 -08:00
/// Get the next argument. Returns 'null' if we are at the end.
/// Returned slice is pointing to the iterator's internal buffer.
pub fn next ( self : * ArgIterator ) ? ( [ : 0 ] const u8 ) {
2020-05-20 19:42:15 +02:00
return self . inner . next ( ) ;
}
2019-05-24 18:27:18 -04:00
/// Parse past 1 argument without capturing it.
/// Returns `true` if skipped an arg, `false` if we are at the end.
pub fn skip ( self : * ArgIterator ) bool {
return self . inner . skip ( ) ;
}
2020-05-20 19:42:15 +02:00
2020-05-29 08:40:32 +02:00
/// Call this to free the iterator's internal buffer if the iterator
/// was created with `initWithAllocator` function.
pub fn deinit ( self : * ArgIterator ) void {
2022-01-30 11:27:52 -08:00
// Unless we're targeting WASI or Windows, this is a no-op.
2021-07-27 08:59:34 +09:00
if ( builtin . os . tag == . wasi and ! builtin . link_libc ) {
2020-05-29 08:40:32 +02:00
self . inner . deinit ( ) ;
}
2022-01-30 11:27:52 -08:00
if ( builtin . os . tag == . windows ) {
self . inner . deinit ( ) ;
}
2020-05-20 19:42:15 +02:00
}
2019-05-24 18:27:18 -04:00
} ;
2023-04-06 22:57:25 +02:00
/// Holds the command-line arguments, with the program name as the first entry.
/// Use argsWithAllocator() for cross-platform code.
2019-05-24 18:27:18 -04:00
pub fn args ( ) ArgIterator {
return ArgIterator . init ( ) ;
}
2020-05-29 08:40:32 +02:00
/// You must deinitialize iterator's internal buffers by calling `deinit` when done.
2022-11-10 14:00:55 -07:00
pub fn argsWithAllocator ( allocator : Allocator ) ArgIterator . InitError ! ArgIterator {
2020-05-29 08:40:32 +02:00
return ArgIterator . initWithAllocator ( allocator ) ;
}
2019-05-24 18:27:18 -04:00
/// Caller must call argsFree on result.
2022-11-10 14:00:55 -07:00
pub fn argsAlloc ( allocator : Allocator ) ! [ ] [ : 0 ] u8 {
2019-05-24 18:27:18 -04:00
// TODO refactor to only make 1 allocation.
2022-01-30 11:27:52 -08:00
var it = try argsWithAllocator ( allocator ) ;
2020-05-29 08:40:32 +02:00
defer it . deinit ( ) ;
2020-05-20 19:42:15 +02:00
2020-02-11 23:01:30 +11:00
var contents = std . ArrayList ( u8 ) . init ( allocator ) ;
2019-05-24 18:27:18 -04:00
defer contents . deinit ( ) ;
2019-05-26 23:35:26 -04:00
var slice_list = std . ArrayList ( usize ) . init ( allocator ) ;
2019-05-24 18:27:18 -04:00
defer slice_list . deinit ( ) ;
2022-01-30 11:27:52 -08:00
while ( it . next ( ) ) | arg | {
2020-10-22 17:52:48 -04:00
try contents . appendSlice ( arg [ 0 .. arg . len + 1 ] ) ;
2019-05-24 18:27:18 -04:00
try slice_list . append ( arg . len ) ;
}
2020-11-06 18:54:08 +00:00
const contents_slice = contents . items ;
const slice_sizes = slice_list . items ;
2019-05-24 18:27:18 -04:00
const slice_list_bytes = try math . mul ( usize , @sizeOf ( [ ] u8 ) , slice_sizes . len ) ;
2022-01-28 10:40:03 +01:00
const total_bytes = try math . add ( usize , slice_list_bytes , contents_slice . len ) ;
2019-05-24 18:27:18 -04:00
const buf = try allocator . alignedAlloc ( u8 , @alignOf ( [ ] u8 ) , total_bytes ) ;
errdefer allocator . free ( buf ) ;
2020-10-22 17:52:48 -04:00
const result_slice_list = mem . bytesAsSlice ( [ : 0 ] u8 , buf [ 0 .. slice_list_bytes ] ) ;
2019-05-24 18:27:18 -04:00
const result_contents = buf [ slice_list_bytes .. ] ;
2023-04-26 13:57:08 -07:00
@memcpy ( result_contents [ 0 .. contents_slice . len ] , contents_slice ) ;
2019-05-24 18:27:18 -04:00
var contents_index : usize = 0 ;
2023-02-18 09:02:57 -07:00
for ( slice_sizes , 0 .. ) | len , i | {
2019-05-24 18:27:18 -04:00
const new_index = contents_index + len ;
2020-10-22 17:52:48 -04:00
result_slice_list [ i ] = result_contents [ contents_index .. new_index : 0 ] ;
contents_index = new_index + 1 ;
2019-05-24 18:27:18 -04:00
}
return result_slice_list ;
}
2022-11-10 14:00:55 -07:00
pub fn argsFree ( allocator : Allocator , args_alloc : [ ] const [ : 0 ] u8 ) void {
2019-05-24 18:27:18 -04:00
var total_bytes : usize = 0 ;
for ( args_alloc ) | arg | {
2020-10-22 17:52:48 -04:00
total_bytes += @sizeOf ( [ ] u8 ) + arg . len + 1 ;
2019-05-24 18:27:18 -04:00
}
2023-06-22 18:46:56 +01:00
const unaligned_allocated_buf = @as ( [ * ] const u8 , @ptrCast ( args_alloc . ptr ) ) [ 0 .. total_bytes ] ;
const aligned_allocated_buf : [ ] align ( @alignOf ( [ ] u8 ) ) const u8 = @alignCast ( unaligned_allocated_buf ) ;
2019-05-24 18:27:18 -04:00
return allocator . free ( aligned_allocated_buf ) ;
}
2023-12-18 22:55:46 +01:00
test " ArgIteratorWindows " {
const t = testArgIteratorWindows ;
try t (
\\"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\")"
, & . {
\\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")
,
} ) ;
// Empty
try t ( " " , & . { } ) ;
// Separators
try t ( " aa bb cc " , & . { " aa " , " bb " , " cc " } ) ;
try t ( " aa \t bb \t cc " , & . { " aa " , " bb " , " cc " } ) ;
try t ( " aa \n bb \n cc " , & . { " aa " , " bb \n cc " } ) ;
try t ( " aa \r \n bb \r \n cc " , & . { " aa " , " \n bb \r \n cc " } ) ;
try t ( " aa \r bb \r cc " , & . { " aa " , " bb \r cc " } ) ;
try t ( " aa \x07 bb \x07 cc " , & . { " aa " , " bb \x07 cc " } ) ;
try t ( " aa \x7F bb \x7F cc " , & . { " aa \x7F bb \x7F cc " } ) ;
try t ( " aa🦎bb🦎cc " , & . { " aa🦎bb🦎cc " } ) ;
// Leading/trailing whitespace
try t ( " " , & . { " " } ) ;
try t ( " aa bb " , & . { " " , " aa " , " bb " } ) ;
try t ( " \t \t " , & . { " " } ) ;
try t ( " \t \t aa \t \t bb \t \t " , & . { " " , " aa " , " bb " } ) ;
try t ( " \n \n " , & . { " " , " \n " } ) ;
try t ( " \n \n aa \n \n bb \n \n " , & . { " " , " \n aa \n \n bb \n \n " } ) ;
// Executable name with quotes/backslashes
try t ( " \" aa bb \t cc \n dd \" " , & . { " aa bb \t cc \n dd " } ) ;
try t ( " \" " , & . { " " } ) ;
try t ( " \" \" " , & . { " " } ) ;
try t ( " \" \" \" " , & . { " " , " " } ) ;
try t ( " \" \" \" \" " , & . { " " , " " } ) ;
try t ( " \" \" \" \" \" " , & . { " " , " \" " } ) ;
try t ( " aa \" bb \" cc \" dd " , & . { " aa \" bb \" cc \" dd " } ) ;
try t ( " aa \" bb cc \" dd " , & . { " aa \" bb " , " ccdd " } ) ;
try t ( " \" aa \\ \" bb \" " , & . { " aa \\ " , " bb " } ) ;
try t ( " \" aa \\ \\ \" " , & . { " aa \\ \\ " } ) ;
try t ( " aa \\ \" bb " , & . { " aa \\ \" bb " } ) ;
try t ( " aa \\ \\ \" bb " , & . { " aa \\ \\ \" bb " } ) ;
// Arguments with quotes/backslashes
try t ( " . \" aa bb \t cc \n dd \" " , & . { " . " , " aa bb \t cc \n dd " } ) ;
try t ( " . aa \" \" bb \" \t \" cc \" \n \" dd \" " , & . { " . " , " aa bb \t cc \n dd " } ) ;
try t ( " . " , & . { " . " } ) ;
try t ( " . \" " , & . { " . " , " " } ) ;
try t ( " . \" \" " , & . { " . " , " " } ) ;
try t ( " . \" \" \" " , & . { " . " , " \" " } ) ;
try t ( " . \" \" \" \" " , & . { " . " , " \" " } ) ;
try t ( " . \" \" \" \" \" " , & . { " . " , " \" " } ) ;
try t ( " . \" \" \" \" \" \" " , & . { " . " , " \" \" " } ) ;
try t ( " . \" \" " , & . { " . " , " " } ) ;
try t ( " . \" \" \" " , & . { " . " , " \" " } ) ;
try t ( " . \" \" \" \" " , & . { " . " , " \" " } ) ;
try t ( " . \" \" \" \" \" " , & . { " . " , " \" " } ) ;
try t ( " . \" \" \" \" \" \" " , & . { " . " , " \" \" " } ) ;
try t ( " . \" \" \" \" \" \" \" " , & . { " . " , " \" \" " } ) ;
try t ( " . \\ \" " , & . { " . " , " \" " } ) ;
try t ( " . \\ \" \" " , & . { " . " , " \" " } ) ;
try t ( " . \\ \" \" \" " , & . { " . " , " \" " } ) ;
try t ( " . \\ \" \" \" \" " , & . { " . " , " \" \" " } ) ;
try t ( " . \\ \" \" \" \" \" " , & . { " . " , " \" \" " } ) ;
try t ( " . \\ \" \" \" \" \" \" " , & . { " . " , " \" \" " } ) ;
try t ( " . \" \\ \" " , & . { " . " , " \" " } ) ;
try t ( " . \" \\ \" \" " , & . { " . " , " \" " } ) ;
try t ( " . \" \\ \" \" \" " , & . { " . " , " \" \" " } ) ;
try t ( " . \" \\ \" \" \" \" " , & . { " . " , " \" \" " } ) ;
try t ( " . \" \\ \" \" \" \" \" " , & . { " . " , " \" \" " } ) ;
try t ( " . \" \\ \" \" \" \" \" \" " , & . { " . " , " \" \" \" " } ) ;
try t ( " . aa \\ bb \\ \\ cc \\ \\ \\ dd " , & . { " . " , " aa \\ bb \\ \\ cc \\ \\ \\ dd " } ) ;
try t ( " . \\ \\ \\ \" aa bb \" " , & . { " . " , " \\ \" aa " , " bb " } ) ;
try t ( " . \\ \\ \\ \\ \" aa bb \" " , & . { " . " , " \\ \\ aa bb " } ) ;
}
fn testArgIteratorWindows ( cmd_line : [ ] const u8 , expected_args : [ ] const [ ] const u8 ) ! void {
const cmd_line_w = try std . unicode . utf8ToUtf16LeWithNull ( testing . allocator , cmd_line ) ;
defer testing . allocator . free ( cmd_line_w ) ;
// next
{
var it = try ArgIteratorWindows . init ( testing . allocator , cmd_line_w ) ;
defer it . deinit ( ) ;
for ( expected_args ) | expected | {
if ( it . next ( ) ) | actual | {
try testing . expectEqualStrings ( expected , actual ) ;
} else {
return error . TestUnexpectedResult ;
}
}
try testing . expect ( it . next ( ) == null ) ;
}
// skip
{
var it = try ArgIteratorWindows . init ( testing . allocator , cmd_line_w ) ;
defer it . deinit ( ) ;
for ( 0 .. expected_args . len ) | _ | {
try testing . expect ( it . skip ( ) ) ;
}
try testing . expect ( ! it . skip ( ) ) ;
}
}
2022-01-30 11:27:52 -08:00
test " general arg parsing " {
2022-02-04 19:55:32 +02:00
try testGeneralCmdLine ( " a b \t c d " , & . { " a " , " b " , " c " , " d " } ) ;
try testGeneralCmdLine ( " \" abc \" d e " , & . { " abc " , " d " , " e " } ) ;
try testGeneralCmdLine ( " a \\ \\ \\ b d \" e f \" g h " , & . { " a \\ \\ \\ b " , " de fg " , " h " } ) ;
try testGeneralCmdLine ( " a \\ \\ \\ \" b c d " , & . { " a \\ \" b " , " c " , " d " } ) ;
try testGeneralCmdLine ( " a \\ \\ \\ \\ \" b c \" d e " , & . { " a \\ \\ b c " , " d " , " e " } ) ;
try testGeneralCmdLine ( " a b \t c \" d f " , & . { " a " , " b " , " c " , " d f " } ) ;
try testGeneralCmdLine ( " j k l \\ " , & . { " j " , " k " , " l \\ " } ) ;
try testGeneralCmdLine ( " \" \" x y z \\ \\ " , & . { " " , " x " , " y " , " z \\ \\ " } ) ;
try testGeneralCmdLine ( " \" . \\ .. \\ zig-cache \\ build \" \" bin \\ zig.exe \" \" . \\ .. \" \" . \\ .. \\ zig-cache \" \" --help \" " , & . {
2019-05-24 18:27:18 -04:00
" . \\ .. \\ zig-cache \\ build " ,
" bin \\ zig.exe " ,
" . \\ .. " ,
" . \\ .. \\ zig-cache " ,
" --help " ,
} ) ;
2022-02-04 19:55:32 +02:00
try testGeneralCmdLine (
\\ 'foo' "bar"
, & . { " 'foo' " , " bar " } ) ;
2019-05-24 18:27:18 -04:00
}
2022-01-30 11:27:52 -08:00
fn testGeneralCmdLine ( input_cmd_line : [ ] const u8 , expected_args : [ ] const [ ] const u8 ) ! void {
2022-02-04 19:55:32 +02:00
var it = try ArgIteratorGeneral ( . { } ) . init ( std . testing . allocator , input_cmd_line ) ;
2022-01-30 11:27:52 -08:00
defer it . deinit ( ) ;
for ( expected_args ) | expected_arg | {
const arg = it . next ( ) .? ;
try testing . expectEqualStrings ( expected_arg , arg ) ;
}
try testing . expect ( it . next ( ) == null ) ;
}
test " response file arg parsing " {
try testResponseFileCmdLine (
\\a b
\\c d\
2022-02-04 19:55:32 +02:00
, & . { " a " , " b " , " c " , " d \\ " } ) ;
try testResponseFileCmdLine ( " a b c d \\ " , & . { " a " , " b " , " c " , " d \\ " } ) ;
2022-01-30 11:27:52 -08:00
try testResponseFileCmdLine (
\\j
\\ k l # this is a comment \\ \\\ \\\\ "none" "\\" "\\\"
\\ "m" #another comment
\\
2022-02-04 19:55:32 +02:00
, & . { " j " , " k " , " l " , " m " } ) ;
2022-01-30 11:27:52 -08:00
try testResponseFileCmdLine (
\\ "" q ""
\\ "r s # t" "u\" v" #another comment
\\
2022-02-04 19:55:32 +02:00
, & . { " " , " q " , " " , " r s # t " , " u \" v " } ) ;
2022-01-30 11:27:52 -08:00
try testResponseFileCmdLine (
\\ -l"advapi32" a# b#c d#
\\e\\\
2022-02-04 19:55:32 +02:00
, & . { " -ladvapi32 " , " a# " , " b#c " , " d# " , " e \\ \\ \\ " } ) ;
try testResponseFileCmdLine (
\\ 'foo' "bar"
, & . { " foo " , " bar " } ) ;
2022-01-30 11:27:52 -08:00
}
fn testResponseFileCmdLine ( input_cmd_line : [ ] const u8 , expected_args : [ ] const [ ] const u8 ) ! void {
2022-02-04 19:55:32 +02:00
var it = try ArgIteratorGeneral ( . { . comments = true , . single_quotes = true } )
2022-01-30 11:27:52 -08:00
. init ( std . testing . allocator , input_cmd_line ) ;
defer it . deinit ( ) ;
2019-05-24 18:27:18 -04:00
for ( expected_args ) | expected_arg | {
2022-01-30 11:27:52 -08:00
const arg = it . next ( ) .? ;
2021-05-04 20:47:26 +03:00
try testing . expectEqualStrings ( expected_arg , arg ) ;
2019-05-24 18:27:18 -04:00
}
2022-01-30 11:27:52 -08:00
try testing . expect ( it . next ( ) == null ) ;
2019-05-24 18:27:18 -04:00
}
pub const UserInfo = struct {
2020-09-03 15:08:37 +02:00
uid : os . uid_t ,
gid : os . gid_t ,
2019-05-24 18:27:18 -04:00
} ;
/// POSIX function which gets a uid from username.
pub fn getUserInfo ( name : [ ] const u8 ) ! UserInfo {
2020-02-25 01:52:27 -05:00
return switch ( builtin . os . tag ) {
2023-10-01 23:09:14 +11:00
. linux ,
. macos ,
. watchos ,
. tvos ,
. ios ,
. freebsd ,
. netbsd ,
. openbsd ,
. haiku ,
. solaris ,
. illumos ,
= > posixGetUserInfo ( name ) ,
2019-05-24 18:27:18 -04:00
else = > @compileError ( " Unsupported OS " ) ,
} ;
}
/// TODO this reads /etc/passwd. But sometimes the user/id mapping is in something else
/// like NIS, AD, etc. See `man nss` or look at an strace for `id myuser`.
pub fn posixGetUserInfo ( name : [ ] const u8 ) ! UserInfo {
2020-09-10 13:36:34 +02:00
const file = try std . fs . openFileAbsolute ( " /etc/passwd " , . { } ) ;
defer file . close ( ) ;
const reader = file . reader ( ) ;
2019-05-24 18:27:18 -04:00
const State = enum {
Start ,
WaitForNextLine ,
SkipPassword ,
ReadUserId ,
ReadGroupId ,
} ;
var buf : [ std . mem . page_size ] u8 = undefined ;
var name_index : usize = 0 ;
var state = State . Start ;
2020-09-03 15:08:37 +02:00
var uid : os . uid_t = 0 ;
var gid : os . gid_t = 0 ;
2019-05-24 18:27:18 -04:00
while ( true ) {
2020-06-08 22:34:50 -06:00
const amt_read = try reader . read ( buf [ 0 .. ] ) ;
2019-05-24 18:27:18 -04:00
for ( buf [ 0 .. amt_read ] ) | byte | {
switch ( state ) {
. Start = > switch ( byte ) {
':' = > {
state = if ( name_index == name . len ) State . SkipPassword else State . WaitForNextLine ;
} ,
'\n' = > return error . CorruptPasswordFile ,
else = > {
if ( name_index == name . len or name [ name_index ] != byte ) {
state = . WaitForNextLine ;
}
name_index += 1 ;
} ,
} ,
. WaitForNextLine = > switch ( byte ) {
'\n' = > {
name_index = 0 ;
state = . Start ;
} ,
else = > continue ,
} ,
. SkipPassword = > switch ( byte ) {
'\n' = > return error . CorruptPasswordFile ,
':' = > {
state = . ReadUserId ;
} ,
else = > continue ,
} ,
. ReadUserId = > switch ( byte ) {
':' = > {
state = . ReadGroupId ;
} ,
'\n' = > return error . CorruptPasswordFile ,
else = > {
const digit = switch ( byte ) {
'0' .. . '9' = > byte - '0' ,
else = > return error . CorruptPasswordFile ,
} ;
2022-12-21 16:40:30 +02:00
{
const ov = @mulWithOverflow ( uid , 10 ) ;
if ( ov [ 1 ] != 0 ) return error . CorruptPasswordFile ;
uid = ov [ 0 ] ;
}
{
const ov = @addWithOverflow ( uid , digit ) ;
if ( ov [ 1 ] != 0 ) return error . CorruptPasswordFile ;
uid = ov [ 0 ] ;
}
2019-05-24 18:27:18 -04:00
} ,
} ,
. ReadGroupId = > switch ( byte ) {
'\n' , ':' = > {
return UserInfo {
. uid = uid ,
. gid = gid ,
} ;
} ,
else = > {
const digit = switch ( byte ) {
'0' .. . '9' = > byte - '0' ,
else = > return error . CorruptPasswordFile ,
} ;
2022-12-21 16:40:30 +02:00
{
const ov = @mulWithOverflow ( gid , 10 ) ;
if ( ov [ 1 ] != 0 ) return error . CorruptPasswordFile ;
gid = ov [ 0 ] ;
}
{
const ov = @addWithOverflow ( gid , digit ) ;
if ( ov [ 1 ] != 0 ) return error . CorruptPasswordFile ;
gid = ov [ 0 ] ;
}
2019-05-24 18:27:18 -04:00
} ,
} ,
}
}
if ( amt_read < buf . len ) return error . UserNotFound ;
}
}
2019-05-26 13:17:34 -04:00
pub fn getBaseAddress ( ) usize {
2020-02-25 01:52:27 -05:00
switch ( builtin . os . tag ) {
2019-05-26 13:17:34 -04:00
. linux = > {
const base = os . system . getauxval ( std . elf . AT_BASE ) ;
if ( base != 0 ) {
return base ;
}
const phdr = os . system . getauxval ( std . elf . AT_PHDR ) ;
return phdr - @sizeOf ( std . elf . Ehdr ) ;
} ,
2020-10-12 14:29:43 +05:30
. macos , . freebsd , . netbsd = > {
2023-06-15 13:14:16 +06:00
return @intFromPtr ( & std . c . _mh_execute_header ) ;
2019-05-26 13:17:34 -04:00
} ,
2023-06-15 13:14:16 +06:00
. windows = > return @intFromPtr ( os . windows . kernel32 . GetModuleHandleW ( null ) ) ,
2019-05-26 13:17:34 -04:00
else = > @compileError ( " Unsupported OS " ) ,
}
}
2020-02-17 15:23:59 -05:00
2020-12-26 13:50:26 -07:00
/// Tells whether calling the `execv` or `execve` functions will be a compile error.
2021-05-22 00:56:30 -05:00
pub const can_execv = switch ( builtin . os . tag ) {
2022-02-03 15:27:01 -07:00
. windows , . haiku , . wasi = > false ,
else = > true ,
} ;
/// Tells whether spawning child processes is supported (e.g. via ChildProcess)
pub const can_spawn = switch ( builtin . os . tag ) {
2023-03-23 02:21:15 +08:00
. wasi , . watchos , . tvos = > false ,
2021-05-22 00:56:30 -05:00
else = > true ,
} ;
2020-12-26 13:50:26 -07:00
pub const ExecvError = std . os . ExecveError || error { OutOfMemory } ;
/// Replaces the current process image with the executed process.
/// This function must allocate memory to add a null terminating bytes on path and each arg.
/// It must also convert to KEY=VALUE\0 format for environment variables, and include null
/// pointers after the args and after the environment variables.
/// `argv[0]` is the executable path.
/// This function also uses the PATH environment variable to get the full path to the executable.
/// Due to the heap-allocation, it is illegal to call this function in a fork() child.
/// For that use case, use the `std.os` functions directly.
2022-11-10 14:00:55 -07:00
pub fn execv ( allocator : Allocator , argv : [ ] const [ ] const u8 ) ExecvError {
2020-12-26 13:50:26 -07:00
return execve ( allocator , argv , null ) ;
}
/// Replaces the current process image with the executed process.
/// This function must allocate memory to add a null terminating bytes on path and each arg.
/// It must also convert to KEY=VALUE\0 format for environment variables, and include null
/// pointers after the args and after the environment variables.
/// `argv[0]` is the executable path.
/// This function also uses the PATH environment variable to get the full path to the executable.
/// Due to the heap-allocation, it is illegal to call this function in a fork() child.
/// For that use case, use the `std.os` functions directly.
pub fn execve (
2022-11-10 14:00:55 -07:00
allocator : Allocator ,
2020-12-26 13:50:26 -07:00
argv : [ ] const [ ] const u8 ,
2022-02-06 23:52:08 -07:00
env_map : ? * const EnvMap ,
2020-12-26 13:50:26 -07:00
) ExecvError {
if ( ! can_execv ) @compileError ( " The target OS does not support execv " ) ;
var arena_allocator = std . heap . ArenaAllocator . init ( allocator ) ;
defer arena_allocator . deinit ( ) ;
2021-10-29 02:08:41 +01:00
const arena = arena_allocator . allocator ( ) ;
2020-12-26 13:50:26 -07:00
2023-06-01 21:03:33 -04:00
const argv_buf = try arena . allocSentinel ( ? [ * : 0 ] const u8 , argv . len , null ) ;
2023-02-18 09:02:57 -07:00
for ( argv , 0 .. ) | arg , i | argv_buf [ i ] = ( try arena . dupeZ ( u8 , arg ) ) . ptr ;
2020-12-26 13:50:26 -07:00
const envp = m : {
if ( env_map ) | m | {
const envp_buf = try child_process . createNullDelimitedEnvMap ( arena , m ) ;
break : m envp_buf . ptr ;
2021-10-04 23:47:27 -07:00
} else if ( builtin . link_libc ) {
2020-12-26 13:50:26 -07:00
break : m std . c . environ ;
2021-10-04 23:47:27 -07:00
} else if ( builtin . output_mode == . Exe ) {
2020-12-26 13:50:26 -07:00
// Then we have Zig start code and this works.
// TODO type-safety for null-termination of `os.environ`.
2023-06-22 18:46:56 +01:00
break : m @as ( [ * : null ] const ? [ * : 0 ] const u8 , @ptrCast ( os . environ . ptr ) ) ;
2020-12-26 13:50:26 -07:00
} else {
// TODO come up with a solution for this.
@compileError ( " missing std lib enhancement: std.process.execv implementation has no way to collect the environment variables to forward to the child process " ) ;
}
} ;
2020-12-27 13:00:35 +01:00
return os . execvpeZ_expandArg0 ( . no_expand , argv_buf . ptr [ 0 ] .? , argv_buf . ptr , envp ) ;
2020-12-26 13:50:26 -07:00
}
2023-03-06 00:19:32 -07:00
pub const TotalSystemMemoryError = error {
UnknownTotalSystemMemory ,
} ;
/// Returns the total system memory, in bytes.
pub fn totalSystemMemory ( ) TotalSystemMemoryError ! usize {
switch ( builtin . os . tag ) {
. linux = > {
return totalSystemMemoryLinux ( ) catch return error . UnknownTotalSystemMemory ;
} ,
2023-07-31 11:20:21 -07:00
. freebsd = > {
2023-04-21 23:23:14 +01:00
var physmem : c_ulong = undefined ;
var len : usize = @sizeOf ( c_ulong ) ;
2023-07-31 11:20:21 -07:00
os . sysctlbynameZ ( " hw.physmem " , & physmem , & len , null , 0 ) catch | err | switch ( err ) {
2023-04-21 23:23:14 +01:00
error . NameTooLong , error . UnknownName = > unreachable ,
2023-07-31 22:10:42 -07:00
else = > return error . UnknownTotalSystemMemory ,
2023-04-21 23:23:14 +01:00
} ;
2023-06-22 18:46:56 +01:00
return @as ( usize , @intCast ( physmem ) ) ;
2023-04-21 23:23:14 +01:00
} ,
2023-06-15 14:48:20 -04:00
. openbsd = > {
const mib : [ 2 ] c_int = [ _ ] c_int {
std . os . CTL . HW ,
std . os . HW . PHYSMEM64 ,
} ;
var physmem : i64 = undefined ;
var len : usize = @sizeOf ( @TypeOf ( physmem ) ) ;
std . os . sysctl ( & mib , & physmem , & len , null , 0 ) catch | err | switch ( err ) {
error . NameTooLong = > unreachable , // constant, known good value
error . PermissionDenied = > unreachable , // only when setting values,
error . SystemResources = > unreachable , // memory already on the stack
error . UnknownName = > unreachable , // constant, known good value
else = > return error . UnknownTotalSystemMemory ,
} ;
assert ( physmem >= 0 ) ;
2023-06-22 18:46:56 +01:00
return @as ( usize , @bitCast ( physmem ) ) ;
2023-06-15 14:48:20 -04:00
} ,
2023-03-06 00:19:32 -07:00
. windows = > {
2023-04-12 18:22:07 -05:00
var sbi : std . os . windows . SYSTEM_BASIC_INFORMATION = undefined ;
const rc = std . os . windows . ntdll . NtQuerySystemInformation (
. SystemBasicInformation ,
& sbi ,
@sizeOf ( std . os . windows . SYSTEM_BASIC_INFORMATION ) ,
null ,
) ;
if ( rc != . SUCCESS ) {
2023-04-04 18:08:02 +02:00
return error . UnknownTotalSystemMemory ;
2023-04-12 18:22:07 -05:00
}
return @as ( usize , sbi . NumberOfPhysicalPages ) * sbi . PageSize ;
2023-03-06 00:19:32 -07:00
} ,
else = > return error . UnknownTotalSystemMemory ,
}
}
fn totalSystemMemoryLinux ( ) ! usize {
var file = try std . fs . openFileAbsoluteZ ( " /proc/meminfo " , . { } ) ;
defer file . close ( ) ;
var buf : [ 50 ] u8 = undefined ;
const amt = try file . read ( & buf ) ;
if ( amt != 50 ) return error . Unexpected ;
2023-05-04 18:05:40 -07:00
var it = std . mem . tokenizeAny ( u8 , buf [ 0 .. amt ] , " \n " ) ;
2023-03-06 00:19:32 -07:00
const label = it . next ( ) .? ;
if ( ! std . mem . eql ( u8 , label , " MemTotal: " ) ) return error . Unexpected ;
const int_text = it . next ( ) orelse return error . Unexpected ;
const units = it . next ( ) orelse return error . Unexpected ;
if ( ! std . mem . eql ( u8 , units , " kB " ) ) return error . Unexpected ;
const kilobytes = try std . fmt . parseInt ( usize , int_text , 10 ) ;
return kilobytes * 1024 ;
}
2023-03-12 00:34:11 -07:00
/// Indicate that we are now terminating with a successful exit code.
/// In debug builds, this is a no-op, so that the calling code's
/// cleanup mechanisms are tested and so that external tools that
/// check for resource leaks can be accurate. In release builds, this
/// calls exit(0), and does not return.
pub fn cleanExit ( ) void {
if ( builtin . mode == . Debug ) {
return ;
} else {
exit ( 0 ) ;
}
}