added 21 changesets to branch 'refs/remotes/HaikuPM-github/package-management' old head: ece582547a43df2c2009900ac638fecb97c71d9c new head: c143884fdf03ec3a8b7a89c88baa477f8629395c overview: https://github.com/haiku/HaikuPM/compare/ece5825...c143884 ---------------------------------------------------------------------------- 1055112: Move PUuid from shared to support 535207a: BPoseView::AddPosesTask(): fix count checks In case GetNextDirents() returned an error, the wrong blocks were executed. 2752612: BMergedDirectory::GetNextDirents(): fix end-of-list return value 5fa3519: BString::Private: Add IsShareable() 62b0b64: BStringList::Add(): fix ref counting problem Handle the case that the private data of the given string is not shareable. 6ca95bd: PathMonitor.cpp: some style cleanup ad1875f: BPathMonitor: use pthread_once for initialization 77ca66c: BPathMonitor: make the node watching mechanism configurable Add inner class BWatchingInterface and method SetWatchingInterface(). This abstracts the calls to watch_node() and stop_watching(), thus making it possible to use the path monitor in Tracker. ddd775f: BPoseView::FSNotification(): fix issue in debug build 3209bc4: BPathMonitor: PathHandler::_NotifyTarget(): simplify * Add optional entry_ref return parameter to _HasFile(). * Simplify _NotifyTarget() by using _HasDirectory() and _HasFile(). 8d572c9: Add class NotOwningEntryRef A entry_ref subclass that avoids cloning the entry name. 7b198d8: B_WATCH_FOLDERS_ONLY -> B_WATCH_DIRECTORIES_ONLY Stick to the nomenclature generally used in the public API. 38afe23: BPathMonitor: pass BMessenger by reference cb4a05c: Missed B_WATCH_FOLDERS_ONLY occurrence 3e8daeb: Add BMessenger::HashValue() 1eda851: BOpenHashTable: Add IsEmpty() 0d603ac: Fix node monitoring slot accounting for stop_watching() NodeMonitorService::RemoveUserListeners() didn't decrement io_context::num_monitors when removing a listener, so limit checks would be off afterwards. 8c974aa: node monitor: add TODO regarding the syscalls cc4d194: Add test suite for BPathMonitor 749 / 1504 tests fail 04382d4: BPathMonitor: rewrite This resolves all issues the test suite uncovered. It should also deal with hard links correctly, though that hasn't been tested. Still unsupported are: * changes due to mounting/unmounting a volume, * tracking of symlinks in the path components. c143884: Use incorrect use of BPathMonitor in input/midi/net server The B_ENTRY_* constants aren't valid watch flags. [ Ingo Weinhold <ingo_weinhold@xxxxxx> ] ---------------------------------------------------------------------------- 21 files changed, 3454 insertions(+), 787 deletions(-) headers/os/app/Messenger.h | 2 + headers/private/kernel/util/OpenHashTable.h | 5 + headers/private/storage/NotOwningEntryRef.h | 90 + headers/private/storage/PathMonitor.h | 66 +- headers/private/support/StringPrivate.h | 5 + headers/private/{shared => support}/Uuid.h | 0 src/kits/app/Messenger.cpp | 7 + src/kits/shared/Jamfile | 1 - src/kits/storage/Jamfile | 2 +- src/kits/storage/MergedDirectory.cpp | 2 +- src/kits/storage/PathMonitor.cpp | 2568 +++++++++++++----- src/kits/support/Jamfile | 1 + src/kits/support/StringList.cpp | 16 +- src/kits/{shared => support}/Uuid.cpp | 0 src/kits/tracker/PoseView.cpp | 15 +- src/servers/input/AddOnManager.cpp | 5 +- src/servers/midi/DeviceWatcher.cpp | 5 +- src/servers/net/NetServer.cpp | 4 +- src/system/kernel/fs/node_monitor.cpp | 7 + src/tests/kits/storage/testapps/Jamfile | 2 + .../kits/storage/testapps/PathMonitorTest2.cpp | 1438 ++++++++++ ############################################################################ Commit: 105511275e00602f982f593afea9f65c6bd075e9 Author: Ingo Weinhold <ingo_weinhold@xxxxxx> Date: Thu Jun 20 01:41:36 2013 UTC Move PUuid from shared to support ---------------------------------------------------------------------------- diff --git a/headers/private/shared/Uuid.h b/headers/private/support/Uuid.h similarity index 100% rename from headers/private/shared/Uuid.h rename to headers/private/support/Uuid.h diff --git a/src/kits/shared/Jamfile b/src/kits/shared/Jamfile index d4aca53..2459568 100644 --- a/src/kits/shared/Jamfile +++ b/src/kits/shared/Jamfile @@ -33,7 +33,6 @@ StaticLibrary libshared.a : ShakeTrackingFilter.cpp StringForRate.cpp StringForSize.cpp - Uuid.cpp Variant.cpp ; diff --git a/src/kits/support/Jamfile b/src/kits/support/Jamfile index 40ecc11..80b74f3 100644 --- a/src/kits/support/Jamfile +++ b/src/kits/support/Jamfile @@ -23,6 +23,7 @@ MergeObject <libbe>support_kit.o : String.cpp StringList.cpp Url.cpp + Uuid.cpp ; StaticLibrary libreferenceable.a : : [ FGristFiles Referenceable.o ] ; diff --git a/src/kits/shared/Uuid.cpp b/src/kits/support/Uuid.cpp similarity index 100% rename from src/kits/shared/Uuid.cpp rename to src/kits/support/Uuid.cpp ############################################################################ Commit: 535207ae9a3e759ac960379905a5e1661174eb7c Author: Ingo Weinhold <ingo_weinhold@xxxxxx> Date: Thu Jun 20 03:52:07 2013 UTC BPoseView::AddPosesTask(): fix count checks In case GetNextDirents() returned an error, the wrong blocks were executed. ---------------------------------------------------------------------------- diff --git a/src/kits/tracker/PoseView.cpp b/src/kits/tracker/PoseView.cpp index 88583bd..fe69b14 100644 --- a/src/kits/tracker/PoseView.cpp +++ b/src/kits/tracker/PoseView.cpp @@ -1349,7 +1349,7 @@ BPoseView::AddPosesTask(void* castToParams) if (count <= 0 && modelChunkIndex == -1) break; - if (count) { + if (count > 0) { ASSERT(count == 1); if ((!hideDotFiles && (!strcmp(eptr->d_name, ".") @@ -1393,7 +1393,7 @@ BPoseView::AddPosesTask(void* castToParams) throw failToLock(); } - if (count) { + if (count > 0) { // try to watch the model, no matter what if (result != B_OK) { @@ -1421,7 +1421,7 @@ BPoseView::AddPosesTask(void* castToParams) bigtime_t now = system_time(); - if (!count || modelChunkIndex >= kMaxAddPosesChunk - 1 + if (count <= 0 || modelChunkIndex >= kMaxAddPosesChunk - 1 || now > nextChunkTime) { // keep getting models until we get <kMaxAddPosesChunk> of them // or until 300000 runs out @@ -1446,7 +1446,7 @@ BPoseView::AddPosesTask(void* castToParams) snooze(500); // be nice } - if (!count) + if (count <= 0) break; } ############################################################################ Commit: 2752612bc60b08174c98181b33b7be3a7b93c3e3 Author: Ingo Weinhold <ingo_weinhold@xxxxxx> Date: Thu Jun 20 03:52:45 2013 UTC BMergedDirectory::GetNextDirents(): fix end-of-list return value ---------------------------------------------------------------------------- diff --git a/src/kits/storage/MergedDirectory.cpp b/src/kits/storage/MergedDirectory.cpp index 2ec338f..fb35094 100644 --- a/src/kits/storage/MergedDirectory.cpp +++ b/src/kits/storage/MergedDirectory.cpp @@ -170,7 +170,7 @@ BMergedDirectory::GetNextDirents(struct dirent* direntBuffer, size_t bufferSize, } } - return B_ENTRY_NOT_FOUND; + return 0; } ############################################################################ Commit: 5fa3519dece47e6f9b519e56df643e52642b2fa6 Author: Ingo Weinhold <ingo_weinhold@xxxxxx> Date: Thu Jun 20 08:28:12 2013 UTC BString::Private: Add IsShareable() ---------------------------------------------------------------------------- diff --git a/headers/private/support/StringPrivate.h b/headers/private/support/StringPrivate.h index 68a098d..799151b 100644 --- a/headers/private/support/StringPrivate.h +++ b/headers/private/support/StringPrivate.h @@ -27,6 +27,11 @@ public: return fString.fPrivateData; } + bool IsShareable() const + { + return fString._IsShareable(); + } + static vint32& DataRefCount(char* data) { return *(((int32 *)data) - 2); ############################################################################ Commit: 62b0b64124aa1eb9007ecbeae69938e99aeae4d6 Author: Ingo Weinhold <ingo_weinhold@xxxxxx> Date: Thu Jun 20 08:30:08 2013 UTC BStringList::Add(): fix ref counting problem Handle the case that the private data of the given string is not shareable. ---------------------------------------------------------------------------- diff --git a/src/kits/support/StringList.cpp b/src/kits/support/StringList.cpp index 8d51f3f..fe6e138 100644 --- a/src/kits/support/StringList.cpp +++ b/src/kits/support/StringList.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2011, Ingo Weinhold, ingo_weinhold@xxxxxx + * Copyright 2011-2013, Ingo Weinhold, ingo_weinhold@xxxxxx * Copyright 2011, Clemens Zeidler <haiku@xxxxxxxxxxxxxxxxxx> * * Distributed under the terms of the MIT License. @@ -55,8 +55,13 @@ BStringList::~BStringList() bool -BStringList::Add(const BString& string, int32 index) +BStringList::Add(const BString& _string, int32 index) { + BString string(_string); + // makes sure the string is shareable + if (string.Length() != _string.Length()) + return false; + char* privateData = BString::Private(string).Data(); if (!fStrings.AddItem(privateData, index)) return false; @@ -67,8 +72,13 @@ BStringList::Add(const BString& string, int32 index) bool -BStringList::Add(const BString& string) +BStringList::Add(const BString& _string) { + BString string(_string); + // makes sure the string is shareable + if (string.Length() != _string.Length()) + return false; + char* privateData = BString::Private(string).Data(); if (!fStrings.AddItem(privateData)) return false; ############################################################################ Commit: 6ca95bd81343ac9be3836d112cf5e079a6e6ff9f Author: Ingo Weinhold <ingo_weinhold@xxxxxx> Date: Thu Jun 20 11:46:56 2013 UTC PathMonitor.cpp: some style cleanup ---------------------------------------------------------------------------- diff --git a/src/kits/storage/PathMonitor.cpp b/src/kits/storage/PathMonitor.cpp index 27ee2f1..cb66b2e 100644 --- a/src/kits/storage/PathMonitor.cpp +++ b/src/kits/storage/PathMonitor.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2007-2008, Haiku Inc. All Rights Reserved. + * Copyright 2007-2013, Haiku Inc. All Rights Reserved. * Distributed under the terms of the MIT License. * * Authors: @@ -10,7 +10,12 @@ #include <PathMonitor.h> -#include <ObjectList.h> +#include <pthread.h> +#include <stdio.h> + +#include <map> +#include <new> +#include <set> #include <Autolock.h> #include <Directory.h> @@ -21,10 +26,8 @@ #include <Path.h> #include <String.h> -#include <map> -#include <new> -#include <set> -#include <stdio.h> +#include <ObjectList.h> + #undef TRACE //#define TRACE_PATH_MONITOR @@ -34,10 +37,12 @@ # define TRACE(x...) ; #endif + using namespace BPrivate; using namespace std; using std::nothrow; // TODO: Remove this line if the above line is enough. + // TODO: Use optimizations where stuff is already known to avoid iterating // the watched directory and files set too often. ############################################################################ Commit: ad1875fd705fe89064bcc91788ab4a05d14bf59d Author: Ingo Weinhold <ingo_weinhold@xxxxxx> Date: Thu Jun 20 11:48:21 2013 UTC BPathMonitor: use pthread_once for initialization ---------------------------------------------------------------------------- diff --git a/headers/private/storage/PathMonitor.h b/headers/private/storage/PathMonitor.h index 68c1cb8..c32e94e 100644 --- a/headers/private/storage/PathMonitor.h +++ b/headers/private/storage/PathMonitor.h @@ -33,8 +33,8 @@ private: BPathMonitor(); ~BPathMonitor(); - static status_t _InitLockerIfNeeded(); - static status_t _InitLooperIfNeeded(); + static status_t _InitIfNeeded(); + static void _Init(); }; } // namespace BPrivate diff --git a/src/kits/storage/PathMonitor.cpp b/src/kits/storage/PathMonitor.cpp index cb66b2e..dfa2137 100644 --- a/src/kits/storage/PathMonitor.cpp +++ b/src/kits/storage/PathMonitor.cpp @@ -143,6 +143,7 @@ class PathHandler : public BHandler { }; +static pthread_once_t sInitOnce = PTHREAD_ONCE_INIT; static WatcherMap sWatchers; static BLocker* sLocker = NULL; static BLooper* sLooper = NULL; @@ -975,50 +976,32 @@ BPathMonitor::~BPathMonitor() /*static*/ status_t -BPathMonitor::_InitLockerIfNeeded() +BPathMonitor::_InitIfNeeded() { - static vint32 lock = 0; - - if (sLocker != NULL) - return B_OK; - - while (sLocker == NULL) { - if (atomic_add(&lock, 1) == 0) { - sLocker = new (nothrow) BLocker("path monitor"); - TRACE("Create PathMonitor locker\n"); - if (sLocker == NULL) - return B_NO_MEMORY; - } - snooze(5000); - } - - return B_OK; + pthread_once(&sInitOnce, &BPathMonitor::_Init); + return sLooper != NULL ? B_OK : B_NO_MEMORY; } -/*static*/ status_t -BPathMonitor::_InitLooperIfNeeded() +/*static*/ void +BPathMonitor::_Init() { - static vint32 lock = 0; - - if (sLooper != NULL) - return B_OK; + sLocker = new (nothrow) BLocker("path monitor"); + TRACE("Create PathMonitor locker\n"); + if (sLocker == NULL) + return; - while (sLooper == NULL) { - if (atomic_add(&lock, 1) == 0) { - // first thread initializes the global looper - sLooper = new (nothrow) BLooper("PathMonitor looper"); - TRACE("Start PathMonitor looper\n"); - if (sLooper == NULL) - return B_NO_MEMORY; - thread_id thread = sLooper->Run(); - if (thread < B_OK) - return (status_t)thread; - } - snooze(5000); + BLooper* looper = new (nothrow) BLooper("PathMonitor looper"); + TRACE("Start PathMonitor looper\n"); + if (looper == NULL) + return; + thread_id thread = looper->Run(); + if (thread < 0) { + delete looper; + return; } - return sLooper->Thread() >= 0 ? B_OK : B_ERROR; + sLooper = looper; } @@ -1027,15 +1010,10 @@ BPathMonitor::StartWatching(const char* path, uint32 flags, BMessenger target) { TRACE("StartWatching(%s)\n", path); - status_t status = _InitLockerIfNeeded(); + status_t status = _InitIfNeeded(); if (status != B_OK) return status; - // use the global looper for receiving node monitor notifications - status = _InitLooperIfNeeded(); - if (status < B_OK) - return status; - BAutolock _(sLocker); WatcherMap::iterator iterator = sWatchers.find(target); ############################################################################ Commit: 77ca66cdb7b44669fc03e80e084234bee21bfd0e Author: Ingo Weinhold <ingo_weinhold@xxxxxx> Date: Thu Jun 20 11:59:22 2013 UTC BPathMonitor: make the node watching mechanism configurable Add inner class BWatchingInterface and method SetWatchingInterface(). This abstracts the calls to watch_node() and stop_watching(), thus making it possible to use the path monitor in Tracker. ---------------------------------------------------------------------------- diff --git a/headers/private/storage/PathMonitor.h b/headers/private/storage/PathMonitor.h index c32e94e..777ffd8 100644 --- a/headers/private/storage/PathMonitor.h +++ b/headers/private/storage/PathMonitor.h @@ -1,5 +1,5 @@ /* - * Copyright 2007-2008, Haiku Inc. All Rights Reserved. + * Copyright 2007-2013, Haiku Inc. All Rights Reserved. * Distributed under the terms of the MIT License. */ #ifndef _PATH_MONITOR_H @@ -18,10 +18,15 @@ #define B_PATH_MONITOR '_PMN' + namespace BPrivate { + class BPathMonitor { public: + class BWatchingInterface; + +public: static status_t StartWatching(const char* path, uint32 flags, BMessenger target); @@ -29,6 +34,10 @@ public: BMessenger target); static status_t StopWatching(BMessenger target); + static void SetWatchingInterface( + BWatchingInterface* watchingInterface); + // pass NULL to reset to default + private: BPathMonitor(); ~BPathMonitor(); @@ -37,6 +46,27 @@ private: static void _Init(); }; + +/*! Base class just delegates to the respective C functions. + */ +class BPathMonitor::BWatchingInterface { +public: + BWatchingInterface(); + virtual ~BWatchingInterface(); + + virtual status_t WatchNode(const node_ref* node, uint32 flags, + const BMessenger& target); + virtual status_t WatchNode(const node_ref* node, uint32 flags, + const BHandler* handler, + const BLooper* looper = NULL); + + virtual status_t StopWatching(const BMessenger& target); + virtual status_t StopWatching(const BHandler* handler, + const BLooper* looper = NULL); +}; + + } // namespace BPrivate + #endif // _PATH_MONITOR_H diff --git a/src/kits/storage/PathMonitor.cpp b/src/kits/storage/PathMonitor.cpp index dfa2137..cab06a5 100644 --- a/src/kits/storage/PathMonitor.cpp +++ b/src/kits/storage/PathMonitor.cpp @@ -147,6 +147,8 @@ static pthread_once_t sInitOnce = PTHREAD_ONCE_INIT; static WatcherMap sWatchers; static BLocker* sLocker = NULL; static BLooper* sLooper = NULL; +static BPathMonitor::BWatchingInterface* sDefaultWatchingInterface = NULL; +static BPathMonitor::BWatchingInterface* sWatchingInterface = NULL; static status_t @@ -248,7 +250,7 @@ void PathHandler::Quit() { if (sLooper->Lock()) { - stop_watching(this); + sWatchingInterface->StopWatching(this); sLooper->RemoveHandler(this); sLooper->Unlock(); } @@ -706,7 +708,7 @@ PathHandler::_AddDirectory(BEntry& entry, bool notify) else flags = B_WATCH_DIRECTORY; - status = watch_node(&directory.node, flags, this); + status = sWatchingInterface->WatchNode(&directory.node, flags, this); if (status != B_OK) return status; @@ -765,7 +767,7 @@ PathHandler::_RemoveDirectory(const node_ref& nodeRef, ino_t directoryNode) if (iterator == fDirectories.end()) return B_ENTRY_NOT_FOUND; - watch_node(&directory.node, B_STOP_WATCHING, this); + sWatchingInterface->WatchNode(&directory.node, B_STOP_WATCHING, this); node_ref directoryRef; directoryRef.device = nodeRef.device; @@ -852,7 +854,8 @@ PathHandler::_AddFile(BEntry& entry, bool notify) if (_HasFile(nodeRef)) return B_OK; - status = watch_node(&nodeRef, (fFlags & WATCH_NODE_FLAG_MASK), this); + status = sWatchingInterface->WatchNode(&nodeRef, + (fFlags & WATCH_NODE_FLAG_MASK), this); if (status != B_OK) return status; @@ -893,7 +896,7 @@ PathHandler::_RemoveFile(const node_ref& nodeRef) if (iterator == fFiles.end()) return B_ENTRY_NOT_FOUND; - watch_node(&nodeRef, B_STOP_WATCHING, this); + sWatchingInterface->WatchNode(&nodeRef, B_STOP_WATCHING, this); fFiles.erase(iterator); return B_OK; } @@ -991,6 +994,13 @@ BPathMonitor::_Init() if (sLocker == NULL) return; + sDefaultWatchingInterface = new(std::nothrow) BWatchingInterface; + if (sDefaultWatchingInterface == NULL) + return; + + if (sWatchingInterface == NULL) + SetWatchingInterface(sDefaultWatchingInterface); + BLooper* looper = new (nothrow) BLooper("PathMonitor looper"); TRACE("Start PathMonitor looper\n"); if (looper == NULL) @@ -1106,4 +1116,57 @@ BPathMonitor::StopWatching(BMessenger target) return B_OK; } + +/*static*/ void +BPathMonitor::SetWatchingInterface(BWatchingInterface* watchingInterface) +{ + sWatchingInterface = watchingInterface != NULL + ? watchingInterface : sDefaultWatchingInterface; +} + + +// #pragma mark - BWatchingInterface + + +BPathMonitor::BWatchingInterface::BWatchingInterface() +{ +} + + +BPathMonitor::BWatchingInterface::~BWatchingInterface() +{ +} + + +status_t +BPathMonitor::BWatchingInterface::WatchNode(const node_ref* node, uint32 flags, + const BMessenger& target) +{ + return watch_node(node, flags, target); +} + + +status_t +BPathMonitor::BWatchingInterface::WatchNode(const node_ref* node, uint32 flags, + const BHandler* handler, const BLooper* looper) +{ + return watch_node(node, flags, handler, looper); +} + + +status_t +BPathMonitor::BWatchingInterface::StopWatching(const BMessenger& target) +{ + return stop_watching(target); +} + + +status_t +BPathMonitor::BWatchingInterface::StopWatching(const BHandler* handler, + const BLooper* looper) +{ + return stop_watching(handler, looper); +} + + } // namespace BPrivate ############################################################################ Commit: ddd775f5ac1bf7dee15dd492500b50b935b71935 Author: Ingo Weinhold <ingo_weinhold@xxxxxx> Date: Thu Jun 20 12:58:34 2013 UTC BPoseView::FSNotification(): fix issue in debug build ---------------------------------------------------------------------------- diff --git a/src/kits/tracker/PoseView.cpp b/src/kits/tracker/PoseView.cpp index fe69b14..1b03aaf 100644 --- a/src/kits/tracker/PoseView.cpp +++ b/src/kits/tracker/PoseView.cpp @@ -5174,13 +5174,12 @@ BPoseView::FSNotification(const BMessage* message) } const char* name; - if (message->FindString("name", &name) != B_OK) - break; + if (message->FindString("name", &name) != B_OK) { #if DEBUG - else SERIAL_PRINT(("no name in entry creation message\n")); - break; #endif + break; + } if (count) { // basically, let's say we have a broken link : // ./a_link -> ./some_folder/another_folder/a_target ############################################################################ Commit: 3209bc40c523d63e98d635ae302df6bece0966d3 Author: Ingo Weinhold <ingo_weinhold@xxxxxx> Date: Sat Jun 22 11:41:48 2013 UTC BPathMonitor: PathHandler::_NotifyTarget(): simplify * Add optional entry_ref return parameter to _HasFile(). * Simplify _NotifyTarget() by using _HasDirectory() and _HasFile(). ---------------------------------------------------------------------------- diff --git a/src/kits/storage/PathMonitor.cpp b/src/kits/storage/PathMonitor.cpp index cab06a5..9e43750 100644 --- a/src/kits/storage/PathMonitor.cpp +++ b/src/kits/storage/PathMonitor.cpp @@ -126,7 +126,8 @@ class PathHandler : public BHandler { status_t _RemoveDirectory(const node_ref& nodeRef, ino_t directoryNode); status_t _RemoveDirectory(BEntry& entry, ino_t directoryNode); - bool _HasFile(const node_ref& nodeRef) const; + bool _HasFile(const node_ref& nodeRef, const entry_ref** _ref = NULL) + const; status_t _AddFile(BEntry& entry, bool notify = false); status_t _RemoveFile(const node_ref& nodeRef); status_t _RemoveFile(BEntry& entry); @@ -635,11 +636,7 @@ PathHandler::_NotifyTarget(BMessage* message, const node_ref& nodeRef) const TRACE("_NotifyTarget(): node ref %ld.%Ld\n", nodeRef.device, nodeRef.node); - WatchedDirectory directory; - directory.node = nodeRef; - - DirectorySet::const_iterator iterator = fDirectories.find(directory); - if (iterator != fDirectories.end()) { + if (_HasDirectory(nodeRef)) { if (_WatchFilesOnly()) { // stat or attr notification for a directory return; @@ -655,13 +652,10 @@ PathHandler::_NotifyTarget(BMessage* message, const node_ref& nodeRef) const // this is bound to be a notification for a file return; } - FileEntry setEntry; - setEntry.ref.device = nodeRef.device; - setEntry.node = nodeRef.node; - // name does not need to be set, since it's not used for comparing - FileSet::const_iterator i = fFiles.find(setEntry); - if (i != fFiles.end()) { - BPath path(&(i->ref)); + + const entry_ref* entryRef; + if (_HasFile(nodeRef, &entryRef)) { + BPath path(entryRef); update.AddString("path", path.Path()); } } @@ -815,14 +809,20 @@ PathHandler::_RemoveDirectory(BEntry& entry, ino_t directoryNode) bool -PathHandler::_HasFile(const node_ref& nodeRef) const +PathHandler::_HasFile(const node_ref& nodeRef, + const entry_ref** _ref /*= NULL*/) const { FileEntry setEntry; setEntry.ref.device = nodeRef.device; setEntry.node = nodeRef.node; // name does not need to be set, since it's not used for comparing FileSet::const_iterator iterator = fFiles.find(setEntry); - return iterator != fFiles.end(); + if (iterator == fFiles.end()) + return false; + + if (_ref != NULL) + *_ref = &iterator->ref; + return true; } ############################################################################ Commit: 8d572c926432f2e29b3ffe88576a64ba731772a4 Author: Ingo Weinhold <ingo_weinhold@xxxxxx> Date: Sat Jun 22 14:40:32 2013 UTC Add class NotOwningEntryRef A entry_ref subclass that avoids cloning the entry name. ---------------------------------------------------------------------------- diff --git a/headers/private/storage/NotOwningEntryRef.h b/headers/private/storage/NotOwningEntryRef.h new file mode 100644 index 0000000..9e3312a --- /dev/null +++ b/headers/private/storage/NotOwningEntryRef.h @@ -0,0 +1,90 @@ +/* + * Copyright 2013, Haiku, Inc. All Rights Reserved. + * Distributed under the terms of the MIT License. + * + * Authors: + * Ingo Weinhold <ingo_weinhold@xxxxxx> + */ +#ifndef _NOT_OWNING_ENTRY_REF_H +#define _NOT_OWNING_ENTRY_REF_H + + +#include <Entry.h> +#include <Node.h> + + +namespace BPrivate { + + +/*! entry_ref subclass that avoids cloning the entry name. + + Therefore initialization is cheaper and cannot fail. It derives from + entry_ref for convenience. However, care must be taken that: + - the name remains valid while the object is in use, + - the object isn't passed as an entry_ref to a function that modifies it. + */ +class NotOwningEntryRef : public entry_ref { +public: + NotOwningEntryRef() + { + } + + NotOwningEntryRef(dev_t device, ino_t directory, const char* name) + { + SetTo(device, directory, name); + } + + NotOwningEntryRef(const node_ref& directoryRef, const char* name) + { + SetTo(directoryRef, name); + } + + NotOwningEntryRef(const entry_ref& other) + { + *this = other; + } + + ~NotOwningEntryRef() + { + name = NULL; + } + + NotOwningEntryRef& SetTo(dev_t device, ino_t directory, const char* name) + { + this->device = device; + this->directory = directory; + this->name = const_cast<char*>(name); + return *this; + } + + NotOwningEntryRef& SetTo(const node_ref& directoryRef, const char* name) + { + return SetTo(directoryRef.device, directoryRef.node, name); + } + + node_ref DirectoryNodeRef() const + { + return node_ref(device, directory); + } + + NotOwningEntryRef& SetDirectoryNodeRef(const node_ref& directoryRef) + { + device = directoryRef.device; + directory = directoryRef.node; + return *this; + } + + NotOwningEntryRef& operator=(const entry_ref& other) + { + return SetTo(other.device, other.directory, other.name); + } +}; + + +} // namespace BPrivate + + +using BPrivate::NotOwningEntryRef; + + +#endif // _NOT_OWNING_ENTRY_REF_H ############################################################################ Commit: 7b198d812e45dfdaf08d5660fec27b3b4acf2a6f Author: Ingo Weinhold <ingo_weinhold@xxxxxx> Date: Sun Jun 23 11:01:09 2013 UTC B_WATCH_FOLDERS_ONLY -> B_WATCH_DIRECTORIES_ONLY Stick to the nomenclature generally used in the public API. ---------------------------------------------------------------------------- diff --git a/headers/private/storage/PathMonitor.h b/headers/private/storage/PathMonitor.h index 777ffd8..657162d 100644 --- a/headers/private/storage/PathMonitor.h +++ b/headers/private/storage/PathMonitor.h @@ -10,9 +10,9 @@ // additional flags (combined with those in NodeMonitor.h) -#define B_WATCH_FILES_ONLY 0x0100 -#define B_WATCH_RECURSIVELY 0x0200 -#define B_WATCH_FOLDERS_ONLY 0x0400 +#define B_WATCH_FILES_ONLY 0x0100 +#define B_WATCH_RECURSIVELY 0x0200 +#define B_WATCH_DIRECTORIES_ONLY 0x0400 // NOTE: B_WATCH_RECURSIVELY usually implies to watch for file changes as well, // if that is not desired, add B_WATCH_FOLDERS_ONLY to the flags. diff --git a/src/kits/storage/PathMonitor.cpp b/src/kits/storage/PathMonitor.cpp index 9e43750..9763280 100644 --- a/src/kits/storage/PathMonitor.cpp +++ b/src/kits/storage/PathMonitor.cpp @@ -334,7 +334,7 @@ PathHandler::_WatchFilesOnly() const bool PathHandler::_WatchFoldersOnly() const { - return (fFlags & B_WATCH_FOLDERS_ONLY) != 0; + return (fFlags & B_WATCH_DIRECTORIES_ONLY) != 0; } ############################################################################ Commit: 38afe232de0aba455fb21897ac8a739dd5793e52 Author: Ingo Weinhold <ingo_weinhold@xxxxxx> Date: Sun Jun 23 11:12:38 2013 UTC BPathMonitor: pass BMessenger by reference ---------------------------------------------------------------------------- diff --git a/headers/private/storage/PathMonitor.h b/headers/private/storage/PathMonitor.h index 657162d..f623e9c 100644 --- a/headers/private/storage/PathMonitor.h +++ b/headers/private/storage/PathMonitor.h @@ -28,11 +28,11 @@ public: public: static status_t StartWatching(const char* path, uint32 flags, - BMessenger target); + const BMessenger& target); static status_t StopWatching(const char* path, - BMessenger target); - static status_t StopWatching(BMessenger target); + const BMessenger& target); + static status_t StopWatching(const BMessenger& target); static void SetWatchingInterface( BWatchingInterface* watchingInterface); diff --git a/src/kits/storage/PathMonitor.cpp b/src/kits/storage/PathMonitor.cpp index 9763280..a782171 100644 --- a/src/kits/storage/PathMonitor.cpp +++ b/src/kits/storage/PathMonitor.cpp @@ -1016,7 +1016,8 @@ BPathMonitor::_Init() /*static*/ status_t -BPathMonitor::StartWatching(const char* path, uint32 flags, BMessenger target) +BPathMonitor::StartWatching(const char* path, uint32 flags, + const BMessenger& target) { TRACE("StartWatching(%s)\n", path); @@ -1056,7 +1057,7 @@ BPathMonitor::StartWatching(const char* path, uint32 flags, BMessenger target) /*static*/ status_t -BPathMonitor::StopWatching(const char* path, BMessenger target) +BPathMonitor::StopWatching(const char* path, const BMessenger& target) { if (sLocker == NULL) return B_NO_INIT; @@ -1090,7 +1091,7 @@ BPathMonitor::StopWatching(const char* path, BMessenger target) /*static*/ status_t -BPathMonitor::StopWatching(BMessenger target) +BPathMonitor::StopWatching(const BMessenger& target) { if (sLocker == NULL) return B_NO_INIT; ############################################################################ Commit: cb4a05cfdf59c3645e0e09e3cf154204e1a28c41 Author: Ingo Weinhold <ingo_weinhold@xxxxxx> Date: Mon Jun 24 08:52:07 2013 UTC Missed B_WATCH_FOLDERS_ONLY occurrence ---------------------------------------------------------------------------- diff --git a/headers/private/storage/PathMonitor.h b/headers/private/storage/PathMonitor.h index f623e9c..3309eb2 100644 --- a/headers/private/storage/PathMonitor.h +++ b/headers/private/storage/PathMonitor.h @@ -14,7 +14,7 @@ #define B_WATCH_RECURSIVELY 0x0200 #define B_WATCH_DIRECTORIES_ONLY 0x0400 // NOTE: B_WATCH_RECURSIVELY usually implies to watch for file changes as well, -// if that is not desired, add B_WATCH_FOLDERS_ONLY to the flags. +// if that is not desired, add B_WATCH_DIRECTORIES_ONLY to the flags. #define B_PATH_MONITOR '_PMN' ############################################################################ Commit: 3e8daeb7bc9c99f4858c072638b11f8fc9c7c220 Author: Ingo Weinhold <ingo_weinhold@xxxxxx> Date: Thu Jun 27 19:13:08 2013 UTC Add BMessenger::HashValue() ---------------------------------------------------------------------------- diff --git a/headers/os/app/Messenger.h b/headers/os/app/Messenger.h index 1285014..3adec7d 100644 --- a/headers/os/app/Messenger.h +++ b/headers/os/app/Messenger.h @@ -57,6 +57,8 @@ public: bool IsValid() const; team_id Team() const; + uint32 HashValue() const; + //----- Private or reserved ----------------------------------------- class Private; diff --git a/src/kits/app/Messenger.cpp b/src/kits/app/Messenger.cpp index 3e284a3..11975b8 100644 --- a/src/kits/app/Messenger.cpp +++ b/src/kits/app/Messenger.cpp @@ -504,6 +504,13 @@ BMessenger::Team() const } +uint32 +BMessenger::HashValue() const +{ + return fPort * 19 + fHandlerToken; +} + + // #pragma mark - Private or reserved ############################################################################ Commit: 1eda8517f126fed1af44d199a9c06d6f4584ac8d Author: Ingo Weinhold <ingo_weinhold@xxxxxx> Date: Thu Jun 27 19:14:11 2013 UTC BOpenHashTable: Add IsEmpty() ---------------------------------------------------------------------------- diff --git a/headers/private/kernel/util/OpenHashTable.h b/headers/private/kernel/util/OpenHashTable.h index a2af3aa..b8804cb 100644 --- a/headers/private/kernel/util/OpenHashTable.h +++ b/headers/private/kernel/util/OpenHashTable.h @@ -126,6 +126,11 @@ public: return fTableSize; } + bool IsEmpty() const + { + return fItemCount == 0; + } + size_t CountElements() const { return fItemCount; ############################################################################ Commit: 0d603ac65ca118bfc02ea8ec34ef74f93c5fc956 Author: Ingo Weinhold <ingo_weinhold@xxxxxx> Date: Thu Jun 27 19:21:42 2013 UTC Fix node monitoring slot accounting for stop_watching() NodeMonitorService::RemoveUserListeners() didn't decrement io_context::num_monitors when removing a listener, so limit checks would be off afterwards. ---------------------------------------------------------------------------- diff --git a/src/system/kernel/fs/node_monitor.cpp b/src/system/kernel/fs/node_monitor.cpp index 601fd6c..d665344 100644 --- a/src/system/kernel/fs/node_monitor.cpp +++ b/src/system/kernel/fs/node_monitor.cpp @@ -982,6 +982,7 @@ NodeMonitorService::RemoveUserListeners(struct io_context *context, // to remove its successor (which is saved in "removeListener") _RemoveListener(removeListener); + context->num_monitors--; count++; } ############################################################################ Commit: 8c974aa8006f2f33f6b14563bda5a14f5bcc412d Author: Ingo Weinhold <ingo_weinhold@xxxxxx> Date: Thu Jun 27 19:22:02 2013 UTC node monitor: add TODO regarding the syscalls ---------------------------------------------------------------------------- diff --git a/src/system/kernel/fs/node_monitor.cpp b/src/system/kernel/fs/node_monitor.cpp index d665344..ae5e66e 100644 --- a/src/system/kernel/fs/node_monitor.cpp +++ b/src/system/kernel/fs/node_monitor.cpp @@ -1247,6 +1247,12 @@ notify_query_attr_changed(port_id port, int32 token, dev_t device, // #pragma mark - User syscalls +// TODO: We should verify that the port specified in the syscalls does actually +// belong to the calling team. The situation is complicated by the fact that a +// port can be transferred to another team. Consequently we should remove all +// associated monitor listeners when a port is transferred/deleted. + + status_t _user_stop_notifying(port_id port, uint32 token) { ############################################################################ Commit: cc4d194aeb41c48a4f7b7d00e3715f2f65a8af2c Author: Ingo Weinhold <ingo_weinhold@xxxxxx> Date: Thu Jun 27 19:37:08 2013 UTC Add test suite for BPathMonitor 749 / 1504 tests fail ---------------------------------------------------------------------------- diff --git a/src/tests/kits/storage/testapps/Jamfile b/src/tests/kits/storage/testapps/Jamfile index 2698a85..0b6ef3f 100644 --- a/src/tests/kits/storage/testapps/Jamfile +++ b/src/tests/kits/storage/testapps/Jamfile @@ -20,3 +20,5 @@ SimpleTest PathMonitorTest : PathMonitorTest.cpp PathMonitor.cpp : be $(TARGET_LIBSTDC++) ; + +SimpleTest PathMonitorTest2 : PathMonitorTest2.cpp : be $(TARGET_LIBSTDC++) ; diff --git a/src/tests/kits/storage/testapps/PathMonitorTest2.cpp b/src/tests/kits/storage/testapps/PathMonitorTest2.cpp new file mode 100644 index 0000000..0aa8400 --- /dev/null +++ b/src/tests/kits/storage/testapps/PathMonitorTest2.cpp @@ -0,0 +1,1438 @@ +/* + * Copyright 2013, Haiku, Inc. + * Distributed under the terms of the MIT License. + * + * Authors: + * Ingo Weinhold, ingo_weinhold@xxxxxx + */ + + +#include <errno.h> +#include <stdio.h> +#include <string.h> +#include <sys/time.h> + +#include <algorithm> +#include <set> +#include <vector> + +#include <Directory.h> +#include <Entry.h> +#include <File.h> +#include <Looper.h> +#include <ObjectList.h> +#include <Path.h> +#include <String.h> + +#include <AutoDeleter.h> +#include <AutoLocker.h> +#include <NotOwningEntryRef.h> +#include <PathMonitor.h> + + +using BPrivate::BPathMonitor; + + +static const char* const kTestBasePath = "/tmp/path-monitor-test"; +static const bigtime_t kMaxNotificationDelay = 100000; + + +#define FATAL(...) \ + do { \ + throw FatalException( \ + BString().SetToFormat("%s:%d: ", __FILE__, __LINE__) \ + << BString().SetToFormat(__VA_ARGS__)); \ + } while (false) + +#define FATAL_IF_ERROR(error, ...) \ + do { \ + status_t _fatalError = (error); \ + if (_fatalError < 0) { \ + throw FatalException( \ + BString().SetToFormat("%s:%d: ", __FILE__, __LINE__) \ + << BString().SetToFormat(__VA_ARGS__) \ + << BString().SetToFormat( \ + ": %s\n", strerror(_fatalError))); \ + } \ + } while (false) + +#define FATAL_IF_POSIX_ERROR(error, ...) \ + if ((error) < 0) \ + FATAL_IF_ERROR(errno, __VA_ARGS__) + +#define FAIL(...) \ + throw TestException(BString().SetToFormat(__VA_ARGS__)) + + +struct TestException { + TestException(const BString& message) + : + fMessage(message) + { + } + + const BString& Message() const + { + return fMessage; + } + +private: + BString fMessage; +}; + + +struct FatalException { + FatalException(const BString& message) + : + fMessage(message) + { + } + + const BString& Message() const + { + return fMessage; + } + +private: + BString fMessage; +}; + + +static BString +test_path(const BString& maybeRelativePath) +{ + if (maybeRelativePath.ByteAt(0) == '/') + return maybeRelativePath; + + BString path; + path.SetToFormat("%s/%s", kTestBasePath, maybeRelativePath.String()); + if (path.IsEmpty()) + FATAL_IF_ERROR(B_NO_MEMORY, "Failed to make absolute path"); + return path; +} + + +static BString +node_ref_to_string(const node_ref& nodeRef) +{ + return BString().SetToFormat("%" B_PRIdDEV ":%" B_PRIdINO, nodeRef.device, + nodeRef.node); +} + + +static BString +entry_ref_to_string(const entry_ref& entryRef) +{ + return BString().SetToFormat("%" B_PRIdDEV ":%" B_PRIdINO ":\"%s\"", + entryRef.device, entryRef.directory, entryRef.name); +} + + +static BString +indented_string(const char* string, const char* indent, + const char* firstIndent = NULL) +{ + const char* end = string + strlen(string); + BString result; + const char* line = string; + while (line < end) { + const char* lineEnd = strchr(line, '\n'); + lineEnd = lineEnd != NULL ? lineEnd + 1 : end; + result + << (line == string && firstIndent != NULL ? firstIndent : indent); + result.Append(line, lineEnd - line); + line = lineEnd; + } + + return result; +} + + +static BString +message_to_string(const BMessage& message) +{ + BString result; + + char* name; + type_code typeCode; + int32 count; + for (int32 i = 0; + message.GetInfo(B_ANY_TYPE, i, &name, &typeCode, &count) == B_OK; + i++) { + if (i > 0) + result << '\n'; + + result << '"' << name << '"'; + BString type; + + switch (typeCode) { + case B_UINT8_TYPE: + case B_INT8_TYPE: + type << "int8"; + break; + + case B_UINT16_TYPE: + type = "u"; + case B_INT16_TYPE: + type << "int16"; + break; + + case B_UINT32_TYPE: + type = "u"; + case B_INT32_TYPE: + type << "int32"; + break; + + case B_UINT64_TYPE: + type = "u"; + case B_INT64_TYPE: + type << "int64"; + break; + + case B_STRING_TYPE: + type = "string"; + break; + + default: + { + int code = (int)typeCode; + type.SetToFormat("'%02x%02x%02x%02x'", code >> 24, + (code >> 16) & 0xff, (code >> 8) & 0xff, code & 0xff); + break; + } + } + + result << " (" << type << "):"; + + for (int32 k = 0; k < count; k++) { + BString value; + switch (typeCode) { + case B_UINT8_TYPE: + value << message.GetUInt8(name, k, 0); + break; + case B_INT8_TYPE: + value << message.GetInt8(name, k, 0); + break; + case B_UINT16_TYPE: + value << message.GetUInt16(name, k, 0); + break; + case B_INT16_TYPE: + value << message.GetInt16(name, k, 0); + break; + case B_UINT32_TYPE: + value << message.GetUInt32(name, k, 0); + break; + case B_INT32_TYPE: + value << message.GetInt32(name, k, 0); + break; + case B_UINT64_TYPE: + value << message.GetUInt64(name, k, 0); + break; + case B_INT64_TYPE: + value << message.GetInt64(name, k, 0); + break; + case B_STRING_TYPE: + value.SetToFormat("\"%s\"", message.GetString(name, k, "")); + break; + default: + { + const void* data; + ssize_t size; + if (message.FindData(name, typeCode, k, &data, &size) + != B_OK) { + value = "???"; + break; + } + + for (ssize_t l = 0; l < size; l++) { + uint8 v = ((const uint8*)data)[l]; + value << BString().SetToFormat("%02x", v); + } + break; + } + } + + if (k == 0 && count == 1) { + result << ' ' << value; + } else { + result << BString().SetToFormat("\n [%2" B_PRId32 "] ", k) + << value; + } + } + } + + return result; +} + + +static BString +watch_flags_to_string(uint32 flags) +{ + BString result; + if ((flags & B_WATCH_NAME) != 0) + result << "name "; + if ((flags & B_WATCH_STAT) != 0) + result << "stat "; + if ((flags & B_WATCH_ATTR) != 0) + result << "attr "; + if ((flags & B_WATCH_DIRECTORY) != 0) + result << "dir "; + if ((flags & B_WATCH_RECURSIVELY) != 0) + result << "recursive "; + if ((flags & B_WATCH_FILES_ONLY) != 0) + result << "files-only "; + if ((flags & B_WATCH_DIRECTORIES_ONLY) != 0) + result << "dirs-only "; + + if (!result.IsEmpty()) + result.Truncate(result.Length() - 1); + return result; +} + + +struct MonitoringInfo { + MonitoringInfo() + { + } + + MonitoringInfo(int32 opcode, const char* path) + : + fOpcode(opcode) + { + _Init(opcode, path); + } + + MonitoringInfo(int32 opcode, const char* fromPath, const char* toPath) + { + _Init(opcode, toPath); + + // init fFromEntryRef + BEntry entry; + FATAL_IF_ERROR(entry.SetTo(fromPath), + "Failed to init BEntry for \"%s\"", fromPath); + FATAL_IF_ERROR(entry.GetRef(&fFromEntryRef), + "Failed to get entry_ref for \"%s\"", fromPath); + } + + BString ToString() const + { + switch (fOpcode) { + case B_ENTRY_CREATED: + case B_ENTRY_REMOVED: + return BString().SetToFormat("%s %s at %s", + fOpcode == B_ENTRY_CREATED ? "created" : "removed", + node_ref_to_string(fNodeRef).String(), + entry_ref_to_string(fEntryRef).String()); + + case B_ENTRY_MOVED: + return BString().SetToFormat("moved %s from %s to %s", + node_ref_to_string(fNodeRef).String(), + entry_ref_to_string(fFromEntryRef).String(), + entry_ref_to_string(fEntryRef).String()); + + case B_STAT_CHANGED: + return BString().SetToFormat("stat changed for %s", + node_ref_to_string(fNodeRef).String()); + + case B_ATTR_CHANGED: + return BString().SetToFormat("attr changed for %s", + node_ref_to_string(fNodeRef).String()); + + case B_DEVICE_MOUNTED: + return BString().SetToFormat("volume mounted"); + + case B_DEVICE_UNMOUNTED: + return BString().SetToFormat("volume unmounted"); + } + + return BString(); + } + + bool Matches(const BMessage& message) const + { + if (fOpcode != message.GetInt32("opcode", -1)) + return false; + + switch (fOpcode) { + case B_ENTRY_CREATED: + case B_ENTRY_REMOVED: + { + NotOwningEntryRef entryRef; + node_ref nodeRef; + + if (message.FindInt32("device", &nodeRef.device) != B_OK + || message.FindInt64("node", &nodeRef.node) != B_OK + || message.FindInt64("directory", &entryRef.directory) + != B_OK + || message.FindString("name", (const char**)&entryRef.name) + != B_OK) { + return false; + } + entryRef.device = nodeRef.device; + + return nodeRef == fNodeRef && entryRef == fEntryRef; + } + + case B_ENTRY_MOVED: + { + NotOwningEntryRef fromEntryRef; + NotOwningEntryRef toEntryRef; + node_ref nodeRef; + + if (message.FindInt32("node device", &nodeRef.device) != B_OK + || message.FindInt64("node", &nodeRef.node) != B_OK + || message.FindInt32("device", &fromEntryRef.device) + != B_OK + || message.FindInt64("from directory", + &fromEntryRef.directory) != B_OK + || message.FindInt64("to directory", &toEntryRef.directory) + != B_OK + || message.FindString("from name", + (const char**)&fromEntryRef.name) != B_OK + || message.FindString("name", + (const char**)&toEntryRef.name) != B_OK) { + return false; + } + toEntryRef.device = fromEntryRef.device; + + return nodeRef == fNodeRef && toEntryRef == fEntryRef + && fromEntryRef == fFromEntryRef; + } + + case B_STAT_CHANGED: + case B_ATTR_CHANGED: + { + node_ref nodeRef; + + if (message.FindInt32("device", &nodeRef.device) != B_OK + || message.FindInt64("node", &nodeRef.node) != B_OK) { + return false; + } + + return nodeRef == fNodeRef; + } + + case B_DEVICE_MOUNTED: + case B_DEVICE_UNMOUNTED: + return true; + } + + return false; + } + +private: + void _Init(int32 opcode, const char* path) + { + fOpcode = opcode; + BEntry entry; + FATAL_IF_ERROR(entry.SetTo(path), "Failed to init BEntry for \"%s\"", + path); + FATAL_IF_ERROR(entry.GetRef(&fEntryRef), + "Failed to get entry_ref for \"%s\"", path); + FATAL_IF_ERROR(entry.GetNodeRef(&fNodeRef), + "Failed to get node_ref for \"%s\"", path); + } + +private: + int32 fOpcode; + node_ref fNodeRef; + entry_ref fEntryRef; + entry_ref fFromEntryRef; +}; + + +struct MonitoringInfoSet { + MonitoringInfoSet() + { + } + + MonitoringInfoSet& Add(const MonitoringInfo& info, bool expected = true) + { + if (expected) + fInfos.push_back(info); + return *this; + } + + MonitoringInfoSet& Add(int32 opcode, const BString& path, + bool expected = true) + { + return Add(MonitoringInfo(opcode, test_path(path)), expected); + } + + MonitoringInfoSet& Add(int32 opcode, const BString& fromPath, + const BString& toPath, bool expected = true) + { + return Add(MonitoringInfo(opcode, test_path(fromPath), + test_path(toPath)), expected); + } + + bool IsEmpty() const + { + return fInfos.empty(); + } + + int32 CountInfos() const + { + return fInfos.size(); + } + + const MonitoringInfo& InfoAt(int32 index) const + { + return fInfos[index]; + } + + void Remove(int32 index) + { + fInfos.erase(fInfos.begin() + index); + } + + BString ToString() const + { + BString result; + for (int32 i = 0; i < CountInfos(); i++) { + const MonitoringInfo& info = InfoAt(i); + if (i > 0) + result << '\n'; + result << info.ToString(); + } + return result; + } + +private: + std::vector<MonitoringInfo> fInfos; +}; + + +struct Test : private BLooper { + Test(const char* name) + : + fName(name), + fFlags(0), + fLooperThread(-1), + fNotifications(10, true), + fProcessedMonitoringInfos(), + fIsWatching(false) + { + } + + void Init(uint32 flags) + { + fFlags = flags; + + // delete and re-create the test directory + BEntry entry; + FATAL_IF_ERROR(entry.SetTo(kTestBasePath), + "Failed to init entry to \"%s\"", kTestBasePath); + + if (entry.Exists()) + _RemoveRecursively(entry); + + _CreateDirectory(kTestBasePath); + + fLooperThread = BLooper::Run(); + if (fLooperThread < 0) + FATAL_IF_ERROR(fLooperThread, "Failed to init looper"); + } + + void Delete() + { + if (fIsWatching) + BPathMonitor::StopWatching(this); + + if (fLooperThread < 0) { + delete this; + } else { + PostMessage(B_QUIT_REQUESTED); + wait_for_thread(fLooperThread, NULL); + } + } + + void Do() + { + bool recursive = (fFlags & B_WATCH_RECURSIVELY) != 0; + DoInternal(recursive && (fFlags & B_WATCH_DIRECTORIES_ONLY) != 0, + recursive && (fFlags & B_WATCH_FILES_ONLY) != 0, recursive, + !recursive && (fFlags & B_WATCH_DIRECTORY) == 0, + (fFlags & B_WATCH_STAT) != 0); + + // verify that there aren't any spurious notifications + snooze(kMaxNotificationDelay); + + AutoLocker<BLooper> locker(this); + if (fNotifications.IsEmpty()) + return; + + BString pendingNotifications + = "unexpected notification(s) at end of test:"; + for (int32 i = 0; BMessage* message = fNotifications.ItemAt(i); i++) { + pendingNotifications << '\n' + << indented_string(message_to_string(*message), " ", " * "); + } + + FAIL("%s%s", pendingNotifications.String(), + _ProcessedInfosString().String()); + } + + const BString& Name() const + { + return fName; + } + +protected: + ~Test() + { + } + + void StartWatching(const char* path) + { + BString absolutePath(test_path(path)); + FATAL_IF_ERROR(BPathMonitor::StartWatching(absolutePath, fFlags, this), + "Failed to start watching \"%s\"", absolutePath.String()); + fIsWatching = true; + } + + MonitoringInfo CreateDirectory(const char* path) + { + BString absolutePath(test_path(path)); + _CreateDirectory(absolutePath); + return MonitoringInfo(B_ENTRY_CREATED, absolutePath); + } + + MonitoringInfo CreateFile(const char* path) + { + BString absolutePath(test_path(path)); + FATAL_IF_ERROR( + BFile().SetTo(absolutePath, B_CREATE_FILE | B_READ_WRITE), + "Failed to create file \"%s\"", absolutePath.String()); + return MonitoringInfo(B_ENTRY_CREATED, absolutePath); + } + + MonitoringInfo MoveEntry(const char* fromPath, const char* toPath) + { + BString absoluteFromPath(test_path(fromPath)); + BString absoluteToPath(test_path(toPath)); + FATAL_IF_POSIX_ERROR(rename(absoluteFromPath, absoluteToPath), + "Failed to move \"%s\" to \"%s\"", absoluteFromPath.String(), + absoluteToPath.String()); + return MonitoringInfo(B_ENTRY_MOVED, absoluteFromPath, absoluteToPath); + } + + MonitoringInfo RemoveEntry(const char* path) + { + BString absolutePath(test_path(path)); + MonitoringInfo info(B_ENTRY_REMOVED, absolutePath); + BEntry entry; + FATAL_IF_ERROR(entry.SetTo(absolutePath), + "Failed to init BEntry for \"%s\"", absolutePath.String()); + FATAL_IF_ERROR(entry.Remove(), + "Failed to remove entry \"%s\"", absolutePath.String()); + return info; + } + + MonitoringInfo TouchEntry(const char* path) + { + BString absolutePath(test_path(path)); + FATAL_IF_POSIX_ERROR(utimes(absolutePath, NULL), + "Failed to touch \"%s\"", absolutePath.String()); + MonitoringInfo info(B_STAT_CHANGED, absolutePath); + return info; + } + + void ExpectNotification(const MonitoringInfo& info, bool expected = true) + { + if (!expected) + return; + + AutoLocker<BLooper> locker(this); + if (fNotifications.IsEmpty()) { + locker.Unlock(); + snooze(kMaxNotificationDelay); + locker.Lock(); + } + + if (fNotifications.IsEmpty()) { + FAIL("missing notification, expected:\n %s", + info.ToString().String()); + } + + BMessage* message = fNotifications.RemoveItemAt(0); + ObjectDeleter<BMessage> messageDeleter(message); + + if (!info.Matches(*message)) { + BString processedInfosString(_ProcessedInfosString()); + FAIL("unexpected notification:\n expected:\n %s\n got:\n%s%s", + info.ToString().String(), + indented_string(message_to_string(*message), " ").String(), + processedInfosString.String()); + } + + fProcessedMonitoringInfos.Add(info); + } + + void ExpectNotifications(MonitoringInfoSet infos) + { + bool waited = false; + AutoLocker<BLooper> locker(this); + + while (!infos.IsEmpty()) { + if (fNotifications.IsEmpty()) { + locker.Unlock(); + if (!waited) { + snooze(kMaxNotificationDelay); + waited = true; + } + locker.Lock(); + } + + if (fNotifications.IsEmpty()) { + FAIL("missing notification(s), expected:\n%s", + indented_string(infos.ToString(), " ").String()); + } + + BMessage* message = fNotifications.RemoveItemAt(0); + ObjectDeleter<BMessage> messageDeleter(message); + + bool foundMatch = false; + for (int32 i = 0; i < infos.CountInfos(); i++) { + const MonitoringInfo& info = infos.InfoAt(i); + if (info.Matches(*message)) { + infos.Remove(i); + foundMatch = true; + break; + } + } + + if (foundMatch) + continue; + + BString processedInfosString(_ProcessedInfosString()); + FAIL("unexpected notification:\n expected:\n%s\n got:\n%s%s", + indented_string(infos.ToString(), " ").String(), + indented_string(message_to_string(*message), " ").String(), + processedInfosString.String()); + } + } + + virtual void DoInternal(bool directoriesOnly, bool filesOnly, + bool recursive, bool pathOnly, bool watchStat) = 0; + +private: + typedef BObjectList<BMessage> MessageList; + typedef BObjectList<MonitoringInfo> MonitoringInfoList; + +private: + virtual void MessageReceived(BMessage* message) + { + switch (message->what) { + case B_PATH_MONITOR: + if (!fNotifications.AddItem(new BMessage(*message))) + FATAL_IF_ERROR(B_NO_MEMORY, "Failed to store notification"); + break; + + default: + BLooper::MessageReceived(message); + break; + } + } + +private: + void _CreateDirectory(const char* path) + { + FATAL_IF_ERROR(create_directory(path, 0755), + "Failed to create directory \"%s\"", path); + } + + void _RemoveRecursively(BEntry& entry) + { + // recurse, if the entry is a directory + if (entry.IsDirectory()) { + BDirectory directory; + FATAL_IF_ERROR(directory.SetTo(&entry), + "Failed to init BDirectory for \"%s\"", + BPath(&entry).Path()); + + BEntry childEntry; + while (directory.GetNextEntry(&childEntry) == B_OK) + _RemoveRecursively(childEntry); + } + + // remove the entry + FATAL_IF_ERROR(entry.Remove(), "Failed to remove entry \"%s\"", + BPath(&entry).Path()); + } + + BString _ProcessedInfosString() const + { + BString processedInfosString; + if (!fProcessedMonitoringInfos.IsEmpty()) { + processedInfosString << "\nprocessed so far:\n" + << indented_string(fProcessedMonitoringInfos.ToString(), " "); + } + return processedInfosString; + } + +protected: + BString fName; + uint32 fFlags; + thread_id fLooperThread; + MessageList fNotifications; + MonitoringInfoSet fProcessedMonitoringInfos; + bool fIsWatching; +}; + + +struct TestBase : Test { +protected: + TestBase(const char* name) + : + Test(name) + { + } + + void StandardSetup() + { + CreateDirectory("base"); + CreateDirectory("base/dir1"); + CreateDirectory("base/dir1/dir0"); + CreateFile("base/file0"); + CreateFile("base/dir1/file0.0"); + } +}; + + +#define CREATE_TEST_WITH_CUSTOM_SETUP(name, code) \ + struct Test##name : TestBase { \ + Test##name() : TestBase(#name) {} \ + virtual void DoInternal(bool directoriesOnly, bool filesOnly, \ + bool recursive, bool pathOnly, bool watchStat) \ + { \ + code \ + } \ + }; \ + tests.push_back(new Test##name); + +#define CREATE_TEST(name, code) \ + CREATE_TEST_WITH_CUSTOM_SETUP(name, \ + StandardSetup(); \ + StartWatching("base"); \ + code \ + ) + + +static void +create_tests(std::vector<Test*>& tests) +{ + // test coverage: + // - file/directory outside + // - file/directory at top level + // - file/directory at sub level + // - move file/directory into/within/out of + // - move non-empty directory into/within/out of + // - create/move ancestor folder + // - remove/move ancestor folder + // - touch path, file/directory at top and sub level + // - base file instead of directory + // + // not covered (yet): + // - mount/unmount below/in our path + // - test symlink in watched path + // - attribute watching (should be similar to stat watching) + + CREATE_TEST(FileOutside, + CreateFile("file1"); + MoveEntry("file1", "file2"); + RemoveEntry("file2"); + ) + + CREATE_TEST(DirectoryOutside, + CreateDirectory("dir1"); + MoveEntry("dir1", "dir2"); + RemoveEntry("dir2"); + ) + + CREATE_TEST(FileTopLevel, + ExpectNotification(CreateFile("base/file1"), + !directoriesOnly && !pathOnly); + ExpectNotification(MoveEntry("base/file1", "base/file2"), + !directoriesOnly && !pathOnly); + ExpectNotification(RemoveEntry("base/file2"), + !directoriesOnly && !pathOnly); + ) + + CREATE_TEST(DirectoryTopLevel, + ExpectNotification(CreateDirectory("base/dir2"), + !filesOnly && !pathOnly); + ExpectNotification(MoveEntry("base/dir2", "base/dir3"), + !filesOnly && !pathOnly); + ExpectNotification(RemoveEntry("base/dir3"), + !filesOnly && !pathOnly); + ) + + CREATE_TEST(FileSubLevel, + ExpectNotification(CreateFile("base/dir1/file1"), + recursive && !directoriesOnly); + ExpectNotification(MoveEntry("base/dir1/file1", "base/dir1/file2"), + recursive && !directoriesOnly); + ExpectNotification(RemoveEntry("base/dir1/file2"), + recursive && !directoriesOnly); + ) + + CREATE_TEST(DirectorySubLevel, + ExpectNotification(CreateDirectory("base/dir1/dir2"), + recursive && !filesOnly); + ExpectNotification(MoveEntry("base/dir1/dir2", "base/dir1/dir3"), + recursive && !filesOnly); + ExpectNotification(RemoveEntry("base/dir1/dir3"), + recursive && !filesOnly); + ) + + CREATE_TEST(FileMoveIntoTopLevel, + CreateFile("file1"); + ExpectNotification(MoveEntry("file1", "base/file2"), + !directoriesOnly && !pathOnly); + ExpectNotification(RemoveEntry("base/file2"), + !directoriesOnly && !pathOnly); + ) + + CREATE_TEST(DirectoryMoveIntoTopLevel, + CreateDirectory("dir2"); + ExpectNotification(MoveEntry("dir2", "base/dir3"), + !filesOnly && !pathOnly); + ExpectNotification(RemoveEntry("base/dir3"), + !filesOnly && !pathOnly); + ) + + CREATE_TEST(FileMoveIntoSubLevel, + CreateFile("file1"); + ExpectNotification(MoveEntry("file1", "base/dir1/file2"), + recursive && !directoriesOnly); + ExpectNotification(RemoveEntry("base/dir1/file2"), + recursive && !directoriesOnly); + ) + + CREATE_TEST(DirectoryMoveIntoSubLevel, + CreateDirectory("dir2"); + ExpectNotification(MoveEntry("dir2", "base/dir1/dir3"), + recursive && !filesOnly); + ExpectNotification(RemoveEntry("base/dir1/dir3"), + recursive && !filesOnly); + ) + + CREATE_TEST(FileMoveOutOfTopLevel, + ExpectNotification(CreateFile("base/file1"), + !directoriesOnly && !pathOnly); + ExpectNotification(MoveEntry("base/file1", "file2"), + !directoriesOnly && !pathOnly); + RemoveEntry("file2"); + ) + + CREATE_TEST(DirectoryMoveOutOfTopLevel, + ExpectNotification(CreateDirectory("base/dir2"), + !filesOnly && !pathOnly); + ExpectNotification(MoveEntry("base/dir2", "dir3"), + !filesOnly && !pathOnly); + RemoveEntry("dir3"); + ) + + CREATE_TEST(FileMoveOutOfSubLevel, + ExpectNotification(CreateFile("base/dir1/file1"), + recursive && !directoriesOnly); + ExpectNotification(MoveEntry("base/dir1/file1", "file2"), + recursive && !directoriesOnly); + RemoveEntry("file2"); + ) + + CREATE_TEST(DirectoryMoveOutOfSubLevel, + ExpectNotification(CreateDirectory("base/dir1/dir2"), + recursive && !filesOnly); + ExpectNotification(MoveEntry("base/dir1/dir2", "dir3"), + recursive && !filesOnly); + RemoveEntry("dir3"); + ) + + CREATE_TEST(FileMoveToTopLevel, + ExpectNotification(CreateFile("base/dir1/file1"), + !directoriesOnly && recursive); + ExpectNotification(MoveEntry("base/dir1/file1", "base/file2"), + !directoriesOnly && !pathOnly); + ExpectNotification(RemoveEntry("base/file2"), + !directoriesOnly && !pathOnly); + ) + + CREATE_TEST(DirectoryMoveToTopLevel, + ExpectNotification(CreateDirectory("base/dir1/dir2"), + !filesOnly && recursive); + ExpectNotification(MoveEntry("base/dir1/dir2", "base/dir3"), + !filesOnly && !pathOnly); + ExpectNotification(RemoveEntry("base/dir3"), + !filesOnly && !pathOnly); + ) + + CREATE_TEST(FileMoveToSubLevel, + ExpectNotification(CreateFile("base/file1"), + !directoriesOnly && !pathOnly); + ExpectNotification(MoveEntry("base/file1", "base/dir1/file2"), + !directoriesOnly && !pathOnly); + ExpectNotification(RemoveEntry("base/dir1/file2"), + !directoriesOnly && recursive); + ) + + CREATE_TEST(DirectoryMoveToSubLevel, + ExpectNotification(CreateDirectory("base/dir2"), + !filesOnly && !pathOnly); + ExpectNotification(MoveEntry("base/dir2", "base/dir1/dir3"), + !filesOnly && !pathOnly); + ExpectNotification(RemoveEntry("base/dir1/dir3"), + !filesOnly && recursive); + ) + + CREATE_TEST(NonEmptyDirectoryMoveIntoTopLevel, + CreateDirectory("dir2"); + CreateDirectory("dir2/dir3"); + CreateDirectory("dir2/dir4"); + CreateFile("dir2/file1"); + CreateFile("dir2/dir3/file2"); + ExpectNotification(MoveEntry("dir2", "base/dir5"), + !filesOnly && !pathOnly); + if (recursive && filesOnly) { + ExpectNotifications(MonitoringInfoSet() + .Add(B_ENTRY_CREATED, "base/dir5/file1") + .Add(B_ENTRY_CREATED, "base/dir5/dir3/file2")); + } + ) + + CREATE_TEST(NonEmptyDirectoryMoveIntoSubLevel, + CreateDirectory("dir2"); + CreateDirectory("dir2/dir3"); + CreateDirectory("dir2/dir4"); + CreateFile("dir2/file1"); + CreateFile("dir2/dir3/file2"); + ExpectNotification(MoveEntry("dir2", "base/dir1/dir5"), + !filesOnly && recursive); + if (recursive && filesOnly) { + ExpectNotifications(MonitoringInfoSet() + .Add(B_ENTRY_CREATED, "base/dir1/dir5/file1") + .Add(B_ENTRY_CREATED, "base/dir1/dir5/dir3/file2")); + } + ) + + CREATE_TEST_WITH_CUSTOM_SETUP(NonEmptyDirectoryMoveOutOfTopLevel, + StandardSetup(); + CreateDirectory("base/dir2"); + CreateDirectory("base/dir2/dir3"); + CreateDirectory("base/dir2/dir4"); + CreateFile("base/dir2/file1"); + CreateFile("base/dir2/dir3/file2"); + StartWatching("base"); + MonitoringInfoSet filesRemoved; + if (recursive && filesOnly) { + filesRemoved + .Add(B_ENTRY_REMOVED, "base/dir2/file1") + .Add(B_ENTRY_REMOVED, "base/dir2/dir3/file2"); + } + ExpectNotification(MoveEntry("base/dir2", "dir5"), + !filesOnly && !pathOnly); + ExpectNotifications(filesRemoved); + ) + + CREATE_TEST_WITH_CUSTOM_SETUP(NonEmptyDirectoryMoveOutOfSubLevel, + StandardSetup(); + CreateDirectory("base/dir1/dir2"); + CreateDirectory("base/dir1/dir2/dir3"); + CreateDirectory("base/dir1/dir2/dir4"); + CreateFile("base/dir1/dir2/file1"); + CreateFile("base/dir1/dir2/dir3/file2"); + StartWatching("base"); + MonitoringInfoSet filesRemoved; + if (recursive && filesOnly) { + filesRemoved + .Add(B_ENTRY_REMOVED, "base/dir1/dir2/file1") + .Add(B_ENTRY_REMOVED, "base/dir1/dir2/dir3/file2"); + } + ExpectNotification(MoveEntry("base/dir1/dir2", "dir5"), + !filesOnly && recursive); + ExpectNotifications(filesRemoved); + ) + + CREATE_TEST_WITH_CUSTOM_SETUP(NonEmptyDirectoryMoveToTopLevel, + StandardSetup(); + CreateDirectory("base/dir1/dir2"); + CreateDirectory("base/dir1/dir2/dir3"); + CreateDirectory("base/dir1/dir2/dir4"); + CreateFile("base/dir1/dir2/file1"); + CreateFile("base/dir1/dir2/dir3/file2"); + StartWatching("base"); + MonitoringInfoSet filesMoved; + if (recursive && filesOnly) { + filesMoved + .Add(B_ENTRY_REMOVED, "base/dir1/dir2/file1") + .Add(B_ENTRY_REMOVED, "base/dir1/dir2/dir3/file2"); + } + ExpectNotification(MoveEntry("base/dir1/dir2", "base/dir5"), + !filesOnly && !pathOnly); + if (recursive && filesOnly) { + filesMoved + .Add(B_ENTRY_CREATED, "base/dir5/file1") + .Add(B_ENTRY_CREATED, "base/dir5/dir3/file2"); + } + ExpectNotifications(filesMoved); + ) + + CREATE_TEST_WITH_CUSTOM_SETUP(NonEmptyDirectoryMoveToSubLevel, + StandardSetup(); + CreateDirectory("base/dir2"); + CreateDirectory("base/dir2/dir3"); + CreateDirectory("base/dir2/dir4"); + CreateFile("base/dir2/file1"); + CreateFile("base/dir2/dir3/file2"); + StartWatching("base"); + MonitoringInfoSet filesMoved; + if (recursive && filesOnly) { + filesMoved + .Add(B_ENTRY_REMOVED, "base/dir2/file1") + .Add(B_ENTRY_REMOVED, "base/dir2/dir3/file2"); + } + ExpectNotification(MoveEntry("base/dir2", "base/dir1/dir5"), + !filesOnly && !pathOnly); + if (recursive && filesOnly) { + filesMoved + .Add(B_ENTRY_CREATED, "base/dir1/dir5/file1") + .Add(B_ENTRY_CREATED, "base/dir1/dir5/dir3/file2"); + } + ExpectNotifications(filesMoved); + ) + + CREATE_TEST_WITH_CUSTOM_SETUP(CreateAncestor, + StartWatching("ancestor/base"); + CreateDirectory("ancestor"); + ) + + CREATE_TEST_WITH_CUSTOM_SETUP(MoveCreateAncestor, + CreateDirectory("ancestorSibling"); + StartWatching("ancestor/base"); + MoveEntry("ancestorSibling", "ancestor"); + ) + + CREATE_TEST_WITH_CUSTOM_SETUP(MoveCreateAncestorWithBase, + CreateDirectory("ancestorSibling"); + CreateDirectory("ancestorSibling/base"); + StartWatching("ancestor/base"); + MoveEntry("ancestorSibling", "ancestor"); + MonitoringInfoSet entriesCreated; + if (!filesOnly) + entriesCreated.Add(B_ENTRY_CREATED, "ancestor/base"); + ExpectNotifications(entriesCreated); + ) + + CREATE_TEST_WITH_CUSTOM_SETUP(MoveCreateAncestorWithBaseAndFile, + CreateDirectory("ancestorSibling"); + CreateDirectory("ancestorSibling/base"); + CreateFile("ancestorSibling/base/file1"); + StartWatching("ancestor/base"); + MoveEntry("ancestorSibling", "ancestor"); + MonitoringInfoSet entriesCreated; + if (!filesOnly) + entriesCreated.Add(B_ENTRY_CREATED, "ancestor/base"); + else if (!pathOnly) + entriesCreated.Add(B_ENTRY_CREATED, "ancestor/base/file1"); + ExpectNotifications(entriesCreated); + ) + + CREATE_TEST_WITH_CUSTOM_SETUP(MoveCreateAncestorWithBaseAndDirectory, + CreateDirectory("ancestorSibling"); + CreateDirectory("ancestorSibling/base"); + CreateDirectory("ancestorSibling/base/dir1"); + CreateFile("ancestorSibling/base/dir1/file1"); + StartWatching("ancestor/base"); + MoveEntry("ancestorSibling", "ancestor"); + MonitoringInfoSet entriesCreated; + if (!filesOnly) { + entriesCreated.Add(B_ENTRY_CREATED, "ancestor/base"); + } else if (recursive) + entriesCreated.Add(B_ENTRY_CREATED, "ancestor/base/dir1/file1"); + ExpectNotifications(entriesCreated); + ) + + CREATE_TEST_WITH_CUSTOM_SETUP(CreateBase, + CreateDirectory("ancestor"); + StartWatching("ancestor/base"); + ExpectNotification(CreateDirectory("ancestor/base"), + !filesOnly); + ) + + CREATE_TEST_WITH_CUSTOM_SETUP(MoveCreateBase, + CreateDirectory("ancestor"); + CreateDirectory("ancestor/baseSibling"); + StartWatching("ancestor/base"); + ExpectNotification(MoveEntry("ancestor/baseSibling", "ancestor/base"), + !filesOnly); + ) + + CREATE_TEST_WITH_CUSTOM_SETUP(MoveCreateBaseWithFile, + CreateDirectory("ancestor"); + CreateDirectory("ancestor/baseSibling"); + CreateFile("ancestor/baseSibling/file1"); + StartWatching("ancestor/base"); + ExpectNotification(MoveEntry("ancestor/baseSibling", "ancestor/base"), + !filesOnly); + MonitoringInfoSet entriesCreated; + if (filesOnly && !pathOnly) + entriesCreated.Add(B_ENTRY_CREATED, "ancestor/base/file1"); + ExpectNotifications(entriesCreated); + ) + + CREATE_TEST_WITH_CUSTOM_SETUP(MoveCreateBaseWithDirectory, + CreateDirectory("ancestor"); + CreateDirectory("ancestor/baseSibling"); + CreateDirectory("ancestor/baseSibling/dir1"); + CreateFile("ancestor/baseSibling/dir1/file1"); + StartWatching("ancestor/base"); + ExpectNotification(MoveEntry("ancestor/baseSibling", "ancestor/base"), + !filesOnly); + MonitoringInfoSet entriesCreated; + if (filesOnly && recursive) + entriesCreated.Add(B_ENTRY_CREATED, "ancestor/base/dir1/file1"); + ExpectNotifications(entriesCreated); + ) + + CREATE_TEST_WITH_CUSTOM_SETUP(MoveRemoveAncestorWithBaseAndFile, + CreateDirectory("ancestor"); + CreateDirectory("ancestor/base"); + CreateFile("ancestor/base/file1"); + StartWatching("ancestor/base"); + MonitoringInfoSet entriesRemoved; + if (!filesOnly) + entriesRemoved.Add(B_ENTRY_REMOVED, "ancestor/base"); + else if (!pathOnly) + entriesRemoved.Add(B_ENTRY_REMOVED, "ancestor/base/file1"); + MoveEntry("ancestor", "ancestorSibling"); + ExpectNotifications(entriesRemoved); + ) + + CREATE_TEST_WITH_CUSTOM_SETUP(MoveRemoveAncestorWithBaseAndDirectory, + CreateDirectory("ancestor"); + CreateDirectory("ancestor/base"); + CreateDirectory("ancestor/base/dir1"); + CreateFile("ancestor/base/dir1/file1"); + StartWatching("ancestor/base"); + MonitoringInfoSet entriesRemoved; + if (!filesOnly) + entriesRemoved.Add(B_ENTRY_REMOVED, "ancestor/base"); + else if (recursive) + entriesRemoved.Add(B_ENTRY_REMOVED, "ancestor/base/dir1/file1"); + MoveEntry("ancestor", "ancestorSibling"); + ExpectNotifications(entriesRemoved); + ) + + CREATE_TEST_WITH_CUSTOM_SETUP(MoveRemoveBaseWithFile, + CreateDirectory("ancestor"); + CreateDirectory("ancestor/base"); + CreateFile("ancestor/base/file1"); + StartWatching("ancestor/base"); + MonitoringInfoSet entriesRemoved; + if (filesOnly && !pathOnly) + entriesRemoved.Add(B_ENTRY_REMOVED, "ancestor/base/file1"); + ExpectNotification(MoveEntry("ancestor/base", "ancestor/baseSibling"), + !filesOnly); + ExpectNotifications(entriesRemoved); + ) + + CREATE_TEST_WITH_CUSTOM_SETUP(MoveRemoveBaseWithDirectory, + CreateDirectory("ancestor"); + CreateDirectory("ancestor/base"); + CreateDirectory("ancestor/base/dir1"); + CreateFile("ancestor/base/dir1/file1"); + StartWatching("ancestor/base"); + MonitoringInfoSet entriesRemoved; + if (filesOnly && recursive) + entriesRemoved.Add(B_ENTRY_REMOVED, "ancestor/base/dir1/file1"); + ExpectNotification(MoveEntry("ancestor/base", "ancestor/baseSibling"), + !filesOnly); + ExpectNotifications(entriesRemoved); + ) + + CREATE_TEST(TouchBase, + ExpectNotification(TouchEntry("base"), watchStat && !filesOnly); + ) + + CREATE_TEST(TouchFileTopLevel, + ExpectNotification(TouchEntry("base/file0"), + watchStat && recursive && !directoriesOnly); + ) + + CREATE_TEST(TouchFileSubLevel, + ExpectNotification(TouchEntry("base/dir1/file0.0"), + watchStat && recursive && !directoriesOnly); + ) + + CREATE_TEST(TouchDirectoryTopLevel, + ExpectNotification(TouchEntry("base/dir1"), + watchStat && recursive && !filesOnly); + ) + + CREATE_TEST(TouchDirectorySubLevel, + ExpectNotification(TouchEntry("base/dir1/dir0"), + watchStat && recursive && !filesOnly); + ) + + CREATE_TEST_WITH_CUSTOM_SETUP(CreateFileBase, + StartWatching("file"); + ExpectNotification(CreateFile("file"), + !directoriesOnly); + ) + + CREATE_TEST_WITH_CUSTOM_SETUP(MoveCreateFileBase, + CreateFile("fileSibling"); + StartWatching("file"); + ExpectNotification(MoveEntry("fileSibling", "file"), + !directoriesOnly); + ) + + CREATE_TEST_WITH_CUSTOM_SETUP(RemoveFileBase, + CreateFile("file"); + StartWatching("file"); + ExpectNotification(RemoveEntry("file"), + !directoriesOnly); + ) + + CREATE_TEST_WITH_CUSTOM_SETUP(MoveRemoveFileBase, + CreateFile("file"); + StartWatching("file"); + ExpectNotification(MoveEntry("file", "fileSibling"), + !directoriesOnly); + ) + + CREATE_TEST_WITH_CUSTOM_SETUP(TouchFileBase, + CreateFile("file"); + StartWatching("file"); + ExpectNotification(TouchEntry("file"), + watchStat && !directoriesOnly); + ) +} + + +static void +run_tests(std::set<BString> testNames, uint32 watchFlags, + size_t& totalTests, size_t& succeededTests) +{ + std::vector<Test*> tests; + create_tests(tests); + + // filter the tests, if test names have been specified + size_t testCount = tests.size(); + if (!testNames.empty()) { + for (size_t i = 0; i < testCount;) { + Test* test = tests[i]; + std::set<BString>::iterator it = testNames.find(test->Name()); + if (it != testNames.end()) { + testNames.erase(it); + i++; + } else { + tests.erase(tests.begin() + i); + test->Delete(); + testCount--; + } + } + + if (!testNames.empty()) { + printf("no such test(s):\n"); + for (std::set<BString>::iterator it = testNames.begin(); + it != testNames.end(); ++it) { + printf(" %s\n", it->String()); + exit(1); + } + } + } + + printf("\nrunning tests with flags: %s\n", + watch_flags_to_string(watchFlags).String()); + + int32 longestTestName = 0; + + for (size_t i = 0; i < testCount; i++) { + Test* test = tests[i]; + longestTestName = std::max(longestTestName, test->Name().Length()); + } + + for (size_t i = 0; i < testCount; i++) { + Test* test = tests[i]; + bool terminate = false; + + try { + totalTests++; + test->Init(watchFlags); + printf(" %s: %*s", test->Name().String(), + int(longestTestName - test->Name().Length()), ""); + fflush(stdout); + test->Do(); + printf("SUCCEEDED\n"); + succeededTests++; + } catch (FatalException& exception) { + printf("FAILED FATALLY\n"); + printf("%s\n", + indented_string(exception.Message(), " ").String()); + terminate = true; + } catch (TestException& exception) { + printf("FAILED\n"); + printf("%s\n", + indented_string(exception.Message(), " ").String()); + } + + test->Delete(); + + if (terminate) + exit(1); + } +} + + +int +main(int argc, const char* const* argv) +{ + // any args are test names + std::set<BString> testNames; + for (int i = 1; i < argc; i++) + testNames.insert(argv[i]); + + // flags that can be combined arbitrarily + const uint32 kFlags[] = { + B_WATCH_NAME, + B_WATCH_STAT, + // not that interesting, since similar to B_WATCH_STAT: B_WATCH_ATTR + B_WATCH_DIRECTORY, + B_WATCH_RECURSIVELY, + }; + const size_t kFlagCount = sizeof(kFlags) / sizeof(kFlags[0]); + + size_t totalTests = 0; + size_t succeededTests = 0; + + for (size_t i = 0; i < 1 << kFlagCount; i++) { + // construct flags mask + uint32 flags = 0; + for (size_t k = 0; k < kFlagCount; k++) { + if ((i & (1 << k)) != 0) + flags |= kFlags[k]; + } + + // run tests -- in recursive mode do that additionally for the mutually + // B_WATCH_FILES_ONLY and B_WATCH_DIRECTORIES_ONLY flags. + run_tests(testNames, flags, totalTests, succeededTests); + if ((flags & B_WATCH_RECURSIVELY) != 0) { + run_tests(testNames, flags | B_WATCH_FILES_ONLY, totalTests, + succeededTests); + run_tests(testNames, flags | B_WATCH_DIRECTORIES_ONLY, totalTests, + succeededTests); + } + } + + printf("\n"); + if (succeededTests == totalTests) { + printf("ALL TESTS SUCCEEDED\n"); + } else { + printf("%zu of %zu TESTS FAILED\n", totalTests - succeededTests, + totalTests); + } + + return 0; +} ############################################################################ Commit: 04382d496e445d90a3aab7c8752ee9cb59825035 Author: Ingo Weinhold <ingo_weinhold@xxxxxx> Date: Thu Jun 27 19:46:29 2013 UTC BPathMonitor: rewrite This resolves all issues the test suite uncovered. It should also deal with hard links correctly, though that hasn't been tested. Still unsupported are: * changes due to mounting/unmounting a volume, * tracking of symlinks in the path components. ---------------------------------------------------------------------------- diff --git a/headers/private/storage/PathMonitor.h b/headers/private/storage/PathMonitor.h index 3309eb2..2a511ec 100644 --- a/headers/private/storage/PathMonitor.h +++ b/headers/private/storage/PathMonitor.h @@ -1,5 +1,5 @@ /* - * Copyright 2007-2013, Haiku Inc. All Rights Reserved. + * Copyright 2007-2013, Haiku, Inc. All Rights Reserved. * Distributed under the terms of the MIT License. */ #ifndef _PATH_MONITOR_H @@ -9,12 +9,23 @@ #include <NodeMonitor.h> +// Monitoring a path always implies B_WATCH_NAME for the path itself. I.e. even +// if only B_WATCH_STAT is specified, B_ENTRY_{CREATED,MOVED,REMOVED} +// notifications are sent when the respective entry is created/moved/removed. + // additional flags (combined with those in NodeMonitor.h) -#define B_WATCH_FILES_ONLY 0x0100 -#define B_WATCH_RECURSIVELY 0x0200 +#define B_WATCH_RECURSIVELY 0x0100 + // Watch not only the entry specified by the path, but also recursively all + // descendents. A recursive B_WATCH_DIRECTORY is implied, i.e. + // B_ENTRY_{CREATED,MOVED,REMOVED} notifications will be sent for any entry + // change below the given path, unless explicitly suppressed by + // B_WATCH_{FILES,DIRECTORIES}_ONLY. +#define B_WATCH_FILES_ONLY 0x0200 + // A notification will only be sent when the node it concerns is not a + // directory. No effect in non-recursive mode. #define B_WATCH_DIRECTORIES_ONLY 0x0400 -// NOTE: B_WATCH_RECURSIVELY usually implies to watch for file changes as well, -// if that is not desired, add B_WATCH_DIRECTORIES_ONLY to the flags. + // A notification will only be sent when the node it concerns is a + // directory. No effect in non-recursive mode. #define B_PATH_MONITOR '_PMN' @@ -69,4 +80,7 @@ public: } // namespace BPrivate +using BPrivate::BPathMonitor; + + #endif // _PATH_MONITOR_H diff --git a/src/kits/storage/Jamfile b/src/kits/storage/Jamfile index 6a053c7..dba6e60 100644 --- a/src/kits/storage/Jamfile +++ b/src/kits/storage/Jamfile @@ -2,7 +2,7 @@ SubDir HAIKU_TOP src kits storage ; SetSubDirSupportedPlatforms haiku libbe_test ; -UsePrivateHeaders app libroot shared storage ; +UsePrivateHeaders app kernel libroot shared storage ; UsePrivateSystemHeaders ; # for libbe_test diff --git a/src/kits/storage/PathMonitor.cpp b/src/kits/storage/PathMonitor.cpp index a782171..1167c34 100644 --- a/src/kits/storage/PathMonitor.cpp +++ b/src/kits/storage/PathMonitor.cpp @@ -1,10 +1,11 @@ /* - * Copyright 2007-2013, Haiku Inc. All Rights Reserved. + * Copyright 2007-2013, Haiku, Inc. All Rights Reserved. * Distributed under the terms of the MIT License. * * Authors: * Axel Dörfler, axeld@xxxxxxxxxxxxxxxx - * Stephan Aßmus <superstippi@xxxxxx> + * Stephan Aßmus, superstippi@xxxxxx + * Ingo Weinhold, ingo_weinhold@xxxxxx */ @@ -26,217 +27,847 @@ #include <Path.h> #include <String.h> +#include <AutoDeleter.h> +#include <NotOwningEntryRef.h> #include <ObjectList.h> +#include <util/OpenHashTable.h> +#include <util/SinglyLinkedList.h> #undef TRACE //#define TRACE_PATH_MONITOR #ifdef TRACE_PATH_MONITOR -# define TRACE(x...) debug_printf(x) +# define TRACE(...) debug_printf("BPathMonitor: " __VA_ARGS__) #else -# define TRACE(x...) ; +# define TRACE(...) ; #endif -using namespace BPrivate; -using namespace std; -using std::nothrow; // TODO: Remove this line if the above line is enough. +// TODO: Support symlink components in the path. +// TODO: Support mounting/unmounting of volumes in path components and within +// the watched path tree. -// TODO: Use optimizations where stuff is already known to avoid iterating -// the watched directory and files set too often. - #define WATCH_NODE_FLAG_MASK 0x00ff -namespace BPrivate { -struct FileEntry { - entry_ref ref; - ino_t node; -}; +namespace { + + +struct Directory; +struct Node; +struct WatcherHashDefinition; +typedef BOpenHashTable<WatcherHashDefinition> WatcherMap; + + +static pthread_once_t sInitOnce = PTHREAD_ONCE_INIT; +static WatcherMap* sWatchers = NULL; +static BLocker* sLocker = NULL; +static BLooper* sLooper = NULL; +static BPathMonitor::BWatchingInterface* sDefaultWatchingInterface = NULL; +static BPathMonitor::BWatchingInterface* sWatchingInterface = NULL; + + +// #pragma mark - + + +/*! Returns empty path, if either \a parent or \a subPath is empty or an + allocation fails. + */ +static BString +make_path(const BString& parent, const char* subPath) +{ + BString path = parent; + int32 length = path.Length(); + if (length == 0 || subPath[0] == '\0') + return BString(); + + if (parent.ByteAt(length - 1) != '/') { + path << '/'; + if (path.Length() < ++length) + return BString(); + } + + path << subPath; + if (path.Length() <= length) + return BString(); + return path; +} + -#if __GNUC__ > 3 - bool operator<(const FileEntry& a, const FileEntry& b); - class FileEntryLess : public binary_function<FileEntry, FileEntry, bool> +// #pragma mark - Ancestor + + +class Ancestor { +public: + Ancestor(Ancestor* parent, const BString& path, size_t pathComponentOffset) + : + fParent(parent), + fChild(NULL), + fPath(path), + fEntryRef(-1, -1, fPath.String() + pathComponentOffset), + fNodeRef(), + fWatchingFlags(0), + fIsDirectory(false) { - public: - bool operator() (const FileEntry& a, const FileEntry& b) const - { - return a < b; + if (pathComponentOffset == 0) { + // must be "/" + fEntryRef.SetTo(-1, -1, "."); [ *** diff truncated: 2720 lines dropped *** ] ############################################################################ Commit: c143884fdf03ec3a8b7a89c88baa477f8629395c Author: Ingo Weinhold <ingo_weinhold@xxxxxx> Date: Thu Jun 27 19:54:43 2013 UTC Use incorrect use of BPathMonitor in input/midi/net server The B_ENTRY_* constants aren't valid watch flags. ----------------------------------------------------------------------------