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