diff options
-rw-r--r-- | net/dns/dns_config_service.cc | 82 | ||||
-rw-r--r-- | net/dns/dns_config_service.h | 67 | ||||
-rw-r--r-- | net/dns/dns_config_service_posix.cc | 205 | ||||
-rw-r--r-- | net/dns/dns_config_service_posix.h | 81 | ||||
-rw-r--r-- | net/dns/dns_config_service_posix_unittest.cc | 349 | ||||
-rw-r--r-- | net/dns/dns_config_service_unittest.cc | 108 | ||||
-rw-r--r-- | net/dns/dns_hosts.cc | 47 | ||||
-rw-r--r-- | net/dns/dns_hosts.h | 41 | ||||
-rw-r--r-- | net/dns/dns_hosts_unittest.cc | 61 | ||||
-rw-r--r-- | net/dns/watching_file_reader.cc | 133 | ||||
-rw-r--r-- | net/dns/watching_file_reader.h | 133 | ||||
-rw-r--r-- | net/dns/watching_file_reader_unittest.cc | 289 | ||||
-rw-r--r-- | net/net.gyp | 7 |
13 files changed, 992 insertions, 611 deletions
diff --git a/net/dns/dns_config_service.cc b/net/dns/dns_config_service.cc index f32f293..ed3f1d2 100644 --- a/net/dns/dns_config_service.cc +++ b/net/dns/dns_config_service.cc @@ -4,6 +4,7 @@ #include "net/dns/dns_config_service.h" +#include "base/file_util.h" #include "net/base/ip_endpoint.h" namespace net { @@ -18,7 +19,7 @@ DnsConfig::DnsConfig() DnsConfig::~DnsConfig() {} -bool DnsConfig::Equals(const DnsConfig& d) const { +bool DnsConfig::EqualsIgnoreHosts(const DnsConfig& d) const { return (nameservers == d.nameservers) && (search == d.search) && (ndots == d.ndots) && @@ -28,5 +29,84 @@ bool DnsConfig::Equals(const DnsConfig& d) const { (edns0 == d.edns0); } +bool DnsConfig::Equals(const DnsConfig& d) const { + return EqualsIgnoreHosts(d) && (hosts == d.hosts); +} + +DnsConfigService::DnsConfigService() + : have_config_(false), + have_hosts_(false) {} + +DnsConfigService::~DnsConfigService() {} + +void DnsConfigService::AddObserver(Observer* observer) { + DCHECK(CalledOnValidThread()); + observers_.AddObserver(observer); + if (have_config_ && have_hosts_) { + observer->OnConfigChanged(dns_config_); + } +} + +void DnsConfigService::RemoveObserver(Observer* observer) { + DCHECK(CalledOnValidThread()); + observers_.RemoveObserver(observer); +} + +void DnsConfigService::OnConfigRead(const DnsConfig& config) { + DCHECK(CalledOnValidThread()); + if (!config.EqualsIgnoreHosts(dns_config_)) { + DnsConfig copy = config; + copy.hosts.swap(dns_config_.hosts); + dns_config_ = copy; + have_config_ = true; + if (have_hosts_) { + FOR_EACH_OBSERVER(Observer, observers_, OnConfigChanged(dns_config_)); + } + } +} + +void DnsConfigService::OnHostsRead(const DnsHosts& hosts) { + DCHECK(CalledOnValidThread()); + if (hosts != dns_config_.hosts || !have_hosts_) { + dns_config_.hosts = hosts; + have_hosts_ = true; + if (have_config_) { + FOR_EACH_OBSERVER(Observer, observers_, OnConfigChanged(dns_config_)); + } + } +} + +DnsHostsReader::DnsHostsReader(DnsConfigService* service) + : service_(service), + success_(false) { + DCHECK(service); +} + +// Reads the contents of the file at |path| into |str| if the total length is +// less than |size|. +static bool ReadFile(const FilePath& path, int64 size, std::string* str) { + int64 sz; + if (!file_util::GetFileSize(path, &sz) || sz > size) + return false; + return file_util::ReadFileToString(path, str); +} + +void DnsHostsReader::DoRead() { + success_ = false; + std::string contents; + const int64 kMaxHostsSize = 1 << 16; + if (ReadFile(get_path(), kMaxHostsSize, &contents)) { + success_ = true; + ParseHosts(contents, &dns_hosts_); + } +} + +void DnsHostsReader::OnReadFinished() { + if (success_) + service_->OnHostsRead(dns_hosts_); +} + +DnsHostsReader::~DnsHostsReader() {} + } // namespace net diff --git a/net/dns/dns_config_service.h b/net/dns/dns_config_service.h index 535dcc1..6ceeaa6 100644 --- a/net/dns/dns_config_service.h +++ b/net/dns/dns_config_service.h @@ -6,16 +6,20 @@ #define NET_DNS_DNS_CONFIG_SERVICE_H_ #pragma once +#include <map> #include <string> #include <vector> +#include "base/gtest_prod_util.h" +#include "base/observer_list.h" #include "base/time.h" +#include "net/base/ip_endpoint.h" // win requires size of IPEndPoint #include "net/base/net_export.h" +#include "net/dns/dns_hosts.h" +#include "net/dns/watching_file_reader.h" namespace net { -class IPEndPoint; - // DnsConfig stores configuration of the system resolver. struct NET_EXPORT_PRIVATE DnsConfig { DnsConfig(); @@ -23,7 +27,9 @@ struct NET_EXPORT_PRIVATE DnsConfig { bool Equals(const DnsConfig& d) const; - bool Valid() const { + bool EqualsIgnoreHosts(const DnsConfig& d) const; + + bool IsValid() const { return !nameservers.empty(); } @@ -33,9 +39,12 @@ struct NET_EXPORT_PRIVATE DnsConfig { // is less than |ndots|. std::vector<std::string> search; + DnsHosts hosts; + // Resolver options; see man resolv.conf. // TODO(szym): use |ndots| and |search| to determine the sequence of FQDNs // to query given a specific name. + // TODO(szym): implement DNS Devolution for windows // Minimum number of dots before global resolution precedes |search|. int ndots; @@ -49,9 +58,11 @@ struct NET_EXPORT_PRIVATE DnsConfig { bool edns0; }; + // Service for watching when the system DNS settings have changed. // Depending on the platform, watches files in /etc/ or win registry. -class NET_EXPORT_PRIVATE DnsConfigService { +class NET_EXPORT_PRIVATE DnsConfigService + : NON_EXPORTED_BASE(public base::NonThreadSafe) { public: // Callback interface for the client. The observer is called on the same // thread as Watch(). Observer must outlive the service. @@ -59,30 +70,64 @@ class NET_EXPORT_PRIVATE DnsConfigService { public: virtual ~Observer() {} - // Called only when |dns_config| is different from the last check. + // Called only when |dns_config| is different from the last call. virtual void OnConfigChanged(const DnsConfig& dns_config) = 0; }; // Creates the platform-specific DnsConfigService. static DnsConfigService* CreateSystemService(); - DnsConfigService() {} - virtual ~DnsConfigService() {} + DnsConfigService(); + virtual ~DnsConfigService(); // Immediately starts watching system configuration for changes and attempts // to read the configuration. For some platform implementations, the current // thread must have an IO loop (for base::files::FilePathWatcher). - virtual void Watch() = 0; + virtual void Watch() {} // If a config is available, |observer| will immediately be called with // OnConfigChanged. - virtual void AddObserver(Observer* observer) = 0; - virtual void RemoveObserver(Observer* observer) = 0; - + void AddObserver(Observer* observer); + void RemoveObserver(Observer* observer); + + protected: + FRIEND_TEST_ALL_PREFIXES(DnsConfigServiceTest, NotifyOnChange); + friend class DnsHostsReader; + // To be called with new config. |config|.hosts is ignored. + virtual void OnConfigRead(const DnsConfig& config); + // To be called with new hosts. Rest of the config is assumed unchanged. + virtual void OnHostsRead(const DnsHosts& hosts); + + DnsConfig dns_config_; + // True after first OnConfigRead and OnHostsRead; indicate complete config. + bool have_config_; + bool have_hosts_; + + ObserverList<Observer> observers_; private: DISALLOW_COPY_AND_ASSIGN(DnsConfigService); }; +// A WatchingFileReader that reads a HOSTS file and notifies +// DnsConfigService::OnHostsRead(). +class DnsHostsReader : public WatchingFileReader { + public: + explicit DnsHostsReader(DnsConfigService* service); + + virtual void DoRead() OVERRIDE; + virtual void OnReadFinished() OVERRIDE; + + private: + virtual ~DnsHostsReader(); + + DnsConfigService* service_; + // Written in DoRead, read in OnReadFinished, no locking necessary. + DnsHosts dns_hosts_; + bool success_; + + DISALLOW_COPY_AND_ASSIGN(DnsHostsReader); +}; + } // namespace net #endif // NET_DNS_DNS_CONFIG_SERVICE_H_ diff --git a/net/dns/dns_config_service_posix.cc b/net/dns/dns_config_service_posix.cc index c79e43c..c8b4ab1 100644 --- a/net/dns/dns_config_service_posix.cc +++ b/net/dns/dns_config_service_posix.cc @@ -6,16 +6,10 @@ #include <string> -#include "base/bind.h" #include "base/compiler_specific.h" #include "base/file_path.h" -#include "base/files/file_path_watcher.h" -#include "base/memory/ref_counted.h" +#include "base/file_util.h" #include "base/memory/scoped_ptr.h" -#include "base/message_loop.h" -#include "base/message_loop_proxy.h" -#include "base/observer_list.h" -#include "base/threading/worker_pool.h" #include "net/base/ip_endpoint.h" #include "net/base/net_util.h" @@ -25,198 +19,53 @@ namespace net { -// FilePathWatcher::Delegate is refcounted, so we separate it from the Service -// It also hosts callbacks on the WorkerPool. -class DnsConfigServicePosix::WatcherDelegate - : public base::files::FilePathWatcher::Delegate { +// A WatcherDelegate that uses ResolverLib to initialize res_state and converts +// it to DnsConfig. +class DnsConfigServicePosix::DnsConfigReader : public WatchingFileReader { public: - // Takes ownership of |lib|. - WatcherDelegate(DnsConfigServicePosix* service, - DnsConfigServicePosix::ResolverLib* lib) + explicit DnsConfigReader(DnsConfigServicePosix* service) : service_(service), - resolver_lib_(lib), - message_loop_(base::MessageLoopProxy::current()), - reading_(false), - read_pending_(false) {} + success_(false) {} - void Cancel() { - DCHECK(message_loop_->BelongsToCurrentThread()); - service_ = NULL; - } - - void RescheduleWatch() { - DCHECK(message_loop_->BelongsToCurrentThread()); - // Retry Watch in 100ms or so. - message_loop_->PostDelayedTask( - FROM_HERE, base::Bind(&WatcherDelegate::StartWatch, this), 100); - } - - // FilePathWatcher::Delegate interface - virtual void OnFilePathChanged(const FilePath& path) OVERRIDE { - DCHECK(message_loop_->BelongsToCurrentThread()); - if (!service_) - return; - ScheduleRead(); - } - - virtual void OnFilePathError(const FilePath& path) OVERRIDE { - DCHECK(message_loop_->BelongsToCurrentThread()); - StartWatch(); - } - - private: - virtual ~WatcherDelegate() {} - - // Unless already scheduled, post DoRead to WorkerPool. - void ScheduleRead() { - if (reading_) { - // Mark that we need to re-read after DoRead posts results. - read_pending_ = true; - } else { - if (!base::WorkerPool::PostTask(FROM_HERE, base::Bind( - &WatcherDelegate::DoRead, this), false)) { - // See worker_pool_posix.cc. - NOTREACHED() << "WorkerPool::PostTask is not expected to fail on posix"; - } - reading_ = true; - read_pending_ = false; - } - } - - // Reads DnsConfig and posts OnResultAvailable to |message_loop_|. - // Must be called on the worker thread. - void DoRead() { - DnsConfig config; + void DoRead() OVERRIDE { struct __res_state res; - bool success = false; - if (resolver_lib_->ninit(&res) == 0) { - success = ConvertResToConfig(res, &config); - resolver_lib_->nclose(&res); - } - // If this fails, the loop is gone, so there is no point retrying. - message_loop_->PostTask(FROM_HERE, base::Bind( - &WatcherDelegate::OnResultAvailable, this, config, success)); - } - - // Communicates result to the service. Must be called on the the same thread - // that constructed WatcherDelegate. - void OnResultAvailable(const DnsConfig &config, bool success) { - DCHECK(message_loop_->BelongsToCurrentThread()); - if (!service_) - return; - reading_ = false; - if (read_pending_) { - // Discard this result and re-schedule. - ScheduleRead(); - return; - } - if (!success) { - VLOG(1) << "Failed to read DnsConfig"; + if ((res_ninit(&res) == 0) && (res.options & RES_INIT)) { + success_ = ConvertResToConfig(res, &dns_config_); } else { - service_->OnConfigRead(config); + // Note: res_ninit in glibc always returns 0 and sets RES_INIT. + success_ = false; } + res_nclose(&res); } - // To avoid refcounting the service, use this method in tasks/callbacks. - void StartWatch() { - if (!service_) - return; - service_->StartWatch(); + void OnReadFinished() OVERRIDE { + if (success_) + service_->OnConfigRead(dns_config_); } + private: + virtual ~DnsConfigReader() {} + DnsConfigServicePosix* service_; - scoped_ptr<DnsConfigServicePosix::ResolverLib> resolver_lib_; - // Message loop for the thread on which Watch is called (of TYPE_IO). - scoped_refptr<base::MessageLoopProxy> message_loop_; - // True after DoRead before OnResultsAvailable. - bool reading_; - // True after OnFilePathChanged fires while |reading_| is true. - bool read_pending_; + // Written in DoRead, read in OnReadFinished, no locking necessary. + DnsConfig dns_config_; + bool success_; }; DnsConfigServicePosix::DnsConfigServicePosix() - : have_config_(false), - resolver_lib_(new ResolverLib()), - watcher_factory_(new FilePathWatcherFactory()) { -} + : config_reader_(new DnsConfigReader(this)), + hosts_reader_(new DnsHostsReader(this)) {} DnsConfigServicePosix::~DnsConfigServicePosix() { DCHECK(CalledOnValidThread()); - if (watcher_delegate_.get()) - watcher_delegate_->Cancel(); -} - -void DnsConfigServicePosix::AddObserver(Observer* observer) { - DCHECK(CalledOnValidThread()); - observers_.AddObserver(observer); - if (have_config_) { - observer->OnConfigChanged(dns_config_); - } -} - -void DnsConfigServicePosix::RemoveObserver(Observer* observer) { - DCHECK(CalledOnValidThread()); - observers_.RemoveObserver(observer); + config_reader_->Cancel(); + hosts_reader_->Cancel(); } void DnsConfigServicePosix::Watch() { DCHECK(CalledOnValidThread()); - DCHECK(!watcher_delegate_.get()); - DCHECK(!resolv_file_watcher_.get()); - DCHECK(resolver_lib_.get()); - DCHECK(watcher_factory_.get()); - - watcher_delegate_ = new WatcherDelegate(this, resolver_lib_.release()); - StartWatch(); -} - -void DnsConfigServicePosix::OnConfigRead(const DnsConfig& config) { - DCHECK(CalledOnValidThread()); - if (!config.Equals(dns_config_)) { - dns_config_ = config; - have_config_ = true; - FOR_EACH_OBSERVER(Observer, observers_, OnConfigChanged(config)); - } -} - -void DnsConfigServicePosix::StartWatch() { - DCHECK(CalledOnValidThread()); - DCHECK(watcher_delegate_.get()); - - FilePath path(FILE_PATH_LITERAL(_PATH_RESCONF)); - - // FilePathWatcher allows only one Watch per lifetime, so we need a new one. - resolv_file_watcher_.reset(watcher_factory_->CreateFilePathWatcher()); - if (resolv_file_watcher_->Watch(path, watcher_delegate_.get())) { - // Make the initial read after watch is installed. - watcher_delegate_->OnFilePathChanged(path); - } else { - VLOG(1) << "Watch failed, scheduling restart"; - watcher_delegate_->RescheduleWatch(); - } -} - -int DnsConfigServicePosix::ResolverLib::ninit(res_state statp) { - return ::res_ninit(statp); -} - -void DnsConfigServicePosix::ResolverLib::nclose(res_state statp) { - return ::res_nclose(statp); -} - -DnsConfigServicePosix::FilePathWatcherShim::FilePathWatcherShim() - : watcher_(new base::files::FilePathWatcher()) {} -DnsConfigServicePosix::FilePathWatcherShim::~FilePathWatcherShim() {} - -bool DnsConfigServicePosix::FilePathWatcherShim::Watch( - const FilePath& path, - base::files::FilePathWatcher::Delegate* delegate) { - return watcher_->Watch(path, delegate); -} - -DnsConfigServicePosix::FilePathWatcherShim* -DnsConfigServicePosix::FilePathWatcherFactory::CreateFilePathWatcher() { - return new FilePathWatcherShim(); + config_reader_->StartWatch(FilePath(FILE_PATH_LITERAL(_PATH_RESCONF))); + hosts_reader_->StartWatch(FilePath(FILE_PATH_LITERAL("/etc/hosts"))); } // static diff --git a/net/dns/dns_config_service_posix.h b/net/dns/dns_config_service_posix.h index 1cdd0b2..5349adf 100644 --- a/net/dns/dns_config_service_posix.h +++ b/net/dns/dns_config_service_posix.h @@ -9,102 +9,29 @@ #include <resolv.h> #include "base/compiler_specific.h" -#include "base/files/file_path_watcher.h" #include "base/memory/ref_counted.h" -#include "base/memory/scoped_ptr.h" -#include "base/observer_list.h" -#include "base/threading/non_thread_safe.h" #include "net/base/net_export.h" #include "net/dns/dns_config_service.h" namespace net { class NET_EXPORT_PRIVATE DnsConfigServicePosix - : public NON_EXPORTED_BASE(DnsConfigService), - public NON_EXPORTED_BASE(base::NonThreadSafe) { + : NON_EXPORTED_BASE(public DnsConfigService) { public: - class ResolverLib; - class FilePathWatcherShim; - class FilePathWatcherFactory; - class WatcherDelegate; + class DnsConfigReader; DnsConfigServicePosix(); virtual ~DnsConfigServicePosix(); - virtual void AddObserver(Observer* observer) OVERRIDE; - virtual void RemoveObserver(Observer* observer) OVERRIDE; virtual void Watch() OVERRIDE; - // Takes ownership of |lib|. Must be set before Watch. - void set_resolver_lib(ResolverLib* lib) { - DCHECK(!watcher_delegate_.get()); - resolver_lib_.reset(lib); - } - - // Takes ownership of |factory|. Must be set before Watch. - void set_watcher_factory(FilePathWatcherFactory* factory) { - DCHECK(!watcher_delegate_.get()); - watcher_factory_.reset(factory); - } - private: - void OnConfigRead(const DnsConfig& config); - - // Configure a FilePathWatcher and install watcher_delegate_. Executed each - // time FilePathWatcher fails. - void StartWatch(); - - DnsConfig dns_config_; - // True after first OnConfigChanged, that is, dns_config_ is valid. - bool have_config_; - - scoped_ptr<ResolverLib> resolver_lib_; - scoped_ptr<FilePathWatcherFactory> watcher_factory_; - scoped_ptr<FilePathWatcherShim> resolv_file_watcher_; - scoped_refptr<WatcherDelegate> watcher_delegate_; - ObserverList<Observer> observers_; + scoped_refptr<WatchingFileReader> config_reader_; + scoped_refptr<WatchingFileReader> hosts_reader_; DISALLOW_COPY_AND_ASSIGN(DnsConfigServicePosix); }; - -// Allows mocking res_ninit. -class NET_EXPORT_PRIVATE DnsConfigServicePosix::ResolverLib - : public NON_EXPORTED_BASE(base::NonThreadSafe) { - public: - ResolverLib() {} - virtual ~ResolverLib() {} - virtual int ninit(res_state statp); - virtual void nclose(res_state statp); - private: - DISALLOW_COPY_AND_ASSIGN(ResolverLib); -}; - -// Allows mocking FilePathWatcher -class NET_EXPORT_PRIVATE DnsConfigServicePosix::FilePathWatcherShim - : public NON_EXPORTED_BASE(base::NonThreadSafe) { - public: - FilePathWatcherShim(); - virtual ~FilePathWatcherShim(); - - virtual bool Watch( - const FilePath& path, - base::files::FilePathWatcher::Delegate* delegate) WARN_UNUSED_RESULT; - private: - scoped_ptr<base::files::FilePathWatcher> watcher_; - DISALLOW_COPY_AND_ASSIGN(FilePathWatcherShim); -}; - -class NET_EXPORT_PRIVATE DnsConfigServicePosix::FilePathWatcherFactory - : public NON_EXPORTED_BASE(base::NonThreadSafe) { - public: - FilePathWatcherFactory() {} - virtual ~FilePathWatcherFactory() {} - virtual FilePathWatcherShim* CreateFilePathWatcher(); - private: - DISALLOW_COPY_AND_ASSIGN(FilePathWatcherFactory); -}; - // Fills in |dns_config| from |res|. Exposed for tests. bool NET_EXPORT_PRIVATE ConvertResToConfig(const struct __res_state& res, DnsConfig* dns_config); diff --git a/net/dns/dns_config_service_posix_unittest.cc b/net/dns/dns_config_service_posix_unittest.cc index d38d589..a3f90aa 100644 --- a/net/dns/dns_config_service_posix_unittest.cc +++ b/net/dns/dns_config_service_posix_unittest.cc @@ -5,14 +5,8 @@ #include <arpa/inet.h> #include <resolv.h> -#include "base/bind.h" -#include "base/compiler_specific.h" -#include "base/message_loop.h" -#include "base/message_loop_proxy.h" -#include "base/synchronization/waitable_event.h" -#include "base/threading/thread.h" -#include "net/base/ip_endpoint.h" #include "net/dns/dns_config_service_posix.h" + #include "testing/gtest/include/gtest/gtest.h" namespace net { @@ -114,264 +108,18 @@ void CloseResState(res_state res) { #endif } -class DnsConfigServiceTest : public testing::Test, - public DnsConfigService::Observer { - public: - // Mocks - - // DnsConfigService owns the instances of ResolverLib and - // FilePathWatcherFactory that it gets, so use simple proxies to call - // DnsConfigServiceTest. - - // ResolverLib is owned by WatcherDelegate which is posted to WorkerPool so - // it must be canceled before the test is over. - class MockResolverLib : public DnsConfigServicePosix::ResolverLib { - public: - explicit MockResolverLib(DnsConfigServiceTest *test) : test_(test) {} - virtual ~MockResolverLib() { - base::AutoLock lock(lock_); - if (test_) { - EXPECT_TRUE(test_->IsComplete()); - } - } - virtual int ninit(res_state res) OVERRIDE { - base::AutoLock lock(lock_); - if (test_) - return test_->OnNinit(res); - else - return -1; - } - virtual void nclose(res_state res) OVERRIDE { - CloseResState(res); - } - void Cancel() { - base::AutoLock lock(lock_); - test_ = NULL; - } - private: - base::Lock lock_; - DnsConfigServiceTest *test_; - }; - - class MockFilePathWatcherShim - : public DnsConfigServicePosix::FilePathWatcherShim { - public: - typedef base::files::FilePathWatcher::Delegate Delegate; - - explicit MockFilePathWatcherShim(DnsConfigServiceTest* t) : test_(t) {} - virtual ~MockFilePathWatcherShim() { - test_->OnShimDestroyed(this); - } - - // Enforce one-Watch-per-lifetime as the original FilePathWatcher - virtual bool Watch(const FilePath& path, - Delegate* delegate) OVERRIDE { - EXPECT_TRUE(path_.empty()) << "Only one-Watch-per-lifetime allowed."; - EXPECT_TRUE(!delegate_.get()); - path_ = path; - delegate_ = delegate; - return test_->OnWatch(); - } - - void PathChanged() { - delegate_->OnFilePathChanged(path_); - } - - void PathError() { - delegate_->OnFilePathError(path_); - } - - private: - FilePath path_; - scoped_refptr<Delegate> delegate_; - DnsConfigServiceTest* test_; - }; - - class MockFilePathWatcherFactory - : public DnsConfigServicePosix::FilePathWatcherFactory { - public: - explicit MockFilePathWatcherFactory(DnsConfigServiceTest* t) : test(t) {} - virtual ~MockFilePathWatcherFactory() { - EXPECT_TRUE(test->IsComplete()); - } - virtual DnsConfigServicePosix::FilePathWatcherShim* - CreateFilePathWatcher() OVERRIDE { - return test->CreateFilePathWatcher(); - } - DnsConfigServiceTest* test; - }; - - // Helpers for mocks. - - DnsConfigServicePosix::FilePathWatcherShim* CreateFilePathWatcher() { - watcher_shim_ = new MockFilePathWatcherShim(this); - return watcher_shim_; - } - - void OnShimDestroyed(MockFilePathWatcherShim* destroyed_shim) { - // Precaution to avoid segfault. - if (watcher_shim_ == destroyed_shim) - watcher_shim_ = NULL; - } - - // On each event, post QuitTask to allow use of MessageLoop::Run() to - // synchronize the threads. - - bool OnWatch() { - EXPECT_TRUE(message_loop_ == MessageLoop::current()); - watch_called_ = true; - BreakNow("OnWatch"); - return !fail_on_watch_; - } - - int OnNinit(res_state res) { - { // Check that res_ninit is executed serially. - base::AutoLock lock(ninit_lock_); - EXPECT_FALSE(ninit_running_) << "res_ninit is not called serially!"; - ninit_running_ = true; - } - BreakNow("OnNinit"); - ninit_allowed_.Wait(); - // Calling from another thread is a bit dirty, but it's protected. - int rv = OnNinitNonThreadSafe(res); - // This lock might be destroyed after ninit_called_ is signalled. - { - base::AutoLock lock(ninit_lock_); - ninit_running_ = false; - } - ninit_called_.Signal(); - return rv; - } - - virtual void OnConfigChanged(const DnsConfig& new_config) OVERRIDE { - EXPECT_TRUE(message_loop_ == MessageLoop::current()); - CompareConfig(res_, new_config); - EXPECT_FALSE(new_config.Equals(last_config_)) << - "Config must be different from last call."; - last_config_ = new_config; - got_config_ = true; - BreakNow("OnConfigChanged"); - } - - bool IsComplete() { - return complete_; - } - - protected: - friend class BreakTask; - class BreakTask : public Task { - public: - BreakTask(DnsConfigServiceTest* test, std::string breakpoint) - : test_(test), breakpoint_(breakpoint) {} - virtual ~BreakTask() {} - virtual void Run() OVERRIDE { - test_->breakpoint_ = breakpoint_; - MessageLoop::current()->QuitNow(); - } - private: - DnsConfigServiceTest* test_; - std::string breakpoint_; - }; - - void BreakNow(std::string b) { - message_loop_->PostTask(FROM_HERE, new BreakTask(this, b)); - } - - void RunUntilBreak(std::string b) { - message_loop_->Run(); - ASSERT_EQ(breakpoint_, b); - } - - DnsConfigServiceTest() - : res_lib_(new MockResolverLib(this)), - watcher_shim_(NULL), - res_generation_(1), - watch_called_(false), - got_config_(false), - fail_on_watch_(false), - fail_on_ninit_(false), - complete_(false), - ninit_allowed_(false, false), - ninit_called_(false, false), - ninit_running_(false) { - } - - // This is on WorkerPool, but protected by ninit_allowed_. - int OnNinitNonThreadSafe(res_state res) { - if (fail_on_ninit_) - return -1; - InitializeResState(res, res_generation_); - // Store a (deep) copy in the fixture to later verify correctness. - CloseResState(&res_); - InitializeResState(&res_, res_generation_); - return 0; - } - - // Helpers for tests. - - // Lets OnNinit run OnNinitNonThreadSafe and waits for it to complete. - // Might get OnConfigChanged scheduled on the loop but we have no certainty. - void WaitForNinit() { - RunUntilBreak("OnNinit"); - ninit_allowed_.Signal(); - ninit_called_.Wait(); - } - - // test::Test methods - virtual void SetUp() OVERRIDE { - message_loop_ = MessageLoop::current(); - service_.reset(new DnsConfigServicePosix()); - service_->set_resolver_lib(res_lib_); - service_->set_watcher_factory(new MockFilePathWatcherFactory(this)); - memset(&res_, 0, sizeof(res_)); - } - - virtual void TearDown() OVERRIDE { - // res_lib_ could outlive the test, so make sure it doesn't call it. - res_lib_->Cancel(); - CloseResState(&res_); - // Allow service_ to clean up ResolverLib and FilePathWatcherFactory. - complete_ = true; - } - - MockResolverLib* res_lib_; - MockFilePathWatcherShim* watcher_shim_; - // Adds variety to the content of res_state. - int res_generation_; - struct __res_state res_; - DnsConfig last_config_; - - bool watch_called_; - bool got_config_; - bool fail_on_watch_; - bool fail_on_ninit_; - bool complete_; - - // Ninit is called on WorkerPool so we need to synchronize with it. - base::WaitableEvent ninit_allowed_; - base::WaitableEvent ninit_called_; - - // Protected by ninit_lock_. Used to verify that Ninit calls are serialized. - bool ninit_running_; - base::Lock ninit_lock_; - - // Loop for this thread. - MessageLoop* message_loop_; - - // Service under test. - scoped_ptr<DnsConfigServicePosix> service_; - - std::string breakpoint_; -}; +} // namespace TEST(DnsConfigTest, ResolverConfigConvertAndEquals) { struct __res_state res[2]; DnsConfig config[2]; for (int i = 0; i < 2; ++i) { + EXPECT_FALSE(config[i].IsValid()); InitializeResState(&res[i], i); ASSERT_TRUE(ConvertResToConfig(res[i], &config[i])); } for (int i = 0; i < 2; ++i) { + EXPECT_TRUE(config[i].IsValid()); CompareConfig(res[i], config[i]); CloseResState(&res[i]); } @@ -380,93 +128,6 @@ TEST(DnsConfigTest, ResolverConfigConvertAndEquals) { EXPECT_FALSE(config[1].Equals(config[0])); } -TEST_F(DnsConfigServiceTest, FilePathWatcherFailures) { - // For these tests, disable ninit. - res_lib_->Cancel(); - - fail_on_watch_ = true; - service_->Watch(); - RunUntilBreak("OnWatch"); - EXPECT_TRUE(watch_called_) << "Must call FilePathWatcher::Watch()."; - - fail_on_watch_ = false; - watch_called_ = false; - RunUntilBreak("OnWatch"); // Due to backoff this will take 100ms. - EXPECT_TRUE(watch_called_) << - "Must restart on FilePathWatcher::Watch() failure."; - - watch_called_ = false; - ASSERT_TRUE(watcher_shim_); - watcher_shim_->PathError(); - RunUntilBreak("OnWatch"); - EXPECT_TRUE(watch_called_) << - "Must restart on FilePathWatcher::Delegate::OnFilePathError()."; - - // Worker thread could still be posting OnResultAvailable to the message loop -} - -TEST_F(DnsConfigServiceTest, NotifyOnValidAndDistinctConfig) { - service_->AddObserver(this); - service_->Watch(); - RunUntilBreak("OnWatch"); - fail_on_ninit_ = true; - WaitForNinit(); - - // If OnNinit posts OnResultAvailable before the next call, then this test - // verifies that failure on ninit should not cause OnConfigChanged. - // Otherwise, this only verifies that ninit calls are serialized. - - fail_on_ninit_ = false; - ASSERT_TRUE(watcher_shim_); - watcher_shim_->PathChanged(); - WaitForNinit(); - - RunUntilBreak("OnConfigChanged"); - EXPECT_TRUE(got_config_); - - message_loop_->AssertIdle(); - - got_config_ = false; - // Forget about the config to test if we get it again on AddObserver. - last_config_ = DnsConfig(); - service_->RemoveObserver(this); - service_->AddObserver(this); - RunUntilBreak("OnConfigChanged"); - EXPECT_TRUE(got_config_) << "Did not get config after AddObserver."; - - // Simulate spurious FilePathChanged. - ASSERT_TRUE(watcher_shim_); - watcher_shim_->PathChanged(); - WaitForNinit(); - - // OnConfigChanged will catch that the config did not actually change. - - got_config_ = false; - ++res_generation_; - ASSERT_TRUE(watcher_shim_); - watcher_shim_->PathChanged(); - WaitForNinit(); - RunUntilBreak("OnConfigChanged"); - EXPECT_TRUE(got_config_) << "Did not get config after change"; - - message_loop_->AssertIdle(); - - // Schedule two calls. OnNinit checks if it is called serially. - ++res_generation_; - ASSERT_TRUE(watcher_shim_); - watcher_shim_->PathChanged(); - // ninit is blocked, so this will have to induce read_pending - watcher_shim_->PathChanged(); - WaitForNinit(); - WaitForNinit(); - RunUntilBreak("OnConfigChanged"); - EXPECT_TRUE(got_config_) << "Did not get config after change"; - - // We should be done with all tasks. - message_loop_->AssertIdle(); -} - -} // namespace - } // namespace net + diff --git a/net/dns/dns_config_service_unittest.cc b/net/dns/dns_config_service_unittest.cc new file mode 100644 index 0000000..0a86dca --- /dev/null +++ b/net/dns/dns_config_service_unittest.cc @@ -0,0 +1,108 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/dns/dns_config_service.h" + +#include "base/compiler_specific.h" +#include "base/message_loop.h" +#include "base/task.h" +#include "base/test/test_timeouts.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace net { + +namespace { + +class DnsConfigServiceTest : public testing::Test, + public DnsConfigService::Observer { + public: + void OnConfigChanged(const DnsConfig& config) OVERRIDE { + EXPECT_FALSE(config.Equals(last_config_)) << + "Config must be different from last call."; + last_config_ = config; + if (quit_on_config_) + MessageLoop::current()->Quit(); + } + void Timeout() const { + MessageLoop::current()->Quit(); + } + + protected: + // test::Test methods + virtual void SetUp() OVERRIDE { + service_.reset(new DnsConfigService()); + quit_on_config_ = false; + } + + DnsConfig last_config_; + bool quit_on_config_; + + // Service under test. + scoped_ptr<DnsConfigService> service_; +}; + +} // namespace + +TEST_F(DnsConfigServiceTest, NotifyOnChange) { + service_->AddObserver(this); + service_->Watch(); + + DnsConfig config; + IPAddressNumber ip; + ASSERT_TRUE(ParseIPLiteralToNumber("1.2.3.4", &ip)); + config.nameservers.push_back(IPEndPoint(ip, 53)); + ASSERT_TRUE(config.IsValid()); + service_->OnConfigRead(config); + + EXPECT_FALSE(last_config_.Equals(config)) << + "Unexpected notification with incomplete config."; + + DnsHosts hosts; + ParseHosts("127.0.0.1 localhost", &hosts); + ASSERT_FALSE(hosts.empty()); + service_->OnHostsRead(hosts); + + EXPECT_TRUE(last_config_.hosts == hosts); + EXPECT_TRUE(last_config_.EqualsIgnoreHosts(config)); + + DnsConfig complete_config = config; + complete_config.hosts = hosts; + EXPECT_TRUE(last_config_.Equals(complete_config)); + + // Check if it ignores no-change in config or hosts. + service_->OnConfigRead(config); + service_->OnHostsRead(hosts); + + // Check if it provides config and hosts after AddObserver; + service_->RemoveObserver(this); + last_config_ = DnsConfig(); + service_->AddObserver(this); + EXPECT_TRUE(last_config_.Equals(complete_config)); +} + +#if defined(OS_POSIX) +// TODO(szym): enable OS_WIN once ready +// This is really an integration test. +TEST_F(DnsConfigServiceTest, GetSystemConfig) { + scoped_ptr<DnsConfigService> service(DnsConfigService::CreateSystemService()); + + // Quit the loop after timeout unless cancelled + const int64 kTimeout = TestTimeouts::action_timeout_ms(); + ScopedRunnableMethodFactory<DnsConfigServiceTest> factory_(this); + MessageLoop::current()->PostDelayedTask( + FROM_HERE, + factory_.NewRunnableMethod(&DnsConfigServiceTest::Timeout), + kTimeout); + + service->AddObserver(this); + service->Watch(); + quit_on_config_ = true; + MessageLoop::current()->Run(); + ASSERT_TRUE(last_config_.IsValid()) << "Did not receive DnsConfig in " << + kTimeout << "ms"; +} +#endif // OS_POSIX + +} // namespace net + diff --git a/net/dns/dns_hosts.cc b/net/dns/dns_hosts.cc new file mode 100644 index 0000000..1342808 --- /dev/null +++ b/net/dns/dns_hosts.cc @@ -0,0 +1,47 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/dns/dns_hosts.h" + +#include "base/logging.h" +#include "base/string_tokenizer.h" + +namespace net { + +void ParseHosts(const std::string& contents, DnsHosts* dns_hosts) { + CHECK(dns_hosts); + DnsHosts& hosts = *dns_hosts; + // Split into lines. Accept CR for Windows. + StringTokenizer contents_lines(contents, "\n\r"); + while (contents_lines.GetNext()) { + // Ignore comments after '#'. + std::string line = contents_lines.token(); + StringTokenizer line_parts(line, "#"); + line_parts.set_options(StringTokenizer::RETURN_DELIMS); + + if (line_parts.GetNext() && !line_parts.token_is_delim()) { + // Split and trim whitespace. + std::string part = line_parts.token(); + StringTokenizer tokens(part, " \t"); + + if (tokens.GetNext()) { + IPAddressNumber ip; + // TODO(szym): handle %iface notation on mac + if (!ParseIPLiteralToNumber(tokens.token(), &ip)) + continue; // Ignore malformed lines. + AddressFamily fam = (ip.size() == 4) ? ADDRESS_FAMILY_IPV4 : + ADDRESS_FAMILY_IPV6; + while (tokens.GetNext()) { + IPAddressNumber& mapped_ip = hosts[DnsHostsKey(tokens.token(), fam)]; + if (mapped_ip.empty()) + mapped_ip = ip; + // else ignore this entry (first hit counts) + } + } + } + } +} + +} // namespace net + diff --git a/net/dns/dns_hosts.h b/net/dns/dns_hosts.h new file mode 100644 index 0000000..fda7733 --- /dev/null +++ b/net/dns/dns_hosts.h @@ -0,0 +1,41 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef NET_DNS_DNS_HOSTS_H_ +#define NET_DNS_DNS_HOSTS_H_ +#pragma once + +#include <map> +#include <string> +#include <utility> +#include <vector> + +#include "net/base/address_family.h" +#include "net/base/net_export.h" +#include "net/base/net_util.h" // can't forward-declare IPAddressNumber + +namespace net { + +// Parsed results of a Hosts file. +// +// Although Hosts files map IP address to a list of domain names, for name +// resolution the desired mapping direction is: domain name to IP address. +// When parsing Hosts, we apply the "first hit" rule as Windows and glibc do. +// With a Hosts file of: +// 300.300.300.300 localhost # bad ip +// 127.0.0.1 localhost +// 10.0.0.1 localhost +// The expected resolution of localhost is 127.0.0.1. +typedef std::pair<std::string, AddressFamily> DnsHostsKey; +typedef std::map<DnsHostsKey, IPAddressNumber> DnsHosts; + +// Parses |contents| (as read from /etc/hosts or equivalent) and stores results +// in |dns_hosts|. Invalid lines are ignored (as in most implementations). +void NET_EXPORT_PRIVATE ParseHosts(const std::string& contents, + DnsHosts* dns_hosts); + +} // namespace net + +#endif // NET_DNS_DNS_HOSTS_H_ + diff --git a/net/dns/dns_hosts_unittest.cc b/net/dns/dns_hosts_unittest.cc new file mode 100644 index 0000000..6da41a9 --- /dev/null +++ b/net/dns/dns_hosts_unittest.cc @@ -0,0 +1,61 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/dns/dns_hosts.h" + +#include "testing/gtest/include/gtest/gtest.h" + +namespace net { + +namespace { + +TEST(DnsHostsTest, ParseHosts) { + std::string contents = + "127.0.0.1 localhost\tlocalhost.localdomain # standard\n" + "\n" + "1.0.0.1 localhost # ignored, first hit above\n" + "fe00::x example company # ignored, malformed IPv6\n" + "1.0.0.300 company # ignored, malformed IPv4\n" + "1.0.0.1 # ignored, missing hostname\n" + "1.0.0.1\t company \n" + "::1\tlocalhost ip6-localhost ip6-loopback # comment # within a comment\n" + "\t fe00::0 ip6-localnet\r\n" + "2048::2 example\n" + "2048::1 company example # ignored for 'example' \n" + "gibberish"; + + const struct { + const char* host; + AddressFamily family; + const char* ip; + } entries[] = { + { "localhost", ADDRESS_FAMILY_IPV4, "127.0.0.1" }, + { "localhost.localdomain", ADDRESS_FAMILY_IPV4, "127.0.0.1" }, + { "company", ADDRESS_FAMILY_IPV4, "1.0.0.1" }, + { "localhost", ADDRESS_FAMILY_IPV6, "::1" }, + { "ip6-localhost", ADDRESS_FAMILY_IPV6, "::1" }, + { "ip6-loopback", ADDRESS_FAMILY_IPV6, "::1" }, + { "ip6-localnet", ADDRESS_FAMILY_IPV6, "fe00::0" }, + { "company", ADDRESS_FAMILY_IPV6, "2048::1" }, + { "example", ADDRESS_FAMILY_IPV6, "2048::2" }, + }; + + DnsHosts expected; + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(entries); ++i) { + DnsHostsKey key(entries[i].host, entries[i].family); + IPAddressNumber& ip = expected[key]; + ASSERT_TRUE(ip.empty()); + ASSERT_TRUE(ParseIPLiteralToNumber(entries[i].ip, &ip)); + ASSERT_EQ(ip.size(), (entries[i].family == ADDRESS_FAMILY_IPV4) ? 4u : 16u); + } + + DnsHosts hosts; + ParseHosts(contents, &hosts); + ASSERT_EQ(expected, hosts); +} + +} // namespace + +} // namespace net + diff --git a/net/dns/watching_file_reader.cc b/net/dns/watching_file_reader.cc new file mode 100644 index 0000000..b5f9159 --- /dev/null +++ b/net/dns/watching_file_reader.cc @@ -0,0 +1,133 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/dns/watching_file_reader.h" + +#include "base/bind.h" +#include "base/message_loop_proxy.h" +#include "base/threading/worker_pool.h" + +namespace net { + +FilePathWatcherShim::FilePathWatcherShim() + : watcher_(new base::files::FilePathWatcher()) {} +FilePathWatcherShim::~FilePathWatcherShim() {} + +bool FilePathWatcherShim::Watch( + const FilePath& path, + base::files::FilePathWatcher::Delegate* delegate) { + DCHECK(CalledOnValidThread()); + return watcher_->Watch(path, delegate); +} + +FilePathWatcherShim* +FilePathWatcherFactory::CreateFilePathWatcher() { + DCHECK(CalledOnValidThread()); + return new FilePathWatcherShim(); +} + +WatchingFileReader::WatchingFileReader() + : message_loop_(base::MessageLoopProxy::current()), + factory_(new FilePathWatcherFactory()), + reading_(false), + read_pending_(false), + cancelled_(false) {} + +void WatchingFileReader::StartWatch(const FilePath& path) { + DCHECK(message_loop_->BelongsToCurrentThread()); + DCHECK(!watcher_.get()); + DCHECK(path_.empty()); + path_ = path; + RestartWatch(); +} + +void WatchingFileReader::Cancel() { + DCHECK(message_loop_->BelongsToCurrentThread()); + cancelled_ = true; + // Let go of the watcher to break the reference cycle. + watcher_.reset(); +} + +void WatchingFileReader::OnFilePathChanged(const FilePath& path) { + DCHECK(message_loop_->BelongsToCurrentThread()); + ReadNow(); +} + +void WatchingFileReader::OnFilePathError(const FilePath& path) { + DCHECK(message_loop_->BelongsToCurrentThread()); + RestartWatch(); +} + +WatchingFileReader::~WatchingFileReader() {} + +void WatchingFileReader::RescheduleWatch() { + DCHECK(message_loop_->BelongsToCurrentThread()); + message_loop_->PostDelayedTask( + FROM_HERE, + base::Bind(&WatchingFileReader::RestartWatch, this), + kWatchRetryDelayMs); +} + +void WatchingFileReader::RestartWatch() { + if (cancelled_) + return; + watcher_.reset(factory_->CreateFilePathWatcher()); + if (watcher_->Watch(path_, this)) { + ReadNow(); + } else { + LOG(WARNING) << "Watch on " << + path_.LossyDisplayName() << + " failed, scheduling restart"; + RescheduleWatch(); + } +} + +void WatchingFileReader::ReadNow() { + if (cancelled_) + return; + if (reading_) { + // Remember to re-read after DoRead posts results. + read_pending_ = true; + } else { + if (!base::WorkerPool::PostTask(FROM_HERE, base::Bind( + &WatchingFileReader::DoReadJob, this), false)) { +#if defined(OS_POSIX) + // See worker_pool_posix.cc. + NOTREACHED() << "WorkerPool::PostTask is not expected to fail on posix"; +#else + LOG(WARNING) << "Failed to WorkerPool::PostTask, will retry later"; + message_loop_->PostDelayedTask( + FROM_HERE, + base::Bind(&WatchingFileReader::ReadNow, this), + kWorkerPoolRetryDelayMs); + return; +#endif + } + reading_ = true; + read_pending_ = false; + } +} + +void WatchingFileReader::DoReadJob() { + this->DoRead(); + // If this fails, the loop is gone, so there is no point retrying. + message_loop_->PostTask(FROM_HERE, base::Bind( + &WatchingFileReader::OnReadJobFinished, this)); +} + +void WatchingFileReader::OnReadJobFinished() { + DCHECK(message_loop_->BelongsToCurrentThread()); + if (cancelled_) + return; + reading_ = false; + if (read_pending_) { + // Discard this result and re-read. + ReadNow(); + return; + } + this->OnReadFinished(); +} + +} // namespace net + diff --git a/net/dns/watching_file_reader.h b/net/dns/watching_file_reader.h new file mode 100644 index 0000000..15237ea --- /dev/null +++ b/net/dns/watching_file_reader.h @@ -0,0 +1,133 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef NET_DNS_WATCHING_FILE_READER_H_ +#define NET_DNS_WATCHING_FILE_READER_H_ +#pragma once + +#include <string> + +#include "base/compiler_specific.h" +#include "base/files/file_path_watcher.h" +#include "base/memory/scoped_ptr.h" +#include "base/threading/non_thread_safe.h" +#include "net/base/net_export.h" + +// Forward declaration +namespace base { +class MessageLoopProxy; +} + +namespace net { + +// Allows mocking FilePathWatcher +class NET_EXPORT_PRIVATE FilePathWatcherShim + : NON_EXPORTED_BASE(public base::NonThreadSafe) { + public: + FilePathWatcherShim(); + virtual ~FilePathWatcherShim(); + + virtual bool Watch( + const FilePath& path, + base::files::FilePathWatcher::Delegate* delegate) WARN_UNUSED_RESULT; + private: + scoped_ptr<base::files::FilePathWatcher> watcher_; + DISALLOW_COPY_AND_ASSIGN(FilePathWatcherShim); +}; + +class NET_EXPORT_PRIVATE FilePathWatcherFactory + : NON_EXPORTED_BASE(public base::NonThreadSafe) { + public: + FilePathWatcherFactory() {} + virtual ~FilePathWatcherFactory() {} + virtual FilePathWatcherShim* CreateFilePathWatcher(); + private: + DISALLOW_COPY_AND_ASSIGN(FilePathWatcherFactory); +}; + +// A FilePathWatcher::Delegate that restarts Watch on failures and executes +// DoRead on WorkerPool (one at a time) in response to FilePathChanged. +// Derived classes should store results of the work done in DoRead in dedicated +// fields and read them in OnReadFinished. +// +// This implementation avoids locking by using the reading_ flag to ensure that +// DoRead and OnReadFinished cannot execute in parallel. +// +// There's no need to call Cancel() on destruction. On the contrary, a ref-cycle +// between the WatchingFileReader and a FilePathWatcher will prevent destruction +// until Cancel() is called. +// +// TODO(szym): update to WorkerPool::PostTaskAndReply once available. +class NET_EXPORT_PRIVATE WatchingFileReader + : NON_EXPORTED_BASE(public base::files::FilePathWatcher::Delegate) { + public: + WatchingFileReader(); + + // Must be called at most once. Made virtual to allow mocking. + virtual void StartWatch(const FilePath& path); + + // Unless already scheduled, post DoRead to WorkerPool. + void ReadNow(); + + // Do not perform any further work, except perhaps a latent DoRead(). + // Always call it once done with the delegate to ensure destruction. + void Cancel(); + + void set_watcher_factory(FilePathWatcherFactory* factory) { + DCHECK(!watcher_.get()); + factory_.reset(factory); + } + + FilePath get_path() const { + return path_; + } + + // Delay between calls to FilePathWatcher::Watch. + static const int kWatchRetryDelayMs = 100; + + // Delay between calls to WorkerPool::PostTask + static const int kWorkerPoolRetryDelayMs = 100; + + protected: + // protected to allow sub-classing, but prevent deleting + virtual ~WatchingFileReader(); + + // Executed on WorkerPool, at most once at a time. + virtual void DoRead() = 0; + // Executed on origin thread after DoRead() completes. + virtual void OnReadFinished() = 0; + + private: + // FilePathWatcher::Delegate interface + virtual void OnFilePathChanged(const FilePath& path) OVERRIDE; + virtual void OnFilePathError(const FilePath& path) OVERRIDE; + + void RescheduleWatch(); + void RestartWatch(); + + // Called on the worker thread, executes DoRead and notifies the origin + // thread. + void DoReadJob(); + + // Called on the the origin thread after DoRead completes. + void OnReadJobFinished(); + + // Message loop for the thread on which watcher is used (of TYPE_IO). + scoped_refptr<base::MessageLoopProxy> message_loop_; + FilePath path_; + scoped_ptr<FilePathWatcherFactory> factory_; + scoped_ptr<FilePathWatcherShim> watcher_; + // True after DoRead before OnResultsAvailable. + bool reading_; + // True after OnFilePathChanged fires while |reading_| is true. + bool read_pending_; + bool cancelled_; + + DISALLOW_COPY_AND_ASSIGN(WatchingFileReader); +}; + +} // namespace net + +#endif // NET_DNS_WATCHING_FILE_READER_H_ + diff --git a/net/dns/watching_file_reader_unittest.cc b/net/dns/watching_file_reader_unittest.cc new file mode 100644 index 0000000..24021f0 --- /dev/null +++ b/net/dns/watching_file_reader_unittest.cc @@ -0,0 +1,289 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/dns/watching_file_reader.h" + +#include "base/message_loop.h" +#include "base/synchronization/lock.h" +#include "base/synchronization/waitable_event.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace net { + +namespace { + +class WatchingFileReaderTest : public testing::Test { + public: + + // The class under test + class TestWatchingFileReader : public WatchingFileReader { + public: + explicit TestWatchingFileReader(WatchingFileReaderTest* t) + : test_(t) {} + virtual void DoRead() OVERRIDE { + ASSERT_TRUE(test_); + test_->OnRead(); + } + virtual void OnReadFinished() OVERRIDE { + ASSERT_TRUE(test_); + test_->OnReadFinished(); + } + void TestCancel() { + // Could execute concurrently with DoRead() + test_ = NULL; + } + private: + virtual ~TestWatchingFileReader() {} + WatchingFileReaderTest* test_; + base::Lock lock_; + }; + + // Mocks + + // WatcherDelegate owns the FilePathWatcherFactory it gets, so use simple + // proxies to call the test fixture. + + class MockFilePathWatcherShim + : public FilePathWatcherShim { + public: + typedef base::files::FilePathWatcher::Delegate Delegate; + + explicit MockFilePathWatcherShim(WatchingFileReaderTest* t) : test_(t) {} + virtual ~MockFilePathWatcherShim() { + test_->OnShimDestroyed(this); + } + + // Enforce one-Watch-per-lifetime as the original FilePathWatcher + virtual bool Watch(const FilePath& path, + Delegate* delegate) OVERRIDE { + EXPECT_TRUE(path_.empty()) << "Only one-Watch-per-lifetime allowed."; + EXPECT_TRUE(!delegate_.get()); + path_ = path; + delegate_ = delegate; + return test_->OnWatch(); + } + + void PathChanged() { + delegate_->OnFilePathChanged(path_); + } + + void PathError() { + delegate_->OnFilePathError(path_); + } + + private: + FilePath path_; + scoped_refptr<Delegate> delegate_; + WatchingFileReaderTest* test_; + }; + + class MockFilePathWatcherFactory + : public FilePathWatcherFactory { + public: + explicit MockFilePathWatcherFactory(WatchingFileReaderTest* t) + : test(t) {} + virtual FilePathWatcherShim* + CreateFilePathWatcher() OVERRIDE { + return test->CreateFilePathWatcher(); + } + WatchingFileReaderTest* test; + }; + + // Helpers for mocks. + + FilePathWatcherShim* CreateFilePathWatcher() { + watcher_shim_ = new MockFilePathWatcherShim(this); + return watcher_shim_; + } + + void OnShimDestroyed(MockFilePathWatcherShim* destroyed_shim) { + // Precaution to avoid segfault. + if (watcher_shim_ == destroyed_shim) + watcher_shim_ = NULL; + } + + // On each event, post QuitTask to allow use of MessageLoop::Run() to + // synchronize the threads. + + bool OnWatch() { + EXPECT_TRUE(message_loop_ == MessageLoop::current()); + BreakNow("OnWatch"); + return !fail_on_watch_; + } + + void OnRead() { + { // Check that OnRead is executed serially. + base::AutoLock lock(read_lock_); + EXPECT_FALSE(read_running_) << "DoRead is not called serially!"; + read_running_ = true; + } + BreakNow("OnRead"); + read_allowed_.Wait(); + // Calling from WorkerPool, but protected by read_allowed_/read_called_. + output_value_ = input_value_; + + { // This lock might be destroyed after read_called_ is signalled. + base::AutoLock lock(read_lock_); + read_running_ = false; + } + read_called_.Signal(); + } + + void OnReadFinished() { + EXPECT_TRUE(message_loop_ == MessageLoop::current()); + EXPECT_EQ(output_value_, input_value_); + BreakNow("OnReadFinished"); + } + + protected: + friend class BreakTask; + class BreakTask : public Task { + public: + BreakTask(WatchingFileReaderTest* test, std::string breakpoint) + : test_(test), breakpoint_(breakpoint) {} + virtual ~BreakTask() {} + virtual void Run() OVERRIDE { + test_->breakpoint_ = breakpoint_; + MessageLoop::current()->QuitNow(); + } + private: + WatchingFileReaderTest* test_; + std::string breakpoint_; + }; + + void BreakNow(std::string b) { + message_loop_->PostTask(FROM_HERE, new BreakTask(this, b)); + } + + void RunUntilBreak(std::string b) { + message_loop_->Run(); + ASSERT_EQ(breakpoint_, b); + } + + WatchingFileReaderTest() + : watcher_shim_(NULL), + fail_on_watch_(false), + input_value_(0), + output_value_(-1), + read_allowed_(false, false), + read_called_(false, false), + read_running_(false) { + } + + // Helpers for tests. + + // Lets OnRead run and waits for it to complete. Can only return if OnRead is + // executed on a concurrent thread. + void WaitForRead() { + RunUntilBreak("OnRead"); + read_allowed_.Signal(); + read_called_.Wait(); + } + + // test::Test methods + virtual void SetUp() OVERRIDE { + message_loop_ = MessageLoop::current(); + watcher_ = new TestWatchingFileReader(this); + watcher_->set_watcher_factory(new MockFilePathWatcherFactory(this)); + } + + virtual void TearDown() OVERRIDE { + // Cancel the watcher to catch if it makes a late DoRead call. + watcher_->TestCancel(); + watcher_->Cancel(); + // Check if OnRead is stalled. + EXPECT_FALSE(read_running_) << "OnRead should be done by TearDown"; + // Release it for cleanliness. + if (read_running_) { + WaitForRead(); + } + } + + MockFilePathWatcherShim* watcher_shim_; + + bool fail_on_watch_; + + // Input value read on WorkerPool. + int input_value_; + // Output value written on WorkerPool. + int output_value_; + + // read is called on WorkerPool so we need to synchronize with it. + base::WaitableEvent read_allowed_; + base::WaitableEvent read_called_; + + // Protected by read_lock_. Used to verify that read calls are serialized. + bool read_running_; + base::Lock read_lock_; + + // Loop for this thread. + MessageLoop* message_loop_; + + // WatcherDelegate under test. + scoped_refptr<TestWatchingFileReader> watcher_; + + std::string breakpoint_; +}; + +TEST_F(WatchingFileReaderTest, FilePathWatcherFailures) { + fail_on_watch_ = true; + watcher_->StartWatch(FilePath(FILE_PATH_LITERAL("some_file_name"))); + RunUntilBreak("OnWatch"); + + fail_on_watch_ = false; + RunUntilBreak("OnWatch"); // Due to backoff this will take 100ms. + WaitForRead(); + RunUntilBreak("OnReadFinished"); + + ASSERT_TRUE(watcher_shim_); + watcher_shim_->PathError(); + RunUntilBreak("OnWatch"); + WaitForRead(); + RunUntilBreak("OnReadFinished"); + + message_loop_->AssertIdle(); +} + +TEST_F(WatchingFileReaderTest, ExecuteAndSerializeReads) { + watcher_->StartWatch(FilePath(FILE_PATH_LITERAL("some_file_name"))); + RunUntilBreak("OnWatch"); + WaitForRead(); + RunUntilBreak("OnReadFinished"); + + message_loop_->AssertIdle(); + + ++input_value_; + ASSERT_TRUE(watcher_shim_); + watcher_shim_->PathChanged(); + WaitForRead(); + RunUntilBreak("OnReadFinished"); + + message_loop_->AssertIdle(); + + ++input_value_; + ASSERT_TRUE(watcher_shim_); + watcher_shim_->PathChanged(); + WaitForRead(); + RunUntilBreak("OnReadFinished"); + + message_loop_->AssertIdle(); + + // Schedule two calls. OnRead checks if it is called serially. + ++input_value_; + ASSERT_TRUE(watcher_shim_); + watcher_shim_->PathChanged(); + // read is blocked, so this will have to induce read_pending + watcher_shim_->PathChanged(); + WaitForRead(); + WaitForRead(); + RunUntilBreak("OnReadFinished"); + + // No more tasks should remain. + message_loop_->AssertIdle(); +} + +} // namespace + +} // namespace net + diff --git a/net/net.gyp b/net/net.gyp index 73b6d62..a551c5e 100644 --- a/net/net.gyp +++ b/net/net.gyp @@ -303,12 +303,16 @@ 'dns/dns_config_service.h', 'dns/dns_config_service_posix.cc', 'dns/dns_config_service_posix.h', + 'dns/dns_hosts.cc', + 'dns/dns_hosts.h', 'dns/dns_query.cc', 'dns/dns_query.h', 'dns/dns_response.cc', 'dns/dns_response.h', 'dns/dns_transaction.cc', 'dns/dns_transaction.h', + 'dns/watching_file_reader.cc', + 'dns/watching_file_reader.h', 'ftp/ftp_auth_cache.cc', 'ftp/ftp_auth_cache.h', 'ftp/ftp_ctrl_response_buffer.cc', @@ -968,9 +972,12 @@ 'disk_cache/storage_block_unittest.cc', 'dns/async_host_resolver_unittest.cc', 'dns/dns_config_service_posix_unittest.cc', + 'dns/dns_config_service_unittest.cc', + 'dns/dns_hosts_unittest.cc', 'dns/dns_query_unittest.cc', 'dns/dns_response_unittest.cc', 'dns/dns_transaction_unittest.cc', + 'dns/watching_file_reader_unittest.cc', 'ftp/ftp_auth_cache_unittest.cc', 'ftp/ftp_ctrl_response_buffer_unittest.cc', 'ftp/ftp_directory_listing_parser_ls_unittest.cc', |