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