// Copyright (c) 2010 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "chrome/browser/policy/file_based_policy_provider.h" #include #include "base/logging.h" #include "base/message_loop.h" #include "base/task.h" #include "base/utf_string_conversions.h" #include "base/values.h" #include "chrome/browser/browser_thread.h" #include "chrome/common/json_value_serializer.h" namespace policy { // Amount of time we wait for the files on disk to settle before trying to load // it. This alleviates the problem of reading partially written files and allows // to batch quasi-simultaneous changes. const int kSettleIntervalSeconds = 5; // The time interval for rechecking policy. This is our fallback in case the // file path watch fails or doesn't report a change. const int kReloadIntervalMinutes = 15; // FileBasedPolicyProvider implementation: FileBasedPolicyProvider::Delegate::~Delegate() { } FileBasedPolicyProvider::Delegate::Delegate(const FilePath& config_file_path) : config_file_path_(config_file_path) { } FileBasedPolicyProvider::FileBasedPolicyProvider( const ConfigurationPolicyProvider::PolicyDefinitionList* policy_list, FileBasedPolicyProvider::Delegate* delegate) : ConfigurationPolicyProvider(policy_list) { loader_ = new FileBasedPolicyLoader(AsWeakPtr(), delegate, kSettleIntervalSeconds, kReloadIntervalMinutes); watcher_ = new FileBasedPolicyWatcher; watcher_->Init(loader_.get()); } FileBasedPolicyProvider::~FileBasedPolicyProvider() { loader_->Stop(); } bool FileBasedPolicyProvider::Provide( ConfigurationPolicyStoreInterface* store) { scoped_ptr policy(loader_->GetPolicy()); DCHECK(policy.get()); DecodePolicyValueTree(policy.get(), store); return true; } // FileBasedPolicyLoader implementation: FileBasedPolicyLoader::FileBasedPolicyLoader( base::WeakPtr provider, FileBasedPolicyProvider::Delegate* delegate, int settle_interval_seconds, int reload_interval_minutes) : delegate_(delegate), provider_(provider), origin_loop_(MessageLoop::current()), reload_task_(NULL), settle_interval_seconds_(settle_interval_seconds), reload_interval_minutes_(reload_interval_minutes) { // Force an initial load, so GetPolicy() works. policy_.reset(delegate_->Load()); DCHECK(policy_.get()); } void FileBasedPolicyLoader::Stop() { if (!BrowserThread::CurrentlyOn(BrowserThread::FILE)) { BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE, NewRunnableMethod(this, &FileBasedPolicyLoader::Stop)); return; } if (reload_task_) { reload_task_->Cancel(); reload_task_ = NULL; } } void FileBasedPolicyLoader::Reload() { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); // Check the directory time in order to see whether a reload is required. base::TimeDelta delay; base::Time now = base::Time::Now(); if (!IsSafeToReloadPolicy(now, &delay)) { ScheduleReloadTask(delay); return; } // Load the policy definitions. scoped_ptr new_policy(delegate_->Load()); // Check again in case the directory has changed while reading it. if (!IsSafeToReloadPolicy(now, &delay)) { ScheduleReloadTask(delay); return; } // Replace policy definition. bool changed = false; { AutoLock lock(lock_); changed = !policy_->Equals(new_policy.get()); policy_.reset(new_policy.release()); } // There's a change, report it! if (changed) { VLOG(0) << "Policy reload from " << config_file_path().value() << " succeeded."; origin_loop_->PostTask(FROM_HERE, NewRunnableMethod(this, &FileBasedPolicyLoader::NotifyPolicyChanged)); } // As a safeguard in case the file watcher fails, schedule a reload task // that'll make us recheck after a reasonable interval. ScheduleReloadTask(base::TimeDelta::FromMinutes(reload_interval_minutes_)); } DictionaryValue* FileBasedPolicyLoader::GetPolicy() { AutoLock lock(lock_); return static_cast(policy_->DeepCopy()); } void FileBasedPolicyLoader::OnFilePathChanged(const FilePath& path) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); Reload(); } void FileBasedPolicyLoader::OnError() { LOG(ERROR) << "FilePathWatcher on " << config_file_path().value() << " failed."; } FileBasedPolicyLoader::~FileBasedPolicyLoader() { } bool FileBasedPolicyLoader::IsSafeToReloadPolicy(const base::Time& now, base::TimeDelta* delay) { DCHECK(delay); // A null modification time indicates there's no data. base::Time last_modification(delegate_->GetLastModification()); if (last_modification.is_null()) return true; // If there was a change since the last recorded modification, wait some more. base::TimeDelta settleInterval( base::TimeDelta::FromSeconds(settle_interval_seconds_)); if (last_modification != last_modification_file_) { last_modification_file_ = last_modification; last_modification_clock_ = now; *delay = settleInterval; return false; } // Check whether the settle interval has elapsed. base::TimeDelta age = now - last_modification_clock_; if (age < settleInterval) { *delay = settleInterval - age; return false; } return true; } void FileBasedPolicyLoader::ScheduleReloadTask(const base::TimeDelta& delay) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); if (reload_task_) reload_task_->Cancel(); reload_task_ = NewRunnableMethod(this, &FileBasedPolicyLoader::ReloadFromTask); BrowserThread::PostDelayedTask(BrowserThread::FILE, FROM_HERE, reload_task_, delay.InMilliseconds()); } void FileBasedPolicyLoader::NotifyPolicyChanged() { DCHECK_EQ(origin_loop_, MessageLoop::current()); if (provider_) provider_->NotifyStoreOfPolicyChange(); } void FileBasedPolicyLoader::ReloadFromTask() { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); // Drop the reference to the reload task, since the task might be the only // referer that keeps us alive, so we should not Cancel() it. reload_task_ = NULL; Reload(); } // FileBasedPolicyWatcher implementation: FileBasedPolicyWatcher::FileBasedPolicyWatcher() { } void FileBasedPolicyWatcher::Init(FileBasedPolicyLoader* loader) { // Initialization can happen early when the file thread is not yet available. // So post a task to ourselves on the UI thread which will run after threading // is up and schedule watch initialization on the file thread. BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, NewRunnableMethod(this, &FileBasedPolicyWatcher::InitWatcher, scoped_refptr(loader))); } FileBasedPolicyWatcher::~FileBasedPolicyWatcher() { } void FileBasedPolicyWatcher::InitWatcher( const scoped_refptr& loader) { if (!BrowserThread::CurrentlyOn(BrowserThread::FILE)) { BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE, NewRunnableMethod(this, &FileBasedPolicyWatcher::InitWatcher, loader)); return; } if (!loader->config_file_path().empty() && !watcher_.Watch(loader->config_file_path(), loader.get())) loader->OnError(); // There might have been changes to the directory in the time between // construction of the loader and initialization of the watcher. Call reload // to detect if that is the case. loader->Reload(); } } // namespace policy