/src/util-linux/libmount/src/cache.c
Line | Count | Source |
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 | | /* sd-device is a replacement for libudev */ |
34 | | #ifdef USE_LIBMOUNT_UDEV_SUPPORT |
35 | | # include "dl-systemd.h" |
36 | | #endif |
37 | | |
38 | | #include "canonicalize.h" |
39 | | #include "mountP.h" |
40 | | #include "loopdev.h" |
41 | | #include "strutils.h" |
42 | | #include "mangle.h" |
43 | | |
44 | | /* |
45 | | * Canonicalized (resolved) paths & tags cache |
46 | | */ |
47 | 0 | #define MNT_CACHE_CHUNKSZ 128 |
48 | | |
49 | 0 | #define MNT_CACHE_ISTAG (1 << 1) /* entry is TAG */ |
50 | 0 | #define MNT_CACHE_ISPATH (1 << 2) /* entry is path */ |
51 | 0 | #define MNT_CACHE_TAGREAD (1 << 3) /* tag read by mnt_cache_read_tags() */ |
52 | | |
53 | | /* path cache entry */ |
54 | | struct mnt_cache_entry { |
55 | | char *key; /* search key (e.g. uncanonicalized path) */ |
56 | | char *value; /* value (e.g. canonicalized path) */ |
57 | | int flag; |
58 | | }; |
59 | | |
60 | | struct libmnt_cache { |
61 | | struct mnt_cache_entry *ents; |
62 | | size_t nents; |
63 | | size_t nallocs; |
64 | | int refcount; |
65 | | int probe_sb_extra; /* extra BLKID_SUBLKS_* flags */ |
66 | | bool noprobe; /* disable libblkid device probing */ |
67 | | |
68 | | const struct ul_vfs_ops *vfs; /* borrowed VFS ops (not owned) */ |
69 | | |
70 | | /* blkid_evaluate_tag() works in two ways: |
71 | | * |
72 | | * 1/ all tags are evaluated by udev /dev/disk/by-* symlinks, |
73 | | * then the blkid_cache is NULL. |
74 | | * |
75 | | * 2/ all tags are read from blkid.tab and verified by /dev |
76 | | * scanning, then the blkid_cache is not NULL and then it's |
77 | | * better to reuse the blkid_cache. |
78 | | */ |
79 | | blkid_cache bc; |
80 | | |
81 | | struct libmnt_table *mountinfo; |
82 | | }; |
83 | | |
84 | | |
85 | | struct libmnt_cachetag { |
86 | | const char *mnt_name; /* tag name used by libmount */ |
87 | | const char *blk_name; /* tag name used by libblkid */ |
88 | | const char *udev_name; /* tag name used by udev db */ |
89 | | }; |
90 | | |
91 | | static const struct libmnt_cachetag mnttags[] = |
92 | | { |
93 | | /* mount blkid udev */ |
94 | | { "LABEL", "LABEL", "ID_FS_LABEL_ENC" }, |
95 | | { "UUID", "UUID", "ID_FS_UUID_ENC" }, |
96 | | { "TYPE", "TYPE", "ID_FS_TYPE" }, |
97 | | { "PARTUUID", "PART_ENTRY_UUID", "ID_PART_ENTRY_UUID" }, |
98 | | { "PARTLABEL", "PART_ENTRY_NAME", "ID_PART_ENTRY_NAME" }, |
99 | | |
100 | | { NULL, NULL } |
101 | | }; |
102 | | |
103 | | /** |
104 | | * mnt_new_cache: |
105 | | * |
106 | | * Returns: new struct libmnt_cache instance or NULL in case of ENOMEM error. |
107 | | */ |
108 | | struct libmnt_cache *mnt_new_cache(void) |
109 | 0 | { |
110 | 0 | struct libmnt_cache *cache = calloc(1, sizeof(*cache)); |
111 | 0 | if (!cache) |
112 | 0 | return NULL; |
113 | 0 | DBG_OBJ(CACHE, cache, ul_debug("alloc")); |
114 | 0 | cache->refcount = 1; |
115 | 0 | return cache; |
116 | 0 | } |
117 | | |
118 | | /** |
119 | | * mnt_free_cache: |
120 | | * @cache: pointer to struct libmnt_cache instance |
121 | | * |
122 | | * Deallocates the cache. This function does not care about reference count. Don't |
123 | | * use this function directly -- it's better to use mnt_unref_cache(). |
124 | | */ |
125 | | void mnt_free_cache(struct libmnt_cache *cache) |
126 | 0 | { |
127 | 0 | size_t i; |
128 | |
|
129 | 0 | if (!cache) |
130 | 0 | return; |
131 | | |
132 | 0 | DBG_OBJ(CACHE, cache, ul_debug("free [refcount=%d]", cache->refcount)); |
133 | |
|
134 | 0 | for (i = 0; i < cache->nents; i++) { |
135 | 0 | struct mnt_cache_entry *e = &cache->ents[i]; |
136 | 0 | if (e->value != e->key) |
137 | 0 | free(e->value); |
138 | 0 | free(e->key); |
139 | 0 | } |
140 | 0 | free(cache->ents); |
141 | 0 | if (cache->bc) |
142 | 0 | blkid_put_cache(cache->bc); |
143 | 0 | free(cache); |
144 | 0 | } |
145 | | |
146 | | /** |
147 | | * mnt_ref_cache: |
148 | | * @cache: cache pointer |
149 | | * |
150 | | * Increments reference counter. |
151 | | */ |
152 | | void mnt_ref_cache(struct libmnt_cache *cache) |
153 | 0 | { |
154 | 0 | if (cache) { |
155 | 0 | cache->refcount++; |
156 | | /*DBG_OBJ(CACHE, cache, ul_debug("ref=%d", cache->refcount));*/ |
157 | 0 | } |
158 | 0 | } |
159 | | |
160 | | /** |
161 | | * mnt_unref_cache: |
162 | | * @cache: cache pointer |
163 | | * |
164 | | * De-increments reference counter, on zero the cache is automatically |
165 | | * deallocated by mnt_free_cache(). |
166 | | */ |
167 | | void mnt_unref_cache(struct libmnt_cache *cache) |
168 | 3.50k | { |
169 | 3.50k | if (cache) { |
170 | 0 | cache->refcount--; |
171 | | /*DBG_OBJ(CACHE, cache, ul_debug("unref=%d", cache->refcount));*/ |
172 | 0 | if (cache->refcount <= 0) { |
173 | 0 | mnt_unref_table(cache->mountinfo); |
174 | |
|
175 | 0 | mnt_free_cache(cache); |
176 | 0 | } |
177 | 0 | } |
178 | 3.50k | } |
179 | | |
180 | | /** |
181 | | * mnt_cache_set_targets: |
182 | | * @cache: cache pointer |
183 | | * @mountinfo: table with already canonicalized mountpoints |
184 | | * |
185 | | * Add to @cache reference to @mountinfo. This can be used to avoid unnecessary paths |
186 | | * canonicalization in mnt_resolve_target(). |
187 | | * |
188 | | * Returns: negative number in case of error, or 0 o success. |
189 | | */ |
190 | | int mnt_cache_set_targets(struct libmnt_cache *cache, |
191 | | struct libmnt_table *mountinfo) |
192 | 0 | { |
193 | 0 | if (!cache) |
194 | 0 | return -EINVAL; |
195 | | |
196 | 0 | mnt_ref_table(mountinfo); |
197 | 0 | mnt_unref_table(cache->mountinfo); |
198 | 0 | cache->mountinfo = mountinfo; |
199 | 0 | return 0; |
200 | 0 | } |
201 | | |
202 | | /** |
203 | | * mnt_cache_set_sbprobe: |
204 | | * @cache: cache pointer |
205 | | * @flags: BLKID_SUBLKS_* flags |
206 | | * |
207 | | * Add extra flags to the libblkid prober. Don't use if not sure. |
208 | | * |
209 | | * Returns: negative number in case of error, or 0 o success. |
210 | | */ |
211 | | int mnt_cache_set_sbprobe(struct libmnt_cache *cache, int flags) |
212 | 0 | { |
213 | 0 | if (!cache) |
214 | 0 | return -EINVAL; |
215 | | |
216 | 0 | cache->probe_sb_extra = flags; |
217 | 0 | return 0; |
218 | 0 | } |
219 | | |
220 | | void mnt_cache_enable_noprobe(struct libmnt_cache *cache, int enable) |
221 | 0 | { |
222 | 0 | if (cache) |
223 | 0 | cache->noprobe = !!enable; |
224 | 0 | } |
225 | | |
226 | | /** |
227 | | * mnt_cache_refer_vfs: |
228 | | * @cache: cache pointer |
229 | | * @vfs: VFS operations or NULL |
230 | | * |
231 | | * Set reference to VFS I/O operations. The cache does not own the @vfs |
232 | | * pointer -- the caller is responsible for its lifetime (e.g. the mount |
233 | | * context owns it). |
234 | | * |
235 | | * The VFS is used for blkid device probing (see mnt_cache_read_tags()). |
236 | | * |
237 | | * Returns: 0 on success, negative number in case of error. |
238 | | */ |
239 | | int mnt_cache_refer_vfs(struct libmnt_cache *cache, const struct ul_vfs_ops *vfs) |
240 | 0 | { |
241 | 0 | if (!cache) |
242 | 0 | return -EINVAL; |
243 | 0 | cache->vfs = vfs; |
244 | 0 | return 0; |
245 | 0 | } |
246 | | |
247 | | /* note that the @key could be the same pointer as @value */ |
248 | | static int cache_add_entry(struct libmnt_cache *cache, char *key, |
249 | | char *value, int flag) |
250 | 0 | { |
251 | 0 | struct mnt_cache_entry *e; |
252 | |
|
253 | 0 | assert(cache); |
254 | 0 | assert(value); |
255 | 0 | assert(key); |
256 | |
|
257 | 0 | if (cache->nents == cache->nallocs) { |
258 | 0 | size_t sz = cache->nallocs + MNT_CACHE_CHUNKSZ; |
259 | |
|
260 | 0 | e = reallocarray(cache->ents, sz, sizeof(struct mnt_cache_entry)); |
261 | 0 | if (!e) |
262 | 0 | return -ENOMEM; |
263 | 0 | cache->ents = e; |
264 | 0 | cache->nallocs = sz; |
265 | 0 | } |
266 | | |
267 | 0 | e = &cache->ents[cache->nents]; |
268 | 0 | e->key = key; |
269 | 0 | e->value = value; |
270 | 0 | e->flag = flag; |
271 | 0 | cache->nents++; |
272 | |
|
273 | 0 | DBG_OBJ(CACHE, cache, ul_debug("add entry [%2zu] (%s): %s: %s", |
274 | 0 | cache->nents, |
275 | 0 | (flag & MNT_CACHE_ISPATH) ? "path" : "tag", |
276 | 0 | value, key)); |
277 | 0 | return 0; |
278 | 0 | } |
279 | | |
280 | | /* add tag to the cache, @devname has to be an allocated string */ |
281 | | static int cache_add_tag(struct libmnt_cache *cache, const char *tagname, |
282 | | const char *tagval, char *devname, int flag) |
283 | 0 | { |
284 | 0 | size_t tksz, vlsz; |
285 | 0 | char *key; |
286 | 0 | int rc; |
287 | |
|
288 | 0 | assert(cache); |
289 | 0 | assert(devname); |
290 | 0 | assert(tagname); |
291 | 0 | assert(tagval); |
292 | | |
293 | | /* add into cache -- cache format for TAGs is |
294 | | * key = "TAG_NAME\0TAG_VALUE\0" |
295 | | * value = "/dev/foo" |
296 | | */ |
297 | 0 | tksz = strlen(tagname); |
298 | 0 | vlsz = strlen(tagval); |
299 | |
|
300 | 0 | key = malloc(tksz + vlsz + 2); |
301 | 0 | if (!key) |
302 | 0 | return -ENOMEM; |
303 | | |
304 | 0 | memcpy(key, tagname, tksz + 1); /* include '\0' */ |
305 | 0 | memcpy(key + tksz + 1, tagval, vlsz + 1); |
306 | |
|
307 | 0 | rc = cache_add_entry(cache, key, devname, flag | MNT_CACHE_ISTAG); |
308 | 0 | if (!rc) |
309 | 0 | return 0; |
310 | | |
311 | 0 | free(key); |
312 | 0 | return rc; |
313 | 0 | } |
314 | | |
315 | | |
316 | | /* |
317 | | * Returns cached canonicalized path or NULL. |
318 | | */ |
319 | | static const char *cache_find_path(struct libmnt_cache *cache, const char *path) |
320 | 0 | { |
321 | 0 | size_t i; |
322 | |
|
323 | 0 | if (!cache || !path) |
324 | 0 | return NULL; |
325 | | |
326 | 0 | for (i = 0; i < cache->nents; i++) { |
327 | 0 | struct mnt_cache_entry *e = &cache->ents[i]; |
328 | 0 | if (!(e->flag & MNT_CACHE_ISPATH)) |
329 | 0 | continue; |
330 | 0 | if (streq_paths(path, e->key)) |
331 | 0 | return e->value; |
332 | 0 | } |
333 | 0 | return NULL; |
334 | 0 | } |
335 | | |
336 | | /* |
337 | | * Returns cached path or NULL. |
338 | | */ |
339 | | static const char *cache_find_tag(struct libmnt_cache *cache, |
340 | | const char *token, const char *value) |
341 | 0 | { |
342 | 0 | size_t i; |
343 | 0 | size_t tksz; |
344 | |
|
345 | 0 | if (!cache || !token || !value) |
346 | 0 | return NULL; |
347 | | |
348 | 0 | tksz = strlen(token); |
349 | |
|
350 | 0 | for (i = 0; i < cache->nents; i++) { |
351 | 0 | struct mnt_cache_entry *e = &cache->ents[i]; |
352 | 0 | if (!(e->flag & MNT_CACHE_ISTAG)) |
353 | 0 | continue; |
354 | 0 | if (strcmp(token, e->key) == 0 && |
355 | 0 | strcmp(value, e->key + tksz + 1) == 0) |
356 | 0 | return e->value; |
357 | 0 | } |
358 | 0 | return NULL; |
359 | 0 | } |
360 | | |
361 | | static char *cache_find_tag_value(struct libmnt_cache *cache, |
362 | | const char *devname, const char *token) |
363 | 0 | { |
364 | 0 | size_t i; |
365 | |
|
366 | 0 | assert(cache); |
367 | 0 | assert(devname); |
368 | 0 | assert(token); |
369 | |
|
370 | 0 | for (i = 0; i < cache->nents; i++) { |
371 | 0 | struct mnt_cache_entry *e = &cache->ents[i]; |
372 | 0 | if (!(e->flag & MNT_CACHE_ISTAG)) |
373 | 0 | continue; |
374 | 0 | if (strcmp(e->value, devname) == 0 && /* dev name */ |
375 | 0 | strcmp(token, e->key) == 0) /* tag name */ |
376 | 0 | return e->key + strlen(token) + 1; /* tag value */ |
377 | 0 | } |
378 | | |
379 | 0 | return NULL; |
380 | 0 | } |
381 | | |
382 | | static bool is_device_cached(struct libmnt_cache *cache, const char *devname) |
383 | 0 | { |
384 | 0 | size_t i; |
385 | |
|
386 | 0 | for (i = 0; i < cache->nents; i++) { |
387 | 0 | struct mnt_cache_entry *e = &cache->ents[i]; |
388 | 0 | if (!(e->flag & MNT_CACHE_TAGREAD)) |
389 | 0 | continue; |
390 | 0 | if (strcmp(e->value, devname) == 0) |
391 | 0 | return 1; /* already in cache */ |
392 | 0 | } |
393 | | |
394 | 0 | return 0; |
395 | 0 | } |
396 | | |
397 | | /* |
398 | | * read data from libblkid into local cache |
399 | | * returns: <0 on error; 0 success; 1 nothing |
400 | | */ |
401 | | static int read_from_blkid(struct libmnt_cache *cache, const char *devname) |
402 | 0 | { |
403 | 0 | blkid_probe pr; |
404 | 0 | const struct libmnt_cachetag *t; |
405 | 0 | size_t ntags = 0; |
406 | 0 | int rc; |
407 | 0 | char *cacheval = NULL; |
408 | |
|
409 | 0 | assert(cache); |
410 | 0 | assert(devname); |
411 | |
|
412 | 0 | if (cache->noprobe) { |
413 | 0 | DBG_OBJ(CACHE, cache, ul_debug("%s: skip blkid probe (noprobe)", devname)); |
414 | 0 | return 1; |
415 | 0 | } |
416 | | |
417 | 0 | DBG_OBJ(CACHE, cache, ul_debug("%s: reading from blkid", devname)); |
418 | |
|
419 | 0 | pr = blkid_new_probe(); |
420 | 0 | if (!pr) |
421 | 0 | return -EINVAL; |
422 | | |
423 | 0 | if (cache->vfs) |
424 | 0 | blkid_probe_set_vfs(pr, cache->vfs); |
425 | 0 | if (blkid_probe_open_device(pr, devname, 0)) { |
426 | 0 | blkid_free_probe(pr); |
427 | 0 | return -errno; |
428 | 0 | } |
429 | | |
430 | 0 | blkid_probe_enable_superblocks(pr, 1); |
431 | 0 | blkid_probe_set_superblocks_flags(pr, |
432 | 0 | BLKID_SUBLKS_LABEL | BLKID_SUBLKS_UUID | |
433 | 0 | BLKID_SUBLKS_TYPE | cache->probe_sb_extra); |
434 | |
|
435 | 0 | blkid_probe_enable_partitions(pr, 1); |
436 | 0 | blkid_probe_set_partitions_flags(pr, BLKID_PARTS_ENTRY_DETAILS); |
437 | |
|
438 | 0 | rc = blkid_do_safeprobe(pr); |
439 | 0 | if (rc) |
440 | 0 | goto done; |
441 | | |
442 | 0 | for (t = mnttags; t && t->mnt_name; t++) { |
443 | 0 | const char *data; |
444 | |
|
445 | 0 | if (cache_find_tag_value(cache, devname, t->mnt_name)) |
446 | 0 | continue; |
447 | 0 | if (blkid_probe_lookup_value(pr, t->blk_name, &data, NULL)) |
448 | 0 | continue; |
449 | | |
450 | 0 | cacheval = strdup(devname); |
451 | 0 | rc = !cacheval ? -ENOMEM : |
452 | 0 | cache_add_tag(cache, t->mnt_name, data, cacheval, MNT_CACHE_TAGREAD); |
453 | 0 | if (rc) |
454 | 0 | break; |
455 | 0 | ntags++; |
456 | 0 | cacheval = NULL; /* stored into cache */ |
457 | 0 | } |
458 | |
|
459 | 0 | done: |
460 | 0 | DBG_OBJ(CACHE, cache, ul_debug("\tread %zu tags [rc=%d]", ntags, rc)); |
461 | 0 | blkid_free_probe(pr); |
462 | 0 | free(cacheval); |
463 | |
|
464 | 0 | return rc ? rc : ntags ? 0 : 1; |
465 | 0 | } |
466 | | |
467 | | #ifdef USE_LIBMOUNT_UDEV_SUPPORT |
468 | | /* |
469 | | * read data from udev into local cache |
470 | | * returns: <0 on error; 0 success; 1 nothing |
471 | | */ |
472 | | static int read_from_udev(struct libmnt_cache *cache, const char *devname) |
473 | | { |
474 | | sd_device *sd = NULL; |
475 | | const struct libmnt_cachetag *t; |
476 | | size_t ntags = 0; |
477 | | char *tagval = NULL, *cacheval = NULL; |
478 | | int rc; |
479 | | |
480 | | assert(cache); |
481 | | assert(devname); |
482 | | |
483 | | if (ul_dlopen_libsystemd() != 0) |
484 | | return -ENOSYS; |
485 | | |
486 | | rc = systemd_call(sd_device_new_from_devname)(&sd, devname); |
487 | | if (rc < 0) |
488 | | return rc; |
489 | | |
490 | | DBG_OBJ(CACHE, cache, ul_debug("%s: reading from udev", devname)); |
491 | | |
492 | | for (t = mnttags; t && t->mnt_name; t++) { |
493 | | const char *data; |
494 | | |
495 | | if (cache_find_tag_value(cache, devname, t->mnt_name)) |
496 | | continue; |
497 | | if (systemd_call(sd_device_get_property_value)(sd, t->udev_name, &data) < 0) |
498 | | continue; |
499 | | |
500 | | tagval = strdup(data); /* temporary for unhexmangle() */ |
501 | | cacheval = strdup(devname); |
502 | | |
503 | | if (tagval && cacheval) { |
504 | | unhexmangle_string(tagval); |
505 | | rc = cache_add_tag(cache, t->mnt_name, |
506 | | tagval, cacheval, MNT_CACHE_TAGREAD); |
507 | | } else |
508 | | rc = -ENOMEM; |
509 | | if (rc) |
510 | | break; |
511 | | ntags++; |
512 | | cacheval = NULL; /* stored into cache */ |
513 | | free(tagval), tagval = NULL; |
514 | | } |
515 | | |
516 | | DBG_OBJ(CACHE, cache, ul_debug("\tread %zu tags [rc=%d]", ntags, rc)); |
517 | | systemd_call(sd_device_unref)(sd); |
518 | | free(cacheval); |
519 | | free(tagval); |
520 | | |
521 | | return rc ? rc : ntags ? 0 : 1; |
522 | | } |
523 | | #endif /* USE_LIBMOUNT_UDEV_SUPPORT */ |
524 | | |
525 | | |
526 | | /** |
527 | | * mnt_cache_read_tags |
528 | | * @cache: pointer to struct libmnt_cache instance |
529 | | * @devname: path device |
530 | | * |
531 | | * Reads @devname information into the @cache. |
532 | | * |
533 | | * Returns: 0 if at least one tag was added, 1 if no tag was added or |
534 | | * negative number in case of error. |
535 | | */ |
536 | | int mnt_cache_read_tags(struct libmnt_cache *cache, const char *devname) |
537 | 0 | { |
538 | 0 | if (!cache || !devname) |
539 | 0 | return -EINVAL; |
540 | | |
541 | 0 | DBG_OBJ(CACHE, cache, ul_debug("tags for %s requested", devname)); |
542 | |
|
543 | 0 | if (is_device_cached(cache, devname)) |
544 | 0 | return 0; |
545 | | |
546 | | #ifdef USE_LIBMOUNT_UDEV_SUPPORT |
547 | | if (read_from_udev(cache, devname) == 0) |
548 | | return 0; |
549 | | #endif |
550 | 0 | return read_from_blkid(cache, devname); |
551 | 0 | } |
552 | | |
553 | | /** |
554 | | * mnt_cache_device_has_tag: |
555 | | * @cache: paths cache |
556 | | * @devname: path to the device |
557 | | * @token: tag name (e.g "LABEL") |
558 | | * @value: tag value |
559 | | * |
560 | | * Look up @cache to check if @tag+@value are associated with @devname. |
561 | | * |
562 | | * Returns: 1 on success or 0. |
563 | | */ |
564 | | int mnt_cache_device_has_tag(struct libmnt_cache *cache, const char *devname, |
565 | | const char *token, const char *value) |
566 | 0 | { |
567 | 0 | const char *path = cache_find_tag(cache, token, value); |
568 | |
|
569 | 0 | if (path && devname && strcmp(path, devname) == 0) |
570 | 0 | return 1; |
571 | 0 | return 0; |
572 | 0 | } |
573 | | |
574 | | static int __mnt_cache_find_tag_value(struct libmnt_cache *cache, |
575 | | const char *devname, const char *token, char **data) |
576 | 0 | { |
577 | 0 | int rc = 0; |
578 | |
|
579 | 0 | if (!cache || !devname || !token || !data) |
580 | 0 | return -EINVAL; |
581 | | |
582 | 0 | rc = mnt_cache_read_tags(cache, devname); |
583 | 0 | if (rc) |
584 | 0 | return rc; |
585 | | |
586 | 0 | *data = cache_find_tag_value(cache, devname, token); |
587 | 0 | return *data ? 0 : -1; |
588 | 0 | } |
589 | | |
590 | | /** |
591 | | * mnt_cache_find_tag_value: |
592 | | * @cache: cache for results |
593 | | * @devname: device name |
594 | | * @token: tag name ("LABEL", "UUID", ...) |
595 | | * |
596 | | * Returns: LABEL or UUID for the @devname or NULL in case of error. |
597 | | */ |
598 | | char *mnt_cache_find_tag_value(struct libmnt_cache *cache, |
599 | | const char *devname, const char *token) |
600 | 0 | { |
601 | 0 | char *data = NULL; |
602 | |
|
603 | 0 | if (__mnt_cache_find_tag_value(cache, devname, token, &data) == 0) |
604 | 0 | return data; |
605 | 0 | return NULL; |
606 | 0 | } |
607 | | |
608 | | static char *fstype_from_cache(const char *devname, struct libmnt_cache *cache) |
609 | 0 | { |
610 | 0 | char *val = NULL; |
611 | |
|
612 | 0 | assert(cache); |
613 | |
|
614 | 0 | if (__mnt_cache_find_tag_value(cache, devname, "TYPE", &val) != 0) |
615 | 0 | return NULL; |
616 | 0 | return val; |
617 | 0 | } |
618 | | |
619 | | static char *fstype_from_blkid(const char *devname, int *ambi, |
620 | | const struct ul_vfs_ops *vfs) |
621 | 0 | { |
622 | 0 | blkid_probe pr; |
623 | 0 | const char *data; |
624 | 0 | char *type = NULL; |
625 | 0 | int rc; |
626 | |
|
627 | 0 | pr = blkid_new_probe(); |
628 | 0 | if (!pr) |
629 | 0 | return NULL; |
630 | 0 | if (vfs) |
631 | 0 | blkid_probe_set_vfs(pr, vfs); |
632 | 0 | if (blkid_probe_open_device(pr, devname, 0)) { |
633 | 0 | blkid_free_probe(pr); |
634 | 0 | return NULL; |
635 | 0 | } |
636 | | |
637 | 0 | blkid_probe_enable_superblocks(pr, 1); |
638 | 0 | blkid_probe_set_superblocks_flags(pr, BLKID_SUBLKS_TYPE); |
639 | |
|
640 | 0 | rc = blkid_do_safeprobe(pr); |
641 | |
|
642 | 0 | if (!rc && !blkid_probe_lookup_value(pr, "TYPE", &data, NULL)) |
643 | 0 | type = strdup(data); |
644 | 0 | if (ambi) |
645 | 0 | *ambi = rc == -2 ? TRUE : FALSE; |
646 | |
|
647 | 0 | blkid_free_probe(pr); |
648 | 0 | return type; |
649 | 0 | } |
650 | | |
651 | | /** |
652 | | * mnt_get_fstype: |
653 | | * @devname: device name |
654 | | * @ambi: returns TRUE if probing result is ambivalent (optional argument) |
655 | | * @cache: cache for results or NULL |
656 | | * |
657 | | * Note: If the cache is not specified, it reads the file system type from the |
658 | | * device, and in this case, there is no optimization like udev db, etc. * |
659 | | * |
660 | | * Returns: filesystem type or NULL in case of error. The result has to be |
661 | | * deallocated by free() if @cache is NULL. |
662 | | */ |
663 | | char *mnt_get_fstype(const char *devname, int *ambi, struct libmnt_cache *cache) |
664 | 0 | { |
665 | 0 | DBG_OBJ(CACHE, cache, ul_debug("get %s FS type", devname)); |
666 | |
|
667 | 0 | if (cache) |
668 | 0 | return fstype_from_cache(devname, cache); |
669 | | |
670 | 0 | return fstype_from_blkid(devname, ambi, NULL); |
671 | 0 | } |
672 | | |
673 | | static char *canonicalize_path_and_cache(const char *path, |
674 | | struct libmnt_cache *cache) |
675 | 460 | { |
676 | 460 | char *p; |
677 | 460 | char *key; |
678 | 460 | char *value; |
679 | | |
680 | 460 | DBG_OBJ(CACHE, cache, ul_debug("canonicalize path %s", path)); |
681 | 460 | p = ul_canonicalize_path(path); |
682 | | |
683 | 460 | if (p && cache) { |
684 | 0 | value = p; |
685 | 0 | key = strcmp(path, p) == 0 ? value : strdup(path); |
686 | |
|
687 | 0 | if (!key || !value) |
688 | 0 | goto error; |
689 | | |
690 | 0 | if (cache_add_entry(cache, key, value, |
691 | 0 | MNT_CACHE_ISPATH)) |
692 | 0 | goto error; |
693 | 0 | } |
694 | | |
695 | 460 | return p; |
696 | 0 | error: |
697 | 0 | if (value != key) |
698 | 0 | free(value); |
699 | 0 | free(key); |
700 | 0 | return NULL; |
701 | 460 | } |
702 | | |
703 | | /** |
704 | | * mnt_resolve_path: |
705 | | * @path: "native" path |
706 | | * @cache: cache for results or NULL |
707 | | * |
708 | | * Converts path: |
709 | | * - to the absolute path |
710 | | * - /dev/dm-N to /dev/mapper/name |
711 | | * |
712 | | * Returns: absolute path or NULL in case of error. The result has to be |
713 | | * deallocated by free() if @cache is NULL. |
714 | | */ |
715 | | char *mnt_resolve_path(const char *path, struct libmnt_cache *cache) |
716 | 460 | { |
717 | 460 | char *p = NULL; |
718 | | |
719 | | /*DBG_OBJ(CACHE, cache, ul_debug("resolving path %s", path));*/ |
720 | | |
721 | 460 | if (!path) |
722 | 0 | return NULL; |
723 | 460 | if (cache) |
724 | 0 | p = (char *) cache_find_path(cache, path); |
725 | 460 | if (!p) |
726 | 460 | p = canonicalize_path_and_cache(path, cache); |
727 | | |
728 | 460 | return p; |
729 | 460 | } |
730 | | |
731 | | /** |
732 | | * mnt_resolve_target: |
733 | | * @path: "native" path, a potential mount point |
734 | | * @cache: cache for results or NULL. |
735 | | * |
736 | | * Like mnt_resolve_path(), unless @cache is not NULL and |
737 | | * mnt_cache_set_targets(cache, mountinfo) was called: if @path is found in the |
738 | | * cached @mountinfo and the matching entry was provided by the kernel, assume that |
739 | | * @path is already canonicalized. By avoiding a call to realpath(2) on |
740 | | * known mount points, there is a lower risk of stepping on a stale mount |
741 | | * point, which can result in an application freeze. This is also faster in |
742 | | * general, as stat(2) on a mount point is slower than on a regular file. |
743 | | * |
744 | | * Returns: absolute path or NULL in case of error. The result has to be |
745 | | * deallocated by free() if @cache is NULL. |
746 | | */ |
747 | | char *mnt_resolve_target(const char *path, struct libmnt_cache *cache) |
748 | 0 | { |
749 | 0 | char *p = NULL; |
750 | |
|
751 | 0 | if (!path) |
752 | 0 | return NULL; |
753 | | |
754 | | /*DBG_OBJ(CACHE, cache, ul_debug("resolving target %s", path));*/ |
755 | | |
756 | 0 | if (!cache || !cache->mountinfo) |
757 | 0 | return mnt_resolve_path(path, cache); |
758 | | |
759 | 0 | p = (char *) cache_find_path(cache, path); |
760 | 0 | if (p) |
761 | 0 | return p; |
762 | | |
763 | 0 | { |
764 | 0 | struct libmnt_iter itr; |
765 | 0 | struct libmnt_fs *fs = NULL; |
766 | |
|
767 | 0 | mnt_reset_iter(&itr, MNT_ITER_BACKWARD); |
768 | 0 | while (mnt_table_next_fs(cache->mountinfo, &itr, &fs) == 0) { |
769 | |
|
770 | 0 | if (!mnt_fs_is_kernel(fs) |
771 | 0 | || mnt_fs_is_swaparea(fs) |
772 | 0 | || !mnt_fs_streq_target(fs, path)) |
773 | 0 | continue; |
774 | | |
775 | 0 | p = strdup(path); |
776 | 0 | if (!p) |
777 | 0 | return NULL; /* ENOMEM */ |
778 | | |
779 | 0 | if (cache_add_entry(cache, p, p, MNT_CACHE_ISPATH)) { |
780 | 0 | free(p); |
781 | 0 | return NULL; /* ENOMEM */ |
782 | 0 | } |
783 | 0 | break; |
784 | 0 | } |
785 | 0 | } |
786 | | |
787 | 0 | if (!p) |
788 | 0 | p = canonicalize_path_and_cache(path, cache); |
789 | 0 | return p; |
790 | 0 | } |
791 | | |
792 | | /** |
793 | | * mnt_pretty_path: |
794 | | * @path: any path |
795 | | * @cache: NULL or pointer to the cache |
796 | | * |
797 | | * Converts path: |
798 | | * - to the absolute path |
799 | | * - /dev/dm-N to /dev/mapper/name |
800 | | * - /dev/loopN to the loop backing filename |
801 | | * - empty path (NULL) to 'none' |
802 | | * |
803 | | * Returns: newly allocated string with path, result always has to be deallocated |
804 | | * by free(). |
805 | | */ |
806 | | char *mnt_pretty_path(const char *path, struct libmnt_cache *cache) |
807 | 0 | { |
808 | 0 | char *pretty = mnt_resolve_path(path, cache); |
809 | |
|
810 | 0 | if (!pretty) |
811 | 0 | return strdup("none"); |
812 | | |
813 | 0 | #ifdef __linux__ |
814 | | /* users assume backing file name rather than /dev/loopN in |
815 | | * output if the device has been initialized by mount(8). |
816 | | */ |
817 | 0 | if (strncmp(pretty, "/dev/loop", 9) == 0) { |
818 | 0 | struct loopdev_cxt lc; |
819 | |
|
820 | 0 | if (loopcxt_init(&lc, 0) || loopcxt_set_device(&lc, pretty)) |
821 | 0 | goto done; |
822 | | |
823 | 0 | if (loopcxt_is_autoclear(&lc)) { |
824 | 0 | char *tmp = loopcxt_get_backing_file(&lc); |
825 | 0 | if (tmp) { |
826 | 0 | loopcxt_deinit(&lc); |
827 | 0 | if (!cache) |
828 | 0 | free(pretty); /* not cached, deallocate */ |
829 | 0 | return tmp; /* return backing file */ |
830 | 0 | } |
831 | 0 | } |
832 | 0 | loopcxt_deinit(&lc); |
833 | |
|
834 | 0 | } |
835 | 0 | #endif |
836 | | |
837 | 0 | done: |
838 | | /* don't return pointer to the cache, allocate a new string */ |
839 | 0 | return cache ? strdup(pretty) : pretty; |
840 | 0 | } |
841 | | |
842 | | /** |
843 | | * mnt_resolve_tag: |
844 | | * @token: tag name |
845 | | * @value: tag value |
846 | | * @cache: for results or NULL |
847 | | * |
848 | | * Returns: device name or NULL in case of error. The result has to be |
849 | | * deallocated by free() if @cache is NULL. |
850 | | */ |
851 | | char *mnt_resolve_tag(const char *token, const char *value, |
852 | | struct libmnt_cache *cache) |
853 | 38 | { |
854 | 38 | char *p = NULL; |
855 | | |
856 | 38 | if (!token || !value) |
857 | 0 | return NULL; |
858 | | |
859 | 38 | if (cache) |
860 | 0 | p = (char *) cache_find_tag(cache, token, value); |
861 | | |
862 | 38 | if (!p) { |
863 | 38 | DBG_OBJ(CACHE, cache, ul_debug("evaluating (by blkid) tag %s=%s", token, value)); |
864 | | |
865 | | /* returns newly allocated string */ |
866 | 38 | p = blkid_evaluate_tag2(token, value, |
867 | 38 | cache ? &cache->bc : NULL, |
868 | 38 | cache && cache->noprobe ? BLKID_EVALUATE_NOPROBE : 0); |
869 | | |
870 | 38 | if (p && cache && |
871 | 0 | cache_add_tag(cache, token, value, p, 0)) |
872 | 0 | goto error; |
873 | 38 | } |
874 | | |
875 | 38 | DBG_OBJ(CACHE, cache, ul_debug("resolve tag %s=%s -> %s", |
876 | 38 | token, value, p ? p : "NOT FOUND")); |
877 | 38 | return p; |
878 | 0 | error: |
879 | 0 | free(p); |
880 | 0 | return NULL; |
881 | 38 | } |
882 | | |
883 | | |
884 | | |
885 | | /** |
886 | | * mnt_resolve_spec: |
887 | | * @spec: path or tag |
888 | | * @cache: paths cache |
889 | | * |
890 | | * Returns: canonicalized path or NULL. The result has to be |
891 | | * deallocated by free() if @cache is NULL. |
892 | | */ |
893 | | char *mnt_resolve_spec(const char *spec, struct libmnt_cache *cache) |
894 | 38 | { |
895 | 38 | char *cn = NULL; |
896 | 38 | char *t = NULL, *v = NULL; |
897 | | |
898 | 38 | if (!spec) |
899 | 0 | return NULL; |
900 | | |
901 | 38 | if (blkid_parse_tag_string(spec, &t, &v) == 0 && mnt_valid_tagname(t)) |
902 | 38 | cn = mnt_resolve_tag(t, v, cache); |
903 | 0 | else |
904 | 0 | cn = mnt_resolve_path(spec, cache); |
905 | | |
906 | 38 | free(t); |
907 | 38 | free(v); |
908 | 38 | return cn; |
909 | 38 | } |
910 | | |
911 | | |
912 | | #ifdef TEST_PROGRAM |
913 | | |
914 | | static int test_resolve_path(struct libmnt_test *ts __attribute__((unused)), |
915 | | int argc __attribute__((unused)), |
916 | | char *argv[] __attribute__((unused))) |
917 | | { |
918 | | char line[BUFSIZ]; |
919 | | struct libmnt_cache *cache; |
920 | | |
921 | | cache = mnt_new_cache(); |
922 | | if (!cache) |
923 | | return -ENOMEM; |
924 | | |
925 | | while(fgets(line, sizeof(line), stdin)) { |
926 | | size_t sz = strlen(line); |
927 | | char *p; |
928 | | |
929 | | if (sz > 0 && line[sz - 1] == '\n') |
930 | | line[sz - 1] = '\0'; |
931 | | |
932 | | p = mnt_resolve_path(line, cache); |
933 | | printf("%s : %s\n", line, p); |
934 | | } |
935 | | mnt_unref_cache(cache); |
936 | | return 0; |
937 | | } |
938 | | |
939 | | static int test_resolve_spec(struct libmnt_test *ts __attribute__((unused)), |
940 | | int argc __attribute__((unused)), |
941 | | char *argv[] __attribute__((unused))) |
942 | | { |
943 | | char line[BUFSIZ]; |
944 | | struct libmnt_cache *cache; |
945 | | |
946 | | cache = mnt_new_cache(); |
947 | | if (!cache) |
948 | | return -ENOMEM; |
949 | | |
950 | | while(fgets(line, sizeof(line), stdin)) { |
951 | | size_t sz = strlen(line); |
952 | | char *p; |
953 | | |
954 | | if (sz > 0 && line[sz - 1] == '\n') |
955 | | line[sz - 1] = '\0'; |
956 | | |
957 | | p = mnt_resolve_spec(line, cache); |
958 | | printf("%s : %s\n", line, p); |
959 | | } |
960 | | mnt_unref_cache(cache); |
961 | | return 0; |
962 | | } |
963 | | |
964 | | static int test_read_tags(struct libmnt_test *ts __attribute__((unused)), |
965 | | int argc __attribute__((unused)), |
966 | | char *argv[] __attribute__((unused))) |
967 | | { |
968 | | char line[BUFSIZ]; |
969 | | struct libmnt_cache *cache; |
970 | | size_t i; |
971 | | |
972 | | cache = mnt_new_cache(); |
973 | | if (!cache) |
974 | | return -ENOMEM; |
975 | | |
976 | | while(fgets(line, sizeof(line), stdin)) { |
977 | | size_t sz = strlen(line); |
978 | | char *t = NULL, *v = NULL; |
979 | | |
980 | | if (sz > 0 && line[sz - 1] == '\n') |
981 | | line[sz - 1] = '\0'; |
982 | | |
983 | | if (!strcmp(line, "quit")) |
984 | | break; |
985 | | |
986 | | if (*line == '/') { |
987 | | if (mnt_cache_read_tags(cache, line) < 0) |
988 | | fprintf(stderr, "%s: read tags failed\n", line); |
989 | | |
990 | | } else if (blkid_parse_tag_string(line, &t, &v) == 0) { |
991 | | const char *cn = NULL; |
992 | | |
993 | | if (mnt_valid_tagname(t)) |
994 | | cn = cache_find_tag(cache, t, v); |
995 | | free(t); |
996 | | free(v); |
997 | | |
998 | | if (cn) |
999 | | printf("%s: %s\n", line, cn); |
1000 | | else |
1001 | | printf("%s: not cached\n", line); |
1002 | | } |
1003 | | } |
1004 | | |
1005 | | for (i = 0; i < cache->nents; i++) { |
1006 | | struct mnt_cache_entry *e = &cache->ents[i]; |
1007 | | if (!(e->flag & MNT_CACHE_ISTAG)) |
1008 | | continue; |
1009 | | |
1010 | | printf("%15s : %5s : %s\n", e->value, e->key, |
1011 | | e->key + strlen(e->key) + 1); |
1012 | | } |
1013 | | |
1014 | | mnt_unref_cache(cache); |
1015 | | return 0; |
1016 | | |
1017 | | } |
1018 | | |
1019 | | int main(int argc, char *argv[]) |
1020 | | { |
1021 | | struct libmnt_test ts[] = { |
1022 | | { "--resolve-path", test_resolve_path, " resolve paths from stdin" }, |
1023 | | { "--resolve-spec", test_resolve_spec, " evaluate specs from stdin" }, |
1024 | | { "--read-tags", test_read_tags, " read devname or TAG from stdin (\"quit\" to exit)" }, |
1025 | | { NULL } |
1026 | | }; |
1027 | | |
1028 | | return mnt_run_test(ts, argc, argv); |
1029 | | } |
1030 | | #endif |