]>
Commit | Line | Data |
---|---|---|
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 |