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