]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/core/umount.c
Add SPDX license identifiers to source files under the LGPL
[thirdparty/systemd.git] / src / core / umount.c
CommitLineData
53e1b683 1/* SPDX-License-Identifier: LGPL-2.1+ */
e3478379
FF
2/***
3 This file is part of systemd.
4
5 Copyright 2010 ProFUSION embedded systems
6
7 systemd is free software; you can redistribute it and/or modify it
5430f7f2
LP
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
e3478379
FF
10 (at your option) any later version.
11
12 systemd is distributed in the hope that it will be useful, but
13 WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
5430f7f2 15 Lesser General Public License for more details.
e3478379 16
5430f7f2 17 You should have received a copy of the GNU Lesser General Public License
e3478379
FF
18 along with systemd; If not, see <http://www.gnu.org/licenses/>.
19***/
20
21#include <errno.h>
22#include <fcntl.h>
4f5dd394 23#include <linux/loop.h>
e3478379
FF
24#include <string.h>
25#include <sys/mount.h>
26#include <sys/swap.h>
e3478379 27
b4bbcaa9
TA
28#include "libudev.h"
29
b5efdb8a 30#include "alloc-util.h"
07630cea 31#include "escape.h"
3ffd4af2 32#include "fd-util.h"
471b48ed 33#include "fstab-util.h"
dcce98a4 34#include "linux-3.13/dm-ioctl.h"
e3478379
FF
35#include "list.h"
36#include "mount-setup.h"
9eb977db 37#include "path-util.h"
07630cea 38#include "string-util.h"
4f5dd394 39#include "udev-util.h"
3ffd4af2 40#include "umount.h"
9cbc4547 41#include "mount-util.h"
e3478379 42#include "util.h"
024f268d 43#include "virt.h"
e3478379
FF
44
45typedef struct MountPoint {
46 char *path;
471b48ed 47 char *options;
9cbc4547 48 char *type;
2d9a3397 49 dev_t devnum;
71fda00f 50 LIST_FIELDS(struct MountPoint, mount_point);
e3478379
FF
51} MountPoint;
52
12aad1d0
LP
53static void mount_point_free(MountPoint **head, MountPoint *m) {
54 assert(head);
55 assert(m);
e3478379 56
71fda00f 57 LIST_REMOVE(mount_point, *head, m);
12aad1d0
LP
58
59 free(m->path);
60 free(m);
e3478379
FF
61}
62
12aad1d0
LP
63static void mount_points_list_free(MountPoint **head) {
64 assert(head);
65
66 while (*head)
67 mount_point_free(head, *head);
e3478379
FF
68}
69
12aad1d0 70static int mount_points_list_get(MountPoint **head) {
c3544e8d 71 _cleanup_fclose_ FILE *proc_self_mountinfo = NULL;
e3478379 72 unsigned int i;
527b7a42 73 int r;
e3478379 74
12aad1d0
LP
75 assert(head);
76
c3544e8d
LP
77 proc_self_mountinfo = fopen("/proc/self/mountinfo", "re");
78 if (!proc_self_mountinfo)
e3478379
FF
79 return -errno;
80
81 for (i = 1;; i++) {
9cbc4547 82 _cleanup_free_ char *path = NULL, *options = NULL, *type = NULL;
c3544e8d 83 char *p = NULL;
12aad1d0 84 MountPoint *m;
c3544e8d 85 int k;
e3478379 86
c3544e8d
LP
87 k = fscanf(proc_self_mountinfo,
88 "%*s " /* (1) mount id */
89 "%*s " /* (2) parent id */
90 "%*s " /* (3) major:minor */
91 "%*s " /* (4) root */
92 "%ms " /* (5) mount point */
471b48ed 93 "%*s" /* (6) mount flags */
c3544e8d
LP
94 "%*[^-]" /* (7) optional fields */
95 "- " /* (8) separator */
9cbc4547 96 "%ms " /* (9) file system type */
c3544e8d 97 "%*s" /* (10) mount source */
471b48ed 98 "%ms" /* (11) mount options */
c3544e8d 99 "%*[^\n]", /* some rubbish at the end */
9cbc4547 100 &path, &type, &options);
3d4ec012 101 if (k != 3) {
e3478379
FF
102 if (k == EOF)
103 break;
104
105 log_warning("Failed to parse /proc/self/mountinfo:%u.", i);
e3478379
FF
106 continue;
107 }
108
527b7a42
LP
109 r = cunescape(path, UNESCAPE_RELAX, &p);
110 if (r < 0)
111 return r;
e3478379 112
46108b3b
LP
113 /* Ignore mount points we can't unmount because they
114 * are API or because we are keeping them open (like
874d3404
LP
115 * /dev/console). Also, ignore all mounts below API
116 * file systems, since they are likely virtual too,
117 * and hence not worth spending time on. Also, in
118 * unprivileged containers we might lack the rights to
119 * unmount these things, hence don't bother. */
46108b3b
LP
120 if (mount_point_is_api(p) ||
121 mount_point_ignore(p) ||
874d3404
LP
122 path_startswith(p, "/dev") ||
123 path_startswith(p, "/sys") ||
124 path_startswith(p, "/proc")) {
2054a5b8
LP
125 free(p);
126 continue;
127 }
128
c3544e8d
LP
129 m = new0(MountPoint, 1);
130 if (!m) {
2054a5b8 131 free(p);
c3544e8d 132 return -ENOMEM;
e3478379 133 }
e3478379 134
12aad1d0 135 m->path = p;
471b48ed
JJ
136 m->options = options;
137 options = NULL;
9cbc4547
N
138 m->type = type;
139 type = NULL;
471b48ed 140
71fda00f 141 LIST_PREPEND(mount_point, *head, m);
e3478379
FF
142 }
143
c3544e8d 144 return 0;
e3478379
FF
145}
146
12aad1d0 147static int swap_list_get(MountPoint **head) {
e1d75803 148 _cleanup_fclose_ FILE *proc_swaps = NULL;
e3478379 149 unsigned int i;
527b7a42 150 int r;
e3478379 151
12aad1d0
LP
152 assert(head);
153
527b7a42
LP
154 proc_swaps = fopen("/proc/swaps", "re");
155 if (!proc_swaps)
dee87d61 156 return (errno == ENOENT) ? 0 : -errno;
e3478379
FF
157
158 (void) fscanf(proc_swaps, "%*s %*s %*s %*s %*s\n");
159
160 for (i = 2;; i++) {
161 MountPoint *swap;
162 char *dev = NULL, *d;
163 int k;
164
527b7a42
LP
165 k = fscanf(proc_swaps,
166 "%ms " /* device/file */
167 "%*s " /* type of swap */
168 "%*s " /* swap size */
169 "%*s " /* used */
170 "%*s\n", /* priority */
171 &dev);
e3478379 172
527b7a42 173 if (k != 1) {
e3478379
FF
174 if (k == EOF)
175 break;
176
177 log_warning("Failed to parse /proc/swaps:%u.", i);
e3478379
FF
178 free(dev);
179 continue;
180 }
181
a87f0f72 182 if (endswith(dev, " (deleted)")) {
e3478379
FF
183 free(dev);
184 continue;
185 }
186
527b7a42 187 r = cunescape(dev, UNESCAPE_RELAX, &d);
e3478379 188 free(dev);
527b7a42
LP
189 if (r < 0)
190 return r;
e3478379 191
527b7a42
LP
192 swap = new0(MountPoint, 1);
193 if (!swap) {
e3478379 194 free(d);
e1d75803 195 return -ENOMEM;
e3478379
FF
196 }
197
2d9a3397 198 swap->path = d;
71fda00f 199 LIST_PREPEND(mount_point, *head, swap);
e3478379
FF
200 }
201
e1d75803 202 return 0;
e3478379
FF
203}
204
12aad1d0 205static int loopback_list_get(MountPoint **head) {
1ca208fb 206 _cleanup_udev_enumerate_unref_ struct udev_enumerate *e = NULL;
e3478379 207 struct udev_list_entry *item = NULL, *first = NULL;
06acf2d4
LP
208 _cleanup_udev_unref_ struct udev *udev = NULL;
209 int r;
e3478379 210
12aad1d0
LP
211 assert(head);
212
1ca208fb
ZJS
213 udev = udev_new();
214 if (!udev)
215 return -ENOMEM;
e3478379 216
1ca208fb
ZJS
217 e = udev_enumerate_new(udev);
218 if (!e)
219 return -ENOMEM;
e3478379 220
06acf2d4
LP
221 r = udev_enumerate_add_match_subsystem(e, "block");
222 if (r < 0)
223 return r;
224
225 r = udev_enumerate_add_match_sysname(e, "loop*");
226 if (r < 0)
227 return r;
228
229 r = udev_enumerate_add_match_sysattr(e, "loop/backing_file", NULL);
230 if (r < 0)
231 return r;
e3478379 232
06acf2d4
LP
233 r = udev_enumerate_scan_devices(e);
234 if (r < 0)
235 return r;
e3478379
FF
236
237 first = udev_enumerate_get_list_entry(e);
e3478379
FF
238 udev_list_entry_foreach(item, first) {
239 MountPoint *lb;
1ca208fb 240 _cleanup_udev_device_unref_ struct udev_device *d;
e3478379 241 char *loop;
b854a7e7 242 const char *dn;
e3478379 243
1ca208fb
ZJS
244 d = udev_device_new_from_syspath(udev, udev_list_entry_get_name(item));
245 if (!d)
246 return -ENOMEM;
e3478379 247
1ca208fb
ZJS
248 dn = udev_device_get_devnode(d);
249 if (!dn)
2d9a3397 250 continue;
b854a7e7 251
2d9a3397 252 loop = strdup(dn);
1ca208fb
ZJS
253 if (!loop)
254 return -ENOMEM;
b854a7e7 255
1ca208fb
ZJS
256 lb = new0(MountPoint, 1);
257 if (!lb) {
e3478379 258 free(loop);
1ca208fb 259 return -ENOMEM;
e3478379
FF
260 }
261
2d9a3397 262 lb->path = loop;
71fda00f 263 LIST_PREPEND(mount_point, *head, lb);
e3478379
FF
264 }
265
1ca208fb 266 return 0;
e3478379
FF
267}
268
12aad1d0 269static int dm_list_get(MountPoint **head) {
1ca208fb 270 _cleanup_udev_enumerate_unref_ struct udev_enumerate *e = NULL;
d48141ba 271 struct udev_list_entry *item = NULL, *first = NULL;
06acf2d4
LP
272 _cleanup_udev_unref_ struct udev *udev = NULL;
273 int r;
d48141ba 274
12aad1d0
LP
275 assert(head);
276
1ca208fb
ZJS
277 udev = udev_new();
278 if (!udev)
279 return -ENOMEM;
d48141ba 280
1ca208fb
ZJS
281 e = udev_enumerate_new(udev);
282 if (!e)
283 return -ENOMEM;
d48141ba 284
06acf2d4
LP
285 r = udev_enumerate_add_match_subsystem(e, "block");
286 if (r < 0)
287 return r;
d48141ba 288
06acf2d4
LP
289 r = udev_enumerate_add_match_sysname(e, "dm-*");
290 if (r < 0)
291 return r;
d48141ba 292
06acf2d4
LP
293 r = udev_enumerate_scan_devices(e);
294 if (r < 0)
295 return r;
d48141ba 296
06acf2d4 297 first = udev_enumerate_get_list_entry(e);
d48141ba 298 udev_list_entry_foreach(item, first) {
2d9a3397 299 MountPoint *m;
1ca208fb 300 _cleanup_udev_device_unref_ struct udev_device *d;
2d9a3397
LP
301 dev_t devnum;
302 char *node;
303 const char *dn;
d48141ba 304
1ca208fb
ZJS
305 d = udev_device_new_from_syspath(udev, udev_list_entry_get_name(item));
306 if (!d)
307 return -ENOMEM;
d48141ba 308
2d9a3397
LP
309 devnum = udev_device_get_devnum(d);
310 dn = udev_device_get_devnode(d);
1ca208fb 311 if (major(devnum) == 0 || !dn)
2d9a3397 312 continue;
d48141ba 313
2d9a3397 314 node = strdup(dn);
1ca208fb
ZJS
315 if (!node)
316 return -ENOMEM;
d48141ba 317
1ca208fb
ZJS
318 m = new(MountPoint, 1);
319 if (!m) {
2d9a3397 320 free(node);
1ca208fb 321 return -ENOMEM;
d48141ba
LP
322 }
323
2d9a3397
LP
324 m->path = node;
325 m->devnum = devnum;
71fda00f 326 LIST_PREPEND(mount_point, *head, m);
d48141ba
LP
327 }
328
1ca208fb 329 return 0;
d48141ba
LP
330}
331
e3478379 332static int delete_loopback(const char *device) {
03e334a1
LP
333 _cleanup_close_ int fd = -1;
334 int r;
e3478379 335
03e334a1
LP
336 fd = open(device, O_RDONLY|O_CLOEXEC);
337 if (fd < 0)
c4f8bd1a 338 return errno == ENOENT ? 0 : -errno;
e3478379 339
ce726252 340 r = ioctl(fd, LOOP_CLR_FD, 0);
12aad1d0
LP
341 if (r >= 0)
342 return 1;
343
ce726252 344 /* ENXIO: not bound, so no error */
12aad1d0
LP
345 if (errno == ENXIO)
346 return 0;
347
348 return -errno;
e3478379
FF
349}
350
2d9a3397 351static int delete_dm(dev_t devnum) {
cf139e60 352
b92bea5d 353 struct dm_ioctl dm = {
cf139e60
LP
354 .version = {
355 DM_VERSION_MAJOR,
356 DM_VERSION_MINOR,
357 DM_VERSION_PATCHLEVEL
358 },
b92bea5d
ZJS
359 .data_size = sizeof(dm),
360 .dev = devnum,
361 };
d48141ba 362
cf139e60
LP
363 _cleanup_close_ int fd = -1;
364
2d9a3397 365 assert(major(devnum) != 0);
d48141ba 366
e62d8c39
ZJS
367 fd = open("/dev/mapper/control", O_RDWR|O_CLOEXEC);
368 if (fd < 0)
d48141ba
LP
369 return -errno;
370
cf139e60
LP
371 if (ioctl(fd, DM_DEV_REMOVE, &dm) < 0)
372 return -errno;
373
374 return 0;
d48141ba
LP
375}
376
c826cd3f
ZJS
377static bool nonunmountable_path(const char *path) {
378 return path_equal(path, "/")
349cc4a5 379#if ! HAVE_SPLIT_USR
c826cd3f
ZJS
380 || path_equal(path, "/usr")
381#endif
382 || path_startswith(path, "/run/initramfs");
383}
384
116e6d96
AJ
385/* This includes remounting readonly, which changes the kernel mount options.
386 * Therefore the list passed to this function is invalidated, and should not be reused. */
387
31657718 388static int mount_points_list_umount(MountPoint **head, bool *changed, bool log_error) {
116e6d96 389 MountPoint *m;
12aad1d0 390 int n_failed = 0;
e3478379 391
12aad1d0
LP
392 assert(head);
393
116e6d96
AJ
394 LIST_FOREACH(mount_point, m, *head) {
395 bool mount_is_readonly;
396
397 mount_is_readonly = fstab_test_yes_no_option(m->options, "ro\0rw\0");
93bd1577
LP
398
399 /* If we are in a container, don't attempt to
400 read-only mount anything as that brings no real
401 benefits, but might confuse the host, as we remount
9cbc4547
N
402 the superblock here, not the bind mount.
403 If the filesystem is a network fs, also skip the
404 remount. It brings no value (we cannot leave
c44cac7c
N
405 a "dirty fs") and could hang if the network is down.
406 Note that umount2() is more careful and will not
407 hang because of the network being down. */
9cbc4547 408 if (detect_container() <= 0 &&
116e6d96
AJ
409 !fstype_is_network(m->type) &&
410 !mount_is_readonly) {
471b48ed
JJ
411 _cleanup_free_ char *options = NULL;
412 /* MS_REMOUNT requires that the data parameter
413 * should be the same from the original mount
414 * except for the desired changes. Since we want
415 * to remount read-only, we should filter out
416 * rw (and ro too, because it confuses the kernel) */
417 (void) fstab_filter_options(m->options, "rw\0ro\0", NULL, NULL, &options);
418
93bd1577
LP
419 /* We always try to remount directories
420 * read-only first, before we go on and umount
421 * them.
422 *
423 * Mount points can be stacked. If a mount
424 * point is stacked below / or /usr, we
ab06eef8 425 * cannot umount or remount it directly,
93bd1577
LP
426 * since there is no way to refer to the
427 * underlying mount. There's nothing we can do
428 * about it for the general case, but we can
429 * do something about it if it is aliased
430 * somehwere else via a bind mount. If we
431 * explicitly remount the super block of that
432 * alias read-only we hence should be
c826cd3f
ZJS
433 * relatively safe regarding keeping dirty an fs
434 * we cannot otherwise see. */
471b48ed 435 log_info("Remounting '%s' read-only with options '%s'.", m->path, options);
c826cd3f
ZJS
436 if (mount(NULL, m->path, NULL, MS_REMOUNT|MS_RDONLY, options) < 0) {
437 if (log_error)
438 log_notice_errno(errno, "Failed to remount '%s' read-only: %m", m->path);
439 if (nonunmountable_path(m->path))
440 n_failed++;
441 }
93bd1577
LP
442 }
443
444 /* Skip / and /usr since we cannot unmount that
5a6f9d23
HG
445 * anyway, since we are running from it. They have
446 * already been remounted ro. */
c826cd3f 447 if (nonunmountable_path(m->path))
e3478379
FF
448 continue;
449
c44cac7c
N
450 /* Trying to umount. Using MNT_FORCE causes some
451 * filesystems (e.g. FUSE and NFS and other network
452 * filesystems) to abort any pending requests and
453 * return -EIO rather than blocking indefinitely.
454 * If the filesysten is "busy", this may allow processes
455 * to die, thus making the filesystem less busy so
456 * the unmount might succeed (rather then return EBUSY).*/
bce93b7a 457 log_info("Unmounting %s.", m->path);
c44cac7c 458 if (umount2(m->path, MNT_FORCE) == 0) {
12aad1d0
LP
459 if (changed)
460 *changed = true;
c826cd3f
ZJS
461 } else {
462 if (log_error)
463 log_warning_errno(errno, "Could not unmount %s: %m", m->path);
12aad1d0 464 n_failed++;
e3478379
FF
465 }
466 }
467
12aad1d0 468 return n_failed;
e3478379
FF
469}
470
12aad1d0
LP
471static int swap_points_list_off(MountPoint **head, bool *changed) {
472 MountPoint *m, *n;
473 int n_failed = 0;
474
475 assert(head);
e3478379 476
12aad1d0 477 LIST_FOREACH_SAFE(mount_point, m, n, *head) {
735e0712 478 log_info("Deactivating swap %s.", m->path);
12aad1d0
LP
479 if (swapoff(m->path) == 0) {
480 if (changed)
481 *changed = true;
482
483 mount_point_free(head, m);
484 } else {
56f64d95 485 log_warning_errno(errno, "Could not deactivate swap %s: %m", m->path);
12aad1d0 486 n_failed++;
e3478379
FF
487 }
488 }
489
12aad1d0 490 return n_failed;
e3478379
FF
491}
492
12aad1d0
LP
493static int loopback_points_list_detach(MountPoint **head, bool *changed) {
494 MountPoint *m, *n;
7fc942b2
LP
495 int n_failed = 0, k;
496 struct stat root_st;
12aad1d0
LP
497
498 assert(head);
499
7fc942b2
LP
500 k = lstat("/", &root_st);
501
12aad1d0
LP
502 LIST_FOREACH_SAFE(mount_point, m, n, *head) {
503 int r;
7fc942b2
LP
504 struct stat loopback_st;
505
506 if (k >= 0 &&
507 major(root_st.st_dev) != 0 &&
508 lstat(m->path, &loopback_st) >= 0 &&
509 root_st.st_dev == loopback_st.st_rdev) {
313cefa1 510 n_failed++;
7fc942b2
LP
511 continue;
512 }
e3478379 513
735e0712 514 log_info("Detaching loopback %s.", m->path);
bce93b7a
MS
515 r = delete_loopback(m->path);
516 if (r >= 0) {
12aad1d0
LP
517 if (r > 0 && changed)
518 *changed = true;
519
520 mount_point_free(head, m);
521 } else {
56f64d95 522 log_warning_errno(errno, "Could not detach loopback %s: %m", m->path);
12aad1d0 523 n_failed++;
e3478379
FF
524 }
525 }
526
12aad1d0 527 return n_failed;
e3478379
FF
528}
529
12aad1d0
LP
530static int dm_points_list_detach(MountPoint **head, bool *changed) {
531 MountPoint *m, *n;
33e8d8af
FB
532 int n_failed = 0, r;
533 dev_t rootdev;
12aad1d0
LP
534
535 assert(head);
536
33e8d8af
FB
537 r = get_block_device("/", &rootdev);
538 if (r <= 0)
539 rootdev = 0;
7fc942b2 540
12aad1d0 541 LIST_FOREACH_SAFE(mount_point, m, n, *head) {
12aad1d0 542
33e8d8af
FB
543 if (major(rootdev) != 0)
544 if (rootdev == m->devnum) {
545 n_failed ++;
546 continue;
547 }
7fc942b2 548
735e0712 549 log_info("Detaching DM %u:%u.", major(m->devnum), minor(m->devnum));
bce93b7a
MS
550 r = delete_dm(m->devnum);
551 if (r >= 0) {
c6784066 552 if (changed)
12aad1d0 553 *changed = true;
d48141ba 554
12aad1d0
LP
555 mount_point_free(head, m);
556 } else {
56f64d95 557 log_warning_errno(errno, "Could not detach DM %s: %m", m->path);
12aad1d0 558 n_failed++;
d48141ba
LP
559 }
560 }
561
12aad1d0 562 return n_failed;
d48141ba
LP
563}
564
116e6d96 565static int umount_all_once(bool *changed, bool log_error) {
e3478379
FF
566 int r;
567 LIST_HEAD(MountPoint, mp_list_head);
568
71fda00f 569 LIST_HEAD_INIT(mp_list_head);
e3478379
FF
570 r = mount_points_list_get(&mp_list_head);
571 if (r < 0)
572 goto end;
573
116e6d96
AJ
574 r = mount_points_list_umount(&mp_list_head, changed, log_error);
575
576 end:
577 mount_points_list_free(&mp_list_head);
578
579 return r;
580}
581
582int umount_all(bool *changed) {
583 bool umount_changed;
584 int r;
585
6f7f51f7
HH
586 /* retry umount, until nothing can be umounted anymore */
587 do {
588 umount_changed = false;
3e085b6c 589
116e6d96 590 umount_all_once(&umount_changed, false);
6f7f51f7
HH
591 if (umount_changed)
592 *changed = true;
3e085b6c
LP
593 } while (umount_changed);
594
31657718 595 /* umount one more time with logging enabled */
116e6d96 596 r = umount_all_once(&umount_changed, true);
066b753d
AJ
597 if (umount_changed)
598 *changed = true;
e3478379 599
e3478379
FF
600 return r;
601}
602
12aad1d0 603int swapoff_all(bool *changed) {
e3478379
FF
604 int r;
605 LIST_HEAD(MountPoint, swap_list_head);
606
71fda00f 607 LIST_HEAD_INIT(swap_list_head);
e3478379
FF
608
609 r = swap_list_get(&swap_list_head);
610 if (r < 0)
611 goto end;
612
12aad1d0 613 r = swap_points_list_off(&swap_list_head, changed);
e3478379
FF
614
615 end:
616 mount_points_list_free(&swap_list_head);
617
618 return r;
619}
620
12aad1d0 621int loopback_detach_all(bool *changed) {
e3478379
FF
622 int r;
623 LIST_HEAD(MountPoint, loopback_list_head);
624
71fda00f 625 LIST_HEAD_INIT(loopback_list_head);
e3478379
FF
626
627 r = loopback_list_get(&loopback_list_head);
628 if (r < 0)
629 goto end;
630
12aad1d0 631 r = loopback_points_list_detach(&loopback_list_head, changed);
e3478379
FF
632
633 end:
634 mount_points_list_free(&loopback_list_head);
635
636 return r;
637}
d48141ba 638
12aad1d0 639int dm_detach_all(bool *changed) {
d48141ba
LP
640 int r;
641 LIST_HEAD(MountPoint, dm_list_head);
642
71fda00f 643 LIST_HEAD_INIT(dm_list_head);
d48141ba
LP
644
645 r = dm_list_get(&dm_list_head);
646 if (r < 0)
647 goto end;
648
12aad1d0 649 r = dm_points_list_detach(&dm_list_head, changed);
d48141ba
LP
650
651 end:
652 mount_points_list_free(&dm_list_head);
653
654 return r;
655}