1 /*
2 * Copyright (C) 2018 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 #define ATRACE_TAG ATRACE_TAG_PACKAGE_MANAGER
18
19 #include "apexd_loop.h"
20
21 #include <ApexProperties.sysprop.h>
22 #include <android-base/file.h>
23 #include <android-base/logging.h>
24 #include <android-base/parseint.h>
25 #include <android-base/properties.h>
26 #include <android-base/stringprintf.h>
27 #include <android-base/strings.h>
28 #include <dirent.h>
29 #include <fcntl.h>
30 #include <libdm/dm.h>
31 #include <linux/fs.h>
32 #include <linux/loop.h>
33 #include <string>
34 #include <sys/ioctl.h>
35 #include <sys/stat.h>
36 #include <sys/statfs.h>
37 #include <sys/sysmacros.h>
38 #include <sys/types.h>
39 #include <unistd.h>
40 #include <utils/Trace.h>
41
42 #include <array>
43 #include <filesystem>
44 #include <mutex>
45 #include <string_view>
46
47 #include "apexd_utils.h"
48
49 using android::base::Basename;
50 using android::base::Dirname;
51 using android::base::ErrnoError;
52 using android::base::Error;
53 using android::base::GetBoolProperty;
54 using android::base::ParseUint;
55 using android::base::ReadFileToString;
56 using android::base::Result;
57 using android::base::StartsWith;
58 using android::base::StringPrintf;
59 using android::base::unique_fd;
60 using android::dm::DeviceMapper;
61
62 namespace android {
63 namespace apex {
64 namespace loop {
65
66 static constexpr const char* kApexLoopIdPrefix = "apex:";
67
68 // 128 kB read-ahead, which we currently use for /system as well
69 static constexpr const unsigned int kReadAheadKb = 128;
70
MaybeCloseBad()71 void LoopbackDeviceUniqueFd::MaybeCloseBad() {
72 if (device_fd.get() != -1) {
73 // Disassociate any files.
74 if (ioctl(device_fd.get(), LOOP_CLR_FD) == -1) {
75 PLOG(ERROR) << "Unable to clear fd for loopback device";
76 }
77 }
78 }
79
ConfigureScheduler(const std::string & device_path)80 Result<void> ConfigureScheduler(const std::string& device_path) {
81 ATRACE_NAME("ConfigureScheduler");
82 if (!StartsWith(device_path, "/dev/")) {
83 return Error() << "Invalid argument " << device_path;
84 }
85
86 const std::string device_name = Basename(device_path);
87
88 const std::string sysfs_path =
89 StringPrintf("/sys/block/%s/queue/scheduler", device_name.c_str());
90 unique_fd sysfs_fd(open(sysfs_path.c_str(), O_RDWR | O_CLOEXEC));
91 if (sysfs_fd.get() == -1) {
92 return ErrnoError() << "Failed to open " << sysfs_path;
93 }
94
95 // Kernels before v4.1 only support 'noop'. Kernels [v4.1, v5.0) support
96 // 'noop' and 'none'. Kernels v5.0 and later only support 'none'.
97 static constexpr const std::array<std::string_view, 2> kNoScheduler = {
98 "none", "noop"};
99
100 int ret = 0;
101 std::string cur_sched_str;
102 if (!ReadFileToString(sysfs_path, &cur_sched_str)) {
103 return ErrnoError() << "Failed to read " << sysfs_path;
104 }
105 cur_sched_str = android::base::Trim(cur_sched_str);
106 if (std::count(kNoScheduler.begin(), kNoScheduler.end(), cur_sched_str)) {
107 return {};
108 }
109
110 for (const std::string_view& scheduler : kNoScheduler) {
111 ret = write(sysfs_fd.get(), scheduler.data(), scheduler.size());
112 if (ret > 0) {
113 break;
114 }
115 }
116
117 if (ret <= 0) {
118 return ErrnoError() << "Failed to write to " << sysfs_path;
119 }
120
121 return {};
122 }
123
124 // Return the parent device of a partition. Converts e.g. "sda26" into "sda".
PartitionParent(const std::string & blockdev)125 static Result<std::string> PartitionParent(const std::string& blockdev) {
126 if (blockdev.find('/') != std::string::npos) {
127 return Error() << "Invalid argument " << blockdev;
128 }
129
130 std::string link_path;
131 std::string path = "/sys/class/block/" + blockdev;
132 if (!android::base::Readlink(path, &link_path)) {
133 PLOG(ERROR) << "readlink('" << path << "') failed";
134 return blockdev;
135 }
136
137 if (Basename(link_path) != blockdev) {
138 LOG(ERROR) << "readlink('" << path << "') returned '" << link_path
139 << "' but it doesn't end with '" << blockdev << "'";
140 return blockdev;
141 }
142
143 // for parent devices like "sda", link_path looks like ".../block/sda"
144 // for child devices like "sda26", link_path looks like ".../block/sda/sda26"
145 std::string parent_path = Dirname(link_path);
146 std::string parent = Basename(parent_path);
147 if (parent != "block")
148 return parent;
149 else
150 return blockdev;
151 }
152
153 // Convert a major:minor pair into a block device name.
BlockdevName(dev_t dev)154 static Result<std::string> BlockdevName(dev_t dev) {
155 std::string link_path;
156 std::string path = "/sys/dev/block/" + std::to_string(major(dev)) + ":" +
157 std::to_string(minor(dev));
158 if (!android::base::Readlink(path, &link_path)) {
159 return ErrnoErrorf("readlink('{}') failed", path.c_str());
160 }
161
162 return Basename(link_path);
163 }
164
165 // For file `file_path`, retrieve the block device backing the filesystem on
166 // which the file exists and return the queue depth of the block device. The
167 // loop in this function may e.g. traverse the following hierarchy:
168 // /dev/block/dm-9 (system-verity; dm-verity)
169 // -> /dev/block/dm-1 (system_b; dm-linear)
170 // -> /dev/sda26
BlockDeviceQueueDepth(const std::string & file_path)171 static Result<uint32_t> BlockDeviceQueueDepth(const std::string& file_path) {
172 static std::unordered_map<std::string, uint32_t> cache;
173 static std::mutex cache_mutex;
174
175 struct stat statbuf;
176 int res = stat(file_path.c_str(), &statbuf);
177 if (res < 0) {
178 return ErrnoErrorf("stat({})", file_path.c_str());
179 }
180 std::string blockdev;
181 if (auto blockdev_name = BlockdevName(statbuf.st_dev); blockdev_name.ok()) {
182 blockdev = "/dev/block/" + *blockdev_name;
183 LOG(VERBOSE) << file_path << " -> " << blockdev;
184 } else {
185 return Error() << "Failed to convert " << major(statbuf.st_dev) << ":"
186 << minor(statbuf.st_dev)
187 << "to block device name: " << blockdev_name.error();
188 }
189
190 {
191 std::lock_guard<std::mutex> lock(cache_mutex);
192 auto it = cache.find(blockdev);
193 if (it != cache.end()) {
194 return it->second;
195 }
196 }
197
198 auto& dm = DeviceMapper::Instance();
199 for (;;) {
200 std::optional<std::string> child = dm.GetParentBlockDeviceByPath(blockdev);
201 if (!child) {
202 break;
203 }
204 LOG(VERBOSE) << blockdev << " -> " << *child;
205 blockdev = *child;
206 }
207 std::optional<std::string> maybe_blockdev =
208 android::dm::ExtractBlockDeviceName(blockdev);
209 if (!maybe_blockdev) {
210 return Error() << "Failed to remove /dev/block/ prefix from " << blockdev;
211 }
212 Result<std::string> maybe_parent = PartitionParent(*maybe_blockdev);
213 if (!maybe_parent.ok()) {
214 return Error() << "Failed to determine parent of " << *maybe_blockdev;
215 }
216 blockdev = *maybe_parent;
217 LOG(VERBOSE) << "Partition parent: " << blockdev;
218 const std::string nr_tags_path =
219 StringPrintf("/sys/class/block/%s/mq/0/nr_tags", blockdev.c_str());
220 std::string nr_tags;
221 if (!ReadFileToString(nr_tags_path, &nr_tags)) {
222 return ErrnoError() << "Failed to read " << nr_tags_path;
223 }
224 nr_tags = android::base::Trim(nr_tags);
225 LOG(VERBOSE) << file_path << " is backed by /dev/" << blockdev
226 << " and that block device supports queue depth " << nr_tags;
227 uint32_t result = strtol(nr_tags.c_str(), NULL, 0);
228
229 {
230 std::lock_guard<std::mutex> lock(cache_mutex);
231 cache[blockdev] = result;
232 }
233 return result;
234 }
235
236 // Set 'nr_requests' of `loop_device_path` equal to the queue depth of
237 // the block device backing `file_path`.
ConfigureQueueDepth(const std::string & loop_device_path,const std::string & file_path)238 Result<void> ConfigureQueueDepth(const std::string& loop_device_path,
239 const std::string& file_path) {
240 ATRACE_NAME("ConfigureQueueDepth");
241 if (!StartsWith(loop_device_path, "/dev/")) {
242 return Error() << "Invalid argument " << loop_device_path;
243 }
244
245 const std::string loop_device_name = Basename(loop_device_path);
246
247 const std::string sysfs_path =
248 StringPrintf("/sys/block/%s/queue/nr_requests", loop_device_name.c_str());
249 std::string cur_nr_requests_str;
250 if (!ReadFileToString(sysfs_path, &cur_nr_requests_str)) {
251 return ErrnoError() << "Failed to read " << sysfs_path;
252 }
253 cur_nr_requests_str = android::base::Trim(cur_nr_requests_str);
254 uint32_t cur_nr_requests = 0;
255 if (!ParseUint(cur_nr_requests_str.c_str(), &cur_nr_requests)) {
256 return Error() << "Failed to parse " << cur_nr_requests_str;
257 }
258
259 unique_fd sysfs_fd(open(sysfs_path.c_str(), O_RDWR | O_CLOEXEC));
260 if (sysfs_fd.get() == -1) {
261 return ErrnoErrorf("Failed to open {}", sysfs_path);
262 }
263
264 const auto qd = BlockDeviceQueueDepth(file_path);
265 if (!qd.ok()) {
266 return qd.error();
267 }
268 if (*qd == cur_nr_requests) {
269 return {};
270 }
271 // Only report write failures if reducing the queue depth. Attempts to
272 // increase the queue depth are rejected by the kernel if no I/O scheduler
273 // is associated with the request queue.
274 if (!WriteStringToFd(StringPrintf("%u", *qd), sysfs_fd) &&
275 *qd < cur_nr_requests) {
276 return ErrnoErrorf("Failed to write {} to {}", *qd, sysfs_path);
277 }
278 return {};
279 }
280
ConfigureReadAhead(const std::string & device_path)281 Result<void> ConfigureReadAhead(const std::string& device_path) {
282 ATRACE_NAME("ConfigureReadAhead");
283 CHECK(StartsWith(device_path, "/dev/"));
284 std::string device_name = Basename(device_path);
285
286 std::string sysfs_device =
287 StringPrintf("/sys/block/%s/queue/read_ahead_kb", device_name.c_str());
288 unique_fd sysfs_fd(open(sysfs_device.c_str(), O_RDWR | O_CLOEXEC));
289 if (sysfs_fd.get() == -1) {
290 return ErrnoError() << "Failed to open " << sysfs_device;
291 }
292
293 std::string readAheadKb = std::to_string(
294 android::sysprop::ApexProperties::loopback_readahead().value_or(kReadAheadKb));
295
296 int ret = TEMP_FAILURE_RETRY(
297 write(sysfs_fd.get(), readAheadKb.c_str(), readAheadKb.length()));
298 if (ret < 0) {
299 return ErrnoError() << "Failed to write to " << sysfs_device;
300 }
301
302 return {};
303 }
304
PreAllocateLoopDevices(size_t num)305 Result<void> PreAllocateLoopDevices(size_t num) {
306 Result<void> loop_ready = WaitForFile("/dev/loop-control", 20s);
307 if (!loop_ready.ok()) {
308 return loop_ready;
309 }
310 unique_fd ctl_fd(
311 TEMP_FAILURE_RETRY(open("/dev/loop-control", O_RDWR | O_CLOEXEC)));
312 if (ctl_fd.get() == -1) {
313 return ErrnoError() << "Failed to open loop-control";
314 }
315
316 int new_allocations = 0; // for logging purpose
317
318 // Assumption: loop device ID [0..num) is valid.
319 // This is because pre-allocation happens during bootstrap.
320 // Anyway Kernel pre-allocated loop devices
321 // as many as CONFIG_BLK_DEV_LOOP_MIN_COUNT,
322 // Within the amount of kernel-pre-allocation,
323 // LOOP_CTL_ADD will fail with EEXIST
324 for (size_t id = 0ul, cnt = 0; cnt < num; ++id) {
325 int ret = ioctl(ctl_fd.get(), LOOP_CTL_ADD, id);
326 if (ret > 0) {
327 new_allocations++;
328 cnt++;
329 } else if (errno == EEXIST) {
330 // When LOOP_CTL_ADD failed with EEXIST, it can check
331 // whether it is already in use.
332 // Otherwise, the loop devices pre-allocated by the kernel can be used.
333 std::string loop_device = StringPrintf("/sys/block/loop%zu/loop", id);
334 if (access(loop_device.c_str(), F_OK) == 0) {
335 LOG(WARNING) << "Loop device " << id << " already in use";
336 } else {
337 cnt++;
338 }
339 } else {
340 return ErrnoError() << "Failed LOOP_CTL_ADD id = " << id;
341 }
342 }
343
344 // Don't wait until the dev nodes are actually created, which
345 // will delay the boot. By simply returing here, the creation of the dev
346 // nodes will be done in parallel with other boot processes, and we
347 // just optimistally hope that they are all created when we actually
348 // access them for activating APEXes. If the dev nodes are not ready
349 // even then, we wait 50ms and warning message will be printed (see below
350 // CreateLoopDevice()).
351 LOG(INFO) << "Found " << (num - new_allocations)
352 << " idle loopback devices that were "
353 << "pre-allocated by kernel. Allocated " << new_allocations
354 << " more.";
355 return {};
356 }
357
358 // This is a temporary/empty object for a loop device before the backing file is
359 // set.
360 struct EmptyLoopDevice {
361 unique_fd fd;
362 std::string name;
ToOwnedandroid::apex::loop::EmptyLoopDevice363 LoopbackDeviceUniqueFd ToOwned() { return {std::move(fd), std::move(name)}; }
364 };
365
ConfigureLoopDevice(EmptyLoopDevice && inner,const std::string & target,const uint32_t image_offset,const size_t image_size)366 static Result<LoopbackDeviceUniqueFd> ConfigureLoopDevice(
367 EmptyLoopDevice&& inner, const std::string& target,
368 const uint32_t image_offset, const size_t image_size) {
369 static bool use_loop_configure;
370 static std::once_flag once_flag;
371 auto device_fd = inner.fd.get();
372 std::call_once(once_flag, [&]() {
373 // LOOP_CONFIGURE is a new ioctl in Linux 5.8 (and backported in Android
374 // common) that allows atomically configuring a loop device. It is a lot
375 // faster than the traditional LOOP_SET_FD/LOOP_SET_STATUS64 combo, but
376 // it may not be available on updating devices, so try once before
377 // deciding.
378 struct loop_config config;
379 memset(&config, 0, sizeof(config));
380 config.fd = -1;
381 if (ioctl(device_fd, LOOP_CONFIGURE, &config) == -1 && errno == EBADF) {
382 // If the IOCTL exists, it will fail with EBADF for the -1 fd
383 use_loop_configure = true;
384 }
385 });
386
387 /*
388 * Using O_DIRECT will tell the kernel that we want to use Direct I/O
389 * on the underlying file, which we want to do to avoid double caching.
390 * Note that Direct I/O won't be enabled immediately, because the block
391 * size of the underlying block device may not match the default loop
392 * device block size (512); when we call LOOP_SET_BLOCK_SIZE below, the
393 * kernel driver will automatically enable Direct I/O when it sees that
394 * condition is now met.
395 */
396 bool use_buffered_io = false;
397 unique_fd target_fd(open(target.c_str(), O_RDONLY | O_CLOEXEC | O_DIRECT));
398 if (target_fd.get() == -1) {
399 struct statfs stbuf;
400 int saved_errno = errno;
401 // let's give another try with buffered I/O for EROFS and squashfs
402 if (statfs(target.c_str(), &stbuf) != 0 ||
403 (stbuf.f_type != EROFS_SUPER_MAGIC_V1 &&
404 stbuf.f_type != SQUASHFS_MAGIC &&
405 stbuf.f_type != OVERLAYFS_SUPER_MAGIC)) {
406 return Error(saved_errno) << "Failed to open " << target;
407 }
408 LOG(WARNING) << "Fallback to buffered I/O for " << target;
409 use_buffered_io = true;
410 target_fd.reset(open(target.c_str(), O_RDONLY | O_CLOEXEC));
411 if (target_fd.get() == -1) {
412 return ErrnoError() << "Failed to open " << target;
413 }
414 }
415
416 struct loop_info64 li;
417 memset(&li, 0, sizeof(li));
418 strlcpy((char*)li.lo_crypt_name, kApexLoopIdPrefix, LO_NAME_SIZE);
419 li.lo_offset = image_offset;
420 li.lo_sizelimit = image_size;
421 // Automatically free loop device on last close.
422 li.lo_flags |= LO_FLAGS_AUTOCLEAR;
423
424 if (use_loop_configure) {
425 struct loop_config config;
426 memset(&config, 0, sizeof(config));
427 config.fd = target_fd.get();
428 config.info = li;
429 config.block_size = 4096;
430 if (!use_buffered_io) {
431 li.lo_flags |= LO_FLAGS_DIRECT_IO;
432 }
433
434 if (ioctl(device_fd, LOOP_CONFIGURE, &config) == -1) {
435 return ErrnoError() << "Failed to LOOP_CONFIGURE";
436 }
437
438 return inner.ToOwned();
439 } else {
440 if (ioctl(device_fd, LOOP_SET_FD, target_fd.get()) == -1) {
441 return ErrnoError() << "Failed to LOOP_SET_FD";
442 }
443 // Now, we have a fully-owned loop device.
444 LoopbackDeviceUniqueFd loop_device = inner.ToOwned();
445
446 if (ioctl(device_fd, LOOP_SET_STATUS64, &li) == -1) {
447 return ErrnoError() << "Failed to LOOP_SET_STATUS64";
448 }
449
450 if (ioctl(device_fd, BLKFLSBUF, 0) == -1) {
451 // This works around a kernel bug where the following happens.
452 // 1) The device runs with a value of loop.max_part > 0
453 // 2) As part of LOOP_SET_FD above, we do a partition scan, which loads
454 // the first 2 pages of the underlying file into the buffer cache
455 // 3) When we then change the offset with LOOP_SET_STATUS64, those pages
456 // are not invalidated from the cache.
457 // 4) When we try to mount an ext4 filesystem on the loop device, the ext4
458 // code will try to find a superblock by reading 4k at offset 0; but,
459 // because we still have the old pages at offset 0 lying in the cache,
460 // those pages will be returned directly. However, those pages contain
461 // the data at offset 0 in the underlying file, not at the offset that
462 // we configured
463 // 5) the ext4 driver fails to find a superblock in the (wrong) data, and
464 // fails to mount the filesystem.
465 //
466 // To work around this, explicitly flush the block device, which will
467 // flush the buffer cache and make sure we actually read the data at the
468 // correct offset.
469 return ErrnoError() << "Failed to flush buffers on the loop device";
470 }
471
472 // Direct-IO requires the loop device to have the same block size as the
473 // underlying filesystem.
474 if (ioctl(device_fd, LOOP_SET_BLOCK_SIZE, 4096) == -1) {
475 PLOG(WARNING) << "Failed to LOOP_SET_BLOCK_SIZE";
476 }
477 return loop_device;
478 }
479 }
480
WaitForLoopDevice(int num)481 static Result<EmptyLoopDevice> WaitForLoopDevice(int num) {
482 std::vector<std::string> candidate_devices = {
483 StringPrintf("/dev/block/loop%d", num),
484 StringPrintf("/dev/loop%d", num),
485 };
486
487 // apexd-bootstrap runs in parallel with ueventd to optimize boot time. In
488 // rare cases apexd would try attempt to mount an apex before ueventd created
489 // a loop device for it. To work around this we keep polling for loop device
490 // to be created until ueventd's cold boot sequence is done.
491 bool cold_boot_done = GetBoolProperty("ro.cold_boot_done", false);
492
493 // Even though the kernel has created the loop device, we still depend on
494 // ueventd to run to actually create the device node in userspace. To solve
495 // this properly we should listen on the netlink socket for uevents, or use
496 // inotify. For now, this will have to do.
497 size_t attempts =
498 android::sysprop::ApexProperties::loop_wait_attempts().value_or(3u);
499 for (size_t i = 0; i != attempts; ++i) {
500 if (!cold_boot_done) {
501 cold_boot_done = GetBoolProperty("ro.cold_boot_done", false);
502 }
503 for (const auto& device : candidate_devices) {
504 unique_fd sysfs_fd(open(device.c_str(), O_RDWR | O_CLOEXEC));
505 if (sysfs_fd.get() != -1) {
506 return EmptyLoopDevice{std::move(sysfs_fd), std::move(device)};
507 }
508 }
509 PLOG(WARNING) << "Loopback device " << num << " not ready. Waiting 50ms...";
510 usleep(50000);
511 if (!cold_boot_done) {
512 // ueventd hasn't finished cold boot yet, keep trying.
513 i = 0;
514 }
515 }
516
517 return Error() << "Failed to open loopback device " << num;
518 }
519
CreateLoopDevice(const std::string & target,uint32_t image_offset,size_t image_size)520 static Result<LoopbackDeviceUniqueFd> CreateLoopDevice(
521 const std::string& target, uint32_t image_offset, size_t image_size) {
522 ATRACE_NAME("CreateLoopDevice");
523
524 unique_fd ctl_fd(open("/dev/loop-control", O_RDWR | O_CLOEXEC));
525 if (ctl_fd.get() == -1) {
526 return ErrnoError() << "Failed to open loop-control";
527 }
528
529 static std::mutex mtx;
530 std::lock_guard lock(mtx);
531 int num = ioctl(ctl_fd.get(), LOOP_CTL_GET_FREE);
532 if (num == -1) {
533 return ErrnoError() << "Failed LOOP_CTL_GET_FREE";
534 }
535
536 auto loop_device = OR_RETURN(WaitForLoopDevice(num));
537 CHECK_NE(loop_device.fd.get(), -1);
538
539 return ConfigureLoopDevice(std::move(loop_device), target, image_offset,
540 image_size);
541 }
542
CreateAndConfigureLoopDevice(const std::string & target,uint32_t image_offset,size_t image_size)543 Result<LoopbackDeviceUniqueFd> CreateAndConfigureLoopDevice(
544 const std::string& target, uint32_t image_offset, size_t image_size) {
545 ATRACE_NAME("CreateAndConfigureLoopDevice");
546 // Do minimal amount of work while holding a mutex. We need it because
547 // acquiring + configuring a loop device is not atomic. Ideally we should
548 // pre-acquire all the loop devices in advance, so that when we run APEX
549 // activation in-parallel, we can do it without holding any lock.
550 // Unfortunately, this will require some refactoring of how we manage loop
551 // devices, and probably some new loop-control ioctls, so for the time being
552 // we just limit the scope that requires locking.
553 android::base::Timer timer;
554 Result<LoopbackDeviceUniqueFd> loop_device;
555 while (timer.duration() < 1s) {
556 loop_device = CreateLoopDevice(target, image_offset, image_size);
557 if (loop_device.ok()) {
558 break;
559 }
560 std::this_thread::sleep_for(5ms);
561 }
562
563 if (!loop_device.ok()) {
564 return loop_device.error();
565 }
566
567 Result<void> sched_status = ConfigureScheduler(loop_device->name);
568 if (!sched_status.ok()) {
569 LOG(WARNING) << "Configuring I/O scheduler failed: "
570 << sched_status.error();
571 }
572
573 Result<void> qd_status = ConfigureQueueDepth(loop_device->name, target);
574 if (!qd_status.ok()) {
575 LOG(WARNING) << qd_status.error();
576 }
577
578 Result<void> read_ahead_status = ConfigureReadAhead(loop_device->name);
579 if (!read_ahead_status.ok()) {
580 return read_ahead_status.error();
581 }
582
583 return loop_device;
584 }
585
DestroyLoopDevice(const std::string & path,const DestroyLoopFn & extra)586 void DestroyLoopDevice(const std::string& path, const DestroyLoopFn& extra) {
587 unique_fd fd(open(path.c_str(), O_RDWR | O_CLOEXEC));
588 if (fd.get() == -1) {
589 if (errno != ENOENT) {
590 PLOG(WARNING) << "Failed to open " << path;
591 }
592 return;
593 }
594
595 struct loop_info64 li;
596 if (ioctl(fd.get(), LOOP_GET_STATUS64, &li) < 0) {
597 if (errno != ENXIO) {
598 PLOG(WARNING) << "Failed to LOOP_GET_STATUS64 " << path;
599 }
600 return;
601 }
602
603 auto id = std::string((char*)li.lo_crypt_name);
604 if (StartsWith(id, kApexLoopIdPrefix)) {
605 extra(path, id);
606
607 if (ioctl(fd.get(), LOOP_CLR_FD, 0) < 0) {
608 PLOG(WARNING) << "Failed to LOOP_CLR_FD " << path;
609 }
610 }
611 }
612
613 } // namespace loop
614 } // namespace apex
615 } // namespace android
616