From 21e7b629cbc8d4e382204189d4c2cbaf8b0e989e Mon Sep 17 00:00:00 2001 From: Anna Henningsen Date: Wed, 13 Mar 2019 12:24:27 +0000 Subject: [PATCH] src: allow per-Environment set of env vars Abstract the `process.env` backing mechanism in C++ to allow different kinds of backing stores for `process.env` for different Environments. PR-URL: https://github.com/nodejs/node/pull/26544 Fixes: https://github.com/nodejs/node/issues/24947 Reviewed-By: Ruben Bridgewater Reviewed-By: Vse Mozhet Byt Reviewed-By: Yongsheng Zhang Reviewed-By: James M Snell Reviewed-By: Benjamin Gruenbaum Reviewed-By: Joyee Cheung --- src/env-inl.h | 8 ++ src/env.cc | 4 +- src/env.h | 20 ++++ src/node_credentials.cc | 24 ++++- src/node_env_var.cc | 202 ++++++++++++++++++++++++---------------- src/node_internals.h | 2 +- 6 files changed, 176 insertions(+), 84 deletions(-) diff --git a/src/env-inl.h b/src/env-inl.h index 69c70804c8a750..3eedbb857bc7f3 100644 --- a/src/env-inl.h +++ b/src/env-inl.h @@ -447,6 +447,14 @@ inline uint64_t Environment::timer_base() const { return timer_base_; } +inline std::shared_ptr Environment::envvars() { + return envvars_; +} + +inline void Environment::set_envvars(std::shared_ptr envvars) { + envvars_ = envvars; +} + inline bool Environment::printed_error() const { return printed_error_; } diff --git a/src/env.cc b/src/env.cc index 174cadcc04336c..ffdb4289bb1570 100644 --- a/src/env.cc +++ b/src/env.cc @@ -178,6 +178,8 @@ Environment::Environment(IsolateData* isolate_data, set_as_callback_data_template(templ); } + set_envvars(per_process::real_environment); + // We create new copies of the per-Environment option sets, so that it is // easier to modify them after Environment creation. The defaults are // part of the per-Isolate option set, for which in turn the defaults are @@ -221,7 +223,7 @@ Environment::Environment(IsolateData* isolate_data, should_abort_on_uncaught_toggle_[0] = 1; std::string debug_cats; - credentials::SafeGetenv("NODE_DEBUG_NATIVE", &debug_cats); + credentials::SafeGetenv("NODE_DEBUG_NATIVE", &debug_cats, this); set_debug_categories(debug_cats, true); isolate()->GetHeapProfiler()->AddBuildEmbedderGraphCallback( diff --git a/src/env.h b/src/env.h index 743c236827b07d..e6c3a1c0374aab 100644 --- a/src/env.h +++ b/src/env.h @@ -540,6 +540,23 @@ class AsyncRequest : public MemoryRetainer { std::atomic_bool stopped_ {true}; }; +class KVStore { + public: + virtual v8::Local Get(v8::Isolate* isolate, + v8::Local key) const = 0; + virtual void Set(v8::Isolate* isolate, + v8::Local key, + v8::Local value) = 0; + virtual int32_t Query(v8::Isolate* isolate, + v8::Local key) const = 0; + virtual void Delete(v8::Isolate* isolate, v8::Local key) = 0; + virtual v8::Local Enumerate(v8::Isolate* isolate) const = 0; +}; + +namespace per_process { +extern std::shared_ptr real_environment; +} + class AsyncHooks { public: // Reason for both UidFields and Fields are that one is stored as a double* @@ -789,6 +806,8 @@ class Environment { inline ImmediateInfo* immediate_info(); inline TickInfo* tick_info(); inline uint64_t timer_base() const; + inline std::shared_ptr envvars(); + inline void set_envvars(std::shared_ptr envvars); inline IsolateData* isolate_data() const; @@ -1075,6 +1094,7 @@ class Environment { ImmediateInfo immediate_info_; TickInfo tick_info_; const uint64_t timer_base_; + std::shared_ptr envvars_; bool printed_error_ = false; bool emit_env_nonstring_warning_ = true; bool emit_err_name_warning_ = true; diff --git a/src/node_credentials.cc b/src/node_credentials.cc index 8d38c38a0c5706..23b9ad2893dcae 100644 --- a/src/node_credentials.cc +++ b/src/node_credentials.cc @@ -15,11 +15,14 @@ using v8::Array; using v8::Context; using v8::Function; using v8::FunctionCallbackInfo; +using v8::HandleScope; using v8::Isolate; using v8::Local; using v8::MaybeLocal; +using v8::NewStringType; using v8::Object; using v8::String; +using v8::TryCatch; using v8::Uint32; using v8::Value; @@ -30,13 +33,27 @@ bool linux_at_secure = false; namespace credentials { // Look up environment variable unless running as setuid root. -bool SafeGetenv(const char* key, std::string* text) { +bool SafeGetenv(const char* key, std::string* text, Environment* env) { #if !defined(__CloudABI__) && !defined(_WIN32) if (per_process::linux_at_secure || getuid() != geteuid() || getgid() != getegid()) goto fail; #endif + if (env != nullptr) { + HandleScope handle_scope(env->isolate()); + TryCatch ignore_errors(env->isolate()); + MaybeLocal value = env->envvars()->Get( + env->isolate(), + String::NewFromUtf8(env->isolate(), key, NewStringType::kNormal) + .ToLocalChecked()); + if (value.IsEmpty()) goto fail; + String::Utf8Value utf8_value(env->isolate(), value.ToLocalChecked()); + if (*utf8_value == nullptr) goto fail; + *text = std::string(*utf8_value, utf8_value.length()); + return true; + } + { Mutex::ScopedLock lock(per_process::env_var_mutex); if (const char* value = getenv(key)) { @@ -52,10 +69,11 @@ bool SafeGetenv(const char* key, std::string* text) { static void SafeGetenv(const FunctionCallbackInfo& args) { CHECK(args[0]->IsString()); - Isolate* isolate = args.GetIsolate(); + Environment* env = Environment::GetCurrent(args); + Isolate* isolate = env->isolate(); Utf8Value strenvtag(isolate, args[0]); std::string text; - if (!SafeGetenv(*strenvtag, &text)) return; + if (!SafeGetenv(*strenvtag, &text, env)) return; Local result = ToV8Value(isolate->GetCurrentContext(), text).ToLocalChecked(); args.GetReturnValue().Set(result); diff --git a/src/node_env_var.cc b/src/node_env_var.cc index 7e4913702b86cd..6bd799e19f8fc8 100644 --- a/src/node_env_var.cc +++ b/src/node_env_var.cc @@ -27,24 +27,29 @@ using v8::PropertyCallbackInfo; using v8::String; using v8::Value; +class RealEnvStore final : public KVStore { + public: + Local Get(Isolate* isolate, Local key) const override; + void Set(Isolate* isolate, Local key, Local value) override; + int32_t Query(Isolate* isolate, Local key) const override; + void Delete(Isolate* isolate, Local key) override; + Local Enumerate(Isolate* isolate) const override; +}; + namespace per_process { Mutex env_var_mutex; +std::shared_ptr real_environment = std::make_shared(); } // namespace per_process -static void EnvGetter(Local property, - const PropertyCallbackInfo& info) { - Isolate* isolate = info.GetIsolate(); - if (property->IsSymbol()) { - return info.GetReturnValue().SetUndefined(); - } +Local RealEnvStore::Get(Isolate* isolate, + Local property) const { Mutex::ScopedLock lock(per_process::env_var_mutex); #ifdef __POSIX__ node::Utf8Value key(isolate, property); const char* val = getenv(*key); if (val) { - return info.GetReturnValue().Set( - String::NewFromUtf8(isolate, val, NewStringType::kNormal) - .ToLocalChecked()); + return String::NewFromUtf8(isolate, val, NewStringType::kNormal) + .ToLocalChecked(); } #else // _WIN32 node::TwoByteValue key(isolate, property); @@ -62,106 +67,72 @@ static void EnvGetter(Local property, isolate, two_byte_buffer, NewStringType::kNormal); if (rc.IsEmpty()) { isolate->ThrowException(ERR_STRING_TOO_LONG(isolate)); - return; + return Local(); } - return info.GetReturnValue().Set(rc.ToLocalChecked()); + return rc.ToLocalChecked(); } #endif + return Local(); } -static void EnvSetter(Local property, - Local value, - const PropertyCallbackInfo& info) { - Environment* env = Environment::GetCurrent(info); - // calling env->EmitProcessEnvWarning() sets a variable indicating that - // warnings have been emitted. It should be called last after other - // conditions leading to a warning have been met. - if (env->options()->pending_deprecation && !value->IsString() && - !value->IsNumber() && !value->IsBoolean() && - env->EmitProcessEnvWarning()) { - if (ProcessEmitDeprecationWarning( - env, - "Assigning any value other than a string, number, or boolean to a " - "process.env property is deprecated. Please make sure to convert " - "the " - "value to a string before setting process.env with it.", - "DEP0104") - .IsNothing()) - return; - } - +void RealEnvStore::Set(Isolate* isolate, + Local property, + Local value) { Mutex::ScopedLock lock(per_process::env_var_mutex); #ifdef __POSIX__ - node::Utf8Value key(info.GetIsolate(), property); - node::Utf8Value val(info.GetIsolate(), value); + node::Utf8Value key(isolate, property); + node::Utf8Value val(isolate, value); setenv(*key, *val, 1); #else // _WIN32 - node::TwoByteValue key(info.GetIsolate(), property); - node::TwoByteValue val(info.GetIsolate(), value); + node::TwoByteValue key(isolate, property); + node::TwoByteValue val(isolate, value); WCHAR* key_ptr = reinterpret_cast(*key); // Environment variables that start with '=' are read-only. if (key_ptr[0] != L'=') { SetEnvironmentVariableW(key_ptr, reinterpret_cast(*val)); } #endif - // Whether it worked or not, always return value. - info.GetReturnValue().Set(value); } -static void EnvQuery(Local property, - const PropertyCallbackInfo& info) { +int32_t RealEnvStore::Query(Isolate* isolate, Local property) const { Mutex::ScopedLock lock(per_process::env_var_mutex); - int32_t rc = -1; // Not found unless proven otherwise. - if (property->IsString()) { #ifdef __POSIX__ - node::Utf8Value key(info.GetIsolate(), property); - if (getenv(*key)) rc = 0; + node::Utf8Value key(isolate, property); + if (getenv(*key)) return 0; #else // _WIN32 - node::TwoByteValue key(info.GetIsolate(), property); - WCHAR* key_ptr = reinterpret_cast(*key); - SetLastError(ERROR_SUCCESS); - if (GetEnvironmentVariableW(key_ptr, nullptr, 0) > 0 || - GetLastError() == ERROR_SUCCESS) { - rc = 0; - if (key_ptr[0] == L'=') { - // Environment variables that start with '=' are hidden and read-only. - rc = static_cast(v8::ReadOnly) | + node::TwoByteValue key(isolate, property); + WCHAR* key_ptr = reinterpret_cast(*key); + SetLastError(ERROR_SUCCESS); + if (GetEnvironmentVariableW(key_ptr, nullptr, 0) > 0 || + GetLastError() == ERROR_SUCCESS) { + if (key_ptr[0] == L'=') { + // Environment variables that start with '=' are hidden and read-only. + return static_cast(v8::ReadOnly) | static_cast(v8::DontDelete) | static_cast(v8::DontEnum); - } } -#endif + return 0; } - if (rc != -1) info.GetReturnValue().Set(rc); +#endif + return -1; } -static void EnvDeleter(Local property, - const PropertyCallbackInfo& info) { +void RealEnvStore::Delete(Isolate* isolate, Local property) { Mutex::ScopedLock lock(per_process::env_var_mutex); - if (property->IsString()) { #ifdef __POSIX__ - node::Utf8Value key(info.GetIsolate(), property); - unsetenv(*key); + node::Utf8Value key(isolate, property); + unsetenv(*key); #else - node::TwoByteValue key(info.GetIsolate(), property); - WCHAR* key_ptr = reinterpret_cast(*key); - SetEnvironmentVariableW(key_ptr, nullptr); + node::TwoByteValue key(isolate, property); + WCHAR* key_ptr = reinterpret_cast(*key); + SetEnvironmentVariableW(key_ptr, nullptr); #endif - } - - // process.env never has non-configurable properties, so always - // return true like the tc39 delete operator. - info.GetReturnValue().Set(true); } -static void EnvEnumerator(const PropertyCallbackInfo& info) { - Environment* env = Environment::GetCurrent(info); - Isolate* isolate = env->isolate(); - +Local RealEnvStore::Enumerate(Isolate* isolate) const { Mutex::ScopedLock lock(per_process::env_var_mutex); - Local envarr; - int env_size = 0; #ifdef __POSIX__ + int env_size = 0; while (environ[env_size]) { env_size++; } @@ -177,7 +148,8 @@ static void EnvEnumerator(const PropertyCallbackInfo& info) { #else // _WIN32 std::vector> env_v; WCHAR* environment = GetEnvironmentStringsW(); - if (environment == nullptr) return; // This should not happen. + if (environment == nullptr) + return Array::New(isolate); // This should not happen. WCHAR* p = environment; while (*p) { WCHAR* s; @@ -198,7 +170,7 @@ static void EnvEnumerator(const PropertyCallbackInfo& info) { if (rc.IsEmpty()) { isolate->ThrowException(ERR_STRING_TOO_LONG(isolate)); FreeEnvironmentStringsW(environment); - return; + return Local(); } env_v.push_back(rc.ToLocalChecked()); p = s + wcslen(s) + 1; @@ -206,8 +178,80 @@ static void EnvEnumerator(const PropertyCallbackInfo& info) { FreeEnvironmentStringsW(environment); #endif - envarr = Array::New(isolate, env_v.data(), env_v.size()); - info.GetReturnValue().Set(envarr); + return Array::New(isolate, env_v.data(), env_v.size()); +} + +static void EnvGetter(Local property, + const PropertyCallbackInfo& info) { + Environment* env = Environment::GetCurrent(info); + if (property->IsSymbol()) { + return info.GetReturnValue().SetUndefined(); + } + CHECK(property->IsString()); + info.GetReturnValue().Set( + env->envvars()->Get(env->isolate(), property.As())); +} + +static void EnvSetter(Local property, + Local value, + const PropertyCallbackInfo& info) { + Environment* env = Environment::GetCurrent(info); + // calling env->EmitProcessEnvWarning() sets a variable indicating that + // warnings have been emitted. It should be called last after other + // conditions leading to a warning have been met. + if (env->options()->pending_deprecation && !value->IsString() && + !value->IsNumber() && !value->IsBoolean() && + env->EmitProcessEnvWarning()) { + if (ProcessEmitDeprecationWarning( + env, + "Assigning any value other than a string, number, or boolean to a " + "process.env property is deprecated. Please make sure to convert " + "the " + "value to a string before setting process.env with it.", + "DEP0104") + .IsNothing()) + return; + } + + Local key; + Local value_string; + if (!property->ToString(env->context()).ToLocal(&key) || + !value->ToString(env->context()).ToLocal(&value_string)) { + return; + } + + env->envvars()->Set(env->isolate(), key, value_string); + + // Whether it worked or not, always return value. + info.GetReturnValue().Set(value); +} + +static void EnvQuery(Local property, + const PropertyCallbackInfo& info) { + Environment* env = Environment::GetCurrent(info); + if (property->IsString()) { + int32_t rc = env->envvars()->Query(env->isolate(), property.As()); + if (rc != -1) info.GetReturnValue().Set(rc); + } +} + +static void EnvDeleter(Local property, + const PropertyCallbackInfo& info) { + Environment* env = Environment::GetCurrent(info); + if (property->IsString()) { + env->envvars()->Delete(env->isolate(), property.As()); + } + + // process.env never has non-configurable properties, so always + // return true like the tc39 delete operator. + info.GetReturnValue().Set(true); +} + +static void EnvEnumerator(const PropertyCallbackInfo& info) { + Environment* env = Environment::GetCurrent(info); + + info.GetReturnValue().Set( + env->envvars()->Enumerate(env->isolate())); } MaybeLocal CreateEnvVarProxy(Local context, diff --git a/src/node_internals.h b/src/node_internals.h index 8c2864aae8d3c6..012848491ea721 100644 --- a/src/node_internals.h +++ b/src/node_internals.h @@ -293,7 +293,7 @@ int ThreadPoolWork::CancelWork() { #endif // __POSIX__ && !defined(__ANDROID__) && !defined(__CloudABI__) namespace credentials { -bool SafeGetenv(const char* key, std::string* text); +bool SafeGetenv(const char* key, std::string* text, Environment* env = nullptr); } // namespace credentials void DefineZlibConstants(v8::Local target);