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