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