blob: 1add09fca2a2e5b05529ed7ee85eccd3ca4daa63 [file] [log] [blame]
bigbiff7b4c7a62015-01-01 19:44:14 -05001
2#include "fdiskP.h"
3
4/**
5 * SECTION: table
6 * @title: Table
7 * @short_description: container for fdisk partitions
8 *
9 * The fdisk_table is simple container for fdisk_partitions. The table is no
10 * directly connected to label data (partition table), and table changes don't
11 * affect in-memory or on-disk data.
12 */
13
14/**
15 * fdisk_new_table:
16 *
17 * The table is a container for struct fdisk_partition entries. The container
18 * does not have any real connection with label (partition table) and with
19 * real on-disk data.
20 *
21 * Returns: newly allocated table struct.
22 */
23struct fdisk_table *fdisk_new_table(void)
24{
25 struct fdisk_table *tb = NULL;
26
27 tb = calloc(1, sizeof(*tb));
28 if (!tb)
29 return NULL;
30
31 DBG(TAB, ul_debugobj(tb, "alloc"));
32 tb->refcount = 1;
33 INIT_LIST_HEAD(&tb->parts);
34 return tb;
35}
36
37/**
38 * fdisk_reset_table:
39 * @tb: tab pointer
40 *
41 * Removes all entries (partitions) from the table. The parititons with zero
42 * reference count will be deallocated. This function does not modify partition
43 * table.
44 *
45 * Returns: 0 on success or negative number in case of error.
46 */
47int fdisk_reset_table(struct fdisk_table *tb)
48{
49 if (!tb)
50 return -EINVAL;
51
52 DBG(TAB, ul_debugobj(tb, "reset"));
53
54 while (!list_empty(&tb->parts)) {
55 struct fdisk_partition *pa = list_entry(tb->parts.next,
56 struct fdisk_partition, parts);
57 fdisk_table_remove_partition(tb, pa);
58 }
59
60 tb->nents = 0;
61 return 0;
62}
63
64/**
65 * fdisk_ref_table:
66 * @tb: table pointer
67 *
68 * Incremparts reference counter.
69 */
70void fdisk_ref_table(struct fdisk_table *tb)
71{
72 if (tb)
73 tb->refcount++;
74}
75
76/**
77 * fdisk_unref_table:
78 * @tb: table pointer
79 *
80 * De-incremparts reference counter, on zero the @tb is automatically
81 * deallocated.
82 */
83void fdisk_unref_table(struct fdisk_table *tb)
84{
85 if (!tb)
86 return;
87
88 tb->refcount--;
89 if (tb->refcount <= 0) {
90 fdisk_reset_table(tb);
91
92 DBG(TAB, ul_debugobj(tb, "free"));
93 free(tb);
94 }
95}
96
97/**
98 * fdisk_table_is_empty:
99 * @tb: pointer to tab
100 *
101 * Returns: 1 if the table is without filesystems, or 0.
102 */
103int fdisk_table_is_empty(struct fdisk_table *tb)
104{
105 return tb == NULL || list_empty(&tb->parts) ? 1 : 0;
106}
107
108/**
109 * fdisk_table_get_nents:
110 * @tb: pointer to tab
111 *
112 * Returns: number of entries in table.
113 */
114size_t fdisk_table_get_nents(struct fdisk_table *tb)
115{
116 return tb ? tb->nents : 0;
117}
118
119/**
120 * fdisk_table_next_partition:
121 * @tb: tab pointer
122 * @itr: iterator
123 * @pa: returns the next tab entry
124 *
125 * Returns: 0 on success, negative number in case of error or 1 at the end of list.
126 *
127 * Example:
128 * <informalexample>
129 * <programlisting>
130 * while(fdisk_table_next_partition(tb, itr, &pa) == 0) {
131 * ...
132 * }
133 * </programlisting>
134 * </informalexample>
135 */
136int fdisk_table_next_partition(
137 struct fdisk_table *tb,
138 struct fdisk_iter *itr,
139 struct fdisk_partition **pa)
140{
141 int rc = 1;
142
143 assert(tb);
144 assert(itr);
145 assert(pa);
146
147 if (!tb || !itr || !pa)
148 return -EINVAL;
149 *pa = NULL;
150
151 if (!itr->head)
152 FDISK_ITER_INIT(itr, &tb->parts);
153 if (itr->p != itr->head) {
154 FDISK_ITER_ITERATE(itr, *pa, struct fdisk_partition, parts);
155 rc = 0;
156 }
157
158 return rc;
159}
160
161struct fdisk_partition *fdisk_table_get_partition(
162 struct fdisk_table *tb,
163 size_t n)
164{
165 struct fdisk_partition *pa = NULL;
166 struct fdisk_iter itr;
167
168 if (!tb)
169 return NULL;
170
171 fdisk_reset_iter(&itr, FDISK_ITER_FORWARD);
172
173 while (fdisk_table_next_partition(tb, &itr, &pa) == 0) {
174 if (n == 0)
175 return pa;
176 n--;
177 }
178
179 return NULL;
180}
181
182/**
183 * fdisk_table_add_partition
184 * @tb: tab pointer
185 * @pa: new entry
186 *
187 * Adds a new entry to table and increment @pa reference counter. Don't forget to
188 * use fdisk_unref_pa() after fdisk_table_add_partition() if you want to keep
189 * the @pa referenced by the table only.
190 *
191 * Returns: 0 on success or negative number in case of error.
192 */
193int fdisk_table_add_partition(struct fdisk_table *tb, struct fdisk_partition *pa)
194{
195 assert(tb);
196 assert(pa);
197
198 if (!tb || !pa)
199 return -EINVAL;
200
201 fdisk_ref_partition(pa);
202 list_add_tail(&pa->parts, &tb->parts);
203 tb->nents++;
204
205 DBG(TAB, ul_debugobj(tb, "add entry %p [start=%ju, end=%ju, size=%ju, %s %s %s]",
206 pa,
207 (uintmax_t) fdisk_partition_get_start(pa),
208 (uintmax_t) fdisk_partition_get_end(pa),
209 (uintmax_t) fdisk_partition_get_size(pa),
210 fdisk_partition_is_freespace(pa) ? "freespace" : "",
211 fdisk_partition_is_nested(pa) ? "nested" : "",
212 fdisk_partition_is_container(pa) ? "container" : "primary"));
213 return 0;
214}
215
216/* inserts @pa after @poz */
217static int table_insert_partition(
218 struct fdisk_table *tb,
219 struct fdisk_partition *poz,
220 struct fdisk_partition *pa)
221{
222 assert(tb);
223 assert(pa);
224
225 fdisk_ref_partition(pa);
226 if (poz)
227 list_add(&pa->parts, &poz->parts);
228 else
229 list_add(&pa->parts, &tb->parts);
230 tb->nents++;
231
232 DBG(TAB, ul_debugobj(tb, "insert entry %p pre=%p [start=%ju, end=%ju, size=%ju, %s %s %s]",
233 pa, poz ? poz : NULL,
234 (uintmax_t) fdisk_partition_get_start(pa),
235 (uintmax_t) fdisk_partition_get_end(pa),
236 (uintmax_t) fdisk_partition_get_size(pa),
237 fdisk_partition_is_freespace(pa) ? "freespace" : "",
238 fdisk_partition_is_nested(pa) ? "nested" : "",
239 fdisk_partition_is_container(pa) ? "container" : ""));
240 return 0;
241}
242
243/**
244 * fdisk_table_remove_partition
245 * @tb: tab pointer
246 * @pa: new entry
247 *
248 * Removes the @pa from the table and de-increment reference counter of the @pa. The
249 * partition with zero reference counter will be deallocated. Don't forget to use
250 * fdisk_ref_partition() before call fdisk_table_remove_partition() if you want
251 * to use @pa later.
252 *
253 * Returns: 0 on success or negative number in case of error.
254 */
255int fdisk_table_remove_partition(struct fdisk_table *tb, struct fdisk_partition *pa)
256{
257 assert(tb);
258 assert(pa);
259
260 if (!tb || !pa)
261 return -EINVAL;
262
263 DBG(TAB, ul_debugobj(tb, "remove entry %p", pa));
264 list_del(&pa->parts);
265 INIT_LIST_HEAD(&pa->parts);
266
267 fdisk_unref_partition(pa);
268 tb->nents--;
269
270 return 0;
271}
272
273/**
274 * fdisk_get_partitions
275 * @cxt: fdisk context
276 * @tb: returns table
277 *
278 * This function adds partitions from disklabel to @table, it allocates a new
279 * table if if @table points to NULL.
280 *
281 * Returns: 0 on success, otherwise, a corresponding error.
282 */
283int fdisk_get_partitions(struct fdisk_context *cxt, struct fdisk_table **tb)
284{
285 size_t i;
286
287 if (!cxt || !cxt->label || !tb)
288 return -EINVAL;
289 if (!cxt->label->op->get_part)
290 return -ENOSYS;
291
292 DBG(CXT, ul_debugobj(cxt, "get table"));
293
294 if (!*tb && !(*tb = fdisk_new_table()))
295 return -ENOMEM;
296
297 for (i = 0; i < cxt->label->nparts_max; i++) {
298 struct fdisk_partition *pa = NULL;
299
300 if (fdisk_get_partition(cxt, i, &pa) != 0)
301 continue;
302 if (fdisk_partition_is_used(pa))
303 fdisk_table_add_partition(*tb, pa);
304 fdisk_unref_partition(pa);
305 }
306
307 return 0;
308}
309
310static void debug_print_table(struct fdisk_table *tb)
311{
312 struct fdisk_iter itr;
313 struct fdisk_partition *pa;
314
315 fdisk_reset_iter(&itr, FDISK_ITER_FORWARD);
316 while (fdisk_table_next_partition(tb, &itr, &pa) == 0)
317 ul_debugobj(tb, "partition %p [partno=%zu, start=%ju, end=%ju, size=%ju] ",
318 pa, pa->partno,
319 (uintmax_t) fdisk_partition_get_start(pa),
320 (uintmax_t) fdisk_partition_get_end(pa),
321 (uintmax_t) fdisk_partition_get_size(pa));
322
323}
324
325
326typedef int (*fdisk_partcmp_t)(struct fdisk_partition *, struct fdisk_partition *);
327
328static int cmp_parts_wrapper(struct list_head *a, struct list_head *b, void *data)
329{
330 struct fdisk_partition *pa = list_entry(a, struct fdisk_partition, parts),
331 *pb = list_entry(b, struct fdisk_partition, parts);
332
333 fdisk_partcmp_t cmp = (fdisk_partcmp_t) data;
334
335 return cmp(pa, pb);
336}
337
338
339/**
340 * fdisk_table_sort_partitions:
341 * @tb: table
342 * @cmp: compare function
343 *
344 * Sort partition in the table.
345 *
346 * Returns: 0 on success, <0 on error.
347 */
348int fdisk_table_sort_partitions(struct fdisk_table *tb,
349 int (*cmp)(struct fdisk_partition *,
350 struct fdisk_partition *))
351{
352 if (!tb)
353 return -EINVAL;
354
355 DBG(TAB, ul_debugobj(tb, "Before sort:"));
356 ON_DBG(TAB, debug_print_table(tb));
357
358 list_sort(&tb->parts, cmp_parts_wrapper, (void *) cmp);
359
360 DBG(TAB, ul_debugobj(tb, "After sort:"));
361 ON_DBG(TAB, debug_print_table(tb));
362
363 return 0;
364}
365
366/* allocates a new freespace description */
367static int new_freespace(struct fdisk_context *cxt,
368 fdisk_sector_t start,
369 fdisk_sector_t end,
370 struct fdisk_partition *parent,
371 struct fdisk_partition **pa)
372{
373 assert(cxt);
374 assert(pa);
375
376 *pa = NULL;
377
378 if (start == end)
379 return 0;
380 *pa = fdisk_new_partition();
381 if (!*pa)
382 return -ENOMEM;
383
384 assert(start);
385 assert(end);
386 assert(end > start);
387
388 (*pa)->freespace = 1;
389 (*pa)->start = fdisk_align_lba_in_range(cxt, start, start, end);
390 (*pa)->size = end - (*pa)->start + 1ULL;
391
392 if (parent)
393 (*pa)->parent_partno = parent->partno;
394 return 0;
395}
396
397/* add freespace description to the right place within @tb */
398static int table_add_freespace(
399 struct fdisk_context *cxt,
400 struct fdisk_table *tb,
401 fdisk_sector_t start,
402 fdisk_sector_t end,
403 struct fdisk_partition *parent)
404{
405 struct fdisk_partition *pa, *x, *real_parent = NULL, *best = NULL;
406 struct fdisk_iter itr;
407 int rc = 0;
408
409 assert(tb);
410
411 rc = new_freespace(cxt, start, end, parent, &pa);
412 if (rc)
413 return -ENOMEM;
414 if (!pa)
415 return 0;
416
417 assert(fdisk_partition_has_start(pa));
418 assert(fdisk_partition_has_end(pa));
419
420 DBG(TAB, ul_debugobj(tb, "adding freespace"));
421
422 fdisk_reset_iter(&itr, FDISK_ITER_FORWARD);
423 if (parent && fdisk_partition_has_partno(parent)) {
424 while (fdisk_table_next_partition(tb, &itr, &x) == 0) {
425 if (!fdisk_partition_has_partno(x))
426 continue;
427 if (x->partno == parent->partno) {
428 real_parent = x;
429 break;
430 }
431 }
432 if (!real_parent) {
433 DBG(TAB, ul_debugobj(tb, "not found freespace parent (partno=%zu)",
434 parent->partno));
435 fdisk_reset_iter(&itr, FDISK_ITER_FORWARD);
436 }
437 }
438
439 while (fdisk_table_next_partition(tb, &itr, &x) == 0) {
440 fdisk_sector_t end, best_end = 0;
441
442 if (!fdisk_partition_has_end(x))
443 continue;
444
445 end = fdisk_partition_get_end(x);
446 if (best)
447 best_end = fdisk_partition_get_end(best);
448
449 if (end < pa->start && (!best || best_end < end))
450 best = x;
451 }
452
453 if (!best && real_parent)
454 best = real_parent;
455 rc = table_insert_partition(tb, best, pa);
456
457 fdisk_unref_partition(pa);
458
459 DBG(TAB, ul_debugobj(tb, "adding freespace DONE [rc=%d]", rc));
460 return rc;
461}
462
463/* analyze @cont(ainer) in @parts and add all detected freespace into @tb, note
464 * that @parts has to be sorted by partition starts */
465static int check_container_freespace(struct fdisk_context *cxt,
466 struct fdisk_table *parts,
467 struct fdisk_table *tb,
468 struct fdisk_partition *cont)
469{
470 struct fdisk_iter itr;
471 struct fdisk_partition *pa;
472 fdisk_sector_t x, last, grain, lastplusoff;
473 int rc = 0;
474
475 assert(cxt);
476 assert(parts);
477 assert(tb);
478 assert(cont);
479 assert(fdisk_partition_has_start(cont));
480
481 DBG(TAB, ul_debugobj(tb, "analyze container 0x%p", cont));
482
483 last = fdisk_partition_get_start(cont);
484 grain = cxt->grain > cxt->sector_size ? cxt->grain / cxt->sector_size : 1;
485 fdisk_reset_iter(&itr, FDISK_ITER_FORWARD);
486
487 DBG(CXT, ul_debugobj(cxt, "initialized: last=%ju, grain=%ju", last, grain));
488
489 while (fdisk_table_next_partition(parts, &itr, &pa) == 0) {
490
491 DBG(CXT, ul_debugobj(cxt, "partno=%zu, start=%ju", pa->partno, pa->start));
492
493 if (!pa->used || !fdisk_partition_is_nested(pa)
494 || !fdisk_partition_has_start(pa))
495 continue;
496
497 DBG(CXT, ul_debugobj(cxt, "freespace container analyze: partno=%zu, start=%ju, end=%ju",
498 pa->partno,
499 (uintmax_t) fdisk_partition_get_start(pa),
500 (uintmax_t) fdisk_partition_get_end(pa)));
501
502 lastplusoff = last + cxt->first_lba;
503 if (pa->start > lastplusoff && pa->start - lastplusoff > grain)
504 rc = table_add_freespace(cxt, tb, lastplusoff, pa->start, cont);
505 if (rc)
506 goto done;
507 last = fdisk_partition_get_end(pa);
508 }
509
510 /* free-space remaining in extended partition */
511 x = fdisk_partition_get_start(cont) + fdisk_partition_get_size(cont) - 1;
512 lastplusoff = last + cxt->first_lba;
513 if (lastplusoff < x && x - lastplusoff > grain) {
514 DBG(TAB, ul_debugobj(tb, "add remaining space in container 0x%p", cont));
515 rc = table_add_freespace(cxt, tb, lastplusoff, x, cont);
516 }
517
518done:
519 DBG(TAB, ul_debugobj(tb, "analyze container 0x%p DONE [rc=%d]", cont, rc));
520 return rc;
521}
522
523
524/**
525 * fdisk_get_freespaces
526 * @cxt: fdisk context
527 * @tb: returns table
528 *
529 * This function adds freespace (described by fdisk_partition) to @table, it
530 * allocates a new table if the @table points to NULL.
531 *
532 * Note that free space smaller than grain (see fdisk_get_grain()) is ignored.
533
534 * Returns: 0 on success, otherwise, a corresponding error.
535 */
536int fdisk_get_freespaces(struct fdisk_context *cxt, struct fdisk_table **tb)
537{
538 int rc = 0;
539 fdisk_sector_t last, grain;
540 struct fdisk_table *parts = NULL;
541 struct fdisk_partition *pa;
542 struct fdisk_iter itr;
543
544 DBG(CXT, ul_debugobj(cxt, "get freespace"));
545
546 if (!cxt || !cxt->label || !tb)
547 return -EINVAL;
548 if (!*tb && !(*tb = fdisk_new_table()))
549 return -ENOMEM;
550
551 rc = fdisk_get_partitions(cxt, &parts);
552 if (rc)
553 goto done;
554
555 fdisk_table_sort_partitions(parts, fdisk_partition_cmp_start);
556 fdisk_reset_iter(&itr, FDISK_ITER_FORWARD);
557 last = cxt->first_lba;
558 grain = cxt->grain > cxt->sector_size ? cxt->grain / cxt->sector_size : 1;
559
560 DBG(CXT, ul_debugobj(cxt, "initialized: last=%ju, grain=%ju", last, grain));
561
562 /* analyze gaps between partitions */
563 while (rc == 0 && fdisk_table_next_partition(parts, &itr, &pa) == 0) {
564
565 DBG(CXT, ul_debugobj(cxt, "partno=%zu, start=%ju", pa->partno, pa->start));
566
567 if (!pa->used || pa->wholedisk || fdisk_partition_is_nested(pa)
568 || !fdisk_partition_has_start(pa))
569 continue;
570 DBG(CXT, ul_debugobj(cxt, "freespace analyze: partno=%zu, start=%ju, end=%ju",
571 pa->partno,
572 (uintmax_t) fdisk_partition_get_start(pa),
573 (uintmax_t) fdisk_partition_get_end(pa)));
574 if (last + grain <= pa->start) {
575 rc = table_add_freespace(cxt, *tb,
576 last + (last > cxt->first_lba ? 1 : 0),
577 pa->start - 1, NULL);
578 }
579 /* add gaps between logical partitions */
580 if (fdisk_partition_is_container(pa))
581 rc = check_container_freespace(cxt, parts, *tb, pa);
582 last = fdisk_partition_get_end(pa);
583 }
584
585 /* add free-space behind last partition to the end of the table (so
586 * don't use table_add_freespace()) */
587 if (rc == 0 && last + grain < cxt->total_sectors - 1) {
588 DBG(CXT, ul_debugobj(cxt, "freespace behind last partition detected"));
589 rc = new_freespace(cxt,
590 last + (last > cxt->first_lba ? 1 : 0),
591 cxt->last_lba, NULL, &pa);
592 if (pa) {
593 fdisk_table_add_partition(*tb, pa);
594 fdisk_unref_partition(pa);
595 }
596 }
597
598done:
599 fdisk_unref_table(parts);
600
601 DBG(CXT, ul_debugobj(cxt, "get freespace DONE [rc=%d]", rc));
602 return rc;
603}
604
605/**
606 * fdisk_table_wrong_order:
607 * @tb: table
608 *
609 * Returns: 1 of the table is not in disk order
610 */
611int fdisk_table_wrong_order(struct fdisk_table *tb)
612{
613 struct fdisk_partition *pa;
614 struct fdisk_iter itr;
615 fdisk_sector_t last = 0;
616
617 DBG(TAB, ul_debugobj(tb, "wrong older check"));
618
619 fdisk_reset_iter(&itr, FDISK_ITER_FORWARD);
620 while (tb && fdisk_table_next_partition(tb, &itr, &pa) == 0) {
621 if (!fdisk_partition_has_start(pa))
622 continue;
623 if (pa->start < last)
624 return 1;
625 last = pa->start;
626 }
627 return 0;
628}
629
630/**
631 * fdisk_apply_table:
632 * @cxt: context
633 * @tb: table
634 *
635 * Add partitions from table @tb to the in-memory disk label. See
636 * fdisk_add_partition(), fdisk_delete_all_partitions(). The partitons
637 * that does not define start (or does not follow the default start)
638 * are ingored.
639 *
640 * Returns: 0 on success, <0 on error.
641 */
642int fdisk_apply_table(struct fdisk_context *cxt, struct fdisk_table *tb)
643{
644 struct fdisk_partition *pa;
645 struct fdisk_iter itr;
646 int rc = 0;
647
648 assert(cxt);
649 assert(tb);
650
651 DBG(TAB, ul_debugobj(tb, "applying to context %p", cxt));
652
653 fdisk_reset_iter(&itr, FDISK_ITER_FORWARD);
654 while (tb && fdisk_table_next_partition(tb, &itr, &pa) == 0) {
655 if (!fdisk_partition_has_start(pa) && !pa->start_follow_default)
656 continue;
657 rc = fdisk_add_partition(cxt, pa, NULL);
658 if (rc)
659 break;
660 }
661
662 return rc;
663}
664