1 // Copyright (c) 2012 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 // Most of this code is copied from various classes in 6 // src/chrome/browser/policy. In particular, look at 7 // 8 // file_based_policy_loader.{h,cc} 9 // config_dir_policy_provider.{h,cc} 10 // 11 // This is a reduction of the functionality in those classes. 12 13 #include <set> 14 15 #include "remoting/host/policy_hack/policy_watcher.h" 16 17 #include "base/bind.h" 18 #include "base/compiler_specific.h" 19 #include "base/files/file_enumerator.h" 20 #include "base/files/file_path.h" 21 #include "base/files/file_path_watcher.h" 22 #include "base/files/file_util.h" 23 #include "base/json/json_file_value_serializer.h" 24 #include "base/memory/scoped_ptr.h" 25 #include "base/memory/weak_ptr.h" 26 #include "base/single_thread_task_runner.h" 27 #include "base/synchronization/waitable_event.h" 28 #include "base/time/time.h" 29 #include "base/values.h" 30 31 namespace remoting { 32 namespace policy_hack { 33 34 namespace { 35 36 const base::FilePath::CharType kPolicyDir[] = 37 // Always read the Chrome policies (even on Chromium) so that policy 38 // enforcement can't be bypassed by running Chromium. 39 FILE_PATH_LITERAL("/etc/opt/chrome/policies/managed"); 40 41 // Amount of time we wait for the files on disk to settle before trying to load 42 // them. This alleviates the problem of reading partially written files and 43 // makes it possible to batch quasi-simultaneous changes. 44 const int kSettleIntervalSeconds = 5; 45 46 } // namespace 47 48 class PolicyWatcherLinux : public PolicyWatcher { 49 public: 50 PolicyWatcherLinux(scoped_refptr<base::SingleThreadTaskRunner> task_runner, 51 const base::FilePath& config_dir) 52 : PolicyWatcher(task_runner), 53 config_dir_(config_dir), 54 weak_factory_(this) { 55 } 56 57 virtual ~PolicyWatcherLinux() {} 58 59 protected: 60 virtual void StartWatchingInternal() OVERRIDE { 61 DCHECK(OnPolicyWatcherThread()); 62 watcher_.reset(new base::FilePathWatcher()); 63 64 if (!config_dir_.empty() && 65 !watcher_->Watch( 66 config_dir_, false, 67 base::Bind(&PolicyWatcherLinux::OnFilePathChanged, 68 weak_factory_.GetWeakPtr()))) { 69 OnFilePathChanged(config_dir_, true); 70 } 71 72 // There might have been changes to the directory in the time between 73 // construction of the loader and initialization of the watcher. Call reload 74 // to detect if that is the case. 75 Reload(); 76 77 ScheduleFallbackReloadTask(); 78 } 79 80 virtual void StopWatchingInternal() OVERRIDE { 81 DCHECK(OnPolicyWatcherThread()); 82 83 // Stop watching for changes to files in the policies directory. 84 watcher_.reset(); 85 86 // Orphan any pending OnFilePathChanged tasks. 87 weak_factory_.InvalidateWeakPtrs(); 88 } 89 90 private: 91 void OnFilePathChanged(const base::FilePath& path, bool error) { 92 DCHECK(OnPolicyWatcherThread()); 93 94 if (!error) 95 Reload(); 96 else 97 LOG(ERROR) << "PolicyWatcherLinux on " << path.value() << " failed."; 98 } 99 100 base::Time GetLastModification() { 101 DCHECK(OnPolicyWatcherThread()); 102 base::Time last_modification = base::Time(); 103 base::File::Info file_info; 104 105 // If the path does not exist or points to a directory, it's safe to load. 106 if (!base::GetFileInfo(config_dir_, &file_info) || 107 !file_info.is_directory) { 108 return last_modification; 109 } 110 111 // Enumerate the files and find the most recent modification timestamp. 112 base::FileEnumerator file_enumerator(config_dir_, 113 false, 114 base::FileEnumerator::FILES); 115 for (base::FilePath config_file = file_enumerator.Next(); 116 !config_file.empty(); 117 config_file = file_enumerator.Next()) { 118 if (base::GetFileInfo(config_file, &file_info) && 119 !file_info.is_directory) { 120 last_modification = std::max(last_modification, 121 file_info.last_modified); 122 } 123 } 124 125 return last_modification; 126 } 127 128 // Returns NULL if the policy dictionary couldn't be read. 129 scoped_ptr<base::DictionaryValue> Load() { 130 DCHECK(OnPolicyWatcherThread()); 131 // Enumerate the files and sort them lexicographically. 132 std::set<base::FilePath> files; 133 base::FileEnumerator file_enumerator(config_dir_, false, 134 base::FileEnumerator::FILES); 135 for (base::FilePath config_file_path = file_enumerator.Next(); 136 !config_file_path.empty(); config_file_path = file_enumerator.Next()) 137 files.insert(config_file_path); 138 139 // Start with an empty dictionary and merge the files' contents. 140 scoped_ptr<base::DictionaryValue> policy(new base::DictionaryValue()); 141 for (std::set<base::FilePath>::iterator config_file_iter = files.begin(); 142 config_file_iter != files.end(); ++config_file_iter) { 143 JSONFileValueSerializer deserializer(*config_file_iter); 144 deserializer.set_allow_trailing_comma(true); 145 int error_code = 0; 146 std::string error_msg; 147 scoped_ptr<base::Value> value( 148 deserializer.Deserialize(&error_code, &error_msg)); 149 if (!value.get()) { 150 LOG(WARNING) << "Failed to read configuration file " 151 << config_file_iter->value() << ": " << error_msg; 152 return scoped_ptr<base::DictionaryValue>(); 153 } 154 if (!value->IsType(base::Value::TYPE_DICTIONARY)) { 155 LOG(WARNING) << "Expected JSON dictionary in configuration file " 156 << config_file_iter->value(); 157 return scoped_ptr<base::DictionaryValue>(); 158 } 159 policy->MergeDictionary(static_cast<base::DictionaryValue*>(value.get())); 160 } 161 162 return policy.Pass(); 163 } 164 165 virtual void Reload() OVERRIDE { 166 DCHECK(OnPolicyWatcherThread()); 167 // Check the directory time in order to see whether a reload is required. 168 base::TimeDelta delay; 169 base::Time now = base::Time::Now(); 170 if (!IsSafeToReloadPolicy(now, &delay)) { 171 ScheduleReloadTask(delay); 172 return; 173 } 174 175 // Check again in case the directory has changed while reading it. 176 if (!IsSafeToReloadPolicy(now, &delay)) { 177 ScheduleReloadTask(delay); 178 return; 179 } 180 181 // Load the policy definitions. 182 scoped_ptr<base::DictionaryValue> new_policy = Load(); 183 if (new_policy.get()) { 184 UpdatePolicies(new_policy.get()); 185 ScheduleFallbackReloadTask(); 186 } else { 187 // A failure to load policy definitions is probably temporary, so try 188 // again soon. 189 ScheduleReloadTask(base::TimeDelta::FromSeconds(kSettleIntervalSeconds)); 190 } 191 } 192 193 bool IsSafeToReloadPolicy(const base::Time& now, base::TimeDelta* delay) { 194 DCHECK(OnPolicyWatcherThread()); 195 DCHECK(delay); 196 const base::TimeDelta kSettleInterval = 197 base::TimeDelta::FromSeconds(kSettleIntervalSeconds); 198 199 base::Time last_modification = GetLastModification(); 200 if (last_modification.is_null()) 201 return true; 202 203 if (last_modification_file_.is_null()) 204 last_modification_file_ = last_modification; 205 206 // If there was a change since the last recorded modification, wait some 207 // more. 208 if (last_modification != last_modification_file_) { 209 last_modification_file_ = last_modification; 210 last_modification_clock_ = now; 211 *delay = kSettleInterval; 212 return false; 213 } 214 215 // Check whether the settle interval has elapsed. 216 base::TimeDelta age = now - last_modification_clock_; 217 if (age < kSettleInterval) { 218 *delay = kSettleInterval - age; 219 return false; 220 } 221 222 return true; 223 } 224 225 // Managed with a scoped_ptr rather than being declared as an inline member to 226 // decouple the watcher's life cycle from the PolicyWatcherLinux. This 227 // decoupling makes it possible to destroy the watcher before the loader's 228 // destructor is called (e.g. during Stop), since |watcher_| internally holds 229 // a reference to the loader and keeps it alive. 230 scoped_ptr<base::FilePathWatcher> watcher_; 231 232 // Records last known modification timestamp of |config_dir_|. 233 base::Time last_modification_file_; 234 235 // The wall clock time at which the last modification timestamp was 236 // recorded. It's better to not assume the file notification time and the 237 // wall clock times come from the same source, just in case there is some 238 // non-local filesystem involved. 239 base::Time last_modification_clock_; 240 241 const base::FilePath config_dir_; 242 243 // Allows us to cancel any inflight FileWatcher events or scheduled reloads. 244 base::WeakPtrFactory<PolicyWatcherLinux> weak_factory_; 245 }; 246 247 PolicyWatcher* PolicyWatcher::Create( 248 scoped_refptr<base::SingleThreadTaskRunner> task_runner) { 249 base::FilePath policy_dir(kPolicyDir); 250 return new PolicyWatcherLinux(task_runner, policy_dir); 251 } 252 253 } // namespace policy_hack 254 } // namespace remoting 255