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