| OLD | NEW |
| 1 // Copyright (c) 2010 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2010 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "chrome/browser/policy/config_dir_policy_provider.h" | 5 #include "chrome/browser/policy/config_dir_policy_provider.h" |
| 6 | 6 |
| 7 #include <set> | 7 #include <set> |
| 8 | 8 |
| 9 #include "base/file_util.h" | 9 #include "base/file_util.h" |
| 10 #include "base/logging.h" | 10 #include "base/logging.h" |
| 11 #include "base/scoped_ptr.h" |
| 11 #include "base/utf_string_conversions.h" | 12 #include "base/utf_string_conversions.h" |
| 12 #include "base/values.h" | 13 #include "base/values.h" |
| 13 #include "chrome/common/json_value_serializer.h" | 14 #include "chrome/common/json_value_serializer.h" |
| 14 | 15 |
| 15 namespace { | 16 ConfigDirPolicyProvider::ConfigDirPolicyProvider(const FilePath& config_dir) |
| 16 | 17 : config_dir_(config_dir) { |
| 17 // Amount of time we wait for the files in the policy directory to settle before | |
| 18 // trying to load it. This alleviates the problem of reading partially written | |
| 19 // files and allows to batch quasi-simultaneous changes. | |
| 20 const int kSettleIntervalSeconds = 5; | |
| 21 | |
| 22 // The time interval for rechecking policy. This is our fallback in case the | |
| 23 // directory watch fails or doesn't report a change. | |
| 24 const int kReloadIntervalMinutes = 15; | |
| 25 | |
| 26 } // namespace | |
| 27 | |
| 28 // PolicyDirLoader implementation: | |
| 29 | |
| 30 PolicyDirLoader::PolicyDirLoader( | |
| 31 base::WeakPtr<ConfigDirPolicyProvider> provider, | |
| 32 const FilePath& config_dir, | |
| 33 int settle_interval_seconds, | |
| 34 int reload_interval_minutes) | |
| 35 : provider_(provider), | |
| 36 origin_loop_(MessageLoop::current()), | |
| 37 config_dir_(config_dir), | |
| 38 reload_task_(NULL), | |
| 39 settle_interval_seconds_(settle_interval_seconds), | |
| 40 reload_interval_minutes_(reload_interval_minutes) { | |
| 41 // Force an initial load, so GetPolicy() works. | |
| 42 policy_.reset(Load()); | |
| 43 DCHECK(policy_.get()); | |
| 44 } | 18 } |
| 45 | 19 |
| 46 void PolicyDirLoader::Stop() { | 20 bool ConfigDirPolicyProvider::Provide(ConfigurationPolicyStore* store) { |
| 47 if (!ChromeThread::CurrentlyOn(ChromeThread::FILE)) { | 21 scoped_ptr<DictionaryValue> policy(ReadPolicies()); |
| 48 ChromeThread::PostTask(ChromeThread::FILE, FROM_HERE, | 22 DecodePolicyValueTree(policy.get(), store); |
| 49 NewRunnableMethod(this, &PolicyDirLoader::Stop)); | 23 return true; |
| 50 return; | |
| 51 } | |
| 52 | |
| 53 if (reload_task_) { | |
| 54 reload_task_->Cancel(); | |
| 55 reload_task_ = NULL; | |
| 56 } | |
| 57 } | 24 } |
| 58 | 25 |
| 59 void PolicyDirLoader::Reload() { | 26 DictionaryValue* ConfigDirPolicyProvider::ReadPolicies() { |
| 60 DCHECK(ChromeThread::CurrentlyOn(ChromeThread::FILE)); | |
| 61 | |
| 62 // Check the directory time in order to see whether a reload is required. | |
| 63 base::Time now = base::Time::Now(); | |
| 64 base::TimeDelta delay; | |
| 65 if (!IsSafeToReloadPolicy(now, &delay)) { | |
| 66 ScheduleReloadTask(delay); | |
| 67 return; | |
| 68 } | |
| 69 | |
| 70 // Load the policy definitions. | |
| 71 scoped_ptr<DictionaryValue> new_policy(Load()); | |
| 72 | |
| 73 // Check again in case the directory has changed while reading it. | |
| 74 if (!IsSafeToReloadPolicy(now, &delay)) { | |
| 75 ScheduleReloadTask(delay); | |
| 76 return; | |
| 77 } | |
| 78 | |
| 79 // Replace policy definition. | |
| 80 bool changed = false; | |
| 81 { | |
| 82 AutoLock lock(lock_); | |
| 83 changed = !policy_->Equals(new_policy.get()); | |
| 84 policy_.reset(new_policy.release()); | |
| 85 } | |
| 86 | |
| 87 // There's a change, report it! | |
| 88 if (changed) { | |
| 89 LOG(INFO) << "Policy reload from " << config_dir_.value() << " succeeded."; | |
| 90 origin_loop_->PostTask(FROM_HERE, | |
| 91 NewRunnableMethod(this, &PolicyDirLoader::NotifyPolicyChanged)); | |
| 92 } | |
| 93 | |
| 94 // As a safeguard in case the file watcher fails, schedule a reload task | |
| 95 // that'll make us recheck after a reasonable interval. | |
| 96 ScheduleReloadTask(base::TimeDelta::FromMinutes(reload_interval_minutes_)); | |
| 97 } | |
| 98 | |
| 99 DictionaryValue* PolicyDirLoader::GetPolicy() { | |
| 100 AutoLock lock(lock_); | |
| 101 return static_cast<DictionaryValue*>(policy_->DeepCopy()); | |
| 102 } | |
| 103 | |
| 104 void PolicyDirLoader::OnFilePathChanged(const FilePath& path) { | |
| 105 DCHECK(ChromeThread::CurrentlyOn(ChromeThread::FILE)); | |
| 106 Reload(); | |
| 107 } | |
| 108 | |
| 109 void PolicyDirLoader::OnError() { | |
| 110 LOG(ERROR) << "FileWatcher on " << config_dir_.value() << " failed."; | |
| 111 } | |
| 112 | |
| 113 DictionaryValue* PolicyDirLoader::Load() { | |
| 114 // Enumerate the files and sort them lexicographically. | 27 // Enumerate the files and sort them lexicographically. |
| 115 std::set<FilePath> files; | 28 std::set<FilePath> files; |
| 116 file_util::FileEnumerator file_enumerator(config_dir_, false, | 29 file_util::FileEnumerator file_enumerator(config_dir_, false, |
| 117 file_util::FileEnumerator::FILES); | 30 file_util::FileEnumerator::FILES); |
| 118 for (FilePath config_file_path = file_enumerator.Next(); | 31 for (FilePath config_file_path = file_enumerator.Next(); |
| 119 !config_file_path.empty(); config_file_path = file_enumerator.Next()) | 32 !config_file_path.empty(); config_file_path = file_enumerator.Next()) |
| 120 files.insert(config_file_path); | 33 files.insert(config_file_path); |
| 121 | 34 |
| 122 // Start with an empty dictionary and merge the files' contents. | 35 // Start with an empty dictionary and merge the files' contents. |
| 123 DictionaryValue* policy = new DictionaryValue; | 36 DictionaryValue* policy = new DictionaryValue; |
| (...skipping 12 matching lines...) Expand all Loading... |
| 136 LOG(WARNING) << "Expected JSON dictionary in configuration file " | 49 LOG(WARNING) << "Expected JSON dictionary in configuration file " |
| 137 << config_file_iter->value(); | 50 << config_file_iter->value(); |
| 138 continue; | 51 continue; |
| 139 } | 52 } |
| 140 policy->MergeDictionary(static_cast<DictionaryValue*>(value.get())); | 53 policy->MergeDictionary(static_cast<DictionaryValue*>(value.get())); |
| 141 } | 54 } |
| 142 | 55 |
| 143 return policy; | 56 return policy; |
| 144 } | 57 } |
| 145 | 58 |
| 146 bool PolicyDirLoader::IsSafeToReloadPolicy(const base::Time& now, | |
| 147 base::TimeDelta* delay) { | |
| 148 DCHECK(delay); | |
| 149 file_util::FileInfo dir_info; | |
| 150 | |
| 151 // Reading an empty directory or a file is always safe. | |
| 152 if (!file_util::GetFileInfo(config_dir_, &dir_info) || | |
| 153 !dir_info.is_directory) { | |
| 154 return true; | |
| 155 } | |
| 156 | |
| 157 // Check for too recent changes. | |
| 158 base::TimeDelta settleInterval( | |
| 159 base::TimeDelta::FromSeconds(settle_interval_seconds_)); | |
| 160 base::TimeDelta age = now - dir_info.last_modified; | |
| 161 if (age < settleInterval) { | |
| 162 *delay = settleInterval - age; | |
| 163 return false; | |
| 164 } | |
| 165 | |
| 166 return true; | |
| 167 } | |
| 168 | |
| 169 void PolicyDirLoader::ScheduleReloadTask(const base::TimeDelta& delay) { | |
| 170 DCHECK(ChromeThread::CurrentlyOn(ChromeThread::FILE)); | |
| 171 | |
| 172 if (reload_task_) | |
| 173 reload_task_->Cancel(); | |
| 174 | |
| 175 reload_task_ = NewRunnableMethod(this, &PolicyDirLoader::ReloadFromTask); | |
| 176 ChromeThread::PostDelayedTask(ChromeThread::FILE, FROM_HERE, reload_task_, | |
| 177 delay.InMilliseconds()); | |
| 178 } | |
| 179 | |
| 180 void PolicyDirLoader::NotifyPolicyChanged() { | |
| 181 DCHECK_EQ(origin_loop_, MessageLoop::current()); | |
| 182 if (provider_) | |
| 183 provider_->NotifyStoreOfPolicyChange(); | |
| 184 } | |
| 185 | |
| 186 void PolicyDirLoader::ReloadFromTask() { | |
| 187 DCHECK(ChromeThread::CurrentlyOn(ChromeThread::FILE)); | |
| 188 | |
| 189 // Drop the reference to the reload task, since the task might be the only | |
| 190 // referer that keeps us alive, so we should not Cancel() it. | |
| 191 reload_task_ = NULL; | |
| 192 | |
| 193 Reload(); | |
| 194 } | |
| 195 | |
| 196 // PolicyDirWatcher implementation: | |
| 197 | |
| 198 void PolicyDirWatcher::Init(PolicyDirLoader* loader) { | |
| 199 // Initialization can happen early when the file thread is not yet available. | |
| 200 // So post a task to ourselves on th UI thread which will run after threading | |
| 201 // is up and schedule watch initialization on the file thread. | |
| 202 ChromeThread::PostTask(ChromeThread::UI, FROM_HERE, | |
| 203 NewRunnableMethod(this, | |
| 204 &PolicyDirWatcher::InitWatcher, | |
| 205 scoped_refptr<PolicyDirLoader>(loader))); | |
| 206 } | |
| 207 | |
| 208 void PolicyDirWatcher::InitWatcher( | |
| 209 const scoped_refptr<PolicyDirLoader>& loader) { | |
| 210 if (!ChromeThread::CurrentlyOn(ChromeThread::FILE)) { | |
| 211 ChromeThread::PostTask(ChromeThread::FILE, FROM_HERE, | |
| 212 NewRunnableMethod(this, &PolicyDirWatcher::InitWatcher, loader)); | |
| 213 return; | |
| 214 } | |
| 215 | |
| 216 if (!Watch(loader->config_dir(), loader.get())) | |
| 217 loader->OnError(); | |
| 218 | |
| 219 // There might have been changes to the directory in the time between | |
| 220 // construction of the loader and initialization of the watcher. Call reload | |
| 221 // to detect if that is the case. | |
| 222 loader->Reload(); | |
| 223 } | |
| 224 | |
| 225 // ConfigDirPolicyProvider implementation: | |
| 226 | |
| 227 ConfigDirPolicyProvider::ConfigDirPolicyProvider(const FilePath& config_dir) { | |
| 228 loader_ = new PolicyDirLoader(AsWeakPtr(), config_dir, kSettleIntervalSeconds, | |
| 229 kReloadIntervalMinutes); | |
| 230 watcher_ = new PolicyDirWatcher; | |
| 231 watcher_->Init(loader_.get()); | |
| 232 } | |
| 233 | |
| 234 ConfigDirPolicyProvider::~ConfigDirPolicyProvider() { | |
| 235 loader_->Stop(); | |
| 236 } | |
| 237 | |
| 238 bool ConfigDirPolicyProvider::Provide(ConfigurationPolicyStore* store) { | |
| 239 scoped_ptr<DictionaryValue> policy(loader_->GetPolicy()); | |
| 240 DCHECK(policy.get()); | |
| 241 DecodePolicyValueTree(policy.get(), store); | |
| 242 return true; | |
| 243 } | |
| 244 | |
| 245 void ConfigDirPolicyProvider::DecodePolicyValueTree( | 59 void ConfigDirPolicyProvider::DecodePolicyValueTree( |
| 246 DictionaryValue* policies, | 60 DictionaryValue* policies, |
| 247 ConfigurationPolicyStore* store) { | 61 ConfigurationPolicyStore* store) { |
| 248 const PolicyValueMap* mapping = PolicyValueMapping(); | 62 const PolicyValueMap* mapping = PolicyValueMapping(); |
| 249 for (PolicyValueMap::const_iterator i = mapping->begin(); | 63 for (PolicyValueMap::const_iterator i = mapping->begin(); |
| 250 i != mapping->end(); ++i) { | 64 i != mapping->end(); ++i) { |
| 251 const PolicyValueMapEntry& entry(*i); | 65 const PolicyValueMapEntry& entry(*i); |
| 252 Value* value; | 66 Value* value; |
| 253 if (policies->Get(entry.name, &value) && value->IsType(entry.value_type)) | 67 if (policies->Get(entry.name, &value) && value->IsType(entry.value_type)) |
| 254 store->Apply(entry.policy_type, value->DeepCopy()); | 68 store->Apply(entry.policy_type, value->DeepCopy()); |
| 255 } | 69 } |
| 256 | 70 |
| 257 // TODO(mnissler): Handle preference overrides once |ConfigurationPolicyStore| | 71 // TODO(mnissler): Handle preference overrides once |ConfigurationPolicyStore| |
| 258 // supports it. | 72 // supports it. |
| 259 } | 73 } |
| 74 |
| OLD | NEW |