]>
git.ipfire.org Git - thirdparty/systemd.git/blob - src/shared/loop-util.c
1 /* SPDX-License-Identifier: LGPL-2.1+ */
3 #if HAVE_VALGRIND_MEMCHECK_H
4 #include <valgrind/memcheck.h>
9 #include <linux/blkpg.h>
11 #include <linux/loop.h>
13 #include <sys/ioctl.h>
16 #include "alloc-util.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"
25 static void cleanup_clear_loop_close(int *fd
) {
27 (void) ioctl(*fd
, LOOP_CLR_FD
);
28 (void) safe_close(*fd
);
32 int loop_device_make_full(
40 _cleanup_free_
char *loopdev
= NULL
;
41 struct loop_info64 info
;
48 assert(IN_SET(open_flags
, O_RDWR
, O_RDONLY
));
50 if (fstat(fd
, &st
) < 0)
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! */
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
));
63 if (asprintf(&loopdev
, "/dev/loop%i", nr
) < 0)
67 if (offset
== 0 && IN_SET(size
, 0, UINT64_MAX
)) {
68 _cleanup_close_
int copy
= -1;
70 /* If this is already a block device, store a copy of the fd as it is */
72 copy
= fcntl(fd
, F_DUPFD_CLOEXEC
, 3);
76 d
= new(LoopDevice
, 1);
82 .node
= TAKE_PTR(loopdev
),
83 .relinquished
= true, /* It's not allocated by us, don't destroy it when this object is freed */
90 r
= stat_verify_regular(&st
);
95 _cleanup_close_
int control
= -1;
96 _cleanup_(cleanup_clear_loop_close
) int loop_with_fd
= -1;
98 control
= open("/dev/loop-control", O_RDWR
|O_CLOEXEC
|O_NOCTTY
|O_NONBLOCK
);
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;
107 nr
= ioctl(control
, LOOP_CTL_GET_FREE
);
111 if (asprintf(&loopdev
, "/dev/loop%i", nr
) < 0)
114 loop
= open(loopdev
, O_CLOEXEC
|O_NONBLOCK
|O_NOCTTY
|open_flags
);
117 if (ioctl(loop
, LOOP_SET_FD
, fd
) >= 0) {
118 loop_with_fd
= TAKE_FD(loop
);
123 if (++n_attempts
>= 64) /* Give up eventually */
126 loopdev
= mfree(loopdev
);
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
,
133 .lo_sizelimit
= size
== UINT64_MAX
? 0 : size
,
136 if (ioctl(loop_with_fd
, LOOP_SET_STATUS64
, &info
) < 0)
139 d
= new(LoopDevice
, 1);
143 .fd
= TAKE_FD(loop_with_fd
),
144 .node
= TAKE_PTR(loopdev
),
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;
157 assert(IN_SET(open_flags
, O_RDWR
, O_RDONLY
));
159 fd
= open(path
, O_CLOEXEC
|O_NONBLOCK
|O_NOCTTY
|open_flags
);
163 return loop_device_make_full(fd
, open_flags
, 0, 0, loop_flags
, ret
);
166 LoopDevice
* loop_device_unref(LoopDevice
*d
) {
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");
180 if (d
->nr
>= 0 && !d
->relinquished
) {
181 _cleanup_close_
int control
= -1;
183 control
= open("/dev/loop-control", O_RDWR
|O_CLOEXEC
|O_NOCTTY
|O_NONBLOCK
);
185 log_warning_errno(errno
,
186 "Failed to open loop control device, cannot remove loop device %s: %m",
189 for (unsigned n_attempts
= 0;;) {
190 if (ioctl(control
, LOOP_CTL_REMOVE
, d
->nr
) >= 0)
192 if (errno
!= EBUSY
|| ++n_attempts
>= 64) {
193 log_warning_errno(errno
, "Failed to remove device %s: %m", strna(d
->node
));
196 usleep(50 * USEC_PER_MSEC
);
204 void loop_device_relinquish(LoopDevice
*d
) {
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. */
210 d
->relinquished
= true;
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
;
224 loop_fd
= open(loop_path
, O_CLOEXEC
|O_NONBLOCK
|O_NOCTTY
|open_flags
);
228 if (fstat(loop_fd
, &st
) < 0)
230 if (!S_ISBLK(st
.st_mode
))
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
));
242 p
= strdup(loop_path
);
246 d
= new(LoopDevice
, 1);
251 .fd
= TAKE_FD(loop_fd
),
254 .relinquished
= true, /* It's not ours, don't try to destroy it when this object is freed */
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;
270 assert(partition_fd
>= 0);
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. */
276 if (fstat(partition_fd
, &st
) < 0)
279 assert(S_ISBLK(st
.st_mode
));
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 */
287 r
= safe_atou64(buffer
, &partno
);
291 xsprintf(sysfs
, "/sys/dev/block/%u:%u/start", major(st
.st_rdev
), minor(st
.st_rdev
));
293 buffer
= mfree(buffer
);
294 r
= read_one_line_file(sysfs
, &buffer
);
297 r
= safe_atou64(buffer
, ¤t_offset
);
300 if (current_offset
> UINT64_MAX
/512U)
302 current_offset
*= 512U;
304 if (ioctl(partition_fd
, BLKGETSIZE64
, ¤t_size
) < 0)
307 if (size
== UINT64_MAX
&& offset
== UINT64_MAX
)
309 if (current_size
== size
&& current_offset
== offset
)
312 xsprintf(sysfs
, "/sys/dev/block/%u:%u/../dev", major(st
.st_rdev
), minor(st
.st_rdev
));
314 buffer
= mfree(buffer
);
315 r
= read_one_line_file(sysfs
, &buffer
);
318 r
= parse_dev(buffer
, &devno
);
322 r
= device_path_make_major_minor(S_IFBLK
, devno
, &whole
);
326 whole_fd
= open(whole
, O_RDWR
|O_CLOEXEC
|O_NONBLOCK
|O_NOCTTY
);
330 struct blkpg_partition bp
= {
332 .start
= offset
== UINT64_MAX
? current_offset
: offset
,
333 .length
= size
== UINT64_MAX
? current_size
: size
,
336 struct blkpg_ioctl_arg ba
= {
337 .op
= BLKPG_RESIZE_PARTITION
,
339 .datalen
= sizeof(bp
),
342 if (ioctl(whole_fd
, BLKPG
, &ba
) < 0)
348 int loop_device_refresh_size(LoopDevice
*d
, uint64_t offset
, uint64_t size
) {
349 struct loop_info64 info
;
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.
356 * If either offset or size is UINT64_MAX we won't change that parameter. */
361 if (d
->nr
< 0) /* not a loopback device */
362 return resize_partition(d
->fd
, offset
, size
);
364 if (ioctl(d
->fd
, LOOP_GET_STATUS64
, &info
) < 0)
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
));
372 if (size
== UINT64_MAX
&& offset
== UINT64_MAX
)
374 if (info
.lo_sizelimit
== size
&& info
.lo_offset
== offset
)
377 if (size
!= UINT64_MAX
)
378 info
.lo_sizelimit
= size
;
379 if (offset
!= UINT64_MAX
)
380 info
.lo_offset
= offset
;
382 if (ioctl(d
->fd
, LOOP_SET_STATUS64
, &info
) < 0)
388 int loop_device_flock(LoopDevice
*d
, int operation
) {
394 if (flock(d
->fd
, operation
) < 0)