2 * Copyright (c) 2021-present Samsung Electronics Co., Ltd
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
23 #include "lwnode-loader.h"
25 #include "node_native_module.h"
28 using namespace LWNode;
31 namespace native_module {
37 class ArchiveFileScope {
39 ArchiveFileScope() = default;
40 ArchiveFileScope(const char* path) { open(path); }
47 void open(const char* path) {
48 if (file_ == nullptr) {
49 file_ = unzOpen(path);
53 bool isFileOpened() { return (file_ != nullptr) ? true : false; }
54 unzFile file() { return file_; }
57 unzFile file_{nullptr};
60 struct UnzFileCachedInfo {
61 unz_file_pos position{0, 0};
62 uLong uncompressedSize{0};
65 static ArchiveFileScope s_archiveFileScope;
66 static std::map<std::string, UnzFileCachedInfo> s_unzFileInfoDictionary;
68 std::string getSelfProcPath() {
70 ssize_t length = readlink("/proc/self/exe", path, PATH_MAX);
72 ERROR_AND_ABORT("readlink fails");
75 return std::string(path);
78 bool readCurrentFileFromArchive(const unzFile file,
79 uLong uncompressedSize,
82 if (unzOpenCurrentFile(file) < 0) {
86 *fileSize = uncompressedSize;
87 *buffer = (char*)allocateStringBuffer(uncompressedSize + 1);
91 if (unzReadCurrentFile(file, *buffer, *fileSize) < 0) {
92 freeStringBuffer(buffer);
97 (*buffer)[*fileSize] = '\0';
99 unzCloseCurrentFile(file);
103 bool readFileFromArchive(const std::string& archiveFilename,
104 const std::string& filename,
107 DCHECK_NOT_NULL(buffer);
108 DCHECK_NOT_NULL(fileSize);
110 if (s_archiveFileScope.isFileOpened() == false) {
111 s_archiveFileScope.open(archiveFilename.c_str());
114 const unzFile file = s_archiveFileScope.file();
115 CHECK_NOT_NULL(file);
117 // 1.1 check if the cache on this filename exists
118 const auto& it = s_unzFileInfoDictionary.find(filename);
119 if (it != s_unzFileInfoDictionary.end()) {
120 UnzFileCachedInfo cache = it->second;
122 // 1.2 move the file position using the cached info and read the data
123 unzGoToFilePos(file, &cache.position);
124 return readCurrentFileFromArchive(
125 file, cache.uncompressedSize, buffer, fileSize);
128 // 2. read the data by searching the file position from the first one.
129 if (unzGoToFirstFile(file) < 0) {
133 bool isFileFound = false;
135 unz_file_info fileInfo;
136 char currentFileName[PATH_MAX];
138 if (unzGetCurrentFileInfo(file,
141 sizeof(currentFileName),
149 if (filename.compare(currentFileName) == 0) {
152 // 2.1 read the data from the current file poistion
153 if (readCurrentFileFromArchive(
154 file, fileInfo.uncompressed_size, buffer, fileSize) == false) {
158 // 2.2 create the cache for this file and register it to the dictionary
159 UnzFileCachedInfo cache;
160 auto result = unzGetFilePos(file, &cache.position);
161 CHECK(result == UNZ_OK);
162 cache.uncompressedSize = fileInfo.uncompressed_size;
164 s_unzFileInfoDictionary[filename] = cache;
168 } while (unzGoToNextFile(file) != UNZ_END_OF_LIST_OF_FILE);
173 FileData readFileFromArchive(std::string filename,
174 const Encoding encodingHint) {
175 CHECK(encodingHint != UNKNOWN);
177 size_t bufferSize = 0;
178 char* buffer = nullptr;
180 static std::string s_externalBuiltinsPath;
182 if (s_externalBuiltinsPath.empty()) {
183 std::string executablePath = getSelfProcPath();
184 executablePath = executablePath.substr(0, executablePath.rfind('/') + 1);
185 s_externalBuiltinsPath = executablePath + LWNODE_EXTERNAL_BUILTINS_FILENAME;
188 if (readFileFromArchive(
189 s_externalBuiltinsPath, filename, &buffer, &bufferSize) == false) {
193 std::unique_ptr<void, std::function<void(void*)>> bufferHolder(
194 buffer, freeStringBuffer);
196 Loader::U8String latin1String;
197 Encoding encoding = UNKNOWN;
199 if (encodingHint == ONE_BYTE) {
201 } else if (encodingHint == TWO_BYTE) {
203 } else if (encodingHint == ONE_BYTE_LATIN1) {
204 Loader::tryConvertUTF8ToLatin1(
205 latin1String, encoding, (uint8_t*)buffer, bufferSize, encodingHint);
207 CHECK(encodingHint == UNKNOWN);
208 Loader::tryConvertUTF8ToLatin1(
209 latin1String, encoding, (uint8_t*)buffer, bufferSize, encodingHint);
212 if (encoding == TWO_BYTE) {
213 // Treat non-latin1 as UTF-8 and encode it as UTF-16 Little Endian.
214 if (encodingHint == UNKNOWN) {
215 LWNODE_LOG_INFO("%s contains characters outside of the Latin1 range.",
219 char* newStringBuffer = nullptr;
220 size_t newStringBufferSize = 0;
222 bool isConverted = convertUTF8ToUTF16le(&newStringBuffer,
223 &newStringBufferSize,
224 (const char*)bufferHolder.get(),
226 if (isConverted == false) {
230 bufferHolder.reset(newStringBuffer);
231 bufferSize = newStringBufferSize;
233 if (encoding == ONE_BYTE_LATIN1) {
234 if (encodingHint == UNKNOWN) {
235 LWNODE_LOG_INFO("%s contains Latin1 characters.", filename.c_str());
238 bufferSize = latin1String.length();
239 bufferHolder.reset(allocateStringBuffer(bufferSize + 1));
240 ((uint8_t*)bufferHolder.get())[bufferSize] = '\0';
242 memcpy(bufferHolder.get(), latin1String.data(), bufferSize);
246 return FileData(bufferHolder.release(), bufferSize, encoding);
249 bool NativeModuleLoader::IsOneByte(const char* id) {
250 const auto& it = source_.find(id);
251 if (it == source_.end()) {
254 return it->second.is_one_byte();
257 static std::string getFileNameOnArchive(const char* id) {
258 std::string filename;
259 if (strncmp(id, "internal/deps", strlen("internal/deps")) == 0) {
260 id += strlen("internal/");
275 MaybeLocal<String> NativeModuleLoader::LoadExternalBuiltinSource(
276 Isolate* isolate, const char* id) {
277 std::string filename = getFileNameOnArchive(id);
280 readFileFromArchive(filename, (IsOneByte(id) ? ONE_BYTE : TWO_BYTE));
282 if (fileData.buffer == nullptr) {
283 ERROR_AND_ABORT("Failed to open builtins");
284 return MaybeLocal<String>();
287 auto data = Loader::ReloadableSourceData::create(
288 filename, fileData.buffer, fileData.size, fileData.encoding);
290 return Loader::NewReloadableString(
293 // Load-ReloadableSource
294 [](void* userData) -> void* {
295 auto data = reinterpret_cast<Loader::ReloadableSourceData*>(userData);
297 LWNODE_LOG_INFO(" Load: %d (%d) %p %s (+%.2f kB)",
302 (float)data->preloadedDataLength() / 1024);
304 if (data->preloadedData) {
305 auto buffer = data->preloadedData;
306 data->preloadedData = nullptr;
307 return buffer; // move memory ownership to js engine
312 FileData fileData = readFileFromArchive(data->path(), data->encoding());
314 CHECK_NOT_NULL(fileData.buffer);
315 return fileData.buffer;
317 // Unload-ReloadableSource
318 [](void* preloadedData, void* userData) -> void {
319 auto data = reinterpret_cast<Loader::ReloadableSourceData*>(userData);
321 LWNODE_LOG_INFO("Unload: %d (%d) %p %s (-%.2f kB)",
326 (float)data->preloadedDataLength() / 1024);
328 if (data->preloadedData) {
329 freeStringBuffer(data->preloadedData);
330 data->preloadedData = nullptr;
332 freeStringBuffer(preloadedData);
336 } // namespace native_module