]>
Commit | Line | Data |
---|---|---|
53e1b683 | 1 | /* SPDX-License-Identifier: LGPL-2.1+ */ |
8c1be37e | 2 | |
dccca82b | 3 | #include <errno.h> |
8c1be37e LP |
4 | #include <fcntl.h> |
5 | #include <linux/loop.h> | |
6 | #include <sys/ioctl.h> | |
8c1be37e LP |
7 | |
8 | #include "alloc-util.h" | |
9 | #include "fd-util.h" | |
10 | #include "loop-util.h" | |
3cc44114 | 11 | #include "stat-util.h" |
8c1be37e | 12 | |
ed9eeb7b LP |
13 | int loop_device_make_full( |
14 | int fd, | |
15 | int open_flags, | |
16 | uint64_t offset, | |
17 | uint64_t size, | |
18 | uint32_t loop_flags, | |
19 | LoopDevice **ret) { | |
8c1be37e LP |
20 | |
21 | _cleanup_close_ int control = -1, loop = -1; | |
22 | _cleanup_free_ char *loopdev = NULL; | |
0f6519d4 | 23 | unsigned n_attempts = 0; |
ed9eeb7b | 24 | struct loop_info64 info; |
8c1be37e LP |
25 | struct stat st; |
26 | LoopDevice *d; | |
3cc44114 | 27 | int nr, r; |
8c1be37e LP |
28 | |
29 | assert(fd >= 0); | |
30 | assert(ret); | |
31 | assert(IN_SET(open_flags, O_RDWR, O_RDONLY)); | |
32 | ||
33 | if (fstat(fd, &st) < 0) | |
34 | return -errno; | |
35 | ||
36 | if (S_ISBLK(st.st_mode)) { | |
ed9eeb7b LP |
37 | if (offset == 0 && IN_SET(size, 0, UINT64_MAX)) { |
38 | int copy; | |
8c1be37e | 39 | |
ed9eeb7b | 40 | /* If this is already a block device, store a copy of the fd as it is */ |
8c1be37e | 41 | |
ed9eeb7b LP |
42 | copy = fcntl(fd, F_DUPFD_CLOEXEC, 3); |
43 | if (copy < 0) | |
44 | return -errno; | |
8c1be37e | 45 | |
ed9eeb7b LP |
46 | d = new(LoopDevice, 1); |
47 | if (!d) | |
48 | return -ENOMEM; | |
49 | ||
50 | *d = (LoopDevice) { | |
51 | .fd = copy, | |
52 | .nr = -1, | |
53 | .relinquished = true, /* It's not allocated by us, don't destroy it when this object is freed */ | |
54 | }; | |
55 | ||
56 | *ret = d; | |
57 | return d->fd; | |
58 | } | |
59 | } else { | |
60 | r = stat_verify_regular(&st); | |
61 | if (r < 0) | |
62 | return r; | |
8c1be37e LP |
63 | } |
64 | ||
8c1be37e LP |
65 | control = open("/dev/loop-control", O_RDWR|O_CLOEXEC|O_NOCTTY|O_NONBLOCK); |
66 | if (control < 0) | |
67 | return -errno; | |
68 | ||
0f6519d4 LP |
69 | /* Loop around LOOP_CTL_GET_FREE, since at the moment we attempt to open the returned device it might |
70 | * be gone already, taken by somebody else racing against us. */ | |
71 | for (;;) { | |
72 | nr = ioctl(control, LOOP_CTL_GET_FREE); | |
73 | if (nr < 0) | |
74 | return -errno; | |
8c1be37e | 75 | |
0f6519d4 LP |
76 | if (asprintf(&loopdev, "/dev/loop%i", nr) < 0) |
77 | return -ENOMEM; | |
8c1be37e | 78 | |
0f6519d4 LP |
79 | loop = open(loopdev, O_CLOEXEC|O_NONBLOCK|O_NOCTTY|open_flags); |
80 | if (loop < 0) | |
81 | return -errno; | |
82 | if (ioctl(loop, LOOP_SET_FD, fd) < 0) { | |
83 | if (errno != EBUSY) | |
84 | return -errno; | |
8c1be37e | 85 | |
0f6519d4 LP |
86 | if (++n_attempts >= 64) /* Give up eventually */ |
87 | return -EBUSY; | |
88 | } else | |
89 | break; | |
90 | ||
91 | loopdev = mfree(loopdev); | |
92 | loop = safe_close(loop); | |
93 | } | |
8c1be37e | 94 | |
ed9eeb7b LP |
95 | info = (struct loop_info64) { |
96 | /* Use the specified flags, but configure the read-only flag from the open flags, and force autoclear */ | |
97 | .lo_flags = (loop_flags & ~LO_FLAGS_READ_ONLY) | ((loop_flags & O_ACCMODE) == O_RDONLY ? LO_FLAGS_READ_ONLY : 0) | LO_FLAGS_AUTOCLEAR, | |
98 | .lo_offset = offset, | |
99 | .lo_sizelimit = size == UINT64_MAX ? 0 : size, | |
100 | }; | |
101 | ||
8c1be37e LP |
102 | if (ioctl(loop, LOOP_SET_STATUS64, &info) < 0) |
103 | return -errno; | |
104 | ||
105 | d = new(LoopDevice, 1); | |
106 | if (!d) | |
107 | return -ENOMEM; | |
108 | ||
109 | *d = (LoopDevice) { | |
1cc6c93a YW |
110 | .fd = TAKE_FD(loop), |
111 | .node = TAKE_PTR(loopdev), | |
8c1be37e LP |
112 | .nr = nr, |
113 | }; | |
114 | ||
8c1be37e | 115 | *ret = d; |
26c1be0f | 116 | return d->fd; |
8c1be37e LP |
117 | } |
118 | ||
e08f94ac | 119 | int loop_device_make_by_path(const char *path, int open_flags, uint32_t loop_flags, LoopDevice **ret) { |
8c1be37e LP |
120 | _cleanup_close_ int fd = -1; |
121 | ||
122 | assert(path); | |
123 | assert(ret); | |
124 | assert(IN_SET(open_flags, O_RDWR, O_RDONLY)); | |
125 | ||
126 | fd = open(path, O_CLOEXEC|O_NONBLOCK|O_NOCTTY|open_flags); | |
127 | if (fd < 0) | |
128 | return -errno; | |
129 | ||
e08f94ac | 130 | return loop_device_make(fd, open_flags, loop_flags, ret); |
8c1be37e LP |
131 | } |
132 | ||
133 | LoopDevice* loop_device_unref(LoopDevice *d) { | |
134 | if (!d) | |
135 | return NULL; | |
136 | ||
137 | if (d->fd >= 0) { | |
138 | ||
a2ea3b2f | 139 | if (d->nr >= 0 && !d->relinquished) { |
8c1be37e LP |
140 | if (ioctl(d->fd, LOOP_CLR_FD) < 0) |
141 | log_debug_errno(errno, "Failed to clear loop device: %m"); | |
142 | ||
143 | } | |
144 | ||
145 | safe_close(d->fd); | |
146 | } | |
147 | ||
a2ea3b2f | 148 | if (d->nr >= 0 && !d->relinquished) { |
8c1be37e LP |
149 | _cleanup_close_ int control = -1; |
150 | ||
151 | control = open("/dev/loop-control", O_RDWR|O_CLOEXEC|O_NOCTTY|O_NONBLOCK); | |
152 | if (control < 0) | |
153 | log_debug_errno(errno, "Failed to open loop control device: %m"); | |
154 | else { | |
155 | if (ioctl(control, LOOP_CTL_REMOVE, d->nr) < 0) | |
156 | log_debug_errno(errno, "Failed to remove loop device: %m"); | |
157 | } | |
158 | } | |
159 | ||
160 | free(d->node); | |
5fecf46d | 161 | return mfree(d); |
8c1be37e | 162 | } |
a2ea3b2f LP |
163 | |
164 | void loop_device_relinquish(LoopDevice *d) { | |
165 | assert(d); | |
166 | ||
167 | /* Don't attempt to clean up the loop device anymore from this point on. Leave the clean-ing up to the kernel | |
168 | * itself, using the loop device "auto-clear" logic we already turned on when creating the device. */ | |
169 | ||
170 | d->relinquished = true; | |
171 | } | |
9dabc4fd LP |
172 | |
173 | int loop_device_open(const char *loop_path, int open_flags, LoopDevice **ret) { | |
174 | _cleanup_close_ int loop_fd = -1; | |
175 | _cleanup_free_ char *p = NULL; | |
176 | struct stat st; | |
177 | LoopDevice *d; | |
178 | ||
179 | assert(loop_path); | |
180 | assert(ret); | |
181 | ||
182 | loop_fd = open(loop_path, O_CLOEXEC|O_NONBLOCK|O_NOCTTY|open_flags); | |
183 | if (loop_fd < 0) | |
184 | return -errno; | |
185 | ||
186 | if (fstat(loop_fd, &st) < 0) | |
187 | return -errno; | |
188 | ||
189 | if (!S_ISBLK(st.st_mode)) | |
190 | return -ENOTBLK; | |
191 | ||
192 | p = strdup(loop_path); | |
193 | if (!p) | |
194 | return -ENOMEM; | |
195 | ||
196 | d = new(LoopDevice, 1); | |
197 | if (!d) | |
198 | return -ENOMEM; | |
199 | ||
200 | *d = (LoopDevice) { | |
201 | .fd = TAKE_FD(loop_fd), | |
202 | .nr = -1, | |
203 | .node = TAKE_PTR(p), | |
204 | .relinquished = true, /* It's not ours, don't try to destroy it when this object is freed */ | |
205 | }; | |
206 | ||
207 | *ret = d; | |
208 | return d->fd; | |
209 | } | |
210 | ||
c37878fc LP |
211 | int loop_device_refresh_size(LoopDevice *d, uint64_t offset, uint64_t size) { |
212 | struct loop_info64 info; | |
9dabc4fd LP |
213 | assert(d); |
214 | ||
215 | if (d->fd < 0) | |
216 | return -EBADF; | |
217 | ||
c37878fc LP |
218 | if (ioctl(d->fd, LOOP_GET_STATUS64, &info) < 0) |
219 | return -errno; | |
220 | ||
221 | if (size == UINT64_MAX && offset == UINT64_MAX) | |
222 | return 0; | |
223 | if (info.lo_sizelimit == size && info.lo_offset == offset) | |
224 | return 0; | |
225 | ||
226 | if (size != UINT64_MAX) | |
227 | info.lo_sizelimit = size; | |
228 | if (offset != UINT64_MAX) | |
229 | info.lo_offset = offset; | |
230 | ||
231 | if (ioctl(d->fd, LOOP_SET_STATUS64, &info) < 0) | |
9dabc4fd LP |
232 | return -errno; |
233 | ||
234 | return 0; | |
235 | } |