1 /*
2 * Copyright (c) 2022-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 "b_filesystem/b_dir.h"
17
18 #include <algorithm>
19 #include <dirent.h>
20 #include <fnmatch.h>
21 #include <functional>
22 #include <glob.h>
23 #include <memory>
24 #include <set>
25 #include <string>
26 #include <tuple>
27 #include <vector>
28
29 #include "b_error/b_error.h"
30 #include "b_resources/b_constants.h"
31 #include "directory_ex.h"
32 #include "errors.h"
33 #include "filemgmt_libhilog.h"
34
35 namespace OHOS::FileManagement::Backup {
36 using namespace std;
37
IsEmptyDirectory(const string & path)38 static bool IsEmptyDirectory(const string &path)
39 {
40 DIR *dir = opendir(path.c_str());
41 if (dir == nullptr) {
42 return false;
43 }
44 bool isEmpty = true;
45 struct dirent *entry;
46 while ((entry = readdir(dir)) != nullptr) {
47 if (entry->d_type != DT_DIR || (strcmp(entry->d_name, ".") != 0 && strcmp(entry->d_name, "..") != 0)) {
48 isEmpty = false;
49 break;
50 }
51 }
52 closedir(dir);
53 return isEmpty;
54 }
55
GetFile(const string & path,off_t size=-1)56 static tuple<ErrCode, map<string, struct stat>, vector<string>> GetFile(const string &path, off_t size = -1)
57 {
58 map<string, struct stat> files;
59 vector<string> smallFiles;
60 struct stat sta = {};
61 if (stat(path.data(), &sta) == -1) {
62 return {BError(errno).GetCode(), files, smallFiles};
63 }
64 if (path == "/") {
65 return {BError(BError::Codes::OK).GetCode(), files, smallFiles};
66 }
67 if (sta.st_size <= size) {
68 smallFiles.emplace_back(path);
69 } else {
70 files.try_emplace(path, sta);
71 }
72 return {BError(BError::Codes::OK).GetCode(), files, smallFiles};
73 }
74
GetDirFilesDetail(const string & path,bool recursion,off_t size=-1)75 static tuple<ErrCode, map<string, struct stat>, vector<string>> GetDirFilesDetail(const string &path,
76 bool recursion,
77 off_t size = -1)
78 {
79 map<string, struct stat> files;
80 vector<string> smallFiles;
81
82 if (IsEmptyDirectory(path)) {
83 string newPath = path;
84 if (path.at(path.size()-1) != '/') {
85 newPath += '/';
86 }
87 smallFiles.emplace_back(newPath);
88 return {ERR_OK, files, smallFiles};
89 }
90
91 unique_ptr<DIR, function<void(DIR *)>> dir = {opendir(path.c_str()), closedir};
92 if (!dir) {
93 HILOGE("Invalid directory path: %{private}s", path.c_str());
94 return GetFile(path, size);
95 }
96 struct dirent *ptr = nullptr;
97 while (!!(ptr = readdir(dir.get()))) {
98 // current dir OR parent dir
99 if ((strcmp(ptr->d_name, ".") == 0) || (strcmp(ptr->d_name, "..") == 0)) {
100 continue;
101 } else if (ptr->d_type == DT_DIR) {
102 if (!recursion) {
103 continue;
104 }
105 auto [errCode, subFiles, subSmallFiles] =
106 GetDirFilesDetail(IncludeTrailingPathDelimiter(path) + string(ptr->d_name), recursion, size);
107 if (errCode != 0) {
108 return {errCode, files, smallFiles};
109 }
110 files.merge(subFiles);
111 smallFiles.insert(smallFiles.end(), subSmallFiles.begin(), subSmallFiles.end());
112 } else if (ptr->d_type == DT_LNK) {
113 continue;
114 } else {
115 struct stat sta = {};
116 string fileName = IncludeTrailingPathDelimiter(path) + string(ptr->d_name);
117 if (stat(fileName.data(), &sta) == -1) {
118 continue;
119 }
120 if (sta.st_size <= size) {
121 smallFiles.emplace_back(fileName);
122 continue;
123 }
124
125 files.try_emplace(fileName, sta);
126 }
127 }
128 return {ERR_OK, files, smallFiles};
129 }
130
GetDirFiles(const string & path)131 tuple<ErrCode, vector<string>> BDir::GetDirFiles(const string &path)
132 {
133 vector<string> files;
134 unique_ptr<DIR, function<void(DIR *)>> dir = {opendir(path.c_str()), closedir};
135 if (!dir) {
136 HILOGE("Invalid directory path: %{private}s", path.c_str());
137 return {BError(errno).GetCode(), files};
138 }
139
140 struct dirent *ptr = nullptr;
141 while (!!(ptr = readdir(dir.get()))) {
142 // current dir OR parent dir
143 if ((strcmp(ptr->d_name, ".") == 0) || (strcmp(ptr->d_name, "..") == 0)) {
144 continue;
145 } else if (ptr->d_type == DT_DIR) {
146 continue;
147 } else {
148 files.push_back(IncludeTrailingPathDelimiter(path) + string(ptr->d_name));
149 }
150 }
151
152 return {ERR_OK, files};
153 }
154
ExpandPathWildcard(const vector<string> & vec,bool onlyPath)155 static set<string> ExpandPathWildcard(const vector<string> &vec, bool onlyPath)
156 {
157 unique_ptr<glob_t, function<void(glob_t *)>> gl {new glob_t, [](glob_t *ptr) { globfree(ptr); }};
158 *gl = {};
159
160 int flags = GLOB_DOOFFS | GLOB_MARK;
161 for (const string &pattern : vec) {
162 if (!pattern.empty()) {
163 glob(pattern.data(), flags, NULL, gl.get());
164 flags |= GLOB_APPEND;
165 }
166 }
167
168 set<string> expandPath;
169 set<string> filteredPath;
170 for (size_t i = 0; i < gl->gl_pathc; ++i) {
171 expandPath.emplace(gl->gl_pathv[i]);
172 }
173
174 for (auto it = expandPath.begin(); it != expandPath.end(); ++it) {
175 filteredPath.insert(*it);
176 if (onlyPath && *it->rbegin() != '/') {
177 continue;
178 }
179 auto jt = it;
180 for (++jt; jt != expandPath.end() && (jt->find(*it) == 0); ++jt) {
181 }
182
183 it = --jt;
184 }
185
186 return filteredPath;
187 }
188
GetBigFiles(const vector<string> & includes,const vector<string> & excludes)189 tuple<ErrCode, map<string, struct stat>, vector<string>> BDir::GetBigFiles(const vector<string> &includes,
190 const vector<string> &excludes)
191 {
192 set<string> inc = ExpandPathWildcard(includes, false);
193
194 map<string, struct stat> incFiles;
195 vector<string> incSmallFiles;
196 for (const auto &item : inc) {
197 auto [errCode, files, smallFiles] = GetDirFilesDetail(item, true, BConstants::BIG_FILE_BOUNDARY);
198 if (errCode == 0) {
199 int32_t num = static_cast<int32_t>(files.size());
200 incFiles.merge(move(files));
201 HILOGI("big files: %{public}d; small files: %{public}d", num, static_cast<int32_t>(smallFiles.size()));
202 incSmallFiles.insert(incSmallFiles.end(), smallFiles.begin(), smallFiles.end());
203 }
204 }
205
206 auto isMatch = [](const vector<string> &s, const string &str) -> bool {
207 if (str.empty()) {
208 return false;
209 }
210 for (const string &item : s) {
211 if (item.empty()) {
212 continue;
213 }
214 string excludeItem = item;
215 if (excludeItem.at(item.size() - 1) == '/') {
216 excludeItem += "*";
217 }
218 if (fnmatch(excludeItem.data(), str.data(), FNM_LEADING_DIR) == 0) {
219 return true;
220 }
221 }
222 return false;
223 };
224
225 vector<string> resSmallFiles;
226 for (const auto &item : incSmallFiles) {
227 if (!isMatch(excludes, item)) {
228 resSmallFiles.emplace_back(item);
229 }
230 }
231
232 map<string, struct stat> bigFiles;
233 for (const auto &item : incFiles) {
234 if (!isMatch(excludes, item.first)) {
235 bigFiles[item.first] = item.second;
236 }
237 }
238 HILOGI("total number of big files is %{public}d", static_cast<int32_t>(bigFiles.size()));
239 HILOGI("total number of small files is %{public}d", static_cast<int32_t>(resSmallFiles.size()));
240 return {ERR_OK, move(bigFiles), move(resSmallFiles)};
241 }
242
GetDirs(const vector<string_view> & paths)243 vector<string> BDir::GetDirs(const vector<string_view> &paths)
244 {
245 vector<string> wildcardPath(paths.begin(), paths.end());
246 set<string> inc = ExpandPathWildcard(wildcardPath, true);
247 vector<string> dirs(inc.begin(), inc.end());
248 return dirs;
249 }
250
251 } // namespace OHOS::FileManagement::Backup