]>
Commit | Line | Data |
---|---|---|
0986f29a KZ |
1 | /* |
2 | * These functions implement tree of block devices. The devtree struct contains | |
3 | * two basic lists: | |
4 | * | |
5 | * 1) devtree->devices -- This is simple list without any hierarchy. We use | |
6 | * reference counting here. | |
7 | * | |
8 | * 2) devtree->roots -- The root nodes of the trees. The code does not use | |
9 | * reference counting here due to complexity and it's unnecessary. | |
10 | * | |
11 | * Note that the same device maybe have more parents and more children. The | |
12 | * device is allocated only once and shared within the tree. The dependence | |
13 | * (devdep struct) contains reference to child as well as to parent and the | |
14 | * dependence is reference by ls_childs from parent device and by ls_parents | |
15 | * from child. (Yes, "childs" is used for children ;-) | |
16 | * | |
17 | * Copyright (C) 2018 Karel Zak <kzak@redhat.com> | |
18 | */ | |
5bb395f4 KZ |
19 | #include "lsblk.h" |
20 | #include "sysfs.h" | |
21 | ||
22 | ||
23 | void lsblk_reset_iter(struct lsblk_iter *itr, int direction) | |
24 | { | |
25 | if (direction == -1) | |
26 | direction = itr->direction; | |
27 | ||
28 | memset(itr, 0, sizeof(*itr)); | |
29 | itr->direction = direction; | |
30 | } | |
31 | ||
10501add | 32 | struct lsblk_device *lsblk_new_device() |
5bb395f4 KZ |
33 | { |
34 | struct lsblk_device *dev; | |
35 | ||
36 | dev = calloc(1, sizeof(*dev)); | |
37 | if (!dev) | |
38 | return NULL; | |
39 | ||
40 | dev->refcount = 1; | |
81a8936c | 41 | dev->removable = -1; |
8a2f175c | 42 | dev->discard_granularity = (uint64_t) -1; |
5bb395f4 | 43 | |
0986f29a KZ |
44 | INIT_LIST_HEAD(&dev->childs); |
45 | INIT_LIST_HEAD(&dev->parents); | |
5bb395f4 KZ |
46 | INIT_LIST_HEAD(&dev->ls_roots); |
47 | INIT_LIST_HEAD(&dev->ls_devices); | |
48 | ||
49 | DBG(DEV, ul_debugobj(dev, "alloc")); | |
50 | return dev; | |
51 | } | |
52 | ||
53 | void lsblk_ref_device(struct lsblk_device *dev) | |
54 | { | |
55 | if (dev) | |
56 | dev->refcount++; | |
57 | } | |
58 | ||
0986f29a KZ |
59 | /* removes dependence from child as well as from parent */ |
60 | static int remove_dependence(struct lsblk_devdep *dep) | |
5bb395f4 | 61 | { |
0986f29a | 62 | if (!dep) |
5bb395f4 KZ |
63 | return -EINVAL; |
64 | ||
0986f29a KZ |
65 | DBG(DEP, ul_debugobj(dep, " dealloc")); |
66 | ||
67 | list_del_init(&dep->ls_childs); | |
68 | list_del_init(&dep->ls_parents); | |
69 | ||
5bb395f4 KZ |
70 | free(dep); |
71 | return 0; | |
72 | } | |
73 | ||
74 | static int device_remove_dependences(struct lsblk_device *dev) | |
75 | { | |
76 | if (!dev) | |
77 | return -EINVAL; | |
78 | ||
0986f29a KZ |
79 | if (!list_empty(&dev->childs)) |
80 | DBG(DEV, ul_debugobj(dev, " %s: remove all children deps", dev->name)); | |
81 | while (!list_empty(&dev->childs)) { | |
82 | struct lsblk_devdep *dp = list_entry(dev->childs.next, | |
83 | struct lsblk_devdep, ls_childs); | |
84 | remove_dependence(dp); | |
85 | } | |
86 | ||
87 | if (!list_empty(&dev->parents)) | |
88 | DBG(DEV, ul_debugobj(dev, " %s: remove all parents deps", dev->name)); | |
89 | while (!list_empty(&dev->parents)) { | |
90 | struct lsblk_devdep *dp = list_entry(dev->parents.next, | |
91 | struct lsblk_devdep, ls_parents); | |
92 | remove_dependence(dp); | |
5bb395f4 | 93 | } |
0986f29a | 94 | |
5bb395f4 KZ |
95 | return 0; |
96 | } | |
97 | ||
98 | void lsblk_unref_device(struct lsblk_device *dev) | |
99 | { | |
57502dd3 | 100 | if (!dev) |
5bb395f4 KZ |
101 | return; |
102 | ||
103 | if (--dev->refcount <= 0) { | |
0986f29a | 104 | DBG(DEV, ul_debugobj(dev, " freeing [%s] <<", dev->name)); |
5bb395f4 KZ |
105 | |
106 | device_remove_dependences(dev); | |
107 | lsblk_device_free_properties(dev->properties); | |
108 | ||
10501add KZ |
109 | lsblk_unref_device(dev->wholedisk); |
110 | ||
5bb395f4 KZ |
111 | free(dev->dm_name); |
112 | free(dev->filename); | |
113 | free(dev->mountpoint); | |
dc4662f0 | 114 | free(dev->dedupkey); |
5bb395f4 KZ |
115 | |
116 | ul_unref_path(dev->sysfs); | |
5bb395f4 | 117 | |
0986f29a KZ |
118 | DBG(DEV, ul_debugobj(dev, " >> dealloc [%s]", dev->name)); |
119 | free(dev->name); | |
5bb395f4 KZ |
120 | free(dev); |
121 | } | |
122 | } | |
123 | ||
0986f29a | 124 | int lsblk_device_has_child(struct lsblk_device *dev, struct lsblk_device *child) |
5bb395f4 | 125 | { |
e2d7870b KZ |
126 | struct lsblk_device *x = NULL; |
127 | struct lsblk_iter itr; | |
5bb395f4 | 128 | |
e2d7870b KZ |
129 | lsblk_reset_iter(&itr, LSBLK_ITER_FORWARD); |
130 | ||
131 | while (lsblk_device_next_child(dev, &itr, &x) == 0) { | |
132 | if (x == child) | |
133 | return 1; | |
5bb395f4 KZ |
134 | } |
135 | ||
e2d7870b KZ |
136 | return 0; |
137 | } | |
138 | ||
139 | int lsblk_device_new_dependence(struct lsblk_device *parent, struct lsblk_device *child) | |
140 | { | |
141 | struct lsblk_devdep *dp; | |
142 | ||
143 | if (!parent || !child) | |
144 | return -EINVAL; | |
145 | ||
0986f29a | 146 | if (lsblk_device_has_child(parent, child)) |
e2d7870b KZ |
147 | return 1; |
148 | ||
5bb395f4 KZ |
149 | dp = calloc(1, sizeof(*dp)); |
150 | if (!dp) | |
e2d7870b | 151 | return -ENOMEM; |
5bb395f4 | 152 | |
0986f29a KZ |
153 | INIT_LIST_HEAD(&dp->ls_childs); |
154 | INIT_LIST_HEAD(&dp->ls_parents); | |
5bb395f4 | 155 | |
5bb395f4 | 156 | dp->child = child; |
0986f29a KZ |
157 | list_add_tail(&dp->ls_childs, &parent->childs); |
158 | ||
159 | dp->parent = parent; | |
0bd05f5e | 160 | list_add_tail(&dp->ls_parents, &child->parents); |
5bb395f4 KZ |
161 | |
162 | DBG(DEV, ul_debugobj(parent, "add dependence 0x%p [%s->%s]", dp, parent->name, child->name)); | |
5bb395f4 | 163 | |
e2d7870b | 164 | return 0; |
5bb395f4 KZ |
165 | } |
166 | ||
0986f29a | 167 | static int device_next_child(struct lsblk_device *dev, |
5bb395f4 | 168 | struct lsblk_iter *itr, |
dc4662f0 | 169 | struct lsblk_devdep **dp) |
5bb395f4 KZ |
170 | { |
171 | int rc = 1; | |
172 | ||
dc4662f0 | 173 | if (!dev || !itr || !dp) |
5bb395f4 | 174 | return -EINVAL; |
dc4662f0 | 175 | *dp = NULL; |
5bb395f4 KZ |
176 | |
177 | if (!itr->head) | |
0986f29a | 178 | LSBLK_ITER_INIT(itr, &dev->childs); |
5bb395f4 | 179 | if (itr->p != itr->head) { |
0986f29a | 180 | LSBLK_ITER_ITERATE(itr, *dp, struct lsblk_devdep, ls_childs); |
5bb395f4 KZ |
181 | rc = 0; |
182 | } | |
183 | ||
184 | return rc; | |
185 | } | |
186 | ||
dc4662f0 KZ |
187 | int lsblk_device_next_child(struct lsblk_device *dev, |
188 | struct lsblk_iter *itr, | |
189 | struct lsblk_device **child) | |
190 | { | |
191 | struct lsblk_devdep *dp = NULL; | |
0986f29a | 192 | int rc = device_next_child(dev, itr, &dp); |
dc4662f0 KZ |
193 | |
194 | if (!child) | |
195 | return -EINVAL; | |
196 | ||
197 | *child = rc == 0 ? dp->child : NULL; | |
198 | return rc; | |
199 | } | |
200 | ||
0bd05f5e KZ |
201 | int lsblk_device_is_last_parent(struct lsblk_device *dev, struct lsblk_device *parent) |
202 | { | |
203 | struct lsblk_devdep *dp = list_last_entry( | |
204 | &dev->parents, | |
205 | struct lsblk_devdep, ls_parents); | |
e361253e SK |
206 | if (!dp) |
207 | return 0; | |
0bd05f5e KZ |
208 | return dp->parent == parent; |
209 | } | |
210 | ||
211 | int lsblk_device_next_parent( | |
212 | struct lsblk_device *dev, | |
213 | struct lsblk_iter *itr, | |
214 | struct lsblk_device **parent) | |
215 | { | |
216 | int rc = 1; | |
217 | ||
218 | if (!dev || !itr || !parent) | |
219 | return -EINVAL; | |
220 | *parent = NULL; | |
221 | ||
222 | if (!itr->head) | |
223 | LSBLK_ITER_INIT(itr, &dev->parents); | |
224 | if (itr->p != itr->head) { | |
225 | struct lsblk_devdep *dp = NULL; | |
226 | LSBLK_ITER_ITERATE(itr, dp, struct lsblk_devdep, ls_parents); | |
227 | if (dp) | |
228 | *parent = dp->parent; | |
229 | rc = 0; | |
230 | } | |
231 | ||
232 | return rc; | |
233 | } | |
e2d7870b | 234 | |
5bb395f4 KZ |
235 | struct lsblk_devtree *lsblk_new_devtree() |
236 | { | |
237 | struct lsblk_devtree *tr; | |
238 | ||
239 | tr = calloc(1, sizeof(*tr)); | |
240 | if (!tr) | |
241 | return NULL; | |
242 | ||
243 | tr->refcount = 1; | |
244 | ||
245 | INIT_LIST_HEAD(&tr->roots); | |
246 | INIT_LIST_HEAD(&tr->devices); | |
247 | ||
248 | DBG(TREE, ul_debugobj(tr, "alloc")); | |
249 | return tr; | |
250 | } | |
251 | ||
252 | void lsblk_ref_devtree(struct lsblk_devtree *tr) | |
253 | { | |
254 | if (tr) | |
255 | tr->refcount++; | |
256 | } | |
257 | ||
258 | void lsblk_unref_devtree(struct lsblk_devtree *tr) | |
259 | { | |
57502dd3 | 260 | if (!tr) |
5bb395f4 KZ |
261 | return; |
262 | ||
263 | if (--tr->refcount <= 0) { | |
264 | DBG(TREE, ul_debugobj(tr, "dealloc")); | |
265 | ||
5bb395f4 KZ |
266 | while (!list_empty(&tr->devices)) { |
267 | struct lsblk_device *dev = list_entry(tr->devices.next, | |
268 | struct lsblk_device, ls_devices); | |
57502dd3 | 269 | lsblk_devtree_remove_device(tr, dev); |
5bb395f4 KZ |
270 | } |
271 | free(tr); | |
272 | } | |
273 | } | |
274 | ||
275 | int lsblk_devtree_add_root(struct lsblk_devtree *tr, struct lsblk_device *dev) | |
276 | { | |
21d4a48c KZ |
277 | if (!lsblk_devtree_has_device(tr, dev)) |
278 | lsblk_devtree_add_device(tr, dev); | |
279 | ||
280 | /* We don't increment reference counter for tr->roots list. The primary | |
281 | * reference is tr->devices */ | |
5bb395f4 KZ |
282 | |
283 | DBG(TREE, ul_debugobj(tr, "add root device 0x%p [%s]", dev, dev->name)); | |
284 | list_add_tail(&dev->ls_roots, &tr->roots); | |
285 | return 0; | |
286 | } | |
287 | ||
288 | int lsblk_devtree_next_root(struct lsblk_devtree *tr, | |
289 | struct lsblk_iter *itr, | |
290 | struct lsblk_device **dev) | |
291 | { | |
292 | int rc = 1; | |
293 | ||
294 | if (!tr || !itr || !dev) | |
295 | return -EINVAL; | |
296 | *dev = NULL; | |
297 | if (!itr->head) | |
298 | LSBLK_ITER_INIT(itr, &tr->roots); | |
299 | if (itr->p != itr->head) { | |
300 | LSBLK_ITER_ITERATE(itr, *dev, struct lsblk_device, ls_roots); | |
301 | rc = 0; | |
302 | } | |
303 | return rc; | |
304 | } | |
305 | ||
306 | int lsblk_devtree_add_device(struct lsblk_devtree *tr, struct lsblk_device *dev) | |
307 | { | |
308 | lsblk_ref_device(dev); | |
309 | ||
310 | DBG(TREE, ul_debugobj(tr, "add device 0x%p [%s]", dev, dev->name)); | |
311 | list_add_tail(&dev->ls_devices, &tr->devices); | |
312 | return 0; | |
313 | } | |
314 | ||
315 | int lsblk_devtree_next_device(struct lsblk_devtree *tr, | |
316 | struct lsblk_iter *itr, | |
317 | struct lsblk_device **dev) | |
318 | { | |
319 | int rc = 1; | |
320 | ||
321 | if (!tr || !itr || !dev) | |
322 | return -EINVAL; | |
323 | *dev = NULL; | |
324 | if (!itr->head) | |
325 | LSBLK_ITER_INIT(itr, &tr->devices); | |
326 | if (itr->p != itr->head) { | |
327 | LSBLK_ITER_ITERATE(itr, *dev, struct lsblk_device, ls_devices); | |
328 | rc = 0; | |
329 | } | |
330 | return rc; | |
331 | } | |
332 | ||
21d4a48c KZ |
333 | int lsblk_devtree_has_device(struct lsblk_devtree *tr, struct lsblk_device *dev) |
334 | { | |
335 | struct lsblk_device *x = NULL; | |
336 | struct lsblk_iter itr; | |
337 | ||
338 | lsblk_reset_iter(&itr, LSBLK_ITER_FORWARD); | |
339 | ||
340 | while (lsblk_devtree_next_device(tr, &itr, &x) == 0) { | |
341 | if (x == dev) | |
342 | return 1; | |
343 | } | |
344 | ||
345 | return 0; | |
346 | } | |
347 | ||
5bb395f4 KZ |
348 | struct lsblk_device *lsblk_devtree_get_device(struct lsblk_devtree *tr, const char *name) |
349 | { | |
350 | struct lsblk_device *dev = NULL; | |
351 | struct lsblk_iter itr; | |
352 | ||
353 | lsblk_reset_iter(&itr, LSBLK_ITER_FORWARD); | |
354 | ||
355 | while (lsblk_devtree_next_device(tr, &itr, &dev) == 0) { | |
356 | if (strcmp(name, dev->name) == 0) | |
357 | return dev; | |
358 | } | |
359 | ||
360 | return NULL; | |
361 | } | |
362 | ||
21d4a48c KZ |
363 | int lsblk_devtree_remove_device(struct lsblk_devtree *tr, struct lsblk_device *dev) |
364 | { | |
365 | DBG(TREE, ul_debugobj(tr, "remove device 0x%p [%s]", dev, dev->name)); | |
366 | ||
367 | if (!lsblk_devtree_has_device(tr, dev)) | |
368 | return 1; | |
369 | ||
370 | list_del_init(&dev->ls_roots); | |
371 | list_del_init(&dev->ls_devices); | |
372 | lsblk_unref_device(dev); | |
373 | ||
374 | return 0; | |
375 | } | |
5bb395f4 | 376 | |
dc4662f0 KZ |
377 | static int device_dedupkey_is_equal( |
378 | struct lsblk_device *dev, | |
379 | struct lsblk_device *pattern) | |
380 | { | |
381 | assert(pattern->dedupkey); | |
382 | ||
383 | if (!dev->dedupkey || dev == pattern) | |
384 | return 0; | |
385 | if (strcmp(dev->dedupkey, pattern->dedupkey) == 0) { | |
386 | if (!device_is_partition(dev) || | |
9e677de1 | 387 | !dev->wholedisk->dedupkey || |
dc4662f0 KZ |
388 | strcmp(dev->dedupkey, dev->wholedisk->dedupkey) != 0) { |
389 | DBG(DEV, ul_debugobj(dev, "%s: match deduplication pattern", dev->name)); | |
390 | return 1; | |
391 | } | |
392 | } | |
393 | return 0; | |
394 | } | |
395 | ||
396 | static void device_dedup_dependencies( | |
397 | struct lsblk_device *dev, | |
398 | struct lsblk_device *pattern) | |
399 | { | |
400 | struct lsblk_iter itr; | |
401 | struct lsblk_devdep *dp; | |
402 | ||
403 | lsblk_reset_iter(&itr, LSBLK_ITER_FORWARD); | |
404 | ||
0986f29a | 405 | while (device_next_child(dev, &itr, &dp) == 0) { |
dc4662f0 KZ |
406 | struct lsblk_device *child = dp->child; |
407 | ||
408 | if (device_dedupkey_is_equal(child, pattern)) { | |
409 | DBG(DEV, ul_debugobj(dev, "remove duplicate dependence: 0x%p [%s]", | |
410 | dp->child, dp->child->name)); | |
0986f29a | 411 | remove_dependence(dp); |
dc4662f0 KZ |
412 | } else |
413 | device_dedup_dependencies(child, pattern); | |
414 | } | |
415 | } | |
416 | ||
417 | static void devtree_dedup(struct lsblk_devtree *tr, struct lsblk_device *pattern) | |
418 | { | |
419 | struct lsblk_iter itr; | |
420 | struct lsblk_device *dev = NULL; | |
421 | ||
422 | lsblk_reset_iter(&itr, LSBLK_ITER_FORWARD); | |
423 | ||
424 | DBG(TREE, ul_debugobj(tr, "de-duplicate by key: %s", pattern->dedupkey)); | |
425 | ||
426 | while (lsblk_devtree_next_root(tr, &itr, &dev) == 0) { | |
427 | if (device_dedupkey_is_equal(dev, pattern)) { | |
428 | DBG(TREE, ul_debugobj(tr, "remove duplicate device: 0x%p [%s]", | |
429 | dev, dev->name)); | |
430 | /* Note that root list does not use ref-counting; the | |
431 | * primary reference is ls_devices */ | |
432 | list_del_init(&dev->ls_roots); | |
433 | } else | |
434 | device_dedup_dependencies(dev, pattern); | |
435 | } | |
436 | } | |
437 | ||
438 | static int cmp_devices_devno(struct list_head *a, struct list_head *b, | |
439 | __attribute__((__unused__)) void *data) | |
440 | { | |
441 | struct lsblk_device *ax = list_entry(a, struct lsblk_device, ls_devices), | |
442 | *bx = list_entry(b, struct lsblk_device, ls_devices); | |
443 | ||
444 | return cmp_numbers(makedev(ax->maj, ax->min), | |
445 | makedev(bx->maj, bx->min)); | |
446 | } | |
447 | ||
448 | /* Note that dev->dedupkey has to be already set */ | |
449 | int lsblk_devtree_deduplicate_devices(struct lsblk_devtree *tr) | |
450 | { | |
451 | struct lsblk_device *pattern = NULL; | |
452 | struct lsblk_iter itr; | |
453 | char *last = NULL; | |
454 | ||
455 | list_sort(&tr->devices, cmp_devices_devno, NULL); | |
456 | lsblk_reset_iter(&itr, LSBLK_ITER_FORWARD); | |
457 | ||
458 | while (lsblk_devtree_next_device(tr, &itr, &pattern) == 0) { | |
459 | if (!pattern->dedupkey) | |
460 | continue; | |
461 | if (device_is_partition(pattern) && | |
9e677de1 | 462 | pattern->wholedisk->dedupkey && |
dc4662f0 KZ |
463 | strcmp(pattern->dedupkey, pattern->wholedisk->dedupkey) == 0) |
464 | continue; | |
465 | if (last && strcmp(pattern->dedupkey, last) == 0) | |
466 | continue; | |
467 | ||
468 | devtree_dedup(tr, pattern); | |
469 | last = pattern->dedupkey; | |
470 | } | |
471 | return 0; | |
472 | } |