1// Copyright (c) 2011 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_loader.h"
6
7#include "base/compiler_specific.h"
8#include "content/browser/browser_thread.h"
9
10using ::base::files::FilePathWatcher;
11
12namespace {
13
14// Amount of time we wait for the files on disk to settle before trying to load
15// them. This alleviates the problem of reading partially written files and
16// makes it possible to batch quasi-simultaneous changes.
17const int kSettleIntervalSeconds = 5;
18
19// The time interval for rechecking policy. This is our fallback in case the
20// delegate never reports a change to the ReloadObserver.
21const int kReloadIntervalMinutes = 15;
22
23}  // namespace
24
25namespace policy {
26
27FileBasedPolicyLoader::FileBasedPolicyLoader(
28    FileBasedPolicyProvider::ProviderDelegate* provider_delegate)
29    : AsynchronousPolicyLoader(provider_delegate,
30                               kReloadIntervalMinutes),
31      config_file_path_(provider_delegate->config_file_path()),
32      settle_interval_(base::TimeDelta::FromSeconds(kSettleIntervalSeconds)) {
33}
34
35FileBasedPolicyLoader::~FileBasedPolicyLoader() {}
36
37class FileBasedPolicyWatcherDelegate : public FilePathWatcher::Delegate {
38 public:
39  explicit FileBasedPolicyWatcherDelegate(
40      scoped_refptr<FileBasedPolicyLoader> loader)
41      : loader_(loader) {}
42  virtual ~FileBasedPolicyWatcherDelegate() {}
43
44  // FilePathWatcher::Delegate implementation:
45  virtual void OnFilePathChanged(const FilePath& path) OVERRIDE {
46    loader_->OnFilePathChanged(path);
47  }
48
49  virtual void OnFilePathError(const FilePath& path) OVERRIDE {
50    loader_->OnFilePathError(path);
51  }
52
53 private:
54  scoped_refptr<FileBasedPolicyLoader> loader_;
55  DISALLOW_COPY_AND_ASSIGN(FileBasedPolicyWatcherDelegate);
56};
57
58void FileBasedPolicyLoader::OnFilePathChanged(
59    const FilePath& path) {
60  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
61  Reload();
62}
63
64void FileBasedPolicyLoader::OnFilePathError(const FilePath& path) {
65  LOG(ERROR) << "FilePathWatcher on " << path.value()
66             << " failed.";
67}
68
69void FileBasedPolicyLoader::Reload() {
70  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
71
72  if (!delegate())
73    return;
74
75  // Check the directory time in order to see whether a reload is required.
76  base::TimeDelta delay;
77  base::Time now = base::Time::Now();
78  if (!IsSafeToReloadPolicy(now, &delay)) {
79    ScheduleReloadTask(delay);
80    return;
81  }
82
83  // Load the policy definitions.
84  scoped_ptr<DictionaryValue> new_policy(delegate()->Load());
85
86  // Check again in case the directory has changed while reading it.
87  if (!IsSafeToReloadPolicy(now, &delay)) {
88    ScheduleReloadTask(delay);
89    return;
90  }
91
92  PostUpdatePolicyTask(new_policy.release());
93
94  ScheduleFallbackReloadTask();
95}
96
97void FileBasedPolicyLoader::InitOnFileThread() {
98  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
99  watcher_.reset(new FilePathWatcher);
100  const FilePath& path = config_file_path();
101  if (!path.empty() &&
102      !watcher_->Watch(path, new FileBasedPolicyWatcherDelegate(this))) {
103    OnFilePathError(path);
104  }
105
106  // There might have been changes to the directory in the time between
107  // construction of the loader and initialization of the watcher. Call reload
108  // to detect if that is the case.
109  Reload();
110
111  ScheduleFallbackReloadTask();
112}
113
114void FileBasedPolicyLoader::StopOnFileThread() {
115  watcher_.reset();
116  AsynchronousPolicyLoader::StopOnFileThread();
117}
118
119bool FileBasedPolicyLoader::IsSafeToReloadPolicy(
120    const base::Time& now,
121    base::TimeDelta* delay) {
122  DCHECK(delay);
123
124  // A null modification time indicates there's no data.
125  FileBasedPolicyProvider::ProviderDelegate* provider_delegate =
126      static_cast<FileBasedPolicyProvider::ProviderDelegate*>(delegate());
127  base::Time last_modification(provider_delegate->GetLastModification());
128  if (last_modification.is_null())
129    return true;
130
131  // If there was a change since the last recorded modification, wait some more.
132  if (last_modification != last_modification_file_) {
133    last_modification_file_ = last_modification;
134    last_modification_clock_ = now;
135    *delay = settle_interval_;
136    return false;
137  }
138
139  // Check whether the settle interval has elapsed.
140  base::TimeDelta age = now - last_modification_clock_;
141  if (age < settle_interval_) {
142    *delay = settle_interval_ - age;
143    return false;
144  }
145
146  return true;
147}
148
149}  // namespace policy
150