1 /*
2  * Copyright (c) 2023 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 "copydir.h"
17 
18 #include <dirent.h>
19 #include <filesystem>
20 #include <memory>
21 #include <tuple>
22 #include <unistd.h>
23 #include <vector>
24 
25 #include "common_func.h"
26 #include "file_utils.h"
27 #include "filemgmt_libhilog.h"
28 
29 namespace OHOS {
30 namespace FileManagement {
31 namespace ModuleFileIO {
32 using namespace std;
33 using namespace OHOS::FileManagement::LibN;
34 
35 static int RecurCopyDir(const string &srcPath, const string &destPath, const int mode,
36     vector<struct ConflictFiles> &errfiles);
37 
EndWithSlash(const string &src)38 static bool EndWithSlash(const string &src)
39 {
40     return src.back() == '/';
41 }
42 
AllowToCopy(const string &src, const string &dest)43 static bool AllowToCopy(const string &src, const string &dest)
44 {
45     if (src == dest) {
46         HILOGE("Failed to copy file, the same path");
47         return false;
48     }
49     if (EndWithSlash(src) ? dest.find(src) == 0 : dest.find(src + "/") == 0) {
50         HILOGE("Failed to copy file, dest is under src");
51         return false;
52     }
53     if (filesystem::path(src).parent_path() == dest) {
54         HILOGE("Failed to copy file, src's parent path is dest");
55         return false;
56     }
57     return true;
58 }
59 
ParseAndCheckJsOperand(napi_env env, const NFuncArg &funcArg)60 static tuple<bool, unique_ptr<char[]>, unique_ptr<char[]>, int> ParseAndCheckJsOperand(napi_env env,
61     const NFuncArg &funcArg)
62 {
63     auto [resGetFirstArg, src, ignore] = NVal(env, funcArg[NARG_POS::FIRST]).ToUTF8StringPath();
64     std::error_code errCode;
65     if (!resGetFirstArg || !filesystem::is_directory(filesystem::status(src.get(), errCode))) {
66         HILOGE("Invalid src, errCode = %{public}d", errCode.value());
67         return { false, nullptr, nullptr, 0 };
68     }
69     auto [resGetSecondArg, dest, unused] = NVal(env, funcArg[NARG_POS::SECOND]).ToUTF8StringPath();
70     if (!resGetSecondArg || !filesystem::is_directory(filesystem::status(dest.get(), errCode))) {
71         HILOGE("Invalid dest, errCode = %{public}d", errCode.value());
72         return { false, nullptr, nullptr, 0 };
73     }
74     if (!AllowToCopy(src.get(), dest.get())) {
75         return { false, nullptr, nullptr, 0 };
76     }
77     int mode = 0;
78     if (funcArg.GetArgc() >= NARG_CNT::THREE) {
79         bool resGetThirdArg = false;
80         tie(resGetThirdArg, mode) = NVal(env, funcArg[NARG_POS::THIRD]).ToInt32(mode);
81         if (!resGetThirdArg || (mode < COPYMODE_MIN || mode > COPYMODE_MAX)) {
82             HILOGE("Invalid mode");
83             return { false, nullptr, nullptr, 0 };
84         }
85     }
86     return { true, move(src), move(dest), mode };
87 }
88 
MakeDir(const string &path)89 static int MakeDir(const string &path)
90 {
91     filesystem::path destDir(path);
92     std::error_code errCode;
93     if (!filesystem::create_directory(destDir, errCode)) {
94         HILOGE("Failed to create directory, error code: %{public}d", errCode.value());
95         return errCode.value();
96     }
97     return ERRNO_NOERR;
98 }
99 
100 struct NameList {
101     struct dirent** namelist = { nullptr };
102     int direntNum = 0;
103 };
104 
RemoveFile(const string& destPath)105 static int RemoveFile(const string& destPath)
106 {
107     filesystem::path destFile(destPath);
108     std::error_code errCode;
109     if (!filesystem::remove(destFile, errCode)) {
110         HILOGE("Failed to remove file with path, error code: %{public}d", errCode.value());
111         return errCode.value();
112     }
113     return ERRNO_NOERR;
114 }
115 
Deleter(struct NameList *arg)116 static void Deleter(struct NameList *arg)
117 {
118     for (int i = 0; i < arg->direntNum; i++) {
119         free((arg->namelist)[i]);
120         (arg->namelist)[i] = nullptr;
121     }
122     free(arg->namelist);
123     arg->namelist = nullptr;
124     delete arg;
125     arg = nullptr;
126 }
127 
CopyFile(const string &src, const string &dest, const int mode)128 static int CopyFile(const string &src, const string &dest, const int mode)
129 {
130     filesystem::path dstPath(dest);
131     std::error_code errCode;
132     if (filesystem::exists(dstPath, errCode)) {
133         int ret = (mode == DIRMODE_FILE_COPY_THROW_ERR) ? EEXIST : RemoveFile(dest);
134         if (ret) {
135             HILOGE("Failed to copy file due to existing destPath with throw err");
136             return ret;
137         }
138     }
139     if (errCode.value() != ERRNO_NOERR) {
140         HILOGE("fs exists fail, errcode is %{public}d", errCode.value());
141         return errCode.value();
142     }
143     filesystem::path srcPath(src);
144     if (!filesystem::copy_file(srcPath, dstPath, filesystem::copy_options::overwrite_existing, errCode)) {
145         HILOGE("Failed to copy file, error code: %{public}d", errCode.value());
146         return errCode.value();
147     }
148     return ERRNO_NOERR;
149 }
150 
CopySubDir(const string &srcPath, const string &destPath, const int mode, vector<struct ConflictFiles> &errfiles)151 static int CopySubDir(const string &srcPath, const string &destPath, const int mode,
152     vector<struct ConflictFiles> &errfiles)
153 {
154     std::error_code errCode;
155     if (!filesystem::exists(destPath, errCode) && errCode.value() == ERRNO_NOERR) {
156         int res = MakeDir(destPath);
157         if (res != ERRNO_NOERR) {
158             HILOGE("Failed to mkdir");
159             return res;
160         }
161     } else if (errCode.value() != ERRNO_NOERR) {
162         HILOGE("fs exists fail, errcode is %{public}d", errCode.value());
163         return errCode.value();
164     }
165     return RecurCopyDir(srcPath, destPath, mode, errfiles);
166 }
167 
FilterFunc(const struct dirent *filename)168 static int FilterFunc(const struct dirent *filename)
169 {
170     if (string_view(filename->d_name) == "." || string_view(filename->d_name) == "..") {
171         return DISMATCH;
172     }
173     return MATCH;
174 }
175 
RecurCopyDir(const string &srcPath, const string &destPath, const int mode, vector<struct ConflictFiles> &errfiles)176 static int RecurCopyDir(const string &srcPath, const string &destPath, const int mode,
177     vector<struct ConflictFiles> &errfiles)
178 {
179     unique_ptr<struct NameList, decltype(Deleter)*> pNameList = {new (nothrow) struct NameList, Deleter};
180     if (pNameList == nullptr) {
181         HILOGE("Failed to request heap memory.");
182         return ENOMEM;
183     }
184     int num = scandir(srcPath.c_str(), &(pNameList->namelist), FilterFunc, alphasort);
185     if (num < 0) {
186         HILOGE("scandir fail errno is %{public}d", errno);
187         return errno;
188     }
189     pNameList->direntNum = num;
190 
191     for (int i = 0; i < num; i++) {
192         if ((pNameList->namelist[i])->d_type == DT_DIR) {
193             string srcTemp = srcPath + '/' + string((pNameList->namelist[i])->d_name);
194             string destTemp = destPath + '/' + string((pNameList->namelist[i])->d_name);
195             int res = CopySubDir(srcTemp, destTemp, mode, errfiles);
196             if (res == ERRNO_NOERR) {
197                 continue;
198             }
199             return res;
200         } else {
201             string src = srcPath + '/' + string((pNameList->namelist[i])->d_name);
202             string dest = destPath + '/' + string((pNameList->namelist[i])->d_name);
203             int res = CopyFile(src, dest, mode);
204             if (res == EEXIST) {
205                 errfiles.emplace_back(src, dest);
206                 continue;
207             } else if (res == ERRNO_NOERR) {
208                 continue;
209             } else {
210                 HILOGE("Failed to copy file for error %{public}d", res);
211                 return res;
212             }
213         }
214     }
215     return ERRNO_NOERR;
216 }
217 
CopyDirFunc(const string &src, const string &dest, const int mode, vector<struct ConflictFiles> &errfiles)218 static int CopyDirFunc(const string &src, const string &dest, const int mode, vector<struct ConflictFiles> &errfiles)
219 {
220     size_t found = string(src).rfind('/');
221     if (found == std::string::npos) {
222         return EINVAL;
223     }
224     string dirName = string(src).substr(found);
225     string destStr = dest + dirName;
226     std::error_code errCode;
227     if (!filesystem::exists(destStr, errCode) && errCode.value() == ERRNO_NOERR) {
228         int res = MakeDir(destStr);
229         if (res != ERRNO_NOERR) {
230             HILOGE("Failed to mkdir");
231             return res;
232         }
233     } else if (errCode.value() != ERRNO_NOERR) {
234         HILOGE("fs exists fail, errcode is %{public}d", errCode.value());
235         return errCode.value();
236     }
237     int res = RecurCopyDir(src, destStr, mode, errfiles);
238     if (!errfiles.empty() && res == ERRNO_NOERR) {
239         return EEXIST;
240     }
241     return res;
242 }
243 
PushErrFilesInData(napi_env env, vector<struct ConflictFiles> &errfiles)244 static napi_value PushErrFilesInData(napi_env env, vector<struct ConflictFiles> &errfiles)
245 {
246     napi_value res = nullptr;
247     napi_status status = napi_create_array(env, &res);
248     if (status != napi_ok) {
249         HILOGE("Failed to creat array");
250         return nullptr;
251     }
252     for (size_t i = 0; i < errfiles.size(); i++) {
253         NVal obj = NVal::CreateObject(env);
254         obj.AddProp("srcFile", NVal::CreateUTF8String(env, errfiles[i].srcFiles).val_);
255         obj.AddProp("destFile", NVal::CreateUTF8String(env, errfiles[i].destFiles).val_);
256         status = napi_set_element(env, res, i, obj.val_);
257         if (status != napi_ok) {
258             HILOGE("Failed to set element on data");
259             return nullptr;
260         }
261     }
262     return res;
263 }
264 
Sync(napi_env env, napi_callback_info info)265 napi_value CopyDir::Sync(napi_env env, napi_callback_info info)
266 {
267     NFuncArg funcArg(env, info);
268     if (!funcArg.InitArgs(NARG_CNT::TWO, NARG_CNT::THREE)) {
269         HILOGE("Number of arguments unmatched");
270         NError(EINVAL).ThrowErr(env);
271         return nullptr;
272     }
273     auto [succ, src, dest, mode] = ParseAndCheckJsOperand(env, funcArg);
274     if (!succ) {
275         NError(EINVAL).ThrowErr(env);
276         return nullptr;
277     }
278 
279     vector<struct ConflictFiles> errfiles = {};
280     int ret = CopyDirFunc(src.get(), dest.get(), mode, errfiles);
281     if (ret == EEXIST && mode == DIRMODE_FILE_COPY_THROW_ERR) {
282         NError(ret).ThrowErrAddData(env, EEXIST, PushErrFilesInData(env, errfiles));
283         return nullptr;
284     } else if (ret) {
285         NError(ret).ThrowErr(env);
286         return nullptr;
287     }
288     return NVal::CreateUndefined(env).val_;
289 }
290 
291 struct CopyDirArgs {
292     vector<ConflictFiles> errfiles;
293     int errNo = ERRNO_NOERR;
294 };
295 
Async(napi_env env, napi_callback_info info)296 napi_value CopyDir::Async(napi_env env, napi_callback_info info)
297 {
298     NFuncArg funcArg(env, info);
299     if (!funcArg.InitArgs(NARG_CNT::TWO, NARG_CNT::FOUR)) {
300         HILOGE("Number of arguments unmatched");
301         NError(EINVAL).ThrowErr(env);
302         return nullptr;
303     }
304     auto [succ, src, dest, mode] = ParseAndCheckJsOperand(env, funcArg);
305     if (!succ) {
306         NError(EINVAL).ThrowErr(env);
307         return nullptr;
308     }
309 
310     auto arg = CreateSharedPtr<CopyDirArgs>();
311     if (arg == nullptr) {
312         HILOGE("Failed to request heap memory.");
313         NError(ENOMEM).ThrowErr(env);
314         return nullptr;
315     }
316     auto cbExec = [srcPath = string(src.get()), destPath = string(dest.get()), mode = mode, arg]() -> NError {
317         arg->errNo = CopyDirFunc(srcPath, destPath, mode, arg->errfiles);
318         if (arg->errNo) {
319             return NError(arg->errNo);
320         }
321         return NError(ERRNO_NOERR);
322     };
323 
324     auto cbComplCallback = [arg, mode = mode](napi_env env, NError err) -> NVal {
325         if (arg->errNo == EEXIST && mode == DIRMODE_FILE_COPY_THROW_ERR) {
326             napi_value data = err.GetNapiErr(env);
327             napi_status status = napi_set_named_property(env, data, FILEIO_TAG_ERR_DATA.c_str(),
328                 PushErrFilesInData(env, arg->errfiles));
329             if (status != napi_ok) {
330                 HILOGE("Failed to set data property on Error");
331             }
332             return { env, data };
333         } else if (arg->errNo) {
334             return { env, err.GetNapiErr(env) };
335         }
336         return NVal::CreateUndefined(env);
337     };
338 
339     NVal thisVar(env, funcArg.GetThisVar());
340     if (funcArg.GetArgc() == NARG_CNT::TWO || (funcArg.GetArgc() == NARG_CNT::THREE &&
341             !NVal(env, funcArg[NARG_POS::THIRD]).TypeIs(napi_function))) {
342         return NAsyncWorkPromise(env, thisVar).Schedule(PROCEDURE_COPYDIR_NAME, cbExec, cbComplCallback).val_;
343     } else {
344         int cbIdex = ((funcArg.GetArgc() == NARG_CNT::THREE) ? NARG_POS::THIRD : NARG_POS::FOURTH);
345         NVal cb(env, funcArg[cbIdex]);
346         return NAsyncWorkCallback(env, thisVar, cb).Schedule(PROCEDURE_COPYDIR_NAME, cbExec, cbComplCallback).val_;
347     }
348 }
349 
350 } // ModuleFileIO
351 } // FileManagement
352 } // OHOS