1 // Copyright 2020 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/chromeos/crosapi/browser_manager.h"
6 
7 #include <fcntl.h>
8 #include <unistd.h>
9 
10 #include <memory>
11 #include <string>
12 #include <utility>
13 #include <vector>
14 
15 #include "base/bind.h"
16 #include "base/command_line.h"
17 #include "base/environment.h"
18 #include "base/files/file_path.h"
19 #include "base/files/file_util.h"
20 #include "base/logging.h"
21 #include "base/metrics/histogram_functions.h"
22 #include "base/metrics/user_metrics.h"
23 #include "base/metrics/user_metrics_action.h"
24 #include "base/posix/eintr_wrapper.h"
25 #include "base/process/launch.h"
26 #include "base/process/process_handle.h"
27 #include "base/strings/string_number_conversions.h"
28 #include "base/strings/string_split.h"
29 #include "base/system/sys_info.h"
30 #include "base/task/task_traits.h"
31 #include "base/task/thread_pool.h"
32 #include "chrome/browser/chromeos/crosapi/ash_chrome_service_impl.h"
33 #include "chrome/browser/chromeos/crosapi/browser_loader.h"
34 #include "chrome/browser/chromeos/crosapi/browser_util.h"
35 #include "chrome/browser/chromeos/crosapi/environment_provider.h"
36 #include "chrome/browser/chromeos/crosapi/test_mojo_connection_manager.h"
37 #include "chrome/browser/component_updater/cros_component_manager.h"
38 #include "chrome/browser/profiles/profile_manager.h"
39 #include "chrome/browser/ui/ash/launcher/chrome_launcher_controller.h"
40 #include "chromeos/constants/chromeos_features.h"
41 #include "chromeos/constants/chromeos_switches.h"
42 #include "components/prefs/pref_service.h"
43 #include "components/session_manager/core/session_manager.h"
44 #include "google_apis/google_api_keys.h"
45 #include "mojo/public/cpp/bindings/pending_remote.h"
46 #include "mojo/public/cpp/platform/platform_channel.h"
47 
48 // TODO(crbug.com/1101667): Currently, this source has log spamming
49 // by LOG(WARNING) for non critical errors to make it easy
50 // to debug and develop. Get rid of the log spamming
51 // when it gets stable enough.
52 
53 namespace crosapi {
54 
55 namespace {
56 
57 // Pointer to the global instance of BrowserManager.
58 BrowserManager* g_instance = nullptr;
59 
60 // The min version of LacrosChromeService mojo interface that supports
61 // GetFeedbackData API.
62 constexpr uint32_t kGetFeedbackDataMinVersion = 6;
63 // The min version of LacrosChromeService mojo interface that supports
64 // GetHistograms API.
65 constexpr uint32_t kGetHistogramsMinVersion = 7;
66 // The min version of LacrosChromeService mojo interface that supports
67 // GetActiveTabUrl API.
68 constexpr uint32_t kGetActiveTabUrlMinVersion = 8;
69 
LacrosLogPath()70 base::FilePath LacrosLogPath() {
71   return browser_util::GetUserDataDir().Append("lacros.log");
72 }
73 
CreateLogFile()74 base::ScopedFD CreateLogFile() {
75   base::FilePath::StringType log_path = LacrosLogPath().value();
76 
77   // Delete old log file if exists.
78   if (unlink(log_path.c_str()) != 0) {
79     if (errno != ENOENT) {
80       // unlink() failed for reason other than the file not existing.
81       PLOG(ERROR) << "Failed to unlink the log file " << log_path;
82       return base::ScopedFD();
83     }
84 
85     // If log file does not exist, most likely the user directory does not exist
86     // either. So create it here.
87     base::File::Error error;
88     if (!base::CreateDirectoryAndGetError(browser_util::GetUserDataDir(),
89                                           &error)) {
90       LOG(ERROR) << "Failed to make directory "
91                  << browser_util::GetUserDataDir()
92                  << base::File::ErrorToString(error);
93       return base::ScopedFD();
94     }
95   }
96 
97   int fd =
98       HANDLE_EINTR(open(log_path.c_str(), O_WRONLY | O_CREAT | O_EXCL, 0644));
99 
100   if (fd < 0) {
101     PLOG(ERROR) << "Failed to get file descriptor for " << log_path;
102     return base::ScopedFD();
103   }
104 
105   return base::ScopedFD(fd);
106 }
107 
GetXdgRuntimeDir()108 std::string GetXdgRuntimeDir() {
109   // If ash-chrome was given an environment variable, use it.
110   std::unique_ptr<base::Environment> env = base::Environment::Create();
111   std::string xdg_runtime_dir;
112   if (env->GetVar("XDG_RUNTIME_DIR", &xdg_runtime_dir))
113     return xdg_runtime_dir;
114 
115   // Otherwise provide the default for Chrome OS devices.
116   return "/run/chrome";
117 }
118 
TerminateLacrosChrome(base::Process process)119 void TerminateLacrosChrome(base::Process process) {
120   // Here, lacros-chrome process may crashed, or be in the shutdown procedure.
121   // Give some amount of time for the collection. In most cases,
122   // this wait captures the process termination.
123   constexpr base::TimeDelta kGracefulShutdownTimeout =
124       base::TimeDelta::FromSeconds(5);
125   if (process.WaitForExitWithTimeout(kGracefulShutdownTimeout, nullptr))
126     return;
127 
128   // Here, the process is not yet terminated.
129   // This happens if some critical error happens on the mojo connection,
130   // while both ash-chrome and lacros-chrome are still alive.
131   // Terminate the lacros-chrome.
132   bool success = process.Terminate(/*exit_code=*/0, /*wait=*/true);
133   LOG_IF(ERROR, !success) << "Failed to terminate the lacros-chrome.";
134 }
135 
SetLaunchOnLoginPref(bool launch_on_login)136 void SetLaunchOnLoginPref(bool launch_on_login) {
137   ProfileManager::GetPrimaryUserProfile()->GetPrefs()->SetBoolean(
138       browser_util::kLaunchOnLoginPref, launch_on_login);
139 }
140 
GetLaunchOnLoginPref()141 bool GetLaunchOnLoginPref() {
142   return ProfileManager::GetPrimaryUserProfile()->GetPrefs()->GetBoolean(
143       browser_util::kLaunchOnLoginPref);
144 }
145 
146 }  // namespace
147 
148 // static
Get()149 BrowserManager* BrowserManager::Get() {
150   return g_instance;
151 }
152 
BrowserManager(scoped_refptr<component_updater::CrOSComponentManager> manager)153 BrowserManager::BrowserManager(
154     scoped_refptr<component_updater::CrOSComponentManager> manager)
155     : component_manager_(manager),
156       environment_provider_(std::make_unique<EnvironmentProvider>()) {
157   DCHECK(!g_instance);
158   g_instance = this;
159 
160   // Wait to query the flag until the user has entered the session. Enterprise
161   // devices restart Chrome during login to apply flags. We don't want to run
162   // the flag-off cleanup logic until we know we have the final flag state.
163   if (session_manager::SessionManager::Get())
164     session_manager::SessionManager::Get()->AddObserver(this);
165 
166   std::string socket_path =
167       base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
168           chromeos::switches::kLacrosMojoSocketForTesting);
169   if (!socket_path.empty()) {
170     test_mojo_connection_manager_ =
171         std::make_unique<crosapi::TestMojoConnectionManager>(
172             base::FilePath(socket_path));
173   }
174 }
175 
~BrowserManager()176 BrowserManager::~BrowserManager() {
177   // Unregister, just in case the manager is destroyed before
178   // OnUserSessionStarted() is called.
179   if (session_manager::SessionManager::Get())
180     session_manager::SessionManager::Get()->RemoveObserver(this);
181 
182   // Try to kill the lacros-chrome binary.
183   if (lacros_process_.IsValid())
184     lacros_process_.Terminate(/*ignored=*/0, /*wait=*/false);
185 
186   DCHECK_EQ(g_instance, this);
187   g_instance = nullptr;
188 }
189 
IsReady() const190 bool BrowserManager::IsReady() const {
191   return state_ != State::NOT_INITIALIZED && state_ != State::LOADING &&
192          state_ != State::UNAVAILABLE;
193 }
194 
IsRunning() const195 bool BrowserManager::IsRunning() const {
196   return state_ == State::RUNNING;
197 }
198 
SetLoadCompleteCallback(LoadCompleteCallback callback)199 void BrowserManager::SetLoadCompleteCallback(LoadCompleteCallback callback) {
200   load_complete_callback_ = std::move(callback);
201 }
202 
NewWindow()203 void BrowserManager::NewWindow() {
204   if (!browser_util::IsLacrosAllowed())
205     return;
206 
207   if (!IsReady()) {
208     LOG(WARNING) << "lacros component image not yet available";
209     return;
210   }
211   DCHECK(!lacros_path_.empty());
212 
213   if (state_ == State::TERMINATING) {
214     LOG(WARNING) << "lacros-chrome is terminating, so cannot start now";
215     return;
216   }
217 
218   if (state_ == State::CREATING_LOG_FILE) {
219     LOG(WARNING) << "lacros-chrome is in the process of launching";
220     return;
221   }
222 
223   if (state_ == State::STOPPED) {
224     // If lacros-chrome is not running, launch it.
225     Start();
226     return;
227   }
228 
229   DCHECK(lacros_chrome_service_.is_connected());
230   lacros_chrome_service_->NewWindow(base::DoNothing());
231 }
232 
GetFeedbackDataSupported() const233 bool BrowserManager::GetFeedbackDataSupported() const {
234   return lacros_chrome_service_version_ >= kGetFeedbackDataMinVersion;
235 }
236 
GetFeedbackData(GetFeedbackDataCallback callback)237 void BrowserManager::GetFeedbackData(GetFeedbackDataCallback callback) {
238   DCHECK(lacros_chrome_service_.is_connected());
239   DCHECK(GetFeedbackDataSupported());
240   lacros_chrome_service_->GetFeedbackData(std::move(callback));
241 }
242 
GetHistogramsSupported() const243 bool BrowserManager::GetHistogramsSupported() const {
244   return lacros_chrome_service_version_ >= kGetHistogramsMinVersion;
245 }
246 
GetHistograms(GetHistogramsCallback callback)247 void BrowserManager::GetHistograms(GetHistogramsCallback callback) {
248   DCHECK(lacros_chrome_service_.is_connected());
249   DCHECK(GetHistogramsSupported());
250   lacros_chrome_service_->GetHistograms(std::move(callback));
251 }
252 
GetActiveTabUrlSupported() const253 bool BrowserManager::GetActiveTabUrlSupported() const {
254   return lacros_chrome_service_version_ >= kGetActiveTabUrlMinVersion;
255 }
256 
GetActiveTabUrl(GetActiveTabUrlCallback callback)257 void BrowserManager::GetActiveTabUrl(GetActiveTabUrlCallback callback) {
258   DCHECK(lacros_chrome_service_.is_connected());
259   DCHECK(GetActiveTabUrlSupported());
260   lacros_chrome_service_->GetActiveTabUrl(std::move(callback));
261 }
262 
AddObserver(BrowserManagerObserver * observer)263 void BrowserManager::AddObserver(BrowserManagerObserver* observer) {
264   observers_.AddObserver(observer);
265 }
266 
RemoveObserver(BrowserManagerObserver * observer)267 void BrowserManager::RemoveObserver(BrowserManagerObserver* observer) {
268   observers_.RemoveObserver(observer);
269 }
270 
Start()271 void BrowserManager::Start() {
272   DCHECK_EQ(state_, State::STOPPED);
273   DCHECK(!lacros_path_.empty());
274   // Ensure we're not trying to open a window before the shelf is initialized.
275   DCHECK(ChromeLauncherController::instance());
276 
277   state_ = State::CREATING_LOG_FILE;
278 
279   base::ThreadPool::PostTaskAndReplyWithResult(
280       FROM_HERE, {base::MayBlock()}, base::BindOnce(&CreateLogFile),
281       base::BindOnce(&BrowserManager::StartWithLogFile,
282                      weak_factory_.GetWeakPtr()));
283 }
284 
StartWithLogFile(base::ScopedFD logfd)285 void BrowserManager::StartWithLogFile(base::ScopedFD logfd) {
286   DCHECK_EQ(state_, State::CREATING_LOG_FILE);
287 
288   std::string chrome_path = lacros_path_.MaybeAsASCII() + "/chrome";
289   LOG(WARNING) << "Launching lacros-chrome at " << chrome_path;
290 
291   base::LaunchOptions options;
292   options.environment["EGL_PLATFORM"] = "surfaceless";
293   options.environment["XDG_RUNTIME_DIR"] = GetXdgRuntimeDir();
294 
295   std::string api_key;
296   if (google_apis::HasAPIKeyConfigured())
297     api_key = google_apis::GetAPIKey();
298   else
299     api_key = google_apis::GetNonStableAPIKey();
300   options.environment["GOOGLE_API_KEY"] = api_key;
301   options.environment["GOOGLE_DEFAULT_CLIENT_ID"] =
302       google_apis::GetOAuth2ClientID(google_apis::CLIENT_MAIN);
303   options.environment["GOOGLE_DEFAULT_CLIENT_SECRET"] =
304       google_apis::GetOAuth2ClientSecret(google_apis::CLIENT_MAIN);
305 
306   options.kill_on_parent_death = true;
307 
308   // Paths are UTF-8 safe on Chrome OS.
309   std::string user_data_dir = browser_util::GetUserDataDir().AsUTF8Unsafe();
310   std::string crash_dir =
311       browser_util::GetUserDataDir().Append("crash_dumps").AsUTF8Unsafe();
312 
313   // Static configuration should be enabled from Lacros rather than Ash. This
314   // vector should only be used for dynamic configuration.
315   // TODO(https://crbug.com/1145713): Remove existing static configuration.
316   std::vector<std::string> argv = {chrome_path,
317                                    "--ozone-platform=wayland",
318                                    "--user-data-dir=" + user_data_dir,
319                                    "--enable-gpu-rasterization",
320                                    "--enable-oop-rasterization",
321                                    "--lang=en-US",
322                                    "--enable-crashpad",
323                                    "--enable-webgl-image-chromium",
324                                    "--breakpad-dump-location=" + crash_dir};
325 
326   // CrAS is the default audio server in Chrome OS.
327   if (base::SysInfo::IsRunningOnChromeOS())
328     argv.push_back("--use-cras");
329 
330   std::string additional_flags =
331       base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
332           chromeos::switches::kLacrosChromeAdditionalArgs);
333   std::vector<std::string> delimited_flags = base::SplitStringUsingSubstr(
334       additional_flags, "####", base::TRIM_WHITESPACE,
335       base::SPLIT_WANT_NONEMPTY);
336   for (const std::string& flag : delimited_flags) {
337     argv.push_back(flag);
338   }
339 
340   // If logfd is valid, enable logging and redirect stdout/stderr to logfd.
341   if (logfd.is_valid()) {
342     // The next flag will make chrome log only via stderr. See
343     // DetermineLoggingDestination in logging_chrome.cc.
344     argv.push_back("--enable-logging=stderr");
345 
346     // These options will assign stdout/stderr fds to logfd in the fd table of
347     // the new process.
348     options.fds_to_remap.push_back(std::make_pair(logfd.get(), STDOUT_FILENO));
349     options.fds_to_remap.push_back(std::make_pair(logfd.get(), STDERR_FILENO));
350   }
351 
352   // Set up Mojo channel.
353   base::CommandLine command_line(argv);
354   LOG(WARNING) << "Launching lacros with command: "
355                << command_line.GetCommandLineString();
356   mojo::PlatformChannel channel;
357   channel.PrepareToPassRemoteEndpoint(&options, &command_line);
358 
359   // TODO(crbug.com/1124490): Support multiple mojo connections from lacros.
360   lacros_chrome_service_ = browser_util::SendMojoInvitationToLacrosChrome(
361       environment_provider_.get(), channel.TakeLocalEndpoint(),
362       base::BindOnce(&BrowserManager::OnMojoDisconnected,
363                      weak_factory_.GetWeakPtr()),
364       base::BindOnce(&BrowserManager::OnAshChromeServiceReceiverReceived,
365                      weak_factory_.GetWeakPtr()));
366 
367   lacros_chrome_service_.QueryVersion(
368       base::BindOnce(&BrowserManager::OnLacrosChromeServiceVersionReady,
369                      weak_factory_.GetWeakPtr()));
370 
371   // Create the lacros-chrome subprocess.
372   base::RecordAction(base::UserMetricsAction("Lacros.Launch"));
373   lacros_launch_time_ = base::TimeTicks::Now();
374   // If lacros_process_ already exists, because it does not call waitpid(2),
375   // the process will never be collected.
376   lacros_process_ = base::LaunchProcess(command_line, options);
377   if (!lacros_process_.IsValid()) {
378     LOG(ERROR) << "Failed to launch lacros-chrome";
379     state_ = State::STOPPED;
380     return;
381   }
382   state_ = State::STARTING;
383   LOG(WARNING) << "Launched lacros-chrome with pid " << lacros_process_.Pid();
384   channel.RemoteProcessLaunchAttempted();
385 }
386 
OnAshChromeServiceReceiverReceived(mojo::PendingReceiver<crosapi::mojom::AshChromeService> pending_receiver)387 void BrowserManager::OnAshChromeServiceReceiverReceived(
388     mojo::PendingReceiver<crosapi::mojom::AshChromeService> pending_receiver) {
389   DCHECK_EQ(state_, State::STARTING);
390   ash_chrome_service_ =
391       std::make_unique<AshChromeServiceImpl>(std::move(pending_receiver));
392   state_ = State::RUNNING;
393   base::UmaHistogramMediumTimes("ChromeOS.Lacros.StartTime",
394                                 base::TimeTicks::Now() - lacros_launch_time_);
395   // Set the launch-on-login pref every time lacros-chrome successfully starts,
396   // instead of once during ash-chrome shutdown, so we have the right value
397   // even if ash-chrome crashes.
398   SetLaunchOnLoginPref(true);
399   LOG(WARNING) << "Connection to lacros-chrome is established.";
400 }
401 
OnMojoDisconnected()402 void BrowserManager::OnMojoDisconnected() {
403   DCHECK(state_ == State::STARTING || state_ == State::RUNNING);
404   LOG(WARNING)
405       << "Mojo to lacros-chrome is disconnected. Terminating lacros-chrome";
406   state_ = State::TERMINATING;
407 
408   lacros_chrome_service_.reset();
409   ash_chrome_service_ = nullptr;
410   base::ThreadPool::PostTaskAndReply(
411       FROM_HERE, {base::WithBaseSyncPrimitives()},
412       base::BindOnce(&TerminateLacrosChrome, std::move(lacros_process_)),
413       base::BindOnce(&BrowserManager::OnLacrosChromeTerminated,
414                      weak_factory_.GetWeakPtr()));
415 
416   NotifyMojoDisconnected();
417 }
418 
OnLacrosChromeTerminated()419 void BrowserManager::OnLacrosChromeTerminated() {
420   DCHECK_EQ(state_, State::TERMINATING);
421   LOG(WARNING) << "Lacros-chrome is terminated";
422   state_ = State::STOPPED;
423   // TODO(https://crbug.com/1109366): Restart lacros-chrome if it exits
424   // abnormally (e.g. crashes). For now, assume the user meant to close it.
425   SetLaunchOnLoginPref(false);
426 }
427 
OnSessionStateChanged()428 void BrowserManager::OnSessionStateChanged() {
429   DCHECK_EQ(state_, State::NOT_INITIALIZED);
430 
431   // Wait for session to become active.
432   auto* session_manager = session_manager::SessionManager::Get();
433   if (session_manager->session_state() != session_manager::SessionState::ACTIVE)
434     return;
435 
436   // Ensure this isn't run multiple times.
437   session_manager::SessionManager::Get()->RemoveObserver(this);
438 
439   // Must be checked after user session start because it depends on user type.
440   if (!browser_util::IsLacrosAllowed())
441     return;
442 
443   // May be null in tests.
444   if (!component_manager_)
445     return;
446 
447   DCHECK(!browser_loader_);
448   browser_loader_ = std::make_unique<BrowserLoader>(component_manager_);
449   if (chromeos::features::IsLacrosSupportEnabled()) {
450     state_ = State::LOADING;
451     browser_loader_->Load(base::BindOnce(&BrowserManager::OnLoadComplete,
452                                          weak_factory_.GetWeakPtr()));
453   } else {
454     state_ = State::UNAVAILABLE;
455     browser_loader_->Unload();
456   }
457 }
458 
OnLoadComplete(const base::FilePath & path)459 void BrowserManager::OnLoadComplete(const base::FilePath& path) {
460   DCHECK_EQ(state_, State::LOADING);
461 
462   lacros_path_ = path;
463   state_ = path.empty() ? State::UNAVAILABLE : State::STOPPED;
464   if (load_complete_callback_) {
465     const bool success = !path.empty();
466     std::move(load_complete_callback_).Run(success);
467   }
468 
469   if (state_ == State::STOPPED && GetLaunchOnLoginPref()) {
470     Start();
471   }
472 }
473 
NotifyMojoDisconnected()474 void BrowserManager::NotifyMojoDisconnected() {
475   for (auto& observer : observers_)
476     observer.OnMojoDisconnected();
477 }
478 
OnLacrosChromeServiceVersionReady(uint32_t version)479 void BrowserManager::OnLacrosChromeServiceVersionReady(uint32_t version) {
480   lacros_chrome_service_version_ = version;
481 }
482 
483 }  // namespace crosapi
484