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 "file_fs_impl.h"
17#include "n_error.h"
18#include "securec.h"
19#include "copy_dir.h"
20
21using namespace OHOS;
22using namespace OHOS::FFI;
23using namespace OHOS::FileManagement;
24using namespace OHOS::CJSystemapi;
25using namespace OHOS::FileManagement::LibN;
26
27namespace {
28
29static int RecurCopyDir(const std::string &srcPath, const std::string &destPath, const int mode,
30    std::vector<struct ConflictFiles> &errfiles);
31
32static int MakeDir(const std::string &path)
33{
34    std::filesystem::path destDir(path);
35    std::error_code errCode;
36    if (!std::filesystem::create_directory(destDir, errCode)) {
37        LOGE("Failed to create directory, error code: %{public}d", errCode.value());
38        return errCode.value();
39    }
40    return OHOS::FileManagement::LibN::ERRNO_NOERR;
41}
42
43struct NameList {
44    struct dirent** namelist = { nullptr };
45    int direntNum = 0;
46};
47
48static int RemoveFile(const std::string &destPath)
49{
50    std::filesystem::path destFile(destPath);
51    std::error_code errCode;
52    if (!std::filesystem::remove(destFile, errCode)) {
53        LOGE("Failed to remove file with path, error code: %{public}d", errCode.value());
54        return errCode.value();
55    }
56    return OHOS::FileManagement::LibN::ERRNO_NOERR;
57}
58
59static void Deleter(struct NameList *arg)
60{
61    for (int i = 0; i < arg->direntNum; i++) {
62        free((arg->namelist)[i]);
63        (arg->namelist)[i] = nullptr;
64    }
65    free(arg->namelist);
66    arg->namelist = nullptr;
67    delete arg;
68    arg = nullptr;
69}
70
71static int CopyFile(const std::string &src, const std::string &dest, const int mode)
72{
73    std::filesystem::path dstPath(dest);
74    if (std::filesystem::exists(dstPath)) {
75        int ret = (mode == DIRMODE_FILE_COPY_THROW_ERR) ? EEXIST : RemoveFile(dest);
76        if (ret) {
77            LOGE("Failed to copy file due to existing destPath with throw err");
78            return ret;
79        }
80    }
81    std::filesystem::path srcPath(src);
82    std::error_code errCode;
83    if (!std::filesystem::copy_file(srcPath, dstPath, std::filesystem::copy_options::overwrite_existing, errCode)) {
84        LOGE("Failed to copy file, error code: %{public}d", errCode.value());
85        return errCode.value();
86    }
87    return OHOS::FileManagement::LibN::ERRNO_NOERR;
88}
89
90static int CopySubDir(const std::string &srcPath, const std::string &destPath, const int mode,
91    std::vector<struct ConflictFiles> &errfiles)
92{
93    if (!std::filesystem::exists(destPath)) {
94        int res = MakeDir(destPath);
95        if (res != OHOS::FileManagement::LibN::ERRNO_NOERR) {
96            LOGE("Failed to mkdir");
97            return res;
98        }
99    }
100    return RecurCopyDir(srcPath, destPath, mode, errfiles);
101}
102
103static int FilterFunc(const struct dirent *filename)
104{
105    if (std::string_view(filename->d_name) == "." || std::string_view(filename->d_name) == "..") {
106        return DISMATCH;
107    }
108    return MATCH;
109}
110
111static int RecurCopyDir(const std::string &srcPath, const std::string &destPath, const int mode,
112    std::vector<struct ConflictFiles> &errfiles)
113{
114    std::unique_ptr<struct NameList, decltype(Deleter)*> pNameList = {new (std::nothrow) struct NameList, Deleter};
115    if (pNameList == nullptr) {
116        LOGE("Failed to request heap memory.");
117        return ENOMEM;
118    }
119    int num = scandir(srcPath.c_str(), &(pNameList->namelist), FilterFunc, alphasort);
120    pNameList->direntNum = num;
121
122    for (int i = 0; i < num; i++) {
123        if ((pNameList->namelist[i])->d_type == DT_DIR) {
124            std::string srcTemp = srcPath + '/' + std::string((pNameList->namelist[i])->d_name);
125            std::string destTemp = destPath + '/' + std::string((pNameList->namelist[i])->d_name);
126            LOGI("srcTemp %{public}s from", srcTemp.c_str());
127            LOGI("destTemp %{public}s to", destTemp.c_str());
128            int res = CopySubDir(srcTemp, destTemp, mode, errfiles);
129            if (res == OHOS::FileManagement::LibN::ERRNO_NOERR) {
130                continue;
131            }
132            return res;
133        } else {
134            LOGI("srcPath %{public}s from", srcPath.c_str());
135            LOGI("destPath %{public}s to", destPath.c_str());
136            std::string src = srcPath + '/' + std::string((pNameList->namelist[i])->d_name);
137            std::string dest = destPath + '/' + std::string((pNameList->namelist[i])->d_name);
138            LOGI("CopyFile %{public}s from", src.c_str());
139            LOGI("CopyFile %{public}s to", dest.c_str());
140            int res = CopyFile(src, dest, mode);
141            if (res == EEXIST) {
142                errfiles.emplace_back(src, dest);
143                continue;
144            } else if (res == OHOS::FileManagement::LibN::ERRNO_NOERR) {
145                continue;
146            } else {
147                LOGE("Failed to copy file for error %{public}d", res);
148                return res;
149            }
150        }
151    }
152    return OHOS::FileManagement::LibN::ERRNO_NOERR;
153}
154
155static bool AllowToCopy(const std::string& src, const std::string& dest)
156{
157    if (dest.find(src) == 0 || std::filesystem::path(src).parent_path() == dest) {
158        return false;
159    }
160    return true;
161}
162
163static int CopyDirFunc(const std::string &src, const std::string &dest, const int mode,
164    std::vector<struct ConflictFiles> &errfiles)
165{
166    size_t found = std::string(src).rfind('/');
167    if (found == std::string::npos) {
168        LOGE("CopyDirFunc EINVAL");
169        return EINVAL;
170    }
171    std::string dirName = std::string(src).substr(found);
172    std::string destStr = dest + dirName;
173    LOGI("destStr: %{public}s", destStr.c_str());
174    if (!std::filesystem::exists(destStr)) {
175        int res = MakeDir(destStr);
176        if (res != OHOS::FileManagement::LibN::ERRNO_NOERR) {
177            LOGE("Failed to mkdir");
178            return res;
179        }
180    }
181    int res = RecurCopyDir(src, destStr, mode, errfiles);
182    if (!errfiles.empty() && res == OHOS::FileManagement::LibN::ERRNO_NOERR) {
183        LOGE("CopyDirFunc EEXIST");
184        return EEXIST;
185    }
186    return res;
187}
188
189static CConflictFiles* VectorToCConflict(std::vector<struct ConflictFiles> &errfiles)
190{
191    CConflictFiles* result = new(std::nothrow) CConflictFiles[errfiles.size()];
192    if (result == nullptr) {
193        return nullptr;
194    }
195    size_t temp = 0;
196    for (size_t i = 0; i < errfiles.size(); i++) {
197        size_t srcFilesLen = errfiles[i].srcFiles.length() + 1;
198        result[i].srcFiles = new(std::nothrow) char[srcFilesLen];
199        if (result[i].srcFiles == nullptr) {
200            break;
201        }
202        if (strcpy_s(result[i].srcFiles, srcFilesLen, errfiles[i].srcFiles.c_str()) != 0) {
203            delete[] result[i].srcFiles;
204            result[i].srcFiles = nullptr;
205            break;
206        }
207        size_t destFilesLen = errfiles[i].destFiles.length() + 1;
208        result[i].destFiles = new(std::nothrow) char[destFilesLen];
209        if (result[i].destFiles == nullptr) {
210            delete[] result[i].srcFiles;
211            result[i].srcFiles = nullptr;
212            break;
213        }
214        if (strcpy_s(result[i].destFiles, destFilesLen, errfiles[i].destFiles.c_str()) != 0) {
215            delete[] result[i].srcFiles;
216            delete[] result[i].destFiles;
217
218            result[i].srcFiles = nullptr;
219            result[i].destFiles = nullptr;
220            break;
221        }
222        temp++;
223    }
224    if (temp != errfiles.size()) {
225        for (size_t j = temp; j > 0; j--) {
226            delete[] result[j - 1].srcFiles;
227            delete[] result[j - 1].destFiles;
228
229            result[j - 1].srcFiles = nullptr;
230            result[j - 1].destFiles = nullptr;
231        }
232        delete[] result;
233        return nullptr;
234    }
235    return result;
236}
237
238}
239
240namespace OHOS {
241namespace CJSystemapi {
242
243RetDataCArrConflictFiles CopyDirImpl::CopyDir(const std::string& src, const std::string& dest, int mode)
244{
245    LOGI("FS_TEST:: FileFsImpl::CopyDir start");
246    RetDataCArrConflictFiles ret = { .code = EINVAL, .data = { .head = nullptr, .size = 0 } };
247    if (!std::filesystem::is_directory(std::filesystem::status(dest))) {
248        LOGE("Invalid dest");
249        return ret;
250    }
251    if (!AllowToCopy(src, dest)) {
252        LOGE("Failed to copy file");
253        return ret;
254    }
255    if (mode < COPYMODE_MIN || mode > COPYMODE_MAX) {
256        LOGE("Invalid mode");
257        return ret;
258    }
259    LOGI("FS_TEST:: FileFsImpl::Copy parameter check passed");
260    std::vector<struct ConflictFiles> errfiles = {};
261    ret.code = CopyDirFunc(src, dest, mode, errfiles);
262    ret.data.size = (int64_t)errfiles.size();
263    ret.data.head = VectorToCConflict(errfiles);
264    return ret;
265}
266
267}
268} // namespace OHOS::CJSystemapi