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
17 #include "snapuserd_core.h"
18
19 #include <android-base/chrono_utils.h>
20 #include <android-base/scopeguard.h>
21 #include <android-base/strings.h>
22
23 namespace android {
24 namespace snapshot {
25
26 using namespace android;
27 using namespace android::dm;
28 using android::base::unique_fd;
29
UpdateVerify(const std::string & misc_name)30 UpdateVerify::UpdateVerify(const std::string& misc_name)
31 : misc_name_(misc_name), state_(UpdateVerifyState::VERIFY_UNKNOWN) {}
32
CheckPartitionVerification()33 bool UpdateVerify::CheckPartitionVerification() {
34 auto now = std::chrono::system_clock::now();
35 auto deadline = now + 10s;
36 {
37 std::unique_lock<std::mutex> cv_lock(m_lock_);
38 while (state_ == UpdateVerifyState::VERIFY_UNKNOWN) {
39 auto status = m_cv_.wait_until(cv_lock, deadline);
40 if (status == std::cv_status::timeout) {
41 return false;
42 }
43 }
44 }
45
46 return (state_ == UpdateVerifyState::VERIFY_SUCCESS);
47 }
48
UpdatePartitionVerificationState(UpdateVerifyState state)49 void UpdateVerify::UpdatePartitionVerificationState(UpdateVerifyState state) {
50 {
51 std::lock_guard<std::mutex> lock(m_lock_);
52 state_ = state;
53 }
54 m_cv_.notify_all();
55 }
56
VerifyUpdatePartition()57 void UpdateVerify::VerifyUpdatePartition() {
58 bool succeeded = false;
59
60 auto scope_guard = android::base::make_scope_guard([this, &succeeded]() -> void {
61 if (!succeeded) {
62 UpdatePartitionVerificationState(UpdateVerifyState::VERIFY_FAILED);
63 }
64 });
65
66 auto& dm = DeviceMapper::Instance();
67 auto dm_block_devices = dm.FindDmPartitions();
68 if (dm_block_devices.empty()) {
69 SNAP_LOG(ERROR) << "No dm-enabled block device is found.";
70 return;
71 }
72
73 const auto parts = android::base::Split(misc_name_, "-");
74 std::string partition_name = parts[0];
75
76 constexpr auto&& suffix_b = "_b";
77 constexpr auto&& suffix_a = "_a";
78
79 partition_name.erase(partition_name.find_last_not_of(suffix_b) + 1);
80 partition_name.erase(partition_name.find_last_not_of(suffix_a) + 1);
81
82 if (dm_block_devices.find(partition_name) == dm_block_devices.end()) {
83 SNAP_LOG(ERROR) << "Failed to find dm block device for " << partition_name;
84 return;
85 }
86
87 if (!VerifyPartition(partition_name, dm_block_devices.at(partition_name))) {
88 SNAP_LOG(ERROR) << "Partition: " << partition_name
89 << " Block-device: " << dm_block_devices.at(partition_name)
90 << " verification failed";
91 }
92 succeeded = true;
93 }
94
VerifyBlocks(const std::string & partition_name,const std::string & dm_block_device,off_t offset,int skip_blocks,uint64_t dev_sz)95 bool UpdateVerify::VerifyBlocks(const std::string& partition_name,
96 const std::string& dm_block_device, off_t offset, int skip_blocks,
97 uint64_t dev_sz) {
98 unique_fd fd(TEMP_FAILURE_RETRY(open(dm_block_device.c_str(), O_RDONLY | O_DIRECT)));
99 if (fd < 0) {
100 SNAP_LOG(ERROR) << "open failed: " << dm_block_device;
101 return false;
102 }
103
104 loff_t file_offset = offset;
105 const uint64_t read_sz = kBlockSizeVerify;
106
107 void* addr;
108 ssize_t page_size = getpagesize();
109 if (posix_memalign(&addr, page_size, read_sz) < 0) {
110 SNAP_PLOG(ERROR) << "posix_memalign failed "
111 << " page_size: " << page_size << " read_sz: " << read_sz;
112 return false;
113 }
114
115 std::unique_ptr<void, decltype(&::free)> buffer(addr, ::free);
116
117 uint64_t bytes_read = 0;
118
119 while (true) {
120 size_t to_read = std::min((dev_sz - file_offset), read_sz);
121
122 if (!android::base::ReadFullyAtOffset(fd.get(), buffer.get(), to_read, file_offset)) {
123 SNAP_PLOG(ERROR) << "Failed to read block from block device: " << dm_block_device
124 << " partition-name: " << partition_name
125 << " at offset: " << file_offset << " read-size: " << to_read
126 << " block-size: " << dev_sz;
127 return false;
128 }
129
130 bytes_read += to_read;
131 file_offset += (skip_blocks * kBlockSizeVerify);
132 if (file_offset >= dev_sz) {
133 break;
134 }
135 }
136
137 SNAP_LOG(DEBUG) << "Verification success with bytes-read: " << bytes_read
138 << " dev_sz: " << dev_sz << " partition_name: " << partition_name;
139
140 return true;
141 }
142
VerifyPartition(const std::string & partition_name,const std::string & dm_block_device)143 bool UpdateVerify::VerifyPartition(const std::string& partition_name,
144 const std::string& dm_block_device) {
145 android::base::Timer timer;
146
147 SNAP_LOG(INFO) << "VerifyPartition: " << partition_name << " Block-device: " << dm_block_device;
148
149 bool succeeded = false;
150 auto scope_guard = android::base::make_scope_guard([this, &succeeded]() -> void {
151 if (!succeeded) {
152 UpdatePartitionVerificationState(UpdateVerifyState::VERIFY_FAILED);
153 }
154 });
155
156 unique_fd fd(TEMP_FAILURE_RETRY(open(dm_block_device.c_str(), O_RDONLY | O_DIRECT)));
157 if (fd < 0) {
158 SNAP_LOG(ERROR) << "open failed: " << dm_block_device;
159 return false;
160 }
161
162 uint64_t dev_sz = get_block_device_size(fd.get());
163 if (!dev_sz) {
164 SNAP_PLOG(ERROR) << "Could not determine block device size: " << dm_block_device;
165 return false;
166 }
167
168 if (!IsBlockAligned(dev_sz)) {
169 SNAP_LOG(ERROR) << "dev_sz: " << dev_sz << " is not block aligned";
170 return false;
171 }
172
173 /*
174 * Not all partitions are of same size. Some partitions are as small as
175 * 100Mb. We can just finish them in a single thread. For bigger partitions
176 * such as product, 4 threads are sufficient enough.
177 *
178 * TODO: With io_uring SQ_POLL support, we can completely cut this
179 * down to just single thread for all partitions and potentially verify all
180 * the partitions with zero syscalls. Additionally, since block layer
181 * supports polling, IO_POLL could be used which will further cut down
182 * latency.
183 */
184 int num_threads = kMinThreadsToVerify;
185 if (dev_sz > kThresholdSize) {
186 num_threads = kMaxThreadsToVerify;
187 }
188
189 std::vector<std::future<bool>> threads;
190 off_t start_offset = 0;
191 const int skip_blocks = num_threads;
192
193 while (num_threads) {
194 threads.emplace_back(std::async(std::launch::async, &UpdateVerify::VerifyBlocks, this,
195 partition_name, dm_block_device, start_offset, skip_blocks,
196 dev_sz));
197 start_offset += kBlockSizeVerify;
198 num_threads -= 1;
199 if (start_offset >= dev_sz) {
200 break;
201 }
202 }
203
204 bool ret = true;
205 for (auto& t : threads) {
206 ret = t.get() && ret;
207 }
208
209 if (ret) {
210 succeeded = true;
211 UpdatePartitionVerificationState(UpdateVerifyState::VERIFY_SUCCESS);
212 SNAP_LOG(INFO) << "Partition: " << partition_name << " Block-device: " << dm_block_device
213 << " Size: " << dev_sz
214 << " verification success. Duration : " << timer.duration().count() << " ms";
215 return true;
216 }
217
218 return false;
219 }
220
221 } // namespace snapshot
222 } // namespace android
223