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]).ToUTF8String();
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]).ToUTF8String();
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 }
110
CopyFile(const string & src,const string & dest,const int mode)111 static int CopyFile(const string &src, const string &dest, const int mode)
112 {
113 filesystem::path dstPath(dest);
114 if (filesystem::exists(dstPath)) {
115 int ret = (mode == DIRMODE_FILE_COPY_THROW_ERR) ? EEXIST : RemoveFile(dest);
116 if (ret) {
117 HILOGE("Failed to copy file due to existing destPath with throw err");
118 return ret;
119 }
120 }
121 filesystem::path srcPath(src);
122 std::error_code errCode;
123 if (!filesystem::copy_file(srcPath, dstPath, filesystem::copy_options::overwrite_existing, errCode)) {
124 HILOGE("Failed to copy file, error code: %{public}d", errCode.value());
125 return errCode.value();
126 }
127 return ERRNO_NOERR;
128 }
129
CopySubDir(const string & srcPath,const string & destPath,const int mode,vector<struct ConflictFiles> & errfiles)130 static int CopySubDir(const string &srcPath, const string &destPath, const int mode,
131 vector<struct ConflictFiles> &errfiles)
132 {
133 if (!filesystem::exists(destPath)) {
134 int res = MakeDir(destPath);
135 if (res != ERRNO_NOERR) {
136 HILOGE("Failed to mkdir");
137 return res;
138 }
139 }
140 return RecurCopyDir(srcPath, destPath, mode, errfiles);
141 }
142
FilterFunc(const struct dirent * filename)143 static int FilterFunc(const struct dirent *filename)
144 {
145 if (string_view(filename->d_name) == "." || string_view(filename->d_name) == "..") {
146 return DISMATCH;
147 }
148 return MATCH;
149 }
150
RecurCopyDir(const string & srcPath,const string & destPath,const int mode,vector<struct ConflictFiles> & errfiles)151 static int RecurCopyDir(const string &srcPath, const string &destPath, const int mode,
152 vector<struct ConflictFiles> &errfiles)
153 {
154 unique_ptr<struct NameList, decltype(Deleter)*> pNameList = {new (nothrow) struct NameList, Deleter};
155 if (pNameList == nullptr) {
156 HILOGE("Failed to request heap memory.");
157 return ENOMEM;
158 }
159 int num = scandir(srcPath.c_str(), &(pNameList->namelist), FilterFunc, alphasort);
160 pNameList->direntNum = num;
161
162 for (int i = 0; i < num; i++) {
163 if ((pNameList->namelist[i])->d_type == DT_DIR) {
164 string srcTemp = srcPath + '/' + string((pNameList->namelist[i])->d_name);
165 string destTemp = destPath + '/' + string((pNameList->namelist[i])->d_name);
166 int res = CopySubDir(srcTemp, destTemp, mode, errfiles);
167 if (res == ERRNO_NOERR) {
168 continue;
169 }
170 return res;
171 } else {
172 string src = srcPath + '/' + string((pNameList->namelist[i])->d_name);
173 string dest = destPath + '/' + string((pNameList->namelist[i])->d_name);
174 int res = CopyFile(src, dest, mode);
175 if (res == EEXIST) {
176 errfiles.emplace_back(src, dest);
177 continue;
178 } else if (res == ERRNO_NOERR) {
179 continue;
180 } else {
181 HILOGE("Failed to copy file for error %{public}d", res);
182 return res;
183 }
184 }
185 }
186 return ERRNO_NOERR;
187 }
188
CopyDirFunc(const string & src,const string & dest,const int mode,vector<struct ConflictFiles> & errfiles)189 static int CopyDirFunc(const string &src, const string &dest, const int mode, vector<struct ConflictFiles> &errfiles)
190 {
191 size_t found = string(src).rfind('/');
192 if (found == std::string::npos) {
193 return EINVAL;
194 }
195 string dirName = string(src).substr(found);
196 string destStr = dest + dirName;
197 if (!filesystem::exists(destStr)) {
198 int res = MakeDir(destStr);
199 if (res != ERRNO_NOERR) {
200 HILOGE("Failed to mkdir");
201 return res;
202 }
203 }
204 int res = RecurCopyDir(src, destStr, mode, errfiles);
205 if (!errfiles.empty() && res == ERRNO_NOERR) {
206 return EEXIST;
207 }
208 return res;
209 }
210
PushErrFilesInData(napi_env env,vector<struct ConflictFiles> & errfiles)211 static napi_value PushErrFilesInData(napi_env env, vector<struct ConflictFiles> &errfiles)
212 {
213 napi_value res = nullptr;
214 napi_status status = napi_create_array(env, &res);
215 if (status != napi_ok) {
216 HILOGE("Failed to creat array");
217 return nullptr;
218 }
219 for (size_t i = 0; i < errfiles.size(); i++) {
220 NVal obj = NVal::CreateObject(env);
221 obj.AddProp("srcFile", NVal::CreateUTF8String(env, errfiles[i].srcFiles).val_);
222 obj.AddProp("destFile", NVal::CreateUTF8String(env, errfiles[i].destFiles).val_);
223 status = napi_set_element(env, res, i, obj.val_);
224 if (status != napi_ok) {
225 HILOGE("Failed to set element on data");
226 return nullptr;
227 }
228 }
229 return res;
230 }
231
Sync(napi_env env,napi_callback_info info)232 napi_value CopyDir::Sync(napi_env env, napi_callback_info info)
233 {
234 NFuncArg funcArg(env, info);
235 if (!funcArg.InitArgs(NARG_CNT::TWO, NARG_CNT::THREE)) {
236 HILOGE("Number of arguments unmatched");
237 NError(EINVAL).ThrowErr(env);
238 return nullptr;
239 }
240 auto [succ, src, dest, mode] = ParseAndCheckJsOperand(env, funcArg);
241 if (!succ) {
242 NError(EINVAL).ThrowErr(env);
243 return nullptr;
244 }
245
246 vector<struct ConflictFiles> errfiles = {};
247 int ret = CopyDirFunc(src.get(), dest.get(), mode, errfiles);
248 if (ret == EEXIST && mode == DIRMODE_FILE_COPY_THROW_ERR) {
249 NError(ret).ThrowErrAddData(env, EEXIST, PushErrFilesInData(env, errfiles));
250 return nullptr;
251 } else if (ret) {
252 NError(ret).ThrowErr(env);
253 return nullptr;
254 }
255 return NVal::CreateUndefined(env).val_;
256 }
257
258 struct CopyDirArgs {
259 vector<ConflictFiles> errfiles;
260 int errNo = ERRNO_NOERR;
261 };
262
Async(napi_env env,napi_callback_info info)263 napi_value CopyDir::Async(napi_env env, napi_callback_info info)
264 {
265 NFuncArg funcArg(env, info);
266 if (!funcArg.InitArgs(NARG_CNT::TWO, NARG_CNT::FOUR)) {
267 HILOGE("Number of arguments unmatched");
268 NError(EINVAL).ThrowErr(env);
269 return nullptr;
270 }
271 auto [succ, src, dest, mode] = ParseAndCheckJsOperand(env, funcArg);
272 if (!succ) {
273 NError(EINVAL).ThrowErr(env);
274 return nullptr;
275 }
276
277 auto arg = CreateSharedPtr<CopyDirArgs>();
278 if (arg == nullptr) {
279 HILOGE("Failed to request heap memory.");
280 NError(ENOMEM).ThrowErr(env);
281 return nullptr;
282 }
283 auto cbExec = [srcPath = string(src.get()), destPath = string(dest.get()), mode = mode, arg]() -> NError {
284 arg->errNo = CopyDirFunc(srcPath, destPath, mode, arg->errfiles);
285 if (arg->errNo) {
286 return NError(arg->errNo);
287 }
288 return NError(ERRNO_NOERR);
289 };
290
291 auto cbComplCallback = [arg, mode = mode](napi_env env, NError err) -> NVal {
292 if (arg->errNo == EEXIST && mode == DIRMODE_FILE_COPY_THROW_ERR) {
293 napi_value data = err.GetNapiErr(env);
294 napi_status status = napi_set_named_property(env, data, FILEIO_TAG_ERR_DATA.c_str(),
295 PushErrFilesInData(env, arg->errfiles));
296 if (status != napi_ok) {
297 HILOGE("Failed to set data property on Error");
298 }
299 return { env, data };
300 } else if (arg->errNo) {
301 return { env, err.GetNapiErr(env) };
302 }
303 return NVal::CreateUndefined(env);
304 };
305
306 NVal thisVar(env, funcArg.GetThisVar());
307 if (funcArg.GetArgc() == NARG_CNT::TWO || (funcArg.GetArgc() == NARG_CNT::THREE &&
308 !NVal(env, funcArg[NARG_POS::THIRD]).TypeIs(napi_function))) {
309 return NAsyncWorkPromise(env, thisVar).Schedule(PROCEDURE_COPYDIR_NAME, cbExec, cbComplCallback).val_;
310 } else {
311 int cbIdex = ((funcArg.GetArgc() == NARG_CNT::THREE) ? NARG_POS::THIRD : NARG_POS::FOURTH);
312 NVal cb(env, funcArg[cbIdex]);
313 return NAsyncWorkCallback(env, thisVar, cb).Schedule(PROCEDURE_COPYDIR_NAME, cbExec, cbComplCallback).val_;
314 }
315 }
316
317 } // ModuleFileIO
318 } // FileManagement
319 } // OHOS