1// Copyright 2014 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 "content/browser/time_zone_monitor.h"
6
7#include <stdlib.h>
8
9#include <vector>
10
11#include "base/basictypes.h"
12#include "base/bind.h"
13#include "base/files/file_path.h"
14#include "base/files/file_path_watcher.h"
15#include "base/memory/ref_counted.h"
16#include "base/stl_util.h"
17#include "content/public/browser/browser_thread.h"
18
19#if !defined(OS_CHROMEOS)
20
21namespace content {
22
23namespace {
24class TimeZoneMonitorLinuxImpl;
25}  // namespace
26
27class TimeZoneMonitorLinux : public TimeZoneMonitor {
28 public:
29  TimeZoneMonitorLinux();
30  virtual ~TimeZoneMonitorLinux();
31
32  void NotifyRenderersFromImpl() {
33    NotifyRenderers();
34  }
35
36 private:
37  scoped_refptr<TimeZoneMonitorLinuxImpl> impl_;
38
39  DISALLOW_COPY_AND_ASSIGN(TimeZoneMonitorLinux);
40};
41
42namespace {
43
44// FilePathWatcher needs to run on the FILE thread, but TimeZoneMonitor runs
45// on the UI thread. TimeZoneMonitorLinuxImpl is the bridge between these
46// threads.
47class TimeZoneMonitorLinuxImpl
48    : public base::RefCountedThreadSafe<TimeZoneMonitorLinuxImpl> {
49 public:
50  explicit TimeZoneMonitorLinuxImpl(TimeZoneMonitorLinux* owner)
51      : base::RefCountedThreadSafe<TimeZoneMonitorLinuxImpl>(),
52        file_path_watchers_(),
53        owner_(owner) {
54    DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
55    BrowserThread::PostTask(
56        BrowserThread::FILE,
57        FROM_HERE,
58        base::Bind(&TimeZoneMonitorLinuxImpl::StartWatchingOnFileThread, this));
59  }
60
61  void StopWatching() {
62    DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
63    owner_ = NULL;
64    BrowserThread::PostTask(
65        BrowserThread::FILE,
66        FROM_HERE,
67        base::Bind(&TimeZoneMonitorLinuxImpl::StopWatchingOnFileThread, this));
68  }
69
70 private:
71  friend class base::RefCountedThreadSafe<TimeZoneMonitorLinuxImpl>;
72
73  ~TimeZoneMonitorLinuxImpl() {
74    DCHECK(!owner_);
75    STLDeleteElements(&file_path_watchers_);
76  }
77
78  void StartWatchingOnFileThread() {
79    DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
80
81    // There is no true standard for where time zone information is actually
82    // stored. glibc uses /etc/localtime, uClibc uses /etc/TZ, and some older
83    // systems store the name of the time zone file within /usr/share/zoneinfo
84    // in /etc/timezone. Different libraries and custom builds may mean that
85    // still more paths are used. Just watch all three of these paths, because
86    // false positives are harmless, assuming the false positive rate is
87    // reasonable.
88    const char* kFilesToWatch[] = {
89      "/etc/localtime",
90      "/etc/timezone",
91      "/etc/TZ",
92    };
93
94    for (size_t index = 0; index < arraysize(kFilesToWatch); ++index) {
95      file_path_watchers_.push_back(new base::FilePathWatcher());
96      file_path_watchers_.back()->Watch(
97          base::FilePath(kFilesToWatch[index]),
98          false,
99          base::Bind(&TimeZoneMonitorLinuxImpl::OnTimeZoneFileChanged, this));
100    }
101  }
102
103  void StopWatchingOnFileThread() {
104    DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
105    STLDeleteElements(&file_path_watchers_);
106  }
107
108  void OnTimeZoneFileChanged(const base::FilePath& path, bool error) {
109    DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
110    BrowserThread::PostTask(
111        BrowserThread::UI,
112        FROM_HERE,
113        base::Bind(&TimeZoneMonitorLinuxImpl::OnTimeZoneFileChangedOnUIThread,
114                   this));
115  }
116
117  void OnTimeZoneFileChangedOnUIThread() {
118    DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
119    if (owner_) {
120      owner_->NotifyRenderersFromImpl();
121    }
122  }
123
124  std::vector<base::FilePathWatcher*> file_path_watchers_;
125  TimeZoneMonitorLinux* owner_;
126
127  DISALLOW_COPY_AND_ASSIGN(TimeZoneMonitorLinuxImpl);
128};
129
130}  // namespace
131
132TimeZoneMonitorLinux::TimeZoneMonitorLinux()
133    : TimeZoneMonitor(),
134      impl_() {
135  // If the TZ environment variable is set, its value specifies the time zone
136  // specification, and it's pointless to monitor any files in /etc for
137  // changes because such changes would have no effect on the TZ environment
138  // variable and thus the interpretation of the local time zone in the
139  // or renderer processes.
140  //
141  // The system-specific format for the TZ environment variable beginning with
142  // a colon is implemented by glibc as the path to a time zone data file, and
143  // it would be possible to monitor this file for changes if a TZ variable of
144  // this format was encountered, but this is not necessary: when loading a
145  // time zone specification in this way, glibc does not reload the file when
146  // it changes, so it's pointless to respond to a notification that it has
147  // changed.
148  if (!getenv("TZ")) {
149    impl_ = new TimeZoneMonitorLinuxImpl(this);
150  }
151}
152
153TimeZoneMonitorLinux::~TimeZoneMonitorLinux() {
154  if (impl_.get()) {
155    impl_->StopWatching();
156  }
157}
158
159// static
160scoped_ptr<TimeZoneMonitor> TimeZoneMonitor::Create() {
161  return scoped_ptr<TimeZoneMonitor>(new TimeZoneMonitorLinux());
162}
163
164}  // namespace content
165
166#endif  // !OS_CHROMEOS
167