| |
| #include "fdiskP.h" |
| |
| /** |
| * SECTION: table |
| * @title: Table |
| * @short_description: container for fdisk partitions |
| * |
| * The fdisk_table is simple container for fdisk_partitions. The table is no |
| * directly connected to label data (partition table), and table changes don't |
| * affect in-memory or on-disk data. |
| */ |
| |
| /** |
| * fdisk_new_table: |
| * |
| * The table is a container for struct fdisk_partition entries. The container |
| * does not have any real connection with label (partition table) and with |
| * real on-disk data. |
| * |
| * Returns: newly allocated table struct. |
| */ |
| struct fdisk_table *fdisk_new_table(void) |
| { |
| struct fdisk_table *tb = NULL; |
| |
| tb = calloc(1, sizeof(*tb)); |
| if (!tb) |
| return NULL; |
| |
| DBG(TAB, ul_debugobj(tb, "alloc")); |
| tb->refcount = 1; |
| INIT_LIST_HEAD(&tb->parts); |
| return tb; |
| } |
| |
| /** |
| * fdisk_reset_table: |
| * @tb: tab pointer |
| * |
| * Removes all entries (partitions) from the table. The parititons with zero |
| * reference count will be deallocated. This function does not modify partition |
| * table. |
| * |
| * Returns: 0 on success or negative number in case of error. |
| */ |
| int fdisk_reset_table(struct fdisk_table *tb) |
| { |
| if (!tb) |
| return -EINVAL; |
| |
| DBG(TAB, ul_debugobj(tb, "reset")); |
| |
| while (!list_empty(&tb->parts)) { |
| struct fdisk_partition *pa = list_entry(tb->parts.next, |
| struct fdisk_partition, parts); |
| fdisk_table_remove_partition(tb, pa); |
| } |
| |
| tb->nents = 0; |
| return 0; |
| } |
| |
| /** |
| * fdisk_ref_table: |
| * @tb: table pointer |
| * |
| * Incremparts reference counter. |
| */ |
| void fdisk_ref_table(struct fdisk_table *tb) |
| { |
| if (tb) |
| tb->refcount++; |
| } |
| |
| /** |
| * fdisk_unref_table: |
| * @tb: table pointer |
| * |
| * De-incremparts reference counter, on zero the @tb is automatically |
| * deallocated. |
| */ |
| void fdisk_unref_table(struct fdisk_table *tb) |
| { |
| if (!tb) |
| return; |
| |
| tb->refcount--; |
| if (tb->refcount <= 0) { |
| fdisk_reset_table(tb); |
| |
| DBG(TAB, ul_debugobj(tb, "free")); |
| free(tb); |
| } |
| } |
| |
| /** |
| * fdisk_table_is_empty: |
| * @tb: pointer to tab |
| * |
| * Returns: 1 if the table is without filesystems, or 0. |
| */ |
| int fdisk_table_is_empty(struct fdisk_table *tb) |
| { |
| return tb == NULL || list_empty(&tb->parts) ? 1 : 0; |
| } |
| |
| /** |
| * fdisk_table_get_nents: |
| * @tb: pointer to tab |
| * |
| * Returns: number of entries in table. |
| */ |
| size_t fdisk_table_get_nents(struct fdisk_table *tb) |
| { |
| return tb ? tb->nents : 0; |
| } |
| |
| /** |
| * fdisk_table_next_partition: |
| * @tb: tab pointer |
| * @itr: iterator |
| * @pa: returns the next tab entry |
| * |
| * Returns: 0 on success, negative number in case of error or 1 at the end of list. |
| * |
| * Example: |
| * <informalexample> |
| * <programlisting> |
| * while(fdisk_table_next_partition(tb, itr, &pa) == 0) { |
| * ... |
| * } |
| * </programlisting> |
| * </informalexample> |
| */ |
| int fdisk_table_next_partition( |
| struct fdisk_table *tb, |
| struct fdisk_iter *itr, |
| struct fdisk_partition **pa) |
| { |
| int rc = 1; |
| |
| assert(tb); |
| assert(itr); |
| assert(pa); |
| |
| if (!tb || !itr || !pa) |
| return -EINVAL; |
| *pa = NULL; |
| |
| if (!itr->head) |
| FDISK_ITER_INIT(itr, &tb->parts); |
| if (itr->p != itr->head) { |
| FDISK_ITER_ITERATE(itr, *pa, struct fdisk_partition, parts); |
| rc = 0; |
| } |
| |
| return rc; |
| } |
| |
| struct fdisk_partition *fdisk_table_get_partition( |
| struct fdisk_table *tb, |
| size_t n) |
| { |
| struct fdisk_partition *pa = NULL; |
| struct fdisk_iter itr; |
| |
| if (!tb) |
| return NULL; |
| |
| fdisk_reset_iter(&itr, FDISK_ITER_FORWARD); |
| |
| while (fdisk_table_next_partition(tb, &itr, &pa) == 0) { |
| if (n == 0) |
| return pa; |
| n--; |
| } |
| |
| return NULL; |
| } |
| |
| /** |
| * fdisk_table_add_partition |
| * @tb: tab pointer |
| * @pa: new entry |
| * |
| * Adds a new entry to table and increment @pa reference counter. Don't forget to |
| * use fdisk_unref_pa() after fdisk_table_add_partition() if you want to keep |
| * the @pa referenced by the table only. |
| * |
| * Returns: 0 on success or negative number in case of error. |
| */ |
| int fdisk_table_add_partition(struct fdisk_table *tb, struct fdisk_partition *pa) |
| { |
| assert(tb); |
| assert(pa); |
| |
| if (!tb || !pa) |
| return -EINVAL; |
| |
| fdisk_ref_partition(pa); |
| list_add_tail(&pa->parts, &tb->parts); |
| tb->nents++; |
| |
| DBG(TAB, ul_debugobj(tb, "add entry %p [start=%ju, end=%ju, size=%ju, %s %s %s]", |
| pa, |
| (uintmax_t) fdisk_partition_get_start(pa), |
| (uintmax_t) fdisk_partition_get_end(pa), |
| (uintmax_t) fdisk_partition_get_size(pa), |
| fdisk_partition_is_freespace(pa) ? "freespace" : "", |
| fdisk_partition_is_nested(pa) ? "nested" : "", |
| fdisk_partition_is_container(pa) ? "container" : "primary")); |
| return 0; |
| } |
| |
| /* inserts @pa after @poz */ |
| static int table_insert_partition( |
| struct fdisk_table *tb, |
| struct fdisk_partition *poz, |
| struct fdisk_partition *pa) |
| { |
| assert(tb); |
| assert(pa); |
| |
| fdisk_ref_partition(pa); |
| if (poz) |
| list_add(&pa->parts, &poz->parts); |
| else |
| list_add(&pa->parts, &tb->parts); |
| tb->nents++; |
| |
| DBG(TAB, ul_debugobj(tb, "insert entry %p pre=%p [start=%ju, end=%ju, size=%ju, %s %s %s]", |
| pa, poz ? poz : NULL, |
| (uintmax_t) fdisk_partition_get_start(pa), |
| (uintmax_t) fdisk_partition_get_end(pa), |
| (uintmax_t) fdisk_partition_get_size(pa), |
| fdisk_partition_is_freespace(pa) ? "freespace" : "", |
| fdisk_partition_is_nested(pa) ? "nested" : "", |
| fdisk_partition_is_container(pa) ? "container" : "")); |
| return 0; |
| } |
| |
| /** |
| * fdisk_table_remove_partition |
| * @tb: tab pointer |
| * @pa: new entry |
| * |
| * Removes the @pa from the table and de-increment reference counter of the @pa. The |
| * partition with zero reference counter will be deallocated. Don't forget to use |
| * fdisk_ref_partition() before call fdisk_table_remove_partition() if you want |
| * to use @pa later. |
| * |
| * Returns: 0 on success or negative number in case of error. |
| */ |
| int fdisk_table_remove_partition(struct fdisk_table *tb, struct fdisk_partition *pa) |
| { |
| assert(tb); |
| assert(pa); |
| |
| if (!tb || !pa) |
| return -EINVAL; |
| |
| DBG(TAB, ul_debugobj(tb, "remove entry %p", pa)); |
| list_del(&pa->parts); |
| INIT_LIST_HEAD(&pa->parts); |
| |
| fdisk_unref_partition(pa); |
| tb->nents--; |
| |
| return 0; |
| } |
| |
| /** |
| * fdisk_get_partitions |
| * @cxt: fdisk context |
| * @tb: returns table |
| * |
| * This function adds partitions from disklabel to @table, it allocates a new |
| * table if if @table points to NULL. |
| * |
| * Returns: 0 on success, otherwise, a corresponding error. |
| */ |
| int fdisk_get_partitions(struct fdisk_context *cxt, struct fdisk_table **tb) |
| { |
| size_t i; |
| |
| if (!cxt || !cxt->label || !tb) |
| return -EINVAL; |
| if (!cxt->label->op->get_part) |
| return -ENOSYS; |
| |
| DBG(CXT, ul_debugobj(cxt, "get table")); |
| |
| if (!*tb && !(*tb = fdisk_new_table())) |
| return -ENOMEM; |
| |
| for (i = 0; i < cxt->label->nparts_max; i++) { |
| struct fdisk_partition *pa = NULL; |
| |
| if (fdisk_get_partition(cxt, i, &pa) != 0) |
| continue; |
| if (fdisk_partition_is_used(pa)) |
| fdisk_table_add_partition(*tb, pa); |
| fdisk_unref_partition(pa); |
| } |
| |
| return 0; |
| } |
| |
| static void debug_print_table(struct fdisk_table *tb) |
| { |
| struct fdisk_iter itr; |
| struct fdisk_partition *pa; |
| |
| fdisk_reset_iter(&itr, FDISK_ITER_FORWARD); |
| while (fdisk_table_next_partition(tb, &itr, &pa) == 0) |
| ul_debugobj(tb, "partition %p [partno=%zu, start=%ju, end=%ju, size=%ju] ", |
| pa, pa->partno, |
| (uintmax_t) fdisk_partition_get_start(pa), |
| (uintmax_t) fdisk_partition_get_end(pa), |
| (uintmax_t) fdisk_partition_get_size(pa)); |
| |
| } |
| |
| |
| typedef int (*fdisk_partcmp_t)(struct fdisk_partition *, struct fdisk_partition *); |
| |
| static int cmp_parts_wrapper(struct list_head *a, struct list_head *b, void *data) |
| { |
| struct fdisk_partition *pa = list_entry(a, struct fdisk_partition, parts), |
| *pb = list_entry(b, struct fdisk_partition, parts); |
| |
| fdisk_partcmp_t cmp = (fdisk_partcmp_t) data; |
| |
| return cmp(pa, pb); |
| } |
| |
| |
| /** |
| * fdisk_table_sort_partitions: |
| * @tb: table |
| * @cmp: compare function |
| * |
| * Sort partition in the table. |
| * |
| * Returns: 0 on success, <0 on error. |
| */ |
| int fdisk_table_sort_partitions(struct fdisk_table *tb, |
| int (*cmp)(struct fdisk_partition *, |
| struct fdisk_partition *)) |
| { |
| if (!tb) |
| return -EINVAL; |
| |
| DBG(TAB, ul_debugobj(tb, "Before sort:")); |
| ON_DBG(TAB, debug_print_table(tb)); |
| |
| list_sort(&tb->parts, cmp_parts_wrapper, (void *) cmp); |
| |
| DBG(TAB, ul_debugobj(tb, "After sort:")); |
| ON_DBG(TAB, debug_print_table(tb)); |
| |
| return 0; |
| } |
| |
| /* allocates a new freespace description */ |
| static int new_freespace(struct fdisk_context *cxt, |
| fdisk_sector_t start, |
| fdisk_sector_t end, |
| struct fdisk_partition *parent, |
| struct fdisk_partition **pa) |
| { |
| assert(cxt); |
| assert(pa); |
| |
| *pa = NULL; |
| |
| if (start == end) |
| return 0; |
| *pa = fdisk_new_partition(); |
| if (!*pa) |
| return -ENOMEM; |
| |
| assert(start); |
| assert(end); |
| assert(end > start); |
| |
| (*pa)->freespace = 1; |
| (*pa)->start = fdisk_align_lba_in_range(cxt, start, start, end); |
| (*pa)->size = end - (*pa)->start + 1ULL; |
| |
| if (parent) |
| (*pa)->parent_partno = parent->partno; |
| return 0; |
| } |
| |
| /* add freespace description to the right place within @tb */ |
| static int table_add_freespace( |
| struct fdisk_context *cxt, |
| struct fdisk_table *tb, |
| fdisk_sector_t start, |
| fdisk_sector_t end, |
| struct fdisk_partition *parent) |
| { |
| struct fdisk_partition *pa, *x, *real_parent = NULL, *best = NULL; |
| struct fdisk_iter itr; |
| int rc = 0; |
| |
| assert(tb); |
| |
| rc = new_freespace(cxt, start, end, parent, &pa); |
| if (rc) |
| return -ENOMEM; |
| if (!pa) |
| return 0; |
| |
| assert(fdisk_partition_has_start(pa)); |
| assert(fdisk_partition_has_end(pa)); |
| |
| DBG(TAB, ul_debugobj(tb, "adding freespace")); |
| |
| fdisk_reset_iter(&itr, FDISK_ITER_FORWARD); |
| if (parent && fdisk_partition_has_partno(parent)) { |
| while (fdisk_table_next_partition(tb, &itr, &x) == 0) { |
| if (!fdisk_partition_has_partno(x)) |
| continue; |
| if (x->partno == parent->partno) { |
| real_parent = x; |
| break; |
| } |
| } |
| if (!real_parent) { |
| DBG(TAB, ul_debugobj(tb, "not found freespace parent (partno=%zu)", |
| parent->partno)); |
| fdisk_reset_iter(&itr, FDISK_ITER_FORWARD); |
| } |
| } |
| |
| while (fdisk_table_next_partition(tb, &itr, &x) == 0) { |
| fdisk_sector_t end, best_end = 0; |
| |
| if (!fdisk_partition_has_end(x)) |
| continue; |
| |
| end = fdisk_partition_get_end(x); |
| if (best) |
| best_end = fdisk_partition_get_end(best); |
| |
| if (end < pa->start && (!best || best_end < end)) |
| best = x; |
| } |
| |
| if (!best && real_parent) |
| best = real_parent; |
| rc = table_insert_partition(tb, best, pa); |
| |
| fdisk_unref_partition(pa); |
| |
| DBG(TAB, ul_debugobj(tb, "adding freespace DONE [rc=%d]", rc)); |
| return rc; |
| } |
| |
| /* analyze @cont(ainer) in @parts and add all detected freespace into @tb, note |
| * that @parts has to be sorted by partition starts */ |
| static int check_container_freespace(struct fdisk_context *cxt, |
| struct fdisk_table *parts, |
| struct fdisk_table *tb, |
| struct fdisk_partition *cont) |
| { |
| struct fdisk_iter itr; |
| struct fdisk_partition *pa; |
| fdisk_sector_t x, last, grain, lastplusoff; |
| int rc = 0; |
| |
| assert(cxt); |
| assert(parts); |
| assert(tb); |
| assert(cont); |
| assert(fdisk_partition_has_start(cont)); |
| |
| DBG(TAB, ul_debugobj(tb, "analyze container 0x%p", cont)); |
| |
| last = fdisk_partition_get_start(cont); |
| grain = cxt->grain > cxt->sector_size ? cxt->grain / cxt->sector_size : 1; |
| fdisk_reset_iter(&itr, FDISK_ITER_FORWARD); |
| |
| DBG(CXT, ul_debugobj(cxt, "initialized: last=%ju, grain=%ju", last, grain)); |
| |
| while (fdisk_table_next_partition(parts, &itr, &pa) == 0) { |
| |
| DBG(CXT, ul_debugobj(cxt, "partno=%zu, start=%ju", pa->partno, pa->start)); |
| |
| if (!pa->used || !fdisk_partition_is_nested(pa) |
| || !fdisk_partition_has_start(pa)) |
| continue; |
| |
| DBG(CXT, ul_debugobj(cxt, "freespace container analyze: partno=%zu, start=%ju, end=%ju", |
| pa->partno, |
| (uintmax_t) fdisk_partition_get_start(pa), |
| (uintmax_t) fdisk_partition_get_end(pa))); |
| |
| lastplusoff = last + cxt->first_lba; |
| if (pa->start > lastplusoff && pa->start - lastplusoff > grain) |
| rc = table_add_freespace(cxt, tb, lastplusoff, pa->start, cont); |
| if (rc) |
| goto done; |
| last = fdisk_partition_get_end(pa); |
| } |
| |
| /* free-space remaining in extended partition */ |
| x = fdisk_partition_get_start(cont) + fdisk_partition_get_size(cont) - 1; |
| lastplusoff = last + cxt->first_lba; |
| if (lastplusoff < x && x - lastplusoff > grain) { |
| DBG(TAB, ul_debugobj(tb, "add remaining space in container 0x%p", cont)); |
| rc = table_add_freespace(cxt, tb, lastplusoff, x, cont); |
| } |
| |
| done: |
| DBG(TAB, ul_debugobj(tb, "analyze container 0x%p DONE [rc=%d]", cont, rc)); |
| return rc; |
| } |
| |
| |
| /** |
| * fdisk_get_freespaces |
| * @cxt: fdisk context |
| * @tb: returns table |
| * |
| * This function adds freespace (described by fdisk_partition) to @table, it |
| * allocates a new table if the @table points to NULL. |
| * |
| * Note that free space smaller than grain (see fdisk_get_grain()) is ignored. |
| |
| * Returns: 0 on success, otherwise, a corresponding error. |
| */ |
| int fdisk_get_freespaces(struct fdisk_context *cxt, struct fdisk_table **tb) |
| { |
| int rc = 0; |
| fdisk_sector_t last, grain; |
| struct fdisk_table *parts = NULL; |
| struct fdisk_partition *pa; |
| struct fdisk_iter itr; |
| |
| DBG(CXT, ul_debugobj(cxt, "get freespace")); |
| |
| if (!cxt || !cxt->label || !tb) |
| return -EINVAL; |
| if (!*tb && !(*tb = fdisk_new_table())) |
| return -ENOMEM; |
| |
| rc = fdisk_get_partitions(cxt, &parts); |
| if (rc) |
| goto done; |
| |
| fdisk_table_sort_partitions(parts, fdisk_partition_cmp_start); |
| fdisk_reset_iter(&itr, FDISK_ITER_FORWARD); |
| last = cxt->first_lba; |
| grain = cxt->grain > cxt->sector_size ? cxt->grain / cxt->sector_size : 1; |
| |
| DBG(CXT, ul_debugobj(cxt, "initialized: last=%ju, grain=%ju", last, grain)); |
| |
| /* analyze gaps between partitions */ |
| while (rc == 0 && fdisk_table_next_partition(parts, &itr, &pa) == 0) { |
| |
| DBG(CXT, ul_debugobj(cxt, "partno=%zu, start=%ju", pa->partno, pa->start)); |
| |
| if (!pa->used || pa->wholedisk || fdisk_partition_is_nested(pa) |
| || !fdisk_partition_has_start(pa)) |
| continue; |
| DBG(CXT, ul_debugobj(cxt, "freespace analyze: partno=%zu, start=%ju, end=%ju", |
| pa->partno, |
| (uintmax_t) fdisk_partition_get_start(pa), |
| (uintmax_t) fdisk_partition_get_end(pa))); |
| if (last + grain <= pa->start) { |
| rc = table_add_freespace(cxt, *tb, |
| last + (last > cxt->first_lba ? 1 : 0), |
| pa->start - 1, NULL); |
| } |
| /* add gaps between logical partitions */ |
| if (fdisk_partition_is_container(pa)) |
| rc = check_container_freespace(cxt, parts, *tb, pa); |
| last = fdisk_partition_get_end(pa); |
| } |
| |
| /* add free-space behind last partition to the end of the table (so |
| * don't use table_add_freespace()) */ |
| if (rc == 0 && last + grain < cxt->total_sectors - 1) { |
| DBG(CXT, ul_debugobj(cxt, "freespace behind last partition detected")); |
| rc = new_freespace(cxt, |
| last + (last > cxt->first_lba ? 1 : 0), |
| cxt->last_lba, NULL, &pa); |
| if (pa) { |
| fdisk_table_add_partition(*tb, pa); |
| fdisk_unref_partition(pa); |
| } |
| } |
| |
| done: |
| fdisk_unref_table(parts); |
| |
| DBG(CXT, ul_debugobj(cxt, "get freespace DONE [rc=%d]", rc)); |
| return rc; |
| } |
| |
| /** |
| * fdisk_table_wrong_order: |
| * @tb: table |
| * |
| * Returns: 1 of the table is not in disk order |
| */ |
| int fdisk_table_wrong_order(struct fdisk_table *tb) |
| { |
| struct fdisk_partition *pa; |
| struct fdisk_iter itr; |
| fdisk_sector_t last = 0; |
| |
| DBG(TAB, ul_debugobj(tb, "wrong older check")); |
| |
| fdisk_reset_iter(&itr, FDISK_ITER_FORWARD); |
| while (tb && fdisk_table_next_partition(tb, &itr, &pa) == 0) { |
| if (!fdisk_partition_has_start(pa)) |
| continue; |
| if (pa->start < last) |
| return 1; |
| last = pa->start; |
| } |
| return 0; |
| } |
| |
| /** |
| * fdisk_apply_table: |
| * @cxt: context |
| * @tb: table |
| * |
| * Add partitions from table @tb to the in-memory disk label. See |
| * fdisk_add_partition(), fdisk_delete_all_partitions(). The partitons |
| * that does not define start (or does not follow the default start) |
| * are ingored. |
| * |
| * Returns: 0 on success, <0 on error. |
| */ |
| int fdisk_apply_table(struct fdisk_context *cxt, struct fdisk_table *tb) |
| { |
| struct fdisk_partition *pa; |
| struct fdisk_iter itr; |
| int rc = 0; |
| |
| assert(cxt); |
| assert(tb); |
| |
| DBG(TAB, ul_debugobj(tb, "applying to context %p", cxt)); |
| |
| fdisk_reset_iter(&itr, FDISK_ITER_FORWARD); |
| while (tb && fdisk_table_next_partition(tb, &itr, &pa) == 0) { |
| if (!fdisk_partition_has_start(pa) && !pa->start_follow_default) |
| continue; |
| rc = fdisk_add_partition(cxt, pa, NULL); |
| if (rc) |
| break; |
| } |
| |
| return rc; |
| } |
| |