// Copyright (c) 2012 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. // Most of this code is copied from various classes in // src/chrome/browser/policy. In particular, look at // // file_based_policy_loader.{h,cc} // config_dir_policy_provider.{h,cc} // // This is a reduction of the functionality in those classes. #include #include "remoting/host/policy_hack/policy_watcher.h" #include "base/bind.h" #include "base/compiler_specific.h" #include "base/file_util.h" #include "base/files/file_enumerator.h" #include "base/files/file_path.h" #include "base/files/file_path_watcher.h" #include "base/json/json_file_value_serializer.h" #include "base/memory/scoped_ptr.h" #include "base/memory/weak_ptr.h" #include "base/single_thread_task_runner.h" #include "base/synchronization/waitable_event.h" #include "base/time/time.h" #include "base/values.h" namespace remoting { namespace policy_hack { namespace { const base::FilePath::CharType kPolicyDir[] = // Always read the Chrome policies (even on Chromium) so that policy // enforcement can't be bypassed by running Chromium. FILE_PATH_LITERAL("/etc/opt/chrome/policies/managed"); // Amount of time we wait for the files on disk to settle before trying to load // them. This alleviates the problem of reading partially written files and // makes it possible to batch quasi-simultaneous changes. const int kSettleIntervalSeconds = 5; } // namespace class PolicyWatcherLinux : public PolicyWatcher { public: PolicyWatcherLinux(scoped_refptr task_runner, const base::FilePath& config_dir) : PolicyWatcher(task_runner), config_dir_(config_dir), weak_factory_(this) { } virtual ~PolicyWatcherLinux() {} protected: virtual void StartWatchingInternal() OVERRIDE { DCHECK(OnPolicyWatcherThread()); watcher_.reset(new base::FilePathWatcher()); if (!config_dir_.empty() && !watcher_->Watch( config_dir_, false, base::Bind(&PolicyWatcherLinux::OnFilePathChanged, weak_factory_.GetWeakPtr()))) { OnFilePathChanged(config_dir_, true); } // 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. Reload(); ScheduleFallbackReloadTask(); } virtual void StopWatchingInternal() OVERRIDE { DCHECK(OnPolicyWatcherThread()); // Stop watching for changes to files in the policies directory. watcher_.reset(); // Orphan any pending OnFilePathChanged tasks. weak_factory_.InvalidateWeakPtrs(); } private: void OnFilePathChanged(const base::FilePath& path, bool error) { DCHECK(OnPolicyWatcherThread()); if (!error) Reload(); else LOG(ERROR) << "PolicyWatcherLinux on " << path.value() << " failed."; } base::Time GetLastModification() { DCHECK(OnPolicyWatcherThread()); base::Time last_modification = base::Time(); base::File::Info file_info; // If the path does not exist or points to a directory, it's safe to load. if (!base::GetFileInfo(config_dir_, &file_info) || !file_info.is_directory) { return last_modification; } // Enumerate the files and find the most recent modification timestamp. base::FileEnumerator file_enumerator(config_dir_, false, base::FileEnumerator::FILES); for (base::FilePath config_file = file_enumerator.Next(); !config_file.empty(); config_file = file_enumerator.Next()) { if (base::GetFileInfo(config_file, &file_info) && !file_info.is_directory) { last_modification = std::max(last_modification, file_info.last_modified); } } return last_modification; } // Returns NULL if the policy dictionary couldn't be read. scoped_ptr Load() { DCHECK(OnPolicyWatcherThread()); // Enumerate the files and sort them lexicographically. std::set files; base::FileEnumerator file_enumerator(config_dir_, false, base::FileEnumerator::FILES); for (base::FilePath config_file_path = file_enumerator.Next(); !config_file_path.empty(); config_file_path = file_enumerator.Next()) files.insert(config_file_path); // Start with an empty dictionary and merge the files' contents. scoped_ptr policy(new base::DictionaryValue()); for (std::set::iterator config_file_iter = files.begin(); config_file_iter != files.end(); ++config_file_iter) { JSONFileValueSerializer deserializer(*config_file_iter); deserializer.set_allow_trailing_comma(true); int error_code = 0; std::string error_msg; scoped_ptr value( deserializer.Deserialize(&error_code, &error_msg)); if (!value.get()) { LOG(WARNING) << "Failed to read configuration file " << config_file_iter->value() << ": " << error_msg; return scoped_ptr(); } if (!value->IsType(base::Value::TYPE_DICTIONARY)) { LOG(WARNING) << "Expected JSON dictionary in configuration file " << config_file_iter->value(); return scoped_ptr(); } policy->MergeDictionary(static_cast(value.get())); } return policy.Pass(); } virtual void Reload() OVERRIDE { DCHECK(OnPolicyWatcherThread()); // 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; } // Check again in case the directory has changed while reading it. if (!IsSafeToReloadPolicy(now, &delay)) { ScheduleReloadTask(delay); return; } // Load the policy definitions. scoped_ptr new_policy = Load(); if (new_policy.get()) { UpdatePolicies(new_policy.get()); ScheduleFallbackReloadTask(); } else { // A failure to load policy definitions is probably temporary, so try // again soon. ScheduleReloadTask(base::TimeDelta::FromSeconds(kSettleIntervalSeconds)); } } bool IsSafeToReloadPolicy(const base::Time& now, base::TimeDelta* delay) { DCHECK(OnPolicyWatcherThread()); DCHECK(delay); const base::TimeDelta kSettleInterval = base::TimeDelta::FromSeconds(kSettleIntervalSeconds); base::Time last_modification = GetLastModification(); if (last_modification.is_null()) return true; if (last_modification_file_.is_null()) last_modification_file_ = last_modification; // If there was a change since the last recorded modification, wait some // more. if (last_modification != last_modification_file_) { last_modification_file_ = last_modification; last_modification_clock_ = now; *delay = kSettleInterval; return false; } // Check whether the settle interval has elapsed. base::TimeDelta age = now - last_modification_clock_; if (age < kSettleInterval) { *delay = kSettleInterval - age; return false; } return true; } // Managed with a scoped_ptr rather than being declared as an inline member to // decouple the watcher's life cycle from the PolicyWatcherLinux. This // decoupling makes it possible to destroy the watcher before the loader's // destructor is called (e.g. during Stop), since |watcher_| internally holds // a reference to the loader and keeps it alive. scoped_ptr watcher_; // Records last known modification timestamp of |config_dir_|. base::Time last_modification_file_; // The wall clock time at which the last modification timestamp was // recorded. It's better to not assume the file notification time and the // wall clock times come from the same source, just in case there is some // non-local filesystem involved. base::Time last_modification_clock_; const base::FilePath config_dir_; // Allows us to cancel any inflight FileWatcher events or scheduled reloads. base::WeakPtrFactory weak_factory_; }; PolicyWatcher* PolicyWatcher::Create( scoped_refptr task_runner) { base::FilePath policy_dir(kPolicyDir); return new PolicyWatcherLinux(task_runner, policy_dir); } } // namespace policy_hack } // namespace remoting