| #ifdef HAVE_LIBBLKID |
| # include <blkid.h> |
| #endif |
| |
| #include "blkdev.h" |
| #ifdef __linux__ |
| # include "partx.h" |
| #endif |
| #include "loopdev.h" |
| #include "fdiskP.h" |
| |
| |
| /** |
| * SECTION: context |
| * @title: Context |
| * @short_description: stores info about device, labels etc. |
| * |
| * The library distinguish between three types of partitioning objects. |
| * |
| * on-disk data |
| * - disk label specific |
| * - probed and read by disklabel drivers when assign device to the context |
| * or when switch to another disk label type |
| * - only fdisk_write_disklabel() modify on-disk data |
| * |
| * in-memory data |
| * - generic data and disklabel specific data stored in struct fdisk_label |
| * - all partitioning operations are based on in-memory data only |
| * |
| * struct fdisk_partition |
| * - provides abstraction to present partitions to users |
| * - fdisk_partition is possible to gather to fdisk_table container |
| * - used as unified template for new partitions |
| * - the struct fdisk_partition is always completely independent object and |
| * any change to the object has no effect to in-memory (or on-disk) label data |
| */ |
| |
| /** |
| * fdisk_new_context: |
| * |
| * Returns: newly allocated libfdisk handler |
| */ |
| struct fdisk_context *fdisk_new_context(void) |
| { |
| struct fdisk_context *cxt; |
| |
| cxt = calloc(1, sizeof(*cxt)); |
| if (!cxt) |
| return NULL; |
| |
| DBG(CXT, ul_debugobj(cxt, "alloc")); |
| cxt->dev_fd = -1; |
| cxt->refcount = 1; |
| |
| INIT_LIST_HEAD(&cxt->wipes); |
| |
| /* |
| * Allocate label specific structs. |
| * |
| * This is necessary (for example) to store label specific |
| * context setting. |
| */ |
| cxt->labels[ cxt->nlabels++ ] = fdisk_new_gpt_label(cxt); |
| cxt->labels[ cxt->nlabels++ ] = fdisk_new_dos_label(cxt); |
| cxt->labels[ cxt->nlabels++ ] = fdisk_new_bsd_label(cxt); |
| cxt->labels[ cxt->nlabels++ ] = fdisk_new_sgi_label(cxt); |
| cxt->labels[ cxt->nlabels++ ] = fdisk_new_sun_label(cxt); |
| |
| bindtextdomain(LIBFDISK_TEXTDOMAIN, LOCALEDIR); |
| |
| return cxt; |
| } |
| |
| static int init_nested_from_parent(struct fdisk_context *cxt, int isnew) |
| { |
| struct fdisk_context *parent; |
| |
| assert(cxt); |
| assert(cxt->parent); |
| |
| parent = cxt->parent; |
| |
| cxt->alignment_offset = parent->alignment_offset; |
| cxt->ask_cb = parent->ask_cb; |
| cxt->ask_data = parent->ask_data; |
| cxt->dev_fd = parent->dev_fd; |
| cxt->first_lba = parent->first_lba; |
| cxt->firstsector_bufsz = parent->firstsector_bufsz; |
| cxt->firstsector = parent->firstsector; |
| cxt->geom = parent->geom; |
| cxt->grain = parent->grain; |
| cxt->io_size = parent->io_size; |
| cxt->last_lba = parent->last_lba; |
| cxt->min_io_size = parent->min_io_size; |
| cxt->optimal_io_size = parent->optimal_io_size; |
| cxt->phy_sector_size = parent->phy_sector_size; |
| cxt->readonly = parent->readonly; |
| cxt->script = parent->script; |
| fdisk_ref_script(cxt->script); |
| cxt->sector_size = parent->sector_size; |
| cxt->total_sectors = parent->total_sectors; |
| cxt->user_geom = parent->user_geom; |
| cxt->user_log_sector = parent->user_log_sector; |
| cxt->user_pyh_sector = parent->user_pyh_sector; |
| |
| /* parent <--> nested independent setting, initialize for new nested |
| * contexts only */ |
| if (isnew) { |
| cxt->listonly = parent->listonly; |
| cxt->display_details = parent->display_details; |
| cxt->display_in_cyl_units = parent->display_in_cyl_units; |
| cxt->protect_bootbits = parent->protect_bootbits; |
| } |
| |
| free(cxt->dev_path); |
| cxt->dev_path = NULL; |
| |
| if (parent->dev_path) { |
| cxt->dev_path = strdup(parent->dev_path); |
| if (!cxt->dev_path) |
| return -ENOMEM; |
| } |
| |
| INIT_LIST_HEAD(&cxt->wipes); |
| |
| return 0; |
| } |
| |
| /** |
| * fdisk_new_nested_context: |
| * @parent: parental context |
| * @name: optional label name (e.g. "bsd") |
| * |
| * Create a new nested fdisk context for nested disk labels (e.g. BSD or PMBR). |
| * The function also probes for the nested label on the device if device is |
| * already assigned to parent. |
| * |
| * The new context is initialized according to @parent and both context shares |
| * some settings and file descriptor to the device. The child propagate some |
| * changes (like fdisk_assign_device()) to parent, but it does not work |
| * vice-versa. The behavior is undefined if you assign another device to |
| * parent. |
| * |
| * Returns: new context for nested partition table. |
| */ |
| struct fdisk_context *fdisk_new_nested_context(struct fdisk_context *parent, |
| const char *name) |
| { |
| struct fdisk_context *cxt; |
| struct fdisk_label *lb = NULL; |
| |
| assert(parent); |
| |
| cxt = calloc(1, sizeof(*cxt)); |
| if (!cxt) |
| return NULL; |
| |
| DBG(CXT, ul_debugobj(parent, "alloc nested [%p] [name=%s]", cxt, name)); |
| cxt->refcount = 1; |
| |
| fdisk_ref_context(parent); |
| cxt->parent = parent; |
| |
| if (init_nested_from_parent(cxt, 1) != 0) { |
| cxt->parent = NULL; |
| fdisk_unref_context(cxt); |
| return NULL; |
| } |
| |
| if (name) { |
| if (strcasecmp(name, "bsd") == 0) |
| lb = cxt->labels[ cxt->nlabels++ ] = fdisk_new_bsd_label(cxt); |
| else if (strcasecmp(name, "dos") == 0 || strcasecmp(name, "mbr") == 0) |
| lb = cxt->labels[ cxt->nlabels++ ] = fdisk_new_dos_label(cxt); |
| } |
| |
| if (lb && parent->dev_fd >= 0) { |
| DBG(CXT, ul_debugobj(cxt, "probing for nested %s", lb->name)); |
| |
| cxt->label = lb; |
| |
| if (lb->op->probe(cxt) == 1) |
| __fdisk_switch_label(cxt, lb); |
| else { |
| DBG(CXT, ul_debugobj(cxt, "not found %s label", lb->name)); |
| if (lb->op->deinit) |
| lb->op->deinit(lb); |
| cxt->label = NULL; |
| } |
| } |
| |
| return cxt; |
| } |
| |
| |
| /** |
| * fdisk_ref_context: |
| * @cxt: context pointer |
| * |
| * Increments reference counter. |
| */ |
| void fdisk_ref_context(struct fdisk_context *cxt) |
| { |
| if (cxt) |
| cxt->refcount++; |
| } |
| |
| /** |
| * fdisk_get_label: |
| * @cxt: context instance |
| * @name: label name (e.g. "gpt") |
| * |
| * If no @name specified then returns the current context label. |
| * |
| * The label is allocated and maintained within the context #cxt. There is |
| * nothing like reference counting for labels, you cannot deallocate the |
| * label. |
| * |
| * Returns: label struct or NULL in case of error. |
| */ |
| struct fdisk_label *fdisk_get_label(struct fdisk_context *cxt, const char *name) |
| { |
| size_t i; |
| |
| assert(cxt); |
| |
| if (!name) |
| return cxt->label; |
| else if (strcasecmp(name, "mbr") == 0) |
| name = "dos"; |
| |
| for (i = 0; i < cxt->nlabels; i++) |
| if (cxt->labels[i] |
| && strcasecmp(cxt->labels[i]->name, name) == 0) |
| return cxt->labels[i]; |
| |
| DBG(CXT, ul_debugobj(cxt, "failed to found %s label driver", name)); |
| return NULL; |
| } |
| |
| /** |
| * fdisk_next_label: |
| * @cxt: context instance |
| * @lb: returns pointer to the next label |
| * |
| * <informalexample> |
| * <programlisting> |
| * // print all supported labels |
| * struct fdisk_context *cxt = fdisk_new_context(); |
| * struct fdisk_label *lb = NULL; |
| * |
| * while (fdisk_next_label(cxt, &lb) == 0) |
| * print("label name: %s\n", fdisk_label_get_name(lb)); |
| * fdisk_unref_context(cxt); |
| * </programlisting> |
| * </informalexample> |
| * |
| * Returns: <0 in case of error, 0 on success, 1 at the end. |
| */ |
| int fdisk_next_label(struct fdisk_context *cxt, struct fdisk_label **lb) |
| { |
| size_t i; |
| struct fdisk_label *res = NULL; |
| |
| if (!lb || !cxt) |
| return -EINVAL; |
| |
| if (!*lb) |
| res = cxt->labels[0]; |
| else { |
| for (i = 1; i < cxt->nlabels; i++) { |
| if (*lb == cxt->labels[i - 1]) { |
| res = cxt->labels[i]; |
| break; |
| } |
| } |
| } |
| |
| *lb = res; |
| return res ? 0 : 1; |
| } |
| |
| /** |
| * fdisk_get_nlabels: |
| * @cxt: context |
| * |
| * Returns: number of supported label types |
| */ |
| size_t fdisk_get_nlabels(struct fdisk_context *cxt) |
| { |
| return cxt ? cxt->nlabels : 0; |
| } |
| |
| int __fdisk_switch_label(struct fdisk_context *cxt, struct fdisk_label *lb) |
| { |
| if (!lb || !cxt) |
| return -EINVAL; |
| if (lb->disabled) { |
| DBG(CXT, ul_debugobj(cxt, "*** attempt to switch to disabled label %s -- ignore!", lb->name)); |
| return -EINVAL; |
| } |
| cxt->label = lb; |
| DBG(CXT, ul_debugobj(cxt, "--> switching context to %s!", lb->name)); |
| |
| fdisk_apply_label_device_properties(cxt); |
| return 0; |
| } |
| |
| /** |
| * fdisk_has_label: |
| * @cxt: fdisk context |
| * |
| * Returns: return 1 if there is label on the device. |
| */ |
| int fdisk_has_label(struct fdisk_context *cxt) |
| { |
| return cxt && cxt->label; |
| } |
| |
| /** |
| * fdisk_has_protected_bootbits: |
| * @cxt: fdisk context |
| * |
| * Returns: return 1 if boot bits protection enabled. |
| */ |
| int fdisk_has_protected_bootbits(struct fdisk_context *cxt) |
| { |
| return cxt && cxt->protect_bootbits; |
| } |
| |
| /** |
| * fdisk_enable_bootbits_protection: |
| * @cxt: fdisk context |
| * @enable: 1 or 0 |
| * |
| * The library zeroizes all the first sector when create a new disk label by |
| * default. This function allows to control this behavior. For now it's |
| * supported for MBR and GPT. |
| * |
| * Returns: 0 on success, < 0 on error. |
| */ |
| int fdisk_enable_bootbits_protection(struct fdisk_context *cxt, int enable) |
| { |
| if (!cxt) |
| return -EINVAL; |
| cxt->protect_bootbits = enable ? 1 : 0; |
| return 0; |
| } |
| /** |
| * fdisk_disable_dialogs |
| * @cxt: fdisk context |
| * @disable: 1 or 0 |
| * |
| * The library uses dialog driven partitioning by default. |
| * |
| * Returns: 0 on success, < 0 on error. |
| * |
| * Since: 2.31 |
| */ |
| int fdisk_disable_dialogs(struct fdisk_context *cxt, int disable) |
| { |
| if (!cxt) |
| return -EINVAL; |
| |
| cxt->no_disalogs = disable; |
| return 0; |
| } |
| |
| /** |
| * fdisk_has_dialogs |
| * @cxt: fdisk context |
| * |
| * See fdisk_disable_dialogs() |
| * |
| * Returns: 1 if dialog driven partitioning enabled (default), or 0. |
| * |
| * Since: 2.31 |
| */ |
| int fdisk_has_dialogs(struct fdisk_context *cxt) |
| { |
| return cxt->no_disalogs == 0; |
| } |
| |
| /** |
| * fdisk_enable_wipe |
| * @cxt: fdisk context |
| * @enable: 1 or 0 |
| * |
| * The library removes all PT/filesystem/RAID signatures before it writes |
| * partition table. The probing area where it looks for signatures is from |
| * the begin of the disk. The device is wiped by libblkid. |
| * |
| * See also fdisk_wipe_partition(). |
| * |
| * Returns: 0 on success, < 0 on error. |
| */ |
| int fdisk_enable_wipe(struct fdisk_context *cxt, int enable) |
| { |
| if (!cxt) |
| return -EINVAL; |
| |
| fdisk_set_wipe_area(cxt, 0, cxt->total_sectors, enable); |
| return 0; |
| } |
| |
| /** |
| * fdisk_has_wipe |
| * @cxt: fdisk context |
| * |
| * Returns the current wipe setting. See fdisk_enable_wipe(). |
| * |
| * Returns: 0 on success, < 0 on error. |
| */ |
| int fdisk_has_wipe(struct fdisk_context *cxt) |
| { |
| if (!cxt) |
| return 0; |
| |
| return fdisk_has_wipe_area(cxt, 0, cxt->total_sectors); |
| } |
| |
| |
| /** |
| * fdisk_get_collision |
| * @cxt: fdisk context |
| * |
| * Returns: name of the filesystem or RAID detected on the device or NULL. |
| */ |
| const char *fdisk_get_collision(struct fdisk_context *cxt) |
| { |
| return cxt->collision; |
| } |
| |
| /** |
| * fdisk_is_ptcollision: |
| * @cxt: fdisk context |
| * |
| * The collision detected by libblkid (usually another partition table). Note |
| * that libfdisk does not support all partitions tables, so fdisk_has_label() |
| * may return false, but fdisk_is_ptcollision() may return true. |
| * |
| * Since: 2.30 |
| * |
| * Returns: 0 or 1 |
| */ |
| int fdisk_is_ptcollision(struct fdisk_context *cxt) |
| { |
| return cxt->pt_collision; |
| } |
| |
| /** |
| * fdisk_get_npartitions: |
| * @cxt: context |
| * |
| * The maximal number of the partitions depends on disklabel and does not |
| * have to describe the real limit of PT. |
| * |
| * For example the limit for MBR without extend partition is 4, with extended |
| * partition it's unlimited (so the function returns the current number of all |
| * partitions in this case). |
| * |
| * And for example for GPT it depends on space allocated on disk for array of |
| * entry records (usually 128). |
| * |
| * It's fine to use fdisk_get_npartitions() in loops, but don't forget that |
| * partition may be unused (see fdisk_is_partition_used()). |
| * |
| * <informalexample> |
| * <programlisting> |
| * struct fdisk_partition *pa = NULL; |
| * size_t i, nmax = fdisk_get_npartitions(cxt); |
| * |
| * for (i = 0; i < nmax; i++) { |
| * if (!fdisk_is_partition_used(cxt, i)) |
| * continue; |
| * ... do something ... |
| * } |
| * </programlisting> |
| * </informalexample> |
| * |
| * Note that the recommended way to list partitions is to use |
| * fdisk_get_partitions() and struct fdisk_table than ask disk driver for each |
| * individual partitions. |
| * |
| * Returns: maximal number of partitions for the current label. |
| */ |
| size_t fdisk_get_npartitions(struct fdisk_context *cxt) |
| { |
| return cxt && cxt->label ? cxt->label->nparts_max : 0; |
| } |
| |
| /** |
| * fdisk_is_labeltype: |
| * @cxt: fdisk context |
| * @id: FDISK_DISKLABEL_* |
| * |
| * See also fdisk_is_label() macro in libfdisk.h. |
| * |
| * Returns: return 1 if the current label is @id |
| */ |
| int fdisk_is_labeltype(struct fdisk_context *cxt, enum fdisk_labeltype id) |
| { |
| assert(cxt); |
| |
| return cxt->label && (unsigned)fdisk_label_get_type(cxt->label) == id; |
| } |
| |
| /** |
| * fdisk_get_parent: |
| * @cxt: nested fdisk context |
| * |
| * Returns: pointer to parental context, or NULL |
| */ |
| struct fdisk_context *fdisk_get_parent(struct fdisk_context *cxt) |
| { |
| assert(cxt); |
| return cxt->parent; |
| } |
| |
| static void reset_context(struct fdisk_context *cxt) |
| { |
| size_t i; |
| |
| DBG(CXT, ul_debugobj(cxt, "*** resetting context")); |
| |
| /* reset drives' private data */ |
| for (i = 0; i < cxt->nlabels; i++) |
| fdisk_deinit_label(cxt->labels[i]); |
| |
| if (cxt->parent) { |
| /* the first sector may be independent on parent */ |
| if (cxt->parent->firstsector != cxt->firstsector) |
| free(cxt->firstsector); |
| } else { |
| /* we close device only in primary context */ |
| if (cxt->dev_fd > -1) |
| close(cxt->dev_fd); |
| free(cxt->firstsector); |
| } |
| |
| free(cxt->dev_path); |
| cxt->dev_path = NULL; |
| |
| free(cxt->collision); |
| cxt->collision = NULL; |
| |
| memset(&cxt->dev_st, 0, sizeof(cxt->dev_st)); |
| |
| cxt->dev_fd = -1; |
| cxt->firstsector = NULL; |
| cxt->firstsector_bufsz = 0; |
| |
| fdisk_zeroize_device_properties(cxt); |
| |
| fdisk_unref_script(cxt->script); |
| cxt->script = NULL; |
| |
| cxt->label = NULL; |
| |
| fdisk_free_wipe_areas(cxt); |
| } |
| |
| /** |
| * fdisk_assign_device: |
| * @cxt: context |
| * @fname: path to the device to be handled |
| * @readonly: how to open the device |
| * |
| * Open the device, discovery topology, geometry, detect disklabel and switch |
| * the current label driver to reflect the probing result. |
| * |
| * Note that this function resets all generic setting in context. If the @cxt |
| * is nested context then the device is assigned to the parental context and |
| * necessary properties are copied to the @cxt. The change is propagated in |
| * child->parent direction only. It's impossible to use a different device for |
| * primary and nested contexts. |
| * |
| * Returns: 0 on success, < 0 on error. |
| */ |
| int fdisk_assign_device(struct fdisk_context *cxt, |
| const char *fname, int readonly) |
| { |
| int fd; |
| |
| DBG(CXT, ul_debugobj(cxt, "assigning device %s", fname)); |
| assert(cxt); |
| |
| /* redirect request to parent */ |
| if (cxt->parent) { |
| int rc, org = fdisk_is_listonly(cxt->parent); |
| |
| /* assign_device() is sensitive to "listonly" mode, so let's |
| * follow the current context setting for the parent to avoid |
| * unwanted extra warnings. */ |
| fdisk_enable_listonly(cxt->parent, fdisk_is_listonly(cxt)); |
| |
| rc = fdisk_assign_device(cxt->parent, fname, readonly); |
| fdisk_enable_listonly(cxt->parent, org); |
| |
| if (!rc) |
| rc = init_nested_from_parent(cxt, 0); |
| if (!rc) |
| fdisk_probe_labels(cxt); |
| return rc; |
| } |
| |
| reset_context(cxt); |
| |
| fd = open(fname, (readonly ? O_RDONLY : O_RDWR ) | O_CLOEXEC); |
| if (fd < 0) |
| goto fail; |
| |
| if (fstat(fd, &cxt->dev_st) != 0) |
| goto fail; |
| |
| cxt->readonly = readonly; |
| cxt->dev_fd = fd; |
| cxt->dev_path = strdup(fname); |
| if (!cxt->dev_path) |
| goto fail; |
| |
| fdisk_discover_topology(cxt); |
| fdisk_discover_geometry(cxt); |
| |
| fdisk_apply_user_device_properties(cxt); |
| |
| if (fdisk_read_firstsector(cxt) < 0) |
| goto fail; |
| |
| fdisk_probe_labels(cxt); |
| |
| fdisk_apply_label_device_properties(cxt); |
| |
| /* warn about obsolete stuff on the device if we aren't in |
| * list-only mode and there is not PT yet */ |
| if (!fdisk_is_listonly(cxt) && !fdisk_has_label(cxt) |
| && fdisk_check_collisions(cxt) < 0) |
| goto fail; |
| |
| DBG(CXT, ul_debugobj(cxt, "initialized for %s [%s]", |
| fname, readonly ? "READ-ONLY" : "READ-WRITE")); |
| return 0; |
| fail: |
| { |
| int rc = -errno; |
| if (fd >= 0) |
| close(fd); |
| DBG(CXT, ul_debugobj(cxt, "failed to assign device [rc=%d]", rc)); |
| return rc; |
| } |
| } |
| |
| /** |
| * fdisk_deassign_device: |
| * @cxt: context |
| * @nosync: disable fsync() |
| * |
| * Close device and call fsync(). If the @cxt is nested context than the |
| * request is redirected to the parent. |
| * |
| * Returns: 0 on success, < 0 on error. |
| */ |
| int fdisk_deassign_device(struct fdisk_context *cxt, int nosync) |
| { |
| assert(cxt); |
| assert(cxt->dev_fd >= 0); |
| |
| if (cxt->parent) { |
| int rc = fdisk_deassign_device(cxt->parent, nosync); |
| |
| if (!rc) |
| rc = init_nested_from_parent(cxt, 0); |
| return rc; |
| } |
| |
| DBG(CXT, ul_debugobj(cxt, "de-assigning device %s", cxt->dev_path)); |
| |
| if (cxt->readonly) |
| close(cxt->dev_fd); |
| else { |
| if (fsync(cxt->dev_fd) || close(cxt->dev_fd)) { |
| fdisk_warn(cxt, _("%s: close device failed"), |
| cxt->dev_path); |
| return -errno; |
| } |
| |
| if (!nosync) { |
| fdisk_info(cxt, _("Syncing disks.")); |
| sync(); |
| } |
| } |
| |
| free(cxt->dev_path); |
| cxt->dev_path = NULL; |
| |
| cxt->dev_fd = -1; |
| |
| return 0; |
| } |
| |
| /** |
| * fdisk_reassign_device: |
| * @cxt: context |
| * |
| * This function is "hard reset" of the context and it does not write anything |
| * to the device. All in-memory changes associated with the context will be |
| * lost. It's recommended to use this function after some fatal problem when the |
| * context (and label specific driver) is in an undefined state. |
| * |
| * Returns: 0 on success, < 0 on error. |
| */ |
| int fdisk_reassign_device(struct fdisk_context *cxt) |
| { |
| char *devname; |
| int rdonly, rc; |
| |
| assert(cxt); |
| assert(cxt->dev_fd >= 0); |
| |
| DBG(CXT, ul_debugobj(cxt, "re-assigning device %s", cxt->dev_path)); |
| |
| devname = strdup(cxt->dev_path); |
| if (!devname) |
| return -ENOMEM; |
| |
| rdonly = cxt->readonly; |
| |
| fdisk_deassign_device(cxt, 1); |
| rc = fdisk_assign_device(cxt, devname, rdonly); |
| free(devname); |
| |
| return rc; |
| } |
| |
| /** |
| * fdisk_reread_partition_table: |
| * @cxt: context |
| * |
| * Force *kernel* to re-read partition table on block devices. |
| * |
| * Returns: 0 on success, < 0 in case of error. |
| */ |
| int fdisk_reread_partition_table(struct fdisk_context *cxt) |
| { |
| int i = 0; |
| |
| assert(cxt); |
| assert(cxt->dev_fd >= 0); |
| |
| if (!S_ISBLK(cxt->dev_st.st_mode)) |
| return 0; |
| else { |
| DBG(CXT, ul_debugobj(cxt, "calling re-read ioctl")); |
| sync(); |
| #ifdef BLKRRPART |
| fdisk_info(cxt, _("Calling ioctl() to re-read partition table.")); |
| i = ioctl(cxt->dev_fd, BLKRRPART); |
| #else |
| errno = ENOSYS; |
| i = 1; |
| #endif |
| } |
| |
| if (i) { |
| fdisk_warn(cxt, _("Re-reading the partition table failed.")); |
| fdisk_info(cxt, _( |
| "The kernel still uses the old table. The " |
| "new table will be used at the next reboot " |
| "or after you run partprobe(8) or kpartx(8).")); |
| return -errno; |
| } |
| |
| return 0; |
| } |
| |
| #ifdef __linux__ |
| static inline int add_to_partitions_array( |
| struct fdisk_partition ***ary, |
| struct fdisk_partition *pa, |
| size_t *n, size_t nmax) |
| { |
| if (!*ary) { |
| *ary = calloc(nmax, sizeof(struct fdisk_partition *)); |
| if (!*ary) |
| return -ENOMEM; |
| } |
| (*ary)[*n] = pa; |
| (*n)++; |
| return 0; |
| } |
| #endif |
| |
| /** |
| * fdisk_reread_changes: |
| * @cxt: context |
| * @org: original layout (on disk) |
| * |
| * Like fdisk_reread_partition_table() but don't forces kernel re-read all |
| * partition table. The BLKPG_* ioctls are used for individual partitions. The |
| * advantage is that unmodified partitions maybe mounted. |
| * |
| * The function behavies like fdisk_reread_partition_table() on systems where |
| * are no available BLKPG_* ioctls. |
| * |
| * Returns: <0 on error, or 0. |
| */ |
| #ifdef __linux__ |
| int fdisk_reread_changes(struct fdisk_context *cxt, struct fdisk_table *org) |
| { |
| struct fdisk_table *tb = NULL; |
| struct fdisk_iter itr; |
| struct fdisk_partition *pa; |
| struct fdisk_partition **rem = NULL, **add = NULL, **upd = NULL; |
| int change, rc = 0, err = 0; |
| size_t nparts, i, nadds = 0, nupds = 0, nrems = 0; |
| |
| DBG(CXT, ul_debugobj(cxt, "rereading changes")); |
| |
| fdisk_reset_iter(&itr, FDISK_ITER_FORWARD); |
| |
| /* the current layout */ |
| fdisk_get_partitions(cxt, &tb); |
| /* maximal number of partitions */ |
| nparts = max(fdisk_table_get_nents(tb), fdisk_table_get_nents(org)); |
| |
| while (fdisk_diff_tables(org, tb, &itr, &pa, &change) == 0) { |
| if (change == FDISK_DIFF_UNCHANGED) |
| continue; |
| switch (change) { |
| case FDISK_DIFF_REMOVED: |
| rc = add_to_partitions_array(&rem, pa, &nrems, nparts); |
| break; |
| case FDISK_DIFF_ADDED: |
| rc = add_to_partitions_array(&add, pa, &nadds, nparts); |
| break; |
| case FDISK_DIFF_RESIZED: |
| rc = add_to_partitions_array(&upd, pa, &nupds, nparts); |
| break; |
| case FDISK_DIFF_MOVED: |
| rc = add_to_partitions_array(&rem, pa, &nrems, nparts); |
| rc = add_to_partitions_array(&add, pa, &nadds, nparts); |
| break; |
| } |
| if (rc != 0) |
| goto done; |
| } |
| |
| for (i = 0; i < nrems; i++) { |
| pa = rem[i]; |
| DBG(PART, ul_debugobj(pa, "#%zu calling BLKPG_DEL_PARTITION", pa->partno)); |
| if (partx_del_partition(cxt->dev_fd, pa->partno + 1) != 0) { |
| fdisk_warn(cxt, _("Failed to remove partition %zu from system"), pa->partno + 1); |
| err++; |
| } |
| } |
| for (i = 0; i < nupds; i++) { |
| pa = upd[i]; |
| DBG(PART, ul_debugobj(pa, "#%zu calling BLKPG_RESIZE_PARTITION", pa->partno)); |
| if (partx_resize_partition(cxt->dev_fd, pa->partno + 1, pa->start, pa->size) != 0) { |
| fdisk_warn(cxt, _("Failed to update system information about partition %zu"), pa->partno + 1); |
| err++; |
| } |
| } |
| for (i = 0; i < nadds; i++) { |
| pa = add[i]; |
| DBG(PART, ul_debugobj(pa, "#%zu calling BLKPG_ADD_PARTITION", pa->partno)); |
| if (partx_add_partition(cxt->dev_fd, pa->partno + 1, pa->start, pa->size) != 0) { |
| fdisk_warn(cxt, _("Failed to add partition %zu to system"), pa->partno + 1); |
| err++; |
| } |
| } |
| if (err) |
| fdisk_info(cxt, _( |
| "The kernel still uses the old partitions. The new " |
| "table will be used at the next reboot. ")); |
| done: |
| free(rem); |
| free(add); |
| free(upd); |
| fdisk_unref_table(tb); |
| return rc; |
| } |
| #else |
| int fdisk_reread_changes(struct fdisk_context *cxt, |
| struct fdisk_table *org __attribute__((__unused__))) { |
| return fdisk_reread_partition_table(cxt); |
| } |
| #endif |
| |
| /** |
| * fdisk_device_is_used: |
| * @cxt: context |
| * |
| * On systems where is no BLKRRPART ioctl the function returns zero and |
| * sets errno to ENOSYS. |
| * |
| * Returns: 1 if the device assigned to the context is used by system, or 0. |
| */ |
| int fdisk_device_is_used(struct fdisk_context *cxt) |
| { |
| int rc = 0; |
| |
| assert(cxt); |
| assert(cxt->dev_fd >= 0); |
| |
| errno = 0; |
| |
| #ifdef BLKRRPART |
| /* it seems kernel always return EINVAL for BLKRRPART on loopdevices */ |
| if (S_ISBLK(cxt->dev_st.st_mode) |
| && major(cxt->dev_st.st_rdev) != LOOPDEV_MAJOR) { |
| DBG(CXT, ul_debugobj(cxt, "calling re-read ioctl")); |
| rc = ioctl(cxt->dev_fd, BLKRRPART) != 0; |
| } |
| #else |
| errno = ENOSYS; |
| #endif |
| DBG(CXT, ul_debugobj(cxt, "device used: %s [errno=%d]", rc ? "TRUE" : "FALSE", errno)); |
| return rc; |
| } |
| |
| /** |
| * fdisk_is_readonly: |
| * @cxt: context |
| * |
| * Returns: 1 if device open readonly |
| */ |
| int fdisk_is_readonly(struct fdisk_context *cxt) |
| { |
| assert(cxt); |
| return cxt->readonly; |
| } |
| |
| /** |
| * fdisk_is_regfile: |
| * @cxt: context |
| * |
| * Since: 2.30 |
| * |
| * Returns: 1 if open file descriptor is regular file rather than a block device. |
| */ |
| int fdisk_is_regfile(struct fdisk_context *cxt) |
| { |
| assert(cxt); |
| return S_ISREG(cxt->dev_st.st_mode); |
| } |
| |
| /** |
| * fdisk_unref_context: |
| * @cxt: fdisk context |
| * |
| * Deallocates context struct. |
| */ |
| void fdisk_unref_context(struct fdisk_context *cxt) |
| { |
| unsigned i; |
| |
| if (!cxt) |
| return; |
| |
| cxt->refcount--; |
| if (cxt->refcount <= 0) { |
| DBG(CXT, ul_debugobj(cxt, "freeing context %p for %s", cxt, cxt->dev_path)); |
| |
| reset_context(cxt); /* this is sensitive to parent<->child relationship! */ |
| |
| /* deallocate label's private stuff */ |
| for (i = 0; i < cxt->nlabels; i++) { |
| if (!cxt->labels[i]) |
| continue; |
| if (cxt->labels[i]->op->free) |
| cxt->labels[i]->op->free(cxt->labels[i]); |
| else |
| free(cxt->labels[i]); |
| } |
| |
| fdisk_unref_context(cxt->parent); |
| cxt->parent = NULL; |
| |
| free(cxt); |
| } |
| } |
| |
| |
| /** |
| * fdisk_enable_details: |
| * @cxt: context |
| * @enable: true/false |
| * |
| * Enables or disables "details" display mode. This function has effect to |
| * fdisk_partition_to_string() function. |
| * |
| * Returns: 0 on success, < 0 on error. |
| */ |
| int fdisk_enable_details(struct fdisk_context *cxt, int enable) |
| { |
| assert(cxt); |
| cxt->display_details = enable ? 1 : 0; |
| return 0; |
| } |
| |
| /** |
| * fdisk_is_details: |
| * @cxt: context |
| * |
| * Returns: 1 if details are enabled |
| */ |
| int fdisk_is_details(struct fdisk_context *cxt) |
| { |
| assert(cxt); |
| return cxt->display_details == 1; |
| } |
| |
| /** |
| * fdisk_enable_listonly: |
| * @cxt: context |
| * @enable: true/false |
| * |
| * Just list partition only, don't care about another details, mistakes, ... |
| * |
| * Returns: 0 on success, < 0 on error. |
| */ |
| int fdisk_enable_listonly(struct fdisk_context *cxt, int enable) |
| { |
| assert(cxt); |
| cxt->listonly = enable ? 1 : 0; |
| return 0; |
| } |
| |
| /** |
| * fdisk_is_listonly: |
| * @cxt: context |
| * |
| * Returns: 1 if list-only mode enabled |
| */ |
| int fdisk_is_listonly(struct fdisk_context *cxt) |
| { |
| assert(cxt); |
| return cxt->listonly == 1; |
| } |
| |
| |
| /** |
| * fdisk_set_unit: |
| * @cxt: context |
| * @str: "cylinder" or "sector". |
| * |
| * This is pure shit, unfortunately for example Sun addresses begin of the |
| * partition by cylinders... |
| * |
| * Returns: 0 on success, <0 on error. |
| */ |
| int fdisk_set_unit(struct fdisk_context *cxt, const char *str) |
| { |
| assert(cxt); |
| |
| cxt->display_in_cyl_units = 0; |
| |
| if (!str) |
| return 0; |
| |
| if (strcmp(str, "cylinder") == 0 || strcmp(str, "cylinders") == 0) |
| cxt->display_in_cyl_units = 1; |
| |
| else if (strcmp(str, "sector") == 0 || strcmp(str, "sectors") == 0) |
| cxt->display_in_cyl_units = 0; |
| |
| DBG(CXT, ul_debugobj(cxt, "display unit: %s", fdisk_get_unit(cxt, 0))); |
| return 0; |
| } |
| |
| /** |
| * fdisk_get_unit: |
| * @cxt: context |
| * @n: FDISK_PLURAL or FDISK_SINGULAR |
| * |
| * Returns: unit name. |
| */ |
| const char *fdisk_get_unit(struct fdisk_context *cxt, int n) |
| { |
| assert(cxt); |
| |
| if (fdisk_use_cylinders(cxt)) |
| return P_("cylinder", "cylinders", n); |
| return P_("sector", "sectors", n); |
| } |
| |
| /** |
| * fdisk_use_cylinders: |
| * @cxt: context |
| * |
| * Returns: 1 if user wants to display in cylinders. |
| */ |
| int fdisk_use_cylinders(struct fdisk_context *cxt) |
| { |
| assert(cxt); |
| return cxt->display_in_cyl_units == 1; |
| } |
| |
| /** |
| * fdisk_get_units_per_sector: |
| * @cxt: context |
| * |
| * This is necessary only for brain dead situations when we use "cylinders"; |
| * |
| * Returns: number of "units" per sector, default is 1 if display unit is sector. |
| */ |
| unsigned int fdisk_get_units_per_sector(struct fdisk_context *cxt) |
| { |
| assert(cxt); |
| |
| if (fdisk_use_cylinders(cxt)) { |
| assert(cxt->geom.heads); |
| return cxt->geom.heads * cxt->geom.sectors; |
| } |
| return 1; |
| } |
| |
| /** |
| * fdisk_get_optimal_iosize: |
| * @cxt: context |
| * |
| * The optimal I/O is optional and does not have to be provided by device, |
| * anyway libfdisk never returns zero. If the optimal I/O size is not provided |
| * then libfdisk returns minimal I/O size or sector size. |
| * |
| * Returns: optimal I/O size in bytes. |
| */ |
| unsigned long fdisk_get_optimal_iosize(struct fdisk_context *cxt) |
| { |
| assert(cxt); |
| return cxt->optimal_io_size ? cxt->optimal_io_size : cxt->io_size; |
| } |
| |
| /** |
| * fdisk_get_minimal_iosize: |
| * @cxt: context |
| * |
| * Returns: minimal I/O size in bytes |
| */ |
| unsigned long fdisk_get_minimal_iosize(struct fdisk_context *cxt) |
| { |
| assert(cxt); |
| return cxt->min_io_size; |
| } |
| |
| /** |
| * fdisk_get_physector_size: |
| * @cxt: context |
| * |
| * Returns: physical sector size in bytes |
| */ |
| unsigned long fdisk_get_physector_size(struct fdisk_context *cxt) |
| { |
| assert(cxt); |
| return cxt->phy_sector_size; |
| } |
| |
| /** |
| * fdisk_get_sector_size: |
| * @cxt: context |
| * |
| * Returns: logical sector size in bytes |
| */ |
| unsigned long fdisk_get_sector_size(struct fdisk_context *cxt) |
| { |
| assert(cxt); |
| return cxt->sector_size; |
| } |
| |
| /** |
| * fdisk_get_alignment_offset |
| * @cxt: context |
| * |
| * The alignment offset is offset between logical and physical sectors. For |
| * backward compatibility the first logical sector on 4K disks does no have to |
| * start on the same place like physical sectors. |
| * |
| * Returns: alignment offset in bytes |
| */ |
| unsigned long fdisk_get_alignment_offset(struct fdisk_context *cxt) |
| { |
| assert(cxt); |
| return cxt->alignment_offset; |
| } |
| |
| /** |
| * fdisk_get_grain_size: |
| * @cxt: context |
| * |
| * Returns: grain in bytes used to align partitions (usually 1MiB) |
| */ |
| unsigned long fdisk_get_grain_size(struct fdisk_context *cxt) |
| { |
| assert(cxt); |
| return cxt->grain; |
| } |
| |
| /** |
| * fdisk_get_first_lba: |
| * @cxt: context |
| * |
| * Returns: first possible LBA on disk for data partitions. |
| */ |
| fdisk_sector_t fdisk_get_first_lba(struct fdisk_context *cxt) |
| { |
| assert(cxt); |
| return cxt->first_lba; |
| } |
| |
| /** |
| * fdisk_set_first_lba: |
| * @cxt: fdisk context |
| * @lba: first possible logical sector for data |
| * |
| * It's strongly recommended to use the default library setting. The first LBA |
| * is always reset by fdisk_assign_device(), fdisk_override_geometry() |
| * and fdisk_reset_alignment(). This is very low level function and library |
| * does not check if your setting makes any sense. |
| * |
| * This function is necessary only when you want to work with very unusual |
| * partition tables like GPT protective MBR or hybrid partition tables on |
| * bootable media where the first partition may start on very crazy offsets. |
| * |
| * Returns: 0 on success, <0 on error. |
| */ |
| fdisk_sector_t fdisk_set_first_lba(struct fdisk_context *cxt, fdisk_sector_t lba) |
| { |
| assert(cxt); |
| DBG(CXT, ul_debugobj(cxt, "setting first LBA from %ju to %ju", |
| (uintmax_t) cxt->first_lba, (uintmax_t) lba)); |
| cxt->first_lba = lba; |
| return 0; |
| } |
| |
| /** |
| * fdisk_get_last_lba: |
| * @cxt: fdisk context |
| * |
| * Note that the device has to be already assigned. |
| * |
| * Returns: last possible LBA on device |
| */ |
| fdisk_sector_t fdisk_get_last_lba(struct fdisk_context *cxt) |
| { |
| return cxt->last_lba; |
| } |
| |
| /** |
| * fdisk_set_last_lba: |
| * @cxt: fdisk context |
| * @lba: last possible logical sector |
| * |
| * It's strongly recommended to use the default library setting. The last LBA |
| * is always reset by fdisk_assign_device(), fdisk_override_geometry() and |
| * fdisk_reset_alignment(). |
| * |
| * The default is number of sectors on the device, but maybe modified by the |
| * current disklabel driver (for example GPT uses the end of disk for backup |
| * header, so last_lba is smaller than total number of sectors). |
| * |
| * Returns: 0 on success, <0 on error. |
| */ |
| fdisk_sector_t fdisk_set_last_lba(struct fdisk_context *cxt, fdisk_sector_t lba) |
| { |
| assert(cxt); |
| |
| if (lba > cxt->total_sectors - 1 || lba < 1) |
| return -ERANGE; |
| cxt->last_lba = lba; |
| return 0; |
| } |
| |
| /** |
| * fdisk_set_size_unit: |
| * @cxt: fdisk context |
| * @unit: FDISK_SIZEUNIT_* |
| * |
| * Sets unit for SIZE output field (see fdisk_partition_to_string()). |
| * |
| * Returns: 0 on success, <0 on error. |
| */ |
| int fdisk_set_size_unit(struct fdisk_context *cxt, int unit) |
| { |
| assert(cxt); |
| cxt->sizeunit = unit; |
| return 0; |
| } |
| |
| /** |
| * fdisk_get_size_unit: |
| * @cxt: fdisk context |
| * |
| * Gets unit for SIZE output field (see fdisk_partition_to_string()). |
| * |
| * Returns: unit |
| */ |
| int fdisk_get_size_unit(struct fdisk_context *cxt) |
| { |
| assert(cxt); |
| return cxt->sizeunit; |
| } |
| |
| /** |
| * fdisk_get_nsectors: |
| * @cxt: context |
| * |
| * Returns: size of the device in logical sectors. |
| */ |
| fdisk_sector_t fdisk_get_nsectors(struct fdisk_context *cxt) |
| { |
| assert(cxt); |
| return cxt->total_sectors; |
| } |
| |
| /** |
| * fdisk_get_devname: |
| * @cxt: context |
| * |
| * Returns: device name. |
| */ |
| const char *fdisk_get_devname(struct fdisk_context *cxt) |
| { |
| assert(cxt); |
| return cxt->dev_path; |
| } |
| |
| /** |
| * fdisk_get_devfd: |
| * @cxt: context |
| * |
| * Returns: device file descriptor. |
| */ |
| int fdisk_get_devfd(struct fdisk_context *cxt) |
| { |
| assert(cxt); |
| return cxt->dev_fd; |
| } |
| |
| /** |
| * fdisk_get_geom_heads: |
| * @cxt: context |
| * |
| * Returns: number of geometry heads. |
| */ |
| unsigned int fdisk_get_geom_heads(struct fdisk_context *cxt) |
| { |
| assert(cxt); |
| return cxt->geom.heads; |
| } |
| /** |
| * fdisk_get_geom_sectors: |
| * @cxt: context |
| * |
| * Returns: number of geometry sectors. |
| */ |
| fdisk_sector_t fdisk_get_geom_sectors(struct fdisk_context *cxt) |
| { |
| assert(cxt); |
| return cxt->geom.sectors; |
| |
| } |
| |
| /** |
| * fdisk_get_geom_cylinders: |
| * @cxt: context |
| * |
| * Returns: number of geometry cylinders |
| */ |
| fdisk_sector_t fdisk_get_geom_cylinders(struct fdisk_context *cxt) |
| { |
| assert(cxt); |
| return cxt->geom.cylinders; |
| } |
| |
| int fdisk_missing_geometry(struct fdisk_context *cxt) |
| { |
| int rc; |
| |
| if (!cxt || !cxt->label) |
| return 0; |
| |
| rc = (fdisk_label_require_geometry(cxt->label) && |
| (!cxt->geom.heads || !cxt->geom.sectors |
| || !cxt->geom.cylinders)); |
| |
| if (rc && !fdisk_is_listonly(cxt)) |
| fdisk_warnx(cxt, _("Incomplete geometry setting.")); |
| |
| return rc; |
| } |
| |