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