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