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/policy_watcher.h" | |
16 | |
17 #include "base/bind.h" | |
18 #include "base/compiler_specific.h" | |
19 #include "base/files/file_enumerator.h" | |
20 #include "base/files/file_path.h" | |
21 #include "base/files/file_path_watcher.h" | |
22 #include "base/files/file_util.h" | |
23 #include "base/json/json_file_value_serializer.h" | |
24 #include "base/memory/scoped_ptr.h" | |
25 #include "base/memory/weak_ptr.h" | |
26 #include "base/single_thread_task_runner.h" | |
27 #include "base/synchronization/waitable_event.h" | |
28 #include "base/time/time.h" | |
29 #include "base/values.h" | |
30 | |
31 namespace remoting { | |
32 namespace policy_hack { | |
33 | |
34 namespace { | |
35 | |
36 const base::FilePath::CharType kPolicyDir[] = | |
37 // Always read the Chrome policies (even on Chromium) so that policy | |
38 // enforcement can't be bypassed by running Chromium. | |
39 FILE_PATH_LITERAL("/etc/opt/chrome/policies/managed"); | |
40 | |
41 // Amount of time we wait for the files on disk to settle before trying to load | |
42 // them. This alleviates the problem of reading partially written files and | |
43 // makes it possible to batch quasi-simultaneous changes. | |
44 const int kSettleIntervalSeconds = 5; | |
45 | |
46 } // namespace | |
47 | |
48 class PolicyWatcherLinux : public PolicyWatcher { | |
49 public: | |
50 PolicyWatcherLinux(scoped_refptr<base::SingleThreadTaskRunner> task_runner, | |
51 const base::FilePath& config_dir) | |
52 : PolicyWatcher(task_runner), | |
53 config_dir_(config_dir), | |
54 weak_factory_(this) { | |
55 } | |
56 | |
57 ~PolicyWatcherLinux() override {} | |
58 | |
59 protected: | |
60 void StartWatchingInternal() override { | |
61 DCHECK(OnPolicyWatcherThread()); | |
62 watcher_.reset(new base::FilePathWatcher()); | |
63 | |
64 if (!config_dir_.empty() && | |
65 !watcher_->Watch( | |
66 config_dir_, false, | |
67 base::Bind(&PolicyWatcherLinux::OnFilePathChanged, | |
68 weak_factory_.GetWeakPtr()))) { | |
69 OnFilePathChanged(config_dir_, true); | |
70 } | |
71 | |
72 // There might have been changes to the directory in the time between | |
73 // construction of the loader and initialization of the watcher. Call reload | |
74 // to detect if that is the case. | |
75 Reload(); | |
76 | |
77 ScheduleFallbackReloadTask(); | |
78 } | |
79 | |
80 void StopWatchingInternal() override { | |
81 DCHECK(OnPolicyWatcherThread()); | |
82 | |
83 // Stop watching for changes to files in the policies directory. | |
84 watcher_.reset(); | |
85 | |
86 // Orphan any pending OnFilePathChanged tasks. | |
87 weak_factory_.InvalidateWeakPtrs(); | |
88 } | |
89 | |
90 private: | |
91 void OnFilePathChanged(const base::FilePath& path, bool error) { | |
92 DCHECK(OnPolicyWatcherThread()); | |
93 | |
94 if (!error) | |
95 Reload(); | |
96 else | |
97 LOG(ERROR) << "PolicyWatcherLinux on " << path.value() << " failed."; | |
98 } | |
99 | |
100 base::Time GetLastModification() { | |
101 DCHECK(OnPolicyWatcherThread()); | |
102 base::Time last_modification = base::Time(); | |
103 base::File::Info file_info; | |
104 | |
105 // If the path does not exist or points to a directory, it's safe to load. | |
106 if (!base::GetFileInfo(config_dir_, &file_info) || | |
107 !file_info.is_directory) { | |
108 return last_modification; | |
109 } | |
110 | |
111 // Enumerate the files and find the most recent modification timestamp. | |
112 base::FileEnumerator file_enumerator(config_dir_, | |
113 false, | |
114 base::FileEnumerator::FILES); | |
115 for (base::FilePath config_file = file_enumerator.Next(); | |
116 !config_file.empty(); | |
117 config_file = file_enumerator.Next()) { | |
118 if (base::GetFileInfo(config_file, &file_info) && | |
119 !file_info.is_directory) { | |
120 last_modification = std::max(last_modification, | |
121 file_info.last_modified); | |
122 } | |
123 } | |
124 | |
125 return last_modification; | |
126 } | |
127 | |
128 // Returns NULL if the policy dictionary couldn't be read. | |
129 scoped_ptr<base::DictionaryValue> Load() { | |
130 DCHECK(OnPolicyWatcherThread()); | |
131 // Enumerate the files and sort them lexicographically. | |
132 std::set<base::FilePath> files; | |
133 base::FileEnumerator file_enumerator(config_dir_, false, | |
134 base::FileEnumerator::FILES); | |
135 for (base::FilePath config_file_path = file_enumerator.Next(); | |
136 !config_file_path.empty(); config_file_path = file_enumerator.Next()) | |
137 files.insert(config_file_path); | |
138 | |
139 // Start with an empty dictionary and merge the files' contents. | |
140 scoped_ptr<base::DictionaryValue> policy(new base::DictionaryValue()); | |
141 for (std::set<base::FilePath>::iterator config_file_iter = files.begin(); | |
142 config_file_iter != files.end(); ++config_file_iter) { | |
143 JSONFileValueSerializer deserializer(*config_file_iter); | |
144 deserializer.set_allow_trailing_comma(true); | |
145 int error_code = 0; | |
146 std::string error_msg; | |
147 scoped_ptr<base::Value> value( | |
148 deserializer.Deserialize(&error_code, &error_msg)); | |
149 if (!value.get()) { | |
150 LOG(WARNING) << "Failed to read configuration file " | |
151 << config_file_iter->value() << ": " << error_msg; | |
152 return nullptr; | |
153 } | |
154 if (!value->IsType(base::Value::TYPE_DICTIONARY)) { | |
155 LOG(WARNING) << "Expected JSON dictionary in configuration file " | |
156 << config_file_iter->value(); | |
157 return nullptr; | |
158 } | |
159 policy->MergeDictionary(static_cast<base::DictionaryValue*>(value.get())); | |
160 } | |
161 | |
162 return policy.Pass(); | |
163 } | |
164 | |
165 void Reload() override { | |
166 DCHECK(OnPolicyWatcherThread()); | |
167 // Check the directory time in order to see whether a reload is required. | |
168 base::TimeDelta delay; | |
169 base::Time now = base::Time::Now(); | |
170 if (!IsSafeToReloadPolicy(now, &delay)) { | |
171 ScheduleReloadTask(delay); | |
172 return; | |
173 } | |
174 | |
175 // Check again in case the directory has changed while reading it. | |
176 if (!IsSafeToReloadPolicy(now, &delay)) { | |
177 ScheduleReloadTask(delay); | |
178 return; | |
179 } | |
180 | |
181 // Load the policy definitions. | |
182 scoped_ptr<base::DictionaryValue> new_policy = Load(); | |
183 if (new_policy.get()) { | |
184 UpdatePolicies(new_policy.get()); | |
185 ScheduleFallbackReloadTask(); | |
186 } else { | |
187 // A failure to load policy definitions is probably temporary, so try | |
188 // again soon. | |
189 SignalTransientPolicyError(); | |
190 ScheduleReloadTask(base::TimeDelta::FromSeconds(kSettleIntervalSeconds)); | |
191 } | |
192 } | |
193 | |
194 bool IsSafeToReloadPolicy(const base::Time& now, base::TimeDelta* delay) { | |
195 DCHECK(OnPolicyWatcherThread()); | |
196 DCHECK(delay); | |
197 const base::TimeDelta kSettleInterval = | |
198 base::TimeDelta::FromSeconds(kSettleIntervalSeconds); | |
199 | |
200 base::Time last_modification = GetLastModification(); | |
201 if (last_modification.is_null()) | |
202 return true; | |
203 | |
204 if (last_modification_file_.is_null()) | |
205 last_modification_file_ = last_modification; | |
206 | |
207 // If there was a change since the last recorded modification, wait some | |
208 // more. | |
209 if (last_modification != last_modification_file_) { | |
210 last_modification_file_ = last_modification; | |
211 last_modification_clock_ = now; | |
212 *delay = kSettleInterval; | |
213 return false; | |
214 } | |
215 | |
216 // Check whether the settle interval has elapsed. | |
217 base::TimeDelta age = now - last_modification_clock_; | |
218 if (age < kSettleInterval) { | |
219 *delay = kSettleInterval - age; | |
220 return false; | |
221 } | |
222 | |
223 return true; | |
224 } | |
225 | |
226 // Managed with a scoped_ptr rather than being declared as an inline member to | |
227 // decouple the watcher's life cycle from the PolicyWatcherLinux. This | |
228 // decoupling makes it possible to destroy the watcher before the loader's | |
229 // destructor is called (e.g. during Stop), since |watcher_| internally holds | |
230 // a reference to the loader and keeps it alive. | |
231 scoped_ptr<base::FilePathWatcher> watcher_; | |
232 | |
233 // Records last known modification timestamp of |config_dir_|. | |
234 base::Time last_modification_file_; | |
235 | |
236 // The wall clock time at which the last modification timestamp was | |
237 // recorded. It's better to not assume the file notification time and the | |
238 // wall clock times come from the same source, just in case there is some | |
239 // non-local filesystem involved. | |
240 base::Time last_modification_clock_; | |
241 | |
242 const base::FilePath config_dir_; | |
243 | |
244 // Allows us to cancel any inflight FileWatcher events or scheduled reloads. | |
245 base::WeakPtrFactory<PolicyWatcherLinux> weak_factory_; | |
246 }; | |
247 | |
248 scoped_ptr<PolicyWatcher> PolicyWatcher::Create( | |
249 policy::PolicyService* policy_service, | |
250 scoped_refptr<base::SingleThreadTaskRunner> task_runner) { | |
251 base::FilePath policy_dir(kPolicyDir); | |
252 return make_scoped_ptr(new PolicyWatcherLinux(task_runner, policy_dir)); | |
253 } | |
254 | |
255 } // namespace policy_hack | |
256 } // namespace remoting | |
OLD | NEW |