]> git.ipfire.org Git - thirdparty/util-linux.git/blob - libmount/src/cache.c
b7956346f8b157bdad5546d9f0e2f02c821fa2a6
[thirdparty/util-linux.git] / libmount / src / cache.c
1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2
3 /*
4 * This file is part of libmount from util-linux project.
5 *
6 * Copyright (C) 2009-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: cache
16 * @title: Cache
17 * @short_description: paths and tags (UUID/LABEL) caching
18 *
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.
21 *
22 * All returned paths are always canonicalized.
23 */
24 #include <string.h>
25 #include <stdlib.h>
26 #include <ctype.h>
27 #include <limits.h>
28 #include <sys/stat.h>
29 #include <unistd.h>
30 #include <fcntl.h>
31 #include <blkid.h>
32
33 #include "canonicalize.h"
34 #include "mountP.h"
35 #include "loopdev.h"
36 #include "strutils.h"
37
38 /*
39 * Canonicalized (resolved) paths & tags cache
40 */
41 #define MNT_CACHE_CHUNKSZ 128
42
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() */
46
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) */
51 int flag;
52 };
53
54 struct libmnt_cache {
55 struct mnt_cache_entry *ents;
56 size_t nents;
57 size_t nallocs;
58 int refcount;
59 int probe_sb_extra; /* extra BLKID_SUBLKS_* flags */
60
61 /* blkid_evaluate_tag() works in two ways:
62 *
63 * 1/ all tags are evaluated by udev /dev/disk/by-* symlinks,
64 * then the blkid_cache is NULL.
65 *
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.
69 */
70 blkid_cache bc;
71
72 struct libmnt_table *mountinfo;
73 };
74
75 /**
76 * mnt_new_cache:
77 *
78 * Returns: new struct libmnt_cache instance or NULL in case of ENOMEM error.
79 */
80 struct libmnt_cache *mnt_new_cache(void)
81 {
82 struct libmnt_cache *cache = calloc(1, sizeof(*cache));
83 if (!cache)
84 return NULL;
85 DBG(CACHE, ul_debugobj(cache, "alloc"));
86 cache->refcount = 1;
87 return cache;
88 }
89
90 /**
91 * mnt_free_cache:
92 * @cache: pointer to struct libmnt_cache instance
93 *
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().
96 */
97 void mnt_free_cache(struct libmnt_cache *cache)
98 {
99 size_t i;
100
101 if (!cache)
102 return;
103
104 DBG(CACHE, ul_debugobj(cache, "free [refcount=%d]", cache->refcount));
105
106 for (i = 0; i < cache->nents; i++) {
107 struct mnt_cache_entry *e = &cache->ents[i];
108 if (e->value != e->key)
109 free(e->value);
110 free(e->key);
111 }
112 free(cache->ents);
113 if (cache->bc)
114 blkid_put_cache(cache->bc);
115 free(cache);
116 }
117
118 /**
119 * mnt_ref_cache:
120 * @cache: cache pointer
121 *
122 * Increments reference counter.
123 */
124 void mnt_ref_cache(struct libmnt_cache *cache)
125 {
126 if (cache) {
127 cache->refcount++;
128 /*DBG(CACHE, ul_debugobj(cache, "ref=%d", cache->refcount));*/
129 }
130 }
131
132 /**
133 * mnt_unref_cache:
134 * @cache: cache pointer
135 *
136 * De-increments reference counter, on zero the cache is automatically
137 * deallocated by mnt_free_cache().
138 */
139 void mnt_unref_cache(struct libmnt_cache *cache)
140 {
141 if (cache) {
142 cache->refcount--;
143 /*DBG(CACHE, ul_debugobj(cache, "unref=%d", cache->refcount));*/
144 if (cache->refcount <= 0) {
145 mnt_unref_table(cache->mountinfo);
146
147 mnt_free_cache(cache);
148 }
149 }
150 }
151
152 /**
153 * mnt_cache_set_targets:
154 * @cache: cache pointer
155 * @mountinfo: table with already canonicalized mountpoints
156 *
157 * Add to @cache reference to @mountinfo. This can be used to avoid unnecessary paths
158 * canonicalization in mnt_resolve_target().
159 *
160 * Returns: negative number in case of error, or 0 o success.
161 */
162 int mnt_cache_set_targets(struct libmnt_cache *cache,
163 struct libmnt_table *mountinfo)
164 {
165 if (!cache)
166 return -EINVAL;
167
168 mnt_ref_table(mountinfo);
169 mnt_unref_table(cache->mountinfo);
170 cache->mountinfo = mountinfo;
171 return 0;
172 }
173
174 /**
175 * mnt_cache_set_sbprobe:
176 * @cache: cache pointer
177 * @flags: BLKID_SUBLKS_* flags
178 *
179 * Add extra flags to the libblkid prober. Don't use if not sure.
180 *
181 * Returns: negative number in case of error, or 0 o success.
182 */
183 int mnt_cache_set_sbprobe(struct libmnt_cache *cache, int flags)
184 {
185 if (!cache)
186 return -EINVAL;
187
188 cache->probe_sb_extra = flags;
189 return 0;
190 }
191
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)
195 {
196 struct mnt_cache_entry *e;
197
198 assert(cache);
199 assert(value);
200 assert(key);
201
202 if (cache->nents == cache->nallocs) {
203 size_t sz = cache->nallocs + MNT_CACHE_CHUNKSZ;
204
205 e = reallocarray(cache->ents, sz, sizeof(struct mnt_cache_entry));
206 if (!e)
207 return -ENOMEM;
208 cache->ents = e;
209 cache->nallocs = sz;
210 }
211
212 e = &cache->ents[cache->nents];
213 e->key = key;
214 e->value = value;
215 e->flag = flag;
216 cache->nents++;
217
218 DBG(CACHE, ul_debugobj(cache, "add entry [%2zd] (%s): %s: %s",
219 cache->nents,
220 (flag & MNT_CACHE_ISPATH) ? "path" : "tag",
221 value, key));
222 return 0;
223 }
224
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)
228 {
229 size_t tksz, vlsz;
230 char *key;
231 int rc;
232
233 assert(cache);
234 assert(devname);
235 assert(tagname);
236 assert(tagval);
237
238 /* add into cache -- cache format for TAGs is
239 * key = "TAG_NAME\0TAG_VALUE\0"
240 * value = "/dev/foo"
241 */
242 tksz = strlen(tagname);
243 vlsz = strlen(tagval);
244
245 key = malloc(tksz + vlsz + 2);
246 if (!key)
247 return -ENOMEM;
248
249 memcpy(key, tagname, tksz + 1); /* include '\0' */
250 memcpy(key + tksz + 1, tagval, vlsz + 1);
251
252 rc = cache_add_entry(cache, key, devname, flag | MNT_CACHE_ISTAG);
253 if (!rc)
254 return 0;
255
256 free(key);
257 return rc;
258 }
259
260
261 /*
262 * Returns cached canonicalized path or NULL.
263 */
264 static const char *cache_find_path(struct libmnt_cache *cache, const char *path)
265 {
266 size_t i;
267
268 if (!cache || !path)
269 return NULL;
270
271 for (i = 0; i < cache->nents; i++) {
272 struct mnt_cache_entry *e = &cache->ents[i];
273 if (!(e->flag & MNT_CACHE_ISPATH))
274 continue;
275 if (streq_paths(path, e->key))
276 return e->value;
277 }
278 return NULL;
279 }
280
281 /*
282 * Returns cached path or NULL.
283 */
284 static const char *cache_find_tag(struct libmnt_cache *cache,
285 const char *token, const char *value)
286 {
287 size_t i;
288 size_t tksz;
289
290 if (!cache || !token || !value)
291 return NULL;
292
293 tksz = strlen(token);
294
295 for (i = 0; i < cache->nents; i++) {
296 struct mnt_cache_entry *e = &cache->ents[i];
297 if (!(e->flag & MNT_CACHE_ISTAG))
298 continue;
299 if (strcmp(token, e->key) == 0 &&
300 strcmp(value, e->key + tksz + 1) == 0)
301 return e->value;
302 }
303 return NULL;
304 }
305
306 static char *cache_find_tag_value(struct libmnt_cache *cache,
307 const char *devname, const char *token)
308 {
309 size_t i;
310
311 assert(cache);
312 assert(devname);
313 assert(token);
314
315 for (i = 0; i < cache->nents; i++) {
316 struct mnt_cache_entry *e = &cache->ents[i];
317 if (!(e->flag & MNT_CACHE_ISTAG))
318 continue;
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 */
322 }
323
324 return NULL;
325 }
326
327 /**
328 * mnt_cache_read_tags
329 * @cache: pointer to struct libmnt_cache instance
330 * @devname: path device
331 *
332 * Reads @devname LABEL and UUID to the @cache.
333 *
334 * Returns: 0 if at least one tag was added, 1 if no tag was added or
335 * negative number in case of error.
336 */
337 int mnt_cache_read_tags(struct libmnt_cache *cache, const char *devname)
338 {
339 blkid_probe pr;
340 size_t i, ntags = 0;
341 int rc;
342 const char *tags[] = { "LABEL", "UUID", "TYPE", "PARTUUID", "PARTLABEL" };
343 const char *blktags[] = { "LABEL", "UUID", "TYPE", "PART_ENTRY_UUID", "PART_ENTRY_NAME" };
344
345 if (!cache || !devname)
346 return -EINVAL;
347
348 DBG(CACHE, ul_debugobj(cache, "tags for %s requested", devname));
349
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))
354 continue;
355 if (strcmp(e->value, devname) == 0)
356 /* tags have already been read */
357 return 0;
358 }
359
360 pr = blkid_new_probe_from_filename(devname);
361 if (!pr)
362 return -1;
363
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);
368
369 blkid_probe_enable_partitions(pr, 1);
370 blkid_probe_set_partitions_flags(pr, BLKID_PARTS_ENTRY_DETAILS);
371
372 rc = blkid_do_safeprobe(pr);
373 if (rc)
374 goto error;
375
376 DBG(CACHE, ul_debugobj(cache, "reading tags for: %s", devname));
377
378 for (i = 0; i < ARRAY_SIZE(tags); i++) {
379 const char *data;
380 char *dev;
381
382 if (cache_find_tag_value(cache, devname, tags[i])) {
383 DBG(CACHE, ul_debugobj(cache,
384 "\ntag %s already cached", tags[i]));
385 continue;
386 }
387 if (blkid_probe_lookup_value(pr, blktags[i], &data, NULL))
388 continue;
389 dev = strdup(devname);
390 if (!dev)
391 goto error;
392 if (cache_add_tag(cache, tags[i], data, dev,
393 MNT_CACHE_TAGREAD)) {
394 free(dev);
395 goto error;
396 }
397 ntags++;
398 }
399
400 DBG(CACHE, ul_debugobj(cache, "\tread %zd tags", ntags));
401 blkid_free_probe(pr);
402 return ntags ? 0 : 1;
403 error:
404 blkid_free_probe(pr);
405 return rc < 0 ? rc : -1;
406 }
407
408 /**
409 * mnt_cache_device_has_tag:
410 * @cache: paths cache
411 * @devname: path to the device
412 * @token: tag name (e.g "LABEL")
413 * @value: tag value
414 *
415 * Look up @cache to check if @tag+@value are associated with @devname.
416 *
417 * Returns: 1 on success or 0.
418 */
419 int mnt_cache_device_has_tag(struct libmnt_cache *cache, const char *devname,
420 const char *token, const char *value)
421 {
422 const char *path = cache_find_tag(cache, token, value);
423
424 if (path && devname && strcmp(path, devname) == 0)
425 return 1;
426 return 0;
427 }
428
429 static int __mnt_cache_find_tag_value(struct libmnt_cache *cache,
430 const char *devname, const char *token, char **data)
431 {
432 int rc = 0;
433
434 if (!cache || !devname || !token || !data)
435 return -EINVAL;
436
437 rc = mnt_cache_read_tags(cache, devname);
438 if (rc)
439 return rc;
440
441 *data = cache_find_tag_value(cache, devname, token);
442 return *data ? 0 : -1;
443 }
444
445 /**
446 * mnt_cache_find_tag_value:
447 * @cache: cache for results
448 * @devname: device name
449 * @token: tag name ("LABEL" or "UUID")
450 *
451 * Returns: LABEL or UUID for the @devname or NULL in case of error.
452 */
453 char *mnt_cache_find_tag_value(struct libmnt_cache *cache,
454 const char *devname, const char *token)
455 {
456 char *data = NULL;
457
458 if (__mnt_cache_find_tag_value(cache, devname, token, &data) == 0)
459 return data;
460 return NULL;
461 }
462
463 /**
464 * mnt_get_fstype:
465 * @devname: device name
466 * @ambi: returns TRUE if probing result is ambivalent (optional argument)
467 * @cache: cache for results or NULL
468 *
469 * Returns: filesystem type or NULL in case of error. The result has to be
470 * deallocated by free() if @cache is NULL.
471 */
472 char *mnt_get_fstype(const char *devname, int *ambi, struct libmnt_cache *cache)
473 {
474 blkid_probe pr;
475 const char *data;
476 char *type = NULL;
477 int rc;
478
479 DBG(CACHE, ul_debugobj(cache, "get %s FS type", devname));
480
481 if (cache) {
482 char *val = NULL;
483 rc = __mnt_cache_find_tag_value(cache, devname, "TYPE", &val);
484 if (ambi)
485 *ambi = rc == -2 ? TRUE : FALSE;
486 return rc ? NULL : val;
487 }
488
489 /*
490 * no cache, probe directly
491 */
492 pr = blkid_new_probe_from_filename(devname);
493 if (!pr)
494 return NULL;
495
496 blkid_probe_enable_superblocks(pr, 1);
497 blkid_probe_set_superblocks_flags(pr, BLKID_SUBLKS_TYPE);
498
499 rc = blkid_do_safeprobe(pr);
500
501 DBG(CACHE, ul_debugobj(cache, "libblkid rc=%d", rc));
502
503 if (!rc && !blkid_probe_lookup_value(pr, "TYPE", &data, NULL))
504 type = strdup(data);
505
506 if (ambi)
507 *ambi = rc == -2 ? TRUE : FALSE;
508
509 blkid_free_probe(pr);
510 return type;
511 }
512
513 static char *canonicalize_path_and_cache(const char *path,
514 struct libmnt_cache *cache)
515 {
516 char *p;
517 char *key;
518 char *value;
519
520 DBG(CACHE, ul_debugobj(cache, "canonicalize path %s", path));
521 p = canonicalize_path(path);
522
523 if (p && cache) {
524 value = p;
525 key = strcmp(path, p) == 0 ? value : strdup(path);
526
527 if (!key || !value)
528 goto error;
529
530 if (cache_add_entry(cache, key, value,
531 MNT_CACHE_ISPATH))
532 goto error;
533 }
534
535 return p;
536 error:
537 if (value != key)
538 free(value);
539 free(key);
540 return NULL;
541 }
542
543 /**
544 * mnt_resolve_path:
545 * @path: "native" path
546 * @cache: cache for results or NULL
547 *
548 * Converts path:
549 * - to the absolute path
550 * - /dev/dm-N to /dev/mapper/name
551 *
552 * Returns: absolute path or NULL in case of error. The result has to be
553 * deallocated by free() if @cache is NULL.
554 */
555 char *mnt_resolve_path(const char *path, struct libmnt_cache *cache)
556 {
557 char *p = NULL;
558
559 /*DBG(CACHE, ul_debugobj(cache, "resolving path %s", path));*/
560
561 if (!path)
562 return NULL;
563 if (cache)
564 p = (char *) cache_find_path(cache, path);
565 if (!p)
566 p = canonicalize_path_and_cache(path, cache);
567
568 return p;
569 }
570
571 /**
572 * mnt_resolve_target:
573 * @path: "native" path, a potential mount point
574 * @cache: cache for results or NULL.
575 *
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.
583 *
584 * Returns: absolute path or NULL in case of error. The result has to be
585 * deallocated by free() if @cache is NULL.
586 */
587 char *mnt_resolve_target(const char *path, struct libmnt_cache *cache)
588 {
589 char *p = NULL;
590
591 if (!path)
592 return NULL;
593
594 /*DBG(CACHE, ul_debugobj(cache, "resolving target %s", path));*/
595
596 if (!cache || !cache->mountinfo)
597 return mnt_resolve_path(path, cache);
598
599 p = (char *) cache_find_path(cache, path);
600 if (p)
601 return p;
602
603 {
604 struct libmnt_iter itr;
605 struct libmnt_fs *fs = NULL;
606
607 mnt_reset_iter(&itr, MNT_ITER_BACKWARD);
608 while (mnt_table_next_fs(cache->mountinfo, &itr, &fs) == 0) {
609
610 if (!mnt_fs_is_kernel(fs)
611 || mnt_fs_is_swaparea(fs)
612 || !mnt_fs_streq_target(fs, path))
613 continue;
614
615 p = strdup(path);
616 if (!p)
617 return NULL; /* ENOMEM */
618
619 if (cache_add_entry(cache, p, p, MNT_CACHE_ISPATH)) {
620 free(p);
621 return NULL; /* ENOMEM */
622 }
623 break;
624 }
625 }
626
627 if (!p)
628 p = canonicalize_path_and_cache(path, cache);
629 return p;
630 }
631
632 /**
633 * mnt_pretty_path:
634 * @path: any path
635 * @cache: NULL or pointer to the cache
636 *
637 * Converts path:
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'
642 *
643 * Returns: newly allocated string with path, result always has to be deallocated
644 * by free().
645 */
646 char *mnt_pretty_path(const char *path, struct libmnt_cache *cache)
647 {
648 char *pretty = mnt_resolve_path(path, cache);
649
650 if (!pretty)
651 return strdup("none");
652
653 #ifdef __linux__
654 /* users assume backing file name rather than /dev/loopN in
655 * output if the device has been initialized by mount(8).
656 */
657 if (strncmp(pretty, "/dev/loop", 9) == 0) {
658 struct loopdev_cxt lc;
659
660 if (loopcxt_init(&lc, 0) || loopcxt_set_device(&lc, pretty))
661 goto done;
662
663 if (loopcxt_is_autoclear(&lc)) {
664 char *tmp = loopcxt_get_backing_file(&lc);
665 if (tmp) {
666 loopcxt_deinit(&lc);
667 if (!cache)
668 free(pretty); /* not cached, deallocate */
669 return tmp; /* return backing file */
670 }
671 }
672 loopcxt_deinit(&lc);
673
674 }
675 #endif
676
677 done:
678 /* don't return pointer to the cache, allocate a new string */
679 return cache ? strdup(pretty) : pretty;
680 }
681
682 /**
683 * mnt_resolve_tag:
684 * @token: tag name
685 * @value: tag value
686 * @cache: for results or NULL
687 *
688 * Returns: device name or NULL in case of error. The result has to be
689 * deallocated by free() if @cache is NULL.
690 */
691 char *mnt_resolve_tag(const char *token, const char *value,
692 struct libmnt_cache *cache)
693 {
694 char *p = NULL;
695
696 /*DBG(CACHE, ul_debugobj(cache, "resolving tag token=%s value=%s",
697 token, value));*/
698
699 if (!token || !value)
700 return NULL;
701
702 if (cache)
703 p = (char *) cache_find_tag(cache, token, value);
704
705 if (!p) {
706 /* returns newly allocated string */
707 p = blkid_evaluate_tag(token, value, cache ? &cache->bc : NULL);
708
709 if (p && cache &&
710 cache_add_tag(cache, token, value, p, 0))
711 goto error;
712 }
713
714 return p;
715 error:
716 free(p);
717 return NULL;
718 }
719
720
721
722 /**
723 * mnt_resolve_spec:
724 * @spec: path or tag
725 * @cache: paths cache
726 *
727 * Returns: canonicalized path or NULL. The result has to be
728 * deallocated by free() if @cache is NULL.
729 */
730 char *mnt_resolve_spec(const char *spec, struct libmnt_cache *cache)
731 {
732 char *cn = NULL;
733 char *t = NULL, *v = NULL;
734
735 if (!spec)
736 return NULL;
737
738 if (blkid_parse_tag_string(spec, &t, &v) == 0 && mnt_valid_tagname(t))
739 cn = mnt_resolve_tag(t, v, cache);
740 else
741 cn = mnt_resolve_path(spec, cache);
742
743 free(t);
744 free(v);
745 return cn;
746 }
747
748
749 #ifdef TEST_PROGRAM
750
751 static int test_resolve_path(struct libmnt_test *ts __attribute__((unused)),
752 int argc __attribute__((unused)),
753 char *argv[] __attribute__((unused)))
754 {
755 char line[BUFSIZ];
756 struct libmnt_cache *cache;
757
758 cache = mnt_new_cache();
759 if (!cache)
760 return -ENOMEM;
761
762 while(fgets(line, sizeof(line), stdin)) {
763 size_t sz = strlen(line);
764 char *p;
765
766 if (sz > 0 && line[sz - 1] == '\n')
767 line[sz - 1] = '\0';
768
769 p = mnt_resolve_path(line, cache);
770 printf("%s : %s\n", line, p);
771 }
772 mnt_unref_cache(cache);
773 return 0;
774 }
775
776 static int test_resolve_spec(struct libmnt_test *ts __attribute__((unused)),
777 int argc __attribute__((unused)),
778 char *argv[] __attribute__((unused)))
779 {
780 char line[BUFSIZ];
781 struct libmnt_cache *cache;
782
783 cache = mnt_new_cache();
784 if (!cache)
785 return -ENOMEM;
786
787 while(fgets(line, sizeof(line), stdin)) {
788 size_t sz = strlen(line);
789 char *p;
790
791 if (sz > 0 && line[sz - 1] == '\n')
792 line[sz - 1] = '\0';
793
794 p = mnt_resolve_spec(line, cache);
795 printf("%s : %s\n", line, p);
796 }
797 mnt_unref_cache(cache);
798 return 0;
799 }
800
801 static int test_read_tags(struct libmnt_test *ts __attribute__((unused)),
802 int argc __attribute__((unused)),
803 char *argv[] __attribute__((unused)))
804 {
805 char line[BUFSIZ];
806 struct libmnt_cache *cache;
807 size_t i;
808
809 cache = mnt_new_cache();
810 if (!cache)
811 return -ENOMEM;
812
813 while(fgets(line, sizeof(line), stdin)) {
814 size_t sz = strlen(line);
815 char *t = NULL, *v = NULL;
816
817 if (sz > 0 && line[sz - 1] == '\n')
818 line[sz - 1] = '\0';
819
820 if (!strcmp(line, "quit"))
821 break;
822
823 if (*line == '/') {
824 if (mnt_cache_read_tags(cache, line) < 0)
825 fprintf(stderr, "%s: read tags failed\n", line);
826
827 } else if (blkid_parse_tag_string(line, &t, &v) == 0) {
828 const char *cn = NULL;
829
830 if (mnt_valid_tagname(t))
831 cn = cache_find_tag(cache, t, v);
832 free(t);
833 free(v);
834
835 if (cn)
836 printf("%s: %s\n", line, cn);
837 else
838 printf("%s: not cached\n", line);
839 }
840 }
841
842 for (i = 0; i < cache->nents; i++) {
843 struct mnt_cache_entry *e = &cache->ents[i];
844 if (!(e->flag & MNT_CACHE_ISTAG))
845 continue;
846
847 printf("%15s : %5s : %s\n", e->value, e->key,
848 e->key + strlen(e->key) + 1);
849 }
850
851 mnt_unref_cache(cache);
852 return 0;
853
854 }
855
856 int main(int argc, char *argv[])
857 {
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)" },
862 { NULL }
863 };
864
865 return mnt_run_test(ts, argc, argv);
866 }
867 #endif