• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2018 The Chromium OS Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include <brillo/blkdev_utils/loop_device.h>
6 
7 #include <fcntl.h>
8 #include <linux/major.h>
9 #include <sys/ioctl.h>
10 #include <sys/types.h>
11 #include <unistd.h>
12 
13 #include <memory>
14 #include <string>
15 #include <utility>
16 #include <vector>
17 
18 #include <base/files/file_enumerator.h>
19 #include <base/files/file_util.h>
20 #include <base/files/scoped_file.h>
21 #include <base/posix/eintr_wrapper.h>
22 #include <base/strings/string_number_conversions.h>
23 #include <base/strings/string_split.h>
24 #include <base/strings/string_util.h>
25 #include <base/strings/stringprintf.h>
26 
27 namespace brillo {
28 
29 namespace {
30 
31 constexpr char kLoopControl[] = "/dev/loop-control";
32 constexpr char kSysBlockPath[] = "/sys/block";
33 // File containing device id in /sys/block/loopX/.
34 constexpr char kDeviceIdPath[] = "dev";
35 constexpr char kLoopBackingFile[] = "loop/backing_file";
36 constexpr int kLoopDeviceIoctlFlags = O_RDWR | O_NOFOLLOW | O_CLOEXEC;
37 constexpr int kLoopControlIoctlFlags = O_RDONLY | O_NOFOLLOW | O_CLOEXEC;
38 
39 // ioctl runner for LoopDevice and LoopDeviceManager
LoopDeviceIoctl(const base::FilePath & device,int type,uint64_t arg,int open_flag)40 int LoopDeviceIoctl(const base::FilePath& device,
41                     int type,
42                     uint64_t arg,
43                     int open_flag) {
44   base::ScopedFD device_fd(
45       HANDLE_EINTR(open(device.value().c_str(), open_flag)));
46 
47   if (!device_fd.is_valid()) {
48     PLOG(ERROR) << "Unable to open loop device";
49     return -EINVAL;
50   }
51 
52   int rc = ioctl(device_fd.get(), type, arg);
53 
54   if (rc < 0)
55     PLOG(ERROR) << "ioctl failed.";
56 
57   return rc;
58 }
59 
60 // Parse the device number for a valid /sys/block/loopX path
61 // or symlink to such a path.
62 // Returns -1 if invalid.
GetDeviceNumber(const base::FilePath & sys_block_loopdev_path)63 int GetDeviceNumber(const base::FilePath& sys_block_loopdev_path) {
64   std::string device_string;
65   int device_number = -1;
66 
67   base::FilePath device_file = sys_block_loopdev_path.Append(kDeviceIdPath);
68 
69   if (!base::ReadFileToString(device_file, &device_string))
70     return -1;
71 
72   std::vector<std::string> device_ids = base::SplitString(
73       device_string, ":", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
74 
75   if (device_ids.size() != 2 ||
76       device_ids[0] != base::NumberToString(LOOP_MAJOR))
77     return -1;
78 
79   base::StringToInt(device_ids[1], &device_number);
80   return device_number;
81 }
82 
83 // For a validated loop device path, return the backing file path.
84 // Note that a pre-populated loop device path would return an empty
85 // backing file.
GetBackingFile(const base::FilePath & loopdev_path)86 base::FilePath GetBackingFile(const base::FilePath& loopdev_path) {
87   // Backing file contains path to associated source for loop devices.
88   base::FilePath backing_file = loopdev_path.Append(kLoopBackingFile);
89   std::string backing_file_content;
90   // If the backing file doesn't exist, it's not an attached loop device.
91   if (!base::ReadFileToString(backing_file, &backing_file_content))
92     return base::FilePath();
93   base::FilePath backing_file_path(
94       base::TrimWhitespaceASCII(backing_file_content, base::TRIM_ALL));
95 
96   return backing_file_path;
97 }
98 
CreateDevicePath(int device_number)99 base::FilePath CreateDevicePath(int device_number) {
100   return base::FilePath(base::StringPrintf("/dev/loop%d", device_number));
101 }
102 
103 }  // namespace
104 
LoopDevice(int device_number,const base::FilePath & backing_file,const LoopIoctl & ioctl_runner)105 LoopDevice::LoopDevice(int device_number,
106                        const base::FilePath& backing_file,
107                        const LoopIoctl& ioctl_runner)
108     : device_number_(device_number),
109       backing_file_(backing_file),
110       loop_ioctl_(ioctl_runner) {}
111 
SetStatus(struct loop_info64 info)112 bool LoopDevice::SetStatus(struct loop_info64 info) {
113   if (loop_ioctl_.Run(GetDevicePath(), LOOP_SET_STATUS64,
114                       reinterpret_cast<uint64_t>(&info),
115                       kLoopDeviceIoctlFlags) < 0) {
116     LOG(ERROR) << "ioctl(LOOP_SET_STATUS64) failed";
117     return false;
118   }
119   return true;
120 }
121 
GetStatus(struct loop_info64 * info)122 bool LoopDevice::GetStatus(struct loop_info64* info) {
123   if (loop_ioctl_.Run(GetDevicePath(), LOOP_GET_STATUS64,
124                       reinterpret_cast<uint64_t>(info),
125                       kLoopDeviceIoctlFlags) < 0) {
126     LOG(ERROR) << "ioctl(LOOP_GET_STATUS64) failed";
127     return false;
128   }
129   return true;
130 }
131 
SetName(const std::string & name)132 bool LoopDevice::SetName(const std::string& name) {
133   struct loop_info64 info;
134 
135   memset(&info, 0, sizeof(info));
136   strncpy(reinterpret_cast<char*>(info.lo_file_name), name.c_str(),
137           LO_NAME_SIZE);
138   return SetStatus(info);
139 }
140 
Detach()141 bool LoopDevice::Detach() {
142   if (loop_ioctl_.Run(GetDevicePath(), LOOP_CLR_FD, 0, kLoopDeviceIoctlFlags) !=
143       0) {
144     LOG(ERROR) << "ioctl(LOOP_CLR_FD) failed";
145     return false;
146   }
147 
148   return true;
149 }
150 
GetDevicePath()151 base::FilePath LoopDevice::GetDevicePath() {
152   return CreateDevicePath(device_number_);
153 }
154 
IsValid()155 bool LoopDevice::IsValid() {
156   return device_number_ >= 0;
157 }
158 
LoopDeviceManager()159 LoopDeviceManager::LoopDeviceManager()
160     : loop_ioctl_(base::Bind(&LoopDeviceIoctl)) {}
161 
LoopDeviceManager(LoopIoctl ioctl_runner)162 LoopDeviceManager::LoopDeviceManager(LoopIoctl ioctl_runner)
163     : loop_ioctl_(ioctl_runner) {}
164 
AttachDeviceToFile(const base::FilePath & backing_file)165 std::unique_ptr<LoopDevice> LoopDeviceManager::AttachDeviceToFile(
166     const base::FilePath& backing_file) {
167   int device_number = -1;
168   while (true) {
169     device_number =
170         loop_ioctl_.Run(base::FilePath(kLoopControl), LOOP_CTL_GET_FREE, 0,
171                         kLoopControlIoctlFlags);
172 
173     if (device_number < 0) {
174       LOG(ERROR) << "ioctl(LOOP_CTL_GET_FREE) failed";
175       return CreateLoopDevice(-1, base::FilePath());
176     }
177 
178     base::ScopedFD backing_file_fd(
179         HANDLE_EINTR(open(backing_file.value().c_str(), O_RDWR)));
180 
181     if (!backing_file_fd.is_valid()) {
182       LOG(ERROR) << "Failed to open backing file.";
183       return CreateLoopDevice(-1, base::FilePath());
184     }
185 
186     base::FilePath device_path = CreateDevicePath(device_number);
187 
188     if (loop_ioctl_.Run(device_path, LOOP_SET_FD, backing_file_fd.get(),
189                         kLoopDeviceIoctlFlags) == 0)
190       break;
191 
192     if (errno != EBUSY) {
193       LOG(ERROR) << "ioctl(LOOP_SET_FD) failed";
194       return CreateLoopDevice(-1, base::FilePath());
195     }
196   }
197   // All steps of setting up the loop device succeeded.
198   return CreateLoopDevice(device_number, backing_file);
199 }
200 
201 std::vector<std::unique_ptr<LoopDevice>>
GetAttachedDevices()202 LoopDeviceManager::GetAttachedDevices() {
203   return SearchLoopDevicePaths();
204 }
205 
GetAttachedDeviceByNumber(int device_number)206 std::unique_ptr<LoopDevice> LoopDeviceManager::GetAttachedDeviceByNumber(
207     int device_number) {
208   auto devices = SearchLoopDevicePaths(device_number);
209 
210   if (devices.empty())
211     return CreateLoopDevice(-1, base::FilePath());
212 
213   return std::move(devices[0]);
214 }
215 
GetAttachedDeviceByName(const std::string & name)216 std::unique_ptr<LoopDevice> LoopDeviceManager::GetAttachedDeviceByName(
217     const std::string& name) {
218   std::vector<std::unique_ptr<LoopDevice>> devices = GetAttachedDevices();
219 
220   for (auto& attached_device : devices) {
221     struct loop_info64 device_info;
222 
223     if (!attached_device->GetStatus(&device_info)) {
224       LOG(ERROR) << "GetStatus failed";
225       continue;
226     }
227 
228     if (strcmp(reinterpret_cast<char*>(device_info.lo_file_name),
229                name.c_str()) == 0)
230       return std::move(attached_device);
231   }
232 
233   return CreateLoopDevice(-1, base::FilePath());
234 }
235 
236 // virtual
237 std::vector<std::unique_ptr<LoopDevice>>
SearchLoopDevicePaths(int device_number)238 LoopDeviceManager::SearchLoopDevicePaths(int device_number) {
239   std::vector<std::unique_ptr<LoopDevice>> devices;
240   base::FilePath rootdir(kSysBlockPath);
241 
242   if (device_number != -1) {
243     auto loopdev_path =
244         rootdir.Append(base::StringPrintf("loop%d", device_number));
245     if (base::PathExists(loopdev_path))
246       devices.push_back(
247           CreateLoopDevice(device_number, GetBackingFile(loopdev_path)));
248   } else {
249     // Read /sys/block to discover all loop devices.
250     base::FileEnumerator loopdev_enum(
251         rootdir, false /*recursive*/,
252         base::FileEnumerator::FILES | base::FileEnumerator::SHOW_SYM_LINKS,
253         "loop*");
254 
255     for (auto loopdev = loopdev_enum.Next(); !loopdev.empty();
256          loopdev = loopdev_enum.Next()) {
257       int dev_number = GetDeviceNumber(loopdev);
258       if (dev_number != -1)
259         devices.push_back(
260             CreateLoopDevice(dev_number, GetBackingFile(loopdev)));
261     }
262   }
263   return devices;
264 }
265 
CreateLoopDevice(int device_number,const base::FilePath & backing_file)266 std::unique_ptr<LoopDevice> LoopDeviceManager::CreateLoopDevice(
267     int device_number, const base::FilePath& backing_file) {
268   return std::make_unique<LoopDevice>(device_number, backing_file, loop_ioctl_);
269 }
270 
271 }  // namespace brillo
272