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