622 lines
18 KiB
C
622 lines
18 KiB
C
/*---------------------------------------------------------------------------*\
|
|
========= |
|
|
\\ / F ield | OpenFOAM: The Open Source CFD Toolbox
|
|
\\ / O peration |
|
|
\\ / A nd | Copyright (C) 2010-2010 OpenCFD Ltd.
|
|
\\/ M anipulation |
|
|
-------------------------------------------------------------------------------
|
|
License
|
|
This file is part of OpenFOAM.
|
|
|
|
OpenFOAM is free software: you can redistribute it and/or modify it
|
|
under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
OpenFOAM is distributed in the hope that it will be useful, but WITHOUT
|
|
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
|
for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with OpenFOAM. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
Class
|
|
fileMonitor
|
|
|
|
\*----------------------------------------------------------------------------*/
|
|
|
|
#include "fileMonitor.H"
|
|
#include "IOstreams.H"
|
|
#include "Pstream.H"
|
|
#include "PackedList.H"
|
|
#include "PstreamReduceOps.H"
|
|
#include "OSspecific.H"
|
|
#include "regIOobject.H" // for fileModificationSkew symbol
|
|
|
|
#ifdef FOAM_USE_INOTIFY
|
|
# include <sys/inotify.h>
|
|
# include <sys/ioctl.h>
|
|
# include <errno.h>
|
|
# define EVENT_SIZE ( sizeof (struct inotify_event) )
|
|
# define EVENT_LEN (EVENT_SIZE + 16)
|
|
# define EVENT_BUF_LEN ( 1024 * EVENT_LEN )
|
|
#else
|
|
# include "OSspecific.H"
|
|
#endif
|
|
|
|
// * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //
|
|
|
|
defineTypeNameAndDebug(Foam::fileMonitor, 0);
|
|
|
|
const Foam::NamedEnum<Foam::fileMonitor::fileState, 3>
|
|
Foam::fileMonitor::fileStateNames_;
|
|
|
|
namespace Foam
|
|
{
|
|
template<>
|
|
const char* Foam::NamedEnum<Foam::fileMonitor::fileState, 3>::names[] =
|
|
{
|
|
"unmodified",
|
|
"modified",
|
|
"deleted"
|
|
};
|
|
|
|
//- Reduction operator for PackedList of fileState
|
|
class reduceFileStates
|
|
{
|
|
public:
|
|
unsigned int operator()(const unsigned int x, const unsigned int y)
|
|
const
|
|
{
|
|
// x,y are sets of 2bits representing fileState
|
|
|
|
unsigned int mask = 3u;
|
|
unsigned int shift = 0;
|
|
unsigned int result = 0;
|
|
|
|
while (mask)
|
|
{
|
|
// Combine state
|
|
unsigned int xState = (x & mask) >> shift;
|
|
unsigned int yState = (y & mask) >> shift;
|
|
|
|
// Combine and add to result. Combine is such that UNMODIFIED
|
|
// wins.
|
|
unsigned int state = min(xState, yState);
|
|
result |= (state << shift);
|
|
|
|
shift += 2;
|
|
mask <<= 2;
|
|
}
|
|
return result;
|
|
}
|
|
};
|
|
|
|
//- Combine operator for PackedList of fileState
|
|
class combineReduceFileStates
|
|
{
|
|
public:
|
|
void operator()(unsigned int& x, const unsigned int y) const
|
|
{
|
|
x = reduceFileStates()(x, y);
|
|
}
|
|
};
|
|
|
|
|
|
|
|
//! @cond internalClass
|
|
//- Internal tracking via stat(3p) or inotify(7)
|
|
class fileMonitorWatcher
|
|
{
|
|
public:
|
|
|
|
const bool useInotify_;
|
|
|
|
// For inotify
|
|
|
|
//- File descriptor for the inotify instance
|
|
int inotifyFd_;
|
|
|
|
//- Current watchIDs and corresponding directory id
|
|
DynamicList<label> dirWatches_;
|
|
DynamicList<fileName> dirFiles_;
|
|
|
|
// For stat
|
|
|
|
//- From watch descriptor to modified time
|
|
DynamicList<time_t> lastMod_;
|
|
|
|
|
|
|
|
//- initialise inotify
|
|
inline fileMonitorWatcher(const bool useInotify, const label sz = 20)
|
|
:
|
|
useInotify_(useInotify)
|
|
{
|
|
if (useInotify_)
|
|
{
|
|
#ifdef FOAM_USE_INOTIFY
|
|
inotifyFd_ = inotify_init();
|
|
dirWatches_.setCapacity(sz);
|
|
dirFiles_.setCapacity(sz);
|
|
|
|
if (inotifyFd_ < 0)
|
|
{
|
|
static bool hasWarned = false;
|
|
if (!hasWarned)
|
|
{
|
|
hasWarned = true;
|
|
WarningIn("fileMonitorWatcher(const bool, const label)")
|
|
<< "Failed allocating an inotify descriptor : "
|
|
<< string(strerror(errno)) << endl
|
|
<< " Please increase the number of allowable "
|
|
<< "inotify instances" << endl
|
|
<< " (/proc/sys/fs/inotify/max_user_instances"
|
|
<< " on Linux)" << endl
|
|
<< " , switch off runTimeModifiable." << endl
|
|
<< " or compile this file without "
|
|
<< "FOAM_USE_INOTIFY"
|
|
<< " to use time stamps instead of inotify." << endl
|
|
<< " Continuing without additional file"
|
|
<< " monitoring."
|
|
<< endl;
|
|
}
|
|
}
|
|
#else
|
|
FatalErrorIn("fileMonitorWatcher(const bool, const label)")
|
|
<< "You selected inotify but this file was compiled"
|
|
<< " without FOAM_USE_INOTIFY"
|
|
<< "Please select another fileModification test method"
|
|
<< exit(FatalError);
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
lastMod_.setCapacity(sz);
|
|
}
|
|
}
|
|
|
|
//- remove all watches
|
|
inline ~fileMonitorWatcher()
|
|
{
|
|
#ifdef FOAM_USE_INOTIFY
|
|
if (useInotify_ && inotifyFd_ >= 0)
|
|
{
|
|
forAll(dirWatches_, i)
|
|
{
|
|
if (dirWatches_[i] >= 0)
|
|
{
|
|
if (inotify_rm_watch(inotifyFd_, int(dirWatches_[i])))
|
|
{
|
|
WarningIn("fileMonitor::~fileMonitor()")
|
|
<< "Failed deleting directory watch "
|
|
<< dirWatches_[i] << endl;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
inline bool addWatch(const label watchFd, const fileName& fName)
|
|
{
|
|
if (useInotify_)
|
|
{
|
|
if (inotifyFd_ < 0)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
#ifdef FOAM_USE_INOTIFY
|
|
// Add/retrieve watch on directory containing file.
|
|
// Note that fName might be non-existing in special situations
|
|
// (master-only reading for IODictionaries)
|
|
|
|
const fileName dir = fName.path();
|
|
|
|
label dirWatchID = -1;
|
|
if (isDir(dir))
|
|
{
|
|
dirWatchID = inotify_add_watch
|
|
(
|
|
inotifyFd_,
|
|
dir.c_str(),
|
|
IN_CLOSE_WRITE
|
|
);
|
|
|
|
if (dirWatchID < 0)
|
|
{
|
|
FatalErrorIn("addWatch(const label, const fileName&)")
|
|
<< "Failed adding watch " << watchFd
|
|
<< " to directory " << fName << " due to "
|
|
<< string(strerror(errno))
|
|
<< exit(FatalError);
|
|
}
|
|
}
|
|
|
|
if (watchFd < dirWatches_.size() && dirWatches_[watchFd] != -1)
|
|
{
|
|
// Reuse of watchFd : should have dir watchID set to -1.
|
|
FatalErrorIn("addWatch(const label, const fileName&)")
|
|
<< "Problem adding watch " << watchFd
|
|
<< " to file " << fName
|
|
<< abort(FatalError);
|
|
}
|
|
|
|
dirWatches_(watchFd) = dirWatchID;
|
|
dirFiles_(watchFd) = fName.name();
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
if (watchFd < lastMod_.size() && lastMod_[watchFd] != 0)
|
|
{
|
|
// Reuse of watchFd : should have lastMod set to 0.
|
|
FatalErrorIn("addWatch(const label, const fileName&)")
|
|
<< "Problem adding watch " << watchFd
|
|
<< " to file " << fName
|
|
<< abort(FatalError);
|
|
}
|
|
|
|
lastMod_(watchFd) = lastModified(fName);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
inline bool removeWatch(const label watchFd)
|
|
{
|
|
if (useInotify_)
|
|
{
|
|
if (inotifyFd_ < 0)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
dirWatches_[watchFd] = -1;
|
|
}
|
|
else
|
|
{
|
|
lastMod_[watchFd] = 0;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
};
|
|
//! @endcond
|
|
}
|
|
|
|
|
|
// * * * * * * * * * * * * * Private Member Functions * * * * * * * * * * * //
|
|
|
|
void Foam::fileMonitor::checkFiles() const
|
|
{
|
|
if (useInotify_)
|
|
{
|
|
#ifdef FOAM_USE_INOTIFY
|
|
// Large buffer for lots of events
|
|
char buffer[EVENT_BUF_LEN];
|
|
|
|
while (true)
|
|
{
|
|
struct timeval zeroTimeout = {0, 0};
|
|
|
|
//- Pre-allocated structure containing file descriptors
|
|
fd_set fdSet;
|
|
// Add notify descriptor to select fd_set
|
|
FD_ZERO(&fdSet);
|
|
FD_SET(watcher_->inotifyFd_, &fdSet);
|
|
|
|
int ready = select
|
|
(
|
|
watcher_->inotifyFd_+1, // num filedescriptors in fdSet
|
|
&fdSet, // fdSet with only inotifyFd
|
|
NULL, // No writefds
|
|
NULL, // No errorfds
|
|
&zeroTimeout // eNo timeout
|
|
);
|
|
|
|
if (ready < 0)
|
|
{
|
|
FatalErrorIn("fileMonitor::updateStates()")
|
|
<< "Problem in issuing select."
|
|
<< abort(FatalError);
|
|
}
|
|
else if (FD_ISSET(watcher_->inotifyFd_, &fdSet))
|
|
{
|
|
// Read events
|
|
ssize_t nBytes = read
|
|
(
|
|
watcher_->inotifyFd_,
|
|
buffer,
|
|
EVENT_BUF_LEN
|
|
);
|
|
|
|
if (nBytes < 0)
|
|
{
|
|
FatalErrorIn("fileMonitor::updateStates(const fileName&)")
|
|
<< "read of " << watcher_->inotifyFd_
|
|
<< " failed with " << label(nBytes)
|
|
<< abort(FatalError);
|
|
}
|
|
|
|
// Go through buffer, consuming events
|
|
int i = 0;
|
|
while (i < nBytes)
|
|
{
|
|
const struct inotify_event* inotifyEvent =
|
|
reinterpret_cast<const struct inotify_event*>
|
|
(
|
|
&buffer[i]
|
|
);
|
|
|
|
//Pout<< "watchFd:" << inotifyEvent->wd << nl
|
|
// << "mask:" << inotifyEvent->mask << nl
|
|
// << endl;
|
|
//Pout<< "file:" << fileName(inotifyEvent->name) << endl;
|
|
//Pout<< "len:" << inotifyEvent->len << endl;
|
|
|
|
if
|
|
(
|
|
(inotifyEvent->mask & IN_CLOSE_WRITE)
|
|
&& inotifyEvent->len
|
|
)
|
|
{
|
|
// Search for file
|
|
forAll(watcher_->dirWatches_, i)
|
|
{
|
|
label id = watcher_->dirWatches_[i];
|
|
if
|
|
(
|
|
id == inotifyEvent->wd
|
|
&& inotifyEvent->name == watcher_->dirFiles_[i]
|
|
)
|
|
{
|
|
// Correct directory and name
|
|
state_[i] = MODIFIED;
|
|
}
|
|
}
|
|
}
|
|
|
|
i += EVENT_SIZE + inotifyEvent->len;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// No data
|
|
return;
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
forAll(watcher_->lastMod_, watchFd)
|
|
{
|
|
time_t oldTime = watcher_->lastMod_[watchFd];
|
|
|
|
if (oldTime != 0)
|
|
{
|
|
const fileName& fName = watchFile_[watchFd];
|
|
time_t newTime = lastModified(fName);
|
|
|
|
if (newTime == 0)
|
|
{
|
|
state_[watchFd] = DELETED;
|
|
}
|
|
else
|
|
{
|
|
if (newTime > (oldTime + regIOobject::fileModificationSkew))
|
|
{
|
|
watcher_->lastMod_[watchFd] = newTime;
|
|
state_[watchFd] = MODIFIED;
|
|
}
|
|
else
|
|
{
|
|
state_[watchFd] = UNMODIFIED;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// * * * * * * * * * * * * * * * * Constructors * * * * * * * * * * * * * * //
|
|
|
|
|
|
Foam::fileMonitor::fileMonitor(const bool useInotify)
|
|
:
|
|
useInotify_(useInotify),
|
|
state_(20),
|
|
watchFile_(20),
|
|
freeWatchFds_(2),
|
|
watcher_(new fileMonitorWatcher(useInotify_, 20))
|
|
{}
|
|
|
|
|
|
// * * * * * * * * * * * * * * * * Destructor * * * * * * * * * * * * * * * //
|
|
|
|
Foam::fileMonitor::~fileMonitor()
|
|
{}
|
|
|
|
|
|
// * * * * * * * * * * * * * * * Member Functions * * * * * * * * * * * * * //
|
|
|
|
// Note: fName might not exist (on slaves if in master-only mode for
|
|
// regIOobject)
|
|
Foam::label Foam::fileMonitor::addWatch(const fileName& fName)
|
|
{
|
|
label watchFd;
|
|
|
|
label sz = freeWatchFds_.size();
|
|
|
|
if (sz)
|
|
{
|
|
watchFd = freeWatchFds_[sz-1];
|
|
freeWatchFds_.setSize(sz-1);
|
|
}
|
|
else
|
|
{
|
|
watchFd = state_.size();
|
|
}
|
|
|
|
watcher_->addWatch(watchFd, fName);
|
|
|
|
if (debug)
|
|
{
|
|
Pout<< "fileMonitor : added watch " << watchFd << " on file "
|
|
<< fName << endl;
|
|
}
|
|
|
|
if (watchFd < 0)
|
|
{
|
|
WarningIn("fileMonitor::addWatch(const fileName&)")
|
|
<< "could not add watch for file " << fName << endl;
|
|
}
|
|
else
|
|
{
|
|
state_(watchFd) = UNMODIFIED;
|
|
watchFile_(watchFd) = fName;
|
|
}
|
|
return watchFd;
|
|
}
|
|
|
|
|
|
bool Foam::fileMonitor::removeWatch(const label watchFd)
|
|
{
|
|
if (debug)
|
|
{
|
|
Pout<< "fileMonitor : removing watch " << watchFd << " on file "
|
|
<< watchFile_[watchFd] << endl;
|
|
}
|
|
|
|
freeWatchFds_.append(watchFd);
|
|
return watcher_->removeWatch(watchFd);
|
|
}
|
|
|
|
|
|
const Foam::fileName& Foam::fileMonitor::getFile(const label watchFd) const
|
|
{
|
|
return watchFile_[watchFd];
|
|
}
|
|
|
|
|
|
Foam::fileMonitor::fileState Foam::fileMonitor::getState(const label watchFd)
|
|
const
|
|
{
|
|
return state_[watchFd];
|
|
}
|
|
|
|
|
|
void Foam::fileMonitor::updateStates
|
|
(
|
|
const bool masterOnly,
|
|
const bool syncPar
|
|
) const
|
|
{
|
|
if (Pstream::master() || !masterOnly)
|
|
{
|
|
checkFiles();
|
|
}
|
|
|
|
if (syncPar)
|
|
{
|
|
// Pack current state (might be on master only)
|
|
PackedList<2> stats(state_.size(), MODIFIED);
|
|
if (Pstream::master() || !masterOnly)
|
|
{
|
|
forAll(state_, watchFd)
|
|
{
|
|
stats[watchFd] = static_cast<unsigned int>(state_[watchFd]);
|
|
}
|
|
}
|
|
|
|
|
|
// Save local state for warning message below
|
|
PackedList<2> thisProcStats;
|
|
if (!masterOnly)
|
|
{
|
|
thisProcStats = stats;
|
|
}
|
|
|
|
|
|
// Scatter or reduce to synchronise state
|
|
if (masterOnly)
|
|
{
|
|
// Scatter
|
|
if (stats.storage().size() == 1)
|
|
{
|
|
Pstream::scatter(stats.storage()[0]);
|
|
}
|
|
else
|
|
{
|
|
Pstream::listCombineScatter(stats.storage());
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Reduce
|
|
if (stats.storage().size() == 1)
|
|
{
|
|
// Optimisation valid for most cases.
|
|
reduce(stats.storage()[0], reduceFileStates());
|
|
}
|
|
else
|
|
{
|
|
Pstream::listCombineGather
|
|
(
|
|
stats.storage(),
|
|
combineReduceFileStates()
|
|
);
|
|
}
|
|
}
|
|
|
|
|
|
// Update local state
|
|
forAll(state_, watchFd)
|
|
{
|
|
if (masterOnly)
|
|
{
|
|
// No need to check for inconsistent state. Just assign.
|
|
unsigned int stat = stats[watchFd];
|
|
state_[watchFd] = fileState(stat);
|
|
}
|
|
else
|
|
{
|
|
// Check for inconsistent state before assigning.
|
|
if (thisProcStats[watchFd] != UNMODIFIED)
|
|
{
|
|
if (stats[watchFd] == UNMODIFIED)
|
|
{
|
|
WarningIn("fileMonitor::updateStates(const bool) const")
|
|
<< "Delaying reading " << watchFile_[watchFd]
|
|
<< " due to inconsistent "
|
|
"file time-stamps between processors"
|
|
<< endl;
|
|
}
|
|
else
|
|
{
|
|
unsigned int stat = stats[watchFd];
|
|
state_[watchFd] = fileState(stat);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void Foam::fileMonitor::setUnmodified(const label watchFd)
|
|
{
|
|
state_[watchFd] = UNMODIFIED;
|
|
|
|
if (!useInotify_)
|
|
{
|
|
watcher_->lastMod_[watchFd] = lastModified(watchFile_[watchFd]);
|
|
}
|
|
}
|
|
|
|
|
|
// ************************************************************************* //
|