• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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