1 /*
2 * Copyright (C) 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 #include "io/dev/FileMonitor.h"
16
17 #include <algorithm>
18 #include <iostream>
19 #include <cstring>
20
21 #ifdef _WIN32
22 #if __has_include(<filesystem>)
23 #include <filesystem>
24 #define HAS_FILESYSTEM 1
25 #endif
26 #define USE_WIN32
27 #include <Windows.h>
28 #else
29 #include <dirent.h>
30 #endif
31
32 #include <limits.h>
33 #include <sys/stat.h>
34
35 namespace ige {
36
37 namespace {
38
FormatPath(std::string & aPath,bool isDirectory)39 void FormatPath(std::string& aPath, bool isDirectory)
40 {
41 size_t length = aPath.length();
42
43 // Make directory separators consistent.
44 std::replace(aPath.begin(), aPath.end(), '\\', '/');
45
46 // Ensure there is last separator in place.
47 if (aPath.length() > 0 && isDirectory) {
48 if (aPath[length - 1] != '/') {
49 aPath += '/';
50 }
51 }
52 }
53
ResolveAbsolutePath(const std::string & aPath,bool isDirectory)54 std::string ResolveAbsolutePath(const std::string& aPath, bool isDirectory)
55 {
56 std::string absolutePath;
57
58 #ifdef HAS_FILESYSTEM
59 std::error_code ec;
60 std::filesystem::path path = std::filesystem::canonical(aPath, ec);
61 if (ec.value() == 0) {
62 absolutePath = path.string();
63 }
64 #elif defined(USE_WIN32)
65 char resolvedPath[_MAX_PATH] = {};
66 auto ret = GetFullPathNameA(aPath.c_str(), sizeof(resolvedPath), resolvedPath, nullptr);
67 if (ret < sizeof(resolvedPath)) {
68 WIN32_FIND_DATAA data;
69 HANDLE handle = FindFirstFileA(resolvedPath, &data);
70 if (handle != INVALID_HANDLE_VALUE) {
71 absolutePath = resolvedPath;
72 FindClose(handle);
73 }
74 }
75 #elif defined(__MINGW32__) || defined(__MINGW64__)
76 char resolvedPath[_MAX_PATH] = {};
77 if (_fullpath(resolvedPath, aPath.c_str(), _MAX_PATH) != nullptr) {
78 if (isDirectory) {
79 auto handle = opendir(resolvedPath);
80 if (handle) {
81 closedir(handle);
82 absolutePath = resolvedPath;
83 }
84 } else {
85 auto handle = fopen(resolvedPath, "r");
86 if (handle) {
87 fclose(handle);
88 absolutePath = resolvedPath;
89 }
90 }
91 }
92 #else
93 char resolvedPath[PATH_MAX + 1] = { 0 };
94 if (realpath(aPath.c_str(), resolvedPath) != nullptr) {
95 absolutePath = resolvedPath;
96 }
97 #endif
98
99 FormatPath(absolutePath, isDirectory);
100
101 return absolutePath;
102 }
103
recursivelyCollectAllFiles(const std::string & aPath,std::vector<std::string> & aFiles)104 void recursivelyCollectAllFiles(const std::string& aPath, std::vector<std::string>& aFiles)
105 {
106 std::vector<std::string> files;
107 std::vector<std::string> directories;
108 #if defined(USE_WIN32)
109 WIN32_FIND_DATAA data;
110 HANDLE handle = INVALID_HANDLE_VALUE;
111 handle = FindFirstFileA((aPath + "*.*").c_str(), &data);
112 if (handle == INVALID_HANDLE_VALUE) {
113 // Unable to open directory.
114 return;
115 }
116 do {
117 if (data.dwFileAttributes & FILE_ATTRIBUTE_DEVICE) {
118 // skip devices.
119 } else if (data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
120 directories.push_back(data.cFileName);
121 } else {
122 files.push_back(data.cFileName);
123 }
124 } while (FindNextFileA(handle, &data));
125
126 FindClose(handle);
127 #else
128 DIR* handle = opendir(aPath.c_str());
129 if (!handle) {
130 // Unable to open directory.
131 return;
132 }
133
134 struct dirent* directory;
135 while ((directory = readdir(handle)) != nullptr) {
136 switch (directory->d_type) {
137 case DT_DIR:
138 directories.push_back(directory->d_name);
139 break;
140
141 case DT_REG:
142 files.push_back(directory->d_name);
143 break;
144
145 default:
146 break;
147 }
148 }
149
150 closedir(handle);
151 #endif
152
153 // Collect files.
154 for (const auto& filename : files) {
155 std::string absoluteFilename = ResolveAbsolutePath(aPath + filename, false);
156 if (!absoluteFilename.empty()) {
157 aFiles.push_back(absoluteFilename);
158 }
159 }
160
161 // Process recursively.
162 for (const auto& directoryName : directories) {
163 if (directoryName != "." && directoryName != "..") {
164 std::string absoluteDirectory = ResolveAbsolutePath(aPath + directoryName, true);
165 if (!absoluteDirectory.empty()) {
166 recursivelyCollectAllFiles(absoluteDirectory, aFiles);
167 }
168 }
169 }
170 }
171
172 } // namespace
173
FileMonitor()174 FileMonitor::FileMonitor() {}
~FileMonitor()175 FileMonitor::~FileMonitor() {}
AddPath(const std::string & aPath)176 bool FileMonitor::AddPath(const std::string& aPath)
177 {
178 std::string absolutePath = ResolveAbsolutePath(aPath, true);
179 if (absolutePath.empty() || IsWatchingDirectory(absolutePath) || IsWatchingSubDirectory(absolutePath)) {
180 // Already exists or unable to resolve.
181 return false;
182 }
183 std::vector<std::string> files;
184 recursivelyCollectAllFiles(absolutePath, files);
185
186 // Add all files to watch list.
187 for (const auto& ref : files) {
188 AddFile(ref);
189 }
190 // Store directory to watch list.
191 mDirectories.push_back(absolutePath);
192
193 return true;
194 }
195
RemovePath(const std::string & aPath)196 bool FileMonitor::RemovePath(const std::string& aPath)
197 {
198 std::string absolutePath = ResolveAbsolutePath(aPath, true);
199 std::vector<std::string>::iterator iterator = std::find(mDirectories.begin(), mDirectories.end(), absolutePath);
200 if (iterator != mDirectories.end()) {
201 // Collect all tracked files within this directory.
202 std::vector<std::string> files;
203 recursivelyCollectAllFiles(absolutePath, files);
204
205 // Stop tracking of removed files.
206 for (const auto& ref : files) {
207 // Remove from tracked list.
208 RemoveFile(ref);
209 }
210 // Remove directory from watch list.
211 mDirectories.erase(iterator);
212 return true;
213 }
214
215 return false;
216 }
217
AddFile(const std::string & aPath)218 bool FileMonitor::AddFile(const std::string& aPath)
219 {
220 std::string absolutePath = ResolveAbsolutePath(aPath, false);
221 if (absolutePath.empty() || mFiles.find(absolutePath) != mFiles.end()) {
222 // Already exists or unable to resolve.
223 return false;
224 }
225
226 struct stat ds;
227 if (stat(absolutePath.c_str(), &ds) != 0) {
228 // Unable to get file stats.
229 return false;
230 }
231
232 // Collect file data.
233 FileInfo info;
234 info.timestamp = ds.st_mtime;
235 mFiles[absolutePath] = info;
236
237 return true;
238 }
239
RemoveFile(const std::string & aPath)240 bool FileMonitor::RemoveFile(const std::string& aPath)
241 {
242 std::map<std::string, FileInfo>::iterator iterator = mFiles.find(aPath);
243 if (iterator != mFiles.end()) {
244 mFiles.erase(iterator);
245 return true;
246 }
247
248 return false;
249 }
250
IsWatchingDirectory(const std::string & aPath)251 bool FileMonitor::IsWatchingDirectory(const std::string& aPath)
252 {
253 for (const auto& ref : mDirectories) {
254 if (aPath.find(ref) != std::string::npos) {
255 // Already watching this directory or it's parent.
256 return true;
257 }
258 }
259
260 return false;
261 }
262
IsWatchingSubDirectory(const std::string & aPath)263 bool FileMonitor::IsWatchingSubDirectory(const std::string& aPath)
264 {
265 for (const auto& ref : mDirectories) {
266 if (ref.find(aPath) != std::string::npos) {
267 // Already watching subdirectory of given directory.
268 return true;
269 }
270 }
271
272 return false;
273 }
274
scanModifications(std::vector<std::string> & aAdded,std::vector<std::string> & aRemoved,std::vector<std::string> & aModified)275 void FileMonitor::scanModifications(
276 std::vector<std::string>& aAdded, std::vector<std::string>& aRemoved, std::vector<std::string>& aModified)
277 {
278 // Collect all files that are under monitoring.
279 std::vector<std::string> files;
280 for (const auto& ref : mDirectories) {
281 recursivelyCollectAllFiles(ref, files);
282 }
283
284 // See which of the files are modified.
285 for (std::vector<std::string>::iterator it = files.begin(); it != files.end(); ++it) {
286 std::map<std::string, FileInfo>::iterator iterator = mFiles.find(*it);
287 if (iterator != mFiles.end()) {
288 // File being watched, see if it is modified.
289 struct stat fs;
290 if (stat((*it).c_str(), &fs) == 0) {
291 if (fs.st_mtime != iterator->second.timestamp) {
292 // This file is modified.
293 aModified.push_back(*it);
294
295 // Store new time.
296 iterator->second.timestamp = fs.st_mtime;
297 }
298 }
299 } else {
300 // This is a new file.
301 aAdded.push_back(*it);
302 }
303 }
304
305 // See which of the files are removed.
306 for (const auto& ref : mFiles) {
307 if (std::find(files.begin(), files.end(), ref.first) == files.end()) {
308 // This file no longer exists.
309 aRemoved.push_back(ref.first);
310 }
311 }
312
313 // Stop tracking of removed files.
314 for (const auto& ref : aRemoved) {
315 // Remove from tracked list.
316 RemoveFile(ref);
317 }
318
319 // Start tracking of new files.
320 for (const auto& ref : aAdded) {
321 // Add to tracking list.
322 AddFile(ref);
323 }
324 }
325
getMonitoredFiles() const326 std::vector<std::string> FileMonitor::getMonitoredFiles() const
327 {
328 std::vector<std::string> files;
329 files.reserve(mFiles.size());
330
331 for (std::map<std::string, FileInfo>::const_iterator it = mFiles.begin(), end = mFiles.end(); it != end; ++it) {
332 files.push_back(it->first);
333 }
334 return files;
335 }
336
337 } // namespace ige
338