Parent: [e56b28] (diff)

Child: [56fe54] (diff)

Download this file

rclmonrcv.cpp    658 lines (597 with data), 19.8 kB

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
#include "autoconfig.h"
#ifdef RCL_MONITOR
/* Copyright (C) 2006 J.F.Dockes
* This program 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 2 of the License, or
* (at your option) any later version.
*
* This program 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 this program; if not, write to the
* Free Software Foundation, Inc.,
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
#include <unistd.h>
#include <sys/stat.h>
#include <errno.h>
#include <cstdio>
#include <cstring>
#include "debuglog.h"
#include "rclmon.h"
#include "rclinit.h"
#include "fstreewalk.h"
#include "pathut.h"
/**
* Recoll real time monitor event receiver. This file has code to interface
* to FAM or inotify and place events on the event queue.
*/
/** A small virtual interface for monitors. Probably suitable to let
either of fam/gamin or raw imonitor hide behind */
class RclMonitor {
public:
RclMonitor(){}
virtual ~RclMonitor() {}
virtual bool addWatch(const string& path, bool isDir) = 0;
virtual bool getEvent(RclMonEvent& ev, int secs = -1) = 0;
virtual bool ok() const = 0;
// Does this monitor generate 'exist' events at startup?
virtual bool generatesExist() const = 0;
};
// Monitor factory. We only have one compiled-in kind at a time, no
// need for a 'kind' parameter
static RclMonitor *makeMonitor();
/** This class is a callback for the file system tree walker
class. The callback method alternatively creates the directory
watches and flushes the event queue (to avoid a possible overflow
while we create the watches)*/
class WalkCB : public FsTreeWalkerCB {
public:
WalkCB(RclConfig *conf, RclMonitor *mon, RclMonEventQueue *queue,
FsTreeWalker& walker)
: m_config(conf), m_mon(mon), m_queue(queue), m_walker(walker)
{}
virtual ~WalkCB()
{}
virtual FsTreeWalker::Status
processone(const string &fn, const struct stat *st,
FsTreeWalker::CbFlag flg)
{
MONDEB(("rclMonRcvRun: processone %s m_mon %p m_mon->ok %d\n",
fn.c_str(), m_mon, m_mon?m_mon->ok():0));
if (flg == FsTreeWalker::FtwDirEnter ||
flg == FsTreeWalker::FtwDirReturn) {
m_config->setKeyDir(fn);
// Set up skipped patterns for this subtree.
m_walker.setSkippedNames(m_config->getSkippedNames());
}
if (flg == FsTreeWalker::FtwDirEnter) {
// Create watch when entering directory, but first empty
// whatever events we may already have on queue
while (m_queue->ok() && m_mon->ok()) {
RclMonEvent ev;
if (m_mon->getEvent(ev, 0)) {
if (ev.m_etyp != RclMonEvent::RCLEVT_NONE)
m_queue->pushEvent(ev);
} else {
MONDEB(("rclMonRcvRun: no event pending\n"));
break;
}
}
if (!m_mon || !m_mon->ok() || !m_mon->addWatch(fn, true))
return FsTreeWalker::FtwError;
} else if (!m_mon->generatesExist() &&
flg == FsTreeWalker::FtwRegular) {
// Have to synthetize events for regular files existence
// at startup because the monitor does not do it
// Note 2011-09-29: no sure this is actually needed. We just ran
// an incremental indexing pass (before starting the
// monitor). Why go over the files once more ? The only
// reason I can see would be to catch modifications that
// happen between the incremental and the start of
// monitoring ? There should be another way: maybe start
// monitoring without actually handling events (just
// queue), then run incremental then start handling
// events ? But we also have to do it on a directory
// move! So keep it
RclMonEvent ev;
ev.m_path = fn;
ev.m_etyp = RclMonEvent::RCLEVT_MODIFY;
m_queue->pushEvent(ev);
}
return FsTreeWalker::FtwOk;
}
private:
RclConfig *m_config;
RclMonitor *m_mon;
RclMonEventQueue *m_queue;
FsTreeWalker& m_walker;
};
/** Main thread routine: create watches, then forever wait for and queue events */
void *rclMonRcvRun(void *q)
{
RclMonEventQueue *queue = (RclMonEventQueue *)q;
LOGDEB(("rclMonRcvRun: running\n"));
recoll_threadinit();
// Create the fam/whatever interface object
RclMonitor *mon;
if ((mon = makeMonitor()) == 0) {
LOGERR(("rclMonRcvRun: makeMonitor failed\n"));
queue->setTerminate();
return 0;
}
// Make a local copy of the configuration as it doesn't like
// concurrent accesses. It's ok to copy it here as the other
// thread will not work before we have sent events.
RclConfig lconfig(*queue->getConfig());
// Get top directories from config
list<string> tdl = lconfig.getTopdirs();
if (tdl.empty()) {
LOGERR(("rclMonRcvRun:: top directory list (topdirs param.) not"
"found in config or Directory list parse error"));
queue->setTerminate();
return 0;
}
// Walk the directory trees to add watches
FsTreeWalker walker;
walker.setSkippedPaths(lconfig.getDaemSkippedPaths());
WalkCB walkcb(&lconfig, mon, queue, walker);
for (list<string>::iterator it = tdl.begin(); it != tdl.end(); it++) {
lconfig.setKeyDir(*it);
// Adjust the follow symlinks options
bool follow;
if (lconfig.getConfParam("followLinks", &follow) &&
follow) {
walker.setOpts(FsTreeWalker::FtwFollow);
} else {
walker.setOpts(FsTreeWalker::FtwOptNone);
}
LOGDEB(("rclMonRcvRun: walking %s\n", it->c_str()));
walker.walk(*it, walkcb);
}
bool dobeagle = false;
lconfig.getConfParam("processbeaglequeue", &dobeagle);
if (dobeagle) {
string beaglequeuedir;
if (!lconfig.getConfParam("beaglequeuedir", beaglequeuedir))
beaglequeuedir = path_tildexpand("~/.beagle/ToIndex/");
mon->addWatch(beaglequeuedir, true);
}
// Forever wait for monitoring events and add them to queue:
MONDEB(("rclMonRcvRun: waiting for events. q->ok() %d\n", queue->ok()));
while (queue->ok() && mon->ok()) {
RclMonEvent ev;
// Note: I could find no way to get the select
// call to return when a signal is delivered to the process
// (it goes to the main thread, from which I tried to close or
// write to the select fd, with no effect). So set a
// timeout so that an intr will be detected
if (mon->getEvent(ev, 2)) {
if (ev.m_etyp == RclMonEvent::RCLEVT_DIRCREATE) {
// Recursive addwatch: there may already be stuff
// inside this directory. Ie: files were quickly
// created, or this is actually the target of a
// directory move. This is necessary for inotify, but
// it seems that fam/gamin is doing the job for us so
// that we are generating double events here (no big
// deal as prc will sort/merge).
if (!walker.inSkippedNames(path_getsimple(ev.m_path)) &&
!walker.inSkippedPaths(ev.m_path)) {
LOGDEB(("rclMonRcvRun: walking new dir %s\n",
ev.m_path.c_str()));
walker.walk(ev.m_path, walkcb);
}
}
if (ev.m_etyp != RclMonEvent::RCLEVT_NONE)
queue->pushEvent(ev);
}
}
queue->setTerminate();
LOGINFO(("rclMonRcvRun: monrcv thread routine returning\n"));
return 0;
}
// Utility routine used by both the fam/gamin and inotify versions to get
// rid of the id-path translation for a moved dir
bool eraseWatchSubTree(map<int, string>& idtopath, const string& top)
{
bool found = false;
LOGDEB0(("Clearing map for [%s]\n", top.c_str()));
map<int,string>::iterator it = idtopath.begin();
while (it != idtopath.end()) {
if (it->second.find(top) == 0) {
found = true;
idtopath.erase(it++);
} else {
it++;
}
}
return found;
}
// We dont compile both the inotify and the fam interface and inotify
// has preference
#ifndef RCL_USE_INOTIFY
#ifdef RCL_USE_FAM
//////////////////////////////////////////////////////////////////////////
/** Fam/gamin -based monitor class */
#include <fam.h>
#include <sys/select.h>
/** FAM based monitor class. We have to keep a record of FAM watch
request numbers to directory names as the event only contain the
request number and file name, not the full path */
class RclFAM : public RclMonitor {
public:
RclFAM();
virtual ~RclFAM();
virtual bool addWatch(const string& path, bool isdir);
virtual bool getEvent(RclMonEvent& ev, int secs = -1);
bool ok() const {return m_ok;}
virtual bool generatesExist() const {return true;}
private:
bool m_ok;
FAMConnection m_conn;
void close() {
FAMClose(&m_conn);
m_ok = false;
}
map<int,string> m_idtopath;
const char *event_name(int code);
};
// Translate event code to string (debug)
const char *RclFAM::event_name(int code)
{
static const char *famevent[] = {
"",
"FAMChanged",
"FAMDeleted",
"FAMStartExecuting",
"FAMStopExecuting",
"FAMCreated",
"FAMMoved",
"FAMAcknowledge",
"FAMExists",
"FAMEndExist"
};
static char unknown_event[30];
if (code < FAMChanged || code > FAMEndExist) {
sprintf(unknown_event, "unknown (%d)", code);
return unknown_event;
}
return famevent[code];
}
RclFAM::RclFAM()
: m_ok(false)
{
if (FAMOpen2(&m_conn, "Recoll")) {
LOGERR(("RclFAM::RclFAM: FAMOpen2 failed, errno %d\n", errno));
return;
}
m_ok = true;
}
RclFAM::~RclFAM()
{
if (ok())
FAMClose(&m_conn);
}
bool RclFAM::addWatch(const string& path, bool isdir)
{
if (!ok())
return false;
MONDEB(("RclFAM::addWatch: adding %s\n", path.c_str()));
FAMRequest req;
if (isdir) {
if (FAMMonitorDirectory(&m_conn, path.c_str(), &req, 0) != 0) {
LOGERR(("RclFAM::addWatch: FAMMonitorDirectory failed\n"));
return false;
}
} else {
if (FAMMonitorFile(&m_conn, path.c_str(), &req, 0) != 0) {
LOGERR(("RclFAM::addWatch: FAMMonitorFile failed\n"));
return false;
}
}
m_idtopath[req.reqnum] = path;
return true;
}
// Note: return false only for queue empty or error
// Return EVT_NONE for bad event to keep queue processing going
bool RclFAM::getEvent(RclMonEvent& ev, int secs)
{
if (!ok())
return false;
MONDEB(("RclFAM::getEvent:\n"));
fd_set readfds;
int fam_fd = FAMCONNECTION_GETFD(&m_conn);
FD_ZERO(&readfds);
FD_SET(fam_fd, &readfds);
MONDEB(("RclFAM::getEvent: select. fam_fd is %d\n", fam_fd));
struct timeval timeout;
if (secs >= 0) {
memset(&timeout, 0, sizeof(timeout));
timeout.tv_sec = secs;
}
int ret;
if ((ret=select(fam_fd+1, &readfds, 0, 0, secs >= 0 ? &timeout : 0)) < 0) {
LOGERR(("RclFAM::getEvent: select failed, errno %d\n", errno));
close();
return false;
} else if (ret == 0) {
// timeout
MONDEB(("RclFAM::getEvent: select timeout\n"));
return false;
}
MONDEB(("RclFAM::getEvent: select returned %d\n", ret));
if (!FD_ISSET(fam_fd, &readfds))
return false;
// ?? 2011/03/15 gamin v0.1.10. There is initially a single null
// byte on the connection so the first select always succeeds. If
// we then call FAMNextEvent we stall. Using FAMPending works
// around the issue, but we did not need this in the past and this
// is most weird.
if (FAMPending(&m_conn) <= 0) {
MONDEB(("RclFAM::getEvent: FAMPending says no events\n"));
return false;
}
MONDEB(("RclFAM::getEvent: call FAMNextEvent\n"));
FAMEvent fe;
if (FAMNextEvent(&m_conn, &fe) < 0) {
LOGERR(("RclFAM::getEvent: FAMNextEvent failed, errno %d\n", errno));
close();
return false;
}
MONDEB(("RclFAM::getEvent: FAMNextEvent returned\n"));
map<int,string>::const_iterator it;
if ((fe.filename[0] != '/') &&
(it = m_idtopath.find(fe.fr.reqnum)) != m_idtopath.end()) {
ev.m_path = path_cat(it->second, fe.filename);
} else {
ev.m_path = fe.filename;
}
MONDEB(("RclFAM::getEvent: %-12s %s\n",
event_name(fe.code), ev.m_path.c_str()));
switch (fe.code) {
case FAMCreated:
if (path_isdir(ev.m_path)) {
ev.m_etyp = RclMonEvent::RCLEVT_DIRCREATE;
break;
}
/* FALLTHROUGH */
case FAMChanged:
case FAMExists:
// Let the other side sort out the status of this file vs the db
ev.m_etyp = RclMonEvent::RCLEVT_MODIFY;
break;
case FAMMoved:
case FAMDeleted:
ev.m_etyp = RclMonEvent::RCLEVT_DELETE;
// We would like to signal a directory here to enable cleaning
// the subtree (on a dir move), but can't test the actual file
// which is gone. Let's rely on the fact that a directory
// should be watched
if (eraseWatchSubTree(m_idtopath, ev.m_path))
ev.m_etyp |= RclMonEvent::RCLEVT_ISDIR;
break;
case FAMStartExecuting:
case FAMStopExecuting:
case FAMAcknowledge:
case FAMEndExist:
default:
// Have to return something, this is different from an empty queue,
// esp if we are trying to empty it...
if (fe.code != FAMEndExist)
LOGDEB(("RclFAM::getEvent: got other event %d!\n", fe.code));
ev.m_etyp = RclMonEvent::RCLEVT_NONE;
break;
}
return true;
}
#endif // RCL_USE_FAM
#endif // ! INOTIFY
#ifdef RCL_USE_INOTIFY
//////////////////////////////////////////////////////////////////////////
/** Inotify-based monitor class */
#include <sys/inotify.h>
#include <sys/select.h>
class RclIntf : public RclMonitor {
public:
RclIntf()
: m_ok(false), m_fd(-1), m_evp(0), m_ep(0)
{
if ((m_fd = inotify_init()) < 0) {
LOGERR(("RclIntf:: inotify_init failed, errno %d\n", errno));
return;
}
m_ok = true;
}
virtual ~RclIntf()
{
close();
}
virtual bool addWatch(const string& path, bool isdir);
virtual bool getEvent(RclMonEvent& ev, int secs = -1);
bool ok() const {return m_ok;}
virtual bool generatesExist() const {return false;}
private:
bool m_ok;
int m_fd;
map<int,string> m_idtopath; // Watch descriptor to name
#define EVBUFSIZE (32*1024)
char m_evbuf[EVBUFSIZE]; // Event buffer
char *m_evp; // Pointer to next event or 0
char *m_ep; // Pointer to end of events
const char *event_name(int code);
void close() {
if (m_fd >= 0) {
::close(m_fd);
m_fd = -1;
}
m_ok = false;
}
};
const char *RclIntf::event_name(int code)
{
code &= ~(IN_ISDIR|IN_ONESHOT);
switch (code) {
case IN_ACCESS: return "IN_ACCESS";
case IN_MODIFY: return "IN_MODIFY";
case IN_ATTRIB: return "IN_ATTRIB";
case IN_CLOSE_WRITE: return "IN_CLOSE_WRITE";
case IN_CLOSE_NOWRITE: return "IN_CLOSE_NOWRITE";
case IN_CLOSE: return "IN_CLOSE";
case IN_OPEN: return "IN_OPEN";
case IN_MOVED_FROM: return "IN_MOVED_FROM";
case IN_MOVED_TO: return "IN_MOVED_TO";
case IN_MOVE: return "IN_MOVE";
case IN_CREATE: return "IN_CREATE";
case IN_DELETE: return "IN_DELETE";
case IN_DELETE_SELF: return "IN_DELETE_SELF";
case IN_MOVE_SELF: return "IN_MOVE_SELF";
case IN_UNMOUNT: return "IN_UNMOUNT";
case IN_Q_OVERFLOW: return "IN_Q_OVERFLOW";
case IN_IGNORED: return "IN_IGNORED";
default: {
static char msg[50];
sprintf(msg, "Unknown event 0x%x", code);
return msg;
}
};
}
bool RclIntf::addWatch(const string& path, bool)
{
if (!ok())
return false;
MONDEB(("RclIntf::addWatch: adding %s\n", path.c_str()));
// CLOSE_WRITE is covered through MODIFY. CREATE is needed for mkdirs
uint32_t mask = IN_MODIFY | IN_CREATE
| IN_MOVED_FROM | IN_MOVED_TO | IN_DELETE
#ifdef IN_DONT_FOLLOW
| IN_DONT_FOLLOW
#endif
#ifdef IN_EXCL_UNLINK
| IN_EXCL_UNLINK
#endif
;
int wd;
if ((wd = inotify_add_watch(m_fd, path.c_str(), mask)) < 0) {
LOGERR(("RclIntf::addWatch: inotify_add_watch failed\n"));
return false;
}
m_idtopath[wd] = path;
return true;
}
// Note: return false only for queue empty or error
// Return EVT_NONE for bad event to keep queue processing going
bool RclIntf::getEvent(RclMonEvent& ev, int secs)
{
if (!ok())
return false;
ev.m_etyp = RclMonEvent::RCLEVT_NONE;
MONDEB(("RclIntf::getEvent:\n"));
if (m_evp == 0) {
fd_set readfds;
FD_ZERO(&readfds);
FD_SET(m_fd, &readfds);
struct timeval timeout;
if (secs >= 0) {
memset(&timeout, 0, sizeof(timeout));
timeout.tv_sec = secs;
}
int ret;
MONDEB(("RclIntf::getEvent: select\n"));
if ((ret=select(m_fd + 1, &readfds, 0, 0, secs >= 0 ? &timeout : 0)) < 0) {
LOGERR(("RclIntf::getEvent: select failed, errno %d\n", errno));
close();
return false;
} else if (ret == 0) {
MONDEB(("RclIntf::getEvent: select timeout\n"));
// timeout
return false;
}
MONDEB(("RclIntf::getEvent: select returned\n"));
if (!FD_ISSET(m_fd, &readfds))
return false;
int rret;
if ((rret=read(m_fd, m_evbuf, sizeof(m_evbuf))) <= 0) {
LOGERR(("RclIntf::getEvent: read failed, %d->%d errno %d\n",
sizeof(m_evbuf), rret, errno));
close();
return false;
}
m_evp = m_evbuf;
m_ep = m_evbuf + rret;
}
struct inotify_event *evp = (struct inotify_event *)m_evp;
m_evp += sizeof(struct inotify_event);
if (evp->len > 0)
m_evp += evp->len;
if (m_evp >= m_ep)
m_evp = m_ep = 0;
map<int,string>::const_iterator it;
if ((it = m_idtopath.find(evp->wd)) == m_idtopath.end()) {
LOGERR(("RclIntf::getEvent: unknown wd %d\n", evp->wd));
return true;
}
ev.m_path = it->second;
if (evp->len > 0) {
ev.m_path = path_cat(ev.m_path, evp->name);
}
MONDEB(("RclIntf::getEvent: %-12s %s\n",
event_name(evp->mask), ev.m_path.c_str()));
if ((evp->mask & IN_MOVED_FROM) && (evp->mask & IN_ISDIR)) {
// We get this when a directory is renamed. Erase the subtree
// entries in the map. The subsequent MOVED_TO will recreate
// them. This is probably not needed because the watches
// actually still exist in the kernel, so that the wds
// returned by future addwatches will be the old ones, and the
// map will be updated in place. But still, this feels safer
eraseWatchSubTree(m_idtopath, ev.m_path);
}
if (evp->mask & (IN_MODIFY)) {
ev.m_etyp = RclMonEvent::RCLEVT_MODIFY;
} else if (evp->mask & (IN_DELETE | IN_MOVED_FROM)) {
ev.m_etyp = RclMonEvent::RCLEVT_DELETE;
if (evp->mask & IN_ISDIR)
ev.m_etyp |= RclMonEvent::RCLEVT_ISDIR;
} else if (evp->mask & (IN_CREATE | IN_MOVED_TO)) {
if (evp->mask & IN_ISDIR) {
ev.m_etyp = RclMonEvent::RCLEVT_DIRCREATE;
} else {
// We used to return null event because we would get a
// modify event later, but it seems not to be the case any
// more (10-2011). So generate MODIFY event
ev.m_etyp = RclMonEvent::RCLEVT_MODIFY;
}
} else if (evp->mask & (IN_IGNORED)) {
if (!m_idtopath.erase(evp->wd)) {
LOGDEB0(("Got IGNORE event for unknown watch\n"));
} else {
eraseWatchSubTree(m_idtopath, ev.m_path);
}
} else {
LOGDEB(("RclIntf::getEvent: unhandled event %s 0x%x %s\n",
event_name(evp->mask), evp->mask, ev.m_path.c_str()));
return true;
}
return true;
}
#endif // RCL_USE_INOTIFY
///////////////////////////////////////////////////////////////////////
// The monitor 'factory'
static RclMonitor *makeMonitor()
{
#ifdef RCL_USE_INOTIFY
return new RclIntf;
#endif
#ifndef RCL_USE_INOTIFY
#ifdef RCL_USE_FAM
return new RclFAM;
#endif
#endif
LOGINFO(("RclMonitor: neither Inotify nor Fam was compiled as "
"file system change notification interface\n"));
return 0;
}
#endif // RCL_MONITOR