diff --git a/sp.h b/sp.h index ff7424b..ce01011 100644 --- a/sp.h +++ b/sp.h @@ -5057,6 +5057,13 @@ SP_IMP void sp_fmon_os_push_dir(sp_fmon_os_t* os, sp_str_t dir); SP_IMP void sp_fmon_os_push_file(sp_fmon_os_t* os, sp_str_t file); #endif +// @env +SP_IMP void sp_env_table_init(sp_mem_t mem, sp_env_t* env); +#if defined(SP_WIN32) +SP_IMP sp_hash_t sp_env_on_hash_key(void* key, u64 size); +SP_IMP bool sp_env_on_compare_key(void* ka, void* kb, u64 size); +#endif + // @os SP_IMP void sp_os_signal_from_native(s32 sig); SP_IMP s32 sp_os_signal_to_native(sp_os_signal_t signal); @@ -5065,8 +5072,8 @@ SP_IMP BOOL WINAPI sp_os_signal_console_ctrl(DWORD type); #endif // @ps -#if defined(SP_POSIX) SP_IMP sp_env_t sp_ps_build_env(sp_ps_env_config_t* config, sp_mem_t mem); +#if defined(SP_POSIX) SP_IMP bool sp_ps_is_fd_valid(sp_sys_fd_t fd); SP_IMP void sp_ps_set_cwd(posix_spawn_file_actions_t* fa, sp_str_t cwd); SP_IMP sp_io_close_mode_t sp_ps_io_close_mode(sp_ps_io_mode_t mode); @@ -5075,6 +5082,7 @@ SP_IMP void* sp_posix_thread_launch(void* args); #if defined(SP_WIN32) SP_IMP void sp_ps_win32_append_quoted_arg(sp_io_writer_t* builder, sp_str_t arg); SP_IMP c8* sp_ps_build_windows_cmdline(sp_mem_t mem, sp_ps_config_t* config); +SP_IMP sp_str_t sp_env_to_win32_block(sp_mem_t mem, sp_env_t* env); SP_IMP sp_win32_handle_t sp_ps_win32_open_null(sp_win32_dword_t access); SP_IMP sp_win32_handle_t sp_ps_win32_fd_to_handle(sp_sys_fd_t fd); SP_IMP sp_ps_status_t sp_ps_win32_finish_process(sp_ps_t* ps); @@ -14052,11 +14060,44 @@ void sp_sys_env_p(const c8** env, u32* len) { } +#if defined(SP_WIN32) +sp_hash_t sp_env_on_hash_key(void* key, u64 size) { + sp_unused(size); + sp_str_t name = *(sp_str_t*)key; + sp_hash_t hash = SP_HT_HASH_SEED; + sp_for(i, name.len) { + c8 folded = sp_c8_to_lower(name.data[i]); + hash = sp_hash_bytes(&folded, 1, hash); + } + return hash; +} + +bool sp_env_on_compare_key(void* ka, void* kb, u64 size) { + sp_unused(size); + sp_str_t a = *(sp_str_t*)ka; + sp_str_t b = *(sp_str_t*)kb; + if (a.len != b.len) return false; + sp_for(i, a.len) { + if (sp_c8_to_lower(a.data[i]) != sp_c8_to_lower(b.data[i])) return false; + } + return true; +} + +void sp_env_table_init(sp_mem_t mem, sp_env_t* env) { + sp_ht_init(mem, env->vars); + sp_ht_set_fns(env->vars, sp_env_on_hash_key, sp_env_on_compare_key); +} +#else +void sp_env_table_init(sp_mem_t mem, sp_env_t* env) { + sp_str_ht_init(mem, env->vars); +} +#endif + void sp_env_init(sp_mem_t mem, sp_env_t* env) { *env = sp_zero_s(sp_env_t); env->mem = mem; env->arena = sp_mem_arena_new_ex(mem, 4096, SP_MEM_ALIGNMENT); - sp_str_ht_init(mem, env->vars); + sp_env_table_init(mem, env); } sp_env_t sp_env_capture(sp_mem_t mem) { @@ -14630,31 +14671,6 @@ u64 sp_thread_get_id() { // █████ █████ █████ ░░░███████░ ░░█████████ ██████████░░█████████ ░░█████████ // ░░░░░ ░░░░░ ░░░░░ ░░░░░░░ ░░░░░░░░░ ░░░░░░░░░░ ░░░░░░░░░ ░░░░░░░░░ // @ps @process -#if defined(SP_POSIX) -typedef struct { - posix_spawn_file_actions_t* fa; - sp_ps_io_file_number_t file_number; - s32 flag; - s32 mode; - struct { - s32 read; - s32 write; - } pipes; -} sp_ps_stdio_config_entry_t; - -typedef struct { - sp_ps_stdio_config_entry_t in; - sp_ps_stdio_config_entry_t out; - sp_ps_stdio_config_entry_t err; -} sp_ps_stdio_config_t; - -struct sp_ps_os { - s32 pid; -}; - -SP_PRIVATE void sp_ps_set_cwd(posix_spawn_file_actions_t* fa, sp_str_t cwd); -SP_PRIVATE sp_da(c8*) sp_ps_build_posix_args(sp_mem_t mem, sp_ps_config_t* config); - SP_PRIVATE sp_env_t sp_ps_build_env(sp_ps_env_config_t* config, sp_mem_t mem) { sp_env_t env = sp_zero; @@ -14681,6 +14697,31 @@ SP_PRIVATE sp_env_t sp_ps_build_env(sp_ps_env_config_t* config, sp_mem_t mem) { return env; } +#if defined(SP_POSIX) +typedef struct { + posix_spawn_file_actions_t* fa; + sp_ps_io_file_number_t file_number; + s32 flag; + s32 mode; + struct { + s32 read; + s32 write; + } pipes; +} sp_ps_stdio_config_entry_t; + +typedef struct { + sp_ps_stdio_config_entry_t in; + sp_ps_stdio_config_entry_t out; + sp_ps_stdio_config_entry_t err; +} sp_ps_stdio_config_t; + +struct sp_ps_os { + s32 pid; +}; + +SP_PRIVATE void sp_ps_set_cwd(posix_spawn_file_actions_t* fa, sp_str_t cwd); +SP_PRIVATE sp_da(c8*) sp_ps_build_posix_args(sp_mem_t mem, sp_ps_config_t* config); + bool sp_ps_is_fd_valid(sp_sys_fd_t fd) { return fd > 0; } @@ -15586,6 +15627,22 @@ void sp_ps_config_add_arg(sp_mem_t mem, sp_ps_config_t* config, sp_str_t arg) { } } +sp_str_t sp_env_to_win32_block(sp_mem_t mem, sp_env_t* env) { + sp_io_dyn_mem_writer_t b = sp_zero; + sp_io_dyn_mem_writer_init(mem, &b); + + sp_str_ht_for(env->vars, it) { + sp_str_t key = *sp_str_ht_it_getkp(env->vars, it); + sp_str_t val = *sp_str_ht_it_getp(env->vars, it); + sp_fmt_io(&b.base, "{}={}", sp_fmt_str(key), sp_fmt_str(val)); + sp_io_write_c8(&b.base, '\0'); + } + + sp_io_write_c8(&b.base, '\0'); + sp_io_write_c8(&b.base, '\0'); + return sp_io_dyn_mem_writer_as_str(&b); +} + sp_ps_t sp_ps_create(sp_mem_t mem, sp_ps_config_t config) { sp_ps_t proc = sp_zero_s(sp_ps_t); proc.mem = mem; @@ -15599,49 +15656,8 @@ sp_ps_t sp_ps_create(sp_mem_t mem, sp_ps_config_t config) { sp_mem_arena_marker_t scratch = sp_mem_begin_scratch_for(mem); c8* cmdline = sp_ps_build_windows_cmdline(scratch.mem, &config); - - sp_io_dyn_mem_writer_t b = sp_zero; - sp_io_dyn_mem_writer_init(scratch.mem, &b); - - switch (config.env.mode) { - case SP_PS_ENV_INHERIT: { - for (sp_os_env_it_t it = sp_os_env_it_begin(); sp_os_env_it_valid(&it); sp_os_env_it_next(&it)) { - sp_io_write_str(&b.base, it.key, SP_NULLPTR); - sp_io_write_c8(&b.base, '='); - sp_io_write_str(&b.base, it.value, SP_NULLPTR); - sp_io_write_c8(&b.base, '\0'); - } - break; - } - case SP_PS_ENV_EXISTING: { - sp_env_t* env = &config.env.env; - sp_str_ht_for(env->vars, it) { - sp_str_t key = *sp_str_ht_it_getkp(env->vars, it); - sp_str_t val = *sp_str_ht_it_getp(env->vars, it); - - sp_fmt_io(&b.base, "{}={}", sp_fmt_str(key), sp_fmt_str(val)); - sp_io_write_c8(&b.base, '\0'); - } - - break; - } - case SP_PS_ENV_CLEAN: { - break; - } - } - - for (s32 it = SP_PS_MAX_ENV - 1; it >= 0; it--) { - sp_env_var_t e = config.env.extra[it]; - if (sp_str_empty(e.key)) continue; - - sp_fmt_io(&b.base, "{}={}", sp_fmt_str(e.key), sp_fmt_str(e.value)); - sp_io_write_c8(&b.base, '\0'); - } - - sp_io_write_c8(&b.base, '\0'); - sp_io_write_c8(&b.base, '\0'); - - sp_str_t env = sp_io_dyn_mem_writer_as_str(&b); + sp_env_t env = sp_ps_build_env(&config.env, scratch.mem); + sp_str_t block = sp_env_to_win32_block(scratch.mem, &env); c8* cwd = sp_str_empty(config.cwd) ? SP_NULLPTR : sp_str_to_cstr(scratch.mem, config.cwd); @@ -15675,7 +15691,7 @@ sp_ps_t sp_ps_create(sp_mem_t mem, sp_ps_config_t config) { startup.hStdOutput = io.out.child; startup.hStdError = io.err.child; - created = CreateProcessA(SP_NULLPTR, cmdline, SP_NULLPTR, SP_NULLPTR, true, 0, (void*)env.data, cwd, &startup, &process_info); + created = CreateProcessA(SP_NULLPTR, cmdline, SP_NULLPTR, SP_NULLPTR, true, 0, (void*)block.data, cwd, &startup, &process_info); sp_mem_end_scratch(scratch); if (!created) { diff --git a/test/env.c b/test/env.c index 0c876b5..b430f6e 100644 --- a/test/env.c +++ b/test/env.c @@ -281,3 +281,326 @@ UTEST_F(env, iterate_matches_capture) { EXPECT_EQ(it_count, sp_env_count(&captured)); sp_env_destroy(&captured); } + +/////////////// +// HOST KEYS // +/////////////// +#define ENV_TEST_MAX_OS 2 +#define ENV_TEST_MAX_OPS 4 +#define ENV_TEST_MAX_VARS 4 +#define ENV_TEST_SENTINEL "sp-env-host-keys" +#define ENV_TEST_NEW_KEY "SP_ENV_HOST_KEYS_TEST" +#define ENV_TEST_OS_KEY "SP_ENV_HOST_KEYS_OS" +#define ENV_TEST_OS_KEY_LOWER "sp_env_host_keys_os" + +#if defined(SP_WIN32) +static void env_os_set(const c8* key, const c8* value) { + _putenv_s(key, value); +} + +static void env_os_unset(const c8* key) { + _putenv_s(key, ""); +} +#elif defined(SP_POSIX) +static void env_os_set(const c8* key, const c8* value) { + setenv(key, value, 1); +} + +static void env_os_unset(const c8* key) { + unsetenv(key); +} +#else +static void env_os_set(const c8* key, const c8* value) { + SP_UNIMPLEMENTED(); + sp_unused(key); + sp_unused(value); +} + +static void env_os_unset(const c8* key) { + SP_UNIMPLEMENTED(); + sp_unused(key); +} +#endif + +typedef struct { + const c8* key; + const c8* value; +} env_os_var_t; + +typedef enum { + ENV_OP_NONE, + ENV_OP_INSERT, + ENV_OP_ERASE, + ENV_OP_COPY, +} env_op_kind_t; + +typedef struct { + env_op_kind_t kind; + const c8* key; + const c8* value; +} env_op_t; + +typedef enum { + ENV_VALUE_MISSING, + ENV_VALUE_LITERAL, + ENV_VALUE_OS, +} env_value_kind_t; + +typedef struct { + const c8* key; + env_value_kind_t kind; + const c8* value; +} env_var_expect_t; + +typedef struct { + s32 count_delta; + bool keys_preserved; + env_var_expect_t vars [ENV_TEST_MAX_VARS]; +} env_host_expect_t; + +typedef struct { + env_os_var_t os [ENV_TEST_MAX_OS]; + env_op_t ops [ENV_TEST_MAX_OPS]; + env_host_expect_t expect; +} env_host_test_t; + +void run_env_host_test(struct env* utest_fixture, s32* utest_result, env_host_test_t t) { + SKIP_ON_WASM() + EXPECT_FALSE(sp_str_empty(sp_os_env_get(sp_str_lit("PATH")))); + + sp_carr_for(t.os, it) { + if (!t.os[it].key) break; + env_os_set(t.os[it].key, t.os[it].value); + } + + sp_env_t env = sp_env_capture(ut.mem); + u32 captured = sp_env_count(&env); + + sp_ht(sp_str_t, bool) spellings = sp_zero; + sp_str_ht_init(ut.mem, spellings); + sp_str_ht_for(env.vars, it) { + sp_str_ht_insert(spellings, sp_str_copy(ut.mem, *sp_str_ht_it_getkp(env.vars, it)), true); + } + + sp_carr_for(t.ops, it) { + env_op_t op = t.ops[it]; + switch (op.kind) { + case ENV_OP_NONE: { + break; + } + case ENV_OP_INSERT: { + sp_env_insert_c(&env, op.key, op.value); + break; + } + case ENV_OP_ERASE: { + sp_env_erase_c(&env, op.key); + break; + } + case ENV_OP_COPY: { + sp_env_t copy = sp_env_copy(ut.mem, &env); + sp_env_destroy(&env); + env = copy; + break; + } + } + } + + EXPECT_EQ(sp_env_count(&env), (u32)((s32)captured + t.expect.count_delta)); + + sp_carr_for(t.expect.vars, it) { + env_var_expect_t var = t.expect.vars[it]; + if (!var.key) break; + + sp_str_t key = sp_cstr_as_str(var.key); + switch (var.kind) { + case ENV_VALUE_MISSING: { + EXPECT_FALSE(sp_env_contains(&env, key)); + break; + } + case ENV_VALUE_LITERAL: { + EXPECT_TRUE(sp_env_contains(&env, key)); + SP_EXPECT_STR_EQ_CSTR(sp_env_get(&env, key), var.value); + break; + } + case ENV_VALUE_OS: { + sp_str_t os = sp_os_env_get(key); + EXPECT_TRUE(sp_env_contains(&env, key) == !sp_str_empty(os)); + SP_EXPECT_STR_EQ(sp_env_get(&env, key), os); + break; + } + } + } + + if (t.expect.keys_preserved) { + EXPECT_EQ(sp_env_count(&env), (u32)sp_str_ht_size(spellings)); + sp_str_ht_for(env.vars, it) { + EXPECT_TRUE(sp_str_ht_get(spellings, *sp_str_ht_it_getkp(env.vars, it)) != SP_NULLPTR); + } + } + + sp_carr_for(t.os, it) { + if (!t.os[it].key) break; + env_os_unset(t.os[it].key); + } + + sp_str_ht_free(spellings); + sp_env_destroy(&env); +} + +UTEST_F(env, host_capture_get_matches_os) { + run_env_host_test(&ut, &ur, (env_host_test_t) { + .expect = { + .vars = { + { .key = "PATH", .kind = ENV_VALUE_OS }, + { .key = SP_TEST_ENV_OS_KEY, .kind = ENV_VALUE_OS }, + { .key = "SP_ENV_HOST_KEYS_UNSET", .kind = ENV_VALUE_OS }, + }, + }, + }); +} + +UTEST_F(env, host_os_set_spelling_matches_os) { + SKIP_ON_FREESTANDING() + SKIP_ON_WASM() + run_env_host_test(&ut, &ur, (env_host_test_t) { + .os = { + { .key = ENV_TEST_OS_KEY, .value = ENV_TEST_SENTINEL }, + }, + .expect = { + .vars = { + { .key = ENV_TEST_OS_KEY, .kind = ENV_VALUE_OS }, + { .key = ENV_TEST_OS_KEY_LOWER, .kind = ENV_VALUE_OS }, + }, + }, + }); +} + +UTEST_F(env, host_insert_over_captured) { + run_env_host_test(&ut, &ur, (env_host_test_t) { + .ops = { + { .kind = ENV_OP_INSERT, .key = "PATH", .value = ENV_TEST_SENTINEL }, + }, + .expect = { + .keys_preserved = true, + .vars = { + { .key = "PATH", .kind = ENV_VALUE_LITERAL, .value = ENV_TEST_SENTINEL }, + { .key = SP_TEST_ENV_OS_KEY, .kind = ENV_VALUE_OS }, + }, + }, + }); +} + +UTEST_F(env, host_insert_over_captured_twice) { + run_env_host_test(&ut, &ur, (env_host_test_t) { + .ops = { + { .kind = ENV_OP_INSERT, .key = "PATH", .value = "first" }, + { .kind = ENV_OP_INSERT, .key = "PATH", .value = ENV_TEST_SENTINEL }, + }, + .expect = { + .keys_preserved = true, + .vars = { + { .key = "PATH", .kind = ENV_VALUE_LITERAL, .value = ENV_TEST_SENTINEL }, + }, + }, + }); +} + +UTEST_F(env, host_erase_captured) { + run_env_host_test(&ut, &ur, (env_host_test_t) { + .ops = { + { .kind = ENV_OP_ERASE, .key = "PATH" }, + }, + .expect = { + .count_delta = -1, + .vars = { + { .key = "PATH" }, + { .key = SP_TEST_ENV_OS_KEY, .kind = ENV_VALUE_OS }, + }, + }, + }); +} + +UTEST_F(env, host_erase_then_insert) { + run_env_host_test(&ut, &ur, (env_host_test_t) { + .ops = { + { .kind = ENV_OP_ERASE, .key = "PATH" }, + { .kind = ENV_OP_INSERT, .key = "PATH", .value = ENV_TEST_SENTINEL }, + }, + .expect = { + .vars = { + { .key = "PATH", .kind = ENV_VALUE_LITERAL, .value = ENV_TEST_SENTINEL }, + }, + }, + }); +} + +UTEST_F(env, host_copy_matches_os) { + run_env_host_test(&ut, &ur, (env_host_test_t) { + .ops = { + { .kind = ENV_OP_COPY }, + }, + .expect = { + .keys_preserved = true, + .vars = { + { .key = "PATH", .kind = ENV_VALUE_OS }, + { .key = SP_TEST_ENV_OS_KEY, .kind = ENV_VALUE_OS }, + }, + }, + }); +} + +UTEST_F(env, host_copy_then_insert_over) { + run_env_host_test(&ut, &ur, (env_host_test_t) { + .ops = { + { .kind = ENV_OP_COPY }, + { .kind = ENV_OP_INSERT, .key = "PATH", .value = ENV_TEST_SENTINEL }, + }, + .expect = { + .keys_preserved = true, + .vars = { + { .key = "PATH", .kind = ENV_VALUE_LITERAL, .value = ENV_TEST_SENTINEL }, + }, + }, + }); +} + +UTEST_F(env, host_insert_over_then_copy) { + run_env_host_test(&ut, &ur, (env_host_test_t) { + .ops = { + { .kind = ENV_OP_INSERT, .key = "PATH", .value = ENV_TEST_SENTINEL }, + { .kind = ENV_OP_COPY }, + }, + .expect = { + .keys_preserved = true, + .vars = { + { .key = "PATH", .kind = ENV_VALUE_LITERAL, .value = ENV_TEST_SENTINEL }, + }, + }, + }); +} + +UTEST_F(env, host_insert_new) { + run_env_host_test(&ut, &ur, (env_host_test_t) { + .ops = { + { .kind = ENV_OP_INSERT, .key = ENV_TEST_NEW_KEY, .value = ENV_TEST_SENTINEL }, + }, + .expect = { + .count_delta = 1, + .vars = { + { .key = ENV_TEST_NEW_KEY, .kind = ENV_VALUE_LITERAL, .value = ENV_TEST_SENTINEL }, + { .key = "PATH", .kind = ENV_VALUE_OS }, + }, + }, + }); +} + +UTEST_F(env, capture_values_match_os) { + SKIP_ON_WASM() + sp_env_t captured = sp_env_capture(ut.mem); + + for (sp_os_env_it_t it = sp_os_env_it_begin(); sp_os_env_it_valid(&it); sp_os_env_it_next(&it)) { + SP_EXPECT_STR_EQ(sp_env_get(&captured, it.key), sp_os_env_get(it.key)); + } + + sp_env_destroy(&captured); +} diff --git a/test/ps.c b/test/ps.c index e7a625a..8638771 100644 --- a/test/ps.c +++ b/test/ps.c @@ -618,6 +618,40 @@ UTEST_F(ps, env_existing) { }); } +UTEST_F(ps, env_inherit_extra_overrides) { + sp_test_proc_env_verify(&ut, &ur, (sp_test_proc_env_config_t) { + .config = { + .extra = { + { .key = sp_str_lit("PATH"), .value = sp_str_lit("sp-env-host-keys") }, + }, + }, + .expected = { + { .key = sp_str_lit("PATH"), .value = sp_str_lit("sp-env-host-keys") }, + } + }); +} + +UTEST_F(ps, env_existing_extra_overrides) { + sp_env_t env = sp_zero; + sp_env_init(ut.mem, &env); + sp_env_insert(&env, sp_str_lit("jerry"), sp_str_lit("garcia")); + sp_env_insert(&env, sp_str_lit("phil"), sp_str_lit("lesh")); + + sp_test_proc_env_verify(&ut, &ur, (sp_test_proc_env_config_t) { + .config = { + .env = env, + .extra = { + { .key = sp_str_lit("jerry"), .value = sp_str_lit("weir") }, + }, + .mode = SP_PS_ENV_EXISTING, + }, + .expected = { + { .key = sp_str_lit("jerry"), .value = sp_str_lit("weir") }, + { .key = sp_str_lit("phil"), .value = sp_str_lit("lesh") }, + } + }); +} + UTEST_F(ps, empty_env_var) { sp_test_proc_env_verify(&ut, &ur, (sp_test_proc_env_config_t) { .config = {