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