• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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_verify.h"
18 
19 #include <android-base/chrono_utils.h>
20 #include <android-base/scopeguard.h>
21 #include <android-base/strings.h>
22 
23 #include <future>
24 
25 #include "snapuserd_core.h"
26 #include "utility.h"
27 
28 namespace android {
29 namespace snapshot {
30 
31 using namespace android;
32 using namespace android::dm;
33 using android::base::unique_fd;
34 
UpdateVerify(const std::string & misc_name,uint32_t verify_block_size,uint32_t num_verification_threads)35 UpdateVerify::UpdateVerify(const std::string& misc_name, uint32_t verify_block_size,
36                            uint32_t num_verification_threads)
37     : misc_name_(misc_name),
38       state_(UpdateVerifyState::VERIFY_UNKNOWN),
39       verify_block_size_(verify_block_size),
40       num_verification_threads_(num_verification_threads) {}
41 
CheckPartitionVerification()42 bool UpdateVerify::CheckPartitionVerification() {
43     auto now = std::chrono::system_clock::now();
44     auto deadline = now + 10s;
45     {
46         std::unique_lock<std::mutex> cv_lock(m_lock_);
47         while (state_ == UpdateVerifyState::VERIFY_UNKNOWN) {
48             auto status = m_cv_.wait_until(cv_lock, deadline);
49             if (status == std::cv_status::timeout) {
50                 return false;
51             }
52         }
53     }
54 
55     return (state_ == UpdateVerifyState::VERIFY_SUCCESS);
56 }
57 
UpdatePartitionVerificationState(UpdateVerifyState state)58 void UpdateVerify::UpdatePartitionVerificationState(UpdateVerifyState state) {
59     {
60         std::lock_guard<std::mutex> lock(m_lock_);
61         state_ = state;
62     }
63     m_cv_.notify_all();
64 }
65 
VerifyUpdatePartition()66 void UpdateVerify::VerifyUpdatePartition() {
67     bool succeeded = false;
68 
69     auto scope_guard = android::base::make_scope_guard([this, &succeeded]() -> void {
70         if (!succeeded) {
71             UpdatePartitionVerificationState(UpdateVerifyState::VERIFY_FAILED);
72         }
73     });
74 
75     auto& dm = DeviceMapper::Instance();
76     auto dm_block_devices = dm.FindDmPartitions();
77     if (dm_block_devices.empty()) {
78         SNAP_LOG(ERROR) << "No dm-enabled block device is found.";
79         return;
80     }
81 
82     const auto parts = android::base::Split(misc_name_, "-");
83     std::string partition_name = parts[0];
84 
85     constexpr auto&& suffix_b = "_b";
86     constexpr auto&& suffix_a = "_a";
87 
88     partition_name.erase(partition_name.find_last_not_of(suffix_b) + 1);
89     partition_name.erase(partition_name.find_last_not_of(suffix_a) + 1);
90 
91     if (dm_block_devices.find(partition_name) == dm_block_devices.end()) {
92         SNAP_LOG(ERROR) << "Failed to find dm block device for " << partition_name;
93         return;
94     }
95 
96     if (!VerifyPartition(partition_name, dm_block_devices.at(partition_name))) {
97         SNAP_LOG(ERROR) << "Partition: " << partition_name
98                         << " Block-device: " << dm_block_devices.at(partition_name)
99                         << " verification failed";
100     }
101     succeeded = true;
102 }
103 
VerifyBlocks(const std::string & partition_name,const std::string & dm_block_device,off_t offset,int skip_blocks,uint64_t dev_sz)104 bool UpdateVerify::VerifyBlocks(const std::string& partition_name,
105                                 const std::string& dm_block_device, off_t offset, int skip_blocks,
106                                 uint64_t dev_sz) {
107     unique_fd fd(TEMP_FAILURE_RETRY(open(dm_block_device.c_str(), O_RDONLY | O_DIRECT)));
108     if (fd < 0) {
109         SNAP_LOG(ERROR) << "open failed: " << dm_block_device;
110         return false;
111     }
112 
113     int queue_depth = std::max(queue_depth_, 1);
114     int verify_block_size = verify_block_size_;
115 
116     // Smaller partitions don't need a bigger queue-depth.
117     // This is required for low-memory devices.
118     if (dev_sz < threshold_size_) {
119         queue_depth = std::max(queue_depth / 2, 1);
120         verify_block_size >>= 2;
121     }
122 
123     if (!IsBlockAligned(verify_block_size)) {
124         verify_block_size = EXT4_ALIGN(verify_block_size, BLOCK_SZ);
125     }
126 
127     std::unique_ptr<io_uring_cpp::IoUringInterface> ring =
128             io_uring_cpp::IoUringInterface::CreateLinuxIoUring(queue_depth, 0);
129     if (ring.get() == nullptr) {
130         PLOG(ERROR) << "Verify: io_uring_queue_init failed for queue_depth: " << queue_depth;
131         return false;
132     }
133 
134     std::unique_ptr<struct iovec[]> vecs = std::make_unique<struct iovec[]>(queue_depth);
135     std::vector<std::unique_ptr<void, decltype(&::free)>> buffers;
136     for (int i = 0; i < queue_depth; i++) {
137         void* addr;
138         ssize_t page_size = getpagesize();
139         if (posix_memalign(&addr, page_size, verify_block_size) < 0) {
140             LOG(ERROR) << "posix_memalign failed";
141             return false;
142         }
143 
144         buffers.emplace_back(addr, ::free);
145         vecs[i].iov_base = addr;
146         vecs[i].iov_len = verify_block_size;
147     }
148 
149     auto ret = ring->RegisterBuffers(vecs.get(), queue_depth);
150     if (!ret.IsOk()) {
151         SNAP_LOG(ERROR) << "io_uring_register_buffers failed: " << ret.ErrCode();
152         return false;
153     }
154 
155     loff_t file_offset = offset;
156     const uint64_t read_sz = verify_block_size;
157     uint64_t total_read = 0;
158     int num_submitted = 0;
159 
160     SNAP_LOG(DEBUG) << "VerifyBlocks: queue_depth: " << queue_depth
161                     << " verify_block_size: " << verify_block_size << " dev_sz: " << dev_sz
162                     << " file_offset: " << file_offset << " skip_blocks: " << skip_blocks;
163 
164     while (file_offset < dev_sz) {
165         for (size_t i = 0; i < queue_depth; i++) {
166             uint64_t to_read = std::min((dev_sz - file_offset), read_sz);
167             if (to_read <= 0) break;
168 
169             const auto sqe =
170                     ring->PrepReadFixed(fd.get(), vecs[i].iov_base, to_read, file_offset, i);
171             if (!sqe.IsOk()) {
172                 SNAP_PLOG(ERROR) << "PrepReadFixed failed";
173                 return false;
174             }
175             file_offset += (skip_blocks * to_read);
176             total_read += to_read;
177             num_submitted += 1;
178             if (file_offset >= dev_sz) {
179                 break;
180             }
181         }
182 
183         if (num_submitted == 0) {
184             break;
185         }
186 
187         const auto io_submit = ring->SubmitAndWait(num_submitted);
188         if (!io_submit.IsOk()) {
189             SNAP_LOG(ERROR) << "SubmitAndWait failed: " << io_submit.ErrMsg()
190                             << " for: " << num_submitted << " entries.";
191             return false;
192         }
193 
194         SNAP_LOG(DEBUG) << "io_uring_submit: " << total_read << "num_submitted: " << num_submitted
195                         << "ret: " << ret;
196 
197         const auto cqes = ring->PopCQE(num_submitted);
198         if (cqes.IsErr()) {
199             SNAP_LOG(ERROR) << "PopCqe failed for: " << num_submitted
200                             << " error: " << cqes.GetError().ErrMsg();
201             return false;
202         }
203         for (const auto& cqe : cqes.GetResult()) {
204             if (cqe.res < 0) {
205                 SNAP_LOG(ERROR) << "I/O failed: cqe->res: " << cqe.res;
206                 return false;
207             }
208             num_submitted -= 1;
209         }
210     }
211 
212     SNAP_LOG(DEBUG) << "Verification success with io_uring: " << " dev_sz: " << dev_sz
213                     << " partition_name: " << partition_name << " total_read: " << total_read;
214 
215     return true;
216 }
217 
VerifyPartition(const std::string & partition_name,const std::string & dm_block_device)218 bool UpdateVerify::VerifyPartition(const std::string& partition_name,
219                                    const std::string& dm_block_device) {
220     android::base::Timer timer;
221 
222     SNAP_LOG(INFO) << "VerifyPartition: " << partition_name << " Block-device: " << dm_block_device;
223 
224     bool succeeded = false;
225     auto scope_guard = android::base::make_scope_guard([this, &succeeded]() -> void {
226         if (!succeeded) {
227             UpdatePartitionVerificationState(UpdateVerifyState::VERIFY_FAILED);
228         }
229     });
230 
231     unique_fd fd(TEMP_FAILURE_RETRY(open(dm_block_device.c_str(), O_RDONLY | O_DIRECT)));
232     if (fd < 0) {
233         SNAP_LOG(ERROR) << "open failed: " << dm_block_device;
234         return false;
235     }
236 
237     uint64_t dev_sz = get_block_device_size(fd.get());
238     if (!dev_sz) {
239         SNAP_PLOG(ERROR) << "Could not determine block device size: " << dm_block_device;
240         return false;
241     }
242 
243     if (!IsBlockAligned(dev_sz)) {
244         SNAP_LOG(ERROR) << "dev_sz: " << dev_sz << " is not block aligned";
245         return false;
246     }
247 
248     if (!KernelSupportsIoUring()) {
249         SNAP_LOG(INFO) << "Kernel does not support io_uring. Skipping verification.\n";
250         // This will fallback to update_verifier to do the verification.
251         return false;
252     }
253 
254     int num_threads = kMinThreadsToVerify;
255     if (dev_sz > threshold_size_) {
256         num_threads = kMaxThreadsToVerify;
257         if (num_verification_threads_ != 0) {
258             num_threads = num_verification_threads_;
259         }
260     }
261 
262     std::vector<std::future<bool>> threads;
263     off_t start_offset = 0;
264     const int skip_blocks = num_threads;
265 
266     while (num_threads) {
267         threads.emplace_back(std::async(std::launch::async, &UpdateVerify::VerifyBlocks, this,
268                                         partition_name, dm_block_device, start_offset, skip_blocks,
269                                         dev_sz));
270         start_offset += verify_block_size_;
271         num_threads -= 1;
272         if (start_offset >= dev_sz) {
273             break;
274         }
275     }
276 
277     bool ret = true;
278     for (auto& t : threads) {
279         ret = t.get() && ret;
280     }
281 
282     if (ret) {
283         succeeded = true;
284         UpdatePartitionVerificationState(UpdateVerifyState::VERIFY_SUCCESS);
285         SNAP_LOG(INFO) << "Partition verification success: " << partition_name
286                        << " Block-device: " << dm_block_device << " Size: " << dev_sz
287                        << " Duration : " << timer.duration().count() << " ms";
288         return true;
289     }
290 
291     return false;
292 }
293 
294 }  // namespace snapshot
295 }  // namespace android
296