1/*
2 * Copyright (c) 2021 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 "directory_ex.h"
17#include <dirent.h>
18#include <cerrno>
19#include <fcntl.h>
20#include <stack>
21#include "securec.h"
22#include "unistd.h"
23#include "utils_log.h"
24using namespace std;
25
26namespace OHOS {
27
28#ifdef UTILS_CXX_RUST
29rust::String RustGetCurrentProcFullFileName()
30{
31    return rust::String(GetCurrentProcFullFileName());
32}
33
34rust::String RustGetCurrentProcPath()
35{
36    return rust::String(GetCurrentProcPath());
37}
38
39rust::String RustExtractFilePath(const rust::String& fileFullName)
40{
41    std::string tmpName = std::string(fileFullName);
42    return rust::String(ExtractFilePath(tmpName));
43}
44
45rust::String RustExtractFileName(const rust::String& fileFullName)
46{
47    std::string tmpName = std::string(fileFullName);
48    return rust::String(ExtractFileName(tmpName));
49}
50
51rust::String RustExtractFileExt(const rust::String& fileName)
52{
53    std::string tmpName = std::string(fileName);
54    return rust::String(ExtractFileExt(tmpName));
55}
56
57rust::String RustExcludeTrailingPathDelimiter(const rust::String& path)
58{
59    std::string tmpPath = std::string(path);
60    return rust::String(ExcludeTrailingPathDelimiter(tmpPath));
61}
62
63rust::String RustIncludeTrailingPathDelimiter(const rust::String& path)
64{
65    std::string tmpPath = std::string(path);
66    return rust::String(IncludeTrailingPathDelimiter(tmpPath));
67}
68
69bool RustPathToRealPath(const rust::String& path, rust::String& realPath)
70{
71    std::string tmpPath = std::string(path);
72    std::string tmpResolved;
73
74    if (PathToRealPath(tmpPath, tmpResolved)) {
75        realPath = tmpResolved;
76        return true;
77    }
78
79    return false;
80}
81
82void RustGetDirFiles(const rust::String& path, rust::vec<rust::String>& files)
83{
84    std::string tmpPath(path);
85    std::vector<std::string> tmpFiles(files.begin(), files.end());
86    GetDirFiles(tmpPath, tmpFiles);
87    std::copy(tmpFiles.begin(), tmpFiles.end(), std::back_inserter(files));
88}
89#endif
90
91string GetCurrentProcFullFileName()
92{
93    char procFile[PATH_MAX + 1] = {0};
94    int ret = readlink("/proc/self/exe", procFile, PATH_MAX);
95    if (ret < 0 || ret > PATH_MAX) {
96        UTILS_LOGD("Get proc name failed, ret is: %{public}d!", ret);
97        return string();
98    }
99    procFile[ret] = '\0';
100    return string(procFile);
101}
102
103string GetCurrentProcPath()
104{
105    return ExtractFilePath(GetCurrentProcFullFileName());
106}
107
108string ExtractFilePath(const string& fileFullName)
109{
110    return string(fileFullName).substr(0, fileFullName.rfind("/") + 1);
111}
112
113std::string ExtractFileName(const std::string& fileFullName)
114{
115    return string(fileFullName).substr(fileFullName.rfind("/") + 1, fileFullName.size());
116}
117
118string ExtractFileExt(const string& fileName)
119{
120    string::size_type pos = fileName.rfind(".");
121    if (pos == string::npos) {
122        return "";
123    }
124
125    return string(fileName).substr(pos + 1, fileName.size());
126}
127
128string ExcludeTrailingPathDelimiter(const std::string& path)
129{
130    if (path.rfind("/") != path.size() - 1) {
131        return path;
132    }
133
134    if (!path.empty()) {
135        return path.substr(0, (int)path.size() - 1);
136    }
137
138    return path;
139}
140
141string IncludeTrailingPathDelimiter(const std::string& path)
142{
143    if (path.empty()) {
144        return  "/";
145    }
146    if (path.rfind("/") != path.size() - 1) {
147        return path + "/";
148    }
149
150    return path;
151}
152
153void GetDirFiles(const string& path, vector<string>& files)
154{
155    DIR *dir = opendir(path.c_str());
156    if (dir == nullptr) {
157        UTILS_LOGD("Failed to open root dir: %{public}s: %{public}s ", path.c_str(), strerror(errno));
158        return;
159    }
160
161    string currentPath = ExcludeTrailingPathDelimiter(path);
162    stack<DIR *> traverseStack;
163    traverseStack.push(dir);
164    while (!traverseStack.empty()) {
165        DIR *topNode = traverseStack.top();
166        dirent *ptr = readdir(topNode);
167        if (ptr == nullptr) {
168            closedir(topNode);
169            traverseStack.pop();
170            auto pos = currentPath.find_last_of("/");
171            if (pos != string::npos) {
172                currentPath.erase(pos);
173            }
174            continue;
175        }
176
177        string name = ptr->d_name;
178        if (name == "." || name == "..") {
179            continue;
180        }
181        if (ptr->d_type == DT_DIR) {
182            int currentFd = dirfd(topNode);
183            if (currentFd < 0) {
184                UTILS_LOGD("Failed to get dirfd, fd: %{public}d: %{public}s ", currentFd, strerror(errno));
185                continue;
186            }
187            int subFd = openat(currentFd, name.c_str(), O_RDONLY | O_DIRECTORY | O_NOFOLLOW | O_CLOEXEC);
188            if (subFd < 0) {
189                UTILS_LOGD("Failed in subFd openat: %{public}s ", name.c_str());
190                continue;
191            }
192            DIR *subDir = fdopendir(subFd);
193            if (subDir == nullptr) {
194                close(subFd);
195                UTILS_LOGD("Failed in fdopendir: %{public}s", strerror(errno));
196                continue;
197            }
198            traverseStack.push(subDir);
199            currentPath = IncludeTrailingPathDelimiter(currentPath) + name;
200        } else {
201            files.push_back(IncludeTrailingPathDelimiter(currentPath) + name);
202        }
203    }
204}
205
206bool ForceCreateDirectory(const string& path)
207{
208    string::size_type index = 0;
209    do {
210        string subPath;
211        index = path.find('/', index + 1);
212        if (index == string::npos) {
213            subPath = path;
214        } else {
215            subPath = path.substr(0, index);
216        }
217
218        if (access(subPath.c_str(), F_OK) != 0) {
219            if (mkdir(subPath.c_str(), (S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH)) != 0 && errno != EEXIST) {
220                return false;
221            }
222        }
223    } while (index != string::npos);
224
225    return access(path.c_str(), F_OK) == 0;
226}
227
228struct DirectoryNode {
229    DIR *dir;
230    int currentFd;
231    char name[256]; // the same max char length with d_name in struct dirent
232};
233
234bool ForceRemoveDirectory(const string& path)
235{
236    bool ret = true;
237    int strRet;
238    DIR *dir = opendir(path.c_str());
239    if (dir == nullptr) {
240        UTILS_LOGE("Failed to open root dir: %{public}s: %{public}s ", path.c_str(), strerror(errno));
241        return false;
242    }
243    stack<DIR *> traversStack;
244    stack<DirectoryNode> removeStack;
245    traversStack.push(dir);
246    while (!traversStack.empty()) {
247        DIR *currentDir = traversStack.top();
248        traversStack.pop();
249        DirectoryNode node;
250        int currentFd = dirfd(currentDir);
251        if (currentFd < 0) {
252            UTILS_LOGE("Failed to get dirfd, fd: %{public}d: %{public}s ", currentFd, strerror(errno));
253            ret = false;
254            continue;
255        }
256
257        while (true) {
258            struct dirent *ptr = readdir(currentDir);
259            if (ptr == nullptr) {
260                break;
261            }
262            const char *name = ptr->d_name;
263            // current dir or parent dir
264            if (strcmp(name, ".") == 0 || strcmp(name, "..") == 0) {
265                continue;
266            }
267
268            if (ptr->d_type == DT_DIR) {
269                int subFd = openat(currentFd, name, O_RDONLY | O_DIRECTORY | O_NOFOLLOW | O_CLOEXEC);
270                if (subFd < 0) {
271                    UTILS_LOGE("Failed in subFd openat: %{public}s ", name);
272                    ret = false;
273                    continue;
274                }
275                DIR *subDir = fdopendir(subFd);
276                if (subDir == nullptr) {
277                    close(subFd);
278                    UTILS_LOGE("Failed in fdopendir: %{public}s", strerror(errno));
279                    ret = false;
280                    continue;
281                }
282                node.dir = subDir;
283                node.currentFd = currentFd;
284                strRet = strcpy_s(node.name, sizeof(node.name), name);
285                if (strRet != EOK) {
286                    UTILS_LOGE("Failed to exec strcpy_s, name= %{public}s, strRet= %{public}d", name, strRet);
287                }
288                removeStack.push(node);
289                traversStack.push(subDir);
290            } else {
291                if (faccessat(currentFd, name, F_OK, AT_SYMLINK_NOFOLLOW) == 0) {
292                    if (unlinkat(currentFd, name, 0) < 0) {
293                        UTILS_LOGE("Couldn't unlinkat subFile %{public}s: %{public}s", name, strerror(errno));
294                        ret = false;
295                        break;
296                    }
297                } else {
298                    UTILS_LOGE("Access to file: %{public}s is failed", name);
299                    ret = false;
300                    break;
301                }
302            }
303        }
304    }
305    if (!ret) {
306        UTILS_LOGD("Failed to remove some subfile under path: %{public}s", path.c_str());
307    }
308    while (!removeStack.empty()) {
309        DirectoryNode node = removeStack.top();
310        removeStack.pop();
311        closedir(node.dir);
312        if (unlinkat(node.currentFd, node.name, AT_REMOVEDIR) < 0) {
313            UTILS_LOGE("Couldn't unlinkat subDir %{public}s: %{public}s", node.name, strerror(errno));
314            continue;
315        }
316    }
317    closedir(dir);
318    if (faccessat(AT_FDCWD, path.c_str(), F_OK, AT_SYMLINK_NOFOLLOW) == 0) {
319        if (remove(path.c_str()) != 0) {
320            UTILS_LOGE("Failed to remove root dir: %{public}s: %{public}s ", path.c_str(), strerror(errno));
321            return false;
322        }
323    }
324    return faccessat(AT_FDCWD, path.c_str(), F_OK, AT_SYMLINK_NOFOLLOW) != 0;
325}
326
327bool RemoveFile(const string& fileName)
328{
329    if (access(fileName.c_str(), F_OK) == 0) {
330        return remove(fileName.c_str()) == 0;
331    }
332
333    return true;
334}
335
336bool IsEmptyFolder(const string& path)
337{
338    vector<string> files;
339    GetDirFiles(path, files);
340    return files.empty();
341}
342
343uint64_t GetFolderSize(const string& path)
344{
345    vector<string> files;
346    struct stat statbuf = {0};
347    GetDirFiles(path, files);
348    uint64_t totalSize = 0;
349    for (auto& file : files) {
350        if (stat(file.c_str(), &statbuf) == 0) {
351            totalSize += statbuf.st_size;
352        }
353    }
354
355    return totalSize;
356}
357
358// inner function, and param is legitimate
359bool ChangeMode(const string& fileName, const mode_t& mode)
360{
361    return (chmod(fileName.c_str(), mode) == 0);
362}
363
364bool ChangeModeFile(const string& fileName, const mode_t& mode)
365{
366    if (access(fileName.c_str(), F_OK) != 0) {
367        return false;
368    }
369
370    return ChangeMode(fileName, mode);
371}
372
373bool ChangeModeDirectory(const string& path, const mode_t& mode)
374{
375    string subPath;
376    bool ret = true;
377    DIR *dir = opendir(path.c_str());
378    if (dir == nullptr) {
379        return false;
380    }
381
382    while (true) {
383        struct dirent *ptr = readdir(dir);
384        if (ptr == nullptr) {
385            break;
386        }
387
388        // current dir or parent dir
389        if (strcmp(ptr->d_name, ".") == 0 || strcmp(ptr->d_name, "..") == 0) {
390            continue;
391        }
392        subPath = IncludeTrailingPathDelimiter(path) + string(ptr->d_name);
393        if (ptr->d_type == DT_DIR) {
394            ret = ChangeModeDirectory(subPath, mode);
395        } else {
396            if (access(subPath.c_str(), F_OK) == 0) {
397                if (!ChangeMode(subPath, mode)) {
398                    UTILS_LOGD("Failed to exec ChangeMode");
399                    closedir(dir);
400                    return false;
401                }
402            }
403        }
404    }
405    closedir(dir);
406    string currentPath = ExcludeTrailingPathDelimiter(path);
407    if (access(currentPath.c_str(), F_OK) == 0) {
408        if (!ChangeMode(currentPath, mode)) {
409            UTILS_LOGD("Failed to exec ChangeMode");
410            return false;
411        }
412    }
413    return ret;
414}
415
416bool PathToRealPath(const string& path, string& realPath)
417{
418    if (path.empty()) {
419        UTILS_LOGD("path is empty!");
420        return false;
421    }
422
423    if ((path.length() >= PATH_MAX)) {
424        UTILS_LOGD("path len is error, the len is: [%{public}zu]", path.length());
425        return false;
426    }
427
428    char tmpPath[PATH_MAX] = {0};
429    if (realpath(path.c_str(), tmpPath) == nullptr) {
430        UTILS_LOGE("path (%{public}s) to realpath error: %{public}s", path.c_str(), strerror(errno));
431        return false;
432    }
433
434    realPath = tmpPath;
435    if (access(realPath.c_str(), F_OK) != 0) {
436        UTILS_LOGE("check realpath (%{private}s) error: %{public}s", realPath.c_str(), strerror(errno));
437        return false;
438    }
439    return true;
440}
441
442#if defined(IOS_PLATFORM) || defined(_WIN32)
443string TransformFileName(const string& fileName)
444{
445    string::size_type pos = fileName.find(".");
446    string transformfileName = "";
447    if (pos == string::npos) {
448        transformfileName = fileName;
449
450#ifdef _WIN32
451        transformfileName = transformfileName.append(".dll");
452#elif defined IOS_PLATFORM
453        transformfileName = transformfileName.append(".dylib");
454#endif
455
456        return transformfileName;
457    } else {
458        transformfileName = string(fileName).substr(0, pos + 1);
459
460#ifdef _WIN32
461        transformfileName = transformfileName.append("dll");
462#elif defined IOS_PLATFORM
463        transformfileName = transformfileName.append("dylib");
464#endif
465
466        return transformfileName;
467    }
468}
469#endif
470
471} // OHOS
472