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