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 "io/std_directory.h"
17
18 #include <algorithm>
19
20 #ifdef __has_include
21 #if __has_include(<filesystem>)
22 #include <filesystem>
23 #include <chrono>
24 #include <system_error>
25 #endif
26 #endif
27 #if !defined(HAS_FILESYSTEM)
28 #include <dirent.h>
29 #include <sys/stat.h>
30 #include <sys/types.h>
31 #endif
32
33 #include <cstddef>
34 #include <cstdint>
35
36 #include <base/containers/iterator.h>
37 #include <base/containers/string.h>
38 #include <base/containers/string_view.h>
39 #include <base/containers/type_traits.h>
40 #include <base/containers/unique_ptr.h>
41 #include <base/containers/vector.h>
42 #include <base/namespace.h>
43 #include <core/io/intf_directory.h>
44 #include <core/log.h>
45 #include <core/namespace.h>
46
47 CORE_BEGIN_NAMESPACE()
48 using BASE_NS::make_unique;
49 using BASE_NS::string;
50 using BASE_NS::string_view;
51 using BASE_NS::vector;
52
53 #if defined(HAS_FILESYSTEM)
54
55 // Hiding the directory implementation from the header.
56 struct DirImpl {
DirImplDirImpl57 explicit DirImpl(string_view path) : path_(path) {}
58 string path_;
59 };
60
61 namespace {
GetTimeStamp(const std::filesystem::directory_entry & entry)62 uint64_t GetTimeStamp(const std::filesystem::directory_entry& entry)
63 {
64 return static_cast<uint64_t>(entry.last_write_time().time_since_epoch().count());
65 }
66
GetEntryType(const std::filesystem::directory_entry & entry)67 IDirectory::Entry::Type GetEntryType(const std::filesystem::directory_entry& entry)
68 {
69 if (entry.is_directory()) {
70 return IDirectory::Entry::DIRECTORY;
71 }
72 if (entry.is_regular_file()) {
73 return IDirectory::Entry::FILE;
74 }
75
76 return IDirectory::Entry::UNKNOWN;
77 }
78
U8Path(string_view str)79 std::filesystem::path U8Path(string_view str)
80 {
81 return std::filesystem::u8path(str.begin().ptr(), str.end().ptr());
82 }
83 } // namespace
84
StdDirectory(BASE_NS::unique_ptr<DirImpl> dir)85 StdDirectory::StdDirectory(BASE_NS::unique_ptr<DirImpl> dir) : dir_(BASE_NS::move(dir)) {}
86
~StdDirectory()87 StdDirectory::~StdDirectory()
88 {
89 Close();
90 }
91
Close()92 void StdDirectory::Close()
93 {
94 if (dir_) {
95 dir_.reset();
96 }
97 }
98
Create(BASE_NS::string_view path)99 IDirectory::Ptr StdDirectory::Create(BASE_NS::string_view path)
100 {
101 std::error_code ec;
102 if (std::filesystem::create_directory(U8Path(path), ec)) {
103 // Directory creation successful.
104 return IDirectory::Ptr { BASE_NS::make_unique<StdDirectory>(make_unique<DirImpl>(path)).release() };
105 }
106 return {};
107 }
108
Open(const string_view path)109 IDirectory::Ptr StdDirectory::Open(const string_view path)
110 {
111 std::error_code ec;
112 if (std::filesystem::is_directory(U8Path(path), ec)) {
113 return IDirectory::Ptr { BASE_NS::make_unique<StdDirectory>(make_unique<DirImpl>(path)).release() };
114 }
115 const auto message = ec.message();
116 CORE_LOG_E("'%.*s ec %d '%s'", static_cast<int>(path.size()), path.data(), ec.value(), message.data());
117 return {};
118 }
119
GetEntries() const120 vector<IDirectory::Entry> StdDirectory::GetEntries() const
121 {
122 CORE_ASSERT_MSG(dir_, "Dir not open");
123 vector<IDirectory::Entry> result;
124 if (dir_) {
125 std::error_code ec;
126 for (auto& iter : std::filesystem::directory_iterator(U8Path(dir_->path_), ec)) {
127 const auto filename = iter.path().filename().u8string();
128 auto str = string(filename.c_str(), filename.length());
129 result.push_back(IDirectory::Entry { GetEntryType(iter), move(str), GetTimeStamp(iter) });
130 }
131 }
132
133 return result;
134 }
135
136 #else // HAS_FILESYSTEM not defined
137
138 // Hiding the directory implementation from the header.
139 struct DirImpl {
DirImplDirImpl140 DirImpl(const string_view path, DIR* aDir) : path_(path), dir_(aDir) {}
141 string path_;
142 DIR* dir_;
143 };
144
145 namespace {
CreateEntry(const string_view path,const dirent & entry)146 IDirectory::Entry CreateEntry(const string_view path, const dirent& entry)
147 {
148 const auto fullPath = path + string(entry.d_name);
149 struct stat statBuf {};
150 const auto statResult = (stat(fullPath.c_str(), &statBuf) == 0);
151
152 IDirectory::Entry::Type type = IDirectory::Entry::UNKNOWN;
153 #ifdef _DIRENT_HAVE_D_TYPE
154 switch (entry.d_type) {
155 case DT_DIR:
156 type = IDirectory::Entry::DIRECTORY;
157 break;
158 case DT_REG:
159 type = IDirectory::Entry::FILE;
160 break;
161 default:
162 type = IDirectory::Entry::UNKNOWN;
163 break;
164 }
165 #else
166 if (statResult) {
167 if (S_ISDIR(statBuf.st_mode)) {
168 type = IDirectory::Entry::DIRECTORY;
169 } else if (S_ISREG(statBuf.st_mode)) {
170 type = IDirectory::Entry::FILE;
171 }
172 }
173 #endif
174
175 // Get the timestamp for the directory entry.
176 uint64_t timestamp = 0;
177 if (statResult) {
178 timestamp = static_cast<uint64_t>(statBuf.st_mtime);
179 }
180
181 return IDirectory::Entry { type, entry.d_name, timestamp };
182 }
183 } // namespace
184
StdDirectory(BASE_NS::unique_ptr<DirImpl> dir)185 StdDirectory::StdDirectory(BASE_NS::unique_ptr<DirImpl> dir) : dir_(BASE_NS::move(dir)) {}
186
~StdDirectory()187 StdDirectory::~StdDirectory()
188 {
189 Close();
190 }
191
Close()192 void StdDirectory::Close()
193 {
194 if (dir_) {
195 closedir(dir_->dir_);
196 dir_.reset();
197 }
198 }
199
Create(BASE_NS::string_view path)200 IDirectory::Ptr StdDirectory::Create(BASE_NS::string_view path)
201 {
202 int result = mkdir(string(path).c_str(), S_IRWXU | S_IRWXO);
203 if (result == 0) {
204 // Directory creation successful.
205 return Open(path);
206 }
207 return {};
208 }
209
Open(const string_view path)210 IDirectory::Ptr StdDirectory::Open(const string_view path)
211 {
212 DIR* dir = opendir(string(path).c_str());
213 if (dir) {
214 return IDirectory::Ptr { make_unique<StdDirectory>(make_unique<DirImpl>(path, dir)).release() };
215 }
216 return {};
217 }
218
GetEntries() const219 vector<IDirectory::Entry> StdDirectory::GetEntries() const
220 {
221 CORE_ASSERT_MSG(dir_, "Dir not open");
222 vector<IDirectory::Entry> result;
223 if (dir_) {
224 // Go back to start.
225 rewinddir(dir_->dir_);
226
227 // Iterate all entries and write to result.
228 struct dirent* directoryEntry = nullptr;
229 while ((directoryEntry = readdir(dir_->dir_)) != nullptr) {
230 if (!strcmp(directoryEntry->d_name, ".") || !strcmp(directoryEntry->d_name, "..")) {
231 continue;
232 }
233 result.push_back(CreateEntry(dir_->path_, *directoryEntry));
234 }
235 }
236
237 return result;
238 }
239
240 #endif // HAS_FILESYSTEM
241
ResolveAbsolutePath(const string_view pathIn,bool isDirectory)242 string StdDirectory::ResolveAbsolutePath(const string_view pathIn, bool isDirectory)
243 {
244 string absolutePath;
245 auto path = pathIn;
246 #ifdef _WIN32
247 // remove the '/' slash..
248 path = path.substr(1);
249 #endif
250
251 #ifdef HAS_FILESYSTEM
252 std::error_code ec;
253 std::filesystem::path fsPath = std::filesystem::canonical(U8Path(path), ec);
254 if (ec.value() == 0) {
255 const auto pathStr = fsPath.string();
256 absolutePath.assign(pathStr.data(), pathStr.size());
257 }
258 #elif defined(_WIN32)
259 char resolvedPath[_MAX_PATH] = {};
260 if (_fullpath(resolvedPath, string(path).c_str(), _MAX_PATH) != nullptr) {
261 if (isDirectory) {
262 auto handle = opendir(resolvedPath);
263 if (handle) {
264 closedir(handle);
265 absolutePath = resolvedPath;
266 }
267 } else {
268 auto handle = fopen(resolvedPath, "r");
269 if (handle) {
270 fclose(handle);
271 absolutePath = resolvedPath;
272 }
273 }
274 }
275 #elif defined(__linux__)
276 char resolvedPath[PATH_MAX];
277 if (realpath(string(path).c_str(), resolvedPath) != nullptr) {
278 absolutePath = resolvedPath;
279 }
280 #endif
281
282 FormatPath(absolutePath, isDirectory);
283
284 return absolutePath;
285 }
286
FormatPath(string & path,bool isDirectory)287 void StdDirectory::FormatPath(string& path, bool isDirectory)
288 {
289 const size_t length = path.length();
290
291 // Make directory separators consistent.
292 std::replace(path.begin(), path.end(), '\\', '/');
293
294 // Ensure there is last separator in place.
295 if (path.length() > 0 && isDirectory) {
296 if (path[length - 1] != '/') {
297 path += '/';
298 }
299 }
300 }
301
GetDirName(const string_view fullPath)302 string StdDirectory::GetDirName(const string_view fullPath)
303 {
304 auto path = string(fullPath);
305 StdDirectory::FormatPath(path, false);
306
307 const size_t separatorPos = path.find_last_of('/');
308 if (separatorPos == string::npos) {
309 return ".";
310 }
311 path.resize(separatorPos);
312 return path;
313 }
314
GetBaseName(const string_view fullPath)315 string StdDirectory::GetBaseName(const string_view fullPath)
316 {
317 auto path = string(fullPath);
318 StdDirectory::FormatPath(path, false);
319
320 const size_t separatorPos = path.find_last_of('/');
321 if (separatorPos == string::npos) {
322 return path;
323 }
324 path.erase(0, separatorPos + 1);
325 return path;
326 }
327 CORE_END_NAMESPACE()
328