#include "node.h" #include "node_internals.h" #include "env-inl.h" #include "util-inl.h" #include "uv.h" #include "v8.h" #include // PATH_MAX #include #if defined(_MSC_VER) #include #include #define umask _umask typedef int mode_t; #else #include #include // getrlimit, setrlimit #include // tcgetattr, tcsetattr #include // setuid, getuid #endif #if defined(__POSIX__) && !defined(__ANDROID__) && !defined(__CloudABI__) #include // getpwnam() #include // getgrnam() #endif #ifdef __APPLE__ #include #define environ (*_NSGetEnviron()) #elif !defined(_MSC_VER) extern char **environ; #endif namespace node { using v8::Array; using v8::ArrayBuffer; using v8::BigUint64Array; using v8::Boolean; using v8::Context; using v8::Float64Array; using v8::Function; using v8::FunctionCallbackInfo; using v8::HeapStatistics; using v8::Integer; using v8::Isolate; using v8::Local; using v8::Name; using v8::Number; using v8::PropertyCallbackInfo; using v8::String; using v8::Uint32; using v8::Uint32Array; using v8::Value; Mutex process_mutex; Mutex environ_mutex; // Microseconds in a second, as a float, used in CPUUsage() below #define MICROS_PER_SEC 1e6 // used in Hrtime() below #define NANOS_PER_SEC 1000000000 #ifdef _WIN32 /* MAX_PATH is in characters, not bytes. Make sure we have enough headroom. */ #define CHDIR_BUFSIZE (MAX_PATH * 4) #else #define CHDIR_BUFSIZE (PATH_MAX) #endif void Abort(const FunctionCallbackInfo& args) { Abort(); } void Chdir(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); CHECK(env->is_main_thread()); CHECK_EQ(args.Length(), 1); CHECK(args[0]->IsString()); Utf8Value path(env->isolate(), args[0]); int err = uv_chdir(*path); if (err) { // Also include the original working directory, since that will usually // be helpful information when debugging a `chdir()` failure. char buf[CHDIR_BUFSIZE]; size_t cwd_len = sizeof(buf); uv_cwd(buf, &cwd_len); return env->ThrowUVException(err, "chdir", nullptr, buf, *path); } } // CPUUsage use libuv's uv_getrusage() this-process resource usage accessor, // to access ru_utime (user CPU time used) and ru_stime (system CPU time used), // which are uv_timeval_t structs (long tv_sec, long tv_usec). // Returns those values as Float64 microseconds in the elements of the array // passed to the function. void CPUUsage(const FunctionCallbackInfo& args) { uv_rusage_t rusage; // Call libuv to get the values we'll return. int err = uv_getrusage(&rusage); if (err) { // On error, return the strerror version of the error code. Local errmsg = OneByteString(args.GetIsolate(), uv_strerror(err)); return args.GetReturnValue().Set(errmsg); } // Get the double array pointer from the Float64Array argument. CHECK(args[0]->IsFloat64Array()); Local array = args[0].As(); CHECK_EQ(array->Length(), 2); Local ab = array->Buffer(); double* fields = static_cast(ab->GetContents().Data()); // Set the Float64Array elements to be user / system values in microseconds. fields[0] = MICROS_PER_SEC * rusage.ru_utime.tv_sec + rusage.ru_utime.tv_usec; fields[1] = MICROS_PER_SEC * rusage.ru_stime.tv_sec + rusage.ru_stime.tv_usec; } void Cwd(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); char buf[CHDIR_BUFSIZE]; size_t cwd_len = sizeof(buf); int err = uv_cwd(buf, &cwd_len); if (err) return env->ThrowUVException(err, "uv_cwd"); Local cwd = String::NewFromUtf8(env->isolate(), buf, v8::NewStringType::kNormal, cwd_len).ToLocalChecked(); args.GetReturnValue().Set(cwd); } // Hrtime exposes libuv's uv_hrtime() high-resolution timer. // This is the legacy version of hrtime before BigInt was introduced in // JavaScript. // The value returned by uv_hrtime() is a 64-bit int representing nanoseconds, // so this function instead fills in an Uint32Array with 3 entries, // to avoid any integer overflow possibility. // The first two entries contain the second part of the value // broken into the upper/lower 32 bits to be converted back in JS, // because there is no Uint64Array in JS. // The third entry contains the remaining nanosecond part of the value. void Hrtime(const FunctionCallbackInfo& args) { uint64_t t = uv_hrtime(); Local ab = args[0].As()->Buffer(); uint32_t* fields = static_cast(ab->GetContents().Data()); fields[0] = (t / NANOS_PER_SEC) >> 32; fields[1] = (t / NANOS_PER_SEC) & 0xffffffff; fields[2] = t % NANOS_PER_SEC; } void HrtimeBigInt(const FunctionCallbackInfo& args) { Local ab = args[0].As()->Buffer(); uint64_t* fields = static_cast(ab->GetContents().Data()); fields[0] = uv_hrtime(); } void Kill(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); Local context = env->context(); if (args.Length() != 2) return env->ThrowError("Bad argument."); int pid; if (!args[0]->Int32Value(context).To(&pid)) return; int sig; if (!args[1]->Int32Value(context).To(&sig)) return; int err = uv_kill(pid, sig); args.GetReturnValue().Set(err); } void MemoryUsage(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); size_t rss; int err = uv_resident_set_memory(&rss); if (err) return env->ThrowUVException(err, "uv_resident_set_memory"); Isolate* isolate = env->isolate(); // V8 memory usage HeapStatistics v8_heap_stats; isolate->GetHeapStatistics(&v8_heap_stats); // Get the double array pointer from the Float64Array argument. CHECK(args[0]->IsFloat64Array()); Local array = args[0].As(); CHECK_EQ(array->Length(), 4); Local ab = array->Buffer(); double* fields = static_cast(ab->GetContents().Data()); fields[0] = rss; fields[1] = v8_heap_stats.total_heap_size(); fields[2] = v8_heap_stats.used_heap_size(); fields[3] = isolate->AdjustAmountOfExternalAllocatedMemory(0); } // Most of the time, it's best to use `console.error` to write // to the process.stderr stream. However, in some cases, such as // when debugging the stream.Writable class or the process.nextTick // function, it is useful to bypass JavaScript entirely. void RawDebug(const FunctionCallbackInfo& args) { CHECK(args.Length() == 1 && args[0]->IsString() && "must be called with a single string"); Utf8Value message(args.GetIsolate(), args[0]); PrintErrorString("%s\n", *message); fflush(stderr); } void StartProfilerIdleNotifier(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); env->StartProfilerIdleNotifier(); } void StopProfilerIdleNotifier(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); env->StopProfilerIdleNotifier(); } void Umask(const FunctionCallbackInfo& args) { uint32_t old; CHECK_EQ(args.Length(), 1); CHECK(args[0]->IsUndefined() || args[0]->IsUint32()); if (args[0]->IsUndefined()) { old = umask(0); umask(static_cast(old)); } else { int oct = args[0].As()->Value(); old = umask(static_cast(oct)); } args.GetReturnValue().Set(old); } void Uptime(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); double uptime; uv_update_time(env->event_loop()); uptime = uv_now(env->event_loop()) - prog_start_time; args.GetReturnValue().Set(Number::New(env->isolate(), uptime / 1000)); } #if defined(__POSIX__) && !defined(__ANDROID__) && !defined(__CloudABI__) static const uid_t uid_not_found = static_cast(-1); static const gid_t gid_not_found = static_cast(-1); static uid_t uid_by_name(const char* name) { struct passwd pwd; struct passwd* pp; char buf[8192]; errno = 0; pp = nullptr; if (getpwnam_r(name, &pwd, buf, sizeof(buf), &pp) == 0 && pp != nullptr) return pp->pw_uid; return uid_not_found; } static char* name_by_uid(uid_t uid) { struct passwd pwd; struct passwd* pp; char buf[8192]; int rc; errno = 0; pp = nullptr; if ((rc = getpwuid_r(uid, &pwd, buf, sizeof(buf), &pp)) == 0 && pp != nullptr) { return strdup(pp->pw_name); } if (rc == 0) errno = ENOENT; return nullptr; } static gid_t gid_by_name(const char* name) { struct group pwd; struct group* pp; char buf[8192]; errno = 0; pp = nullptr; if (getgrnam_r(name, &pwd, buf, sizeof(buf), &pp) == 0 && pp != nullptr) return pp->gr_gid; return gid_not_found; } #if 0 // For future use. static const char* name_by_gid(gid_t gid) { struct group pwd; struct group* pp; char buf[8192]; int rc; errno = 0; pp = nullptr; if ((rc = getgrgid_r(gid, &pwd, buf, sizeof(buf), &pp)) == 0 && pp != nullptr) { return strdup(pp->gr_name); } if (rc == 0) errno = ENOENT; return nullptr; } #endif static uid_t uid_by_name(Isolate* isolate, Local value) { if (value->IsUint32()) { return static_cast(value.As()->Value()); } else { Utf8Value name(isolate, value); return uid_by_name(*name); } } static gid_t gid_by_name(Isolate* isolate, Local value) { if (value->IsUint32()) { return static_cast(value.As()->Value()); } else { Utf8Value name(isolate, value); return gid_by_name(*name); } } void GetUid(const FunctionCallbackInfo& args) { // uid_t is an uint32_t on all supported platforms. args.GetReturnValue().Set(static_cast(getuid())); } void GetGid(const FunctionCallbackInfo& args) { // gid_t is an uint32_t on all supported platforms. args.GetReturnValue().Set(static_cast(getgid())); } void GetEUid(const FunctionCallbackInfo& args) { // uid_t is an uint32_t on all supported platforms. args.GetReturnValue().Set(static_cast(geteuid())); } void GetEGid(const FunctionCallbackInfo& args) { // gid_t is an uint32_t on all supported platforms. args.GetReturnValue().Set(static_cast(getegid())); } void SetGid(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); CHECK(env->is_main_thread()); CHECK_EQ(args.Length(), 1); CHECK(args[0]->IsUint32() || args[0]->IsString()); gid_t gid = gid_by_name(env->isolate(), args[0]); if (gid == gid_not_found) { // Tells JS to throw ERR_INVALID_CREDENTIAL args.GetReturnValue().Set(1); } else if (setgid(gid)) { env->ThrowErrnoException(errno, "setgid"); } else { args.GetReturnValue().Set(0); } } void SetEGid(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); CHECK(env->is_main_thread()); CHECK_EQ(args.Length(), 1); CHECK(args[0]->IsUint32() || args[0]->IsString()); gid_t gid = gid_by_name(env->isolate(), args[0]); if (gid == gid_not_found) { // Tells JS to throw ERR_INVALID_CREDENTIAL args.GetReturnValue().Set(1); } else if (setegid(gid)) { env->ThrowErrnoException(errno, "setegid"); } else { args.GetReturnValue().Set(0); } } void SetUid(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); CHECK(env->is_main_thread()); CHECK_EQ(args.Length(), 1); CHECK(args[0]->IsUint32() || args[0]->IsString()); uid_t uid = uid_by_name(env->isolate(), args[0]); if (uid == uid_not_found) { // Tells JS to throw ERR_INVALID_CREDENTIAL args.GetReturnValue().Set(1); } else if (setuid(uid)) { env->ThrowErrnoException(errno, "setuid"); } else { args.GetReturnValue().Set(0); } } void SetEUid(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); CHECK(env->is_main_thread()); CHECK_EQ(args.Length(), 1); CHECK(args[0]->IsUint32() || args[0]->IsString()); uid_t uid = uid_by_name(env->isolate(), args[0]); if (uid == uid_not_found) { // Tells JS to throw ERR_INVALID_CREDENTIAL args.GetReturnValue().Set(1); } else if (seteuid(uid)) { env->ThrowErrnoException(errno, "seteuid"); } else { args.GetReturnValue().Set(0); } } void GetGroups(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); int ngroups = getgroups(0, nullptr); if (ngroups == -1) return env->ThrowErrnoException(errno, "getgroups"); gid_t* groups = new gid_t[ngroups]; ngroups = getgroups(ngroups, groups); if (ngroups == -1) { delete[] groups; return env->ThrowErrnoException(errno, "getgroups"); } Local groups_list = Array::New(env->isolate(), ngroups); bool seen_egid = false; gid_t egid = getegid(); for (int i = 0; i < ngroups; i++) { groups_list->Set(i, Integer::New(env->isolate(), groups[i])); if (groups[i] == egid) seen_egid = true; } delete[] groups; if (seen_egid == false) groups_list->Set(ngroups, Integer::New(env->isolate(), egid)); args.GetReturnValue().Set(groups_list); } void SetGroups(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); CHECK_EQ(args.Length(), 1); CHECK(args[0]->IsArray()); Local groups_list = args[0].As(); size_t size = groups_list->Length(); gid_t* groups = new gid_t[size]; for (size_t i = 0; i < size; i++) { gid_t gid = gid_by_name(env->isolate(), groups_list->Get(i)); if (gid == gid_not_found) { delete[] groups; // Tells JS to throw ERR_INVALID_CREDENTIAL args.GetReturnValue().Set(static_cast(i + 1)); return; } groups[i] = gid; } int rc = setgroups(size, groups); delete[] groups; if (rc == -1) return env->ThrowErrnoException(errno, "setgroups"); args.GetReturnValue().Set(0); } void InitGroups(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); CHECK_EQ(args.Length(), 2); CHECK(args[0]->IsUint32() || args[0]->IsString()); CHECK(args[1]->IsUint32() || args[1]->IsString()); Utf8Value arg0(env->isolate(), args[0]); gid_t extra_group; bool must_free; char* user; if (args[0]->IsUint32()) { user = name_by_uid(args[0].As()->Value()); must_free = true; } else { user = *arg0; must_free = false; } if (user == nullptr) { // Tells JS to throw ERR_INVALID_CREDENTIAL return args.GetReturnValue().Set(1); } extra_group = gid_by_name(env->isolate(), args[1]); if (extra_group == gid_not_found) { if (must_free) free(user); // Tells JS to throw ERR_INVALID_CREDENTIAL return args.GetReturnValue().Set(2); } int rc = initgroups(user, extra_group); if (must_free) free(user); if (rc) return env->ThrowErrnoException(errno, "initgroups"); args.GetReturnValue().Set(0); } #endif // __POSIX__ && !defined(__ANDROID__) && !defined(__CloudABI__) void ProcessTitleGetter(Local property, const PropertyCallbackInfo& info) { char buffer[512]; uv_get_process_title(buffer, sizeof(buffer)); info.GetReturnValue().Set(String::NewFromUtf8(info.GetIsolate(), buffer, v8::NewStringType::kNormal).ToLocalChecked()); } void ProcessTitleSetter(Local property, Local value, const PropertyCallbackInfo& info) { node::Utf8Value title(info.GetIsolate(), value); TRACE_EVENT_METADATA1("__metadata", "process_name", "name", TRACE_STR_COPY(*title)); uv_set_process_title(*title); } void EnvGetter(Local property, const PropertyCallbackInfo& info) { Isolate* isolate = info.GetIsolate(); if (property->IsSymbol()) { return info.GetReturnValue().SetUndefined(); } Mutex::ScopedLock lock(environ_mutex); #ifdef __POSIX__ node::Utf8Value key(isolate, property); const char* val = getenv(*key); if (val) { return info.GetReturnValue().Set(String::NewFromUtf8(isolate, val, v8::NewStringType::kNormal).ToLocalChecked()); } #else // _WIN32 node::TwoByteValue key(isolate, property); WCHAR buffer[32767]; // The maximum size allowed for environment variables. SetLastError(ERROR_SUCCESS); DWORD result = GetEnvironmentVariableW(reinterpret_cast(*key), buffer, arraysize(buffer)); // If result >= sizeof buffer the buffer was too small. That should never // happen. If result == 0 and result != ERROR_SUCCESS the variable was not // found. if ((result > 0 || GetLastError() == ERROR_SUCCESS) && result < arraysize(buffer)) { const uint16_t* two_byte_buffer = reinterpret_cast(buffer); Local rc = String::NewFromTwoByte(isolate, two_byte_buffer); return info.GetReturnValue().Set(rc); } #endif } void EnvSetter(Local property, Local value, const PropertyCallbackInfo& info) { Environment* env = Environment::GetCurrent(info); if (env->options()->pending_deprecation && env->EmitProcessEnvWarning() && !value->IsString() && !value->IsNumber() && !value->IsBoolean()) { 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; } Mutex::ScopedLock lock(environ_mutex); #ifdef __POSIX__ node::Utf8Value key(info.GetIsolate(), property); node::Utf8Value val(info.GetIsolate(), value); setenv(*key, *val, 1); #else // _WIN32 node::TwoByteValue key(info.GetIsolate(), property); node::TwoByteValue val(info.GetIsolate(), 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); } void EnvQuery(Local property, const PropertyCallbackInfo& info) { Mutex::ScopedLock lock(environ_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; #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) | static_cast(v8::DontDelete) | static_cast(v8::DontEnum); } } #endif } if (rc != -1) info.GetReturnValue().Set(rc); } void EnvDeleter(Local property, const PropertyCallbackInfo& info) { Mutex::ScopedLock lock(environ_mutex); if (property->IsString()) { #ifdef __POSIX__ node::Utf8Value key(info.GetIsolate(), property); unsetenv(*key); #else node::TwoByteValue key(info.GetIsolate(), 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); } void EnvEnumerator(const PropertyCallbackInfo& info) { Environment* env = Environment::GetCurrent(info); Isolate* isolate = env->isolate(); Local ctx = env->context(); Local fn = env->push_values_to_array_function(); Local argv[NODE_PUSH_VAL_TO_ARRAY_MAX]; size_t idx = 0; Mutex::ScopedLock lock(environ_mutex); #ifdef __POSIX__ int size = 0; while (environ[size]) size++; Local envarr = Array::New(isolate); for (int i = 0; i < size; ++i) { const char* var = environ[i]; const char* s = strchr(var, '='); const int length = s ? s - var : strlen(var); argv[idx] = String::NewFromUtf8(isolate, var, v8::NewStringType::kNormal, length).ToLocalChecked(); if (++idx >= arraysize(argv)) { fn->Call(ctx, envarr, idx, argv).ToLocalChecked(); idx = 0; } } if (idx > 0) { fn->Call(ctx, envarr, idx, argv).ToLocalChecked(); } #else // _WIN32 WCHAR* environment = GetEnvironmentStringsW(); if (environment == nullptr) return; // This should not happen. Local envarr = Array::New(isolate); WCHAR* p = environment; while (*p) { WCHAR* s; if (*p == L'=') { // If the key starts with '=' it is a hidden environment variable. p += wcslen(p) + 1; continue; } else { s = wcschr(p, L'='); } if (!s) { s = p + wcslen(p); } const uint16_t* two_byte_buffer = reinterpret_cast(p); const size_t two_byte_buffer_len = s - p; argv[idx] = String::NewFromTwoByte(isolate, two_byte_buffer, String::kNormalString, two_byte_buffer_len); if (++idx >= arraysize(argv)) { fn->Call(ctx, envarr, idx, argv).ToLocalChecked(); idx = 0; } p = s + wcslen(s) + 1; } if (idx > 0) { fn->Call(ctx, envarr, idx, argv).ToLocalChecked(); } FreeEnvironmentStringsW(environment); #endif info.GetReturnValue().Set(envarr); } void GetParentProcessId(Local property, const PropertyCallbackInfo& info) { info.GetReturnValue().Set(Integer::New(info.GetIsolate(), uv_os_getppid())); } } // namespace node