]> git.ipfire.org Git - thirdparty/util-linux.git/blob - libmount/src/tab.c
674dc67e5b17796f293b3dadb7a97d7ba85f1788
[thirdparty/util-linux.git] / libmount / src / tab.c
1
2 /* SPDX-License-Identifier: LGPL-2.1-or-later */
3 /*
4 * This file is part of libmount from util-linux project.
5 *
6 * Copyright (C) 2008-2018 Karel Zak <kzak@redhat.com>
7 *
8 * libmount is free software; you can redistribute it and/or modify it
9 * under the terms of the GNU Lesser General Public License as published by
10 * the Free Software Foundation; either version 2.1 of the License, or
11 * (at your option) any later version.
12 */
13
14 /**
15 * SECTION: table
16 * @title: Table of filesystems
17 * @short_description: container for entries from fstab, mtab or mountinfo
18 *
19 * Note that mnt_table_find_* functions are mount(8) compatible. These functions
20 * try to find an entry in more iterations, where the first attempt is always
21 * based on comparison with unmodified (non-canonicalized or un-evaluated)
22 * paths or tags. For example a fstab with two entries:
23 * <informalexample>
24 * <programlisting>
25 * LABEL=foo /foo auto rw
26 * /dev/foo /foo auto rw
27 * </programlisting>
28 * </informalexample>
29 *
30 * where both lines are used for the *same* device, then
31 * <informalexample>
32 * <programlisting>
33 * mnt_table_find_source(tb, "/dev/foo", &fs);
34 * </programlisting>
35 * </informalexample>
36 * will returns the second line, and
37 * <informalexample>
38 * <programlisting>
39 * mnt_table_find_source(tb, "LABEL=foo", &fs);
40 * </programlisting>
41 * </informalexample>
42 * will returns the first entry, and
43 * <informalexample>
44 * <programlisting>
45 * mnt_table_find_source(tb, "UUID=anyuuid", &fs);
46 * </programlisting>
47 * </informalexample>
48 * will return the first entry (if UUID matches with the device).
49 */
50 #include <blkid.h>
51
52 #include "mountP.h"
53 #include "strutils.h"
54 #include "loopdev.h"
55 #include "fileutils.h"
56 #include "canonicalize.h"
57
58 int is_mountinfo(struct libmnt_table *tb)
59 {
60 struct libmnt_fs *fs;
61
62 if (!tb)
63 return 0;
64
65 fs = list_first_entry(&tb->ents, struct libmnt_fs, ents);
66 if (fs && mnt_fs_is_kernel(fs) && mnt_fs_get_root(fs))
67 return 1;
68
69 return 0;
70 }
71
72 /**
73 * mnt_new_table:
74 *
75 * The tab is a container for struct libmnt_fs entries that usually represents a fstab,
76 * mtab or mountinfo file from your system.
77 *
78 * See also mnt_table_parse_file().
79 *
80 * Returns: newly allocated tab struct.
81 */
82 struct libmnt_table *mnt_new_table(void)
83 {
84 struct libmnt_table *tb = NULL;
85
86 tb = calloc(1, sizeof(*tb));
87 if (!tb)
88 return NULL;
89
90 DBG(TAB, ul_debugobj(tb, "alloc"));
91 tb->refcount = 1;
92 INIT_LIST_HEAD(&tb->ents);
93 return tb;
94 }
95
96 /**
97 * mnt_reset_table:
98 * @tb: tab pointer
99 *
100 * Removes all entries (filesystems) from the table. The filesystems with zero
101 * reference count will be deallocated.
102 *
103 * Returns: 0 on success or negative number in case of error.
104 */
105 int mnt_reset_table(struct libmnt_table *tb)
106 {
107 if (!tb)
108 return -EINVAL;
109
110 DBG(TAB, ul_debugobj(tb, "reset"));
111
112 while (!list_empty(&tb->ents)) {
113 struct libmnt_fs *fs = list_entry(tb->ents.next,
114 struct libmnt_fs, ents);
115 mnt_table_remove_fs(tb, fs);
116 }
117
118 tb->nents = 0;
119 return 0;
120 }
121
122 /**
123 * mnt_ref_table:
124 * @tb: table pointer
125 *
126 * Increments reference counter.
127 */
128 void mnt_ref_table(struct libmnt_table *tb)
129 {
130 if (tb) {
131 tb->refcount++;
132 /*DBG(FS, ul_debugobj(tb, "ref=%d", tb->refcount));*/
133 }
134 }
135
136 /**
137 * mnt_unref_table:
138 * @tb: table pointer
139 *
140 * De-increments reference counter, on zero the @tb is automatically
141 * deallocated by mnt_free_table().
142 */
143 void mnt_unref_table(struct libmnt_table *tb)
144 {
145 if (tb) {
146 tb->refcount--;
147 /*DBG(FS, ul_debugobj(tb, "unref=%d", tb->refcount));*/
148 if (tb->refcount <= 0)
149 mnt_free_table(tb);
150 }
151 }
152
153
154 /**
155 * mnt_free_table:
156 * @tb: tab pointer
157 *
158 * Deallocates the table. This function does not care about reference count. Don't
159 * use this function directly -- it's better to use mnt_unref_table().
160 *
161 * The table entries (filesystems) are unreferenced by mnt_reset_table() and
162 * cache by mnt_unref_cache().
163 */
164 void mnt_free_table(struct libmnt_table *tb)
165 {
166 if (!tb)
167 return;
168
169 mnt_reset_table(tb);
170 DBG(TAB, ul_debugobj(tb, "free [refcount=%d]", tb->refcount));
171
172 mnt_unref_cache(tb->cache);
173 free(tb->comm_intro);
174 free(tb->comm_tail);
175 free(tb);
176 }
177
178 /**
179 * mnt_table_get_nents:
180 * @tb: pointer to tab
181 *
182 * Returns: number of entries in table.
183 */
184 int mnt_table_get_nents(struct libmnt_table *tb)
185 {
186 return tb ? tb->nents : 0;
187 }
188
189 /**
190 * mnt_table_is_empty:
191 * @tb: pointer to tab
192 *
193 * Returns: 1 if the table is without filesystems, or 0.
194 */
195 int mnt_table_is_empty(struct libmnt_table *tb)
196 {
197 return tb == NULL || list_empty(&tb->ents) ? 1 : 0;
198 }
199
200 /**
201 * mnt_table_set_userdata:
202 * @tb: pointer to tab
203 * @data: pointer to user data
204 *
205 * Sets pointer to the private user data.
206 *
207 * Returns: 0 on success or negative number in case of error.
208 */
209 int mnt_table_set_userdata(struct libmnt_table *tb, void *data)
210 {
211 if (!tb)
212 return -EINVAL;
213
214 tb->userdata = data;
215 return 0;
216 }
217
218 /**
219 * mnt_table_get_userdata:
220 * @tb: pointer to tab
221 *
222 * Returns: pointer to user's data.
223 */
224 void *mnt_table_get_userdata(struct libmnt_table *tb)
225 {
226 return tb ? tb->userdata : NULL;
227 }
228
229 /**
230 * mnt_table_enable_comments:
231 * @tb: pointer to tab
232 * @enable: TRUE or FALSE
233 *
234 * Enables parsing of comments.
235 *
236 * The initial (intro) file comment is accessible by
237 * mnt_table_get_intro_comment(). The intro and the comment of the first fstab
238 * entry has to be separated by blank line. The filesystem comments are
239 * accessible by mnt_fs_get_comment(). The trailing fstab comment is accessible
240 * by mnt_table_get_trailing_comment().
241 *
242 * <informalexample>
243 * <programlisting>
244 * #
245 * # Intro comment
246 * #
247 *
248 * # this comments belongs to the first fs
249 * LABEL=foo /mnt/foo auto defaults 1 2
250 * # this comments belongs to the second fs
251 * LABEL=bar /mnt/bar auto defaults 1 2
252 * # tailing comment
253 * </programlisting>
254 * </informalexample>
255 */
256 void mnt_table_enable_comments(struct libmnt_table *tb, int enable)
257 {
258 if (tb)
259 tb->comms = enable;
260 }
261
262 /**
263 * mnt_table_with_comments:
264 * @tb: pointer to table
265 *
266 * Returns: 1 if comments parsing is enabled, or 0.
267 */
268 int mnt_table_with_comments(struct libmnt_table *tb)
269 {
270 assert(tb);
271 return tb ? tb->comms : 0;
272 }
273
274 /**
275 * mnt_table_get_intro_comment:
276 * @tb: pointer to tab
277 *
278 * Returns: initial comment in tb
279 */
280 const char *mnt_table_get_intro_comment(struct libmnt_table *tb)
281 {
282 return tb ? tb->comm_intro : NULL;
283 }
284
285 /**
286 * mnt_table_set_into_comment:
287 * @tb: pointer to tab
288 * @comm: comment or NULL
289 *
290 * Sets the initial comment in tb.
291 *
292 * Returns: 0 on success or negative number in case of error.
293 */
294 int mnt_table_set_intro_comment(struct libmnt_table *tb, const char *comm)
295 {
296 return strdup_to_struct_member(tb, comm_intro, comm);
297 }
298
299 /**
300 * mnt_table_append_into_comment:
301 * @tb: pointer to tab
302 * @comm: comment of NULL
303 *
304 * Appends the initial comment in tb.
305 *
306 * Returns: 0 on success or negative number in case of error.
307 */
308 int mnt_table_append_intro_comment(struct libmnt_table *tb, const char *comm)
309 {
310 if (!tb)
311 return -EINVAL;
312 return append_string(&tb->comm_intro, comm);
313 }
314
315 /**
316 * mnt_table_get_trailing_comment:
317 * @tb: pointer to tab
318 *
319 * Returns: table trailing comment
320 */
321 const char *mnt_table_get_trailing_comment(struct libmnt_table *tb)
322 {
323 return tb ? tb->comm_tail : NULL;
324 }
325
326 /**
327 * mnt_table_set_trailing_comment
328 * @tb: pointer to tab
329 * @comm: comment string
330 *
331 * Sets the trailing comment in table.
332 *
333 * Returns: 0 on success or negative number in case of error.
334 */
335 int mnt_table_set_trailing_comment(struct libmnt_table *tb, const char *comm)
336 {
337 return strdup_to_struct_member(tb, comm_tail, comm);
338 }
339
340 /**
341 * mnt_table_append_trailing_comment:
342 * @tb: pointer to tab
343 * @comm: comment of NULL
344 *
345 * Appends to the trailing table comment.
346 *
347 * Returns: 0 on success or negative number in case of error.
348 */
349 int mnt_table_append_trailing_comment(struct libmnt_table *tb, const char *comm)
350 {
351 if (!tb)
352 return -EINVAL;
353 return append_string(&tb->comm_tail, comm);
354 }
355
356 /**
357 * mnt_table_set_cache:
358 * @tb: pointer to tab
359 * @mpc: pointer to struct libmnt_cache instance
360 *
361 * Sets up a cache for canonicalized paths and evaluated tags (LABEL/UUID). The
362 * cache is recommended for mnt_table_find_*() functions.
363 *
364 * The cache could be shared between more tabs. Be careful when you share the
365 * same cache between more threads -- currently the cache does not provide any
366 * locking method.
367 *
368 * This function increments cache reference counter. It's recommended to use
369 * mnt_unref_cache() after mnt_table_set_cache() if you want to keep the cache
370 * referenced by @tb only.
371 *
372 * See also mnt_new_cache().
373 *
374 * Returns: 0 on success or negative number in case of error.
375 */
376 int mnt_table_set_cache(struct libmnt_table *tb, struct libmnt_cache *mpc)
377 {
378 if (!tb)
379 return -EINVAL;
380
381 mnt_ref_cache(mpc); /* new */
382 mnt_unref_cache(tb->cache); /* old */
383 tb->cache = mpc;
384 return 0;
385 }
386
387 /**
388 * mnt_table_get_cache:
389 * @tb: pointer to tab
390 *
391 * Returns: pointer to struct libmnt_cache instance or NULL.
392 */
393 struct libmnt_cache *mnt_table_get_cache(struct libmnt_table *tb)
394 {
395 return tb ? tb->cache : NULL;
396 }
397
398 /**
399 * mnt_table_find_fs:
400 * @tb: tab pointer
401 * @fs: entry to look for
402 *
403 * Checks if @fs is part of table @tb.
404 *
405 * Returns: index of @fs in table, 0 if not found or negative number in case of error.
406 */
407 int mnt_table_find_fs(struct libmnt_table *tb, struct libmnt_fs *fs)
408 {
409 struct list_head *p;
410 int i = 0;
411
412 if (!tb || !fs)
413 return -EINVAL;
414
415 if (list_empty(&fs->ents))
416 return 0;
417
418 /* Let's use directly list rather than mnt_table_next_fs() as we
419 * compare list entry with fs only.
420 */
421 list_for_each(p, &tb->ents) {
422 ++i;
423 if (list_entry(p, struct libmnt_fs, ents) == fs)
424 return i;
425 }
426
427 return 0;
428 }
429
430 /**
431 * mnt_table_add_fs:
432 * @tb: tab pointer
433 * @fs: new entry
434 *
435 * Adds a new entry to tab and increment @fs reference counter. Don't forget to
436 * use mnt_unref_fs() after mnt_table_add_fs() you want to keep the @fs
437 * referenced by the table only.
438 *
439 * Returns: 0 on success or negative number in case of error.
440 */
441 int mnt_table_add_fs(struct libmnt_table *tb, struct libmnt_fs *fs)
442 {
443 if (!tb || !fs)
444 return -EINVAL;
445
446 if (!list_empty(&fs->ents))
447 return -EBUSY;
448
449 mnt_ref_fs(fs);
450 list_add_tail(&fs->ents, &tb->ents);
451 tb->nents++;
452
453 DBG(TAB, ul_debugobj(tb, "add entry: %s %s",
454 mnt_fs_get_source(fs), mnt_fs_get_target(fs)));
455 return 0;
456 }
457
458 static int __table_insert_fs(
459 struct libmnt_table *tb, int before,
460 struct libmnt_fs *pos, struct libmnt_fs *fs)
461 {
462 struct list_head *head = pos ? &pos->ents : &tb->ents;
463
464 if (before)
465 list_add(&fs->ents, head);
466 else
467 list_add_tail(&fs->ents, head);
468
469 tb->nents++;
470
471 DBG(TAB, ul_debugobj(tb, "insert entry: %s %s",
472 mnt_fs_get_source(fs), mnt_fs_get_target(fs)));
473 return 0;
474 }
475
476 /**
477 * mnt_table_insert_fs:
478 * @tb: tab pointer
479 * @before: 1 to insert before pos, 0 to insert after pos
480 * @pos: entry to specify position or NULL
481 * @fs: new entry
482 *
483 * Adds a new entry to @tb before or after a specific table entry @pos. If the
484 * @pos is NULL than add the begin of the @tab if @before is 1; or to the tail
485 * of the @tb if @before is 0.
486 *
487 * This function inncrements reference to @fs. Don't forget to use
488 * mnt_unref_fs() after mnt_table_insert_fs() if you want to keep the @fs
489 * referenced by the table only.
490
491 *
492 * Returns: 0 on success or negative number in case of error.
493 */
494 int mnt_table_insert_fs(struct libmnt_table *tb, int before,
495 struct libmnt_fs *pos, struct libmnt_fs *fs)
496 {
497 if (!tb || !fs)
498 return -EINVAL;
499
500 if (!list_empty(&fs->ents))
501 return -EBUSY;
502
503 if (pos && mnt_table_find_fs(tb, pos) < 1)
504 return -ENOENT;
505
506 mnt_ref_fs(fs);
507 return __table_insert_fs(tb, before, pos, fs);
508 }
509
510 /**
511 * mnt_table_move_fs:
512 * @src: tab pointer of source table
513 * @dst: tab pointer of destination table
514 * @before: 1 to move before position, 0 to move after position
515 * @pos: entry to specify position or NULL
516 * @fs: entry to move
517 *
518 * Removes @fs from @src table and adds it before/after a specific entry @pos
519 * of @dst table. If the @pos is NULL than add the begin of the @dst if @before
520 * is 1; or to the tail of the @dst if @before is 0.
521 *
522 * The reference counter of @fs is not modified.
523 *
524 * Returns: 0 on success or negative number in case of error.
525 */
526 int mnt_table_move_fs(struct libmnt_table *src, struct libmnt_table *dst,
527 int before, struct libmnt_fs *pos, struct libmnt_fs *fs)
528 {
529 if (!src || !dst || !fs)
530 return -EINVAL;
531
532 if (mnt_table_find_fs(src, fs) < 1)
533 return -ENOENT;
534
535 if (pos && mnt_table_find_fs(dst, pos) < 1)
536 return -ENOENT;
537
538 /* remove from source */
539 list_del_init(&fs->ents);
540 src->nents--;
541
542 /* insert to the destination */
543 return __table_insert_fs(dst, before, pos, fs);
544 }
545
546
547 /**
548 * mnt_table_remove_fs:
549 * @tb: tab pointer
550 * @fs: new entry
551 *
552 * Removes the @fs from the table and de-increment reference counter of the @fs. The
553 * filesystem with zero reference counter will be deallocated. Don't forget to use
554 * mnt_ref_fs() before call mnt_table_remove_fs() if you want to use @fs later.
555 *
556 * Returns: 0 on success or negative number in case of error.
557 */
558 int mnt_table_remove_fs(struct libmnt_table *tb, struct libmnt_fs *fs)
559 {
560 if (!tb || !fs || mnt_table_find_fs(tb, fs) < 1)
561 return -EINVAL;
562
563 list_del_init(&fs->ents);
564
565 mnt_unref_fs(fs);
566 tb->nents--;
567 return 0;
568 }
569
570 static inline struct libmnt_fs *get_parent_fs(struct libmnt_table *tb, struct libmnt_fs *fs)
571 {
572 struct libmnt_iter itr;
573 struct libmnt_fs *x;
574 int parent_id = mnt_fs_get_parent_id(fs);
575
576 mnt_reset_iter(&itr, MNT_ITER_FORWARD);
577 while (mnt_table_next_fs(tb, &itr, &x) == 0) {
578 if (mnt_fs_get_id(x) == parent_id)
579 return x;
580 }
581
582 return NULL;
583 }
584
585 /**
586 * mnt_table_get_root_fs:
587 * @tb: mountinfo file (/proc/self/mountinfo)
588 * @root: returns pointer to the root filesystem (/)
589 *
590 * The function uses the parent ID from the mountinfo file to determine the
591 * root filesystem (the filesystem with the smallest ID with parent ID missing
592 * in the table). The function is designed mostly for applications where it is
593 * necessary to sort mountpoints by IDs to get the tree of the mountpoints
594 * (e.g. findmnt default output).
595 *
596 * If you're not sure, then use
597 *
598 * mnt_table_find_target(tb, "/", MNT_ITER_BACKWARD);
599 *
600 * this is more robust and usable for arbitrary tab files (including fstab).
601 *
602 * Returns: 0 on success or negative number in case of error.
603 */
604 int mnt_table_get_root_fs(struct libmnt_table *tb, struct libmnt_fs **root)
605 {
606 struct libmnt_iter itr;
607 struct libmnt_fs *fs;
608 int root_id = 0;
609
610 if (!tb || !root || !is_mountinfo(tb))
611 return -EINVAL;
612
613 DBG(TAB, ul_debugobj(tb, "lookup root fs"));
614
615 *root = NULL;
616
617 /* get smallest possible ID from the table */
618 mnt_reset_iter(&itr, MNT_ITER_FORWARD);
619 while(mnt_table_next_fs(tb, &itr, &fs) == 0) {
620 int id = mnt_fs_get_parent_id(fs);
621
622 if (!*root || id < root_id) {
623 *root = fs;
624 root_id = id;
625 }
626 }
627
628 /* go to the root node by "parent_id -> id" relation */
629 while (*root) {
630 struct libmnt_fs *x = get_parent_fs(tb, *root);
631 if (!x || x == *root)
632 break;
633 DBG(TAB, ul_debugobj(tb, " messy mountinfo, walk to %s", mnt_fs_get_target(x)));
634 *root = x;
635 }
636
637 return *root ? 0 : -EINVAL;
638 }
639
640 /**
641 * mnt_table_next_child_fs:
642 * @tb: mountinfo file (/proc/self/mountinfo)
643 * @itr: iterator
644 * @parent: parental FS
645 * @chld: returns the next child filesystem
646 *
647 * Note that filesystems are returned in the order of mounting (according to
648 * IDs in /proc/self/mountinfo).
649 *
650 * Returns: 0 on success, negative number in case of error or 1 at the end of list.
651 */
652 int mnt_table_next_child_fs(struct libmnt_table *tb, struct libmnt_iter *itr,
653 struct libmnt_fs *parent, struct libmnt_fs **chld)
654 {
655 struct libmnt_fs *fs;
656 int parent_id, lastchld_id = 0, chld_id = 0;
657
658 if (!tb || !itr || !parent || !is_mountinfo(tb))
659 return -EINVAL;
660
661 DBG(TAB, ul_debugobj(tb, "lookup next child of '%s'",
662 mnt_fs_get_target(parent)));
663
664 parent_id = mnt_fs_get_id(parent);
665
666 /* get ID of the previously returned child */
667 if (itr->head && itr->p != itr->head) {
668 MNT_ITER_ITERATE(itr, fs, struct libmnt_fs, ents);
669 lastchld_id = mnt_fs_get_id(fs);
670 }
671
672 *chld = NULL;
673
674 mnt_reset_iter(itr, MNT_ITER_FORWARD);
675 while(mnt_table_next_fs(tb, itr, &fs) == 0) {
676 int id;
677
678 if (mnt_fs_get_parent_id(fs) != parent_id)
679 continue;
680
681 id = mnt_fs_get_id(fs);
682
683 /* avoid an infinite loop. This only happens in rare cases
684 * such as in early userspace when the rootfs is its own parent */
685 if (id == parent_id)
686 continue;
687
688 if ((!lastchld_id || id > lastchld_id) &&
689 (!*chld || id < chld_id)) {
690 *chld = fs;
691 chld_id = id;
692 }
693 }
694
695 if (!*chld)
696 return 1; /* end of iterator */
697
698 /* set the iterator to the @chld for the next call */
699 mnt_table_set_iter(tb, itr, *chld);
700
701 return 0;
702 }
703
704 /**
705 * mnt_table_next_fs:
706 * @tb: tab pointer
707 * @itr: iterator
708 * @fs: returns the next tab entry
709 *
710 * Returns: 0 on success, negative number in case of error or 1 at the end of list.
711 *
712 * Example:
713 * <informalexample>
714 * <programlisting>
715 * while(mnt_table_next_fs(tb, itr, &fs) == 0) {
716 * const char *dir = mnt_fs_get_target(fs);
717 * printf("mount point: %s\n", dir);
718 * }
719 * </programlisting>
720 * </informalexample>
721 *
722 * lists all mountpoints from fstab in reverse order.
723 */
724 int mnt_table_next_fs(struct libmnt_table *tb, struct libmnt_iter *itr, struct libmnt_fs **fs)
725 {
726 int rc = 1;
727
728 if (!tb || !itr || !fs)
729 return -EINVAL;
730 *fs = NULL;
731
732 if (!itr->head)
733 MNT_ITER_INIT(itr, &tb->ents);
734 if (itr->p != itr->head) {
735 MNT_ITER_ITERATE(itr, *fs, struct libmnt_fs, ents);
736 rc = 0;
737 }
738
739 return rc;
740 }
741
742 /**
743 * mnt_table_first_fs:
744 * @tb: tab pointer
745 * @fs: returns the first tab entry
746 *
747 * Returns: 0 on success, negative number in case of error or 1 at the end of list.
748 */
749 int mnt_table_first_fs(struct libmnt_table *tb, struct libmnt_fs **fs)
750 {
751 if (!tb || !fs)
752 return -EINVAL;
753 if (list_empty(&tb->ents))
754 return 1;
755 *fs = list_first_entry(&tb->ents, struct libmnt_fs, ents);
756 return 0;
757 }
758
759 /**
760 * mnt_table_last_fs:
761 * @tb: tab pointer
762 * @fs: returns the last tab entry
763 *
764 * Returns: 0 on success, negative number in case of error or 1 at the end of list.
765 */
766 int mnt_table_last_fs(struct libmnt_table *tb, struct libmnt_fs **fs)
767 {
768 if (!tb || !fs)
769 return -EINVAL;
770 if (list_empty(&tb->ents))
771 return 1;
772 *fs = list_last_entry(&tb->ents, struct libmnt_fs, ents);
773 return 0;
774 }
775
776 /**
777 * mnt_table_find_next_fs:
778 * @tb: table
779 * @itr: iterator
780 * @match_func: function returning 1 or 0
781 * @userdata: extra data for match_func
782 * @fs: returns pointer to the next matching table entry
783 *
784 * This function allows searching in @tb.
785 *
786 * Returns: negative number in case of error, 1 at end of table or 0 o success.
787 */
788 int mnt_table_find_next_fs(struct libmnt_table *tb, struct libmnt_iter *itr,
789 int (*match_func)(struct libmnt_fs *, void *), void *userdata,
790 struct libmnt_fs **fs)
791 {
792 if (!tb || !itr || !fs || !match_func)
793 return -EINVAL;
794
795 DBG(TAB, ul_debugobj(tb, "lookup next fs"));
796
797 if (!itr->head)
798 MNT_ITER_INIT(itr, &tb->ents);
799
800 do {
801 if (itr->p != itr->head)
802 MNT_ITER_ITERATE(itr, *fs, struct libmnt_fs, ents);
803 else
804 break; /* end */
805
806 if (match_func(*fs, userdata))
807 return 0;
808 } while(1);
809
810 *fs = NULL;
811 return 1;
812 }
813
814 static int mnt_table_move_parent(struct libmnt_table *tb, int oldid, int newid)
815 {
816 struct libmnt_iter itr;
817 struct libmnt_fs *fs;
818
819 if (!tb)
820 return -EINVAL;
821 if (list_empty(&tb->ents))
822 return 0;
823
824 DBG(TAB, ul_debugobj(tb, "moving parent ID from %d -> %d", oldid, newid));
825 mnt_reset_iter(&itr, MNT_ITER_FORWARD);
826
827 while (mnt_table_next_fs(tb, &itr, &fs) == 0) {
828 if (fs->parent == oldid)
829 fs->parent = newid;
830 }
831 return 0;
832 }
833
834 /**
835 * mnt_table_uniq_fs:
836 * @tb: table
837 * @flags: MNT_UNIQ_*
838 * @cmp: function to compare filesystems
839 *
840 * This function de-duplicate the @tb, but does not change order of the
841 * filesystems. The @cmp function has to return 0 if the filesystems are
842 * equal, otherwise non-zero.
843 *
844 * The default is to keep in the table later mounted filesystems (function uses
845 * backward mode iterator).
846 *
847 * @MNT_UNIQ_FORWARD: remove later mounted filesystems
848 * @MNT_UNIQ_KEEPTREE: keep parent->id relationship still valid
849 *
850 * Returns: negative number in case of error, or 0 o success.
851 */
852 int mnt_table_uniq_fs(struct libmnt_table *tb, int flags,
853 int (*cmp)(struct libmnt_table *,
854 struct libmnt_fs *,
855 struct libmnt_fs *))
856 {
857 struct libmnt_iter itr;
858 struct libmnt_fs *fs;
859 int direction = MNT_ITER_BACKWARD;
860
861 if (!tb || !cmp)
862 return -EINVAL;
863 if (list_empty(&tb->ents))
864 return 0;
865
866 if (flags & MNT_UNIQ_FORWARD)
867 direction = MNT_ITER_FORWARD;
868
869 DBG(TAB, ul_debugobj(tb, "de-duplicate"));
870 mnt_reset_iter(&itr, direction);
871
872 if ((flags & MNT_UNIQ_KEEPTREE) && !is_mountinfo(tb))
873 flags &= ~MNT_UNIQ_KEEPTREE;
874
875 while (mnt_table_next_fs(tb, &itr, &fs) == 0) {
876 int want = 1;
877 struct libmnt_iter xtr;
878 struct libmnt_fs *x;
879
880 mnt_reset_iter(&xtr, direction);
881 while (want && mnt_table_next_fs(tb, &xtr, &x) == 0) {
882 if (fs == x)
883 break;
884 want = cmp(tb, x, fs) != 0;
885 }
886
887 if (!want) {
888 if (flags & MNT_UNIQ_KEEPTREE)
889 mnt_table_move_parent(tb, mnt_fs_get_id(fs),
890 mnt_fs_get_parent_id(fs));
891
892 DBG(TAB, ul_debugobj(tb, "remove duplicate %s",
893 mnt_fs_get_target(fs)));
894 mnt_table_remove_fs(tb, fs);
895 }
896 }
897
898 return 0;
899 }
900
901 /**
902 * mnt_table_set_iter:
903 * @tb: tab pointer
904 * @itr: iterator
905 * @fs: tab entry
906 *
907 * Sets @iter to the position of @fs in the file @tb.
908 *
909 * Returns: 0 on success, negative number in case of error.
910 */
911 int mnt_table_set_iter(struct libmnt_table *tb, struct libmnt_iter *itr, struct libmnt_fs *fs)
912 {
913 if (!tb || !itr || !fs)
914 return -EINVAL;
915
916 MNT_ITER_INIT(itr, &tb->ents);
917 itr->p = &fs->ents;
918
919 return 0;
920 }
921
922 /**
923 * mnt_table_find_mountpoint:
924 * @tb: tab pointer
925 * @path: directory
926 * @direction: MNT_ITER_{FORWARD,BACKWARD}
927 *
928 * Same as mnt_get_mountpoint(), except this function does not rely on
929 * st_dev numbers.
930 *
931 * Returns: a tab entry or NULL.
932 */
933 struct libmnt_fs *mnt_table_find_mountpoint(struct libmnt_table *tb,
934 const char *path,
935 int direction)
936 {
937 char *mnt;
938 struct stat st;
939
940 if (!tb || !path || !*path)
941 return NULL;
942 if (direction != MNT_ITER_FORWARD && direction != MNT_ITER_BACKWARD)
943 return NULL;
944
945 DBG(TAB, ul_debugobj(tb, "lookup MOUNTPOINT: '%s'", path));
946
947 if (mnt_stat_mountpoint(path, &st))
948 return NULL;
949
950 mnt = strdup(path);
951 if (!mnt)
952 return NULL;
953
954 do {
955 char *p;
956 struct libmnt_fs *fs;
957
958 fs = mnt_table_find_target(tb, mnt, direction);
959 if (fs) {
960 free(mnt);
961 return fs;
962 }
963
964 p = stripoff_last_component(mnt);
965 if (!p)
966 break;
967 } while (mnt && *(mnt + 1) != '\0');
968
969 free(mnt);
970 return mnt_table_find_target(tb, "/", direction);
971 }
972
973 /**
974 * mnt_table_find_target:
975 * @tb: tab pointer
976 * @path: mountpoint directory
977 * @direction: MNT_ITER_{FORWARD,BACKWARD}
978 *
979 * Try to lookup an entry in the given tab, three iterations are possible, the first
980 * with @path, the second with realpath(@path) and the third with realpath(@path)
981 * against realpath(fs->target). The 2nd and 3rd iterations are not performed when
982 * the @tb cache is not set (see mnt_table_set_cache()). If
983 * mnt_cache_set_targets(cache, mtab) was called, the 3rd iteration skips any
984 * @fs->target found in @mtab (see mnt_resolve_target()).
985 *
986 * Returns: a tab entry or NULL.
987 */
988 struct libmnt_fs *mnt_table_find_target(struct libmnt_table *tb, const char *path, int direction)
989 {
990 struct libmnt_iter itr;
991 struct libmnt_fs *fs = NULL;
992 char *cn;
993
994 if (!tb || !path || !*path)
995 return NULL;
996 if (direction != MNT_ITER_FORWARD && direction != MNT_ITER_BACKWARD)
997 return NULL;
998
999 DBG(TAB, ul_debugobj(tb, "lookup TARGET: '%s'", path));
1000
1001 /* native @target */
1002 mnt_reset_iter(&itr, direction);
1003 while(mnt_table_next_fs(tb, &itr, &fs) == 0) {
1004 if (mnt_fs_streq_target(fs, path))
1005 return fs;
1006 }
1007
1008 /* try absolute path */
1009 if (is_relative_path(path) && (cn = absolute_path(path))) {
1010 DBG(TAB, ul_debugobj(tb, "lookup absolute TARGET: '%s'", cn));
1011 mnt_reset_iter(&itr, direction);
1012 while (mnt_table_next_fs(tb, &itr, &fs) == 0) {
1013 if (mnt_fs_streq_target(fs, cn)) {
1014 free(cn);
1015 return fs;
1016 }
1017 }
1018 free(cn);
1019 }
1020
1021 if (!tb->cache || !(cn = mnt_resolve_path(path, tb->cache)))
1022 return NULL;
1023
1024 DBG(TAB, ul_debugobj(tb, "lookup canonical TARGET: '%s'", cn));
1025
1026 /* canonicalized paths in struct libmnt_table */
1027 mnt_reset_iter(&itr, direction);
1028 while(mnt_table_next_fs(tb, &itr, &fs) == 0) {
1029 if (mnt_fs_streq_target(fs, cn))
1030 return fs;
1031 }
1032
1033 /* non-canonical path in struct libmnt_table
1034 * -- note that mountpoint in /proc/self/mountinfo is already
1035 * canonicalized by the kernel
1036 */
1037 mnt_reset_iter(&itr, direction);
1038 while(mnt_table_next_fs(tb, &itr, &fs) == 0) {
1039 char *p;
1040
1041 if (!fs->target
1042 || mnt_fs_is_swaparea(fs)
1043 || mnt_fs_is_kernel(fs)
1044 || (*fs->target == '/' && *(fs->target + 1) == '\0'))
1045 continue;
1046
1047 p = mnt_resolve_target(fs->target, tb->cache);
1048 /* both canonicalized, strcmp() is fine here */
1049 if (p && strcmp(cn, p) == 0)
1050 return fs;
1051 }
1052 return NULL;
1053 }
1054
1055 /**
1056 * mnt_table_find_srcpath:
1057 * @tb: tab pointer
1058 * @path: source path (devname or dirname) or NULL
1059 * @direction: MNT_ITER_{FORWARD,BACKWARD}
1060 *
1061 * Try to lookup an entry in the given tab, four iterations are possible, the first
1062 * with @path, the second with realpath(@path), the third with tags (LABEL, UUID, ..)
1063 * from @path and the fourth with realpath(@path) against realpath(entry->srcpath).
1064 *
1065 * The 2nd, 3rd and 4th iterations are not performed when the @tb cache is not
1066 * set (see mnt_table_set_cache()).
1067 *
1068 * For btrfs returns tab entry for default id.
1069 *
1070 * Note that NULL is a valid source path; it will be replaced with "none". The
1071 * "none" is used in /proc/{mounts,self/mountinfo} for pseudo filesystems.
1072 *
1073 * Returns: a tab entry or NULL.
1074 */
1075 struct libmnt_fs *mnt_table_find_srcpath(struct libmnt_table *tb, const char *path, int direction)
1076 {
1077 struct libmnt_iter itr;
1078 struct libmnt_fs *fs = NULL;
1079 int ntags = 0, nents;
1080 char *cn;
1081 const char *p;
1082
1083 if (!tb || !path || !*path)
1084 return NULL;
1085 if (direction != MNT_ITER_FORWARD && direction != MNT_ITER_BACKWARD)
1086 return NULL;
1087
1088 DBG(TAB, ul_debugobj(tb, "lookup SRCPATH: '%s'", path));
1089
1090 /* native paths */
1091 mnt_reset_iter(&itr, direction);
1092
1093 while(mnt_table_next_fs(tb, &itr, &fs) == 0) {
1094
1095 if (mnt_fs_streq_srcpath(fs, path)) {
1096 #ifdef HAVE_BTRFS_SUPPORT
1097 if (fs->fstype && !strcmp(fs->fstype, "btrfs")) {
1098 uint64_t default_id = btrfs_get_default_subvol_id(mnt_fs_get_target(fs));
1099 char *val;
1100 size_t len;
1101
1102 if (default_id == UINT64_MAX)
1103 DBG(TAB, ul_debug("not found btrfs volume setting"));
1104
1105 else if (mnt_fs_get_option(fs, "subvolid", &val, &len) == 0) {
1106 uint64_t subvol_id;
1107
1108 if (mnt_parse_offset(val, len, &subvol_id)) {
1109 DBG(TAB, ul_debugobj(tb, "failed to parse subvolid="));
1110 continue;
1111 }
1112 if (subvol_id != default_id)
1113 continue;
1114 }
1115 }
1116 #endif /* HAVE_BTRFS_SUPPORT */
1117 return fs;
1118 }
1119 if (mnt_fs_get_tag(fs, NULL, NULL) == 0)
1120 ntags++;
1121 }
1122
1123 if (!path || !tb->cache || !(cn = mnt_resolve_path(path, tb->cache)))
1124 return NULL;
1125
1126 DBG(TAB, ul_debugobj(tb, "lookup canonical SRCPATH: '%s'", cn));
1127
1128 nents = mnt_table_get_nents(tb);
1129
1130 /* canonicalized paths in struct libmnt_table */
1131 if (ntags < nents) {
1132 mnt_reset_iter(&itr, direction);
1133 while(mnt_table_next_fs(tb, &itr, &fs) == 0) {
1134 if (mnt_fs_streq_srcpath(fs, cn))
1135 return fs;
1136 }
1137 }
1138
1139 /* evaluated tag */
1140 if (ntags) {
1141 int rc = mnt_cache_read_tags(tb->cache, cn);
1142
1143 mnt_reset_iter(&itr, direction);
1144
1145 if (rc == 0) {
1146 /* @path's TAGs are in the cache */
1147 while(mnt_table_next_fs(tb, &itr, &fs) == 0) {
1148 const char *t, *v;
1149
1150 if (mnt_fs_get_tag(fs, &t, &v))
1151 continue;
1152
1153 if (mnt_cache_device_has_tag(tb->cache, cn, t, v))
1154 return fs;
1155 }
1156 } else if (rc < 0 && errno == EACCES) {
1157 /* @path is inaccessible, try evaluating all TAGs in @tb
1158 * by udev symlinks -- this could be expensive on systems
1159 * with a huge fstab/mtab */
1160 while(mnt_table_next_fs(tb, &itr, &fs) == 0) {
1161 const char *t, *v, *x;
1162 if (mnt_fs_get_tag(fs, &t, &v))
1163 continue;
1164 x = mnt_resolve_tag(t, v, tb->cache);
1165
1166 /* both canonicalized, strcmp() is fine here */
1167 if (x && strcmp(x, cn) == 0)
1168 return fs;
1169 }
1170 }
1171 }
1172
1173 /* non-canonicalized paths in struct libmnt_table */
1174 if (ntags <= nents) {
1175 mnt_reset_iter(&itr, direction);
1176 while(mnt_table_next_fs(tb, &itr, &fs) == 0) {
1177 if (mnt_fs_is_netfs(fs) || mnt_fs_is_pseudofs(fs))
1178 continue;
1179 p = mnt_fs_get_srcpath(fs);
1180 if (p)
1181 p = mnt_resolve_path(p, tb->cache);
1182
1183 /* both canonicalized, strcmp() is fine here */
1184 if (p && strcmp(p, cn) == 0)
1185 return fs;
1186 }
1187 }
1188
1189 return NULL;
1190 }
1191
1192
1193 /**
1194 * mnt_table_find_tag:
1195 * @tb: tab pointer
1196 * @tag: tag name (e.g "LABEL", "UUID", ...)
1197 * @val: tag value
1198 * @direction: MNT_ITER_{FORWARD,BACKWARD}
1199 *
1200 * Try to lookup an entry in the given tab, the first attempt is to lookup by @tag and
1201 * @val, for the second attempt the tag is evaluated (converted to the device
1202 * name) and mnt_table_find_srcpath() is performed. The second attempt is not
1203 * performed when @tb cache is not set (see mnt_table_set_cache()).
1204
1205 * Returns: a tab entry or NULL.
1206 */
1207 struct libmnt_fs *mnt_table_find_tag(struct libmnt_table *tb, const char *tag,
1208 const char *val, int direction)
1209 {
1210 struct libmnt_iter itr;
1211 struct libmnt_fs *fs = NULL;
1212
1213 if (!tb || !tag || !*tag || !val)
1214 return NULL;
1215 if (direction != MNT_ITER_FORWARD && direction != MNT_ITER_BACKWARD)
1216 return NULL;
1217
1218 DBG(TAB, ul_debugobj(tb, "lookup by TAG: %s %s", tag, val));
1219
1220 /* look up by TAG */
1221 mnt_reset_iter(&itr, direction);
1222 while(mnt_table_next_fs(tb, &itr, &fs) == 0) {
1223 if (fs->tagname && fs->tagval &&
1224 strcmp(fs->tagname, tag) == 0 &&
1225 strcmp(fs->tagval, val) == 0)
1226 return fs;
1227 }
1228
1229 if (tb->cache) {
1230 /* look up by device name */
1231 char *cn = mnt_resolve_tag(tag, val, tb->cache);
1232 if (cn)
1233 return mnt_table_find_srcpath(tb, cn, direction);
1234 }
1235 return NULL;
1236 }
1237
1238 /**
1239 * mnt_table_find_target_with_option:
1240 * @tb: tab pointer
1241 * @path: mountpoint directory
1242 * @option: option name (e.g "subvol", "subvolid", ...)
1243 * @val: option value or NULL
1244 * @direction: MNT_ITER_{FORWARD,BACKWARD}
1245 *
1246 * Try to lookup an entry in the given tab that matches combination of @path
1247 * and @option. In difference to mnt_table_find_target(), only @path iteration
1248 * is done. No lookup by device name, no canonicalization.
1249 *
1250 * Returns: a tab entry or NULL.
1251 *
1252 * Since: 2.28
1253 */
1254 struct libmnt_fs *mnt_table_find_target_with_option(
1255 struct libmnt_table *tb, const char *path,
1256 const char *option, const char *val, int direction)
1257 {
1258 struct libmnt_iter itr;
1259 struct libmnt_fs *fs = NULL;
1260 char *optval = NULL;
1261 size_t optvalsz = 0, valsz = val ? strlen(val) : 0;
1262
1263 if (!tb || !path || !*path || !option || !*option || !val)
1264 return NULL;
1265 if (direction != MNT_ITER_FORWARD && direction != MNT_ITER_BACKWARD)
1266 return NULL;
1267
1268 DBG(TAB, ul_debugobj(tb, "lookup TARGET: '%s' with OPTION %s %s", path, option, val));
1269
1270 /* look up by native @target with OPTION */
1271 mnt_reset_iter(&itr, direction);
1272 while (mnt_table_next_fs(tb, &itr, &fs) == 0) {
1273 if (mnt_fs_streq_target(fs, path)
1274 && mnt_fs_get_option(fs, option, &optval, &optvalsz) == 0
1275 && (!val || (optvalsz == valsz
1276 && strncmp(optval, val, optvalsz) == 0)))
1277 return fs;
1278 }
1279 return NULL;
1280 }
1281
1282 /**
1283 * mnt_table_find_source:
1284 * @tb: tab pointer
1285 * @source: TAG or path
1286 * @direction: MNT_ITER_{FORWARD,BACKWARD}
1287 *
1288 * This is a high-level API for mnt_table_find_{srcpath,tag}. You needn't care
1289 * about the @source format (device, LABEL, UUID, ...). This function parses
1290 * the @source and calls mnt_table_find_tag() or mnt_table_find_srcpath().
1291 *
1292 * Returns: a tab entry or NULL.
1293 */
1294 struct libmnt_fs *mnt_table_find_source(struct libmnt_table *tb,
1295 const char *source, int direction)
1296 {
1297 struct libmnt_fs *fs;
1298 char *t = NULL, *v = NULL;
1299
1300 if (!tb)
1301 return NULL;
1302 if (direction != MNT_ITER_FORWARD && direction != MNT_ITER_BACKWARD)
1303 return NULL;
1304
1305 DBG(TAB, ul_debugobj(tb, "lookup SOURCE: '%s'", source));
1306
1307 if (blkid_parse_tag_string(source, &t, &v) || !mnt_valid_tagname(t))
1308 fs = mnt_table_find_srcpath(tb, source, direction);
1309 else
1310 fs = mnt_table_find_tag(tb, t, v, direction);
1311
1312 free(t);
1313 free(v);
1314
1315 return fs;
1316 }
1317
1318 /**
1319 * mnt_table_find_pair
1320 * @tb: tab pointer
1321 * @source: TAG or path
1322 * @target: mountpoint
1323 * @direction: MNT_ITER_{FORWARD,BACKWARD}
1324 *
1325 * This function is implemented by mnt_fs_match_source() and
1326 * mnt_fs_match_target() functions. It means that this is more expensive than
1327 * others mnt_table_find_* function, because every @tab entry is fully evaluated.
1328 *
1329 * Returns: a tab entry or NULL.
1330 */
1331 struct libmnt_fs *mnt_table_find_pair(struct libmnt_table *tb, const char *source,
1332 const char *target, int direction)
1333 {
1334 struct libmnt_fs *fs = NULL;
1335 struct libmnt_iter itr;
1336
1337 if (!tb || !target || !*target || !source || !*source)
1338 return NULL;
1339 if (direction != MNT_ITER_FORWARD && direction != MNT_ITER_BACKWARD)
1340 return NULL;
1341
1342 DBG(TAB, ul_debugobj(tb, "lookup SOURCE: %s TARGET: %s", source, target));
1343
1344 mnt_reset_iter(&itr, direction);
1345 while(mnt_table_next_fs(tb, &itr, &fs) == 0) {
1346
1347 if (mnt_fs_match_target(fs, target, tb->cache) &&
1348 mnt_fs_match_source(fs, source, tb->cache))
1349 return fs;
1350 }
1351
1352 return NULL;
1353 }
1354
1355 /**
1356 * mnt_table_find_devno
1357 * @tb: /proc/self/mountinfo
1358 * @devno: device number
1359 * @direction: MNT_ITER_{FORWARD,BACKWARD}
1360 *
1361 * Note that zero could be a valid device number for the root pseudo filesystem (e.g.
1362 * tmpfs).
1363 *
1364 * Returns: a tab entry or NULL.
1365 */
1366 struct libmnt_fs *mnt_table_find_devno(struct libmnt_table *tb,
1367 dev_t devno, int direction)
1368 {
1369 struct libmnt_fs *fs = NULL;
1370 struct libmnt_iter itr;
1371
1372 if (!tb)
1373 return NULL;
1374 if (direction != MNT_ITER_FORWARD && direction != MNT_ITER_BACKWARD)
1375 return NULL;
1376
1377 DBG(TAB, ul_debugobj(tb, "lookup DEVNO: %d", (int) devno));
1378
1379 mnt_reset_iter(&itr, direction);
1380
1381 while(mnt_table_next_fs(tb, &itr, &fs) == 0) {
1382 if (mnt_fs_get_devno(fs) == devno)
1383 return fs;
1384 }
1385
1386 return NULL;
1387 }
1388
1389 static char *remove_mountpoint_from_path(const char *path, const char *mnt)
1390 {
1391 char *res;
1392 const char *p;
1393 size_t sz;
1394
1395 sz = strlen(mnt);
1396 p = sz > 1 ? path + sz : path;
1397
1398 res = *p ? strdup(p) : strdup("/");
1399 DBG(UTILS, ul_debug("%s fs-root is %s", path, res));
1400 return res;
1401 }
1402
1403 #ifdef HAVE_BTRFS_SUPPORT
1404 static int get_btrfs_fs_root(struct libmnt_table *tb, struct libmnt_fs *fs, char **root)
1405 {
1406 char *vol = NULL, *p;
1407 size_t sz, volsz = 0;
1408
1409 DBG(BTRFS, ul_debug("lookup for btrfs FS root"));
1410 *root = NULL;
1411
1412 if (mnt_fs_get_option(fs, "subvolid", &vol, &volsz) == 0) {
1413 char *target;
1414 struct libmnt_fs *f;
1415 char subvolidstr[sizeof(stringify_value(UINT64_MAX))];
1416
1417 DBG(BTRFS, ul_debug(" found subvolid=%s, checking", vol));
1418
1419 assert (volsz + 1 < sizeof(stringify_value(UINT64_MAX)));
1420 memcpy(subvolidstr, vol, volsz);
1421 subvolidstr[volsz] = '\0';
1422
1423 target = mnt_resolve_target(mnt_fs_get_target(fs), tb->cache);
1424 if (!target)
1425 goto err;
1426
1427 DBG(BTRFS, ul_debug(" trying target=%s subvolid=%s", target, subvolidstr));
1428 f = mnt_table_find_target_with_option(tb, target,
1429 "subvolid", subvolidstr,
1430 MNT_ITER_BACKWARD);
1431 if (!tb->cache)
1432 free(target);
1433 if (!f)
1434 goto not_found;
1435
1436 /* Instead of set of BACKREF queries constructing subvol path
1437 * corresponding to a particular subvolid, use the one in
1438 * mountinfo. Kernel keeps subvol path up to date.
1439 */
1440 if (mnt_fs_get_option(f, "subvol", &vol, &volsz) != 0)
1441 goto not_found;
1442
1443 } else if (mnt_fs_get_option(fs, "subvol", &vol, &volsz) != 0) {
1444 /* If fstab entry does not contain "subvol", we have to
1445 * check, whether btrfs has default subvolume defined.
1446 */
1447 uint64_t default_id;
1448 char *target;
1449 struct libmnt_fs *f;
1450 char default_id_str[sizeof(stringify_value(UINT64_MAX))];
1451
1452 DBG(BTRFS, ul_debug(" subvolid/subvol not found, checking default"));
1453
1454 default_id = btrfs_get_default_subvol_id(mnt_fs_get_target(fs));
1455 if (default_id == UINT64_MAX)
1456 goto not_found;
1457
1458 /* Volume has default subvolume. Check if it matches to
1459 * the one in mountinfo.
1460 *
1461 * Only kernel >= 4.2 reports subvolid. On older
1462 * kernels, there is no reasonable way to detect which
1463 * subvolume was mounted.
1464 */
1465 target = mnt_resolve_target(mnt_fs_get_target(fs), tb->cache);
1466 if (!target)
1467 goto err;
1468
1469 snprintf(default_id_str, sizeof(default_id_str), "%llu",
1470 (unsigned long long int) default_id);
1471
1472 DBG(BTRFS, ul_debug(" trying target=%s default subvolid=%s",
1473 target, default_id_str));
1474
1475 f = mnt_table_find_target_with_option(tb, target,
1476 "subvolid", default_id_str,
1477 MNT_ITER_BACKWARD);
1478 if (!tb->cache)
1479 free(target);
1480 if (!f)
1481 goto not_found;
1482
1483 /* Instead of set of BACKREF queries constructing
1484 * subvol path, use the one in mountinfo. Kernel does
1485 * the evaluation for us.
1486 */
1487 DBG(BTRFS, ul_debug("setting FS root: btrfs default subvolid = %s",
1488 default_id_str));
1489
1490 if (mnt_fs_get_option(f, "subvol", &vol, &volsz) != 0)
1491 goto not_found;
1492 }
1493
1494 DBG(BTRFS, ul_debug(" using subvol=%s", vol));
1495 sz = volsz;
1496 if (*vol != '/')
1497 sz++;
1498 *root = malloc(sz + 1);
1499 if (!*root)
1500 goto err;
1501 p = *root;
1502 if (*vol != '/')
1503 *p++ = '/';
1504 memcpy(p, vol, volsz);
1505 *(*root + sz) = '\0';
1506 return 0;
1507
1508 not_found:
1509 DBG(BTRFS, ul_debug(" not found btrfs volume setting"));
1510 return 1;
1511 err:
1512 DBG(BTRFS, ul_debug(" error on btrfs volume setting evaluation"));
1513 return errno ? -errno : -1;
1514 }
1515 #endif /* HAVE_BTRFS_SUPPORT */
1516
1517 static const char *get_cifs_unc_subdir_path (const char *unc)
1518 {
1519 /*
1520 * 1 or more slash: %*[/]
1521 * 1 or more non-slash: %*[^/]
1522 * number of byte read: %n
1523 */
1524 int share_end = 0;
1525 int r = sscanf(unc, "%*[/]%*[^/]%*[/]%*[^/]%n", &share_end);
1526 if (r == EOF || share_end == 0)
1527 return NULL;
1528 return unc + share_end;
1529 }
1530
1531 /*
1532 * tb: /proc/self/mountinfo
1533 * fs: filesystem
1534 * mountflags: MS_BIND or 0
1535 * fsroot: fs-root that will probably be used in the mountinfo file
1536 * for @fs after mount(2)
1537 *
1538 * For btrfs subvolumes this function returns NULL, but @fsroot properly set.
1539 *
1540 * If @tb is NULL then defaults to '/'.
1541 *
1542 * Returns: entry from @tb that will be used as a source for @fs if the @fs is
1543 * bindmount.
1544 *
1545 * Don't export to library API!
1546 */
1547 struct libmnt_fs *mnt_table_get_fs_root(struct libmnt_table *tb,
1548 struct libmnt_fs *fs,
1549 unsigned long mountflags,
1550 char **fsroot)
1551 {
1552 char *root = NULL;
1553 const char *mnt = NULL;
1554 struct libmnt_fs *src_fs = NULL;
1555
1556 assert(fs);
1557 assert(fsroot);
1558
1559 DBG(TAB, ul_debug("lookup fs-root for '%s'", mnt_fs_get_source(fs)));
1560
1561 if (tb && (mountflags & MS_BIND)) {
1562 const char *src, *src_root;
1563 char *xsrc = NULL;
1564
1565 DBG(TAB, ul_debug("fs-root for bind"));
1566
1567 src = xsrc = mnt_resolve_spec(mnt_fs_get_source(fs), tb->cache);
1568 if (src) {
1569 struct libmnt_fs *f = mnt_table_find_mountpoint(tb,
1570 src, MNT_ITER_BACKWARD);
1571 if (f)
1572 mnt = mnt_fs_get_target(f);
1573 }
1574 if (mnt)
1575 root = remove_mountpoint_from_path(src, mnt);
1576
1577 if (xsrc && !tb->cache) {
1578 free(xsrc);
1579 src = NULL;
1580 }
1581 if (!mnt)
1582 goto err;
1583
1584 src_fs = mnt_table_find_target(tb, mnt, MNT_ITER_BACKWARD);
1585 if (!src_fs) {
1586 DBG(TAB, ul_debug("not found '%s' in mountinfo -- using default", mnt));
1587 goto dflt;
1588 }
1589
1590 /* It's possible that fstab_fs source is subdirectory on btrfs
1591 * subvolume or another bind mount. For example:
1592 *
1593 * /dev/sdc /mnt/test btrfs subvol=/anydir
1594 * /dev/sdc /mnt/test btrfs defaults
1595 * /mnt/test/foo /mnt/test2 auto bind
1596 *
1597 * in this case, the root for /mnt/test2 will be /anydir/foo on
1598 * /dev/sdc. It means we have to compose the final root from
1599 * root and src_root.
1600 */
1601 src_root = mnt_fs_get_root(src_fs);
1602
1603 DBG(FS, ul_debugobj(fs, "source root: %s, source FS root: %s", root, src_root));
1604
1605 if (src_root && !startswith(root, src_root)) {
1606 if (strcmp(root, "/") == 0) {
1607 free(root);
1608 root = strdup(src_root);
1609 if (!root)
1610 goto err;
1611 } else {
1612 char *tmp;
1613 if (asprintf(&tmp, "%s%s", src_root, root) < 0)
1614 goto err;
1615 free(root);
1616 root = tmp;
1617 }
1618 }
1619 }
1620
1621 #ifdef HAVE_BTRFS_SUPPORT
1622 /*
1623 * btrfs-subvolume mount -- get subvolume name and use it as a root-fs path
1624 */
1625 else if (tb && fs->fstype &&
1626 (!strcmp(fs->fstype, "btrfs") || !strcmp(fs->fstype, "auto"))) {
1627 if (get_btrfs_fs_root(tb, fs, &root) < 0)
1628 goto err;
1629 }
1630 #endif /* HAVE_BTRFS_SUPPORT */
1631
1632 dflt:
1633 if (!root) {
1634 root = strdup("/");
1635 if (!root)
1636 goto err;
1637 }
1638 *fsroot = root;
1639
1640 DBG(TAB, ul_debug("FS root result: %s", root));
1641
1642 return src_fs;
1643 err:
1644 free(root);
1645 return NULL;
1646 }
1647
1648 /**
1649 * mnt_table_is_fs_mounted:
1650 * @tb: /proc/self/mountinfo file
1651 * @fstab_fs: /etc/fstab entry
1652 *
1653 * Checks if the @fstab_fs entry is already in the @tb table. The "swap" is
1654 * ignored. This function explicitly compares the source, target and root of the
1655 * filesystems.
1656 *
1657 * Note that source and target are canonicalized only if a cache for @tb is
1658 * defined (see mnt_table_set_cache()). The target canonicalization may
1659 * trigger automount on autofs mountpoints!
1660 *
1661 * Don't use it if you want to know if a device is mounted, just use
1662 * mnt_table_find_source() on the device.
1663 *
1664 * This function is designed mostly for "mount -a".
1665 *
1666 * Returns: 0 or 1
1667 */
1668 int mnt_table_is_fs_mounted(struct libmnt_table *tb, struct libmnt_fs *fstab_fs)
1669 {
1670 struct libmnt_iter itr;
1671 struct libmnt_fs *fs;
1672
1673 char *root = NULL;
1674 char *src2 = NULL;
1675 const char *src = NULL, *tgt = NULL;
1676 char *xtgt = NULL;
1677 int rc = 0;
1678 dev_t devno = 0;
1679
1680 DBG(FS, ul_debugobj(fstab_fs, "mnt_table_is_fs_mounted: target=%s, source=%s",
1681 mnt_fs_get_target(fstab_fs),
1682 mnt_fs_get_source(fstab_fs)));
1683
1684 if (mnt_fs_is_swaparea(fstab_fs) || mnt_table_is_empty(tb)) {
1685 DBG(FS, ul_debugobj(fstab_fs, "- ignore (swap or no data)"));
1686 return 0;
1687 }
1688
1689 if (is_mountinfo(tb)) {
1690 /* @tb is mountinfo, so we can try to use fs-roots */
1691 struct libmnt_fs *rootfs;
1692 int flags = 0;
1693
1694 if (mnt_fs_get_option(fstab_fs, "bind", NULL, NULL) == 0 ||
1695 mnt_fs_get_option(fstab_fs, "rbind", NULL, NULL) == 0)
1696 flags = MS_BIND;
1697
1698 rootfs = mnt_table_get_fs_root(tb, fstab_fs, flags, &root);
1699 if (rootfs) {
1700 const char *fstype = mnt_fs_get_fstype(rootfs);
1701
1702 src = mnt_fs_get_srcpath(rootfs);
1703 if (fstype && strncmp(fstype, "nfs", 3) == 0 && root) {
1704 /* NFS stores the root at the end of the source */
1705 src = src2 = strappend(src, root);
1706 free(root);
1707 root = NULL;
1708 }
1709 }
1710 }
1711
1712 if (!src)
1713 src = mnt_fs_get_source(fstab_fs);
1714
1715 if (src && tb->cache && !mnt_fs_is_pseudofs(fstab_fs))
1716 src = mnt_resolve_spec(src, tb->cache);
1717
1718 if (src && root) {
1719 struct stat st;
1720
1721 devno = mnt_fs_get_devno(fstab_fs);
1722 if (!devno && stat(src, &st) == 0 && S_ISBLK(st.st_mode))
1723 devno = st.st_rdev;
1724 }
1725
1726 tgt = mnt_fs_get_target(fstab_fs);
1727
1728 if (!tgt || !src) {
1729 DBG(FS, ul_debugobj(fstab_fs, "- ignore (no source/target)"));
1730 goto done;
1731 }
1732 mnt_reset_iter(&itr, MNT_ITER_FORWARD);
1733
1734 DBG(FS, ul_debugobj(fstab_fs, "mnt_table_is_fs_mounted: src=%s, tgt=%s, root=%s", src, tgt, root));
1735
1736 while (mnt_table_next_fs(tb, &itr, &fs) == 0) {
1737
1738 int eq = mnt_fs_streq_srcpath(fs, src);
1739
1740 if (!eq && devno && mnt_fs_get_devno(fs) == devno)
1741 eq = 1;
1742
1743 if (!eq) {
1744 /* The source does not match. Maybe the source is a loop
1745 * device backing file.
1746 */
1747 uint64_t offset = 0;
1748 char *val;
1749 size_t len;
1750 int flags = 0;
1751
1752 if (!mnt_fs_get_srcpath(fs) ||
1753 !startswith(mnt_fs_get_srcpath(fs), "/dev/loop"))
1754 continue; /* does not look like loopdev */
1755
1756 if (mnt_fs_get_option(fstab_fs, "offset", &val, &len) == 0) {
1757 if (mnt_parse_offset(val, len, &offset)) {
1758 DBG(FS, ul_debugobj(fstab_fs, "failed to parse offset="));
1759 continue;
1760 }
1761 flags = LOOPDEV_FL_OFFSET;
1762 }
1763
1764 DBG(FS, ul_debugobj(fs, "checking for loop: src=%s", mnt_fs_get_srcpath(fs)));
1765 #if __linux__
1766 if (!loopdev_is_used(mnt_fs_get_srcpath(fs), src, offset, 0, flags))
1767 continue;
1768
1769 DBG(FS, ul_debugobj(fs, "used loop"));
1770 #endif
1771 }
1772
1773 if (root) {
1774 const char *fstype = mnt_fs_get_fstype(fs);
1775
1776 if (fstype && strcmp(fstype, "cifs") == 0) {
1777 const char *unc_subdir = get_cifs_unc_subdir_path(src);
1778 const char *path_on_fs = mnt_fs_get_root(fs);
1779 if (!unc_subdir || !path_on_fs || !streq_paths(unc_subdir, path_on_fs))
1780 continue;
1781 } else {
1782 const char *r = mnt_fs_get_root(fs);
1783 if (!r || strcmp(r, root) != 0)
1784 continue;
1785 }
1786 }
1787
1788 /*
1789 * Compare target, try to minimize the number of situations when we
1790 * need to canonicalize the path to avoid readlink() on
1791 * mountpoints.
1792 */
1793 if (!xtgt) {
1794 if (mnt_fs_streq_target(fs, tgt))
1795 break;
1796 if (tb->cache)
1797 xtgt = mnt_resolve_path(tgt, tb->cache);
1798 }
1799 if (xtgt && mnt_fs_streq_target(fs, xtgt))
1800 break;
1801 }
1802
1803 if (fs)
1804 rc = 1; /* success */
1805 done:
1806 free(root);
1807
1808 DBG(TAB, ul_debugobj(tb, "mnt_table_is_fs_mounted: %s [rc=%d]", src, rc));
1809 free(src2);
1810 return rc;
1811 }
1812
1813 #ifdef TEST_PROGRAM
1814 #include "pathnames.h"
1815
1816 static int parser_errcb(struct libmnt_table *tb, const char *filename, int line)
1817 {
1818 fprintf(stderr, "%s:%d: parse error\n", filename, line);
1819
1820 return 1; /* all errors are recoverable -- this is the default */
1821 }
1822
1823 static struct libmnt_table *create_table(const char *file, int comments)
1824 {
1825 struct libmnt_table *tb;
1826
1827 if (!file)
1828 return NULL;
1829 tb = mnt_new_table();
1830 if (!tb)
1831 goto err;
1832
1833 mnt_table_enable_comments(tb, comments);
1834 mnt_table_set_parser_errcb(tb, parser_errcb);
1835
1836 if (mnt_table_parse_file(tb, file) != 0)
1837 goto err;
1838 return tb;
1839 err:
1840 fprintf(stderr, "%s: parsing failed\n", file);
1841 mnt_unref_table(tb);
1842 return NULL;
1843 }
1844
1845 static int test_copy_fs(struct libmnt_test *ts, int argc, char *argv[])
1846 {
1847 struct libmnt_table *tb;
1848 struct libmnt_fs *fs;
1849 int rc = -1;
1850
1851 tb = create_table(argv[1], FALSE);
1852 if (!tb)
1853 return -1;
1854
1855 fs = mnt_table_find_target(tb, "/", MNT_ITER_FORWARD);
1856 if (!fs)
1857 goto done;
1858
1859 printf("ORIGINAL:\n");
1860 mnt_fs_print_debug(fs, stdout);
1861
1862 fs = mnt_copy_fs(NULL, fs);
1863 if (!fs)
1864 goto done;
1865
1866 printf("COPY:\n");
1867 mnt_fs_print_debug(fs, stdout);
1868 mnt_unref_fs(fs);
1869 rc = 0;
1870 done:
1871 mnt_unref_table(tb);
1872 return rc;
1873 }
1874
1875 static int test_parse(struct libmnt_test *ts, int argc, char *argv[])
1876 {
1877 struct libmnt_table *tb = NULL;
1878 struct libmnt_iter *itr = NULL;
1879 struct libmnt_fs *fs;
1880 int rc = -1;
1881 int parse_comments = FALSE;
1882
1883 if (argc == 3 && !strcmp(argv[2], "--comments"))
1884 parse_comments = TRUE;
1885
1886 tb = create_table(argv[1], parse_comments);
1887 if (!tb)
1888 return -1;
1889
1890 itr = mnt_new_iter(MNT_ITER_FORWARD);
1891 if (!itr)
1892 goto done;
1893
1894 if (mnt_table_get_intro_comment(tb))
1895 fprintf(stdout, "Initial comment:\n\"%s\"\n",
1896 mnt_table_get_intro_comment(tb));
1897
1898 while(mnt_table_next_fs(tb, itr, &fs) == 0)
1899 mnt_fs_print_debug(fs, stdout);
1900
1901 if (mnt_table_get_trailing_comment(tb))
1902 fprintf(stdout, "Trailing comment:\n\"%s\"\n",
1903 mnt_table_get_trailing_comment(tb));
1904 rc = 0;
1905 done:
1906 mnt_free_iter(itr);
1907 mnt_unref_table(tb);
1908 return rc;
1909 }
1910
1911 static int test_find_idx(struct libmnt_test *ts, int argc, char *argv[])
1912 {
1913 struct libmnt_table *tb;
1914 struct libmnt_fs *fs = NULL;
1915 struct libmnt_cache *mpc = NULL;
1916 const char *file, *what;
1917 int rc = -1;
1918
1919 if (argc != 3) {
1920 fprintf(stderr, "try --help\n");
1921 return -EINVAL;
1922 }
1923
1924 file = argv[1], what = argv[2];
1925
1926 tb = create_table(file, FALSE);
1927 if (!tb)
1928 goto done;
1929
1930 /* create a cache for canonicalized paths */
1931 mpc = mnt_new_cache();
1932 if (!mpc)
1933 goto done;
1934 mnt_table_set_cache(tb, mpc);
1935 mnt_unref_cache(mpc);
1936
1937 fs = mnt_table_find_target(tb, what, MNT_ITER_BACKWARD);
1938
1939 if (!fs)
1940 fprintf(stderr, "%s: not found '%s'\n", file, what);
1941 else {
1942 int idx = mnt_table_find_fs(tb, fs);
1943
1944 if (idx < 1)
1945 fprintf(stderr, "%s: not found '%s' fs pointer", file, what);
1946 else {
1947 printf("%s index is %d\n", what, idx);
1948 rc = 0;
1949 }
1950 }
1951 done:
1952 mnt_unref_table(tb);
1953 return rc;
1954 }
1955
1956 static int test_find(struct libmnt_test *ts, int argc, char *argv[], int dr)
1957 {
1958 struct libmnt_table *tb;
1959 struct libmnt_fs *fs = NULL;
1960 struct libmnt_cache *mpc = NULL;
1961 const char *file, *find, *what;
1962 int rc = -1;
1963
1964 if (argc != 4) {
1965 fprintf(stderr, "try --help\n");
1966 return -EINVAL;
1967 }
1968
1969 file = argv[1], find = argv[2], what = argv[3];
1970
1971 tb = create_table(file, FALSE);
1972 if (!tb)
1973 goto done;
1974
1975 /* create a cache for canonicalized paths */
1976 mpc = mnt_new_cache();
1977 if (!mpc)
1978 goto done;
1979 mnt_table_set_cache(tb, mpc);
1980 mnt_unref_cache(mpc);
1981
1982 if (strcasecmp(find, "source") == 0)
1983 fs = mnt_table_find_source(tb, what, dr);
1984 else if (strcasecmp(find, "target") == 0)
1985 fs = mnt_table_find_target(tb, what, dr);
1986
1987 if (!fs)
1988 fprintf(stderr, "%s: not found %s '%s'\n", file, find, what);
1989 else {
1990 mnt_fs_print_debug(fs, stdout);
1991 rc = 0;
1992 }
1993 done:
1994 mnt_unref_table(tb);
1995 return rc;
1996 }
1997
1998 static int test_find_bw(struct libmnt_test *ts, int argc, char *argv[])
1999 {
2000 return test_find(ts, argc, argv, MNT_ITER_BACKWARD);
2001 }
2002
2003 static int test_find_fw(struct libmnt_test *ts, int argc, char *argv[])
2004 {
2005 return test_find(ts, argc, argv, MNT_ITER_FORWARD);
2006 }
2007
2008 static int test_find_pair(struct libmnt_test *ts, int argc, char *argv[])
2009 {
2010 struct libmnt_table *tb;
2011 struct libmnt_fs *fs;
2012 struct libmnt_cache *mpc = NULL;
2013 int rc = -1;
2014
2015 tb = create_table(argv[1], FALSE);
2016 if (!tb)
2017 return -1;
2018 mpc = mnt_new_cache();
2019 if (!mpc)
2020 goto done;
2021 mnt_table_set_cache(tb, mpc);
2022 mnt_unref_cache(mpc);
2023
2024 fs = mnt_table_find_pair(tb, argv[2], argv[3], MNT_ITER_FORWARD);
2025 if (!fs)
2026 goto done;
2027
2028 mnt_fs_print_debug(fs, stdout);
2029 rc = 0;
2030 done:
2031 mnt_unref_table(tb);
2032 return rc;
2033 }
2034
2035 static int test_find_mountpoint(struct libmnt_test *ts, int argc, char *argv[])
2036 {
2037 struct libmnt_table *tb;
2038 struct libmnt_fs *fs;
2039 struct libmnt_cache *mpc = NULL;
2040 int rc = -1;
2041
2042 tb = mnt_new_table_from_file(_PATH_PROC_MOUNTINFO);
2043 if (!tb)
2044 return -1;
2045 mpc = mnt_new_cache();
2046 if (!mpc)
2047 goto done;
2048 mnt_table_set_cache(tb, mpc);
2049 mnt_unref_cache(mpc);
2050
2051 fs = mnt_table_find_mountpoint(tb, argv[1], MNT_ITER_BACKWARD);
2052 if (!fs)
2053 goto done;
2054
2055 mnt_fs_print_debug(fs, stdout);
2056 rc = 0;
2057 done:
2058 mnt_unref_table(tb);
2059 return rc;
2060 }
2061
2062 static int test_is_mounted(struct libmnt_test *ts, int argc, char *argv[])
2063 {
2064 struct libmnt_table *tb = NULL, *fstab = NULL;
2065 struct libmnt_fs *fs;
2066 struct libmnt_iter *itr = NULL;
2067 struct libmnt_cache *mpc = NULL;
2068 int writable = 0;
2069 const char *path = NULL;
2070
2071 if (mnt_has_regular_mtab(&path, &writable) == 1 && writable == 0)
2072 tb = mnt_new_table_from_file(path);
2073 else
2074 tb = mnt_new_table_from_file("/proc/self/mountinfo");
2075
2076 if (!tb) {
2077 fprintf(stderr, "failed to parse mountinfo\n");
2078 return -1;
2079 }
2080
2081 fstab = create_table(argv[1], FALSE);
2082 if (!fstab)
2083 goto done;
2084
2085 itr = mnt_new_iter(MNT_ITER_FORWARD);
2086 if (!itr)
2087 goto done;
2088
2089 mpc = mnt_new_cache();
2090 if (!mpc)
2091 goto done;
2092 mnt_table_set_cache(tb, mpc);
2093 mnt_unref_cache(mpc);
2094
2095 while (mnt_table_next_fs(fstab, itr, &fs) == 0) {
2096 if (mnt_table_is_fs_mounted(tb, fs))
2097 printf("%s already mounted on %s\n",
2098 mnt_fs_get_source(fs),
2099 mnt_fs_get_target(fs));
2100 else
2101 printf("%s not mounted on %s\n",
2102 mnt_fs_get_source(fs),
2103 mnt_fs_get_target(fs));
2104 }
2105
2106 done:
2107 mnt_unref_table(tb);
2108 mnt_unref_table(fstab);
2109 mnt_free_iter(itr);
2110 return 0;
2111 }
2112
2113 /* returns 0 if @a and @b targets are the same */
2114 static int test_uniq_cmp(struct libmnt_table *tb __attribute__((__unused__)),
2115 struct libmnt_fs *a,
2116 struct libmnt_fs *b)
2117 {
2118 assert(a);
2119 assert(b);
2120
2121 return mnt_fs_streq_target(a, mnt_fs_get_target(b)) ? 0 : 1;
2122 }
2123
2124 static int test_uniq(struct libmnt_test *ts, int argc, char *argv[])
2125 {
2126 struct libmnt_table *tb;
2127 int rc = -1;
2128
2129 if (argc != 2) {
2130 fprintf(stderr, "try --help\n");
2131 return -EINVAL;
2132 }
2133
2134 tb = create_table(argv[1], FALSE);
2135 if (!tb)
2136 goto done;
2137
2138 if (mnt_table_uniq_fs(tb, 0, test_uniq_cmp) == 0) {
2139 struct libmnt_iter *itr = mnt_new_iter(MNT_ITER_FORWARD);
2140 struct libmnt_fs *fs;
2141 if (!itr)
2142 goto done;
2143 while (mnt_table_next_fs(tb, itr, &fs) == 0)
2144 mnt_fs_print_debug(fs, stdout);
2145 mnt_free_iter(itr);
2146 rc = 0;
2147 }
2148 done:
2149 mnt_unref_table(tb);
2150 return rc;
2151 }
2152
2153
2154 int main(int argc, char *argv[])
2155 {
2156 struct libmnt_test tss[] = {
2157 { "--parse", test_parse, "<file> [--comments] parse and print tab" },
2158 { "--find-forward", test_find_fw, "<file> <source|target> <string>" },
2159 { "--find-backward", test_find_bw, "<file> <source|target> <string>" },
2160 { "--uniq-target", test_uniq, "<file>" },
2161 { "--find-pair", test_find_pair, "<file> <source> <target>" },
2162 { "--find-fs", test_find_idx, "<file> <target>" },
2163 { "--find-mountpoint", test_find_mountpoint, "<path>" },
2164 { "--copy-fs", test_copy_fs, "<file> copy root FS from the file" },
2165 { "--is-mounted", test_is_mounted, "<fstab> check what from fstab is already mounted" },
2166 { NULL }
2167 };
2168
2169 return mnt_run_test(tss, argc, argv);
2170 }
2171
2172 #endif /* TEST_PROGRAM */