1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
4 * This file is part of libmount from util-linux project.
6 * Copyright (C) 2009-2018 Karel Zak <kzak@redhat.com>
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.
17 * @short_description: paths and tags (UUID/LABEL) caching
19 * The cache is a very simple API for working with tags (LABEL, UUID, ...) and
20 * paths. The cache uses libblkid as a backend for TAGs resolution.
22 * All returned paths are always canonicalized.
33 #include "canonicalize.h"
39 * Canonicalized (resolved) paths & tags cache
41 #define MNT_CACHE_CHUNKSZ 128
43 #define MNT_CACHE_ISTAG (1 << 1) /* entry is TAG */
44 #define MNT_CACHE_ISPATH (1 << 2) /* entry is path */
45 #define MNT_CACHE_TAGREAD (1 << 3) /* tag read by mnt_cache_read_tags() */
47 /* path cache entry */
48 struct mnt_cache_entry
{
49 char *key
; /* search key (e.g. uncanonicalized path) */
50 char *value
; /* value (e.g. canonicalized path) */
55 struct mnt_cache_entry
*ents
;
59 int probe_sb_extra
; /* extra BLKID_SUBLKS_* flags */
61 /* blkid_evaluate_tag() works in two ways:
63 * 1/ all tags are evaluated by udev /dev/disk/by-* symlinks,
64 * then the blkid_cache is NULL.
66 * 2/ all tags are read from blkid.tab and verified by /dev
67 * scanning, then the blkid_cache is not NULL and then it's
68 * better to reuse the blkid_cache.
72 struct libmnt_table
*mountinfo
;
78 * Returns: new struct libmnt_cache instance or NULL in case of ENOMEM error.
80 struct libmnt_cache
*mnt_new_cache(void)
82 struct libmnt_cache
*cache
= calloc(1, sizeof(*cache
));
85 DBG(CACHE
, ul_debugobj(cache
, "alloc"));
92 * @cache: pointer to struct libmnt_cache instance
94 * Deallocates the cache. This function does not care about reference count. Don't
95 * use this function directly -- it's better to use mnt_unref_cache().
97 void mnt_free_cache(struct libmnt_cache
*cache
)
104 DBG(CACHE
, ul_debugobj(cache
, "free [refcount=%d]", cache
->refcount
));
106 for (i
= 0; i
< cache
->nents
; i
++) {
107 struct mnt_cache_entry
*e
= &cache
->ents
[i
];
108 if (e
->value
!= e
->key
)
114 blkid_put_cache(cache
->bc
);
120 * @cache: cache pointer
122 * Increments reference counter.
124 void mnt_ref_cache(struct libmnt_cache
*cache
)
128 /*DBG(CACHE, ul_debugobj(cache, "ref=%d", cache->refcount));*/
134 * @cache: cache pointer
136 * De-increments reference counter, on zero the cache is automatically
137 * deallocated by mnt_free_cache().
139 void mnt_unref_cache(struct libmnt_cache
*cache
)
143 /*DBG(CACHE, ul_debugobj(cache, "unref=%d", cache->refcount));*/
144 if (cache
->refcount
<= 0) {
145 mnt_unref_table(cache
->mountinfo
);
147 mnt_free_cache(cache
);
153 * mnt_cache_set_targets:
154 * @cache: cache pointer
155 * @mountinfo: table with already canonicalized mountpoints
157 * Add to @cache reference to @mountinfo. This can be used to avoid unnecessary paths
158 * canonicalization in mnt_resolve_target().
160 * Returns: negative number in case of error, or 0 o success.
162 int mnt_cache_set_targets(struct libmnt_cache
*cache
,
163 struct libmnt_table
*mountinfo
)
168 mnt_ref_table(mountinfo
);
169 mnt_unref_table(cache
->mountinfo
);
170 cache
->mountinfo
= mountinfo
;
175 * mnt_cache_set_sbprobe:
176 * @cache: cache pointer
177 * @flags: BLKID_SUBLKS_* flags
179 * Add extra flags to the libblkid prober. Don't use if not sure.
181 * Returns: negative number in case of error, or 0 o success.
183 int mnt_cache_set_sbprobe(struct libmnt_cache
*cache
, int flags
)
188 cache
->probe_sb_extra
= flags
;
192 /* note that the @key could be the same pointer as @value */
193 static int cache_add_entry(struct libmnt_cache
*cache
, char *key
,
194 char *value
, int flag
)
196 struct mnt_cache_entry
*e
;
202 if (cache
->nents
== cache
->nallocs
) {
203 size_t sz
= cache
->nallocs
+ MNT_CACHE_CHUNKSZ
;
205 e
= reallocarray(cache
->ents
, sz
, sizeof(struct mnt_cache_entry
));
212 e
= &cache
->ents
[cache
->nents
];
218 DBG(CACHE
, ul_debugobj(cache
, "add entry [%2zd] (%s): %s: %s",
220 (flag
& MNT_CACHE_ISPATH
) ? "path" : "tag",
225 /* add tag to the cache, @devname has to be an allocated string */
226 static int cache_add_tag(struct libmnt_cache
*cache
, const char *tagname
,
227 const char *tagval
, char *devname
, int flag
)
238 /* add into cache -- cache format for TAGs is
239 * key = "TAG_NAME\0TAG_VALUE\0"
242 tksz
= strlen(tagname
);
243 vlsz
= strlen(tagval
);
245 key
= malloc(tksz
+ vlsz
+ 2);
249 memcpy(key
, tagname
, tksz
+ 1); /* include '\0' */
250 memcpy(key
+ tksz
+ 1, tagval
, vlsz
+ 1);
252 rc
= cache_add_entry(cache
, key
, devname
, flag
| MNT_CACHE_ISTAG
);
262 * Returns cached canonicalized path or NULL.
264 static const char *cache_find_path(struct libmnt_cache
*cache
, const char *path
)
271 for (i
= 0; i
< cache
->nents
; i
++) {
272 struct mnt_cache_entry
*e
= &cache
->ents
[i
];
273 if (!(e
->flag
& MNT_CACHE_ISPATH
))
275 if (streq_paths(path
, e
->key
))
282 * Returns cached path or NULL.
284 static const char *cache_find_tag(struct libmnt_cache
*cache
,
285 const char *token
, const char *value
)
290 if (!cache
|| !token
|| !value
)
293 tksz
= strlen(token
);
295 for (i
= 0; i
< cache
->nents
; i
++) {
296 struct mnt_cache_entry
*e
= &cache
->ents
[i
];
297 if (!(e
->flag
& MNT_CACHE_ISTAG
))
299 if (strcmp(token
, e
->key
) == 0 &&
300 strcmp(value
, e
->key
+ tksz
+ 1) == 0)
306 static char *cache_find_tag_value(struct libmnt_cache
*cache
,
307 const char *devname
, const char *token
)
315 for (i
= 0; i
< cache
->nents
; i
++) {
316 struct mnt_cache_entry
*e
= &cache
->ents
[i
];
317 if (!(e
->flag
& MNT_CACHE_ISTAG
))
319 if (strcmp(e
->value
, devname
) == 0 && /* dev name */
320 strcmp(token
, e
->key
) == 0) /* tag name */
321 return e
->key
+ strlen(token
) + 1; /* tag value */
328 * mnt_cache_read_tags
329 * @cache: pointer to struct libmnt_cache instance
330 * @devname: path device
332 * Reads @devname LABEL and UUID to the @cache.
334 * Returns: 0 if at least one tag was added, 1 if no tag was added or
335 * negative number in case of error.
337 int mnt_cache_read_tags(struct libmnt_cache
*cache
, const char *devname
)
342 const char *tags
[] = { "LABEL", "UUID", "TYPE", "PARTUUID", "PARTLABEL" };
343 const char *blktags
[] = { "LABEL", "UUID", "TYPE", "PART_ENTRY_UUID", "PART_ENTRY_NAME" };
345 if (!cache
|| !devname
)
348 DBG(CACHE
, ul_debugobj(cache
, "tags for %s requested", devname
));
350 /* check if device is already cached */
351 for (i
= 0; i
< cache
->nents
; i
++) {
352 struct mnt_cache_entry
*e
= &cache
->ents
[i
];
353 if (!(e
->flag
& MNT_CACHE_TAGREAD
))
355 if (strcmp(e
->value
, devname
) == 0)
356 /* tags have already been read */
360 pr
= blkid_new_probe_from_filename(devname
);
364 blkid_probe_enable_superblocks(pr
, 1);
365 blkid_probe_set_superblocks_flags(pr
,
366 BLKID_SUBLKS_LABEL
| BLKID_SUBLKS_UUID
|
367 BLKID_SUBLKS_TYPE
| cache
->probe_sb_extra
);
369 blkid_probe_enable_partitions(pr
, 1);
370 blkid_probe_set_partitions_flags(pr
, BLKID_PARTS_ENTRY_DETAILS
);
372 rc
= blkid_do_safeprobe(pr
);
376 DBG(CACHE
, ul_debugobj(cache
, "reading tags for: %s", devname
));
378 for (i
= 0; i
< ARRAY_SIZE(tags
); i
++) {
382 if (cache_find_tag_value(cache
, devname
, tags
[i
])) {
383 DBG(CACHE
, ul_debugobj(cache
,
384 "\ntag %s already cached", tags
[i
]));
387 if (blkid_probe_lookup_value(pr
, blktags
[i
], &data
, NULL
))
389 dev
= strdup(devname
);
392 if (cache_add_tag(cache
, tags
[i
], data
, dev
,
393 MNT_CACHE_TAGREAD
)) {
400 DBG(CACHE
, ul_debugobj(cache
, "\tread %zd tags", ntags
));
401 blkid_free_probe(pr
);
402 return ntags
? 0 : 1;
404 blkid_free_probe(pr
);
405 return rc
< 0 ? rc
: -1;
409 * mnt_cache_device_has_tag:
410 * @cache: paths cache
411 * @devname: path to the device
412 * @token: tag name (e.g "LABEL")
415 * Look up @cache to check if @tag+@value are associated with @devname.
417 * Returns: 1 on success or 0.
419 int mnt_cache_device_has_tag(struct libmnt_cache
*cache
, const char *devname
,
420 const char *token
, const char *value
)
422 const char *path
= cache_find_tag(cache
, token
, value
);
424 if (path
&& devname
&& strcmp(path
, devname
) == 0)
429 static int __mnt_cache_find_tag_value(struct libmnt_cache
*cache
,
430 const char *devname
, const char *token
, char **data
)
434 if (!cache
|| !devname
|| !token
|| !data
)
437 rc
= mnt_cache_read_tags(cache
, devname
);
441 *data
= cache_find_tag_value(cache
, devname
, token
);
442 return *data
? 0 : -1;
446 * mnt_cache_find_tag_value:
447 * @cache: cache for results
448 * @devname: device name
449 * @token: tag name ("LABEL" or "UUID")
451 * Returns: LABEL or UUID for the @devname or NULL in case of error.
453 char *mnt_cache_find_tag_value(struct libmnt_cache
*cache
,
454 const char *devname
, const char *token
)
458 if (__mnt_cache_find_tag_value(cache
, devname
, token
, &data
) == 0)
465 * @devname: device name
466 * @ambi: returns TRUE if probing result is ambivalent (optional argument)
467 * @cache: cache for results or NULL
469 * Returns: filesystem type or NULL in case of error. The result has to be
470 * deallocated by free() if @cache is NULL.
472 char *mnt_get_fstype(const char *devname
, int *ambi
, struct libmnt_cache
*cache
)
479 DBG(CACHE
, ul_debugobj(cache
, "get %s FS type", devname
));
483 rc
= __mnt_cache_find_tag_value(cache
, devname
, "TYPE", &val
);
485 *ambi
= rc
== -2 ? TRUE
: FALSE
;
486 return rc
? NULL
: val
;
490 * no cache, probe directly
492 pr
= blkid_new_probe_from_filename(devname
);
496 blkid_probe_enable_superblocks(pr
, 1);
497 blkid_probe_set_superblocks_flags(pr
, BLKID_SUBLKS_TYPE
);
499 rc
= blkid_do_safeprobe(pr
);
501 DBG(CACHE
, ul_debugobj(cache
, "libblkid rc=%d", rc
));
503 if (!rc
&& !blkid_probe_lookup_value(pr
, "TYPE", &data
, NULL
))
507 *ambi
= rc
== -2 ? TRUE
: FALSE
;
509 blkid_free_probe(pr
);
513 static char *canonicalize_path_and_cache(const char *path
,
514 struct libmnt_cache
*cache
)
520 DBG(CACHE
, ul_debugobj(cache
, "canonicalize path %s", path
));
521 p
= canonicalize_path(path
);
525 key
= strcmp(path
, p
) == 0 ? value
: strdup(path
);
530 if (cache_add_entry(cache
, key
, value
,
545 * @path: "native" path
546 * @cache: cache for results or NULL
549 * - to the absolute path
550 * - /dev/dm-N to /dev/mapper/name
552 * Returns: absolute path or NULL in case of error. The result has to be
553 * deallocated by free() if @cache is NULL.
555 char *mnt_resolve_path(const char *path
, struct libmnt_cache
*cache
)
559 /*DBG(CACHE, ul_debugobj(cache, "resolving path %s", path));*/
564 p
= (char *) cache_find_path(cache
, path
);
566 p
= canonicalize_path_and_cache(path
, cache
);
572 * mnt_resolve_target:
573 * @path: "native" path, a potential mount point
574 * @cache: cache for results or NULL.
576 * Like mnt_resolve_path(), unless @cache is not NULL and
577 * mnt_cache_set_targets(cache, mountinfo) was called: if @path is found in the
578 * cached @mountinfo and the matching entry was provided by the kernel, assume that
579 * @path is already canonicalized. By avoiding a call to realpath(2) on
580 * known mount points, there is a lower risk of stepping on a stale mount
581 * point, which can result in an application freeze. This is also faster in
582 * general, as stat(2) on a mount point is slower than on a regular file.
584 * Returns: absolute path or NULL in case of error. The result has to be
585 * deallocated by free() if @cache is NULL.
587 char *mnt_resolve_target(const char *path
, struct libmnt_cache
*cache
)
594 /*DBG(CACHE, ul_debugobj(cache, "resolving target %s", path));*/
596 if (!cache
|| !cache
->mountinfo
)
597 return mnt_resolve_path(path
, cache
);
599 p
= (char *) cache_find_path(cache
, path
);
604 struct libmnt_iter itr
;
605 struct libmnt_fs
*fs
= NULL
;
607 mnt_reset_iter(&itr
, MNT_ITER_BACKWARD
);
608 while (mnt_table_next_fs(cache
->mountinfo
, &itr
, &fs
) == 0) {
610 if (!mnt_fs_is_kernel(fs
)
611 || mnt_fs_is_swaparea(fs
)
612 || !mnt_fs_streq_target(fs
, path
))
617 return NULL
; /* ENOMEM */
619 if (cache_add_entry(cache
, p
, p
, MNT_CACHE_ISPATH
)) {
621 return NULL
; /* ENOMEM */
628 p
= canonicalize_path_and_cache(path
, cache
);
635 * @cache: NULL or pointer to the cache
638 * - to the absolute path
639 * - /dev/dm-N to /dev/mapper/name
640 * - /dev/loopN to the loop backing filename
641 * - empty path (NULL) to 'none'
643 * Returns: newly allocated string with path, result always has to be deallocated
646 char *mnt_pretty_path(const char *path
, struct libmnt_cache
*cache
)
648 char *pretty
= mnt_resolve_path(path
, cache
);
651 return strdup("none");
654 /* users assume backing file name rather than /dev/loopN in
655 * output if the device has been initialized by mount(8).
657 if (strncmp(pretty
, "/dev/loop", 9) == 0) {
658 struct loopdev_cxt lc
;
660 if (loopcxt_init(&lc
, 0) || loopcxt_set_device(&lc
, pretty
))
663 if (loopcxt_is_autoclear(&lc
)) {
664 char *tmp
= loopcxt_get_backing_file(&lc
);
668 free(pretty
); /* not cached, deallocate */
669 return tmp
; /* return backing file */
678 /* don't return pointer to the cache, allocate a new string */
679 return cache
? strdup(pretty
) : pretty
;
686 * @cache: for results or NULL
688 * Returns: device name or NULL in case of error. The result has to be
689 * deallocated by free() if @cache is NULL.
691 char *mnt_resolve_tag(const char *token
, const char *value
,
692 struct libmnt_cache
*cache
)
696 /*DBG(CACHE, ul_debugobj(cache, "resolving tag token=%s value=%s",
699 if (!token
|| !value
)
703 p
= (char *) cache_find_tag(cache
, token
, value
);
706 /* returns newly allocated string */
707 p
= blkid_evaluate_tag(token
, value
, cache
? &cache
->bc
: NULL
);
710 cache_add_tag(cache
, token
, value
, p
, 0))
725 * @cache: paths cache
727 * Returns: canonicalized path or NULL. The result has to be
728 * deallocated by free() if @cache is NULL.
730 char *mnt_resolve_spec(const char *spec
, struct libmnt_cache
*cache
)
733 char *t
= NULL
, *v
= NULL
;
738 if (blkid_parse_tag_string(spec
, &t
, &v
) == 0 && mnt_valid_tagname(t
))
739 cn
= mnt_resolve_tag(t
, v
, cache
);
741 cn
= mnt_resolve_path(spec
, cache
);
751 static int test_resolve_path(struct libmnt_test
*ts
__attribute__((unused
)),
752 int argc
__attribute__((unused
)),
753 char *argv
[] __attribute__((unused
)))
756 struct libmnt_cache
*cache
;
758 cache
= mnt_new_cache();
762 while(fgets(line
, sizeof(line
), stdin
)) {
763 size_t sz
= strlen(line
);
766 if (sz
> 0 && line
[sz
- 1] == '\n')
769 p
= mnt_resolve_path(line
, cache
);
770 printf("%s : %s\n", line
, p
);
772 mnt_unref_cache(cache
);
776 static int test_resolve_spec(struct libmnt_test
*ts
__attribute__((unused
)),
777 int argc
__attribute__((unused
)),
778 char *argv
[] __attribute__((unused
)))
781 struct libmnt_cache
*cache
;
783 cache
= mnt_new_cache();
787 while(fgets(line
, sizeof(line
), stdin
)) {
788 size_t sz
= strlen(line
);
791 if (sz
> 0 && line
[sz
- 1] == '\n')
794 p
= mnt_resolve_spec(line
, cache
);
795 printf("%s : %s\n", line
, p
);
797 mnt_unref_cache(cache
);
801 static int test_read_tags(struct libmnt_test
*ts
__attribute__((unused
)),
802 int argc
__attribute__((unused
)),
803 char *argv
[] __attribute__((unused
)))
806 struct libmnt_cache
*cache
;
809 cache
= mnt_new_cache();
813 while(fgets(line
, sizeof(line
), stdin
)) {
814 size_t sz
= strlen(line
);
815 char *t
= NULL
, *v
= NULL
;
817 if (sz
> 0 && line
[sz
- 1] == '\n')
820 if (!strcmp(line
, "quit"))
824 if (mnt_cache_read_tags(cache
, line
) < 0)
825 fprintf(stderr
, "%s: read tags failed\n", line
);
827 } else if (blkid_parse_tag_string(line
, &t
, &v
) == 0) {
828 const char *cn
= NULL
;
830 if (mnt_valid_tagname(t
))
831 cn
= cache_find_tag(cache
, t
, v
);
836 printf("%s: %s\n", line
, cn
);
838 printf("%s: not cached\n", line
);
842 for (i
= 0; i
< cache
->nents
; i
++) {
843 struct mnt_cache_entry
*e
= &cache
->ents
[i
];
844 if (!(e
->flag
& MNT_CACHE_ISTAG
))
847 printf("%15s : %5s : %s\n", e
->value
, e
->key
,
848 e
->key
+ strlen(e
->key
) + 1);
851 mnt_unref_cache(cache
);
856 int main(int argc
, char *argv
[])
858 struct libmnt_test ts
[] = {
859 { "--resolve-path", test_resolve_path
, " resolve paths from stdin" },
860 { "--resolve-spec", test_resolve_spec
, " evaluate specs from stdin" },
861 { "--read-tags", test_read_tags
, " read devname or TAG from stdin (\"quit\" to exit)" },
865 return mnt_run_test(ts
, argc
, argv
);