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