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 |