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