]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/shared/loop-util.c
shared/loop-util: spin on LOOP_CTL_REMOVE
[thirdparty/systemd.git] / src / shared / loop-util.c
1 /* SPDX-License-Identifier: LGPL-2.1+ */
2
3 #if HAVE_VALGRIND_MEMCHECK_H
4 #include <valgrind/memcheck.h>
5 #endif
6
7 #include <errno.h>
8 #include <fcntl.h>
9 #include <linux/blkpg.h>
10 #include <linux/fs.h>
11 #include <linux/loop.h>
12 #include <sys/file.h>
13 #include <sys/ioctl.h>
14 #include <unistd.h>
15
16 #include "alloc-util.h"
17 #include "fd-util.h"
18 #include "fileio.h"
19 #include "loop-util.h"
20 #include "parse-util.h"
21 #include "stat-util.h"
22 #include "stdio-util.h"
23 #include "string-util.h"
24
25 static void cleanup_clear_loop_close(int *fd) {
26 if (*fd >= 0) {
27 (void) ioctl(*fd, LOOP_CLR_FD);
28 (void) safe_close(*fd);
29 }
30 }
31
32 int loop_device_make_full(
33 int fd,
34 int open_flags,
35 uint64_t offset,
36 uint64_t size,
37 uint32_t loop_flags,
38 LoopDevice **ret) {
39
40 _cleanup_free_ char *loopdev = NULL;
41 struct loop_info64 info;
42 LoopDevice *d = NULL;
43 struct stat st;
44 int nr = -1, r;
45
46 assert(fd >= 0);
47 assert(ret);
48 assert(IN_SET(open_flags, O_RDWR, O_RDONLY));
49
50 if (fstat(fd, &st) < 0)
51 return -errno;
52
53 if (S_ISBLK(st.st_mode)) {
54 if (ioctl(fd, LOOP_GET_STATUS64, &info) >= 0) {
55 /* Oh! This is a loopback device? That's interesting! */
56
57 #if HAVE_VALGRIND_MEMCHECK_H
58 /* Valgrind currently doesn't know LOOP_GET_STATUS64. Remove this once it does */
59 VALGRIND_MAKE_MEM_DEFINED(&info, sizeof(info));
60 #endif
61 nr = info.lo_number;
62
63 if (asprintf(&loopdev, "/dev/loop%i", nr) < 0)
64 return -ENOMEM;
65 }
66
67 if (offset == 0 && IN_SET(size, 0, UINT64_MAX)) {
68 _cleanup_close_ int copy = -1;
69
70 /* If this is already a block device, store a copy of the fd as it is */
71
72 copy = fcntl(fd, F_DUPFD_CLOEXEC, 3);
73 if (copy < 0)
74 return -errno;
75
76 d = new(LoopDevice, 1);
77 if (!d)
78 return -ENOMEM;
79 *d = (LoopDevice) {
80 .fd = TAKE_FD(copy),
81 .nr = nr,
82 .node = TAKE_PTR(loopdev),
83 .relinquished = true, /* It's not allocated by us, don't destroy it when this object is freed */
84 };
85
86 *ret = d;
87 return d->fd;
88 }
89 } else {
90 r = stat_verify_regular(&st);
91 if (r < 0)
92 return r;
93 }
94
95 _cleanup_close_ int control = -1;
96 _cleanup_(cleanup_clear_loop_close) int loop_with_fd = -1;
97
98 control = open("/dev/loop-control", O_RDWR|O_CLOEXEC|O_NOCTTY|O_NONBLOCK);
99 if (control < 0)
100 return -errno;
101
102 /* Loop around LOOP_CTL_GET_FREE, since at the moment we attempt to open the returned device it might
103 * be gone already, taken by somebody else racing against us. */
104 for (unsigned n_attempts = 0;;) {
105 _cleanup_close_ int loop = -1;
106
107 nr = ioctl(control, LOOP_CTL_GET_FREE);
108 if (nr < 0)
109 return -errno;
110
111 if (asprintf(&loopdev, "/dev/loop%i", nr) < 0)
112 return -ENOMEM;
113
114 loop = open(loopdev, O_CLOEXEC|O_NONBLOCK|O_NOCTTY|open_flags);
115 if (loop < 0)
116 return -errno;
117 if (ioctl(loop, LOOP_SET_FD, fd) >= 0) {
118 loop_with_fd = TAKE_FD(loop);
119 break;
120 }
121 if (errno != EBUSY)
122 return -errno;
123 if (++n_attempts >= 64) /* Give up eventually */
124 return -EBUSY;
125
126 loopdev = mfree(loopdev);
127 }
128
129 info = (struct loop_info64) {
130 /* Use the specified flags, but configure the read-only flag from the open flags, and force autoclear */
131 .lo_flags = (loop_flags & ~LO_FLAGS_READ_ONLY) | ((loop_flags & O_ACCMODE) == O_RDONLY ? LO_FLAGS_READ_ONLY : 0) | LO_FLAGS_AUTOCLEAR,
132 .lo_offset = offset,
133 .lo_sizelimit = size == UINT64_MAX ? 0 : size,
134 };
135
136 if (ioctl(loop_with_fd, LOOP_SET_STATUS64, &info) < 0)
137 return -errno;
138
139 d = new(LoopDevice, 1);
140 if (!d)
141 return -ENOMEM;
142 *d = (LoopDevice) {
143 .fd = TAKE_FD(loop_with_fd),
144 .node = TAKE_PTR(loopdev),
145 .nr = nr,
146 };
147
148 *ret = d;
149 return 0;
150 }
151
152 int loop_device_make_by_path(const char *path, int open_flags, uint32_t loop_flags, LoopDevice **ret) {
153 _cleanup_close_ int fd = -1;
154
155 assert(path);
156 assert(ret);
157 assert(IN_SET(open_flags, O_RDWR, O_RDONLY));
158
159 fd = open(path, O_CLOEXEC|O_NONBLOCK|O_NOCTTY|open_flags);
160 if (fd < 0)
161 return -errno;
162
163 return loop_device_make_full(fd, open_flags, 0, 0, loop_flags, ret);
164 }
165
166 LoopDevice* loop_device_unref(LoopDevice *d) {
167 if (!d)
168 return NULL;
169
170 if (d->fd >= 0) {
171 if (d->nr >= 0 && !d->relinquished) {
172 if (ioctl(d->fd, LOOP_CLR_FD) < 0)
173 log_debug_errno(errno, "Failed to clear loop device: %m");
174
175 }
176
177 safe_close(d->fd);
178 }
179
180 if (d->nr >= 0 && !d->relinquished) {
181 _cleanup_close_ int control = -1;
182
183 control = open("/dev/loop-control", O_RDWR|O_CLOEXEC|O_NOCTTY|O_NONBLOCK);
184 if (control < 0)
185 log_warning_errno(errno,
186 "Failed to open loop control device, cannot remove loop device %s: %m",
187 strna(d->node));
188 else
189 for (unsigned n_attempts = 0;;) {
190 if (ioctl(control, LOOP_CTL_REMOVE, d->nr) >= 0)
191 break;
192 if (errno != EBUSY || ++n_attempts >= 64) {
193 log_warning_errno(errno, "Failed to remove device %s: %m", strna(d->node));
194 break;
195 }
196 usleep(50 * USEC_PER_MSEC);
197 }
198 }
199
200 free(d->node);
201 return mfree(d);
202 }
203
204 void loop_device_relinquish(LoopDevice *d) {
205 assert(d);
206
207 /* Don't attempt to clean up the loop device anymore from this point on. Leave the clean-ing up to the kernel
208 * itself, using the loop device "auto-clear" logic we already turned on when creating the device. */
209
210 d->relinquished = true;
211 }
212
213 int loop_device_open(const char *loop_path, int open_flags, LoopDevice **ret) {
214 _cleanup_close_ int loop_fd = -1;
215 _cleanup_free_ char *p = NULL;
216 struct loop_info64 info;
217 struct stat st;
218 LoopDevice *d;
219 int nr;
220
221 assert(loop_path);
222 assert(ret);
223
224 loop_fd = open(loop_path, O_CLOEXEC|O_NONBLOCK|O_NOCTTY|open_flags);
225 if (loop_fd < 0)
226 return -errno;
227
228 if (fstat(loop_fd, &st) < 0)
229 return -errno;
230 if (!S_ISBLK(st.st_mode))
231 return -ENOTBLK;
232
233 if (ioctl(loop_fd, LOOP_GET_STATUS64, &info) >= 0) {
234 #if HAVE_VALGRIND_MEMCHECK_H
235 /* Valgrind currently doesn't know LOOP_GET_STATUS64. Remove this once it does */
236 VALGRIND_MAKE_MEM_DEFINED(&info, sizeof(info));
237 #endif
238 nr = info.lo_number;
239 } else
240 nr = -1;
241
242 p = strdup(loop_path);
243 if (!p)
244 return -ENOMEM;
245
246 d = new(LoopDevice, 1);
247 if (!d)
248 return -ENOMEM;
249
250 *d = (LoopDevice) {
251 .fd = TAKE_FD(loop_fd),
252 .nr = nr,
253 .node = TAKE_PTR(p),
254 .relinquished = true, /* It's not ours, don't try to destroy it when this object is freed */
255 };
256
257 *ret = d;
258 return d->fd;
259 }
260
261 static int resize_partition(int partition_fd, uint64_t offset, uint64_t size) {
262 char sysfs[STRLEN("/sys/dev/block/:/partition") + 2*DECIMAL_STR_MAX(dev_t) + 1];
263 _cleanup_free_ char *whole = NULL, *buffer = NULL;
264 uint64_t current_offset, current_size, partno;
265 _cleanup_close_ int whole_fd = -1;
266 struct stat st;
267 dev_t devno;
268 int r;
269
270 assert(partition_fd >= 0);
271
272 /* Resizes the partition the loopback device refer to (assuming it refers to one instead of an actual
273 * loopback device), and changes the offset, if needed. This is a fancy wrapper around
274 * BLKPG_RESIZE_PARTITION. */
275
276 if (fstat(partition_fd, &st) < 0)
277 return -errno;
278
279 assert(S_ISBLK(st.st_mode));
280
281 xsprintf(sysfs, "/sys/dev/block/%u:%u/partition", major(st.st_rdev), minor(st.st_rdev));
282 r = read_one_line_file(sysfs, &buffer);
283 if (r == -ENOENT) /* not a partition, cannot resize */
284 return -ENOTTY;
285 if (r < 0)
286 return r;
287 r = safe_atou64(buffer, &partno);
288 if (r < 0)
289 return r;
290
291 xsprintf(sysfs, "/sys/dev/block/%u:%u/start", major(st.st_rdev), minor(st.st_rdev));
292
293 buffer = mfree(buffer);
294 r = read_one_line_file(sysfs, &buffer);
295 if (r < 0)
296 return r;
297 r = safe_atou64(buffer, &current_offset);
298 if (r < 0)
299 return r;
300 if (current_offset > UINT64_MAX/512U)
301 return -EINVAL;
302 current_offset *= 512U;
303
304 if (ioctl(partition_fd, BLKGETSIZE64, &current_size) < 0)
305 return -EINVAL;
306
307 if (size == UINT64_MAX && offset == UINT64_MAX)
308 return 0;
309 if (current_size == size && current_offset == offset)
310 return 0;
311
312 xsprintf(sysfs, "/sys/dev/block/%u:%u/../dev", major(st.st_rdev), minor(st.st_rdev));
313
314 buffer = mfree(buffer);
315 r = read_one_line_file(sysfs, &buffer);
316 if (r < 0)
317 return r;
318 r = parse_dev(buffer, &devno);
319 if (r < 0)
320 return r;
321
322 r = device_path_make_major_minor(S_IFBLK, devno, &whole);
323 if (r < 0)
324 return r;
325
326 whole_fd = open(whole, O_RDWR|O_CLOEXEC|O_NONBLOCK|O_NOCTTY);
327 if (whole_fd < 0)
328 return -errno;
329
330 struct blkpg_partition bp = {
331 .pno = partno,
332 .start = offset == UINT64_MAX ? current_offset : offset,
333 .length = size == UINT64_MAX ? current_size : size,
334 };
335
336 struct blkpg_ioctl_arg ba = {
337 .op = BLKPG_RESIZE_PARTITION,
338 .data = &bp,
339 .datalen = sizeof(bp),
340 };
341
342 if (ioctl(whole_fd, BLKPG, &ba) < 0)
343 return -errno;
344
345 return 0;
346 }
347
348 int loop_device_refresh_size(LoopDevice *d, uint64_t offset, uint64_t size) {
349 struct loop_info64 info;
350 assert(d);
351
352 /* Changes the offset/start of the loop device relative to the beginning of the underlying file or
353 * block device. If this loop device actually refers to a partition and not a loopback device, we'll
354 * try to adjust the partition offsets instead.
355 *
356 * If either offset or size is UINT64_MAX we won't change that parameter. */
357
358 if (d->fd < 0)
359 return -EBADF;
360
361 if (d->nr < 0) /* not a loopback device */
362 return resize_partition(d->fd, offset, size);
363
364 if (ioctl(d->fd, LOOP_GET_STATUS64, &info) < 0)
365 return -errno;
366
367 #if HAVE_VALGRIND_MEMCHECK_H
368 /* Valgrind currently doesn't know LOOP_GET_STATUS64. Remove this once it does */
369 VALGRIND_MAKE_MEM_DEFINED(&info, sizeof(info));
370 #endif
371
372 if (size == UINT64_MAX && offset == UINT64_MAX)
373 return 0;
374 if (info.lo_sizelimit == size && info.lo_offset == offset)
375 return 0;
376
377 if (size != UINT64_MAX)
378 info.lo_sizelimit = size;
379 if (offset != UINT64_MAX)
380 info.lo_offset = offset;
381
382 if (ioctl(d->fd, LOOP_SET_STATUS64, &info) < 0)
383 return -errno;
384
385 return 0;
386 }
387
388 int loop_device_flock(LoopDevice *d, int operation) {
389 assert(d);
390
391 if (d->fd < 0)
392 return -EBADF;
393
394 if (flock(d->fd, operation) < 0)
395 return -errno;
396
397 return 0;
398 }