diff --git a/interfaces/kits/js/BUILD.gn b/interfaces/kits/js/BUILD.gn index 7e2ccedbe2c178a088d89e5ec3d5ef10a7540dc0..0bbc04dc4f132ace7eb12af5de48b439dc25f6e1 100644 --- a/interfaces/kits/js/BUILD.gn +++ b/interfaces/kits/js/BUILD.gn @@ -662,6 +662,8 @@ ohos_shared_library("ani_file_fs") { include_dirs = [ "include/ipc", "src/mod_fs/ani", + "src/mod_fs/class_atomicfile", + "src/mod_fs/class_atomicfile/ani", "src/mod_fs/class_file", "src/mod_fs/class_file/ani", "src/mod_fs/class_randomaccessfile", @@ -686,6 +688,8 @@ ohos_shared_library("ani_file_fs") { "src/common/ani_helper/type_converter.cpp", "src/common/file_helper/fd_guard.cpp", "src/mod_fs/ani/bind_function_class.cpp", + "src/mod_fs/class_atomicfile/ani/atomicfile_ani.cpp", + "src/mod_fs/class_atomicfile/fs_atomicfile.cpp", "src/mod_fs/class_file/ani/file_ani.cpp", "src/mod_fs/class_file/ani/file_wrapper.cpp", "src/mod_fs/class_file/file_instantiator.cpp", diff --git a/interfaces/kits/js/src/common/ani_helper/type_converter.cpp b/interfaces/kits/js/src/common/ani_helper/type_converter.cpp index 2e995d41efb31f07f13c69934656af66eaf60f5c..ba23a182bb56662d15cfc02139c6a17e6ed89f96 100644 --- a/interfaces/kits/js/src/common/ani_helper/type_converter.cpp +++ b/interfaces/kits/js/src/common/ani_helper/type_converter.cpp @@ -244,6 +244,52 @@ std::tuple TypeConverter::ToArrayBuffer(ani_env *env, ani_arr return { true, ArrayBuffer { std::move(buf), length } }; } +std::tuple TypeConverter::ToAniArrayBuffer(ani_env *env, void *buffer, size_t length) +{ + if (env == nullptr) { + return { false, nullptr }; + } + + static const char *className = "Lescompat/ArrayBuffer;"; + ani_status ret; + ani_class cls; + if ((ret = env->FindClass(className, &cls)) != ANI_OK) { + HILOGE("Not found %{private}s, err: %{private}d", className, ret); + return { false, nullptr }; + } + + ani_method ctor; + if ((ret = env->Class_FindMethod(cls, "", "I:V", &ctor)) != ANI_OK) { + HILOGE("Not found ctor, err: %{private}d", ret); + return { false, nullptr }; + } + + ani_object obj; + if ((ret = env->Object_New(cls, ctor, &obj, length)) != ANI_OK) { + HILOGE("New Uint8Array err: %{private}d", ret); + return { false, nullptr }; + } + + if (!buffer || !length) { + return { true, static_cast(obj) }; + } + + void *buf = nullptr; + ani_size len = 0; + + if ((ANI_OK != env->ArrayBuffer_GetInfo(static_cast(obj), &buf, &len)) && (!buf)) { + return { false, nullptr }; + } + + int res = memcpy_s(buf, length, buffer, length); + if (res != 0) { + return { false, nullptr }; + } + len = length; + + return { true, static_cast(obj) }; +} + std::tuple TypeConverter::ToAniStringList( ani_env *env, const std::string strList[], const uint32_t length) { diff --git a/interfaces/kits/js/src/common/ani_helper/type_converter.h b/interfaces/kits/js/src/common/ani_helper/type_converter.h index a6d06265992b98d16aedb01d1a7acf99367e817d..bac25493fda5311c32340a5ce0371dd981a9af1d 100644 --- a/interfaces/kits/js/src/common/ani_helper/type_converter.h +++ b/interfaces/kits/js/src/common/ani_helper/type_converter.h @@ -32,6 +32,7 @@ public: static std::tuple ToUTF8String(ani_env *env, const ani_string &path); static std::tuple> ToOptionalInt32(ani_env *env, const ani_object &value); static std::tuple> ToOptionalInt64(ani_env *env, const ani_object &value); + static std::tuple ToAniArrayBuffer(ani_env *env, void *buffer, size_t length); static std::tuple ToAniString(ani_env *env, std::string str); static std::tuple ToAniString(ani_env *env, std::string str, size_t size); static std::tuple ToAniString(ani_env *env, const char *str); diff --git a/interfaces/kits/js/src/mod_fs/ani/bind_function_class.cpp b/interfaces/kits/js/src/mod_fs/ani/bind_function_class.cpp index 10e86d4ba2a557e12d3ea4ee639121eadafe26ba..ca851366c6ea19d88c329bf10e09d4a3495c6286 100644 --- a/interfaces/kits/js/src/mod_fs/ani/bind_function_class.cpp +++ b/interfaces/kits/js/src/mod_fs/ani/bind_function_class.cpp @@ -19,6 +19,7 @@ #include "access_ani.h" #include "ani_signature.h" +#include "atomicfile_ani.h" #include "bind_function.h" #include "close_ani.h" #include "connectdfs_ani.h" @@ -163,6 +164,24 @@ static ani_status BindTaskSignalClassMethods(ani_env *env) return BindClass(env, classDesc, methods); } +static ani_status BindAtomicFileMethods(ani_env *env) +{ + static const char *className = "L@ohos/file/fs/fileIo/AtomicFile;"; + + std::array methods = { + ani_native_function { "getPath", nullptr, reinterpret_cast(AtomicFileAni::GetPath) }, + ani_native_function { "getBaseFile", nullptr, reinterpret_cast(AtomicFileAni::GetBaseFile) }, + ani_native_function { "readFully", nullptr, reinterpret_cast(AtomicFileAni::ReadFully) }, + ani_native_function { "nativeStartWrite", nullptr, reinterpret_cast(AtomicFileAni::StartWrite) }, + ani_native_function { "nativeFinishWrite", nullptr, reinterpret_cast(AtomicFileAni::FinishWrite) }, + ani_native_function { "nativeFailWrite", nullptr, reinterpret_cast(AtomicFileAni::FailWrite) }, + ani_native_function { "delete", nullptr, reinterpret_cast(AtomicFileAni::Delete) }, + ani_native_function { "", "Lstd/core/String;:V", reinterpret_cast(AtomicFileAni::Constructor) }, + ani_native_function { "openRead", nullptr, reinterpret_cast(AtomicFileAni::OpenRead) }, + }; + + return BindClass(env, className, methods); +} const static string mkdirCtorSig0 = Builder::BuildSignatureDescriptor({ BuiltInTypes::stringType }); const static string mkdirCtorSig1 = Builder::BuildSignatureDescriptor({ BuiltInTypes::stringType, BasicTypes::booleanType }); @@ -215,25 +234,9 @@ static ani_status BindStaticMethods(ani_env *env) return BindClass(env, classDesc, methods); } -ANI_EXPORT ani_status ANI_Constructor(ani_vm *vm, uint32_t *result) +static ani_status DoBindMethods(ani_env *env) { - if (vm == nullptr) { - HILOGE("Invalid parameter vm"); - return ANI_INVALID_ARGS; - } - - if (result == nullptr) { - HILOGE("Invalid parameter result"); - return ANI_INVALID_ARGS; - } - - ani_env *env; - ani_status status = vm->GetEnv(ANI_VERSION_1, &env); - if (status != ANI_OK) { - HILOGE("Invalid ani version!"); - return ANI_INVALID_VERSION; - } - + ani_status status; if ((status = BindStaticMethods(env)) != ANI_OK) { HILOGE("Cannot bind native static methods for BindStaticMethods!"); return status; @@ -274,6 +277,38 @@ ANI_EXPORT ani_status ANI_Constructor(ani_vm *vm, uint32_t *result) return status; }; + if ((status = BindAtomicFileMethods(env)) != ANI_OK) { + HILOGE("Cannot bind native methods for AtomicFile Class!"); + return status; + }; + + return ANI_OK; +} + +ANI_EXPORT ani_status ANI_Constructor(ani_vm *vm, uint32_t *result) +{ + if (vm == nullptr) { + HILOGE("Invalid parameter vm"); + return ANI_INVALID_ARGS; + } + + if (result == nullptr) { + HILOGE("Invalid parameter result"); + return ANI_INVALID_ARGS; + } + + ani_env *env; + ani_status status = vm->GetEnv(ANI_VERSION_1, &env); + if (status != ANI_OK) { + HILOGE("Invalid ani version!"); + return ANI_INVALID_VERSION; + } + + status = DoBindMethods(env); + if (status != ANI_OK) { + return status; + } + *result = ANI_VERSION_1; return ANI_OK; } diff --git a/interfaces/kits/js/src/mod_fs/ani/ets/@ohos.file.fs.ets b/interfaces/kits/js/src/mod_fs/ani/ets/@ohos.file.fs.ets index a590ecb22376333eda24b0a5a941cd405e8fdc8b..446a4e010bb3b29e4d5ec44c683097cde14858f5 100644 --- a/interfaces/kits/js/src/mod_fs/ani/ets/@ohos.file.fs.ets +++ b/interfaces/kits/js/src/mod_fs/ani/ets/@ohos.file.fs.ets @@ -15,6 +15,16 @@ import { BusinessError, AsyncCallback } from '@ohos.base'; import stream from '@ohos.util.stream'; +const UNKNOWN_ERR: number = 13900042 +const UNKNOWN_MSG: string = "Unknown error" + +function createBusinessError(code: number, msg: string): BusinessError { + let err = new BusinessError(); + err.code = code; + err.message = msg; + return err; +} + namespace fileIo { export namespace OpenMode { export const READ_ONLY = 0o0; @@ -1863,8 +1873,8 @@ export class StreamInner implements Stream { } export class ReadStream extends stream.Readable { - private pathInner: string; - private bytesReadInner: number; + path: string; + bytesRead: number; private offset: number; private start?: number; private end?: number; @@ -1872,22 +1882,14 @@ export class ReadStream extends stream.Readable { constructor(path: string, options?: ReadStreamOptions) { super(); - this.pathInner = path; - this.bytesReadInner = 0; + this.path = path; + this.bytesRead = 0; this.start = options?.start; this.end = options?.end; - this.stream = createStreamSync(this.pathInner, 'r'); + this.stream = createStreamSync(this.path, 'r'); this.offset = this.start ?? 0; } - get path(): string { - return this.pathInner; - } - - get bytesRead(): number { - return this.bytesReadInner; - } - seek(offset: number, whence?: WhenceType): number { if (whence === undefined) { let off = this.stream?.seek(offset); @@ -1929,7 +1931,7 @@ export class ReadStream extends stream.Readable { this.stream?.read(buffer, { offset: off, length: readSize }) .then((readOut: number) => { if (readOut > 0) { - this.bytesReadInner += readOut; + this.bytesRead += readOut; this.push(new Uint8Array(buffer.slice(0, readOut))); } if (readOut !== readSize || readOut < size) { @@ -1941,8 +1943,8 @@ export class ReadStream extends stream.Readable { } export class WriteStream extends stream.Writable { - private pathInner: string; - private bytesWrittenInner: number; + path: string; + bytesWritten: number; private offset: number; private mode: string; private start?: number; @@ -1950,22 +1952,14 @@ export class WriteStream extends stream.Writable { constructor(path: string, options?: WriteStreamOptions) { super(); - this.pathInner = path; - this.bytesWrittenInner = 0; + this.path = path; + this.bytesWritten = 0; this.start = options?.start; this.mode = this.convertOpenMode(options?.mode); - this.stream = createStreamSync(this.pathInner, this.mode); + this.stream = createStreamSync(this.path, this.mode); this.offset = this.start ?? 0; } - get path(): string { - return this.pathInner; - } - - get bytesWritten(): number { - return this.bytesWrittenInner; - } - seek(offset: number, whence?: WhenceType): number { if (whence === undefined) { let off = this.stream?.seek(offset); @@ -1985,6 +1979,10 @@ export class WriteStream extends stream.Writable { this.stream?.close(); } + closeSync(): void { + this.stream?.closeSync(); + } + doInitialize(callback: () => void): void { callback(); } @@ -1993,7 +1991,7 @@ export class WriteStream extends stream.Writable { this.stream?.write(chunk, { offset: this.offset }) .then((writeIn: number) => { this.offset += writeIn; - this.bytesWrittenInner += writeIn; + this.bytesWritten += writeIn; callback(); }) .finally(() => { @@ -2022,6 +2020,53 @@ export class WriteStream extends stream.Writable { } } +export class AtomicFile { + static { + loadLibrary("ani_file_fs"); + } + + private nativePtr: long = 0; + private native getPath(): string; + private writeStream: WriteStream | null = null; + + native constructor(path: string); + + native getBaseFile(): File; + + native openRead(): ReadStream; + + native readFully(): ArrayBuffer; + + native nativeStartWrite(): WriteStream; + startWrite(): WriteStream { + let ws = this.nativeStartWrite(); + this.writeStream = ws; + return ws; + } + + native nativeFinishWrite(): void; + finishWrite(): void { + if (!this.writeStream) { + throw createBusinessError(UNKNOWN_ERR, UNKNOWN_MSG); + } + this.writeStream?.close(); + this.nativeFinishWrite(); + this.writeStream = null; + }; + + native nativeFailWrite(): void; + failWrite(): void { + if (!this.writeStream) { + throw createBusinessError(UNKNOWN_ERR, UNKNOWN_MSG); + } + this.writeStream?.close(); + this.nativeFailWrite(); + this.writeStream = null; + }; + + native delete(): void; +} + export enum WhenceType { SEEK_SET = 0, SEEK_CUR = 1, diff --git a/interfaces/kits/js/src/mod_fs/class_atomicfile/ani/atomicfile_ani.cpp b/interfaces/kits/js/src/mod_fs/class_atomicfile/ani/atomicfile_ani.cpp new file mode 100644 index 0000000000000000000000000000000000000000..5dc3e3bac009eae40dc57679682581e36c285d16 --- /dev/null +++ b/interfaces/kits/js/src/mod_fs/class_atomicfile/ani/atomicfile_ani.cpp @@ -0,0 +1,356 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "atomicfile_ani.h" + +#include + +#include "error_handler.h" +#include "file_wrapper.h" +#include "filemgmt_libhilog.h" +#include "fs_atomicfile.h" +#include "type_converter.h" + +namespace OHOS { +namespace FileManagement { +namespace ModuleFileIO { +namespace ANI { +namespace fs = std::filesystem; +using namespace std; +using namespace OHOS::FileManagement::ModuleFileIO; + +const std::string READ_STREAM_CLASS = "ReadStream"; +const std::string WRITE_STREAM_CLASS = "WriteStream"; +const std::string TEMP_FILE_SUFFIX = "_XXXXXX"; + +void AtomicFileAni::Constructor(ani_env *env, ani_object obj, ani_string pathObj) +{ + auto [succ, filePath] = TypeConverter::ToUTF8String(env, pathObj); + if (!succ) { + HILOGE("Invalid path"); + ErrorHandler::Throw(env, E_PARAMS); + return; + } + + auto ret = FsAtomicFile::Constructor(filePath); + if (!ret.IsSuccess()) { + const auto &err = ret.GetError(); + ErrorHandler::Throw(env, err); + return; + } + + if (ANI_OK != + env->Object_SetFieldByName_Long(obj, "nativePtr", reinterpret_cast(ret.GetData().value()))) { + HILOGE("Failed to wrap entity for obj AtomicFile"); + ErrorHandler::Throw(env, EIO); + return; + } +} + +static FsAtomicFile *Unwrap(ani_env *env, ani_object object) +{ + ani_long file; + auto ret = env->Object_GetFieldByName_Long(object, "nativePtr", &file); + if (ret != ANI_OK) { + HILOGE("Unwrap file err: %{private}d", ret); + return nullptr; + } + + return reinterpret_cast(file); +} + +ani_string AtomicFileAni::GetPath(ani_env *env, [[maybe_unused]] ani_object object) +{ + auto file = Unwrap(env, object); + if (file == nullptr) { + ErrorHandler::Throw(env, E_PARAMS); + return nullptr; + } + + string path = file->GetPath(); + auto [succ, result] = TypeConverter::ToAniString(env, path); + if (!succ) { + HILOGE("ToAniString failed"); + ErrorHandler::Throw(env, UNKNOWN_ERR); + return nullptr; + } + + return result; +} + +ani_object AtomicFileAni::GetBaseFile(ani_env *env, [[maybe_unused]] ani_object object) +{ + auto atomicFile = Unwrap(env, object); + if (atomicFile == nullptr) { + HILOGE("Failed to get atomicFile"); + ErrorHandler::Throw(env, UNKNOWN_ERR); + return nullptr; + } + + auto ret = atomicFile->GetBaseFile(); + if (!ret.IsSuccess()) { + HILOGE("Failed to GetBaseFile"); + const auto &err = ret.GetError(); + ErrorHandler::Throw(env, err); + return nullptr; + } + + const FsFile *fsFile = ret.GetData().value(); + auto result = FileWrapper::Wrap(env, move(fsFile)); + if (result == nullptr) { + HILOGE("Failed to wrap"); + ErrorHandler::Throw(env, UNKNOWN_ERR); + return nullptr; + } + + return result; +} + +static ani_object CreateReadStream(ani_env *env, ani_string filePath) +{ + static const char *className = "L@ohos/file/fs/fileIo/ReadStream;"; + ani_class cls; + if (ANI_OK != env->FindClass(className, &cls)) { + HILOGE("Cannot find class %s", className); + return nullptr; + } + ani_method ctor; + if (ANI_OK != env->Class_FindMethod(cls, "", "Lstd/core/String;:V", &ctor)) { + HILOGE("Cannot find constructor method for class %s", className); + return nullptr; + } + ani_object obj; + if (ANI_OK != env->Object_New(cls, ctor, &obj, filePath)) { + HILOGE("New %s obj Failed", className); + return nullptr; + } + + return move(obj); +} + +static ani_object CreateWriteStream(ani_env *env, ani_string filePath) +{ + static const char *className = "L@ohos/file/fs/fileIo/WriteStream;"; + ani_class cls; + if (ANI_OK != env->FindClass(className, &cls)) { + HILOGE("Cannot find class %s", className); + return nullptr; + } + ani_method ctor; + if (ANI_OK != env->Class_FindMethod(cls, "", "Lstd/core/String;:V", &ctor)) { + HILOGE("Cannot find constructor method for class %s", className); + return nullptr; + } + ani_object obj; + if (ANI_OK != env->Object_New(cls, ctor, &obj, filePath)) { + HILOGE("New %s obj Failed", className); + return nullptr; + } + + return move(obj); +} + +static ani_object CreateStream( + ani_env *env, [[maybe_unused]] ani_object object, const std::string &streamName, const std::string &fineName) +{ + auto [succ, filePath] = TypeConverter::ToAniString(env, fineName); + if (!succ) { + HILOGE("Failed to ani_string"); + ErrorHandler::Throw(env, UNKNOWN_ERR); + return nullptr; + } + + if (streamName == READ_STREAM_CLASS) { + auto stream = CreateReadStream(env, filePath); + if (stream == nullptr) { + HILOGE("Failed to create read stream"); + ErrorHandler::Throw(env, UNKNOWN_ERR); + return nullptr; + } + return move(stream); + } + if (streamName == WRITE_STREAM_CLASS) { + auto stream = CreateWriteStream(env, filePath); + if (stream == nullptr) { + HILOGE("Failed to create write stream"); + ErrorHandler::Throw(env, UNKNOWN_ERR); + return nullptr; + } + return move(stream); + } + + ErrorHandler::Throw(env, UNKNOWN_ERR); + return nullptr; +} + +ani_object AtomicFileAni::OpenRead(ani_env *env, [[maybe_unused]] ani_object object) +{ + auto atomicFile = Unwrap(env, object); + if (atomicFile == nullptr) { + HILOGE("Failed to get atomicFile"); + ErrorHandler::Throw(env, UNKNOWN_ERR); + return nullptr; + } + + auto entity = atomicFile->GetEntity(); + if (entity == nullptr) { + ErrorHandler::Throw(env, UNKNOWN_ERR); + return nullptr; + } + + return CreateStream(env, object, READ_STREAM_CLASS, entity->baseFileName); +} + +ani_arraybuffer AtomicFileAni::ReadFully(ani_env *env, [[maybe_unused]] ani_object object) +{ + auto atomicFile = Unwrap(env, object); + if (atomicFile == nullptr) { + HILOGE("Failed to get atomicFile"); + ErrorHandler::Throw(env, UNKNOWN_ERR); + return nullptr; + } + + auto ret = atomicFile->ReadFully(); + if (!ret.IsSuccess()) { + HILOGE("Failed to read fully"); + ErrorHandler::Throw(env, ret.GetError()); + return nullptr; + } + + auto &bufferData = ret.GetData().value(); + uint8_t *buffer = bufferData->buffer; + size_t length = bufferData->length; + auto [succ, obj] = TypeConverter::ToAniArrayBuffer(env, buffer, length); + if (!succ) { + HILOGE("Failed to ani_arrayBuffer"); + ErrorHandler::Throw(env, UNKNOWN_ERR); + return nullptr; + } + + return obj; +} + +ani_object AtomicFileAni::StartWrite(ani_env *env, [[maybe_unused]] ani_object object) +{ + auto atomicFile = Unwrap(env, object); + if (atomicFile == nullptr) { + HILOGE("Failed to get atomicFile"); + ErrorHandler::Throw(env, UNKNOWN_ERR); + return nullptr; + } + + auto entity = atomicFile->GetEntity(); + if (entity == nullptr) { + HILOGE("Failed to get atomicFile entity"); + ErrorHandler::Throw(env, UNKNOWN_ERR); + return nullptr; + } + + fs::path filePath = entity->newFileName; + fs::path parentPath = filePath.parent_path(); + if (access(parentPath.c_str(), F_OK) != 0) { + HILOGE("Parent directory does not exist, err:%{public}d", errno); + ErrorHandler::Throw(env, ENOENT); + return nullptr; + } + + char *tmpfile = const_cast(entity->newFileName.c_str()); + if (mkstemp(tmpfile) == -1) { + HILOGE("Fail to create tmp file err:%{public}d!", errno); + ErrorHandler::Throw(env, ENOENT); + return nullptr; + } + + ani_object writeStream = CreateStream(env, object, WRITE_STREAM_CLASS, entity->newFileName); + if (writeStream == nullptr) { + HILOGE("Failed to create write stream"); + return nullptr; + } + + return writeStream; +} + +void AtomicFileAni::FinishWrite(ani_env *env, [[maybe_unused]] ani_object object) +{ + auto atomicFile = Unwrap(env, object); + if (atomicFile == nullptr) { + HILOGE("Failed to get atomicFile"); + ErrorHandler::Throw(env, UNKNOWN_ERR); + return; + } + + auto entity = atomicFile->GetEntity(); + if (entity == nullptr) { + HILOGE("Failed to get atomicFile entity"); + ErrorHandler::Throw(env, UNKNOWN_ERR); + return; + } + + auto ret = atomicFile->FinishWrite(); + if (!ret.IsSuccess()) { + HILOGE("Failed to finish write"); + const auto &err = ret.GetError(); + ErrorHandler::Throw(env, err); + } + return; +} + +void AtomicFileAni::FailWrite(ani_env *env, [[maybe_unused]] ani_object object) +{ + auto atomicFile = Unwrap(env, object); + if (atomicFile == nullptr) { + HILOGE("Failed to get atomicFile"); + ErrorHandler::Throw(env, UNKNOWN_ERR); + return; + } + + auto entity = atomicFile->GetEntity(); + if (entity == nullptr) { + HILOGE("Failed to get atomicFile entity"); + ErrorHandler::Throw(env, UNKNOWN_ERR); + return; + } + + auto ret = atomicFile->FailWrite(); + if (!ret.IsSuccess()) { + HILOGE("Failed to fail write"); + const auto &err = ret.GetError(); + ErrorHandler::Throw(env, err); + } + return; +} + +void AtomicFileAni::Delete(ani_env *env, [[maybe_unused]] ani_object object) +{ + auto atomicFile = Unwrap(env, object); + if (atomicFile == nullptr) { + HILOGE("Failed to get atomicFile"); + ErrorHandler::Throw(env, UNKNOWN_ERR); + return; + } + + auto ret = atomicFile->Delete(); + if (!ret.IsSuccess()) { + HILOGE("Failed to delete"); + ErrorHandler::Throw(env, ret.GetError()); + return; + } + + return; +} +} // namespace ANI +} // namespace ModuleFileIO +} // namespace FileManagement +} // namespace OHOS \ No newline at end of file diff --git a/interfaces/kits/js/src/mod_fs/class_atomicfile/ani/atomicfile_ani.h b/interfaces/kits/js/src/mod_fs/class_atomicfile/ani/atomicfile_ani.h new file mode 100644 index 0000000000000000000000000000000000000000..08898f125e60a2f3ae79f39ae7340428392a1c87 --- /dev/null +++ b/interfaces/kits/js/src/mod_fs/class_atomicfile/ani/atomicfile_ani.h @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef INTERFACES_KITS_JS_SRC_MOD_FS_ATOMICFILE_ANI_H +#define INTERFACES_KITS_JS_SRC_MOD_FS_ATOMICFILE_ANI_H + +#include + +namespace OHOS { +namespace FileManagement { +namespace ModuleFileIO { +namespace ANI { + +class AtomicFileAni final { +public: + static void Constructor(ani_env *env, ani_object obj, ani_string pathObj); + static ani_string GetPath(ani_env *env, [[maybe_unused]] ani_object object); + static ani_object GetBaseFile(ani_env *env, [[maybe_unused]] ani_object object); + static ani_object OpenRead(ani_env *env, [[maybe_unused]] ani_object object); + static ani_arraybuffer ReadFully(ani_env *env, [[maybe_unused]] ani_object object); + static ani_object StartWrite(ani_env *env, [[maybe_unused]] ani_object object); + static void FinishWrite(ani_env *env, [[maybe_unused]] ani_object object); + static void FailWrite(ani_env *env, [[maybe_unused]] ani_object object); + static void Delete(ani_env *env, [[maybe_unused]] ani_object object); +}; +} // namespace ANI +} // namespace ModuleFileIO +} // namespace FileManagement +} // namespace OHOS + +#endif // INTERFACES_KITS_JS_SRC_MOD_FS_ATOMICFILE_ANI_H \ No newline at end of file diff --git a/interfaces/kits/js/src/mod_fs/class_atomicfile/fs_atomicfile.cpp b/interfaces/kits/js/src/mod_fs/class_atomicfile/fs_atomicfile.cpp new file mode 100644 index 0000000000000000000000000000000000000000..2caa33889b4355e772b956cb41798fe94bbb0e53 --- /dev/null +++ b/interfaces/kits/js/src/mod_fs/class_atomicfile/fs_atomicfile.cpp @@ -0,0 +1,235 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "fs_atomicfile.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "fs_atomicfile_entity.h" +#include "filemgmt_libfs.h" +#include "file_instantiator.h" +#include "file_utils.h" + +namespace OHOS { +namespace FileManagement { +namespace ModuleFileIO { +namespace fs = std::filesystem; + +const std::string READ_STREAM_CLASS = "ReadStream"; +const std::string WRITE_STREAM_CLASS = "WriteStream"; +const std::string TEMP_FILE_SUFFIX = "_XXXXXX"; + +FsAtomicFileEntity *FsAtomicFile::GetEntity() +{ + if (!entity) { + return nullptr; + } + return entity.get(); +} + +void FsAtomicFile::FinalizeCallback(void *finalizeData, [[maybe_unused]] void *finalizeHint) +{ + BufferData *bufferData = static_cast(finalizeData); + delete bufferData; +} + +string FsAtomicFile::GetPath() +{ + return entity->baseFileName; +} + +FsResult FsAtomicFile::GetBaseFile() +{ + if (entity == nullptr) { + HILOGE("Failed to get atomicFileEntity"); + return FsResult::Error(UNKNOWN_ERR); + } + + if (entity->baseFileName.size() >= PATH_MAX) { + HILOGE("Base file name is too long"); + return FsResult::Error(UNKNOWN_ERR); + } + + auto absolutePath = std::make_unique(PATH_MAX); + char *result = realpath(entity->baseFileName.c_str(), absolutePath.get()); + if (result == nullptr) { + HILOGE("Failed to resolve real path, err:%{public}d", errno); + return FsResult::Error(errno); + } + + int fd = open(result, O_RDONLY); + if (fd < 0) { + HILOGE("Failed to open file, err:%{public}d", errno); + return FsResult::Error(errno); + } + + return FileInstantiator::InstantiateFile(fd, entity->baseFileName, false); +} + +static std::tuple, int32_t> ReadFileToBuffer(FILE *fp) +{ + int fd = fileno(fp); + if (fd < 0) { + HILOGE("Failed to get file descriptor, err:%{public}d", errno); + return { nullptr, UNKNOWN_ERR }; + } + + struct stat fileStat {}; + if (fstat(fd, &fileStat) < 0) { + HILOGE("Failed to get file stats, err:%{public}d", errno); + return { nullptr, errno }; + } + + long fileSize = fileStat.st_size; + if (fileSize <= 0) { + HILOGE("Invalid file size"); + return { nullptr, EIO }; + } + + auto bufferData = std::make_unique(); + bufferData->buffer = new (std::nothrow) uint8_t[fileSize]; + if (bufferData->buffer == nullptr) { + HILOGE("Failed to allocate memory"); + return { nullptr, ENOMEM }; + } + bufferData->length = fread(bufferData->buffer, sizeof(uint8_t), fileSize, fp); + if ((bufferData->length != static_cast(fileSize) && !feof(fp)) || ferror(fp)) { + HILOGE("Failed to read file, actual length is:%zu, fileSize:%ld", bufferData->length, fileSize); + delete[] bufferData->buffer; + bufferData->buffer = nullptr; + bufferData->length = 0; + return { nullptr, EIO }; + } + return { std::move(bufferData), ERRNO_NOERR }; +} + +FsResult> FsAtomicFile::ReadFully() +{ + if (entity == nullptr) { + HILOGE("Failed to get atomicFileEntity"); + return FsResult>::Error(UNKNOWN_ERR); + } + + auto absolutePath = std::make_unique(PATH_MAX); + char *result = realpath(entity->baseFileName.c_str(), absolutePath.get()); + if (result == nullptr) { + HILOGE("Failed to resolve file real path, err:%{public}d", errno); + return FsResult>::Error(errno); + } + + auto file = std::unique_ptr(std::fopen(result, "rb"), &std::fclose); + if (!file) { + HILOGE("Failed to open file, err:%{public}d", errno); + return FsResult>::Error(errno); + } + + auto [bufferData, errCode] = ReadFileToBuffer(file.get()); + if (errCode != ERRNO_NOERR) { + return FsResult>::Error(errCode); + } + return FsResult>::Success(move(bufferData)); +} + +FsResult FsAtomicFile::StartWrite() +{ + fs::path filePath = entity->newFileName; + fs::path parentPath = filePath.parent_path(); + if (access(parentPath.c_str(), F_OK) != 0) { + HILOGE("Parent directory does not exist, err:%{public}d", errno); + return FsResult::Error(ENOENT); + } + + char *tmpfile = const_cast(entity->newFileName.c_str()); + if (mkstemp(tmpfile) == -1) { + HILOGE("Fail to create tmp file err:%{public}d!", errno); + return FsResult::Error(ENOENT); + } + + return FsResult::Success(entity->newFileName); +} + +FsResult FsAtomicFile::FinishWrite() +{ + if (std::rename(entity->newFileName.c_str(), entity->baseFileName.c_str()) != 0) { + HILOGE("rename failed"); + return FsResult::Error(errno); + } + std::string tmpNewFileName = entity->baseFileName; + entity->newFileName = tmpNewFileName.append(TEMP_FILE_SUFFIX); + + return FsResult::Success(); +} + +FsResult FsAtomicFile::FailWrite() +{ + if (!fs::remove(entity->newFileName)) { + HILOGW("Failed to remove file"); + return FsResult::Error(errno); + } + std::string tmpNewFileName = entity->baseFileName; + entity->newFileName = tmpNewFileName.append(TEMP_FILE_SUFFIX); + + return FsResult::Success(); +} + +FsResult FsAtomicFile::Delete() +{ + auto rafentity = GetEntity(); + if (rafentity == nullptr) { + HILOGE("Failed to get atomicFileEntity"); + return FsResult::Error(UNKNOWN_ERR); + } + + bool errFlag = false; + std::error_code fsErrcode; + if (fs::exists(rafentity->newFileName, fsErrcode) && !fs::remove(rafentity->newFileName, fsErrcode)) { + errFlag = true; + } + if (fs::exists(rafentity->baseFileName, fsErrcode) && !fs::remove(rafentity->baseFileName, fsErrcode)) { + errFlag = true; + } + if (errFlag) { + HILOGE("Failed to remove file, err:%{public}s", fsErrcode.message().c_str()); + return FsResult::Error(fsErrcode.value()); + } + + rafentity->newFileName.clear(); + rafentity->baseFileName.clear(); + return FsResult::Success(); +} + +FsResult FsAtomicFile::Constructor(string path) +{ + auto atomicFileEntity = CreateUniquePtr(); + if (atomicFileEntity == nullptr) { + HILOGE("Failed to request heap memory"); + return FsResult::Error(ENOMEM); + } + atomicFileEntity->baseFileName = path; + atomicFileEntity->newFileName = path.append(TEMP_FILE_SUFFIX); + + auto file = new FsAtomicFile(move(atomicFileEntity)); + + return FsResult::Success(file); +} +} // namespace ModuleFileIO +} // namespace FileManagement +} // namespace OHOS \ No newline at end of file diff --git a/interfaces/kits/js/src/mod_fs/class_atomicfile/fs_atomicfile.h b/interfaces/kits/js/src/mod_fs/class_atomicfile/fs_atomicfile.h new file mode 100644 index 0000000000000000000000000000000000000000..80996b1c76844d90248768f25499a424dbf07352 --- /dev/null +++ b/interfaces/kits/js/src/mod_fs/class_atomicfile/fs_atomicfile.h @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef INTERFACES_KITS_JS_SRC_MOD_FS_CLASS_ATOMICFILE_FS_ATOMICFILE_H +#define INTERFACES_KITS_JS_SRC_MOD_FS_CLASS_ATOMICFILE_FS_ATOMICFILE_H + +#include "fs_atomicfile_entity.h" +#include "fs_file.h" + +namespace OHOS { +namespace FileManagement { +namespace ModuleFileIO { +using namespace std; + +struct BufferData { + uint8_t *buffer = nullptr; + size_t length = 0; + + ~BufferData() + { + delete[] buffer; + } +}; + +class FsAtomicFile final { +public: + FsAtomicFileEntity *GetEntity(); + static FsResult Constructor(string path); + string GetPath(); + FsResult GetBaseFile(); + FsResult> ReadFully(); + FsResult StartWrite(); + FsResult FinishWrite(); + FsResult FailWrite(); + FsResult Delete(); + static void FinalizeCallback(void *finalizeData, [[maybe_unused]] void *finalizeHint); + +private: + unique_ptr entity; + explicit FsAtomicFile(unique_ptr entity) : entity(move(entity)) {} +}; +} // namespace ModuleFileIO +} // namespace FileManagement +} // namespace OHOS +#endif // INTERFACES_KITS_JS_SRC_MOD_FS_CLASS_ATOMICFILE_FS_ATOMICFILE_H \ No newline at end of file diff --git a/interfaces/kits/js/src/mod_fs/class_atomicfile/fs_atomicfile_entity.h b/interfaces/kits/js/src/mod_fs/class_atomicfile/fs_atomicfile_entity.h new file mode 100644 index 0000000000000000000000000000000000000000..71bcb9ee18e96c6f3b174dcc380aeae781d7a2b3 --- /dev/null +++ b/interfaces/kits/js/src/mod_fs/class_atomicfile/fs_atomicfile_entity.h @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef INTERFACES_KITS_JS_SRC_MOD_FS_CLASS_ATOMICFILE_FS_ATOMICFILE_ENTITY_H +#define INTERFACES_KITS_JS_SRC_MOD_FS_CLASS_ATOMICFILE_FS_ATOMICFILE_ENTITY_H + +#include + +namespace OHOS { +namespace FileManagement { +namespace ModuleFileIO { +struct FsAtomicFileEntity { + std::string baseFileName = ""; + std::string newFileName = ""; +}; +} // namespace ModuleFileIO +} // namespace FileManagement +} // namespace OHOS +#endif // INTERFACES_KITS_JS_SRC_MOD_FS_CLASS_ATOMICFILE_FS_ATOMICFILE_ENTITY_H \ No newline at end of file