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 "services/device/time_zone_monitor/time_zone_monitor.h"
6
7 #include <stddef.h>
8 #include <stdlib.h>
9
10 #include <memory>
11 #include <vector>
12
13 #include "base/bind.h"
14 #include "base/files/file_path.h"
15 #include "base/files/file_path_watcher.h"
16 #include "base/memory/ref_counted.h"
17 #include "base/sequenced_task_runner.h"
18 #include "base/stl_util.h"
19 #include "base/threading/scoped_blocking_call.h"
20 #include "base/threading/thread_task_runner_handle.h"
21 #include "build/build_config.h"
22 #include "build/chromecast_buildflags.h"
23 #include "third_party/icu/source/i18n/unicode/timezone.h"
24
25 namespace device {
26
27 namespace {
28 class TimeZoneMonitorLinuxImpl;
29 } // namespace
30
31 class TimeZoneMonitorLinux : public TimeZoneMonitor {
32 public:
33 TimeZoneMonitorLinux(
34 scoped_refptr<base::SequencedTaskRunner> file_task_runner);
35 ~TimeZoneMonitorLinux() override;
36
NotifyClientsFromImpl()37 void NotifyClientsFromImpl() {
38 #if BUILDFLAG(IS_CHROMECAST)
39 // On Chromecast, ICU's default time zone is already set to a new zone. No
40 // need to redetect it with detectHostTimeZone() or to update ICU.
41 // See http://b/112498903 and http://b/113344065.
42 std::unique_ptr<icu::TimeZone> new_zone(icu::TimeZone::createDefault());
43 NotifyClients(GetTimeZoneId(*new_zone));
44 #else
45 std::unique_ptr<icu::TimeZone> new_zone(DetectHostTimeZoneFromIcu());
46
47 // We get here multiple times on Linux per a single tz change, but
48 // want to update the ICU default zone and notify renderer only once.
49 // The timezone must have previously been populated. See InitializeICU().
50 std::unique_ptr<icu::TimeZone> current_zone(icu::TimeZone::createDefault());
51 if (*current_zone == *new_zone) {
52 VLOG(1) << "timezone already updated";
53 return;
54 }
55
56 UpdateIcuAndNotifyClients(std::move(new_zone));
57 #endif // defined(IS_CHROMECAST)
58 }
59
60 private:
61 scoped_refptr<TimeZoneMonitorLinuxImpl> impl_;
62
63 DISALLOW_COPY_AND_ASSIGN(TimeZoneMonitorLinux);
64 };
65
66 namespace {
67
68 // FilePathWatcher needs to run on the FILE thread, but TimeZoneMonitor runs
69 // on the UI thread. TimeZoneMonitorLinuxImpl is the bridge between these
70 // threads.
71 class TimeZoneMonitorLinuxImpl
72 : public base::RefCountedThreadSafe<TimeZoneMonitorLinuxImpl> {
73 public:
Create(TimeZoneMonitorLinux * owner,scoped_refptr<base::SequencedTaskRunner> file_task_runner)74 static scoped_refptr<TimeZoneMonitorLinuxImpl> Create(
75 TimeZoneMonitorLinux* owner,
76 scoped_refptr<base::SequencedTaskRunner> file_task_runner) {
77 auto impl = base::WrapRefCounted(
78 new TimeZoneMonitorLinuxImpl(owner, file_task_runner));
79 file_task_runner->PostTask(
80 FROM_HERE,
81 base::BindOnce(&TimeZoneMonitorLinuxImpl::StartWatchingOnFileThread,
82 impl));
83 return impl;
84 }
85
StopWatching()86 void StopWatching() {
87 DCHECK(main_task_runner_->RunsTasksInCurrentSequence());
88 owner_ = nullptr;
89 file_task_runner_->PostTask(
90 FROM_HERE,
91 base::BindOnce(&TimeZoneMonitorLinuxImpl::StopWatchingOnFileThread,
92 base::RetainedRef(this)));
93 }
94
95 private:
96 friend class base::RefCountedThreadSafe<TimeZoneMonitorLinuxImpl>;
97
TimeZoneMonitorLinuxImpl(TimeZoneMonitorLinux * owner,scoped_refptr<base::SequencedTaskRunner> file_task_runner)98 TimeZoneMonitorLinuxImpl(
99 TimeZoneMonitorLinux* owner,
100 scoped_refptr<base::SequencedTaskRunner> file_task_runner)
101 : base::RefCountedThreadSafe<TimeZoneMonitorLinuxImpl>(),
102 main_task_runner_(base::ThreadTaskRunnerHandle::Get()),
103 file_task_runner_(file_task_runner),
104 owner_(owner) {
105 DCHECK(main_task_runner_->RunsTasksInCurrentSequence());
106 }
107
~TimeZoneMonitorLinuxImpl()108 ~TimeZoneMonitorLinuxImpl() { DCHECK(!owner_); }
109
StartWatchingOnFileThread()110 void StartWatchingOnFileThread() {
111 DCHECK(file_task_runner_->RunsTasksInCurrentSequence());
112
113 auto callback =
114 base::BindRepeating(&TimeZoneMonitorLinuxImpl::OnTimeZoneFileChanged,
115 base::RetainedRef(this));
116
117 base::ScopedBlockingCall scoped_blocking_call(
118 FROM_HERE, base::BlockingType::MAY_BLOCK);
119
120 // There is no true standard for where time zone information is actually
121 // stored. glibc uses /etc/localtime, uClibc uses /etc/TZ, and some older
122 // systems store the name of the time zone file within /usr/share/zoneinfo
123 // in /etc/timezone. Different libraries and custom builds may mean that
124 // still more paths are used. Just watch all three of these paths, because
125 // false positives are harmless, assuming the false positive rate is
126 // reasonable.
127 const char* const kFilesToWatch[] = {
128 #if defined(OS_BSD)
129 "/etc/localtime",
130 #else
131 "/etc/localtime", "/etc/timezone", "/etc/TZ",
132 #endif
133 };
134 for (size_t index = 0; index < base::size(kFilesToWatch); ++index) {
135 file_path_watchers_.push_back(std::make_unique<base::FilePathWatcher>());
136 file_path_watchers_.back()->Watch(base::FilePath(kFilesToWatch[index]),
137 false, callback);
138 }
139 }
140
StopWatchingOnFileThread()141 void StopWatchingOnFileThread() {
142 DCHECK(file_task_runner_->RunsTasksInCurrentSequence());
143 file_path_watchers_.clear();
144 }
145
OnTimeZoneFileChanged(const base::FilePath & path,bool error)146 void OnTimeZoneFileChanged(const base::FilePath& path, bool error) {
147 DCHECK(file_task_runner_->RunsTasksInCurrentSequence());
148 main_task_runner_->PostTask(
149 FROM_HERE,
150 base::BindOnce(
151 &TimeZoneMonitorLinuxImpl::OnTimeZoneFileChangedOnUIThread,
152 base::RetainedRef(this)));
153 }
154
OnTimeZoneFileChangedOnUIThread()155 void OnTimeZoneFileChangedOnUIThread() {
156 DCHECK(main_task_runner_->RunsTasksInCurrentSequence());
157 if (owner_) {
158 owner_->NotifyClientsFromImpl();
159 }
160 }
161
162 std::vector<std::unique_ptr<base::FilePathWatcher>> file_path_watchers_;
163
164 scoped_refptr<base::SequencedTaskRunner> main_task_runner_;
165 scoped_refptr<base::SequencedTaskRunner> file_task_runner_;
166 TimeZoneMonitorLinux* owner_;
167
168 DISALLOW_COPY_AND_ASSIGN(TimeZoneMonitorLinuxImpl);
169 };
170
171 } // namespace
172
TimeZoneMonitorLinux(scoped_refptr<base::SequencedTaskRunner> file_task_runner)173 TimeZoneMonitorLinux::TimeZoneMonitorLinux(
174 scoped_refptr<base::SequencedTaskRunner> file_task_runner)
175 : TimeZoneMonitor(), impl_() {
176 // If the TZ environment variable is set, its value specifies the time zone
177 // specification, and it's pointless to monitor any files in /etc for
178 // changes because such changes would have no effect on the TZ environment
179 // variable and thus the interpretation of the local time zone in the
180 // or renderer processes.
181 //
182 // The system-specific format for the TZ environment variable beginning with
183 // a colon is implemented by glibc as the path to a time zone data file, and
184 // it would be possible to monitor this file for changes if a TZ variable of
185 // this format was encountered, but this is not necessary: when loading a
186 // time zone specification in this way, glibc does not reload the file when
187 // it changes, so it's pointless to respond to a notification that it has
188 // changed.
189 if (!getenv("TZ")) {
190 impl_ = TimeZoneMonitorLinuxImpl::Create(this, file_task_runner);
191 }
192 }
193
~TimeZoneMonitorLinux()194 TimeZoneMonitorLinux::~TimeZoneMonitorLinux() {
195 if (impl_.get()) {
196 impl_->StopWatching();
197 }
198 }
199
200 // static
Create(scoped_refptr<base::SequencedTaskRunner> file_task_runner)201 std::unique_ptr<TimeZoneMonitor> TimeZoneMonitor::Create(
202 scoped_refptr<base::SequencedTaskRunner> file_task_runner) {
203 return std::unique_ptr<TimeZoneMonitor>(
204 new TimeZoneMonitorLinux(file_task_runner));
205 }
206
207 } // namespace device
208