summaryrefslogtreecommitdiffstats
path: root/net/dns
diff options
context:
space:
mode:
authorszym@chromium.org <szym@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-09-01 05:41:30 +0000
committerszym@chromium.org <szym@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-09-01 05:41:30 +0000
commitd75edae0fee063c13086ce10ba443deb4968fbde (patch)
tree544030d83e306da0e115f2d1b518fe1699c9c370 /net/dns
parent47a0b0018dd5d400d9eba166c61582fb38eae807 (diff)
downloadchromium_src-d75edae0fee063c13086ce10ba443deb4968fbde.zip
chromium_src-d75edae0fee063c13086ce10ba443deb4968fbde.tar.gz
chromium_src-d75edae0fee063c13086ce10ba443deb4968fbde.tar.bz2
Refactoring and remaining work on DnsConfigService:
- Isolate WatchingFileReader for reusability and testability - ParseHosts to parse /etc/hosts - tests BUG=90881 TEST=./net_unittests --gtest_filter='DnsConfig*:DnsHosts*:WatchingFileReader*' Review URL: http://codereview.chromium.org/7753027 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@99135 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'net/dns')
-rw-r--r--net/dns/dns_config_service.cc83
-rw-r--r--net/dns/dns_config_service.h67
-rw-r--r--net/dns/dns_config_service_posix.cc205
-rw-r--r--net/dns/dns_config_service_posix.h81
-rw-r--r--net/dns/dns_config_service_posix_unittest.cc349
-rw-r--r--net/dns/dns_config_service_unittest.cc108
-rw-r--r--net/dns/dns_hosts.cc47
-rw-r--r--net/dns/dns_hosts.h41
-rw-r--r--net/dns/dns_hosts_unittest.cc61
-rw-r--r--net/dns/watching_file_reader.cc133
-rw-r--r--net/dns/watching_file_reader.h133
-rw-r--r--net/dns/watching_file_reader_unittest.cc289
12 files changed, 986 insertions, 611 deletions
diff --git a/net/dns/dns_config_service.cc b/net/dns/dns_config_service.cc
index f32f293..b3a27d7 100644
--- a/net/dns/dns_config_service.cc
+++ b/net/dns/dns_config_service.cc
@@ -4,6 +4,8 @@
#include "net/dns/dns_config_service.h"
+#include "base/file_util.h"
+#include "net/base/io_buffer.h"
#include "net/base/ip_endpoint.h"
namespace net {
@@ -18,7 +20,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 +30,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..5500e70
--- /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) {
+ 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..77a9f12
--- /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
+ : 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
+