]>
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"
17 #include "errno-util.h"
20 #include "loop-util.h"
21 #include "parse-util.h"
22 #include "stat-util.h"
23 #include "stdio-util.h"
24 #include "string-util.h"
26 static void cleanup_clear_loop_close(int *fd
) {
28 (void) ioctl(*fd
, LOOP_CLR_FD
);
29 (void) safe_close(*fd
);
41 _cleanup_free_
char *loopdev
= NULL
;
42 struct loop_info64 info
;
49 assert(IN_SET(open_flags
, O_RDWR
, O_RDONLY
));
51 if (fstat(fd
, &st
) < 0)
54 if (S_ISBLK(st
.st_mode
)) {
55 if (ioctl(fd
, LOOP_GET_STATUS64
, &info
) >= 0) {
56 /* Oh! This is a loopback device? That's interesting! */
58 #if HAVE_VALGRIND_MEMCHECK_H
59 /* Valgrind currently doesn't know LOOP_GET_STATUS64. Remove this once it does */
60 VALGRIND_MAKE_MEM_DEFINED(&info
, sizeof(info
));
64 if (asprintf(&loopdev
, "/dev/loop%i", nr
) < 0)
68 if (offset
== 0 && IN_SET(size
, 0, UINT64_MAX
)) {
69 _cleanup_close_
int copy
= -1;
71 /* If this is already a block device, store a copy of the fd as it is */
73 copy
= fcntl(fd
, F_DUPFD_CLOEXEC
, 3);
77 d
= new(LoopDevice
, 1);
83 .node
= TAKE_PTR(loopdev
),
84 .relinquished
= true, /* It's not allocated by us, don't destroy it when this object is freed */
91 r
= stat_verify_regular(&st
);
96 _cleanup_close_
int control
= -1;
97 _cleanup_(cleanup_clear_loop_close
) int loop_with_fd
= -1;
99 control
= open("/dev/loop-control", O_RDWR
|O_CLOEXEC
|O_NOCTTY
|O_NONBLOCK
);
103 /* Loop around LOOP_CTL_GET_FREE, since at the moment we attempt to open the returned device it might
104 * be gone already, taken by somebody else racing against us. */
105 for (unsigned n_attempts
= 0;;) {
106 _cleanup_close_
int loop
= -1;
108 nr
= ioctl(control
, LOOP_CTL_GET_FREE
);
112 if (asprintf(&loopdev
, "/dev/loop%i", nr
) < 0)
115 loop
= open(loopdev
, O_CLOEXEC
|O_NONBLOCK
|O_NOCTTY
|open_flags
);
117 /* Somebody might've gotten the same number from the kernel, used the device,
118 * and called LOOP_CTL_REMOVE on it. Let's retry with a new number. */
122 if (ioctl(loop
, LOOP_SET_FD
, fd
) >= 0) {
123 loop_with_fd
= TAKE_FD(loop
);
130 if (++n_attempts
>= 64) /* Give up eventually */
133 loopdev
= mfree(loopdev
);
136 info
= (struct loop_info64
) {
137 /* Use the specified flags, but configure the read-only flag from the open flags, and force autoclear */
138 .lo_flags
= (loop_flags
& ~LO_FLAGS_READ_ONLY
) | ((loop_flags
& O_ACCMODE
) == O_RDONLY
? LO_FLAGS_READ_ONLY
: 0) | LO_FLAGS_AUTOCLEAR
,
140 .lo_sizelimit
= size
== UINT64_MAX
? 0 : size
,
143 if (ioctl(loop_with_fd
, LOOP_SET_STATUS64
, &info
) < 0)
146 d
= new(LoopDevice
, 1);
150 .fd
= TAKE_FD(loop_with_fd
),
151 .node
= TAKE_PTR(loopdev
),
159 int loop_device_make_by_path(const char *path
, int open_flags
, uint32_t loop_flags
, LoopDevice
**ret
) {
160 _cleanup_close_
int fd
= -1;
165 assert(open_flags
< 0 || IN_SET(open_flags
, O_RDWR
, O_RDONLY
));
167 /* Passing < 0 as open_flags here means we'll try to open the device writable if we can, retrying
168 * read-only if we cannot. */
170 fd
= open(path
, O_CLOEXEC
|O_NONBLOCK
|O_NOCTTY
|(open_flags
>= 0 ? open_flags
: O_RDWR
));
174 /* Retry read-only? */
175 if (open_flags
>= 0 || !(ERRNO_IS_PRIVILEGE(r
) || r
== -EROFS
))
178 fd
= open(path
, O_CLOEXEC
|O_NONBLOCK
|O_NOCTTY
|O_RDONLY
);
180 return r
; /* Propagate original error */
182 open_flags
= O_RDONLY
;
183 } else if (open_flags
< 0)
186 return loop_device_make(fd
, open_flags
, 0, 0, loop_flags
, ret
);
189 LoopDevice
* loop_device_unref(LoopDevice
*d
) {
194 /* Implicitly sync the device, since otherwise in-flight blocks might not get written */
195 if (fsync(d
->fd
) < 0)
196 log_debug_errno(errno
, "Failed to sync loop block device, ignoring: %m");
198 if (d
->nr
>= 0 && !d
->relinquished
) {
199 if (ioctl(d
->fd
, LOOP_CLR_FD
) < 0)
200 log_debug_errno(errno
, "Failed to clear loop device: %m");
207 if (d
->nr
>= 0 && !d
->relinquished
) {
208 _cleanup_close_
int control
= -1;
210 control
= open("/dev/loop-control", O_RDWR
|O_CLOEXEC
|O_NOCTTY
|O_NONBLOCK
);
212 log_warning_errno(errno
,
213 "Failed to open loop control device, cannot remove loop device %s: %m",
216 for (unsigned n_attempts
= 0;;) {
217 if (ioctl(control
, LOOP_CTL_REMOVE
, d
->nr
) >= 0)
219 if (errno
!= EBUSY
|| ++n_attempts
>= 64) {
220 log_warning_errno(errno
, "Failed to remove device %s: %m", strna(d
->node
));
223 (void) usleep(50 * USEC_PER_MSEC
);
231 void loop_device_relinquish(LoopDevice
*d
) {
234 /* Don't attempt to clean up the loop device anymore from this point on. Leave the clean-ing up to the kernel
235 * itself, using the loop device "auto-clear" logic we already turned on when creating the device. */
237 d
->relinquished
= true;
240 int loop_device_open(const char *loop_path
, int open_flags
, LoopDevice
**ret
) {
241 _cleanup_close_
int loop_fd
= -1;
242 _cleanup_free_
char *p
= NULL
;
243 struct loop_info64 info
;
251 loop_fd
= open(loop_path
, O_CLOEXEC
|O_NONBLOCK
|O_NOCTTY
|open_flags
);
255 if (fstat(loop_fd
, &st
) < 0)
257 if (!S_ISBLK(st
.st_mode
))
260 if (ioctl(loop_fd
, LOOP_GET_STATUS64
, &info
) >= 0) {
261 #if HAVE_VALGRIND_MEMCHECK_H
262 /* Valgrind currently doesn't know LOOP_GET_STATUS64. Remove this once it does */
263 VALGRIND_MAKE_MEM_DEFINED(&info
, sizeof(info
));
269 p
= strdup(loop_path
);
273 d
= new(LoopDevice
, 1);
278 .fd
= TAKE_FD(loop_fd
),
281 .relinquished
= true, /* It's not ours, don't try to destroy it when this object is freed */
288 static int resize_partition(int partition_fd
, uint64_t offset
, uint64_t size
) {
289 char sysfs
[STRLEN("/sys/dev/block/:/partition") + 2*DECIMAL_STR_MAX(dev_t
) + 1];
290 _cleanup_free_
char *whole
= NULL
, *buffer
= NULL
;
291 uint64_t current_offset
, current_size
, partno
;
292 _cleanup_close_
int whole_fd
= -1;
297 assert(partition_fd
>= 0);
299 /* Resizes the partition the loopback device refer to (assuming it refers to one instead of an actual
300 * loopback device), and changes the offset, if needed. This is a fancy wrapper around
301 * BLKPG_RESIZE_PARTITION. */
303 if (fstat(partition_fd
, &st
) < 0)
306 assert(S_ISBLK(st
.st_mode
));
308 xsprintf(sysfs
, "/sys/dev/block/%u:%u/partition", major(st
.st_rdev
), minor(st
.st_rdev
));
309 r
= read_one_line_file(sysfs
, &buffer
);
310 if (r
== -ENOENT
) /* not a partition, cannot resize */
314 r
= safe_atou64(buffer
, &partno
);
318 xsprintf(sysfs
, "/sys/dev/block/%u:%u/start", major(st
.st_rdev
), minor(st
.st_rdev
));
320 buffer
= mfree(buffer
);
321 r
= read_one_line_file(sysfs
, &buffer
);
324 r
= safe_atou64(buffer
, ¤t_offset
);
327 if (current_offset
> UINT64_MAX
/512U)
329 current_offset
*= 512U;
331 if (ioctl(partition_fd
, BLKGETSIZE64
, ¤t_size
) < 0)
334 if (size
== UINT64_MAX
&& offset
== UINT64_MAX
)
336 if (current_size
== size
&& current_offset
== offset
)
339 xsprintf(sysfs
, "/sys/dev/block/%u:%u/../dev", major(st
.st_rdev
), minor(st
.st_rdev
));
341 buffer
= mfree(buffer
);
342 r
= read_one_line_file(sysfs
, &buffer
);
345 r
= parse_dev(buffer
, &devno
);
349 r
= device_path_make_major_minor(S_IFBLK
, devno
, &whole
);
353 whole_fd
= open(whole
, O_RDWR
|O_CLOEXEC
|O_NONBLOCK
|O_NOCTTY
);
357 struct blkpg_partition bp
= {
359 .start
= offset
== UINT64_MAX
? current_offset
: offset
,
360 .length
= size
== UINT64_MAX
? current_size
: size
,
363 struct blkpg_ioctl_arg ba
= {
364 .op
= BLKPG_RESIZE_PARTITION
,
366 .datalen
= sizeof(bp
),
369 if (ioctl(whole_fd
, BLKPG
, &ba
) < 0)
375 int loop_device_refresh_size(LoopDevice
*d
, uint64_t offset
, uint64_t size
) {
376 struct loop_info64 info
;
379 /* Changes the offset/start of the loop device relative to the beginning of the underlying file or
380 * block device. If this loop device actually refers to a partition and not a loopback device, we'll
381 * try to adjust the partition offsets instead.
383 * If either offset or size is UINT64_MAX we won't change that parameter. */
388 if (d
->nr
< 0) /* not a loopback device */
389 return resize_partition(d
->fd
, offset
, size
);
391 if (ioctl(d
->fd
, LOOP_GET_STATUS64
, &info
) < 0)
394 #if HAVE_VALGRIND_MEMCHECK_H
395 /* Valgrind currently doesn't know LOOP_GET_STATUS64. Remove this once it does */
396 VALGRIND_MAKE_MEM_DEFINED(&info
, sizeof(info
));
399 if (size
== UINT64_MAX
&& offset
== UINT64_MAX
)
401 if (info
.lo_sizelimit
== size
&& info
.lo_offset
== offset
)
404 if (size
!= UINT64_MAX
)
405 info
.lo_sizelimit
= size
;
406 if (offset
!= UINT64_MAX
)
407 info
.lo_offset
= offset
;
409 if (ioctl(d
->fd
, LOOP_SET_STATUS64
, &info
) < 0)
415 int loop_device_flock(LoopDevice
*d
, int operation
) {
421 if (flock(d
->fd
, operation
) < 0)