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