]> git.ipfire.org Git - thirdparty/util-linux.git/blame - libmount/src/hook_loopdev.c
Merge branch 'lsns--Q' of https://github.com/masatake/util-linux
[thirdparty/util-linux.git] / libmount / src / hook_loopdev.c
CommitLineData
2c37ca7c 1/* SPDX-License-Identifier: LGPL-2.1-or-later */
7f8b2bf3 2/*
2c37ca7c 3 * This file is part of libmount from util-linux project.
7f8b2bf3 4 *
3611b10c 5 * Copyright (C) 2011-2022 Karel Zak <kzak@redhat.com>
2c37ca7c
KZ
6 *
7 * libmount is free software; you can redistribute it and/or modify it
8 * under the terms of the GNU Lesser General Public License as published by
9 * the Free Software Foundation; either version 2.1 of the License, or
10 * (at your option) any later version.
7f014eda
KZ
11 *
12 * Please, see the comment in libmount/src/hooks.c to understand how hooks work.
7f8b2bf3 13 */
7f8b2bf3 14#include <blkid.h>
7b46647d 15#include <stdbool.h>
7f8b2bf3
KZ
16
17#include "mountP.h"
18#include "loopdev.h"
93c68844 19#include "strutils.h"
7f8b2bf3
KZ
20#include "linux_version.h"
21
3611b10c
KZ
22struct hook_data {
23 int loopdev_fd;
24};
7f8b2bf3 25
3611b10c
KZ
26/* de-initiallize this module */
27static int hookset_deinit(struct libmnt_context *cxt, const struct libmnt_hookset *hs)
28{
29 void *data;
93c68844 30
3611b10c 31 DBG(HOOK, ul_debugobj(hs, "deinit '%s'", hs->name));
7f8b2bf3 32
3611b10c
KZ
33 /* remove all our hooks */
34 while (mnt_context_remove_hook(cxt, hs, 0, &data) == 0) {
35 free(data);
36 data = NULL;
c8512236 37 }
7f8b2bf3 38
3611b10c
KZ
39 return 0;
40}
7f8b2bf3 41
3611b10c
KZ
42static inline struct hook_data *new_hook_data(void)
43{
44 struct hook_data *hd = calloc(1, sizeof(*hd));
93c68844 45
3611b10c
KZ
46 if (!hd)
47 return NULL;
7f8b2bf3 48
3611b10c
KZ
49 hd->loopdev_fd = -1;
50 return hd;
7f8b2bf3
KZ
51}
52
d58b3157 53/* Check if there already exists a mounted loop device on the mountpoint node
8b470b20
KZ
54 * with the same parameters.
55 */
ba2bdf41
KZ
56static int __attribute__((nonnull))
57is_mounted_same_loopfile(struct libmnt_context *cxt,
8b470b20
KZ
58 const char *target,
59 const char *backing_file,
60 uint64_t offset)
61{
dabfab0e 62 struct libmnt_table *tb = NULL;
8b470b20
KZ
63 struct libmnt_iter itr;
64 struct libmnt_fs *fs;
65 struct libmnt_cache *cache;
68c41a5f
KZ
66 const char *bf;
67 int rc = 0;
cddd2eaa 68 struct libmnt_ns *ns_old;
93c68844 69 unsigned long flags = 0;
8b470b20
KZ
70
71 assert(cxt);
72 assert(cxt->fs);
73 assert((cxt->flags & MNT_FL_MOUNTFLAGS_MERGED));
74
e9d52e6e 75 if (mnt_context_get_mountinfo(cxt, &tb))
8b470b20
KZ
76 return 0;
77
cddd2eaa
VD
78 ns_old = mnt_context_switch_target_ns(cxt);
79 if (!ns_old)
80 return -MNT_ERR_NAMESPACE;
81
7bbde59c 82 DBG(LOOP, ul_debugobj(cxt, "checking if %s mounted on %s",
8b470b20
KZ
83 backing_file, target));
84
93c68844
KZ
85 rc = mnt_context_get_user_mflags(cxt, &flags);
86 if (rc)
87 return 0;
88
8b470b20
KZ
89 cache = mnt_context_get_cache(cxt);
90 mnt_reset_iter(&itr, MNT_ITER_BACKWARD);
91
68c41a5f
KZ
92 bf = cache ? mnt_resolve_path(backing_file, cache) : backing_file;
93
e9d52e6e 94 /* Search for a mountpoint node in mountinfo, proceed if any of these have the
8b470b20
KZ
95 * loop option set or the device is a loop device
96 */
68c41a5f 97 while (rc == 0 && mnt_table_next_fs(tb, &itr, &fs) == 0) {
8b470b20
KZ
98 const char *src = mnt_fs_get_source(fs);
99 const char *opts = mnt_fs_get_user_options(fs);
100 char *val;
101 size_t len;
8b470b20
KZ
102
103 if (!src || !mnt_fs_match_target(fs, target, cache))
104 continue;
105
68c41a5f
KZ
106 rc = 0;
107
8b470b20 108 if (strncmp(src, "/dev/loop", 9) == 0) {
74a4705a 109 rc = loopdev_is_used((char *) src, bf, offset, 0, LOOPDEV_FL_OFFSET);
8b470b20 110
93c68844 111 } else if (opts && (flags & MNT_MS_LOOP) &&
8b470b20
KZ
112 mnt_optstr_get_option(opts, "loop", &val, &len) == 0 && val) {
113
114 val = strndup(val, len);
74a4705a 115 rc = loopdev_is_used((char *) val, bf, offset, 0, LOOPDEV_FL_OFFSET);
8b470b20
KZ
116 free(val);
117 }
8b470b20 118 }
68c41a5f 119 if (rc)
7bbde59c 120 DBG(LOOP, ul_debugobj(cxt, "%s already mounted", backing_file));
cddd2eaa
VD
121
122 if (!mnt_context_switch_ns(cxt, ns_old))
123 return -MNT_ERR_NAMESPACE;
68c41a5f 124 return rc;
8b470b20
KZ
125}
126
3611b10c
KZ
127static int setup_loopdev(struct libmnt_context *cxt,
128 struct libmnt_optlist *ol, struct hook_data *hd)
7f8b2bf3 129{
93c68844 130 const char *backing_file, *loopdev = NULL;
7f8b2bf3 131 struct loopdev_cxt lc;
b9fd3340
KZ
132 int rc = 0, lo_flags = 0;
133 uint64_t offset = 0, sizelimit = 0;
7b46647d 134 bool reuse = FALSE;
93c68844 135 struct libmnt_opt *opt, *loopopt = NULL;
7f8b2bf3 136
7f8b2bf3
KZ
137 backing_file = mnt_fs_get_srcpath(cxt->fs);
138 if (!backing_file)
139 return -EINVAL;
140
7bbde59c 141 DBG(LOOP, ul_debugobj(cxt, "trying to setup device for %s", backing_file));
7f8b2bf3 142
93c68844 143 if (mnt_optlist_is_rdonly(ol)) {
7bbde59c 144 DBG(LOOP, ul_debugobj(cxt, "enabling READ-ONLY flag"));
7f8b2bf3
KZ
145 lo_flags |= LO_FLAGS_READ_ONLY;
146 }
365e5a7c 147
b9fd3340
KZ
148 /*
149 * loop=
150 */
93c68844
KZ
151 if (!rc)
152 loopopt = mnt_optlist_get_opt(ol, MNT_MS_LOOP, cxt->map_userspace);
b9fd3340
KZ
153
154 /*
155 * offset=
156 */
93c68844
KZ
157 if (!rc && (opt = mnt_optlist_get_opt(ol, MNT_MS_OFFSET, cxt->map_userspace))
158 && mnt_opt_has_value(opt)) {
159 if (strtosize(mnt_opt_get_value(opt), &offset)) {
7bbde59c 160 DBG(LOOP, ul_debugobj(cxt, "failed to parse offset="));
61f5ff6c
KZ
161 rc = -MNT_ERR_MOUNTOPT;
162 }
7f8b2bf3
KZ
163 }
164
b9fd3340
KZ
165 /*
166 * sizelimit=
167 */
93c68844
KZ
168 if (!rc && (opt = mnt_optlist_get_opt(ol, MNT_MS_SIZELIMIT, cxt->map_userspace))
169 && mnt_opt_has_value(opt)) {
170 if (strtosize(mnt_opt_get_value(opt), &sizelimit)) {
7bbde59c 171 DBG(LOOP, ul_debugobj(cxt, "failed to parse sizelimit="));
61f5ff6c
KZ
172 rc = -MNT_ERR_MOUNTOPT;
173 }
b9fd3340
KZ
174 }
175
1a7a421e
KZ
176 /*
177 * encryption=
178 */
93c68844 179 if (!rc && mnt_optlist_get_opt(ol, MNT_MS_ENCRYPTION, cxt->map_userspace)) {
7bbde59c 180 DBG(LOOP, ul_debugobj(cxt, "encryption no longer supported"));
5cf05c71 181 rc = -MNT_ERR_MOUNTOPT;
1a7a421e
KZ
182 }
183
93c68844 184 if (!rc && is_mounted_same_loopfile(cxt,
8b470b20
KZ
185 mnt_context_get_target(cxt),
186 backing_file, offset))
187 rc = -EBUSY;
188
6e258026
SB
189 if (rc)
190 goto done_no_deinit;
191
bdf46c4d
SB
192 /* It is possible to mount the same file more times. If we set more
193 * than one loop device referring to the same file, kernel has no
194 * mechanism to detect it. To prevent data corruption, the same loop
195 * device has to be recycled.
196 */
8efad715
SB
197 if (backing_file) {
198 rc = loopcxt_init(&lc, 0);
199 if (rc)
200 goto done_no_deinit;
201
dff7e160
KZ
202 rc = loopcxt_find_overlap(&lc, backing_file, offset, sizelimit);
203 switch (rc) {
204 case 0: /* not found */
73afd3f8 205 DBG(LOOP, ul_debugobj(cxt, "not found overlapping loopdev"));
dff7e160
KZ
206 loopcxt_deinit(&lc);
207 break;
208
209 case 1: /* overlap */
73afd3f8 210 DBG(LOOP, ul_debugobj(cxt, "overlapping %s detected",
dff7e160
KZ
211 loopcxt_get_device(&lc)));
212 rc = -MNT_ERR_LOOPOVERLAP;
8efad715 213 goto done;
dff7e160
KZ
214
215 case 2: /* overlap -- full size and offset match (reuse) */
216 {
22a64b02 217 uint32_t lc_encrypt_type = 0;
8efad715 218
dff7e160 219 DBG(LOOP, ul_debugobj(cxt, "re-using existing loop device %s",
8efad715
SB
220 loopcxt_get_device(&lc)));
221
3e1fc3bb
JK
222 /* Open loop device to block device autoclear... */
223 if (loopcxt_get_fd(&lc) < 0) {
224 DBG(LOOP, ul_debugobj(cxt, "failed to get loopdev FD"));
225 rc = -errno;
226 goto done;
227 }
228
229 /*
230 * Now that we certainly have the loop device open,
231 * verify the loop device was not autocleared in the
232 * mean time.
233 */
234 if (!loopcxt_get_info(&lc)) {
235 DBG(LOOP, ul_debugobj(cxt, "lost race with %s teardown",
236 loopcxt_get_device(&lc)));
237 loopcxt_deinit(&lc);
238 break;
239 }
240
8efad715 241 /* Once a loop is initialized RO, there is no
dff7e160
KZ
242 * way to change its parameters. */
243 if (loopcxt_is_readonly(&lc)
244 && !(lo_flags & LO_FLAGS_READ_ONLY)) {
245 DBG(LOOP, ul_debugobj(cxt, "%s is read-only",
246 loopcxt_get_device(&lc)));
8efad715
SB
247 rc = -EROFS;
248 goto done;
249 }
250
dff7e160
KZ
251 /* This is no more supported, but check to be safe. */
252 if (loopcxt_get_encrypt_type(&lc, &lc_encrypt_type) == 0
253 && lc_encrypt_type != LO_CRYPT_NONE) {
8efad715
SB
254 DBG(LOOP, ul_debugobj(cxt, "encryption no longer supported for device %s",
255 loopcxt_get_device(&lc)));
256 rc = -MNT_ERR_LOOPOVERLAP;
257 goto done;
258 }
dff7e160 259 rc = 0;
3789806d 260 /* loop= used with argument. Conflict will occur. */
93c68844 261 if (mnt_opt_has_value(loopopt)) {
3789806d
SB
262 rc = -MNT_ERR_LOOPOVERLAP;
263 goto done;
7b46647d
SB
264 } else {
265 reuse = TRUE;
3789806d 266 goto success;
7b46647d 267 }
8efad715 268 }
dff7e160 269 default: /* error */
bdf46c4d
SB
270 goto done;
271 }
bdf46c4d 272 }
bdf46c4d 273
dff7e160 274 DBG(LOOP, ul_debugobj(cxt, "not found; create a new loop device"));
6e258026 275 rc = loopcxt_init(&lc, 0);
441cdba9
SB
276 if (rc)
277 goto done_no_deinit;
93c68844
KZ
278 if (mnt_opt_has_value(loopopt)) {
279 rc = loopcxt_set_device(&lc, mnt_opt_get_value(loopopt));
6e258026
SB
280 if (rc == 0)
281 loopdev = loopcxt_get_device(&lc);
282 }
b9fd3340
KZ
283 if (rc)
284 goto done;
285
e9d52e6e 286 /* since 2.6.37 we don't have to store backing filename to mountinfo
7f8b2bf3
KZ
287 * because kernel provides the name in /sys.
288 */
e9d52e6e 289 if (get_linux_version() >= KERNEL_VERSION(2, 6, 37)) {
7bbde59c 290 DBG(LOOP, ul_debugobj(cxt, "enabling AUTOCLEAR flag"));
7f8b2bf3
KZ
291 lo_flags |= LO_FLAGS_AUTOCLEAR;
292 }
293
294 do {
295 /* found free device */
296 if (!loopdev) {
297 rc = loopcxt_find_unused(&lc);
298 if (rc)
299 goto done;
7bbde59c 300 DBG(LOOP, ul_debugobj(cxt, "trying to use %s",
7f8b2bf3
KZ
301 loopcxt_get_device(&lc)));
302 }
303
b9fd3340
KZ
304 /* set device attributes
305 * -- note that loopcxt_find_unused() resets "lc"
306 */
7f8b2bf3 307 rc = loopcxt_set_backing_file(&lc, backing_file);
7f8b2bf3 308
b9fd3340
KZ
309 if (!rc && offset)
310 rc = loopcxt_set_offset(&lc, offset);
311 if (!rc && sizelimit)
312 rc = loopcxt_set_sizelimit(&lc, sizelimit);
313 if (!rc)
314 loopcxt_set_flags(&lc, lo_flags);
315 if (rc) {
7bbde59c 316 DBG(LOOP, ul_debugobj(cxt, "failed to set loop attributes"));
b9fd3340
KZ
317 goto done;
318 }
7f8b2bf3
KZ
319
320 /* setup the device */
321 rc = loopcxt_setup_device(&lc);
322 if (!rc)
323 break; /* success */
324
325 if (loopdev || rc != -EBUSY) {
7bbde59c 326 DBG(LOOP, ul_debugobj(cxt, "failed to setup device"));
82756a74 327 rc = -MNT_ERR_LOOPDEV;
b9fd3340 328 goto done;
7f8b2bf3 329 }
7bbde59c 330 DBG(LOOP, ul_debugobj(cxt, "device stolen...trying again"));
7f8b2bf3
KZ
331 } while (1);
332
bdf46c4d 333success:
7f8b2bf3
KZ
334 if (!rc)
335 rc = mnt_fs_set_source(cxt->fs, loopcxt_get_device(&lc));
336
337 if (!rc) {
93c68844 338 if (loopopt && (reuse || loopcxt_is_autoclear(&lc))) {
7f8b2bf3 339 /*
d58b3157 340 * autoclear flag accepted by the kernel, don't store
e9d52e6e 341 * the "loop=" option to utab.
7f8b2bf3 342 */
e9d52e6e 343 DBG(LOOP, ul_debugobj(cxt, "removing unnecessary loop= from utab"));
93c68844
KZ
344 mnt_optlist_remove_opt(ol, loopopt);
345 loopopt = NULL;
f0d3ff0a 346 }
7f8b2bf3 347
93c68844 348 if (!mnt_optlist_is_rdonly(ol) && loopcxt_is_readonly(&lc))
7f8b2bf3
KZ
349 /*
350 * mount planned read-write, but loopdev is read-only,
351 * let's fix mount options...
352 */
93c68844 353 mnt_optlist_append_flags(ol, MS_RDONLY, cxt->map_linux);
7f8b2bf3 354
1cde32f3
JK
355 /*
356 * We have to keep the device open until mount(1), otherwise it
357 * will be auto-cleared by kernel. However we don't want to
358 * keep writeable fd as kernel wants to block all writers to
359 * the device being mounted (in the more hardened
360 * configurations). So grab read-only fd instead.
7f8b2bf3 361 */
1cde32f3 362 hd->loopdev_fd = open(lc.device, O_RDONLY | O_CLOEXEC);
3611b10c 363 if (hd->loopdev_fd < 0) {
1cde32f3
JK
364 DBG(LOOP,
365 ul_debugobj(cxt, "failed to reopen loopdev FD"));
dff7e160 366 rc = -errno;
1cde32f3 367 }
7f8b2bf3
KZ
368 }
369done:
370 loopcxt_deinit(&lc);
6e258026 371done_no_deinit:
7f8b2bf3
KZ
372 return rc;
373}
374
3611b10c 375static int delete_loopdev(struct libmnt_context *cxt, struct hook_data *hd)
7f8b2bf3
KZ
376{
377 const char *src;
378 int rc;
379
380 assert(cxt);
381 assert(cxt->fs);
382
383 src = mnt_fs_get_srcpath(cxt->fs);
384 if (!src)
385 return -EINVAL;
386
3611b10c
KZ
387 if (hd && hd->loopdev_fd > -1) {
388 close(hd->loopdev_fd);
389 hd->loopdev_fd = -1;
390 }
7f8b2bf3 391
3611b10c 392 rc = loopdev_delete(src); /* see lib/loopdev.c */
7f8b2bf3 393
7bbde59c 394 DBG(LOOP, ul_debugobj(cxt, "deleted [rc=%d]", rc));
7f8b2bf3
KZ
395 return rc;
396}
397
3611b10c
KZ
398/* Now used by umount until context_umount.c will use hooks toosee */
399int mnt_context_delete_loopdev(struct libmnt_context *cxt)
7f8b2bf3 400{
3611b10c
KZ
401 return delete_loopdev(cxt, NULL);
402}
403
404static int is_loopdev_required(struct libmnt_context *cxt, struct libmnt_optlist *ol)
405{
406 const char *src, *type;
407 unsigned long flags = 0;
408
409 if (cxt->action != MNT_ACT_MOUNT)
410 return 0;
411 if (!cxt->fs)
412 return 0;
413 if (mnt_optlist_is_bind(ol)
414 || mnt_optlist_is_move(ol)
415 || mnt_context_propagation_only(cxt))
416 return 0;
417
418 src = mnt_fs_get_srcpath(cxt->fs);
419 if (!src)
420 return 0; /* backing file not set */
421
422 /* userspace flags */
423 if (mnt_context_get_user_mflags(cxt, &flags))
424 return 0;
425
426 if (flags & (MNT_MS_LOOP | MNT_MS_OFFSET | MNT_MS_SIZELIMIT)) {
427 DBG(LOOP, ul_debugobj(cxt, "loopdev specific options detected"));
428 return 1;
429 }
430
431 /* Automatically create a loop device from a regular file if a
432 * filesystem is not specified or the filesystem is known for libblkid
433 * (these filesystems work with block devices only). The file size
434 * should be at least 1KiB, otherwise we will create an empty loopdev with
435 * no mountable filesystem...
436 *
437 * Note that there is no restriction (on kernel side) that would prevent a regular
438 * file as a mount(2) source argument. A filesystem that is able to mount
439 * regular files could be implemented.
440 */
441 type = mnt_fs_get_fstype(cxt->fs);
442
443 if (mnt_fs_is_regularfs(cxt->fs) &&
444 (!type || strcmp(type, "auto") == 0 || blkid_known_fstype(type))) {
445 struct stat st;
446
447 if (stat(src, &st) == 0 && S_ISREG(st.st_mode) &&
448 st.st_size > 1024) {
449
450 DBG(LOOP, ul_debugobj(cxt, "automatically enabling loop= option"));
451 mnt_optlist_append_flags(ol, MNT_MS_LOOP, cxt->map_userspace);
452 return 1;
453 }
454 }
7f8b2bf3 455
3611b10c
KZ
456 return 0;
457}
458
459/* call after mount(2) */
460static int hook_cleanup_loopdev(
461 struct libmnt_context *cxt,
462 const struct libmnt_hookset *hs __attribute__((__unused__)),
463 void *data)
464{
465 struct hook_data *hd = (struct hook_data *) data;
466
467 if (!hd || hd->loopdev_fd < 0)
468 return 0;
469
470 if (mnt_context_get_status(cxt) == 0) {
7f8b2bf3
KZ
471 /*
472 * mount(2) failed, delete loopdev
473 */
3611b10c 474 delete_loopdev(cxt, hd);
7f8b2bf3 475
3611b10c 476 } else {
7f8b2bf3
KZ
477 /*
478 * mount(2) success, close the device
479 */
7bbde59c 480 DBG(LOOP, ul_debugobj(cxt, "closing FD"));
3611b10c
KZ
481 close(hd->loopdev_fd);
482 hd->loopdev_fd = -1;
7f8b2bf3 483 }
3611b10c 484
7f8b2bf3
KZ
485 return 0;
486}
487
3611b10c
KZ
488/* call to prepare mount source */
489static int hook_prepare_loopdev(
490 struct libmnt_context *cxt,
491 const struct libmnt_hookset *hs,
492 void *data __attribute__((__unused__)))
493{
494 struct libmnt_optlist *ol;
495 struct hook_data *hd;
496 int rc;
497
498 assert(cxt);
499
500 ol = mnt_context_get_optlist(cxt);
501 if (!ol)
502 return -ENOMEM;
503 if (!is_loopdev_required(cxt, ol))
504 return 0;
505 hd = new_hook_data();
506 if (!hd)
507 return -ENOMEM;
508
509 rc = setup_loopdev(cxt, ol, hd);
510 if (!rc)
511 rc = mnt_context_append_hook(cxt, hs,
512 MNT_STAGE_MOUNT_POST,
513 hd, hook_cleanup_loopdev);
514 if (rc) {
515 delete_loopdev(cxt, hd);
516 free(hd);
517 }
518 return rc;
519}
520
521
522const struct libmnt_hookset hookset_loopdev =
523{
524 .name = "__loopdev",
525
526 .firststage = MNT_STAGE_PREP_SOURCE,
527 .firstcall = hook_prepare_loopdev,
528
529 .deinit = hookset_deinit
530};