Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. | |
| 2 // Use of this source code is governed by a BSD-style license that can be | |
| 3 // found in the LICENSE file. | |
|
Wez
2011/08/11 20:00:27
Is this brand-new code, or cribbed from the main C
awong
2011/08/11 23:54:54
Mostly cribbed, but it's an amalgamation of a bunc
Wez
2011/08/12 00:54:42
Perhaps add a comment indicating where it's cribbe
awong
2011/08/12 00:57:03
Done.
| |
| 4 | |
| 5 #include <set> | |
| 6 | |
| 7 #include "base/bind.h" | |
| 8 #include "base/compiler_specific.h" | |
| 9 #include "base/file_path.h" | |
| 10 #include "base/file_util.h" | |
| 11 #include "base/files/file_path_watcher.h" | |
| 12 #include "base/memory/weak_ptr.h" | |
| 13 #include "base/message_loop.h" | |
| 14 #include "base/scoped_ptr.h" | |
| 15 #include "base/synchronization/waitable_event.h" | |
| 16 #include "base/time.h" | |
| 17 #include "base/values.h" | |
| 18 #include "content/common/json_value_serializer.h" | |
| 19 #include "remoting/host/plugin/policy_hack/nat_policy.h" | |
|
dmac
2011/08/11 18:42:55
should this include be at the top (given that it i
awong
2011/08/11 19:04:07
Done.
| |
| 20 | |
| 21 namespace remoting { | |
| 22 namespace policy_hack { | |
| 23 | |
| 24 namespace { | |
| 25 | |
| 26 #if defined(GOOGLE_CHROME_BUILD) | |
| 27 const FilePath::CharType kPolicyDir[] = | |
| 28 FILE_PATH_LITERAL("/etc/opt/chrome/policies/managed"); | |
| 29 #else | |
| 30 const FilePath::CharType kPolicyDir[] = | |
| 31 FILE_PATH_LITERAL("/etc/chromium/policies/managed"); | |
| 32 #endif | |
| 33 | |
| 34 // The time interval for rechecking policy. This is our fallback in case the | |
| 35 // delegate never reports a change to the ReloadObserver. | |
| 36 const int kFallbackReloadDelayMinutes = 15; | |
| 37 | |
| 38 // Amount of time we wait for the files on disk to settle before trying to load | |
| 39 // them. This alleviates the problem of reading partially written files and | |
| 40 // makes it possible to batch quasi-simultaneous changes. | |
| 41 const int kSettleIntervalSeconds = 5; | |
| 42 | |
| 43 } // namespace | |
| 44 | |
| 45 class NatPolicyLinux : public NatPolicy { | |
| 46 public: | |
| 47 NatPolicyLinux(MessageLoop* message_loop, | |
| 48 const FilePath& config_dir, | |
| 49 const NatPolicy::NatEnabledCallback& nat_enabled_cb) | |
| 50 : message_loop_(message_loop), | |
| 51 config_dir_(config_dir), | |
| 52 nat_enabled_cb_(nat_enabled_cb), | |
| 53 current_nat_enabled_state_(false), | |
| 54 first_state_published_(false), | |
| 55 ALLOW_THIS_IN_INITIALIZER_LIST(weak_factory_(this)) { | |
| 56 // Detach the factory cause we handle ensure that only the message_loop_ | |
|
dmac
2011/08/11 18:42:55
s/cause/because/
handle ensure?
s/uses/threads/?
awong
2011/08/11 19:04:07
Rewrote.
| |
| 57 // thread ever calls methods on this, and that all uses are ended before | |
| 58 // we destroy this object. This allows us to create/destroy | |
| 59 // NatPolicyLinux on any thread. | |
| 60 weak_factory_.DetachFromThread(); | |
| 61 } | |
| 62 | |
| 63 virtual ~NatPolicyLinux() { | |
| 64 } | |
|
dmac
2011/08/11 18:42:55
NIT: standard seems to be to put {} on one line...
awong
2011/08/11 19:04:07
I don't think there is actually a standard on this
| |
| 65 | |
| 66 virtual void StartWatching() OVERRIDE { | |
| 67 if (MessageLoop::current() != message_loop_) { | |
| 68 message_loop_->PostTask(FROM_HERE, | |
| 69 base::Bind(&NatPolicyLinux::StartWatching, | |
| 70 base::Unretained(this))); | |
| 71 return; | |
| 72 } | |
| 73 | |
| 74 watcher_.reset(new base::files::FilePathWatcher()); | |
| 75 | |
| 76 if (!config_dir_.empty() && | |
| 77 !watcher_->Watch( | |
| 78 config_dir_, | |
| 79 new FilePathWatcherDelegate(weak_factory_.GetWeakPtr()))) { | |
| 80 OnFilePathError(config_dir_); | |
| 81 } | |
| 82 | |
| 83 // There might have been changes to the directory in the time between | |
| 84 // construction of the loader and initialization of the watcher. Call reload | |
| 85 // to detect if that is the case. | |
| 86 Reload(); | |
| 87 | |
| 88 ScheduleFallbackReloadTask(); | |
| 89 } | |
| 90 | |
| 91 virtual void StopWatching(base::WaitableEvent* done) OVERRIDE { | |
| 92 if (MessageLoop::current() != message_loop_) { | |
| 93 message_loop_->PostTask(FROM_HERE, | |
| 94 base::Bind(&NatPolicyLinux::StopWatching, | |
| 95 base::Unretained(this), done)); | |
| 96 return; | |
| 97 } | |
| 98 | |
| 99 // Cancel any inflight requests. | |
| 100 weak_factory_.InvalidateWeakPtrs(); | |
| 101 watcher_.reset(); | |
| 102 | |
| 103 done->Signal(); | |
| 104 } | |
| 105 | |
| 106 // Called by FilePathWatcherDelegate. | |
| 107 virtual void OnFilePathError(const FilePath& path) { | |
| 108 LOG(ERROR) << "NatPolicyLinux on " << path.value() | |
| 109 << " failed."; | |
| 110 } | |
| 111 | |
| 112 // Called by FilePathWatcherDelegate. | |
| 113 virtual void OnFilePathChanged(const FilePath& path) { | |
| 114 DCHECK_EQ(MessageLoop::current(), message_loop_); | |
| 115 | |
| 116 Reload(); | |
| 117 } | |
| 118 | |
| 119 private: | |
| 120 // Needed since NatPolicyLinux ends up getting its refcount from | |
|
dmac
2011/08/11 18:42:55
NatPolicyLinux ends up getting its refcount from N
awong
2011/08/11 19:04:07
Again stale. Originally, itw as getting its refco
| |
| 121 // NatPolicyLinux. | |
| 122 class FilePathWatcherDelegate : | |
| 123 public base::files::FilePathWatcher::Delegate { | |
| 124 public: | |
| 125 FilePathWatcherDelegate(base::WeakPtr<NatPolicyLinux> policy_watcher) | |
| 126 : policy_watcher_(policy_watcher) { | |
| 127 } | |
| 128 | |
| 129 virtual void OnFilePathError(const FilePath& path) { | |
| 130 if (policy_watcher_) { | |
| 131 policy_watcher_->OnFilePathError(path); | |
| 132 } | |
| 133 } | |
| 134 | |
| 135 virtual void OnFilePathChanged(const FilePath& path) { | |
| 136 if (policy_watcher_) { | |
| 137 policy_watcher_->OnFilePathChanged(path); | |
| 138 } | |
| 139 } | |
| 140 | |
| 141 private: | |
| 142 base::WeakPtr<NatPolicyLinux> policy_watcher_; | |
| 143 }; | |
| 144 | |
| 145 void ScheduleFallbackReloadTask() { | |
| 146 DCHECK_EQ(MessageLoop::current(), message_loop_); | |
| 147 ScheduleReloadTask( | |
| 148 base::TimeDelta::FromMinutes(kFallbackReloadDelayMinutes)); | |
| 149 } | |
| 150 | |
| 151 void ScheduleReloadTask(const base::TimeDelta& delay) { | |
| 152 DCHECK_EQ(MessageLoop::current(), message_loop_); | |
| 153 message_loop_->PostDelayedTask( | |
| 154 FROM_HERE, | |
| 155 base::Bind(&NatPolicyLinux::Reload, weak_factory_.GetWeakPtr()), | |
| 156 delay.InMilliseconds()); | |
| 157 } | |
| 158 | |
| 159 base::Time GetLastModification() { | |
| 160 DCHECK_EQ(MessageLoop::current(), message_loop_); | |
| 161 base::Time last_modification = base::Time(); | |
| 162 base::PlatformFileInfo file_info; | |
| 163 | |
| 164 // If the path does not exist or points to a directory, it's safe to load. | |
| 165 if (!file_util::GetFileInfo(config_dir_, &file_info) || | |
| 166 !file_info.is_directory) { | |
| 167 return last_modification; | |
| 168 } | |
| 169 | |
| 170 // Enumerate the files and find the most recent modification timestamp. | |
| 171 file_util::FileEnumerator file_enumerator(config_dir_, | |
| 172 false, | |
| 173 file_util::FileEnumerator::FILES); | |
| 174 for (FilePath config_file = file_enumerator.Next(); | |
| 175 !config_file.empty(); | |
| 176 config_file = file_enumerator.Next()) { | |
| 177 if (file_util::GetFileInfo(config_file, &file_info) && | |
| 178 !file_info.is_directory) { | |
| 179 last_modification = std::max(last_modification, | |
| 180 file_info.last_modified); | |
| 181 } | |
| 182 } | |
| 183 | |
| 184 return last_modification; | |
| 185 } | |
| 186 | |
| 187 // Caller owns the value. | |
| 188 DictionaryValue* Load() { | |
| 189 DCHECK_EQ(MessageLoop::current(), message_loop_); | |
| 190 // Enumerate the files and sort them lexicographically. | |
| 191 std::set<FilePath> files; | |
| 192 file_util::FileEnumerator file_enumerator(config_dir_, false, | |
| 193 file_util::FileEnumerator::FILES); | |
| 194 for (FilePath config_file_path = file_enumerator.Next(); | |
| 195 !config_file_path.empty(); config_file_path = file_enumerator.Next()) | |
| 196 files.insert(config_file_path); | |
| 197 | |
| 198 // Start with an empty dictionary and merge the files' contents. | |
| 199 DictionaryValue* policy = new DictionaryValue; | |
| 200 for (std::set<FilePath>::iterator config_file_iter = files.begin(); | |
| 201 config_file_iter != files.end(); ++config_file_iter) { | |
| 202 JSONFileValueSerializer deserializer(*config_file_iter); | |
| 203 int error_code = 0; | |
| 204 std::string error_msg; | |
| 205 scoped_ptr<Value> value( | |
| 206 deserializer.Deserialize(&error_code, &error_msg)); | |
| 207 if (!value.get()) { | |
| 208 LOG(WARNING) << "Failed to read configuration file " | |
| 209 << config_file_iter->value() << ": " << error_msg; | |
| 210 continue; | |
| 211 } | |
| 212 if (!value->IsType(Value::TYPE_DICTIONARY)) { | |
| 213 LOG(WARNING) << "Expected JSON dictionary in configuration file " | |
| 214 << config_file_iter->value(); | |
| 215 continue; | |
| 216 } | |
| 217 policy->MergeDictionary(static_cast<DictionaryValue*>(value.get())); | |
| 218 } | |
| 219 | |
| 220 return policy; | |
| 221 } | |
| 222 | |
| 223 void Reload() { | |
| 224 DCHECK_EQ(MessageLoop::current(), message_loop_); | |
| 225 // Check the directory time in order to see whether a reload is required. | |
| 226 base::TimeDelta delay; | |
| 227 base::Time now = base::Time::Now(); | |
| 228 if (!IsSafeToReloadPolicy(now, &delay)) { | |
| 229 ScheduleReloadTask(delay); | |
| 230 return; | |
| 231 } | |
| 232 | |
| 233 // Load the policy definitions. | |
| 234 scoped_ptr<DictionaryValue> new_policy(Load()); | |
| 235 | |
| 236 // Check again in case the directory has changed while reading it. | |
| 237 if (!IsSafeToReloadPolicy(now, &delay)) { | |
| 238 ScheduleReloadTask(delay); | |
| 239 return; | |
| 240 } | |
| 241 | |
| 242 // Read out just the host firewall traversal policy. Name of policy taken | |
| 243 // from the generated policy/policy_constants.h file. | |
| 244 bool new_nat_enabled_state = false; | |
| 245 if (!new_policy->HasKey("RemoteAccessHostFirewallTraversal")) { | |
| 246 // If unspecified, the default value of this policy is true. | |
| 247 new_nat_enabled_state = true; | |
| 248 } else { | |
| 249 // Otherwise, try to parse the value and only change from false if we get | |
| 250 // a successful read. | |
| 251 base::Value* value; | |
| 252 if (new_policy->Get("RemoteAccessHostFirewallTraversal", &value) && | |
| 253 value->IsType(base::Value::TYPE_BOOLEAN)) { | |
| 254 CHECK(value->GetAsBoolean(&new_nat_enabled_state)); | |
| 255 } | |
| 256 } | |
| 257 | |
| 258 if (!first_state_published_ || | |
| 259 (new_nat_enabled_state != current_nat_enabled_state_)) { | |
| 260 first_state_published_ = true; | |
| 261 current_nat_enabled_state_ = new_nat_enabled_state; | |
| 262 nat_enabled_cb_.Run(current_nat_enabled_state_); | |
| 263 } | |
| 264 | |
| 265 ScheduleFallbackReloadTask(); | |
| 266 } | |
| 267 | |
| 268 bool IsSafeToReloadPolicy(const base::Time& now, base::TimeDelta* delay) { | |
| 269 DCHECK_EQ(MessageLoop::current(), message_loop_); | |
| 270 DCHECK(delay); | |
| 271 const base::TimeDelta kSettleInterval = | |
| 272 base::TimeDelta::FromSeconds(kSettleIntervalSeconds); | |
| 273 | |
| 274 base::Time last_modification = GetLastModification(); | |
| 275 if (last_modification.is_null()) | |
| 276 return true; | |
| 277 | |
| 278 // If there was a change since the last recorded modification, wait some | |
| 279 // more. | |
| 280 if (last_modification != last_modification_file_) { | |
| 281 last_modification_file_ = last_modification; | |
| 282 last_modification_clock_ = now; | |
| 283 *delay = kSettleInterval; | |
| 284 return false; | |
| 285 } | |
| 286 | |
| 287 // Check whether the settle interval has elapsed. | |
| 288 base::TimeDelta age = now - last_modification_clock_; | |
| 289 if (age < kSettleInterval) { | |
| 290 *delay = kSettleInterval - age; | |
| 291 return false; | |
| 292 } | |
| 293 | |
| 294 return true; | |
| 295 } | |
| 296 | |
| 297 // Managed with a scoped_ptr rather than being declared as an inline member to | |
| 298 // decouple the watcher's life cycle from the NatPolicyLinux. This decoupling | |
| 299 // makes it possible to destroy the watcher before the loader's destructor is | |
| 300 // called (e.g. during Stop), since |watcher_| internally holds a reference to | |
| 301 // the loader and keeps it alive. | |
| 302 scoped_ptr<base::files::FilePathWatcher> watcher_; | |
| 303 | |
| 304 // Records last known modification timestamp of |config_dir_|. | |
| 305 base::Time last_modification_file_; | |
| 306 | |
| 307 // The wall clock time at which the last modification timestamp was | |
| 308 // recorded. It's better to not assume the file notification time and the | |
| 309 // wall clock times come from the same source, just in case there is some | |
| 310 // non-local filesystem involved. | |
| 311 base::Time last_modification_clock_; | |
| 312 | |
| 313 MessageLoop* message_loop_; | |
| 314 const FilePath config_dir_; | |
| 315 NatEnabledCallback nat_enabled_cb_; | |
| 316 | |
| 317 bool current_nat_enabled_state_; | |
| 318 bool first_state_published_; | |
| 319 | |
| 320 // Allows us to cancel any inflight FileWatcher events or scheduled reloads. | |
| 321 base::WeakPtrFactory<NatPolicyLinux> weak_factory_; | |
| 322 }; | |
| 323 | |
| 324 NatPolicy* NatPolicy::Create(MessageLoop* message_loop, | |
| 325 const NatEnabledCallback& nat_enabled_cb) { | |
| 326 FilePath policy_dir(kPolicyDir); | |
| 327 return new NatPolicyLinux(message_loop, policy_dir, nat_enabled_cb); | |
| 328 } | |
| 329 | |
| 330 } // namespace policy_hack | |
| 331 } // namespace remoting | |
| OLD | NEW |