1 //
2 // Copyright (C) 2022 The Android Open Source Project
3 //
4 // Licensed under the Apache License, Version 2.0 (the "License");
5 // you may not use this file except in compliance with the License.
6 // You may obtain a copy of the License at
7 //
8 //      http://www.apache.org/licenses/LICENSE-2.0
9 //
10 // Unless required by applicable law or agreed to in writing, software
11 // distributed under the License is distributed on an "AS IS" BASIS,
12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 // See the License for the specific language governing permissions and
14 // limitations under the License.
15 
16 #include "host/commands/assemble_cvd/disk_builder.h"
17 
18 #include <fstream>
19 #include <sstream>
20 #include <string>
21 #include <vector>
22 
23 #include <android-base/file.h>
24 
25 #include "common/libs/fs/shared_fd.h"
26 #include "common/libs/utils/files.h"
27 #include "host/libs/config/cuttlefish_config.h"
28 #include "host/libs/image_aggregator/image_aggregator.h"
29 #include "host/libs/vm_manager/crosvm_manager.h"
30 
31 namespace cuttlefish {
32 
LastUpdatedInputDisk(const std::vector<ImagePartition> & partitions)33 static std::chrono::system_clock::time_point LastUpdatedInputDisk(
34     const std::vector<ImagePartition>& partitions) {
35   std::chrono::system_clock::time_point ret;
36   for (auto& partition : partitions) {
37     if (partition.label == "frp") {
38       continue;
39     }
40 
41     auto partition_mod_time = FileModificationTime(partition.image_file_path);
42     if (partition_mod_time > ret) {
43       ret = partition_mod_time;
44     }
45   }
46   return ret;
47 }
48 
EntireDisk(std::string disk)49 DiskBuilder& DiskBuilder::EntireDisk(std::string disk) & {
50   entire_disk_ = std::move(disk);
51   return *this;
52 }
EntireDisk(std::string disk)53 DiskBuilder DiskBuilder::EntireDisk(std::string disk) && {
54   entire_disk_ = std::move(disk);
55   return *this;
56 }
57 
Partitions(std::vector<ImagePartition> partitions)58 DiskBuilder& DiskBuilder::Partitions(std::vector<ImagePartition> partitions) & {
59   partitions_ = std::move(partitions);
60   return *this;
61 }
Partitions(std::vector<ImagePartition> partitions)62 DiskBuilder DiskBuilder::Partitions(std::vector<ImagePartition> partitions) && {
63   partitions_ = std::move(partitions);
64   return *this;
65 }
66 
HeaderPath(std::string header_path)67 DiskBuilder& DiskBuilder::HeaderPath(std::string header_path) & {
68   header_path_ = std::move(header_path);
69   return *this;
70 }
HeaderPath(std::string header_path)71 DiskBuilder DiskBuilder::HeaderPath(std::string header_path) && {
72   header_path_ = std::move(header_path);
73   return *this;
74 }
75 
FooterPath(std::string footer_path)76 DiskBuilder& DiskBuilder::FooterPath(std::string footer_path) & {
77   footer_path_ = std::move(footer_path);
78   return *this;
79 }
FooterPath(std::string footer_path)80 DiskBuilder DiskBuilder::FooterPath(std::string footer_path) && {
81   footer_path_ = std::move(footer_path);
82   return *this;
83 }
84 
CrosvmPath(std::string crosvm_path)85 DiskBuilder& DiskBuilder::CrosvmPath(std::string crosvm_path) & {
86   crosvm_path_ = std::move(crosvm_path);
87   return *this;
88 }
CrosvmPath(std::string crosvm_path)89 DiskBuilder DiskBuilder::CrosvmPath(std::string crosvm_path) && {
90   crosvm_path_ = std::move(crosvm_path);
91   return *this;
92 }
93 
VmManager(VmmMode vm_manager)94 DiskBuilder& DiskBuilder::VmManager(VmmMode vm_manager) & {
95   vm_manager_ = std::move(vm_manager);
96   return *this;
97 }
VmManager(VmmMode vm_manager)98 DiskBuilder DiskBuilder::VmManager(VmmMode vm_manager) && {
99   vm_manager_ = std::move(vm_manager);
100   return *this;
101 }
102 
ConfigPath(std::string config_path)103 DiskBuilder& DiskBuilder::ConfigPath(std::string config_path) & {
104   config_path_ = std::move(config_path);
105   return *this;
106 }
ConfigPath(std::string config_path)107 DiskBuilder DiskBuilder::ConfigPath(std::string config_path) && {
108   config_path_ = std::move(config_path);
109   return *this;
110 }
111 
CompositeDiskPath(std::string composite_disk_path)112 DiskBuilder& DiskBuilder::CompositeDiskPath(std::string composite_disk_path) & {
113   composite_disk_path_ = std::move(composite_disk_path);
114   return *this;
115 }
CompositeDiskPath(std::string composite_disk_path)116 DiskBuilder DiskBuilder::CompositeDiskPath(std::string composite_disk_path) && {
117   composite_disk_path_ = std::move(composite_disk_path);
118   return *this;
119 }
120 
OverlayPath(std::string overlay_path)121 DiskBuilder& DiskBuilder::OverlayPath(std::string overlay_path) & {
122   overlay_path_ = std::move(overlay_path);
123   return *this;
124 }
OverlayPath(std::string overlay_path)125 DiskBuilder DiskBuilder::OverlayPath(std::string overlay_path) && {
126   overlay_path_ = std::move(overlay_path);
127   return *this;
128 }
129 
ResumeIfPossible(bool resume_if_possible)130 DiskBuilder& DiskBuilder::ResumeIfPossible(bool resume_if_possible) & {
131   resume_if_possible_ = resume_if_possible;
132   return *this;
133 }
ResumeIfPossible(bool resume_if_possible)134 DiskBuilder DiskBuilder::ResumeIfPossible(bool resume_if_possible) && {
135   resume_if_possible_ = resume_if_possible;
136   return *this;
137 }
138 
TextConfig()139 Result<std::string> DiskBuilder::TextConfig() {
140   std::ostringstream disk_conf;
141 
142   CF_EXPECT(vm_manager_ != VmmMode::kUnknown, "Missing vm_manager");
143   disk_conf << vm_manager_ << "\n";
144 
145   CF_EXPECT(!partitions_.empty() ^ !entire_disk_.empty(),
146             "Specify either partitions or a whole disk");
147   for (auto& partition : partitions_) {
148     disk_conf << partition.image_file_path << "\n";
149   }
150   if (!entire_disk_.empty()) {
151     disk_conf << entire_disk_;
152   }
153   return disk_conf.str();
154 }
155 
WillRebuildCompositeDisk()156 Result<bool> DiskBuilder::WillRebuildCompositeDisk() {
157   if (!resume_if_possible_) {
158     return true;
159   }
160 
161   CF_EXPECT(!config_path_.empty(), "No config path");
162   if (ReadFile(config_path_) != CF_EXPECT(TextConfig())) {
163     LOG(DEBUG) << "Composite disk text config mismatch";
164     return true;
165   }
166 
167   CF_EXPECT(!partitions_.empty() ^ !entire_disk_.empty(),
168             "Specify either partitions or a whole disk");
169   if (!entire_disk_.empty()) {
170     LOG(DEBUG) << "No composite disk to build";
171     return false;
172   }
173   auto last_component_mod_time = LastUpdatedInputDisk(partitions_);
174 
175   CF_EXPECT(!composite_disk_path_.empty(), "No composite disk path");
176   auto composite_mod_time = FileModificationTime(composite_disk_path_);
177 
178   if (composite_mod_time == decltype(composite_mod_time)()) {
179     LOG(DEBUG) << "No prior composite disk";
180     return true;
181   } else if (last_component_mod_time > composite_mod_time) {
182     LOG(DEBUG) << "Composite disk component file updated";
183     return true;
184   }
185 
186   return false;
187 }
188 
BuildCompositeDiskIfNecessary()189 Result<bool> DiskBuilder::BuildCompositeDiskIfNecessary() {
190   if (!entire_disk_.empty()) {
191     LOG(DEBUG) << "No composite disk to build";
192     return false;
193   }
194   if (!CF_EXPECT(WillRebuildCompositeDisk())) {
195     return false;
196   }
197 
198   CF_EXPECT(vm_manager_ != VmmMode::kUnknown);
199   // TODO: b/346855591 - run with QEMU when crosvm block device is integrated
200   if (vm_manager_ == VmmMode::kCrosvm) {
201     CF_EXPECT(!header_path_.empty(), "No header path");
202     CF_EXPECT(!footer_path_.empty(), "No footer path");
203     CreateCompositeDisk(partitions_, AbsolutePath(header_path_),
204                         AbsolutePath(footer_path_),
205                         AbsolutePath(composite_disk_path_));
206   } else {
207     // If this doesn't fit into the disk, it will fail while aggregating. The
208     // aggregator doesn't maintain any sparse attributes.
209     AggregateImage(partitions_, AbsolutePath(composite_disk_path_));
210   }
211 
212   using android::base::WriteStringToFile;
213   CF_EXPECT(WriteStringToFile(CF_EXPECT(TextConfig()), config_path_), true);
214 
215   return true;
216 }
217 
BuildOverlayIfNecessary()218 Result<bool> DiskBuilder::BuildOverlayIfNecessary() {
219 #ifdef __APPLE__
220   return false;
221 #else
222   bool can_reuse_overlay = resume_if_possible_;
223 
224   CF_EXPECT(!overlay_path_.empty(), "Overlay path missing");
225   auto overlay_mod_time = FileModificationTime(overlay_path_);
226 
227   CF_EXPECT(!composite_disk_path_.empty(), "Composite disk path missing");
228   auto composite_disk_mod_time = FileModificationTime(composite_disk_path_);
229   if (overlay_mod_time == decltype(overlay_mod_time)()) {
230     LOG(DEBUG) << "No prior overlay";
231     can_reuse_overlay = false;
232   } else if (overlay_mod_time < composite_disk_mod_time) {
233     LOG(DEBUG) << "Overlay is out of date";
234     can_reuse_overlay = false;
235   }
236 
237   if (can_reuse_overlay) {
238     return false;
239   }
240 
241   CF_EXPECT(!crosvm_path_.empty(), "crosvm binary missing");
242   CreateQcowOverlay(crosvm_path_, composite_disk_path_, overlay_path_);
243 
244   return true;
245 #endif
246 }
247 
248 }  // namespace cuttlefish
249