• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (c) 2025 Huawei Device Co., Ltd.
3  * Licensed under the Apache License, Version 2.0 (the "License");
4  * you may not use this file except in compliance with the License.
5  * You may obtain a copy of the License at
6  *
7  * http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14  */
15 
16 #include "atomicfile_n_exporter.h"
17 
18 #include <cstdio>
19 #include <cstdlib>
20 #include <cstring>
21 #include <filesystem>
22 #include <memory>
23 #include <sys/stat.h>
24 
25 #include "atomicfile_entity.h"
26 #include "class_file/file_entity.h"
27 #include "class_file/file_n_exporter.h"
28 #include "common_func.h"
29 #include "file_utils.h"
30 #include "filemgmt_libhilog.h"
31 #include "filemgmt_libn.h"
32 
33 namespace OHOS {
34 namespace FileManagement {
35 namespace ModuleFileIO {
36 namespace fs = std::filesystem;
37 using namespace OHOS::FileManagement::LibN;
38 
39 namespace {
40 const std::string READ_STREAM_CLASS = "ReadStream";
41 const std::string WRITE_STREAM_CLASS = "WriteStream";
42 
43 struct BufferData {
44     uint8_t* buffer = nullptr;
45     size_t length = 0;
46 
~BufferDataOHOS::FileManagement::ModuleFileIO::__anon6c122cc00111::BufferData47     ~BufferData()
48     {
49         delete[] buffer;
50     }
51 };
52 }
53 
FinalizeCallback(napi_env env,void * finalizeData,void * finalizeHint)54 static void FinalizeCallback(napi_env env, void *finalizeData, void *finalizeHint)
55 {
56     BufferData *bufferData = static_cast<BufferData *>(finalizeData);
57     delete bufferData;
58 }
59 
CreateStream(napi_env env,napi_callback_info info,const std::string & streamName,const std::string & fineName)60 static napi_value CreateStream(napi_env env, napi_callback_info info, const std::string &streamName,
61     const std::string &fineName)
62 {
63     NFuncArg funcArg(env, info);
64     if (!funcArg.InitArgs(NARG_CNT::ZERO)) {
65         HILOGE("Number of arguments unmatched");
66         NError(E_PARAMS).ThrowErr(env);
67         return nullptr;
68     }
69 
70     const char moduleName[] = "@ohos.file.streamrw";
71     napi_value streamrw;
72     napi_status status = napi_load_module(env, moduleName, &streamrw);
73     if (status != napi_ok) {
74         HILOGE("Failed to load module");
75         NError(UNKROWN_ERR).ThrowErr(env, "Internal error");
76         return nullptr;
77     }
78 
79     napi_value constructor = nullptr;
80     status = napi_get_named_property(env, streamrw, streamName.c_str(), &constructor);
81     if (status != napi_ok) {
82         HILOGE("Failed to get named property");
83         NError(UNKROWN_ERR).ThrowErr(env, "Internal error");
84         return nullptr;
85     }
86 
87     napi_value filePath = NVal::CreateUTF8String(env, fineName).val_;
88     napi_value argv[NARG_CNT::ONE] = {filePath};
89     napi_value streamObj;
90     size_t argc = 1;
91     status = napi_new_instance(env, constructor, argc, argv, &streamObj);
92     if (status != napi_ok) {
93         HILOGE("Failed to create napi new instance");
94         NError(UNKROWN_ERR).ThrowErr(env, "Internal error");
95         return nullptr;
96     }
97 
98     return NVal(env, streamObj).val_;
99 }
100 
CallFunctionByName(napi_env env,napi_value objStream,const std::string & funcName)101 static void CallFunctionByName(napi_env env, napi_value objStream, const std::string &funcName)
102 {
103     napi_valuetype valuetype;
104     napi_typeof(env, objStream, &valuetype);
105     if (valuetype != napi_object) {
106         HILOGE("Valuetype is unmatched");
107         return;
108     }
109 
110     napi_value key;
111     napi_status status = napi_create_string_utf8(env, funcName.c_str(), funcName.length(), &key);
112     if (status != napi_ok) {
113         HILOGE("Failed to create string utf8");
114         return;
115     }
116 
117     napi_value value;
118     status = napi_get_property(env, objStream, key, &value);
119     if (status != napi_ok) {
120         HILOGE("Failed to get property");
121         return;
122     }
123 
124     status = napi_call_function(env, objStream, value, 0, nullptr, nullptr);
125     if (status != napi_ok) {
126         HILOGE("Failed to call %{public}s function", funcName.c_str());
127         return;
128     }
129 }
130 
InstantiateFile(napi_env env,int fd,std::string path,bool isUri)131 static NVal InstantiateFile(napi_env env, int fd, std::string path, bool isUri)
132 {
133     napi_value objFile = NClass::InstantiateClass(env, FileNExporter::className_, {});
134     if (!objFile) {
135         close(fd);
136         HILOGE("Failed to instantiate class");
137         NError(UNKROWN_ERR).ThrowErr(env, "Internal error");
138         return NVal();
139     }
140 
141     auto fileEntity = NClass::GetEntityOf<FileEntity>(env, objFile);
142     if (fileEntity == nullptr) {
143         close(fd);
144         HILOGE("Failed to get fileEntity");
145         NError(UNKROWN_ERR).ThrowErr(env, "Internal error");
146         return NVal();
147     }
148     auto fdg = CreateUniquePtr<DistributedFS::FDGuard>(fd, false);
149     if (fdg == nullptr) {
150         close(fd);
151         HILOGE("Failed to request heap memory.");
152         NError(ENOMEM).ThrowErr(env);
153         return NVal();
154     }
155     fileEntity->fd_.swap(fdg);
156     if (isUri) {
157         fileEntity->path_ = "";
158         fileEntity->uri_ = path;
159     } else {
160         fileEntity->path_ = path;
161         fileEntity->uri_ = "";
162     }
163     return { env, objFile };
164 }
165 
GetAtomicFileEntity(napi_env env,napi_callback_info info)166 static std::tuple<AtomicFileEntity*, int32_t> GetAtomicFileEntity(napi_env env, napi_callback_info info)
167 {
168     NFuncArg funcArg(env, info);
169     if (!funcArg.InitArgs(NARG_CNT::ZERO)) {
170         HILOGE("Number of arguments unmatched");
171         return {nullptr, E_PARAMS};
172     }
173 
174     auto rafEntity = NClass::GetEntityOf<AtomicFileEntity>(env, funcArg.GetThisVar());
175     if (rafEntity == nullptr) {
176         HILOGE("Failed to get atomicFile");
177         return {nullptr, UNKROWN_ERR};
178     }
179 
180     return {rafEntity, 0};
181 }
182 
GetBaseFile(napi_env env,napi_callback_info info)183 napi_value AtomicFileNExporter::GetBaseFile(napi_env env, napi_callback_info info)
184 {
185     auto [rafEntity, errcode] = GetAtomicFileEntity(env, info);
186     if (errcode != 0) {
187         if (errcode == UNKROWN_ERR) {
188             NError(errcode).ThrowErr(env, "Internal error");
189         } else {
190             NError(errcode).ThrowErr(env);
191         }
192         return nullptr;
193     }
194 
195     if (rafEntity->baseFileName.size() >= PATH_MAX) {
196         HILOGE("Base file name is too long");
197         NError(UNKROWN_ERR).ThrowErr(env, "Internal error");
198         return nullptr;
199     }
200 
201     char realPath[PATH_MAX];
202     char *result = realpath(rafEntity->baseFileName.c_str(), realPath);
203     if (result == nullptr) {
204         HILOGE("Failed to resolve real path, err:%{public}d", errno);
205         NError(errno).ThrowErr(env);
206         return nullptr;
207     }
208 
209     int fd = open(result, O_RDONLY);
210     if (fd < 0) {
211         HILOGE("Failed to open file, err:%{public}d", errno);
212         NError(errno).ThrowErr(env);
213         return nullptr;
214     }
215     return InstantiateFile(env, fd, rafEntity->baseFileName, false).val_;
216 }
217 
OpenRead(napi_env env,napi_callback_info info)218 napi_value AtomicFileNExporter::OpenRead(napi_env env, napi_callback_info info)
219 {
220     auto [rafEntity, errcode] = GetAtomicFileEntity(env, info);
221     if (errcode != 0) {
222         if (errcode == UNKROWN_ERR) {
223             NError(errcode).ThrowErr(env, "Internal error");
224         } else {
225             NError(errcode).ThrowErr(env);
226         }
227         return nullptr;
228     }
229 
230     return CreateStream(env, info, READ_STREAM_CLASS, rafEntity->baseFileName);
231 }
232 
ReadFileToBuffer(napi_env env,FILE * fp)233 static std::tuple<std::unique_ptr<BufferData>, int32_t> ReadFileToBuffer(napi_env env, FILE* fp)
234 {
235     int fd = fileno(fp);
236     if (fd < 0) {
237         HILOGE("Failed to get file descriptor, err:%{public}d", errno);
238         return {nullptr, UNKROWN_ERR};
239     }
240 
241     struct stat fileStat {};
242     if (fstat(fd, &fileStat) < 0) {
243         HILOGE("Failed to get file stats, err:%{public}d", errno);
244         return {nullptr, errno};
245     }
246 
247     long fileSize = fileStat.st_size;
248     if (fileSize <= 0) {
249         HILOGE("Invalid file size");
250         return {nullptr, EIO};
251     }
252 
253     auto bufferData = std::make_unique<BufferData>();
254     bufferData->buffer = new(std::nothrow) uint8_t[fileSize];
255     if (bufferData->buffer == nullptr) {
256         HILOGE("Failed to allocate memory");
257         return {nullptr, ENOMEM};
258     }
259     bufferData->length = fread(bufferData->buffer, sizeof(uint8_t), fileSize, fp);
260     if ((bufferData->length != static_cast<size_t>(fileSize) && !feof(fp)) || ferror(fp)) {
261         HILOGE("Failed to read file, actual length is:%zu, fileSize:%ld", bufferData->length, fileSize);
262         delete[] bufferData->buffer;
263         bufferData->buffer = nullptr;
264         bufferData->length = 0;
265         return {nullptr, EIO};
266     }
267     return {std::move(bufferData), 0};
268 }
269 
ReadFully(napi_env env,napi_callback_info info)270 napi_value AtomicFileNExporter::ReadFully(napi_env env, napi_callback_info info)
271 {
272     auto [rafEntity, errcode] = GetAtomicFileEntity(env, info);
273     if (errcode != 0) {
274         if (errcode == UNKROWN_ERR) {
275             NError(errcode).ThrowErr(env, "Internal error");
276         } else {
277             NError(errcode).ThrowErr(env);
278         }
279         return nullptr;
280     }
281 
282     char realPath[PATH_MAX];
283     char *result = realpath(rafEntity->baseFileName.c_str(), realPath);
284     if (result == nullptr) {
285         HILOGE("Failed to resolve file real path, err:%{public}d", errno);
286         NError(errno).ThrowErr(env);
287         return nullptr;
288     }
289 
290     auto file = std::unique_ptr<FILE, decltype(&std::fclose)>(
291         std::fopen(result, "rb"), &std::fclose);
292     if (!file) {
293         HILOGE("Failed to open file, err:%{public}d", errno);
294         NError(errno).ThrowErr(env);
295         return nullptr;
296     }
297 
298     auto [bufferData, readErrcode] = ReadFileToBuffer(env, file.get());
299     if (readErrcode != 0) {
300         if (readErrcode == UNKROWN_ERR) {
301             NError(readErrcode).ThrowErr(env, "Internal error");
302         } else {
303             NError(readErrcode).ThrowErr(env);
304         }
305         return nullptr;
306     }
307 
308     napi_value externalBuffer = nullptr;
309     size_t length = bufferData->length;
310     napi_status status = napi_create_external_arraybuffer(
311         env, bufferData->buffer, bufferData->length, FinalizeCallback, bufferData.release(), &externalBuffer);
312     if (status != napi_ok) {
313         NError(UNKROWN_ERR).ThrowErr(env, "Internal error");
314         return nullptr;
315     }
316 
317     napi_value outputArray = nullptr;
318     status = napi_create_typedarray(env, napi_int8_array, length, externalBuffer, 0, &outputArray);
319     if (status != napi_ok) {
320         NError(UNKROWN_ERR).ThrowErr(env, "Internal error");
321         return nullptr;
322     }
323 
324     return outputArray;
325 }
326 
StartWrite(napi_env env,napi_callback_info info)327 napi_value AtomicFileNExporter::StartWrite(napi_env env, napi_callback_info info)
328 {
329     auto [rafEntity, errcode] = GetAtomicFileEntity(env, info);
330     if (errcode != 0) {
331         if (errcode == UNKROWN_ERR) {
332             NError(errcode).ThrowErr(env, "Internal error");
333         } else {
334             NError(errcode).ThrowErr(env);
335         }
336         return nullptr;
337     }
338 
339     fs::path filePath = rafEntity->newFileName;
340     fs::path parentPath = filePath.parent_path();
341     if (access(parentPath.c_str(), F_OK) != 0) {
342         HILOGE("Parent directory does not exist, err:%{public}d", errno);
343         NError(ENOENT).ThrowErr(env);
344         return nullptr;
345     }
346 
347     char *tmpfile = const_cast<char *>(rafEntity->newFileName.c_str());
348     if (mkstemp(tmpfile) == -1) {
349         HILOGE("Fail to create tmp file err:%{public}d!", errno);
350         NError(ENOENT).ThrowErr(env);
351         return nullptr;
352     }
353 
354     napi_value writeStream = CreateStream(env, info, WRITE_STREAM_CLASS, rafEntity->newFileName);
355     if (writeStream == nullptr) {
356         HILOGE("Failed to create write stream");
357         return nullptr;
358     }
359     napi_status status = napi_create_reference(env, writeStream, 1, &rafEntity->writeStreamObj);
360     if (status != napi_ok) {
361         HILOGE("Failed to create reference");
362         NError(UNKROWN_ERR).ThrowErr(env, "Internal error");
363         return nullptr;
364     }
365     return writeStream;
366 }
367 
FinishWrite(napi_env env,napi_callback_info info)368 napi_value AtomicFileNExporter::FinishWrite(napi_env env, napi_callback_info info)
369 {
370     auto [rafEntity, errcode] = GetAtomicFileEntity(env, info);
371     if (errcode != 0) {
372         if (errcode == UNKROWN_ERR) {
373             NError(errcode).ThrowErr(env, "Internal error");
374         } else {
375             NError(errcode).ThrowErr(env);
376         }
377         return nullptr;
378     }
379 
380     napi_value writeStream;
381     napi_status status = napi_get_reference_value(env, rafEntity->writeStreamObj, &writeStream);
382     if (status != napi_ok) {
383         HILOGE("Failed to get reference value");
384         NError(UNKROWN_ERR).ThrowErr(env, "Internal error");
385         return nullptr;
386     }
387 
388     CallFunctionByName(env, writeStream, "closeSync");
389 
390     std::rename(rafEntity->newFileName.c_str(), rafEntity->baseFileName.c_str());
391     std::string tmpNewFileName = rafEntity->baseFileName;
392     rafEntity->newFileName = tmpNewFileName.append("_XXXXXX");
393     status = napi_delete_reference(env, rafEntity->writeStreamObj);
394     if (status != napi_ok) {
395         HILOGE("Failed to delete reference");
396         NError(UNKROWN_ERR).ThrowErr(env, "Internal error");
397         return nullptr;
398     }
399     return nullptr;
400 }
401 
FailWrite(napi_env env,napi_callback_info info)402 napi_value AtomicFileNExporter::FailWrite(napi_env env, napi_callback_info info)
403 {
404     auto [rafEntity, errcode] = GetAtomicFileEntity(env, info);
405     if (errcode != 0) {
406         if (errcode == UNKROWN_ERR) {
407             NError(errcode).ThrowErr(env, "Internal error");
408         } else {
409             NError(errcode).ThrowErr(env);
410         }
411         return nullptr;
412     }
413 
414     napi_value writeStream;
415     napi_status status = napi_get_reference_value(env, rafEntity->writeStreamObj, &writeStream);
416     if (status != napi_ok) {
417         HILOGE("Failed to get reference value");
418         NError(UNKROWN_ERR).ThrowErr(env, "Internal error");
419         return nullptr;
420     }
421 
422     CallFunctionByName(env, writeStream, "closeSync");
423 
424     if (!fs::remove(rafEntity->newFileName)) {
425         HILOGW("Failed to remove file");
426     }
427     std::string tmpNewFileName = rafEntity->baseFileName;
428     rafEntity->newFileName = tmpNewFileName.append("_XXXXXX");
429     status = napi_delete_reference(env, rafEntity->writeStreamObj);
430     if (status != napi_ok) {
431         HILOGE("Failed to delete reference");
432         NError(UNKROWN_ERR).ThrowErr(env, "Internal error");
433     }
434     return nullptr;
435 }
436 
Delete(napi_env env,napi_callback_info info)437 napi_value AtomicFileNExporter::Delete(napi_env env, napi_callback_info info)
438 {
439     auto [rafEntity, errcode] = GetAtomicFileEntity(env, info);
440     if (errcode != 0) {
441         if (errcode == UNKROWN_ERR) {
442             NError(errcode).ThrowErr(env, "Internal error");
443         } else {
444             NError(errcode).ThrowErr(env);
445         }
446         return nullptr;
447     }
448 
449     bool errFlag = false;
450     std::error_code fsErrcode;
451     if (fs::exists(rafEntity->newFileName, fsErrcode) && !fs::remove(rafEntity->newFileName, fsErrcode)) {
452         errFlag = true;
453     }
454     if (fs::exists(rafEntity->baseFileName, fsErrcode) && !fs::remove(rafEntity->baseFileName, fsErrcode)) {
455         errFlag = true;
456     }
457     if (errFlag) {
458         HILOGE("Failed to remove file, err:%{public}s", fsErrcode.message().c_str());
459         NError(fsErrcode.value()).ThrowErr(env);
460     }
461 
462     rafEntity->newFileName.clear();
463     rafEntity->baseFileName.clear();
464     return nullptr;
465 }
466 
Constructor(napi_env env,napi_callback_info info)467 napi_value AtomicFileNExporter::Constructor(napi_env env, napi_callback_info info)
468 {
469     NFuncArg funcArg(env, info);
470     if (!funcArg.InitArgs(NARG_CNT::ONE)) {
471         HILOGE("Number of arguments unmatched");
472         NError(E_PARAMS).ThrowErr(env);
473         return nullptr;
474     }
475 
476     auto [resGetFirstArg, file, num] = NVal(env, funcArg[NARG_POS::FIRST]).ToUTF8String();
477     if (!resGetFirstArg) {
478         HILOGE("Invalid path");
479         NError(E_PARAMS).ThrowErr(env);
480         return nullptr;
481     }
482 
483     auto atomicFileEntity = CreateUniquePtr<AtomicFileEntity>();
484     if (atomicFileEntity == nullptr) {
485         HILOGE("Failed to request heap memory");
486         NError(ENOMEM).ThrowErr(env);
487         return nullptr;
488     }
489     std::string filePath = file.get();
490     atomicFileEntity->baseFileName = filePath;
491     atomicFileEntity->newFileName = filePath.append("_XXXXXX");
492     if (!NClass::SetEntityFor<AtomicFileEntity>(env, funcArg.GetThisVar(), move(atomicFileEntity))) {
493         HILOGE("Failed to wrap entity for obj AtomicFile");
494         NError(EIO).ThrowErr(env);
495         return nullptr;
496     }
497 
498     return funcArg.GetThisVar();
499 }
500 
Export()501 bool AtomicFileNExporter::Export()
502 {
503     std::vector<napi_property_descriptor> props = {
504 #if !defined(WIN_PLATFORM) && !defined(IOS_PLATFORM)
505         NVal::DeclareNapiFunction("getBaseFile", GetBaseFile),
506         NVal::DeclareNapiFunction("openRead", OpenRead),
507         NVal::DeclareNapiFunction("readFully", ReadFully),
508         NVal::DeclareNapiFunction("startWrite", StartWrite),
509         NVal::DeclareNapiFunction("finishWrite", FinishWrite),
510         NVal::DeclareNapiFunction("failWrite", FailWrite),
511         NVal::DeclareNapiFunction("delete", Delete),
512 #endif
513     };
514 
515     std::string className = GetClassName();
516     bool succ = false;
517     napi_value classValue = nullptr;
518     std::tie(succ, classValue) = NClass::DefineClass(
519         exports_.env_, className, AtomicFileNExporter::Constructor, move(props));
520     if (!succ) {
521         HILOGE("INNER BUG. Failed to define class");
522         NError(ENOMEM).ThrowErr(exports_.env_);
523         return false;
524     }
525     succ = NClass::SaveClass(exports_.env_, className, classValue);
526     if (!succ) {
527         HILOGE("INNER BUG. Failed to save class");
528         NError(ENOMEM).ThrowErr(exports_.env_);
529         return false;
530     }
531 
532     return exports_.AddProp(className, classValue);
533 }
534 
GetClassName()535 std::string AtomicFileNExporter::GetClassName()
536 {
537     return AtomicFileNExporter::className_;
538 }
539 
AtomicFileNExporter(napi_env env,napi_value exports)540 AtomicFileNExporter::AtomicFileNExporter(napi_env env, napi_value exports) : NExporter(env, exports) {}
~AtomicFileNExporter()541 AtomicFileNExporter::~AtomicFileNExporter() {}
542 } // namespace ModuleFileIO
543 } // namespace DistributedFS
544 } // namespace OHOS
545