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::__anon503943fe0111::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 *>(finalizeData);
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, "Internal error");
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, "Internal error");
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, "Internal error");
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, "Internal error");
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, "Internal error");
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, "Internal error");
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, "Internal error");
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, "Internal error");
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, "Internal error");
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, "Internal error");
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, "Internal error");
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, "Internal error");
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, "Internal error");
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, "Internal error");
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, "Internal error");
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, "Internal error");
386 return nullptr;
387 }
388
389 CallFunctionByName(env, writeStream, "closeSync");
390
391 std::rename(rafEntity->newFileName.c_str(), rafEntity->baseFileName.c_str());
392 std::string tmpNewFileName = rafEntity->baseFileName;
393 rafEntity->newFileName = tmpNewFileName.append(TEMP_FILE_SUFFIX);
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, "Internal error");
398 return nullptr;
399 }
400 return nullptr;
401 }
402
FailWrite(napi_env env,napi_callback_info info)403 napi_value AtomicFileNExporter::FailWrite(napi_env env, napi_callback_info info)
404 {
405 auto [rafEntity, errcode] = GetAtomicFileEntity(env, info);
406 if (errcode != 0) {
407 if (errcode == UNKROWN_ERR) {
408 NError(errcode).ThrowErr(env, "Internal error");
409 } else {
410 NError(errcode).ThrowErr(env);
411 }
412 return nullptr;
413 }
414
415 napi_value writeStream;
416 napi_status status = napi_get_reference_value(env, rafEntity->writeStreamObj, &writeStream);
417 if (status != napi_ok) {
418 HILOGE("Failed to get reference value");
419 NError(UNKROWN_ERR).ThrowErr(env, "Internal error");
420 return nullptr;
421 }
422
423 CallFunctionByName(env, writeStream, "closeSync");
424
425 if (!fs::remove(rafEntity->newFileName)) {
426 HILOGW("Failed to remove file");
427 }
428 std::string tmpNewFileName = rafEntity->baseFileName;
429 rafEntity->newFileName = tmpNewFileName.append(TEMP_FILE_SUFFIX);
430 status = napi_delete_reference(env, rafEntity->writeStreamObj);
431 if (status != napi_ok) {
432 HILOGE("Failed to delete reference");
433 NError(UNKROWN_ERR).ThrowErr(env, "Internal error");
434 }
435 return nullptr;
436 }
437
Delete(napi_env env,napi_callback_info info)438 napi_value AtomicFileNExporter::Delete(napi_env env, napi_callback_info info)
439 {
440 auto [rafEntity, errcode] = GetAtomicFileEntity(env, info);
441 if (errcode != 0) {
442 if (errcode == UNKROWN_ERR) {
443 NError(errcode).ThrowErr(env, "Internal error");
444 } else {
445 NError(errcode).ThrowErr(env);
446 }
447 return nullptr;
448 }
449
450 bool errFlag = false;
451 std::error_code fsErrcode;
452 if (fs::exists(rafEntity->newFileName, fsErrcode) && !fs::remove(rafEntity->newFileName, fsErrcode)) {
453 errFlag = true;
454 }
455 if (fs::exists(rafEntity->baseFileName, fsErrcode) && !fs::remove(rafEntity->baseFileName, fsErrcode)) {
456 errFlag = true;
457 }
458 if (errFlag) {
459 HILOGE("Failed to remove file, err:%{public}s", fsErrcode.message().c_str());
460 NError(fsErrcode.value()).ThrowErr(env);
461 }
462
463 rafEntity->newFileName.clear();
464 rafEntity->baseFileName.clear();
465 return nullptr;
466 }
467
Constructor(napi_env env,napi_callback_info info)468 napi_value AtomicFileNExporter::Constructor(napi_env env, napi_callback_info info)
469 {
470 NFuncArg funcArg(env, info);
471 if (!funcArg.InitArgs(NARG_CNT::ONE)) {
472 HILOGE("Number of arguments unmatched");
473 NError(E_PARAMS).ThrowErr(env);
474 return nullptr;
475 }
476
477 auto [resGetFirstArg, file, num] = NVal(env, funcArg[NARG_POS::FIRST]).ToUTF8String();
478 if (!resGetFirstArg) {
479 HILOGE("Invalid path");
480 NError(E_PARAMS).ThrowErr(env);
481 return nullptr;
482 }
483
484 auto atomicFileEntity = CreateUniquePtr<AtomicFileEntity>();
485 if (atomicFileEntity == nullptr) {
486 HILOGE("Failed to request heap memory");
487 NError(ENOMEM).ThrowErr(env);
488 return nullptr;
489 }
490 std::string filePath = file.get();
491 atomicFileEntity->baseFileName = filePath;
492 atomicFileEntity->newFileName = filePath.append(TEMP_FILE_SUFFIX);
493 if (!NClass::SetEntityFor<AtomicFileEntity>(env, funcArg.GetThisVar(), move(atomicFileEntity))) {
494 HILOGE("Failed to wrap entity for obj AtomicFile");
495 NError(EIO).ThrowErr(env);
496 return nullptr;
497 }
498
499 return funcArg.GetThisVar();
500 }
501
Export()502 bool AtomicFileNExporter::Export()
503 {
504 std::vector<napi_property_descriptor> props = {
505 #if !defined(WIN_PLATFORM) && !defined(IOS_PLATFORM)
506 NVal::DeclareNapiFunction("getBaseFile", GetBaseFile),
507 NVal::DeclareNapiFunction("openRead", OpenRead),
508 NVal::DeclareNapiFunction("readFully", ReadFully),
509 NVal::DeclareNapiFunction("startWrite", StartWrite),
510 NVal::DeclareNapiFunction("finishWrite", FinishWrite),
511 NVal::DeclareNapiFunction("failWrite", FailWrite),
512 NVal::DeclareNapiFunction("delete", Delete),
513 #endif
514 };
515
516 std::string className = GetClassName();
517 bool succ = false;
518 napi_value classValue = nullptr;
519 std::tie(succ, classValue) = NClass::DefineClass(
520 exports_.env_, className, AtomicFileNExporter::Constructor, move(props));
521 if (!succ) {
522 HILOGE("INNER BUG. Failed to define class");
523 NError(ENOMEM).ThrowErr(exports_.env_);
524 return false;
525 }
526 succ = NClass::SaveClass(exports_.env_, className, classValue);
527 if (!succ) {
528 HILOGE("INNER BUG. Failed to save class");
529 NError(ENOMEM).ThrowErr(exports_.env_);
530 return false;
531 }
532
533 return exports_.AddProp(className, classValue);
534 }
535
GetClassName()536 std::string AtomicFileNExporter::GetClassName()
537 {
538 return AtomicFileNExporter::className_;
539 }
540
AtomicFileNExporter(napi_env env,napi_value exports)541 AtomicFileNExporter::AtomicFileNExporter(napi_env env, napi_value exports) : NExporter(env, exports) {}
~AtomicFileNExporter()542 AtomicFileNExporter::~AtomicFileNExporter() {}
543 } // namespace ModuleFileIO
544 } // namespace DistributedFS
545 } // namespace OHOS
546