#include "napi/native_api.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "hilog/log.h" extern "C" { #include "smb2/smb2.h" #include "smb2/libsmb2.h" } namespace { constexpr unsigned int SMB_LOG_DOMAIN = 0xD001780; constexpr const char *SMB_LOG_TAG = "libsmb2"; #define SMB_LOGI(fmt, ...) OH_LOG_Print(LOG_APP, LOG_INFO, SMB_LOG_DOMAIN, SMB_LOG_TAG, fmt, ##__VA_ARGS__) #define SMB_LOGE(fmt, ...) OH_LOG_Print(LOG_APP, LOG_ERROR, SMB_LOG_DOMAIN, SMB_LOG_TAG, fmt, ##__VA_ARGS__) inline void NapiCheck(napi_status status, const char *message) { if (status != napi_ok) { throw std::runtime_error(message == nullptr ? "NAPI call failed" : message); } } struct SambaClient { int64_t id = 0; std::string host; smb2_context *ctx = nullptr; bool closed = false; }; struct SambaSession { int64_t id = 0; std::weak_ptr client; std::string username; std::string password; std::string domain; bool closed = false; int64_t activeTreeId = 0; }; struct SambaTree { int64_t id = 0; std::weak_ptr session; std::string share; bool connected = false; }; std::atomic g_nextHandle{1}; std::mutex g_mutex; std::unordered_map> g_clients; std::unordered_map> g_sessions; std::unordered_map> g_trees; int64_t GenerateHandle() { return g_nextHandle.fetch_add(1); } void Ensure(bool condition, const std::string &message) { if (!condition) { throw std::runtime_error(message); } } std::string Trim(const std::string &value) { if (value.empty()) { return value; } size_t start = 0; size_t end = value.size(); while (start < end && std::isspace(static_cast(value[start])) != 0) { start++; } if (start == end) { return ""; } while (end > start && std::isspace(static_cast(value[end - 1])) != 0) { end--; } return value.substr(start, end - start); } std::string NormalizeRemotePath(const std::string &raw) { std::string trimmed = Trim(raw); if (trimmed.empty() || trimmed == "/" || trimmed == "\\") { return ""; } size_t index = 0; while (index < trimmed.size() && (trimmed[index] == '/' || trimmed[index] == '\\')) { index++; } std::string normalized = trimmed.substr(index); if (normalized.empty()) { return ""; } std::replace(normalized.begin(), normalized.end(), '\\', '/'); return normalized; } std::string BuildSmbErrorMessage(smb2_context *ctx, const std::string &fallback) { const char *error = ctx != nullptr ? smb2_get_error(ctx) : nullptr; if (error == nullptr || std::strlen(error) == 0) { return fallback; } return fallback + ": " + error; } std::string ReadString(napi_env env, napi_value value, const char *name, bool required) { napi_valuetype type = napi_undefined; NapiCheck(napi_typeof(env, value, &type), "Failed to read argument type"); if (type == napi_null || type == napi_undefined) { if (required) { throw std::runtime_error(std::string(name) + " is required"); } return ""; } if (type != napi_string) { throw std::runtime_error(std::string(name) + " must be a string"); } size_t length = 0; NapiCheck(napi_get_value_string_utf8(env, value, nullptr, 0, &length), "Failed to measure string length"); std::string result(length, '\0'); size_t written = 0; NapiCheck(napi_get_value_string_utf8(env, value, result.data(), length + 1, &written), "Failed to read string value"); result.resize(written); return result; } int64_t ReadInt64(napi_env env, napi_value value, const char *name) { napi_valuetype type = napi_undefined; NapiCheck(napi_typeof(env, value, &type), "Failed to read numeric argument type"); if (type != napi_number) { throw std::runtime_error(std::string(name) + " must be a number"); } int64_t result = 0; NapiCheck(napi_get_value_int64(env, value, &result), "Failed to read numeric argument"); return result; } napi_value CreateInt64Value(napi_env env, int64_t value) { napi_value result = nullptr; NapiCheck(napi_create_int64(env, value, &result), "Failed to create int value"); return result; } napi_value CreateUndefined(napi_env env) { napi_value result = nullptr; NapiCheck(napi_get_undefined(env, &result), "Failed to create undefined value"); return result; } std::shared_ptr RequireClient(int64_t clientId) { std::lock_guard lock(g_mutex); auto it = g_clients.find(clientId); if (it == g_clients.end() || it->second == nullptr || it->second->closed) { throw std::runtime_error("Invalid SMB client handle"); } return it->second; } std::shared_ptr RequireSession(int64_t sessionId) { std::lock_guard lock(g_mutex); auto it = g_sessions.find(sessionId); if (it == g_sessions.end() || it->second == nullptr || it->second->closed) { throw std::runtime_error("Invalid SMB session handle"); } return it->second; } std::shared_ptr RequireTree(int64_t treeId) { std::lock_guard lock(g_mutex); auto it = g_trees.find(treeId); if (it == g_trees.end() || it->second == nullptr) { throw std::runtime_error("Invalid SMB tree handle"); } return it->second; } void RemoveTreeEntry(int64_t treeId) { std::lock_guard lock(g_mutex); g_trees.erase(treeId); } void ResetSessionTree(const std::shared_ptr &session, int64_t treeId) { if (session == nullptr) { return; } std::lock_guard lock(g_mutex); if (session->activeTreeId == treeId) { session->activeTreeId = 0; } } void CloseTreeInternal(const std::shared_ptr &tree) { if (!tree) { return; } auto session = tree->session.lock(); auto client = session ? session->client.lock() : nullptr; if (client && client->ctx && tree->connected) { smb2_disconnect_share(client->ctx); } tree->connected = false; ResetSessionTree(session, tree->id); } void RemoveTreesForSession(int64_t sessionId) { std::vector> ownedTrees; { std::lock_guard lock(g_mutex); for (auto it = g_trees.begin(); it != g_trees.end(); ) { auto session = it->second ? it->second->session.lock() : nullptr; if (!session || session->id == sessionId) { ownedTrees.push_back(it->second); it = g_trees.erase(it); continue; } ++it; } } for (const auto &tree : ownedTrees) { CloseTreeInternal(tree); } } void RemoveSessionsForClient(int64_t clientId) { std::vector> sessions; { std::lock_guard lock(g_mutex); for (auto it = g_sessions.begin(); it != g_sessions.end(); ) { auto client = it->second ? it->second->client.lock() : nullptr; if (!client || client->id == clientId) { it->second->closed = true; sessions.push_back(it->second); it = g_sessions.erase(it); continue; } ++it; } } for (const auto &session : sessions) { RemoveTreesForSession(session->id); } } void CloseClientContext(const std::shared_ptr &client) { if (!client || client->ctx == nullptr) { return; } smb2_disconnect_share(client->ctx); smb2_destroy_context(client->ctx); client->ctx = nullptr; client->closed = true; } napi_value CreateClient(napi_env env, napi_callback_info info) { try { size_t argc = 1; napi_value args[1] = {nullptr}; NapiCheck(napi_get_cb_info(env, info, &argc, args, nullptr, nullptr), "Failed to get arguments for createClient"); if (argc < 1) { throw std::runtime_error("host argument is required"); } std::string host = Trim(ReadString(env, args[0], "host", true)); Ensure(!host.empty(), "host must not be empty"); smb2_context *ctx = smb2_init_context(); if (ctx == nullptr) { throw std::runtime_error("Unable to initialize libsmb2 context"); } smb2_set_timeout(ctx, 15); smb2_set_authentication(ctx, SMB2_SEC_NTLMSSP); auto client = std::make_shared(); client->id = GenerateHandle(); client->host = host; client->ctx = ctx; { std::lock_guard lock(g_mutex); g_clients[client->id] = client; } return CreateInt64Value(env, client->id); } catch (const std::exception &error) { napi_throw_error(env, nullptr, error.what()); return nullptr; } } napi_value DestroyClient(napi_env env, napi_callback_info info) { try { size_t argc = 1; napi_value args[1] = {nullptr}; NapiCheck(napi_get_cb_info(env, info, &argc, args, nullptr, nullptr), "Failed to get arguments for destroyClient"); if (argc < 1) { return CreateUndefined(env); } int64_t clientId = ReadInt64(env, args[0], "clientId"); std::shared_ptr client; { std::lock_guard lock(g_mutex); auto it = g_clients.find(clientId); if (it != g_clients.end()) { client = it->second; g_clients.erase(it); } } if (client) { RemoveSessionsForClient(client->id); CloseClientContext(client); } return CreateUndefined(env); } catch (const std::exception &error) { napi_throw_error(env, nullptr, error.what()); return nullptr; } } napi_value Authenticate(napi_env env, napi_callback_info info) { try { size_t argc = 4; napi_value args[4] = {nullptr}; NapiCheck(napi_get_cb_info(env, info, &argc, args, nullptr, nullptr), "Failed to get arguments for authenticate"); if (argc < 3) { throw std::runtime_error("authenticate requires clientId, username and password"); } int64_t clientId = ReadInt64(env, args[0], "clientId"); std::string username = Trim(ReadString(env, args[1], "username", true)); std::string password = ReadString(env, args[2], "password", true); std::string domain; if (argc >= 4) { domain = Trim(ReadString(env, args[3], "domain", false)); } Ensure(!username.empty(), "username must not be empty"); auto client = RequireClient(clientId); auto session = std::make_shared(); session->id = GenerateHandle(); session->client = client; session->username = username; session->password = password; session->domain = domain; { std::lock_guard lock(g_mutex); g_sessions[session->id] = session; } return CreateInt64Value(env, session->id); } catch (const std::exception &error) { napi_throw_error(env, nullptr, error.what()); return nullptr; } } napi_value DisconnectSession(napi_env env, napi_callback_info info) { try { size_t argc = 1; napi_value args[1] = {nullptr}; NapiCheck(napi_get_cb_info(env, info, &argc, args, nullptr, nullptr), "Failed to get arguments for disconnectSession"); if (argc < 1) { return CreateUndefined(env); } int64_t sessionId = ReadInt64(env, args[0], "sessionId"); std::shared_ptr session; { std::lock_guard lock(g_mutex); auto it = g_sessions.find(sessionId); if (it != g_sessions.end()) { session = it->second; g_sessions.erase(it); } } if (session) { session->closed = true; RemoveTreesForSession(session->id); } return CreateUndefined(env); } catch (const std::exception &error) { napi_throw_error(env, nullptr, error.what()); return nullptr; } } napi_value ConnectTree(napi_env env, napi_callback_info info) { try { size_t argc = 2; napi_value args[2] = {nullptr}; NapiCheck(napi_get_cb_info(env, info, &argc, args, nullptr, nullptr), "Failed to get arguments for connectTree"); if (argc < 2) { throw std::runtime_error("connectTree requires sessionId and share name"); } int64_t sessionId = ReadInt64(env, args[0], "sessionId"); std::string share = Trim(ReadString(env, args[1], "share", true)); Ensure(!share.empty(), "share must not be empty"); auto session = RequireSession(sessionId); auto client = session->client.lock(); if (!client || client->closed) { throw std::runtime_error("SMB client is not available"); } Ensure(session->activeTreeId == 0, "Session already has an active tree"); smb2_context *ctx = client->ctx; if (ctx == nullptr) { throw std::runtime_error("SMB context is not initialized"); } smb2_set_user(ctx, session->username.c_str()); smb2_set_password(ctx, session->password.c_str()); smb2_set_domain(ctx, session->domain.empty() ? nullptr : session->domain.c_str()); int rc = smb2_connect_share(ctx, client->host.c_str(), share.c_str(), session->username.c_str()); if (rc != 0) { throw std::runtime_error(BuildSmbErrorMessage(ctx, "Failed to connect to share")); } auto tree = std::make_shared(); tree->id = GenerateHandle(); tree->session = session; tree->share = share; tree->connected = true; { std::lock_guard lock(g_mutex); session->activeTreeId = tree->id; g_trees[tree->id] = tree; } return CreateInt64Value(env, tree->id); } catch (const std::exception &error) { napi_throw_error(env, nullptr, error.what()); return nullptr; } } napi_value DisconnectTree(napi_env env, napi_callback_info info) { try { size_t argc = 1; napi_value args[1] = {nullptr}; NapiCheck(napi_get_cb_info(env, info, &argc, args, nullptr, nullptr), "Failed to get arguments for disconnectTree"); if (argc < 1) { return CreateUndefined(env); } int64_t treeId = ReadInt64(env, args[0], "treeId"); std::shared_ptr tree; { std::lock_guard lock(g_mutex); auto it = g_trees.find(treeId); if (it != g_trees.end()) { tree = it->second; g_trees.erase(it); } } CloseTreeInternal(tree); return CreateUndefined(env); } catch (const std::exception &error) { napi_throw_error(env, nullptr, error.what()); return nullptr; } } napi_value ReadDirectory(napi_env env, napi_callback_info info) { try { size_t argc = 2; napi_value args[2] = {nullptr}; NapiCheck(napi_get_cb_info(env, info, &argc, args, nullptr, nullptr), "Failed to get arguments for readDirectory"); if (argc < 2) { throw std::runtime_error("readDirectory requires treeId and remote path"); } int64_t treeId = ReadInt64(env, args[0], "treeId"); std::string path = ReadString(env, args[1], "path", false); auto tree = RequireTree(treeId); auto session = tree->session.lock(); auto client = session ? session->client.lock() : nullptr; if (!client || client->ctx == nullptr) { throw std::runtime_error("SMB client context is not available"); } Ensure(tree->connected, "SMB tree is not connected"); std::string normalized = NormalizeRemotePath(path); SMB_LOGI("readDirectory invoked tree=%{public}lld path=%{public}s", static_cast(treeId), normalized.c_str()); smb2_context *ctx = client->ctx; smb2dir *dir = smb2_opendir(ctx, normalized.c_str()); if (dir == nullptr) { const char *err = ctx != nullptr ? smb2_get_error(ctx) : ""; SMB_LOGE("smb2_opendir failed tree=%{public}lld path=%{public}s err=%{public}s", static_cast(treeId), normalized.c_str(), err ? err : ""); throw std::runtime_error(BuildSmbErrorMessage(ctx, "Failed to open remote directory")); } std::vector entries; struct smb2dirent *dirent = nullptr; while ((dirent = smb2_readdir(ctx, dir)) != nullptr) { if (!dirent->name) { continue; } if (std::strcmp(dirent->name, ".") == 0 || std::strcmp(dirent->name, "..") == 0) { continue; } napi_value entry = nullptr; NapiCheck(napi_create_object(env, &entry), "Failed to create directory entry object"); napi_value nameValue = nullptr; NapiCheck(napi_create_string_utf8(env, dirent->name, NAPI_AUTO_LENGTH, &nameValue), "Failed to create directory name"); NapiCheck(napi_set_named_property(env, entry, "name", nameValue), "Failed to set name property"); NapiCheck(napi_set_named_property(env, entry, "fileName", nameValue), "Failed to set fileName property"); bool isDirectory = dirent->st.smb2_type == SMB2_TYPE_DIRECTORY; bool isFile = dirent->st.smb2_type == SMB2_TYPE_FILE; napi_value dirValue = nullptr; NapiCheck(napi_get_boolean(env, isDirectory, &dirValue), "Failed to create directory flag"); NapiCheck(napi_set_named_property(env, entry, "isDirectory", dirValue), "Failed to set directory flag"); napi_value fileValue = nullptr; NapiCheck(napi_get_boolean(env, isFile, &fileValue), "Failed to create file flag"); NapiCheck(napi_set_named_property(env, entry, "isFile", fileValue), "Failed to set file flag"); napi_value sizeValue = nullptr; NapiCheck(napi_create_double(env, static_cast(dirent->st.smb2_size), &sizeValue), "Failed to create size value"); NapiCheck(napi_set_named_property(env, entry, "size", sizeValue), "Failed to set size property"); entries.push_back(entry); } smb2_closedir(ctx, dir); napi_value result = nullptr; NapiCheck(napi_create_array_with_length(env, entries.size(), &result), "Failed to create directory result array"); for (size_t i = 0; i < entries.size(); i++) { NapiCheck(napi_set_element(env, result, i, entries[i]), "Failed to append directory entry"); } return result; } catch (const std::exception &error) { napi_throw_error(env, nullptr, error.what()); return nullptr; } } napi_value DownloadSmbFile(napi_env env, napi_callback_info info) { try { size_t argc = 7; napi_value args[7] = {nullptr}; NapiCheck(napi_get_cb_info(env, info, &argc, args, nullptr, nullptr), "Failed to get arguments for downloadSmbFile"); if (argc < 7) { throw std::runtime_error("downloadSmbFile requires host, share, username, password, domain, remotePath and localPath"); } std::string host = ReadString(env, args[0], "host", true); std::string share = ReadString(env, args[1], "share", true); std::string username = ReadString(env, args[2], "username", true); std::string password = ReadString(env, args[3], "password", true); std::string domain = ReadString(env, args[4], "domain", false); std::string remotePath = ReadString(env, args[5], "remotePath", true); std::string localPath = ReadString(env, args[6], "localPath", true); if (share.empty()) { throw std::runtime_error("share must not be empty"); } if (localPath.empty()) { throw std::runtime_error("localPath must not be empty"); } std::string normalizedRemotePath = NormalizeRemotePath(remotePath); if (normalizedRemotePath.empty()) { throw std::runtime_error("remotePath must not be empty"); } std::error_code fsError; std::filesystem::path targetPath(localPath); auto parent = targetPath.parent_path(); if (!parent.empty()) { std::filesystem::create_directories(parent, fsError); if (fsError) { throw std::runtime_error("Failed to prepare cache directory: " + fsError.message()); } } FILE *output = std::fopen(localPath.c_str(), "wb"); if (output == nullptr) { throw std::runtime_error("Unable to open local file for writing"); } smb2_context *ctx = smb2_init_context(); if (ctx == nullptr) { std::fclose(output); throw std::runtime_error("Unable to initialize libsmb2 context"); } auto cleanupContext = [&ctx]() { if (ctx != nullptr) { smb2_disconnect_share(ctx); smb2_destroy_context(ctx); ctx = nullptr; } }; auto cleanupFile = [&output]() { if (output != nullptr) { std::fclose(output); output = nullptr; } }; auto removeCacheFile = [&localPath]() { std::remove(localPath.c_str()); }; smb2_set_timeout(ctx, 30); smb2_set_authentication(ctx, SMB2_SEC_NTLMSSP); smb2_set_user(ctx, username.c_str()); smb2_set_password(ctx, password.c_str()); smb2_set_domain(ctx, domain.empty() ? nullptr : domain.c_str()); int rc = smb2_connect_share(ctx, host.c_str(), share.c_str(), username.c_str()); if (rc != 0) { cleanupFile(); cleanupContext(); removeCacheFile(); throw std::runtime_error(BuildSmbErrorMessage(ctx, "Failed to connect to share")); } smb2fh *fileHandle = smb2_open(ctx, normalizedRemotePath.c_str(), O_RDONLY); if (fileHandle == nullptr) { cleanupFile(); cleanupContext(); removeCacheFile(); throw std::runtime_error(BuildSmbErrorMessage(ctx, "Failed to open remote file")); } const size_t BUFFER_SIZE = 64 * 1024; std::vector buffer(BUFFER_SIZE); int bytesRead = 0; while ((bytesRead = smb2_read(ctx, fileHandle, buffer.data(), static_cast(buffer.size()))) > 0) { size_t written = std::fwrite(buffer.data(), 1, static_cast(bytesRead), output); if (written != static_cast(bytesRead)) { smb2_close(ctx, fileHandle); cleanupFile(); cleanupContext(); removeCacheFile(); throw std::runtime_error("Failed to write to local file"); } } if (bytesRead < 0) { std::string message = BuildSmbErrorMessage(ctx, "Failed to read remote file"); smb2_close(ctx, fileHandle); cleanupFile(); cleanupContext(); removeCacheFile(); throw std::runtime_error(message); } smb2_close(ctx, fileHandle); cleanupFile(); cleanupContext(); return CreateUndefined(env); } catch (const std::exception &error) { napi_throw_error(env, nullptr, error.what()); return nullptr; } } napi_value ReadSmbFileRange(napi_env env, napi_callback_info info) { constexpr size_t MAX_RANGE_SIZE = 2 * 1024 * 1024; constexpr size_t BUFFER_SIZE = 64 * 1024; try { size_t argc = 8; napi_value args[8] = {nullptr}; NapiCheck(napi_get_cb_info(env, info, &argc, args, nullptr, nullptr), "Failed to get arguments for readSmbFileRange"); if (argc < 8) { throw std::runtime_error("readSmbFileRange requires host, share, username, password, domain, remotePath, offset and length"); } std::string host = ReadString(env, args[0], "host", true); std::string share = ReadString(env, args[1], "share", true); std::string username = ReadString(env, args[2], "username", true); std::string password = ReadString(env, args[3], "password", true); std::string domain = ReadString(env, args[4], "domain", false); std::string remotePath = ReadString(env, args[5], "remotePath", true); int64_t offsetValue = ReadInt64(env, args[6], "offset"); int64_t lengthValue = ReadInt64(env, args[7], "length"); if (offsetValue < 0) { offsetValue = 0; } if (lengthValue <= 0) { napi_value emptyResult = nullptr; void *tmp = nullptr; NapiCheck(napi_create_arraybuffer(env, 0, &tmp, &emptyResult), "Failed to create empty buffer"); return emptyResult; } size_t requestedLength = static_cast(lengthValue); if (requestedLength > MAX_RANGE_SIZE) { requestedLength = MAX_RANGE_SIZE; } smb2_context *ctx = smb2_init_context(); if (ctx == nullptr) { throw std::runtime_error("Unable to initialize libsmb2 context"); } auto cleanupContext = [&ctx]() { if (ctx != nullptr) { smb2_disconnect_share(ctx); smb2_destroy_context(ctx); ctx = nullptr; } }; smb2_set_timeout(ctx, 30); smb2_set_authentication(ctx, SMB2_SEC_NTLMSSP); smb2_set_user(ctx, username.c_str()); smb2_set_password(ctx, password.c_str()); smb2_set_domain(ctx, domain.empty() ? nullptr : domain.c_str()); int rc = smb2_connect_share(ctx, host.c_str(), share.c_str(), username.c_str()); if (rc != 0) { cleanupContext(); throw std::runtime_error(BuildSmbErrorMessage(ctx, "Failed to connect to share")); } std::string normalizedRemotePath = NormalizeRemotePath(remotePath); smb2fh *fileHandle = smb2_open(ctx, normalizedRemotePath.c_str(), O_RDONLY); if (fileHandle == nullptr) { cleanupContext(); throw std::runtime_error(BuildSmbErrorMessage(ctx, "Failed to open remote file")); } std::vector buffer(requestedLength); size_t totalRead = 0; while (totalRead < requestedLength) { const size_t remaining = requestedLength - totalRead; const uint32_t toRead = static_cast(std::min(BUFFER_SIZE, remaining)); int bytesRead = smb2_pread(ctx, fileHandle, buffer.data() + totalRead, toRead, static_cast(offsetValue) + totalRead); if (bytesRead < 0) { std::string message = BuildSmbErrorMessage(ctx, "Failed to read remote range"); smb2_close(ctx, fileHandle); cleanupContext(); throw std::runtime_error(message); } if (bytesRead == 0) { break; } totalRead += static_cast(bytesRead); if (static_cast(bytesRead) < toRead) { break; } } smb2_close(ctx, fileHandle); cleanupContext(); napi_value bufferValue = nullptr; void *outData = nullptr; NapiCheck(napi_create_arraybuffer(env, totalRead, &outData, &bufferValue), "Failed to create range buffer"); if (totalRead > 0) { std::memcpy(outData, buffer.data(), totalRead); } return bufferValue; } catch (const std::exception &error) { napi_throw_error(env, nullptr, error.what()); return nullptr; } } } EXTERN_C_START static napi_value Init(napi_env env, napi_value exports) { napi_property_descriptor descriptors[] = { {"createClient", nullptr, CreateClient, nullptr, nullptr, nullptr, napi_default, nullptr}, {"destroyClient", nullptr, DestroyClient, nullptr, nullptr, nullptr, napi_default, nullptr}, {"authenticate", nullptr, Authenticate, nullptr, nullptr, nullptr, napi_default, nullptr}, {"disconnectSession", nullptr, DisconnectSession, nullptr, nullptr, nullptr, napi_default, nullptr}, {"connectTree", nullptr, ConnectTree, nullptr, nullptr, nullptr, napi_default, nullptr}, {"disconnectTree", nullptr, DisconnectTree, nullptr, nullptr, nullptr, napi_default, nullptr}, {"readDirectory", nullptr, ReadDirectory, nullptr, nullptr, nullptr, napi_default, nullptr}, {"downloadSmbFile", nullptr, DownloadSmbFile, nullptr, nullptr, nullptr, napi_default, nullptr}, {"readSmbFileRange", nullptr, ReadSmbFileRange, nullptr, nullptr, nullptr, napi_default, nullptr} }; NapiCheck(napi_define_properties(env, exports, sizeof(descriptors) / sizeof(descriptors[0]), descriptors), "Failed to define native exports"); return exports; } EXTERN_C_END static napi_module g_module = { .nm_version = 1, .nm_flags = 0, .nm_filename = nullptr, .nm_register_func = Init, .nm_modname = "entry", .nm_priv = nullptr, .reserved = {0}, }; extern "C" __attribute__((constructor)) void RegisterEntryModule(void) { napi_module_register(&g_module); }