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 "movedir.h"
17
18 #include <dirent.h>
19 #include <filesystem>
20 #include <memory>
21 #include <string_view>
22 #include <tuple>
23 #include <unistd.h>
24 #include <deque>
25
26 #include "common_func.h"
27 #include "file_utils.h"
28 #include "filemgmt_libhilog.h"
29
30 namespace OHOS {
31 namespace FileManagement {
32 namespace ModuleFileIO {
33 using namespace std;
34 using namespace OHOS::FileManagement::LibN;
35
36 static int RecurMoveDir(const string &srcPath, const string &destPath, const int mode,
37 deque<struct ErrFiles> &errfiles);
38
JudgeExistAndEmpty(const string & path)39 static tuple<bool, bool> JudgeExistAndEmpty(const string &path)
40 {
41 std::error_code errCode;
42 filesystem::path pathName(path);
43 if (filesystem::exists(pathName, errCode)) {
44 if (filesystem::is_empty(pathName, errCode)) {
45 return { true, true };
46 }
47 return { true, false };
48 }
49 return { false, false };
50 }
51
RmDirectory(const string & path)52 static int RmDirectory(const string &path)
53 {
54 filesystem::path pathName(path);
55 std::error_code errCode;
56 if (filesystem::exists(pathName, errCode)) {
57 std::error_code errCode;
58 (void)filesystem::remove_all(pathName, errCode);
59 if (errCode.value() != 0) {
60 HILOGE("Failed to remove directory, error code: %{public}d", errCode.value());
61 return errCode.value();
62 }
63 } else if (errCode.value() != ERRNO_NOERR) {
64 HILOGE("fs exists fail, errcode is %{public}d", errCode.value());
65 return errCode.value();
66 }
67 return ERRNO_NOERR;
68 }
69
RemovePath(const string & pathStr)70 static int RemovePath(const string& pathStr)
71 {
72 filesystem::path pathTarget(pathStr);
73 std::error_code errCode;
74 if (!filesystem::remove(pathTarget, errCode)) {
75 HILOGE("Failed to remove file or directory, error code: %{public}d", errCode.value());
76 return errCode.value();
77 }
78 return ERRNO_NOERR;
79 }
80
ParseJsOperand(napi_env env,const NFuncArg & funcArg)81 static tuple<bool, unique_ptr<char[]>, unique_ptr<char[]>, int> ParseJsOperand(napi_env env, const NFuncArg& funcArg)
82 {
83 auto [resGetFirstArg, src, ignore] = NVal(env, funcArg[NARG_POS::FIRST]).ToUTF8StringPath();
84 if (!resGetFirstArg || !filesystem::is_directory(filesystem::status(src.get()))) {
85 HILOGE("Invalid src");
86 return { false, nullptr, nullptr, 0 };
87 }
88 auto [resGetSecondArg, dest, unused] = NVal(env, funcArg[NARG_POS::SECOND]).ToUTF8StringPath();
89 if (!resGetSecondArg || !filesystem::is_directory(filesystem::status(dest.get()))) {
90 HILOGE("Invalid dest");
91 return { false, nullptr, nullptr, 0 };
92 }
93 int mode = 0;
94 if (funcArg.GetArgc() >= NARG_CNT::THREE) {
95 bool resGetThirdArg = false;
96 tie(resGetThirdArg, mode) = NVal(env, funcArg[NARG_POS::THIRD]).ToInt32(mode);
97 if (!resGetThirdArg || (mode < DIRMODE_MIN || mode > DIRMODE_MAX)) {
98 HILOGE("Invalid mode");
99 return { false, nullptr, nullptr, 0 };
100 }
101 }
102 return { true, move(src), move(dest), mode };
103 }
104
CopyAndDeleteFile(const string & src,const string & dest)105 static int CopyAndDeleteFile(const string &src, const string &dest)
106 {
107 filesystem::path dstPath(dest);
108 std::error_code errCode;
109 if (filesystem::exists(dstPath, errCode)) {
110 int removeRes = RemovePath(dest);
111 if (removeRes != 0) {
112 HILOGE("Failed to remove dest file");
113 return removeRes;
114 }
115 }
116 filesystem::path srcPath(src);
117 if (!filesystem::copy_file(srcPath, dstPath, filesystem::copy_options::overwrite_existing, errCode)) {
118 HILOGE("Failed to copy file, error code: %{public}d", errCode.value());
119 return errCode.value();
120 }
121 return RemovePath(src);
122 }
123
RenameFile(const string & src,const string & dest,const int mode,deque<struct ErrFiles> & errfiles)124 static int RenameFile(const string &src, const string &dest, const int mode, deque<struct ErrFiles> &errfiles)
125 {
126 filesystem::path dstPath(dest);
127 std::error_code errCode;
128 if (filesystem::exists(dstPath, errCode)) {
129 if (filesystem::is_directory(dstPath, errCode)) {
130 errfiles.emplace_front(src, dest);
131 return ERRNO_NOERR;
132 }
133 if (mode == DIRMODE_FILE_THROW_ERR) {
134 errfiles.emplace_back(src, dest);
135 return ERRNO_NOERR;
136 }
137 }
138 if (errCode.value() != ERRNO_NOERR) {
139 HILOGE("fs exists or is_directory fail, errcode is %{public}d", errCode.value());
140 }
141 filesystem::path srcPath(src);
142 filesystem::rename(srcPath, dstPath, errCode);
143 if (errCode.value() == EXDEV) {
144 HILOGD("Failed to rename file due to EXDEV");
145 return CopyAndDeleteFile(src, dest);
146 }
147 return errCode.value();
148 }
149
FilterFunc(const struct dirent * filename)150 static int32_t FilterFunc(const struct dirent *filename)
151 {
152 if (string_view(filename->d_name) == "." || string_view(filename->d_name) == "..") {
153 return FILE_DISMATCH;
154 }
155 return FILE_MATCH;
156 }
157
RenameDir(const string & src,const string & dest,const int mode,deque<struct ErrFiles> & errfiles)158 static int RenameDir(const string &src, const string &dest, const int mode, deque<struct ErrFiles> &errfiles)
159 {
160 filesystem::path destPath(dest);
161 std::error_code errCode;
162 if (filesystem::exists(destPath, errCode)) {
163 return RecurMoveDir(src, dest, mode, errfiles);
164 } else if (errCode.value() != ERRNO_NOERR) {
165 HILOGE("fs exists fail, errcode is %{public}d", errCode.value());
166 return errCode.value();
167 }
168 filesystem::path srcPath(src);
169 filesystem::rename(srcPath, destPath, errCode);
170 if (errCode.value() == EXDEV) {
171 HILOGD("Failed to rename file due to EXDEV");
172 if (!filesystem::create_directory(destPath, errCode)) {
173 HILOGE("Failed to create directory, error code: %{public}d", errCode.value());
174 return errCode.value();
175 }
176 return RecurMoveDir(src, dest, mode, errfiles);
177 }
178 if (errCode.value() != 0) {
179 HILOGE("Failed to rename file, error code: %{public}d", errCode.value());
180 return errCode.value();
181 }
182 return ERRNO_NOERR;
183 }
184
185 struct NameListArg {
186 struct dirent** namelist;
187 int num;
188 };
189
Deleter(struct NameListArg * arg)190 static void Deleter(struct NameListArg *arg)
191 {
192 for (int i = 0; i < arg->num; i++) {
193 free((arg->namelist)[i]);
194 (arg->namelist)[i] = nullptr;
195 }
196 free(arg->namelist);
197 arg->namelist = nullptr;
198 delete arg;
199 arg = nullptr;
200 }
201
RecurMoveDir(const string & srcPath,const string & destPath,const int mode,deque<struct ErrFiles> & errfiles)202 static int RecurMoveDir(const string &srcPath, const string &destPath, const int mode,
203 deque<struct ErrFiles> &errfiles)
204 {
205 filesystem::path dpath(destPath);
206 if (!filesystem::is_directory(dpath)) {
207 errfiles.emplace_front(srcPath, destPath);
208 return ERRNO_NOERR;
209 }
210
211 unique_ptr<struct NameListArg, decltype(Deleter)*> ptr = {new struct NameListArg, Deleter};
212 if (!ptr) {
213 HILOGE("Failed to request heap memory.");
214 return ENOMEM;
215 }
216 int num = scandir(srcPath.c_str(), &(ptr->namelist), FilterFunc, alphasort);
217 ptr->num = num;
218
219 for (int i = 0; i < num; i++) {
220 if ((ptr->namelist[i])->d_type == DT_DIR) {
221 string srcTemp = srcPath + '/' + string((ptr->namelist[i])->d_name);
222 string destTemp = destPath + '/' + string((ptr->namelist[i])->d_name);
223 size_t size = errfiles.size();
224 int res = RenameDir(srcTemp, destTemp, mode, errfiles);
225 if (res != ERRNO_NOERR) {
226 return res;
227 }
228 if (size != errfiles.size()) {
229 continue;
230 }
231 res = RemovePath(srcTemp);
232 if (res) {
233 return res;
234 }
235 } else {
236 string src = srcPath + '/' + string((ptr->namelist[i])->d_name);
237 string dest = destPath + '/' + string((ptr->namelist[i])->d_name);
238 int res = RenameFile(src, dest, mode, errfiles);
239 if (res != ERRNO_NOERR) {
240 HILOGE("Failed to rename file for error %{public}d", res);
241 return res;
242 }
243 }
244 }
245 return ERRNO_NOERR;
246 }
247
MoveDirFunc(const string & src,const string & dest,const int mode,deque<struct ErrFiles> & errfiles)248 static int MoveDirFunc(const string &src, const string &dest, const int mode, deque<struct ErrFiles> &errfiles)
249 {
250 size_t found = string(src).rfind('/');
251 if (found == std::string::npos) {
252 return EINVAL;
253 }
254 if (access(src.c_str(), W_OK) != 0) {
255 HILOGE("Failed to move src directory due to doesn't exist or hasn't write permission");
256 return errno;
257 }
258 string dirName = string(src).substr(found);
259 string destStr = dest + dirName;
260 auto [destStrExist, destStrEmpty] = JudgeExistAndEmpty(destStr);
261 if (destStrExist && !destStrEmpty) {
262 if (mode == DIRMODE_DIRECTORY_REPLACE) {
263 int removeRes = RmDirectory(destStr);
264 if (removeRes) {
265 HILOGE("Failed to remove dest directory in DIRMODE_DIRECTORY_REPLACE");
266 return removeRes;
267 }
268 }
269 if (mode == DIRMODE_DIRECTORY_THROW_ERR) {
270 HILOGE("Failed to move directory in DIRMODE_DIRECTORY_THROW_ERR");
271 return ENOTEMPTY;
272 }
273 }
274 int res = RenameDir(src, destStr, mode, errfiles);
275 if (res == ERRNO_NOERR) {
276 if (!errfiles.empty()) {
277 HILOGE("Failed to movedir with some conflicted files");
278 return EEXIST;
279 }
280 int removeRes = RmDirectory(src);
281 if (removeRes) {
282 HILOGE("Failed to remove src directory");
283 return removeRes;
284 }
285 }
286 return res;
287 }
288
GetErrData(napi_env env,deque<struct ErrFiles> & errfiles)289 static napi_value GetErrData(napi_env env, deque<struct ErrFiles> &errfiles)
290 {
291 napi_value res = nullptr;
292 napi_status status = napi_create_array(env, &res);
293 if (status != napi_ok) {
294 HILOGE("Failed to create array");
295 return nullptr;
296 }
297 size_t index = 0;
298 for (auto &iter : errfiles) {
299 NVal obj = NVal::CreateObject(env);
300 obj.AddProp("srcFile", NVal::CreateUTF8String(env, iter.srcFiles).val_);
301 obj.AddProp("destFile", NVal::CreateUTF8String(env, iter.destFiles).val_);
302 status = napi_set_element(env, res, index++, obj.val_);
303 if (status != napi_ok) {
304 HILOGE("Failed to set element on data");
305 return nullptr;
306 }
307 }
308 return res;
309 }
310
Sync(napi_env env,napi_callback_info info)311 napi_value MoveDir::Sync(napi_env env, napi_callback_info info)
312 {
313 NFuncArg funcArg(env, info);
314 if (!funcArg.InitArgs(NARG_CNT::TWO, NARG_CNT::THREE)) {
315 HILOGE("Number of arguments unmatched");
316 NError(EINVAL).ThrowErr(env);
317 return nullptr;
318 }
319 auto [succ, src, dest, mode] = ParseJsOperand(env, funcArg);
320 if (!succ) {
321 NError(EINVAL).ThrowErr(env);
322 return nullptr;
323 }
324
325 deque<struct ErrFiles> errfiles = {};
326 int ret = MoveDirFunc(src.get(), dest.get(), mode, errfiles);
327 if (ret == EEXIST) {
328 NError(ret).ThrowErrAddData(env, EEXIST, GetErrData(env, errfiles));
329 return nullptr;
330 } else if (ret) {
331 NError(ret).ThrowErr(env);
332 return nullptr;
333 }
334 return NVal::CreateUndefined(env).val_;
335 }
336
337 struct MoveDirArgs {
338 deque<ErrFiles> errfiles;
339 int errNo = 0;
340 ~MoveDirArgs() = default;
341 };
342
Async(napi_env env,napi_callback_info info)343 napi_value MoveDir::Async(napi_env env, napi_callback_info info)
344 {
345 NFuncArg funcArg(env, info);
346 if (!funcArg.InitArgs(NARG_CNT::TWO, NARG_CNT::FOUR)) {
347 HILOGE("Number of arguments unmatched");
348 NError(EINVAL).ThrowErr(env);
349 return nullptr;
350 }
351 auto [succ, src, dest, mode] = ParseJsOperand(env, funcArg);
352 if (!succ) {
353 NError(EINVAL).ThrowErr(env);
354 return nullptr;
355 }
356 auto arg = CreateSharedPtr<MoveDirArgs>();
357 if (arg == nullptr) {
358 HILOGE("Failed to request heap memory.");
359 NError(ENOMEM).ThrowErr(env);
360 return nullptr;
361 }
362 auto cbExec = [srcPath = string(src.get()), destPath = string(dest.get()), mode = mode, arg]() -> NError {
363 arg->errNo = MoveDirFunc(srcPath, destPath, mode, arg->errfiles);
364 if (arg->errNo) {
365 return NError(arg->errNo);
366 }
367 return NError(ERRNO_NOERR);
368 };
369
370 auto cbComplCallback = [arg, mode = mode](napi_env env, NError err) -> NVal {
371 if (arg->errNo == EEXIST) {
372 napi_value data = err.GetNapiErr(env);
373 napi_status status = napi_set_named_property(env, data, FILEIO_TAG_ERR_DATA.c_str(),
374 GetErrData(env, arg->errfiles));
375 if (status != napi_ok) {
376 HILOGE("Failed to set data property on Error");
377 }
378 return { env, data };
379 } else if (arg->errNo) {
380 return { env, err.GetNapiErr(env) };
381 }
382 return NVal::CreateUndefined(env);
383 };
384
385 NVal thisVar(env, funcArg.GetThisVar());
386 if (funcArg.GetArgc() == NARG_CNT::TWO || (funcArg.GetArgc() == NARG_CNT::THREE &&
387 !NVal(env, funcArg[NARG_POS::THIRD]).TypeIs(napi_function))) {
388 return NAsyncWorkPromise(env, thisVar).Schedule(PROCEDURE_MOVEDIR_NAME, cbExec, cbComplCallback).val_;
389 } else {
390 int cbIdex = ((funcArg.GetArgc() == NARG_CNT::THREE) ? NARG_POS::THIRD : NARG_POS::FOURTH);
391 NVal cb(env, funcArg[cbIdex]);
392 return NAsyncWorkCallback(env, thisVar, cb).Schedule(PROCEDURE_MOVEDIR_NAME, cbExec, cbComplCallback).val_;
393 }
394 }
395
396 } // ModuleFileIO
397 } // FileManagement
398 } // OHOS