3
MediaTomb - http://www.mediatomb.cc/
5
autoscan_inotify.cc - this file is part of MediaTomb.
7
Copyright (C) 2005 Gena Batyan <bgeradz@mediatomb.cc>,
8
Sergey 'Jin' Bostandzhyan <jin@mediatomb.cc>
10
Copyright (C) 2006-2007 Gena Batyan <bgeradz@mediatomb.cc>,
11
Sergey 'Jin' Bostandzhyan <jin@mediatomb.cc>,
12
Leonhard Wimmer <leo@mediatomb.cc>
14
MediaTomb is free software; you can redistribute it and/or modify
15
it under the terms of the GNU General Public License version 2
16
as published by the Free Software Foundation.
18
MediaTomb is distributed in the hope that it will be useful,
19
but WITHOUT ANY WARRANTY; without even the implied warranty of
20
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21
GNU General Public License for more details.
23
You should have received a copy of the GNU General Public License
24
version 2 along with MediaTomb; if not, write to the Free Software
25
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
27
$Id: autoscan_inotify.cc 102 2007-05-25 22:46:50Z leo $
30
/// \file autoscan_inotify.cc
33
#include "autoconfig.h"
38
#include "autoscan_inotify.h"
39
#include "content_manager.h"
44
#define AUTOSCAN_INOTIFY_INITIAL_QUEUE_SIZE 20
46
// always use a prime!
47
#define AUTOSCAN_INOTIFY_HASH_SIZE 30851
49
#define INOTIFY_MAX_USER_WATCHES_FILE "/proc/sys/fs/inotify/max_user_watches"
52
AutoscanInotify::AutoscanInotify()
54
mutex = Ref<Mutex>(new Mutex());
55
cond = Ref<Cond>(new Cond(mutex));
57
int hash_size = AUTOSCAN_INOTIFY_HASH_SIZE;
59
if (check_path(_(INOTIFY_MAX_USER_WATCHES_FILE)))
64
max_watches = trim_string(read_text_file(_(INOTIFY_MAX_USER_WATCHES_FILE))).toInt();
65
log_debug("Max watches on the system: %d\n", max_watches);
69
log_error("Could not determine maximum number of inotify user watches: %s\n", ex.getMessage().c_str());
73
hash_size = max_watches * 5;
76
watches = Ref<DBOHash<int, Wd> >(new DBOHash<int, Wd>(hash_size, -1, -2));
78
monitorQueue = Ref<ObjectQueue<AutoscanDirectory> >(new ObjectQueue<AutoscanDirectory>(AUTOSCAN_INOTIFY_INITIAL_QUEUE_SIZE));
79
unmonitorQueue = Ref<ObjectQueue<AutoscanDirectory> >(new ObjectQueue<AutoscanDirectory>(AUTOSCAN_INOTIFY_INITIAL_QUEUE_SIZE));
80
events = IN_CLOSE_WRITE | IN_CREATE | IN_MOVED_FROM | IN_MOVED_TO | IN_DELETE | IN_DELETE_SELF | IN_MOVE_SELF | IN_UNMOUNT;
83
void AutoscanInotify::init()
89
inotify = Ref<Inotify>(new Inotify());
90
log_debug("starting inotify thread...\n");
91
int ret = pthread_create(
94
AutoscanInotify::staticThreadProc,
99
throw _Exception(_("failed to start inotify thread: ") + ret);
103
AutoscanInotify::~AutoscanInotify()
108
void AutoscanInotify::shutdown()
113
log_debug("start\n");
118
pthread_join(thread, NULL);
120
log_debug("inotify thread died.\n");
126
void *AutoscanInotify::staticThreadProc(void *arg)
128
log_debug("started inotify thread.\n");
129
AutoscanInotify *inst = (AutoscanInotify *)arg;
131
log_debug("exiting inotify thread...\n");
136
void AutoscanInotify::threadProc()
138
Ref<ContentManager> cm;
141
inotify_event *event;
143
Ref<StringBuffer> pathBuf (new StringBuffer());
147
cm = ContentManager::getInstance();
148
st = Storage::getInstance();
152
log_error("Inotify thread caught: %s\n", e.getMessage().c_str());
157
while(! shutdownFlag)
161
Ref<AutoscanDirectory> adir;
164
while ((adir = unmonitorQueue->dequeue()) != nil)
168
String location = normalizePathNoEx(adir->getLocation());
169
if (! string_ok(location))
175
if (adir->getRecursive())
177
log_debug("removing recursive watch: %s\n", location.c_str());
178
monitorUnmonitorRecursive(location, true, adir, location, true);
182
log_debug("removing non-recursive watch: %s\n", location.c_str());
183
unmonitorDirectory(location, adir);
189
while ((adir = monitorQueue->dequeue()) != nil)
193
String location = normalizePathNoEx(adir->getLocation());
194
if (! string_ok(location))
200
if (adir->getRecursive())
202
log_debug("adding recursive watch: %s\n", location.c_str());
203
monitorUnmonitorRecursive(location, false, adir, location, true);
207
log_debug("adding non-recursive watch: %s\n", location.c_str());
208
monitorDirectory(location, adir, location, true);
210
cm->rescanDirectory(adir->getObjectID(), adir->getScanID(), adir->getScanMode(), nil, false);
217
/* --- get event --- (blocking) */
218
event = inotify->nextEvent();
224
int mask = event->mask;
225
String name(event->name);
226
log_debug("inotify event: %d %d %s\n", wd, mask, name.c_str());
228
Ref<Wd> wdObj = watches->get(wd);
231
inotify->removeWatch(wd);
237
*pathBuf << wdObj->getPath();
238
if (! (mask & (IN_DELETE_SELF | IN_MOVE_SELF | IN_UNMOUNT)))
240
String path = pathBuf->toString();
242
Ref<AutoscanDirectory> adir;
243
Ref<WatchAutoscan> watchAs = getAppropriateAutoscan(wdObj, path);
245
adir = watchAs->getAutoscanDirectory();
249
if (mask & IN_MOVE_SELF)
251
checkMoveWatches(wd, wdObj);
254
if (mask & (IN_DELETE_SELF | IN_MOVE_SELF | IN_UNMOUNT))
256
recheckNonexistingMonitors(wd, wdObj);
261
if (mask & (IN_CREATE | IN_MOVED_TO))
263
recheckNonexistingMonitors(wd, wdObj);
266
if (adir != nil && adir->getRecursive())
268
if (mask & IN_CREATE)
270
if (adir->getHidden() || name.charAt(0) != '.')
272
log_debug("new dir detected, adding to inotify: %s\n", path.c_str());
273
monitorUnmonitorRecursive(path, false, adir, watchAs->getNormalizedAutoscanPath(), false);
277
log_debug("new dir detected, irgnoring because it's hidden: %s\n", path.c_str());
283
if (adir != nil && mask & (IN_DELETE | IN_DELETE_SELF | IN_MOVE_SELF | IN_CLOSE_WRITE | IN_MOVED_FROM | IN_MOVED_TO | IN_UNMOUNT))
287
fullPath = path + DIR_SEPARATOR;
291
if (! (mask & IN_MOVED_TO))
293
log_debug("deleting %s\n", fullPath.c_str());
295
if (mask & (IN_DELETE_SELF | IN_MOVE_SELF | IN_UNMOUNT))
298
inotify->removeWatch(wd);
299
Ref<WatchAutoscan> watch = getStartPoint(wdObj);
302
if (adir->persistent())
304
monitorNonexisting(path, watch->getAutoscanDirectory(), watch->getNormalizedAutoscanPath());
305
cm->handlePeristentAutoscanRemove(adir->getScanID(), InotifyScanMode);
310
int objectID = st->findObjectIDByPath(fullPath);
311
if (objectID != INVALID_OBJECT_ID)
312
cm->removeObject(objectID);
314
if (mask & (IN_CLOSE_WRITE | IN_MOVED_TO))
316
log_debug("adding %s\n", path.c_str());
317
// path, recursive, async, hidden, low priority, cancellable
318
cm->addFile(fullPath, adir->getRecursive(), true, adir->getHidden(), true, false);
321
if (mask & IN_IGNORED)
323
removeWatchMoves(wd);
324
removeDescendants(wd);
331
log_error("Inotify thread caught exception: %s\n", e.getMessage().c_str());
337
void AutoscanInotify::monitor(zmm::Ref<AutoscanDirectory> dir)
341
assert(dir->getScanMode() == InotifyScanMode);
342
log_debug("---> INCOMING REQUEST TO MONITOR [%s]\n",
343
dir->getLocation().c_str());
345
monitorQueue->enqueue(dir);
349
void AutoscanInotify::unmonitor(zmm::Ref<AutoscanDirectory> dir)
351
// must not be persistent
352
assert(! dir->persistent());
354
log_debug("---> INCOMING REQUEST TO UNMONITOR [%s]\n",
355
dir->getLocation().c_str());
357
unmonitorQueue->enqueue(dir);
361
int AutoscanInotify::watchPathForMoves(String path, int wd)
363
Ref<Array<StringBase> > pathAr = split_string(path, DIR_SEPARATOR);
364
Ref<StringBuffer> buf(new StringBuffer());
365
int parentWd = INOTIFY_ROOT;
366
for (int i = -1; i < pathAr->size() - 1; i++)
369
*buf << DIR_SEPARATOR;
371
*buf << pathAr->get(i);
372
log_debug("adding move watch: %s\n", buf->c_str());
373
parentWd = addMoveWatch(buf->toString(), wd, parentWd);
378
int AutoscanInotify::addMoveWatch(String path, int removeWd, int parentWd)
380
int wd = inotify->addWatch(path, events);
383
bool alreadyThere = false;
384
Ref<Wd> wdObj = watches->get(wd);
387
wdObj = Ref<Wd>(new Wd(path, wd, parentWd));
388
watches->put(wd, wdObj);
392
int parentWdSet = wdObj->getParentWd();
393
if (parentWdSet >= 0)
395
if (parentWd != parentWdSet)
397
log_debug("error: parentWd doesn't match wd: %d, parent is: %d, should be: %d\n", wd, parentWdSet, parentWd);
398
wdObj->setParentWd(parentWd);
402
wdObj->setParentWd(parentWd);
409
Ref<WatchMove> watch(new WatchMove(removeWd));
410
wdObj->getWdWatches()->append(RefCast(watch, Watch));
416
void AutoscanInotify::monitorNonexisting(String path, Ref<AutoscanDirectory> adir, String normalizedAutoscanPath)
418
String pathTmp = path;
419
Ref<Array<StringBase> > pathAr = split_string(path, DIR_SEPARATOR);
420
recheckNonexistingMonitor(-1, pathAr, adir, normalizedAutoscanPath);
423
void AutoscanInotify::recheckNonexistingMonitor(int curWd, Ref<Array<StringBase> > pathAr, Ref<AutoscanDirectory> adir, String normalizedAutoscanPath)
425
Ref<StringBuffer> buf(new StringBuffer());
427
for (int i = pathAr->size(); i >= 0; i--)
431
*buf << DIR_SEPARATOR;
434
for (int j = 0; j < i; j++)
436
*buf << DIR_SEPARATOR << pathAr->get(j);
437
// log_debug("adding: %s\n", pathAr->get(j)->data);
440
bool pathExists = check_path(buf->toString(), true);
441
// log_debug("checking %s: %d\n", buf->c_str(), pathExists);
445
removeNonexistingMonitor(curWd, watches->get(curWd), pathAr);
447
String path = buf->toString() + DIR_SEPARATOR;
450
monitorDirectory(path, adir, normalizedAutoscanPath, true);
451
ContentManager::getInstance()->handlePersistentAutoscanRecreate(adir->getScanID(), adir->getScanMode());
455
monitorDirectory(path, adir, normalizedAutoscanPath, false, pathAr);
464
void AutoscanInotify::checkMoveWatches(int wd, Ref<Wd> wdObj)
467
Ref<WatchMove> watchMv;
468
Ref<Array<Watch> > wdWatches = wdObj->getWdWatches();
469
for (int i = 0; i < wdWatches->size(); i++)
471
watch = wdWatches->get(i);
472
if (watch->getType() == WatchMoveType)
474
if (wdWatches->size() == 1)
476
inotify->removeWatch(wd);
480
wdWatches->removeUnordered(i);
483
watchMv = RefCast(watch, WatchMove);
484
int removeWd = watchMv->getRemoveWd();
485
Ref<Wd> wdToRemove = watches->get(removeWd);
486
if (wdToRemove != nil)
488
recheckNonexistingMonitors(removeWd, wdToRemove);
490
String path = wdToRemove->getPath();
491
log_debug("found wd to remove because of move event: %d %s\n", removeWd, path.c_str());
493
inotify->removeWatch(removeWd);
494
Ref<ContentManager> cm = ContentManager::getInstance();
495
Ref<WatchAutoscan> watch = getStartPoint(wdToRemove);
498
Ref<AutoscanDirectory> adir = watch->getAutoscanDirectory();
499
if (adir->persistent())
501
monitorNonexisting(path, adir, watch->getNormalizedAutoscanPath());
502
cm->handlePeristentAutoscanRemove(adir->getScanID(), InotifyScanMode);
505
int objectID = Storage::getInstance()->findObjectIDByPath(path);
506
if (objectID != INVALID_OBJECT_ID)
507
cm->removeObject(objectID);
514
void AutoscanInotify::recheckNonexistingMonitors(int wd, Ref<Wd> wdObj)
517
Ref<WatchAutoscan> watchAs;
518
Ref<Array<Watch> > wdWatches = wdObj->getWdWatches();
519
for (int i = 0; i < wdWatches->size(); i++)
521
watch = wdWatches->get(i);
522
if (watch->getType() == WatchAutoscanType)
524
watchAs = RefCast(watch, WatchAutoscan);
525
Ref<Array<StringBase> > pathAr = watchAs->getNonexistingPathArray();
528
recheckNonexistingMonitor(wd, pathAr, watchAs->getAutoscanDirectory(), watchAs->getNormalizedAutoscanPath());
534
void AutoscanInotify::removeNonexistingMonitor(int wd, Ref<Wd> wdObj, Ref<Array<StringBase> > pathAr)
537
Ref<WatchAutoscan> watchAs;
538
Ref<Array<Watch> > wdWatches = wdObj->getWdWatches();
539
for (int i = 0; i < wdWatches->size(); i++)
541
watch = wdWatches->get(i);
542
if (watch->getType() == WatchAutoscanType)
544
watchAs = RefCast(watch, WatchAutoscan);
545
if (watchAs->getNonexistingPathArray() == pathAr)
547
if (wdWatches->size() == 1)
549
// should be done automatically, because removeWatch triggers an IGNORED event
550
//watches->remove(wd);
552
inotify->removeWatch(wd);
556
wdWatches->removeUnordered(i);
564
void AutoscanInotify::monitorUnmonitorRecursive(String startPath, bool unmonitor, Ref<AutoscanDirectory> adir, String normalizedAutoscanPath, bool startPoint)
568
unmonitorDirectory(startPath, adir);
571
bool ok = (monitorDirectory(startPath, adir, normalizedAutoscanPath, startPoint) > 0);
579
DIR *dir = opendir(startPath.c_str());
582
log_warning("Could not open %s\n", startPath.c_str());
586
while ((dent = readdir(dir)) != NULL && ! shutdownFlag)
588
char *name = dent->d_name;
593
else if (name[1] == '.' && name[2] == 0)
597
String fullPath = startPath + DIR_SEPARATOR + name;
599
if (stat(fullPath.c_str(), &statbuf) != 0)
602
if (S_ISDIR(statbuf.st_mode))
604
monitorUnmonitorRecursive(fullPath, unmonitor, adir, normalizedAutoscanPath, false);
611
int AutoscanInotify::monitorDirectory(String pathOri, Ref<AutoscanDirectory> adir, String normalizedAutoscanPath, bool startPoint, Ref<Array<StringBase> > pathArray)
613
String path = pathOri + DIR_SEPARATOR;
615
int wd = inotify->addWatch(path, events);
618
if (startPoint && adir->persistent())
620
monitorNonexisting(path, adir, normalizedAutoscanPath);
625
bool alreadyWatching = false;
626
Ref<Wd> wdObj = watches->get(wd);
627
int parentWd = INOTIFY_UNKNOWN_PARENT_WD;
629
parentWd = watchPathForMoves(pathOri, wd);
632
wdObj = Ref<Wd>(new Wd(path, wd, parentWd));
633
watches->put(wd, wdObj);
637
if (parentWd >= 0 && wdObj->getParentWd() < 0)
639
wdObj->setParentWd(parentWd);
642
if (pathArray == nil)
643
alreadyWatching = (getAppropriateAutoscan(wdObj, adir) != nil);
645
// should we check for already existing "nonexisting" watches?
648
if (! alreadyWatching)
650
Ref<WatchAutoscan> watch(new WatchAutoscan(startPoint, adir, normalizedAutoscanPath));
651
if (pathArray != nil)
653
watch->setNonexistingPathArray(pathArray);
655
wdObj->getWdWatches()->append(RefCast(watch, Watch));
659
int startPointWd = inotify->addWatch(normalizedAutoscanPath, events);
660
log_debug("getting start point for %s -> %s wd=%d\n", pathOri.c_str(), normalizedAutoscanPath.c_str(), startPointWd);
662
addDescendant(startPointWd, wd, adir);
669
void AutoscanInotify::unmonitorDirectory(String path, Ref<AutoscanDirectory> adir)
671
path = path + DIR_SEPARATOR;
673
// maybe there is a faster method...
674
// we use addWatch, because it returns the wd to the filename
675
// this should not add a new watch, because it should be already watched
676
int wd = inotify->addWatch(path, events);
680
// doesn't seem to be monitored currently
681
log_debug("unmonitorDirectory called, but it isn't monitored? (%s)\n", path.c_str());
685
Ref<Wd> wdObj = watches->get(wd);
688
log_error("wd not found in watches!? (%d, %s)\n", wd, path.c_str());
692
Ref<WatchAutoscan> watchAs = getAppropriateAutoscan(wdObj, adir);
695
log_debug("autoscan not found in watches? (%d, %s)\n", wd, path.c_str());
699
if (wdObj->getWdWatches()->size() == 1)
701
// should be done automatically, because removeWatch triggers an IGNORED event
702
//watches->remove(wd);
704
inotify->removeWatch(wd);
708
removeFromWdObj(wdObj, watchAs);
713
Ref<AutoscanInotify::WatchAutoscan> AutoscanInotify::getAppropriateAutoscan(Ref<Wd> wdObj, Ref<AutoscanDirectory> adir)
716
Ref<WatchAutoscan> watchAs;
717
Ref<Array<Watch> > wdWatches = wdObj->getWdWatches();
718
for (int i = 0; i < wdWatches->size(); i++)
720
watch = wdWatches->get(i);
721
if (watch->getType() == WatchAutoscanType)
723
watchAs = RefCast(watch, WatchAutoscan);
724
if (watchAs->getNonexistingPathArray() == nil)
726
if (watchAs->getAutoscanDirectory()->getLocation() == adir->getLocation())
736
Ref<AutoscanInotify::WatchAutoscan> AutoscanInotify::getAppropriateAutoscan(Ref<Wd> wdObj, String path)
738
String pathBestMatch;
739
Ref<WatchAutoscan> bestMatch = nil;
741
Ref<WatchAutoscan> watchAs;
742
Ref<Array<Watch> > wdWatches = wdObj->getWdWatches();
743
for (int i = 0; i < wdWatches->size(); i++)
745
watch = wdWatches->get(i);
746
if (watch->getType() == WatchAutoscanType)
748
watchAs = RefCast(watch, WatchAutoscan);
749
if (watchAs->getNonexistingPathArray() == nil)
751
String testLocation = watchAs->getNormalizedAutoscanPath();
752
if (path.startsWith(testLocation))
754
if (string_ok(pathBestMatch))
756
if (pathBestMatch.length() < testLocation.length())
758
pathBestMatch = testLocation;
764
pathBestMatch = testLocation;
774
void AutoscanInotify::removeWatchMoves(int wd)
777
Ref<Array<Watch> > wdWatches;
779
Ref<WatchMove> watchMv;
784
wdObj = watches->get(checkWd);
787
wdWatches = wdObj->getWdWatches();
788
if (wdWatches == nil)
795
for (int i = 0; i < wdWatches->size(); i++)
797
watch = wdWatches->get(i);
798
if (watch->getType() == WatchMoveType)
800
watchMv = RefCast(watch, WatchMove);
801
if (watchMv->getRemoveWd() == wd)
803
log_debug("removing watch move\n");
804
if (wdWatches->size() == 1)
805
inotify->removeWatch(checkWd);
807
wdWatches->removeUnordered(i);
812
checkWd = wdObj->getParentWd();
817
bool AutoscanInotify::removeFromWdObj(Ref<Wd> wdObj, Ref<Watch> toRemove)
819
Ref<Array<Watch> > wdWatches = wdObj->getWdWatches();
821
for (int i = 0; i < wdWatches->size(); i++)
823
watch = wdWatches->get(i);
824
if (watch == toRemove)
826
if (wdWatches->size() == 1)
827
inotify->removeWatch(wdObj->getWd());
829
wdWatches->removeUnordered(i);
836
bool AutoscanInotify::removeFromWdObj(Ref<Wd> wdObj, Ref<WatchAutoscan> toRemove)
838
return removeFromWdObj(wdObj, RefCast(toRemove, Watch));
841
bool AutoscanInotify::removeFromWdObj(Ref<Wd> wdObj, Ref<WatchMove> toRemove)
843
return removeFromWdObj(wdObj, RefCast(toRemove, Watch));
846
Ref<AutoscanInotify::WatchAutoscan> AutoscanInotify::getStartPoint(Ref<Wd> wdObj)
849
Ref<WatchAutoscan> watchAs;
850
Ref<Array<Watch> > wdWatches = wdObj->getWdWatches();
851
for (int i = 0; i < wdWatches->size(); i++)
853
watch = wdWatches->get(i);
854
if (watch->getType() == WatchAutoscanType)
856
watchAs = RefCast(watch, WatchAutoscan);
857
if (watchAs->isStartPoint())
864
void AutoscanInotify::addDescendant(int startPointWd, int addWd, Ref<AutoscanDirectory> adir)
866
// log_debug("called for %d, (adir->path=%s); adding %d\n", startPointWd, adir->getLocation().c_str(), addWd);
867
Ref<Wd> wdObj = watches->get(startPointWd);
870
// log_debug("found wdObj\n");
871
Ref<WatchAutoscan> watch = getAppropriateAutoscan(wdObj, adir);
874
// log_debug("adding descendant\n");
875
watch->addDescendant(addWd);
876
// log_debug("added descendant to %d (adir->path=%s): %d; now: %s\n", startPointWd, adir->getLocation().c_str(), addWd, watch->getDescendants()->toCSV().c_str());
879
void AutoscanInotify::removeDescendants(int wd)
881
Ref<Wd> wdObj = watches->get(wd);
884
Ref<Array<Watch> > wdWatches = wdObj->getWdWatches();
885
if (wdWatches == nil)
889
Ref<WatchAutoscan> watchAs;
890
for (int i = 0; i < wdWatches->size(); i++)
892
watch = wdWatches->get(i);
893
if (watch->getType() == WatchAutoscanType)
895
watchAs = RefCast(watch, WatchAutoscan);
896
Ref<IntArray> descendants = watchAs->getDescendants();
897
if (descendants != nil)
899
for (int i = 0; i < descendants->size(); i++)
901
int descWd = descendants->get(i);
902
inotify->removeWatch(descWd);
909
String AutoscanInotify::normalizePathNoEx(String path)
913
return normalizePath(path);
917
log_error("%s\n", e.getMessage().c_str());
922
#endif // HAVE_INOTIFY