fileMonitor.C
Go to the documentation of this file.
1 /*---------------------------------------------------------------------------*\
2  ========= |
3  \\ / F ield | OpenFOAM: The Open Source CFD Toolbox
4  \\ / O peration | Website: https://openfoam.org
5  \\ / A nd | Copyright (C) 2011-2025 OpenFOAM Foundation
6  \\/ M anipulation |
7 -------------------------------------------------------------------------------
8 License
9  This file is part of OpenFOAM.
10 
11  OpenFOAM is free software: you can redistribute it and/or modify it
12  under the terms of the GNU General Public License as published by
13  the Free Software Foundation, either version 3 of the License, or
14  (at your option) any later version.
15 
16  OpenFOAM is distributed in the hope that it will be useful, but WITHOUT
17  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
18  FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
19  for more details.
20 
21  You should have received a copy of the GNU General Public License
22  along with OpenFOAM. If not, see <http://www.gnu.org/licenses/>.
23 
24 \*---------------------------------------------------------------------------*/
25 
26 #include "fileMonitor.H"
27 #include "IOstreams.H"
28 #include "Pstream.H"
29 #include "PackedList.H"
30 #include "PstreamReduceOps.H"
31 #include "OSspecific.H"
32 #include "regIOobject.H" // for fileModificationSkew symbol
33 
34 #ifdef FOAM_USE_INOTIFY
35  #include <unistd.h>
36  #include <sys/inotify.h>
37  #include <sys/ioctl.h>
38  #include <errno.h>
39  #define EVENT_SIZE ( sizeof (struct inotify_event) )
40  #define EVENT_LEN (EVENT_SIZE + 16)
41  #define EVENT_BUF_LEN ( 1024 * EVENT_LEN )
42 #else
43  #include "OSspecific.H"
44 #endif
45 
46 // * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //
47 
50 {
51  "unmodified",
52  "modified",
53  "deleted"
54 };
55 
56 namespace Foam
57 {
59 
60  //- Reduction operator for PackedList of fileState
62  {
63  public:
64  unsigned int operator()(const unsigned int x, const unsigned int y)
65  const
66  {
67  // x,y are sets of 2bits representing fileState
68 
69  unsigned int mask = 3u;
70  unsigned int shift = 0;
71  unsigned int result = 0;
72 
73  while (mask)
74  {
75  // Combine state
76  unsigned int xState = (x & mask) >> shift;
77  unsigned int yState = (y & mask) >> shift;
78 
79  // Combine and add to result. Combine is such that UNMODIFIED
80  // wins.
81  unsigned int state = min(xState, yState);
82  result |= (state << shift);
83 
84  shift += 2;
85  mask <<= 2;
86  }
87  return result;
88  }
89  };
90 
91  //- Combine operator for PackedList of fileState
93  {
94  public:
95  void operator()(unsigned int& x, const unsigned int y) const
96  {
97  x = reduceFileStates()(x, y);
98  }
99  };
100 
101 
102 
103  //- Internal tracking via stat(3p) or inotify(7)
105  {
106  public:
107 
108  const bool useInotify_;
109 
110  // For inotify
111 
112  //- File descriptor for the inotify instance
114 
115  //- Current watchIDs and corresponding directory id
118 
119  // For stat
120 
121  //- From watch descriptor to modified time
123 
124 
125 
126  //- Initialise inotify
127  inline fileMonitorWatcher(const bool useInotify, const label sz = 20)
128  :
129  useInotify_(useInotify),
130  inotifyFd_(-1)
131  {
132  if (useInotify_)
133  {
134  #ifdef FOAM_USE_INOTIFY
135  inotifyFd_ = inotify_init();
137  dirFiles_.setCapacity(sz);
138 
139  if (inotifyFd_ < 0)
140  {
141  static bool hasWarned = false;
142  if (!hasWarned)
143  {
144  hasWarned = true;
146  << "Failed allocating an inotify descriptor : "
147  << string(strerror(errno)) << endl
148  << " Please increase the number of allowable "
149  << "inotify instances" << endl
150  << " (/proc/sys/fs/inotify/max_user_instances"
151  << " on Linux)" << endl
152  << " , switch off runTimeModifiable." << endl
153  << " or compile this file without "
154  << "FOAM_USE_INOTIFY"
155  << " to use time stamps instead of inotify." << endl
156  << " Continuing without additional file"
157  << " monitoring."
158  << endl;
159  }
160  }
161  #else
163  << "You selected inotify but this file was compiled"
164  << " without FOAM_USE_INOTIFY"
165  << " Please select another fileModification test method"
166  << exit(FatalError);
167  #endif
168  }
169  else
170  {
171  lastMod_.setCapacity(sz);
172  }
173  }
174 
175  //- Remove all watches
177  {
178  #ifdef FOAM_USE_INOTIFY
179  if (useInotify_ && inotifyFd_ >= 0)
180  {
181  forAll(dirWatches_, i)
182  {
183  if (dirWatches_[i] >= 0)
184  {
185  if (inotify_rm_watch(inotifyFd_, int(dirWatches_[i])))
186  {
188  << "Failed deleting directory watch "
189  << dirWatches_[i] << endl;
190  }
191  }
192  }
193  }
194  #endif
195  }
196 
197  inline bool addWatch(const label watchFd, const fileName& fName)
198  {
199  if (useInotify_)
200  {
201  if (inotifyFd_ < 0)
202  {
203  return false;
204  }
205 
206  #ifdef FOAM_USE_INOTIFY
207  // Add/retrieve watch on directory containing file.
208  // Note that fName might be non-existing in special situations
209  // (master-only reading for IODictionaries)
210 
211  const fileName dir = fName.path();
212 
213  label dirWatchID = -1;
214  if (isDir(dir))
215  {
216  dirWatchID = inotify_add_watch
217  (
218  inotifyFd_,
219  dir.c_str(),
220  IN_CLOSE_WRITE
221  );
222 
223  if (dirWatchID < 0)
224  {
226  << "Failed adding watch " << watchFd
227  << " to directory " << fName << " due to "
228  << string(strerror(errno))
229  << exit(FatalError);
230  }
231  }
232 
233  if (watchFd < dirWatches_.size() && dirWatches_[watchFd] != -1)
234  {
235  // Reuse of watchFd : should have dir watchID set to -1.
237  << "Problem adding watch " << watchFd
238  << " to file " << fName
239  << abort(FatalError);
240  }
241 
242  dirWatches_(watchFd) = dirWatchID;
243  dirFiles_(watchFd) = fName.name();
244  #endif
245  }
246  else
247  {
248  if (watchFd < lastMod_.size() && lastMod_[watchFd] != 0)
249  {
250  // Reuse of watchFd : should have lastMod set to 0.
252  << "Problem adding watch " << watchFd
253  << " to file " << fName
254  << abort(FatalError);
255  }
256 
257  lastMod_(watchFd) = highResLastModified(fName);
258  }
259 
260  return true;
261  }
262 
263  inline bool removeWatch(const label watchFd)
264  {
265  if (useInotify_)
266  {
267  if (inotifyFd_ < 0)
268  {
269  return false;
270  }
271 
272  dirWatches_[watchFd] = -1;
273  }
274  else
275  {
276  lastMod_[watchFd] = 0;
277  }
278  return true;
279  }
280 
281  };
282 }
283 
284 
285 // * * * * * * * * * * * * * Private Member Functions * * * * * * * * * * * //
286 
287 void Foam::fileMonitor::checkFiles() const
288 {
289  if (useInotify_)
290  {
291  #ifdef FOAM_USE_INOTIFY
292  // Large buffer for lots of events
293  char buffer[EVENT_BUF_LEN];
294 
295  while (true)
296  {
297  struct timeval zeroTimeout = {0, 0};
298 
299  //- Pre-allocated structure containing file descriptors
300  fd_set fdSet;
301  // Add notify descriptor to select fd_set
302  FD_ZERO(&fdSet);
303  FD_SET(watcher_->inotifyFd_, &fdSet);
304 
305  int ready = select
306  (
307  watcher_->inotifyFd_+1, // num filedescriptors in fdSet
308  &fdSet, // fdSet with only inotifyFd
309  nullptr, // No writefds
310  nullptr, // No errorfds
311  &zeroTimeout // eNo timeout
312  );
313 
314  if (ready < 0)
315  {
317  << "Problem in issuing select."
318  << abort(FatalError);
319  }
320  else if (FD_ISSET(watcher_->inotifyFd_, &fdSet))
321  {
322  // Read events
323  ssize_t nBytes = ::read
324  (
325  watcher_->inotifyFd_,
326  buffer,
327  EVENT_BUF_LEN
328  );
329 
330  if (nBytes < 0)
331  {
333  << "read of " << watcher_->inotifyFd_
334  << " failed with " << label(nBytes)
335  << abort(FatalError);
336  }
337 
338  // Go through buffer, consuming events
339  int i = 0;
340  while (i < nBytes)
341  {
342  const struct inotify_event* inotifyEvent =
343  reinterpret_cast<const struct inotify_event*>
344  (
345  &buffer[i]
346  );
347 
348  // Pout<< "watchFd:" << inotifyEvent->wd << nl
349  // << "mask:" << inotifyEvent->mask << nl
350  // << endl;
351  // Pout<< "file:" << fileName(inotifyEvent->name) << endl;
352  // Pout<< "len:" << inotifyEvent->len << endl;
353 
354  if
355  (
356  (inotifyEvent->mask & IN_CLOSE_WRITE)
357  && inotifyEvent->len
358  )
359  {
360  // Search for file
361  forAll(watcher_->dirWatches_, i)
362  {
363  label id = watcher_->dirWatches_[i];
364  if
365  (
366  id == inotifyEvent->wd
367  && inotifyEvent->name == watcher_->dirFiles_[i]
368  )
369  {
370  // Correct directory and name
371  localState_[i] = MODIFIED;
372  }
373  }
374  }
375 
376  i += EVENT_SIZE + inotifyEvent->len;
377  }
378  }
379  else
380  {
381  // No data
382  return;
383  }
384  }
385  #endif
386  }
387  else
388  {
389  forAll(watcher_->lastMod_, watchFd)
390  {
391  double oldTime = watcher_->lastMod_[watchFd];
392 
393  if (oldTime != 0)
394  {
395  const fileName& fName = watchFile_[watchFd];
396  double newTime = highResLastModified(fName);
397 
398  if (newTime == 0)
399  {
400  localState_[watchFd] = DELETED;
401  }
402  else
403  {
404  if (newTime > (oldTime + regIOobject::fileModificationSkew))
405  {
406  localState_[watchFd] = MODIFIED;
407  }
408  else
409  {
410  localState_[watchFd] = UNMODIFIED;
411  }
412  }
413  }
414  }
415  }
416 }
417 
418 
419 // * * * * * * * * * * * * * * * * Constructors * * * * * * * * * * * * * * //
420 
421 
422 Foam::fileMonitor::fileMonitor(const bool useInotify)
423 :
424  useInotify_(useInotify),
425  localState_(20),
426  state_(20),
427  watchFile_(20),
428  freeWatchFds_(2),
429  watcher_(new fileMonitorWatcher(useInotify_, 20))
430 {}
431 
432 
433 // * * * * * * * * * * * * * * * * Destructor * * * * * * * * * * * * * * * //
434 
436 {}
437 
438 
439 // * * * * * * * * * * * * * * * Member Functions * * * * * * * * * * * * * //
440 
441 // Note: fName might not exist (on slaves if in master-only mode for
442 // regIOobject)
444 {
445  label watchFd;
446 
447  label sz = freeWatchFds_.size();
448 
449  if (sz)
450  {
451  watchFd = freeWatchFds_[sz-1];
452  freeWatchFds_.setSize(sz-1);
453  }
454  else
455  {
456  watchFd = state_.size();
457  }
458 
459  watcher_->addWatch(watchFd, fName);
460 
461  if (debug)
462  {
463  Pout<< "fileMonitor : added watch " << watchFd << " on file "
464  << fName << endl;
465  }
466 
467  if (watchFd < 0)
468  {
470  << "could not add watch for file " << fName << endl;
471  }
472  else
473  {
474  localState_(watchFd) = UNMODIFIED;
475  state_(watchFd) = UNMODIFIED;
476  watchFile_(watchFd) = fName;
477  }
478  return watchFd;
479 }
480 
481 
483 {
484  if (debug)
485  {
486  Pout<< "fileMonitor : removing watch " << watchFd << " on file "
487  << watchFile_[watchFd] << endl;
488  }
489 
490  freeWatchFds_.append(watchFd);
491  return watcher_->removeWatch(watchFd);
492 }
493 
494 
496 {
497  return watchFile_[watchFd];
498 }
499 
500 
502 const
503 {
504  return state_[watchFd];
505 }
506 
507 
509 (
510  const bool masterOnly,
511  const bool syncPar
512 ) const
513 {
514  if (Pstream::master() || !masterOnly)
515  {
516  // Update the localState_
517  checkFiles();
518  }
519 
520  if (syncPar)
521  {
522  // Pack local state (might be on master only)
523  PackedList<2> stats(state_.size(), MODIFIED);
524  if (Pstream::master() || !masterOnly)
525  {
526  forAll(state_, watchFd)
527  {
528  stats[watchFd] = static_cast<unsigned int>
529  (
530  localState_[watchFd]
531  );
532  }
533  }
534 
535 
536  // Scatter or reduce to synchronise state
537  if (masterOnly)
538  {
539  // Scatter
540  if (stats.storage().size() == 1)
541  {
542  Pstream::scatter(stats.storage()[0]);
543  }
544  else
545  {
547  }
548  }
549  else
550  {
551  // Reduce
552  if (stats.storage().size() == 1)
553  {
554  // Optimisation valid for most cases.
555  reduce(stats.storage()[0], reduceFileStates());
556  }
557  else
558  {
560  (
561  stats.storage(),
563  );
564  }
565  }
566 
567 
568  // Update synchronised state
569  forAll(state_, watchFd)
570  {
571  // Assign synchronised state
572  unsigned int stat = stats[watchFd];
573  state_[watchFd] = fileState(stat);
574 
575  if (!masterOnly)
576  {
577  // Give warning for inconsistent state
578  if (state_[watchFd] != localState_[watchFd])
579  {
580  if (debug)
581  {
582  Pout<< "fileMonitor : Delaying reading "
583  << watchFile_[watchFd]
584  << " due to inconsistent "
585  "file time-stamps between processors"
586  << endl;
587  }
588 
590  << "Delaying reading " << watchFile_[watchFd]
591  << " due to inconsistent "
592  "file time-stamps between processors" << endl;
593  }
594  }
595  }
596  }
597  else
598  {
599  state_ = localState_;
600  }
601 }
602 
603 
605 {
606  state_[watchFd] = UNMODIFIED;
607  localState_[watchFd] = UNMODIFIED;
608 
609  if (!useInotify_)
610  {
611  watcher_->lastMod_[watchFd] = highResLastModified(watchFile_[watchFd]);
612  }
613 }
614 
615 
616 // ************************************************************************* //
Useful combination of include files which define Sin, Sout and Serr and the use of IO streams general...
scalar y
Functions used by OpenFOAM that are specific to POSIX compliant operating systems and need to be repl...
Inter-processor communication reduction functions.
#define forAll(list, i)
Loop across all elements in list.
Definition: UList.H:433
void setCapacity(const label)
Alter the size of the underlying storage.
Definition: DynamicListI.H:130
void size(const label)
Override size to be inconsistent with allocated storage.
Definition: ListI.H:164
Initialise the NamedEnum HashTable from the static list of names.
Definition: NamedEnum.H:55
List< unsigned int > & storage()
Return the underlying packed storage.
Definition: PackedListI.H:916
static void scatter(const List< commsStruct > &comms, T &Value, const int tag, const label comm)
Scatter data. Distribute without modification. Reverse of gather.
static void listCombineGather(const List< commsStruct > &comms, List< T > &Value, const CombineOp &cop, const int tag, const label comm)
static void listCombineScatter(const List< commsStruct > &comms, List< T > &Value, const int tag, const label comm)
Scatter data. Reverse of combineGather.
static bool master(const label communicator=0)
Am I the master process.
Definition: UPstream.H:423
Combine operator for PackedList of fileState.
Definition: fileMonitor.C:93
void operator()(unsigned int &x, const unsigned int y) const
Definition: fileMonitor.C:95
Internal tracking via stat(3p) or inotify(7)
Definition: fileMonitor.C:105
fileMonitorWatcher(const bool useInotify, const label sz=20)
Initialise inotify.
Definition: fileMonitor.C:127
DynamicList< double > lastMod_
From watch descriptor to modified time.
Definition: fileMonitor.C:122
int inotifyFd_
File descriptor for the inotify instance.
Definition: fileMonitor.C:113
DynamicList< label > dirWatches_
Current watchIDs and corresponding directory id.
Definition: fileMonitor.C:116
bool addWatch(const label watchFd, const fileName &fName)
Definition: fileMonitor.C:197
DynamicList< fileName > dirFiles_
Definition: fileMonitor.C:117
~fileMonitorWatcher()
Remove all watches.
Definition: fileMonitor.C:176
bool removeWatch(const label watchFd)
Definition: fileMonitor.C:263
Checking for changes to files.
Definition: fileMonitor.H:62
fileState getState(const label watchFd) const
Check state using handle.
Definition: fileMonitor.C:501
label addWatch(const fileName &)
Add file to watch. Return watch descriptor.
Definition: fileMonitor.C:443
void setUnmodified(const label watchFd)
Reset state (e.g. after having read it) using handle.
Definition: fileMonitor.C:604
~fileMonitor()
Destructor.
Definition: fileMonitor.C:435
static const NamedEnum< fileState, 3 > fileStateNames_
Definition: fileMonitor.H:76
const fileName & getFile(const label watchFd) const
Get name of file being watched.
Definition: fileMonitor.C:495
void updateStates(const bool masterOnly, const bool syncPar) const
Check state of all files. Updates state_.
Definition: fileMonitor.C:509
fileState
Enumeration defining the file state.
Definition: fileMonitor.H:70
fileMonitor(const bool useInotify)
Construct null.
Definition: fileMonitor.C:422
bool removeWatch(const label watchFd)
Remove file to watch. Return true if successful.
Definition: fileMonitor.C:482
A class for handling file names.
Definition: fileName.H:82
word name() const
Return file name (part beyond last /)
Definition: fileName.C:195
fileName path() const
Return directory path name (part before last /)
Definition: fileName.C:284
Reduction operator for PackedList of fileState.
Definition: fileMonitor.C:62
unsigned int operator()(const unsigned int x, const unsigned int y) const
Definition: fileMonitor.C:64
static float fileModificationSkew
Definition: regIOobject.H:100
A class for handling character strings derived from std::string.
Definition: string.H:79
#define FatalErrorInFunction
Report an error message using Foam::FatalError.
Definition: error.H:334
#define WarningInFunction
Report a warning using Foam::Warning.
Namespace for OpenFOAM.
errorManipArg< error, int > exit(error &err, const int errNo=1)
Definition: errorManip.H:124
bool read(const char *, int32_t &)
Definition: int32IO.C:85
intWM_LABEL_SIZE_t label
A label is an int32_t or int64_t as specified by the pre-processor macro WM_LABEL_SIZE.
Definition: label.H:59
Ostream & endl(Ostream &os)
Add newline and flush stream.
Definition: Ostream.H:258
double highResLastModified(const fileName &, const bool checkVariants=true, const bool followLink=true)
Return time of last file modification.
Definition: POSIX.C:632
errorManip< error > abort(error &err)
Definition: errorManip.H:131
layerAndWeight min(const layerAndWeight &a, const layerAndWeight &b)
void reduce(const List< UPstream::commsStruct > &comms, T &Value, const BinaryOp &bop, const int tag, const label comm)
defineTypeNameAndDebug(combustionModel, 0)
bool isDir(const fileName &, const bool followLink=true)
Does the name exist as a directory in the file system?
Definition: POSIX.C:539
prefixOSstream Pout(cout, "Pout")
Definition: IOstreams.H:53
error FatalError