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