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