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