]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/shared/loop-util.c
shared/varlink: add missing terminator in json strings
[thirdparty/systemd.git] / src / shared / loop-util.c
1 /* SPDX-License-Identifier: LGPL-2.1+ */
2
3 #include <errno.h>
4 #include <fcntl.h>
5 #include <linux/loop.h>
6 #include <sys/ioctl.h>
7 #include <sys/stat.h>
8
9 #include "alloc-util.h"
10 #include "fd-util.h"
11 #include "loop-util.h"
12 #include "stat-util.h"
13
14 int loop_device_make(int fd, int open_flags, LoopDevice **ret) {
15 const struct loop_info64 info = {
16 .lo_flags = LO_FLAGS_AUTOCLEAR|LO_FLAGS_PARTSCAN|(open_flags == O_RDONLY ? LO_FLAGS_READ_ONLY : 0),
17 };
18
19 _cleanup_close_ int control = -1, loop = -1;
20 _cleanup_free_ char *loopdev = NULL;
21 unsigned n_attempts = 0;
22 struct stat st;
23 LoopDevice *d;
24 int nr, r;
25
26 assert(fd >= 0);
27 assert(ret);
28 assert(IN_SET(open_flags, O_RDWR, O_RDONLY));
29
30 if (fstat(fd, &st) < 0)
31 return -errno;
32
33 if (S_ISBLK(st.st_mode)) {
34 int copy;
35
36 /* If this is already a block device, store a copy of the fd as it is */
37
38 copy = fcntl(fd, F_DUPFD_CLOEXEC, 3);
39 if (copy < 0)
40 return -errno;
41
42 d = new0(LoopDevice, 1);
43 if (!d)
44 return -ENOMEM;
45
46 *d = (LoopDevice) {
47 .fd = copy,
48 .nr = -1,
49 .relinquished = true, /* It's not allocated by us, don't destroy it when this object is freed */
50 };
51
52 *ret = d;
53 return d->fd;
54 }
55
56 r = stat_verify_regular(&st);
57 if (r < 0)
58 return r;
59
60 control = open("/dev/loop-control", O_RDWR|O_CLOEXEC|O_NOCTTY|O_NONBLOCK);
61 if (control < 0)
62 return -errno;
63
64 /* Loop around LOOP_CTL_GET_FREE, since at the moment we attempt to open the returned device it might
65 * be gone already, taken by somebody else racing against us. */
66 for (;;) {
67 nr = ioctl(control, LOOP_CTL_GET_FREE);
68 if (nr < 0)
69 return -errno;
70
71 if (asprintf(&loopdev, "/dev/loop%i", nr) < 0)
72 return -ENOMEM;
73
74 loop = open(loopdev, O_CLOEXEC|O_NONBLOCK|O_NOCTTY|open_flags);
75 if (loop < 0)
76 return -errno;
77 if (ioctl(loop, LOOP_SET_FD, fd) < 0) {
78 if (errno != EBUSY)
79 return -errno;
80
81 if (++n_attempts >= 64) /* Give up eventually */
82 return -EBUSY;
83 } else
84 break;
85
86 loopdev = mfree(loopdev);
87 loop = safe_close(loop);
88 }
89
90 if (ioctl(loop, LOOP_SET_STATUS64, &info) < 0)
91 return -errno;
92
93 d = new(LoopDevice, 1);
94 if (!d)
95 return -ENOMEM;
96
97 *d = (LoopDevice) {
98 .fd = TAKE_FD(loop),
99 .node = TAKE_PTR(loopdev),
100 .nr = nr,
101 };
102
103 *ret = d;
104 return d->fd;
105 }
106
107 int loop_device_make_by_path(const char *path, int open_flags, LoopDevice **ret) {
108 _cleanup_close_ int fd = -1;
109
110 assert(path);
111 assert(ret);
112 assert(IN_SET(open_flags, O_RDWR, O_RDONLY));
113
114 fd = open(path, O_CLOEXEC|O_NONBLOCK|O_NOCTTY|open_flags);
115 if (fd < 0)
116 return -errno;
117
118 return loop_device_make(fd, open_flags, ret);
119 }
120
121 LoopDevice* loop_device_unref(LoopDevice *d) {
122 if (!d)
123 return NULL;
124
125 if (d->fd >= 0) {
126
127 if (d->nr >= 0 && !d->relinquished) {
128 if (ioctl(d->fd, LOOP_CLR_FD) < 0)
129 log_debug_errno(errno, "Failed to clear loop device: %m");
130
131 }
132
133 safe_close(d->fd);
134 }
135
136 if (d->nr >= 0 && !d->relinquished) {
137 _cleanup_close_ int control = -1;
138
139 control = open("/dev/loop-control", O_RDWR|O_CLOEXEC|O_NOCTTY|O_NONBLOCK);
140 if (control < 0)
141 log_debug_errno(errno, "Failed to open loop control device: %m");
142 else {
143 if (ioctl(control, LOOP_CTL_REMOVE, d->nr) < 0)
144 log_debug_errno(errno, "Failed to remove loop device: %m");
145 }
146 }
147
148 free(d->node);
149 return mfree(d);
150 }
151
152 void loop_device_relinquish(LoopDevice *d) {
153 assert(d);
154
155 /* Don't attempt to clean up the loop device anymore from this point on. Leave the clean-ing up to the kernel
156 * itself, using the loop device "auto-clear" logic we already turned on when creating the device. */
157
158 d->relinquished = true;
159 }