• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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