OLD | NEW |
| (Empty) |
1 // Copyright (c) 2012 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. | |
4 | |
5 // Most of this code is copied from various classes in | |
6 // src/chrome/browser/policy. In particular, look at | |
7 // | |
8 // file_based_policy_loader.{h,cc} | |
9 // config_dir_policy_provider.{h,cc} | |
10 // | |
11 // This is a reduction of the functionality in those classes. | |
12 | |
13 #include <set> | |
14 | |
15 #include "remoting/host/policy_hack/nat_policy.h" | |
16 | |
17 #include "base/bind.h" | |
18 #include "base/compiler_specific.h" | |
19 #include "base/file_path.h" | |
20 #include "base/file_util.h" | |
21 #include "base/files/file_path_watcher.h" | |
22 #include "base/json/json_file_value_serializer.h" | |
23 #include "base/memory/scoped_ptr.h" | |
24 #include "base/memory/weak_ptr.h" | |
25 #include "base/single_thread_task_runner.h" | |
26 #include "base/synchronization/waitable_event.h" | |
27 #include "base/time.h" | |
28 #include "base/values.h" | |
29 | |
30 namespace remoting { | |
31 namespace policy_hack { | |
32 | |
33 namespace { | |
34 | |
35 const FilePath::CharType kPolicyDir[] = | |
36 FILE_PATH_LITERAL("/etc/opt/chrome/policies/managed"); | |
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(scoped_refptr<base::SingleThreadTaskRunner> task_runner, | |
48 const FilePath& config_dir) | |
49 : NatPolicy(task_runner), | |
50 config_dir_(config_dir), | |
51 ALLOW_THIS_IN_INITIALIZER_LIST(weak_factory_(this)) { | |
52 // Detach the factory because we ensure that only the policy thread ever | |
53 // calls methods on this. Also, the API contract of having to call | |
54 // StopWatching() (which signals completion) after StartWatching() | |
55 // before this object can be destructed ensures there are no users of | |
56 // this object before it is destructed. | |
57 weak_factory_.DetachFromThread(); | |
58 } | |
59 | |
60 virtual ~NatPolicyLinux() {} | |
61 | |
62 protected: | |
63 virtual void StartWatchingInternal() OVERRIDE { | |
64 DCHECK(OnPolicyThread()); | |
65 watcher_.reset(new base::files::FilePathWatcher()); | |
66 | |
67 if (!config_dir_.empty() && | |
68 !watcher_->Watch( | |
69 config_dir_, | |
70 new FilePathWatcherDelegate(weak_factory_.GetWeakPtr()))) { | |
71 OnFilePathError(config_dir_); | |
72 } | |
73 | |
74 // There might have been changes to the directory in the time between | |
75 // construction of the loader and initialization of the watcher. Call reload | |
76 // to detect if that is the case. | |
77 Reload(); | |
78 | |
79 ScheduleFallbackReloadTask(); | |
80 } | |
81 | |
82 virtual void StopWatchingInternal() OVERRIDE { | |
83 DCHECK(OnPolicyThread()); | |
84 // Cancel any inflight requests. | |
85 watcher_.reset(); | |
86 } | |
87 | |
88 // Called by FilePathWatcherDelegate. | |
89 virtual void OnFilePathError(const FilePath& path) { | |
90 LOG(ERROR) << "NatPolicyLinux on " << path.value() | |
91 << " failed."; | |
92 } | |
93 | |
94 // Called by FilePathWatcherDelegate. | |
95 virtual void OnFilePathChanged(const FilePath& path) { | |
96 DCHECK(OnPolicyThread()); | |
97 | |
98 Reload(); | |
99 } | |
100 | |
101 private: | |
102 // Needed to avoid refcounting NatPolicyLinux. | |
103 class FilePathWatcherDelegate : | |
104 public base::files::FilePathWatcher::Delegate { | |
105 public: | |
106 FilePathWatcherDelegate(base::WeakPtr<NatPolicyLinux> policy_watcher) | |
107 : policy_watcher_(policy_watcher) { | |
108 } | |
109 | |
110 virtual void OnFilePathError(const FilePath& path) { | |
111 if (policy_watcher_) { | |
112 policy_watcher_->OnFilePathError(path); | |
113 } | |
114 } | |
115 | |
116 virtual void OnFilePathChanged(const FilePath& path) { | |
117 if (policy_watcher_) { | |
118 policy_watcher_->OnFilePathChanged(path); | |
119 } | |
120 } | |
121 | |
122 protected: | |
123 virtual ~FilePathWatcherDelegate() {} | |
124 | |
125 private: | |
126 base::WeakPtr<NatPolicyLinux> policy_watcher_; | |
127 }; | |
128 | |
129 base::Time GetLastModification() { | |
130 DCHECK(OnPolicyThread()); | |
131 base::Time last_modification = base::Time(); | |
132 base::PlatformFileInfo file_info; | |
133 | |
134 // If the path does not exist or points to a directory, it's safe to load. | |
135 if (!file_util::GetFileInfo(config_dir_, &file_info) || | |
136 !file_info.is_directory) { | |
137 return last_modification; | |
138 } | |
139 | |
140 // Enumerate the files and find the most recent modification timestamp. | |
141 file_util::FileEnumerator file_enumerator(config_dir_, | |
142 false, | |
143 file_util::FileEnumerator::FILES); | |
144 for (FilePath config_file = file_enumerator.Next(); | |
145 !config_file.empty(); | |
146 config_file = file_enumerator.Next()) { | |
147 if (file_util::GetFileInfo(config_file, &file_info) && | |
148 !file_info.is_directory) { | |
149 last_modification = std::max(last_modification, | |
150 file_info.last_modified); | |
151 } | |
152 } | |
153 | |
154 return last_modification; | |
155 } | |
156 | |
157 // Caller owns the value. | |
158 DictionaryValue* Load() { | |
159 DCHECK(OnPolicyThread()); | |
160 // Enumerate the files and sort them lexicographically. | |
161 std::set<FilePath> files; | |
162 file_util::FileEnumerator file_enumerator(config_dir_, false, | |
163 file_util::FileEnumerator::FILES); | |
164 for (FilePath config_file_path = file_enumerator.Next(); | |
165 !config_file_path.empty(); config_file_path = file_enumerator.Next()) | |
166 files.insert(config_file_path); | |
167 | |
168 // Start with an empty dictionary and merge the files' contents. | |
169 DictionaryValue* policy = new DictionaryValue(); | |
170 for (std::set<FilePath>::iterator config_file_iter = files.begin(); | |
171 config_file_iter != files.end(); ++config_file_iter) { | |
172 JSONFileValueSerializer deserializer(*config_file_iter); | |
173 deserializer.set_allow_trailing_comma(true); | |
174 int error_code = 0; | |
175 std::string error_msg; | |
176 scoped_ptr<Value> value( | |
177 deserializer.Deserialize(&error_code, &error_msg)); | |
178 if (!value.get()) { | |
179 LOG(WARNING) << "Failed to read configuration file " | |
180 << config_file_iter->value() << ": " << error_msg; | |
181 continue; | |
182 } | |
183 if (!value->IsType(Value::TYPE_DICTIONARY)) { | |
184 LOG(WARNING) << "Expected JSON dictionary in configuration file " | |
185 << config_file_iter->value(); | |
186 continue; | |
187 } | |
188 policy->MergeDictionary(static_cast<DictionaryValue*>(value.get())); | |
189 } | |
190 | |
191 return policy; | |
192 } | |
193 | |
194 void Reload() { | |
195 DCHECK(OnPolicyThread()); | |
196 // Check the directory time in order to see whether a reload is required. | |
197 base::TimeDelta delay; | |
198 base::Time now = base::Time::Now(); | |
199 if (!IsSafeToReloadPolicy(now, &delay)) { | |
200 ScheduleReloadTask(delay); | |
201 return; | |
202 } | |
203 | |
204 // Check again in case the directory has changed while reading it. | |
205 if (!IsSafeToReloadPolicy(now, &delay)) { | |
206 ScheduleReloadTask(delay); | |
207 return; | |
208 } | |
209 | |
210 // Load the policy definitions. | |
211 scoped_ptr<DictionaryValue> new_policy(Load()); | |
212 UpdateNatPolicy(new_policy.get()); | |
213 | |
214 ScheduleFallbackReloadTask(); | |
215 } | |
216 | |
217 bool IsSafeToReloadPolicy(const base::Time& now, base::TimeDelta* delay) { | |
218 DCHECK(OnPolicyThread()); | |
219 DCHECK(delay); | |
220 const base::TimeDelta kSettleInterval = | |
221 base::TimeDelta::FromSeconds(kSettleIntervalSeconds); | |
222 | |
223 base::Time last_modification = GetLastModification(); | |
224 if (last_modification.is_null()) | |
225 return true; | |
226 | |
227 if (last_modification_file_.is_null()) | |
228 last_modification_file_ = last_modification; | |
229 | |
230 // If there was a change since the last recorded modification, wait some | |
231 // more. | |
232 if (last_modification != last_modification_file_) { | |
233 last_modification_file_ = last_modification; | |
234 last_modification_clock_ = now; | |
235 *delay = kSettleInterval; | |
236 return false; | |
237 } | |
238 | |
239 // Check whether the settle interval has elapsed. | |
240 base::TimeDelta age = now - last_modification_clock_; | |
241 if (age < kSettleInterval) { | |
242 *delay = kSettleInterval - age; | |
243 return false; | |
244 } | |
245 | |
246 return true; | |
247 } | |
248 | |
249 // Managed with a scoped_ptr rather than being declared as an inline member to | |
250 // decouple the watcher's life cycle from the NatPolicyLinux. This decoupling | |
251 // makes it possible to destroy the watcher before the loader's destructor is | |
252 // called (e.g. during Stop), since |watcher_| internally holds a reference to | |
253 // the loader and keeps it alive. | |
254 scoped_ptr<base::files::FilePathWatcher> watcher_; | |
255 | |
256 // Records last known modification timestamp of |config_dir_|. | |
257 base::Time last_modification_file_; | |
258 | |
259 // The wall clock time at which the last modification timestamp was | |
260 // recorded. It's better to not assume the file notification time and the | |
261 // wall clock times come from the same source, just in case there is some | |
262 // non-local filesystem involved. | |
263 base::Time last_modification_clock_; | |
264 | |
265 const FilePath config_dir_; | |
266 | |
267 // Allows us to cancel any inflight FileWatcher events or scheduled reloads. | |
268 base::WeakPtrFactory<NatPolicyLinux> weak_factory_; | |
269 }; | |
270 | |
271 NatPolicy* NatPolicy::Create( | |
272 scoped_refptr<base::SingleThreadTaskRunner> task_runner) { | |
273 FilePath policy_dir(kPolicyDir); | |
274 return new NatPolicyLinux(task_runner, policy_dir); | |
275 } | |
276 | |
277 } // namespace policy_hack | |
278 } // namespace remoting | |
OLD | NEW |