1/*
2 * Copyright (c) 2024 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 "move_file.h"
17#include "macro.h"
18#include "stat_impl.h"
19
20#include <cstring>
21#include <fcntl.h>
22
23#ifdef __MUSL__
24#include <filesystem>
25#else
26#include <sys/stat.h>
27#endif
28
29#include "uv.h"
30#include <tuple>
31#include <unistd.h>
32
33namespace OHOS {
34namespace CJSystemapi {
35using namespace std;
36using namespace OHOS::FileManagement::LibN;
37
38#ifdef __MUSL__
39static bool CheckDir(const string &path)
40{
41    if (!filesystem::is_directory(filesystem::status(path))) {
42        return false;
43    }
44    return true;
45}
46#else
47static bool CheckDir(const string &path)
48{
49    struct stat fileInformation;
50    if (stat(path.c_str(), &fileInformation) == 0) {
51        if (fileInformation.st_mode & S_IFDIR) {
52            return true;
53        }
54    } else {
55        LOGE("Failed to stat file");
56    }
57    return false;
58}
59#endif
60
61static int ChangeTime(const string &path, uv_fs_t *statReq)
62{
63    std::unique_ptr<uv_fs_t, decltype(CommonFunc::FsReqCleanup)*> utime_req = {
64        new (std::nothrow) uv_fs_t, CommonFunc::FsReqCleanup };
65    if (!utime_req) {
66        LOGE("Failed to request heap memory.");
67        return ENOMEM;
68    }
69    double atime = static_cast<double>(statReq->statbuf.st_atim.tv_sec) +
70        static_cast<double>(statReq->statbuf.st_atim.tv_nsec) / NS;
71    double mtime = static_cast<double>(statReq->statbuf.st_mtim.tv_sec) +
72        static_cast<double>(statReq->statbuf.st_mtim.tv_nsec) / NS;
73    int ret = uv_fs_utime(nullptr, utime_req.get(), path.c_str(), atime, mtime, nullptr);
74    if (ret < 0) {
75        LOGE("Failed to utime dstPath");
76    }
77    return ret;
78}
79static int CopyAndDeleteFile(const string &src, const string &dest)
80{
81    std::unique_ptr<uv_fs_t, decltype(CommonFunc::FsReqCleanup)*> stat_req = {
82        new (std::nothrow) uv_fs_t, CommonFunc::FsReqCleanup };
83    if (!stat_req) {
84        LOGE("Failed to request heap memory.");
85        return ENOMEM;
86    }
87    int ret = uv_fs_stat(nullptr, stat_req.get(), src.c_str(), nullptr);
88    if (ret < 0) {
89        LOGE("Failed to stat srcPath");
90        return ret;
91    }
92#if !defined(WIN_PLATFORM) && !defined(IOS_PLATFORM)
93    filesystem::path dstPath(dest);
94    std::error_code errCode;
95    if (filesystem::exists(dstPath)) {
96        if (!filesystem::remove(dstPath, errCode)) {
97            LOGE("Failed to remove dest file, error code: %{public}d", errCode.value());
98            return errCode.value();
99        }
100    }
101    filesystem::path srcPath(src);
102    if (!filesystem::copy_file(srcPath, dstPath, filesystem::copy_options::overwrite_existing, errCode)) {
103        LOGE("Failed to copy file, error code: %{public}d", errCode.value());
104        return errCode.value();
105    }
106#else
107    uv_fs_t copyfile_req;
108    ret = uv_fs_copyfile(nullptr, &copyfile_req, src.c_str(), dest.c_str(), MODE_FORCE_MOVE, nullptr);
109    uv_fs_req_cleanup(&copyfile_req);
110    if (ret < 0) {
111        LOGE("Failed to move file using copyfile interface.");
112        return ret;
113    }
114#endif
115    uv_fs_t unlinkReq;
116    ret = uv_fs_unlink(nullptr, &unlinkReq, src.c_str(), nullptr);
117    if (ret < 0) {
118        LOGE("Failed to unlink src file");
119        int result = uv_fs_unlink(nullptr, &unlinkReq, dest.c_str(), nullptr);
120        if (result < 0) {
121            LOGE("Failed to unlink dest file");
122            return result;
123        }
124        uv_fs_req_cleanup(&unlinkReq);
125        return ret;
126    }
127    uv_fs_req_cleanup(&unlinkReq);
128    return ChangeTime(dest, stat_req.get());
129}
130
131static int RenameFile(const string &src, const string &dest)
132{
133    int ret = 0;
134    uv_fs_t renameReq;
135    ret = uv_fs_rename(nullptr, &renameReq, src.c_str(), dest.c_str(), nullptr);
136    if (ret < 0 && (string_view(uv_err_name(ret)) == "EXDEV")) {
137        return CopyAndDeleteFile(src, dest);
138    }
139    if (ret < 0) {
140        LOGE("Failed to move file using rename syscall.");
141        return ret;
142    }
143    return OHOS::FileManagement::LibN::ERRNO_NOERR;
144}
145
146int MoveFileImpl::MoveFile(const std::string& src, const std::string& dest, int mode)
147{
148    LOGI("FS_TEST:: MoveFileImpl::MoveFile start");
149    if (CheckDir(src)) {
150        LOGE("Invalid src");
151        return EINVAL;
152    }
153    if (CheckDir(dest)) {
154        LOGE("Invalid dest");
155        return EINVAL;
156    }
157    uv_fs_t accessReq;
158    int ret = uv_fs_access(nullptr, &accessReq, src.c_str(), W_OK, nullptr);
159    if (ret < 0) {
160        LOGE("Failed to move src file due to doesn't exist or hasn't write permission");
161        uv_fs_req_cleanup(&accessReq);
162        return ret;
163    }
164    if (mode == MODE_THROW_ERR) {
165        ret = uv_fs_access(nullptr, &accessReq, dest.c_str(), 0, nullptr);
166        uv_fs_req_cleanup(&accessReq);
167        if (ret == 0) {
168            LOGE("Failed to move file due to existing destPath with MODE_THROW_ERR.");
169            return EEXIST;
170        }
171        if (ret < 0 && (string_view(uv_err_name(ret)) != "ENOENT")) {
172            LOGE("Failed to access destPath with MODE_THROW_ERR.");
173            return ret;
174        }
175    } else {
176        uv_fs_req_cleanup(&accessReq);
177    }
178    return RenameFile(src, dest);
179}
180
181}
182}