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