1 /*
2 * Copyright (c) 2023-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 "copy.h"
17
18 #include <cstring>
19 #include <dirent.h>
20 #include <fcntl.h>
21 #include <filesystem>
22 #include <limits>
23 #include <memory>
24 #include <poll.h>
25 #include <sys/eventfd.h>
26 #include <sys/inotify.h>
27 #include <sys/prctl.h>
28 #include <sys/stat.h>
29 #include <sys/types.h>
30 #include <tuple>
31 #include <unistd.h>
32 #include <vector>
33
34 #include "copy/file_copy_manager.h"
35 #include "datashare_helper.h"
36 #include "file_uri.h"
37 #include "file_utils.h"
38 #include "filemgmt_libhilog.h"
39 #include "if_system_ability_manager.h"
40 #include "iservice_registry.h"
41 #include "ipc_skeleton.h"
42 #include "system_ability_definition.h"
43 #include "trans_listener.h"
44 #include "utils_log.h"
45 #include "common_func.h"
46
47 namespace OHOS {
48 namespace FileManagement {
49 namespace ModuleFileIO {
50 using namespace AppFileService::ModuleFileUri;
51 namespace fs = std::filesystem;
52 const std::string FILE_PREFIX_NAME = "file://";
53 const std::string NETWORK_PARA = "?networkid=";
54 const string PROCEDURE_COPY_NAME = "FileFSCopy";
55 const std::string MEDIA = "media";
56 constexpr std::chrono::milliseconds NOTIFY_PROGRESS_DELAY(300);
57 std::recursive_mutex Copy::mutex_;
58 std::map<FileInfos, std::shared_ptr<JsCallbackObject>> Copy::jsCbMap_;
59 uint32_t g_apiCompatibleVersion = 0;
60
GetCopySignalFromOptionArg(napi_env env,const NFuncArg & funcArg)61 tuple<bool, NVal> Copy::GetCopySignalFromOptionArg(napi_env env, const NFuncArg &funcArg)
62 {
63 if (funcArg.GetArgc() < NARG_CNT::THREE) {
64 return { true, NVal() };
65 }
66 NVal op(env, funcArg[NARG_POS::THIRD]);
67 if (op.HasProp("copySignal") && !op.GetProp("copySignal").TypeIs(napi_undefined)) {
68 if (!op.GetProp("copySignal").TypeIs(napi_object)) {
69 HILOGE("Illegal options.CopySignal type");
70 return { false, NVal() };
71 }
72 return { true, op.GetProp("copySignal") };
73 }
74 return { true, NVal() };
75 }
76
IsRemoteUri(const std::string & uri)77 bool Copy::IsRemoteUri(const std::string &uri)
78 {
79 // NETWORK_PARA
80 return uri.find(NETWORK_PARA) != uri.npos;
81 }
82
IsDirectory(const std::string & path)83 bool Copy::IsDirectory(const std::string &path)
84 {
85 struct stat buf {};
86 int ret = stat(path.c_str(), &buf);
87 if (ret == -1) {
88 HILOGE("stat failed, errno is %{public}d", errno);
89 return false;
90 }
91 return (buf.st_mode & S_IFMT) == S_IFDIR;
92 }
93
IsFile(const std::string & path)94 bool Copy::IsFile(const std::string &path)
95 {
96 struct stat buf {};
97 int ret = stat(path.c_str(), &buf);
98 if (ret == -1) {
99 HILOGI("stat failed, errno is %{public}d, ", errno);
100 return false;
101 }
102 return (buf.st_mode & S_IFMT) == S_IFREG;
103 }
104
IsMediaUri(const std::string & uriPath)105 bool Copy::IsMediaUri(const std::string &uriPath)
106 {
107 Uri uri(uriPath);
108 string bundleName = uri.GetAuthority();
109 return bundleName == MEDIA;
110 }
111
GetFileSize(const std::string & path)112 tuple<int, uint64_t> Copy::GetFileSize(const std::string &path)
113 {
114 struct stat buf {};
115 int ret = stat(path.c_str(), &buf);
116 if (ret == -1) {
117 HILOGI("Stat failed.");
118 return { errno, 0 };
119 }
120 return { ERRNO_NOERR, buf.st_size };
121 }
122
MakeDir(const string & path)123 int Copy::MakeDir(const string &path)
124 {
125 filesystem::path destDir(path);
126 std::error_code errCode;
127 if (!filesystem::create_directory(destDir, errCode)) {
128 HILOGE("Failed to create directory, error code: %{public}d", errCode.value());
129 return errCode.value();
130 }
131 return ERRNO_NOERR;
132 }
133
134 struct NameList {
135 struct dirent **namelist = { nullptr };
136 int direntNum = 0;
137 };
138
RegisterListener(napi_env env,const std::shared_ptr<FileInfos> & infos)139 std::shared_ptr<JsCallbackObject> Copy::RegisterListener(napi_env env, const std::shared_ptr<FileInfos> &infos)
140 {
141 auto callback = CreateSharedPtr<JsCallbackObject>(env, infos->listener);
142 if (callback == nullptr) {
143 HILOGE("Failed to request heap memory.");
144 return nullptr;
145 }
146 std::lock_guard<std::recursive_mutex> lock(mutex_);
147 auto iter = jsCbMap_.find(*infos);
148 if (iter != jsCbMap_.end()) {
149 HILOGE("Copy::RegisterListener, already registered.");
150 return nullptr;
151 }
152 jsCbMap_.insert({ *infos, callback });
153 return callback;
154 }
155
UnregisterListener(std::shared_ptr<FileInfos> fileInfos)156 void Copy::UnregisterListener(std::shared_ptr<FileInfos> fileInfos)
157 {
158 if (fileInfos == nullptr) {
159 HILOGE("fileInfos is nullptr");
160 return;
161 }
162 std::lock_guard<std::recursive_mutex> lock(mutex_);
163 auto iter = jsCbMap_.find(*fileInfos);
164 if (iter == jsCbMap_.end()) {
165 HILOGI("It is not be registered.");
166 return;
167 }
168 jsCbMap_.erase(*fileInfos);
169 }
170
ReceiveComplete(std::shared_ptr<UvEntry> entry)171 void Copy::ReceiveComplete(std::shared_ptr<UvEntry> entry)
172 {
173 if (entry == nullptr) {
174 HILOGE("entry pointer is nullptr.");
175 return;
176 }
177 auto processedSize = entry->progressSize;
178 if (processedSize < entry->callback->maxProgressSize) {
179 return;
180 }
181 entry->callback->maxProgressSize = processedSize;
182
183 napi_handle_scope scope = nullptr;
184 napi_env env = entry->callback->env;
185 napi_status status = napi_open_handle_scope(env, &scope);
186 if (status != napi_ok) {
187 HILOGE("Failed to open handle scope, status: %{public}d.", status);
188 return;
189 }
190 NVal obj = NVal::CreateObject(env);
191 if (processedSize <= MAX_VALUE && entry->totalSize <= MAX_VALUE) {
192 obj.AddProp("processedSize", NVal::CreateInt64(env, processedSize).val_);
193 obj.AddProp("totalSize", NVal::CreateInt64(env, entry->totalSize).val_);
194 }
195 napi_value result = nullptr;
196 napi_value jsCallback = entry->callback->nRef.Deref(env).val_;
197 status = napi_call_function(env, nullptr, jsCallback, 1, &(obj.val_), &result);
198 if (status != napi_ok) {
199 HILOGE("Failed to get result, status: %{public}d.", status);
200 }
201 status = napi_close_handle_scope(env, scope);
202 if (status != napi_ok) {
203 HILOGE("Failed to close scope, status: %{public}d.", status);
204 }
205 }
206
GetUVwork(std::shared_ptr<FileInfos> infos)207 UvEntry *Copy::GetUVwork(std::shared_ptr<FileInfos> infos)
208 {
209 UvEntry *entry = nullptr;
210 {
211 std::lock_guard<std::recursive_mutex> lock(mutex_);
212 auto iter = jsCbMap_.find(*infos);
213 if (iter == jsCbMap_.end()) {
214 HILOGE("Failed to find callback");
215 return nullptr;
216 }
217 auto callback = iter->second;
218 infos->env = callback->env;
219 entry = new (std::nothrow) UvEntry(iter->second, infos);
220 if (entry == nullptr) {
221 HILOGE("entry ptr is nullptr.");
222 return nullptr;
223 }
224 entry->progressSize = callback->progressSize;
225 entry->totalSize = callback->totalSize;
226 }
227
228 return entry;
229 }
230
OnFileReceive(std::shared_ptr<FileInfos> infos)231 void Copy::OnFileReceive(std::shared_ptr<FileInfos> infos)
232 {
233 std::shared_ptr<UvEntry> entry(GetUVwork(infos));
234 auto task = [entry] () {
235 ReceiveComplete(entry);
236 };
237 auto ret = napi_send_event(infos->env, task, napi_eprio_immediate);
238 if (ret != 0) {
239 HILOGE("failed to uv_queue_work");
240 }
241 }
242
GetRegisteredListener(std::shared_ptr<FileInfos> infos)243 std::shared_ptr<JsCallbackObject> Copy::GetRegisteredListener(std::shared_ptr<FileInfos> infos)
244 {
245 std::lock_guard<std::recursive_mutex> lock(mutex_);
246 auto iter = jsCbMap_.find(*infos);
247 if (iter == jsCbMap_.end()) {
248 HILOGE("It is not registered.");
249 return nullptr;
250 }
251 return iter->second;
252 }
253
GetListenerFromOptionArg(napi_env env,const NFuncArg & funcArg)254 tuple<bool, NVal> Copy::GetListenerFromOptionArg(napi_env env, const NFuncArg &funcArg)
255 {
256 if (funcArg.GetArgc() >= NARG_CNT::THREE) {
257 NVal op(env, funcArg[NARG_POS::THIRD]);
258 if (op.HasProp("progressListener") && !op.GetProp("progressListener").TypeIs(napi_undefined)) {
259 if (!op.GetProp("progressListener").TypeIs(napi_function)) {
260 HILOGE("Illegal options.progressListener type");
261 return { false, NVal() };
262 }
263 return { true, op.GetProp("progressListener") };
264 }
265 }
266 return { true, NVal() };
267 }
268
ParseJsOperand(napi_env env,NVal pathOrFdFromJsArg)269 tuple<bool, std::string> Copy::ParseJsOperand(napi_env env, NVal pathOrFdFromJsArg)
270 {
271 auto [succ, uri, ignore] = pathOrFdFromJsArg.ToUTF8StringPath();
272 if (!succ) {
273 HILOGE("parse uri failed.");
274 return { false, "" };
275 }
276 std::string uriStr = std::string(uri.get());
277 if (IsValidUri(uriStr)) {
278 return { true, uriStr };
279 }
280 return { false, "" };
281 }
282
IsValidUri(const std::string & uri)283 bool Copy::IsValidUri(const std::string &uri)
284 {
285 return uri.find(FILE_PREFIX_NAME) == 0;
286 }
287
GetRealPath(const std::string & path)288 std::string Copy::GetRealPath(const std::string& path)
289 {
290 fs::path tempPath(path);
291 fs::path realPath{};
292 for (const auto& component : tempPath) {
293 if (component == ".") {
294 continue;
295 } else if (component == "..") {
296 realPath = realPath.parent_path();
297 } else {
298 realPath /= component;
299 }
300 }
301 return realPath.string();
302 }
303
CreateFileInfos(const std::string & srcUri,const std::string & destUri,NVal & listener,NVal copySignal)304 tuple<int, std::shared_ptr<FileInfos>> Copy::CreateFileInfos(
305 const std::string &srcUri, const std::string &destUri, NVal &listener, NVal copySignal)
306 {
307 auto infos = CreateSharedPtr<FileInfos>();
308 if (infos == nullptr) {
309 HILOGE("Failed to request heap memory.");
310 return { ENOMEM, nullptr };
311 }
312 infos->srcUri = srcUri;
313 infos->destUri = destUri;
314 infos->listener = listener;
315 infos->env = listener.env_;
316 infos->copySignal = copySignal;
317 FileUri srcFileUri(infos->srcUri);
318 infos->srcPath = srcFileUri.GetRealPath();
319 FileUri dstFileUri(infos->destUri);
320 infos->destPath = dstFileUri.GetPath();
321 infos->srcPath = GetRealPath(infos->srcPath);
322 infos->destPath = GetRealPath(infos->destPath);
323 infos->isFile = IsMediaUri(infos->srcUri) || IsFile(infos->srcPath);
324 infos->notifyTime = std::chrono::steady_clock::now() + NOTIFY_PROGRESS_DELAY;
325 if (listener) {
326 infos->hasListener = true;
327 }
328 if (infos->copySignal) {
329 auto taskSignalEntity = NClass::GetEntityOf<TaskSignalEntity>(infos->env, infos->copySignal.val_);
330 if (taskSignalEntity != nullptr) {
331 infos->taskSignal = taskSignalEntity->taskSignal_;
332 }
333 }
334 return { ERRNO_NOERR, infos };
335 }
336
ParseJsParam(napi_env env,NFuncArg & funcArg,std::shared_ptr<FileInfos> & fileInfos)337 int Copy::ParseJsParam(napi_env env, NFuncArg &funcArg, std::shared_ptr<FileInfos> &fileInfos)
338 {
339 if (!funcArg.InitArgs(NARG_CNT::TWO, NARG_CNT::FOUR)) {
340 HILOGE("Number of arguments unmatched");
341 return E_PARAMS;
342 }
343 auto [succSrc, srcUri] = ParseJsOperand(env, { env, funcArg[NARG_POS::FIRST] });
344 auto [succDest, destUri] = ParseJsOperand(env, { env, funcArg[NARG_POS::SECOND] });
345 auto [succListener, listener] = GetListenerFromOptionArg(env, funcArg);
346 auto [succSignal, copySignal] = GetCopySignalFromOptionArg(env, funcArg);
347 if (!succSrc || !succDest || !succListener || !succSignal) {
348 HILOGE("The first/second/third argument requires uri/uri/napi_function");
349 return E_PARAMS;
350 }
351 auto [errCode, infos] = CreateFileInfos(srcUri, destUri, listener, copySignal);
352 if (errCode != ERRNO_NOERR) {
353 return errCode;
354 }
355 fileInfos = infos;
356 return ERRNO_NOERR;
357 }
358
CopyComplete(std::shared_ptr<FileInfos> infos,std::shared_ptr<JsCallbackObject> callback)359 void Copy::CopyComplete(std::shared_ptr<FileInfos> infos, std::shared_ptr<JsCallbackObject> callback)
360 {
361 if (callback != nullptr && infos->hasListener) {
362 callback->progressSize = callback->totalSize;
363 OnFileReceive(infos);
364 }
365 }
366
ExecCopy(std::shared_ptr<FileInfos> infos,std::shared_ptr<JsCallbackObject> callback)367 int32_t Copy::ExecCopy(std::shared_ptr<FileInfos> infos, std::shared_ptr<JsCallbackObject> callback)
368 {
369 Storage::DistributedFile::ProcessCallback processListener;
370 if (infos->hasListener) {
371 processListener = [infos, callback](uint64_t processSize, uint64_t totalSize) -> void {
372 callback->progressSize = processSize;
373 callback->totalSize = totalSize;
374 if (processSize != totalSize) {
375 OnFileReceive(infos);
376 }
377 };
378 }
379 if (infos->taskSignal != nullptr) {
380 infos->taskSignal->MarkDfsTask();
381 infos->taskSignal->SetCopyTaskUri(infos->srcUri, infos->destUri);
382 }
383 LOGI("Copy begin");
384 auto result = Storage::DistributedFile::FileCopyManager::GetInstance()->Copy(infos->srcUri, infos->destUri,
385 processListener);
386 LOGI("Copy end, result = %{public}d", result);
387 return result;
388 }
389
Async(napi_env env,napi_callback_info info)390 napi_value Copy::Async(napi_env env, napi_callback_info info)
391 {
392 NFuncArg funcArg(env, info);
393 std::shared_ptr<FileInfos> infos = nullptr;
394 auto result = ParseJsParam(env, funcArg, infos);
395 if (result != ERRNO_NOERR) {
396 NError(result).ThrowErr(env);
397 return nullptr;
398 }
399 auto callback = RegisterListener(env, infos);
400 if (callback == nullptr) {
401 NError(EINVAL).ThrowErr(env);
402 return nullptr;
403 }
404 auto cbExec = [infos, callback]() -> NError {
405 auto result = ExecCopy(infos, callback);
406 auto it = softbusErr2ErrCodeTable.find(result);
407 if (it != softbusErr2ErrCodeTable.end()) {
408 result = it->second;
409 }
410 if (result != ERRNO_NOERR) {
411 infos->exceptionCode = result;
412 return NError(infos->exceptionCode);
413 }
414 CopyComplete(infos, callback);
415 return NError(infos->exceptionCode);
416 };
417
418 auto cbCompl = [infos](napi_env env, NError err) -> NVal {
419 UnregisterListener(infos);
420 if (err) {
421 return { env, err.GetNapiErr(env) };
422 }
423 return { NVal::CreateUndefined(env) };
424 };
425
426 NVal thisVar(env, funcArg.GetThisVar());
427 if (funcArg.GetArgc() == NARG_CNT::TWO ||
428 (funcArg.GetArgc() == NARG_CNT::THREE && !NVal(env, funcArg[NARG_POS::THIRD]).TypeIs(napi_function))) {
429 return NAsyncWorkPromise(env, thisVar).Schedule(PROCEDURE_COPY_NAME, cbExec, cbCompl).val_;
430 } else {
431 NVal cb(env, funcArg[((funcArg.GetArgc() == NARG_CNT::THREE) ? NARG_POS::THIRD : NARG_POS::FOURTH)]);
432 return NAsyncWorkCallback(env, thisVar, cb).Schedule(PROCEDURE_COPY_NAME, cbExec, cbCompl).val_;
433 }
434 }
435 } // namespace ModuleFileIO
436 } // namespace FileManagement
437 } // namespace OHOS