node-process-watcher
Advanced tools
+2
-2
| { | ||
| "name": "node-process-watcher", | ||
| "version": "1.6.2", | ||
| "version": "1.6.3", | ||
| "description": "Get process information in real time", | ||
@@ -47,3 +47,3 @@ "main": "./src/index.js", | ||
| "mocha": "^10.7.3", | ||
| "node-addon-api": "^8.5.0", | ||
| "node-addon-api": "^8.6.0", | ||
| "node-gyp": "^10.3.1", | ||
@@ -50,0 +50,0 @@ "prebuild": "^13.0.1" |
@@ -435,2 +435,34 @@ | ||
| return processes; | ||
| } | ||
| Napi::Value getUsernameByUid(const Napi::CallbackInfo& info) { | ||
| Napi::Env env = info.Env(); | ||
| if (info.Length() < 1 || !info[0].IsNumber()) { | ||
| Napi::TypeError::New(env, "UID required").ThrowAsJavaScriptException(); | ||
| return env.Null(); | ||
| } | ||
| uid_t uid = static_cast<uid_t>(info[0].As<Napi::Number>().Uint32Value()); | ||
| struct passwd *pw = getpwuid(uid); | ||
| if (!pw) return env.Null(); | ||
| return Napi::String::New(env, pw->pw_name); | ||
| } | ||
| Napi::Array getAllUsers(const Napi::CallbackInfo& info) { | ||
| Napi::Env env = info.Env(); | ||
| Napi::Array arr = Napi::Array::New(env); | ||
| struct passwd *pw; | ||
| setpwent(); | ||
| uint32_t idx = 0; | ||
| while ((pw = getpwent()) != nullptr) { | ||
| Napi::Object obj = Napi::Object::New(env); | ||
| obj.Set("uid", static_cast<uint32_t>(pw->pw_uid)); | ||
| obj.Set("username", pw->pw_name); | ||
| arr.Set(idx++, obj); | ||
| } | ||
| endpwent(); | ||
| return arr; | ||
| } |
+33
-0
@@ -651,2 +651,35 @@ #include <napi.h> | ||
| return result; | ||
| } | ||
| Napi::Value getUsernameByUid(const Napi::CallbackInfo& info) { | ||
| Napi::Env env = info.Env(); | ||
| if (info.Length() < 1 || !info[0].IsNumber()) { | ||
| Napi::TypeError::New(env, "UID required").ThrowAsJavaScriptException(); | ||
| return env.Null(); | ||
| } | ||
| uid_t uid = static_cast<uid_t>(info[0].As<Napi::Number>().Uint32Value()); | ||
| struct passwd *pw = getpwuid(uid); | ||
| if (!pw) return env.Null(); | ||
| return Napi::String::New(env, pw->pw_name); | ||
| } | ||
| Napi::Array getAllUsers(const Napi::CallbackInfo& info) { | ||
| Napi::Env env = info.Env(); | ||
| Napi::Array arr = Napi::Array::New(env); | ||
| struct passwd *pw; | ||
| setpwent(); | ||
| uint32_t idx = 0; | ||
| while ((pw = getpwent()) != nullptr) { | ||
| Napi::Object obj = Napi::Object::New(env); | ||
| obj.Set("uid", static_cast<uint32_t>(pw->pw_uid)); | ||
| obj.Set("username", pw->pw_name); | ||
| arr.Set(idx++, obj); | ||
| } | ||
| endpwent(); | ||
| return arr; | ||
| } |
+3
-0
@@ -418,2 +418,4 @@ | ||
| Napi::Function::New(env, get_all_processes)); | ||
| exports.Set("get_username_by_uid", Napi::Function::New(env, getUsernameByUid)); | ||
| exports.Set("get_all_users", Napi::Function::New(env, getAllUsers)); | ||
| #ifdef _WIN32 | ||
@@ -425,2 +427,3 @@ // windwos 下才注册的函数 | ||
| ); | ||
| exports.Set("get_file_owner", Napi::Function::New(env, GetFileOwner)); | ||
| #endif | ||
@@ -427,0 +430,0 @@ |
+18
-0
@@ -63,2 +63,11 @@ // | ||
| // 根据 UID 获取用户名 | ||
| Napi::Value getUsernameByUid(const Napi::CallbackInfo& info); | ||
| // 获取全部用户 [{ uid, username }, ...] | ||
| Napi::Array getAllUsers(const Napi::CallbackInfo& info); | ||
| #ifdef _WIN32 | ||
@@ -71,2 +80,11 @@ // windwos 下才注册的函数 | ||
| struct WindowsUser { | ||
| std::string username; | ||
| std::string domain; | ||
| std::string sid; | ||
| std::vector<std::string> groups; | ||
| }; | ||
| Napi::Object GetFileOwner(const Napi::CallbackInfo& info); | ||
| #endif |
+193
-0
@@ -10,2 +10,3 @@ #ifdef _WIN32 | ||
| #pragma ide diagnostic ignored "UnreachableCode" | ||
| #pragma comment(lib, "netapi32.lib") | ||
@@ -26,2 +27,4 @@ | ||
| #include <userenv.h> | ||
| #include <lm.h> | ||
| #include <sddl.h> | ||
@@ -677,4 +680,194 @@ #pragma comment(lib, "wininet.lib") | ||
| std::vector<WindowsUser> listWindowsUsersWithGroups() { | ||
| std::vector<WindowsUser> users; | ||
| LPUSER_INFO_1 pBuf = nullptr; | ||
| DWORD dwLevel = 1; | ||
| DWORD dwPrefMaxLen = MAX_PREFERRED_LENGTH; | ||
| DWORD dwEntriesRead = 0; | ||
| DWORD dwTotalEntries = 0; | ||
| DWORD dwResumeHandle = 0; | ||
| NET_API_STATUS nStatus; | ||
| do { | ||
| nStatus = NetUserEnum( | ||
| nullptr, | ||
| dwLevel, | ||
| FILTER_NORMAL_ACCOUNT, | ||
| (LPBYTE*)&pBuf, | ||
| dwPrefMaxLen, | ||
| &dwEntriesRead, | ||
| &dwTotalEntries, | ||
| &dwResumeHandle | ||
| ); | ||
| if ((nStatus == NERR_Success || nStatus == ERROR_MORE_DATA) && pBuf != nullptr) { | ||
| LPUSER_INFO_1 pTmpBuf = pBuf; | ||
| for (DWORD i = 0; i < dwEntriesRead; i++) { | ||
| LPUSER_INFO_1 userInfo = &pTmpBuf[i]; | ||
| WindowsUser user; | ||
| // 用户名和域 | ||
| int unameLen = WideCharToMultiByte(CP_UTF8, 0, userInfo->usri1_name, -1, nullptr, 0, nullptr, nullptr); | ||
| std::vector<char> unameBuf(unameLen); | ||
| WideCharToMultiByte(CP_UTF8, 0, userInfo->usri1_name, -1, unameBuf.data(), unameLen, nullptr, nullptr); | ||
| user.username = unameBuf.data(); | ||
| wchar_t domainName[256]; | ||
| DWORD domainSize = 256; | ||
| SID_NAME_USE sidType; | ||
| DWORD cbSid = 0; | ||
| // 先获取 SID 大小 | ||
| LookupAccountNameW(nullptr, userInfo->usri1_name, nullptr, &cbSid, nullptr, &domainSize, &sidType); | ||
| if (cbSid > 0) { | ||
| PSID pSid = (PSID)malloc(cbSid); | ||
| domainSize = 256; | ||
| if (LookupAccountNameW(nullptr, userInfo->usri1_name, pSid, &cbSid, domainName, &domainSize, &sidType)) { | ||
| // 转域名 UTF-8 | ||
| int dLen = WideCharToMultiByte(CP_UTF8, 0, domainName, -1, nullptr, 0, nullptr, nullptr); | ||
| std::vector<char> domainBuf(dLen); | ||
| WideCharToMultiByte(CP_UTF8, 0, domainName, -1, domainBuf.data(), dLen, nullptr, nullptr); | ||
| user.domain = domainBuf.data(); | ||
| // 转 SID 字符串 | ||
| LPSTR sidString = nullptr; | ||
| ConvertSidToStringSidA(pSid, &sidString); | ||
| user.sid = sidString ? sidString : ""; | ||
| if (sidString) LocalFree(sidString); | ||
| } | ||
| free(pSid); | ||
| } | ||
| // 获取用户所属本地组 | ||
| LPLOCALGROUP_USERS_INFO_0 pGroupBuf = nullptr; | ||
| DWORD dwEntries = 0, dwTotal = 0; | ||
| if (NetUserGetLocalGroups(nullptr, userInfo->usri1_name, 0, LG_INCLUDE_INDIRECT, (LPBYTE*)&pGroupBuf, MAX_PREFERRED_LENGTH, &dwEntries, &dwTotal) == NERR_Success) { | ||
| for (DWORD j = 0; j < dwEntries; ++j) { | ||
| int gLen = WideCharToMultiByte(CP_UTF8, 0, pGroupBuf[j].lgrui0_name, -1, nullptr, 0, nullptr, nullptr); | ||
| std::vector<char> gBuf(gLen); | ||
| WideCharToMultiByte(CP_UTF8, 0, pGroupBuf[j].lgrui0_name, -1, gBuf.data(), gLen, nullptr, nullptr); | ||
| user.groups.push_back(gBuf.data()); | ||
| } | ||
| if (pGroupBuf) NetApiBufferFree(pGroupBuf); | ||
| } | ||
| users.push_back(user); | ||
| } | ||
| NetApiBufferFree(pBuf); | ||
| pBuf = nullptr; | ||
| } | ||
| } while (nStatus == ERROR_MORE_DATA); | ||
| return users; | ||
| } | ||
| // 返回全部用户 | ||
| Napi::Array getAllUsers(const Napi::CallbackInfo& info) { | ||
| Napi::Env env = info.Env(); | ||
| Napi::Array arr = Napi::Array::New(env); | ||
| std::vector<WindowsUser> users = listWindowsUsersWithGroups(); | ||
| for (size_t i = 0; i < users.size(); i++) { | ||
| Napi::Object obj = Napi::Object::New(env); | ||
| obj.Set("username", users[i].username); | ||
| obj.Set("domain", users[i].domain); | ||
| obj.Set("sid", users[i].sid); | ||
| // groups 数组 | ||
| Napi::Array groupArr = Napi::Array::New(env, users[i].groups.size()); | ||
| for (size_t j = 0; j < users[i].groups.size(); j++) { | ||
| groupArr.Set(j, users[i].groups[j]); | ||
| } | ||
| obj.Set("groups", groupArr); | ||
| arr.Set(i, obj); | ||
| } | ||
| return arr; | ||
| } | ||
| // 根据 UID 获取用户名 → Windows 下返回 null | ||
| Napi::Value getUsernameByUid(const Napi::CallbackInfo& info) { | ||
| return info.Env().Null(); | ||
| } | ||
| Napi::Object GetFileOwner(const Napi::CallbackInfo& info) { | ||
| Napi::Env env = info.Env(); | ||
| if (info.Length() < 1 || !info[0].IsString()) { | ||
| Napi::TypeError::New(env, "File path expected").ThrowAsJavaScriptException(); | ||
| return Napi::Object::New(env); | ||
| } | ||
| std::string filePath = info[0].As<Napi::String>().Utf8Value(); | ||
| // 转为宽字符 | ||
| int wlen = MultiByteToWideChar(CP_UTF8, 0, filePath.c_str(), -1, nullptr, 0); | ||
| std::wstring wFilePath(wlen, 0); | ||
| MultiByteToWideChar(CP_UTF8, 0, filePath.c_str(), -1, &wFilePath[0], wlen); | ||
| DWORD size = 0; | ||
| GetFileSecurityW(wFilePath.c_str(), OWNER_SECURITY_INFORMATION, nullptr, 0, &size); | ||
| PSECURITY_DESCRIPTOR pSD = (PSECURITY_DESCRIPTOR)malloc(size); | ||
| if (!GetFileSecurityW(wFilePath.c_str(), OWNER_SECURITY_INFORMATION, pSD, size, &size)) { | ||
| free(pSD); | ||
| Napi::Error::New(env, "Failed to get security descriptor").ThrowAsJavaScriptException(); | ||
| return Napi::Object::New(env); | ||
| } | ||
| PSID pOwner = nullptr; | ||
| BOOL ownerDefaulted = FALSE; | ||
| if (!GetSecurityDescriptorOwner(pSD, &pOwner, &ownerDefaulted)) { | ||
| free(pSD); | ||
| Napi::Error::New(env, "Failed to get owner from security descriptor").ThrowAsJavaScriptException(); | ||
| return Napi::Object::New(env); | ||
| } | ||
| // 获取用户名和域名 | ||
| char username[256] = {0}, domain[256] = {0}; | ||
| DWORD unameLen = sizeof(username), domainLen = sizeof(domain); | ||
| SID_NAME_USE sidType; | ||
| LookupAccountSidA(nullptr, pOwner, username, &unameLen, domain, &domainLen, &sidType); | ||
| // 获取 SID 字符串 | ||
| LPSTR sidString = nullptr; | ||
| ConvertSidToStringSidA(pOwner, &sidString); | ||
| // 构造返回对象 | ||
| Napi::Object result = Napi::Object::New(env); | ||
| result.Set("username", std::string(username, strnlen(username, 256))); | ||
| result.Set("domain", std::string(domain, strnlen(domain, 256))); | ||
| result.Set("sid", sidString ? sidString : ""); | ||
| // 根据 SID 类型判断是 user 还是 group | ||
| std::string typeStr = "unknown"; | ||
| switch (sidType) { | ||
| case SidTypeUser: typeStr = "user"; break; | ||
| case SidTypeGroup: typeStr = "group"; break; | ||
| case SidTypeWellKnownGroup: typeStr = "well-known-group"; break; | ||
| case SidTypeAlias: typeStr = "alias"; break; | ||
| case SidTypeDeletedAccount: typeStr = "deleted-account"; break; | ||
| case SidTypeInvalid: typeStr = "invalid"; break; | ||
| case SidTypeUnknown: typeStr = "unknown"; break; | ||
| case SidTypeComputer: typeStr = "computer"; break; | ||
| default: typeStr = "other"; break; | ||
| } | ||
| result.Set("type", typeStr); | ||
| if (sidString) LocalFree(sidString); | ||
| free(pSD); | ||
| return result; | ||
| } | ||
| #pragma clang diagnostic pop | ||
| #endif |
+19
-2
@@ -9,3 +9,3 @@ export interface process_info { | ||
| export interface node_process_watcher { | ||
| export interface node_process_watcher_type { | ||
@@ -127,4 +127,21 @@ /** | ||
| get_all_processes():{pid:number,name:string}[]; | ||
| get_username_by_uid(uid:number):string; | ||
| get_all_users(): ({uid:number,username:string} | | ||
| { | ||
| groups:string[], | ||
| username:string, | ||
| domain:string, | ||
| sid:string | ||
| })[]; | ||
| get_file_owner(file_path:string):{ | ||
| username:string, | ||
| domain:string, | ||
| sid:string, | ||
| type:string | ||
| } | ||
| } | ||
| export declare const node_process_watcher: node_process_watcher; | ||
| export declare const node_process_watcher: node_process_watcher_type; |
96243
11.8%127
12.39%