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