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