This commit is contained in:
Jean-Francois Dockes 2019-03-14 15:18:03 +01:00
parent 6636d67f5b
commit 2606d78b18

View File

@ -40,14 +40,9 @@
*/ */
class RclMonitor { class RclMonitor {
public: public:
RclMonitor() RclMonitor() {}
: saved_errno(0) virtual ~RclMonitor() {}
{
}
virtual ~RclMonitor()
{
}
virtual bool addWatch(const string& path, bool isDir) = 0; virtual bool addWatch(const string& path, bool isDir) = 0;
virtual bool getEvent(RclMonEvent& ev, int msecs = -1) = 0; virtual bool getEvent(RclMonEvent& ev, int msecs = -1) = 0;
virtual bool ok() const = 0; virtual bool ok() const = 0;
@ -55,7 +50,7 @@ public:
virtual bool generatesExist() const = 0; virtual bool generatesExist() const = 0;
// Save significant errno after monitor calls // Save significant errno after monitor calls
int saved_errno; int saved_errno{0};
}; };
// Monitor factory. We only have one compiled-in kind at a time, no // Monitor factory. We only have one compiled-in kind at a time, no
@ -73,15 +68,15 @@ static RclMonitor *makeMonitor();
class WalkCB : public FsTreeWalkerCB { class WalkCB : public FsTreeWalkerCB {
public: public:
WalkCB(RclConfig *conf, RclMonitor *mon, RclMonEventQueue *queue, WalkCB(RclConfig *conf, RclMonitor *mon, RclMonEventQueue *queue,
FsTreeWalker& walker) FsTreeWalker& walker)
: m_config(conf), m_mon(mon), m_queue(queue), m_walker(walker) : m_config(conf), m_mon(mon), m_queue(queue), m_walker(walker)
{} {}
virtual ~WalkCB() {} virtual ~WalkCB() {}
virtual FsTreeWalker::Status virtual FsTreeWalker::Status
processone(const string &fn, const struct stat *st, processone(const string &fn, const struct stat *st,
FsTreeWalker::CbFlag flg) { FsTreeWalker::CbFlag flg) {
MONDEB("rclMonRcvRun: processone " << fn << " m_mon " << m_mon << MONDEB("rclMonRcvRun: processone " << fn << " m_mon " << m_mon <<
" m_mon->ok " << (m_mon ? m_mon->ok() : false) << std::endl); " m_mon->ok " << (m_mon ? m_mon->ok() : false) << std::endl);
if (flg == FsTreeWalker::FtwDirEnter || if (flg == FsTreeWalker::FtwDirEnter ||
@ -91,47 +86,47 @@ public:
m_walker.setSkippedNames(m_config->getSkippedNames()); m_walker.setSkippedNames(m_config->getSkippedNames());
} }
if (flg == FsTreeWalker::FtwDirEnter) { if (flg == FsTreeWalker::FtwDirEnter) {
// Create watch when entering directory, but first empty // Create watch when entering directory, but first empty
// whatever events we may already have on queue // whatever events we may already have on queue
while (m_queue->ok() && m_mon->ok()) { while (m_queue->ok() && m_mon->ok()) {
RclMonEvent ev; RclMonEvent ev;
if (m_mon->getEvent(ev, 0)) { if (m_mon->getEvent(ev, 0)) {
if (ev.m_etyp != RclMonEvent::RCLEVT_NONE) if (ev.m_etyp != RclMonEvent::RCLEVT_NONE)
m_queue->pushEvent(ev); m_queue->pushEvent(ev);
} else { } else {
MONDEB("rclMonRcvRun: no event pending\n"); MONDEB("rclMonRcvRun: no event pending\n");
break; break;
} }
} }
if (!m_mon || !m_mon->ok()) if (!m_mon || !m_mon->ok())
return FsTreeWalker::FtwError; return FsTreeWalker::FtwError;
// We do nothing special if addWatch fails for a reasonable reason // We do nothing special if addWatch fails for a reasonable reason
if (!m_mon->addWatch(fn, true)) { if (!m_mon->addWatch(fn, true)) {
if (m_mon->saved_errno != EACCES && if (m_mon->saved_errno != EACCES &&
m_mon->saved_errno != ENOENT) m_mon->saved_errno != ENOENT)
return FsTreeWalker::FtwError; return FsTreeWalker::FtwError;
} }
} else if (!m_mon->generatesExist() && } else if (!m_mon->generatesExist() &&
flg == FsTreeWalker::FtwRegular) { flg == FsTreeWalker::FtwRegular) {
// Have to synthetize events for regular files existence // Have to synthetize events for regular files existence
// at startup because the monitor does not do it // at startup because the monitor does not do it
// Note 2011-09-29: no sure this is actually needed. We just ran // Note 2011-09-29: no sure this is actually needed. We just ran
// an incremental indexing pass (before starting the // an incremental indexing pass (before starting the
// monitor). Why go over the files once more ? The only // monitor). Why go over the files once more ? The only
// reason I can see would be to catch modifications that // reason I can see would be to catch modifications that
// happen between the incremental and the start of // happen between the incremental and the start of
// monitoring ? There should be another way: maybe start // monitoring ? There should be another way: maybe start
// monitoring without actually handling events (just // monitoring without actually handling events (just
// queue), then run incremental then start handling // queue), then run incremental then start handling
// events ? But we also have to do it on a directory // events ? But we also have to do it on a directory
// move! So keep it // move! So keep it
RclMonEvent ev; RclMonEvent ev;
ev.m_path = fn; ev.m_path = fn;
ev.m_etyp = RclMonEvent::RCLEVT_MODIFY; ev.m_etyp = RclMonEvent::RCLEVT_MODIFY;
m_queue->pushEvent(ev); m_queue->pushEvent(ev);
} }
return FsTreeWalker::FtwOk; return FsTreeWalker::FtwOk;
} }
private: private:
@ -156,19 +151,19 @@ void *rclMonRcvRun(void *q)
// Create the fam/whatever interface object // Create the fam/whatever interface object
RclMonitor *mon; RclMonitor *mon;
if ((mon = makeMonitor()) == 0) { if ((mon = makeMonitor()) == 0) {
LOGERR("rclMonRcvRun: makeMonitor failed\n"); LOGERR("rclMonRcvRun: makeMonitor failed\n");
queue->setTerminate(); queue->setTerminate();
return 0; return 0;
} }
// Get top directories from config. Special monitor sublist if // Get top directories from config. Special monitor sublist if
// set, else full list. // set, else full list.
vector<string> tdl = lconfig.getTopdirs(true); vector<string> tdl = lconfig.getTopdirs(true);
if (tdl.empty()) { if (tdl.empty()) {
LOGERR("rclMonRcvRun:: top directory list (topdirs param.) not found " LOGERR("rclMonRcvRun:: top directory list (topdirs param.) not found "
"in configuration or topdirs list parse error"); "in configuration or topdirs list parse error");
queue->setTerminate(); queue->setTerminate();
return 0; return 0;
} }
// Walk the directory trees to add watches // Walk the directory trees to add watches
@ -176,15 +171,15 @@ void *rclMonRcvRun(void *q)
walker.setSkippedPaths(lconfig.getDaemSkippedPaths()); walker.setSkippedPaths(lconfig.getDaemSkippedPaths());
WalkCB walkcb(&lconfig, mon, queue, walker); WalkCB walkcb(&lconfig, mon, queue, walker);
for (auto it = tdl.begin(); it != tdl.end(); it++) { for (auto it = tdl.begin(); it != tdl.end(); it++) {
lconfig.setKeyDir(*it); lconfig.setKeyDir(*it);
// Adjust the follow symlinks options // Adjust the follow symlinks options
bool follow; bool follow;
if (lconfig.getConfParam("followLinks", &follow) && if (lconfig.getConfParam("followLinks", &follow) &&
follow) { follow) {
walker.setOpts(FsTreeWalker::FtwFollow); walker.setOpts(FsTreeWalker::FtwFollow);
} else { } else {
walker.setOpts(FsTreeWalker::FtwOptNone); walker.setOpts(FsTreeWalker::FtwOptNone);
} }
// We have to special-case regular files which are part of the topdirs // We have to special-case regular files which are part of the topdirs
// list because we the tree walker only adds watches for directories // list because we the tree walker only adds watches for directories
struct stat st; struct stat st;
@ -206,68 +201,68 @@ void *rclMonRcvRun(void *q)
if (!mon->addWatch(*it, false)) { if (!mon->addWatch(*it, false)) {
LOGERR("rclMonRcvRun: addWatch failed for " << *it << LOGERR("rclMonRcvRun: addWatch failed for " << *it <<
" errno " << mon->saved_errno << std::endl); " errno " << mon->saved_errno << std::endl);
} }
} }
} }
{ {
bool doweb = false; bool doweb = false;
lconfig.getConfParam("processwebqueue", &doweb); lconfig.getConfParam("processwebqueue", &doweb);
if (doweb) { if (doweb) {
string webqueuedir = lconfig.getWebQueueDir(); string webqueuedir = lconfig.getWebQueueDir();
if (!mon->addWatch(webqueuedir, true)) { if (!mon->addWatch(webqueuedir, true)) {
LOGERR("rclMonRcvRun: addwatch (webqueuedir) failed\n"); LOGERR("rclMonRcvRun: addwatch (webqueuedir) failed\n");
if (mon->saved_errno != EACCES && mon->saved_errno != ENOENT) if (mon->saved_errno != EACCES && mon->saved_errno != ENOENT)
goto terminate; goto terminate;
} }
} }
} }
// Forever wait for monitoring events and add them to queue: // Forever wait for monitoring events and add them to queue:
MONDEB("rclMonRcvRun: waiting for events. q->ok(): " << queue->ok() << MONDEB("rclMonRcvRun: waiting for events. q->ok(): " << queue->ok() <<
std::endl); std::endl);
while (queue->ok() && mon->ok()) { while (queue->ok() && mon->ok()) {
RclMonEvent ev; RclMonEvent ev;
// Note: I could find no way to get the select // Note: I could find no way to get the select
// call to return when a signal is delivered to the process // call to return when a signal is delivered to the process
// (it goes to the main thread, from which I tried to close or // (it goes to the main thread, from which I tried to close or
// write to the select fd, with no effect). So set a // write to the select fd, with no effect). So set a
// timeout so that an intr will be detected // timeout so that an intr will be detected
if (mon->getEvent(ev, 2000)) { if (mon->getEvent(ev, 2000)) {
// Don't push events for skipped files. This would get // Don't push events for skipped files. This would get
// filtered on the processing side anyway, but causes // filtered on the processing side anyway, but causes
// unnecessary wakeups and messages. Do not test // unnecessary wakeups and messages. Do not test
// skippedPaths here, this would be incorrect (because a // skippedPaths here, this would be incorrect (because a
// topdir can be under a skippedPath and this was handled // topdir can be under a skippedPath and this was handled
// while adding the watches). // while adding the watches).
lconfig.setKeyDir(path_getfather(ev.m_path)); lconfig.setKeyDir(path_getfather(ev.m_path));
walker.setSkippedNames(lconfig.getSkippedNames()); walker.setSkippedNames(lconfig.getSkippedNames());
if (walker.inSkippedNames(path_getsimple(ev.m_path))) if (walker.inSkippedNames(path_getsimple(ev.m_path)))
continue; continue;
if (ev.m_etyp == RclMonEvent::RCLEVT_DIRCREATE) { if (ev.m_etyp == RclMonEvent::RCLEVT_DIRCREATE) {
// Recursive addwatch: there may already be stuff // Recursive addwatch: there may already be stuff
// inside this directory. Ie: files were quickly // inside this directory. Ie: files were quickly
// created, or this is actually the target of a // created, or this is actually the target of a
// directory move. This is necessary for inotify, but // directory move. This is necessary for inotify, but
// it seems that fam/gamin is doing the job for us so // it seems that fam/gamin is doing the job for us so
// that we are generating double events here (no big // that we are generating double events here (no big
// deal as prc will sort/merge). // deal as prc will sort/merge).
LOGDEB("rclMonRcvRun: walking new dir " << ev.m_path << "\n"); LOGDEB("rclMonRcvRun: walking new dir " << ev.m_path << "\n");
if (walker.walk(ev.m_path, walkcb) != FsTreeWalker::FtwOk) { if (walker.walk(ev.m_path, walkcb) != FsTreeWalker::FtwOk) {
LOGERR("rclMonRcvRun: walking new dir " << ev.m_path << LOGERR("rclMonRcvRun: walking new dir " << ev.m_path <<
" : " << walker.getReason() << "\n"); " : " << walker.getReason() << "\n");
goto terminate; goto terminate;
} }
if (walker.getErrCnt() > 0) { if (walker.getErrCnt() > 0) {
LOGINFO("rclMonRcvRun: fs walker errors: " << LOGINFO("rclMonRcvRun: fs walker errors: " <<
walker.getReason() << "\n"); walker.getReason() << "\n");
} }
} }
if (ev.m_etyp != RclMonEvent::RCLEVT_NONE) if (ev.m_etyp != RclMonEvent::RCLEVT_NONE)
queue->pushEvent(ev); queue->pushEvent(ev);
} }
} }
terminate: terminate:
@ -284,12 +279,12 @@ bool eraseWatchSubTree(map<int, string>& idtopath, const string& top)
MONDEB("Clearing map for [" << top << "]\n"); MONDEB("Clearing map for [" << top << "]\n");
map<int,string>::iterator it = idtopath.begin(); map<int,string>::iterator it = idtopath.begin();
while (it != idtopath.end()) { while (it != idtopath.end()) {
if (it->second.find(top) == 0) { if (it->second.find(top) == 0) {
found = true; found = true;
idtopath.erase(it++); idtopath.erase(it++);
} else { } else {
it++; it++;
} }
} }
return found; return found;
} }
@ -321,8 +316,8 @@ private:
bool m_ok; bool m_ok;
FAMConnection m_conn; FAMConnection m_conn;
void close() { void close() {
FAMClose(&m_conn); FAMClose(&m_conn);
m_ok = false; m_ok = false;
} }
map<int,string> m_idtopath; map<int,string> m_idtopath;
const char *event_name(int code); const char *event_name(int code);
@ -356,8 +351,8 @@ RclFAM::RclFAM()
: m_ok(false) : m_ok(false)
{ {
if (FAMOpen2(&m_conn, "Recoll")) { if (FAMOpen2(&m_conn, "Recoll")) {
LOGERR("RclFAM::RclFAM: FAMOpen2 failed, errno " << errno << "\n"); LOGERR("RclFAM::RclFAM: FAMOpen2 failed, errno " << errno << "\n");
return; return;
} }
m_ok = true; m_ok = true;
} }
@ -365,7 +360,7 @@ RclFAM::RclFAM()
RclFAM::~RclFAM() RclFAM::~RclFAM()
{ {
if (ok()) if (ok())
FAMClose(&m_conn); FAMClose(&m_conn);
} }
static jmp_buf jbuf; static jmp_buf jbuf;
@ -376,7 +371,7 @@ static void onalrm(int sig)
bool RclFAM::addWatch(const string& path, bool isdir) bool RclFAM::addWatch(const string& path, bool isdir)
{ {
if (!ok()) if (!ok())
return false; return false;
bool ret = false; bool ret = false;
MONDEB("RclFAM::addWatch: adding " << path << std::endl); MONDEB("RclFAM::addWatch: adding " << path << std::endl);
@ -387,22 +382,22 @@ bool RclFAM::addWatch(const string& path, bool isdir)
// to unblock signals. SIGALRM is not used by the main thread, so at least // to unblock signals. SIGALRM is not used by the main thread, so at least
// ensure that we exit after gamin gets stuck. // ensure that we exit after gamin gets stuck.
if (setjmp(jbuf)) { if (setjmp(jbuf)) {
LOGERR("RclFAM::addWatch: timeout talking to FAM\n"); LOGERR("RclFAM::addWatch: timeout talking to FAM\n");
return false; return false;
} }
signal(SIGALRM, onalrm); signal(SIGALRM, onalrm);
alarm(20); alarm(20);
FAMRequest req; FAMRequest req;
if (isdir) { if (isdir) {
if (FAMMonitorDirectory(&m_conn, path.c_str(), &req, 0) != 0) { if (FAMMonitorDirectory(&m_conn, path.c_str(), &req, 0) != 0) {
LOGERR("RclFAM::addWatch: FAMMonitorDirectory failed\n"); LOGERR("RclFAM::addWatch: FAMMonitorDirectory failed\n");
goto out; goto out;
} }
} else { } else {
if (FAMMonitorFile(&m_conn, path.c_str(), &req, 0) != 0) { if (FAMMonitorFile(&m_conn, path.c_str(), &req, 0) != 0) {
LOGERR("RclFAM::addWatch: FAMMonitorFile failed\n"); LOGERR("RclFAM::addWatch: FAMMonitorFile failed\n");
goto out; goto out;
} }
} }
m_idtopath[req.reqnum] = path; m_idtopath[req.reqnum] = path;
ret = true; ret = true;
@ -417,7 +412,7 @@ out:
bool RclFAM::getEvent(RclMonEvent& ev, int msecs) bool RclFAM::getEvent(RclMonEvent& ev, int msecs)
{ {
if (!ok()) if (!ok())
return false; return false;
MONDEB("RclFAM::getEvent:\n"); MONDEB("RclFAM::getEvent:\n");
fd_set readfds; fd_set readfds;
@ -430,27 +425,27 @@ bool RclFAM::getEvent(RclMonEvent& ev, int msecs)
// a little timeout, because if we fail to retrieve enough events, // a little timeout, because if we fail to retrieve enough events,
// we risk deadlocking in addwatch() // we risk deadlocking in addwatch()
if (msecs == 0) if (msecs == 0)
msecs = 2; msecs = 2;
struct timeval timeout; struct timeval timeout;
if (msecs >= 0) { if (msecs >= 0) {
timeout.tv_sec = msecs / 1000; timeout.tv_sec = msecs / 1000;
timeout.tv_usec = (msecs % 1000) * 1000; timeout.tv_usec = (msecs % 1000) * 1000;
} }
int ret; int ret;
if ((ret=select(fam_fd+1, &readfds, 0, 0, msecs >= 0 ? &timeout : 0)) < 0) { if ((ret=select(fam_fd+1, &readfds, 0, 0, msecs >= 0 ? &timeout : 0)) < 0) {
LOGERR("RclFAM::getEvent: select failed, errno " << errno << "\n"); LOGERR("RclFAM::getEvent: select failed, errno " << errno << "\n");
close(); close();
return false; return false;
} else if (ret == 0) { } else if (ret == 0) {
// timeout // timeout
MONDEB("RclFAM::getEvent: select timeout\n"); MONDEB("RclFAM::getEvent: select timeout\n");
return false; return false;
} }
MONDEB("RclFAM::getEvent: select returned " << ret << std::endl); MONDEB("RclFAM::getEvent: select returned " << ret << std::endl);
if (!FD_ISSET(fam_fd, &readfds)) if (!FD_ISSET(fam_fd, &readfds))
return false; return false;
// ?? 2011/03/15 gamin v0.1.10. There is initially a single null // ?? 2011/03/15 gamin v0.1.10. There is initially a single null
// byte on the connection so the first select always succeeds. If // byte on the connection so the first select always succeeds. If
@ -458,25 +453,25 @@ bool RclFAM::getEvent(RclMonEvent& ev, int msecs)
// around the issue, but we did not need this in the past and this // around the issue, but we did not need this in the past and this
// is most weird. // is most weird.
if (FAMPending(&m_conn) <= 0) { if (FAMPending(&m_conn) <= 0) {
MONDEB("RclFAM::getEvent: FAMPending says no events\n"); MONDEB("RclFAM::getEvent: FAMPending says no events\n");
return false; return false;
} }
MONDEB("RclFAM::getEvent: call FAMNextEvent\n"); MONDEB("RclFAM::getEvent: call FAMNextEvent\n");
FAMEvent fe; FAMEvent fe;
if (FAMNextEvent(&m_conn, &fe) < 0) { if (FAMNextEvent(&m_conn, &fe) < 0) {
LOGERR("RclFAM::getEvent: FAMNextEvent: errno " << errno << "\n"); LOGERR("RclFAM::getEvent: FAMNextEvent: errno " << errno << "\n");
close(); close();
return false; return false;
} }
MONDEB("RclFAM::getEvent: FAMNextEvent returned\n"); MONDEB("RclFAM::getEvent: FAMNextEvent returned\n");
map<int,string>::const_iterator it; map<int,string>::const_iterator it;
if ((!path_isabsolute(fe.filename)) && if ((!path_isabsolute(fe.filename)) &&
(it = m_idtopath.find(fe.fr.reqnum)) != m_idtopath.end()) { (it = m_idtopath.find(fe.fr.reqnum)) != m_idtopath.end()) {
ev.m_path = path_cat(it->second, fe.filename); ev.m_path = path_cat(it->second, fe.filename);
} else { } else {
ev.m_path = fe.filename; ev.m_path = fe.filename;
} }
MONDEB("RclFAM::getEvent: " << event_name(fe.code) < " " << MONDEB("RclFAM::getEvent: " << event_name(fe.code) < " " <<
@ -484,39 +479,39 @@ bool RclFAM::getEvent(RclMonEvent& ev, int msecs)
switch (fe.code) { switch (fe.code) {
case FAMCreated: case FAMCreated:
if (path_isdir(ev.m_path)) { if (path_isdir(ev.m_path)) {
ev.m_etyp = RclMonEvent::RCLEVT_DIRCREATE; ev.m_etyp = RclMonEvent::RCLEVT_DIRCREATE;
break; break;
} }
/* FALLTHROUGH */ /* FALLTHROUGH */
case FAMChanged: case FAMChanged:
case FAMExists: case FAMExists:
// Let the other side sort out the status of this file vs the db // Let the other side sort out the status of this file vs the db
ev.m_etyp = RclMonEvent::RCLEVT_MODIFY; ev.m_etyp = RclMonEvent::RCLEVT_MODIFY;
break; break;
case FAMMoved: case FAMMoved:
case FAMDeleted: case FAMDeleted:
ev.m_etyp = RclMonEvent::RCLEVT_DELETE; ev.m_etyp = RclMonEvent::RCLEVT_DELETE;
// We would like to signal a directory here to enable cleaning // We would like to signal a directory here to enable cleaning
// the subtree (on a dir move), but can't test the actual file // the subtree (on a dir move), but can't test the actual file
// which is gone, and fam doesn't tell us if it's a dir or reg. // which is gone, and fam doesn't tell us if it's a dir or reg.
// Let's rely on the fact that a directory should be watched // Let's rely on the fact that a directory should be watched
if (eraseWatchSubTree(m_idtopath, ev.m_path)) if (eraseWatchSubTree(m_idtopath, ev.m_path))
ev.m_etyp |= RclMonEvent::RCLEVT_ISDIR; ev.m_etyp |= RclMonEvent::RCLEVT_ISDIR;
break; break;
case FAMStartExecuting: case FAMStartExecuting:
case FAMStopExecuting: case FAMStopExecuting:
case FAMAcknowledge: case FAMAcknowledge:
case FAMEndExist: case FAMEndExist:
default: default:
// Have to return something, this is different from an empty queue, // Have to return something, this is different from an empty queue,
// esp if we are trying to empty it... // esp if we are trying to empty it...
if (fe.code != FAMEndExist) if (fe.code != FAMEndExist)
LOGDEB("RclFAM::getEvent: got other event " << fe.code << "!\n"); LOGDEB("RclFAM::getEvent: got other event " << fe.code << "!\n");
ev.m_etyp = RclMonEvent::RCLEVT_NONE; ev.m_etyp = RclMonEvent::RCLEVT_NONE;
break; break;
} }
return true; return true;
} }
@ -532,18 +527,18 @@ bool RclFAM::getEvent(RclMonEvent& ev, int msecs)
class RclIntf : public RclMonitor { class RclIntf : public RclMonitor {
public: public:
RclIntf() RclIntf()
: m_ok(false), m_fd(-1), m_evp(0), m_ep(0) : m_ok(false), m_fd(-1), m_evp(0), m_ep(0)
{ {
if ((m_fd = inotify_init()) < 0) { if ((m_fd = inotify_init()) < 0) {
LOGERR("RclIntf:: inotify_init failed, errno " << errno << "\n"); LOGERR("RclIntf:: inotify_init failed, errno " << errno << "\n");
return; return;
} }
m_ok = true; m_ok = true;
} }
virtual ~RclIntf() virtual ~RclIntf()
{ {
close(); close();
} }
virtual bool addWatch(const string& path, bool isdir); virtual bool addWatch(const string& path, bool isdir);
virtual bool getEvent(RclMonEvent& ev, int msecs = -1); virtual bool getEvent(RclMonEvent& ev, int msecs = -1);
@ -560,11 +555,11 @@ private:
char *m_ep; // Pointer to end of events char *m_ep; // Pointer to end of events
const char *event_name(int code); const char *event_name(int code);
void close() { void close() {
if (m_fd >= 0) { if (m_fd >= 0) {
::close(m_fd); ::close(m_fd);
m_fd = -1; m_fd = -1;
} }
m_ok = false; m_ok = false;
} }
}; };
@ -590,42 +585,42 @@ const char *RclIntf::event_name(int code)
case IN_Q_OVERFLOW: return "IN_Q_OVERFLOW"; case IN_Q_OVERFLOW: return "IN_Q_OVERFLOW";
case IN_IGNORED: return "IN_IGNORED"; case IN_IGNORED: return "IN_IGNORED";
default: { default: {
static char msg[50]; static char msg[50];
sprintf(msg, "Unknown event 0x%x", code); sprintf(msg, "Unknown event 0x%x", code);
return msg; return msg;
} }
}; };
} }
bool RclIntf::addWatch(const string& path, bool) bool RclIntf::addWatch(const string& path, bool)
{ {
if (!ok()) if (!ok())
return false; return false;
MONDEB("RclIntf::addWatch: adding " << path << std::endl); MONDEB("RclIntf::addWatch: adding " << path << std::endl);
// CLOSE_WRITE is covered through MODIFY. CREATE is needed for mkdirs // CLOSE_WRITE is covered through MODIFY. CREATE is needed for mkdirs
uint32_t mask = IN_MODIFY | IN_CREATE uint32_t mask = IN_MODIFY | IN_CREATE
| IN_MOVED_FROM | IN_MOVED_TO | IN_DELETE | IN_MOVED_FROM | IN_MOVED_TO | IN_DELETE
// IN_ATTRIB used to be not needed to receive extattr // IN_ATTRIB used to be not needed to receive extattr
// modification events, which was a bit weird because only ctime is // modification events, which was a bit weird because only ctime is
// set, and now it is... // set, and now it is...
| IN_ATTRIB | IN_ATTRIB
#ifdef IN_DONT_FOLLOW #ifdef IN_DONT_FOLLOW
| IN_DONT_FOLLOW | IN_DONT_FOLLOW
#endif #endif
#ifdef IN_EXCL_UNLINK #ifdef IN_EXCL_UNLINK
| IN_EXCL_UNLINK | IN_EXCL_UNLINK
#endif #endif
; ;
int wd; int wd;
if ((wd = inotify_add_watch(m_fd, path.c_str(), mask)) < 0) { if ((wd = inotify_add_watch(m_fd, path.c_str(), mask)) < 0) {
saved_errno = errno; saved_errno = errno;
LOGERR("RclIntf::addWatch: inotify_add_watch failed. errno " << LOGERR("RclIntf::addWatch: inotify_add_watch failed. errno " <<
saved_errno << "\n"); saved_errno << "\n");
if (errno == ENOSPC) { if (errno == ENOSPC) {
LOGERR("RclIntf::addWatch: ENOSPC error may mean that you should " LOGERR("RclIntf::addWatch: ENOSPC error may mean that you should "
"increase the inotify kernel constants. See inotify(7)\n"); "increase the inotify kernel constants. See inotify(7)\n");
} }
return false; return false;
} }
m_idtopath[wd] = path; m_idtopath[wd] = path;
return true; return true;
@ -636,103 +631,103 @@ bool RclIntf::addWatch(const string& path, bool)
bool RclIntf::getEvent(RclMonEvent& ev, int msecs) bool RclIntf::getEvent(RclMonEvent& ev, int msecs)
{ {
if (!ok()) if (!ok())
return false; return false;
ev.m_etyp = RclMonEvent::RCLEVT_NONE; ev.m_etyp = RclMonEvent::RCLEVT_NONE;
MONDEB("RclIntf::getEvent:\n"); MONDEB("RclIntf::getEvent:\n");
if (m_evp == 0) { if (m_evp == 0) {
fd_set readfds; fd_set readfds;
FD_ZERO(&readfds); FD_ZERO(&readfds);
FD_SET(m_fd, &readfds); FD_SET(m_fd, &readfds);
struct timeval timeout; struct timeval timeout;
if (msecs >= 0) { if (msecs >= 0) {
timeout.tv_sec = msecs / 1000; timeout.tv_sec = msecs / 1000;
timeout.tv_usec = (msecs % 1000) * 1000; timeout.tv_usec = (msecs % 1000) * 1000;
} }
int ret; int ret;
MONDEB("RclIntf::getEvent: select\n"); MONDEB("RclIntf::getEvent: select\n");
if ((ret = select(m_fd + 1, &readfds, 0, 0, msecs >= 0 ? &timeout : 0)) if ((ret = select(m_fd + 1, &readfds, 0, 0, msecs >= 0 ? &timeout : 0))
< 0) { < 0) {
LOGERR("RclIntf::getEvent: select failed, errno " << errno << "\n"); LOGERR("RclIntf::getEvent: select failed, errno " << errno << "\n");
close(); close();
return false; return false;
} else if (ret == 0) { } else if (ret == 0) {
MONDEB("RclIntf::getEvent: select timeout\n"); MONDEB("RclIntf::getEvent: select timeout\n");
// timeout // timeout
return false; return false;
} }
MONDEB("RclIntf::getEvent: select returned\n"); MONDEB("RclIntf::getEvent: select returned\n");
if (!FD_ISSET(m_fd, &readfds)) if (!FD_ISSET(m_fd, &readfds))
return false; return false;
int rret; int rret;
if ((rret=read(m_fd, m_evbuf, sizeof(m_evbuf))) <= 0) { if ((rret=read(m_fd, m_evbuf, sizeof(m_evbuf))) <= 0) {
LOGERR("RclIntf::getEvent: read failed, " << sizeof(m_evbuf) << LOGERR("RclIntf::getEvent: read failed, " << sizeof(m_evbuf) <<
"->" << rret << " errno " << errno << "\n"); "->" << rret << " errno " << errno << "\n");
close(); close();
return false; return false;
} }
m_evp = m_evbuf; m_evp = m_evbuf;
m_ep = m_evbuf + rret; m_ep = m_evbuf + rret;
} }
struct inotify_event *evp = (struct inotify_event *)m_evp; struct inotify_event *evp = (struct inotify_event *)m_evp;
m_evp += sizeof(struct inotify_event); m_evp += sizeof(struct inotify_event);
if (evp->len > 0) if (evp->len > 0)
m_evp += evp->len; m_evp += evp->len;
if (m_evp >= m_ep) if (m_evp >= m_ep)
m_evp = m_ep = 0; m_evp = m_ep = 0;
map<int,string>::const_iterator it; map<int,string>::const_iterator it;
if ((it = m_idtopath.find(evp->wd)) == m_idtopath.end()) { if ((it = m_idtopath.find(evp->wd)) == m_idtopath.end()) {
LOGERR("RclIntf::getEvent: unknown wd " << evp->wd << "\n"); LOGERR("RclIntf::getEvent: unknown wd " << evp->wd << "\n");
return true; return true;
} }
ev.m_path = it->second; ev.m_path = it->second;
if (evp->len > 0) { if (evp->len > 0) {
ev.m_path = path_cat(ev.m_path, evp->name); ev.m_path = path_cat(ev.m_path, evp->name);
} }
MONDEB("RclIntf::getEvent: " << event_name(evp->mask) << " " << MONDEB("RclIntf::getEvent: " << event_name(evp->mask) << " " <<
ev.m_path << std::endl); ev.m_path << std::endl);
if ((evp->mask & IN_MOVED_FROM) && (evp->mask & IN_ISDIR)) { if ((evp->mask & IN_MOVED_FROM) && (evp->mask & IN_ISDIR)) {
// We get this when a directory is renamed. Erase the subtree // We get this when a directory is renamed. Erase the subtree
// entries in the map. The subsequent MOVED_TO will recreate // entries in the map. The subsequent MOVED_TO will recreate
// them. This is probably not needed because the watches // them. This is probably not needed because the watches
// actually still exist in the kernel, so that the wds // actually still exist in the kernel, so that the wds
// returned by future addwatches will be the old ones, and the // returned by future addwatches will be the old ones, and the
// map will be updated in place. But still, this feels safer // map will be updated in place. But still, this feels safer
eraseWatchSubTree(m_idtopath, ev.m_path); eraseWatchSubTree(m_idtopath, ev.m_path);
} }
// IN_ATTRIB used to be not needed, but now it is // IN_ATTRIB used to be not needed, but now it is
if (evp->mask & (IN_MODIFY|IN_ATTRIB)) { if (evp->mask & (IN_MODIFY|IN_ATTRIB)) {
ev.m_etyp = RclMonEvent::RCLEVT_MODIFY; ev.m_etyp = RclMonEvent::RCLEVT_MODIFY;
} else if (evp->mask & (IN_DELETE | IN_MOVED_FROM)) { } else if (evp->mask & (IN_DELETE | IN_MOVED_FROM)) {
ev.m_etyp = RclMonEvent::RCLEVT_DELETE; ev.m_etyp = RclMonEvent::RCLEVT_DELETE;
if (evp->mask & IN_ISDIR) if (evp->mask & IN_ISDIR)
ev.m_etyp |= RclMonEvent::RCLEVT_ISDIR; ev.m_etyp |= RclMonEvent::RCLEVT_ISDIR;
} else if (evp->mask & (IN_CREATE | IN_MOVED_TO)) { } else if (evp->mask & (IN_CREATE | IN_MOVED_TO)) {
if (evp->mask & IN_ISDIR) { if (evp->mask & IN_ISDIR) {
ev.m_etyp = RclMonEvent::RCLEVT_DIRCREATE; ev.m_etyp = RclMonEvent::RCLEVT_DIRCREATE;
} else { } else {
// We used to return null event because we would get a // We used to return null event because we would get a
// modify event later, but it seems not to be the case any // modify event later, but it seems not to be the case any
// more (10-2011). So generate MODIFY event // more (10-2011). So generate MODIFY event
ev.m_etyp = RclMonEvent::RCLEVT_MODIFY; ev.m_etyp = RclMonEvent::RCLEVT_MODIFY;
} }
} else if (evp->mask & (IN_IGNORED)) { } else if (evp->mask & (IN_IGNORED)) {
if (!m_idtopath.erase(evp->wd)) { if (!m_idtopath.erase(evp->wd)) {
LOGDEB0("Got IGNORE event for unknown watch\n"); LOGDEB0("Got IGNORE event for unknown watch\n");
} else { } else {
eraseWatchSubTree(m_idtopath, ev.m_path); eraseWatchSubTree(m_idtopath, ev.m_path);
} }
} else { } else {
LOGDEB("RclIntf::getEvent: unhandled event " << event_name(evp->mask) << LOGDEB("RclIntf::getEvent: unhandled event " << event_name(evp->mask) <<
" " << evp->mask << " " << ev.m_path << "\n"); " " << evp->mask << " " << ev.m_path << "\n");
return true; return true;
} }
return true; return true;
} }
@ -757,4 +752,3 @@ static RclMonitor *makeMonitor()
return 0; return 0;
} }
#endif // RCL_MONITOR #endif // RCL_MONITOR