1 // Copyright 2018 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/crostini/crostini_manager.h"
6
7 #include <algorithm>
8 #include <string>
9 #include <vector>
10
11 #include "base/barrier_closure.h"
12 #include "base/bind.h"
13 #include "base/callback_helpers.h"
14 #include "base/compiler_specific.h"
15 #include "base/feature_list.h"
16 #include "base/files/file_util.h"
17 #include "base/metrics/histogram_functions.h"
18 #include "base/no_destructor.h"
19 #include "base/strings/string_split.h"
20 #include "base/strings/stringprintf.h"
21 #include "base/system/sys_info.h"
22 #include "base/task/thread_pool.h"
23 #include "base/time/clock.h"
24 #include "base/time/default_clock.h"
25 #include "chrome/browser/browser_process.h"
26 #include "chrome/browser/browser_process_platform_part.h"
27 #include "chrome/browser/chromeos/crostini/ansible/ansible_management_service.h"
28 #include "chrome/browser/chromeos/crostini/crostini_features.h"
29 #include "chrome/browser/chromeos/crostini/crostini_manager_factory.h"
30 #include "chrome/browser/chromeos/crostini/crostini_port_forwarder.h"
31 #include "chrome/browser/chromeos/crostini/crostini_pref_names.h"
32 #include "chrome/browser/chromeos/crostini/crostini_remover.h"
33 #include "chrome/browser/chromeos/crostini/crostini_reporting_util.h"
34 #include "chrome/browser/chromeos/crostini/crostini_stability_monitor.h"
35 #include "chrome/browser/chromeos/crostini/crostini_types.mojom.h"
36 #include "chrome/browser/chromeos/crostini/crostini_upgrade_available_notification.h"
37 #include "chrome/browser/chromeos/crostini/throttle/crostini_throttle.h"
38 #include "chrome/browser/chromeos/file_manager/path_util.h"
39 #include "chrome/browser/chromeos/file_manager/volume_manager.h"
40 #include "chrome/browser/chromeos/guest_os/guest_os_share_path.h"
41 #include "chrome/browser/chromeos/policy/powerwash_requirements_checker.h"
42 #include "chrome/browser/chromeos/profiles/profile_helper.h"
43 #include "chrome/browser/chromeos/scheduler_configuration_manager.h"
44 #include "chrome/browser/chromeos/usb/cros_usb_detector.h"
45 #include "chrome/browser/profiles/profile.h"
46 #include "chrome/browser/profiles/profile_manager.h"
47 #include "chrome/browser/ui/browser.h"
48 #include "chrome/common/chrome_features.h"
49 #include "chrome/common/pref_names.h"
50 #include "chromeos/constants/chromeos_features.h"
51 #include "chromeos/dbus/anomaly_detector_client.h"
52 #include "chromeos/dbus/concierge_client.h"
53 #include "chromeos/dbus/cros_disks_client.h"
54 #include "chromeos/dbus/dbus_thread_manager.h"
55 #include "chromeos/dbus/debug_daemon/debug_daemon_client.h"
56 #include "chromeos/dbus/image_loader_client.h"
57 #include "chromeos/dbus/session_manager/session_manager_client.h"
58 #include "chromeos/disks/disk_mount_manager.h"
59 #include "chromeos/network/device_state.h"
60 #include "chromeos/network/network_device_handler.h"
61 #include "components/component_updater/component_updater_service.h"
62 #include "components/keyed_service/content/browser_context_dependency_manager.h"
63 #include "components/keyed_service/content/browser_context_keyed_service_factory.h"
64 #include "components/prefs/pref_service.h"
65 #include "content/public/browser/browser_task_traits.h"
66 #include "content/public/browser/browser_thread.h"
67 #include "content/public/browser/network_service_instance.h"
68 #include "dbus/message.h"
69 #include "extensions/browser/extension_registry.h"
70 #include "services/device/public/mojom/usb_device.mojom.h"
71 #include "services/device/public/mojom/usb_enumeration_options.mojom.h"
72 #include "services/network/public/cpp/network_connection_tracker.h"
73 #include "storage/browser/file_system/external_mount_points.h"
74 #include "ui/base/window_open_disposition.h"
75
76 namespace crostini {
77
78 namespace {
79
GetCiceroneClient()80 chromeos::CiceroneClient* GetCiceroneClient() {
81 return chromeos::DBusThreadManager::Get()->GetCiceroneClient();
82 }
83
GetConciergeClient()84 chromeos::ConciergeClient* GetConciergeClient() {
85 return chromeos::DBusThreadManager::Get()->GetConciergeClient();
86 }
87
GetAnomalyDetectorClient()88 chromeos::AnomalyDetectorClient* GetAnomalyDetectorClient() {
89 return chromeos::DBusThreadManager::Get()->GetAnomalyDetectorClient();
90 }
91
92 // Find any callbacks for the specified |vm_name|, invoke them with
93 // |arguments|... and erase them from the map.
94 template <typename... Parameters, typename... Arguments>
InvokeAndErasePendingCallbacks(std::map<ContainerId,base::OnceCallback<void (Parameters...)>> * vm_keyed_map,const std::string & vm_name,Arguments &&...arguments)95 void InvokeAndErasePendingCallbacks(
96 std::map<ContainerId, base::OnceCallback<void(Parameters...)>>*
97 vm_keyed_map,
98 const std::string& vm_name,
99 Arguments&&... arguments) {
100 for (auto it = vm_keyed_map->begin(); it != vm_keyed_map->end();) {
101 if (it->first.vm_name == vm_name) {
102 std::move(it->second).Run(std::forward<Arguments>(arguments)...);
103 vm_keyed_map->erase(it++);
104 } else {
105 ++it;
106 }
107 }
108 }
109
110 // Find any callbacks for the specified |vm_name|, invoke them with
111 // |arguments|... and erase them from the map.
112 template <typename... Parameters, typename... Arguments>
InvokeAndErasePendingCallbacks(std::map<std::string,base::OnceCallback<void (Parameters...)>> * vm_keyed_map,const std::string & vm_name,Arguments &&...arguments)113 void InvokeAndErasePendingCallbacks(
114 std::map<std::string, base::OnceCallback<void(Parameters...)>>*
115 vm_keyed_map,
116 const std::string& vm_name,
117 Arguments&&... arguments) {
118 for (auto it = vm_keyed_map->begin(); it != vm_keyed_map->end();) {
119 if (it->first == vm_name) {
120 std::move(it->second).Run(std::forward<Arguments>(arguments)...);
121 vm_keyed_map->erase(it++);
122 } else {
123 ++it;
124 }
125 }
126 }
127
128 // Find any container callbacks for the specified |container_id|, invoke them
129 // with |result| and erase them from the map.
InvokeAndErasePendingContainerCallbacks(std::multimap<ContainerId,CrostiniManager::CrostiniResultCallback> * container_callbacks,const ContainerId & container_id,CrostiniResult result)130 void InvokeAndErasePendingContainerCallbacks(
131 std::multimap<ContainerId, CrostiniManager::CrostiniResultCallback>*
132 container_callbacks,
133 const ContainerId& container_id,
134 CrostiniResult result) {
135 auto range = container_callbacks->equal_range(container_id);
136 for (auto it = range.first; it != range.second; ++it) {
137 std::move(it->second).Run(result);
138 }
139 container_callbacks->erase(range.first, range.second);
140 }
141
EmitCorruptionStateMetric(CorruptionStates state)142 void EmitCorruptionStateMetric(CorruptionStates state) {
143 base::UmaHistogramEnumeration("Crostini.FilesystemCorruption", state);
144 }
145
146 } // namespace
147
148 CrostiniManager::RestartOptions::RestartOptions() = default;
149 CrostiniManager::RestartOptions::RestartOptions(RestartOptions&&) = default;
150 CrostiniManager::RestartOptions::~RestartOptions() = default;
151 CrostiniManager::RestartOptions& CrostiniManager::RestartOptions::operator=(
152 RestartOptions&&) = default;
153
154 class CrostiniManager::CrostiniRestarter
155 : public crostini::VmShutdownObserver,
156 public chromeos::disks::DiskMountManager::Observer,
157 public chromeos::SchedulerConfigurationManagerBase::Observer {
158 public:
CrostiniRestarter(Profile * profile,CrostiniManager * crostini_manager,ContainerId container_id,RestartOptions options,CrostiniManager::CrostiniResultCallback callback)159 CrostiniRestarter(Profile* profile,
160 CrostiniManager* crostini_manager,
161 ContainerId container_id,
162 RestartOptions options,
163 CrostiniManager::CrostiniResultCallback callback)
164 : profile_(profile),
165 crostini_manager_(crostini_manager),
166 container_id_(std::move(container_id)),
167 options_(std::move(options)),
168 completed_callback_(std::move(callback)),
169 restart_id_(next_restart_id_++) {}
170
Restart()171 void Restart() {
172 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
173 if (!CrostiniFeatures::Get()->IsUIAllowed(profile_)) {
174 LOG(ERROR) << "Crostini UI not allowed for profile "
175 << profile_->GetProfileUserName();
176 std::move(completed_callback_).Run(CrostiniResult::NOT_ALLOWED);
177 return;
178 }
179
180 crostini_manager_->AddVmShutdownObserver(this);
181
182 StartStage(mojom::InstallerState::kStart);
183 is_initial_install_ =
184 crostini_manager_->GetCrostiniDialogStatus(DialogType::INSTALLER);
185 if (ReturnEarlyIfAborted()) {
186 return;
187 }
188
189 auto vm_info = crostini_manager_->GetVmInfo(container_id_.vm_name);
190 // If vm is stopping, we wait until OnVmShutdown() to kick it off.
191 if (vm_info && vm_info->state == VmState::STOPPING) {
192 LOG(WARNING) << "Delay restart due to vm stopping";
193 } else {
194 ContinueRestart();
195 }
196 }
197
AddObserver(CrostiniManager::RestartObserver * observer)198 void AddObserver(CrostiniManager::RestartObserver* observer) {
199 observer->set_restart_id(restart_id_);
200 observer_list_.AddObserver(observer);
201 }
202
RunCallback(CrostiniResult result)203 void RunCallback(CrostiniResult result) {
204 // Observer should not be called if we have completed.
205 observer_list_.Clear();
206 if (completed_callback_) {
207 std::move(completed_callback_).Run(result);
208 }
209 }
210
211 // crostini::VmShutdownObserver
OnVmShutdown(const std::string & vm_name)212 void OnVmShutdown(const std::string& vm_name) override {
213 if (ReturnEarlyIfAborted()) {
214 return;
215 }
216 if (vm_name == container_id_.vm_name) {
217 if (is_running_) {
218 LOG(WARNING) << "Unexpected VM shutdown during restart for " << vm_name;
219 FinishRestart(CrostiniResult::RESTART_FAILED_VM_STOPPED);
220 } else {
221 // We can only get here if Restart() was called to register the shutdown
222 // observer, and since is_running_ is false, we are waiting for this
223 // shutdown to actually kick off the process.
224 VLOG(1) << "resume restart on vm shutdown";
225 content::GetUIThreadTaskRunner({})->PostTask(
226 FROM_HERE, base::BindOnce(&CrostiniRestarter::ContinueRestart,
227 weak_ptr_factory_.GetWeakPtr()));
228 }
229 }
230 }
231
Abort(base::OnceClosure callback)232 void Abort(base::OnceClosure callback) {
233 is_aborted_ = true;
234 observer_list_.Clear();
235 abort_callbacks_.push_back(std::move(callback));
236 result_ = CrostiniResult::RESTART_ABORTED;
237 // Don't want to use FinishRestart here, because the next restarter in
238 // line needs to run.
239 RunCallback(result_);
240 }
241
242 // If this method returns true, then |this| may have been deleted and it is
243 // unsafe to refer to any member variables.
ReturnEarlyIfAborted()244 bool ReturnEarlyIfAborted() {
245 if (is_aborted_ && !abort_callbacks_.empty()) {
246 // The abort callbacks may delete this, so move the callback vector out of
247 // the class.
248 std::vector<base::OnceClosure> abort_callbacks_safe =
249 std::move(abort_callbacks_);
250 for (auto& abort_callback : abort_callbacks_safe) {
251 std::move(abort_callback).Run();
252 }
253 // The abort callback may delete this, so it's not safe to
254 // refer to |is_aborted_| after this point.
255 return true;
256 }
257 return is_aborted_;
258 }
259
OnContainerDownloading(int download_percent)260 void OnContainerDownloading(int download_percent) {
261 if (!is_running_) {
262 return;
263 }
264 for (auto& observer : observer_list_) {
265 observer.OnContainerDownloading(download_percent);
266 }
267 }
268
restart_id() const269 CrostiniManager::RestartId restart_id() const { return restart_id_; }
container_id()270 const ContainerId& container_id() { return container_id_; }
is_aborted() const271 bool is_aborted() const { return is_aborted_; }
272 CrostiniResult result_ = CrostiniResult::NEVER_FINISHED;
273
~CrostiniRestarter()274 ~CrostiniRestarter() override {
275 // Do not record results if this restart was triggered by the installer.
276 // The crostini installer has its own histograms that should be kept
277 // separate.
278 if (!is_initial_install_) {
279 base::UmaHistogramEnumeration("Crostini.RestarterResult", result_);
280 }
281 crostini_manager_->RemoveVmShutdownObserver(this);
282 if (completed_callback_) {
283 LOG(ERROR) << "Destroying without having called the callback.";
284 }
285 auto* mount_manager = chromeos::disks::DiskMountManager::GetInstance();
286 if (mount_manager)
287 mount_manager->RemoveObserver(this);
288 }
289
290 private:
ContinueRestart()291 void ContinueRestart() {
292 is_running_ = true;
293 // Skip to the end immediately if testing.
294 if (crostini_manager_->skip_restart_for_testing()) {
295 content::GetUIThreadTaskRunner({})->PostTask(
296 FROM_HERE,
297 base::BindOnce(&CrostiniRestarter::StartLxdContainerFinished,
298 weak_ptr_factory_.GetWeakPtr(),
299 CrostiniResult::SUCCESS));
300 return;
301 }
302
303 StartStage(mojom::InstallerState::kInstallImageLoader);
304 crostini_manager_->InstallTermina(
305 base::BindOnce(&CrostiniRestarter::LoadComponentFinished,
306 weak_ptr_factory_.GetWeakPtr()));
307 }
308
StartStage(mojom::InstallerState stage)309 void StartStage(mojom::InstallerState stage) {
310 for (auto& observer : observer_list_) {
311 observer.OnStageStarted(stage);
312 }
313 }
314
FinishRestart(CrostiniResult result)315 void FinishRestart(CrostiniResult result) {
316 DCHECK(!is_aborted_);
317
318 // FinishRestart will delete this, so it's not safe to call any methods
319 // after this point.
320 crostini_manager_->FinishRestart(this, result);
321 }
322
LoadComponentFinished(CrostiniResult result)323 void LoadComponentFinished(CrostiniResult result) {
324 for (auto& observer : observer_list_) {
325 observer.OnComponentLoaded(result);
326 }
327 if (ReturnEarlyIfAborted()) {
328 return;
329 }
330 if (result != CrostiniResult::SUCCESS) {
331 FinishRestart(result);
332 return;
333 }
334 // Set the pref here, after we first successfully install something
335 profile_->GetPrefs()->SetBoolean(crostini::prefs::kCrostiniEnabled, true);
336
337 // Allow concierge to choose an appropriate disk image size.
338 int64_t disk_size_bytes = options_.disk_size_bytes.value_or(0);
339 // If we have an already existing disk, CreateDiskImage will just return its
340 // path so we can pass it to StartTerminaVm.
341 StartStage(mojom::InstallerState::kCreateDiskImage);
342 crostini_manager_->CreateDiskImage(
343 base::FilePath(container_id_.vm_name),
344 vm_tools::concierge::StorageLocation::STORAGE_CRYPTOHOME_ROOT,
345 disk_size_bytes,
346 base::BindOnce(&CrostiniRestarter::CreateDiskImageFinished,
347 weak_ptr_factory_.GetWeakPtr(), disk_size_bytes));
348 }
349
CreateDiskImageFinished(int64_t disk_size_bytes,bool success,vm_tools::concierge::DiskImageStatus status,const base::FilePath & result_path)350 void CreateDiskImageFinished(int64_t disk_size_bytes,
351 bool success,
352 vm_tools::concierge::DiskImageStatus status,
353 const base::FilePath& result_path) {
354 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
355 for (auto& observer : observer_list_) {
356 observer.OnDiskImageCreated(success, status, disk_size_bytes);
357 }
358 if (ReturnEarlyIfAborted()) {
359 return;
360 }
361 if (!success) {
362 FinishRestart(CrostiniResult::CREATE_DISK_IMAGE_FAILED);
363 return;
364 }
365 crostini_manager_->EmitVmDiskTypeMetric(container_id_.vm_name);
366 disk_path_ = result_path;
367
368 auto* scheduler_configuration_manager =
369 g_browser_process->platform_part()->scheduler_configuration_manager();
370 base::Optional<std::pair<bool, size_t>> scheduler_configuration =
371 scheduler_configuration_manager->GetLastReply();
372 if (!scheduler_configuration) {
373 // Wait for the configuration to become available.
374 LOG(WARNING) << "Scheduler configuration is not yet ready";
375 scheduler_configuration_manager->AddObserver(this);
376 return;
377 }
378 OnConfigurationSet(scheduler_configuration->first,
379 scheduler_configuration->second);
380 }
381
382 // chromeos::SchedulerConfigurationManagerBase::Observer:
OnConfigurationSet(bool success,size_t num_cores_disabled)383 void OnConfigurationSet(bool success, size_t num_cores_disabled) override {
384 // Note: On non-x86_64 devices, the configuration request to debugd always
385 // fails. It is WAI, and to support that case, don't log anything even when
386 // |success| is false. |num_cores_disabled| is always set regardless of
387 // whether the call is successful.
388 g_browser_process->platform_part()
389 ->scheduler_configuration_manager()
390 ->RemoveObserver(this);
391 StartStage(mojom::InstallerState::kStartTerminaVm);
392 crostini_manager_->StartTerminaVm(
393 container_id_.vm_name, disk_path_, num_cores_disabled,
394 base::BindOnce(&CrostiniRestarter::StartTerminaVmFinished,
395 weak_ptr_factory_.GetWeakPtr()));
396 }
397
StartTerminaVmFinished(bool success)398 void StartTerminaVmFinished(bool success) {
399 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
400 for (auto& observer : observer_list_) {
401 observer.OnVmStarted(success);
402 }
403 if (ReturnEarlyIfAborted()) {
404 return;
405 }
406 if (!success) {
407 FinishRestart(CrostiniResult::VM_START_FAILED);
408 return;
409 }
410 // Cache kernel version for enterprise reporting, if it is enabled
411 // by policy, and we are in the default Termina/penguin case.
412 if (profile_->GetPrefs()->GetBoolean(
413 crostini::prefs::kReportCrostiniUsageEnabled) &&
414 container_id_ == ContainerId::GetDefault()) {
415 crostini_manager_->GetTerminaVmKernelVersion(
416 base::BindOnce(&CrostiniRestarter::GetTerminaVmKernelVersionFinished,
417 weak_ptr_factory_.GetWeakPtr()));
418 }
419 crostini_manager_->StartLxd(
420 container_id_.vm_name,
421 base::BindOnce(&CrostiniRestarter::StartLxdFinished,
422 weak_ptr_factory_.GetWeakPtr()));
423 }
424
StartLxdFinished(CrostiniResult result)425 void StartLxdFinished(CrostiniResult result) {
426 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
427 if (ReturnEarlyIfAborted()) {
428 return;
429 }
430 if (result != CrostiniResult::SUCCESS) {
431 FinishRestart(result);
432 return;
433 }
434 StartStage(mojom::InstallerState::kCreateContainer);
435 crostini_manager_->CreateLxdContainer(
436 container_id_,
437 base::BindOnce(&CrostiniRestarter::CreateLxdContainerFinished,
438 weak_ptr_factory_.GetWeakPtr()));
439 }
440
GetTerminaVmKernelVersionFinished(const base::Optional<std::string> & maybe_kernel_version)441 void GetTerminaVmKernelVersionFinished(
442 const base::Optional<std::string>& maybe_kernel_version) {
443 // In the error case, Crostini should still start, so we do not propagate
444 // errors any further here. Also, any error would already have been logged
445 // by CrostiniManager, so here we just (re)set the kernel version pref to
446 // the empty string in case the response is empty.
447 std::string kernel_version;
448 if (maybe_kernel_version.has_value()) {
449 kernel_version = maybe_kernel_version.value();
450 }
451 WriteTerminaVmKernelVersionToPrefsForReporting(profile_->GetPrefs(),
452 kernel_version);
453 }
454
CreateLxdContainerFinished(CrostiniResult result)455 void CreateLxdContainerFinished(CrostiniResult result) {
456 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
457 for (auto& observer : observer_list_) {
458 observer.OnContainerCreated(result);
459 }
460 if (ReturnEarlyIfAborted()) {
461 return;
462 }
463 if (result != CrostiniResult::SUCCESS) {
464 LOG(ERROR) << "Failed to Create Lxd Container.";
465 FinishRestart(result);
466 return;
467 }
468 StartStage(mojom::InstallerState::kSetupContainer);
469 crostini_manager_->SetUpLxdContainerUser(
470 container_id_,
471 options_.container_username.value_or(
472 DefaultContainerUserNameForProfile(profile_)),
473 base::BindOnce(&CrostiniRestarter::SetUpLxdContainerUserFinished,
474 weak_ptr_factory_.GetWeakPtr()));
475 }
476
SetUpLxdContainerUserFinished(bool success)477 void SetUpLxdContainerUserFinished(bool success) {
478 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
479
480 for (auto& observer : observer_list_) {
481 observer.OnContainerSetup(success);
482 }
483 if (ReturnEarlyIfAborted()) {
484 return;
485 }
486 if (!success) {
487 FinishRestart(CrostiniResult::CONTAINER_SETUP_FAILED);
488 return;
489 }
490
491 StartStage(mojom::InstallerState::kStartContainer);
492 crostini_manager_->StartLxdContainer(
493 container_id_,
494 base::BindOnce(&CrostiniRestarter::StartLxdContainerFinished,
495 weak_ptr_factory_.GetWeakPtr()));
496 }
497
StartLxdContainerFinished(CrostiniResult result)498 void StartLxdContainerFinished(CrostiniResult result) {
499 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
500
501 CloseCrostiniUpdateFilesystemView();
502 for (auto& observer : observer_list_) {
503 observer.OnContainerStarted(result);
504 }
505 if (ReturnEarlyIfAborted()) {
506 return;
507 }
508 if (result != CrostiniResult::SUCCESS) {
509 LOG(ERROR) << "Failed to Start Lxd Container.";
510 FinishRestart(result);
511 return;
512 }
513 // If default termina/penguin, then sshfs mount and reshare folders, else we
514 // are finished.
515 // If arc sideloading is enabled, configure the container for that.
516 crostini_manager_->ConfigureForArcSideload();
517 auto info = crostini_manager_->GetContainerInfo(container_id_);
518 if (container_id_ == ContainerId::GetDefault() && info &&
519 !info->sshfs_mounted) {
520 StartStage(mojom::InstallerState::kFetchSshKeys);
521 crostini_manager_->GetContainerSshKeys(
522 container_id_,
523 base::BindOnce(&CrostiniRestarter::GetContainerSshKeysFinished,
524 weak_ptr_factory_.GetWeakPtr(), info->username,
525 info->homedir));
526 } else {
527 FinishRestart(result);
528 }
529 }
530
GetContainerSshKeysFinished(const std::string & container_username,const base::FilePath & container_homedir,bool success,const std::string & container_public_key,const std::string & host_private_key,const std::string & hostname)531 void GetContainerSshKeysFinished(const std::string& container_username,
532 const base::FilePath& container_homedir,
533 bool success,
534 const std::string& container_public_key,
535 const std::string& host_private_key,
536 const std::string& hostname) {
537 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
538 for (auto& observer : observer_list_) {
539 observer.OnSshKeysFetched(success);
540 }
541 if (ReturnEarlyIfAborted()) {
542 return;
543 }
544 if (!success) {
545 FinishRestart(CrostiniResult::GET_CONTAINER_SSH_KEYS_FAILED);
546 return;
547 }
548
549 // Add DiskMountManager::OnMountEvent observer.
550 auto* dmgr = chromeos::disks::DiskMountManager::GetInstance();
551 dmgr->AddObserver(this);
552
553 // Call to sshfs to mount.
554 source_path_ = base::StringPrintf(
555 "sshfs://%s@%s:", container_username.c_str(), hostname.c_str());
556 container_homedir_ = container_homedir;
557 StartStage(mojom::InstallerState::kMountContainer);
558 dmgr->MountPath(source_path_, "",
559 file_manager::util::GetCrostiniMountPointName(profile_),
560 file_manager::util::GetCrostiniMountOptions(
561 hostname, host_private_key, container_public_key),
562 chromeos::MOUNT_TYPE_NETWORK_STORAGE,
563 chromeos::MOUNT_ACCESS_MODE_READ_WRITE);
564 }
565
566 // chromeos::disks::DiskMountManager::Observer
OnMountEvent(chromeos::disks::DiskMountManager::MountEvent event,chromeos::MountError error_code,const chromeos::disks::DiskMountManager::MountPointInfo & mount_info)567 void OnMountEvent(chromeos::disks::DiskMountManager::MountEvent event,
568 chromeos::MountError error_code,
569 const chromeos::disks::DiskMountManager::MountPointInfo&
570 mount_info) override {
571 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
572
573 // Ignore any other mount/unmount events.
574 if (event != chromeos::disks::DiskMountManager::MountEvent::MOUNTING ||
575 mount_info.source_path != source_path_) {
576 return;
577 }
578 bool success = error_code == chromeos::MountError::MOUNT_ERROR_NONE;
579 for (auto& observer : observer_list_) {
580 observer.OnContainerMounted(success);
581 }
582 // Remove DiskMountManager::OnMountEvent observer.
583 chromeos::disks::DiskMountManager::GetInstance()->RemoveObserver(this);
584
585 if (!success) {
586 LOG(ERROR) << "Error mounting crostini container: error_code="
587 << error_code << ", source_path=" << mount_info.source_path
588 << ", mount_path=" << mount_info.mount_path
589 << ", mount_type=" << mount_info.mount_type
590 << ", mount_condition=" << mount_info.mount_condition;
591 if (ReturnEarlyIfAborted()) {
592 return;
593 }
594 FinishRestart(CrostiniResult::SSHFS_MOUNT_ERROR);
595 return;
596 }
597
598 crostini_manager_->SetContainerSshfsMounted(container_id_, true);
599
600 // Register filesystem and add volume to VolumeManager.
601 base::FilePath mount_path = base::FilePath(mount_info.mount_path);
602 storage::ExternalMountPoints::GetSystemInstance()->RegisterFileSystem(
603 file_manager::util::GetCrostiniMountPointName(profile_),
604 storage::kFileSystemTypeNativeLocal, storage::FileSystemMountOption(),
605 mount_path);
606
607 // VolumeManager is null in unittest.
608 if (auto* vmgr = file_manager::VolumeManager::Get(profile_))
609 vmgr->AddSshfsCrostiniVolume(mount_path, container_homedir_);
610
611 // Abort not checked until exiting this function. On abort, do not
612 // continue, but still remove observer and add volume as per above.
613 if (ReturnEarlyIfAborted()) {
614 return;
615 }
616
617 FinishRestart(CrostiniResult::SUCCESS);
618 }
619
620 Profile* profile_;
621 // This isn't accessed after the CrostiniManager is destroyed and we need a
622 // reference to it during the CrostiniRestarter destructor.
623 CrostiniManager* crostini_manager_;
624
625 const ContainerId container_id_;
626 base::FilePath disk_path_;
627 RestartOptions options_;
628 std::string source_path_;
629 base::FilePath container_homedir_;
630 bool is_initial_install_ = false;
631 CrostiniManager::CrostiniResultCallback completed_callback_;
632 std::vector<base::OnceClosure> abort_callbacks_;
633 base::ObserverList<CrostiniManager::RestartObserver>::Unchecked
634 observer_list_;
635 CrostiniManager::RestartId restart_id_;
636 bool is_aborted_ = false;
637 bool is_running_ = false;
638
639 static CrostiniManager::RestartId next_restart_id_;
640
641 base::WeakPtrFactory<CrostiniRestarter> weak_ptr_factory_{this};
642 };
643
644 CrostiniManager::RestartId
645 CrostiniManager::CrostiniRestarter::next_restart_id_ = 0;
646 // Unit tests need this initialized to true. In Browser tests and real life,
647 // it is updated via MaybeUpdateCrostini.
648 bool CrostiniManager::is_dev_kvm_present_ = true;
649
UpdateVmState(std::string vm_name,VmState vm_state)650 void CrostiniManager::UpdateVmState(std::string vm_name, VmState vm_state) {
651 auto vm_info = running_vms_.find(std::move(vm_name));
652 if (vm_info != running_vms_.end()) {
653 vm_info->second.state = vm_state;
654 return;
655 }
656 // This can happen normally when StopVm is called right after start up.
657 LOG(WARNING) << "Attempted to set state for unknown vm: " << vm_name;
658 }
659
IsVmRunning(std::string vm_name)660 bool CrostiniManager::IsVmRunning(std::string vm_name) {
661 auto vm_info = running_vms_.find(std::move(vm_name));
662 if (vm_info != running_vms_.end()) {
663 return vm_info->second.state == VmState::STARTED;
664 }
665 return false;
666 }
667
GetVmInfo(std::string vm_name)668 base::Optional<VmInfo> CrostiniManager::GetVmInfo(std::string vm_name) {
669 auto it = running_vms_.find(std::move(vm_name));
670 if (it != running_vms_.end())
671 return it->second;
672 return base::nullopt;
673 }
674
AddRunningVmForTesting(std::string vm_name)675 void CrostiniManager::AddRunningVmForTesting(std::string vm_name) {
676 running_vms_[std::move(vm_name)] = VmInfo{VmState::STARTED};
677 }
678
AddStoppingVmForTesting(std::string vm_name)679 void CrostiniManager::AddStoppingVmForTesting(std::string vm_name) {
680 running_vms_[std::move(vm_name)] = VmInfo{VmState::STOPPING};
681 }
682
683 LinuxPackageInfo::LinuxPackageInfo() = default;
684 LinuxPackageInfo::LinuxPackageInfo(LinuxPackageInfo&&) = default;
685 LinuxPackageInfo::LinuxPackageInfo(const LinuxPackageInfo&) = default;
686 LinuxPackageInfo& LinuxPackageInfo::operator=(LinuxPackageInfo&&) = default;
687 LinuxPackageInfo& LinuxPackageInfo::operator=(const LinuxPackageInfo&) =
688 default;
689 LinuxPackageInfo::~LinuxPackageInfo() = default;
690
ContainerInfo(std::string container_name,std::string container_username,std::string container_homedir,std::string ipv4_address)691 ContainerInfo::ContainerInfo(std::string container_name,
692 std::string container_username,
693 std::string container_homedir,
694 std::string ipv4_address)
695 : name(std::move(container_name)),
696 username(std::move(container_username)),
697 homedir(std::move(container_homedir)),
698 ipv4_address(std::move(ipv4_address)) {}
699 ContainerInfo::~ContainerInfo() = default;
700 ContainerInfo::ContainerInfo(ContainerInfo&&) = default;
701 ContainerInfo::ContainerInfo(const ContainerInfo&) = default;
702 ContainerInfo& ContainerInfo::operator=(ContainerInfo&&) = default;
703 ContainerInfo& ContainerInfo::operator=(const ContainerInfo&) = default;
704
SetContainerSshfsMounted(const ContainerId & container_id,bool is_mounted)705 void CrostiniManager::SetContainerSshfsMounted(const ContainerId& container_id,
706 bool is_mounted) {
707 auto range = running_containers_.equal_range(container_id.vm_name);
708 for (auto it = range.first; it != range.second; ++it) {
709 if (it->second.name == container_id.container_name) {
710 it->second.sshfs_mounted = is_mounted;
711 }
712 }
713 }
714
715 namespace {
716
VersionFromOsRelease(const vm_tools::cicerone::OsRelease & os_release)717 ContainerOsVersion VersionFromOsRelease(
718 const vm_tools::cicerone::OsRelease& os_release) {
719 if (os_release.id() == "debian") {
720 if (os_release.version_id() == "9") {
721 return ContainerOsVersion::kDebianStretch;
722 } else if (os_release.version_id() == "10") {
723 return ContainerOsVersion::kDebianBuster;
724 } else {
725 return ContainerOsVersion::kDebianOther;
726 }
727 }
728 return ContainerOsVersion::kOtherOs;
729 }
730
IsUpgradableContainerVersion(ContainerOsVersion version)731 bool IsUpgradableContainerVersion(ContainerOsVersion version) {
732 return version == ContainerOsVersion::kDebianStretch;
733 }
734
735 } // namespace
736
SetContainerOsRelease(const ContainerId & container_id,const vm_tools::cicerone::OsRelease & os_release)737 void CrostiniManager::SetContainerOsRelease(
738 const ContainerId& container_id,
739 const vm_tools::cicerone::OsRelease& os_release) {
740 ContainerOsVersion version = VersionFromOsRelease(os_release);
741 // Store the os release version in prefs. We can use this value to decide if
742 // an upgrade can be offered.
743 UpdateContainerPref(profile_, container_id, prefs::kContainerOsVersionKey,
744 base::Value(static_cast<int>(version)));
745
746 base::Optional<ContainerOsVersion> old_version;
747 auto it = container_os_releases_.find(container_id);
748 if (it != container_os_releases_.end()) {
749 old_version = VersionFromOsRelease(it->second);
750 }
751
752 VLOG(1) << container_id;
753 VLOG(1) << "os_release.pretty_name " << os_release.pretty_name();
754 VLOG(1) << "os_release.name " << os_release.name();
755 VLOG(1) << "os_release.version " << os_release.version();
756 VLOG(1) << "os_release.version_id " << os_release.version_id();
757 VLOG(1) << "os_release.id " << os_release.id();
758 container_os_releases_[container_id] = os_release;
759 if (!old_version || *old_version != version) {
760 for (auto& observer : crostini_container_properties_observers_) {
761 observer.OnContainerOsReleaseChanged(
762 container_id, IsUpgradableContainerVersion(version));
763 }
764 }
765 base::UmaHistogramEnumeration("Crostini.ContainerOsVersion", version);
766 }
767
ConfigureForArcSideload()768 void CrostiniManager::ConfigureForArcSideload() {
769 chromeos::SessionManagerClient* session_manager_client =
770 chromeos::SessionManagerClient::Get();
771 if (!base::FeatureList::IsEnabled(features::kCrostiniArcSideload) ||
772 !session_manager_client)
773 return;
774 session_manager_client->QueryAdbSideload(base::BindOnce(
775 // We use a lambda to keep the arc sideloading implementation local, and
776 // avoid header pollution. This means we have to manually check the weak
777 // pointer is alive.
778 [](base::WeakPtr<CrostiniManager> manager,
779 chromeos::SessionManagerClient::AdbSideloadResponseCode response_code,
780 bool is_allowed) {
781 if (!manager || !is_allowed ||
782 response_code != chromeos::SessionManagerClient::
783 AdbSideloadResponseCode::SUCCESS) {
784 return;
785 }
786 vm_tools::cicerone::ConfigureForArcSideloadRequest request;
787 request.set_owner_id(manager->owner_id_);
788 request.set_vm_name(kCrostiniDefaultVmName);
789 request.set_container_name(kCrostiniDefaultContainerName);
790 GetCiceroneClient()->ConfigureForArcSideload(
791 request,
792 base::BindOnce(
793 [](base::Optional<
794 vm_tools::cicerone::ConfigureForArcSideloadResponse>
795 response) {
796 if (!response) {
797 LOG(ERROR) << "Failed to configure for arc sideloading: no "
798 "response from vm";
799 return;
800 }
801 if (response->status() ==
802 vm_tools::cicerone::ConfigureForArcSideloadResponse::
803 SUCCEEDED) {
804 return;
805 }
806 LOG(ERROR) << "Failed to configure for arc sideloading: "
807 << response->failure_reason();
808 }));
809 },
810 weak_ptr_factory_.GetWeakPtr()));
811 }
812
GetContainerOsRelease(const ContainerId & container_id) const813 const vm_tools::cicerone::OsRelease* CrostiniManager::GetContainerOsRelease(
814 const ContainerId& container_id) const {
815 auto it = container_os_releases_.find(container_id);
816 if (it != container_os_releases_.end()) {
817 return &it->second;
818 }
819 return nullptr;
820 }
821
IsContainerUpgradeable(const ContainerId & container_id) const822 bool CrostiniManager::IsContainerUpgradeable(
823 const ContainerId& container_id) const {
824 ContainerOsVersion version = ContainerOsVersion::kUnknown;
825 const auto* os_release = GetContainerOsRelease(container_id);
826 if (os_release) {
827 version = VersionFromOsRelease(*os_release);
828 } else {
829 // Check prefs instead.
830 const base::Value* value = GetContainerPrefValue(
831 profile_, container_id, prefs::kContainerOsVersionKey);
832 if (value) {
833 version = static_cast<ContainerOsVersion>(value->GetInt());
834 }
835 }
836 return IsUpgradableContainerVersion(version);
837 }
838
ShouldPromptContainerUpgrade(const ContainerId & container_id) const839 bool CrostiniManager::ShouldPromptContainerUpgrade(
840 const ContainerId& container_id) const {
841 if (!CrostiniFeatures::Get()->IsContainerUpgradeUIAllowed(profile_)) {
842 return false;
843 }
844 if (container_upgrade_prompt_shown_.count(container_id) != 0) {
845 // Already shown the upgrade dialog.
846 return false;
847 }
848 if (container_id != ContainerId::GetDefault()) {
849 return false;
850 }
851 bool upgradable = IsContainerUpgradeable(container_id);
852 return upgradable;
853 }
854
UpgradePromptShown(const ContainerId & container_id)855 void CrostiniManager::UpgradePromptShown(const ContainerId& container_id) {
856 container_upgrade_prompt_shown_.insert(container_id);
857 }
858
IsUncleanStartup() const859 bool CrostiniManager::IsUncleanStartup() const {
860 return is_unclean_startup_;
861 }
862
SetUncleanStartupForTesting(bool is_unclean_startup)863 void CrostiniManager::SetUncleanStartupForTesting(bool is_unclean_startup) {
864 is_unclean_startup_ = is_unclean_startup;
865 }
866
GetContainerInfo(const ContainerId & container_id)867 base::Optional<ContainerInfo> CrostiniManager::GetContainerInfo(
868 const ContainerId& container_id) {
869 if (!IsVmRunning(container_id.vm_name)) {
870 return base::nullopt;
871 }
872 auto range = running_containers_.equal_range(container_id.vm_name);
873 for (auto it = range.first; it != range.second; ++it) {
874 if (it->second.name == container_id.container_name) {
875 return it->second;
876 }
877 }
878 return base::nullopt;
879 }
880
AddRunningContainerForTesting(std::string vm_name,ContainerInfo info)881 void CrostiniManager::AddRunningContainerForTesting(std::string vm_name,
882 ContainerInfo info) {
883 running_containers_.emplace(std::move(vm_name), info);
884 }
885
UpdateLaunchMetricsForEnterpriseReporting()886 void CrostiniManager::UpdateLaunchMetricsForEnterpriseReporting() {
887 PrefService* const profile_prefs = profile_->GetPrefs();
888 const component_updater::ComponentUpdateService* const update_service =
889 g_browser_process->component_updater();
890 const base::Clock* const clock = base::DefaultClock::GetInstance();
891 WriteMetricsForReportingToPrefsIfEnabled(profile_prefs, update_service,
892 clock);
893 }
894
GetForProfile(Profile * profile)895 CrostiniManager* CrostiniManager::GetForProfile(Profile* profile) {
896 return CrostiniManagerFactory::GetForProfile(profile);
897 }
898
CrostiniManager(Profile * profile)899 CrostiniManager::CrostiniManager(Profile* profile)
900 : profile_(profile), owner_id_(CryptohomeIdForProfile(profile)) {
901 DCHECK(!profile_->IsOffTheRecord());
902 GetCiceroneClient()->AddObserver(this);
903 GetConciergeClient()->AddVmObserver(this);
904 GetConciergeClient()->AddContainerObserver(this);
905 GetAnomalyDetectorClient()->AddObserver(this);
906 if (chromeos::NetworkHandler::IsInitialized()) {
907 chromeos::NetworkHandler::Get()->network_state_handler()->AddObserver(
908 this, ::base::Location::Current());
909 }
910 if (chromeos::PowerManagerClient::Get()) {
911 chromeos::PowerManagerClient::Get()->AddObserver(this);
912 }
913 CrostiniThrottle::GetForBrowserContext(profile_);
914 crostini_stability_monitor_ =
915 std::make_unique<CrostiniStabilityMonitor>(this);
916 }
917
~CrostiniManager()918 CrostiniManager::~CrostiniManager() {
919 RemoveDBusObservers();
920 if (chromeos::NetworkHandler::IsInitialized()) {
921 chromeos::NetworkHandler::Get()->network_state_handler()->RemoveObserver(
922 this, ::base::Location::Current());
923 }
924 }
925
GetWeakPtr()926 base::WeakPtr<CrostiniManager> CrostiniManager::GetWeakPtr() {
927 return weak_ptr_factory_.GetWeakPtr();
928 }
929
RemoveDBusObservers()930 void CrostiniManager::RemoveDBusObservers() {
931 if (dbus_observers_removed_) {
932 return;
933 }
934 dbus_observers_removed_ = true;
935 GetCiceroneClient()->RemoveObserver(this);
936 GetConciergeClient()->RemoveContainerObserver(this);
937 GetAnomalyDetectorClient()->RemoveObserver(this);
938 if (chromeos::PowerManagerClient::Get()) {
939 chromeos::PowerManagerClient::Get()->RemoveObserver(this);
940 }
941 // CrostiniStabilityMonitor needs to be destructed here so it can unregister
942 // itself from the DBus clients that may no longer exist later.
943 crostini_stability_monitor_.reset();
944 }
945
946 // static
IsDevKvmPresent()947 bool CrostiniManager::IsDevKvmPresent() {
948 return is_dev_kvm_present_;
949 }
950
MaybeUpdateCrostini()951 void CrostiniManager::MaybeUpdateCrostini() {
952 // This is a new user session, perhaps using an old CrostiniManager.
953 container_upgrade_prompt_shown_.clear();
954 base::ThreadPool::PostTaskAndReply(
955 FROM_HERE, {base::MayBlock()},
956 base::BindOnce(&CrostiniManager::CheckPaths),
957 base::BindOnce(&CrostiniManager::MaybeUpdateCrostiniAfterChecks,
958 weak_ptr_factory_.GetWeakPtr()));
959 // Probe Concierge - if it's still running after an unclean shutdown, a
960 // success response will be received.
961 if (profile_->GetLastSessionExitType() == Profile::EXIT_CRASHED) {
962 vm_tools::concierge::GetVmInfoRequest concierge_request;
963 concierge_request.set_owner_id(owner_id_);
964 concierge_request.set_name(kCrostiniDefaultVmName);
965 GetConciergeClient()->GetVmInfo(
966 std::move(concierge_request),
967 base::BindOnce(
968 [](base::WeakPtr<CrostiniManager> weak_this,
969 base::Optional<vm_tools::concierge::GetVmInfoResponse> reply) {
970 if (weak_this) {
971 VLOG(1) << "Exit type: "
972 << static_cast<int>(Profile::EXIT_CRASHED);
973 VLOG(1) << "GetVmInfo result: "
974 << (reply.has_value() && reply->success());
975 weak_this->is_unclean_startup_ =
976 reply.has_value() && reply->success();
977 if (weak_this->is_unclean_startup_) {
978 weak_this->RemoveUncleanSshfsMounts();
979 }
980 }
981 },
982 weak_ptr_factory_.GetWeakPtr()));
983 }
984 }
985
986 // static
CheckPaths()987 void CrostiniManager::CheckPaths() {
988 is_dev_kvm_present_ = base::PathExists(base::FilePath("/dev/kvm"));
989 }
990
MaybeUpdateCrostiniAfterChecks()991 void CrostiniManager::MaybeUpdateCrostiniAfterChecks() {
992 if (!is_dev_kvm_present_) {
993 return;
994 }
995 if (!CrostiniFeatures::Get()->IsEnabled(profile_)) {
996 return;
997 }
998 if (!CrostiniFeatures::Get()->IsAllowed(profile_)) {
999 return;
1000 }
1001 if (ShouldPromptContainerUpgrade(DefaultContainerId())) {
1002 upgrade_available_notification_ =
1003 CrostiniUpgradeAvailableNotification::Show(profile_, base::DoNothing());
1004 }
1005 // TODO(crbug/953544) Remove this once we have transitioned completely to DLC
1006 InstallTermina(base::DoNothing());
1007 }
1008
InstallTermina(CrostiniResultCallback callback)1009 void CrostiniManager::InstallTermina(CrostiniResultCallback callback) {
1010 termina_installer_.Install(base::BindOnce(
1011 [](CrostiniResultCallback callback,
1012 TerminaInstaller::InstallResult result) {
1013 CrostiniResult res;
1014 if (result == TerminaInstaller::InstallResult::Success) {
1015 res = CrostiniResult::SUCCESS;
1016 } else if (result == TerminaInstaller::InstallResult::Offline) {
1017 res = CrostiniResult::OFFLINE_WHEN_UPGRADE_REQUIRED;
1018 } else if (result == TerminaInstaller::InstallResult::Failure) {
1019 res = CrostiniResult::LOAD_COMPONENT_FAILED;
1020 } else {
1021 CHECK(false)
1022 << "Got unexpected value of TerminaInstaller::InstallResult";
1023 res = CrostiniResult::LOAD_COMPONENT_FAILED;
1024 }
1025 std::move(callback).Run(res);
1026 },
1027 std::move(callback)));
1028 }
1029
UninstallTermina(BoolCallback callback)1030 void CrostiniManager::UninstallTermina(BoolCallback callback) {
1031 termina_installer_.Uninstall(std::move(callback));
1032 }
1033
CreateDiskImage(const base::FilePath & disk_path,vm_tools::concierge::StorageLocation storage_location,int64_t disk_size_bytes,CreateDiskImageCallback callback)1034 void CrostiniManager::CreateDiskImage(
1035 const base::FilePath& disk_path,
1036 vm_tools::concierge::StorageLocation storage_location,
1037 int64_t disk_size_bytes,
1038 CreateDiskImageCallback callback) {
1039 std::string disk_path_string = disk_path.AsUTF8Unsafe();
1040 if (disk_path_string.empty()) {
1041 LOG(ERROR) << "Disk path cannot be empty";
1042 std::move(callback).Run(
1043 /*success=*/false,
1044 vm_tools::concierge::DiskImageStatus::DISK_STATUS_UNKNOWN,
1045 base::FilePath());
1046 return;
1047 }
1048
1049 vm_tools::concierge::CreateDiskImageRequest request;
1050 request.set_cryptohome_id(CryptohomeIdForProfile(profile_));
1051 request.set_disk_path(std::move(disk_path_string));
1052 // The type of disk image to be created.
1053 request.set_image_type(vm_tools::concierge::DISK_IMAGE_AUTO);
1054
1055 if (storage_location != vm_tools::concierge::STORAGE_CRYPTOHOME_ROOT) {
1056 LOG(ERROR) << "'" << storage_location
1057 << "' is not a valid storage location";
1058 std::move(callback).Run(
1059 /*success=*/false,
1060 vm_tools::concierge::DiskImageStatus::DISK_STATUS_UNKNOWN,
1061 base::FilePath());
1062 return;
1063 }
1064 request.set_storage_location(storage_location);
1065 // The logical size of the new disk image, in bytes.
1066 request.set_disk_size(std::move(disk_size_bytes));
1067
1068 GetConciergeClient()->CreateDiskImage(
1069 std::move(request),
1070 base::BindOnce(&CrostiniManager::OnCreateDiskImage,
1071 weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
1072 }
1073
DestroyDiskImage(const base::FilePath & disk_path,BoolCallback callback)1074 void CrostiniManager::DestroyDiskImage(const base::FilePath& disk_path,
1075 BoolCallback callback) {
1076 std::string disk_path_string = disk_path.AsUTF8Unsafe();
1077 if (disk_path_string.empty()) {
1078 LOG(ERROR) << "Disk path cannot be empty";
1079 std::move(callback).Run(/*success=*/false);
1080 return;
1081 }
1082
1083 vm_tools::concierge::DestroyDiskImageRequest request;
1084 request.set_cryptohome_id(CryptohomeIdForProfile(profile_));
1085 request.set_disk_path(std::move(disk_path_string));
1086
1087 GetConciergeClient()->DestroyDiskImage(
1088 std::move(request),
1089 base::BindOnce(&CrostiniManager::OnDestroyDiskImage,
1090 weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
1091 }
1092
ListVmDisks(ListVmDisksCallback callback)1093 void CrostiniManager::ListVmDisks(ListVmDisksCallback callback) {
1094 vm_tools::concierge::ListVmDisksRequest request;
1095 request.set_cryptohome_id(CryptohomeIdForProfile(profile_));
1096 request.set_storage_location(vm_tools::concierge::STORAGE_CRYPTOHOME_ROOT);
1097
1098 GetConciergeClient()->ListVmDisks(
1099 std::move(request),
1100 base::BindOnce(&CrostiniManager::OnListVmDisks,
1101 weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
1102 }
1103
StartTerminaVm(std::string name,const base::FilePath & disk_path,size_t num_cores_disabled,BoolCallback callback)1104 void CrostiniManager::StartTerminaVm(std::string name,
1105 const base::FilePath& disk_path,
1106 size_t num_cores_disabled,
1107 BoolCallback callback) {
1108 if (name.empty()) {
1109 LOG(ERROR) << "name is required";
1110 std::move(callback).Run(/*success=*/false);
1111 return;
1112 }
1113
1114 std::string disk_path_string = disk_path.AsUTF8Unsafe();
1115 if (disk_path_string.empty()) {
1116 LOG(ERROR) << "Disk path cannot be empty";
1117 std::move(callback).Run(/*success=*/false);
1118 return;
1119 }
1120 if (!GetAnomalyDetectorClient()->IsGuestFileCorruptionSignalConnected()) {
1121 LOG(ERROR) << "GuestFileCorruptionSignal not connected, will not be "
1122 "able to detect file system corruption.";
1123 std::move(callback).Run(/*success=*/false);
1124 return;
1125 }
1126
1127 // When Crostini is blocked because of powerwash request, do not start VM,
1128 // but show notification requesting powerwash.
1129 policy::PowerwashRequirementsChecker pw_checker(
1130 policy::PowerwashRequirementsChecker::Context::kCrostini, profile_);
1131 if (pw_checker.GetState() !=
1132 policy::PowerwashRequirementsChecker::State::kNotRequired) {
1133 pw_checker.ShowNotification();
1134 std::move(callback).Run(/*success=*/false);
1135 return;
1136 }
1137
1138 for (auto& observer : vm_starting_observers_) {
1139 observer.OnVmStarting();
1140 }
1141
1142 vm_tools::concierge::StartVmRequest request;
1143 base::Optional<std::string> dlc_id = termina_installer_.GetDlcId();
1144 if (dlc_id.has_value()) {
1145 request.mutable_vm()->set_dlc_id(*dlc_id);
1146 }
1147 request.set_name(std::move(name));
1148 request.set_start_termina(true);
1149 request.set_owner_id(owner_id_);
1150 if (base::FeatureList::IsEnabled(chromeos::features::kCrostiniGpuSupport))
1151 request.set_enable_gpu(true);
1152 if (crostini_mic_sharing_enabled_ &&
1153 profile_->GetPrefs()->GetBoolean(::prefs::kAudioCaptureAllowed)) {
1154 request.set_enable_audio_capture(true);
1155 }
1156 const int32_t cpus = base::SysInfo::NumberOfProcessors() - num_cores_disabled;
1157 DCHECK_LT(0, cpus);
1158 request.set_cpus(cpus);
1159
1160 vm_tools::concierge::DiskImage* disk_image = request.add_disks();
1161 disk_image->set_path(std::move(disk_path_string));
1162 disk_image->set_image_type(vm_tools::concierge::DISK_IMAGE_AUTO);
1163 disk_image->set_writable(true);
1164 disk_image->set_do_mount(false);
1165
1166 GetConciergeClient()->StartTerminaVm(
1167 request, base::BindOnce(&CrostiniManager::OnStartTerminaVm,
1168 weak_ptr_factory_.GetWeakPtr(), request.name(),
1169 std::move(callback)));
1170 }
1171
StopVm(std::string name,CrostiniResultCallback callback)1172 void CrostiniManager::StopVm(std::string name,
1173 CrostiniResultCallback callback) {
1174 if (name.empty()) {
1175 LOG(ERROR) << "name is required";
1176 std::move(callback).Run(CrostiniResult::CLIENT_ERROR);
1177 return;
1178 }
1179
1180 UpdateVmState(name, VmState::STOPPING);
1181
1182 vm_tools::concierge::StopVmRequest request;
1183 request.set_owner_id(owner_id_);
1184 request.set_name(name);
1185
1186 GetConciergeClient()->StopVm(
1187 std::move(request),
1188 base::BindOnce(&CrostiniManager::OnStopVm, weak_ptr_factory_.GetWeakPtr(),
1189 std::move(name), std::move(callback)));
1190 }
1191
GetTerminaVmKernelVersion(GetTerminaVmKernelVersionCallback callback)1192 void CrostiniManager::GetTerminaVmKernelVersion(
1193 GetTerminaVmKernelVersionCallback callback) {
1194 vm_tools::concierge::GetVmEnterpriseReportingInfoRequest request;
1195 request.set_vm_name(kCrostiniDefaultVmName);
1196 request.set_owner_id(owner_id_);
1197 GetConciergeClient()->GetVmEnterpriseReportingInfo(
1198 std::move(request),
1199 base::BindOnce(&CrostiniManager::OnGetTerminaVmKernelVersion,
1200 weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
1201 }
1202
StartLxd(std::string vm_name,CrostiniResultCallback callback)1203 void CrostiniManager::StartLxd(std::string vm_name,
1204 CrostiniResultCallback callback) {
1205 if (vm_name.empty()) {
1206 LOG(ERROR) << "vm_name is required";
1207 std::move(callback).Run(CrostiniResult::CLIENT_ERROR);
1208 return;
1209 }
1210 if (!GetCiceroneClient()->IsLxdContainerStartingSignalConnected()) {
1211 LOG(ERROR) << "Async call to StartLxd can't complete when signals "
1212 "are not connected.";
1213 std::move(callback).Run(CrostiniResult::CLIENT_ERROR);
1214 return;
1215 }
1216 vm_tools::cicerone::StartLxdRequest request;
1217 request.set_vm_name(std::move(vm_name));
1218 request.set_owner_id(owner_id_);
1219 request.set_reset_lxd_db(
1220 base::FeatureList::IsEnabled(chromeos::features::kCrostiniResetLxdDb));
1221 GetCiceroneClient()->StartLxd(
1222 std::move(request),
1223 base::BindOnce(&CrostiniManager::OnStartLxd,
1224 weak_ptr_factory_.GetWeakPtr(), request.vm_name(),
1225 std::move(callback)));
1226 }
1227
CreateLxdContainer(ContainerId container_id,CrostiniResultCallback callback)1228 void CrostiniManager::CreateLxdContainer(ContainerId container_id,
1229 CrostiniResultCallback callback) {
1230 if (container_id.vm_name.empty()) {
1231 LOG(ERROR) << "vm_name is required";
1232 std::move(callback).Run(CrostiniResult::CLIENT_ERROR);
1233 return;
1234 }
1235 if (container_id.container_name.empty()) {
1236 LOG(ERROR) << "container_name is required";
1237 std::move(callback).Run(CrostiniResult::CLIENT_ERROR);
1238 return;
1239 }
1240 if (!GetCiceroneClient()->IsLxdContainerCreatedSignalConnected() ||
1241 !GetCiceroneClient()->IsLxdContainerDownloadingSignalConnected()) {
1242 LOG(ERROR)
1243 << "Async call to CreateLxdContainer can't complete when signals "
1244 "are not connected.";
1245 std::move(callback).Run(CrostiniResult::CLIENT_ERROR);
1246 return;
1247 }
1248 vm_tools::cicerone::CreateLxdContainerRequest request;
1249 request.set_vm_name(container_id.vm_name);
1250 request.set_container_name(container_id.container_name);
1251 request.set_owner_id(owner_id_);
1252 std::string image_server_url;
1253 scoped_refptr<component_updater::CrOSComponentManager> component_manager =
1254 g_browser_process->platform_part()->cros_component_manager();
1255 if (component_manager) {
1256 image_server_url =
1257 component_manager->GetCompatiblePath("cros-crostini-image-server-url")
1258 .value();
1259 }
1260 request.set_image_server(image_server_url.empty()
1261 ? kCrostiniDefaultImageServerUrl
1262 : image_server_url);
1263 if (base::FeatureList::IsEnabled(
1264 chromeos::features::kCrostiniUseBusterImage)) {
1265 request.set_image_alias(kCrostiniBusterImageAlias);
1266 } else {
1267 request.set_image_alias(kCrostiniStretchImageAlias);
1268 }
1269 GetCiceroneClient()->CreateLxdContainer(
1270 std::move(request),
1271 base::BindOnce(&CrostiniManager::OnCreateLxdContainer,
1272 weak_ptr_factory_.GetWeakPtr(), std::move(container_id),
1273 std::move(callback)));
1274 }
1275
DeleteLxdContainer(ContainerId container_id,BoolCallback callback)1276 void CrostiniManager::DeleteLxdContainer(ContainerId container_id,
1277 BoolCallback callback) {
1278 if (container_id.vm_name.empty()) {
1279 LOG(ERROR) << "vm_name is required";
1280 std::move(callback).Run(/*success=*/false);
1281 return;
1282 }
1283 if (container_id.container_name.empty()) {
1284 LOG(ERROR) << "container_name is required";
1285 std::move(callback).Run(/*success=*/false);
1286 return;
1287 }
1288 if (!GetCiceroneClient()->IsLxdContainerDeletedSignalConnected()) {
1289 LOG(ERROR)
1290 << "Async call to DeleteLxdContainer can't complete when signals "
1291 "are not connected.";
1292 std::move(callback).Run(/*success=*/false);
1293 return;
1294 }
1295
1296 vm_tools::cicerone::DeleteLxdContainerRequest request;
1297 request.set_vm_name(container_id.vm_name);
1298 request.set_container_name(container_id.container_name);
1299 request.set_owner_id(owner_id_);
1300 GetCiceroneClient()->DeleteLxdContainer(
1301 std::move(request),
1302 base::BindOnce(&CrostiniManager::OnDeleteLxdContainer,
1303 weak_ptr_factory_.GetWeakPtr(), std::move(container_id),
1304 std::move(callback)));
1305 }
1306
OnDeleteLxdContainer(const ContainerId & container_id,BoolCallback callback,base::Optional<vm_tools::cicerone::DeleteLxdContainerResponse> response)1307 void CrostiniManager::OnDeleteLxdContainer(
1308 const ContainerId& container_id,
1309 BoolCallback callback,
1310 base::Optional<vm_tools::cicerone::DeleteLxdContainerResponse> response) {
1311 if (!response) {
1312 LOG(ERROR) << "Failed to delete lxd container in vm. Empty response.";
1313 std::move(callback).Run(/*success=*/false);
1314 return;
1315 }
1316
1317 if (response->status() ==
1318 vm_tools::cicerone::DeleteLxdContainerResponse::DELETING) {
1319 VLOG(1) << "Awaiting LxdContainerDeletedSignal for " << container_id;
1320 delete_lxd_container_callbacks_.emplace(container_id, std::move(callback));
1321
1322 } else if (response->status() ==
1323 vm_tools::cicerone::DeleteLxdContainerResponse::DOES_NOT_EXIST) {
1324 RemoveLxdContainerFromPrefs(profile_, container_id);
1325 std::move(callback).Run(/*success=*/true);
1326
1327 } else {
1328 LOG(ERROR) << "Failed to delete container: " << response->failure_reason();
1329 std::move(callback).Run(/*success=*/false);
1330 }
1331 }
1332
StartLxdContainer(ContainerId container_id,CrostiniResultCallback callback)1333 void CrostiniManager::StartLxdContainer(ContainerId container_id,
1334 CrostiniResultCallback callback) {
1335 if (container_id.vm_name.empty()) {
1336 LOG(ERROR) << "vm_name is required";
1337 std::move(callback).Run(CrostiniResult::CLIENT_ERROR);
1338 return;
1339 }
1340 if (container_id.container_name.empty()) {
1341 LOG(ERROR) << "container_name is required";
1342 std::move(callback).Run(CrostiniResult::CLIENT_ERROR);
1343 return;
1344 }
1345 if (!GetCiceroneClient()->IsContainerStartedSignalConnected() ||
1346 !GetCiceroneClient()->IsContainerShutdownSignalConnected() ||
1347 !GetCiceroneClient()->IsLxdContainerStartingSignalConnected()) {
1348 LOG(ERROR) << "Async call to StartLxdContainer can't complete when signals "
1349 "are not connected.";
1350 std::move(callback).Run(CrostiniResult::CLIENT_ERROR);
1351 return;
1352 }
1353 vm_tools::cicerone::StartLxdContainerRequest request;
1354 request.set_vm_name(container_id.vm_name);
1355 request.set_container_name(container_id.container_name);
1356 request.set_owner_id(owner_id_);
1357 if (auto* integration_service =
1358 drive::DriveIntegrationServiceFactory::GetForProfile(profile_)) {
1359 request.set_drivefs_mount_path(
1360 integration_service->GetMountPointPath().value());
1361 }
1362 GetCiceroneClient()->StartLxdContainer(
1363 std::move(request),
1364 base::BindOnce(&CrostiniManager::OnStartLxdContainer,
1365 weak_ptr_factory_.GetWeakPtr(), std::move(container_id),
1366 std::move(callback)));
1367 }
1368
SetUpLxdContainerUser(ContainerId container_id,std::string container_username,BoolCallback callback)1369 void CrostiniManager::SetUpLxdContainerUser(ContainerId container_id,
1370 std::string container_username,
1371 BoolCallback callback) {
1372 if (container_id.vm_name.empty()) {
1373 LOG(ERROR) << "vm_name is required";
1374 std::move(callback).Run(/*success=*/false);
1375 return;
1376 }
1377 if (container_id.container_name.empty()) {
1378 LOG(ERROR) << "container_name is required";
1379 std::move(callback).Run(/*success=*/false);
1380 return;
1381 }
1382 if (container_username.empty()) {
1383 LOG(ERROR) << "container_username is required";
1384 std::move(callback).Run(/*success=*/false);
1385 return;
1386 }
1387 vm_tools::cicerone::SetUpLxdContainerUserRequest request;
1388 request.set_vm_name(container_id.vm_name);
1389 request.set_container_name(container_id.container_name);
1390 request.set_owner_id(owner_id_);
1391 request.set_container_username(std::move(container_username));
1392 GetCiceroneClient()->SetUpLxdContainerUser(
1393 std::move(request),
1394 base::BindOnce(&CrostiniManager::OnSetUpLxdContainerUser,
1395 weak_ptr_factory_.GetWeakPtr(), std::move(container_id),
1396 std::move(callback)));
1397 }
1398
ExportLxdContainer(ContainerId container_id,base::FilePath export_path,ExportLxdContainerResultCallback callback)1399 void CrostiniManager::ExportLxdContainer(
1400 ContainerId container_id,
1401 base::FilePath export_path,
1402 ExportLxdContainerResultCallback callback) {
1403 if (container_id.vm_name.empty()) {
1404 LOG(ERROR) << "vm_name is required";
1405 std::move(callback).Run(CrostiniResult::CLIENT_ERROR, 0, 0);
1406 return;
1407 }
1408 if (container_id.container_name.empty()) {
1409 LOG(ERROR) << "container_name is required";
1410 std::move(callback).Run(CrostiniResult::CLIENT_ERROR, 0, 0);
1411 return;
1412 }
1413 if (export_path.empty()) {
1414 LOG(ERROR) << "export_path is required";
1415 std::move(callback).Run(CrostiniResult::CLIENT_ERROR, 0, 0);
1416 return;
1417 }
1418
1419 if (export_lxd_container_callbacks_.find(container_id) !=
1420 export_lxd_container_callbacks_.end()) {
1421 LOG(ERROR) << "Export currently in progress for " << container_id;
1422 std::move(callback).Run(CrostiniResult::CONTAINER_EXPORT_IMPORT_FAILED, 0,
1423 0);
1424 return;
1425 }
1426 export_lxd_container_callbacks_.emplace(container_id, std::move(callback));
1427
1428 vm_tools::cicerone::ExportLxdContainerRequest request;
1429 request.set_vm_name(container_id.vm_name);
1430 request.set_container_name(container_id.container_name);
1431 request.set_owner_id(owner_id_);
1432 request.set_export_path(export_path.value());
1433 GetCiceroneClient()->ExportLxdContainer(
1434 std::move(request),
1435 base::BindOnce(&CrostiniManager::OnExportLxdContainer,
1436 weak_ptr_factory_.GetWeakPtr(), std::move(container_id)));
1437 }
1438
ImportLxdContainer(ContainerId container_id,base::FilePath import_path,CrostiniResultCallback callback)1439 void CrostiniManager::ImportLxdContainer(ContainerId container_id,
1440 base::FilePath import_path,
1441 CrostiniResultCallback callback) {
1442 if (container_id.vm_name.empty()) {
1443 LOG(ERROR) << "vm_name is required";
1444 std::move(callback).Run(CrostiniResult::CLIENT_ERROR);
1445 return;
1446 }
1447 if (container_id.container_name.empty()) {
1448 LOG(ERROR) << "container_name is required";
1449 std::move(callback).Run(CrostiniResult::CLIENT_ERROR);
1450 return;
1451 }
1452 if (import_path.empty()) {
1453 LOG(ERROR) << "import_path is required";
1454 std::move(callback).Run(CrostiniResult::CLIENT_ERROR);
1455 return;
1456 }
1457 if (import_lxd_container_callbacks_.find(container_id) !=
1458 import_lxd_container_callbacks_.end()) {
1459 LOG(ERROR) << "Import currently in progress for " << container_id;
1460 std::move(callback).Run(CrostiniResult::CONTAINER_EXPORT_IMPORT_FAILED);
1461 return;
1462 }
1463 import_lxd_container_callbacks_.emplace(container_id, std::move(callback));
1464
1465 vm_tools::cicerone::ImportLxdContainerRequest request;
1466 request.set_vm_name(container_id.vm_name);
1467 request.set_container_name(container_id.container_name);
1468 request.set_owner_id(owner_id_);
1469 request.set_import_path(import_path.value());
1470 GetCiceroneClient()->ImportLxdContainer(
1471 std::move(request),
1472 base::BindOnce(&CrostiniManager::OnImportLxdContainer,
1473 weak_ptr_factory_.GetWeakPtr(), std::move(container_id)));
1474 }
1475
CancelExportLxdContainer(ContainerId key)1476 void CrostiniManager::CancelExportLxdContainer(ContainerId key) {
1477 const auto& vm_name = key.vm_name;
1478 const auto& container_name = key.container_name;
1479 if (vm_name.empty()) {
1480 LOG(ERROR) << "vm_name is required";
1481 return;
1482 }
1483 if (container_name.empty()) {
1484 LOG(ERROR) << "container_name is required";
1485 return;
1486 }
1487
1488 auto it = export_lxd_container_callbacks_.find(key);
1489 if (it == export_lxd_container_callbacks_.end()) {
1490 LOG(ERROR) << "No export currently in progress for " << key;
1491 return;
1492 }
1493
1494 vm_tools::cicerone::CancelExportLxdContainerRequest request;
1495 request.set_vm_name(vm_name);
1496 request.set_owner_id(owner_id_);
1497 request.set_in_progress_container_name(container_name);
1498 GetCiceroneClient()->CancelExportLxdContainer(
1499 std::move(request),
1500 base::BindOnce(&CrostiniManager::OnCancelExportLxdContainer,
1501 weak_ptr_factory_.GetWeakPtr(), std::move(key)));
1502 }
1503
CancelImportLxdContainer(ContainerId key)1504 void CrostiniManager::CancelImportLxdContainer(ContainerId key) {
1505 const auto& vm_name = key.vm_name;
1506 const auto& container_name = key.container_name;
1507 if (vm_name.empty()) {
1508 LOG(ERROR) << "vm_name is required";
1509 return;
1510 }
1511 if (container_name.empty()) {
1512 LOG(ERROR) << "container_name is required";
1513 return;
1514 }
1515
1516 auto it = import_lxd_container_callbacks_.find(key);
1517 if (it == import_lxd_container_callbacks_.end()) {
1518 LOG(ERROR) << "No import currently in progress for " << key;
1519 return;
1520 }
1521
1522 vm_tools::cicerone::CancelImportLxdContainerRequest request;
1523 request.set_vm_name(vm_name);
1524 request.set_owner_id(owner_id_);
1525 request.set_in_progress_container_name(container_name);
1526 GetCiceroneClient()->CancelImportLxdContainer(
1527 std::move(request),
1528 base::BindOnce(&CrostiniManager::OnCancelImportLxdContainer,
1529 weak_ptr_factory_.GetWeakPtr(), std::move(key)));
1530 }
1531
1532 namespace {
ConvertVersion(ContainerVersion from)1533 vm_tools::cicerone::UpgradeContainerRequest::Version ConvertVersion(
1534 ContainerVersion from) {
1535 switch (from) {
1536 case ContainerVersion::STRETCH:
1537 return vm_tools::cicerone::UpgradeContainerRequest::DEBIAN_STRETCH;
1538 case ContainerVersion::BUSTER:
1539 return vm_tools::cicerone::UpgradeContainerRequest::DEBIAN_BUSTER;
1540 case ContainerVersion::UNKNOWN:
1541 default:
1542 return vm_tools::cicerone::UpgradeContainerRequest::UNKNOWN;
1543 }
1544 }
1545
1546 // Watches the Crostini restarter until the VM started phase, then aborts the
1547 // sequence.
1548 class AbortOnVmStartObserver : public CrostiniManager::RestartObserver {
1549 public:
AbortOnVmStartObserver(base::WeakPtr<CrostiniManager> crostini_manager)1550 explicit AbortOnVmStartObserver(
1551 base::WeakPtr<CrostiniManager> crostini_manager)
1552 : crostini_manager_(crostini_manager) {}
OnVmStarted(bool success)1553 void OnVmStarted(bool success) override {
1554 if (crostini_manager_) {
1555 crostini_manager_->AbortRestartCrostini(restart_id(), base::DoNothing());
1556 }
1557 }
1558
1559 private:
1560 base::WeakPtr<CrostiniManager> crostini_manager_;
1561 };
1562
1563 } // namespace
1564
UpgradeContainer(const ContainerId & key,ContainerVersion source_version,ContainerVersion target_version,CrostiniResultCallback callback)1565 void CrostiniManager::UpgradeContainer(const ContainerId& key,
1566 ContainerVersion source_version,
1567 ContainerVersion target_version,
1568 CrostiniResultCallback callback) {
1569 const auto& vm_name = key.vm_name;
1570 const auto& container_name = key.container_name;
1571 if (vm_name.empty()) {
1572 LOG(ERROR) << "vm_name is required";
1573 std::move(callback).Run(CrostiniResult::CLIENT_ERROR);
1574 return;
1575 }
1576 if (container_name.empty()) {
1577 LOG(ERROR) << "container_name is required";
1578 std::move(callback).Run(CrostiniResult::CLIENT_ERROR);
1579 return;
1580 }
1581 if (!GetCiceroneClient()->IsUpgradeContainerProgressSignalConnected()) {
1582 // Technically we could still start the upgrade, but we wouldn't be able
1583 // to detect when the upgrade completes, successfully or otherwise.
1584 LOG(ERROR) << "Attempted to upgrade container when progress signal not "
1585 "connected.";
1586 std::move(callback).Run(CrostiniResult::UPGRADE_CONTAINER_FAILED);
1587 return;
1588 }
1589 vm_tools::cicerone::UpgradeContainerRequest request;
1590 request.set_owner_id(owner_id_);
1591 request.set_vm_name(vm_name);
1592 request.set_container_name(container_name);
1593 request.set_source_version(ConvertVersion(source_version));
1594 request.set_target_version(ConvertVersion(target_version));
1595
1596 CrostiniResultCallback do_upgrade_container = base::BindOnce(
1597 [](base::WeakPtr<CrostiniManager> crostini_manager,
1598 vm_tools::cicerone::UpgradeContainerRequest request,
1599 CrostiniResultCallback final_callback, CrostiniResult result) {
1600 // When we fail to start the VM, we can't continue the upgrade.
1601 if (result != CrostiniResult::SUCCESS &&
1602 result != CrostiniResult::RESTART_ABORTED) {
1603 LOG(ERROR) << "Failed to restart the vm before attempting container "
1604 "upgrade. Result code "
1605 << static_cast<int>(result);
1606 std::move(final_callback)
1607 .Run(CrostiniResult::UPGRADE_CONTAINER_FAILED);
1608 return;
1609 }
1610 GetCiceroneClient()->UpgradeContainer(
1611 std::move(request),
1612 base::BindOnce(&CrostiniManager::OnUpgradeContainer,
1613 crostini_manager, std::move(final_callback)));
1614 },
1615 weak_ptr_factory_.GetWeakPtr(), std::move(request), std::move(callback));
1616
1617 if (!IsVmRunning(vm_name)) {
1618 RestartCrostini(key, std::move(do_upgrade_container));
1619 } else {
1620 std::move(do_upgrade_container).Run(CrostiniResult::SUCCESS);
1621 }
1622 }
1623
EnsureVmRunning(const ContainerId & key,CrostiniResultCallback callback)1624 void CrostiniManager::EnsureVmRunning(const ContainerId& key,
1625 CrostiniResultCallback callback) {
1626 const auto& vm_name = key.vm_name;
1627 const auto& container_name = key.container_name;
1628 if (vm_name.empty()) {
1629 LOG(ERROR) << "vm_name is required";
1630 std::move(callback).Run(CrostiniResult::CLIENT_ERROR);
1631 return;
1632 }
1633 if (container_name.empty()) {
1634 LOG(ERROR) << "container_name is required";
1635 std::move(callback).Run(CrostiniResult::CLIENT_ERROR);
1636 return;
1637 }
1638
1639 CrostiniResultCallback inner_callback = base::BindOnce(
1640 [](CrostiniResultCallback final_callback, CrostiniResult result) {
1641 if (result == CrostiniResult::SUCCESS ||
1642 result == CrostiniResult::RESTART_ABORTED) {
1643 // RESTART_ABORTED is expected when we successfully abort after
1644 // launching the VM, turn it into a success since that's what we were
1645 // asked for.
1646 std::move(final_callback).Run(CrostiniResult::SUCCESS);
1647 } else {
1648 std::move(final_callback).Run(result);
1649 }
1650 },
1651 std::move(callback));
1652
1653 if (!IsVmRunning(vm_name)) {
1654 RestartCrostini(key, std::move(inner_callback),
1655 new AbortOnVmStartObserver(weak_ptr_factory_.GetWeakPtr()));
1656 } else {
1657 std::move(inner_callback).Run(CrostiniResult::SUCCESS);
1658 }
1659 }
1660
CancelUpgradeContainer(const ContainerId & key,CrostiniResultCallback callback)1661 void CrostiniManager::CancelUpgradeContainer(const ContainerId& key,
1662 CrostiniResultCallback callback) {
1663 const auto& vm_name = key.vm_name;
1664 const auto& container_name = key.container_name;
1665 if (vm_name.empty()) {
1666 LOG(ERROR) << "vm_name is required";
1667 std::move(callback).Run(CrostiniResult::CLIENT_ERROR);
1668 return;
1669 }
1670 if (container_name.empty()) {
1671 LOG(ERROR) << "container_name is required";
1672 std::move(callback).Run(CrostiniResult::CLIENT_ERROR);
1673 return;
1674 }
1675 vm_tools::cicerone::CancelUpgradeContainerRequest request;
1676 request.set_owner_id(owner_id_);
1677 request.set_vm_name(vm_name);
1678 request.set_container_name(container_name);
1679 GetCiceroneClient()->CancelUpgradeContainer(
1680 std::move(request),
1681 base::BindOnce(&CrostiniManager::OnCancelUpgradeContainer,
1682 weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
1683 }
1684
LaunchContainerApplication(const ContainerId & container_id,std::string desktop_file_id,const std::vector<std::string> & files,bool display_scaled,CrostiniSuccessCallback callback)1685 void CrostiniManager::LaunchContainerApplication(
1686 const ContainerId& container_id,
1687 std::string desktop_file_id,
1688 const std::vector<std::string>& files,
1689 bool display_scaled,
1690 CrostiniSuccessCallback callback) {
1691 vm_tools::cicerone::LaunchContainerApplicationRequest request;
1692 request.set_owner_id(owner_id_);
1693 request.set_vm_name(container_id.vm_name);
1694 request.set_container_name(container_id.container_name);
1695 request.set_desktop_file_id(std::move(desktop_file_id));
1696 if (display_scaled) {
1697 request.set_display_scaling(
1698 vm_tools::cicerone::LaunchContainerApplicationRequest::SCALED);
1699 }
1700 std::copy(
1701 files.begin(), files.end(),
1702 google::protobuf::RepeatedFieldBackInserter(request.mutable_files()));
1703
1704 GetCiceroneClient()->LaunchContainerApplication(
1705 std::move(request),
1706 base::BindOnce(&CrostiniManager::OnLaunchContainerApplication,
1707 weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
1708 }
1709
GetContainerAppIcons(const ContainerId & container_id,std::vector<std::string> desktop_file_ids,int icon_size,int scale,GetContainerAppIconsCallback callback)1710 void CrostiniManager::GetContainerAppIcons(
1711 const ContainerId& container_id,
1712 std::vector<std::string> desktop_file_ids,
1713 int icon_size,
1714 int scale,
1715 GetContainerAppIconsCallback callback) {
1716 vm_tools::cicerone::ContainerAppIconRequest request;
1717 request.set_owner_id(owner_id_);
1718 request.set_vm_name(container_id.vm_name);
1719 request.set_container_name(container_id.container_name);
1720 google::protobuf::RepeatedPtrField<std::string> ids(
1721 std::make_move_iterator(desktop_file_ids.begin()),
1722 std::make_move_iterator(desktop_file_ids.end()));
1723 request.mutable_desktop_file_ids()->Swap(&ids);
1724 request.set_size(icon_size);
1725 request.set_scale(scale);
1726
1727 GetCiceroneClient()->GetContainerAppIcons(
1728 std::move(request),
1729 base::BindOnce(&CrostiniManager::OnGetContainerAppIcons,
1730 weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
1731 }
1732
GetLinuxPackageInfo(const ContainerId & container_id,std::string package_path,GetLinuxPackageInfoCallback callback)1733 void CrostiniManager::GetLinuxPackageInfo(
1734 const ContainerId& container_id,
1735 std::string package_path,
1736 GetLinuxPackageInfoCallback callback) {
1737 vm_tools::cicerone::LinuxPackageInfoRequest request;
1738 request.set_owner_id(CryptohomeIdForProfile(profile_));
1739 request.set_vm_name(container_id.vm_name);
1740 request.set_container_name(container_id.container_name);
1741 request.set_file_path(std::move(package_path));
1742
1743 GetCiceroneClient()->GetLinuxPackageInfo(
1744 std::move(request),
1745 base::BindOnce(&CrostiniManager::OnGetLinuxPackageInfo,
1746 weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
1747 }
1748
InstallLinuxPackage(const ContainerId & container_id,std::string package_path,InstallLinuxPackageCallback callback)1749 void CrostiniManager::InstallLinuxPackage(
1750 const ContainerId& container_id,
1751 std::string package_path,
1752 InstallLinuxPackageCallback callback) {
1753 if (!CrostiniFeatures::Get()->IsRootAccessAllowed(profile_)) {
1754 LOG(ERROR) << "Attempted to install package when root access to Crostini "
1755 "VM not allowed.";
1756 std::move(callback).Run(CrostiniResult::INSTALL_LINUX_PACKAGE_FAILED);
1757 return;
1758 }
1759
1760 if (!GetCiceroneClient()->IsInstallLinuxPackageProgressSignalConnected()) {
1761 // Technically we could still start the install, but we wouldn't be able
1762 // to detect when the install completes, successfully or otherwise.
1763 LOG(ERROR)
1764 << "Attempted to install package when progress signal not connected.";
1765 std::move(callback).Run(CrostiniResult::INSTALL_LINUX_PACKAGE_FAILED);
1766 return;
1767 }
1768
1769 vm_tools::cicerone::InstallLinuxPackageRequest request;
1770 request.set_owner_id(owner_id_);
1771 request.set_vm_name(container_id.vm_name);
1772 request.set_container_name(container_id.container_name);
1773 request.set_file_path(std::move(package_path));
1774
1775 GetCiceroneClient()->InstallLinuxPackage(
1776 std::move(request),
1777 base::BindOnce(&CrostiniManager::OnInstallLinuxPackage,
1778 weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
1779 }
1780
InstallLinuxPackageFromApt(const ContainerId & container_id,std::string package_id,InstallLinuxPackageCallback callback)1781 void CrostiniManager::InstallLinuxPackageFromApt(
1782 const ContainerId& container_id,
1783 std::string package_id,
1784 InstallLinuxPackageCallback callback) {
1785 if (!GetCiceroneClient()->IsInstallLinuxPackageProgressSignalConnected()) {
1786 // Technically we could still start the install, but we wouldn't be able
1787 // to detect when the install completes, successfully or otherwise.
1788 LOG(ERROR)
1789 << "Attempted to install package when progress signal not connected.";
1790 std::move(callback).Run(CrostiniResult::INSTALL_LINUX_PACKAGE_FAILED);
1791 return;
1792 }
1793
1794 vm_tools::cicerone::InstallLinuxPackageRequest request;
1795 request.set_owner_id(owner_id_);
1796 request.set_vm_name(container_id.vm_name);
1797 request.set_container_name(container_id.container_name);
1798 request.set_package_id(std::move(package_id));
1799
1800 GetCiceroneClient()->InstallLinuxPackage(
1801 std::move(request),
1802 base::BindOnce(&CrostiniManager::OnInstallLinuxPackage,
1803 weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
1804 }
1805
UninstallPackageOwningFile(const ContainerId & container_id,std::string desktop_file_id,CrostiniResultCallback callback)1806 void CrostiniManager::UninstallPackageOwningFile(
1807 const ContainerId& container_id,
1808 std::string desktop_file_id,
1809 CrostiniResultCallback callback) {
1810 if (!GetCiceroneClient()->IsUninstallPackageProgressSignalConnected()) {
1811 // Technically we could still start the uninstall, but we wouldn't be able
1812 // to detect when the uninstall completes, successfully or otherwise.
1813 LOG(ERROR) << "Attempted to uninstall package when progress signal not "
1814 "connected.";
1815 std::move(callback).Run(CrostiniResult::UNINSTALL_PACKAGE_FAILED);
1816 return;
1817 }
1818
1819 vm_tools::cicerone::UninstallPackageOwningFileRequest request;
1820 request.set_owner_id(owner_id_);
1821 request.set_vm_name(container_id.vm_name);
1822 request.set_container_name(container_id.container_name);
1823 request.set_desktop_file_id(std::move(desktop_file_id));
1824
1825 GetCiceroneClient()->UninstallPackageOwningFile(
1826 std::move(request),
1827 base::BindOnce(&CrostiniManager::OnUninstallPackageOwningFile,
1828 weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
1829 }
1830
GetContainerSshKeys(const ContainerId & container_id,GetContainerSshKeysCallback callback)1831 void CrostiniManager::GetContainerSshKeys(
1832 const ContainerId& container_id,
1833 GetContainerSshKeysCallback callback) {
1834 vm_tools::concierge::ContainerSshKeysRequest request;
1835 request.set_vm_name(container_id.vm_name);
1836 request.set_container_name(container_id.container_name);
1837 request.set_cryptohome_id(CryptohomeIdForProfile(profile_));
1838
1839 GetConciergeClient()->GetContainerSshKeys(
1840 std::move(request),
1841 base::BindOnce(&CrostiniManager::OnGetContainerSshKeys,
1842 weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
1843 }
1844
GetCrostiniDialogStatus(DialogType dialog_type) const1845 bool CrostiniManager::GetCrostiniDialogStatus(DialogType dialog_type) const {
1846 return open_crostini_dialogs_.count(dialog_type) == 1;
1847 }
1848
SetCrostiniDialogStatus(DialogType dialog_type,bool open)1849 void CrostiniManager::SetCrostiniDialogStatus(DialogType dialog_type,
1850 bool open) {
1851 if (open) {
1852 open_crostini_dialogs_.insert(dialog_type);
1853 } else {
1854 open_crostini_dialogs_.erase(dialog_type);
1855 }
1856 for (auto& observer : crostini_dialog_status_observers_) {
1857 observer.OnCrostiniDialogStatusChanged(dialog_type, open);
1858 }
1859 }
1860
AddCrostiniDialogStatusObserver(CrostiniDialogStatusObserver * observer)1861 void CrostiniManager::AddCrostiniDialogStatusObserver(
1862 CrostiniDialogStatusObserver* observer) {
1863 crostini_dialog_status_observers_.AddObserver(observer);
1864 }
1865
RemoveCrostiniDialogStatusObserver(CrostiniDialogStatusObserver * observer)1866 void CrostiniManager::RemoveCrostiniDialogStatusObserver(
1867 CrostiniDialogStatusObserver* observer) {
1868 crostini_dialog_status_observers_.RemoveObserver(observer);
1869 }
1870
AddCrostiniContainerPropertiesObserver(CrostiniContainerPropertiesObserver * observer)1871 void CrostiniManager::AddCrostiniContainerPropertiesObserver(
1872 CrostiniContainerPropertiesObserver* observer) {
1873 crostini_container_properties_observers_.AddObserver(observer);
1874 }
1875
RemoveCrostiniContainerPropertiesObserver(CrostiniContainerPropertiesObserver * observer)1876 void CrostiniManager::RemoveCrostiniContainerPropertiesObserver(
1877 CrostiniContainerPropertiesObserver* observer) {
1878 crostini_container_properties_observers_.RemoveObserver(observer);
1879 }
1880
AddContainerStartedObserver(ContainerStartedObserver * observer)1881 void CrostiniManager::AddContainerStartedObserver(
1882 ContainerStartedObserver* observer) {
1883 container_started_observers_.AddObserver(observer);
1884 }
1885
RemoveContainerStartedObserver(ContainerStartedObserver * observer)1886 void CrostiniManager::RemoveContainerStartedObserver(
1887 ContainerStartedObserver* observer) {
1888 container_started_observers_.RemoveObserver(observer);
1889 }
1890
AddContainerShutdownObserver(ContainerShutdownObserver * observer)1891 void CrostiniManager::AddContainerShutdownObserver(
1892 ContainerShutdownObserver* observer) {
1893 container_shutdown_observers_.AddObserver(observer);
1894 }
1895
RemoveContainerShutdownObserver(ContainerShutdownObserver * observer)1896 void CrostiniManager::RemoveContainerShutdownObserver(
1897 ContainerShutdownObserver* observer) {
1898 container_shutdown_observers_.RemoveObserver(observer);
1899 }
1900
OnDBusShuttingDownForTesting()1901 void CrostiniManager::OnDBusShuttingDownForTesting() {
1902 RemoveDBusObservers();
1903 }
1904
AddFileWatch(const ContainerId & container_id,const base::FilePath & path,BoolCallback callback)1905 void CrostiniManager::AddFileWatch(const ContainerId& container_id,
1906 const base::FilePath& path,
1907 BoolCallback callback) {
1908 vm_tools::cicerone::AddFileWatchRequest request;
1909 request.set_vm_name(container_id.vm_name);
1910 request.set_container_name(container_id.container_name);
1911 request.set_owner_id(CryptohomeIdForProfile(profile_));
1912 request.set_path(path.value());
1913 GetCiceroneClient()->AddFileWatch(
1914 request,
1915 base::BindOnce(
1916 [](BoolCallback callback,
1917 base::Optional<vm_tools::cicerone::AddFileWatchResponse>
1918 response) {
1919 std::move(callback).Run(
1920 response &&
1921 response->status() ==
1922 vm_tools::cicerone::AddFileWatchResponse::SUCCEEDED);
1923 },
1924 std::move(callback)));
1925 }
1926
RemoveFileWatch(const ContainerId & container_id,const base::FilePath & path)1927 void CrostiniManager::RemoveFileWatch(const ContainerId& container_id,
1928 const base::FilePath& path) {
1929 vm_tools::cicerone::RemoveFileWatchRequest request;
1930 request.set_vm_name(container_id.vm_name);
1931 request.set_container_name(container_id.container_name);
1932 request.set_owner_id(CryptohomeIdForProfile(profile_));
1933 request.set_path(path.value());
1934 GetCiceroneClient()->RemoveFileWatch(request, base::DoNothing());
1935 }
1936
AddFileChangeObserver(CrostiniFileChangeObserver * observer)1937 void CrostiniManager::AddFileChangeObserver(
1938 CrostiniFileChangeObserver* observer) {
1939 file_change_observers_.AddObserver(observer);
1940 }
1941
RemoveFileChangeObserver(CrostiniFileChangeObserver * observer)1942 void CrostiniManager::RemoveFileChangeObserver(
1943 CrostiniFileChangeObserver* observer) {
1944 file_change_observers_.RemoveObserver(observer);
1945 }
1946
OnFileWatchTriggered(const vm_tools::cicerone::FileWatchTriggeredSignal & signal)1947 void CrostiniManager::OnFileWatchTriggered(
1948 const vm_tools::cicerone::FileWatchTriggeredSignal& signal) {
1949 for (auto& observer : file_change_observers_) {
1950 observer.OnCrostiniFileChanged(
1951 ContainerId(signal.vm_name(), signal.container_name()),
1952 base::FilePath(signal.path()));
1953 }
1954 }
1955
GetVshSession(const ContainerId & container_id,int32_t host_vsh_pid,VshSessionCallback callback)1956 void CrostiniManager::GetVshSession(const ContainerId& container_id,
1957 int32_t host_vsh_pid,
1958 VshSessionCallback callback) {
1959 vm_tools::cicerone::GetVshSessionRequest request;
1960 request.set_vm_name(container_id.vm_name);
1961 request.set_container_name(container_id.container_name);
1962 request.set_owner_id(CryptohomeIdForProfile(profile_));
1963 request.set_host_vsh_pid(host_vsh_pid);
1964
1965 GetCiceroneClient()->GetVshSession(
1966 request, base::BindOnce(
1967 [](VshSessionCallback callback,
1968 base::Optional<vm_tools::cicerone::GetVshSessionResponse>
1969 response) {
1970 if (!response) {
1971 std::move(callback).Run(false, "Empty response", 0);
1972 } else {
1973 std::move(callback).Run(response->success(),
1974 response->failure_reason(),
1975 response->container_shell_pid());
1976 }
1977 },
1978 std::move(callback)));
1979 }
1980
RestartCrostini(ContainerId container_id,CrostiniResultCallback callback,RestartObserver * observer)1981 CrostiniManager::RestartId CrostiniManager::RestartCrostini(
1982 ContainerId container_id,
1983 CrostiniResultCallback callback,
1984 RestartObserver* observer) {
1985 return RestartCrostiniWithOptions(std::move(container_id), RestartOptions{},
1986 std::move(callback), observer);
1987 }
1988
RestartCrostiniWithOptions(ContainerId container_id,RestartOptions options,CrostiniResultCallback callback,RestartObserver * observer)1989 CrostiniManager::RestartId CrostiniManager::RestartCrostiniWithOptions(
1990 ContainerId container_id,
1991 RestartOptions options,
1992 CrostiniResultCallback callback,
1993 RestartObserver* observer) {
1994 if (GetCrostiniDialogStatus(DialogType::INSTALLER)) {
1995 base::UmaHistogramBoolean("Crostini.Setup.Started", true);
1996 } else {
1997 base::UmaHistogramBoolean("Crostini.Restarter.Started", true);
1998 }
1999 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
2000 // Currently, |remove_crostini_callbacks_| is only used just before running
2001 // CrostiniRemover. If that changes, then we should check for a currently
2002 // running uninstaller in some other way.
2003 if (!remove_crostini_callbacks_.empty()) {
2004 LOG(ERROR)
2005 << "Tried to install crostini while crostini uninstaller is running";
2006 std::move(callback).Run(CrostiniResult::CROSTINI_UNINSTALLER_RUNNING);
2007 return kUninitializedRestartId;
2008 }
2009
2010 auto restarter = std::make_unique<CrostiniRestarter>(
2011 profile_, this, container_id, std::move(options), std::move(callback));
2012 auto restart_id = restarter->restart_id();
2013 if (observer)
2014 restarter->AddObserver(observer);
2015 restarters_by_container_.emplace(container_id, restart_id);
2016 restarters_by_id_[restart_id] = std::move(restarter);
2017 if (restarters_by_container_.count(container_id) > 1) {
2018 VLOG(1) << "Already restarting " << container_id;
2019 } else {
2020 // ::Restart needs to be called after the restarter is inserted into
2021 // restarters_by_id_ because some tests will make the restart process
2022 // complete before ::Restart returns.
2023 restarters_by_id_[restart_id]->Restart();
2024 }
2025
2026 return restart_id;
2027 }
2028
AbortRestartCrostini(CrostiniManager::RestartId restart_id,base::OnceClosure callback)2029 void CrostiniManager::AbortRestartCrostini(
2030 CrostiniManager::RestartId restart_id,
2031 base::OnceClosure callback) {
2032 auto restarter_it = restarters_by_id_.find(restart_id);
2033 if (restarter_it == restarters_by_id_.end()) {
2034 // This can happen if a user cancels the install flow at the exact right
2035 // moment, for example.
2036 LOG(ERROR) << "Aborting a restarter that already finished";
2037 content::GetUIThreadTaskRunner({})->PostTask(FROM_HERE,
2038 std::move(callback));
2039 return;
2040 }
2041 restarter_it->second->Abort(base::BindOnce(
2042 &CrostiniManager::OnAbortRestartCrostini, weak_ptr_factory_.GetWeakPtr(),
2043 restart_id, std::move(callback)));
2044 }
2045
OnAbortRestartCrostini(CrostiniManager::RestartId restart_id,base::OnceClosure callback)2046 void CrostiniManager::OnAbortRestartCrostini(
2047 CrostiniManager::RestartId restart_id,
2048 base::OnceClosure callback) {
2049 auto restarter_it = restarters_by_id_.find(restart_id);
2050 if (restarter_it == restarters_by_id_.end()) {
2051 // This can happen if a user cancels the install flow at the exact right
2052 // moment, for example.
2053 LOG(ERROR) << "Aborting a restarter that already finished";
2054 std::move(callback).Run();
2055 return;
2056 }
2057
2058 const ContainerId key(restarter_it->second->container_id());
2059 auto range = restarters_by_container_.equal_range(key);
2060 for (auto it = range.first; it != range.second; ++it) {
2061 if (it->second == restart_id) {
2062 restarters_by_container_.erase(it);
2063 break;
2064 }
2065 }
2066 // This invalidates the iterator and potentially destroys the restarter,
2067 // so those shouldn't be accessed after this.
2068 restarters_by_id_.erase(restarter_it);
2069
2070 // Kick off the "next" (in no order) pending Restart() if any.
2071 auto pending_it = restarters_by_container_.find(key);
2072 if (pending_it != restarters_by_container_.end()) {
2073 restarters_by_id_[pending_it->second]->Restart();
2074 }
2075 std::move(callback).Run();
2076 }
2077
IsRestartPending(RestartId restart_id)2078 bool CrostiniManager::IsRestartPending(RestartId restart_id) {
2079 auto it = restarters_by_id_.find(restart_id);
2080 return it != restarters_by_id_.end() && !it->second->is_aborted();
2081 }
2082
AddShutdownContainerCallback(ContainerId container_id,base::OnceClosure shutdown_callback)2083 void CrostiniManager::AddShutdownContainerCallback(
2084 ContainerId container_id,
2085 base::OnceClosure shutdown_callback) {
2086 shutdown_container_callbacks_.emplace(std::move(container_id),
2087 std::move(shutdown_callback));
2088 }
2089
AddRemoveCrostiniCallback(RemoveCrostiniCallback remove_callback)2090 void CrostiniManager::AddRemoveCrostiniCallback(
2091 RemoveCrostiniCallback remove_callback) {
2092 remove_crostini_callbacks_.emplace_back(std::move(remove_callback));
2093 }
2094
AddLinuxPackageOperationProgressObserver(LinuxPackageOperationProgressObserver * observer)2095 void CrostiniManager::AddLinuxPackageOperationProgressObserver(
2096 LinuxPackageOperationProgressObserver* observer) {
2097 linux_package_operation_progress_observers_.AddObserver(observer);
2098 }
2099
RemoveLinuxPackageOperationProgressObserver(LinuxPackageOperationProgressObserver * observer)2100 void CrostiniManager::RemoveLinuxPackageOperationProgressObserver(
2101 LinuxPackageOperationProgressObserver* observer) {
2102 linux_package_operation_progress_observers_.RemoveObserver(observer);
2103 }
2104
AddPendingAppListUpdatesObserver(PendingAppListUpdatesObserver * observer)2105 void CrostiniManager::AddPendingAppListUpdatesObserver(
2106 PendingAppListUpdatesObserver* observer) {
2107 pending_app_list_updates_observers_.AddObserver(observer);
2108 }
2109
RemovePendingAppListUpdatesObserver(PendingAppListUpdatesObserver * observer)2110 void CrostiniManager::RemovePendingAppListUpdatesObserver(
2111 PendingAppListUpdatesObserver* observer) {
2112 pending_app_list_updates_observers_.RemoveObserver(observer);
2113 }
2114
AddExportContainerProgressObserver(ExportContainerProgressObserver * observer)2115 void CrostiniManager::AddExportContainerProgressObserver(
2116 ExportContainerProgressObserver* observer) {
2117 export_container_progress_observers_.AddObserver(observer);
2118 }
2119
RemoveExportContainerProgressObserver(ExportContainerProgressObserver * observer)2120 void CrostiniManager::RemoveExportContainerProgressObserver(
2121 ExportContainerProgressObserver* observer) {
2122 export_container_progress_observers_.RemoveObserver(observer);
2123 }
2124
AddImportContainerProgressObserver(ImportContainerProgressObserver * observer)2125 void CrostiniManager::AddImportContainerProgressObserver(
2126 ImportContainerProgressObserver* observer) {
2127 import_container_progress_observers_.AddObserver(observer);
2128 }
2129
RemoveImportContainerProgressObserver(ImportContainerProgressObserver * observer)2130 void CrostiniManager::RemoveImportContainerProgressObserver(
2131 ImportContainerProgressObserver* observer) {
2132 import_container_progress_observers_.RemoveObserver(observer);
2133 }
2134
AddUpgradeContainerProgressObserver(UpgradeContainerProgressObserver * observer)2135 void CrostiniManager::AddUpgradeContainerProgressObserver(
2136 UpgradeContainerProgressObserver* observer) {
2137 upgrade_container_progress_observers_.AddObserver(observer);
2138 }
2139
RemoveUpgradeContainerProgressObserver(UpgradeContainerProgressObserver * observer)2140 void CrostiniManager::RemoveUpgradeContainerProgressObserver(
2141 UpgradeContainerProgressObserver* observer) {
2142 upgrade_container_progress_observers_.RemoveObserver(observer);
2143 }
2144
AddVmShutdownObserver(VmShutdownObserver * observer)2145 void CrostiniManager::AddVmShutdownObserver(VmShutdownObserver* observer) {
2146 vm_shutdown_observers_.AddObserver(observer);
2147 }
RemoveVmShutdownObserver(VmShutdownObserver * observer)2148 void CrostiniManager::RemoveVmShutdownObserver(VmShutdownObserver* observer) {
2149 vm_shutdown_observers_.RemoveObserver(observer);
2150 }
2151
AddVmStartingObserver(chromeos::VmStartingObserver * observer)2152 void CrostiniManager::AddVmStartingObserver(
2153 chromeos::VmStartingObserver* observer) {
2154 vm_starting_observers_.AddObserver(observer);
2155 }
RemoveVmStartingObserver(chromeos::VmStartingObserver * observer)2156 void CrostiniManager::RemoveVmStartingObserver(
2157 chromeos::VmStartingObserver* observer) {
2158 vm_starting_observers_.RemoveObserver(observer);
2159 }
2160
OnCreateDiskImage(CreateDiskImageCallback callback,base::Optional<vm_tools::concierge::CreateDiskImageResponse> response)2161 void CrostiniManager::OnCreateDiskImage(
2162 CreateDiskImageCallback callback,
2163 base::Optional<vm_tools::concierge::CreateDiskImageResponse> response) {
2164 if (!response) {
2165 LOG(ERROR) << "Failed to create disk image. Empty response.";
2166 std::move(callback).Run(/*success=*/false,
2167 vm_tools::concierge::DISK_STATUS_UNKNOWN,
2168 base::FilePath());
2169 return;
2170 }
2171
2172 if (response->status() != vm_tools::concierge::DISK_STATUS_EXISTS &&
2173 response->status() != vm_tools::concierge::DISK_STATUS_CREATED) {
2174 LOG(ERROR) << "Failed to create disk image: " << response->failure_reason();
2175 std::move(callback).Run(/*success=*/false, response->status(),
2176 base::FilePath());
2177 return;
2178 }
2179
2180 std::move(callback).Run(/*success=*/true, response->status(),
2181 base::FilePath(response->disk_path()));
2182 }
2183
OnDestroyDiskImage(BoolCallback callback,base::Optional<vm_tools::concierge::DestroyDiskImageResponse> response)2184 void CrostiniManager::OnDestroyDiskImage(
2185 BoolCallback callback,
2186 base::Optional<vm_tools::concierge::DestroyDiskImageResponse> response) {
2187 if (!response) {
2188 LOG(ERROR) << "Failed to destroy disk image. Empty response.";
2189 std::move(callback).Run(/*success=*/false);
2190 return;
2191 }
2192
2193 if (response->status() != vm_tools::concierge::DISK_STATUS_DESTROYED &&
2194 response->status() != vm_tools::concierge::DISK_STATUS_DOES_NOT_EXIST) {
2195 LOG(ERROR) << "Failed to destroy disk image: "
2196 << response->failure_reason();
2197 std::move(callback).Run(/*success=*/false);
2198 return;
2199 }
2200
2201 std::move(callback).Run(/*success=*/true);
2202 }
2203
OnListVmDisks(ListVmDisksCallback callback,base::Optional<vm_tools::concierge::ListVmDisksResponse> response)2204 void CrostiniManager::OnListVmDisks(
2205 ListVmDisksCallback callback,
2206 base::Optional<vm_tools::concierge::ListVmDisksResponse> response) {
2207 if (!response) {
2208 LOG(ERROR) << "Failed to get list of VM disks. Empty response.";
2209 std::move(callback).Run(
2210 CrostiniResult::LIST_VM_DISKS_FAILED,
2211 profile_->GetPrefs()->GetInt64(prefs::kCrostiniLastDiskSize));
2212 return;
2213 }
2214
2215 if (!response->success()) {
2216 LOG(ERROR) << "Failed to list VM disks: " << response->failure_reason();
2217 std::move(callback).Run(
2218 CrostiniResult::LIST_VM_DISKS_FAILED,
2219 profile_->GetPrefs()->GetInt64(prefs::kCrostiniLastDiskSize));
2220 return;
2221 }
2222
2223 profile_->GetPrefs()->SetInt64(prefs::kCrostiniLastDiskSize,
2224 response->total_size());
2225 std::move(callback).Run(CrostiniResult::SUCCESS, response->total_size());
2226 }
2227
OnStartTerminaVm(std::string vm_name,BoolCallback callback,base::Optional<vm_tools::concierge::StartVmResponse> response)2228 void CrostiniManager::OnStartTerminaVm(
2229 std::string vm_name,
2230 BoolCallback callback,
2231 base::Optional<vm_tools::concierge::StartVmResponse> response) {
2232 if (!response) {
2233 LOG(ERROR) << "Failed to start termina vm. Empty response.";
2234 std::move(callback).Run(/*success=*/false);
2235 return;
2236 }
2237
2238 switch (response->mount_result()) {
2239 case vm_tools::concierge::StartVmResponse::PARTIAL_DATA_LOSS:
2240 EmitCorruptionStateMetric(CorruptionStates::MOUNT_ROLLED_BACK);
2241 break;
2242 case vm_tools::concierge::StartVmResponse::FAILURE:
2243 EmitCorruptionStateMetric(CorruptionStates::MOUNT_FAILED);
2244 break;
2245 default:
2246 break;
2247 }
2248
2249 // If the vm is already marked "running" run the callback.
2250 if (response->status() == vm_tools::concierge::VM_STATUS_RUNNING) {
2251 running_vms_[vm_name] =
2252 VmInfo{VmState::STARTED, std::move(response->vm_info())};
2253 std::move(callback).Run(/*success=*/true);
2254 return;
2255 }
2256
2257 // Any pending callbacks must exist from a previously running VM, and should
2258 // be marked as failed.
2259 InvokeAndErasePendingCallbacks(
2260 &export_lxd_container_callbacks_, vm_name,
2261 CrostiniResult::CONTAINER_EXPORT_IMPORT_FAILED_VM_STARTED, 0, 0);
2262 InvokeAndErasePendingCallbacks(
2263 &import_lxd_container_callbacks_, vm_name,
2264 CrostiniResult::CONTAINER_EXPORT_IMPORT_FAILED_VM_STARTED);
2265
2266 if (response->status() == vm_tools::concierge::VM_STATUS_FAILURE ||
2267 response->status() == vm_tools::concierge::VM_STATUS_UNKNOWN) {
2268 LOG(ERROR) << "Failed to start VM: " << response->failure_reason();
2269 // If we thought vms and containers were running before, they aren't now.
2270 running_vms_.erase(vm_name);
2271 running_containers_.erase(vm_name);
2272 std::move(callback).Run(/*success=*/false);
2273 return;
2274 }
2275
2276 // Otherwise, record the container start and run the callback after the VM
2277 // starts.
2278 DCHECK_EQ(response->status(), vm_tools::concierge::VM_STATUS_STARTING);
2279 VLOG(1) << "Awaiting TremplinStartedSignal for " << owner_id_ << ", "
2280 << vm_name;
2281 running_vms_[vm_name] =
2282 VmInfo{VmState::STARTING, std::move(response->vm_info())};
2283 // If we thought a container was running for this VM, we're wrong. This can
2284 // happen if the vm was formerly running, then stopped via crosh.
2285 running_containers_.erase(vm_name);
2286
2287 tremplin_started_callbacks_.emplace(
2288 vm_name, base::BindOnce(&CrostiniManager::OnStartTremplin,
2289 weak_ptr_factory_.GetWeakPtr(), vm_name,
2290 std::move(callback)));
2291 }
2292
OnStartTremplin(std::string vm_name,BoolCallback callback)2293 void CrostiniManager::OnStartTremplin(std::string vm_name,
2294 BoolCallback callback) {
2295 // Record the running vm.
2296 VLOG(1) << "Received TremplinStartedSignal, VM: " << owner_id_ << ", "
2297 << vm_name;
2298 UpdateVmState(vm_name, VmState::STARTED);
2299
2300 // Share fonts directory with the VM but don't persist as a shared path.
2301 guest_os::GuestOsSharePath::GetForProfile(profile_)->SharePath(
2302 vm_name, base::FilePath(file_manager::util::kSystemFontsPath),
2303 /*persist=*/false, base::DoNothing());
2304 // Share folders from Downloads, etc with VM.
2305 guest_os::GuestOsSharePath::GetForProfile(profile_)->SharePersistedPaths(
2306 vm_name, base::DoNothing());
2307
2308 // Run the original callback.
2309 std::move(callback).Run(/*success=*/true);
2310 }
2311
OnStartLxdProgress(const vm_tools::cicerone::StartLxdProgressSignal & signal)2312 void CrostiniManager::OnStartLxdProgress(
2313 const vm_tools::cicerone::StartLxdProgressSignal& signal) {
2314 if (signal.owner_id() != owner_id_)
2315 return;
2316 CrostiniResult result = CrostiniResult::UNKNOWN_ERROR;
2317
2318 switch (signal.status()) {
2319 case vm_tools::cicerone::StartLxdProgressSignal::STARTED:
2320 result = CrostiniResult::SUCCESS;
2321 break;
2322 case vm_tools::cicerone::StartLxdProgressSignal::STARTING:
2323 case vm_tools::cicerone::StartLxdProgressSignal::RECOVERING:
2324 // Still in-progress, keep waiting.
2325 return;
2326 case vm_tools::cicerone::StartLxdProgressSignal::FAILED:
2327 result = CrostiniResult::START_LXD_FAILED;
2328 break;
2329 default:
2330 break;
2331 }
2332
2333 if (result != CrostiniResult::SUCCESS) {
2334 LOG(ERROR) << "Failed to create container. VM: " << signal.vm_name()
2335 << " reason: " << signal.failure_reason();
2336 }
2337
2338 InvokeAndErasePendingCallbacks(&start_lxd_callbacks_, signal.vm_name(),
2339 result);
2340 }
2341
OnStopVm(std::string vm_name,CrostiniResultCallback callback,base::Optional<vm_tools::concierge::StopVmResponse> response)2342 void CrostiniManager::OnStopVm(
2343 std::string vm_name,
2344 CrostiniResultCallback callback,
2345 base::Optional<vm_tools::concierge::StopVmResponse> response) {
2346 if (!response) {
2347 LOG(ERROR) << "Failed to stop termina vm. Empty response.";
2348 std::move(callback).Run(CrostiniResult::VM_STOP_FAILED);
2349 return;
2350 }
2351
2352 if (!response->success()) {
2353 LOG(ERROR) << "Failed to stop VM: " << response->failure_reason();
2354 // TODO(rjwright): Change the service so that "Requested VM does not
2355 // exist" is not an error. "Requested VM does not exist" means that there
2356 // is a disk image for the VM but it is not running, either because it has
2357 // not been started or it has already been stopped. There's no need for
2358 // this to be an error, and making it a success will save us having to
2359 // discriminate on failure_reason here.
2360 if (response->failure_reason() != "Requested VM does not exist") {
2361 std::move(callback).Run(CrostiniResult::VM_STOP_FAILED);
2362 return;
2363 }
2364 }
2365
2366 std::move(callback).Run(CrostiniResult::SUCCESS);
2367 }
2368
OnVmStoppedCleanup(const std::string & vm_name)2369 void CrostiniManager::OnVmStoppedCleanup(const std::string& vm_name) {
2370 for (auto& observer : vm_shutdown_observers_) {
2371 observer.OnVmShutdown(vm_name);
2372 }
2373
2374 // Remove from running_vms_, and other vm-keyed state.
2375 running_vms_.erase(vm_name);
2376 running_containers_.erase(vm_name);
2377 InvokeAndErasePendingCallbacks(
2378 &export_lxd_container_callbacks_, vm_name,
2379 CrostiniResult::CONTAINER_EXPORT_IMPORT_FAILED_VM_STOPPED, 0, 0);
2380 InvokeAndErasePendingCallbacks(
2381 &import_lxd_container_callbacks_, vm_name,
2382 CrostiniResult::CONTAINER_EXPORT_IMPORT_FAILED_VM_STOPPED);
2383 // After we shut down a VM, we are no longer in a state where we need to
2384 // prompt for user cleanup.
2385 is_unclean_startup_ = false;
2386 }
2387
OnGetTerminaVmKernelVersion(GetTerminaVmKernelVersionCallback callback,base::Optional<vm_tools::concierge::GetVmEnterpriseReportingInfoResponse> response)2388 void CrostiniManager::OnGetTerminaVmKernelVersion(
2389 GetTerminaVmKernelVersionCallback callback,
2390 base::Optional<vm_tools::concierge::GetVmEnterpriseReportingInfoResponse>
2391 response) {
2392 if (!response) {
2393 LOG(ERROR) << "No reply to GetVmEnterpriseReportingInfo";
2394 std::move(callback).Run(base::nullopt);
2395 return;
2396 }
2397
2398 if (!response->success()) {
2399 LOG(ERROR) << "Error response for GetVmEnterpriseReportingInfo: "
2400 << response->failure_reason();
2401 std::move(callback).Run(base::nullopt);
2402 return;
2403 }
2404
2405 std::move(callback).Run(response->vm_kernel_version());
2406 }
2407
OnContainerStarted(const vm_tools::cicerone::ContainerStartedSignal & signal)2408 void CrostiniManager::OnContainerStarted(
2409 const vm_tools::cicerone::ContainerStartedSignal& signal) {
2410 if (signal.owner_id() != owner_id_)
2411 return;
2412 ContainerId container_id(signal.vm_name(), signal.container_name());
2413
2414 running_containers_.emplace(
2415 signal.vm_name(),
2416 ContainerInfo(signal.container_name(), signal.container_username(),
2417 signal.container_homedir(), signal.ipv4_address()));
2418
2419 // Additional setup might be required in case of default Crostini container
2420 // such as installing Ansible in default container and applying
2421 // pre-determined configuration to the default container.
2422 if (container_id == ContainerId::GetDefault() &&
2423 ShouldConfigureDefaultContainer(profile_)) {
2424 AnsibleManagementService::GetForProfile(profile_)
2425 ->ConfigureDefaultContainer(
2426 base::BindOnce(&CrostiniManager::OnDefaultContainerConfigured,
2427 weak_ptr_factory_.GetWeakPtr()));
2428 return;
2429 }
2430
2431 InvokeAndErasePendingContainerCallbacks(
2432 &start_container_callbacks_, container_id, CrostiniResult::SUCCESS);
2433
2434 if (signal.vm_name() == kCrostiniDefaultVmName) {
2435 AddShutdownContainerCallback(
2436 container_id,
2437 base::Bind(&CrostiniManager::DeallocateForwardedPortsCallback,
2438 weak_ptr_factory_.GetWeakPtr(), std::move(profile_),
2439 ContainerId(signal.vm_name(), signal.container_name())));
2440 if (signal.container_name() == kCrostiniDefaultContainerName) {
2441 for (auto& observer : container_started_observers_) {
2442 observer.OnContainerStarted(container_id);
2443 }
2444 }
2445 }
2446 }
2447
OnDefaultContainerConfigured(bool success)2448 void CrostiniManager::OnDefaultContainerConfigured(bool success) {
2449 CrostiniResult result = CrostiniResult::SUCCESS;
2450 if (!success) {
2451 LOG(ERROR) << "Failed to configure default Crostini container";
2452 result = CrostiniResult::CONTAINER_CONFIGURATION_FAILED;
2453 }
2454
2455 InvokeAndErasePendingContainerCallbacks(&start_container_callbacks_,
2456 ContainerId::GetDefault(), result);
2457 }
2458
OnGuestFileCorruption(const anomaly_detector::GuestFileCorruptionSignal & signal)2459 void CrostiniManager::OnGuestFileCorruption(
2460 const anomaly_detector::GuestFileCorruptionSignal& signal) {
2461 EmitCorruptionStateMetric(CorruptionStates::OTHER_CORRUPTION);
2462 }
2463
OnVmStarted(const vm_tools::concierge::VmStartedSignal & signal)2464 void CrostiniManager::OnVmStarted(
2465 const vm_tools::concierge::VmStartedSignal& signal) {}
2466
OnVmStopped(const vm_tools::concierge::VmStoppedSignal & signal)2467 void CrostiniManager::OnVmStopped(
2468 const vm_tools::concierge::VmStoppedSignal& signal) {
2469 if (signal.owner_id() != owner_id_)
2470 return;
2471 OnVmStoppedCleanup(signal.name());
2472 }
2473
OnContainerStartupFailed(const vm_tools::concierge::ContainerStartedSignal & signal)2474 void CrostiniManager::OnContainerStartupFailed(
2475 const vm_tools::concierge::ContainerStartedSignal& signal) {
2476 if (signal.owner_id() != owner_id_)
2477 return;
2478
2479 InvokeAndErasePendingContainerCallbacks(
2480 &start_container_callbacks_,
2481 ContainerId(signal.vm_name(), signal.container_name()),
2482 CrostiniResult::CONTAINER_START_FAILED);
2483 }
2484
OnContainerShutdown(const vm_tools::cicerone::ContainerShutdownSignal & signal)2485 void CrostiniManager::OnContainerShutdown(
2486 const vm_tools::cicerone::ContainerShutdownSignal& signal) {
2487 if (signal.owner_id() != owner_id_)
2488 return;
2489 ContainerId container_id(signal.vm_name(), signal.container_name());
2490 if (container_id == ContainerId::GetDefault()) {
2491 for (auto& observer : container_shutdown_observers_) {
2492 observer.OnContainerShutdown(container_id);
2493 }
2494 }
2495 // Find the callbacks to call, then erase them from the map.
2496 auto range_callbacks =
2497 shutdown_container_callbacks_.equal_range(container_id);
2498 for (auto it = range_callbacks.first; it != range_callbacks.second; ++it) {
2499 std::move(it->second).Run();
2500 }
2501 shutdown_container_callbacks_.erase(range_callbacks.first,
2502 range_callbacks.second);
2503
2504 // Remove from running containers multimap.
2505 auto range_containers = running_containers_.equal_range(signal.vm_name());
2506 for (auto it = range_containers.first; it != range_containers.second; ++it) {
2507 if (it->second.name == signal.container_name()) {
2508 running_containers_.erase(it);
2509 break;
2510 }
2511 }
2512 }
2513
OnInstallLinuxPackageProgress(const vm_tools::cicerone::InstallLinuxPackageProgressSignal & signal)2514 void CrostiniManager::OnInstallLinuxPackageProgress(
2515 const vm_tools::cicerone::InstallLinuxPackageProgressSignal& signal) {
2516 if (signal.owner_id() != owner_id_)
2517 return;
2518 if (signal.progress_percent() < 0 || signal.progress_percent() > 100) {
2519 LOG(ERROR) << "Received install progress with invalid progress of "
2520 << signal.progress_percent() << "%.";
2521 return;
2522 }
2523
2524 InstallLinuxPackageProgressStatus status;
2525 std::string error_message;
2526 switch (signal.status()) {
2527 case vm_tools::cicerone::InstallLinuxPackageProgressSignal::SUCCEEDED:
2528 status = InstallLinuxPackageProgressStatus::SUCCEEDED;
2529 break;
2530 case vm_tools::cicerone::InstallLinuxPackageProgressSignal::FAILED:
2531 LOG(ERROR) << "Install failed: " << signal.failure_details();
2532 status = InstallLinuxPackageProgressStatus::FAILED;
2533 error_message = signal.failure_details();
2534 break;
2535 case vm_tools::cicerone::InstallLinuxPackageProgressSignal::DOWNLOADING:
2536 status = InstallLinuxPackageProgressStatus::DOWNLOADING;
2537 break;
2538 case vm_tools::cicerone::InstallLinuxPackageProgressSignal::INSTALLING:
2539 status = InstallLinuxPackageProgressStatus::INSTALLING;
2540 break;
2541 default:
2542 NOTREACHED();
2543 }
2544
2545 ContainerId container_id(signal.vm_name(), signal.container_name());
2546 for (auto& observer : linux_package_operation_progress_observers_) {
2547 observer.OnInstallLinuxPackageProgress(
2548 container_id, status, signal.progress_percent(), error_message);
2549 }
2550 }
2551
OnUninstallPackageProgress(const vm_tools::cicerone::UninstallPackageProgressSignal & signal)2552 void CrostiniManager::OnUninstallPackageProgress(
2553 const vm_tools::cicerone::UninstallPackageProgressSignal& signal) {
2554 if (signal.owner_id() != owner_id_)
2555 return;
2556
2557 if (signal.progress_percent() < 0 || signal.progress_percent() > 100) {
2558 LOG(ERROR) << "Received uninstall progress with invalid progress of "
2559 << signal.progress_percent() << "%.";
2560 return;
2561 }
2562
2563 UninstallPackageProgressStatus status;
2564 switch (signal.status()) {
2565 case vm_tools::cicerone::UninstallPackageProgressSignal::SUCCEEDED:
2566 status = UninstallPackageProgressStatus::SUCCEEDED;
2567 break;
2568 case vm_tools::cicerone::UninstallPackageProgressSignal::FAILED:
2569 status = UninstallPackageProgressStatus::FAILED;
2570 LOG(ERROR) << "Uninstalled failed: " << signal.failure_details();
2571 break;
2572 case vm_tools::cicerone::UninstallPackageProgressSignal::UNINSTALLING:
2573 status = UninstallPackageProgressStatus::UNINSTALLING;
2574 break;
2575 default:
2576 NOTREACHED();
2577 }
2578
2579 ContainerId container_id(signal.vm_name(), signal.container_name());
2580 for (auto& observer : linux_package_operation_progress_observers_) {
2581 observer.OnUninstallPackageProgress(container_id, status,
2582 signal.progress_percent());
2583 }
2584 }
2585
OnApplyAnsiblePlaybookProgress(const vm_tools::cicerone::ApplyAnsiblePlaybookProgressSignal & signal)2586 void CrostiniManager::OnApplyAnsiblePlaybookProgress(
2587 const vm_tools::cicerone::ApplyAnsiblePlaybookProgressSignal& signal) {
2588 if (signal.owner_id() != owner_id_)
2589 return;
2590
2591 // TODO(okalitova): Add an observer.
2592 AnsibleManagementService::GetForProfile(profile_)
2593 ->OnApplyAnsiblePlaybookProgress(signal.status(),
2594 signal.failure_details());
2595 }
2596
OnUpgradeContainerProgress(const vm_tools::cicerone::UpgradeContainerProgressSignal & signal)2597 void CrostiniManager::OnUpgradeContainerProgress(
2598 const vm_tools::cicerone::UpgradeContainerProgressSignal& signal) {
2599 if (signal.owner_id() != owner_id_)
2600 return;
2601
2602 UpgradeContainerProgressStatus status;
2603 switch (signal.status()) {
2604 case vm_tools::cicerone::UpgradeContainerProgressSignal::SUCCEEDED:
2605 status = UpgradeContainerProgressStatus::SUCCEEDED;
2606 break;
2607 case vm_tools::cicerone::UpgradeContainerProgressSignal::UNKNOWN:
2608 case vm_tools::cicerone::UpgradeContainerProgressSignal::FAILED:
2609 status = UpgradeContainerProgressStatus::FAILED;
2610 LOG(ERROR) << "Upgrade failed: " << signal.failure_reason();
2611 break;
2612 case vm_tools::cicerone::UpgradeContainerProgressSignal::IN_PROGRESS:
2613 status = UpgradeContainerProgressStatus::UPGRADING;
2614 break;
2615 default:
2616 NOTREACHED();
2617 }
2618
2619 std::vector<std::string> progress_messages;
2620 progress_messages.reserve(signal.progress_messages().size());
2621 for (const auto& msg : signal.progress_messages()) {
2622 if (!msg.empty()) {
2623 // Blank lines aren't sent to observers.
2624 progress_messages.push_back(msg);
2625 }
2626 }
2627
2628 ContainerId container_id(signal.vm_name(), signal.container_name());
2629 for (auto& observer : upgrade_container_progress_observers_) {
2630 observer.OnUpgradeContainerProgress(container_id, status,
2631 progress_messages);
2632 }
2633 }
2634
OnUninstallPackageOwningFile(CrostiniResultCallback callback,base::Optional<vm_tools::cicerone::UninstallPackageOwningFileResponse> response)2635 void CrostiniManager::OnUninstallPackageOwningFile(
2636 CrostiniResultCallback callback,
2637 base::Optional<vm_tools::cicerone::UninstallPackageOwningFileResponse>
2638 response) {
2639 if (!response) {
2640 LOG(ERROR) << "Failed to uninstall Linux package. Empty response.";
2641 std::move(callback).Run(CrostiniResult::UNINSTALL_PACKAGE_FAILED);
2642 return;
2643 }
2644
2645 if (response->status() ==
2646 vm_tools::cicerone::UninstallPackageOwningFileResponse::FAILED) {
2647 LOG(ERROR) << "Failed to uninstall Linux package: "
2648 << response->failure_reason();
2649 std::move(callback).Run(CrostiniResult::UNINSTALL_PACKAGE_FAILED);
2650 return;
2651 }
2652
2653 if (response->status() ==
2654 vm_tools::cicerone::UninstallPackageOwningFileResponse::
2655 BLOCKING_OPERATION_IN_PROGRESS) {
2656 LOG(WARNING) << "Failed to uninstall Linux package, another operation is "
2657 "already active.";
2658 std::move(callback).Run(CrostiniResult::BLOCKING_OPERATION_ALREADY_ACTIVE);
2659 return;
2660 }
2661
2662 std::move(callback).Run(CrostiniResult::SUCCESS);
2663 }
2664
OnStartLxd(std::string vm_name,CrostiniResultCallback callback,base::Optional<vm_tools::cicerone::StartLxdResponse> response)2665 void CrostiniManager::OnStartLxd(
2666 std::string vm_name,
2667 CrostiniResultCallback callback,
2668 base::Optional<vm_tools::cicerone::StartLxdResponse> response) {
2669 if (!response) {
2670 LOG(ERROR) << "Failed to start lxd in vm. Empty response.";
2671 std::move(callback).Run(CrostiniResult::START_LXD_FAILED);
2672 return;
2673 }
2674
2675 switch (response->status()) {
2676 case vm_tools::cicerone::StartLxdResponse::STARTING:
2677 VLOG(1) << "Awaiting OnStartLxdProgressSignal for " << owner_id_ << ", "
2678 << vm_name;
2679 // The callback will be called when we receive the LxdContainerCreated
2680 // signal.
2681 start_lxd_callbacks_.emplace(std::move(vm_name), std::move(callback));
2682 break;
2683 case vm_tools::cicerone::StartLxdResponse::ALREADY_RUNNING:
2684 std::move(callback).Run(CrostiniResult::SUCCESS);
2685 break;
2686 default:
2687 LOG(ERROR) << "Failed to start LXD: " << response->failure_reason();
2688 std::move(callback).Run(CrostiniResult::START_LXD_FAILED);
2689 }
2690 }
2691
OnCreateLxdContainer(const ContainerId & container_id,CrostiniResultCallback callback,base::Optional<vm_tools::cicerone::CreateLxdContainerResponse> response)2692 void CrostiniManager::OnCreateLxdContainer(
2693 const ContainerId& container_id,
2694 CrostiniResultCallback callback,
2695 base::Optional<vm_tools::cicerone::CreateLxdContainerResponse> response) {
2696 if (!response) {
2697 LOG(ERROR) << "Failed to create lxd container in vm. Empty response.";
2698 std::move(callback).Run(CrostiniResult::CONTAINER_CREATE_FAILED);
2699 return;
2700 }
2701
2702 switch (response->status()) {
2703 case vm_tools::cicerone::CreateLxdContainerResponse::CREATING:
2704 VLOG(1) << "Awaiting LxdContainerCreatedSignal for " << owner_id_ << ", "
2705 << container_id;
2706 // The callback will be called when we receive the LxdContainerCreated
2707 // signal.
2708 create_lxd_container_callbacks_.emplace(container_id,
2709 std::move(callback));
2710 break;
2711 case vm_tools::cicerone::CreateLxdContainerResponse::EXISTS:
2712 std::move(callback).Run(CrostiniResult::SUCCESS);
2713 break;
2714 default:
2715 LOG(ERROR) << "Failed to start container: " << response->failure_reason();
2716 std::move(callback).Run(CrostiniResult::CONTAINER_CREATE_FAILED);
2717 }
2718 }
2719
OnStartLxdContainer(const ContainerId & container_id,CrostiniResultCallback callback,base::Optional<vm_tools::cicerone::StartLxdContainerResponse> response)2720 void CrostiniManager::OnStartLxdContainer(
2721 const ContainerId& container_id,
2722 CrostiniResultCallback callback,
2723 base::Optional<vm_tools::cicerone::StartLxdContainerResponse> response) {
2724 if (!response) {
2725 VLOG(1) << "Failed to start lxd container in vm. Empty response.";
2726 std::move(callback).Run(CrostiniResult::CONTAINER_START_FAILED);
2727 return;
2728 }
2729
2730 switch (response->status()) {
2731 case vm_tools::cicerone::StartLxdContainerResponse::UNKNOWN:
2732 case vm_tools::cicerone::StartLxdContainerResponse::FAILED:
2733 LOG(ERROR) << "Failed to start container: " << response->failure_reason();
2734 std::move(callback).Run(CrostiniResult::CONTAINER_START_FAILED);
2735 break;
2736
2737 case vm_tools::cicerone::StartLxdContainerResponse::STARTED:
2738 case vm_tools::cicerone::StartLxdContainerResponse::RUNNING:
2739 std::move(callback).Run(CrostiniResult::SUCCESS);
2740 break;
2741
2742 case vm_tools::cicerone::StartLxdContainerResponse::REMAPPING:
2743 // Run the update container dialog to warn users of delays.
2744 // The callback will be called when we receive the LxdContainerStarting
2745 PrepareShowCrostiniUpdateFilesystemView(profile_,
2746 CrostiniUISurface::kAppList);
2747 // signal.
2748 // Then perform the same steps as for starting.
2749 FALLTHROUGH;
2750 case vm_tools::cicerone::StartLxdContainerResponse::STARTING: {
2751 VLOG(1) << "Awaiting LxdContainerStartingSignal for " << owner_id_ << ", "
2752 << container_id;
2753 // The callback will be called when we receive the LxdContainerStarting
2754 // signal and (if successful) the ContainerStarted signal from Garcon..
2755 start_container_callbacks_.emplace(container_id, std::move(callback));
2756 break;
2757 }
2758 default:
2759 NOTREACHED();
2760 break;
2761 }
2762 if (response->has_os_release()) {
2763 SetContainerOsRelease(container_id, response->os_release());
2764 }
2765 }
2766
OnSetUpLxdContainerUser(const ContainerId & container_id,BoolCallback callback,base::Optional<vm_tools::cicerone::SetUpLxdContainerUserResponse> response)2767 void CrostiniManager::OnSetUpLxdContainerUser(
2768 const ContainerId& container_id,
2769 BoolCallback callback,
2770 base::Optional<vm_tools::cicerone::SetUpLxdContainerUserResponse>
2771 response) {
2772 if (!response) {
2773 LOG(ERROR) << "Failed to set up lxd container user. Empty response.";
2774 std::move(callback).Run(/*success=*/false);
2775 return;
2776 }
2777
2778 if (response->status() !=
2779 vm_tools::cicerone::SetUpLxdContainerUserResponse::SUCCESS &&
2780 response->status() !=
2781 vm_tools::cicerone::SetUpLxdContainerUserResponse::EXISTS) {
2782 LOG(ERROR) << "Failed to set up container user: "
2783 << response->failure_reason();
2784 std::move(callback).Run(/*success=*/false);
2785 return;
2786 }
2787 std::move(callback).Run(/*success=*/true);
2788 }
2789
OnLxdContainerCreated(const vm_tools::cicerone::LxdContainerCreatedSignal & signal)2790 void CrostiniManager::OnLxdContainerCreated(
2791 const vm_tools::cicerone::LxdContainerCreatedSignal& signal) {
2792 if (signal.owner_id() != owner_id_)
2793 return;
2794 ContainerId container_id(signal.vm_name(), signal.container_name());
2795 CrostiniResult result;
2796
2797 switch (signal.status()) {
2798 case vm_tools::cicerone::LxdContainerCreatedSignal::UNKNOWN:
2799 result = CrostiniResult::UNKNOWN_ERROR;
2800 break;
2801 case vm_tools::cicerone::LxdContainerCreatedSignal::CREATED:
2802 result = CrostiniResult::SUCCESS;
2803 AddNewLxdContainerToPrefs(profile_, container_id);
2804 break;
2805 case vm_tools::cicerone::LxdContainerCreatedSignal::DOWNLOAD_TIMED_OUT:
2806 result = CrostiniResult::CONTAINER_DOWNLOAD_TIMED_OUT;
2807 break;
2808 case vm_tools::cicerone::LxdContainerCreatedSignal::CANCELLED:
2809 result = CrostiniResult::CONTAINER_CREATE_CANCELLED;
2810 break;
2811 case vm_tools::cicerone::LxdContainerCreatedSignal::FAILED:
2812 result = CrostiniResult::CONTAINER_CREATE_FAILED;
2813 break;
2814 default:
2815 result = CrostiniResult::UNKNOWN_ERROR;
2816 break;
2817 }
2818
2819 if (result != CrostiniResult::SUCCESS) {
2820 LOG(ERROR) << "Failed to create container. ID: " << container_id
2821 << " reason: " << signal.failure_reason();
2822 }
2823
2824 InvokeAndErasePendingContainerCallbacks(&create_lxd_container_callbacks_,
2825 container_id, result);
2826 }
2827
OnLxdContainerDeleted(const vm_tools::cicerone::LxdContainerDeletedSignal & signal)2828 void CrostiniManager::OnLxdContainerDeleted(
2829 const vm_tools::cicerone::LxdContainerDeletedSignal& signal) {
2830 if (signal.owner_id() != owner_id_)
2831 return;
2832
2833 ContainerId container_id(signal.vm_name(), signal.container_name());
2834 bool success =
2835 signal.status() == vm_tools::cicerone::LxdContainerDeletedSignal::DELETED;
2836 if (success) {
2837 RemoveLxdContainerFromPrefs(profile_, container_id);
2838 } else {
2839 LOG(ERROR) << "Failed to delete container " << container_id << " : "
2840 << signal.failure_reason();
2841 }
2842
2843 // Find the callbacks to call, then erase them from the map.
2844 auto range = delete_lxd_container_callbacks_.equal_range(container_id);
2845 for (auto it = range.first; it != range.second; ++it) {
2846 std::move(it->second).Run(success);
2847 }
2848 delete_lxd_container_callbacks_.erase(range.first, range.second);
2849 }
2850
OnLxdContainerDownloading(const vm_tools::cicerone::LxdContainerDownloadingSignal & signal)2851 void CrostiniManager::OnLxdContainerDownloading(
2852 const vm_tools::cicerone::LxdContainerDownloadingSignal& signal) {
2853 if (owner_id_ != signal.owner_id()) {
2854 return;
2855 }
2856 ContainerId container_id(signal.vm_name(), signal.container_name());
2857 auto range = restarters_by_container_.equal_range(container_id);
2858 for (auto it = range.first; it != range.second; ++it) {
2859 restarters_by_id_[it->second]->OnContainerDownloading(
2860 signal.download_progress());
2861 }
2862 }
2863
OnTremplinStarted(const vm_tools::cicerone::TremplinStartedSignal & signal)2864 void CrostiniManager::OnTremplinStarted(
2865 const vm_tools::cicerone::TremplinStartedSignal& signal) {
2866 if (signal.owner_id() != owner_id_)
2867 return;
2868 // Find the callbacks to call, then erase them from the map.
2869 auto range = tremplin_started_callbacks_.equal_range(signal.vm_name());
2870 for (auto it = range.first; it != range.second; ++it) {
2871 std::move(it->second).Run();
2872 }
2873 tremplin_started_callbacks_.erase(range.first, range.second);
2874 }
2875
OnLxdContainerStarting(const vm_tools::cicerone::LxdContainerStartingSignal & signal)2876 void CrostiniManager::OnLxdContainerStarting(
2877 const vm_tools::cicerone::LxdContainerStartingSignal& signal) {
2878 if (signal.owner_id() != owner_id_)
2879 return;
2880 ContainerId container_id(signal.vm_name(), signal.container_name());
2881 CrostiniResult result;
2882
2883 switch (signal.status()) {
2884 case vm_tools::cicerone::LxdContainerStartingSignal::UNKNOWN:
2885 result = CrostiniResult::UNKNOWN_ERROR;
2886 break;
2887 case vm_tools::cicerone::LxdContainerStartingSignal::CANCELLED:
2888 result = CrostiniResult::CONTAINER_START_CANCELLED;
2889 break;
2890 case vm_tools::cicerone::LxdContainerStartingSignal::STARTED:
2891 result = CrostiniResult::SUCCESS;
2892 break;
2893 case vm_tools::cicerone::LxdContainerStartingSignal::FAILED:
2894 result = CrostiniResult::CONTAINER_START_FAILED;
2895 break;
2896 default:
2897 result = CrostiniResult::UNKNOWN_ERROR;
2898 break;
2899 }
2900
2901 if (result != CrostiniResult::SUCCESS) {
2902 LOG(ERROR) << "Failed to start container. ID: " << container_id
2903 << " reason: " << signal.failure_reason();
2904 }
2905
2906 if (result == CrostiniResult::SUCCESS && !GetContainerInfo(container_id)) {
2907 VLOG(1) << "Awaiting ContainerStarted signal from Garcon";
2908 return;
2909 }
2910 if (signal.has_os_release()) {
2911 SetContainerOsRelease(container_id, signal.os_release());
2912 }
2913
2914 InvokeAndErasePendingContainerCallbacks(&start_container_callbacks_,
2915 container_id, result);
2916 }
2917
OnLaunchContainerApplication(CrostiniSuccessCallback callback,base::Optional<vm_tools::cicerone::LaunchContainerApplicationResponse> response)2918 void CrostiniManager::OnLaunchContainerApplication(
2919 CrostiniSuccessCallback callback,
2920 base::Optional<vm_tools::cicerone::LaunchContainerApplicationResponse>
2921 response) {
2922 if (!response) {
2923 LOG(ERROR) << "Failed to launch application. Empty response.";
2924 std::move(callback).Run(/*success=*/false,
2925 "Failed to launch application. Empty response.");
2926 return;
2927 }
2928
2929 std::move(callback).Run(response->success(), response->failure_reason());
2930 }
2931
OnGetContainerAppIcons(GetContainerAppIconsCallback callback,base::Optional<vm_tools::cicerone::ContainerAppIconResponse> response)2932 void CrostiniManager::OnGetContainerAppIcons(
2933 GetContainerAppIconsCallback callback,
2934 base::Optional<vm_tools::cicerone::ContainerAppIconResponse> response) {
2935 std::vector<Icon> icons;
2936 if (!response) {
2937 LOG(ERROR) << "Failed to get container application icons. Empty response.";
2938 std::move(callback).Run(/*success=*/false, icons);
2939 return;
2940 }
2941
2942 for (auto& icon : *response->mutable_icons()) {
2943 icons.emplace_back(
2944 Icon{.desktop_file_id = std::move(*icon.mutable_desktop_file_id()),
2945 .content = std::move(*icon.mutable_icon())});
2946 }
2947 std::move(callback).Run(/*success=*/true, icons);
2948 }
2949
OnGetLinuxPackageInfo(GetLinuxPackageInfoCallback callback,base::Optional<vm_tools::cicerone::LinuxPackageInfoResponse> response)2950 void CrostiniManager::OnGetLinuxPackageInfo(
2951 GetLinuxPackageInfoCallback callback,
2952 base::Optional<vm_tools::cicerone::LinuxPackageInfoResponse> response) {
2953 LinuxPackageInfo result;
2954 if (!response) {
2955 LOG(ERROR) << "Failed to get Linux package info. Empty response.";
2956 result.success = false;
2957 // The error message is currently only used in a console message. If we
2958 // want to display it to the user, we'd need to localize this.
2959 result.failure_reason = "D-Bus response was empty.";
2960 std::move(callback).Run(result);
2961 return;
2962 }
2963
2964 if (!response->success()) {
2965 LOG(ERROR) << "Failed to get Linux package info: "
2966 << response->failure_reason();
2967 result.success = false;
2968 result.failure_reason = response->failure_reason();
2969 std::move(callback).Run(result);
2970 return;
2971 }
2972
2973 // The |package_id| field is formatted like "name;version;arch;data". We're
2974 // currently only interested in name and version.
2975 std::vector<std::string> split = base::SplitString(
2976 response->package_id(), ";", base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL);
2977 if (split.size() < 2 || split[0].empty() || split[1].empty()) {
2978 LOG(ERROR) << "Linux package info contained invalid package id: \""
2979 << response->package_id() << '"';
2980 result.success = false;
2981 result.failure_reason = "Linux package info contained invalid package id.";
2982 std::move(callback).Run(result);
2983 return;
2984 }
2985
2986 result.success = true;
2987 result.package_id = response->package_id();
2988 result.name = split[0];
2989 result.version = split[1];
2990 result.description = response->description();
2991 result.summary = response->summary();
2992
2993 std::move(callback).Run(result);
2994 }
2995
OnInstallLinuxPackage(InstallLinuxPackageCallback callback,base::Optional<vm_tools::cicerone::InstallLinuxPackageResponse> response)2996 void CrostiniManager::OnInstallLinuxPackage(
2997 InstallLinuxPackageCallback callback,
2998 base::Optional<vm_tools::cicerone::InstallLinuxPackageResponse> response) {
2999 if (!response) {
3000 LOG(ERROR) << "Failed to install Linux package. Empty response.";
3001 std::move(callback).Run(CrostiniResult::INSTALL_LINUX_PACKAGE_FAILED);
3002 return;
3003 }
3004
3005 if (response->status() ==
3006 vm_tools::cicerone::InstallLinuxPackageResponse::FAILED) {
3007 LOG(ERROR) << "Failed to install Linux package: "
3008 << response->failure_reason();
3009 std::move(callback).Run(CrostiniResult::INSTALL_LINUX_PACKAGE_FAILED);
3010 return;
3011 }
3012
3013 if (response->status() ==
3014 vm_tools::cicerone::InstallLinuxPackageResponse::INSTALL_ALREADY_ACTIVE) {
3015 LOG(WARNING) << "Failed to install Linux package, install already active.";
3016 std::move(callback).Run(CrostiniResult::BLOCKING_OPERATION_ALREADY_ACTIVE);
3017 return;
3018 }
3019
3020 std::move(callback).Run(CrostiniResult::SUCCESS);
3021 }
3022
OnGetContainerSshKeys(GetContainerSshKeysCallback callback,base::Optional<vm_tools::concierge::ContainerSshKeysResponse> response)3023 void CrostiniManager::OnGetContainerSshKeys(
3024 GetContainerSshKeysCallback callback,
3025 base::Optional<vm_tools::concierge::ContainerSshKeysResponse> response) {
3026 if (!response) {
3027 LOG(ERROR) << "Failed to get ssh keys. Empty response.";
3028 std::move(callback).Run(/*success=*/false, "", "", "");
3029 return;
3030 }
3031 std::move(callback).Run(/*success=*/true, response->container_public_key(),
3032 response->host_private_key(), response->hostname());
3033 }
3034
RemoveCrostini(std::string vm_name,RemoveCrostiniCallback callback)3035 void CrostiniManager::RemoveCrostini(std::string vm_name,
3036 RemoveCrostiniCallback callback) {
3037 AddRemoveCrostiniCallback(std::move(callback));
3038
3039 auto crostini_remover = base::MakeRefCounted<CrostiniRemover>(
3040 profile_, std::move(vm_name),
3041 base::BindOnce(&CrostiniManager::OnRemoveCrostini,
3042 weak_ptr_factory_.GetWeakPtr()));
3043
3044 auto abort_callback = base::BarrierClosure(
3045 restarters_by_id_.size(),
3046 base::BindOnce(
3047 [](scoped_refptr<CrostiniRemover> remover) {
3048 content::GetUIThreadTaskRunner({})->PostTask(
3049 FROM_HERE,
3050 base::BindOnce(&CrostiniRemover::RemoveCrostini, remover));
3051 },
3052 crostini_remover));
3053
3054 for (const auto& restarter_it : restarters_by_id_) {
3055 AbortRestartCrostini(restarter_it.first, abort_callback);
3056 }
3057 }
3058
OnRemoveCrostini(CrostiniResult result)3059 void CrostiniManager::OnRemoveCrostini(CrostiniResult result) {
3060 for (auto& callback : remove_crostini_callbacks_) {
3061 std::move(callback).Run(result);
3062 }
3063 remove_crostini_callbacks_.clear();
3064 }
3065
FinishRestart(CrostiniRestarter * restarter,CrostiniResult result)3066 void CrostiniManager::FinishRestart(CrostiniRestarter* restarter,
3067 CrostiniResult result) {
3068 auto range = restarters_by_container_.equal_range(restarter->container_id());
3069 std::vector<std::unique_ptr<CrostiniRestarter>> pending_restarters;
3070 // Erase first, because restarter->RunCallback() may modify our maps.
3071 for (auto it = range.first; it != range.second; ++it) {
3072 CrostiniManager::RestartId restart_id = it->second;
3073 pending_restarters.emplace_back(std::move(restarters_by_id_[restart_id]));
3074 restarters_by_id_.erase(restart_id);
3075 }
3076 restarters_by_container_.erase(range.first, range.second);
3077 for (const auto& pending_restarter : pending_restarters) {
3078 pending_restarter->result_ = result;
3079 pending_restarter->RunCallback(result);
3080 }
3081 }
3082
OnExportLxdContainer(const ContainerId & container_id,base::Optional<vm_tools::cicerone::ExportLxdContainerResponse> response)3083 void CrostiniManager::OnExportLxdContainer(
3084 const ContainerId& container_id,
3085 base::Optional<vm_tools::cicerone::ExportLxdContainerResponse> response) {
3086 auto it = export_lxd_container_callbacks_.find(container_id);
3087 if (it == export_lxd_container_callbacks_.end()) {
3088 LOG(ERROR) << "No export callback for " << container_id;
3089 return;
3090 }
3091
3092 if (!response) {
3093 LOG(ERROR) << "Failed to export lxd container. Empty response.";
3094 std::move(it->second)
3095 .Run(CrostiniResult::CONTAINER_EXPORT_IMPORT_FAILED, 0, 0);
3096 export_lxd_container_callbacks_.erase(it);
3097 return;
3098 }
3099
3100 // If export has started, the callback will be invoked when the
3101 // ExportLxdContainerProgressSignal signal indicates that export is
3102 // complete, otherwise this is an error.
3103 if (response->status() !=
3104 vm_tools::cicerone::ExportLxdContainerResponse::EXPORTING) {
3105 LOG(ERROR) << "Failed to export container: status=" << response->status()
3106 << ", failure_reason=" << response->failure_reason();
3107 std::move(it->second)
3108 .Run(CrostiniResult::CONTAINER_EXPORT_IMPORT_FAILED, 0, 0);
3109 export_lxd_container_callbacks_.erase(it);
3110 }
3111 }
3112
OnExportLxdContainerProgress(const vm_tools::cicerone::ExportLxdContainerProgressSignal & signal)3113 void CrostiniManager::OnExportLxdContainerProgress(
3114 const vm_tools::cicerone::ExportLxdContainerProgressSignal& signal) {
3115 using ProgressSignal = vm_tools::cicerone::ExportLxdContainerProgressSignal;
3116
3117 if (signal.owner_id() != owner_id_)
3118 return;
3119
3120 const ContainerId container_id(signal.vm_name(), signal.container_name());
3121
3122 CrostiniResult result;
3123 switch (signal.status()) {
3124 // TODO(juwa): Remove EXPORTING_[PACK|DOWNLOAD] once a new version of
3125 // tremplin has shipped.
3126 case ProgressSignal::EXPORTING_PACK:
3127 case ProgressSignal::EXPORTING_DOWNLOAD: {
3128 // If we are still exporting, call progress observers.
3129 const auto status = signal.status() == ProgressSignal::EXPORTING_PACK
3130 ? ExportContainerProgressStatus::PACK
3131 : ExportContainerProgressStatus::DOWNLOAD;
3132 for (auto& observer : export_container_progress_observers_) {
3133 observer.OnExportContainerProgress(container_id, status,
3134 signal.progress_percent(),
3135 signal.progress_speed());
3136 }
3137 return;
3138 }
3139 case ProgressSignal::EXPORTING_STREAMING: {
3140 const StreamingExportStatus status{
3141 .total_files = signal.total_input_files(),
3142 .total_bytes = signal.total_input_bytes(),
3143 .exported_files = signal.input_files_streamed(),
3144 .exported_bytes = signal.input_bytes_streamed()};
3145 for (auto& observer : export_container_progress_observers_) {
3146 observer.OnExportContainerProgress(container_id, status);
3147 }
3148 return;
3149 }
3150 case ProgressSignal::CANCELLED:
3151 result = CrostiniResult::CONTAINER_EXPORT_IMPORT_CANCELLED;
3152 break;
3153 case ProgressSignal::DONE:
3154 result = CrostiniResult::SUCCESS;
3155 break;
3156 default:
3157 result = CrostiniResult::CONTAINER_EXPORT_IMPORT_FAILED;
3158 LOG(ERROR) << "Failed during export container: " << signal.status()
3159 << ", " << signal.failure_reason();
3160 }
3161
3162 // Invoke original callback with either success or failure.
3163 auto it = export_lxd_container_callbacks_.find(container_id);
3164 if (it == export_lxd_container_callbacks_.end()) {
3165 LOG(ERROR) << "No export callback for " << container_id;
3166 return;
3167 }
3168 std::move(it->second)
3169 .Run(result, signal.input_bytes_streamed(), signal.bytes_exported());
3170 export_lxd_container_callbacks_.erase(it);
3171 }
3172
OnImportLxdContainer(const ContainerId & container_id,base::Optional<vm_tools::cicerone::ImportLxdContainerResponse> response)3173 void CrostiniManager::OnImportLxdContainer(
3174 const ContainerId& container_id,
3175 base::Optional<vm_tools::cicerone::ImportLxdContainerResponse> response) {
3176 auto it = import_lxd_container_callbacks_.find(container_id);
3177 if (it == import_lxd_container_callbacks_.end()) {
3178 LOG(ERROR) << "No import callback for " << container_id;
3179 return;
3180 }
3181
3182 if (!response) {
3183 LOG(ERROR) << "Failed to import lxd container. Empty response.";
3184 std::move(it->second).Run(CrostiniResult::CONTAINER_EXPORT_IMPORT_FAILED);
3185 import_lxd_container_callbacks_.erase(it);
3186 return;
3187 }
3188
3189 // If import has started, the callback will be invoked when the
3190 // ImportLxdContainerProgressSignal signal indicates that import is
3191 // complete, otherwise this is an error.
3192 if (response->status() !=
3193 vm_tools::cicerone::ImportLxdContainerResponse::IMPORTING) {
3194 LOG(ERROR) << "Failed to import container: " << response->failure_reason();
3195 std::move(it->second).Run(CrostiniResult::CONTAINER_EXPORT_IMPORT_FAILED);
3196 import_lxd_container_callbacks_.erase(it);
3197 }
3198 }
3199
OnImportLxdContainerProgress(const vm_tools::cicerone::ImportLxdContainerProgressSignal & signal)3200 void CrostiniManager::OnImportLxdContainerProgress(
3201 const vm_tools::cicerone::ImportLxdContainerProgressSignal& signal) {
3202 if (signal.owner_id() != owner_id_)
3203 return;
3204
3205 bool call_observers = false;
3206 bool call_original_callback = false;
3207 ImportContainerProgressStatus status;
3208 CrostiniResult result;
3209 switch (signal.status()) {
3210 case vm_tools::cicerone::ImportLxdContainerProgressSignal::IMPORTING_UPLOAD:
3211 call_observers = true;
3212 status = ImportContainerProgressStatus::UPLOAD;
3213 break;
3214 case vm_tools::cicerone::ImportLxdContainerProgressSignal::IMPORTING_UNPACK:
3215 call_observers = true;
3216 status = ImportContainerProgressStatus::UNPACK;
3217 break;
3218 case vm_tools::cicerone::ImportLxdContainerProgressSignal::CANCELLED:
3219 call_original_callback = true;
3220 result = CrostiniResult::CONTAINER_EXPORT_IMPORT_CANCELLED;
3221 break;
3222 case vm_tools::cicerone::ImportLxdContainerProgressSignal::DONE:
3223 call_original_callback = true;
3224 result = CrostiniResult::SUCCESS;
3225 break;
3226 case vm_tools::cicerone::ImportLxdContainerProgressSignal::
3227 FAILED_ARCHITECTURE:
3228 call_observers = true;
3229 status = ImportContainerProgressStatus::FAILURE_ARCHITECTURE;
3230 call_original_callback = true;
3231 result = CrostiniResult::CONTAINER_EXPORT_IMPORT_FAILED_ARCHITECTURE;
3232 break;
3233 case vm_tools::cicerone::ImportLxdContainerProgressSignal::FAILED_SPACE:
3234 call_observers = true;
3235 status = ImportContainerProgressStatus::FAILURE_SPACE;
3236 call_original_callback = true;
3237 result = CrostiniResult::CONTAINER_EXPORT_IMPORT_FAILED_SPACE;
3238 break;
3239 default:
3240 call_original_callback = true;
3241 result = CrostiniResult::CONTAINER_EXPORT_IMPORT_FAILED;
3242 LOG(ERROR) << "Failed during import container: " << signal.status()
3243 << ", " << signal.failure_reason();
3244 }
3245
3246 const ContainerId container_id(signal.vm_name(), signal.container_name());
3247
3248 if (call_observers) {
3249 for (auto& observer : import_container_progress_observers_) {
3250 observer.OnImportContainerProgress(
3251 container_id, status, signal.progress_percent(),
3252 signal.progress_speed(), signal.architecture_device(),
3253 signal.architecture_container(), signal.available_space(),
3254 signal.min_required_space());
3255 }
3256 }
3257
3258 // Invoke original callback with either success or failure.
3259 if (call_original_callback) {
3260 auto it = import_lxd_container_callbacks_.find(container_id);
3261 if (it == import_lxd_container_callbacks_.end()) {
3262 LOG(ERROR) << "No import callback for " << container_id;
3263 return;
3264 }
3265 std::move(it->second).Run(result);
3266 import_lxd_container_callbacks_.erase(it);
3267 }
3268 }
3269
OnCancelExportLxdContainer(const ContainerId & key,base::Optional<vm_tools::cicerone::CancelExportLxdContainerResponse> response)3270 void CrostiniManager::OnCancelExportLxdContainer(
3271 const ContainerId& key,
3272 base::Optional<vm_tools::cicerone::CancelExportLxdContainerResponse>
3273 response) {
3274 auto it = export_lxd_container_callbacks_.find(key);
3275 if (it == export_lxd_container_callbacks_.end()) {
3276 LOG(ERROR) << "No export callback for " << key;
3277 return;
3278 }
3279
3280 if (!response) {
3281 LOG(ERROR) << "Failed to cancel lxd container export. Empty response.";
3282 return;
3283 }
3284
3285 if (response->status() !=
3286 vm_tools::cicerone::CancelExportLxdContainerResponse::CANCEL_QUEUED) {
3287 LOG(ERROR) << "Failed to cancel lxd container export:"
3288 << " status=" << response->status()
3289 << ", failure_reason=" << response->failure_reason();
3290 }
3291 }
3292
OnCancelImportLxdContainer(const ContainerId & key,base::Optional<vm_tools::cicerone::CancelImportLxdContainerResponse> response)3293 void CrostiniManager::OnCancelImportLxdContainer(
3294 const ContainerId& key,
3295 base::Optional<vm_tools::cicerone::CancelImportLxdContainerResponse>
3296 response) {
3297 auto it = import_lxd_container_callbacks_.find(key);
3298 if (it == import_lxd_container_callbacks_.end()) {
3299 LOG(ERROR) << "No import callback for " << key;
3300 return;
3301 }
3302
3303 if (!response) {
3304 LOG(ERROR) << "Failed to cancel lxd container import. Empty response.";
3305 return;
3306 }
3307
3308 if (response->status() !=
3309 vm_tools::cicerone::CancelImportLxdContainerResponse::CANCEL_QUEUED) {
3310 LOG(ERROR) << "Failed to cancel lxd container import:"
3311 << " status=" << response->status()
3312 << ", failure_reason=" << response->failure_reason();
3313 }
3314 }
3315
OnUpgradeContainer(CrostiniResultCallback callback,base::Optional<vm_tools::cicerone::UpgradeContainerResponse> response)3316 void CrostiniManager::OnUpgradeContainer(
3317 CrostiniResultCallback callback,
3318 base::Optional<vm_tools::cicerone::UpgradeContainerResponse> response) {
3319 if (!response) {
3320 LOG(ERROR) << "Failed to start upgrading container. Empty response";
3321 std::move(callback).Run(CrostiniResult::UPGRADE_CONTAINER_FAILED);
3322 return;
3323 }
3324 CrostiniResult result = CrostiniResult::SUCCESS;
3325 switch (response->status()) {
3326 case vm_tools::cicerone::UpgradeContainerResponse::STARTED:
3327 break;
3328 case vm_tools::cicerone::UpgradeContainerResponse::ALREADY_RUNNING:
3329 result = CrostiniResult::UPGRADE_CONTAINER_ALREADY_RUNNING;
3330 LOG(ERROR) << "Upgrade already running. Nothing to do.";
3331 break;
3332 case vm_tools::cicerone::UpgradeContainerResponse::ALREADY_UPGRADED:
3333 LOG(ERROR) << "Container already upgraded. Nothing to do.";
3334 result = CrostiniResult::UPGRADE_CONTAINER_ALREADY_UPGRADED;
3335 break;
3336 case vm_tools::cicerone::UpgradeContainerResponse::NOT_SUPPORTED:
3337 result = CrostiniResult::UPGRADE_CONTAINER_NOT_SUPPORTED;
3338 break;
3339 case vm_tools::cicerone::UpgradeContainerResponse::UNKNOWN:
3340 case vm_tools::cicerone::UpgradeContainerResponse::FAILED:
3341 default:
3342 LOG(ERROR) << "Upgrade container failed. Failure reason "
3343 << response->failure_reason();
3344 result = CrostiniResult::UPGRADE_CONTAINER_FAILED;
3345 break;
3346 }
3347 std::move(callback).Run(result);
3348 }
3349
OnCancelUpgradeContainer(CrostiniResultCallback callback,base::Optional<vm_tools::cicerone::CancelUpgradeContainerResponse> response)3350 void CrostiniManager::OnCancelUpgradeContainer(
3351 CrostiniResultCallback callback,
3352 base::Optional<vm_tools::cicerone::CancelUpgradeContainerResponse>
3353 response) {
3354 if (!response) {
3355 LOG(ERROR) << "Failed to cancel upgrading container. Empty response";
3356 std::move(callback).Run(CrostiniResult::CANCEL_UPGRADE_CONTAINER_FAILED);
3357 return;
3358 }
3359 CrostiniResult result = CrostiniResult::SUCCESS;
3360 switch (response->status()) {
3361 case vm_tools::cicerone::CancelUpgradeContainerResponse::CANCELLED:
3362 case vm_tools::cicerone::CancelUpgradeContainerResponse::NOT_RUNNING:
3363 break;
3364
3365 case vm_tools::cicerone::CancelUpgradeContainerResponse::UNKNOWN:
3366 case vm_tools::cicerone::CancelUpgradeContainerResponse::FAILED:
3367 default:
3368 LOG(ERROR) << "Cancel upgrade container failed. Failure reason "
3369 << response->failure_reason();
3370 result = CrostiniResult::CANCEL_UPGRADE_CONTAINER_FAILED;
3371 break;
3372 }
3373 std::move(callback).Run(result);
3374 }
3375
OnPendingAppListUpdates(const vm_tools::cicerone::PendingAppListUpdatesSignal & signal)3376 void CrostiniManager::OnPendingAppListUpdates(
3377 const vm_tools::cicerone::PendingAppListUpdatesSignal& signal) {
3378 ContainerId container_id(signal.vm_name(), signal.container_name());
3379 for (auto& observer : pending_app_list_updates_observers_) {
3380 observer.OnPendingAppListUpdates(container_id, signal.count());
3381 }
3382 }
3383
3384 // TODO(danielng): Consider handling instant tethering.
ActiveNetworksChanged(const std::vector<const chromeos::NetworkState * > & active_networks)3385 void CrostiniManager::ActiveNetworksChanged(
3386 const std::vector<const chromeos::NetworkState*>& active_networks) {
3387 chromeos::NetworkStateHandler::NetworkStateList active_physical_networks;
3388 chromeos::NetworkHandler::Get()
3389 ->network_state_handler()
3390 ->GetActiveNetworkListByType(chromeos::NetworkTypePattern::Physical(),
3391 &active_physical_networks);
3392 if (active_physical_networks.empty())
3393 return;
3394 const chromeos::NetworkState* network = active_physical_networks.at(0);
3395 if (!network)
3396 return;
3397 const chromeos::DeviceState* device =
3398 chromeos::NetworkHandler::Get()->network_state_handler()->GetDeviceState(
3399 network->device_path());
3400 if (!device)
3401 return;
3402 if (CrostiniFeatures::Get()->IsPortForwardingAllowed(profile_)) {
3403 crostini::CrostiniPortForwarder::GetForProfile(profile_)
3404 ->ActiveNetworksChanged(device->interface());
3405 }
3406 }
3407
SuspendImminent(power_manager::SuspendImminent::Reason reason)3408 void CrostiniManager::SuspendImminent(
3409 power_manager::SuspendImminent::Reason reason) {
3410 auto info = GetContainerInfo(ContainerId::GetDefault());
3411 if (!info || !info->sshfs_mounted) {
3412 return;
3413 }
3414
3415 // Block suspend and try to unmount sshfs (https://crbug.com/968060).
3416 auto token = base::UnguessableToken::Create();
3417 chromeos::PowerManagerClient::Get()->BlockSuspend(token, "CrostiniManager");
3418 file_manager::VolumeManager::Get(profile_)->RemoveSshfsCrostiniVolume(
3419 file_manager::util::GetCrostiniMountDirectory(profile_),
3420 base::BindOnce(&CrostiniManager::OnRemoveSshfsCrostiniVolume,
3421 weak_ptr_factory_.GetWeakPtr(), token));
3422 }
3423
SuspendDone(const base::TimeDelta & sleep_duration)3424 void CrostiniManager::SuspendDone(const base::TimeDelta& sleep_duration) {
3425 // https://crbug.com/968060. Sshfs is unmounted before suspend,
3426 // call RestartCrostini to force remount if container is running.
3427 ContainerId container_id = ContainerId::GetDefault();
3428 if (GetContainerInfo(container_id)) {
3429 RestartCrostini(container_id, base::DoNothing());
3430 }
3431 }
3432
OnRemoveSshfsCrostiniVolume(base::UnguessableToken power_manager_suspend_token,bool result)3433 void CrostiniManager::OnRemoveSshfsCrostiniVolume(
3434 base::UnguessableToken power_manager_suspend_token,
3435 bool result) {
3436 if (result) {
3437 SetContainerSshfsMounted(ContainerId::GetDefault(), false);
3438 }
3439 // Need to let the device suspend after cleaning up.
3440 chromeos::PowerManagerClient::Get()->UnblockSuspend(
3441 power_manager_suspend_token);
3442 }
3443
RemoveUncleanSshfsMounts()3444 void CrostiniManager::RemoveUncleanSshfsMounts() {
3445 file_manager::VolumeManager::Get(profile_)->RemoveSshfsCrostiniVolume(
3446 file_manager::util::GetCrostiniMountDirectory(profile_),
3447 base::DoNothing());
3448 }
3449
DeallocateForwardedPortsCallback(Profile * profile,const ContainerId & container_id)3450 void CrostiniManager::DeallocateForwardedPortsCallback(
3451 Profile* profile,
3452 const ContainerId& container_id) {
3453 crostini::CrostiniPortForwarder::GetForProfile(profile)
3454 ->DeactivateAllActivePorts(container_id);
3455 }
3456
AddCrostiniMicSharingEnabledObserver(CrostiniMicSharingEnabledObserver * observer)3457 void CrostiniManager::AddCrostiniMicSharingEnabledObserver(
3458 CrostiniMicSharingEnabledObserver* observer) {
3459 crostini_mic_sharing_enabled_observers_.AddObserver(observer);
3460 }
3461
RemoveCrostiniMicSharingEnabledObserver(CrostiniMicSharingEnabledObserver * observer)3462 void CrostiniManager::RemoveCrostiniMicSharingEnabledObserver(
3463 CrostiniMicSharingEnabledObserver* observer) {
3464 crostini_mic_sharing_enabled_observers_.RemoveObserver(observer);
3465 }
3466
SetCrostiniMicSharingEnabled(bool enabled)3467 void CrostiniManager::SetCrostiniMicSharingEnabled(bool enabled) {
3468 if (crostini_mic_sharing_enabled_ == enabled)
3469 return;
3470 crostini_mic_sharing_enabled_ = enabled;
3471 for (auto& observer : crostini_mic_sharing_enabled_observers_) {
3472 observer.OnCrostiniMicSharingEnabledChanged(crostini_mic_sharing_enabled_);
3473 }
3474 }
3475
EmitVmDiskTypeMetric(const std::string vm_name)3476 void CrostiniManager::EmitVmDiskTypeMetric(const std::string vm_name) {
3477 if ((time_of_last_disk_type_metric_ + base::TimeDelta::FromHours(12)) >
3478 base::Time::Now()) {
3479 // Only bother doing this once every 12 hours. We care about the number of
3480 // users in each histogram bucket, not the number of times restarted. We
3481 // do this 12-hourly instead of only at first launch since Crostini can
3482 // last for a while, and we want to ensure that e.g. looking at N-day
3483 // aggregation doesn't miss people who've got a long-running session.
3484 return;
3485 }
3486 time_of_last_disk_type_metric_ = base::Time::Now();
3487
3488 vm_tools::concierge::ListVmDisksRequest request;
3489 request.set_cryptohome_id(CryptohomeIdForProfile(profile_));
3490 request.set_storage_location(vm_tools::concierge::STORAGE_CRYPTOHOME_ROOT);
3491 request.set_vm_name(vm_name);
3492 GetConciergeClient()->ListVmDisks(
3493 std::move(request),
3494 base::BindOnce([](base::Optional<vm_tools::concierge::ListVmDisksResponse>
3495 response) {
3496 if (response) {
3497 if (response.value().images().size() != 1) {
3498 LOG(ERROR)
3499 << "Got multiple disks for image, don't know how to proceed";
3500 base::UmaHistogramEnumeration("Crostini.DiskType",
3501 CrostiniDiskImageType::kMultiDisk);
3502 return;
3503 }
3504 auto image = response.value().images().Get(0);
3505 if (image.image_type() ==
3506 vm_tools::concierge::DiskImageType::DISK_IMAGE_QCOW2) {
3507 base::UmaHistogramEnumeration("Crostini.DiskType",
3508 CrostiniDiskImageType::kQCow2Sparse);
3509 } else if (image.image_type() ==
3510 vm_tools::concierge::DiskImageType::DISK_IMAGE_RAW) {
3511 if (image.user_chosen_size()) {
3512 base::UmaHistogramEnumeration(
3513 "Crostini.DiskType", CrostiniDiskImageType::kRawPreallocated);
3514 } else {
3515 base::UmaHistogramEnumeration("Crostini.DiskType",
3516 CrostiniDiskImageType::kRawSparse);
3517 }
3518 } else {
3519 // We shouldn't get back the other disk types for Crostini disks.
3520 base::UmaHistogramEnumeration("Crostini.DiskType",
3521 CrostiniDiskImageType::kUnknown);
3522 }
3523 }
3524 }));
3525 }
3526 } // namespace crostini
3527