summaryrefslogtreecommitdiffstats
path: root/libfdisk/src
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-14 19:10:49 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-14 19:10:49 +0000
commitcfe5e3905201349e9cf3f95d52ff4bd100bde37d (patch)
treed0baf160cbee3195249d095f85e52d20c21acf02 /libfdisk/src
parentInitial commit. (diff)
downloadutil-linux-cfe5e3905201349e9cf3f95d52ff4bd100bde37d.tar.xz
util-linux-cfe5e3905201349e9cf3f95d52ff4bd100bde37d.zip
Adding upstream version 2.39.3.upstream/2.39.3
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'libfdisk/src')
-rw-r--r--libfdisk/src/Makemodule.am140
-rw-r--r--libfdisk/src/alignment.c721
-rw-r--r--libfdisk/src/ask.c1077
-rw-r--r--libfdisk/src/bsd.c1065
-rw-r--r--libfdisk/src/context.c1555
-rw-r--r--libfdisk/src/dos.c2912
-rw-r--r--libfdisk/src/fdiskP.h540
-rw-r--r--libfdisk/src/field.c88
-rw-r--r--libfdisk/src/gpt.c3381
-rw-r--r--libfdisk/src/init.c62
-rw-r--r--libfdisk/src/item.c255
-rw-r--r--libfdisk/src/iter.c82
-rw-r--r--libfdisk/src/label.c752
-rw-r--r--libfdisk/src/libfdisk.h.in895
-rw-r--r--libfdisk/src/libfdisk.sym322
-rw-r--r--libfdisk/src/partition.c1627
-rw-r--r--libfdisk/src/parttype.c569
-rw-r--r--libfdisk/src/script.c1823
-rw-r--r--libfdisk/src/sgi.c1210
-rw-r--r--libfdisk/src/sun.c1192
-rw-r--r--libfdisk/src/table.c797
-rw-r--r--libfdisk/src/test.c59
-rw-r--r--libfdisk/src/utils.c214
-rw-r--r--libfdisk/src/version.c125
-rw-r--r--libfdisk/src/wipe.c213
25 files changed, 21676 insertions, 0 deletions
diff --git a/libfdisk/src/Makemodule.am b/libfdisk/src/Makemodule.am
new file mode 100644
index 0000000..9bd64c1
--- /dev/null
+++ b/libfdisk/src/Makemodule.am
@@ -0,0 +1,140 @@
+
+# libfdisk.h is generated, so it's stored in builddir!
+fdiskincdir = $(includedir)/libfdisk
+nodist_fdiskinc_HEADERS = libfdisk/src/libfdisk.h
+
+usrlib_exec_LTLIBRARIES += libfdisk.la
+libfdisk_la_SOURCES = \
+ include/list.h \
+ \
+ libfdisk/src/fdiskP.h \
+ libfdisk/src/init.c \
+ libfdisk/src/field.c \
+ libfdisk/src/item.c \
+ libfdisk/src/test.c \
+ libfdisk/src/ask.c \
+ libfdisk/src/alignment.c \
+ libfdisk/src/label.c \
+ libfdisk/src/utils.c \
+ libfdisk/src/context.c \
+ libfdisk/src/parttype.c \
+ libfdisk/src/partition.c \
+ libfdisk/src/table.c \
+ libfdisk/src/iter.c \
+ libfdisk/src/script.c \
+ libfdisk/src/version.c \
+ libfdisk/src/wipe.c \
+ \
+ libfdisk/src/sun.c \
+ libfdisk/src/sgi.c \
+ libfdisk/src/dos.c \
+ libfdisk/src/bsd.c \
+ libfdisk/src/gpt.c
+
+libfdisk_la_LIBADD = libcommon.la libuuid.la
+
+libfdisk_la_CFLAGS = \
+ $(AM_CFLAGS) \
+ $(SOLIB_CFLAGS) \
+ -I$(ul_libuuid_incdir) \
+ -I$(ul_libfdisk_incdir) \
+ -I$(top_srcdir)/libfdisk/src
+
+EXTRA_libfdisk_la_DEPENDENCIES = \
+ libfdisk/src/libfdisk.sym
+
+libfdisk_la_LDFLAGS = $(SOLIB_LDFLAGS)
+if HAVE_VSCRIPT
+libfdisk_la_LDFLAGS += $(VSCRIPT_LDFLAGS),$(top_srcdir)/libfdisk/src/libfdisk.sym
+endif
+libfdisk_la_LDFLAGS += -version-info $(LIBFDISK_VERSION_INFO)
+
+
+if BUILD_LIBBLKID
+libfdisk_la_LIBADD += libblkid.la
+libfdisk_la_CFLAGS += -I$(ul_libblkid_incdir)
+endif
+
+EXTRA_DIST += \
+ libfdisk/src/libfdisk.sym
+
+if BUILD_LIBFDISK_TESTS
+check_PROGRAMS += \
+ test_fdisk_ask \
+ test_fdisk_gpt \
+ test_fdisk_script \
+ test_fdisk_utils \
+ test_fdisk_version \
+ test_fdisk_item
+
+libfdisk_tests_cflags = -DTEST_PROGRAM $(libfdisk_la_CFLAGS) $(NO_UNUSED_WARN_CFLAGS)
+libfdisk_tests_ldflags = libuuid.la -static
+libfdisk_tests_ldadd = libfdisk.la $(LDADD)
+
+if BUILD_LIBBLKID
+libfdisk_tests_ldflags += libblkid.la
+endif
+
+test_fdisk_ask_SOURCES = libfdisk/src/ask.c
+test_fdisk_ask_CFLAGS = $(libfdisk_tests_cflags)
+test_fdisk_ask_LDFLAGS = $(libfdisk_tests_ldflags)
+test_fdisk_ask_LDADD = $(libfdisk_tests_ldadd)
+
+test_fdisk_gpt_SOURCES = libfdisk/src/gpt.c
+test_fdisk_gpt_CFLAGS = $(libfdisk_tests_cflags)
+test_fdisk_gpt_LDFLAGS = $(libfdisk_tests_ldflags)
+test_fdisk_gpt_LDADD = $(libfdisk_tests_ldadd)
+
+test_fdisk_utils_SOURCES = libfdisk/src/utils.c
+test_fdisk_utils_CFLAGS = $(libfdisk_tests_cflags)
+test_fdisk_utils_LDFLAGS = $(libfdisk_tests_ldflags)
+test_fdisk_utils_LDADD = $(libfdisk_tests_ldadd)
+
+test_fdisk_script_SOURCES = libfdisk/src/script.c
+test_fdisk_script_CFLAGS = $(libfdisk_tests_cflags)
+test_fdisk_script_LDFLAGS = $(libfdisk_tests_ldflags)
+test_fdisk_script_LDADD = $(libfdisk_tests_ldadd)
+
+if FUZZING_ENGINE
+check_PROGRAMS += test_fdisk_script_fuzz
+
+# https://google.github.io/oss-fuzz/getting-started/new-project-guide/#Requirements
+nodist_EXTRA_test_fdisk_script_fuzz_SOURCES = dummy.cxx
+
+test_fdisk_script_fuzz_SOURCES = libfdisk/src/script.c
+test_fdisk_script_fuzz_CFLAGS = -DFUZZ_TARGET $(libfdisk_la_CFLAGS) $(NO_UNUSED_WARN_CFLAGS)
+test_fdisk_script_fuzz_LDFLAGS = $(libfdisk_tests_ldflags) -lpthread
+test_fdisk_script_fuzz_LDADD = $(libfdisk_tests_ldadd) $(LIB_FUZZING_ENGINE)
+endif
+
+test_fdisk_version_SOURCES = libfdisk/src/version.c
+test_fdisk_version_CFLAGS = $(libfdisk_tests_cflags)
+test_fdisk_version_LDFLAGS = $(libfdisk_tests_ldflags)
+test_fdisk_version_LDADD = $(libfdisk_tests_ldadd)
+
+test_fdisk_item_SOURCES = libfdisk/src/item.c
+test_fdisk_item_CFLAGS = $(libfdisk_tests_cflags)
+test_fdisk_item_LDFLAGS = $(libfdisk_tests_ldflags)
+test_fdisk_item_LDADD = $(libfdisk_tests_ldadd)
+
+endif # BUILD_LIBFDISK_TESTS
+
+
+# move lib from $(usrlib_execdir) to $(libdir) if needed
+install-exec-hook-libfdisk:
+ if test "$(usrlib_execdir)" != "$(libdir)" -a -f "$(DESTDIR)$(usrlib_execdir)/libfdisk.so"; then \
+ $(MKDIR_P) $(DESTDIR)$(libdir); \
+ mv $(DESTDIR)$(usrlib_execdir)/libfdisk.so.* $(DESTDIR)$(libdir); \
+ so_img_name=$$(readlink $(DESTDIR)$(usrlib_execdir)/libfdisk.so); \
+ so_img_rel_target=$$(echo $(usrlib_execdir) | sed 's,\(^/\|\)[^/][^/]*,..,g'); \
+ (cd $(DESTDIR)$(usrlib_execdir) && \
+ rm -f libfdisk.so && \
+ $(LN_S) $$so_img_rel_target$(libdir)/$$so_img_name libfdisk.so); \
+ fi
+
+uninstall-hook-libfdisk:
+ rm -f $(DESTDIR)$(libdir)/libfdisk.so*
+
+INSTALL_EXEC_HOOKS += install-exec-hook-libfdisk
+UNINSTALL_HOOKS += uninstall-hook-libfdisk
+
diff --git a/libfdisk/src/alignment.c b/libfdisk/src/alignment.c
new file mode 100644
index 0000000..3ae7219
--- /dev/null
+++ b/libfdisk/src/alignment.c
@@ -0,0 +1,721 @@
+
+#ifdef HAVE_LIBBLKID
+#include <blkid.h>
+#endif
+#include "blkdev.h"
+
+#include "fdiskP.h"
+
+/**
+ * SECTION: alignment
+ * @title: Alignment
+ * @short_description: functions to align partitions and work with disk topology and geometry
+ *
+ * The libfdisk aligns the end of the partitions to make it possible to align
+ * the next partition to the "grain" (see fdisk_get_grain_size()). The grain is
+ * usually 1MiB (or more for devices where optimal I/O is greater than 1MiB).
+ *
+ * It means that the library does not align strictly to physical sector size
+ * (or minimal or optimal I/O), but it uses greater granularity. It makes
+ * partition tables more portable. If you copy disk layout from 512-sector to
+ * 4K-sector device, all partitions are still aligned to physical sectors.
+ *
+ * This unified concept also makes partition tables more user friendly, all
+ * tables look same, LBA of the first partition is 2048 sectors everywhere, etc.
+ *
+ * It's recommended to not change any alignment or device properties. All is
+ * initialized by default by fdisk_assign_device().
+ *
+ * Note that terminology used by libfdisk is:
+ * - device properties: I/O limits (topology), geometry, sector size, ...
+ * - alignment: first, last LBA, grain, ...
+ *
+ * The alignment setting may be modified by disk label driver.
+ */
+
+/*
+ * Alignment according to logical granularity (usually 1MiB)
+ */
+static int lba_is_aligned(struct fdisk_context *cxt, uintmax_t lba)
+{
+ unsigned long granularity = max(cxt->phy_sector_size, cxt->min_io_size);
+ uintmax_t offset;
+
+ if (cxt->grain > granularity)
+ granularity = cxt->grain;
+
+ offset = (lba * cxt->sector_size) % granularity;
+
+ return !((granularity + cxt->alignment_offset - offset) % granularity);
+}
+
+/*
+ * Alignment according to physical device topology (usually minimal i/o size)
+ */
+static int lba_is_phy_aligned(struct fdisk_context *cxt, fdisk_sector_t lba)
+{
+ unsigned long granularity = max(cxt->phy_sector_size, cxt->min_io_size);
+ uintmax_t offset = (lba * cxt->sector_size) % granularity;
+
+ return !((granularity + cxt->alignment_offset - offset) % granularity);
+}
+
+/**
+ * fdisk_align_lba:
+ * @cxt: context
+ * @lba: address to align
+ * @direction: FDISK_ALIGN_{UP,DOWN,NEAREST}
+ *
+ * This function aligns @lba to the "grain" (see fdisk_get_grain_size()). If the
+ * device uses alignment offset then the result is moved according the offset
+ * to be on the physical boundary.
+ *
+ * Returns: alignment LBA.
+ */
+fdisk_sector_t fdisk_align_lba(struct fdisk_context *cxt, fdisk_sector_t lba, int direction)
+{
+ fdisk_sector_t res;
+
+ if (lba_is_aligned(cxt, lba))
+ res = lba;
+ else {
+ fdisk_sector_t sects_in_phy = cxt->grain / cxt->sector_size;
+
+ if (lba < cxt->first_lba)
+ res = cxt->first_lba;
+
+ else if (direction == FDISK_ALIGN_UP)
+ res = ((lba + sects_in_phy) / sects_in_phy) * sects_in_phy;
+
+ else if (direction == FDISK_ALIGN_DOWN)
+ res = (lba / sects_in_phy) * sects_in_phy;
+
+ else /* FDISK_ALIGN_NEAREST */
+ res = ((lba + sects_in_phy / 2) / sects_in_phy) * sects_in_phy;
+
+ if (cxt->alignment_offset && !lba_is_aligned(cxt, res) &&
+ res > cxt->alignment_offset / cxt->sector_size) {
+ /*
+ * apply alignment_offset
+ *
+ * On disk with alignment compensation physical blocks starts
+ * at LBA < 0 (usually LBA -1). It means we have to move LBA
+ * according the offset to be on the physical boundary.
+ */
+ /* fprintf(stderr, "LBA: %llu apply alignment_offset\n", res); */
+ res -= (max(cxt->phy_sector_size, cxt->min_io_size) -
+ cxt->alignment_offset) / cxt->sector_size;
+
+ if (direction == FDISK_ALIGN_UP && res < lba)
+ res += sects_in_phy;
+ }
+ }
+/*
+ if (lba != res)
+ DBG(CXT, ul_debugobj(cxt, "LBA %12ju aligned-%s %12ju [grain=%lus]",
+ (uintmax_t) lba,
+ direction == FDISK_ALIGN_UP ? "up " :
+ direction == FDISK_ALIGN_DOWN ? "down" : "near",
+ (uintmax_t) res,
+ cxt->grain / cxt->sector_size));
+ else
+ DBG(CXT, ul_debugobj(cxt, "LBA %12ju already aligned", (uintmax_t)lba));
+*/
+ return res;
+}
+
+/**
+ * fdisk_align_lba_in_range:
+ * @cxt: context
+ * @lba: LBA
+ * @start: range start
+ * @stop: range stop
+ *
+ * Align @lba, the result has to be between @start and @stop
+ *
+ * Returns: aligned LBA
+ */
+fdisk_sector_t fdisk_align_lba_in_range(struct fdisk_context *cxt,
+ fdisk_sector_t lba, fdisk_sector_t start, fdisk_sector_t stop)
+{
+ fdisk_sector_t res;
+
+ /*DBG(CXT, ul_debugobj(cxt, "LBA: align in range <%ju..%ju>", (uintmax_t) start, (uintmax_t) stop));*/
+
+ if (start + (cxt->grain / cxt->sector_size) <= stop) {
+ start = fdisk_align_lba(cxt, start, FDISK_ALIGN_UP);
+ stop = fdisk_align_lba(cxt, stop, FDISK_ALIGN_DOWN);
+ }
+
+ if (start + (cxt->grain / cxt->sector_size) > stop) {
+ DBG(CXT, ul_debugobj(cxt, "LBA: area smaller than grain, don't align"));
+ res = lba;
+ goto done;
+ }
+
+ lba = fdisk_align_lba(cxt, lba, FDISK_ALIGN_NEAREST);
+
+ if (lba < start)
+ res = start;
+ else if (lba > stop)
+ res = stop;
+ else
+ res = lba;
+done:
+ DBG(CXT, ul_debugobj(cxt, "%ju in range <%ju..%ju> aligned to %ju",
+ (uintmax_t) lba,
+ (uintmax_t) start,
+ (uintmax_t) stop,
+ (uintmax_t) res));
+ return res;
+}
+
+/**
+ * fdisk_lba_is_phy_aligned:
+ * @cxt: context
+ * @lba: LBA to check
+ *
+ * Check if the @lba is aligned to physical sector boundary.
+ *
+ * Returns: 1 if aligned.
+ */
+int fdisk_lba_is_phy_aligned(struct fdisk_context *cxt, fdisk_sector_t lba)
+{
+ return lba_is_phy_aligned(cxt, lba);
+}
+
+static unsigned long get_sector_size(struct fdisk_context *cxt)
+{
+ int sect_sz;
+
+ if (!fdisk_is_regfile(cxt) &&
+ !blkdev_get_sector_size(cxt->dev_fd, &sect_sz))
+ return (unsigned long) sect_sz;
+
+ return DEFAULT_SECTOR_SIZE;
+}
+
+static void recount_geometry(struct fdisk_context *cxt)
+{
+ if (!cxt->geom.heads)
+ cxt->geom.heads = 255;
+ if (!cxt->geom.sectors)
+ cxt->geom.sectors = 63;
+
+ cxt->geom.cylinders = cxt->total_sectors /
+ (cxt->geom.heads * cxt->geom.sectors);
+}
+
+/**
+ * fdisk_override_geometry:
+ * @cxt: fdisk context
+ * @cylinders: user specified cylinders
+ * @heads: user specified heads
+ * @sectors: user specified sectors
+ *
+ * Overrides auto-discovery. The function fdisk_reset_device_properties()
+ * restores the original setting.
+ *
+ * The difference between fdisk_override_geometry() and fdisk_save_user_geometry()
+ * is that saved user geometry is persistent setting and it's applied always
+ * when device is assigned to the context or device properties are reset.
+ *
+ * Returns: 0 on success, < 0 on error.
+ */
+int fdisk_override_geometry(struct fdisk_context *cxt,
+ unsigned int cylinders,
+ unsigned int heads,
+ unsigned int sectors)
+{
+ if (!cxt)
+ return -EINVAL;
+ if (heads)
+ cxt->geom.heads = heads;
+ if (sectors)
+ cxt->geom.sectors = sectors;
+
+ if (cylinders)
+ cxt->geom.cylinders = cylinders;
+ else
+ recount_geometry(cxt);
+
+ fdisk_reset_alignment(cxt);
+
+ DBG(CXT, ul_debugobj(cxt, "override C/H/S: %u/%u/%u",
+ (unsigned) cxt->geom.cylinders,
+ (unsigned) cxt->geom.heads,
+ (unsigned) cxt->geom.sectors));
+
+ return 0;
+}
+
+/**
+ * fdisk_save_user_geometry:
+ * @cxt: context
+ * @cylinders: C
+ * @heads: H
+ * @sectors: S
+ *
+ * Save user defined geometry to use it for partitioning.
+ *
+ * The user properties are applied by fdisk_assign_device() or
+ * fdisk_reset_device_properties().
+
+ * Returns: <0 on error, 0 on success.
+ */
+int fdisk_save_user_geometry(struct fdisk_context *cxt,
+ unsigned int cylinders,
+ unsigned int heads,
+ unsigned int sectors)
+{
+ if (!cxt)
+ return -EINVAL;
+
+ if (heads)
+ cxt->user_geom.heads = heads > 256 ? 0 : heads;
+ if (sectors)
+ cxt->user_geom.sectors = sectors >= 64 ? 0 : sectors;
+ if (cylinders)
+ cxt->user_geom.cylinders = cylinders;
+
+ DBG(CXT, ul_debugobj(cxt, "user C/H/S: %u/%u/%u",
+ (unsigned) cxt->user_geom.cylinders,
+ (unsigned) cxt->user_geom.heads,
+ (unsigned) cxt->user_geom.sectors));
+
+ return 0;
+}
+
+/**
+ * fdisk_save_user_sector_size:
+ * @cxt: context
+ * @phy: physical sector size
+ * @log: logical sector size
+ *
+ * Save user defined sector sizes to use it for partitioning.
+ *
+ * The user properties are applied by fdisk_assign_device() or
+ * fdisk_reset_device_properties().
+ *
+ * Returns: <0 on error, 0 on success.
+ */
+int fdisk_save_user_sector_size(struct fdisk_context *cxt,
+ unsigned int phy,
+ unsigned int log)
+{
+ if (!cxt)
+ return -EINVAL;
+
+ DBG(CXT, ul_debugobj(cxt, "user phy/log sector size: %u/%u", phy, log));
+
+ cxt->user_pyh_sector = phy;
+ cxt->user_log_sector = log;
+
+ return 0;
+}
+
+/**
+ * fdisk_save_user_grain:
+ * @cxt: context
+ * @grain: size in bytes (>= 512, multiple of 512)
+ *
+ * Save user define grain size. The size is used to align partitions.
+ *
+ * The default is 1MiB (or optimal I/O size if greater than 1MiB). It's strongly
+ * recommended to use the default.
+ *
+ * The smallest possible granularity for partitioning is physical sector size
+ * (or minimal I/O size; the bigger number win). If the user's @grain size is
+ * too small then the smallest possible granularity is used. It means
+ * fdisk_save_user_grain(cxt, 512) forces libfdisk to use grain as small as
+ * possible.
+ *
+ * The setting is applied by fdisk_assign_device() or
+ * fdisk_reset_device_properties().
+ *
+ * Returns: <0 on error, 0 on success.
+ */
+int fdisk_save_user_grain(struct fdisk_context *cxt, unsigned long grain)
+{
+ if (!cxt || grain % 512)
+ return -EINVAL;
+
+ DBG(CXT, ul_debugobj(cxt, "user grain size: %lu", grain));
+ cxt->user_grain = grain;
+ return 0;
+}
+
+/**
+ * fdisk_has_user_device_properties:
+ * @cxt: context
+ *
+ * Returns: 1 if user specified any properties
+ */
+int fdisk_has_user_device_properties(struct fdisk_context *cxt)
+{
+ return (cxt->user_pyh_sector || cxt->user_log_sector ||
+ cxt->user_grain ||
+ fdisk_has_user_device_geometry(cxt));
+}
+
+int fdisk_has_user_device_geometry(struct fdisk_context *cxt)
+{
+ return (cxt->user_geom.heads || cxt->user_geom.sectors || cxt->user_geom.cylinders);
+}
+
+int fdisk_apply_user_device_properties(struct fdisk_context *cxt)
+{
+ if (!cxt)
+ return -EINVAL;
+
+ DBG(CXT, ul_debugobj(cxt, "applying user device properties"));
+
+ if (cxt->user_pyh_sector)
+ cxt->phy_sector_size = cxt->user_pyh_sector;
+ if (cxt->user_log_sector) {
+ uint64_t old_total = cxt->total_sectors;
+ uint64_t old_secsz = cxt->sector_size;
+
+ cxt->sector_size = cxt->min_io_size =
+ cxt->io_size = cxt->user_log_sector;
+
+ if (cxt->sector_size != old_secsz) {
+ cxt->total_sectors = (old_total * (old_secsz/512)) / (cxt->sector_size >> 9);
+ DBG(CXT, ul_debugobj(cxt, "new total sectors: %ju", (uintmax_t)cxt->total_sectors));
+ }
+ }
+
+ if (cxt->user_geom.heads)
+ cxt->geom.heads = cxt->user_geom.heads;
+ if (cxt->user_geom.sectors)
+ cxt->geom.sectors = cxt->user_geom.sectors;
+
+ if (cxt->user_geom.cylinders)
+ cxt->geom.cylinders = cxt->user_geom.cylinders;
+ else if (cxt->user_geom.heads || cxt->user_geom.sectors)
+ recount_geometry(cxt);
+
+ fdisk_reset_alignment(cxt);
+
+ if (cxt->user_grain) {
+ unsigned long granularity = max(cxt->phy_sector_size, cxt->min_io_size);
+
+ cxt->grain = cxt->user_grain < granularity ? granularity : cxt->user_grain;
+ DBG(CXT, ul_debugobj(cxt, "new grain: %lu", cxt->grain));
+ }
+
+ if (cxt->firstsector_bufsz != cxt->sector_size)
+ fdisk_read_firstsector(cxt);
+
+ DBG(CXT, ul_debugobj(cxt, "new C/H/S: %u/%u/%u",
+ (unsigned) cxt->geom.cylinders,
+ (unsigned) cxt->geom.heads,
+ (unsigned) cxt->geom.sectors));
+ DBG(CXT, ul_debugobj(cxt, "new log/phy sector size: %u/%u",
+ (unsigned) cxt->sector_size,
+ (unsigned) cxt->phy_sector_size));
+
+ return 0;
+}
+
+void fdisk_zeroize_device_properties(struct fdisk_context *cxt)
+{
+ assert(cxt);
+
+ cxt->io_size = 0;
+ cxt->optimal_io_size = 0;
+ cxt->min_io_size = 0;
+ cxt->phy_sector_size = 0;
+ cxt->sector_size = 0;
+ cxt->alignment_offset = 0;
+ cxt->grain = 0;
+ cxt->first_lba = 0;
+ cxt->last_lba = 0;
+ cxt->total_sectors = 0;
+
+ memset(&cxt->geom, 0, sizeof(struct fdisk_geometry));
+}
+
+/**
+ * fdisk_reset_device_properties:
+ * @cxt: context
+ *
+ * Resets and discovery topology (I/O limits), geometry, re-read the first
+ * rector on the device if necessary and apply user device setting (geometry
+ * and sector size), then initialize alignment according to label driver (see
+ * fdisk_reset_alignment()).
+ *
+ * You don't have to use this function by default, fdisk_assign_device() is
+ * smart enough to initialize all necessary setting.
+ *
+ * Returns: 0 on success, <0 on error.
+ */
+int fdisk_reset_device_properties(struct fdisk_context *cxt)
+{
+ int rc;
+
+ if (!cxt)
+ return -EINVAL;
+
+ DBG(CXT, ul_debugobj(cxt, "*** resetting device properties"));
+
+ fdisk_zeroize_device_properties(cxt);
+ fdisk_discover_topology(cxt);
+ fdisk_discover_geometry(cxt);
+
+ rc = fdisk_read_firstsector(cxt);
+ if (rc)
+ return rc;
+
+ fdisk_apply_user_device_properties(cxt);
+ return 0;
+}
+
+/*
+ * Generic (label independent) geometry
+ */
+int fdisk_discover_geometry(struct fdisk_context *cxt)
+{
+ fdisk_sector_t nsects = 0;
+ unsigned int h = 0, s = 0;
+
+ assert(cxt);
+ assert(cxt->geom.heads == 0);
+
+ DBG(CXT, ul_debugobj(cxt, "%s: discovering geometry...", cxt->dev_path));
+
+ if (fdisk_is_regfile(cxt))
+ cxt->total_sectors = cxt->dev_st.st_size / cxt->sector_size;
+ else {
+ /* get number of 512-byte sectors, and convert it the real sectors */
+ if (!blkdev_get_sectors(cxt->dev_fd, (unsigned long long *) &nsects))
+ cxt->total_sectors = (nsects / (cxt->sector_size >> 9));
+
+ /* what the kernel/bios thinks the geometry is */
+ blkdev_get_geometry(cxt->dev_fd, &h, &s);
+ }
+
+ DBG(CXT, ul_debugobj(cxt, "total sectors: %ju (ioctl=%ju)",
+ (uintmax_t) cxt->total_sectors,
+ (uintmax_t) nsects));
+
+ cxt->geom.cylinders = 0;
+ cxt->geom.heads = h;
+ cxt->geom.sectors = s;
+
+ /* obtained heads and sectors */
+ recount_geometry(cxt);
+
+ DBG(CXT, ul_debugobj(cxt, "result: C/H/S: %u/%u/%u",
+ (unsigned) cxt->geom.cylinders,
+ (unsigned) cxt->geom.heads,
+ (unsigned) cxt->geom.sectors));
+ return 0;
+}
+
+int fdisk_discover_topology(struct fdisk_context *cxt)
+{
+#ifdef HAVE_LIBBLKID
+ blkid_probe pr;
+#endif
+ assert(cxt);
+ assert(cxt->sector_size == 0);
+
+ DBG(CXT, ul_debugobj(cxt, "%s: discovering topology...", cxt->dev_path));
+#ifdef HAVE_LIBBLKID
+ DBG(CXT, ul_debugobj(cxt, "initialize libblkid prober"));
+
+ pr = blkid_new_probe();
+ if (pr && blkid_probe_set_device(pr, cxt->dev_fd, 0, 0) == 0) {
+ blkid_topology tp = blkid_probe_get_topology(pr);
+
+ if (tp) {
+ cxt->min_io_size = blkid_topology_get_minimum_io_size(tp);
+ cxt->optimal_io_size = blkid_topology_get_optimal_io_size(tp);
+ cxt->phy_sector_size = blkid_topology_get_physical_sector_size(tp);
+ cxt->alignment_offset = blkid_topology_get_alignment_offset(tp);
+
+ /* I/O size used by fdisk */
+ cxt->io_size = cxt->optimal_io_size;
+ if (!cxt->io_size)
+ /* optimal I/O is optional, default to minimum IO */
+ cxt->io_size = cxt->min_io_size;
+
+ if (cxt->io_size && cxt->phy_sector_size) {
+ if (cxt->io_size == 33553920) {
+ /* 33553920 (32 MiB - 512) is always a controller error */
+ DBG(CXT, ul_debugobj(cxt, "ignore bad I/O size 33553920"));
+ cxt->io_size = cxt->phy_sector_size;
+ } else if ((cxt->io_size % cxt->phy_sector_size) != 0) {
+ /* ignore optimal I/O if not aligned to phy.sector size */
+ DBG(CXT, ul_debugobj(cxt, "ignore misaligned I/O size"));
+ cxt->io_size = cxt->phy_sector_size;
+ }
+ }
+
+ }
+ }
+ blkid_free_probe(pr);
+#endif
+
+ cxt->sector_size = get_sector_size(cxt);
+ if (!cxt->phy_sector_size) /* could not discover physical size */
+ cxt->phy_sector_size = cxt->sector_size;
+
+ /* no blkid or error, use default values */
+ if (!cxt->min_io_size)
+ cxt->min_io_size = cxt->sector_size;
+ if (!cxt->io_size)
+ cxt->io_size = cxt->sector_size;
+
+ DBG(CXT, ul_debugobj(cxt, "result: log/phy sector size: %ld/%ld",
+ cxt->sector_size, cxt->phy_sector_size));
+ DBG(CXT, ul_debugobj(cxt, "result: fdisk/optimal/minimal io: %ld/%ld/%ld",
+ cxt->io_size, cxt->optimal_io_size, cxt->min_io_size));
+ return 0;
+}
+
+static int has_topology(struct fdisk_context *cxt)
+{
+ /*
+ * Assume that the device provides topology info if
+ * optimal_io_size is set or alignment_offset is set or
+ * minimum_io_size is not power of 2.
+ */
+ if (cxt &&
+ (cxt->optimal_io_size ||
+ cxt->alignment_offset ||
+ !is_power_of_2(cxt->min_io_size)))
+ return 1;
+ return 0;
+}
+
+/*
+ * The LBA of the first partition is based on the device geometry and topology.
+ * This offset is generic (and recommended) for all labels.
+ *
+ * Returns: 0 on error or number of logical sectors.
+ */
+static fdisk_sector_t topology_get_first_lba(struct fdisk_context *cxt)
+{
+ fdisk_sector_t x = 0, res;
+
+ if (!cxt)
+ return 0;
+
+ if (!cxt->io_size)
+ fdisk_discover_topology(cxt);
+
+ /*
+ * Align the begin of partitions to:
+ *
+ * a) topology
+ * a2) alignment offset
+ * a1) or physical sector (minimal_io_size, aka "grain")
+ *
+ * b) or default to 1MiB (2048 sectors, Windows Vista default)
+ *
+ * c) or for very small devices use 1 phy.sector
+ */
+ if (has_topology(cxt)) {
+ if (cxt->alignment_offset)
+ x = cxt->alignment_offset;
+ else if (cxt->io_size > 2048 * 512)
+ x = cxt->io_size;
+ }
+ /* default to 1MiB */
+ if (!x)
+ x = 2048 * 512;
+
+ res = x / cxt->sector_size;
+
+ /* don't use huge offset on small devices */
+ if (cxt->total_sectors <= res * 4)
+ res = cxt->phy_sector_size / cxt->sector_size;
+
+ return res;
+}
+
+static unsigned long topology_get_grain(struct fdisk_context *cxt)
+{
+ unsigned long res;
+
+ if (!cxt)
+ return 0;
+
+ if (!cxt->io_size)
+ fdisk_discover_topology(cxt);
+
+ res = cxt->io_size;
+
+ /* use 1MiB grain always when possible */
+ if (res < 2048 * 512)
+ res = 2048 * 512;
+
+ /* don't use huge grain on small devices */
+ if (cxt->total_sectors <= (res * 4 / cxt->sector_size))
+ res = cxt->phy_sector_size;
+
+ return res;
+}
+
+/* apply label alignment setting to the context -- if not sure use
+ * fdisk_reset_alignment()
+ */
+int fdisk_apply_label_device_properties(struct fdisk_context *cxt)
+{
+ int rc = 0;
+
+ if (cxt->label && cxt->label->op->reset_alignment) {
+ DBG(CXT, ul_debugobj(cxt, "applying label device properties..."));
+ rc = cxt->label->op->reset_alignment(cxt);
+ }
+ return rc;
+}
+
+/**
+ * fdisk_reset_alignment:
+ * @cxt: fdisk context
+ *
+ * Resets alignment setting to the default and label specific values. This
+ * function does not change device properties (I/O limits, geometry etc.).
+ *
+ * Returns: 0 on success, < 0 in case of error.
+ */
+int fdisk_reset_alignment(struct fdisk_context *cxt)
+{
+ int rc = 0;
+
+ if (!cxt)
+ return -EINVAL;
+
+ DBG(CXT, ul_debugobj(cxt, "resetting alignment..."));
+
+ /* default */
+ cxt->grain = topology_get_grain(cxt);
+ cxt->first_lba = topology_get_first_lba(cxt);
+ cxt->last_lba = cxt->total_sectors - 1;
+
+ /* overwrite default by label stuff */
+ rc = fdisk_apply_label_device_properties(cxt);
+
+ DBG(CXT, ul_debugobj(cxt, "alignment reset to: "
+ "first LBA=%ju, last LBA=%ju, grain=%lu [rc=%d]",
+ (uintmax_t) cxt->first_lba, (uintmax_t) cxt->last_lba,
+ cxt->grain, rc));
+ return rc;
+}
+
+
+fdisk_sector_t fdisk_scround(struct fdisk_context *cxt, fdisk_sector_t num)
+{
+ fdisk_sector_t un = fdisk_get_units_per_sector(cxt);
+ return (num + un - 1) / un;
+}
+
+fdisk_sector_t fdisk_cround(struct fdisk_context *cxt, fdisk_sector_t num)
+{
+ return fdisk_use_cylinders(cxt) ?
+ (num / fdisk_get_units_per_sector(cxt)) + 1 : num;
+}
+
diff --git a/libfdisk/src/ask.c b/libfdisk/src/ask.c
new file mode 100644
index 0000000..274f6ba
--- /dev/null
+++ b/libfdisk/src/ask.c
@@ -0,0 +1,1077 @@
+
+#include "strutils.h"
+#include "fdiskP.h"
+
+/**
+ * SECTION: ask
+ * @title: Ask
+ * @short_description: interface for dialog driven partitioning, warning and info messages
+ *
+ */
+
+static void fdisk_ask_menu_reset_items(struct fdisk_ask *ask);
+
+
+/**
+ * fdisk_set_ask:
+ * @cxt: context
+ * @ask_cb: callback
+ * @data: callback data
+ *
+ * Set callback for dialog driven partitioning and library warnings/errors.
+ *
+ * Returns: 0 on success, < 0 on error.
+ */
+int fdisk_set_ask(struct fdisk_context *cxt,
+ int (*ask_cb)(struct fdisk_context *, struct fdisk_ask *, void *),
+ void *data)
+{
+ assert(cxt);
+
+ cxt->ask_cb = ask_cb;
+ cxt->ask_data = data;
+ return 0;
+}
+
+struct fdisk_ask *fdisk_new_ask(void)
+{
+ struct fdisk_ask *ask = calloc(1, sizeof(struct fdisk_ask));
+
+ if (!ask)
+ return NULL;
+
+ DBG(ASK, ul_debugobj(ask, "alloc"));
+ ask->refcount = 1;
+ return ask;
+}
+
+void fdisk_reset_ask(struct fdisk_ask *ask)
+{
+ int refcount;
+
+ assert(ask);
+ free(ask->query);
+
+ DBG(ASK, ul_debugobj(ask, "reset"));
+ refcount = ask->refcount;
+
+ if (fdisk_is_ask(ask, MENU))
+ fdisk_ask_menu_reset_items(ask);
+
+ memset(ask, 0, sizeof(*ask));
+ ask->refcount = refcount;
+}
+
+/**
+ * fdisk_ref_ask:
+ * @ask: ask instance
+ *
+ * Increments reference counter.
+ */
+void fdisk_ref_ask(struct fdisk_ask *ask)
+{
+ if (ask)
+ ask->refcount++;
+}
+
+
+/**
+ * fdisk_unref_ask:
+ * @ask: ask instance
+ *
+ * Decrements reference counter, on zero the @ask is automatically
+ * deallocated.
+ */
+void fdisk_unref_ask(struct fdisk_ask *ask)
+{
+ if (!ask)
+ return;
+ ask->refcount--;
+
+ if (ask->refcount <= 0) {
+ fdisk_reset_ask(ask);
+ DBG(ASK, ul_debugobj(ask, "free"));
+ free(ask);
+ }
+}
+
+/**
+ * fdisk_ask_get_query:
+ * @ask: ask instance
+ *
+ * Returns: pointer to dialog string.
+ */
+const char *fdisk_ask_get_query(struct fdisk_ask *ask)
+{
+ assert(ask);
+ return ask->query;
+}
+
+int fdisk_ask_set_query(struct fdisk_ask *ask, const char *str)
+{
+ assert(ask);
+ return strdup_to_struct_member(ask, query, str);
+}
+
+/**
+ * fdisk_ask_get_type:
+ * @ask: ask instance
+ *
+ * Returns: FDISK_ASKTYPE_*
+ */
+int fdisk_ask_get_type(struct fdisk_ask *ask)
+{
+ assert(ask);
+ return ask->type;
+}
+
+int fdisk_ask_set_type(struct fdisk_ask *ask, int type)
+{
+ assert(ask);
+ ask->type = type;
+ return 0;
+}
+
+int fdisk_do_ask(struct fdisk_context *cxt, struct fdisk_ask *ask)
+{
+ int rc;
+
+ assert(ask);
+ assert(cxt);
+
+ DBG(ASK, ul_debugobj(ask, "do_ask for '%s'",
+ ask->query ? ask->query :
+ ask->type == FDISK_ASKTYPE_INFO ? "info" :
+ ask->type == FDISK_ASKTYPE_WARNX ? "warnx" :
+ ask->type == FDISK_ASKTYPE_WARN ? "warn" :
+ "?nothing?"));
+
+ if (!fdisk_has_dialogs(cxt) &&
+ !(ask->type == FDISK_ASKTYPE_INFO ||
+ ask->type == FDISK_ASKTYPE_WARNX ||
+ ask->type == FDISK_ASKTYPE_WARN)) {
+ DBG(ASK, ul_debugobj(ask, "dialogs disabled"));
+ return -EINVAL;
+ }
+
+ if (!cxt->ask_cb) {
+ DBG(ASK, ul_debugobj(ask, "no ask callback specified!"));
+ return -EINVAL;
+ }
+
+ rc = cxt->ask_cb(cxt, ask, cxt->ask_data);
+
+ DBG(ASK, ul_debugobj(ask, "do_ask done [rc=%d]", rc));
+ return rc;
+}
+
+#define is_number_ask(a) (fdisk_is_ask(a, NUMBER) || fdisk_is_ask(a, OFFSET))
+
+/**
+ * fdisk_ask_number_get_range:
+ * @ask: ask instance
+ *
+ * Returns: string with range (e.g. "1,3,5-10")
+ */
+const char *fdisk_ask_number_get_range(struct fdisk_ask *ask)
+{
+ assert(ask);
+ assert(is_number_ask(ask));
+ return ask->data.num.range;
+}
+
+int fdisk_ask_number_set_range(struct fdisk_ask *ask, const char *range)
+{
+ assert(ask);
+ assert(is_number_ask(ask));
+ ask->data.num.range = range;
+ return 0;
+}
+
+/**
+ * fdisk_ask_number_get_default:
+ * @ask: ask instance
+ *
+ * Returns: default number
+ *
+ */
+uint64_t fdisk_ask_number_get_default(struct fdisk_ask *ask)
+{
+ assert(ask);
+ assert(is_number_ask(ask));
+ return ask->data.num.dfl;
+}
+
+int fdisk_ask_number_set_default(struct fdisk_ask *ask, uint64_t dflt)
+{
+ assert(ask);
+ ask->data.num.dfl = dflt;
+ return 0;
+}
+
+/**
+ * fdisk_ask_number_get_low:
+ * @ask: ask instance
+ *
+ * Returns: minimal possible number when ask for numbers in range
+ */
+uint64_t fdisk_ask_number_get_low(struct fdisk_ask *ask)
+{
+ assert(ask);
+ assert(is_number_ask(ask));
+ return ask->data.num.low;
+}
+
+int fdisk_ask_number_set_low(struct fdisk_ask *ask, uint64_t low)
+{
+ assert(ask);
+ ask->data.num.low = low;
+ return 0;
+}
+
+/**
+ * fdisk_ask_number_get_high:
+ * @ask: ask instance
+ *
+ * Returns: maximal possible number when ask for numbers in range
+ */
+uint64_t fdisk_ask_number_get_high(struct fdisk_ask *ask)
+{
+ assert(ask);
+ assert(is_number_ask(ask));
+ return ask->data.num.hig;
+}
+
+int fdisk_ask_number_set_high(struct fdisk_ask *ask, uint64_t high)
+{
+ assert(ask);
+ ask->data.num.hig = high;
+ return 0;
+}
+
+/**
+ * fdisk_ask_number_get_result:
+ * @ask: ask instance
+ *
+ * Returns: result
+ */
+uint64_t fdisk_ask_number_get_result(struct fdisk_ask *ask)
+{
+ assert(ask);
+ assert(is_number_ask(ask));
+ return ask->data.num.result;
+}
+
+/**
+ * fdisk_ask_number_set_result:
+ * @ask: ask instance
+ * @result: dialog result
+ *
+ * Returns: 0 on success, <0 on error
+ */
+int fdisk_ask_number_set_result(struct fdisk_ask *ask, uint64_t result)
+{
+ assert(ask);
+ ask->data.num.result = result;
+ return 0;
+}
+
+/**
+ * fdisk_ask_number_get_base:
+ * @ask: ask instance
+ *
+ * Returns: base when user specify number in relative notation (+size)
+ */
+uint64_t fdisk_ask_number_get_base(struct fdisk_ask *ask)
+{
+ assert(ask);
+ assert(is_number_ask(ask));
+ return ask->data.num.base;
+}
+
+int fdisk_ask_number_set_base(struct fdisk_ask *ask, uint64_t base)
+{
+ assert(ask);
+ ask->data.num.base = base;
+ return 0;
+}
+
+/**
+ * fdisk_ask_number_get_unit:
+ * @ask: ask instance
+ *
+ * Returns: number of bytes per the unit
+ */
+uint64_t fdisk_ask_number_get_unit(struct fdisk_ask *ask)
+{
+ assert(ask);
+ assert(is_number_ask(ask));
+ return ask->data.num.unit;
+}
+
+int fdisk_ask_number_set_unit(struct fdisk_ask *ask, uint64_t unit)
+{
+ assert(ask);
+ ask->data.num.unit = unit;
+ return 0;
+}
+
+int fdisk_ask_number_is_relative(struct fdisk_ask *ask)
+{
+ assert(ask);
+ assert(is_number_ask(ask));
+ return ask->data.num.relative;
+}
+
+/**
+ * fdisk_ask_number_is_wrap_negative:
+ * @ask: ask instance
+ *
+ * The wrap-negative flag can be used to accept negative number from user. In this
+ * case the dialog result is calculated as "high - num" (-N from high limit).
+ *
+ * Returns: 1 or 0.
+ *
+ * Since: 2.33
+ */
+int fdisk_ask_number_is_wrap_negative(struct fdisk_ask *ask)
+{
+ assert(ask);
+ assert(is_number_ask(ask));
+ return ask->data.num.wrap_negative;
+}
+
+/**
+ * fdisk_ask_number_set_relative
+ * @ask: ask instance
+ * @relative: 0 or 1
+ *
+ * Inform libfdisk that user can specify the number in relative notation rather than
+ * by explicit number. This is useful for some optimization (e.g.
+ * align end of partition, etc.)
+ *
+ * Returns: 0 on success, <0 on error
+ */
+int fdisk_ask_number_set_relative(struct fdisk_ask *ask, int relative)
+{
+ assert(ask);
+ ask->data.num.relative = relative ? 1 : 0;
+ return 0;
+}
+
+/**
+ * fdisk_ask_number_inchars:
+ * @ask: ask instance
+ *
+ * For example for BSD is normal to address partition by chars rather than by
+ * number (first partition is 'a').
+ *
+ * Returns: 1 if number should be presented as chars
+ *
+ */
+int fdisk_ask_number_inchars(struct fdisk_ask *ask)
+{
+ assert(ask);
+ assert(is_number_ask(ask));
+ return ask->data.num.inchars;
+}
+
+int fdisk_ask_number_set_wrap_negative(struct fdisk_ask *ask, int wrap_negative)
+{
+ assert(ask);
+ ask->data.num.wrap_negative = wrap_negative ? 1 : 0;
+ return 0;
+}
+
+/*
+ * Generates string with list ranges (e.g. 1,2,5-8) for the 'cur'
+ */
+#define tochar(num) ((int) ('a' + num - 1))
+static char *mk_string_list(char *ptr, size_t *len, size_t *begin,
+ size_t *run, ssize_t cur, int inchar)
+{
+ int rlen;
+
+ if (cur != -1) {
+ if (!*begin) { /* begin of the list */
+ *begin = cur + 1;
+ return ptr;
+ }
+
+ if (*begin + *run == (size_t)cur) { /* no gap, continue */
+ (*run)++;
+ return ptr;
+ }
+ } else if (!*begin) {
+ *ptr = '\0';
+ return ptr; /* end of empty list */
+ }
+
+ /* add to the list */
+ if (!*run)
+ rlen = inchar ? snprintf(ptr, *len, "%c,", tochar(*begin)) :
+ snprintf(ptr, *len, "%zu,", *begin);
+ else if (*run == 1)
+ rlen = inchar ?
+ snprintf(ptr, *len, "%c,%c,", tochar(*begin), tochar(*begin + 1)) :
+ snprintf(ptr, *len, "%zu,%zu,", *begin, *begin + 1);
+ else
+ rlen = inchar ?
+ snprintf(ptr, *len, "%c-%c,", tochar(*begin), tochar(*begin + *run)) :
+ snprintf(ptr, *len, "%zu-%zu,", *begin, *begin + *run);
+
+ if (rlen < 0 || (size_t) rlen >= *len)
+ return NULL;
+
+ ptr += rlen;
+ *len -= rlen;
+
+ if (cur == -1 && *begin) {
+ /* end of the list */
+ *(ptr - 1) = '\0'; /* remove tailing ',' from the list */
+ return ptr;
+ }
+
+ *begin = cur + 1;
+ *run = 0;
+
+ return ptr;
+}
+
+/**
+ * fdisk_ask_partnum:
+ * @cxt: context
+ * @partnum: returns partition number
+ * @wantnew: 0|1
+ *
+ * High-level API to ask for used or unused partition number.
+ *
+ * Returns: 0 on success, < 0 on error, 1 if no free/used partition
+ */
+int fdisk_ask_partnum(struct fdisk_context *cxt, size_t *partnum, int wantnew)
+{
+ int rc = 0, inchar = 0;
+ char range[BUFSIZ], *ptr = range;
+ size_t i, len = sizeof(range), begin = 0, run = 0;
+ struct fdisk_ask *ask = NULL;
+ __typeof__(ask->data.num) *num;
+
+ assert(cxt);
+ assert(cxt->label);
+ assert(partnum);
+
+ if (cxt->label && cxt->label->flags & FDISK_LABEL_FL_INCHARS_PARTNO)
+ inchar = 1;
+
+ DBG(ASK, ul_debug("%s: asking for %s partition number "
+ "(max: %zu, inchar: %s)",
+ cxt->label ? cxt->label->name : "???",
+ wantnew ? "new" : "used",
+ cxt->label ? cxt->label->nparts_max : 0,
+ inchar ? "yes" : "not"));
+
+ ask = fdisk_new_ask();
+ if (!ask)
+ return -ENOMEM;
+
+ fdisk_ask_set_type(ask, FDISK_ASKTYPE_NUMBER);
+ num = &ask->data.num;
+
+ ask->data.num.inchars = inchar ? 1 : 0;
+
+ for (i = 0; i < cxt->label->nparts_max; i++) {
+ int used = fdisk_is_partition_used(cxt, i);
+
+ if (wantnew && !used) {
+ ptr = mk_string_list(ptr, &len, &begin, &run, i, inchar);
+ if (!ptr) {
+ rc = -EINVAL;
+ break;
+ }
+ if (!num->low)
+ num->dfl = num->low = i + 1;
+ num->hig = i + 1;
+ } else if (!wantnew && used) {
+ ptr = mk_string_list(ptr, &len, &begin, &run, i, inchar);
+ if (!num->low)
+ num->low = i + 1;
+ num->dfl = num->hig = i + 1;
+ }
+ }
+
+ DBG(ASK, ul_debugobj(ask, "ask limits: low: %"PRIu64", high: %"PRIu64", default: %"PRIu64"",
+ num->low, num->hig, num->dfl));
+
+ if (!rc && !wantnew && num->low == num->hig) {
+ if (num->low > 0) {
+ /* only one existing partition, don't ask, return the number */
+ fdisk_ask_number_set_result(ask, num->low);
+ fdisk_info(cxt, _("Selected partition %ju"), num->low);
+
+ } else if (num->low == 0) {
+ fdisk_warnx(cxt, _("No partition is defined yet!"));
+ rc = 1;
+ }
+ goto dont_ask;
+ }
+ if (!rc && wantnew && num->low == num->hig) {
+ if (num->low > 0) {
+ /* only one free partition, don't ask, return the number */
+ fdisk_ask_number_set_result(ask, num->low);
+ fdisk_info(cxt, _("Selected partition %ju"), num->low);
+ }
+ if (num->low == 0) {
+ fdisk_warnx(cxt, _("No free partition available!"));
+ rc = 1;
+ }
+ goto dont_ask;
+ }
+ if (!rc) {
+ mk_string_list(ptr, &len, &begin, &run, -1, inchar); /* terminate the list */
+ rc = fdisk_ask_number_set_range(ask, range);
+ }
+ if (!rc)
+ rc = fdisk_ask_set_query(ask, _("Partition number"));
+ if (!rc)
+ rc = fdisk_do_ask(cxt, ask);
+
+dont_ask:
+ if (!rc) {
+ *partnum = fdisk_ask_number_get_result(ask);
+ if (*partnum)
+ *partnum -= 1;
+ }
+ DBG(ASK, ul_debugobj(ask, "result: %"PRIu64" [rc=%d]\n", fdisk_ask_number_get_result(ask), rc));
+ fdisk_unref_ask(ask);
+ return rc;
+}
+
+/**
+ * fdisk_ask_number:
+ * @cxt: context
+ * @low: minimal possible number
+ * @dflt: default suggestion
+ * @high: maximal possible number
+ * @query: question string
+ * @result: returns result
+ *
+ * Returns: 0 on success, <0 on error.
+ */
+int fdisk_ask_number(struct fdisk_context *cxt,
+ uintmax_t low,
+ uintmax_t dflt,
+ uintmax_t high,
+ const char *query,
+ uintmax_t *result)
+{
+ struct fdisk_ask *ask;
+ int rc;
+
+ assert(cxt);
+
+ ask = fdisk_new_ask();
+ if (!ask)
+ return -ENOMEM;
+
+ rc = fdisk_ask_set_type(ask, FDISK_ASKTYPE_NUMBER);
+ if (!rc)
+ fdisk_ask_number_set_low(ask, low);
+ if (!rc)
+ fdisk_ask_number_set_default(ask, dflt);
+ if (!rc)
+ fdisk_ask_number_set_high(ask, high);
+ if (!rc)
+ fdisk_ask_set_query(ask, query);
+ if (!rc)
+ rc = fdisk_do_ask(cxt, ask);
+ if (!rc)
+ *result = fdisk_ask_number_get_result(ask);
+
+ DBG(ASK, ul_debugobj(ask, "result: %ju [rc=%d]\n", *result, rc));
+ fdisk_unref_ask(ask);
+ return rc;
+}
+
+/**
+ * fdisk_ask_string_get_result:
+ * @ask: ask instance
+ *
+ * Returns: pointer to dialog result
+ */
+char *fdisk_ask_string_get_result(struct fdisk_ask *ask)
+{
+ assert(ask);
+ assert(fdisk_is_ask(ask, STRING));
+ return ask->data.str.result;
+}
+
+/**
+ * fdisk_ask_string_set_result:
+ * @ask: ask instance
+ * @result: pointer to allocated buffer with string
+ *
+ * You don't have to care about the @result deallocation, libfdisk is going to
+ * deallocate the result when destroy @ask instance.
+ *
+ * Returns: 0 on success, <0 on error
+ */
+int fdisk_ask_string_set_result(struct fdisk_ask *ask, char *result)
+{
+ assert(ask);
+ ask->data.str.result = result;
+ return 0;
+}
+
+/**
+ * fdisk_ask_string:
+ * @cxt: context:
+ * @query: question string
+ * @result: returns allocated buffer
+ *
+ * High-level API to ask for strings. Don't forget to deallocate the @result.
+ *
+ * Returns: 0 on success, <0 on error.
+ */
+int fdisk_ask_string(struct fdisk_context *cxt,
+ const char *query,
+ char **result)
+{
+ struct fdisk_ask *ask;
+ int rc;
+
+ assert(cxt);
+
+ ask = fdisk_new_ask();
+ if (!ask)
+ return -ENOMEM;
+
+ rc = fdisk_ask_set_type(ask, FDISK_ASKTYPE_STRING);
+ if (!rc)
+ fdisk_ask_set_query(ask, query);
+ if (!rc)
+ rc = fdisk_do_ask(cxt, ask);
+ if (!rc)
+ *result = fdisk_ask_string_get_result(ask);
+
+ DBG(ASK, ul_debugobj(ask, "result: %s [rc=%d]\n", *result, rc));
+ fdisk_unref_ask(ask);
+ return rc;
+}
+
+/**
+ * fdisk_ask_yesno:
+ * @cxt: context
+ * @query: question string
+ * @result: returns 0 (no) or 1 (yes)
+ *
+ * High-level API to ask Yes/No questions
+ *
+ * Returns: 0 on success, <0 on error
+ */
+int fdisk_ask_yesno(struct fdisk_context *cxt,
+ const char *query,
+ int *result)
+{
+ struct fdisk_ask *ask;
+ int rc;
+
+ assert(cxt);
+
+ ask = fdisk_new_ask();
+ if (!ask)
+ return -ENOMEM;
+
+ rc = fdisk_ask_set_type(ask, FDISK_ASKTYPE_YESNO);
+ if (!rc)
+ fdisk_ask_set_query(ask, query);
+ if (!rc)
+ rc = fdisk_do_ask(cxt, ask);
+ if (!rc)
+ *result = fdisk_ask_yesno_get_result(ask) == 1 ? 1 : 0;
+
+ DBG(ASK, ul_debugobj(ask, "result: %d [rc=%d]\n", *result, rc));
+ fdisk_unref_ask(ask);
+ return rc;
+}
+
+/**
+ * fdisk_ask_yesno_get_result:
+ * @ask: ask instance
+ *
+ * Returns: 0 or 1
+ */
+int fdisk_ask_yesno_get_result(struct fdisk_ask *ask)
+{
+ assert(ask);
+ assert(fdisk_is_ask(ask, YESNO));
+ return ask->data.yesno.result;
+}
+
+/**
+ * fdisk_ask_yesno_set_result:
+ * @ask: ask instance
+ * @result: 1 or 0
+ *
+ * Returns: 0 on success, <0 on error
+ */
+int fdisk_ask_yesno_set_result(struct fdisk_ask *ask, int result)
+{
+ assert(ask);
+ ask->data.yesno.result = result;
+ return 0;
+}
+
+/*
+ * menu
+ */
+int fdisk_ask_menu_set_default(struct fdisk_ask *ask, int dfl)
+{
+ assert(ask);
+ assert(fdisk_is_ask(ask, MENU));
+ ask->data.menu.dfl = dfl;
+ return 0;
+}
+
+/**
+ * fdisk_ask_menu_get_default:
+ * @ask: ask instance
+ *
+ * Returns: default menu item key
+ */
+int fdisk_ask_menu_get_default(struct fdisk_ask *ask)
+{
+ assert(ask);
+ assert(fdisk_is_ask(ask, MENU));
+ return ask->data.menu.dfl;
+}
+
+/**
+ * fdisk_ask_menu_set_result:
+ * @ask: ask instance
+ * @key: result
+ *
+ * Returns: 0 on success, <0 on error
+ */
+int fdisk_ask_menu_set_result(struct fdisk_ask *ask, int key)
+{
+ assert(ask);
+ assert(fdisk_is_ask(ask, MENU));
+ ask->data.menu.result = key;
+ DBG(ASK, ul_debugobj(ask, "menu result: %c\n", key));
+ return 0;
+
+}
+
+/**
+ * fdisk_ask_menu_get_result:
+ * @ask: ask instance
+ * @key: returns selected menu item key
+ *
+ * Returns: 0 on success, <0 on error.
+ */
+int fdisk_ask_menu_get_result(struct fdisk_ask *ask, int *key)
+{
+ assert(ask);
+ assert(fdisk_is_ask(ask, MENU));
+ if (key)
+ *key = ask->data.menu.result;
+ return 0;
+}
+
+/**
+ * fdisk_ask_menu_get_item:
+ * @ask: ask menu instance
+ * @idx: wanted menu item index
+ * @key: returns key of the menu item
+ * @name: returns name of the menu item
+ * @desc: returns description of the menu item
+ *
+ * Returns: 0 on success, <0 on error, >0 if idx out-of-range
+ */
+int fdisk_ask_menu_get_item(struct fdisk_ask *ask, size_t idx, int *key,
+ const char **name, const char **desc)
+{
+ size_t i;
+ struct ask_menuitem *mi;
+
+ assert(ask);
+ assert(fdisk_is_ask(ask, MENU));
+
+ for (i = 0, mi = ask->data.menu.first; mi; mi = mi->next, i++) {
+ if (i == idx)
+ break;
+ }
+
+ if (!mi)
+ return 1; /* no more items */
+ if (key)
+ *key = mi->key;
+ if (name)
+ *name = mi->name;
+ if (desc)
+ *desc = mi->desc;
+ return 0;
+}
+
+static void fdisk_ask_menu_reset_items(struct fdisk_ask *ask)
+{
+ struct ask_menuitem *mi;
+
+ assert(ask);
+ assert(fdisk_is_ask(ask, MENU));
+
+ for (mi = ask->data.menu.first; mi; ) {
+ struct ask_menuitem *next = mi->next;
+ free(mi);
+ mi = next;
+ }
+}
+
+/**
+ * fdisk_ask_menu_get_nitems:
+ * @ask: ask instance
+ *
+ * Returns: number of menu items
+ */
+size_t fdisk_ask_menu_get_nitems(struct fdisk_ask *ask)
+{
+ struct ask_menuitem *mi;
+ size_t n;
+
+ assert(ask);
+ assert(fdisk_is_ask(ask, MENU));
+
+ for (n = 0, mi = ask->data.menu.first; mi; mi = mi->next, n++);
+
+ return n;
+}
+
+int fdisk_ask_menu_add_item(struct fdisk_ask *ask, int key,
+ const char *name, const char *desc)
+{
+ struct ask_menuitem *mi;
+
+ assert(ask);
+ assert(fdisk_is_ask(ask, MENU));
+
+ mi = calloc(1, sizeof(*mi));
+ if (!mi)
+ return -ENOMEM;
+ mi->key = key;
+ mi->name = name;
+ mi->desc = desc;
+
+ if (!ask->data.menu.first)
+ ask->data.menu.first = mi;
+ else {
+ struct ask_menuitem *last = ask->data.menu.first;
+
+ while (last->next)
+ last = last->next;
+ last->next = mi;
+ }
+
+ DBG(ASK, ul_debugobj(ask, "new menu item: %c, \"%s\" (%s)\n", mi->key, mi->name, mi->desc));
+ return 0;
+}
+
+
+/*
+ * print-like
+ */
+
+#define is_print_ask(a) (fdisk_is_ask(a, WARN) || fdisk_is_ask(a, WARNX) || fdisk_is_ask(a, INFO))
+
+/**
+ * fdisk_ask_print_get_errno:
+ * @ask: ask instance
+ *
+ * Returns: error number for warning/error messages
+ */
+int fdisk_ask_print_get_errno(struct fdisk_ask *ask)
+{
+ assert(ask);
+ assert(is_print_ask(ask));
+ return ask->data.print.errnum;
+}
+
+int fdisk_ask_print_set_errno(struct fdisk_ask *ask, int errnum)
+{
+ assert(ask);
+ ask->data.print.errnum = errnum;
+ return 0;
+}
+
+/**
+ * fdisk_ask_print_get_mesg:
+ * @ask: ask instance
+ *
+ * Returns: pointer to message
+ */
+const char *fdisk_ask_print_get_mesg(struct fdisk_ask *ask)
+{
+ assert(ask);
+ assert(is_print_ask(ask));
+ return ask->data.print.mesg;
+}
+
+/* does not reallocate the message! */
+int fdisk_ask_print_set_mesg(struct fdisk_ask *ask, const char *mesg)
+{
+ assert(ask);
+ ask->data.print.mesg = mesg;
+ return 0;
+}
+
+static int do_vprint(struct fdisk_context *cxt, int errnum, int type,
+ const char *fmt, va_list va)
+{
+ struct fdisk_ask *ask;
+ int rc;
+ char *mesg;
+
+ assert(cxt);
+
+ if (vasprintf(&mesg, fmt, va) < 0)
+ return -ENOMEM;
+
+ ask = fdisk_new_ask();
+ if (!ask) {
+ free(mesg);
+ return -ENOMEM;
+ }
+
+ fdisk_ask_set_type(ask, type);
+ fdisk_ask_print_set_mesg(ask, mesg);
+ if (errnum >= 0)
+ fdisk_ask_print_set_errno(ask, errnum);
+ rc = fdisk_do_ask(cxt, ask);
+
+ fdisk_unref_ask(ask);
+ free(mesg);
+ return rc;
+}
+
+/**
+ * fdisk_info:
+ * @cxt: context
+ * @fmt: printf-like formatted string
+ * @...: variable parameters
+ *
+ * High-level API to print info messages,
+ *
+ * Returns: 0 on success, <0 on error
+ */
+int fdisk_info(struct fdisk_context *cxt, const char *fmt, ...)
+{
+ int rc;
+ va_list ap;
+
+ assert(cxt);
+ va_start(ap, fmt);
+ rc = do_vprint(cxt, -1, FDISK_ASKTYPE_INFO, fmt, ap);
+ va_end(ap);
+ return rc;
+}
+
+/**
+ * fdisk_info:
+ * @cxt: context
+ * @fmt: printf-like formatted string
+ * @...: variable parameters
+ *
+ * High-level API to print warning message (errno expected)
+ *
+ * Returns: 0 on success, <0 on error
+ */
+int fdisk_warn(struct fdisk_context *cxt, const char *fmt, ...)
+{
+ int rc;
+ va_list ap;
+
+ assert(cxt);
+ va_start(ap, fmt);
+ rc = do_vprint(cxt, errno, FDISK_ASKTYPE_WARN, fmt, ap);
+ va_end(ap);
+ return rc;
+}
+
+/**
+ * fdisk_warnx:
+ * @cxt: context
+ * @fmt: printf-like formatted string
+ * @...: variable options
+ *
+ * High-level API to print warning message
+ *
+ * Returns: 0 on success, <0 on error
+ */
+int fdisk_warnx(struct fdisk_context *cxt, const char *fmt, ...)
+{
+ int rc;
+ va_list ap;
+
+ assert(cxt);
+ va_start(ap, fmt);
+ rc = do_vprint(cxt, -1, FDISK_ASKTYPE_WARNX, fmt, ap);
+ va_end(ap);
+ return rc;
+}
+
+int fdisk_info_new_partition(
+ struct fdisk_context *cxt,
+ int num, fdisk_sector_t start, fdisk_sector_t stop,
+ struct fdisk_parttype *t)
+{
+ int rc;
+ char *str = size_to_human_string(SIZE_SUFFIX_3LETTER | SIZE_SUFFIX_SPACE,
+ (uint64_t)(stop - start + 1) * cxt->sector_size);
+
+ rc = fdisk_info(cxt,
+ _("Created a new partition %d of type '%s' and of size %s."),
+ num, t ? t->name : _("Unknown"), str);
+ free(str);
+ return rc;
+}
+
+#ifdef TEST_PROGRAM
+static int test_ranges(struct fdisk_test *ts, int argc, char *argv[])
+{
+ /* 1 - 3, 6, 8, 9, 11 13 */
+ size_t nums[] = { 1, 1, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 1 };
+ size_t numx[] = { 0, 0, 0 };
+ char range[BUFSIZ], *ptr = range;
+ size_t i, len = sizeof(range), begin = 0, run = 0;
+
+ for (i = 0; i < ARRAY_SIZE(nums); i++) {
+ if (!nums[i])
+ continue;
+ ptr = mk_string_list(ptr, &len, &begin, &run, i, 0);
+ }
+ mk_string_list(ptr, &len, &begin, &run, -1, 0);
+ printf("list: '%s'\n", range);
+
+ ptr = range;
+ len = sizeof(range), begin = 0, run = 0;
+ for (i = 0; i < ARRAY_SIZE(numx); i++) {
+ if (!numx[i])
+ continue;
+ ptr = mk_string_list(ptr, &len, &begin, &run, i, 0);
+ }
+ mk_string_list(ptr, &len, &begin, &run, -1, 0);
+ printf("empty list: '%s'\n", range);
+
+ return 0;
+}
+
+int main(int argc, char *argv[])
+{
+ struct fdisk_test tss[] = {
+ { "--ranges", test_ranges, "generates ranges" },
+ { NULL }
+ };
+
+ return fdisk_run_test(tss, argc, argv);
+}
+
+#endif
diff --git a/libfdisk/src/bsd.c b/libfdisk/src/bsd.c
new file mode 100644
index 0000000..313ae5a
--- /dev/null
+++ b/libfdisk/src/bsd.c
@@ -0,0 +1,1065 @@
+/*
+ * Copyright (C) 2007-2013 Karel Zak <kzak@redhat.com>
+ *
+ * Based on the original code from fdisk
+ * written by Bernhard Fastenrath (fasten@informatik.uni-bonn.de)
+ * with code from the NetBSD disklabel command.
+ *
+ * Arnaldo Carvalho de Melo <acme@conectiva.com.br>, March 1999
+ * David Huggins-Daines <dhuggins@linuxcare.com>, January 2000
+ */
+#include <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <sys/param.h>
+
+#include "blkdev.h"
+#include "fdiskP.h"
+#include "pt-mbr.h"
+#include "pt-bsd.h"
+#include "all-io.h"
+
+
+/**
+ * SECTION: bsd
+ * @title: BSD
+ * @short_description: disk label specific functions
+ *
+ */
+
+static const char *bsd_dktypenames[] = {
+ "unknown",
+ "SMD",
+ "MSCP",
+ "old DEC",
+ "SCSI",
+ "ESDI",
+ "ST506",
+ "HP-IB",
+ "HP-FL",
+ "type 9",
+ "floppy",
+ NULL
+};
+#define BSD_DKMAXTYPES (ARRAY_SIZE(bsd_dktypenames) - 1)
+
+static struct fdisk_parttype bsd_fstypes[] = {
+ {BSD_FS_UNUSED, "unused"},
+ {BSD_FS_SWAP, "swap"},
+ {BSD_FS_V6, "Version 6"},
+ {BSD_FS_V7, "Version 7"},
+ {BSD_FS_SYSV, "System V"},
+ {BSD_FS_V71K, "4.1BSD"},
+ {BSD_FS_V8, "Eighth Edition"},
+ {BSD_FS_BSDFFS, "4.2BSD"},
+#ifdef __alpha__
+ {BSD_FS_EXT2, "ext2"},
+#else
+ {BSD_FS_MSDOS, "MS-DOS"},
+#endif
+ {BSD_FS_BSDLFS, "4.4LFS"},
+ {BSD_FS_OTHER, "unknown"},
+ {BSD_FS_HPFS, "HPFS"},
+ {BSD_FS_ISO9660,"ISO-9660"},
+ {BSD_FS_BOOT, "boot"},
+ {BSD_FS_ADOS, "ADOS"},
+ {BSD_FS_HFS, "HFS"},
+ {BSD_FS_ADVFS, "AdvFS"},
+ { 0, NULL }
+};
+#define BSD_FSMAXTYPES (ARRAY_SIZE(bsd_fstypes)-1)
+
+/*
+ * in-memory fdisk BSD stuff
+ */
+struct fdisk_bsd_label {
+ struct fdisk_label head; /* generic part */
+
+ struct dos_partition *dos_part; /* parent */
+ struct bsd_disklabel bsd; /* on disk label */
+#if defined (__alpha__)
+ /* We access this through a u_int64_t * when checksumming */
+ char bsdbuffer[BSD_BBSIZE] __attribute__((aligned(8)));
+#else
+ char bsdbuffer[BSD_BBSIZE];
+#endif
+};
+
+static int bsd_initlabel(struct fdisk_context *cxt);
+static int bsd_readlabel(struct fdisk_context *cxt);
+static void sync_disks(struct fdisk_context *cxt);
+
+static inline struct fdisk_bsd_label *self_label(struct fdisk_context *cxt)
+{
+ assert(cxt);
+ assert(cxt->label);
+ assert(fdisk_is_label(cxt, BSD));
+
+ return (struct fdisk_bsd_label *) cxt->label;
+}
+
+static inline struct bsd_disklabel *self_disklabel(struct fdisk_context *cxt)
+{
+ assert(cxt);
+ assert(cxt->label);
+ assert(fdisk_is_label(cxt, BSD));
+
+ return &((struct fdisk_bsd_label *) cxt->label)->bsd;
+}
+
+static struct fdisk_parttype *bsd_partition_parttype(
+ struct fdisk_context *cxt,
+ struct bsd_partition *p)
+{
+ struct fdisk_parttype *t
+ = fdisk_label_get_parttype_from_code(cxt->label, p->p_fstype);
+ return t ? : fdisk_new_unknown_parttype(p->p_fstype, NULL);
+}
+
+
+#if defined (__alpha__)
+static void alpha_bootblock_checksum (char *boot)
+{
+ uint64_t *dp = (uint64_t *) boot, sum = 0;
+ int i;
+
+ for (i = 0; i < 63; i++)
+ sum += dp[i];
+ dp[63] = sum;
+}
+#endif /* __alpha__ */
+
+#define HIDDEN_MASK 0x10
+
+static int is_bsd_partition_type(int type)
+{
+ return (type == MBR_FREEBSD_PARTITION ||
+ type == (MBR_FREEBSD_PARTITION ^ HIDDEN_MASK) ||
+ type == MBR_NETBSD_PARTITION ||
+ type == (MBR_NETBSD_PARTITION ^ HIDDEN_MASK) ||
+ type == MBR_OPENBSD_PARTITION ||
+ type == (MBR_OPENBSD_PARTITION ^ HIDDEN_MASK));
+}
+
+/*
+ * look for DOS partition usable for nested BSD partition table
+ */
+static int bsd_assign_dos_partition(struct fdisk_context *cxt)
+{
+ struct fdisk_bsd_label *l = self_label(cxt);
+ size_t i;
+
+ for (i = 0; i < 4; i++) {
+ fdisk_sector_t ss;
+
+ l->dos_part = fdisk_dos_get_partition(cxt->parent, i);
+
+ if (!l->dos_part || !is_bsd_partition_type(l->dos_part->sys_ind))
+ continue;
+
+ ss = dos_partition_get_start(l->dos_part);
+ if (!ss) {
+ fdisk_warnx(cxt, _("Partition %zd: has invalid starting "
+ "sector 0."), i + 1);
+ return -1;
+ }
+
+ if (cxt->parent->dev_path) {
+ free(cxt->dev_path);
+ cxt->dev_path = fdisk_partname(
+ cxt->parent->dev_path, i + 1);
+ }
+
+ DBG(LABEL, ul_debug("partition %zu assigned to BSD", i + 1));
+ return 0;
+ }
+
+ fdisk_warnx(cxt, _("There is no *BSD partition on %s."),
+ cxt->parent->dev_path);
+ free(cxt->dev_path);
+ cxt->dev_path = NULL;
+ l->dos_part = NULL;
+ return 1;
+}
+
+static int bsd_probe_label(struct fdisk_context *cxt)
+{
+ int rc = 0;
+
+ if (cxt->parent)
+ rc = bsd_assign_dos_partition(cxt); /* nested BSD partition table */
+ if (!rc)
+ rc = bsd_readlabel(cxt);
+ if (!rc)
+ return 1; /* found BSD */
+ return 0; /* not found */
+}
+
+static int set_parttype(
+ struct fdisk_context *cxt,
+ size_t partnum,
+ struct fdisk_parttype *t)
+{
+ struct bsd_partition *p;
+ struct bsd_disklabel *d = self_disklabel(cxt);
+
+ if (partnum >= d->d_npartitions || !t || t->code > UINT8_MAX)
+ return -EINVAL;
+
+ p = &d->d_partitions[partnum];
+ if (t->code == p->p_fstype)
+ return 0;
+
+ p->p_fstype = t->code;
+ fdisk_label_set_changed(cxt->label, 1);
+ return 0;
+}
+
+static int bsd_add_partition(struct fdisk_context *cxt,
+ struct fdisk_partition *pa,
+ size_t *partno)
+{
+ struct fdisk_bsd_label *l = self_label(cxt);
+ struct bsd_disklabel *d = self_disklabel(cxt);
+ size_t i;
+ unsigned int begin = 0, end;
+ int rc = 0;
+
+ rc = fdisk_partition_next_partno(pa, cxt, &i);
+ if (rc)
+ return rc;
+ if (i >= BSD_MAXPARTITIONS)
+ return -ERANGE;
+ if (l->dos_part) {
+ begin = dos_partition_get_start(l->dos_part);
+ end = begin + dos_partition_get_size(l->dos_part) - 1;
+ } else
+ end = d->d_secperunit - 1;
+
+ /*
+ * First sector
+ */
+ if (pa && pa->start_follow_default)
+ ;
+ else if (pa && fdisk_partition_has_start(pa)) {
+ if (pa->start < begin || pa->start > end)
+ return -ERANGE;
+ begin = pa->start;
+ } else {
+ struct fdisk_ask *ask = fdisk_new_ask();
+
+ if (!ask)
+ return -ENOMEM;
+ fdisk_ask_set_query(ask,
+ fdisk_use_cylinders(cxt) ?
+ _("First cylinder") : _("First sector"));
+ fdisk_ask_set_type(ask, FDISK_ASKTYPE_NUMBER);
+ fdisk_ask_number_set_low(ask, fdisk_cround(cxt, begin));
+ fdisk_ask_number_set_default(ask, fdisk_cround(cxt, begin));
+ fdisk_ask_number_set_high(ask, fdisk_cround(cxt, end));
+
+ rc = fdisk_do_ask(cxt, ask);
+ begin = fdisk_ask_number_get_result(ask);
+ fdisk_unref_ask(ask);
+ if (rc)
+ return rc;
+ if (fdisk_use_cylinders(cxt))
+ begin = (begin - 1) * d->d_secpercyl;
+ }
+
+ /*
+ * Last sector
+ */
+ if (pa && pa->end_follow_default)
+ ;
+ else if (pa && fdisk_partition_has_size(pa)) {
+ if (begin + pa->size > end)
+ return -ERANGE;
+ end = begin + pa->size - 1ULL;
+ } else {
+ /* ask user by dialog */
+ struct fdisk_ask *ask = fdisk_new_ask();
+
+ if (!ask)
+ return -ENOMEM;
+ fdisk_ask_set_type(ask, FDISK_ASKTYPE_OFFSET);
+
+ if (fdisk_use_cylinders(cxt)) {
+ fdisk_ask_set_query(ask, _("Last cylinder, +/-cylinders or +/-size{K,M,G,T,P}"));
+ fdisk_ask_number_set_unit(ask,
+ cxt->sector_size *
+ fdisk_get_units_per_sector(cxt));
+ } else {
+ fdisk_ask_set_query(ask, _("Last sector, +/-sectors or +/-size{K,M,G,T,P}"));
+ fdisk_ask_number_set_unit(ask,cxt->sector_size);
+ }
+
+ fdisk_ask_number_set_low(ask, fdisk_cround(cxt, begin));
+ fdisk_ask_number_set_default(ask, fdisk_cround(cxt, end));
+ fdisk_ask_number_set_high(ask, fdisk_cround(cxt, end));
+ fdisk_ask_number_set_base(ask, fdisk_cround(cxt, begin));
+ fdisk_ask_number_set_wrap_negative(ask, 1); /* wrap negative around high */
+
+ rc = fdisk_do_ask(cxt, ask);
+ end = fdisk_ask_number_get_result(ask);
+ fdisk_unref_ask(ask);
+ if (rc)
+ return rc;
+ if (fdisk_use_cylinders(cxt))
+ end = end * d->d_secpercyl - 1;
+ }
+
+ d->d_partitions[i].p_size = end - begin + 1;
+ d->d_partitions[i].p_offset = begin;
+ d->d_partitions[i].p_fstype = BSD_FS_UNUSED;
+
+ if (i >= d->d_npartitions)
+ d->d_npartitions = i + 1;
+ cxt->label->nparts_cur = d->d_npartitions;
+
+ if (pa && pa->type)
+ set_parttype(cxt, i, pa->type);
+
+ fdisk_label_set_changed(cxt->label, 1);
+ if (partno)
+ *partno = i;
+ return 0;
+}
+
+static int bsd_set_partition(struct fdisk_context *cxt, size_t n,
+ struct fdisk_partition *pa)
+{
+ struct bsd_partition *p;
+ struct fdisk_bsd_label *l = self_label(cxt);
+ struct bsd_disklabel *d = self_disklabel(cxt);
+
+ if (n >= d->d_npartitions)
+ return -EINVAL;
+
+ p = &d->d_partitions[n];
+
+ /* we have to stay within parental DOS partition */
+ if (l->dos_part && (fdisk_partition_has_start(pa) ||
+ fdisk_partition_has_size(pa))) {
+
+ fdisk_sector_t dosbegin = dos_partition_get_start(l->dos_part);
+ fdisk_sector_t dosend = dosbegin + dos_partition_get_size(l->dos_part) - 1;
+ fdisk_sector_t begin = fdisk_partition_has_start(pa) ? pa->start : p->p_offset;
+ fdisk_sector_t end = begin + (fdisk_partition_has_size(pa) ? pa->size : p->p_size) - 1;
+
+ if (begin < dosbegin || begin > dosend)
+ return -ERANGE;
+ if (end < dosbegin || end > dosend)
+ return -ERANGE;
+ }
+
+ if (pa->type) {
+ int rc = set_parttype(cxt, n, pa->type);
+ if (rc)
+ return rc;
+ }
+
+ if (fdisk_partition_has_start(pa))
+ d->d_partitions[n].p_offset = pa->start;
+ if (fdisk_partition_has_size(pa))
+ d->d_partitions[n].p_size = pa->size;
+
+ fdisk_label_set_changed(cxt->label, 1);
+ return 0;
+}
+
+
+/* Returns 0 on success, < 0 on error. */
+static int bsd_create_disklabel(struct fdisk_context *cxt)
+{
+ int rc, yes = 0;
+ struct bsd_disklabel *d = self_disklabel(cxt);
+
+ fdisk_info(cxt, _("The device %s does not contain BSD disklabel."), cxt->dev_path);
+ rc = fdisk_ask_yesno(cxt,
+ _("Do you want to create a BSD disklabel?"),
+ &yes);
+ if (rc)
+ return rc;
+ if (!yes)
+ return 1;
+ if (cxt->parent) {
+ rc = bsd_assign_dos_partition(cxt);
+ if (rc == 1)
+ /* not found DOS partition usable for BSD label */
+ rc = -EINVAL;
+ }
+ if (rc)
+ return rc;
+
+ rc = bsd_initlabel(cxt);
+ if (!rc) {
+ cxt->label->nparts_cur = d->d_npartitions;
+ cxt->label->nparts_max = BSD_MAXPARTITIONS;
+ }
+
+ return rc;
+}
+
+static int bsd_delete_part(
+ struct fdisk_context *cxt,
+ size_t partnum)
+{
+ struct bsd_disklabel *d = self_disklabel(cxt);
+
+ d->d_partitions[partnum].p_size = 0;
+ d->d_partitions[partnum].p_offset = 0;
+ d->d_partitions[partnum].p_fstype = BSD_FS_UNUSED;
+
+ if (d->d_npartitions == partnum + 1)
+ while (!d->d_partitions[d->d_npartitions - 1].p_size)
+ d->d_npartitions--;
+
+ cxt->label->nparts_cur = d->d_npartitions;
+ fdisk_label_set_changed(cxt->label, 1);
+ return 0;
+}
+
+static int bsd_get_disklabel_item(struct fdisk_context *cxt, struct fdisk_labelitem *item)
+{
+ struct bsd_disklabel *d;
+ int rc = 0;
+
+ assert(cxt);
+ assert(cxt->label);
+ assert(fdisk_is_label(cxt, BSD));
+
+ d = self_disklabel(cxt);
+
+ switch (item->id) {
+ case BSD_LABELITEM_TYPE:
+ item->name = _("Type");
+ item->type = 's';
+ if ((unsigned) d->d_type < BSD_DKMAXTYPES) {
+ item->data.str = strdup(bsd_dktypenames[d->d_type]);
+ if (!item->data.str)
+ rc = -ENOMEM;
+ } else if (asprintf(&item->data.str, "%d", d->d_type) < 0)
+ rc = -ENOMEM;
+ break;
+ case BSD_LABELITEM_DISK:
+ item->name = _("Disk");
+ item->type = 's';
+ item->data.str = strndup(d->d_typename, sizeof(d->d_typename));
+ if (!item->data.str)
+ rc = -ENOMEM;
+ break;
+ case BSD_LABELITEM_PACKNAME:
+ item->name = _("Packname");
+ item->type = 's';
+ item->data.str = strndup(d->d_packname, sizeof(d->d_packname));
+ if (!item->data.str)
+ rc = -ENOMEM;
+ break;
+ case BSD_LABELITEM_FLAGS:
+ item->name = _("Flags");
+ item->type = 's';
+ item->data.str = strdup(
+ d->d_flags & BSD_D_REMOVABLE ? _(" removable") :
+ d->d_flags & BSD_D_ECC ? _(" ecc") :
+ d->d_flags & BSD_D_BADSECT ? _(" badsect") : "");
+ if (!item->data.str)
+ rc = -ENOMEM;
+ break;
+
+ /* On various machines the fields of *lp are short/int/long */
+ /* In order to avoid problems, we cast them all uint64. */
+ case BSD_LABELITEM_SECSIZE:
+ item->name = _("Bytes/Sector");
+ item->type = 'j';
+ item->data.num64 = (uint64_t) d->d_secsize;
+ break;
+ case BSD_LABELITEM_NTRACKS:
+ item->name = _("Tracks/Cylinder");
+ item->type = 'j';
+ item->data.num64 = (uint64_t) d->d_ntracks;
+ break;
+ case BSD_LABELITEM_SECPERCYL:
+ item->name = _("Sectors/Cylinder");
+ item->data.num64 = (uint64_t) d->d_secpercyl;
+ item->type = 'j';
+ break;
+ case BSD_LABELITEM_CYLINDERS:
+ item->name = _("Cylinders");
+ item->data.num64 = (uint64_t) d->d_ncylinders;
+ item->type = 'j';
+ break;
+ case BSD_LABELITEM_RPM:
+ item->name = _("Rpm");
+ item->data.num64 = (uint64_t) d->d_rpm;
+ item->type = 'j';
+ break;
+ case BSD_LABELITEM_INTERLEAVE:
+ item->name = _("Interleave");
+ item->data.num64 = (uint64_t) d->d_interleave;
+ item->type = 'j';
+ break;
+ case BSD_LABELITEM_TRACKSKEW:
+ item->name = _("Trackskew");
+ item->data.num64 = (uint64_t) d->d_trackskew;
+ item->type = 'j';
+ break;
+ case BSD_LABELITEM_CYLINDERSKEW:
+ item->name = _("Cylinderskew");
+ item->data.num64 = (uint64_t) d->d_cylskew;
+ item->type = 'j';
+ break;
+ case BSD_LABELITEM_HEADSWITCH:
+ item->name = _("Headswitch");
+ item->data.num64 = (uint64_t) d->d_headswitch;
+ item->type = 'j';
+ break;
+ case BSD_LABELITEM_TRKSEEK:
+ item->name = _("Track-to-track seek");
+ item->data.num64 = (uint64_t) d->d_trkseek;
+ item->type = 'j';
+ break;
+ default:
+ if (item->id < __FDISK_NLABELITEMS)
+ rc = 1; /* unsupported generic item */
+ else
+ rc = 2; /* out of range */
+ break;
+ }
+
+ return rc;
+}
+
+static int bsd_get_partition(struct fdisk_context *cxt, size_t n,
+ struct fdisk_partition *pa)
+{
+ struct bsd_partition *p;
+ struct bsd_disklabel *d = self_disklabel(cxt);
+
+ assert(cxt);
+ assert(cxt->label);
+ assert(fdisk_is_label(cxt, BSD));
+
+ if (n >= d->d_npartitions)
+ return -EINVAL;
+
+ p = &d->d_partitions[n];
+
+ pa->used = p->p_size ? 1 : 0;
+ if (!pa->used)
+ return 0;
+
+ if (fdisk_use_cylinders(cxt) && d->d_secpercyl) {
+ pa->start_post = p->p_offset % d->d_secpercyl ? '*' : ' ';
+ pa->end_post = (p->p_offset + p->p_size) % d->d_secpercyl ? '*' : ' ';
+ }
+
+ pa->start = p->p_offset;
+ pa->size = p->p_size;
+ pa->type = bsd_partition_parttype(cxt, p);
+
+ if (p->p_fstype == BSD_FS_UNUSED || p->p_fstype == BSD_FS_BSDFFS) {
+ pa->fsize = p->p_fsize;
+ pa->bsize = p->p_fsize * p->p_frag;
+ }
+ if (p->p_fstype == BSD_FS_BSDFFS)
+ pa->cpg = p->p_cpg;
+
+ return 0;
+}
+
+static uint32_t ask_uint32(struct fdisk_context *cxt,
+ uint32_t dflt, char *mesg)
+{
+ uintmax_t res;
+
+ if (fdisk_ask_number(cxt, min(dflt, (uint32_t) 1), dflt,
+ UINT32_MAX, mesg, &res) == 0)
+ return res;
+ return dflt;
+}
+
+static uint16_t ask_uint16(struct fdisk_context *cxt,
+ uint16_t dflt, char *mesg)
+{
+ uintmax_t res;
+
+ if (fdisk_ask_number(cxt, min(dflt, (uint16_t) 1),
+ dflt, UINT16_MAX, mesg, &res) == 0)
+ return res;
+ return dflt;
+}
+
+/**
+ * fdisk_bsd_edit_disklabel:
+ * @cxt: context
+ *
+ * Edits fields in BSD disk label.
+ *
+ * Returns: 0 on success, <0 on error
+ */
+int fdisk_bsd_edit_disklabel(struct fdisk_context *cxt)
+{
+ struct bsd_disklabel *d = self_disklabel(cxt);
+ uintmax_t res;
+
+#if defined (__alpha__) || defined (__ia64__)
+ if (fdisk_ask_number(cxt, DEFAULT_SECTOR_SIZE, d->d_secsize,
+ UINT32_MAX, _("bytes/sector"), &res) == 0)
+ d->d_secsize = res;
+
+ d->d_nsectors = ask_uint32(cxt, d->d_nsectors, _("sectors/track"));
+ d->d_ntracks = ask_uint32(cxt, d->d_ntracks, _("tracks/cylinder"));
+ d->d_ncylinders = ask_uint32(cxt, d->d_ncylinders ,_("cylinders"));
+#endif
+ if (fdisk_ask_number(cxt, 1,
+ (uintmax_t) d->d_nsectors * d->d_ntracks,
+ (uintmax_t) d->d_nsectors * d->d_ntracks,
+ _("sectors/cylinder"), &res) == 0)
+ d->d_secpercyl = res;
+
+ d->d_rpm = ask_uint16(cxt, d->d_rpm, _("rpm"));
+ d->d_interleave = ask_uint16(cxt, d->d_interleave, _("interleave"));
+ d->d_trackskew = ask_uint16(cxt, d->d_trackskew, _("trackskew"));
+ d->d_cylskew = ask_uint16(cxt, d->d_cylskew, _("cylinderskew"));
+
+ d->d_headswitch = ask_uint32(cxt, d->d_headswitch, _("headswitch"));
+ d->d_trkseek = ask_uint32(cxt, d->d_trkseek, _("track-to-track seek"));
+
+ d->d_secperunit = d->d_secpercyl * d->d_ncylinders;
+ return 0;
+}
+
+static int bsd_get_bootstrap(struct fdisk_context *cxt,
+ char *path, void *ptr, int size)
+{
+ int fd;
+
+ if ((fd = open(path, O_RDONLY)) < 0) {
+ fdisk_warn(cxt, _("cannot open %s"), path);
+ return -errno;
+ }
+
+ if (read_all(fd, ptr, size) != size) {
+ fdisk_warn(cxt, _("cannot read %s"), path);
+ close(fd);
+ return -errno;
+ }
+
+ fdisk_info(cxt, _("The bootstrap file %s successfully loaded."), path);
+ close (fd);
+ return 0;
+}
+
+/**
+ * fdisk_bsd_write_bootstrap:
+ * @cxt: context
+ *
+ * Install bootstrap file to the BSD device
+ */
+int fdisk_bsd_write_bootstrap(struct fdisk_context *cxt)
+{
+ struct bsd_disklabel dl, *d = self_disklabel(cxt);
+ struct fdisk_bsd_label *l = self_label(cxt);
+ char *name = d->d_type == BSD_DTYPE_SCSI ? "sd" : "wd";
+ char buf[BUFSIZ];
+ char *res, *dp, *p;
+ int rc;
+ fdisk_sector_t sector;
+
+ snprintf(buf, sizeof(buf),
+ _("Bootstrap: %1$sboot -> boot%1$s (default %1$s)"),
+ name);
+ rc = fdisk_ask_string(cxt, buf, &res);
+ if (rc)
+ goto done;
+ if (res && *res)
+ name = res;
+
+ snprintf(buf, sizeof(buf), "%s/%sboot", BSD_LINUX_BOOTDIR, name);
+ rc = bsd_get_bootstrap(cxt, buf, l->bsdbuffer, (int) d->d_secsize);
+ if (rc)
+ goto done;
+
+ /* We need a backup of the disklabel (might have changed). */
+ dp = &l->bsdbuffer[BSD_LABELSECTOR * DEFAULT_SECTOR_SIZE];
+ memmove(&dl, dp, sizeof(struct bsd_disklabel));
+
+ /* The disklabel will be overwritten by 0's from bootxx anyway */
+ memset(dp, 0, sizeof(struct bsd_disklabel));
+
+ snprintf(buf, sizeof(buf), "%s/boot%s", BSD_LINUX_BOOTDIR, name);
+ rc = bsd_get_bootstrap(cxt, buf,
+ &l->bsdbuffer[d->d_secsize],
+ (int) d->d_bbsize - d->d_secsize);
+ if (rc)
+ goto done;
+
+ /* check end of the bootstrap */
+ for (p = dp; p < dp + sizeof(struct bsd_disklabel); p++) {
+ if (!*p)
+ continue;
+ fdisk_warnx(cxt, _("Bootstrap overlaps with disklabel!"));
+ return -EINVAL;
+ }
+
+ /* move disklabel back */
+ memmove(dp, &dl, sizeof(struct bsd_disklabel));
+
+ sector = 0;
+ if (l->dos_part)
+ sector = dos_partition_get_start(l->dos_part);
+#if defined (__alpha__)
+ alpha_bootblock_checksum(l->bsdbuffer);
+#endif
+ if (lseek(cxt->dev_fd, (off_t) sector * DEFAULT_SECTOR_SIZE, SEEK_SET) == -1) {
+ fdisk_warn(cxt, _("seek on %s failed"), cxt->dev_path);
+ rc = -errno;
+ goto done;
+ }
+ if (write_all(cxt->dev_fd, l->bsdbuffer, BSD_BBSIZE)) {
+ fdisk_warn(cxt, _("cannot write %s"), cxt->dev_path);
+ rc = -errno;
+ goto done;
+ }
+
+ fdisk_info(cxt, _("Bootstrap installed on %s."), cxt->dev_path);
+ sync_disks(cxt);
+
+ rc = 0;
+done:
+ free(res);
+ return rc;
+}
+
+static unsigned short bsd_dkcksum (struct bsd_disklabel *lp)
+{
+ unsigned char *ptr, *end;
+ unsigned short sum = 0;
+
+ ptr = (unsigned char *) lp;
+ end = (unsigned char *) &lp->d_partitions[lp->d_npartitions];
+
+ while (ptr < end) {
+ unsigned short val;
+
+ memcpy(&val, ptr, sizeof(unsigned short));
+ sum ^= val;
+
+ ptr += sizeof(unsigned short);
+ }
+ return sum;
+}
+
+static int bsd_initlabel (struct fdisk_context *cxt)
+{
+ struct fdisk_bsd_label *l = self_label(cxt);
+ struct bsd_disklabel *d = self_disklabel(cxt);
+ struct bsd_partition *pp;
+
+ memset (d, 0, sizeof (struct bsd_disklabel));
+
+ d -> d_magic = BSD_DISKMAGIC;
+
+ if (strncmp (cxt->dev_path, "/dev/sd", 7) == 0)
+ d -> d_type = BSD_DTYPE_SCSI;
+ else
+ d -> d_type = BSD_DTYPE_ST506;
+
+#if !defined (__alpha__)
+ d -> d_flags = BSD_D_DOSPART;
+#else
+ d -> d_flags = 0;
+#endif
+ d -> d_secsize = DEFAULT_SECTOR_SIZE; /* bytes/sector */
+ d -> d_nsectors = cxt->geom.sectors; /* sectors/track */
+ d -> d_ntracks = cxt->geom.heads; /* tracks/cylinder (heads) */
+ d -> d_ncylinders = cxt->geom.cylinders;
+ d -> d_secpercyl = cxt->geom.sectors * cxt->geom.heads;/* sectors/cylinder */
+ if (d -> d_secpercyl == 0)
+ d -> d_secpercyl = 1; /* avoid segfaults */
+ d -> d_secperunit = d -> d_secpercyl * d -> d_ncylinders;
+
+ d -> d_rpm = 3600;
+ d -> d_interleave = 1;
+ d -> d_trackskew = 0;
+ d -> d_cylskew = 0;
+ d -> d_headswitch = 0;
+ d -> d_trkseek = 0;
+
+ d -> d_magic2 = BSD_DISKMAGIC;
+ d -> d_bbsize = BSD_BBSIZE;
+ d -> d_sbsize = BSD_SBSIZE;
+
+ if (l->dos_part) {
+ d->d_npartitions = 4;
+
+ pp = &d->d_partitions[2]; /* Partition C should be the NetBSD partition */
+ pp->p_offset = dos_partition_get_start(l->dos_part);
+ pp->p_size = dos_partition_get_size(l->dos_part);
+ pp->p_fstype = BSD_FS_UNUSED;
+
+ pp = &d -> d_partitions[3]; /* Partition D should be the whole disk */
+ pp->p_offset = 0;
+ pp->p_size = d->d_secperunit;
+ pp->p_fstype = BSD_FS_UNUSED;
+ } else {
+ d->d_npartitions = 3;
+
+ pp = &d->d_partitions[2]; /* Partition C should be the whole disk */
+ pp->p_offset = 0;
+ pp->p_size = d->d_secperunit;
+ pp->p_fstype = BSD_FS_UNUSED;
+ }
+
+ return 0;
+}
+
+/*
+ * Read a bsd_disklabel from sector 0 or from the starting sector of p.
+ * If it has the right magic, return 0.
+ */
+static int bsd_readlabel(struct fdisk_context *cxt)
+{
+ struct fdisk_bsd_label *l;
+ struct bsd_disklabel *d;
+ int t;
+ off_t offset = 0;
+
+ l = self_label(cxt);
+ d = self_disklabel(cxt);
+
+ if (l->dos_part)
+ /* BSD is nested within DOS partition, get the begin of the
+ * partition. Note that DOS uses native sector size. */
+ offset = dos_partition_get_start(l->dos_part) * cxt->sector_size;
+
+ if (lseek(cxt->dev_fd, offset, SEEK_SET) == -1)
+ return -1;
+ if (read_all(cxt->dev_fd, l->bsdbuffer, sizeof(l->bsdbuffer)) < 0)
+ return errno ? -errno : -1;
+
+ /* The offset to begin of the disk label. Note that BSD uses
+ * 512-byte (default) sectors. */
+ memmove(d, &l->bsdbuffer[BSD_LABELSECTOR * DEFAULT_SECTOR_SIZE
+ + BSD_LABELOFFSET], sizeof(*d));
+
+ if (d->d_magic != BSD_DISKMAGIC || d->d_magic2 != BSD_DISKMAGIC) {
+ DBG(LABEL, ul_debug("not found magic"));
+ return -1;
+ }
+
+ for (t = d->d_npartitions; t < BSD_MAXPARTITIONS; t++) {
+ d->d_partitions[t].p_size = 0;
+ d->d_partitions[t].p_offset = 0;
+ d->d_partitions[t].p_fstype = BSD_FS_UNUSED;
+ }
+
+ if (d->d_npartitions > BSD_MAXPARTITIONS)
+ fdisk_warnx(cxt, ("Too many partitions (%d, maximum is %d)."),
+ d->d_npartitions, BSD_MAXPARTITIONS);
+
+ /* let's follow in-PT geometry */
+ cxt->geom.sectors = d->d_nsectors;
+ cxt->geom.heads = d->d_ntracks;
+ cxt->geom.cylinders = d->d_ncylinders;
+
+ if (fdisk_has_user_device_geometry(cxt))
+ fdisk_apply_user_device_properties(cxt);
+
+ cxt->label->nparts_cur = d->d_npartitions;
+ cxt->label->nparts_max = BSD_MAXPARTITIONS;
+ DBG(LABEL, ul_debug("read BSD label"));
+ return 0;
+}
+
+static int bsd_write_disklabel(struct fdisk_context *cxt)
+{
+ off_t offset = 0;
+ struct fdisk_bsd_label *l = self_label(cxt);
+ struct bsd_disklabel *d = self_disklabel(cxt);
+
+
+ if (l->dos_part)
+ offset = dos_partition_get_start(l->dos_part) * cxt->sector_size;
+
+ d->d_checksum = 0;
+ d->d_checksum = bsd_dkcksum(d);
+
+ /* Update label within boot block. */
+ memmove(&l->bsdbuffer[BSD_LABELSECTOR * DEFAULT_SECTOR_SIZE
+ + BSD_LABELOFFSET], d, sizeof(*d));
+
+#if defined (__alpha__) && BSD_LABELSECTOR == 0
+ /* Write the checksum to the end of the first sector. */
+ alpha_bootblock_checksum(l->bsdbuffer);
+#endif
+ if (lseek(cxt->dev_fd, offset, SEEK_SET) == -1) {
+ fdisk_warn(cxt, _("seek on %s failed"), cxt->dev_path);
+ return -errno;
+ }
+ if (write_all(cxt->dev_fd, l->bsdbuffer, sizeof(l->bsdbuffer))) {
+ fdisk_warn(cxt, _("cannot write %s"), cxt->dev_path);
+ return -errno;
+ }
+ sync_disks(cxt);
+
+ if (cxt->parent && fdisk_label_is_changed(cxt->parent->label))
+ fdisk_info(cxt, _("Disklabel written to %s. (Don't forget to write the %s disklabel too.)"),
+ cxt->dev_path, cxt->parent->dev_path);
+ else
+ fdisk_info(cxt, _("Disklabel written to %s."), cxt->dev_path);
+ return 0;
+}
+
+static void sync_disks(struct fdisk_context *cxt)
+{
+ fdisk_info(cxt, _("Syncing disks."));
+ sync();
+}
+
+static int bsd_translate_fstype (int linux_type)
+{
+ switch (linux_type) {
+ case 0x01: /* DOS 12-bit FAT */
+ case 0x04: /* DOS 16-bit <32M */
+ case 0x06: /* DOS 16-bit >=32M */
+ case 0xe1: /* DOS access */
+ case 0xe3: /* DOS R/O */
+#if !defined (__alpha__)
+ case 0xf2: /* DOS secondary */
+ return BSD_FS_MSDOS;
+#endif
+ case 0x07: /* OS/2 HPFS */
+ return BSD_FS_HPFS;
+ default:
+ break;
+ }
+
+ return BSD_FS_OTHER;
+}
+
+/**
+ * fdisk_bsd_link_partition:
+ * @cxt: context
+ *
+ * Links partition from parent (DOS) to nested BSD partition table.
+ *
+ * Returns: 0 on success, <0 on error
+ */
+int fdisk_bsd_link_partition(struct fdisk_context *cxt)
+{
+ size_t k, i;
+ int rc;
+ struct dos_partition *p;
+ struct bsd_disklabel *d = self_disklabel(cxt);
+
+ if (!cxt->parent || !fdisk_is_label(cxt->parent, DOS)) {
+ fdisk_warnx(cxt, _("BSD label is not nested within a DOS partition."));
+ return -EINVAL;
+ }
+
+ /* ask for DOS partition */
+ rc = fdisk_ask_partnum(cxt->parent, &k, FALSE);
+ if (rc)
+ return rc;
+ /* ask for BSD partition */
+ rc = fdisk_ask_partnum(cxt, &i, TRUE);
+ if (rc)
+ return rc;
+
+ if (i >= BSD_MAXPARTITIONS)
+ return -EINVAL;
+
+ p = fdisk_dos_get_partition(cxt->parent, k);
+
+ d->d_partitions[i].p_size = dos_partition_get_size(p);
+ d->d_partitions[i].p_offset = dos_partition_get_start(p);
+ d->d_partitions[i].p_fstype = bsd_translate_fstype(p->sys_ind);
+
+ if (i >= d->d_npartitions)
+ d->d_npartitions = i + 1;
+
+ cxt->label->nparts_cur = d->d_npartitions;
+ fdisk_label_set_changed(cxt->label, 1);
+
+ fdisk_info(cxt, _("BSD partition '%c' linked to DOS partition %zu."),
+ 'a' + (int) i, k + 1);
+ return 0;
+}
+
+
+static int bsd_partition_is_used(
+ struct fdisk_context *cxt,
+ size_t partnum)
+{
+ struct bsd_disklabel *d = self_disklabel(cxt);
+
+ if (partnum >= BSD_MAXPARTITIONS)
+ return 0;
+
+ return d->d_partitions[partnum].p_size ? 1 : 0;
+}
+
+
+static const struct fdisk_label_operations bsd_operations =
+{
+ .probe = bsd_probe_label,
+ .get_item = bsd_get_disklabel_item,
+ .write = bsd_write_disklabel,
+ .create = bsd_create_disklabel,
+
+ .del_part = bsd_delete_part,
+ .get_part = bsd_get_partition,
+ .set_part = bsd_set_partition,
+ .add_part = bsd_add_partition,
+
+ .part_is_used = bsd_partition_is_used,
+};
+
+static const struct fdisk_field bsd_fields[] =
+{
+ { FDISK_FIELD_DEVICE, N_("Slice"), 1, 0 },
+ { FDISK_FIELD_START, N_("Start"), 5, FDISK_FIELDFL_NUMBER },
+ { FDISK_FIELD_END, N_("End"), 5, FDISK_FIELDFL_NUMBER },
+ { FDISK_FIELD_SECTORS, N_("Sectors"), 5, FDISK_FIELDFL_NUMBER },
+ { FDISK_FIELD_CYLINDERS,N_("Cylinders"), 5, FDISK_FIELDFL_NUMBER },
+ { FDISK_FIELD_SIZE, N_("Size"), 5, FDISK_FIELDFL_NUMBER },
+ { FDISK_FIELD_TYPE, N_("Type"), 8, 0 },
+ { FDISK_FIELD_FSIZE, N_("Fsize"), 5, FDISK_FIELDFL_NUMBER },
+ { FDISK_FIELD_BSIZE, N_("Bsize"), 5, FDISK_FIELDFL_NUMBER },
+ { FDISK_FIELD_CPG, N_("Cpg"), 5, FDISK_FIELDFL_NUMBER }
+};
+
+/*
+ * allocates BSD label driver
+ */
+struct fdisk_label *fdisk_new_bsd_label(struct fdisk_context *cxt __attribute__ ((__unused__)))
+{
+ struct fdisk_label *lb;
+ struct fdisk_bsd_label *bsd;
+
+ bsd = calloc(1, sizeof(*bsd));
+ if (!bsd)
+ return NULL;
+
+ /* initialize generic part of the driver */
+ lb = (struct fdisk_label *) bsd;
+ lb->name = "bsd";
+ lb->id = FDISK_DISKLABEL_BSD;
+ lb->op = &bsd_operations;
+ lb->parttypes = bsd_fstypes;
+ lb->nparttypes = ARRAY_SIZE(bsd_fstypes) - 1;
+
+ lb->fields = bsd_fields;
+ lb->nfields = ARRAY_SIZE(bsd_fields);
+
+ lb->flags |= FDISK_LABEL_FL_INCHARS_PARTNO;
+ lb->flags |= FDISK_LABEL_FL_REQUIRE_GEOMETRY;
+
+ /* return calloc() result to keep static anaylizers happy */
+ return (struct fdisk_label *) bsd;
+}
diff --git a/libfdisk/src/context.c b/libfdisk/src/context.c
new file mode 100644
index 0000000..0d22124
--- /dev/null
+++ b/libfdisk/src/context.c
@@ -0,0 +1,1555 @@
+#ifdef HAVE_LIBBLKID
+# include <blkid.h>
+#endif
+
+#include "blkdev.h"
+#ifdef __linux__
+# include "partx.h"
+#endif
+#include "loopdev.h"
+#include "fdiskP.h"
+
+#include "strutils.h"
+
+/**
+ * SECTION: context
+ * @title: Context
+ * @short_description: stores info about device, labels etc.
+ *
+ * The library distinguish between three types of partitioning objects.
+ *
+ * on-disk label 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 label 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
+ * - used (with fdisk_table) in fdisk scripts
+ * - 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
+ *
+ * Don't forget to inform kernel about changes by fdisk_reread_partition_table()
+ * or more smart fdisk_reread_changes().
+ */
+
+/**
+ * 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;
+
+ INIT_LIST_HEAD(&cxt->wipes);
+
+ 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_model);
+ cxt->dev_model = NULL;
+ cxt->dev_model_probed = 0;
+
+ return strdup_between_structs(cxt, parent, dev_path);
+}
+
+/**
+ * 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;
+
+ 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 can be used 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 then 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) {
+ DBG(CXT, ul_debugobj(cxt, " firstsector independent on parent (freeing)"));
+ free(cxt->firstsector);
+ }
+ } else {
+ /* we close device only in primary context */
+ if (cxt->dev_fd > -1 && cxt->is_priv)
+ close(cxt->dev_fd);
+ DBG(CXT, ul_debugobj(cxt, " freeing firstsector"));
+ free(cxt->firstsector);
+ }
+
+ free(cxt->dev_path);
+ cxt->dev_path = NULL;
+
+ free(cxt->dev_model);
+ cxt->dev_model = NULL;
+ cxt->dev_model_probed = 0;
+
+ free(cxt->collision);
+ cxt->collision = NULL;
+
+ memset(&cxt->dev_st, 0, sizeof(cxt->dev_st));
+
+ cxt->dev_fd = -1;
+ cxt->is_priv = 0;
+ cxt->is_excl = 0;
+ 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() body */
+static int fdisk_assign_fd(struct fdisk_context *cxt, int fd,
+ const char *fname, int readonly,
+ int priv, int excl)
+{
+ assert(cxt);
+ assert(fd >= 0);
+
+ errno = 0;
+
+ /* 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_fd(cxt->parent, fd, fname, readonly, priv, excl);
+ 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);
+
+ if (fstat(fd, &cxt->dev_st) != 0)
+ goto fail;
+
+ cxt->readonly = readonly ? 1 : 0;
+ cxt->dev_fd = fd;
+ cxt->is_priv = priv ? 1 : 0;
+ cxt->is_excl = excl ? 1 : 0;
+
+ cxt->dev_path = fname ? strdup(fname) : NULL;
+ 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;
+
+ /* warn about obsolete stuff on the device if we aren't in list-only */
+ if (!fdisk_is_listonly(cxt) && fdisk_check_collisions(cxt) < 0)
+ goto fail;
+
+ fdisk_probe_labels(cxt);
+ fdisk_apply_label_device_properties(cxt);
+
+ /* Don't report collision if there is already a valid partition table.
+ * The bootbits are wiped when we create a *new* partition table only. */
+ if (fdisk_is_ptcollision(cxt) && fdisk_has_label(cxt)) {
+ DBG(CXT, ul_debugobj(cxt, "ignore old %s", cxt->collision));
+ cxt->pt_collision = 0;
+ free(cxt->collision);
+ cxt->collision = NULL;
+ }
+
+ DBG(CXT, ul_debugobj(cxt, "initialized for %s [%s %s %s]",
+ fname,
+ cxt->readonly ? "READ-ONLY" : "READ-WRITE",
+ cxt->is_excl ? "EXCL" : "",
+ cxt->is_priv ? "PRIV" : ""));
+ return 0;
+fail:
+ {
+ int rc = errno ? -errno : -EINVAL;
+ cxt->dev_fd = -1;
+ DBG(CXT, ul_debugobj(cxt, "failed to assign device [rc=%d]", rc));
+ return rc;
+ }
+}
+
+/**
+ * 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, check for
+ * collisions and switch the current label driver to reflect the probing
+ * result.
+ *
+ * If in standard mode (!= non-listonly mode) then also detects for collisions.
+ * The result is accessible by fdisk_get_collision() and
+ * fdisk_is_ptcollision(). The collision (e.g. old obsolete PT) may be removed
+ * by fdisk_enable_wipe(). Note that new PT and old PT may be on different
+ * locations.
+ *
+ * Note that this function resets all generic setting in context.
+ *
+ * If the @cxt is nested context (necessary for example to edit BSD or PMBR)
+ * 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, rc, flags = O_CLOEXEC;
+
+ DBG(CXT, ul_debugobj(cxt, "assigning device %s", fname));
+ assert(cxt);
+
+ if (readonly)
+ flags |= O_RDONLY;
+ else
+ flags |= (O_RDWR | O_EXCL);
+
+ errno = 0;
+ fd = open(fname,flags);
+ if (fd < 0 && errno == EBUSY && (flags & O_EXCL)) {
+ flags &= ~O_EXCL;
+ errno = 0;
+ fd = open(fname, flags);
+ }
+
+ if (fd < 0) {
+ rc = -errno;
+ DBG(CXT, ul_debugobj(cxt, "failed to assign device [rc=%d]", rc));
+ return rc;
+ }
+
+ rc = fdisk_assign_fd(cxt, fd, fname, readonly, 1, flags & O_EXCL);
+ if (rc)
+ close(fd);
+ return rc;
+}
+
+/**
+ * fdisk_assign_device_by_fd:
+ * @cxt: context
+ * @fd: device file descriptor
+ * @fname: path to the device (used for dialogs, debugging, partition names, ...)
+ * @readonly: how to use the device
+ *
+ * Like fdisk_assign_device(), but caller is responsible to open and close the
+ * device. The library only fsync() the device on fdisk_deassign_device().
+ *
+ * The device has to be open O_RDWR on @readonly=0.
+ *
+ * Returns: 0 on success, < 0 on error.
+ *
+ * Since: 2.35
+ */
+int fdisk_assign_device_by_fd(struct fdisk_context *cxt, int fd,
+ const char *fname, int readonly)
+{
+ DBG(CXT, ul_debugobj(cxt, "assign by fd"));
+ return fdisk_assign_fd(cxt, fd, fname, readonly, 0, 0);
+}
+
+/**
+ * fdisk_deassign_device:
+ * @cxt: context
+ * @nosync: disable sync() after close().
+ *
+ * Call fsync(), close() and than sync(), but for read-only handler close the
+ * device only. If the @cxt is nested context then 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 && cxt->is_priv)
+ close(cxt->dev_fd);
+ else {
+ if (fsync(cxt->dev_fd)) {
+ fdisk_warn(cxt, _("%s: fsync device failed"),
+ cxt->dev_path);
+ return -errno;
+ }
+ if (cxt->is_priv && 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;
+ cxt->is_priv = 0;
+ cxt->is_excl = 0;
+
+ 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, fd, priv, excl;
+
+ 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;
+ fd = cxt->dev_fd;
+ priv = cxt->is_priv;
+ excl = cxt->is_excl;
+
+ fdisk_deassign_device(cxt, 1);
+
+ if (priv)
+ /* reopen and assign */
+ rc = fdisk_assign_device(cxt, devname, rdonly);
+ else
+ /* assign only */
+ rc = fdisk_assign_fd(cxt, fd, devname, rdonly, priv, excl);
+
+ 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;
+
+ 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 partx(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 behaves 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;
+ unsigned int ssf;
+
+ 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);
+ if (!rc)
+ rc = add_to_partitions_array(&add, pa, &nadds, nparts);
+ break;
+ }
+ if (rc != 0)
+ goto done;
+ }
+
+ /* sector size factor -- used to recount from real to 512-byte sectors */
+ ssf = cxt->sector_size / 512;
+
+ 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 * ssf, pa->size * ssf) != 0) {
+ fdisk_warn(cxt, _("Failed to update system information about partition %zu"), pa->partno + 1);
+ err++;
+ }
+ }
+ for (i = 0; i < nadds; i++) {
+ uint64_t sz;
+
+ pa = add[i];
+ sz = pa->size * ssf;
+
+ DBG(PART, ul_debugobj(pa, "#%zu calling BLKPG_ADD_PARTITION", pa->partno));
+
+ if (fdisk_is_label(cxt, DOS) && fdisk_partition_is_container(pa))
+ /* Let's follow the Linux kernel and reduce
+ * DOS extended partition to 1 or 2 sectors.
+ */
+ sz = min(sz, (uint64_t) 2);
+
+ if (partx_add_partition(cxt->dev_fd, pa->partno + 1,
+ pa->start * ssf, sz) != 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
+ *
+ * The function returns always 0 if the device has not been opened by
+ * fdisk_assign_device() or if open read-only.
+ *
+ * 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;
+ assert(cxt);
+ assert(cxt->dev_fd >= 0);
+
+ rc = cxt->readonly ? 0 :
+ cxt->is_excl ? 0 :
+ cxt->is_priv ? 1 : 0;
+
+ DBG(CXT, ul_debugobj(cxt, "device used: %s [read-only=%d, excl=%d, priv:%d]",
+ rc ? "TRUE" : "FALSE", cxt->readonly,
+ cxt->is_excl, cxt->is_priv));
+ 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]);
+ cxt->labels[i] = NULL;
+ }
+
+ 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.
+ *
+ * Note that this function changes only runtime information. It does not update
+ * any range in on-disk partition table. For example GPT Header contains First
+ * and Last usable LBA fields. These fields are not updated by this function.
+ * Be careful.
+ *
+ * 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_devno:
+ * @cxt: context
+ *
+ * Returns: device number or zero for non-block devices
+ *
+ * Since: 2.33
+ */
+dev_t fdisk_get_devno(struct fdisk_context *cxt)
+{
+ assert(cxt);
+ return S_ISBLK(cxt->dev_st.st_mode) ? cxt->dev_st.st_rdev : 0;
+}
+
+/**
+ * fdisk_get_devmodel:
+ * @cxt: context
+ *
+ * Returns: device model string or NULL.
+ *
+ * Since: 2.33
+ */
+#ifdef __linux__
+const char *fdisk_get_devmodel(struct fdisk_context *cxt)
+{
+ assert(cxt);
+
+ if (cxt->dev_model_probed)
+ return cxt->dev_model;
+
+ if (fdisk_get_devno(cxt)) {
+ struct path_cxt *pc = ul_new_sysfs_path(fdisk_get_devno(cxt), NULL, NULL);
+
+ if (pc) {
+ ul_path_read_string(pc, &cxt->dev_model, "device/model");
+ ul_unref_path(pc);
+ }
+ }
+ cxt->dev_model_probed = 1;
+ return cxt->dev_model;
+}
+#else
+const char *fdisk_get_devmodel(struct fdisk_context *cxt __attribute__((__unused__)))
+{
+ return NULL;
+}
+#endif
+
+/**
+ * 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;
+}
+
diff --git a/libfdisk/src/dos.c b/libfdisk/src/dos.c
new file mode 100644
index 0000000..7970ae1
--- /dev/null
+++ b/libfdisk/src/dos.c
@@ -0,0 +1,2912 @@
+/*
+ *
+ * Copyright (C) 2007-2013 Karel Zak <kzak@redhat.com>
+ * 2012 Davidlohr Bueso <dave@gnu.org>
+ * 2021 Pali Rohár <pali.rohar@gmail.com>
+ *
+ * This is re-written version for libfdisk, the original was fdiskdoslabel.c
+ * from util-linux fdisk.
+ */
+#include "c.h"
+#include "randutils.h"
+#include "pt-mbr.h"
+#include "strutils.h"
+
+#include "fdiskP.h"
+
+#include <ctype.h>
+
+#define MAXIMUM_PARTS 60
+#define ACTIVE_FLAG 0x80
+
+/**
+ * SECTION: dos
+ * @title: DOS
+ * @short_description: disk label specific functions
+ *
+ */
+
+
+#define IS_EXTENDED(i) \
+ ((i) == MBR_DOS_EXTENDED_PARTITION \
+ || (i) == MBR_W95_EXTENDED_PARTITION \
+ || (i) == MBR_LINUX_EXTENDED_PARTITION)
+
+/*
+ * per partition table entry data
+ *
+ * The four primary partitions have the same sectorbuffer
+ * and have NULL ex_entry.
+ *
+ * Each logical partition table entry has two pointers, one for the
+ * partition and one link to the next one.
+ */
+struct pte {
+ struct dos_partition *pt_entry; /* on-disk MBR entry */
+ struct dos_partition *ex_entry; /* on-disk EBR entry */
+ fdisk_sector_t offset; /* disk sector number */
+ unsigned char *sectorbuffer; /* disk sector contents */
+
+ unsigned int changed : 1,
+ private_sectorbuffer : 1;
+};
+
+/*
+ * in-memory fdisk GPT stuff
+ */
+struct fdisk_dos_label {
+ struct fdisk_label head; /* generic part */
+
+ struct pte ptes[MAXIMUM_PARTS]; /* partition */
+ fdisk_sector_t ext_offset; /* start of the ext.partition */
+ size_t ext_index; /* ext.partition index (if ext_offset is set) */
+ unsigned int compatible : 1, /* is DOS compatible? */
+ non_pt_changed : 1; /* MBR, but no PT changed */
+};
+
+/*
+ * Partition types
+ */
+static struct fdisk_parttype dos_parttypes[] = {
+ #include "pt-mbr-partnames.h"
+};
+
+static const struct fdisk_shortcut dos_parttype_cuts[] =
+{
+ { .shortcut = "L", .alias = "linux", .data = "83" },
+ { .shortcut = "S", .alias = "swap", .data = "82" },
+ { .shortcut = "E", .alias = "extended", .data = "05", .deprecated = 1 }, /* collision with 0x0e type */
+ { .shortcut = "Ex",.alias = "extended", .data = "05" }, /* MBR extended */
+ { .shortcut = "U", .alias = "uefi", .data = "EF" }, /* UEFI system */
+ { .shortcut = "R", .alias = "raid", .data = "FD" }, /* Linux RAID */
+ { .shortcut = "V", .alias = "lvm", .data = "8E" }, /* LVM */
+ { .shortcut = "X", .alias = "linuxex", .data = "85" } /* Linux extended */
+};
+
+
+#define sector(s) ((s) & 0x3f)
+#define cylinder(s, c) ((c) | (((s) & 0xc0) << 2))
+
+#define alignment_required(_x) ((_x)->grain != (_x)->sector_size)
+
+#define is_dos_compatible(_x) \
+ (fdisk_is_label(_x, DOS) && \
+ fdisk_dos_is_compatible(fdisk_get_label(_x, NULL)))
+
+#define cround(c, n) fdisk_cround(c, n)
+
+
+static inline struct fdisk_dos_label *self_label(struct fdisk_context *cxt)
+{
+ assert(cxt);
+ assert(cxt->label);
+ assert(fdisk_is_label(cxt, DOS));
+
+ return (struct fdisk_dos_label *) cxt->label;
+}
+
+static inline struct pte *self_pte(struct fdisk_context *cxt, size_t i)
+{
+ struct fdisk_dos_label *l = self_label(cxt);
+
+ if (i >= ARRAY_SIZE(l->ptes))
+ return NULL;
+
+ return &l->ptes[i];
+}
+
+static inline struct dos_partition *self_partition(
+ struct fdisk_context *cxt,
+ size_t i)
+{
+ struct pte *pe = self_pte(cxt, i);
+ return pe ? pe->pt_entry : NULL;
+}
+
+struct dos_partition *fdisk_dos_get_partition(
+ struct fdisk_context *cxt,
+ size_t i)
+{
+ assert(cxt);
+ assert(cxt->label);
+ assert(fdisk_is_label(cxt, DOS));
+
+ return self_partition(cxt, i);
+}
+
+static struct fdisk_parttype *dos_partition_parttype(
+ struct fdisk_context *cxt,
+ struct dos_partition *p)
+{
+ struct fdisk_parttype *t
+ = fdisk_label_get_parttype_from_code(cxt->label, p->sys_ind);
+ return t ? : fdisk_new_unknown_parttype(p->sys_ind, NULL);
+}
+
+/*
+ * Linux kernel cares about partition size only. Things like
+ * partition type or so are completely irrelevant -- kzak Nov-2013
+ */
+static int is_used_partition(struct dos_partition *p)
+{
+ return p && dos_partition_get_size(p) != 0;
+}
+
+static void partition_set_changed(
+ struct fdisk_context *cxt,
+ size_t i,
+ int changed)
+{
+ struct pte *pe = self_pte(cxt, i);
+
+ if (!pe)
+ return;
+
+ DBG(LABEL, ul_debug("DOS: setting %zu partition to %s", i,
+ changed ? "changed" : "unchanged"));
+
+ pe->changed = changed ? 1 : 0;
+ if (changed)
+ fdisk_label_set_changed(cxt->label, 1);
+}
+
+static fdisk_sector_t get_abs_partition_start(struct pte *pe)
+{
+ assert(pe);
+ assert(pe->pt_entry);
+
+ return pe->offset + dos_partition_get_start(pe->pt_entry);
+}
+
+static fdisk_sector_t get_abs_partition_end(struct pte *pe)
+{
+ fdisk_sector_t size;
+
+ assert(pe);
+ assert(pe->pt_entry);
+
+ size = dos_partition_get_size(pe->pt_entry);
+ return get_abs_partition_start(pe) + size - (size ? 1 : 0);
+}
+
+static int is_cleared_partition(struct dos_partition *p)
+{
+ return !(!p || p->boot_ind || p->bh || p->bs || p->bc ||
+ p->sys_ind || p->eh || p->es || p->ec ||
+ dos_partition_get_start(p) || dos_partition_get_size(p));
+}
+
+static int get_partition_unused_primary(struct fdisk_context *cxt,
+ struct fdisk_partition *pa,
+ size_t *partno)
+{
+ size_t org, n;
+ int rc;
+
+ assert(cxt);
+ assert(cxt->label);
+ assert(partno);
+
+ org = cxt->label->nparts_max;
+
+ cxt->label->nparts_max = 4;
+ rc = fdisk_partition_next_partno(pa, cxt, &n);
+ cxt->label->nparts_max = org;
+
+ if (rc == 1) {
+ fdisk_info(cxt, _("All primary partitions have been defined already."));
+ rc = -1;
+ } else if (rc == -ERANGE) {
+ fdisk_warnx(cxt, _("Primary partition not available."));
+ } else if (rc == 0)
+ *partno = n;
+
+ return rc;
+}
+
+static int seek_sector(struct fdisk_context *cxt, fdisk_sector_t secno)
+{
+ off_t offset = (off_t) secno * cxt->sector_size;
+
+ return lseek(cxt->dev_fd, offset, SEEK_SET) == (off_t) -1 ? -errno : 0;
+}
+
+static int read_sector(struct fdisk_context *cxt, fdisk_sector_t secno,
+ unsigned char *buf)
+{
+ int rc = seek_sector(cxt, secno);
+ ssize_t r;
+
+ if (rc < 0)
+ return rc;
+
+ r = read(cxt->dev_fd, buf, cxt->sector_size);
+ if (r == (ssize_t) cxt->sector_size)
+ return 0;
+ if (r < 0)
+ return -errno;
+ return -1;
+}
+
+/* Allocate a buffer and read a partition table sector */
+static int read_pte(struct fdisk_context *cxt, size_t pno, fdisk_sector_t offset)
+{
+ int rc;
+ unsigned char *buf;
+ struct pte *pe = self_pte(cxt, pno);
+
+ if (!pe)
+ return -EINVAL;
+
+ buf = calloc(1, cxt->sector_size);
+ if (!buf)
+ return -ENOMEM;
+
+ DBG(LABEL, ul_debug("DOS: reading EBR %zu: offset=%ju, buffer=%p",
+ pno, (uintmax_t) offset, buf));
+
+ pe->offset = offset;
+ pe->sectorbuffer = buf;
+ pe->private_sectorbuffer = 1;
+
+ rc = read_sector(cxt, offset, pe->sectorbuffer);
+ if (rc) {
+ fdisk_warn(cxt, _("Failed to read extended partition table "
+ "(offset=%ju)"), (uintmax_t) offset);
+ return rc;
+ }
+
+ pe->changed = 0;
+ pe->pt_entry = pe->ex_entry = NULL;
+ return 0;
+}
+
+
+static void clear_partition(struct dos_partition *p)
+{
+ if (!p)
+ return;
+ p->boot_ind = 0;
+ p->bh = 0;
+ p->bs = 0;
+ p->bc = 0;
+ p->sys_ind = 0;
+ p->eh = 0;
+ p->es = 0;
+ p->ec = 0;
+ dos_partition_set_start(p,0);
+ dos_partition_set_size(p,0);
+}
+
+static void dos_init(struct fdisk_context *cxt)
+{
+ struct fdisk_dos_label *l = self_label(cxt);
+ size_t i;
+
+ assert(cxt);
+ assert(cxt->label);
+ assert(fdisk_is_label(cxt, DOS));
+
+ DBG(LABEL, ul_debug("DOS: initialize, first sector buffer %p", cxt->firstsector));
+
+ cxt->label->nparts_max = 4; /* default, unlimited number of logical */
+
+ l->ext_index = 0;
+ l->ext_offset = 0;
+ l->non_pt_changed = 0;
+
+ memset(l->ptes, 0, sizeof(l->ptes));
+
+ for (i = 0; i < 4; i++) {
+ struct pte *pe = self_pte(cxt, i);
+
+ assert(pe);
+ pe->pt_entry = mbr_get_partition(cxt->firstsector, i);
+ pe->ex_entry = NULL;
+ pe->offset = 0;
+ pe->sectorbuffer = cxt->firstsector;
+ pe->private_sectorbuffer = 0;
+ pe->changed = 0;
+
+ DBG(LABEL, ul_debug("DOS: initialize: #%zu start=%u size=%u sysid=%02x",
+ i,
+ dos_partition_get_start(pe->pt_entry),
+ dos_partition_get_size(pe->pt_entry),
+ pe->pt_entry->sys_ind));
+ }
+
+ if (fdisk_is_listonly(cxt))
+ return;
+ /*
+ * Various warnings...
+ */
+ if (fdisk_missing_geometry(cxt))
+ fdisk_warnx(cxt, _("You can set geometry from the extra functions menu."));
+
+ if (is_dos_compatible(cxt)) {
+ fdisk_warnx(cxt, _("DOS-compatible mode is deprecated."));
+
+ if (cxt->sector_size != cxt->phy_sector_size)
+ fdisk_info(cxt, _(
+ "The device presents a logical sector size that is smaller than "
+ "the physical sector size. Aligning to a physical sector (or optimal "
+ "I/O) size boundary is recommended, or performance may be impacted."));
+ }
+
+ if (fdisk_use_cylinders(cxt))
+ fdisk_warnx(cxt, _("Cylinders as display units are deprecated."));
+
+ if (cxt->total_sectors > UINT_MAX) {
+ uint64_t bytes = cxt->total_sectors * cxt->sector_size;
+ char *szstr = size_to_human_string(SIZE_SUFFIX_SPACE
+ | SIZE_SUFFIX_3LETTER, bytes);
+ fdisk_warnx(cxt,
+ _("The size of this disk is %s (%ju bytes). DOS "
+ "partition table format cannot be used on drives for "
+ "volumes larger than %lu bytes for %lu-byte "
+ "sectors. Use GUID partition table format (GPT)."),
+ szstr, bytes,
+ UINT_MAX * cxt->sector_size,
+ cxt->sector_size);
+ free(szstr);
+ }
+}
+
+/* callback called by libfdisk */
+static void dos_deinit(struct fdisk_label *lb)
+{
+ size_t i;
+ struct fdisk_dos_label *l = (struct fdisk_dos_label *) lb;
+
+ for (i = 0; i < ARRAY_SIZE(l->ptes); i++) {
+ struct pte *pe = &l->ptes[i];
+
+ if (pe->private_sectorbuffer && pe->sectorbuffer) {
+ DBG(LABEL, ul_debug("DOS: freeing pte %zu sector buffer %p",
+ i, pe->sectorbuffer));
+ free(pe->sectorbuffer);
+ }
+ pe->sectorbuffer = NULL;
+ pe->private_sectorbuffer = 0;
+ }
+
+ memset(l->ptes, 0, sizeof(l->ptes));
+}
+
+static void reset_pte(struct pte *pe)
+{
+ assert(pe);
+
+ if (pe->private_sectorbuffer) {
+ DBG(LABEL, ul_debug(" --> freeing pte sector buffer %p",
+ pe->sectorbuffer));
+ free(pe->sectorbuffer);
+ }
+ memset(pe, 0, sizeof(struct pte));
+}
+
+static int delete_partition(struct fdisk_context *cxt, size_t partnum)
+{
+ struct fdisk_dos_label *l;
+ struct pte *pe;
+ struct dos_partition *p;
+ struct dos_partition *q;
+
+ assert(cxt);
+ assert(cxt->label);
+ assert(fdisk_is_label(cxt, DOS));
+
+ pe = self_pte(cxt, partnum);
+ if (!pe)
+ return -EINVAL;
+
+ DBG(LABEL, ul_debug("DOS: delete partition %zu (max=%zu)", partnum,
+ cxt->label->nparts_max));
+
+ l = self_label(cxt);
+ p = pe->pt_entry;
+ q = pe->ex_entry;
+
+ /* Note that for the fifth partition (partnum == 4) we don't actually
+ decrement partitions. */
+ if (partnum < 4) {
+ DBG(LABEL, ul_debug("--> delete primary"));
+ if (IS_EXTENDED(p->sys_ind) && partnum == l->ext_index) {
+ size_t i;
+ DBG(LABEL, ul_debug(" --> delete extended"));
+ for (i = 4; i < cxt->label->nparts_max; i++) {
+ DBG(LABEL, ul_debug(" --> delete logical #%zu", i));
+ reset_pte(&l->ptes[i]);
+
+ }
+ cxt->label->nparts_max = 4;
+ l->ptes[l->ext_index].ex_entry = NULL;
+ l->ext_offset = 0;
+ l->ext_index = 0;
+ }
+ partition_set_changed(cxt, partnum, 1);
+ clear_partition(p);
+ } else if (!q->sys_ind && partnum > 4) {
+ DBG(LABEL, ul_debug("--> delete logical [last in the chain]"));
+ reset_pte(&l->ptes[partnum]);
+ --cxt->label->nparts_max;
+ --partnum;
+ /* clear link to deleted partition */
+ clear_partition(l->ptes[partnum].ex_entry);
+ partition_set_changed(cxt, partnum, 1);
+ } else {
+ DBG(LABEL, ul_debug("--> delete logical [move down]"));
+ if (partnum > 4) {
+ DBG(LABEL, ul_debug(" --> delete %zu logical link", partnum));
+ p = l->ptes[partnum - 1].ex_entry;
+ *p = *q;
+ dos_partition_set_start(p, dos_partition_get_start(q));
+ dos_partition_set_size(p, dos_partition_get_size(q));
+ dos_partition_sync_chs(p, pe->offset, cxt->geom.sectors, cxt->geom.heads);
+ partition_set_changed(cxt, partnum - 1, 1);
+
+ } else if (cxt->label->nparts_max > 5) {
+ DBG(LABEL, ul_debug(" --> delete first logical link"));
+ pe = &l->ptes[5]; /* second logical */
+
+ if (pe->pt_entry) /* prevent SEGFAULT */
+ dos_partition_set_start(pe->pt_entry,
+ get_abs_partition_start(pe) -
+ l->ext_offset);
+ pe->offset = l->ext_offset;
+ dos_partition_sync_chs(p, pe->offset, cxt->geom.sectors, cxt->geom.heads);
+ partition_set_changed(cxt, 5, 1);
+ }
+
+ if (cxt->label->nparts_max > 5) {
+ DBG(LABEL, ul_debug(" --> move ptes"));
+ cxt->label->nparts_max--;
+ reset_pte(&l->ptes[partnum]);
+ while (partnum < cxt->label->nparts_max) {
+ DBG(LABEL, ul_debug(" --> moving pte %zu <-- %zu", partnum, partnum + 1));
+ l->ptes[partnum] = l->ptes[partnum + 1];
+ partnum++;
+ }
+ memset(&l->ptes[partnum], 0, sizeof(struct pte));
+ } else {
+ DBG(LABEL, ul_debug(" --> the only logical: clear only"));
+ clear_partition(l->ptes[partnum].pt_entry);
+ cxt->label->nparts_max--;
+
+ if (partnum == 4) {
+ DBG(LABEL, ul_debug(" --> clear last logical"));
+ reset_pte(&l->ptes[partnum]);
+ partition_set_changed(cxt, l->ext_index, 1);
+ }
+ }
+ }
+
+ fdisk_label_set_changed(cxt->label, 1);
+ return 0;
+}
+
+static int dos_delete_partition(struct fdisk_context *cxt, size_t partnum)
+{
+ struct pte *pe;
+
+ assert(cxt);
+ assert(cxt->label);
+ assert(fdisk_is_label(cxt, DOS));
+
+ pe = self_pte(cxt, partnum);
+ if (!pe || !is_used_partition(pe->pt_entry))
+ return -EINVAL;
+
+ return delete_partition(cxt, partnum);
+}
+
+static void read_extended(struct fdisk_context *cxt, size_t ext)
+{
+ size_t i;
+ struct pte *pex, *pe;
+ struct dos_partition *p, *q;
+ struct fdisk_dos_label *l = self_label(cxt);
+
+ l->ext_index = ext;
+ pex = self_pte(cxt, ext);
+ if (!pex) {
+ DBG(LABEL, ul_debug("DOS: uninitialized pointer to %zu pex", ext));
+ return;
+ }
+ pex->ex_entry = pex->pt_entry;
+
+ p = pex->pt_entry;
+ if (!dos_partition_get_start(p)) {
+ fdisk_warnx(cxt, _("Bad offset in primary extended partition."));
+ return;
+ }
+
+ DBG(LABEL, ul_debug("DOS: Reading extended %zu", ext));
+
+ while (IS_EXTENDED (p->sys_ind)) {
+ if (cxt->label->nparts_max >= MAXIMUM_PARTS) {
+ /* This is not a Linux restriction, but
+ this program uses arrays of size MAXIMUM_PARTS.
+ Do not try to `improve' this test. */
+ struct pte *pre = self_pte(cxt,
+ cxt->label->nparts_max - 1);
+ fdisk_warnx(cxt,
+ _("Omitting partitions after #%zu. They will be deleted "
+ "if you save this partition table."),
+ cxt->label->nparts_max);
+
+ if (pre) {
+ clear_partition(pre->ex_entry);
+ partition_set_changed(cxt,
+ cxt->label->nparts_max - 1, 1);
+ }
+ return;
+ }
+
+ pe = self_pte(cxt, cxt->label->nparts_max);
+ if (!pe)
+ return;
+
+ if (read_pte(cxt, cxt->label->nparts_max, l->ext_offset +
+ dos_partition_get_start(p)))
+ return;
+
+ if (!l->ext_offset)
+ l->ext_offset = dos_partition_get_start(p);
+
+ assert(pe->sectorbuffer);
+ q = p = mbr_get_partition(pe->sectorbuffer, 0);
+
+ for (i = 0; i < 4; i++, p++) {
+ if (!dos_partition_get_size(p))
+ continue;
+
+ if (IS_EXTENDED (p->sys_ind)) {
+ if (pe->ex_entry)
+ fdisk_warnx(cxt, _(
+ "Extra link pointer in partition "
+ "table %zu."),
+ cxt->label->nparts_max + 1);
+ else
+ pe->ex_entry = p;
+ } else if (p->sys_ind) {
+ if (pe->pt_entry)
+ fdisk_warnx(cxt, _(
+ "Ignoring extra data in partition "
+ "table %zu."),
+ cxt->label->nparts_max + 1);
+ else
+ pe->pt_entry = p;
+ }
+ }
+
+ /* very strange code here... */
+ if (!pe->pt_entry) {
+ if (q != pe->ex_entry)
+ pe->pt_entry = q;
+ else
+ pe->pt_entry = q + 1;
+ }
+ if (!pe->ex_entry) {
+ if (q != pe->pt_entry)
+ pe->ex_entry = q;
+ else
+ pe->ex_entry = q + 1;
+ }
+
+ p = pe->ex_entry;
+ cxt->label->nparts_cur = ++cxt->label->nparts_max;
+
+ DBG(LABEL, ul_debug("DOS: EBR[offset=%ju]: link: type=%x, start=%u, size=%u; "
+ " data: type=%x, start=%u, size=%u",
+ (uintmax_t) pe->offset,
+ pe->ex_entry->sys_ind,
+ dos_partition_get_start(pe->ex_entry),
+ dos_partition_get_size(pe->ex_entry),
+ pe->pt_entry->sys_ind,
+ dos_partition_get_start(pe->pt_entry),
+ dos_partition_get_size(pe->pt_entry)));
+
+ }
+
+ /* remove last empty EBR */
+ pe = self_pte(cxt, cxt->label->nparts_max - 1);
+ if (pe &&
+ is_cleared_partition(pe->ex_entry) &&
+ is_cleared_partition(pe->pt_entry)) {
+ DBG(LABEL, ul_debug("DOS: EBR[offset=%ju]: empty, remove", (uintmax_t) pe->offset));
+ reset_pte(pe);
+ cxt->label->nparts_max--;
+ cxt->label->nparts_cur--;
+ }
+
+ /* remove empty links */
+ remove:
+ q = self_partition(cxt, 4);
+ for (i = 4; i < cxt->label->nparts_max; i++) {
+ p = self_partition(cxt, i);
+
+ if (p && !dos_partition_get_size(p) &&
+ (cxt->label->nparts_max > 5 || (q && q->sys_ind))) {
+ fdisk_info(cxt, _("omitting empty partition (%zu)"), i+1);
+ delete_partition(cxt, i);
+ goto remove; /* numbering changed */
+ }
+ }
+
+ DBG(LABEL, ul_debug("DOS: nparts_max: %zu", cxt->label->nparts_max));
+}
+
+static int dos_create_disklabel(struct fdisk_context *cxt)
+{
+ unsigned int id = 0;
+ int rc, has_id = 0;
+ struct fdisk_dos_label *l;
+
+ assert(cxt);
+ assert(cxt->label);
+ assert(fdisk_is_label(cxt, DOS));
+
+ DBG(LABEL, ul_debug("DOS: creating new disklabel"));
+
+ if (cxt->script) {
+ char *end = NULL;
+ const char *s = fdisk_script_get_header(cxt->script, "label-id");
+
+ if (s) {
+ errno = 0;
+ id = strtoul(s, &end, 16);
+ if (!errno && end && s < end) {
+ has_id = 1;
+ DBG(LABEL, ul_debug("DOS: re-use ID from script (0x%08x)", id));
+ } else
+ DBG(LABEL, ul_debug("DOS: failed to parse label=id '%s'", s));
+ }
+ }
+
+ /* random disk signature */
+ if (!has_id) {
+ DBG(LABEL, ul_debug("DOS: generate new ID"));
+ ul_random_get_bytes(&id, sizeof(id));
+ }
+
+ if (fdisk_has_protected_bootbits(cxt))
+ rc = fdisk_init_firstsector_buffer(cxt, 0, MBR_PT_BOOTBITS_SIZE);
+ else
+ rc = fdisk_init_firstsector_buffer(cxt, 0, 0);
+ if (rc)
+ return rc;
+ dos_init(cxt);
+
+ l = self_label(cxt);
+
+ /* Generate an MBR ID for this disk */
+ mbr_set_id(cxt->firstsector, id);
+ l->non_pt_changed = 1;
+ fdisk_label_set_changed(cxt->label, 1);
+
+ /* Put MBR signature */
+ mbr_set_magic(cxt->firstsector);
+
+ fdisk_info(cxt, _("Created a new DOS (MBR) disklabel with disk "
+ "identifier 0x%08x."), id);
+ return 0;
+}
+
+static int dos_set_disklabel_id(struct fdisk_context *cxt, const char *str)
+{
+ char *buf = NULL;
+ unsigned int id, old;
+ struct fdisk_dos_label *l;
+ int rc = 0;
+
+ assert(cxt);
+ assert(cxt->label);
+ assert(fdisk_is_label(cxt, DOS));
+
+ DBG(LABEL, ul_debug("DOS: setting Id"));
+
+ l = self_label(cxt);
+ old = mbr_get_id(cxt->firstsector);
+
+ if (!str) {
+ rc = fdisk_ask_string(cxt,
+ _("Enter the new disk identifier"), &buf);
+ str = buf;
+ }
+ if (!rc) {
+ char *end = NULL;
+
+ errno = 0;
+ id = strtoul(str, &end, 0);
+ if (errno || str == end || (end && *end)) {
+ fdisk_warnx(cxt, _("Incorrect value."));
+ rc = -EINVAL;
+ }
+ }
+
+ free(buf);
+ if (rc)
+ return -EINVAL;
+
+ mbr_set_id(cxt->firstsector, id);
+ l->non_pt_changed = 1;
+ fdisk_label_set_changed(cxt->label, 1);
+
+ fdisk_info(cxt, _("Disk identifier changed from 0x%08x to 0x%08x."),
+ old, id);
+ return 0;
+}
+
+static unsigned int chs_div_minus(unsigned int a1, unsigned int a2, unsigned int b1, unsigned int b2)
+{
+ if (a1 > a2 && b1 > b2) {
+ a1 = a1 - a2;
+ b1 = b1 - b2;
+ } else if (a2 > a1 && b2 > b1) {
+ a1 = a2 - a1;
+ b1 = b2 - b1;
+ } else {
+ return 0;
+ }
+ if (a1 % b1)
+ return 0;
+ return a1 / b1;
+}
+
+static inline int chs_overflowed(unsigned int c, unsigned int h, unsigned int s)
+{
+ /* 1023/254/63 or 1023/255/63 indicates overflowed/invalid C/H/S values */
+ return (c == 1023 && (h == 254 || h == 255) && s == 63);
+}
+
+static inline int lba_overflowed(fdisk_sector_t start, fdisk_sector_t sects)
+{
+ /* Check if the last LBA sector can be represented by unsigned 32bit int */
+ return (start + (sects-1) > UINT32_MAX);
+}
+
+static void get_partition_table_geometry(struct fdisk_context *cxt,
+ unsigned int *ph, unsigned int *ps)
+{
+ unsigned char *bufp = cxt->firstsector;
+ struct { unsigned int c, h, o, v; } t[8];
+ unsigned int n1, n2, n3, n4, n5, n6;
+ struct dos_partition *p;
+ unsigned int c, h, s, l;
+ unsigned int hh, ss;
+ unsigned int sects;
+ int i, j, dif;
+
+#define chs_set_t(c, h, s, l, t, i) do { \
+ t[i].c = c; \
+ t[i].h = h; \
+ t[i].o = l - (s-1); \
+ t[i].v = (!chs_overflowed(c, h, s) && s && s-1 <= l); \
+} while (0)
+
+ /*
+ * Conversion from C/H/S to LBA is defined by formula:
+ * LBA = (c * N_heads + h) * N_sectors + (s - 1)
+ * Let o to be:
+ * o = LBA - (s - 1)
+ * Then formula can be expressed as:
+ * o = (c * N_heads + h) * N_sectors
+ * In general from two tuples (LBA1, c1, h1, s1), (LBA2, c2, h2, s2)
+ * we can derive formulas for N_heads and N_sectors:
+ * N_heads = (o1 * h2 - o2 * h1) / (o2 * c1 - o1 * c2)
+ * N_sectors = (o2 * c1 - o1 * c2) / (c1 * h2 - c2 * h1)
+ * MBR table contains for very partition start and end tuple.
+ * So we have up to 8 tuples which leads to up to 28 equations
+ * for calculating N_heads and N_sectors. Try to calculate
+ * N_heads and N_sectors from the first possible partition and
+ * if it fails then try also mixed tuples (beginning from first
+ * partition and end from second). Calculation may fail if both
+ * first and last sectors are on cylinder or head boundary
+ * (dividend or divisor is zero). It is possible that different
+ * partitions would have different C/H/S geometry. In this case
+ * we want geometry from the first partition as in most cases
+ * this partition is or was used by BIOS for booting.
+ */
+
+ hh = ss = 0;
+ for (i = 0; i < 4; i++) {
+ p = mbr_get_partition(bufp, i);
+ if (!p->sys_ind)
+ continue;
+
+ c = cylinder(p->bs, p->bc);
+ h = p->bh;
+ s = sector(p->bs);
+ l = dos_partition_get_start(p);
+ chs_set_t(c, h, s, l, t, 2*i);
+
+ sects = dos_partition_get_size(p);
+ if (!sects || lba_overflowed(l, sects))
+ continue;
+
+ c = cylinder(p->es, p->ec);
+ h = p->eh;
+ s = sector(p->es);
+ l += sects-1;
+ chs_set_t(c, h, s, l, t, 2*i+1);
+ }
+
+ for (dif = 1; dif < 8; dif++) {
+ for (i = 0; i + dif < 8; i++) {
+ j = i + dif;
+ if (!t[i].v || !t[j].v)
+ continue;
+ n1 = t[i].o * t[j].h;
+ n2 = t[j].o * t[i].h;
+ n3 = t[j].o * t[i].c;
+ n4 = t[i].o * t[j].c;
+ n5 = t[i].c * t[j].h;
+ n6 = t[j].c * t[i].h;
+ if (!hh && n1 != n2 && n3 != n4) {
+ h = chs_div_minus(n1, n2, n3, n4);
+ if (h > 0 && h <= 256)
+ hh = h;
+ }
+ if (!ss && n3 != n4 && n5 != n6) {
+ s = chs_div_minus(n3, n4, n5, n6);
+ if (s > 0 && s <= 63)
+ ss = s;
+ }
+ if (hh && ss)
+ break;
+ }
+ if (hh && ss)
+ break;
+ }
+
+ if (hh && ss) {
+ *ph = hh;
+ *ps = ss;
+ }
+
+ DBG(LABEL, ul_debug("DOS PT geometry: heads=%u, sectors=%u", *ph, *ps));
+}
+
+static int dos_reset_alignment(struct fdisk_context *cxt)
+{
+ assert(cxt);
+ assert(cxt->label);
+ assert(fdisk_is_label(cxt, DOS));
+
+ /* overwrite necessary stuff by DOS deprecated stuff */
+ if (is_dos_compatible(cxt)) {
+ DBG(LABEL, ul_debug("DOS: resetting alignment for DOS-compatible PT"));
+ if (cxt->geom.sectors)
+ cxt->first_lba = cxt->geom.sectors; /* usually 63 */
+
+ cxt->grain = cxt->sector_size; /* usually 512 */
+ }
+
+ return 0;
+}
+
+/* TODO: move to include/pt-dos.h and share with libblkid */
+#define AIX_MAGIC_STRING "\xC9\xC2\xD4\xC1"
+#define AIX_MAGIC_STRLEN (sizeof(AIX_MAGIC_STRING) - 1)
+
+static int dos_probe_label(struct fdisk_context *cxt)
+{
+ size_t i;
+ unsigned int h = 0, s = 0;
+
+ assert(cxt);
+ assert(cxt->label);
+ assert(fdisk_is_label(cxt, DOS));
+
+ /* ignore disks with AIX magic number */
+ if (memcmp(cxt->firstsector, AIX_MAGIC_STRING, AIX_MAGIC_STRLEN) == 0)
+ return 0;
+
+ if (!mbr_is_valid_magic(cxt->firstsector))
+ return 0;
+
+ dos_init(cxt);
+
+ get_partition_table_geometry(cxt, &h, &s);
+ if (h && s) {
+ cxt->geom.heads = h;
+ cxt->geom.sectors = s;
+ cxt->geom.cylinders = cxt->total_sectors /
+ (cxt->geom.heads * cxt->geom.sectors);
+
+ if (fdisk_has_user_device_geometry(cxt))
+ fdisk_apply_user_device_properties(cxt);
+ }
+
+ for (i = 0; i < 4; i++) {
+ struct pte *pe = self_pte(cxt, i);
+
+ assert(pe);
+ if (is_used_partition(pe->pt_entry))
+ cxt->label->nparts_cur++;
+
+ if (IS_EXTENDED (pe->pt_entry->sys_ind)) {
+ if (cxt->label->nparts_max != 4)
+ fdisk_warnx(cxt, _(
+ "Ignoring extra extended partition %zu"),
+ i + 1);
+ else
+ read_extended(cxt, i);
+ }
+ }
+
+ for (i = 3; i < cxt->label->nparts_max; i++) {
+ struct pte *pe = self_pte(cxt, i);
+ struct fdisk_dos_label *l = self_label(cxt);
+
+ assert(pe);
+ if (!mbr_is_valid_magic(pe->sectorbuffer)) {
+ fdisk_info(cxt, _(
+ "Invalid flag 0x%02x%02x of EBR (for partition %zu) will "
+ "be corrected by w(rite)."),
+ pe->sectorbuffer[510],
+ pe->sectorbuffer[511],
+ i + 1);
+ partition_set_changed(cxt, i, 1);
+
+ /* mark also extended as changed to update the first EBR
+ * in situation that there is no logical partitions at all */
+ partition_set_changed(cxt, l->ext_index, 1);
+ }
+ }
+
+ return 1;
+}
+
+static void set_partition(struct fdisk_context *cxt,
+ int i, int doext, fdisk_sector_t start,
+ fdisk_sector_t stop, int sysid, int boot)
+{
+ struct pte *pe = self_pte(cxt, i);
+ struct dos_partition *p;
+ fdisk_sector_t offset;
+
+ assert(!FDISK_IS_UNDEF(start));
+ assert(!FDISK_IS_UNDEF(stop));
+ assert(pe);
+
+ if (doext) {
+ struct fdisk_dos_label *l = self_label(cxt);
+ p = pe->ex_entry;
+ offset = l->ext_offset;
+ } else {
+ p = pe->pt_entry;
+ offset = pe->offset;
+ }
+
+ DBG(LABEL, ul_debug("DOS: setting partition %d%s, offset=%zu, start=%zu, size=%zu, sysid=%02x",
+ i, doext ? " [extended]" : "",
+ (size_t) offset,
+ (size_t) (start - offset),
+ (size_t) (stop - start + 1),
+ sysid));
+
+ p->boot_ind = boot ? ACTIVE_FLAG : 0;
+ p->sys_ind = sysid;
+ dos_partition_set_start(p, start - offset);
+ dos_partition_set_size(p, stop - start + 1);
+ dos_partition_sync_chs(p, offset, cxt->geom.sectors, cxt->geom.heads);
+ partition_set_changed(cxt, i, 1);
+}
+
+
+static int get_start_from_user( struct fdisk_context *cxt,
+ fdisk_sector_t *start,
+ fdisk_sector_t low,
+ fdisk_sector_t dflt,
+ fdisk_sector_t limit,
+ struct fdisk_partition *pa)
+{
+ assert(start);
+
+ /* try to use template from 'pa' */
+ if (pa && pa->start_follow_default)
+ *start = dflt;
+
+ else if (pa && fdisk_partition_has_start(pa)) {
+ DBG(LABEL, ul_debug("DOS: start: wanted=%ju, low=%ju, limit=%ju",
+ (uintmax_t) pa->start, (uintmax_t) low, (uintmax_t) limit));
+ *start = pa->start;
+ if (*start < low || *start > limit) {
+ fdisk_warnx(cxt, _("Start sector %ju out of range."),
+ (uintmax_t) *start);
+ return -ERANGE;
+ }
+ } else {
+ /* ask user by dialog */
+ struct fdisk_ask *ask = fdisk_new_ask();
+ int rc;
+
+ if (!ask)
+ return -ENOMEM;
+ fdisk_ask_set_query(ask,
+ fdisk_use_cylinders(cxt) ?
+ _("First cylinder") : _("First sector"));
+ fdisk_ask_set_type(ask, FDISK_ASKTYPE_NUMBER);
+ fdisk_ask_number_set_low(ask, fdisk_cround(cxt, low));
+ fdisk_ask_number_set_default(ask, fdisk_cround(cxt, dflt));
+ fdisk_ask_number_set_high(ask, fdisk_cround(cxt, limit));
+
+ rc = fdisk_do_ask(cxt, ask);
+ *start = fdisk_ask_number_get_result(ask);
+ fdisk_unref_ask(ask);
+ if (rc)
+ return rc;
+ if (fdisk_use_cylinders(cxt)) {
+ *start = (*start - 1)
+ * fdisk_get_units_per_sector(cxt);
+ if (*start < low)
+ *start = low;
+ }
+ }
+
+ DBG(LABEL, ul_debug("DOS: start is %ju", (uintmax_t) *start));
+ return 0;
+}
+
+/* Returns last available sector in the free space pointed to by start. */
+static int find_last_free(
+ struct fdisk_context *cxt,
+ int logical,
+ fdisk_sector_t begin,
+ fdisk_sector_t stop,
+ fdisk_sector_t *result)
+{
+ fdisk_sector_t last = stop;
+
+ size_t i = logical ? 4 : 0;
+
+ for ( ; i < cxt->label->nparts_max; i++) {
+ struct pte *pe = self_pte(cxt, i);
+
+ assert(pe);
+ fdisk_sector_t p_start = get_abs_partition_start(pe);
+ fdisk_sector_t p_end = get_abs_partition_end(pe);
+
+ if (is_cleared_partition(pe->pt_entry))
+ continue;
+ /* count EBR and begin of the logical partition as used area */
+ if (pe->offset)
+ p_start -= cxt->first_lba;
+
+ if ((p_start >= begin && p_start <= last) ||
+ (p_end >= begin && p_end <= last)) {
+ last = p_start - 1;
+ }
+ if (last < begin) {
+ DBG(LABEL, ul_debug("no free space <%ju,%ju>",
+ (uintmax_t) begin, (uintmax_t) stop));
+ return -ENOSPC;
+ }
+ }
+
+ if (last == begin)
+ last = stop;
+
+ DBG(LABEL, ul_debug("DOS: last free sector <%ju,%ju>: %ju",
+ (uintmax_t) begin, (uintmax_t) stop, (uintmax_t) last));
+
+ *result = last;
+ return 0;
+}
+
+static int find_last_free_sector_in_range(
+ struct fdisk_context *cxt,
+ int logical,
+ fdisk_sector_t begin,
+ fdisk_sector_t end,
+ fdisk_sector_t *result)
+{
+ int last_moved;
+ fdisk_sector_t last = end;
+
+ do {
+ size_t i = logical ? 4 : 0;
+
+ last_moved = 0;
+ for ( ; i < cxt->label->nparts_max; i++) {
+ struct pte *pe = self_pte(cxt, i);
+
+ assert(pe);
+ fdisk_sector_t p_start = get_abs_partition_start(pe);
+ fdisk_sector_t p_end = get_abs_partition_end(pe);
+
+ if (is_cleared_partition(pe->pt_entry))
+ continue;
+
+ /* count EBR and begin of the logical partition as used area */
+ if (pe->offset)
+ p_start -= cxt->first_lba;
+
+ if (last >= p_start && last <= p_end) {
+ last = p_start - 1;
+ last_moved = 1;
+
+ if (last < begin) {
+ DBG(LABEL, ul_debug("DOS: last free out of range <%ju,%ju>: %ju",
+ (uintmax_t) begin, (uintmax_t) end, (uintmax_t) last));
+
+ return -ENOSPC;
+ }
+ }
+ }
+ } while (last_moved == 1);
+
+ DBG(LABEL, ul_debug("DOS: last unused sector in range <%ju,%ju>: %ju",
+ (uintmax_t) begin, (uintmax_t) end, (uintmax_t) last));
+
+ *result = last;
+ return 0;
+}
+
+static int find_first_free_sector_in_range(
+ struct fdisk_context *cxt,
+ int logical,
+ fdisk_sector_t begin,
+ fdisk_sector_t end,
+ fdisk_sector_t *result)
+{
+ int first_moved = 0;
+ fdisk_sector_t first = begin;
+
+ do {
+ size_t i = logical ? 4 : 0;
+
+ first_moved = 0;
+ for (; i < cxt->label->nparts_max; i++) {
+ struct pte *pe = self_pte(cxt, i);
+
+ assert(pe);
+ fdisk_sector_t p_start = get_abs_partition_start(pe);
+ fdisk_sector_t p_end = get_abs_partition_end(pe);
+
+ if (is_cleared_partition(pe->pt_entry))
+ continue;
+ /* count EBR and begin of the logical partition as used area */
+ if (pe->offset)
+ p_start -= cxt->first_lba;
+ if (first < p_start)
+ continue;
+ if (first <= p_end) {
+ first = p_end + 1 + (logical ? cxt->first_lba : 0);
+ first_moved = 1;
+
+ if (first > end) {
+ DBG(LABEL, ul_debug("DOS: first free out of range <%ju,%ju>: %ju",
+ (uintmax_t) begin, (uintmax_t) end, (uintmax_t) first));
+ return -ENOSPC;
+ }
+ }
+ }
+ } while (first_moved == 1);
+
+ DBG(LABEL, ul_debug("DOS: first unused sector in range <%ju,%ju>: %ju",
+ (uintmax_t) begin, (uintmax_t) end, (uintmax_t) first));
+ *result = first;
+ return 0;
+}
+
+static int get_disk_ranges(struct fdisk_context *cxt, int logical,
+ fdisk_sector_t *first, fdisk_sector_t *last)
+{
+ if (logical) {
+ /* logical partitions */
+ struct fdisk_dos_label *l = self_label(cxt);
+ struct pte *ext_pe = l->ext_offset ? self_pte(cxt, l->ext_index) : NULL;
+
+ if (!ext_pe)
+ return -EINVAL;
+
+ *first = l->ext_offset + cxt->first_lba;
+ *last = get_abs_partition_end(ext_pe);
+
+ } else {
+ /* primary partitions */
+ if (fdisk_use_cylinders(cxt) || !cxt->total_sectors)
+ *last = cxt->geom.heads * cxt->geom.sectors * cxt->geom.cylinders - 1;
+ else
+ *last = cxt->total_sectors - 1;
+
+ if (*last > UINT_MAX)
+ *last = UINT_MAX;
+ *first = cxt->first_lba;
+ }
+
+ return 0;
+}
+
+/* first free sector on disk */
+static int find_first_free_sector(struct fdisk_context *cxt,
+ int logical,
+ fdisk_sector_t start,
+ fdisk_sector_t *result)
+{
+ fdisk_sector_t first, last;
+ int rc;
+
+ rc = get_disk_ranges(cxt, logical, &first, &last);
+ if (rc)
+ return rc;
+
+ return find_first_free_sector_in_range(cxt, logical, start, last, result);
+}
+
+static int add_partition(struct fdisk_context *cxt, size_t n,
+ struct fdisk_partition *pa)
+{
+ int sys, read = 0, rc, isrel = 0, is_logical;
+ struct fdisk_dos_label *l = self_label(cxt);
+ struct dos_partition *p = self_partition(cxt, n);
+ struct fdisk_ask *ask = NULL;
+
+ fdisk_sector_t start, stop, limit, temp;
+
+ DBG(LABEL, ul_debug("DOS: adding partition %zu", n));
+
+ sys = pa && pa->type ? pa->type->code : MBR_LINUX_DATA_PARTITION;
+ is_logical = n >= 4;
+
+ if (p && is_used_partition(p)) {
+ fdisk_warnx(cxt, _("Partition %zu is already defined. "
+ "Delete it before re-adding it."),
+ n + 1);
+ return -EINVAL;
+ }
+
+ rc = get_disk_ranges(cxt, is_logical, &start, &stop);
+ if (rc)
+ return rc;
+
+ if (!is_logical && cxt->parent && fdisk_is_label(cxt->parent, GPT))
+ start = 1; /* Bad boy modifies hybrid MBR */
+
+ rc = find_last_free_sector_in_range(cxt, is_logical, start, stop, &limit);
+ if (rc == -ENOSPC)
+ fdisk_warnx(cxt, _("No free sectors available."));
+ if (rc)
+ return rc;
+
+ if ((is_logical || !cxt->parent || !fdisk_is_label(cxt->parent, GPT))
+ && cxt->script && pa && fdisk_partition_has_start(pa)
+ && pa->start >= (is_logical ? l->ext_offset : 1)
+ && pa->start < start) {
+ fdisk_set_first_lba(cxt, 1);
+
+ rc = get_disk_ranges(cxt, is_logical, &start, &stop);
+ if (rc) /* won't happen, but checking to be proper */
+ return rc;
+ }
+
+ /*
+ * Ask for first sector
+ */
+ do {
+ fdisk_sector_t dflt, aligned;
+
+ temp = start;
+
+ DBG(LABEL, ul_debug("DOS: >>> search for first free from %ju", start));
+ rc = find_first_free_sector(cxt, is_logical, start, &dflt);
+ if (rc == -ENOSPC)
+ fdisk_warnx(cxt, _("No free sectors available."));
+ if (rc)
+ return rc;
+ start = dflt;
+
+ if (n >= 4 && pa && fdisk_partition_has_start(pa) && cxt->script
+ && cxt->first_lba > 1
+ && temp == start - cxt->first_lba) {
+ fdisk_set_first_lba(cxt, 1);
+ start = pa->start;
+ }
+
+ /* the default sector should be aligned and unused */
+ do {
+ aligned = fdisk_align_lba_in_range(cxt, dflt, dflt, limit);
+ find_first_free_sector(cxt, is_logical, aligned, &dflt);
+ } while (dflt != aligned && dflt > aligned && dflt < limit);
+
+ if (dflt >= limit)
+ dflt = start;
+ if (start > limit)
+ break;
+ if (start >= temp + fdisk_get_units_per_sector(cxt)
+ && read) {
+ if (!pa || !pa->start_follow_default)
+ fdisk_info(cxt, _("Sector %ju is already allocated."),
+ (uintmax_t) temp);
+ temp = start;
+ read = 0;
+ if (pa && fdisk_partition_has_start(pa))
+ break;
+ }
+
+ if (!read && start == temp) {
+ rc = get_start_from_user(cxt, &start, temp, dflt, limit, pa);
+ if (rc)
+ return rc;
+ read = 1;
+ }
+ if (pa && fdisk_partition_has_size(pa)) {
+ fdisk_sector_t last;
+
+ rc = find_last_free(cxt, is_logical, start, limit, &last);
+ if (rc == 0 && last - start + 1 < fdisk_partition_get_size(pa)) {
+ DBG(LABEL, ul_debug("DOS: area <%ju,%ju> too small [wanted=%ju aval=%ju]",
+ (uintmax_t) start, (uintmax_t) last,
+ fdisk_partition_get_size(pa),
+ last - start));
+
+ if (fdisk_partition_has_start(pa)
+ && fdisk_partition_get_start(pa) <= last)
+ rc = -ENOSPC;
+ else {
+ start = last + 1;
+ continue;
+ }
+ }
+ if (rc == -ENOSPC) {
+ fdisk_warnx(cxt, _("No free sectors available."));
+ return rc;
+ }
+ }
+
+ } while (start != temp || !read);
+
+ if (n == 4) {
+ /* The first EBR is stored at begin of the extended partition */
+ struct pte *pe = self_pte(cxt, n);
+
+ assert(pe);
+ pe->offset = l->ext_offset;
+ } else if (n > 4) {
+ /* The second (and another) EBR */
+ struct pte *pe = self_pte(cxt, n);
+
+ assert(pe);
+ assert(start >= cxt->first_lba);
+
+ pe->offset = start - cxt->first_lba;
+ DBG(LABEL, ul_debug("DOS: setting EBR offset to %ju [start=%ju]", pe->offset, start));
+
+ if (pe->offset == l->ext_offset) { /* must be corrected */
+ pe->offset++;
+ if (cxt->first_lba == 1)
+ start++;
+ }
+ }
+
+ rc = find_last_free(cxt, is_logical, start, limit, &stop);
+ if (rc == -ENOSPC)
+ fdisk_warnx(cxt, _("No free sectors available."));
+ if (rc)
+ return rc;
+ limit = stop;
+
+ /*
+ * Ask for last sector
+ */
+ if (fdisk_cround(cxt, start) == fdisk_cround(cxt, limit))
+ stop = limit;
+ else if (pa && pa->end_follow_default)
+ stop = limit;
+ else if (pa && fdisk_partition_has_size(pa)) {
+ stop = start + pa->size;
+ isrel = pa->size_explicit ? 0 : 1;
+ if ((!isrel || !alignment_required(cxt)) && stop > start)
+ stop -= 1;
+ } else {
+ /* ask user by dialog */
+ for (;;) {
+ if (!ask)
+ ask = fdisk_new_ask();
+ else
+ fdisk_reset_ask(ask);
+ if (!ask)
+ return -ENOMEM;
+ fdisk_ask_set_type(ask, FDISK_ASKTYPE_OFFSET);
+
+ if (fdisk_use_cylinders(cxt)) {
+ fdisk_ask_set_query(ask, _("Last cylinder, +/-cylinders or +/-size{K,M,G,T,P}"));
+ fdisk_ask_number_set_unit(ask,
+ cxt->sector_size *
+ fdisk_get_units_per_sector(cxt));
+ } else {
+ fdisk_ask_set_query(ask, _("Last sector, +/-sectors or +/-size{K,M,G,T,P}"));
+ fdisk_ask_number_set_unit(ask,cxt->sector_size);
+ }
+
+ fdisk_ask_number_set_low(ask, fdisk_cround(cxt, start));
+ fdisk_ask_number_set_default(ask, fdisk_cround(cxt, limit));
+ fdisk_ask_number_set_high(ask, fdisk_cround(cxt, limit));
+ fdisk_ask_number_set_base(ask, fdisk_cround(cxt, start)); /* base for relative input */
+ fdisk_ask_number_set_wrap_negative(ask, 1); /* wrap negative around high */
+
+ rc = fdisk_do_ask(cxt, ask);
+ if (rc)
+ goto done;
+
+ stop = fdisk_ask_number_get_result(ask);
+ isrel = fdisk_ask_number_is_relative(ask);
+ if (fdisk_use_cylinders(cxt)) {
+ stop = stop * fdisk_get_units_per_sector(cxt) - 1;
+ if (stop >limit)
+ stop = limit;
+ }
+
+ if (stop >= start && stop <= limit)
+ break;
+ fdisk_warnx(cxt, _("Value out of range."));
+ }
+ }
+
+ DBG(LABEL, ul_debug("DOS: raw stop: %ju [limit %ju]", (uintmax_t) stop, (uintmax_t) limit));
+
+ if (stop > limit)
+ stop = limit;
+
+ if (isrel && stop - start < (cxt->grain / fdisk_get_sector_size(cxt))) {
+ /* Don't try to be smart on very small partitions and don't align so small sizes */
+ isrel = 0;
+ DBG(LABEL, ul_debug("DOS: don't align end of tiny partition [start=%ju, stop=%ju, grain=%lu]",
+ (uintmax_t)start, (uintmax_t)stop, cxt->grain));
+ }
+
+ if (stop < limit && isrel && alignment_required(cxt)) {
+ /* the last sector has not been exactly requested (but
+ * defined by +size{K,M,G} convention), so be smart and
+ * align the end of the partition. The next partition
+ * will start at phy.block boundary.
+ */
+ stop = fdisk_align_lba_in_range(cxt, stop, start, limit);
+ if (stop > start)
+ stop -= 1; /* end one sector before aligned offset */
+ if (stop > limit)
+ stop = limit;
+ DBG(LABEL, ul_debug("DOS: aligned stop: %ju", (uintmax_t) stop));
+ }
+
+ set_partition(cxt, n, 0, start, stop, sys, fdisk_partition_is_bootable(pa));
+ if (n > 4) {
+ struct pte *pe = self_pte(cxt, n);
+ assert(pe);
+ set_partition(cxt, n - 1, 1, pe->offset, stop,
+ MBR_DOS_EXTENDED_PARTITION, 0);
+ }
+
+ /* report */
+ {
+ struct fdisk_parttype *t =
+ fdisk_label_get_parttype_from_code(cxt->label, sys);
+ fdisk_info_new_partition(cxt, n + 1, start, stop, t);
+ fdisk_unref_parttype(t);
+ }
+
+
+ if (IS_EXTENDED(sys)) {
+ struct pte *pen = self_pte(cxt, n);
+
+ assert(pen);
+ l->ext_index = n;
+ l->ext_offset = start;
+ pen->ex_entry = p;
+ }
+
+ fdisk_label_set_changed(cxt->label, 1);
+ rc = 0;
+done:
+ fdisk_unref_ask(ask);
+ return rc;
+}
+
+static int add_logical(struct fdisk_context *cxt,
+ struct fdisk_partition *pa,
+ size_t *partno)
+{
+ struct pte *pe;
+ int rc;
+
+ assert(cxt);
+ assert(partno);
+ assert(cxt->label);
+ assert(self_label(cxt)->ext_offset);
+
+ DBG(LABEL, ul_debug("DOS: nparts max: %zu", cxt->label->nparts_max));
+ pe = self_pte(cxt, cxt->label->nparts_max);
+ assert(pe);
+
+ if (!pe->sectorbuffer) {
+ pe->sectorbuffer = calloc(1, cxt->sector_size);
+ if (!pe->sectorbuffer)
+ return -ENOMEM;
+ DBG(LABEL, ul_debug("DOS: logical: %zu: new EBR sector buffer %p",
+ cxt->label->nparts_max, pe->sectorbuffer));
+ pe->private_sectorbuffer = 1;
+ }
+ pe->pt_entry = mbr_get_partition(pe->sectorbuffer, 0);
+ pe->ex_entry = pe->pt_entry + 1;
+ pe->offset = 0;
+ partition_set_changed(cxt, cxt->label->nparts_max, 1);
+
+ cxt->label->nparts_max++;
+
+ /* this message makes sense only when we use extended/primary/logical
+ * dialog. The dialog is disable for scripts, see dos_add_partition() */
+ if (!cxt->script)
+ fdisk_info(cxt, _("Adding logical partition %zu"),
+ cxt->label->nparts_max);
+ *partno = cxt->label->nparts_max - 1;
+ rc = add_partition(cxt, *partno, pa);
+
+ if (rc) {
+ /* reset on error */
+ cxt->label->nparts_max--;
+ pe->pt_entry = NULL;
+ pe->ex_entry = NULL;
+ pe->offset = 0;
+ pe->changed = 0;
+ }
+
+ return rc;
+}
+
+static int check(struct fdisk_context *cxt, size_t n,
+ unsigned int h, unsigned int s, unsigned int c,
+ unsigned int lba_sector)
+{
+ unsigned int chs_sector, real_s, real_c;
+ int nerrors = 0;
+
+ if (!is_dos_compatible(cxt))
+ return 0;
+
+ real_s = sector(s) - 1;
+ real_c = cylinder(s, c);
+ chs_sector = (real_c * cxt->geom.heads + h) * cxt->geom.sectors + real_s;
+
+ if (!chs_sector) {
+ fdisk_warnx(cxt, _("Partition %zu: contains sector 0"), n);
+ nerrors++;
+ }
+ if (h >= cxt->geom.heads) {
+ fdisk_warnx(cxt, _("Partition %zu: head %d greater than "
+ "maximum %d"), n, h + 1, cxt->geom.heads);
+ nerrors++;
+ }
+ if (real_s >= cxt->geom.sectors) {
+ fdisk_warnx(cxt, _("Partition %zu: sector %d greater than "
+ "maximum %ju"), n, real_s + 1,
+ (uintmax_t) cxt->geom.sectors);
+ nerrors++;
+ }
+ if (real_c >= cxt->geom.cylinders) {
+ fdisk_warnx(cxt, _("Partition %zu: cylinder %d greater than "
+ "maximum %ju"),
+ n, real_c + 1,
+ (uintmax_t) cxt->geom.cylinders);
+ nerrors++;
+ }
+ if (lba_sector / (cxt->geom.heads * cxt->geom.sectors) < 1024 && lba_sector != chs_sector) {
+ fdisk_warnx(cxt, _("Partition %zu: LBA sector %u "
+ "disagrees with C/H/S calculated sector %u"),
+ n, lba_sector, chs_sector);
+ nerrors++;
+ }
+
+ return nerrors;
+}
+
+/* check_consistency() and long2chs() added Sat Mar 6 12:28:16 1993,
+ * faith@cs.unc.edu, based on code fragments from pfdisk by Gordon W. Ross,
+ * Jan. 1990 (version 1.2.1 by Gordon W. Ross Aug. 1990; Modified by S.
+ * Lubkin Oct. 1991). */
+
+static void
+long2chs(struct fdisk_context *cxt, unsigned long ls,
+ unsigned int *c, unsigned int *h, unsigned int *s) {
+ int spc = cxt->geom.heads * cxt->geom.sectors;
+
+ *c = ls / spc;
+ ls = ls % spc;
+ *h = ls / cxt->geom.sectors;
+ *s = ls % cxt->geom.sectors + 1; /* sectors count from 1 */
+}
+
+static int check_consistency(struct fdisk_context *cxt, struct dos_partition *p,
+ size_t partition)
+{
+ unsigned int pbc, pbh, pbs; /* physical beginning c, h, s */
+ unsigned int pec, peh, pes; /* physical ending c, h, s */
+ unsigned int lbc, lbh, lbs; /* logical beginning c, h, s */
+ unsigned int lec, leh, les; /* logical ending c, h, s */
+ int nerrors = 0;
+
+ if (!is_dos_compatible(cxt))
+ return 0;
+
+ if (!cxt->geom.heads || !cxt->geom.sectors || (partition >= 4))
+ return 0; /* do not check extended partitions */
+
+ /* physical beginning c, h, s */
+ pbc = cylinder(p->bs, p->bc);
+ pbh = p->bh;
+ pbs = sector(p->bs);
+
+ /* physical ending c, h, s */
+ pec = cylinder(p->es, p->ec);
+ peh = p->eh;
+ pes = sector(p->es);
+
+ /* compute logical beginning (c, h, s) */
+ long2chs(cxt, dos_partition_get_start(p), &lbc, &lbh, &lbs);
+
+ /* compute logical ending (c, h, s) */
+ long2chs(cxt, dos_partition_get_start(p) + dos_partition_get_size(p) - 1, &lec, &leh, &les);
+
+ /* Same physical / logical beginning? */
+ if (lbc < 1024
+ && (pbc != lbc || pbh != lbh || pbs != lbs)) {
+ fdisk_warnx(cxt, _("Partition %zu: different physical/logical "
+ "beginnings (non-Linux?): "
+ "phys=(%d, %d, %d), logical=(%d, %d, %d)"),
+ partition + 1,
+ pbc, pbh, pbs,
+ lbc, lbh, lbs);
+ nerrors++;
+ }
+
+ /* Same physical / logical ending? */
+ if (lec < 1024
+ && (pec != lec || peh != leh || pes != les)) {
+ fdisk_warnx(cxt, _("Partition %zu: different physical/logical "
+ "endings: phys=(%d, %d, %d), logical=(%d, %d, %d)"),
+ partition + 1,
+ pec, peh, pes,
+ lec, leh, les);
+ nerrors++;
+ }
+
+ /* Ending on cylinder boundary? */
+ if (peh != (cxt->geom.heads - 1) || pes != cxt->geom.sectors) {
+ fdisk_warnx(cxt, _("Partition %zu: does not end on "
+ "cylinder boundary."),
+ partition + 1);
+ nerrors++;
+ }
+
+ return nerrors;
+}
+
+static void fill_bounds(struct fdisk_context *cxt,
+ fdisk_sector_t *first, fdisk_sector_t *last)
+{
+ size_t i;
+ struct pte *pe = self_pte(cxt, 0);
+ struct dos_partition *p;
+
+ assert(pe);
+ for (i = 0; i < cxt->label->nparts_max; pe++,i++) {
+ p = pe->pt_entry;
+ if (is_cleared_partition(p) || IS_EXTENDED (p->sys_ind)) {
+ first[i] = SIZE_MAX;
+ last[i] = 0;
+ } else {
+ first[i] = get_abs_partition_start(pe);
+ last[i] = get_abs_partition_end(pe);
+ }
+ }
+}
+
+static int dos_verify_disklabel(struct fdisk_context *cxt)
+{
+ size_t i, j;
+ fdisk_sector_t total = 1, n_sectors = cxt->total_sectors;
+ fdisk_sector_t first[cxt->label->nparts_max],
+ last[cxt->label->nparts_max];
+ struct dos_partition *p;
+ struct fdisk_dos_label *l = self_label(cxt);
+ int nerrors = 0;
+
+ assert(fdisk_is_label(cxt, DOS));
+
+ fill_bounds(cxt, first, last);
+ for (i = 0; i < cxt->label->nparts_max; i++) {
+ struct pte *pe = self_pte(cxt, i);
+
+ p = self_partition(cxt, i);
+ if (p && is_used_partition(p) && !IS_EXTENDED(p->sys_ind)) {
+ nerrors += check_consistency(cxt, p, i);
+ assert(pe);
+ if (get_abs_partition_start(pe) < first[i]) {
+ fdisk_warnx(cxt, _(
+ "Partition %zu: bad start-of-data."),
+ i + 1);
+ nerrors++;
+ }
+
+ nerrors += check(cxt, i + 1, p->bh, p->bs, p->bc, first[i]);
+ nerrors += check(cxt, i + 1, p->eh, p->es, p->ec, last[i]);
+ total += last[i] + 1 - first[i];
+
+ if (i == 0)
+ total += get_abs_partition_start(pe) - 1;
+
+ for (j = 0; j < i; j++) {
+ if ((first[i] >= first[j] && first[i] <= last[j])
+ || ((last[i] <= last[j] && last[i] >= first[j]))) {
+
+ fdisk_warnx(cxt, _("Partition %zu: "
+ "overlaps partition %zu."),
+ j + 1, i + 1);
+ nerrors++;
+
+ total += first[i] >= first[j] ?
+ first[i] : first[j];
+ total -= last[i] <= last[j] ?
+ last[i] : last[j];
+ }
+ }
+ }
+ }
+
+ if (l->ext_offset) {
+ fdisk_sector_t e_last;
+ struct pte *ext_pe = self_pte(cxt, l->ext_index);
+
+ assert(ext_pe);
+ e_last = get_abs_partition_end(ext_pe);
+
+ for (i = 4; i < cxt->label->nparts_max; i++) {
+ total++;
+ p = self_partition(cxt, i);
+ assert(p);
+
+ if (!p->sys_ind) {
+ if (i != 4 || i + 1 < cxt->label->nparts_max) {
+ fdisk_warnx(cxt,
+ _("Partition %zu: empty."),
+ i + 1);
+ nerrors++;
+ }
+ } else if (first[i] < l->ext_offset
+ || last[i] > e_last) {
+
+ fdisk_warnx(cxt, _("Logical partition %zu: "
+ "not entirely in partition %zu."),
+ i + 1, l->ext_index + 1);
+ nerrors++;
+ }
+ }
+ }
+
+ if (!nerrors) {
+ fdisk_info(cxt, _("No errors detected."));
+ if (total > n_sectors)
+ fdisk_info(cxt, _("Total allocated sectors %ju greater "
+ "than the maximum %ju."), (uintmax_t) total, (uintmax_t) n_sectors);
+ else if (total < n_sectors)
+ fdisk_info(cxt, _("Remaining %ju unallocated %ld-byte "
+ "sectors."), (uintmax_t) n_sectors - total, cxt->sector_size);
+ } else
+ fdisk_warnx(cxt,
+ P_("%d error detected.", "%d errors detected.", nerrors),
+ nerrors);
+
+ return nerrors;
+}
+
+/*
+ * Ask the user for new partition type information (logical, extended).
+ * This function calls the actual partition adding logic - add_partition.
+ *
+ * API callback.
+ */
+static int dos_add_partition(struct fdisk_context *cxt,
+ struct fdisk_partition *pa,
+ size_t *partno)
+{
+ size_t i;
+ uint8_t free_primary = 0, free_sectors = 0;
+ fdisk_sector_t first = 0, grain;
+ int rc = 0;
+ struct fdisk_dos_label *l;
+ struct pte *ext_pe;
+ size_t res = 0; /* partno */
+
+ assert(cxt);
+ assert(cxt->label);
+ assert(fdisk_is_label(cxt, DOS));
+
+ DBG(LABEL, ul_debug("DOS: new partition wanted"));
+
+ l = self_label(cxt);
+
+ if (cxt->label->nparts_max >= MAXIMUM_PARTS) {
+ fdisk_warnx(cxt, _("The maximum number of partitions has "
+ "been created."));
+ return -EINVAL;
+ }
+
+ ext_pe = l->ext_offset ? self_pte(cxt, l->ext_index) : NULL;
+
+ /*
+ * partition template (@pa) based partitioning
+ */
+
+ /* A) template specifies start within extended partition; add logical */
+ if (pa && fdisk_partition_has_start(pa) && ext_pe
+ && pa->start >= l->ext_offset
+ && pa->start <= get_abs_partition_end(ext_pe)) {
+ DBG(LABEL, ul_debug("DOS: pa template %p: add logical (by offset)", pa));
+
+ if (fdisk_partition_has_partno(pa) && fdisk_partition_get_partno(pa) < 4) {
+ DBG(LABEL, ul_debug("DOS: pa template specifies partno<4 for logical partition"));
+ return -EINVAL;
+ }
+ rc = add_logical(cxt, pa, &res);
+ goto done;
+
+ /* B) template specifies start out of extended partition; add primary */
+ } else if (pa && fdisk_partition_has_start(pa) && ext_pe) {
+ DBG(LABEL, ul_debug("DOS: pa template %p: add primary (by offset)", pa));
+
+ if (fdisk_partition_has_partno(pa) && fdisk_partition_get_partno(pa) >= 4) {
+ DBG(LABEL, ul_debug("DOS: pa template specifies partno>=4 for primary partition"));
+ return -EINVAL;
+ }
+ if (ext_pe && pa->type && IS_EXTENDED(pa->type->code)) {
+ fdisk_warnx(cxt, _("Extended partition already exists."));
+ return -EINVAL;
+ }
+ rc = get_partition_unused_primary(cxt, pa, &res);
+ if (rc == 0)
+ rc = add_partition(cxt, res, pa);
+ goto done;
+
+ /* C) template specifies start (or default), partno < 4; add primary */
+ } else if (pa && (fdisk_partition_start_is_default(pa) || fdisk_partition_has_start(pa))
+ && fdisk_partition_has_partno(pa)
+ && pa->partno < 4) {
+ DBG(LABEL, ul_debug("DOS: pa template %p: add primary (by partno)", pa));
+
+ if (ext_pe && pa->type && IS_EXTENDED(pa->type->code)) {
+ fdisk_warnx(cxt, _("Extended partition already exists."));
+ return -EINVAL;
+ }
+ rc = get_partition_unused_primary(cxt, pa, &res);
+ if (rc == 0)
+ rc = add_partition(cxt, res, pa);
+ goto done;
+
+ /* D) template specifies start (or default), partno >= 4; add logical */
+ } else if (pa && (fdisk_partition_start_is_default(pa) || fdisk_partition_has_start(pa))
+ && fdisk_partition_has_partno(pa)
+ && pa->partno >= 4) {
+ DBG(LABEL, ul_debug("DOS: pa template %p: add logical (by partno)", pa));
+
+ if (!ext_pe) {
+ fdisk_warnx(cxt, _("Extended partition does not exists. Failed to add logical partition."));
+ return -EINVAL;
+ }
+
+ if (fdisk_partition_has_start(pa)
+ && pa->start < l->ext_offset
+ && pa->start > get_abs_partition_end(ext_pe)) {
+ DBG(LABEL, ul_debug("DOS: pa template specifies partno>=4, but start out of extended"));
+ return -EINVAL;
+ }
+
+ rc = add_logical(cxt, pa, &res);
+ goto done;
+ }
+
+ DBG(LABEL, ul_debug("DOS: dialog driven partitioning"));
+ /* Note @pa may be still used for things like partition type, etc */
+
+ /* check if there is space for primary partition */
+ grain = cxt->grain > cxt->sector_size ? cxt->grain / cxt->sector_size : 1;
+ first = cxt->first_lba;
+
+ if (cxt->parent && fdisk_is_label(cxt->parent, GPT)) {
+ /* modifying a hybrid MBR, which throws out the rules */
+ grain = 1;
+ first = 1;
+ }
+
+ /* set @first after the last used partition, set @free_sectors if there
+ * is gap in front if the first partition or between used parrtitions. */
+ for (i = 0; i < 4; i++) {
+ struct dos_partition *p = self_partition(cxt, i);
+
+ if (p && is_used_partition(p)) {
+ fdisk_sector_t start = dos_partition_get_start(p);
+ if (first + grain <= start)
+ free_sectors = 1;
+ first = start + dos_partition_get_size(p);
+ } else
+ free_primary++;
+ }
+
+ /* set @free_sectors if there is a space after the first usable sector */
+ if (first + grain - 1 <= cxt->total_sectors - 1)
+ free_sectors = 1;
+
+ DBG(LABEL, ul_debug("DOS: primary: first free: %ju, last on disk: %ju, "
+ "free_sectors=%d, free_primary=%d",
+ (uintmax_t) first,
+ (uintmax_t) cxt->total_sectors - 1,
+ free_sectors, free_primary));
+
+ if (!free_primary || !free_sectors) {
+ DBG(LABEL, ul_debug("DOS: primary impossible"));
+ if (l->ext_offset) {
+ if (!pa || fdisk_partition_has_start(pa)) {
+ /* See above case A); here we have start, but
+ * out of extended partition */
+ const char *msg;
+ if (!free_primary)
+ msg = _("All primary partitions are in use.");
+ else
+ msg = _("All space for primary partitions is in use.");
+
+ if (pa && fdisk_partition_has_start(pa)) {
+ fdisk_warnx(cxt, "%s", msg);
+ return -EINVAL;
+ }
+ fdisk_info(cxt, "%s", msg);
+ }
+ DBG(LABEL, ul_debug("DOS: trying logical"));
+ rc = add_logical(cxt, pa, &res);
+ } else {
+ if (free_primary)
+ fdisk_info(cxt, _("All space for primary partitions is in use."));
+ else
+ /* TRANSLATORS: Try to keep this within 80 characters. */
+ fdisk_info(cxt, _("To create more partitions, first replace "
+ "a primary with an extended partition."));
+ return -EINVAL;
+ }
+ } else {
+ char hint[BUFSIZ];
+ struct fdisk_ask *ask;
+ int c = 0;
+
+ /* the default layout for scripts is to create primary partitions */
+ if (cxt->script || !fdisk_has_dialogs(cxt)) {
+ rc = get_partition_unused_primary(cxt, pa, &res);
+ if (rc == 0)
+ rc = add_partition(cxt, res, pa);
+ goto done;
+ }
+
+ ask = fdisk_new_ask();
+ if (!ask)
+ return -ENOMEM;
+ fdisk_ask_set_type(ask, FDISK_ASKTYPE_MENU);
+ fdisk_ask_set_query(ask, _("Partition type"));
+ fdisk_ask_menu_set_default(ask, free_primary == 1
+ && !l->ext_offset ? 'e' : 'p');
+ snprintf(hint, sizeof(hint),
+ _("%u primary, %d extended, %u free"),
+ 4 - (l->ext_offset ? 1 : 0) - free_primary,
+ l->ext_offset ? 1 : 0,
+ free_primary);
+
+ fdisk_ask_menu_add_item(ask, 'p', _("primary"), hint);
+ if (!l->ext_offset)
+ fdisk_ask_menu_add_item(ask, 'e', _("extended"), _("container for logical partitions"));
+ else
+ fdisk_ask_menu_add_item(ask, 'l', _("logical"), _("numbered from 5"));
+
+ rc = fdisk_do_ask(cxt, ask);
+ if (!rc)
+ fdisk_ask_menu_get_result(ask, &c);
+ fdisk_unref_ask(ask);
+ if (rc)
+ return rc;
+
+ if (c == 'p') {
+ rc = get_partition_unused_primary(cxt, pa, &res);
+ if (rc == 0)
+ rc = add_partition(cxt, res, pa);
+ goto done;
+ } else if (c == 'l' && l->ext_offset) {
+ rc = add_logical(cxt, pa, &res);
+ goto done;
+ } else if (c == 'e' && !l->ext_offset) {
+ rc = get_partition_unused_primary(cxt, pa, &res);
+ if (rc == 0) {
+ struct fdisk_partition *xpa = NULL;
+ struct fdisk_parttype *t;
+
+ t = fdisk_label_get_parttype_from_code(cxt->label,
+ MBR_DOS_EXTENDED_PARTITION);
+ if (!pa) {
+ pa = xpa = fdisk_new_partition();
+ if (!xpa)
+ return -ENOMEM;
+ }
+ fdisk_partition_set_type(pa, t);
+ rc = add_partition(cxt, res, pa);
+ if (xpa) {
+ fdisk_unref_partition(xpa);
+ pa = NULL;
+ }
+ }
+ goto done;
+ } else
+ fdisk_warnx(cxt, _("Invalid partition type `%c'."), c);
+ }
+done:
+ if (rc == 0) {
+ cxt->label->nparts_cur++;
+ if (partno)
+ *partno = res;
+ }
+ return rc;
+}
+
+static int write_sector(struct fdisk_context *cxt, fdisk_sector_t secno,
+ unsigned char *buf)
+{
+ int rc;
+
+ rc = seek_sector(cxt, secno);
+ if (rc != 0) {
+ fdisk_warn(cxt, _("Cannot write sector %jd: seek failed"),
+ (uintmax_t) secno);
+ return rc;
+ }
+
+ DBG(LABEL, ul_debug("DOS: writing to sector %ju", (uintmax_t) secno));
+
+ if (write(cxt->dev_fd, buf, cxt->sector_size) != (ssize_t) cxt->sector_size)
+ return -errno;
+ return 0;
+}
+
+static int dos_write_disklabel(struct fdisk_context *cxt)
+{
+ struct fdisk_dos_label *l = self_label(cxt);
+ size_t i;
+ int rc = 0, mbr_changed = 0;
+
+ assert(cxt);
+ assert(cxt->label);
+ assert(fdisk_is_label(cxt, DOS));
+
+ DBG(LABEL, ul_debug("DOS: write PT requested [label-changed: %d, non-pt-changed: %d]",
+ cxt->label->changed, l->non_pt_changed));
+
+ mbr_changed = l->non_pt_changed;
+
+ /* MBR (primary partitions) */
+ if (!mbr_changed) {
+ for (i = 0; i < 4; i++) {
+ struct pte *pe = self_pte(cxt, i);
+
+ assert(pe);
+ if (pe->changed)
+ mbr_changed = 1;
+ }
+ }
+ if (mbr_changed) {
+ DBG(LABEL, ul_debug("DOS: MBR changed, writing"));
+ mbr_set_magic(cxt->firstsector);
+ rc = write_sector(cxt, 0, cxt->firstsector);
+ if (rc)
+ goto done;
+ }
+
+ if (cxt->label->nparts_max <= 4 && l->ext_offset) {
+ /* we have empty extended partition, check if the partition has
+ * been modified and then cleanup possible remaining EBR */
+ struct pte *pe = self_pte(cxt, l->ext_index);
+ unsigned char empty[512] = { 0 };
+ fdisk_sector_t off = pe ? get_abs_partition_start(pe) : 0;
+
+ if (off && pe->changed) {
+ mbr_set_magic(empty);
+ write_sector(cxt, off, empty);
+ }
+ }
+
+ /* EBR (logical partitions) */
+ for (i = 4; i < cxt->label->nparts_max; i++) {
+ struct pte *pe = self_pte(cxt, i);
+
+ assert(pe);
+ if (!pe->changed || !pe->offset || !pe->sectorbuffer)
+ continue;
+
+ mbr_set_magic(pe->sectorbuffer);
+ rc = write_sector(cxt, pe->offset, pe->sectorbuffer);
+ if (rc)
+ goto done;
+ }
+
+done:
+ return rc;
+}
+
+static int dos_locate_disklabel(struct fdisk_context *cxt, int n,
+ const char **name, uint64_t *offset, size_t *size)
+{
+ assert(cxt);
+
+ *name = NULL;
+ *offset = 0;
+ *size = 0;
+
+ switch (n) {
+ case 0:
+ *name = "MBR";
+ *offset = 0;
+ *size = 512;
+ break;
+ default:
+ /* extended partitions */
+ if ((size_t)n - 1 + 4 < cxt->label->nparts_max) {
+ struct pte *pe = self_pte(cxt, n - 1 + 4);
+
+ assert(pe);
+ assert(pe->private_sectorbuffer);
+
+ *name = "EBR";
+ *offset = (uint64_t) pe->offset * cxt->sector_size;
+ *size = 512;
+ } else
+ return 1;
+ break;
+ }
+
+ return 0;
+}
+
+/*
+ * Check whether partition entries are ordered by their starting positions.
+ * Return 0 if OK. Return i if partition i should have been earlier.
+ * Two separate checks: primary and logical partitions.
+ */
+static int wrong_p_order(struct fdisk_context *cxt, size_t *prev)
+{
+ size_t last_p_start_pos = 0, p_start_pos;
+ size_t i, last_i = 0;
+
+ for (i = 0 ; i < cxt->label->nparts_max; i++) {
+
+ struct pte *pe = self_pte(cxt, i);
+ struct dos_partition *p;
+
+ assert(pe);
+ p = pe->pt_entry;
+
+ if (i == 4) {
+ last_i = 4;
+ last_p_start_pos = 0;
+ }
+ if (is_used_partition(p)) {
+ p_start_pos = get_abs_partition_start(pe);
+
+ if (last_p_start_pos > p_start_pos) {
+ if (prev)
+ *prev = last_i;
+ return i;
+ }
+
+ last_p_start_pos = p_start_pos;
+ last_i = i;
+ }
+ }
+ return 0;
+}
+
+static int dos_get_disklabel_item(struct fdisk_context *cxt, struct fdisk_labelitem *item)
+{
+ int rc = 0;
+
+ assert(cxt);
+ assert(cxt->label);
+ assert(fdisk_is_label(cxt, DOS));
+
+ switch (item->id) {
+ case FDISK_LABELITEM_ID:
+ {
+ unsigned int num = mbr_get_id(cxt->firstsector);
+ item->name = _("Disk identifier");
+ item->type = 's';
+ if (asprintf(&item->data.str, "0x%08x", num) < 0)
+ rc = -ENOMEM;
+ break;
+ }
+ default:
+ if (item->id < __FDISK_NLABELITEMS)
+ rc = 1; /* unsupported generic item */
+ else
+ rc = 2; /* out of range */
+ break;
+ }
+
+ return rc;
+
+}
+
+static int dos_get_partition(struct fdisk_context *cxt, size_t n,
+ struct fdisk_partition *pa)
+{
+ struct dos_partition *p;
+ struct pte *pe;
+ struct fdisk_dos_label *lb;
+
+ assert(cxt);
+ assert(pa);
+ assert(cxt->label);
+ assert(fdisk_is_label(cxt, DOS));
+
+ lb = self_label(cxt);
+
+ pe = self_pte(cxt, n);
+ assert(pe);
+
+ p = pe->pt_entry;
+ pa->used = !is_cleared_partition(p);
+ if (!pa->used)
+ return 0;
+
+ pa->type = dos_partition_parttype(cxt, p);
+ pa->boot = p->boot_ind == ACTIVE_FLAG ? 1 : 0;
+ pa->start = get_abs_partition_start(pe);
+ pa->size = dos_partition_get_size(p);
+ pa->container = lb->ext_offset && n == lb->ext_index;
+
+ if (n >= 4)
+ pa->parent_partno = lb->ext_index;
+
+ if (p->boot_ind && asprintf(&pa->attrs, "%02x", p->boot_ind) < 0)
+ return -ENOMEM;
+
+ /* start C/H/S */
+ if (asprintf(&pa->start_chs, "%d/%d/%d",
+ cylinder(p->bs, p->bc),
+ p->bh,
+ sector(p->bs)) < 0)
+ return -ENOMEM;
+
+ /* end C/H/S */
+ if (asprintf(&pa->end_chs, "%d/%d/%d",
+ cylinder(p->es, p->ec),
+ p->eh,
+ sector(p->es)) < 0)
+ return -ENOMEM;
+
+ return 0;
+}
+
+static int has_logical(struct fdisk_context *cxt)
+{
+ size_t i;
+ struct fdisk_dos_label *l = self_label(cxt);
+
+ for (i = 4; i < cxt->label->nparts_max; i++) {
+ if (l->ptes[i].pt_entry)
+ return 1;
+ }
+ return 0;
+}
+
+static int dos_set_partition(struct fdisk_context *cxt, size_t n,
+ struct fdisk_partition *pa)
+{
+ struct fdisk_dos_label *l;
+ struct dos_partition *p;
+ struct pte *pe;
+ int orgtype;
+ fdisk_sector_t start, size;
+
+ assert(cxt);
+ assert(pa);
+ assert(cxt->label);
+ assert(fdisk_is_label(cxt, DOS));
+
+ if (n >= cxt->label->nparts_max)
+ return -EINVAL;
+
+ l = self_label(cxt);
+ p = self_partition(cxt, n);
+ assert(p);
+
+ pe = self_pte(cxt, n);
+ if (!pe)
+ return -EINVAL;
+
+ orgtype = p->sys_ind;
+
+ if (pa->type) {
+ if (IS_EXTENDED(pa->type->code) && l->ext_offset && l->ext_index != n) {
+ fdisk_warnx(cxt, _("Extended partition already exists."));
+ return -EINVAL;
+ }
+
+ if (!pa->type->code)
+ fdisk_warnx(cxt, _("Type 0 means free space to many systems. "
+ "Having partitions of type 0 is probably unwise."));
+
+ if (IS_EXTENDED(p->sys_ind) && !IS_EXTENDED(pa->type->code) && has_logical(cxt)) {
+ fdisk_warnx(cxt, _(
+ "Cannot change type of the extended partition which is "
+ "already used by logical partitions. Delete logical "
+ "partitions first."));
+ return -EINVAL;
+ }
+ }
+
+ FDISK_INIT_UNDEF(start);
+ FDISK_INIT_UNDEF(size);
+
+ if (fdisk_partition_has_start(pa))
+ start = pa->start;
+ if (fdisk_partition_has_size(pa))
+ size = pa->size;
+
+ if (!FDISK_IS_UNDEF(start) || !FDISK_IS_UNDEF(size)) {
+ DBG(LABEL, ul_debug("DOS: resize partition"));
+
+ if (FDISK_IS_UNDEF(start))
+ start = get_abs_partition_start(pe);
+ if (FDISK_IS_UNDEF(size))
+ size = dos_partition_get_size(p);
+
+ set_partition(cxt, n, 0, start, start + size - 1,
+ pa->type ? pa->type->code : p->sys_ind,
+ FDISK_IS_UNDEF(pa->boot) ?
+ p->boot_ind == ACTIVE_FLAG :
+ fdisk_partition_is_bootable(pa));
+ } else {
+ DBG(LABEL, ul_debug("DOS: keep size, modify properties"));
+ if (pa->type)
+ p->sys_ind = pa->type->code;
+ if (!FDISK_IS_UNDEF(pa->boot))
+ p->boot_ind = fdisk_partition_is_bootable(pa) ? ACTIVE_FLAG : 0;
+ }
+
+ if (pa->type) {
+ if (IS_EXTENDED(pa->type->code) && !IS_EXTENDED(orgtype)) {
+ /* new extended partition - create a reference */
+ l->ext_index = n;
+ l->ext_offset = dos_partition_get_start(p);
+ pe->ex_entry = p;
+ } else if (IS_EXTENDED(orgtype)) {
+ /* remove extended partition */
+ cxt->label->nparts_max = 4;
+ l->ptes[l->ext_index].ex_entry = NULL;
+ l->ext_offset = 0;
+ l->ext_index = 0;
+ }
+ }
+
+ partition_set_changed(cxt, n, 1);
+ return 0;
+}
+
+static void print_chain_of_logicals(struct fdisk_context *cxt)
+{
+ size_t i;
+ struct fdisk_dos_label *l = self_label(cxt);
+
+ fputc('\n', stdout);
+
+ for (i = 4; i < cxt->label->nparts_max; i++) {
+ struct pte *pe = self_pte(cxt, i);
+
+ assert(pe);
+ fprintf(stderr, "#%02zu EBR [%10ju], "
+ "data[start=%10ju (%10ju), size=%10ju], "
+ "link[start=%10ju (%10ju), size=%10ju]\n",
+ i, (uintmax_t) pe->offset,
+ /* data */
+ (uintmax_t) dos_partition_get_start(pe->pt_entry),
+ (uintmax_t) get_abs_partition_start(pe),
+ (uintmax_t) dos_partition_get_size(pe->pt_entry),
+ /* link */
+ (uintmax_t) dos_partition_get_start(pe->ex_entry),
+ (uintmax_t) l->ext_offset + dos_partition_get_start(pe->ex_entry),
+ (uintmax_t) dos_partition_get_size(pe->ex_entry));
+ }
+}
+
+static int cmp_ebr_offsets(const void *a, const void *b)
+{
+ const struct pte *ae = (const struct pte *) a,
+ *be = (const struct pte *) b;
+
+ if (ae->offset == 0 && be->offset == 0)
+ return 0;
+ if (ae->offset == 0)
+ return 1;
+ if (be->offset == 0)
+ return -1;
+
+ return cmp_numbers(ae->offset, be->offset);
+}
+
+/*
+ * Fix the chain of logicals.
+ *
+ * The function does not modify data partitions within EBR tables
+ * (pte->pt_entry). It sorts the chain by EBR offsets and then update links
+ * (pte->ex_entry) between EBR tables.
+ *
+ */
+static void fix_chain_of_logicals(struct fdisk_context *cxt)
+{
+ struct fdisk_dos_label *l = self_label(cxt);
+ struct pte *last;
+ size_t i;
+
+ DBG(LABEL, print_chain_of_logicals(cxt));
+
+ /* Sort chain by EBR offsets */
+ qsort(&l->ptes[4], cxt->label->nparts_max - 4, sizeof(struct pte),
+ cmp_ebr_offsets);
+
+again:
+ /* Sort data partitions by start */
+ for (i = 4; i < cxt->label->nparts_max - 1; i++) {
+ struct pte *cur = self_pte(cxt, i),
+ *nxt = self_pte(cxt, i + 1);
+
+ assert(cur);
+ assert(nxt);
+
+ if (get_abs_partition_start(cur) >
+ get_abs_partition_start(nxt)) {
+
+ struct dos_partition tmp = *cur->pt_entry;
+ fdisk_sector_t cur_start = get_abs_partition_start(cur),
+ nxt_start = get_abs_partition_start(nxt);
+
+ /* swap data partitions */
+ *cur->pt_entry = *nxt->pt_entry;
+ *nxt->pt_entry = tmp;
+
+ /* Recount starts according to EBR offsets, the absolute
+ * address still has to be the same! */
+ dos_partition_set_start(cur->pt_entry, nxt_start - cur->offset);
+ dos_partition_sync_chs(cur->pt_entry, cur->offset, cxt->geom.sectors, cxt->geom.heads);
+ dos_partition_set_start(nxt->pt_entry, cur_start - nxt->offset);
+ dos_partition_sync_chs(nxt->pt_entry, nxt->offset, cxt->geom.sectors, cxt->geom.heads);
+
+ partition_set_changed(cxt, i, 1);
+ partition_set_changed(cxt, i + 1, 1);
+ goto again;
+ }
+ }
+
+ /* Update EBR links */
+ for (i = 4; i < cxt->label->nparts_max - 1; i++) {
+ struct pte *cur = self_pte(cxt, i),
+ *nxt = self_pte(cxt, i + 1);
+
+ assert(cur);
+ assert(nxt);
+
+ fdisk_sector_t noff = nxt->offset - l->ext_offset,
+ ooff = dos_partition_get_start(cur->ex_entry);
+
+ if (noff == ooff)
+ continue;
+
+ DBG(LABEL, ul_debug("DOS: fix EBR [%10ju] link %ju -> %ju",
+ (uintmax_t) cur->offset,
+ (uintmax_t) ooff, (uintmax_t) noff));
+
+ set_partition(cxt, i, 1, nxt->offset,
+ get_abs_partition_end(nxt),
+ MBR_DOS_EXTENDED_PARTITION, 0);
+ }
+
+ /* always terminate the chain ! */
+ last = self_pte(cxt, cxt->label->nparts_max - 1);
+ if (last) {
+ clear_partition(last->ex_entry);
+ partition_set_changed(cxt, cxt->label->nparts_max - 1, 1);
+ }
+
+ DBG(LABEL, print_chain_of_logicals(cxt));
+}
+
+static int dos_reorder(struct fdisk_context *cxt)
+{
+ struct pte *pei, *pek;
+ size_t i,k;
+
+ if (!wrong_p_order(cxt, NULL))
+ return 1;
+
+ while ((i = wrong_p_order(cxt, &k)) != 0 && i < 4) {
+ /* partition i should have come earlier, move it */
+ /* We have to move data in the MBR */
+ struct dos_partition *pi, *pk, *pe, pbuf;
+ pei = self_pte(cxt, i);
+ pek = self_pte(cxt, k);
+
+ assert(pei);
+ assert(pek);
+
+ pe = pei->ex_entry;
+ pei->ex_entry = pek->ex_entry;
+ pek->ex_entry = pe;
+
+ pi = pei->pt_entry;
+ pk = pek->pt_entry;
+
+ memmove(&pbuf, pi, sizeof(struct dos_partition));
+ memmove(pi, pk, sizeof(struct dos_partition));
+ memmove(pk, &pbuf, sizeof(struct dos_partition));
+
+ partition_set_changed(cxt, i, 1);
+ partition_set_changed(cxt, k, 1);
+ }
+
+ if (i)
+ fix_chain_of_logicals(cxt);
+
+ return 0;
+}
+
+/**
+ * fdisk_dos_fix_chs:
+ * @cxt: fdisk context
+ *
+ * Fix beginning and ending C/H/S values for every partition
+ * according to LBA relative offset, relative beginning and
+ * size and fdisk idea of disk geometry (sectors per track
+ * and number of heads).
+ *
+ * Returns: number of fixed (changed) partitions.
+ */
+int fdisk_dos_fix_chs(struct fdisk_context *cxt)
+{
+ unsigned int obc, obh, obs; /* old beginning c, h, s */
+ unsigned int oec, oeh, oes; /* old ending c, h, s */
+ unsigned int nbc, nbh, nbs; /* new beginning c, h, s */
+ unsigned int nec, neh, nes; /* new ending c, h, s */
+ fdisk_sector_t l, sects; /* lba beginning and size */
+ struct dos_partition *p;
+ struct pte *pe;
+ int changed = 0;
+ size_t i;
+
+ assert(fdisk_is_label(cxt, DOS));
+
+ for (i = 0; i < cxt->label->nparts_max; i++) {
+ p = self_partition(cxt, i);
+ if (!p || !is_used_partition(p))
+ continue;
+
+ pe = self_pte(cxt, i);
+
+ /* old beginning c, h, s */
+ obc = cylinder(p->bs, p->bc);
+ obh = p->bh;
+ obs = sector(p->bs);
+
+ /* old ending c, h, s */
+ oec = cylinder(p->es, p->ec);
+ oeh = p->eh;
+ oes = sector(p->es);
+
+ /* new beginning c, h, s */
+ l = get_abs_partition_start(pe);
+ long2chs(cxt, l, &nbc, &nbh, &nbs);
+ if (l > UINT32_MAX || nbc >= 1024) {
+ nbc = 1023;
+ nbh = cxt->geom.heads-1;
+ nbs = cxt->geom.sectors;
+ }
+
+ /* new ending c, h, s */
+ sects = dos_partition_get_size(p);
+ long2chs(cxt, l + sects - 1, &nec, &neh, &nes);
+ if (lba_overflowed(l, sects) || nec >= 1024) {
+ nec = 1023;
+ neh = cxt->geom.heads-1;
+ nes = cxt->geom.sectors;
+ }
+
+ if (obc != nbc || obh != nbh || obs != nbs ||
+ oec != nec || oeh != neh || oes != nes) {
+ DBG(LABEL, ul_debug("DOS: changing %zu partition CHS "
+ "from (%d, %d, %d)-(%d, %d, %d) "
+ "to (%d, %d, %d)-(%d, %d, %d)",
+ i, obc, obh, obs, oec, oeh, oes,
+ nbc, nbh, nbs, nec, neh, nes));
+ p->bc = nbc & 0xff;
+ p->bh = nbh;
+ p->bs = nbs | ((nbc >> 2) & 0xc0);
+ p->ec = nec & 0xff;
+ p->eh = neh;
+ p->es = nes | ((nec >> 2) & 0xc0);
+ partition_set_changed(cxt, i, 1);
+ changed++;
+ }
+ }
+
+ return changed;
+}
+
+/* TODO: use fdisk_set_partition() API */
+int fdisk_dos_move_begin(struct fdisk_context *cxt, size_t i)
+{
+ struct pte *pe;
+ struct dos_partition *p;
+ unsigned int new, free_start, curr_start, last;
+ uintmax_t res = 0;
+ size_t x;
+ int rc;
+
+ assert(cxt);
+ assert(fdisk_is_label(cxt, DOS));
+
+ pe = self_pte(cxt, i);
+ if (!pe)
+ return -EINVAL;
+
+ p = pe->pt_entry;
+
+ if (!is_used_partition(p) || IS_EXTENDED (p->sys_ind)) {
+ fdisk_warnx(cxt, _("Partition %zu: no data area."), i + 1);
+ return 0;
+ }
+
+ /* The safe start is at the second sector, but some use-cases require
+ * to have MBR within the first partition , so default to the first
+ * sector of the disk or at the second sector of the extended partition
+ */
+ free_start = pe->offset ? pe->offset + 1 : 0;
+
+ curr_start = get_abs_partition_start(pe);
+
+ /* look for a free space before the current start of the partition */
+ for (x = 0; x < cxt->label->nparts_max; x++) {
+ unsigned int end;
+ struct pte *prev_pe = self_pte(cxt, x);
+ struct dos_partition *prev_p;
+
+ assert(prev_pe);
+
+ prev_p = prev_pe->pt_entry;
+ if (!prev_p)
+ continue;
+ end = get_abs_partition_start(prev_pe)
+ + dos_partition_get_size(prev_p);
+
+ if (is_used_partition(prev_p) &&
+ end > free_start && end <= curr_start)
+ free_start = end;
+ }
+
+ last = get_abs_partition_end(pe);
+
+ rc = fdisk_ask_number(cxt, free_start, curr_start, last,
+ _("New beginning of data"), &res);
+ if (rc)
+ return rc;
+
+ new = res - pe->offset;
+
+ if (new != dos_partition_get_size(p)) {
+ unsigned int sects = dos_partition_get_size(p)
+ + dos_partition_get_start(p) - new;
+
+ dos_partition_set_size(p, sects);
+ dos_partition_set_start(p, new);
+ dos_partition_sync_chs(p, pe->offset, cxt->geom.sectors, cxt->geom.heads);
+
+ partition_set_changed(cxt, i, 1);
+
+ if (new == 0)
+ fdisk_info(cxt, _("The new beginning of the partition overlaps the disk "
+ "label area. Be very careful when using the partition. "
+ "You can lose all your partitions on the disk."));
+ }
+
+ return rc;
+}
+
+static int dos_partition_is_used(
+ struct fdisk_context *cxt,
+ size_t i)
+{
+ struct dos_partition *p;
+
+ assert(cxt);
+ assert(cxt->label);
+ assert(fdisk_is_label(cxt, DOS));
+
+ if (i >= cxt->label->nparts_max)
+ return 0;
+
+ p = self_partition(cxt, i);
+
+ return p && !is_cleared_partition(p);
+}
+
+static int dos_toggle_partition_flag(
+ struct fdisk_context *cxt,
+ size_t i,
+ unsigned long flag)
+{
+ struct dos_partition *p;
+
+ assert(cxt);
+ assert(cxt->label);
+ assert(fdisk_is_label(cxt, DOS));
+
+ if (i >= cxt->label->nparts_max)
+ return -EINVAL;
+
+ p = self_partition(cxt, i);
+ assert(p);
+
+ switch (flag) {
+ case DOS_FLAG_ACTIVE:
+ if (IS_EXTENDED(p->sys_ind) && !p->boot_ind)
+ fdisk_warnx(cxt, _("Partition %zu: is an extended "
+ "partition."), i + 1);
+
+ p->boot_ind = (p->boot_ind ? 0 : ACTIVE_FLAG);
+ partition_set_changed(cxt, i, 1);
+ fdisk_info(cxt, p->boot_ind ?
+ _("The bootable flag on partition %zu is enabled now.") :
+ _("The bootable flag on partition %zu is disabled now."),
+ i + 1);
+ break;
+ default:
+ return 1;
+ }
+
+ return 0;
+}
+
+static const struct fdisk_field dos_fields[] =
+{
+ /* basic */
+ { FDISK_FIELD_DEVICE, N_("Device"), 10, 0 },
+ { FDISK_FIELD_BOOT, N_("Boot"), 1, 0 },
+ { FDISK_FIELD_START, N_("Start"), 5, FDISK_FIELDFL_NUMBER },
+ { FDISK_FIELD_END, N_("End"), 5, FDISK_FIELDFL_NUMBER },
+ { FDISK_FIELD_SECTORS, N_("Sectors"), 5, FDISK_FIELDFL_NUMBER },
+ { FDISK_FIELD_CYLINDERS,N_("Cylinders"), 5, FDISK_FIELDFL_NUMBER },
+ { FDISK_FIELD_SIZE, N_("Size"), 5, FDISK_FIELDFL_NUMBER | FDISK_FIELDFL_EYECANDY },
+ { FDISK_FIELD_TYPEID, N_("Id"), 2, FDISK_FIELDFL_NUMBER },
+ { FDISK_FIELD_TYPE, N_("Type"), 0.1, 0 },
+
+ /* expert mode */
+ { FDISK_FIELD_SADDR, N_("Start-C/H/S"), 1, FDISK_FIELDFL_NUMBER | FDISK_FIELDFL_DETAIL },
+ { FDISK_FIELD_EADDR, N_("End-C/H/S"), 1, FDISK_FIELDFL_NUMBER | FDISK_FIELDFL_DETAIL },
+ { FDISK_FIELD_ATTR, N_("Attrs"), 2, FDISK_FIELDFL_NUMBER | FDISK_FIELDFL_DETAIL }
+
+};
+
+static const struct fdisk_label_operations dos_operations =
+{
+ .probe = dos_probe_label,
+ .write = dos_write_disklabel,
+ .verify = dos_verify_disklabel,
+ .create = dos_create_disklabel,
+ .locate = dos_locate_disklabel,
+ .get_item = dos_get_disklabel_item,
+ .set_id = dos_set_disklabel_id,
+
+ .get_part = dos_get_partition,
+ .set_part = dos_set_partition,
+ .add_part = dos_add_partition,
+ .del_part = dos_delete_partition,
+ .reorder = dos_reorder,
+
+ .part_toggle_flag = dos_toggle_partition_flag,
+ .part_is_used = dos_partition_is_used,
+
+ .reset_alignment = dos_reset_alignment,
+
+ .deinit = dos_deinit,
+};
+
+/*
+ * allocates DOS in-memory stuff
+ */
+struct fdisk_label *fdisk_new_dos_label(struct fdisk_context *cxt __attribute__ ((__unused__)))
+{
+ struct fdisk_label *lb;
+ struct fdisk_dos_label *dos;
+
+ dos = calloc(1, sizeof(*dos));
+ if (!dos)
+ return NULL;
+
+ /* initialize generic part of the driver */
+ lb = (struct fdisk_label *) dos;
+ lb->name = "dos";
+ lb->id = FDISK_DISKLABEL_DOS;
+ lb->op = &dos_operations;
+
+ lb->parttypes = dos_parttypes;
+ lb->nparttypes = ARRAY_SIZE(dos_parttypes) - 1;
+ lb->parttype_cuts = dos_parttype_cuts;
+ lb->nparttype_cuts = ARRAY_SIZE(dos_parttype_cuts);
+
+ lb->fields = dos_fields;
+ lb->nfields = ARRAY_SIZE(dos_fields);
+
+ lb->geom_min.sectors = 1;
+ lb->geom_min.heads = 1;
+ lb->geom_min.cylinders = 1;
+
+ lb->geom_max.sectors = 63;
+ lb->geom_max.heads = 255;
+ lb->geom_max.cylinders = 1048576;
+
+ /* return calloc() result to keep static anaylizers happy */
+ return (struct fdisk_label *) dos;
+}
+
+/**
+ * fdisk_dos_enable_compatible:
+ * @lb: DOS label (see fdisk_get_label())
+ * @enable: 0 or 1
+ *
+ * Enables deprecated DOS compatible mode, in this mode library checks for
+ * cylinders boundary, cases about CHS addressing and another obscure things.
+ *
+ * Returns: 0 on success, <0 on error.
+ */
+int fdisk_dos_enable_compatible(struct fdisk_label *lb, int enable)
+{
+ struct fdisk_dos_label *dos = (struct fdisk_dos_label *) lb;
+
+ if (!lb)
+ return -EINVAL;
+
+ dos->compatible = enable;
+ if (enable)
+ lb->flags |= FDISK_LABEL_FL_REQUIRE_GEOMETRY;
+ return 0;
+}
+
+/**
+ * fdisk_dos_is_compatible:
+ * @lb: DOS label
+ *
+ * Returns: 0 if DOS compatibility disabled, 1 if enabled
+ */
+int fdisk_dos_is_compatible(struct fdisk_label *lb)
+{
+ return ((struct fdisk_dos_label *) lb)->compatible;
+}
diff --git a/libfdisk/src/fdiskP.h b/libfdisk/src/fdiskP.h
new file mode 100644
index 0000000..7c0830e
--- /dev/null
+++ b/libfdisk/src/fdiskP.h
@@ -0,0 +1,540 @@
+/*
+ * fdiskP.h - private library header file
+ *
+ * Copyright (C) 2012 Karel Zak <kzak@redhat.com>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+
+#ifndef _LIBFDISK_PRIVATE_H
+#define _LIBFDISK_PRIVATE_H
+
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <uuid.h>
+
+#include "c.h"
+#include "libfdisk.h"
+
+#include "list.h"
+#include "debug.h"
+#include <stdio.h>
+#include <stdarg.h>
+
+/*
+ * Debug
+ */
+#define LIBFDISK_DEBUG_HELP (1 << 0)
+#define LIBFDISK_DEBUG_INIT (1 << 1)
+#define LIBFDISK_DEBUG_CXT (1 << 2)
+#define LIBFDISK_DEBUG_LABEL (1 << 3)
+#define LIBFDISK_DEBUG_ASK (1 << 4)
+#define LIBFDISK_DEBUG_PART (1 << 6)
+#define LIBFDISK_DEBUG_PARTTYPE (1 << 7)
+#define LIBFDISK_DEBUG_TAB (1 << 8)
+#define LIBFDISK_DEBUG_SCRIPT (1 << 9)
+#define LIBFDISK_DEBUG_WIPE (1 << 10)
+#define LIBFDISK_DEBUG_ITEM (1 << 11)
+#define LIBFDISK_DEBUG_GPT (1 << 12)
+#define LIBFDISK_DEBUG_ALL 0xFFFF
+
+UL_DEBUG_DECLARE_MASK(libfdisk);
+#define DBG(m, x) __UL_DBG(libfdisk, LIBFDISK_DEBUG_, m, x)
+#define ON_DBG(m, x) __UL_DBG_CALL(libfdisk, LIBFDISK_DEBUG_, m, x)
+#define DBG_FLUSH __UL_DBG_FLUSH(libfdisk, LIBFDISK_DEBUG_)
+
+#define UL_DEBUG_CURRENT_MASK UL_DEBUG_MASK(libfdisk)
+#include "debugobj.h"
+
+/*
+ * NLS -- the library has to be independent on main program, so define
+ * UL_TEXTDOMAIN_EXPLICIT before you include nls.h.
+ *
+ * Now we use util-linux.po (=PACKAGE), rather than maintain the texts
+ * in the separate libfdisk.po file.
+ */
+#define LIBFDISK_TEXTDOMAIN PACKAGE
+#define UL_TEXTDOMAIN_EXPLICIT LIBFDISK_TEXTDOMAIN
+#include "nls.h"
+
+
+#ifdef TEST_PROGRAM
+struct fdisk_test {
+ const char *name;
+ int (*body)(struct fdisk_test *ts, int argc, char *argv[]);
+ const char *usage;
+};
+
+/* test.c */
+extern int fdisk_run_test(struct fdisk_test *tests, int argc, char *argv[]);
+#endif
+
+#define FDISK_GPT_NPARTITIONS_DEFAULT 128
+
+/*
+ * Generic iterator
+ */
+struct fdisk_iter {
+ struct list_head *p; /* current position */
+ struct list_head *head; /* start position */
+ int direction; /* FDISK_ITER_{FOR,BACK}WARD */
+};
+
+#define IS_ITER_FORWARD(_i) ((_i)->direction == FDISK_ITER_FORWARD)
+#define IS_ITER_BACKWARD(_i) ((_i)->direction == FDISK_ITER_BACKWARD)
+
+#define FDISK_ITER_INIT(itr, list) \
+ do { \
+ (itr)->p = IS_ITER_FORWARD(itr) ? \
+ (list)->next : (list)->prev; \
+ (itr)->head = (list); \
+ } while(0)
+
+#define FDISK_ITER_ITERATE(itr, res, restype, member) \
+ do { \
+ res = list_entry((itr)->p, restype, member); \
+ (itr)->p = IS_ITER_FORWARD(itr) ? \
+ (itr)->p->next : (itr)->p->prev; \
+ } while(0)
+
+/*
+ * Partition types
+ */
+struct fdisk_parttype {
+ unsigned int code; /* type as number or zero */
+ char *name; /* description */
+ char *typestr; /* type as string or NULL */
+
+ unsigned int flags; /* FDISK_PARTTYPE_* flags */
+ int refcount; /* reference counter for allocated types */
+};
+
+enum {
+ FDISK_PARTTYPE_UNKNOWN = (1 << 1),
+ FDISK_PARTTYPE_INVISIBLE = (1 << 2),
+ FDISK_PARTTYPE_ALLOCATED = (1 << 3)
+};
+
+#define fdisk_parttype_is_invisible(_x) ((_x) && ((_x)->flags & FDISK_PARTTYPE_INVISIBLE))
+#define fdisk_parttype_is_allocated(_x) ((_x) && ((_x)->flags & FDISK_PARTTYPE_ALLOCATED))
+
+/*
+ * Shortcut (used for partition types)
+ */
+struct fdisk_shortcut {
+ const char *shortcut; /* shortcut, usually one letter (e.h. "H") */
+ const char *alias; /* human readable alias (e.g. "home") */
+ const char *data; /* for example partition type */
+
+ unsigned int deprecated : 1;
+};
+
+struct fdisk_partition {
+ int refcount; /* reference counter */
+
+ size_t partno; /* partition number */
+ size_t parent_partno; /* for logical partitions */
+
+ fdisk_sector_t start; /* first sectors */
+ fdisk_sector_t size; /* size in sectors */
+
+ int movestart; /* FDISK_MOVE_* (scripts only) */
+ int resize; /* FDISK_RESIZE_* (scripts only) */
+
+ char *name; /* partition name */
+ char *uuid; /* partition UUID */
+ char *attrs; /* partition flags/attributes converted to string */
+ struct fdisk_parttype *type; /* partition type */
+
+ char *fstype; /* filesystem type */
+ char *fsuuid; /* filesystem uuid */
+ char *fslabel; /* filesystem label */
+
+ struct list_head parts; /* list of partitions */
+
+ /* extra fields for partition_to_string() */
+ char start_post; /* start postfix (e.g. '+') */
+ char end_post; /* end postfix */
+ char size_post; /* size postfix */
+
+ uint64_t fsize; /* bsd junk */
+ uint64_t bsize;
+ uint64_t cpg;
+
+ char *start_chs; /* start C/H/S in string */
+ char *end_chs; /* end C/H/S in string */
+
+ unsigned int boot; /* MBR: bootable */
+
+ unsigned int container : 1, /* container partition (e.g. extended partition) */
+ end_follow_default : 1, /* use default end */
+ freespace : 1, /* this is free space */
+ partno_follow_default : 1, /* use default partno */
+ size_explicit : 1, /* don't align the size */
+ start_follow_default : 1, /* use default start */
+ fs_probed : 1, /* already probed by blkid */
+ used : 1, /* partition already used */
+ wholedisk : 1; /* special system partition */
+};
+
+enum {
+ FDISK_MOVE_NONE = 0,
+ FDISK_MOVE_DOWN = -1,
+ FDISK_MOVE_UP = 1
+};
+
+enum {
+ FDISK_RESIZE_NONE = 0,
+ FDISK_RESIZE_REDUCE = -1,
+ FDISK_RESIZE_ENLARGE = 1
+};
+
+#define FDISK_INIT_UNDEF(_x) ((_x) = (__typeof__(_x)) -1)
+#define FDISK_IS_UNDEF(_x) ((_x) == (__typeof__(_x)) -1)
+
+struct fdisk_table {
+ struct list_head parts; /* partitions */
+ int refcount;
+ size_t nents; /* number of partitions */
+};
+
+/*
+ * Legacy CHS based geometry
+ */
+struct fdisk_geometry {
+ unsigned int heads;
+ fdisk_sector_t sectors;
+ fdisk_sector_t cylinders;
+};
+
+/*
+ * Label specific operations
+ */
+struct fdisk_label_operations {
+ /* probe disk label */
+ int (*probe)(struct fdisk_context *cxt);
+ /* write in-memory changes to disk */
+ int (*write)(struct fdisk_context *cxt);
+ /* verify the partition table */
+ int (*verify)(struct fdisk_context *cxt);
+ /* create new disk label */
+ int (*create)(struct fdisk_context *cxt);
+ /* returns offset and size of the 'n' part of the PT */
+ int (*locate)(struct fdisk_context *cxt, int n, const char **name,
+ uint64_t *offset, size_t *size);
+ /* reorder partitions */
+ int (*reorder)(struct fdisk_context *cxt);
+ /* get details from label */
+ int (*get_item)(struct fdisk_context *cxt, struct fdisk_labelitem *item);
+ /* set disk label ID */
+ int (*set_id)(struct fdisk_context *cxt, const char *str);
+
+
+ /* new partition */
+ int (*add_part)(struct fdisk_context *cxt, struct fdisk_partition *pa,
+ size_t *partno);
+ /* delete partition */
+ int (*del_part)(struct fdisk_context *cxt, size_t partnum);
+
+ /* fill in partition struct */
+ int (*get_part)(struct fdisk_context *cxt, size_t n,
+ struct fdisk_partition *pa);
+ /* modify partition */
+ int (*set_part)(struct fdisk_context *cxt, size_t n,
+ struct fdisk_partition *pa);
+
+ /* return state of the partition */
+ int (*part_is_used)(struct fdisk_context *cxt, size_t partnum);
+
+ int (*part_toggle_flag)(struct fdisk_context *cxt, size_t i, unsigned long flag);
+
+ /* refresh alignment setting */
+ int (*reset_alignment)(struct fdisk_context *cxt);
+
+ /* free in-memory label stuff */
+ void (*free)(struct fdisk_label *lb);
+
+ /* deinit in-memory label stuff */
+ void (*deinit)(struct fdisk_label *lb);
+};
+
+/*
+ * The fields describes how to display libfdisk_partition
+ */
+struct fdisk_field {
+ int id; /* FDISK_FIELD_* */
+ const char *name; /* field name */
+ double width; /* field width (compatible with libsmartcols whint) */
+ int flags; /* FDISK_FIELDFL_* */
+};
+
+/* note that the defaults is to display a column always */
+enum {
+ FDISK_FIELDFL_DETAIL = (1 << 1), /* only display if fdisk_is_details() */
+ FDISK_FIELDFL_EYECANDY = (1 << 2), /* don't display if fdisk_is_details() */
+ FDISK_FIELDFL_NUMBER = (1 << 3), /* column display numbers */
+};
+
+/*
+ * Generic label
+ */
+struct fdisk_label {
+ const char *name; /* label name */
+ enum fdisk_labeltype id; /* FDISK_DISKLABEL_* */
+ struct fdisk_parttype *parttypes; /* supported partitions types */
+ size_t nparttypes; /* number of items in parttypes[] */
+
+ const struct fdisk_shortcut *parttype_cuts; /* partition type shortcuts */
+ size_t nparttype_cuts; /* number of items in parttype_cuts */
+
+ size_t nparts_max; /* maximal number of partitions */
+ size_t nparts_cur; /* number of currently used partitions */
+
+ int flags; /* FDISK_LABEL_FL_* flags */
+
+ struct fdisk_geometry geom_min; /* minimal geometry */
+ struct fdisk_geometry geom_max; /* maximal geometry */
+
+ unsigned int changed:1, /* label has been modified */
+ disabled:1; /* this driver is disabled at all */
+
+ const struct fdisk_field *fields; /* all possible fields */
+ size_t nfields;
+
+ const struct fdisk_label_operations *op;
+};
+
+
+/* label driver flags */
+enum {
+ FDISK_LABEL_FL_REQUIRE_GEOMETRY = (1 << 2),
+ FDISK_LABEL_FL_INCHARS_PARTNO = (1 << 3)
+};
+
+/* label allocators */
+extern struct fdisk_label *fdisk_new_gpt_label(struct fdisk_context *cxt);
+extern struct fdisk_label *fdisk_new_dos_label(struct fdisk_context *cxt);
+extern struct fdisk_label *fdisk_new_bsd_label(struct fdisk_context *cxt);
+extern struct fdisk_label *fdisk_new_sgi_label(struct fdisk_context *cxt);
+extern struct fdisk_label *fdisk_new_sun_label(struct fdisk_context *cxt);
+
+
+struct ask_menuitem {
+ char key;
+ const char *name;
+ const char *desc;
+
+ struct ask_menuitem *next;
+};
+
+/* fdisk dialog -- note that nothing from this stuff will be directly exported,
+ * we will have get/set() function for everything.
+ */
+struct fdisk_ask {
+ int type; /* FDISK_ASKTYPE_* */
+ char *query;
+
+ int refcount;
+
+ union {
+ /* FDISK_ASKTYPE_{NUMBER,OFFSET} */
+ struct ask_number {
+ uint64_t hig; /* high limit */
+ uint64_t low; /* low limit */
+ uint64_t dfl; /* default */
+ uint64_t result;
+ uint64_t base; /* for relative results */
+ uint64_t unit; /* unit for offsets */
+ const char *range; /* by library generated list */
+ unsigned int relative :1,
+ inchars :1,
+ wrap_negative :1;
+ } num;
+ /* FDISK_ASKTYPE_{WARN,WARNX,..} */
+ struct ask_print {
+ const char *mesg;
+ int errnum; /* errno */
+ } print;
+ /* FDISK_ASKTYPE_YESNO */
+ struct ask_yesno {
+ int result; /* TRUE or FALSE */
+ } yesno;
+ /* FDISK_ASKTYPE_STRING */
+ struct ask_string {
+ char *result; /* allocated */
+ } str;
+ /* FDISK_ASKTYPE_MENU */
+ struct ask_menu {
+ int dfl; /* default menu item */
+ int result;
+ struct ask_menuitem *first;
+ } menu;
+ } data;
+};
+
+struct fdisk_context {
+ int dev_fd; /* device descriptor */
+ char *dev_path; /* device path */
+ char *dev_model; /* on linux /sys/block/<name>/device/model or NULL */
+ struct stat dev_st; /* stat(2) result */
+
+ int refcount;
+
+ unsigned char *firstsector; /* buffer with master boot record */
+ unsigned long firstsector_bufsz;
+
+
+ /* topology */
+ unsigned long io_size; /* I/O size used by fdisk */
+ unsigned long optimal_io_size; /* optional I/O returned by device */
+ unsigned long min_io_size; /* minimal I/O size */
+ unsigned long phy_sector_size; /* physical size */
+ unsigned long sector_size; /* logical size */
+ unsigned long alignment_offset;
+
+ unsigned int readonly : 1, /* don't write to the device */
+ display_in_cyl_units : 1, /* for obscure labels */
+ display_details : 1, /* expert display mode */
+ protect_bootbits : 1, /* don't zeroize first sector */
+ pt_collision : 1, /* another PT detected by libblkid */
+ no_disalogs : 1, /* disable dialog-driven partititoning */
+ dev_model_probed : 1, /* tried to read from sys */
+ is_priv : 1, /* open by libfdisk */
+ is_excl : 1, /* open with O_EXCL */
+ listonly : 1; /* list partition, nothing else */
+
+ char *collision; /* name of already existing FS/PT */
+ struct list_head wipes; /* list of areas to wipe before write */
+
+ int sizeunit; /* SIZE fields, FDISK_SIZEUNIT_* */
+
+ /* alignment */
+ unsigned long grain; /* alignment unit */
+ fdisk_sector_t first_lba; /* recommended begin of the first partition */
+ fdisk_sector_t last_lba; /* recommended end of last partition */
+
+ /* geometry */
+ fdisk_sector_t total_sectors; /* in logical sectors */
+ struct fdisk_geometry geom;
+
+ /* user setting to overwrite device default */
+ struct fdisk_geometry user_geom;
+ unsigned long user_pyh_sector;
+ unsigned long user_log_sector;
+ unsigned long user_grain;
+
+ struct fdisk_label *label; /* current label, pointer to labels[] */
+
+ size_t nlabels; /* number of initialized label drivers */
+ struct fdisk_label *labels[8]; /* all supported labels,
+ * FIXME: use any enum rather than hardcoded number */
+
+ int (*ask_cb)(struct fdisk_context *, struct fdisk_ask *, void *); /* fdisk dialogs callback */
+ void *ask_data; /* ask_cb() data */
+
+ struct fdisk_context *parent; /* for nested PT */
+ struct fdisk_script *script; /* what we want to follow */
+};
+
+/* table */
+enum {
+ FDISK_DIFF_UNCHANGED = 0,
+ FDISK_DIFF_REMOVED,
+ FDISK_DIFF_ADDED,
+ FDISK_DIFF_MOVED,
+ FDISK_DIFF_RESIZED
+};
+extern int fdisk_diff_tables(struct fdisk_table *a, struct fdisk_table *b,
+ struct fdisk_iter *itr,
+ struct fdisk_partition **res, int *change);
+extern void fdisk_debug_print_table(struct fdisk_table *tb);
+
+
+/* context.c */
+extern int __fdisk_switch_label(struct fdisk_context *cxt,
+ struct fdisk_label *lb);
+extern int fdisk_missing_geometry(struct fdisk_context *cxt);
+
+/* alignment.c */
+fdisk_sector_t fdisk_scround(struct fdisk_context *cxt, fdisk_sector_t num);
+fdisk_sector_t fdisk_cround(struct fdisk_context *cxt, fdisk_sector_t num);
+
+extern int fdisk_discover_geometry(struct fdisk_context *cxt);
+extern int fdisk_discover_topology(struct fdisk_context *cxt);
+
+extern int fdisk_has_user_device_geometry(struct fdisk_context *cxt);
+extern int fdisk_apply_user_device_properties(struct fdisk_context *cxt);
+extern int fdisk_apply_label_device_properties(struct fdisk_context *cxt);
+extern void fdisk_zeroize_device_properties(struct fdisk_context *cxt);
+
+/* utils.c */
+extern int fdisk_init_firstsector_buffer(struct fdisk_context *cxt,
+ unsigned int protect_off, unsigned int protect_size);
+extern int fdisk_read_firstsector(struct fdisk_context *cxt);
+
+/* label.c */
+extern int fdisk_probe_labels(struct fdisk_context *cxt);
+extern void fdisk_deinit_label(struct fdisk_label *lb);
+
+struct fdisk_labelitem {
+ int refcount; /* reference counter */
+ int id; /* <label>_ITEM_* */
+ char type; /* s = string, j = uint64 */
+ const char *name; /* human readable name */
+
+ union {
+ char *str;
+ uint64_t num64;
+ } data;
+};
+
+/* Use only internally for non-allocated items, never use
+ * refcouting for such items!
+ */
+#define FDISK_LABELITEM_INIT { .type = 0, .refcount = 0 }
+
+/* ask.c */
+struct fdisk_ask *fdisk_new_ask(void);
+void fdisk_reset_ask(struct fdisk_ask *ask);
+int fdisk_ask_set_query(struct fdisk_ask *ask, const char *str);
+int fdisk_ask_set_type(struct fdisk_ask *ask, int type);
+int fdisk_do_ask(struct fdisk_context *cxt, struct fdisk_ask *ask);
+int fdisk_ask_number_set_range(struct fdisk_ask *ask, const char *range);
+int fdisk_ask_number_set_default(struct fdisk_ask *ask, uint64_t dflt);
+int fdisk_ask_number_set_low(struct fdisk_ask *ask, uint64_t low);
+int fdisk_ask_number_set_high(struct fdisk_ask *ask, uint64_t high);
+int fdisk_ask_number_set_base(struct fdisk_ask *ask, uint64_t base);
+int fdisk_ask_number_set_unit(struct fdisk_ask *ask, uint64_t unit);
+int fdisk_ask_number_is_relative(struct fdisk_ask *ask);
+int fdisk_ask_number_set_wrap_negative(struct fdisk_ask *ask, int wrap_negative);
+int fdisk_ask_menu_set_default(struct fdisk_ask *ask, int dfl);
+int fdisk_ask_menu_add_item(struct fdisk_ask *ask, int key,
+ const char *name, const char *desc);
+int fdisk_ask_print_set_errno(struct fdisk_ask *ask, int errnum);
+int fdisk_ask_print_set_mesg(struct fdisk_ask *ask, const char *mesg);
+int fdisk_info_new_partition(
+ struct fdisk_context *cxt,
+ int num, fdisk_sector_t start, fdisk_sector_t stop,
+ struct fdisk_parttype *t);
+
+/* dos.c */
+extern struct dos_partition *fdisk_dos_get_partition(
+ struct fdisk_context *cxt,
+ size_t i);
+
+/* wipe.c */
+void fdisk_free_wipe_areas(struct fdisk_context *cxt);
+int fdisk_set_wipe_area(struct fdisk_context *cxt, uint64_t start, uint64_t size, int enable);
+int fdisk_do_wipe(struct fdisk_context *cxt);
+int fdisk_has_wipe_area(struct fdisk_context *cxt, uint64_t start, uint64_t size);
+int fdisk_check_collisions(struct fdisk_context *cxt);
+
+/* parttype.c */
+const char *fdisk_label_translate_type_shortcut(const struct fdisk_label *lb, char *cut);
+
+#endif /* _LIBFDISK_PRIVATE_H */
diff --git a/libfdisk/src/field.c b/libfdisk/src/field.c
new file mode 100644
index 0000000..e924cf8
--- /dev/null
+++ b/libfdisk/src/field.c
@@ -0,0 +1,88 @@
+
+#include "fdiskP.h"
+
+/**
+ * SECTION: field
+ * @title: Field
+ * @short_description: description of the partition fields
+ *
+ * The fdisk fields are static user-friendly descriptions of the partition. The
+ * fields are used to avoid label specific stuff in the functions that list disk
+ * partitions (e.g. fdisk -l). The field Id is the same as Id for fdisk_partition_to_string().
+ *
+ * <informalexample>
+ * <programlisting>
+ * int *ids;
+ * size_t nids;
+ * struct fdisk_partition *pa = NULL;
+ * struct fdisk_label *lb = fdisk_get_label(cxt, NULL);
+ *
+ * fdisk_label_get_fields_ids(lb, cxt, &ids, &nids);
+ *
+ * fdisk_get_partition(cxt, 0, &pa);
+ *
+ * for (i = 0; i < nids; i++) {
+ * const struct fdisk_field *field = fdisk_label_get_field(lb, ids[i]);
+ *
+ * int id = fdisk_field_get_id(fl);
+ * const char *name = fdisk_field_get_name(fl);
+ * char *data;
+ *
+ * fdisk_partition_to_string(pa, id, &data);
+ * printf("%s: %s\n", name, data);
+ * free(data);
+ * }
+ * free(ids);
+ * </programlisting>
+ * </informalexample>
+ *
+ * This example lists all information about the first partition. It will work
+ * for MBR as well as for GPT because fields are not hardcoded in the example.
+ *
+ * See also fdisk_label_get_field_by_name(), fdisk_label_get_fields_ids_all()
+ * and fdisk_label_get_fields_ids().
+ */
+
+/**
+ * fdisk_field_get_id:
+ * @field: field instance
+ *
+ * Returns: field Id (FDISK_FIELD_*)
+ */
+int fdisk_field_get_id(const struct fdisk_field *field)
+{
+ return field ? field->id : -EINVAL;
+}
+
+/**
+ * fdisk_field_get_name:
+ * @field: field instance
+ *
+ * Returns: field name
+ */
+const char *fdisk_field_get_name(const struct fdisk_field *field)
+{
+ return field ? field->name : NULL;
+}
+
+/**
+ * fdisk_field_get_width:
+ * @field: field instance
+ *
+ * Returns: libsmartcols compatible width.
+ */
+double fdisk_field_get_width(const struct fdisk_field *field)
+{
+ return field ? field->width : -EINVAL;
+}
+
+/**
+ * fdisk_field_is_number:
+ * @field: field instance
+ *
+ * Returns: 1 if field represent number
+ */
+int fdisk_field_is_number(const struct fdisk_field *field)
+{
+ return field->flags ? field->flags & FDISK_FIELDFL_NUMBER : 0;
+}
diff --git a/libfdisk/src/gpt.c b/libfdisk/src/gpt.c
new file mode 100644
index 0000000..c3c0347
--- /dev/null
+++ b/libfdisk/src/gpt.c
@@ -0,0 +1,3381 @@
+/*
+ * Copyright (C) 2007 Karel Zak <kzak@redhat.com>
+ * Copyright (C) 2012 Davidlohr Bueso <dave@gnu.org>
+ *
+ * GUID Partition Table (GPT) support. Based on UEFI Specs 2.3.1
+ * Chapter 5: GUID Partition Table (GPT) Disk Layout (Jun 27th, 2012).
+ * Some ideas and inspiration from GNU parted and gptfdisk.
+ */
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <inttypes.h>
+#include <stdint.h>
+#include <sys/stat.h>
+#include <sys/utsname.h>
+#include <sys/types.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <errno.h>
+#include <ctype.h>
+#include <uuid.h>
+
+#include "fdiskP.h"
+
+#include "crc32.h"
+#include "blkdev.h"
+#include "bitops.h"
+#include "strutils.h"
+#include "all-io.h"
+#include "pt-mbr.h"
+#include "encode.h"
+
+/**
+ * SECTION: gpt
+ * @title: UEFI GPT
+ * @short_description: specific functionality
+ */
+
+#define GPT_HEADER_SIGNATURE 0x5452415020494645LL /* EFI PART */
+#define GPT_HEADER_REVISION_V1_02 0x00010200
+#define GPT_HEADER_REVISION_V1_00 0x00010000
+#define GPT_HEADER_REVISION_V0_99 0x00009900
+#define GPT_HEADER_MINSZ 92 /* bytes */
+
+#define GPT_PMBR_LBA 0
+#define GPT_MBR_PROTECTIVE 1
+#define GPT_MBR_HYBRID 2
+
+#define GPT_PRIMARY_PARTITION_TABLE_LBA 0x00000001ULL
+
+#define EFI_PMBR_OSTYPE 0xEE
+#define MSDOS_MBR_SIGNATURE 0xAA55
+#define GPT_PART_NAME_LEN (72 / sizeof(uint16_t))
+#define GPT_NPARTITIONS ((size_t) FDISK_GPT_NPARTITIONS_DEFAULT)
+
+/* Globally unique identifier */
+struct gpt_guid {
+ uint32_t time_low;
+ uint16_t time_mid;
+ uint16_t time_hi_and_version;
+ uint8_t clock_seq_hi;
+ uint8_t clock_seq_low;
+ uint8_t node[6];
+};
+
+
+/* only checking that the GUID is 0 is enough to verify an empty partition. */
+#define GPT_UNUSED_ENTRY_GUID \
+ ((struct gpt_guid) { 0x00000000, 0x0000, 0x0000, 0x00, 0x00, \
+ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }})
+
+/* Linux native partition type */
+#define GPT_DEFAULT_ENTRY_TYPE "0FC63DAF-8483-4772-8E79-3D69D8477DE4"
+
+/*
+ * Attribute bits
+ */
+enum {
+ /* UEFI specific */
+ GPT_ATTRBIT_REQ = 0,
+ GPT_ATTRBIT_NOBLOCK = 1,
+ GPT_ATTRBIT_LEGACY = 2,
+
+ /* GUID specific (range 48..64)*/
+ GPT_ATTRBIT_GUID_FIRST = 48,
+ GPT_ATTRBIT_GUID_COUNT = 16
+};
+
+#define GPT_ATTRSTR_REQ "RequiredPartition"
+#define GPT_ATTRSTR_REQ_TYPO "RequiredPartiton"
+#define GPT_ATTRSTR_NOBLOCK "NoBlockIOProtocol"
+#define GPT_ATTRSTR_LEGACY "LegacyBIOSBootable"
+
+/* The GPT Partition entry array contains an array of GPT entries. */
+struct gpt_entry {
+ struct gpt_guid type; /* purpose and type of the partition */
+ struct gpt_guid partition_guid;
+ uint64_t lba_start;
+ uint64_t lba_end;
+ uint64_t attrs;
+ uint16_t name[GPT_PART_NAME_LEN];
+} __attribute__ ((packed));
+
+/* GPT header */
+struct gpt_header {
+ uint64_t signature; /* header identification */
+ uint32_t revision; /* header version */
+ uint32_t size; /* in bytes */
+ uint32_t crc32; /* header CRC checksum */
+ uint32_t reserved1; /* must be 0 */
+ uint64_t my_lba; /* LBA of block that contains this struct (LBA 1) */
+ uint64_t alternative_lba; /* backup GPT header */
+ uint64_t first_usable_lba; /* first usable logical block for partitions */
+ uint64_t last_usable_lba; /* last usable logical block for partitions */
+ struct gpt_guid disk_guid; /* unique disk identifier */
+ uint64_t partition_entry_lba; /* LBA of start of partition entries array */
+ uint32_t npartition_entries; /* total partition entries - normally 128 */
+ uint32_t sizeof_partition_entry; /* bytes for each GUID pt */
+ uint32_t partition_entry_array_crc32; /* partition CRC checksum */
+ uint8_t reserved2[512 - 92]; /* must all be 0 */
+} __attribute__ ((packed));
+
+struct gpt_record {
+ uint8_t boot_indicator; /* unused by EFI, set to 0x80 for bootable */
+ uint8_t start_head; /* unused by EFI, pt start in CHS */
+ uint8_t start_sector; /* unused by EFI, pt start in CHS */
+ uint8_t start_track;
+ uint8_t os_type; /* EFI and legacy non-EFI OS types */
+ uint8_t end_head; /* unused by EFI, pt end in CHS */
+ uint8_t end_sector; /* unused by EFI, pt end in CHS */
+ uint8_t end_track; /* unused by EFI, pt end in CHS */
+ uint32_t starting_lba; /* used by EFI - start addr of the on disk pt */
+ uint32_t size_in_lba; /* used by EFI - size of pt in LBA */
+} __attribute__ ((packed));
+
+/* Protected MBR and legacy MBR share same structure */
+struct gpt_legacy_mbr {
+ uint8_t boot_code[440];
+ uint32_t unique_mbr_signature;
+ uint16_t unknown;
+ struct gpt_record partition_record[4];
+ uint16_t signature;
+} __attribute__ ((packed));
+
+/*
+ * Here be dragons!
+ * See: http://en.wikipedia.org/wiki/GUID_Partition_Table#Partition_type_GUIDs
+ */
+#define DEF_GUID(_u, _n) \
+ { \
+ .typestr = (_u), \
+ .name = (_n), \
+ }
+
+static struct fdisk_parttype gpt_parttypes[] =
+{
+ #include "pt-gpt-partnames.h"
+};
+
+static const struct fdisk_shortcut gpt_parttype_cuts[] =
+{
+ { .shortcut = "L", .alias = "linux", .data = "0FC63DAF-8483-4772-8E79-3D69D8477DE4" }, /* Linux */
+ { .shortcut = "S", .alias = "swap", .data = "0657FD6D-A4AB-43C4-84E5-0933C84B4F4F" }, /* Swap */
+ { .shortcut = "H", .alias = "home", .data = "933AC7E1-2EB4-4F13-B844-0E14E2AEF915" }, /* Home */
+ { .shortcut = "U", .alias = "uefi", .data = "C12A7328-F81F-11D2-BA4B-00A0C93EC93B" }, /* UEFI system */
+ { .shortcut = "R", .alias = "raid", .data = "A19D880F-05FC-4D3B-A006-743F0F84911E" }, /* Linux RAID */
+ { .shortcut = "V", .alias = "lvm", .data = "E6D6D379-F507-44C2-A23C-238F2A3DF928" } /* LVM */
+};
+
+#define alignment_required(_x) ((_x)->grain != (_x)->sector_size)
+
+/* gpt_entry macros */
+#define gpt_partition_start(_e) le64_to_cpu((_e)->lba_start)
+#define gpt_partition_end(_e) le64_to_cpu((_e)->lba_end)
+
+/*
+ * in-memory fdisk GPT stuff
+ */
+struct fdisk_gpt_label {
+ struct fdisk_label head; /* generic part */
+
+ /* gpt specific part */
+ struct gpt_header *pheader; /* primary header */
+ struct gpt_header *bheader; /* backup header */
+
+ unsigned char *ents; /* entries (partitions) */
+
+ unsigned int no_relocate :1, /* do not fix backup location */
+ minimize :1;
+};
+
+static void gpt_deinit(struct fdisk_label *lb);
+
+static inline struct fdisk_gpt_label *self_label(struct fdisk_context *cxt)
+{
+ return (struct fdisk_gpt_label *) cxt->label;
+}
+
+/*
+ * Returns the partition length, or 0 if end is before beginning.
+ */
+static uint64_t gpt_partition_size(const struct gpt_entry *e)
+{
+ uint64_t start = gpt_partition_start(e);
+ uint64_t end = gpt_partition_end(e);
+
+ return start > end ? 0 : end - start + 1ULL;
+}
+
+/* prints UUID in the real byte order! */
+static void gpt_debug_uuid(const char *mesg, struct gpt_guid *guid)
+{
+ const unsigned char *uuid = (unsigned char *) guid;
+
+ fprintf(stderr, "%s: "
+ "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x\n",
+ mesg,
+ uuid[0], uuid[1], uuid[2], uuid[3],
+ uuid[4], uuid[5],
+ uuid[6], uuid[7],
+ uuid[8], uuid[9],
+ uuid[10], uuid[11], uuid[12], uuid[13], uuid[14],uuid[15]);
+}
+
+/*
+ * UUID is traditionally 16 byte big-endian array, except Intel EFI
+ * specification where the UUID is a structure of little-endian fields.
+ */
+static void swap_efi_guid(struct gpt_guid *uid)
+{
+ uid->time_low = swab32(uid->time_low);
+ uid->time_mid = swab16(uid->time_mid);
+ uid->time_hi_and_version = swab16(uid->time_hi_and_version);
+}
+
+static int string_to_guid(const char *in, struct gpt_guid *guid)
+{
+ if (uuid_parse(in, (unsigned char *) guid)) { /* BE */
+ DBG(GPT, ul_debug("failed to parse GUID: %s", in));
+ return -EINVAL;
+ }
+ swap_efi_guid(guid); /* LE */
+ return 0;
+}
+
+static char *guid_to_string(const struct gpt_guid *guid, char *out)
+{
+ struct gpt_guid u = *guid; /* LE */
+
+ swap_efi_guid(&u); /* BE */
+ uuid_unparse_upper((unsigned char *) &u, out);
+
+ return out;
+}
+
+static struct fdisk_parttype *gpt_partition_parttype(
+ struct fdisk_context *cxt,
+ const struct gpt_entry *e)
+{
+ struct fdisk_parttype *t;
+ char str[UUID_STR_LEN];
+ struct gpt_guid guid = e->type;
+
+ guid_to_string(&guid, str);
+ t = fdisk_label_get_parttype_from_string(cxt->label, str);
+ return t ? : fdisk_new_unknown_parttype(0, str);
+}
+
+static void gpt_entry_set_type(struct gpt_entry *e, struct gpt_guid *uuid)
+{
+ e->type = *uuid;
+ DBG(GPT, gpt_debug_uuid("new type", uuid));
+}
+
+static int gpt_entry_set_name(struct gpt_entry *e, char *str)
+{
+ uint16_t name[GPT_PART_NAME_LEN] = { 0 };
+ size_t i, mblen = 0;
+ uint8_t *in = (uint8_t *) str;
+
+ for (i = 0; *in && i < GPT_PART_NAME_LEN; in++) {
+ if (!mblen) {
+ if (!(*in & 0x80)) {
+ name[i++] = *in;
+ } else if ((*in & 0xE0) == 0xC0) {
+ mblen = 1;
+ name[i] = (uint16_t)(*in & 0x1F) << (mblen *6);
+ } else if ((*in & 0xF0) == 0xE0) {
+ mblen = 2;
+ name[i] = (uint16_t)(*in & 0x0F) << (mblen *6);
+ } else {
+ /* broken UTF-8 or code point greater than U+FFFF */
+ return -EILSEQ;
+ }
+ } else {
+ /* incomplete UTF-8 sequence */
+ if ((*in & 0xC0) != 0x80)
+ return -EILSEQ;
+
+ name[i] |= (uint16_t)(*in & 0x3F) << (--mblen *6);
+ if (!mblen) {
+ /* check for code points reserved for surrogate pairs*/
+ if ((name[i] & 0xF800) == 0xD800)
+ return -EILSEQ;
+ i++;
+ }
+ }
+ }
+
+ for (i = 0; i < GPT_PART_NAME_LEN; i++)
+ e->name[i] = cpu_to_le16(name[i]);
+
+ return (int)((char *) in - str);
+}
+
+static int gpt_entry_set_uuid(struct gpt_entry *e, char *str)
+{
+ struct gpt_guid uuid;
+ int rc;
+
+ rc = string_to_guid(str, &uuid);
+ if (rc)
+ return rc;
+
+ e->partition_guid = uuid;
+ return 0;
+}
+
+static inline int gpt_entry_is_used(const struct gpt_entry *e)
+{
+ return memcmp(&e->type, &GPT_UNUSED_ENTRY_GUID,
+ sizeof(struct gpt_guid)) != 0;
+}
+
+
+static const char *gpt_get_header_revstr(struct gpt_header *header)
+{
+ if (!header)
+ goto unknown;
+
+ switch (le32_to_cpu(header->revision)) {
+ case GPT_HEADER_REVISION_V1_02:
+ return "1.2";
+ case GPT_HEADER_REVISION_V1_00:
+ return "1.0";
+ case GPT_HEADER_REVISION_V0_99:
+ return "0.99";
+ default:
+ goto unknown;
+ }
+
+unknown:
+ return "unknown";
+}
+
+static inline unsigned char *gpt_get_entry_ptr(struct fdisk_gpt_label *gpt, size_t i)
+{
+ return gpt->ents + le32_to_cpu(gpt->pheader->sizeof_partition_entry) * i;
+}
+
+static inline struct gpt_entry *gpt_get_entry(struct fdisk_gpt_label *gpt, size_t i)
+{
+ return (struct gpt_entry *) gpt_get_entry_ptr(gpt, i);
+}
+
+static inline struct gpt_entry *gpt_zeroize_entry(struct fdisk_gpt_label *gpt, size_t i)
+{
+ return (struct gpt_entry *) memset(gpt_get_entry_ptr(gpt, i),
+ 0, le32_to_cpu(gpt->pheader->sizeof_partition_entry));
+}
+
+/* Use to access array of entries, for() loops, etc. But don't use when
+ * you directly do something with GPT header, then use uint32_t.
+ */
+static inline size_t gpt_get_nentries(struct fdisk_gpt_label *gpt)
+{
+ return (size_t) le32_to_cpu(gpt->pheader->npartition_entries);
+}
+
+/* calculate size of entries array in bytes for specified number of entries */
+static inline int gpt_calculate_sizeof_entries(
+ struct gpt_header *hdr,
+ uint32_t nents, size_t *sz)
+{
+ uint32_t esz = hdr ? le32_to_cpu(hdr->sizeof_partition_entry) :
+ sizeof(struct gpt_entry);
+
+ if (nents == 0 || esz == 0 || SIZE_MAX/esz < nents) {
+ DBG(GPT, ul_debug("entries array size check failed"));
+ return -ERANGE;
+ }
+
+ *sz = (size_t) nents * esz;
+ return 0;
+}
+
+/* calculate size of entries array in sectors for specified number of entries */
+static inline int gpt_calculate_sectorsof_entries(
+ struct gpt_header *hdr,
+ uint32_t nents, uint64_t *sz,
+ struct fdisk_context *cxt)
+{
+ size_t esz = 0;
+ int rc = gpt_calculate_sizeof_entries(hdr, nents, &esz); /* in bytes */
+
+ if (rc == 0)
+ *sz = (esz + cxt->sector_size - 1) / cxt->sector_size;
+ return rc;
+}
+
+/* calculate alternative (backup) entries array offset from primary header */
+static inline int gpt_calculate_alternative_entries_lba(
+ struct gpt_header *hdr,
+ uint32_t nents,
+ uint64_t *sz,
+ struct fdisk_context *cxt)
+{
+ uint64_t esects = 0;
+ int rc = gpt_calculate_sectorsof_entries(hdr, nents, &esects, cxt);
+
+ if (rc)
+ return rc;
+ if (cxt->total_sectors < 1ULL + esects)
+ return -ENOSPC;
+
+ *sz = cxt->total_sectors - 1ULL - esects;
+ return 0;
+}
+
+static inline int gpt_calculate_last_lba(
+ struct gpt_header *hdr,
+ uint32_t nents,
+ uint64_t *sz,
+ struct fdisk_context *cxt)
+{
+ uint64_t esects = 0;
+ int rc = gpt_calculate_sectorsof_entries(hdr, nents, &esects, cxt);
+
+ if (rc)
+ return rc;
+ if (cxt->total_sectors < 2ULL + esects)
+ return -ENOSPC;
+
+ *sz = cxt->total_sectors - 2ULL - esects;
+ return 0;
+}
+
+static inline int gpt_calculate_first_lba(
+ struct gpt_header *hdr,
+ uint32_t nents,
+ uint64_t *sz,
+ struct fdisk_context *cxt)
+{
+ uint64_t esects = 0;
+ int rc = gpt_calculate_sectorsof_entries(hdr, nents, &esects, cxt);
+
+ if (rc == 0)
+ *sz = esects + 2ULL;
+ return rc;
+}
+
+/* the current size of entries array in bytes */
+static inline int gpt_sizeof_entries(struct gpt_header *hdr, size_t *sz)
+{
+ return gpt_calculate_sizeof_entries(hdr, le32_to_cpu(hdr->npartition_entries), sz);
+}
+
+static char *gpt_get_header_id(struct gpt_header *header)
+{
+ char str[UUID_STR_LEN];
+ struct gpt_guid guid = header->disk_guid;
+
+ guid_to_string(&guid, str);
+
+ return strdup(str);
+}
+
+/*
+ * Builds a clean new valid protective MBR - will wipe out any existing data.
+ * Returns 0 on success, otherwise < 0 on error.
+ */
+static int gpt_mknew_pmbr(struct fdisk_context *cxt)
+{
+ struct gpt_legacy_mbr *pmbr = NULL;
+ int rc;
+
+ if (!cxt || !cxt->firstsector)
+ return -ENOSYS;
+
+ if (fdisk_has_protected_bootbits(cxt))
+ rc = fdisk_init_firstsector_buffer(cxt, 0, MBR_PT_BOOTBITS_SIZE);
+ else
+ rc = fdisk_init_firstsector_buffer(cxt, 0, 0);
+ if (rc)
+ return rc;
+
+ pmbr = (struct gpt_legacy_mbr *) cxt->firstsector;
+ memset(pmbr->partition_record, 0, sizeof(pmbr->partition_record));
+
+ pmbr->signature = cpu_to_le16(MSDOS_MBR_SIGNATURE);
+ pmbr->partition_record[0].os_type = EFI_PMBR_OSTYPE;
+ pmbr->partition_record[0].start_sector = 2;
+ pmbr->partition_record[0].end_head = 0xFF;
+ pmbr->partition_record[0].end_sector = 0xFF;
+ pmbr->partition_record[0].end_track = 0xFF;
+ pmbr->partition_record[0].starting_lba = cpu_to_le32(1);
+ pmbr->partition_record[0].size_in_lba =
+ cpu_to_le32((uint32_t) min( cxt->total_sectors - 1ULL, 0xFFFFFFFFULL) );
+
+ return 0;
+}
+
+
+/* Move backup header to the end of the device */
+static int gpt_fix_alternative_lba(struct fdisk_context *cxt, struct fdisk_gpt_label *gpt)
+{
+ struct gpt_header *p, *b;
+ uint64_t x = 0, orig;
+ size_t nents;
+ int rc;
+
+ if (!cxt)
+ return -EINVAL;
+
+ p = gpt->pheader; /* primary */
+ b = gpt->bheader; /* backup */
+
+ nents = le32_to_cpu(p->npartition_entries);
+ orig = le64_to_cpu(p->alternative_lba);
+
+ /* reference from primary to backup */
+ p->alternative_lba = cpu_to_le64(cxt->total_sectors - 1ULL);
+
+ /* reference from backup to primary */
+ b->alternative_lba = p->my_lba;
+ b->my_lba = p->alternative_lba;
+
+ /* fix backup partitions array address */
+ rc = gpt_calculate_alternative_entries_lba(p, nents, &x, cxt);
+ if (rc)
+ goto failed;
+
+ b->partition_entry_lba = cpu_to_le64(x);
+
+ /* update last usable LBA */
+ rc = gpt_calculate_last_lba(p, nents, &x, cxt);
+ if (rc)
+ goto failed;
+
+ p->last_usable_lba = cpu_to_le64(x);
+ b->last_usable_lba = cpu_to_le64(x);
+
+ DBG(GPT, ul_debug("Alternative-LBA updated from %"PRIu64" to %"PRIu64,
+ orig, le64_to_cpu(p->alternative_lba)));
+ return 0;
+failed:
+ DBG(GPT, ul_debug("failed to fix alternative-LBA [rc=%d]", rc));
+ return rc;
+}
+
+static uint64_t gpt_calculate_minimal_size(struct fdisk_context *cxt, struct fdisk_gpt_label *gpt)
+{
+ size_t i;
+ uint64_t x = 0, total = 0;
+ struct gpt_header *hdr;
+
+ assert(cxt);
+ assert(gpt);
+ assert(gpt->pheader);
+ assert(gpt->ents);
+
+ hdr = gpt->pheader;
+
+ /* LBA behind the last partition */
+ for (i = 0; i < gpt_get_nentries(gpt); i++) {
+ struct gpt_entry *e = gpt_get_entry(gpt, i);
+
+ if (gpt_entry_is_used(e)) {
+ uint64_t end = gpt_partition_end(e);
+ if (end > x)
+ x = end;
+ }
+ }
+ total = x + 1;
+
+ /* the current last LBA usable for partitions */
+ gpt_calculate_last_lba(hdr, le32_to_cpu(hdr->npartition_entries), &x, cxt);
+
+ /* size of all stuff at the end of the device */
+ total += cxt->total_sectors - x;
+
+ DBG(GPT, ul_debug("minimal device is %"PRIu64, total));
+ return total;
+}
+
+static int gpt_possible_minimize(struct fdisk_context *cxt, struct fdisk_gpt_label *gpt)
+{
+ struct gpt_header *hdr = gpt->pheader;
+ uint64_t total = gpt_calculate_minimal_size(cxt, gpt);
+
+ return le64_to_cpu(hdr->alternative_lba) > (total - 1ULL);
+}
+
+/* move backup header behind the last partition */
+static int gpt_minimize_alternative_lba(struct fdisk_context *cxt, struct fdisk_gpt_label *gpt)
+{
+ uint64_t total = gpt_calculate_minimal_size(cxt, gpt);
+ uint64_t orig = cxt->total_sectors;
+ int rc;
+
+ /* Let's temporary change size of the device to recalculate backup header */
+ cxt->total_sectors = total;
+ rc = gpt_fix_alternative_lba(cxt, gpt);
+ if (rc)
+ return rc;
+
+ cxt->total_sectors = orig;
+ fdisk_label_set_changed(cxt->label, 1);
+ return 0;
+}
+
+/* some universal differences between the headers */
+static void gpt_mknew_header_common(struct fdisk_context *cxt,
+ struct gpt_header *header, uint64_t lba)
+{
+ if (!cxt || !header)
+ return;
+
+ header->my_lba = cpu_to_le64(lba);
+
+ if (lba == GPT_PRIMARY_PARTITION_TABLE_LBA) {
+ /* primary */
+ header->alternative_lba = cpu_to_le64(cxt->total_sectors - 1ULL);
+ header->partition_entry_lba = cpu_to_le64(2ULL);
+
+ } else {
+ /* backup */
+ uint64_t x = 0;
+ gpt_calculate_alternative_entries_lba(header,
+ le32_to_cpu(header->npartition_entries), &x, cxt);
+
+ header->alternative_lba = cpu_to_le64(GPT_PRIMARY_PARTITION_TABLE_LBA);
+ header->partition_entry_lba = cpu_to_le64(x);
+ }
+}
+
+/*
+ * Builds a new GPT header (at sector lba) from a backup header2.
+ * If building a primary header, then backup is the secondary, and vice versa.
+ *
+ * Always pass a new (zeroized) header to build upon as we don't
+ * explicitly zero-set some values such as CRCs and reserved.
+ *
+ * Returns 0 on success, otherwise < 0 on error.
+ */
+static int gpt_mknew_header_from_bkp(struct fdisk_context *cxt,
+ struct gpt_header *header,
+ uint64_t lba,
+ struct gpt_header *header2)
+{
+ if (!cxt || !header || !header2)
+ return -ENOSYS;
+
+ header->signature = header2->signature;
+ header->revision = header2->revision;
+ header->size = header2->size;
+ header->npartition_entries = header2->npartition_entries;
+ header->sizeof_partition_entry = header2->sizeof_partition_entry;
+ header->first_usable_lba = header2->first_usable_lba;
+ header->last_usable_lba = header2->last_usable_lba;
+
+ memcpy(&header->disk_guid,
+ &header2->disk_guid, sizeof(header2->disk_guid));
+ gpt_mknew_header_common(cxt, header, lba);
+
+ return 0;
+}
+
+static struct gpt_header *gpt_copy_header(struct fdisk_context *cxt,
+ struct gpt_header *src)
+{
+ struct gpt_header *res;
+
+ if (!cxt || !src)
+ return NULL;
+
+ assert(cxt->sector_size >= sizeof(struct gpt_header));
+
+ res = calloc(1, cxt->sector_size);
+ if (!res) {
+ fdisk_warn(cxt, _("failed to allocate GPT header"));
+ return NULL;
+ }
+
+ res->my_lba = src->alternative_lba;
+ res->alternative_lba = src->my_lba;
+
+ res->signature = src->signature;
+ res->revision = src->revision;
+ res->size = src->size;
+ res->npartition_entries = src->npartition_entries;
+ res->sizeof_partition_entry = src->sizeof_partition_entry;
+ res->first_usable_lba = src->first_usable_lba;
+ res->last_usable_lba = src->last_usable_lba;
+
+ memcpy(&res->disk_guid, &src->disk_guid, sizeof(src->disk_guid));
+
+
+ if (res->my_lba == GPT_PRIMARY_PARTITION_TABLE_LBA)
+ res->partition_entry_lba = cpu_to_le64(2ULL);
+ else {
+ uint64_t esz = (uint64_t) le32_to_cpu(src->npartition_entries) * sizeof(struct gpt_entry);
+ uint64_t esects = (esz + cxt->sector_size - 1) / cxt->sector_size;
+
+ res->partition_entry_lba = cpu_to_le64(cxt->total_sectors - 1ULL - esects);
+ }
+
+ return res;
+}
+
+static int get_script_u64(struct fdisk_context *cxt, uint64_t *num, const char *name)
+{
+ const char *str;
+ int pwr = 0, rc = 0;
+
+ assert(cxt);
+
+ *num = 0;
+
+ if (!cxt->script)
+ return 1;
+
+ str = fdisk_script_get_header(cxt->script, name);
+ if (!str)
+ return 1;
+
+ rc = parse_size(str, (uintmax_t *) num, &pwr);
+ if (rc < 0)
+ return rc;
+ if (pwr)
+ *num /= cxt->sector_size;
+ return 0;
+}
+
+static int count_first_last_lba(struct fdisk_context *cxt,
+ uint64_t *first, uint64_t *last,
+ uint32_t *maxents)
+{
+ int rc = 0;
+ uint64_t flba = 0, llba = 0;
+ uint64_t nents = GPT_NPARTITIONS;
+
+ assert(cxt);
+ assert(first);
+ assert(last);
+
+ *first = *last = 0;
+
+ /* Get the table length from the script, if given */
+ if (cxt->script) {
+ rc = get_script_u64(cxt, &nents, "table-length");
+ if (rc == 1)
+ nents = GPT_NPARTITIONS; /* undefined by script */
+ else if (rc < 0)
+ return rc;
+ }
+
+ /* The table length was not changed by the script, compute it. */
+ if (flba == 0) {
+ /* If the device is not large enough reduce this number of
+ * partitions and try to recalculate it again, until we get
+ * something useful or return error.
+ */
+ for (; nents > 0; nents--) {
+ rc = gpt_calculate_last_lba(NULL, nents, &llba, cxt);
+ if (rc == 0)
+ rc = gpt_calculate_first_lba(NULL, nents, &flba, cxt);
+ if (llba < flba)
+ rc = -ENOSPC;
+ else if (rc == 0)
+ break;
+ }
+ }
+
+ if (rc)
+ return rc;
+ if (maxents)
+ *maxents = nents;
+
+ /* script default */
+ if (cxt->script) {
+ rc = get_script_u64(cxt, first, "first-lba");
+ if (rc < 0)
+ return rc;
+
+ DBG(GPT, ul_debug("FirstLBA: script=%"PRIu64", uefi=%"PRIu64", topology=%ju.",
+ *first, flba, (uintmax_t)cxt->first_lba));
+
+ if (rc == 0 && (*first < flba || *first > llba)) {
+ fdisk_warnx(cxt, _("First LBA specified by script is out of range."));
+ return -ERANGE;
+ }
+
+ rc = get_script_u64(cxt, last, "last-lba");
+ if (rc < 0)
+ return rc;
+
+ DBG(GPT, ul_debug("LastLBA: script=%"PRIu64", uefi=%"PRIu64", topology=%ju.",
+ *last, llba, (uintmax_t)cxt->last_lba));
+
+ if (rc == 0 && (*last > llba || *last < flba)) {
+ fdisk_warnx(cxt, _("Last LBA specified by script is out of range."));
+ return -ERANGE;
+ }
+ }
+
+ if (!*last)
+ *last = llba;
+
+ /* default by topology */
+ if (!*first)
+ *first = flba < cxt->first_lba &&
+ cxt->first_lba < *last ? cxt->first_lba : flba;
+ return 0;
+}
+
+/*
+ * Builds a clean new GPT header (currently under revision 1.0).
+ *
+ * Always pass a new (zeroized) header to build upon as we don't
+ * explicitly zero-set some values such as CRCs and reserved.
+ *
+ * Returns 0 on success, otherwise < 0 on error.
+ */
+static int gpt_mknew_header(struct fdisk_context *cxt,
+ struct gpt_header *header, uint64_t lba)
+{
+ uint64_t first, last;
+ uint32_t nents = 0;
+ int has_id = 0, rc;
+
+ if (!cxt || !header)
+ return -ENOSYS;
+
+ header->signature = cpu_to_le64(GPT_HEADER_SIGNATURE);
+ header->revision = cpu_to_le32(GPT_HEADER_REVISION_V1_00);
+
+ /* According to EFI standard it's valid to count all the first
+ * sector into header size, but some tools may have a problem
+ * to accept it, so use the header without the zeroed area.
+ * This does not have any impact to CRC, etc. --kzak Jan-2015
+ */
+ header->size = cpu_to_le32(sizeof(struct gpt_header)
+ - sizeof(header->reserved2));
+
+ /* Set {First,Last}LBA and number of the partitions
+ * (default is GPT_NPARTITIONS) */
+ rc = count_first_last_lba(cxt, &first, &last, &nents);
+ if (rc)
+ return rc;
+
+ header->npartition_entries = cpu_to_le32(nents);
+ header->sizeof_partition_entry = cpu_to_le32(sizeof(struct gpt_entry));
+
+ header->first_usable_lba = cpu_to_le64(first);
+ header->last_usable_lba = cpu_to_le64(last);
+
+ gpt_mknew_header_common(cxt, header, lba);
+
+ if (cxt->script) {
+ const char *id = fdisk_script_get_header(cxt->script, "label-id");
+ struct gpt_guid guid = header->disk_guid;
+ if (id && string_to_guid(id, &guid) == 0)
+ has_id = 1;
+ header->disk_guid = guid;
+ }
+
+ if (!has_id) {
+ struct gpt_guid guid;
+
+ uuid_generate_random((unsigned char *) &guid);
+ swap_efi_guid(&guid);
+ header->disk_guid = guid;
+ }
+ return 0;
+}
+
+/*
+ * Checks if there is a valid protective MBR partition table.
+ * Returns 0 if it is invalid or failure. Otherwise, return
+ * GPT_MBR_PROTECTIVE or GPT_MBR_HYBRID, depending on the detection.
+ */
+static int valid_pmbr(struct fdisk_context *cxt)
+{
+ int i, part = 0, ret = 0; /* invalid by default */
+ struct gpt_legacy_mbr *pmbr = NULL;
+
+ if (!cxt->firstsector)
+ goto done;
+
+ pmbr = (struct gpt_legacy_mbr *) cxt->firstsector;
+
+ if (le16_to_cpu(pmbr->signature) != MSDOS_MBR_SIGNATURE)
+ goto done;
+
+ /* seems like a valid MBR was found, check DOS primary partitions */
+ for (i = 0; i < 4; i++) {
+ if (pmbr->partition_record[i].os_type == EFI_PMBR_OSTYPE) {
+ /*
+ * Ok, we at least know that there's a protective MBR,
+ * now check if there are other partition types for
+ * hybrid MBR.
+ */
+ part = i;
+ ret = GPT_MBR_PROTECTIVE;
+ break;
+ }
+ }
+
+ if (ret != GPT_MBR_PROTECTIVE)
+ goto done;
+
+
+ for (i = 0 ; i < 4; i++) {
+ if ((pmbr->partition_record[i].os_type != EFI_PMBR_OSTYPE) &&
+ (pmbr->partition_record[i].os_type != 0x00)) {
+ ret = GPT_MBR_HYBRID;
+ goto done;
+ }
+ }
+
+ /* LBA of the GPT partition header */
+ if (pmbr->partition_record[part].starting_lba !=
+ cpu_to_le32(GPT_PRIMARY_PARTITION_TABLE_LBA))
+ goto done;
+
+ /*
+ * Protective MBRs take up the lesser of the whole disk
+ * or 2 TiB (32bit LBA), ignoring the rest of the disk.
+ * Some partitioning programs, nonetheless, choose to set
+ * the size to the maximum 32-bit limitation, disregarding
+ * the disk size.
+ *
+ * Hybrid MBRs do not necessarily comply with this.
+ *
+ * Consider a bad value here to be a warning to support dd-ing
+ * an image from a smaller disk to a bigger disk.
+ */
+ if (ret == GPT_MBR_PROTECTIVE) {
+ uint64_t sz_lba = (uint64_t) le32_to_cpu(pmbr->partition_record[part].size_in_lba);
+ if (sz_lba != cxt->total_sectors - 1ULL && sz_lba != 0xFFFFFFFFULL) {
+
+ fdisk_warnx(cxt, _("GPT PMBR size mismatch (%"PRIu64" != %"PRIu64") "
+ "will be corrected by write."),
+ sz_lba, cxt->total_sectors - (uint64_t) 1);
+
+ /* Note that gpt_write_pmbr() overwrites PMBR, but we want to keep it valid already
+ * in memory too to disable warnings when valid_pmbr() called next time */
+ pmbr->partition_record[part].size_in_lba =
+ cpu_to_le32((uint32_t) min( cxt->total_sectors - 1ULL, 0xFFFFFFFFULL) );
+ fdisk_label_set_changed(cxt->label, 1);
+ }
+ }
+done:
+ DBG(GPT, ul_debug("PMBR type: %s",
+ ret == GPT_MBR_PROTECTIVE ? "protective" :
+ ret == GPT_MBR_HYBRID ? "hybrid" : "???" ));
+ return ret;
+}
+
+static uint64_t last_lba(struct fdisk_context *cxt)
+{
+ struct stat s;
+ uint64_t sectors = 0;
+
+ memset(&s, 0, sizeof(s));
+ if (fstat(cxt->dev_fd, &s) == -1) {
+ fdisk_warn(cxt, _("gpt: stat() failed"));
+ return 0;
+ }
+
+ if (S_ISBLK(s.st_mode))
+ sectors = cxt->total_sectors - 1ULL;
+ else if (S_ISREG(s.st_mode))
+ sectors = ((uint64_t) s.st_size /
+ (uint64_t) cxt->sector_size) - 1ULL;
+ else
+ fdisk_warnx(cxt, _("gpt: cannot handle files with mode %o"), s.st_mode);
+
+ DBG(GPT, ul_debug("last LBA: %"PRIu64"", sectors));
+ return sectors;
+}
+
+static ssize_t read_lba(struct fdisk_context *cxt, uint64_t lba,
+ void *buffer, const size_t bytes)
+{
+ off_t offset = lba * cxt->sector_size;
+
+ if (lseek(cxt->dev_fd, offset, SEEK_SET) == (off_t) -1)
+ return -1;
+ return (size_t)read(cxt->dev_fd, buffer, bytes) != bytes;
+}
+
+
+/* Returns the GPT entry array */
+static unsigned char *gpt_read_entries(struct fdisk_context *cxt,
+ struct gpt_header *header)
+{
+ size_t sz = 0;
+ ssize_t ssz;
+
+ unsigned char *ret = NULL;
+ off_t offset;
+
+ assert(cxt);
+ assert(header);
+
+ if (gpt_sizeof_entries(header, &sz))
+ return NULL;
+
+ ret = calloc(1, sz);
+ if (!ret)
+ return NULL;
+
+ offset = (off_t) le64_to_cpu(header->partition_entry_lba) *
+ cxt->sector_size;
+
+ if (offset != lseek(cxt->dev_fd, offset, SEEK_SET))
+ goto fail;
+
+ ssz = read(cxt->dev_fd, ret, sz);
+ if (ssz < 0 || (size_t) ssz != sz)
+ goto fail;
+
+ return ret;
+
+fail:
+ free(ret);
+ return NULL;
+}
+
+static inline uint32_t count_crc32(const unsigned char *buf, size_t len,
+ size_t ex_off, size_t ex_len)
+{
+ return (ul_crc32_exclude_offset(~0L, buf, len, ex_off, ex_len) ^ ~0L);
+}
+
+static inline uint32_t gpt_header_count_crc32(struct gpt_header *header)
+{
+ return count_crc32((unsigned char *) header, /* buffer */
+ le32_to_cpu(header->size), /* size of buffer */
+ offsetof(struct gpt_header, crc32), /* exclude */
+ sizeof(header->crc32)); /* size of excluded area */
+}
+
+static inline uint32_t gpt_entryarr_count_crc32(struct gpt_header *header, unsigned char *ents)
+{
+ size_t arysz = 0;
+
+ if (gpt_sizeof_entries(header, &arysz))
+ return 0;
+
+ return count_crc32(ents, arysz, 0, 0);
+}
+
+
+/*
+ * Recompute header and partition array 32bit CRC checksums.
+ * This function does not fail - if there's corruption, then it
+ * will be reported when checksumming it again (ie: probing or verify).
+ */
+static void gpt_recompute_crc(struct gpt_header *header, unsigned char *ents)
+{
+ if (!header)
+ return;
+
+ header->partition_entry_array_crc32 =
+ cpu_to_le32( gpt_entryarr_count_crc32(header, ents) );
+
+ header->crc32 = cpu_to_le32( gpt_header_count_crc32(header) );
+}
+
+/*
+ * Compute the 32bit CRC checksum of the partition table header.
+ * Returns 1 if it is valid, otherwise 0.
+ */
+static int gpt_check_header_crc(struct gpt_header *header, unsigned char *ents)
+{
+ uint32_t orgcrc = le32_to_cpu(header->crc32),
+ crc = gpt_header_count_crc32(header);
+
+ if (crc == orgcrc)
+ return 1;
+
+ /*
+ * If we have checksum mismatch it may be due to stale data, like a
+ * partition being added or deleted. Recompute the CRC again and make
+ * sure this is not the case.
+ */
+ if (ents) {
+ gpt_recompute_crc(header, ents);
+ return gpt_header_count_crc32(header) == orgcrc;
+ }
+
+ return 0;
+}
+
+/*
+ * It initializes the partition entry array.
+ * Returns 1 if the checksum is valid, otherwise 0.
+ */
+static int gpt_check_entryarr_crc(struct gpt_header *header, unsigned char *ents)
+{
+ if (!header || !ents)
+ return 0;
+
+ return gpt_entryarr_count_crc32(header, ents) ==
+ le32_to_cpu(header->partition_entry_array_crc32);
+}
+
+static int gpt_check_lba_sanity(struct fdisk_context *cxt, struct gpt_header *header)
+{
+ int ret = 0;
+ uint64_t lu, fu, lastlba = last_lba(cxt);
+
+ fu = le64_to_cpu(header->first_usable_lba);
+ lu = le64_to_cpu(header->last_usable_lba);
+
+ /* check if first and last usable LBA make sense */
+ if (lu < fu) {
+ DBG(GPT, ul_debug("error: header last LBA is before first LBA"));
+ goto done;
+ }
+
+ /* check if first and last usable LBAs with the disk's last LBA */
+ if (fu > lastlba || lu > lastlba) {
+ DBG(GPT, ul_debug("error: header LBAs are after the disk's last LBA (%ju..%ju)",
+ (uintmax_t) fu, (uintmax_t) lu));
+ goto done;
+ }
+
+ /* the header has to be outside usable range */
+ if (fu < GPT_PRIMARY_PARTITION_TABLE_LBA &&
+ GPT_PRIMARY_PARTITION_TABLE_LBA < lu) {
+ DBG(GPT, ul_debug("error: header outside of usable range"));
+ goto done;
+ }
+
+ ret = 1; /* sane */
+done:
+ return ret;
+}
+
+/* Check if there is a valid header signature */
+static int gpt_check_signature(struct gpt_header *header)
+{
+ return header->signature == cpu_to_le64(GPT_HEADER_SIGNATURE);
+}
+
+/*
+ * Return the specified GPT Header, or NULL upon failure/invalid.
+ * Note that all tests must pass to ensure a valid header,
+ * we do not rely on only testing the signature for a valid probe.
+ */
+static struct gpt_header *gpt_read_header(struct fdisk_context *cxt,
+ uint64_t lba,
+ unsigned char **_ents)
+{
+ struct gpt_header *header = NULL;
+ unsigned char *ents = NULL;
+ uint32_t hsz;
+
+ if (!cxt)
+ return NULL;
+
+ /* always allocate all sector, the area after GPT header
+ * has to be fill by zeros */
+ assert(cxt->sector_size >= sizeof(struct gpt_header));
+
+ header = calloc(1, cxt->sector_size);
+ if (!header)
+ return NULL;
+
+ /* read and verify header */
+ if (read_lba(cxt, lba, header, cxt->sector_size) != 0)
+ goto invalid;
+
+ if (!gpt_check_signature(header))
+ goto invalid;
+
+ /* make sure header size is between 92 and sector size bytes */
+ hsz = le32_to_cpu(header->size);
+ if (hsz < GPT_HEADER_MINSZ || hsz > cxt->sector_size)
+ goto invalid;
+
+ if (!gpt_check_header_crc(header, NULL))
+ goto invalid;
+
+ /* read and verify entries */
+ ents = gpt_read_entries(cxt, header);
+ if (!ents)
+ goto invalid;
+
+ if (!gpt_check_entryarr_crc(header, ents))
+ goto invalid;
+
+ if (!gpt_check_lba_sanity(cxt, header))
+ goto invalid;
+
+ /* valid header must be at MyLBA */
+ if (le64_to_cpu(header->my_lba) != lba)
+ goto invalid;
+
+ if (_ents)
+ *_ents = ents;
+ else
+ free(ents);
+
+ DBG(GPT, ul_debug("found valid header on LBA %"PRIu64"", lba));
+ return header;
+invalid:
+ free(header);
+ free(ents);
+
+ DBG(GPT, ul_debug("read header on LBA %"PRIu64" failed", lba));
+ return NULL;
+}
+
+
+static int gpt_locate_disklabel(struct fdisk_context *cxt, int n,
+ const char **name, uint64_t *offset, size_t *size)
+{
+ struct fdisk_gpt_label *gpt;
+
+ assert(cxt);
+
+ *name = NULL;
+ *offset = 0;
+ *size = 0;
+
+ switch (n) {
+ case 0:
+ *name = "PMBR";
+ *offset = 0;
+ *size = 512;
+ break;
+ case 1:
+ *name = _("GPT Header");
+ *offset = (uint64_t) GPT_PRIMARY_PARTITION_TABLE_LBA * cxt->sector_size;
+ *size = sizeof(struct gpt_header);
+ break;
+ case 2:
+ *name = _("GPT Entries");
+ gpt = self_label(cxt);
+ *offset = (uint64_t) le64_to_cpu(gpt->pheader->partition_entry_lba) *
+ cxt->sector_size;
+ return gpt_sizeof_entries(gpt->pheader, size);
+ case 3:
+ *name = _("GPT Backup Entries");
+ gpt = self_label(cxt);
+ *offset = (uint64_t) le64_to_cpu(gpt->bheader->partition_entry_lba) *
+ cxt->sector_size;
+ return gpt_sizeof_entries(gpt->bheader, size);
+ case 4:
+ *name = _("GPT Backup Header");
+ gpt = self_label(cxt);
+ *offset = (uint64_t) le64_to_cpu(gpt->pheader->alternative_lba) * cxt->sector_size;
+ *size = sizeof(struct gpt_header);
+ break;
+ default:
+ return 1; /* no more chunks */
+ }
+
+ return 0;
+}
+
+static int gpt_get_disklabel_item(struct fdisk_context *cxt, struct fdisk_labelitem *item)
+{
+ struct gpt_header *h;
+ int rc = 0;
+ uint64_t x = 0;
+
+ assert(cxt);
+ assert(cxt->label);
+ assert(fdisk_is_label(cxt, GPT));
+
+ h = self_label(cxt)->pheader;
+
+ switch (item->id) {
+ case GPT_LABELITEM_ID:
+ item->name = _("Disk identifier");
+ item->type = 's';
+ item->data.str = gpt_get_header_id(h);
+ if (!item->data.str)
+ rc = -ENOMEM;
+ break;
+ case GPT_LABELITEM_FIRSTLBA:
+ item->name = _("First usable LBA");
+ item->type = 'j';
+ item->data.num64 = le64_to_cpu(h->first_usable_lba);
+ break;
+ case GPT_LABELITEM_LASTLBA:
+ item->name = _("Last usable LBA");
+ item->type = 'j';
+ item->data.num64 = le64_to_cpu(h->last_usable_lba);
+ break;
+ case GPT_LABELITEM_ALTLBA:
+ /* TRANSLATORS: The LBA (Logical Block Address) of the backup GPT header. */
+ item->name = _("Alternative LBA");
+ item->type = 'j';
+ item->data.num64 = le64_to_cpu(h->alternative_lba);
+ break;
+ case GPT_LABELITEM_ENTRIESLBA:
+ /* TRANSLATORS: The start of the array of partition entries. */
+ item->name = _("Partition entries starting LBA");
+ item->type = 'j';
+ item->data.num64 = le64_to_cpu(h->partition_entry_lba);
+ break;
+ case GPT_LABELITEM_ENTRIESLASTLBA:
+ /* TRANSLATORS: The end of the array of partition entries. */
+ item->name = _("Partition entries ending LBA");
+ item->type = 'j';
+ gpt_calculate_sectorsof_entries(h,
+ le32_to_cpu(h->npartition_entries), &x, cxt);
+ item->data.num64 = le64_to_cpu(h->partition_entry_lba) + x - 1;
+ break;
+ case GPT_LABELITEM_ENTRIESALLOC:
+ item->name = _("Allocated partition entries");
+ item->type = 'j';
+ item->data.num64 = le32_to_cpu(h->npartition_entries);
+ break;
+ default:
+ if (item->id < __FDISK_NLABELITEMS)
+ rc = 1; /* unsupported generic item */
+ else
+ rc = 2; /* out of range */
+ break;
+ }
+
+ return rc;
+}
+
+/*
+ * Returns the number of partitions that are in use.
+ */
+static size_t partitions_in_use(struct fdisk_gpt_label *gpt)
+{
+ size_t i, used = 0;
+
+ assert(gpt);
+ assert(gpt->pheader);
+ assert(gpt->ents);
+
+ for (i = 0; i < gpt_get_nentries(gpt); i++) {
+ struct gpt_entry *e = gpt_get_entry(gpt, i);
+
+ if (gpt_entry_is_used(e))
+ used++;
+ }
+ return used;
+}
+
+
+/*
+ * Check if a partition is too big for the disk (sectors).
+ * Returns the faulting partition number, otherwise 0.
+ */
+static uint32_t check_too_big_partitions(struct fdisk_gpt_label *gpt, uint64_t sectors)
+{
+ size_t i;
+
+ assert(gpt);
+ assert(gpt->pheader);
+ assert(gpt->ents);
+
+ for (i = 0; i < gpt_get_nentries(gpt); i++) {
+ struct gpt_entry *e = gpt_get_entry(gpt, i);
+
+ if (!gpt_entry_is_used(e))
+ continue;
+ if (gpt_partition_end(e) >= sectors)
+ return i + 1;
+ }
+
+ return 0;
+}
+
+/*
+ * Check if a partition ends before it begins
+ * Returns the faulting partition number, otherwise 0.
+ */
+static uint32_t check_start_after_end_partitions(struct fdisk_gpt_label *gpt)
+{
+ size_t i;
+
+ assert(gpt);
+ assert(gpt->pheader);
+ assert(gpt->ents);
+
+ for (i = 0; i < gpt_get_nentries(gpt); i++) {
+ struct gpt_entry *e = gpt_get_entry(gpt, i);
+
+ if (!gpt_entry_is_used(e))
+ continue;
+ if (gpt_partition_start(e) > gpt_partition_end(e))
+ return i + 1;
+ }
+
+ return 0;
+}
+
+/*
+ * Check if partition e1 overlaps with partition e2.
+ */
+static inline int partition_overlap(struct gpt_entry *e1, struct gpt_entry *e2)
+{
+ uint64_t start1 = gpt_partition_start(e1);
+ uint64_t end1 = gpt_partition_end(e1);
+ uint64_t start2 = gpt_partition_start(e2);
+ uint64_t end2 = gpt_partition_end(e2);
+
+ return (start1 && start2 && (start1 <= end2) != (end1 < start2));
+}
+
+/*
+ * Find any partitions that overlap.
+ */
+static uint32_t check_overlap_partitions(struct fdisk_gpt_label *gpt)
+{
+ size_t i, j;
+
+ assert(gpt);
+ assert(gpt->pheader);
+ assert(gpt->ents);
+
+ for (i = 0; i < gpt_get_nentries(gpt); i++)
+ for (j = 0; j < i; j++) {
+ struct gpt_entry *ei = gpt_get_entry(gpt, i);
+ struct gpt_entry *ej = gpt_get_entry(gpt, j);
+
+ if (!gpt_entry_is_used(ei) || !gpt_entry_is_used(ej))
+ continue;
+ if (partition_overlap(ei, ej)) {
+ DBG(GPT, ul_debug("partitions overlap detected [%zu vs. %zu]", i, j));
+ return i + 1;
+ }
+ }
+
+ return 0;
+}
+
+/*
+ * Find the first available block after the starting point; returns 0 if
+ * there are no available blocks left, or error. From gdisk.
+ */
+static uint64_t find_first_available(struct fdisk_gpt_label *gpt, uint64_t start)
+{
+ int first_moved = 0;
+ uint64_t first;
+ uint64_t fu, lu;
+
+ assert(gpt);
+ assert(gpt->pheader);
+ assert(gpt->ents);
+
+ fu = le64_to_cpu(gpt->pheader->first_usable_lba);
+ lu = le64_to_cpu(gpt->pheader->last_usable_lba);
+
+ /*
+ * Begin from the specified starting point or from the first usable
+ * LBA, whichever is greater...
+ */
+ first = start < fu ? fu : start;
+
+ /*
+ * Now search through all partitions; if first is within an
+ * existing partition, move it to the next sector after that
+ * partition and repeat. If first was moved, set firstMoved
+ * flag; repeat until firstMoved is not set, so as to catch
+ * cases where partitions are out of sequential order....
+ */
+ do {
+ size_t i;
+
+ first_moved = 0;
+ for (i = 0; i < gpt_get_nentries(gpt); i++) {
+ struct gpt_entry *e = gpt_get_entry(gpt, i);
+
+ if (!gpt_entry_is_used(e))
+ continue;
+ if (first < gpt_partition_start(e))
+ continue;
+ if (first <= gpt_partition_end(e)) {
+ first = gpt_partition_end(e) + 1;
+ first_moved = 1;
+ }
+ }
+ } while (first_moved == 1);
+
+ if (first > lu)
+ first = 0;
+
+ return first;
+}
+
+
+/* Returns last available sector in the free space pointed to by start. From gdisk. */
+static uint64_t find_last_free(struct fdisk_gpt_label *gpt, uint64_t start)
+{
+ size_t i;
+ uint64_t nearest_start;
+
+ assert(gpt);
+ assert(gpt->pheader);
+ assert(gpt->ents);
+
+ nearest_start = le64_to_cpu(gpt->pheader->last_usable_lba);
+
+ for (i = 0; i < gpt_get_nentries(gpt); i++) {
+ struct gpt_entry *e = gpt_get_entry(gpt, i);
+ uint64_t ps = gpt_partition_start(e);
+
+ if (nearest_start > ps && ps > start)
+ nearest_start = ps - 1ULL;
+ }
+
+ return nearest_start;
+}
+
+/* Returns the last free sector on the disk. From gdisk. */
+static uint64_t find_last_free_sector(struct fdisk_gpt_label *gpt)
+{
+ int last_moved;
+ uint64_t last = 0;
+
+ assert(gpt);
+ assert(gpt->pheader);
+ assert(gpt->ents);
+
+ /* start by assuming the last usable LBA is available */
+ last = le64_to_cpu(gpt->pheader->last_usable_lba);
+ do {
+ size_t i;
+
+ last_moved = 0;
+ for (i = 0; i < gpt_get_nentries(gpt); i++) {
+ struct gpt_entry *e = gpt_get_entry(gpt, i);
+
+ if (last >= gpt_partition_start(e) &&
+ last <= gpt_partition_end(e)) {
+ last = gpt_partition_start(e) - 1ULL;
+ last_moved = 1;
+ }
+ }
+ } while (last_moved == 1);
+
+ return last;
+}
+
+/*
+ * Finds the first available sector in the largest block of unallocated
+ * space on the disk. Returns 0 if there are no available blocks left.
+ * From gdisk.
+ */
+static uint64_t find_first_in_largest(struct fdisk_gpt_label *gpt)
+{
+ uint64_t start = 0, first_sect, last_sect;
+ uint64_t segment_size, selected_size = 0, selected_segment = 0;
+
+ assert(gpt);
+ assert(gpt->pheader);
+ assert(gpt->ents);
+
+ do {
+ first_sect = find_first_available(gpt, start);
+ if (first_sect != 0) {
+ last_sect = find_last_free(gpt, first_sect);
+ segment_size = last_sect - first_sect + 1ULL;
+
+ if (segment_size > selected_size) {
+ selected_size = segment_size;
+ selected_segment = first_sect;
+ }
+ start = last_sect + 1ULL;
+ }
+ } while (first_sect != 0);
+
+ return selected_segment;
+}
+
+/*
+ * Find the total number of free sectors, the number of segments in which
+ * they reside, and the size of the largest of those segments. From gdisk.
+ */
+static uint64_t get_free_sectors(struct fdisk_context *cxt,
+ struct fdisk_gpt_label *gpt,
+ uint32_t *nsegments,
+ uint64_t *largest_segment)
+{
+ uint32_t num = 0;
+ uint64_t first_sect, last_sect;
+ uint64_t largest_seg = 0, segment_sz;
+ uint64_t totfound = 0, start = 0; /* starting point for each search */
+
+ if (!cxt->total_sectors)
+ goto done;
+
+ assert(gpt);
+ assert(gpt->pheader);
+ assert(gpt->ents);
+
+ do {
+ first_sect = find_first_available(gpt, start);
+ if (first_sect) {
+ last_sect = find_last_free(gpt, first_sect);
+ segment_sz = last_sect - first_sect + 1;
+
+ if (segment_sz > largest_seg)
+ largest_seg = segment_sz;
+ totfound += segment_sz;
+ num++;
+ start = last_sect + 1ULL;
+ }
+ } while (first_sect);
+
+done:
+ if (nsegments)
+ *nsegments = num;
+ if (largest_segment)
+ *largest_segment = largest_seg;
+
+ return totfound;
+}
+
+static int gpt_probe_label(struct fdisk_context *cxt)
+{
+ int mbr_type;
+ struct fdisk_gpt_label *gpt;
+
+ assert(cxt);
+ assert(cxt->label);
+ assert(fdisk_is_label(cxt, GPT));
+
+ gpt = self_label(cxt);
+
+ /* TODO: it would be nice to support scenario when GPT headers are OK,
+ * but PMBR is corrupt */
+ mbr_type = valid_pmbr(cxt);
+ if (!mbr_type)
+ goto failed;
+
+ /* primary header */
+ gpt->pheader = gpt_read_header(cxt, GPT_PRIMARY_PARTITION_TABLE_LBA,
+ &gpt->ents);
+
+ if (gpt->pheader)
+ /* primary OK, try backup from alternative LBA */
+ gpt->bheader = gpt_read_header(cxt,
+ le64_to_cpu(gpt->pheader->alternative_lba),
+ NULL);
+ else
+ /* primary corrupted -- try last LBA */
+ gpt->bheader = gpt_read_header(cxt, last_lba(cxt), &gpt->ents);
+
+ if (!gpt->pheader && !gpt->bheader)
+ goto failed;
+
+ /* primary OK, backup corrupted -- recovery */
+ if (gpt->pheader && !gpt->bheader) {
+ fdisk_warnx(cxt, _("The backup GPT table is corrupt, but the "
+ "primary appears OK, so that will be used."));
+ gpt->bheader = gpt_copy_header(cxt, gpt->pheader);
+ if (!gpt->bheader)
+ goto failed;
+ gpt_recompute_crc(gpt->bheader, gpt->ents);
+ fdisk_label_set_changed(cxt->label, 1);
+
+ /* primary corrupted, backup OK -- recovery */
+ } else if (!gpt->pheader && gpt->bheader) {
+ fdisk_warnx(cxt, _("The primary GPT table is corrupt, but the "
+ "backup appears OK, so that will be used."));
+ gpt->pheader = gpt_copy_header(cxt, gpt->bheader);
+ if (!gpt->pheader)
+ goto failed;
+ gpt_recompute_crc(gpt->pheader, gpt->ents);
+ fdisk_label_set_changed(cxt->label, 1);
+ }
+
+ /* The headers make be correct, but Backup do not have to be on the end
+ * of the device (due to device resize, etc.). Let's fix this issue. */
+ if (gpt->minimize == 0 &&
+ (le64_to_cpu(gpt->pheader->alternative_lba) > cxt->total_sectors ||
+ le64_to_cpu(gpt->pheader->alternative_lba) < cxt->total_sectors - 1ULL)) {
+
+ if (gpt->no_relocate || fdisk_is_readonly(cxt))
+ fdisk_warnx(cxt, _("The backup GPT table is not on the end of the device."));
+
+ else {
+ fdisk_warnx(cxt, _("The backup GPT table is not on the end of the device. "
+ "This problem will be corrected by write."));
+
+ if (gpt_fix_alternative_lba(cxt, gpt) != 0)
+ fdisk_warnx(cxt, _("Failed to recalculate backup GPT table location"));
+ gpt_recompute_crc(gpt->bheader, gpt->ents);
+ gpt_recompute_crc(gpt->pheader, gpt->ents);
+ fdisk_label_set_changed(cxt->label, 1);
+ }
+ }
+
+ if (gpt->minimize && gpt_possible_minimize(cxt, gpt))
+ fdisk_label_set_changed(cxt->label, 1);
+
+ cxt->label->nparts_max = gpt_get_nentries(gpt);
+ cxt->label->nparts_cur = partitions_in_use(gpt);
+ return 1;
+failed:
+ DBG(GPT, ul_debug("probe failed"));
+ gpt_deinit(cxt->label);
+ return 0;
+}
+
+static char *encode_to_utf8(unsigned char *src, size_t count)
+{
+ unsigned char *dest;
+ size_t len = (count * 3 / 2) + 1;
+
+ dest = calloc(1, len);
+ if (!dest)
+ return NULL;
+
+ ul_encode_to_utf8(UL_ENCODE_UTF16LE, dest, len, src, count);
+ return (char *) dest;
+}
+
+static int gpt_entry_attrs_to_string(struct gpt_entry *e, char **res)
+{
+ unsigned int n, count = 0;
+ size_t l;
+ char *bits, *p;
+ uint64_t attrs;
+
+ assert(e);
+ assert(res);
+
+ *res = NULL;
+ attrs = e->attrs;
+ if (!attrs)
+ return 0; /* no attributes at all */
+
+ bits = (char *) &attrs;
+
+ /* Note that sizeof() is correct here, we need separators between
+ * the strings so also count \0 is correct */
+ *res = calloc(1, sizeof(GPT_ATTRSTR_NOBLOCK) +
+ sizeof(GPT_ATTRSTR_REQ) +
+ sizeof(GPT_ATTRSTR_LEGACY) +
+ sizeof("GUID:") + (GPT_ATTRBIT_GUID_COUNT * 3));
+ if (!*res)
+ return -errno;
+
+ p = *res;
+ if (isset(bits, GPT_ATTRBIT_REQ)) {
+ memcpy(p, GPT_ATTRSTR_REQ, (l = sizeof(GPT_ATTRSTR_REQ)));
+ p += l - 1;
+ }
+ if (isset(bits, GPT_ATTRBIT_NOBLOCK)) {
+ if (p != *res)
+ *p++ = ' ';
+ memcpy(p, GPT_ATTRSTR_NOBLOCK, (l = sizeof(GPT_ATTRSTR_NOBLOCK)));
+ p += l - 1;
+ }
+ if (isset(bits, GPT_ATTRBIT_LEGACY)) {
+ if (p != *res)
+ *p++ = ' ';
+ memcpy(p, GPT_ATTRSTR_LEGACY, (l = sizeof(GPT_ATTRSTR_LEGACY)));
+ p += l - 1;
+ }
+
+ for (n = GPT_ATTRBIT_GUID_FIRST;
+ n < GPT_ATTRBIT_GUID_FIRST + GPT_ATTRBIT_GUID_COUNT; n++) {
+
+ if (!isset(bits, n))
+ continue;
+ if (!count) {
+ if (p != *res)
+ *p++ = ' ';
+ p += sprintf(p, "GUID:%u", n);
+ } else
+ p += sprintf(p, ",%u", n);
+ count++;
+ }
+
+ return 0;
+}
+
+static int gpt_entry_attrs_from_string(
+ struct fdisk_context *cxt,
+ struct gpt_entry *e,
+ const char *str)
+{
+ const char *p = str;
+ uint64_t attrs = 0;
+ char *bits;
+
+ assert(e);
+ assert(p);
+
+ DBG(GPT, ul_debug("parsing string attributes '%s'", p));
+
+ bits = (char *) &attrs;
+
+ while (p && *p) {
+ int bit = -1;
+
+ while (isblank(*p)) p++;
+ if (!*p)
+ break;
+
+ DBG(GPT, ul_debug(" item '%s'", p));
+
+ if (strncmp(p, GPT_ATTRSTR_REQ,
+ sizeof(GPT_ATTRSTR_REQ) - 1) == 0) {
+ bit = GPT_ATTRBIT_REQ;
+ p += sizeof(GPT_ATTRSTR_REQ) - 1;
+ } else if (strncmp(p, GPT_ATTRSTR_REQ_TYPO,
+ sizeof(GPT_ATTRSTR_REQ_TYPO) - 1) == 0) {
+ bit = GPT_ATTRBIT_REQ;
+ p += sizeof(GPT_ATTRSTR_REQ_TYPO) - 1;
+ } else if (strncmp(p, GPT_ATTRSTR_LEGACY,
+ sizeof(GPT_ATTRSTR_LEGACY) - 1) == 0) {
+ bit = GPT_ATTRBIT_LEGACY;
+ p += sizeof(GPT_ATTRSTR_LEGACY) - 1;
+ } else if (strncmp(p, GPT_ATTRSTR_NOBLOCK,
+ sizeof(GPT_ATTRSTR_NOBLOCK) - 1) == 0) {
+ bit = GPT_ATTRBIT_NOBLOCK;
+ p += sizeof(GPT_ATTRSTR_NOBLOCK) - 1;
+
+ /* GUID:<bit> as well as <bit> */
+ } else if (isdigit((unsigned char) *p)
+ || (strncmp(p, "GUID:", 5) == 0
+ && isdigit((unsigned char) *(p + 5)))) {
+ char *end = NULL;
+
+ if (*p == 'G')
+ p += 5;
+
+ errno = 0;
+ bit = strtol(p, &end, 0);
+ if (errno || !end || end == str
+ || bit < GPT_ATTRBIT_GUID_FIRST
+ || bit >= GPT_ATTRBIT_GUID_FIRST + GPT_ATTRBIT_GUID_COUNT)
+ bit = -1;
+ else
+ p = end;
+ }
+
+ if (bit < 0) {
+ fdisk_warnx(cxt, _("unsupported GPT attribute bit '%s'"), p);
+ return -EINVAL;
+ }
+
+ if (*p && *p != ',' && !isblank(*p)) {
+ fdisk_warnx(cxt, _("failed to parse GPT attribute string '%s'"), str);
+ return -EINVAL;
+ }
+
+ setbit(bits, bit);
+
+ while (isblank(*p)) p++;
+ if (*p == ',')
+ p++;
+ }
+
+ e->attrs = attrs;
+ return 0;
+}
+
+static int gpt_get_partition(struct fdisk_context *cxt, size_t n,
+ struct fdisk_partition *pa)
+{
+ struct fdisk_gpt_label *gpt;
+ struct gpt_entry *e;
+ char u_str[UUID_STR_LEN];
+ int rc = 0;
+ struct gpt_guid guid;
+
+ assert(cxt);
+ assert(cxt->label);
+ assert(fdisk_is_label(cxt, GPT));
+
+ gpt = self_label(cxt);
+
+ if (n >= gpt_get_nentries(gpt))
+ return -EINVAL;
+
+ gpt = self_label(cxt);
+ e = gpt_get_entry(gpt, n);
+
+ pa->used = gpt_entry_is_used(e) || gpt_partition_start(e);
+ if (!pa->used)
+ return 0;
+
+ pa->start = gpt_partition_start(e);
+ pa->size = gpt_partition_size(e);
+ pa->type = gpt_partition_parttype(cxt, e);
+
+ guid = e->partition_guid;
+ if (guid_to_string(&guid, u_str)) {
+ pa->uuid = strdup(u_str);
+ if (!pa->uuid) {
+ rc = -errno;
+ goto done;
+ }
+ } else
+ pa->uuid = NULL;
+
+ rc = gpt_entry_attrs_to_string(e, &pa->attrs);
+ if (rc)
+ goto done;
+
+ pa->name = encode_to_utf8((unsigned char *)e->name, sizeof(e->name));
+ return 0;
+done:
+ fdisk_reset_partition(pa);
+ return rc;
+}
+
+
+static int gpt_set_partition(struct fdisk_context *cxt, size_t n,
+ struct fdisk_partition *pa)
+{
+ struct fdisk_gpt_label *gpt;
+ struct gpt_entry *e;
+ int rc = 0;
+ uint64_t start, end;
+
+ assert(cxt);
+ assert(cxt->label);
+ assert(fdisk_is_label(cxt, GPT));
+
+ gpt = self_label(cxt);
+
+ if (n >= gpt_get_nentries(gpt))
+ return -EINVAL;
+
+ FDISK_INIT_UNDEF(start);
+ FDISK_INIT_UNDEF(end);
+
+ gpt = self_label(cxt);
+ e = gpt_get_entry(gpt, n);
+
+ if (pa->uuid) {
+ char new_u[UUID_STR_LEN], old_u[UUID_STR_LEN];
+ struct gpt_guid guid;
+
+ guid = e->partition_guid;
+ guid_to_string(&guid, old_u);
+ rc = gpt_entry_set_uuid(e, pa->uuid);
+ if (rc)
+ return rc;
+ guid = e->partition_guid;
+ guid_to_string(&guid, new_u);
+ fdisk_info(cxt, _("Partition UUID changed from %s to %s."),
+ old_u, new_u);
+ }
+
+ if (pa->name) {
+ int len;
+ char *old = encode_to_utf8((unsigned char *)e->name, sizeof(e->name));
+ len = gpt_entry_set_name(e, pa->name);
+ if (len < 0)
+ fdisk_warn(cxt, _("Failed to translate partition name, name not changed."));
+ else
+ fdisk_info(cxt, _("Partition name changed from '%s' to '%.*s'."),
+ old, len, pa->name);
+ free(old);
+ }
+
+ if (pa->type && pa->type->typestr) {
+ struct gpt_guid typeid;
+
+ rc = string_to_guid(pa->type->typestr, &typeid);
+ if (rc)
+ return rc;
+ gpt_entry_set_type(e, &typeid);
+ }
+ if (pa->attrs) {
+ rc = gpt_entry_attrs_from_string(cxt, e, pa->attrs);
+ if (rc)
+ return rc;
+ }
+
+ if (fdisk_partition_has_start(pa))
+ start = pa->start;
+ if (fdisk_partition_has_size(pa) || fdisk_partition_has_start(pa)) {
+ uint64_t xstart = fdisk_partition_has_start(pa) ? pa->start : gpt_partition_start(e);
+ uint64_t xsize = fdisk_partition_has_size(pa) ? pa->size : gpt_partition_size(e);
+ end = xstart + xsize - 1ULL;
+ }
+
+ if (!FDISK_IS_UNDEF(start)) {
+ if (start < le64_to_cpu(gpt->pheader->first_usable_lba)) {
+ fdisk_warnx(cxt, _("The start of the partition understeps FirstUsableLBA."));
+ return -EINVAL;
+ }
+ e->lba_start = cpu_to_le64(start);
+ }
+ if (!FDISK_IS_UNDEF(end)) {
+ if (end > le64_to_cpu(gpt->pheader->last_usable_lba)) {
+ fdisk_warnx(cxt, _("The end of the partition oversteps LastUsableLBA."));
+ return -EINVAL;
+ }
+ e->lba_end = cpu_to_le64(end);
+ }
+ gpt_recompute_crc(gpt->pheader, gpt->ents);
+ gpt_recompute_crc(gpt->bheader, gpt->ents);
+
+ fdisk_label_set_changed(cxt->label, 1);
+ return rc;
+}
+
+static int gpt_read(struct fdisk_context *cxt, off_t offset, void *buf, size_t count)
+{
+ if (offset != lseek(cxt->dev_fd, offset, SEEK_SET))
+ return -errno;
+
+ if (read_all(cxt->dev_fd, buf, count))
+ return -errno;
+
+ DBG(GPT, ul_debug(" read OK [offset=%zu, size=%zu]",
+ (size_t) offset, count));
+ return 0;
+}
+
+static int gpt_write(struct fdisk_context *cxt, off_t offset, void *buf, size_t count)
+{
+ if (offset != lseek(cxt->dev_fd, offset, SEEK_SET))
+ return -errno;
+
+ if (write_all(cxt->dev_fd, buf, count))
+ return -errno;
+
+ if (fsync(cxt->dev_fd) != 0)
+ return -errno;
+
+ DBG(GPT, ul_debug(" write OK [offset=%zu, size=%zu]",
+ (size_t) offset, count));
+ return 0;
+}
+
+/*
+ * Write partitions.
+ * Returns 0 on success, or corresponding error otherwise.
+ */
+static int gpt_write_partitions(struct fdisk_context *cxt,
+ struct gpt_header *header, unsigned char *ents)
+{
+ size_t esz = 0;
+ int rc;
+
+ rc = gpt_sizeof_entries(header, &esz);
+ if (rc)
+ return rc;
+
+ return gpt_write(cxt,
+ (off_t) le64_to_cpu(header->partition_entry_lba) * cxt->sector_size,
+ ents, esz);
+}
+
+/*
+ * Write a GPT header to a specified LBA.
+ *
+ * We read all sector, so we have to write all sector back
+ * to the device -- never ever rely on sizeof(struct gpt_header)!
+ *
+ * Returns 0 on success, or corresponding error otherwise.
+ */
+static int gpt_write_header(struct fdisk_context *cxt,
+ struct gpt_header *header, uint64_t lba)
+{
+ return gpt_write(cxt, lba * cxt->sector_size, header, cxt->sector_size);
+}
+
+/*
+ * Write the protective MBR.
+ * Returns 0 on success, or corresponding error otherwise.
+ */
+static int gpt_write_pmbr(struct fdisk_context *cxt)
+{
+ struct gpt_legacy_mbr *pmbr;
+ struct gpt_legacy_mbr *current;
+ int rc;
+
+ assert(cxt);
+ assert(cxt->firstsector);
+
+ DBG(GPT, ul_debug("(over)writing PMBR"));
+ pmbr = (struct gpt_legacy_mbr *) cxt->firstsector;
+
+ /* zero out the legacy partitions */
+ memset(pmbr->partition_record, 0, sizeof(pmbr->partition_record));
+
+ pmbr->signature = cpu_to_le16(MSDOS_MBR_SIGNATURE);
+ pmbr->partition_record[0].os_type = EFI_PMBR_OSTYPE;
+ pmbr->partition_record[0].start_sector = 2;
+ pmbr->partition_record[0].end_head = 0xFF;
+ pmbr->partition_record[0].end_sector = 0xFF;
+ pmbr->partition_record[0].end_track = 0xFF;
+ pmbr->partition_record[0].starting_lba = cpu_to_le32(1);
+
+ /*
+ * Set size_in_lba to the size of the disk minus one. If the size of the disk
+ * is too large to be represented by a 32bit LBA (2Tb), set it to 0xFFFFFFFF.
+ */
+ if (cxt->total_sectors - 1ULL > 0xFFFFFFFFULL)
+ pmbr->partition_record[0].size_in_lba = cpu_to_le32(0xFFFFFFFF);
+ else
+ pmbr->partition_record[0].size_in_lba =
+ cpu_to_le32((uint32_t) (cxt->total_sectors - 1ULL));
+
+ /* Read the current PMBR and compare it with the new, don't write if
+ * the same. */
+ current = malloc(sizeof(*current));
+ if (!current)
+ goto do_write;
+
+ rc = gpt_read(cxt, GPT_PMBR_LBA * cxt->sector_size,
+ current, sizeof(*current));
+ if (!rc)
+ rc = memcmp(pmbr, current, sizeof(*current));
+
+ free(current);
+
+ if (!rc) {
+ DBG(GPT, ul_debug("Same MBR on disk => don't write it"));
+ return 0;
+ }
+
+ do_write:
+ /* pMBR covers the first sector (LBA) of the disk */
+ return gpt_write(cxt, GPT_PMBR_LBA * cxt->sector_size,
+ pmbr, cxt->sector_size);
+}
+
+/*
+ * Writes in-memory GPT and pMBR data to disk.
+ * Returns 0 if successful write, otherwise, a corresponding error.
+ * Any indication of error will abort the operation.
+ */
+static int gpt_write_disklabel(struct fdisk_context *cxt)
+{
+ struct fdisk_gpt_label *gpt;
+ int mbr_type;
+
+ assert(cxt);
+ assert(cxt->label);
+ assert(fdisk_is_label(cxt, GPT));
+
+ DBG(GPT, ul_debug("writing..."));
+
+ gpt = self_label(cxt);
+ mbr_type = valid_pmbr(cxt);
+
+ /* check that disk is big enough to handle the backup header */
+ if (le64_to_cpu(gpt->pheader->alternative_lba) > cxt->total_sectors)
+ goto err0;
+
+ /* check that the backup header is properly placed */
+ if (le64_to_cpu(gpt->pheader->alternative_lba) < cxt->total_sectors - 1ULL)
+ goto err0;
+
+ if (check_overlap_partitions(gpt))
+ goto err0;
+
+ if (gpt->minimize)
+ gpt_minimize_alternative_lba(cxt, gpt);
+
+ /* recompute CRCs for both headers */
+ gpt_recompute_crc(gpt->pheader, gpt->ents);
+ gpt_recompute_crc(gpt->bheader, gpt->ents);
+
+ /*
+ * UEFI requires writing in this specific order:
+ * 1) backup partition tables
+ * 2) backup GPT header
+ * 3) primary partition tables
+ * 4) primary GPT header
+ * 5) protective MBR
+ *
+ * If any write fails, we abort the rest.
+ */
+ if (gpt_write_partitions(cxt, gpt->bheader, gpt->ents) != 0)
+ goto err1;
+ if (gpt_write_header(cxt, gpt->bheader,
+ le64_to_cpu(gpt->pheader->alternative_lba)) != 0)
+ goto err1;
+ if (gpt_write_partitions(cxt, gpt->pheader, gpt->ents) != 0)
+ goto err1;
+ if (gpt_write_header(cxt, gpt->pheader, GPT_PRIMARY_PARTITION_TABLE_LBA) != 0)
+ goto err1;
+
+ if (mbr_type == GPT_MBR_HYBRID)
+ fdisk_warnx(cxt, _("The device contains hybrid MBR -- writing GPT only."));
+ else if (gpt_write_pmbr(cxt) != 0)
+ goto err1;
+
+ DBG(GPT, ul_debug("...write success"));
+ return 0;
+err0:
+ DBG(GPT, ul_debug("...write failed: incorrect input"));
+ errno = EINVAL;
+ return -EINVAL;
+err1:
+ DBG(GPT, ul_debug("...write failed: %m"));
+ return -errno;
+}
+
+/*
+ * Verify data integrity and report any found problems for:
+ * - primary and backup header validations
+ * - partition validations
+ */
+static int gpt_verify_disklabel(struct fdisk_context *cxt)
+{
+ int nerror = 0;
+ unsigned int ptnum;
+ struct fdisk_gpt_label *gpt;
+
+ assert(cxt);
+ assert(cxt->label);
+ assert(fdisk_is_label(cxt, GPT));
+
+ gpt = self_label(cxt);
+ if (!gpt)
+ return -EINVAL;
+
+ if (!gpt->bheader) {
+ nerror++;
+ fdisk_warnx(cxt, _("Disk does not contain a valid backup header."));
+ }
+
+ if (!gpt_check_header_crc(gpt->pheader, gpt->ents)) {
+ nerror++;
+ fdisk_warnx(cxt, _("Invalid primary header CRC checksum."));
+ }
+ if (gpt->bheader && !gpt_check_header_crc(gpt->bheader, gpt->ents)) {
+ nerror++;
+ fdisk_warnx(cxt, _("Invalid backup header CRC checksum."));
+ }
+
+ if (!gpt_check_entryarr_crc(gpt->pheader, gpt->ents)) {
+ nerror++;
+ fdisk_warnx(cxt, _("Invalid partition entry checksum."));
+ }
+
+ if (!gpt_check_lba_sanity(cxt, gpt->pheader)) {
+ nerror++;
+ fdisk_warnx(cxt, _("Invalid primary header LBA sanity checks."));
+ }
+ if (gpt->bheader && !gpt_check_lba_sanity(cxt, gpt->bheader)) {
+ nerror++;
+ fdisk_warnx(cxt, _("Invalid backup header LBA sanity checks."));
+ }
+
+ if (le64_to_cpu(gpt->pheader->my_lba) != GPT_PRIMARY_PARTITION_TABLE_LBA) {
+ nerror++;
+ fdisk_warnx(cxt, _("MyLBA mismatch with real position at primary header."));
+ }
+ if (gpt->bheader && le64_to_cpu(gpt->bheader->my_lba) != last_lba(cxt)) {
+ nerror++;
+ fdisk_warnx(cxt, _("MyLBA mismatch with real position at backup header."));
+
+ }
+ if (le64_to_cpu(gpt->pheader->alternative_lba) >= cxt->total_sectors) {
+ nerror++;
+ fdisk_warnx(cxt, _("Disk is too small to hold all data."));
+ }
+
+ /*
+ * if the GPT is the primary table, check the alternateLBA
+ * to see if it is a valid GPT
+ */
+ if (gpt->bheader && (le64_to_cpu(gpt->pheader->my_lba) !=
+ le64_to_cpu(gpt->bheader->alternative_lba))) {
+ nerror++;
+ fdisk_warnx(cxt, _("Primary and backup header mismatch."));
+ }
+
+ ptnum = check_overlap_partitions(gpt);
+ if (ptnum) {
+ nerror++;
+ fdisk_warnx(cxt, _("Partition %u overlaps with partition %u."),
+ ptnum, ptnum+1);
+ }
+
+ ptnum = check_too_big_partitions(gpt, cxt->total_sectors);
+ if (ptnum) {
+ nerror++;
+ fdisk_warnx(cxt, _("Partition %u is too big for the disk."),
+ ptnum);
+ }
+
+ ptnum = check_start_after_end_partitions(gpt);
+ if (ptnum) {
+ nerror++;
+ fdisk_warnx(cxt, _("Partition %u ends before it starts."),
+ ptnum);
+ }
+
+ if (!nerror) { /* yay :-) */
+ uint32_t nsegments = 0;
+ uint64_t free_sectors = 0, largest_segment = 0;
+ char *strsz = NULL;
+
+ fdisk_info(cxt, _("No errors detected."));
+ fdisk_info(cxt, _("Header version: %s"), gpt_get_header_revstr(gpt->pheader));
+ fdisk_info(cxt, _("Using %zu out of %zu partitions."),
+ partitions_in_use(gpt),
+ gpt_get_nentries(gpt));
+
+ free_sectors = get_free_sectors(cxt, gpt, &nsegments, &largest_segment);
+ if (largest_segment)
+ strsz = size_to_human_string(SIZE_SUFFIX_SPACE | SIZE_SUFFIX_3LETTER,
+ largest_segment * cxt->sector_size);
+
+ fdisk_info(cxt,
+ P_("A total of %ju free sectors is available in %u segment.",
+ "A total of %ju free sectors is available in %u segments "
+ "(the largest is %s).", nsegments),
+ free_sectors, nsegments, strsz ? : "0 B");
+ free(strsz);
+
+ } else
+ fdisk_warnx(cxt,
+ P_("%d error detected.", "%d errors detected.", nerror),
+ nerror);
+
+ return nerror;
+}
+
+/* Delete a single GPT partition, specified by partnum. */
+static int gpt_delete_partition(struct fdisk_context *cxt,
+ size_t partnum)
+{
+ struct fdisk_gpt_label *gpt;
+
+ assert(cxt);
+ assert(cxt->label);
+ assert(fdisk_is_label(cxt, GPT));
+
+ gpt = self_label(cxt);
+
+ if (partnum >= cxt->label->nparts_max)
+ return -EINVAL;
+
+ if (!gpt_entry_is_used(gpt_get_entry(gpt, partnum)))
+ return -EINVAL;
+
+ /* hasta la vista, baby! */
+ gpt_zeroize_entry(gpt, partnum);
+
+ gpt_recompute_crc(gpt->pheader, gpt->ents);
+ gpt_recompute_crc(gpt->bheader, gpt->ents);
+ cxt->label->nparts_cur--;
+ fdisk_label_set_changed(cxt->label, 1);
+
+ return 0;
+}
+
+
+/* Performs logical checks to add a new partition entry */
+static int gpt_add_partition(
+ struct fdisk_context *cxt,
+ struct fdisk_partition *pa,
+ size_t *partno)
+{
+ uint64_t user_f, user_l; /* user input ranges for first and last sectors */
+ uint64_t disk_f, disk_l; /* first and last available sector ranges on device*/
+ uint64_t dflt_f, dflt_l, max_l; /* largest segment (default) */
+ struct gpt_guid typeid;
+ struct fdisk_gpt_label *gpt;
+ struct gpt_header *pheader;
+ struct gpt_entry *e;
+ struct fdisk_ask *ask = NULL;
+ size_t partnum;
+ int rc;
+
+ assert(cxt);
+ assert(cxt->label);
+ assert(fdisk_is_label(cxt, GPT));
+
+ gpt = self_label(cxt);
+
+ assert(gpt);
+ assert(gpt->pheader);
+ assert(gpt->ents);
+
+ pheader = gpt->pheader;
+
+ rc = fdisk_partition_next_partno(pa, cxt, &partnum);
+ if (rc) {
+ DBG(GPT, ul_debug("failed to get next partno"));
+ return rc;
+ }
+
+ assert(partnum < gpt_get_nentries(gpt));
+
+ if (gpt_entry_is_used(gpt_get_entry(gpt, partnum))) {
+ fdisk_warnx(cxt, _("Partition %zu is already defined. "
+ "Delete it before re-adding it."), partnum +1);
+ return -ERANGE;
+ }
+ if (gpt_get_nentries(gpt) == partitions_in_use(gpt)) {
+ fdisk_warnx(cxt, _("All partitions are already in use."));
+ return -ENOSPC;
+ }
+ if (!get_free_sectors(cxt, gpt, NULL, NULL)) {
+ fdisk_warnx(cxt, _("No free sectors available."));
+ return -ENOSPC;
+ }
+
+ rc = string_to_guid(pa && pa->type && pa->type->typestr ?
+ pa->type->typestr:
+ GPT_DEFAULT_ENTRY_TYPE, &typeid);
+ if (rc)
+ return rc;
+
+ disk_f = find_first_available(gpt, le64_to_cpu(pheader->first_usable_lba));
+ e = gpt_get_entry(gpt, 0);
+
+ /* if first sector no explicitly defined then ignore small gaps before
+ * the first partition */
+ if ((!pa || !fdisk_partition_has_start(pa))
+ && gpt_entry_is_used(e)
+ && disk_f < gpt_partition_start(e)) {
+
+ do {
+ uint64_t x;
+ DBG(GPT, ul_debug("testing first sector %"PRIu64"", disk_f));
+ disk_f = find_first_available(gpt, disk_f);
+ if (!disk_f)
+ break;
+ x = find_last_free(gpt, disk_f);
+ if (x - disk_f >= cxt->grain / cxt->sector_size)
+ break;
+ DBG(GPT, ul_debug("first sector %"PRIu64" addresses to small space, continue...", disk_f));
+ disk_f = x + 1ULL;
+ } while(1);
+
+ if (disk_f == 0)
+ disk_f = find_first_available(gpt, le64_to_cpu(pheader->first_usable_lba));
+ }
+
+ e = NULL;
+ disk_l = find_last_free_sector(gpt);
+
+ /* the default is the largest free space */
+ dflt_f = find_first_in_largest(gpt);
+ dflt_l = find_last_free(gpt, dflt_f);
+
+ /* don't offer too small free space by default, this is possible to
+ * bypass by sfdisk script */
+ if ((!pa || !fdisk_partition_has_start(pa))
+ && dflt_l - dflt_f + 1 < cxt->grain / cxt->sector_size) {
+ fdisk_warnx(cxt, _("No enough free sectors available."));
+ return -ENOSPC;
+ }
+
+ /* align the default in range <dflt_f,dflt_l>*/
+ dflt_f = fdisk_align_lba_in_range(cxt, dflt_f, dflt_f, dflt_l);
+
+ /* first sector */
+ if (pa && pa->start_follow_default) {
+ user_f = dflt_f;
+
+ } else if (pa && fdisk_partition_has_start(pa)) {
+ DBG(GPT, ul_debug("first sector defined: %ju", (uintmax_t)pa->start));
+ if (pa->start != find_first_available(gpt, pa->start)) {
+ fdisk_warnx(cxt, _("Sector %ju already used."), (uintmax_t)pa->start);
+ return -ERANGE;
+ }
+ user_f = pa->start;
+ } else {
+ /* ask by dialog */
+ for (;;) {
+ if (!ask)
+ ask = fdisk_new_ask();
+ else
+ fdisk_reset_ask(ask);
+ if (!ask)
+ return -ENOMEM;
+
+ /* First sector */
+ fdisk_ask_set_query(ask, _("First sector"));
+ fdisk_ask_set_type(ask, FDISK_ASKTYPE_NUMBER);
+ fdisk_ask_number_set_low(ask, disk_f); /* minimal */
+ fdisk_ask_number_set_default(ask, dflt_f); /* default */
+ fdisk_ask_number_set_high(ask, disk_l); /* maximal */
+
+ rc = fdisk_do_ask(cxt, ask);
+ if (rc)
+ goto done;
+
+ user_f = fdisk_ask_number_get_result(ask);
+ if (user_f != find_first_available(gpt, user_f)) {
+ fdisk_warnx(cxt, _("Sector %ju already used."), user_f);
+ continue;
+ }
+ break;
+ }
+ }
+
+
+ /* Last sector */
+ dflt_l = max_l = find_last_free(gpt, user_f);
+
+ /* Make sure the last partition has aligned size by default because
+ * range specified by LastUsableLBA may be unaligned on disks where
+ * logical sector != physical (512/4K) because backup header size is
+ * calculated from logical sectors. */
+ if (max_l == le64_to_cpu(gpt->pheader->last_usable_lba))
+ dflt_l = fdisk_align_lba_in_range(cxt, max_l, user_f, max_l) - 1;
+
+ if (pa && pa->end_follow_default) {
+ user_l = dflt_l;
+
+ } else if (pa && fdisk_partition_has_size(pa)) {
+ user_l = user_f + pa->size - 1;
+ DBG(GPT, ul_debug("size defined: %ju, end: %"PRIu64
+ "(last possible: %"PRIu64", optimal: %"PRIu64")",
+ (uintmax_t)pa->size, user_l, max_l, dflt_l));
+
+ if (user_l != dflt_l
+ && !pa->size_explicit
+ && alignment_required(cxt)
+ && user_l - user_f > (cxt->grain / fdisk_get_sector_size(cxt))) {
+
+ user_l = fdisk_align_lba_in_range(cxt, user_l, user_f, dflt_l);
+ if (user_l > user_f)
+ user_l -= 1ULL;
+ }
+ } else {
+ for (;;) {
+ if (!ask)
+ ask = fdisk_new_ask();
+ else
+ fdisk_reset_ask(ask);
+ if (!ask)
+ return -ENOMEM;
+
+ fdisk_ask_set_query(ask, _("Last sector, +/-sectors or +/-size{K,M,G,T,P}"));
+ fdisk_ask_set_type(ask, FDISK_ASKTYPE_OFFSET);
+ fdisk_ask_number_set_low(ask, user_f); /* minimal */
+ fdisk_ask_number_set_default(ask, dflt_l); /* default */
+ fdisk_ask_number_set_high(ask, max_l); /* maximal */
+ fdisk_ask_number_set_base(ask, user_f); /* base for relative input */
+ fdisk_ask_number_set_unit(ask, cxt->sector_size);
+ fdisk_ask_number_set_wrap_negative(ask, 1); /* wrap negative around high */
+
+ rc = fdisk_do_ask(cxt, ask);
+ if (rc)
+ goto done;
+
+ user_l = fdisk_ask_number_get_result(ask);
+ if (fdisk_ask_number_is_relative(ask)) {
+ user_l = fdisk_align_lba_in_range(cxt, user_l, user_f, dflt_l);
+ if (user_l > user_f)
+ user_l -= 1ULL;
+ }
+
+ if (user_l >= user_f && user_l <= disk_l)
+ break;
+
+ fdisk_warnx(cxt, _("Value out of range."));
+ }
+ }
+
+
+ if (user_f > user_l || partnum >= cxt->label->nparts_max) {
+ fdisk_warnx(cxt, _("Could not create partition %zu"), partnum + 1);
+ rc = -EINVAL;
+ goto done;
+ }
+
+ /* Be paranoid and check against on-disk setting rather than against libfdisk cxt */
+ if (user_l > le64_to_cpu(pheader->last_usable_lba)) {
+ fdisk_warnx(cxt, _("The last usable GPT sector is %ju, but %ju is requested."),
+ le64_to_cpu(pheader->last_usable_lba), user_l);
+ rc = -EINVAL;
+ goto done;
+ }
+
+ if (user_f < le64_to_cpu(pheader->first_usable_lba)) {
+ fdisk_warnx(cxt, _("The first usable GPT sector is %ju, but %ju is requested."),
+ le64_to_cpu(pheader->first_usable_lba), user_f);
+ rc = -EINVAL;
+ goto done;
+ }
+
+ assert(!FDISK_IS_UNDEF(user_l));
+ assert(!FDISK_IS_UNDEF(user_f));
+ assert(partnum < gpt_get_nentries(gpt));
+
+ e = gpt_get_entry(gpt, partnum);
+ e->lba_end = cpu_to_le64(user_l);
+ e->lba_start = cpu_to_le64(user_f);
+
+ gpt_entry_set_type(e, &typeid);
+
+ if (pa && pa->uuid) {
+ /* Sometimes it's necessary to create a copy of the PT and
+ * reuse already defined UUID
+ */
+ rc = gpt_entry_set_uuid(e, pa->uuid);
+ if (rc)
+ goto done;
+ } else {
+ /* Any time a new partition entry is created a new GUID must be
+ * generated for that partition, and every partition is guaranteed
+ * to have a unique GUID.
+ */
+ struct gpt_guid guid;
+
+ uuid_generate_random((unsigned char *) &guid);
+ swap_efi_guid(&guid);
+ e->partition_guid = guid;
+ }
+
+ if (pa && pa->name && *pa->name)
+ gpt_entry_set_name(e, pa->name);
+ if (pa && pa->attrs)
+ gpt_entry_attrs_from_string(cxt, e, pa->attrs);
+
+ DBG(GPT, ul_debug("new partition: partno=%zu, start=%"PRIu64", end=%"PRIu64", size=%"PRIu64"",
+ partnum,
+ gpt_partition_start(e),
+ gpt_partition_end(e),
+ gpt_partition_size(e)));
+
+ gpt_recompute_crc(gpt->pheader, gpt->ents);
+ gpt_recompute_crc(gpt->bheader, gpt->ents);
+
+ /* report result */
+ {
+ struct fdisk_parttype *t;
+
+ cxt->label->nparts_cur++;
+ fdisk_label_set_changed(cxt->label, 1);
+
+ t = gpt_partition_parttype(cxt, e);
+ fdisk_info_new_partition(cxt, partnum + 1, user_f, user_l, t);
+ fdisk_unref_parttype(t);
+ }
+
+ rc = 0;
+ if (partno)
+ *partno = partnum;
+done:
+ fdisk_unref_ask(ask);
+ return rc;
+}
+
+/*
+ * Create a new GPT disklabel - destroys any previous data.
+ */
+static int gpt_create_disklabel(struct fdisk_context *cxt)
+{
+ int rc = 0;
+ size_t esz = 0;
+ char str[UUID_STR_LEN];
+ struct fdisk_gpt_label *gpt;
+ struct gpt_guid guid;
+
+ assert(cxt);
+ assert(cxt->label);
+ assert(fdisk_is_label(cxt, GPT));
+
+ gpt = self_label(cxt);
+
+ /* label private stuff has to be empty, see gpt_deinit() */
+ assert(gpt->pheader == NULL);
+ assert(gpt->bheader == NULL);
+
+ /*
+ * When no header, entries or pmbr is set, we're probably
+ * dealing with a new, empty disk - so always allocate memory
+ * to deal with the data structures whatever the case is.
+ */
+ rc = gpt_mknew_pmbr(cxt);
+ if (rc < 0)
+ goto done;
+
+ assert(cxt->sector_size >= sizeof(struct gpt_header));
+
+ /* primary */
+ gpt->pheader = calloc(1, cxt->sector_size);
+ if (!gpt->pheader) {
+ rc = -ENOMEM;
+ goto done;
+ }
+ rc = gpt_mknew_header(cxt, gpt->pheader, GPT_PRIMARY_PARTITION_TABLE_LBA);
+ if (rc < 0)
+ goto done;
+
+ /* backup ("copy" primary) */
+ gpt->bheader = calloc(1, cxt->sector_size);
+ if (!gpt->bheader) {
+ rc = -ENOMEM;
+ goto done;
+ }
+ rc = gpt_mknew_header_from_bkp(cxt, gpt->bheader,
+ last_lba(cxt), gpt->pheader);
+ if (rc < 0)
+ goto done;
+
+ rc = gpt_sizeof_entries(gpt->pheader, &esz);
+ if (rc)
+ goto done;
+ gpt->ents = calloc(1, esz);
+ if (!gpt->ents) {
+ rc = -ENOMEM;
+ goto done;
+ }
+ gpt_recompute_crc(gpt->pheader, gpt->ents);
+ gpt_recompute_crc(gpt->bheader, gpt->ents);
+
+ cxt->label->nparts_max = gpt_get_nentries(gpt);
+ cxt->label->nparts_cur = 0;
+
+ guid = gpt->pheader->disk_guid;
+ guid_to_string(&guid, str);
+ fdisk_label_set_changed(cxt->label, 1);
+ fdisk_info(cxt, _("Created a new GPT disklabel (GUID: %s)."), str);
+
+ if (gpt_get_nentries(gpt) < GPT_NPARTITIONS)
+ fdisk_info(cxt, _("The maximal number of partitions is %zu (default is %zu)."),
+ gpt_get_nentries(gpt), GPT_NPARTITIONS);
+done:
+ return rc;
+}
+
+static int gpt_set_disklabel_id(struct fdisk_context *cxt, const char *str)
+{
+ struct fdisk_gpt_label *gpt;
+ struct gpt_guid uuid;
+ char *old, *new;
+ int rc;
+
+ assert(cxt);
+ assert(cxt->label);
+ assert(fdisk_is_label(cxt, GPT));
+
+ gpt = self_label(cxt);
+ if (!str) {
+ char *buf = NULL;
+
+ if (fdisk_ask_string(cxt,
+ _("Enter new disk UUID (in 8-4-4-4-12 format)"), &buf))
+ return -EINVAL;
+ rc = string_to_guid(buf, &uuid);
+ free(buf);
+ } else
+ rc = string_to_guid(str, &uuid);
+
+ if (rc) {
+ fdisk_warnx(cxt, _("Failed to parse your UUID."));
+ return rc;
+ }
+
+ old = gpt_get_header_id(gpt->pheader);
+
+ gpt->pheader->disk_guid = uuid;
+ gpt->bheader->disk_guid = uuid;
+
+ gpt_recompute_crc(gpt->pheader, gpt->ents);
+ gpt_recompute_crc(gpt->bheader, gpt->ents);
+
+ new = gpt_get_header_id(gpt->pheader);
+
+ fdisk_info(cxt, _("Disk identifier changed from %s to %s."), old, new);
+
+ free(old);
+ free(new);
+ fdisk_label_set_changed(cxt->label, 1);
+ return 0;
+}
+
+static int gpt_check_table_overlap(struct fdisk_context *cxt,
+ uint64_t first_usable,
+ uint64_t last_usable)
+{
+ struct fdisk_gpt_label *gpt = self_label(cxt);
+ size_t i;
+ int rc = 0;
+
+ /* First check if there's enough room for the table. last_lba may have wrapped */
+ if (first_usable > cxt->total_sectors || /* far too little space */
+ last_usable > cxt->total_sectors || /* wrapped */
+ first_usable > last_usable) { /* too little space */
+ fdisk_warnx(cxt, _("Not enough space for new partition table!"));
+ return -ENOSPC;
+ }
+
+ /* check that all partitions fit in the remaining space */
+ for (i = 0; i < gpt_get_nentries(gpt); i++) {
+ struct gpt_entry *e = gpt_get_entry(gpt, i);
+
+ if (!gpt_entry_is_used(e))
+ continue;
+ if (gpt_partition_start(e) < first_usable) {
+ fdisk_warnx(cxt, _("Partition #%zu out of range (minimal start is %"PRIu64" sectors)"),
+ i + 1, first_usable);
+ rc = -EINVAL;
+ }
+ if (gpt_partition_end(e) > last_usable) {
+ fdisk_warnx(cxt, _("Partition #%zu out of range (maximal end is %"PRIu64" sectors)"),
+ i + 1, last_usable - (uint64_t) 1);
+ rc = -EINVAL;
+ }
+ }
+ return rc;
+}
+
+/**
+ * fdisk_gpt_set_npartitions:
+ * @cxt: context
+ * @nents: number of wanted entries
+ *
+ * Elarge GPT entries array if possible. The function check if an existing
+ * partition does not overlap the entries array area. If yes, then it report
+ * warning and returns -EINVAL.
+ *
+ * Returns: 0 on success, < 0 on error.
+ * Since: 2.29
+ */
+int fdisk_gpt_set_npartitions(struct fdisk_context *cxt, uint32_t nents)
+{
+ struct fdisk_gpt_label *gpt;
+ size_t new_size = 0;
+ uint32_t old_nents;
+ uint64_t first_usable = 0ULL, last_usable = 0ULL;
+ int rc;
+
+ assert(cxt);
+ assert(cxt->label);
+
+ if (!fdisk_is_label(cxt, GPT))
+ return -EINVAL;
+
+ gpt = self_label(cxt);
+
+ old_nents = le32_to_cpu(gpt->pheader->npartition_entries);
+ if (old_nents == nents)
+ return 0; /* do nothing, say nothing */
+
+ /* calculate the size (bytes) of the entries array */
+ rc = gpt_calculate_sizeof_entries(gpt->pheader, nents, &new_size);
+ if (rc) {
+ uint32_t entry_size = le32_to_cpu(gpt->pheader->sizeof_partition_entry);
+
+ if (entry_size == 0)
+ fdisk_warnx(cxt, _("The partition entry size is zero."));
+ else
+ fdisk_warnx(cxt, _("The number of the partition has to be smaller than %zu."),
+ (size_t) UINT32_MAX / entry_size);
+ return rc;
+ }
+
+ rc = gpt_calculate_first_lba(gpt->pheader, nents, &first_usable, cxt);
+ if (rc == 0)
+ rc = gpt_calculate_last_lba(gpt->pheader, nents, &last_usable, cxt);
+ if (rc)
+ return rc;
+
+ /* if expanding the table, first check that everything fits,
+ * then allocate more memory and zero. */
+ if (nents > old_nents) {
+ unsigned char *ents;
+ size_t old_size = 0;
+
+ rc = gpt_calculate_sizeof_entries(gpt->pheader, old_nents, &old_size);
+ if (rc == 0)
+ rc = gpt_check_table_overlap(cxt, first_usable, last_usable);
+ if (rc)
+ return rc;
+ ents = realloc(gpt->ents, new_size);
+ if (!ents) {
+ fdisk_warnx(cxt, _("Cannot allocate memory!"));
+ return -ENOMEM;
+ }
+ memset(ents + old_size, 0, new_size - old_size);
+ gpt->ents = ents;
+ }
+
+ /* everything's ok, apply the new size */
+ gpt->pheader->npartition_entries = cpu_to_le32(nents);
+ gpt->bheader->npartition_entries = cpu_to_le32(nents);
+
+ /* usable LBA addresses will have changed */
+ fdisk_set_first_lba(cxt, first_usable);
+ fdisk_set_last_lba(cxt, last_usable);
+ gpt->pheader->first_usable_lba = cpu_to_le64(first_usable);
+ gpt->bheader->first_usable_lba = cpu_to_le64(first_usable);
+ gpt->pheader->last_usable_lba = cpu_to_le64(last_usable);
+ gpt->bheader->last_usable_lba = cpu_to_le64(last_usable);
+
+ /* The backup header must be recalculated */
+ gpt_mknew_header_common(cxt, gpt->bheader, le64_to_cpu(gpt->pheader->alternative_lba));
+
+ /* CRCs will have changed */
+ gpt_recompute_crc(gpt->pheader, gpt->ents);
+ gpt_recompute_crc(gpt->bheader, gpt->ents);
+
+ /* update library info */
+ cxt->label->nparts_max = gpt_get_nentries(gpt);
+
+ fdisk_info(cxt, _("Partition table length changed from %"PRIu32" to %"PRIu32"."),
+ old_nents, nents);
+
+ fdisk_label_set_changed(cxt->label, 1);
+ return 0;
+}
+
+static int gpt_part_is_used(struct fdisk_context *cxt, size_t i)
+{
+ struct fdisk_gpt_label *gpt;
+ struct gpt_entry *e;
+
+ assert(cxt);
+ assert(cxt->label);
+ assert(fdisk_is_label(cxt, GPT));
+
+ gpt = self_label(cxt);
+
+ if (i >= gpt_get_nentries(gpt))
+ return 0;
+
+ e = gpt_get_entry(gpt, i);
+
+ return gpt_entry_is_used(e) || gpt_partition_start(e);
+}
+
+/**
+ * fdisk_gpt_is_hybrid:
+ * @cxt: context
+ *
+ * The regular GPT contains PMBR (dummy protective MBR) where the protective
+ * MBR does not address any partitions.
+ *
+ * Hybrid GPT contains regular MBR where this partition table addresses the
+ * same partitions as GPT. It's recommended to not use hybrid GPT due to MBR
+ * limits.
+ *
+ * The libfdisk does not provide functionality to sync GPT and MBR, you have to
+ * directly access and modify (P)MBR (see fdisk_new_nested_context()).
+ *
+ * Returns: 1 if partition table detected as hybrid otherwise return 0
+ */
+int fdisk_gpt_is_hybrid(struct fdisk_context *cxt)
+{
+ assert(cxt);
+ return valid_pmbr(cxt) == GPT_MBR_HYBRID;
+}
+
+/**
+ * fdisk_gpt_get_partition_attrs:
+ * @cxt: context
+ * @partnum: partition number
+ * @attrs: GPT partition attributes
+ *
+ * Sets @attrs for the given partition
+ *
+ * Returns: 0 on success, <0 on error.
+ */
+int fdisk_gpt_get_partition_attrs(
+ struct fdisk_context *cxt,
+ size_t partnum,
+ uint64_t *attrs)
+{
+ struct fdisk_gpt_label *gpt;
+
+ assert(cxt);
+ assert(cxt->label);
+
+ if (!fdisk_is_label(cxt, GPT))
+ return -EINVAL;
+
+ gpt = self_label(cxt);
+
+ if (partnum >= gpt_get_nentries(gpt))
+ return -EINVAL;
+
+ *attrs = le64_to_cpu(gpt_get_entry(gpt, partnum)->attrs);
+ return 0;
+}
+
+/**
+ * fdisk_gpt_set_partition_attrs:
+ * @cxt: context
+ * @partnum: partition number
+ * @attrs: GPT partition attributes
+ *
+ * Sets the GPT partition attributes field to @attrs.
+ *
+ * Returns: 0 on success, <0 on error.
+ */
+int fdisk_gpt_set_partition_attrs(
+ struct fdisk_context *cxt,
+ size_t partnum,
+ uint64_t attrs)
+{
+ struct fdisk_gpt_label *gpt;
+
+ assert(cxt);
+ assert(cxt->label);
+
+ if (!fdisk_is_label(cxt, GPT))
+ return -EINVAL;
+
+ DBG(GPT, ul_debug("entry attributes change requested partno=%zu", partnum));
+ gpt = self_label(cxt);
+
+ if (partnum >= gpt_get_nentries(gpt))
+ return -EINVAL;
+
+ gpt_get_entry(gpt, partnum)->attrs = cpu_to_le64(attrs);
+ fdisk_info(cxt, _("The attributes on partition %zu changed to 0x%016" PRIx64 "."),
+ partnum + 1, attrs);
+
+ gpt_recompute_crc(gpt->pheader, gpt->ents);
+ gpt_recompute_crc(gpt->bheader, gpt->ents);
+ fdisk_label_set_changed(cxt->label, 1);
+ return 0;
+}
+
+static int gpt_toggle_partition_flag(
+ struct fdisk_context *cxt,
+ size_t i,
+ unsigned long flag)
+{
+ struct fdisk_gpt_label *gpt;
+ struct gpt_entry *e;
+ uint64_t attrs;
+ uintmax_t tmp;
+ char *bits;
+ const char *name = NULL;
+ int bit = -1, rc;
+
+ assert(cxt);
+ assert(cxt->label);
+ assert(fdisk_is_label(cxt, GPT));
+
+ DBG(GPT, ul_debug("entry attribute change requested partno=%zu", i));
+ gpt = self_label(cxt);
+
+ if (i >= gpt_get_nentries(gpt))
+ return -EINVAL;
+
+ e = gpt_get_entry(gpt, i);
+ attrs = e->attrs;
+ bits = (char *) &attrs;
+
+ switch (flag) {
+ case GPT_FLAG_REQUIRED:
+ bit = GPT_ATTRBIT_REQ;
+ name = GPT_ATTRSTR_REQ;
+ break;
+ case GPT_FLAG_NOBLOCK:
+ bit = GPT_ATTRBIT_NOBLOCK;
+ name = GPT_ATTRSTR_NOBLOCK;
+ break;
+ case GPT_FLAG_LEGACYBOOT:
+ bit = GPT_ATTRBIT_LEGACY;
+ name = GPT_ATTRSTR_LEGACY;
+ break;
+ case GPT_FLAG_GUIDSPECIFIC:
+ rc = fdisk_ask_number(cxt, 48, 48, 63, _("Enter GUID specific bit"), &tmp);
+ if (rc)
+ return rc;
+ bit = tmp;
+ break;
+ default:
+ /* already specified PT_FLAG_GUIDSPECIFIC bit */
+ if (flag >= 48 && flag <= 63) {
+ bit = flag;
+ flag = GPT_FLAG_GUIDSPECIFIC;
+ }
+ break;
+ }
+
+ if (bit < 0) {
+ fdisk_warnx(cxt, _("failed to toggle unsupported bit %lu"), flag);
+ return -EINVAL;
+ }
+
+ if (!isset(bits, bit))
+ setbit(bits, bit);
+ else
+ clrbit(bits, bit);
+
+ e->attrs = attrs;
+
+ if (flag == GPT_FLAG_GUIDSPECIFIC)
+ fdisk_info(cxt, isset(bits, bit) ?
+ _("The GUID specific bit %d on partition %zu is enabled now.") :
+ _("The GUID specific bit %d on partition %zu is disabled now."),
+ bit, i + 1);
+ else
+ fdisk_info(cxt, isset(bits, bit) ?
+ _("The %s flag on partition %zu is enabled now.") :
+ _("The %s flag on partition %zu is disabled now."),
+ name, i + 1);
+
+ gpt_recompute_crc(gpt->pheader, gpt->ents);
+ gpt_recompute_crc(gpt->bheader, gpt->ents);
+ fdisk_label_set_changed(cxt->label, 1);
+ return 0;
+}
+
+static int gpt_entry_cmp_start(const void *a, const void *b)
+{
+ const struct gpt_entry *ae = (const struct gpt_entry *) a,
+ *be = (const struct gpt_entry *) b;
+ int au = gpt_entry_is_used(ae),
+ bu = gpt_entry_is_used(be);
+
+ if (!au && !bu)
+ return 0;
+ if (!au)
+ return 1;
+ if (!bu)
+ return -1;
+
+ return cmp_numbers(gpt_partition_start(ae), gpt_partition_start(be));
+}
+
+/* sort partition by start sector */
+static int gpt_reorder(struct fdisk_context *cxt)
+{
+ struct fdisk_gpt_label *gpt;
+ size_t i, nparts, mess;
+
+ assert(cxt);
+ assert(cxt->label);
+ assert(fdisk_is_label(cxt, GPT));
+
+ gpt = self_label(cxt);
+ nparts = gpt_get_nentries(gpt);
+
+ for (i = 0, mess = 0; mess == 0 && i + 1 < nparts; i++)
+ mess = gpt_entry_cmp_start(
+ (const void *) gpt_get_entry(gpt, i),
+ (const void *) gpt_get_entry(gpt, i + 1)) > 0;
+
+ if (!mess)
+ return 1;
+
+ qsort(gpt->ents, nparts, sizeof(struct gpt_entry),
+ gpt_entry_cmp_start);
+
+ gpt_recompute_crc(gpt->pheader, gpt->ents);
+ gpt_recompute_crc(gpt->bheader, gpt->ents);
+ fdisk_label_set_changed(cxt->label, 1);
+
+ return 0;
+}
+
+static int gpt_reset_alignment(struct fdisk_context *cxt)
+{
+ struct fdisk_gpt_label *gpt;
+ struct gpt_header *h;
+
+ assert(cxt);
+ assert(cxt->label);
+ assert(fdisk_is_label(cxt, GPT));
+
+ gpt = self_label(cxt);
+ h = gpt ? gpt->pheader : NULL;
+
+ if (h) {
+ /* always follow existing table */
+ cxt->first_lba = le64_to_cpu(h->first_usable_lba);
+ cxt->last_lba = le64_to_cpu(h->last_usable_lba);
+ } else {
+ /* estimate ranges for GPT */
+ uint64_t first, last;
+
+ count_first_last_lba(cxt, &first, &last, NULL);
+ if (cxt->first_lba < first)
+ cxt->first_lba = first;
+ if (cxt->last_lba > last)
+ cxt->last_lba = last;
+ }
+
+ return 0;
+}
+/*
+ * Deinitialize fdisk-specific variables
+ */
+static void gpt_deinit(struct fdisk_label *lb)
+{
+ struct fdisk_gpt_label *gpt = (struct fdisk_gpt_label *) lb;
+
+ if (!gpt)
+ return;
+
+ free(gpt->ents);
+ free(gpt->pheader);
+ free(gpt->bheader);
+
+ gpt->ents = NULL;
+ gpt->pheader = NULL;
+ gpt->bheader = NULL;
+}
+
+static const struct fdisk_label_operations gpt_operations =
+{
+ .probe = gpt_probe_label,
+ .write = gpt_write_disklabel,
+ .verify = gpt_verify_disklabel,
+ .create = gpt_create_disklabel,
+ .locate = gpt_locate_disklabel,
+ .get_item = gpt_get_disklabel_item,
+ .set_id = gpt_set_disklabel_id,
+
+ .get_part = gpt_get_partition,
+ .set_part = gpt_set_partition,
+ .add_part = gpt_add_partition,
+ .del_part = gpt_delete_partition,
+ .reorder = gpt_reorder,
+
+ .part_is_used = gpt_part_is_used,
+ .part_toggle_flag = gpt_toggle_partition_flag,
+
+ .deinit = gpt_deinit,
+
+ .reset_alignment = gpt_reset_alignment
+};
+
+static const struct fdisk_field gpt_fields[] =
+{
+ /* basic */
+ { FDISK_FIELD_DEVICE, N_("Device"), 10, 0 },
+ { FDISK_FIELD_START, N_("Start"), 5, FDISK_FIELDFL_NUMBER },
+ { FDISK_FIELD_END, N_("End"), 5, FDISK_FIELDFL_NUMBER },
+ { FDISK_FIELD_SECTORS, N_("Sectors"), 5, FDISK_FIELDFL_NUMBER },
+ { FDISK_FIELD_SIZE, N_("Size"), 5, FDISK_FIELDFL_NUMBER | FDISK_FIELDFL_EYECANDY },
+ { FDISK_FIELD_TYPE, N_("Type"), 0.1, FDISK_FIELDFL_EYECANDY },
+ /* expert */
+ { FDISK_FIELD_TYPEID, N_("Type-UUID"), 36, FDISK_FIELDFL_DETAIL },
+ { FDISK_FIELD_UUID, N_("UUID"), 36, FDISK_FIELDFL_DETAIL },
+ { FDISK_FIELD_NAME, N_("Name"), 0.2, FDISK_FIELDFL_DETAIL },
+ { FDISK_FIELD_ATTR, N_("Attrs"), 0, FDISK_FIELDFL_DETAIL }
+};
+
+/*
+ * allocates GPT in-memory stuff
+ */
+struct fdisk_label *fdisk_new_gpt_label(struct fdisk_context *cxt __attribute__ ((__unused__)))
+{
+ struct fdisk_label *lb;
+ struct fdisk_gpt_label *gpt;
+
+ gpt = calloc(1, sizeof(*gpt));
+ if (!gpt)
+ return NULL;
+
+ /* initialize generic part of the driver */
+ lb = (struct fdisk_label *) gpt;
+ lb->name = "gpt";
+ lb->id = FDISK_DISKLABEL_GPT;
+ lb->op = &gpt_operations;
+
+ lb->parttypes = gpt_parttypes;
+ lb->nparttypes = ARRAY_SIZE(gpt_parttypes);
+ lb->parttype_cuts = gpt_parttype_cuts;
+ lb->nparttype_cuts = ARRAY_SIZE(gpt_parttype_cuts);
+
+ lb->fields = gpt_fields;
+ lb->nfields = ARRAY_SIZE(gpt_fields);
+
+ /* return calloc() result to keep static anaylizers happy */
+ return (struct fdisk_label *) gpt;
+}
+
+/**
+ * fdisk_gpt_disable_relocation
+ * @lb: label
+ * @disable: 0 or 1
+ *
+ * Disable automatic backup header relocation to the end of the device. The
+ * header position is recalculated during libfdisk probing stage by
+ * fdisk_assign_device() and later written by fdisk_write_disklabel(), so you
+ * need to call it before fdisk_assign_device().
+ *
+ * Since: 2.36
+ */
+void fdisk_gpt_disable_relocation(struct fdisk_label *lb, int disable)
+{
+ struct fdisk_gpt_label *gpt = (struct fdisk_gpt_label *) lb;
+
+ assert(gpt);
+ gpt->no_relocate = disable ? 1 : 0;
+}
+
+/**
+ * fdisk_gpt_enable_minimize
+ * @lb: label
+ * @enable: 0 or 1
+ *
+ * Force libfdisk to write backup header to behind last partition. The
+ * header position is recalculated on fdisk_write_disklabel().
+ *
+ * Since: 2.36
+ */
+void fdisk_gpt_enable_minimize(struct fdisk_label *lb, int enable)
+{
+ struct fdisk_gpt_label *gpt = (struct fdisk_gpt_label *) lb;
+
+ assert(gpt);
+ gpt->minimize = enable ? 1 : 0;
+}
+
+#ifdef TEST_PROGRAM
+static int test_getattr(struct fdisk_test *ts, int argc, char *argv[])
+{
+ const char *disk = argv[1];
+ size_t part = strtoul(argv[2], NULL, 0) - 1;
+ struct fdisk_context *cxt;
+ uint64_t atters = 0;
+
+ cxt = fdisk_new_context();
+ fdisk_assign_device(cxt, disk, 1);
+
+ if (!fdisk_is_label(cxt, GPT))
+ return EXIT_FAILURE;
+
+ if (fdisk_gpt_get_partition_attrs(cxt, part, &atters))
+ return EXIT_FAILURE;
+
+ printf("%s: 0x%016" PRIx64 "\n", argv[2], atters);
+
+ fdisk_unref_context(cxt);
+ return 0;
+}
+
+static int test_setattr(struct fdisk_test *ts, int argc, char *argv[])
+{
+ const char *disk = argv[1];
+ size_t part = strtoul(argv[2], NULL, 0) - 1;
+ uint64_t atters = strtoull(argv[3], NULL, 0);
+ struct fdisk_context *cxt;
+
+ cxt = fdisk_new_context();
+ fdisk_assign_device(cxt, disk, 0);
+
+ if (!fdisk_is_label(cxt, GPT))
+ return EXIT_FAILURE;
+
+ if (fdisk_gpt_set_partition_attrs(cxt, part, atters))
+ return EXIT_FAILURE;
+
+ if (fdisk_write_disklabel(cxt))
+ return EXIT_FAILURE;
+
+ fdisk_unref_context(cxt);
+ return 0;
+}
+
+int main(int argc, char *argv[])
+{
+ struct fdisk_test tss[] = {
+ { "--getattr", test_getattr, "<disk> <partition> print attributes" },
+ { "--setattr", test_setattr, "<disk> <partition> <value> set attributes" },
+ { NULL }
+ };
+
+ return fdisk_run_test(tss, argc, argv);
+}
+
+#endif
diff --git a/libfdisk/src/init.c b/libfdisk/src/init.c
new file mode 100644
index 0000000..501a7d6
--- /dev/null
+++ b/libfdisk/src/init.c
@@ -0,0 +1,62 @@
+
+#include "fdiskP.h"
+
+
+/**
+ * SECTION: init
+ * @title: Library initialization
+ * @short_description: initialize debug stuff
+ *
+ */
+
+UL_DEBUG_DEFINE_MASK(libfdisk);
+UL_DEBUG_DEFINE_MASKNAMES(libfdisk) =
+{
+ { "all", LIBFDISK_DEBUG_ALL, "info about all subsystems" },
+ { "ask", LIBFDISK_DEBUG_ASK, "fdisk dialogs" },
+ { "help", LIBFDISK_DEBUG_HELP, "this help" },
+ { "cxt", LIBFDISK_DEBUG_CXT, "library context (handler)" },
+ { "label", LIBFDISK_DEBUG_LABEL, "disk label utils" },
+ { "part", LIBFDISK_DEBUG_PART, "partition utils" },
+ { "parttype", LIBFDISK_DEBUG_PARTTYPE,"partition type utils" },
+ { "script", LIBFDISK_DEBUG_SCRIPT, "sfdisk-like scripts" },
+ { "tab", LIBFDISK_DEBUG_TAB, "table utils"},
+ { "wipe", LIBFDISK_DEBUG_WIPE, "wipe area utils" },
+ { "item", LIBFDISK_DEBUG_ITEM, "disklabel items" },
+ { "gpt", LIBFDISK_DEBUG_GPT, "GPT subsystems" },
+ { NULL, 0 }
+};
+
+/**
+ * fdisk_init_debug:
+ * @mask: debug mask (0xffff to enable full debugging)
+ *
+ * If the @mask is not specified then this function reads
+ * LIBFDISK_DEBUG environment variable to get the mask.
+ *
+ * Already initialized debugging stuff cannot be changed. It does not
+ * have effect to call this function twice.
+ *
+ * It's strongly recommended to use fdisk_init_debug(0) in your code.
+ */
+void fdisk_init_debug(int mask)
+{
+ if (libfdisk_debug_mask)
+ return;
+
+ __UL_INIT_DEBUG_FROM_ENV(libfdisk, LIBFDISK_DEBUG_, mask, LIBFDISK_DEBUG);
+
+
+ if (libfdisk_debug_mask != LIBFDISK_DEBUG_INIT
+ && libfdisk_debug_mask != (LIBFDISK_DEBUG_HELP|LIBFDISK_DEBUG_INIT)) {
+ const char *ver = NULL;
+
+ fdisk_get_library_version(&ver);
+
+ DBG(INIT, ul_debug("library debug mask: 0x%04x", libfdisk_debug_mask));
+ DBG(INIT, ul_debug("library version: %s", ver));
+ }
+
+ ON_DBG(HELP, ul_debug_print_masks("LIBFDISK_DEBUG",
+ UL_DEBUG_MASKNAMES(libfdisk)));
+}
diff --git a/libfdisk/src/item.c b/libfdisk/src/item.c
new file mode 100644
index 0000000..671f9ad
--- /dev/null
+++ b/libfdisk/src/item.c
@@ -0,0 +1,255 @@
+
+#include <inttypes.h>
+
+#include "fdiskP.h"
+
+/**
+ * SECTION: item
+ * @title: Labelitem
+ * @short_description: disk label items
+ *
+ * The labelitem is label specific items stored in the partition table header.
+ * The information provided by labelitems are not specific to the partitions.
+ *
+ * For example
+ *
+ * <informalexample>
+ * <programlisting>
+ * struct fdisk_labelitem *item = fdisk_new_labelitem();
+ *
+ * fdisk_get_disklabel_item(cxt, GPT_LABELITEM_ALTLBA, item);
+ * print("Backup header LBA: %ju\n", fdisk_labelitem_get_data_u64(item));
+ *
+ * fdisk_unref_labelitem(item);
+ * </programlisting>
+ * </informalexample>
+ *
+ * returns LBA of the alternative GPT header.
+ *
+ * See also fdisk_get_disklabel_item(). The IDs are generic (e.g.
+ * FDISK_LABEL_ITEM_*) and label specific ((e.g. GPT_LABELITEM_*).
+ */
+
+/**
+ * fdisk_new_labelitem
+ *
+ * Returns: new instance.
+ * Since: 2.29
+ */
+struct fdisk_labelitem *fdisk_new_labelitem(void)
+{
+ struct fdisk_labelitem *li = calloc(1, sizeof(*li));
+
+ if (!li)
+ return NULL;
+
+ li->refcount = 1;
+ DBG(ITEM, ul_debugobj(li, "alloc"));
+ return li;
+}
+
+/**
+ * fdisk_ref_labelitem:
+ * @li: label item
+ *
+ * Increments reference counter.
+ * Since: 2.29
+ */
+void fdisk_ref_labelitem(struct fdisk_labelitem *li)
+{
+ if (li) {
+ /* me sure we do not use refcouting for static items */
+ assert(li->refcount > 0);
+ li->refcount++;
+ }
+}
+
+/**
+ * fdisk_reset_labelitem:
+ * @li: label item
+ *
+ * Zeroize data stored in the @li (does not modify anything in disk label).
+ *
+ * Returns: 0 on success, or <0 in case of error
+ * Since: 2.29
+ */
+int fdisk_reset_labelitem(struct fdisk_labelitem *li)
+{
+ int refcount;
+
+ if (!li)
+ return -EINVAL;
+ if (li->type == 's')
+ free(li->data.str);
+
+ refcount = li->refcount;
+ memset(li, 0, sizeof(*li));
+ li->refcount = refcount;
+ return 0;
+}
+
+/**
+ * fdisk_unref_labelitem:
+ * @li: label item
+ *
+ * Decrements reference counter, on zero the @li is automatically
+ * deallocated.
+ *
+ * Since: 2.29
+ */
+void fdisk_unref_labelitem(struct fdisk_labelitem *li)
+{
+ if (!li)
+ return;
+
+ /* me sure we do not use refcouting for static items */
+ assert(li->refcount > 0);
+
+ li->refcount--;
+ if (li->refcount <= 0) {
+ DBG(ITEM, ul_debugobj(li, "free"));
+ fdisk_reset_labelitem(li);
+ free(li);
+ }
+}
+
+/**
+ * fdisk_labelitem_get_name:
+ * @li: label item
+ *
+ * Returns: item name or NULL.
+ * Since: 2.29
+ */
+const char *fdisk_labelitem_get_name(struct fdisk_labelitem *li)
+{
+ return li ? li->name : NULL;
+}
+
+/**
+ * fdisk_labelitem_get_id:
+ * @li: label item
+ *
+ * Returns: item Id or <0 in case of error.
+ * Since: 2.29
+ */
+int fdisk_labelitem_get_id(struct fdisk_labelitem *li)
+{
+ return li ? li->id : -EINVAL;
+}
+
+
+/**
+ * fdisk_labelitem_get_data_u64:
+ * @li: label item
+ * @data: returns data
+ *
+ * Returns: 0 on success, <0 on error
+ * Since: 2.29
+ */
+int fdisk_labelitem_get_data_u64(struct fdisk_labelitem *li, uint64_t *data)
+{
+ if (!li || li->type != 'j')
+ return -EINVAL;
+
+ if (data)
+ *data = li->data.num64;
+ return 0;
+}
+
+/**
+ * fdisk_labelitem_get_data_string:
+ * @li: label item
+ * @data: returns data
+ *
+ * Returns: 0 on success, <0 on error.
+ * Since: 2.29
+ */
+int fdisk_labelitem_get_data_string(struct fdisk_labelitem *li, const char **data)
+{
+ if (!li || li->type != 's')
+ return -EINVAL;
+
+ if (data)
+ *data = li->data.str;
+ return 0;
+}
+
+/**
+ * fdisk_labelitem_is_string:
+ * @li: label item
+ *
+ * Returns: 0 or 1
+ * Since: 2.29
+ */
+int fdisk_labelitem_is_string(struct fdisk_labelitem *li)
+{
+ return li && li->type == 's';
+}
+
+/**
+ * fdisk_labelitem_is_number:
+ * @li: label item
+ *
+ * Returns: 0 or 1
+ * Since: 2.29
+ */
+int fdisk_labelitem_is_number(struct fdisk_labelitem *li)
+{
+ return li && li->type == 'j';
+}
+
+#ifdef TEST_PROGRAM
+static int test_listitems(struct fdisk_test *ts, int argc, char *argv[])
+{
+ const char *disk = argv[1];
+ struct fdisk_context *cxt;
+ struct fdisk_labelitem *item;
+ int i = 0, rc;
+
+ cxt = fdisk_new_context();
+ item = fdisk_new_labelitem();
+
+ fdisk_assign_device(cxt, disk, 1);
+
+ do {
+ rc = fdisk_get_disklabel_item(cxt, i++, item);
+ switch (rc) {
+ case 0: /* success */
+ {
+ const char *name = fdisk_labelitem_get_name(item);
+ const char *str;
+ uint64_t num;
+
+ if (fdisk_labelitem_is_string(item)
+ && fdisk_labelitem_get_data_string(item, &str) == 0)
+ printf("%s: %s\n", name, str);
+ else if (fdisk_labelitem_get_data_u64(item, &num) == 0)
+ printf("%s: %"PRIu64"\n", name, num);
+ break;
+ }
+ case 1: /* item unsupported by label -- ignore */
+ rc = 0;
+ break;
+ case 2: /* end (out of range) */
+ break;
+ default: /* error */
+ break;
+ }
+ } while (rc == 0);
+
+ fdisk_unref_labelitem(item);
+ fdisk_unref_context(cxt);
+ return rc < 0 ? rc : 0;
+}
+
+int main(int argc, char *argv[])
+{
+ struct fdisk_test tss[] = {
+ { "--list-items", test_listitems, "<disk> list items" },
+ { NULL }
+ };
+
+ return fdisk_run_test(tss, argc, argv);
+}
+
+#endif
diff --git a/libfdisk/src/iter.c b/libfdisk/src/iter.c
new file mode 100644
index 0000000..aca5f7c
--- /dev/null
+++ b/libfdisk/src/iter.c
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2009 Karel Zak <kzak@redhat.com>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ */
+
+/**
+ * SECTION: iter
+ * @title: Iterator
+ * @short_description: unified iterator
+ *
+ * The iterator keeps the direction and the last position for access to the
+ * internal library tables/lists.
+ *
+ * It's very unusual to use the same iterator on multiple places in your
+ * application or share the same iterator, for this purpose libfdisk does not
+ * provide reference counting for this object. It's recommended to initialize
+ * the iterator by fdisk_new_iter() at begin of your function and then
+ * fdisk_free_iter() before you return from the function.
+ *
+ * Don't forget to call fdisk_reset_iter() if you want to use the iterator more
+ * than once.
+ */
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include "fdiskP.h"
+
+/**
+ * fdisk_new_iter:
+ * @direction: FDISK_INTER_{FOR,BACK}WARD direction
+ *
+ * Returns: newly allocated generic libmount iterator.
+ */
+struct fdisk_iter *fdisk_new_iter(int direction)
+{
+ struct fdisk_iter *itr = calloc(1, sizeof(*itr));
+ if (!itr)
+ return NULL;
+ itr->direction = direction;
+ return itr;
+}
+
+/**
+ * fdisk_free_iter:
+ * @itr: iterator pointer
+ *
+ * Deallocates the iterator.
+ */
+void fdisk_free_iter(struct fdisk_iter *itr)
+{
+ free(itr);
+}
+
+/**
+ * fdisk_reset_iter:
+ * @itr: iterator pointer
+ * @direction: FDISK_ITER_{FOR,BACK}WARD or -1 to keep the direction unchanged
+ *
+ * Resets the iterator.
+ */
+void fdisk_reset_iter(struct fdisk_iter *itr, int direction)
+{
+ if (direction == -1)
+ direction = itr->direction;
+
+ memset(itr, 0, sizeof(*itr));
+ itr->direction = direction;
+}
+
+/**
+ * fdisk_iter_get_direction:
+ * @itr: iterator pointer
+ *
+ * Returns: FDISK_INTER_{FOR,BACK}WARD
+ */
+int fdisk_iter_get_direction(struct fdisk_iter *itr)
+{
+ return itr->direction;
+}
diff --git a/libfdisk/src/label.c b/libfdisk/src/label.c
new file mode 100644
index 0000000..dc0ad9b
--- /dev/null
+++ b/libfdisk/src/label.c
@@ -0,0 +1,752 @@
+
+#include "fdiskP.h"
+
+
+/**
+ * SECTION: label
+ * @title: Label
+ * @short_description: disk label (PT) specific data and functions
+ *
+ * The fdisk_new_context() initializes all label drivers, and allocate
+ * per-label specific data struct. This concept can be used to store label specific
+ * settings to the label driver independently on the currently active label
+ * driver. Note that label struct cannot be deallocated, so there is no
+ * reference counting for fdisk_label objects. All is destroyed by
+ * fdisk_unref_context() only.
+ *
+ * Anyway, all label drives share in-memory first sector. The function
+ * fdisk_create_disklabel() overwrites this in-memory sector. But it's possible that
+ * label driver also uses another buffers, for example GPT reads more sectors
+ * from the device.
+ *
+ * All label operations are in-memory only, except fdisk_write_disklabel().
+ *
+ * All functions that use "struct fdisk_context" rather than "struct
+ * fdisk_label" use the currently active label driver.
+ */
+
+
+int fdisk_probe_labels(struct fdisk_context *cxt)
+{
+ size_t i;
+
+ cxt->label = NULL;
+
+ for (i = 0; i < cxt->nlabels; i++) {
+ struct fdisk_label *lb = cxt->labels[i];
+ struct fdisk_label *org = fdisk_get_label(cxt, NULL);
+ int rc;
+
+ if (!lb->op->probe)
+ continue;
+ if (lb->disabled) {
+ DBG(CXT, ul_debugobj(cxt, "%s: disabled -- ignore", lb->name));
+ continue;
+ }
+ DBG(CXT, ul_debugobj(cxt, "probing for %s", lb->name));
+
+ cxt->label = lb;
+ rc = lb->op->probe(cxt);
+ cxt->label = org;
+
+ if (rc != 1) {
+ if (lb->op->deinit)
+ lb->op->deinit(lb); /* for sure */
+ continue;
+ }
+
+ __fdisk_switch_label(cxt, lb);
+ return 0;
+ }
+
+ DBG(CXT, ul_debugobj(cxt, "no label found"));
+ return 1; /* not found */
+}
+
+/**
+ * fdisk_label_get_name:
+ * @lb: label
+ *
+ * Returns: label name
+ */
+const char *fdisk_label_get_name(const struct fdisk_label *lb)
+{
+ return lb ? lb->name : NULL;
+}
+
+/**
+ * fdisk_label_is_labeltype:
+ * @lb: label
+ *
+ * Returns: FDISK_DISKLABEL_*.
+ */
+int fdisk_label_get_type(const struct fdisk_label *lb)
+{
+ return lb->id;
+}
+
+/**
+ * fdisk_label_require_geometry:
+ * @lb: label
+ *
+ * Returns: 1 if label requires CHS geometry
+ */
+int fdisk_label_require_geometry(const struct fdisk_label *lb)
+{
+ assert(lb);
+
+ return lb->flags & FDISK_LABEL_FL_REQUIRE_GEOMETRY ? 1 : 0;
+}
+
+/**
+ * fdisk_label_get_fields_ids
+ * @lb: label (or NULL for the current label)
+ * @cxt: context
+ * @ids: returns allocated array with FDISK_FIELD_* IDs
+ * @nids: returns number of items in fields
+ *
+ * This function returns the default fields for the label.
+ *
+ * Note that the set of the default fields depends on fdisk_enable_details()
+ * function. If the details are enabled then this function usually returns more
+ * fields.
+ *
+ * Returns: 0 on success, otherwise, a corresponding error.
+ */
+int fdisk_label_get_fields_ids(
+ const struct fdisk_label *lb,
+ struct fdisk_context *cxt,
+ int **ids, size_t *nids)
+{
+ size_t i, n;
+ int *c;
+
+ if (!cxt || (!lb && !cxt->label))
+ return -EINVAL;
+
+ lb = cxt->label;
+ if (!lb->fields || !lb->nfields)
+ return -ENOSYS;
+ c = calloc(lb->nfields, sizeof(int));
+ if (!c)
+ return -ENOMEM;
+ for (n = 0, i = 0; i < lb->nfields; i++) {
+ int id = lb->fields[i].id;
+
+ if ((fdisk_is_details(cxt) &&
+ (lb->fields[i].flags & FDISK_FIELDFL_EYECANDY))
+ || (!fdisk_is_details(cxt) &&
+ (lb->fields[i].flags & FDISK_FIELDFL_DETAIL))
+ || (id == FDISK_FIELD_SECTORS &&
+ fdisk_use_cylinders(cxt))
+ || (id == FDISK_FIELD_CYLINDERS &&
+ !fdisk_use_cylinders(cxt)))
+ continue;
+
+ c[n++] = id;
+ }
+ if (ids)
+ *ids = c;
+ else
+ free(c);
+ if (nids)
+ *nids = n;
+ return 0;
+}
+
+/**
+ * fdisk_label_get_fields_ids_all
+ * @lb: label (or NULL for the current label)
+ * @cxt: context
+ * @ids: returns allocated array with FDISK_FIELD_* IDs
+ * @nids: returns number of items in fields
+ *
+ * This function returns all fields for the label.
+ *
+ * Returns: 0 on success, otherwise, a corresponding error.
+ */
+int fdisk_label_get_fields_ids_all(
+ const struct fdisk_label *lb,
+ struct fdisk_context *cxt,
+ int **ids, size_t *nids)
+{
+ size_t i, n;
+ int *c;
+
+ if (!cxt || (!lb && !cxt->label))
+ return -EINVAL;
+
+ lb = cxt->label;
+ if (!lb->fields || !lb->nfields)
+ return -ENOSYS;
+ c = calloc(lb->nfields, sizeof(int));
+ if (!c)
+ return -ENOMEM;
+ for (n = 0, i = 0; i < lb->nfields; i++)
+ c[n++] = lb->fields[i].id;
+ if (ids)
+ *ids = c;
+ else
+ free(c);
+ if (nids)
+ *nids = n;
+ return 0;
+}
+
+/**
+ * fdisk_label_get_field:
+ * @lb: label
+ * @id: FDISK_FIELD_*
+ *
+ * The field struct describes data stored in struct fdisk_partition. The info
+ * about data is usable for example to generate human readable output (e.g.
+ * fdisk 'p'rint command). See fdisk_partition_to_string() and fdisk code.
+ *
+ * Returns: pointer to static instance of the field.
+ */
+const struct fdisk_field *fdisk_label_get_field(const struct fdisk_label *lb, int id)
+{
+ size_t i;
+
+ assert(lb);
+ assert(id > 0);
+
+ for (i = 0; i < lb->nfields; i++) {
+ if (lb->fields[i].id == id)
+ return &lb->fields[i];
+ }
+
+ return NULL;
+}
+
+/**
+ * fdisk_label_get_field_by_name
+ * @lb: label
+ * @name: field name
+ *
+ * Returns: pointer to static instance of the field.
+ */
+const struct fdisk_field *fdisk_label_get_field_by_name(
+ const struct fdisk_label *lb,
+ const char *name)
+{
+ size_t i;
+
+ assert(lb);
+ assert(name);
+
+ for (i = 0; i < lb->nfields; i++) {
+ if (lb->fields[i].name && strcasecmp(lb->fields[i].name, name) == 0)
+ return &lb->fields[i];
+ }
+
+ return NULL;
+}
+
+/**
+ * fdisk_write_disklabel:
+ * @cxt: fdisk context
+ *
+ * This function wipes the device (if enabled by fdisk_enable_wipe()) and then
+ * it writes in-memory changes to disk. Be careful!
+ *
+ * Returns: 0 on success, otherwise, a corresponding error.
+ */
+int fdisk_write_disklabel(struct fdisk_context *cxt)
+{
+ if (!cxt || !cxt->label || cxt->readonly)
+ return -EINVAL;
+ if (!cxt->label->op->write)
+ return -ENOSYS;
+
+ fdisk_do_wipe(cxt);
+ return cxt->label->op->write(cxt);
+}
+
+/**
+ * fdisk_verify_disklabel:
+ * @cxt: fdisk context
+ *
+ * Verifies the partition table.
+ *
+ * Returns: 0 on success, <1 runtime or option errors, >0 number of detected issues
+ */
+int fdisk_verify_disklabel(struct fdisk_context *cxt)
+{
+ if (!cxt || !cxt->label)
+ return -EINVAL;
+ if (!cxt->label->op->verify)
+ return -ENOSYS;
+ if (fdisk_missing_geometry(cxt))
+ return -EINVAL;
+
+ return cxt->label->op->verify(cxt);
+}
+
+/**
+ * fdisk_list_disklabel:
+ * @cxt: fdisk context
+ *
+ * Lists details about disklabel, but no partitions.
+ *
+ * This function is based on fdisk_get_disklabel_item() and prints all label
+ * specific information by ASK interface (FDISK_ASKTYPE_INFO, aka fdisk_info()).
+ * The function requires enabled "details" by fdisk_enable_details().
+ *
+ * It's recommended to use fdisk_get_disklabel_item() if you need better
+ * control on output and formatting.
+ *
+ * Returns: 0 on success, otherwise, a corresponding error.
+ */
+int fdisk_list_disklabel(struct fdisk_context *cxt)
+{
+ int id = 0, rc = 0;
+ struct fdisk_labelitem item = { .id = id };
+
+ if (!cxt || !cxt->label)
+ return -EINVAL;
+
+ if (!cxt->display_details)
+ return 0;
+
+ /* List all label items */
+ do {
+ /* rc: < 0 error, 0 success, 1 unknown item, 2 out of range */
+ rc = fdisk_get_disklabel_item(cxt, id++, &item);
+ if (rc != 0)
+ continue;
+ switch (item.type) {
+ case 'j':
+ fdisk_info(cxt, "%s: %ju", item.name, item.data.num64);
+ break;
+ case 's':
+ if (item.data.str && item.name)
+ fdisk_info(cxt, "%s: %s", item.name, item.data.str);
+ break;
+ }
+ fdisk_reset_labelitem(&item);
+ } while (rc == 0 || rc == 1);
+
+ return rc < 0 ? rc : 0;
+}
+
+/**
+ * fdisk_create_disklabel:
+ * @cxt: fdisk context
+ * @name: label name
+ *
+ * Creates a new disk label of type @name. If @name is NULL, then it will
+ * create a default system label type, either SUN or DOS. The function
+ * automatically switches the current label driver to @name. The function
+ * fdisk_get_label() returns the current label driver.
+ *
+ * The function modifies in-memory data only.
+ *
+ * Returns: 0 on success, otherwise, a corresponding error.
+ */
+int fdisk_create_disklabel(struct fdisk_context *cxt, const char *name)
+{
+ int haslabel = 0;
+ struct fdisk_label *lb;
+
+ if (!cxt)
+ return -EINVAL;
+
+ if (!name) { /* use default label creation */
+#ifdef __sparc__
+ name = "sun";
+#else
+ name = "dos";
+#endif
+ }
+
+ if (cxt->label) {
+ fdisk_deinit_label(cxt->label);
+ haslabel = 1;
+ }
+
+ lb = fdisk_get_label(cxt, name);
+ if (!lb || lb->disabled)
+ return -EINVAL;
+
+ if (!haslabel || (lb && cxt->label != lb))
+ fdisk_check_collisions(cxt);
+
+ if (!lb->op->create)
+ return -ENOSYS;
+
+ __fdisk_switch_label(cxt, lb);
+ assert(cxt->label == lb);
+
+ if (haslabel && !cxt->parent)
+ fdisk_reset_device_properties(cxt);
+
+ DBG(CXT, ul_debugobj(cxt, "create a new %s label", lb->name));
+ return lb->op->create(cxt);
+}
+
+/**
+ * fdisk_locate_disklabel:
+ * @cxt: context
+ * @n: N item
+ * @name: return item name
+ * @offset: return offset where is item
+ * @size: of the item
+ *
+ * Locate disklabel and returns info about @n item of the label.
+ *
+ * For example GPT is composed from three items, PMBR and GPT, n=0 return
+ * offset to PMBR and n=1 return offset to GPT Header and n=2 returns offset to
+ * GPT array of partitions, n=3 and n=4 returns location of the backup GPT
+ * label at the end of the disk.
+ *
+ * The function returns the current in-memory situation. It's possible that a
+ * header location is modified by write operation, for example when enabled
+ * minimization (see fdisk_gpt_enable_minimize()). In this case it's better to
+ * call this function after fdisk_write_disklabel().
+ *
+ * For more details see 'D' expert fdisk command.
+ *
+ * Returns: 0 on success, <0 on error, 1 no more items.
+ */
+int fdisk_locate_disklabel(struct fdisk_context *cxt, int n, const char **name,
+ uint64_t *offset, size_t *size)
+{
+ if (!cxt || !cxt->label)
+ return -EINVAL;
+ if (!cxt->label->op->locate)
+ return -ENOSYS;
+
+ DBG(CXT, ul_debugobj(cxt, "locating %d chunk of %s.", n, cxt->label->name));
+ return cxt->label->op->locate(cxt, n, name, offset, size);
+}
+
+
+/**
+ * fdisk_get_disklabel_id:
+ * @cxt: fdisk context
+ * @id: returns pointer to allocated string (MBR Id or GPT dirk UUID)
+ *
+ * Returns: 0 on success, otherwise, a corresponding error.
+ */
+int fdisk_get_disklabel_id(struct fdisk_context *cxt, char **id)
+{
+ struct fdisk_labelitem item = FDISK_LABELITEM_INIT;
+ int rc;
+
+ if (!cxt || !cxt->label || !id)
+ return -EINVAL;
+
+ DBG(CXT, ul_debugobj(cxt, "asking for disk %s ID", cxt->label->name));
+
+ rc = fdisk_get_disklabel_item(cxt, FDISK_LABELITEM_ID, &item);
+ if (rc == 0) {
+ *id = item.data.str;
+ item.data.str = NULL;
+ }
+ fdisk_reset_labelitem(&item);
+ if (rc > 0)
+ rc = 0;
+ return rc;
+}
+
+/**
+ * fdisk_get_disklabel_item:
+ * @cxt: fdisk context
+ * @id: item ID (FDISK_LABELITEM_* or *_LABELITEM_*)
+ * @item: specifies and returns the item
+ *
+ * Note that @id is always in range 0..N. It's fine to use the function in loop
+ * until it returns error or 2, the result in @item should be ignored when
+ * function returns 1. Don't forget to use fdisk_reset_labelitem() or fdisk_unref_labelitem().
+ *
+ * Returns: 0 on success, < 0 on error, 1 on unsupported item, 2 id out of range
+ */
+int fdisk_get_disklabel_item(struct fdisk_context *cxt, int id, struct fdisk_labelitem *item)
+{
+ if (!cxt || !cxt->label || !item)
+ return -EINVAL;
+
+ fdisk_reset_labelitem(item);
+ item->id = id;
+ DBG(CXT, ul_debugobj(cxt, "asking for disk %s item %d", cxt->label->name, item->id));
+
+ if (!cxt->label->op->get_item)
+ return -ENOSYS;
+
+ return cxt->label->op->get_item(cxt, item);
+}
+
+/**
+ * fdisk_set_disklabel_id:
+ * @cxt: fdisk context
+ *
+ * Returns: 0 on success, otherwise, a corresponding error.
+ */
+int fdisk_set_disklabel_id(struct fdisk_context *cxt)
+{
+ if (!cxt || !cxt->label)
+ return -EINVAL;
+ if (!cxt->label->op->set_id)
+ return -ENOSYS;
+
+ DBG(CXT, ul_debugobj(cxt, "setting %s disk ID", cxt->label->name));
+ return cxt->label->op->set_id(cxt, NULL);
+}
+
+/**
+ * fdisk_set_disklabel_id_from_string
+ * @cxt: fdisk context
+ * @str: new Id
+ *
+ * Returns: 0 on success, otherwise, a corresponding error.
+ *
+ * Since: 2.36
+ */
+int fdisk_set_disklabel_id_from_string(struct fdisk_context *cxt, const char *str)
+{
+ if (!cxt || !cxt->label || !str)
+ return -EINVAL;
+ if (!cxt->label->op->set_id)
+ return -ENOSYS;
+
+ DBG(CXT, ul_debugobj(cxt, "setting %s disk ID from '%s'", cxt->label->name, str));
+ return cxt->label->op->set_id(cxt, str);
+}
+
+/**
+ * fdisk_set_partition_type:
+ * @cxt: fdisk context
+ * @partnum: partition number
+ * @t: new type
+ *
+ * Returns: 0 on success, < 0 on error.
+ */
+int fdisk_set_partition_type(struct fdisk_context *cxt,
+ size_t partnum,
+ struct fdisk_parttype *t)
+{
+ if (!cxt || !cxt->label || !t)
+ return -EINVAL;
+
+
+ if (cxt->label->op->set_part) {
+ struct fdisk_partition *pa = fdisk_new_partition();
+ int rc;
+
+ if (!pa)
+ return -ENOMEM;
+ fdisk_partition_set_type(pa, t);
+
+ DBG(CXT, ul_debugobj(cxt, "partition: %zd: set type", partnum));
+ rc = cxt->label->op->set_part(cxt, partnum, pa);
+ fdisk_unref_partition(pa);
+ return rc;
+ }
+
+ return -ENOSYS;
+}
+
+
+/**
+ * fdisk_toggle_partition_flag:
+ * @cxt: fdisk context
+ * @partnum: partition number
+ * @flag: flag ID
+ *
+ * Returns: 0 on success, otherwise, a corresponding error.
+ */
+int fdisk_toggle_partition_flag(struct fdisk_context *cxt,
+ size_t partnum,
+ unsigned long flag)
+{
+ int rc;
+
+ if (!cxt || !cxt->label)
+ return -EINVAL;
+ if (!cxt->label->op->part_toggle_flag)
+ return -ENOSYS;
+
+ rc = cxt->label->op->part_toggle_flag(cxt, partnum, flag);
+
+ DBG(CXT, ul_debugobj(cxt, "partition: %zd: toggle: 0x%04lx [rc=%d]", partnum, flag, rc));
+ return rc;
+}
+
+/**
+ * fdisk_reorder_partitions
+ * @cxt: fdisk context
+ *
+ * Sort partitions according to the partition start sector.
+ *
+ * Returns: 0 on success, 1 reorder unnecessary, otherwise a corresponding error.
+ */
+int fdisk_reorder_partitions(struct fdisk_context *cxt)
+{
+ int rc;
+
+ if (!cxt || !cxt->label)
+ return -EINVAL;
+ if (!cxt->label->op->reorder)
+ return -ENOSYS;
+
+ rc = cxt->label->op->reorder(cxt);
+
+ switch (rc) {
+ case 0:
+ fdisk_info(cxt, _("Partitions order fixed."));
+ break;
+ case 1:
+ fdisk_info(cxt, _("Nothing to do. Ordering is correct already."));
+ break;
+ default:
+ fdisk_warnx(cxt, _("Failed to fix partitions order."));
+ break;
+ }
+
+ return rc;
+}
+
+/*
+ * Resets the current used label driver to initial state
+ */
+void fdisk_deinit_label(struct fdisk_label *lb)
+{
+ assert(lb);
+
+ /* private label information */
+ if (lb->op->deinit)
+ lb->op->deinit(lb);
+}
+
+/**
+ * fdisk_label_set_changed:
+ * @lb: label
+ * @changed: 0/1
+ *
+ * Marks in-memory data as changed, to force fdisk_write_disklabel() to write
+ * to device. This should be unnecessary by default, the library keeps track
+ * about changes.
+ */
+void fdisk_label_set_changed(struct fdisk_label *lb, int changed)
+{
+ assert(lb);
+ lb->changed = changed ? 1 : 0;
+}
+
+/**
+ * fdisk_label_is_changed:
+ * @lb: label
+ *
+ * Returns: 1 if in-memory data has been changed.
+ */
+int fdisk_label_is_changed(const struct fdisk_label *lb)
+{
+ return lb ? lb->changed : 0;
+}
+
+/**
+ * fdisk_label_set_disabled:
+ * @lb: label
+ * @disabled: 0 or 1
+ *
+ * Mark label as disabled, then libfdisk is going to ignore the label when
+ * probe device for labels.
+ */
+void fdisk_label_set_disabled(struct fdisk_label *lb, int disabled)
+{
+ assert(lb);
+
+ DBG(LABEL, ul_debug("%s label %s",
+ lb->name,
+ disabled ? "DISABLED" : "ENABLED"));
+ lb->disabled = disabled ? 1 : 0;
+}
+
+/**
+ * fdisk_label_is_disabled:
+ * @lb: label
+ *
+ * Returns: 1 if label driver disabled.
+ */
+int fdisk_label_is_disabled(const struct fdisk_label *lb)
+{
+ assert(lb);
+ return lb ? lb->disabled : 0;
+}
+
+/**
+ * fdisk_label_get_geomrange_sectors:
+ * @lb: label
+ * @mi: minimal number
+ * @ma: maximal number
+ *
+ * The function provides minimal and maximal geometry supported for the label,
+ * if no range defined by library then returns -ENOSYS.
+ *
+ * Since: 2.32
+ *
+ * Returns: 0 on success, otherwise, a corresponding error.
+ */
+int fdisk_label_get_geomrange_sectors(const struct fdisk_label *lb,
+ fdisk_sector_t *mi, fdisk_sector_t *ma)
+{
+ if (!lb || lb->geom_min.sectors == 0)
+ return -ENOSYS;
+ if (mi)
+ *mi = lb->geom_min.sectors;
+ if (ma)
+ *ma = lb->geom_max.sectors;
+ return 0;
+}
+
+/**
+ * fdisk_label_get_geomrange_heads:
+ * @lb: label
+ * @mi: minimal number
+ * @ma: maximal number
+ *
+ * The function provides minimal and maximal geometry supported for the label,
+ * if no range defined by library then returns -ENOSYS.
+ *
+ * Since: 2.32
+ *
+ * Returns: 0 on success, otherwise, a corresponding error.
+ */
+int fdisk_label_get_geomrange_heads(const struct fdisk_label *lb,
+ unsigned int *mi, unsigned int *ma)
+{
+ if (!lb || lb->geom_min.heads == 0)
+ return -ENOSYS;
+ if (mi)
+ *mi = lb->geom_min.heads;
+ if (ma)
+ *ma = lb->geom_max.heads;
+ return 0;
+}
+
+/**
+ * fdisk_label_get_geomrange_cylinders:
+ * @lb: label
+ * @mi: minimal number
+ * @ma: maximal number
+ *
+ * The function provides minimal and maximal geometry supported for the label,
+ * if no range defined by library then returns -ENOSYS.
+ *
+ * Since: 2.32
+ *
+ * Returns: 0 on success, otherwise, a corresponding error.
+ */
+int fdisk_label_get_geomrange_cylinders(const struct fdisk_label *lb,
+ fdisk_sector_t *mi, fdisk_sector_t *ma)
+{
+ if (!lb || lb->geom_min.cylinders == 0)
+ return -ENOSYS;
+ if (mi)
+ *mi = lb->geom_min.cylinders;
+ if (ma)
+ *ma = lb->geom_max.cylinders;
+ return 0;
+}
+
diff --git a/libfdisk/src/libfdisk.h.in b/libfdisk/src/libfdisk.h.in
new file mode 100644
index 0000000..1c97149
--- /dev/null
+++ b/libfdisk/src/libfdisk.h.in
@@ -0,0 +1,895 @@
+/*
+ * libfdisk.h - libfdisk API
+ *
+ * Copyright (C) 2012-2014 Karel Zak <kzak@redhat.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef _LIBFDISK_H
+#define _LIBFDISK_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <stdio.h>
+#include <stdarg.h>
+#include <stdint.h>
+#include <sys/types.h>
+
+/**
+ * LIBFDISK_VERSION:
+ *
+ * Library version string
+ */
+#define LIBFDISK_VERSION "@LIBFDISK_VERSION@"
+
+#define LIBFDISK_MAJOR_VERSION @LIBFDISK_MAJOR_VERSION@
+#define LIBFDISK_MINOR_VERSION @LIBFDISK_MINOR_VERSION@
+#define LIBFDISK_PATCH_VERSION @LIBFDISK_PATCH_VERSION@
+
+/**
+ * fdisk_context:
+ *
+ * Basic library handler.
+ */
+struct fdisk_context;
+
+/**
+ * fdisk_label:
+ *
+ * Disk label specific driver and setting.
+ */
+struct fdisk_label;
+
+/**
+ * fdisk_parttype:
+ *
+ * Partition type.
+ */
+struct fdisk_parttype;
+
+/**
+ * fdisk_partition:
+ *
+ * Partition abstraction (and template).
+ */
+struct fdisk_partition;
+
+/**
+ * fdisk_ask:
+ *
+ * Ask API handler for dialogs with users.
+ */
+struct fdisk_ask;
+
+/**
+ * fdisk_iter:
+ *
+ * Unified iterator.
+ */
+struct fdisk_iter;
+
+/**
+ * fdisk_table:
+ *
+ * Container for fdisk_partition objects
+ */
+struct fdisk_table;
+
+/**
+ * fdisk_field
+ *
+ * Output field description.
+ */
+struct fdisk_field;
+
+/**
+ * fdisk_script
+ *
+ * library handler for sfdisk compatible scripts and dumps
+ */
+struct fdisk_script;
+
+/**
+ * fdisk_sector_t
+ *
+ * LBA addresses type
+ */
+typedef uint64_t fdisk_sector_t;
+
+/**
+ * fdisk_labeltype:
+ * @FDISK_DISKLABEL_DOS: MBR label type
+ * @FDISK_DISKLABEL_SUN: SUN label type
+ * @FDISK_DISKLABEL_SGI: SGI label type
+ * @FDISK_DISKLABEL_BSD: BSD label type
+ * @FDISK_DISKLABEL_GPT: UEFI GPT type
+ *
+ * Supported partition table types (labels)
+ */
+enum fdisk_labeltype {
+ FDISK_DISKLABEL_DOS = (1 << 1), /* MBR label type */
+ FDISK_DISKLABEL_SUN = (1 << 2), /* SUN label type */
+ FDISK_DISKLABEL_SGI = (1 << 3), /* SGI label type */
+ FDISK_DISKLABEL_BSD = (1 << 4), /* BSD label t ype */
+ FDISK_DISKLABEL_GPT = (1 << 5) /* UEFI GPT type */
+};
+
+/**
+ * fdisk_labelitem:
+ *
+ * library handler for label specific information. See
+ * generic FDISK_LABELITEM_* and label specific {GPT,MBR,..}_LABELITEM_*.
+ */
+struct fdisk_labelitem;
+
+/**
+ * fdisk_asktype:
+ * @FDISK_ASKTYPE_NONE: undefined type
+ * @FDISK_ASKTYPE_NUMBER: ask for number
+ * @FDISK_ASKTYPE_OFFSET: ask for offset
+ * @FDISK_ASKTYPE_WARN: print warning message and errno
+ * @FDISK_ASKTYPE_WARNX: print warning message
+ * @FDISK_ASKTYPE_INFO: print info message
+ * @FDISK_ASKTYPE_YESNO: ask Yes/No question
+ * @FDISK_ASKTYPE_STRING: ask for string
+ * @FDISK_ASKTYPE_MENU: ask for menu item
+ *
+ * Ask API dialog types
+ */
+enum fdisk_asktype {
+ FDISK_ASKTYPE_NONE = 0,
+ FDISK_ASKTYPE_NUMBER,
+ FDISK_ASKTYPE_OFFSET,
+ FDISK_ASKTYPE_WARN,
+ FDISK_ASKTYPE_WARNX,
+ FDISK_ASKTYPE_INFO,
+ FDISK_ASKTYPE_YESNO,
+ FDISK_ASKTYPE_STRING,
+ FDISK_ASKTYPE_MENU
+};
+
+/* init.c */
+extern void fdisk_init_debug(int mask);
+
+/* version.c */
+extern int fdisk_parse_version_string(const char *ver_string);
+extern int fdisk_get_library_version(const char **ver_string);
+extern int fdisk_get_library_features(const char ***features);
+
+/* context.h */
+
+#define FDISK_PLURAL 0
+#define FDISK_SINGULAR 1
+
+struct fdisk_context *fdisk_new_context(void);
+struct fdisk_context *fdisk_new_nested_context(struct fdisk_context *parent, const char *name);
+void fdisk_unref_context(struct fdisk_context *cxt);
+void fdisk_ref_context(struct fdisk_context *cxt);
+
+struct fdisk_context *fdisk_get_parent(struct fdisk_context *cxt);
+size_t fdisk_get_npartitions(struct fdisk_context *cxt);
+
+struct fdisk_label *fdisk_get_label(struct fdisk_context *cxt, const char *name);
+int fdisk_next_label(struct fdisk_context *cxt, struct fdisk_label **lb);
+size_t fdisk_get_nlabels(struct fdisk_context *cxt);
+
+int fdisk_has_label(struct fdisk_context *cxt);
+int fdisk_is_labeltype(struct fdisk_context *cxt, enum fdisk_labeltype id);
+#define fdisk_is_label(c, x) fdisk_is_labeltype(c, FDISK_DISKLABEL_ ## x)
+
+
+int fdisk_assign_device(struct fdisk_context *cxt,
+ const char *fname, int readonly);
+int fdisk_assign_device_by_fd(struct fdisk_context *cxt, int fd,
+ const char *fname, int readonly);
+int fdisk_deassign_device(struct fdisk_context *cxt, int nosync);
+int fdisk_reassign_device(struct fdisk_context *cxt);
+
+int fdisk_is_readonly(struct fdisk_context *cxt);
+int fdisk_is_regfile(struct fdisk_context *cxt);
+int fdisk_device_is_used(struct fdisk_context *cxt);
+
+int fdisk_disable_dialogs(struct fdisk_context *cxt, int disable);
+int fdisk_has_dialogs(struct fdisk_context *cxt);
+
+int fdisk_enable_details(struct fdisk_context *cxt, int enable);
+int fdisk_is_details(struct fdisk_context *cxt);
+
+int fdisk_enable_listonly(struct fdisk_context *cxt, int enable);
+int fdisk_is_listonly(struct fdisk_context *cxt);
+
+int fdisk_enable_wipe(struct fdisk_context *cxt, int enable);
+int fdisk_has_wipe(struct fdisk_context *cxt);
+const char *fdisk_get_collision(struct fdisk_context *cxt);
+int fdisk_is_ptcollision(struct fdisk_context *cxt);
+
+int fdisk_set_unit(struct fdisk_context *cxt, const char *str);
+const char *fdisk_get_unit(struct fdisk_context *cxt, int n);
+int fdisk_use_cylinders(struct fdisk_context *cxt);
+unsigned int fdisk_get_units_per_sector(struct fdisk_context *cxt);
+
+unsigned long fdisk_get_optimal_iosize(struct fdisk_context *cxt);
+unsigned long fdisk_get_minimal_iosize(struct fdisk_context *cxt);
+unsigned long fdisk_get_physector_size(struct fdisk_context *cxt);
+unsigned long fdisk_get_sector_size(struct fdisk_context *cxt);
+unsigned long fdisk_get_alignment_offset(struct fdisk_context *cxt);
+unsigned long fdisk_get_grain_size(struct fdisk_context *cxt);
+fdisk_sector_t fdisk_get_first_lba(struct fdisk_context *cxt);
+fdisk_sector_t fdisk_set_first_lba(struct fdisk_context *cxt, fdisk_sector_t lba);
+fdisk_sector_t fdisk_get_last_lba(struct fdisk_context *cxt);
+fdisk_sector_t fdisk_set_last_lba(struct fdisk_context *cxt, fdisk_sector_t lba);
+fdisk_sector_t fdisk_get_nsectors(struct fdisk_context *cxt);
+
+const char *fdisk_get_devname(struct fdisk_context *cxt);
+int fdisk_get_devfd(struct fdisk_context *cxt);
+dev_t fdisk_get_devno(struct fdisk_context *cxt);
+const char *fdisk_get_devmodel(struct fdisk_context *cxt);
+
+
+unsigned int fdisk_get_geom_heads(struct fdisk_context *cxt);
+fdisk_sector_t fdisk_get_geom_sectors(struct fdisk_context *cxt);
+fdisk_sector_t fdisk_get_geom_cylinders(struct fdisk_context *cxt);
+
+enum {
+ FDISK_SIZEUNIT_HUMAN = 0, /* default, human readable {M,G,P,...} */
+ FDISK_SIZEUNIT_BYTES /* bytes */
+};
+int fdisk_set_size_unit(struct fdisk_context *cxt, int unit);
+int fdisk_get_size_unit(struct fdisk_context *cxt);
+
+int fdisk_has_protected_bootbits(struct fdisk_context *cxt);
+int fdisk_enable_bootbits_protection(struct fdisk_context *cxt, int enable);
+
+/* parttype.c */
+struct fdisk_parttype *fdisk_new_parttype(void);
+void fdisk_ref_parttype(struct fdisk_parttype *t);
+void fdisk_unref_parttype(struct fdisk_parttype *t);
+int fdisk_parttype_set_name(struct fdisk_parttype *t, const char *str);
+int fdisk_parttype_set_typestr(struct fdisk_parttype *t, const char *str);
+int fdisk_parttype_set_code(struct fdisk_parttype *t, int code);
+size_t fdisk_label_get_nparttypes(const struct fdisk_label *lb);
+struct fdisk_parttype *fdisk_label_get_parttype(const struct fdisk_label *lb, size_t n);
+int fdisk_label_get_parttype_shortcut(
+ const struct fdisk_label *lb, size_t n,
+ const char **typestr,
+ const char **shortcut,
+ const char **alias);
+int fdisk_label_has_code_parttypes(const struct fdisk_label *lb);
+int fdisk_label_has_parttypes_shortcuts(const struct fdisk_label *lb);
+struct fdisk_parttype *fdisk_label_get_parttype_from_code(
+ const struct fdisk_label *lb,
+ unsigned int code);
+struct fdisk_parttype *fdisk_label_get_parttype_from_string(
+ const struct fdisk_label *lb,
+ const char *str);
+struct fdisk_parttype *fdisk_new_unknown_parttype(unsigned int code,
+ const char *typestr);
+struct fdisk_parttype *fdisk_copy_parttype(const struct fdisk_parttype *type);
+struct fdisk_parttype *fdisk_label_parse_parttype(
+ const struct fdisk_label *lb,
+ const char *str);
+struct fdisk_parttype *fdisk_label_advparse_parttype(
+ const struct fdisk_label *lb,
+ const char *str,
+ int flags);
+
+/**
+ * fdisk_parttype_parser_flags:
+ * @FDISK_PARTTYPE_PARSE_DATA: parse hex or UUID from string
+ * @FDISK_PARTTYPE_PARSE_DATALAST: try hex or UUID as the last possibility (don't use!)
+ * @FDISK_PARTTYPE_PARSE_SHORTCUT: try input as type shortcut (e.g 'L' for linux partition)
+ * @FDISK_PARTTYPE_PARSE_ALIAS: try input as type alias (e.g. 'linux' for linux partition)
+ * @FDISK_PARTTYPE_PARSE_DEPRECATED: accept also deprecated aliases and shortcuts
+ * @FDISK_PARTTYPE_PARSE_DEFAULT: recommended flags for new code
+ * @FDISK_PARTTYPE_PARSE_NOUNKNOWN: ignore unknown types
+ * @FDISK_PARTTYPE_PARSE_SEQNUM: use input as sequntial number of type (e.g. list-types fdisk dialog)
+ * @FDISK_PARTTYPE_PARSE_NAME: parse type human readable name
+ */
+enum fdisk_parttype_parser_flags {
+ FDISK_PARTTYPE_PARSE_DATA = (1 << 1),
+ FDISK_PARTTYPE_PARSE_DATALAST = (1 << 2),
+ FDISK_PARTTYPE_PARSE_SHORTCUT = (1 << 3),
+ FDISK_PARTTYPE_PARSE_ALIAS = (1 << 4),
+ FDISK_PARTTYPE_PARSE_DEPRECATED = (1 << 5),
+ FDISK_PARTTYPE_PARSE_NOUNKNOWN = (1 << 6),
+ FDISK_PARTTYPE_PARSE_SEQNUM = (1 << 7),
+ FDISK_PARTTYPE_PARSE_NAME = (1 << 8),
+
+ FDISK_PARTTYPE_PARSE_DEFAULT = (FDISK_PARTTYPE_PARSE_DATA | \
+ FDISK_PARTTYPE_PARSE_SHORTCUT | \
+ FDISK_PARTTYPE_PARSE_ALIAS | \
+ FDISK_PARTTYPE_PARSE_NAME | \
+ FDISK_PARTTYPE_PARSE_SEQNUM )
+};
+
+const char *fdisk_parttype_get_string(const struct fdisk_parttype *t);
+unsigned int fdisk_parttype_get_code(const struct fdisk_parttype *t);
+const char *fdisk_parttype_get_name(const struct fdisk_parttype *t);
+int fdisk_parttype_is_unknown(const struct fdisk_parttype *t);
+
+
+/* field.c */
+extern int fdisk_field_get_id(const struct fdisk_field *field);
+extern const char *fdisk_field_get_name(const struct fdisk_field *field);
+extern double fdisk_field_get_width(const struct fdisk_field *field);
+extern int fdisk_field_is_number(const struct fdisk_field *field);
+
+
+/* label.c */
+
+/**
+ * fdisk_fieldtype:
+ * @FDISK_FIELD_NONE: unspecified item
+ * @FDISK_FIELD_DEVICE: partition device name
+ * @FDISK_FIELD_START: start offset of the partition
+ * @FDISK_FIELD_END: end offset of the partition
+ * @FDISK_FIELD_SECTORS: number of sectors
+ * @FDISK_FIELD_CYLINDERS: number of cylinders (deprecated)
+ * @FDISK_FIELD_SIZE: partition size
+ * @FDISK_FIELD_TYPE: partition type
+ * @FDISK_FIELD_TYPEID: partition type ID
+ * @FDISK_FIELD_ATTR: partition attribute (GPT)
+ * @FDISK_FIELD_BOOT: partition boot flag
+ * @FDISK_FIELD_BSIZE: size of the boot area (BSD)
+ * @FDISK_FIELD_CPG: BSD
+ * @FDISK_FIELD_EADDR: End-C/H/S (MBR)
+ * @FDISK_FIELD_FSIZE: BSD
+ * @FDISK_FIELD_NAME: partition label/name
+ * @FDISK_FIELD_SADDR: Start-C/H/S (MBR)
+ * @FDISK_FIELD_UUID: partition UUID (GPT)
+ * @FDISK_FIELD_FSUUID: Filesystem UUID
+ * @FDISK_FIELD_FSLABEL: Filesystem LABEL
+ * @FDISK_FIELD_FSTYPE: Filesystem type
+ * @FDISK_NFIELDS: Don't use, counter.
+ *
+ * Types of fdisk_field. The fields describe a partition.
+ */
+enum fdisk_fieldtype {
+ FDISK_FIELD_NONE = 0,
+
+ /* generic */
+ FDISK_FIELD_DEVICE,
+ FDISK_FIELD_START,
+ FDISK_FIELD_END,
+ FDISK_FIELD_SECTORS,
+ FDISK_FIELD_CYLINDERS,
+ FDISK_FIELD_SIZE,
+ FDISK_FIELD_TYPE,
+ FDISK_FIELD_TYPEID,
+
+ /* label specific */
+ FDISK_FIELD_ATTR,
+ FDISK_FIELD_BOOT,
+ FDISK_FIELD_BSIZE,
+ FDISK_FIELD_CPG,
+ FDISK_FIELD_EADDR,
+ FDISK_FIELD_FSIZE,
+ FDISK_FIELD_NAME,
+ FDISK_FIELD_SADDR,
+ FDISK_FIELD_UUID,
+
+ FDISK_FIELD_FSUUID,
+ FDISK_FIELD_FSLABEL,
+ FDISK_FIELD_FSTYPE,
+
+ FDISK_NFIELDS /* must be last */
+};
+
+int fdisk_label_get_type(const struct fdisk_label *lb);
+const char *fdisk_label_get_name(const struct fdisk_label *lb);
+int fdisk_label_require_geometry(const struct fdisk_label *lb);
+
+
+extern int fdisk_write_disklabel(struct fdisk_context *cxt);
+extern int fdisk_verify_disklabel(struct fdisk_context *cxt);
+extern int fdisk_create_disklabel(struct fdisk_context *cxt, const char *name);
+extern int fdisk_list_disklabel(struct fdisk_context *cxt);
+extern int fdisk_locate_disklabel(struct fdisk_context *cxt, int n,
+ const char **name,
+ uint64_t *offset,
+ size_t *size);
+
+extern int fdisk_label_get_geomrange_cylinders(const struct fdisk_label *lb,
+ fdisk_sector_t *mi, fdisk_sector_t *ma);
+extern int fdisk_label_get_geomrange_heads(const struct fdisk_label *lb,
+ unsigned int *mi, unsigned int *ma);
+extern int fdisk_label_get_geomrange_sectors(const struct fdisk_label *lb,
+ fdisk_sector_t *mi, fdisk_sector_t *ma);
+
+/**
+ * fdisk_labelitem_gen:
+ * @FDISK_LABELITEM_ID: Unique disk identifier
+ * @__FDISK_NLABELITEMS: Specifies reserved range for generic items (0..7)
+ *
+ * Generic disklabel items.
+ */
+enum fdisk_labelitem_gen {
+ FDISK_LABELITEM_ID = 0,
+ __FDISK_NLABELITEMS = 8
+};
+
+/* item.c */
+extern struct fdisk_labelitem *fdisk_new_labelitem(void);
+extern void fdisk_ref_labelitem(struct fdisk_labelitem *li);
+extern int fdisk_reset_labelitem(struct fdisk_labelitem *li);
+extern void fdisk_unref_labelitem(struct fdisk_labelitem *li);
+extern const char *fdisk_labelitem_get_name(struct fdisk_labelitem *li);
+extern int fdisk_labelitem_get_id(struct fdisk_labelitem *li);
+extern int fdisk_labelitem_get_data_u64(struct fdisk_labelitem *li, uint64_t *data);
+extern int fdisk_labelitem_get_data_string(struct fdisk_labelitem *li, const char **data);
+extern int fdisk_labelitem_is_string(struct fdisk_labelitem *li);
+extern int fdisk_labelitem_is_number(struct fdisk_labelitem *li);
+
+extern int fdisk_get_disklabel_item(struct fdisk_context *cxt, int id, struct fdisk_labelitem *item);
+
+extern int fdisk_get_disklabel_id(struct fdisk_context *cxt, char **id);
+extern int fdisk_set_disklabel_id(struct fdisk_context *cxt);
+extern int fdisk_set_disklabel_id_from_string(struct fdisk_context *cxt, const char *str);
+
+extern int fdisk_get_partition(struct fdisk_context *cxt, size_t partno, struct fdisk_partition **pa);
+extern int fdisk_set_partition(struct fdisk_context *cxt, size_t partno, struct fdisk_partition *pa);
+extern int fdisk_add_partition(struct fdisk_context *cxt, struct fdisk_partition *pa, size_t *partno);
+extern int fdisk_delete_partition(struct fdisk_context *cxt, size_t partno);
+extern int fdisk_delete_all_partitions(struct fdisk_context *cxt);
+
+extern int fdisk_wipe_partition(struct fdisk_context *cxt, size_t partno, int enable);
+
+extern int fdisk_set_partition_type(struct fdisk_context *cxt, size_t partnum,
+ struct fdisk_parttype *t);
+
+
+extern int fdisk_label_get_fields_ids(
+ const struct fdisk_label *lb,
+ struct fdisk_context *cxt,
+ int **ids, size_t *nids);
+
+extern int fdisk_label_get_fields_ids_all(
+ const struct fdisk_label *lb,
+ struct fdisk_context *cxt,
+ int **ids, size_t *nids);
+
+extern const struct fdisk_field *fdisk_label_get_field(const struct fdisk_label *lb, int id);
+extern const struct fdisk_field *fdisk_label_get_field_by_name(
+ const struct fdisk_label *lb,
+ const char *name);
+
+extern void fdisk_label_set_changed(struct fdisk_label *lb, int changed);
+extern int fdisk_label_is_changed(const struct fdisk_label *lb);
+
+extern void fdisk_label_set_disabled(struct fdisk_label *lb, int disabled);
+extern int fdisk_label_is_disabled(const struct fdisk_label *lb);
+
+extern int fdisk_is_partition_used(struct fdisk_context *cxt, size_t n);
+
+extern int fdisk_toggle_partition_flag(struct fdisk_context *cxt, size_t partnum, unsigned long flag);
+
+extern struct fdisk_partition *fdisk_new_partition(void);
+extern void fdisk_reset_partition(struct fdisk_partition *pa);
+extern void fdisk_ref_partition(struct fdisk_partition *pa);
+extern void fdisk_unref_partition(struct fdisk_partition *pa);
+extern int fdisk_partition_is_freespace(struct fdisk_partition *pa);
+
+int fdisk_partition_set_start(struct fdisk_partition *pa, uint64_t off);
+int fdisk_partition_unset_start(struct fdisk_partition *pa);
+fdisk_sector_t fdisk_partition_get_start(struct fdisk_partition *pa);
+int fdisk_partition_has_start(struct fdisk_partition *pa);
+int fdisk_partition_cmp_start(struct fdisk_partition *a,
+ struct fdisk_partition *b);
+int fdisk_partition_start_follow_default(struct fdisk_partition *pa, int enable);
+int fdisk_partition_start_is_default(struct fdisk_partition *pa);
+
+int fdisk_partition_set_size(struct fdisk_partition *pa, uint64_t sz);
+int fdisk_partition_unset_size(struct fdisk_partition *pa);
+fdisk_sector_t fdisk_partition_get_size(struct fdisk_partition *pa);
+int fdisk_partition_has_size(struct fdisk_partition *pa);
+int fdisk_partition_size_explicit(struct fdisk_partition *pa, int enable);
+
+int fdisk_partition_has_end(struct fdisk_partition *pa);
+fdisk_sector_t fdisk_partition_get_end(struct fdisk_partition *pa);
+
+int fdisk_partition_set_partno(struct fdisk_partition *pa, size_t num);
+int fdisk_partition_unset_partno(struct fdisk_partition *pa);
+size_t fdisk_partition_get_partno(struct fdisk_partition *pa);
+int fdisk_partition_has_partno(struct fdisk_partition *pa);
+int fdisk_partition_cmp_partno(struct fdisk_partition *a,
+ struct fdisk_partition *b);
+
+int fdisk_partition_partno_follow_default(struct fdisk_partition *pa, int enable);
+
+extern int fdisk_partition_set_type(struct fdisk_partition *pa, struct fdisk_parttype *type);
+extern struct fdisk_parttype *fdisk_partition_get_type(struct fdisk_partition *pa);
+extern int fdisk_partition_set_name(struct fdisk_partition *pa, const char *name);
+extern const char *fdisk_partition_get_name(struct fdisk_partition *pa);
+extern int fdisk_partition_set_uuid(struct fdisk_partition *pa, const char *uuid);
+extern int fdisk_partition_set_attrs(struct fdisk_partition *pa, const char *attrs);
+extern const char *fdisk_partition_get_uuid(struct fdisk_partition *pa);
+extern const char *fdisk_partition_get_attrs(struct fdisk_partition *pa);
+extern int fdisk_partition_is_nested(struct fdisk_partition *pa);
+extern int fdisk_partition_is_container(struct fdisk_partition *pa);
+extern int fdisk_partition_get_parent(struct fdisk_partition *pa, size_t *parent);
+extern int fdisk_partition_is_used(struct fdisk_partition *pa);
+extern int fdisk_partition_is_bootable(struct fdisk_partition *pa);
+extern int fdisk_partition_is_wholedisk(struct fdisk_partition *pa);
+extern int fdisk_partition_to_string(struct fdisk_partition *pa,
+ struct fdisk_context *cxt,
+ int id, char **data);
+
+int fdisk_partition_next_partno(struct fdisk_partition *pa,
+ struct fdisk_context *cxt,
+ size_t *n);
+
+extern int fdisk_partition_end_follow_default(struct fdisk_partition *pa, int enable);
+extern int fdisk_partition_end_is_default(struct fdisk_partition *pa);
+
+extern int fdisk_reorder_partitions(struct fdisk_context *cxt);
+
+extern int fdisk_partition_has_wipe(struct fdisk_context *cxt, struct fdisk_partition *pa);
+
+
+/* table.c */
+extern struct fdisk_table *fdisk_new_table(void);
+extern int fdisk_reset_table(struct fdisk_table *tb);
+extern void fdisk_ref_table(struct fdisk_table *tb);
+extern void fdisk_unref_table(struct fdisk_table *tb);
+extern size_t fdisk_table_get_nents(struct fdisk_table *tb);
+extern int fdisk_table_is_empty(struct fdisk_table *tb);
+extern int fdisk_table_add_partition(struct fdisk_table *tb, struct fdisk_partition *pa);
+extern int fdisk_table_remove_partition(struct fdisk_table *tb, struct fdisk_partition *pa);
+
+extern int fdisk_get_partitions(struct fdisk_context *cxt, struct fdisk_table **tb);
+extern int fdisk_get_freespaces(struct fdisk_context *cxt, struct fdisk_table **tb);
+
+
+extern int fdisk_table_wrong_order(struct fdisk_table *tb);
+extern int fdisk_table_sort_partitions(struct fdisk_table *tb,
+ int (*cmp)(struct fdisk_partition *,
+ struct fdisk_partition *));
+
+extern int fdisk_table_next_partition(
+ struct fdisk_table *tb,
+ struct fdisk_iter *itr,
+ struct fdisk_partition **pa);
+
+extern struct fdisk_partition *fdisk_table_get_partition(
+ struct fdisk_table *tb,
+ size_t n);
+extern struct fdisk_partition *fdisk_table_get_partition_by_partno(
+ struct fdisk_table *tb,
+ size_t partno);
+
+extern int fdisk_apply_table(struct fdisk_context *cxt, struct fdisk_table *tb);
+
+/* alignment.c */
+#define FDISK_ALIGN_UP 1
+#define FDISK_ALIGN_DOWN 2
+#define FDISK_ALIGN_NEAREST 3
+
+fdisk_sector_t fdisk_align_lba(struct fdisk_context *cxt, fdisk_sector_t lba, int direction);
+fdisk_sector_t fdisk_align_lba_in_range(struct fdisk_context *cxt,
+ fdisk_sector_t lba, fdisk_sector_t start, fdisk_sector_t stop);
+int fdisk_lba_is_phy_aligned(struct fdisk_context *cxt, fdisk_sector_t lba);
+
+int fdisk_override_geometry(struct fdisk_context *cxt,
+ unsigned int cylinders,
+ unsigned int heads,
+ unsigned int sectors);
+int fdisk_save_user_geometry(struct fdisk_context *cxt,
+ unsigned int cylinders,
+ unsigned int heads,
+ unsigned int sectors);
+int fdisk_save_user_sector_size(struct fdisk_context *cxt,
+ unsigned int phy,
+ unsigned int log);
+
+int fdisk_save_user_grain(struct fdisk_context *cxt, unsigned long grain);
+
+int fdisk_has_user_device_properties(struct fdisk_context *cxt);
+int fdisk_reset_alignment(struct fdisk_context *cxt);
+int fdisk_reset_device_properties(struct fdisk_context *cxt);
+int fdisk_reread_partition_table(struct fdisk_context *cxt);
+int fdisk_reread_changes(struct fdisk_context *cxt, struct fdisk_table *org);
+
+/* iter.c */
+enum {
+
+ FDISK_ITER_FORWARD = 0,
+ FDISK_ITER_BACKWARD
+};
+extern struct fdisk_iter *fdisk_new_iter(int direction);
+extern void fdisk_free_iter(struct fdisk_iter *itr);
+extern void fdisk_reset_iter(struct fdisk_iter *itr, int direction);
+extern int fdisk_iter_get_direction(struct fdisk_iter *itr);
+
+
+/* dos.c */
+#define DOS_FLAG_ACTIVE 1
+
+extern int fdisk_dos_fix_chs(struct fdisk_context *cxt);
+extern int fdisk_dos_move_begin(struct fdisk_context *cxt, size_t i);
+extern int fdisk_dos_enable_compatible(struct fdisk_label *lb, int enable);
+extern int fdisk_dos_is_compatible(struct fdisk_label *lb);
+
+/* sun.h */
+extern int fdisk_sun_set_alt_cyl(struct fdisk_context *cxt);
+extern int fdisk_sun_set_xcyl(struct fdisk_context *cxt);
+extern int fdisk_sun_set_ilfact(struct fdisk_context *cxt);
+extern int fdisk_sun_set_rspeed(struct fdisk_context *cxt);
+extern int fdisk_sun_set_pcylcount(struct fdisk_context *cxt);
+
+/**
+ * fdisk_labelitem_sun:
+ * @SUN_LABELITEM_LABELID: Label ID
+ * @SUN_LABELITEM_VTOCID: Volume ID
+ * @SUN_LABELITEM_RPM: Rpm
+ * @SUN_LABELITEM_ACYL: Alternate cylinders
+ * @SUN_LABELITEM_PCYL: Physical cylinders
+ * @SUN_LABELITEM_APC: Extra sects/cyl
+ * @SUN_LABELITEM_INTRLV: Interleave
+ *
+ * SUN specific label items.
+ */
+enum fdisk_labelitem_sun {
+ SUN_LABELITEM_LABELID = __FDISK_NLABELITEMS,
+ SUN_LABELITEM_VTOCID,
+ SUN_LABELITEM_RPM,
+ SUN_LABELITEM_ACYL,
+ SUN_LABELITEM_PCYL,
+ SUN_LABELITEM_APC,
+ SUN_LABELITEM_INTRLV
+};
+
+/* bsd.c */
+extern int fdisk_bsd_edit_disklabel(struct fdisk_context *cxt);
+extern int fdisk_bsd_write_bootstrap(struct fdisk_context *cxt);
+extern int fdisk_bsd_link_partition(struct fdisk_context *cxt);
+
+/**
+ * fdisk_labelitem_bsd:
+ * @BSD_LABELITEM_TYPE: type
+ * @BSD_LABELITEM_DISK: disk
+ * @BSD_LABELITEM_PACKNAME: packname
+ * @BSD_LABELITEM_FLAGS: flags (removable, ecc, badsect)
+ * @BSD_LABELITEM_SECSIZE: Bytes/Sector
+ * @BSD_LABELITEM_NTRACKS: Tracks/Cylinder
+ * @BSD_LABELITEM_SECPERCYL: Sectors/Cylinder
+ * @BSD_LABELITEM_CYLINDERS: Cylinders
+ * @BSD_LABELITEM_RPM: rpm
+ * @BSD_LABELITEM_INTERLEAVE: interleave
+ * @BSD_LABELITEM_TRACKSKEW: trackskew
+ * @BSD_LABELITEM_CYLINDERSKEW: cylinderskew
+ * @BSD_LABELITEM_HEADSWITCH: headswitch
+ * @BSD_LABELITEM_TRKSEEK: track-to-track seek
+ *
+ * BSD specific label items.
+ */
+enum fdisk_labelitem_bsd {
+ /* specific */
+ BSD_LABELITEM_TYPE = __FDISK_NLABELITEMS,
+ BSD_LABELITEM_DISK,
+ BSD_LABELITEM_PACKNAME,
+ BSD_LABELITEM_FLAGS,
+ BSD_LABELITEM_SECSIZE,
+ BSD_LABELITEM_NTRACKS,
+ BSD_LABELITEM_SECPERCYL,
+ BSD_LABELITEM_CYLINDERS,
+ BSD_LABELITEM_RPM,
+ BSD_LABELITEM_INTERLEAVE,
+ BSD_LABELITEM_TRACKSKEW,
+ BSD_LABELITEM_CYLINDERSKEW,
+ BSD_LABELITEM_HEADSWITCH,
+ BSD_LABELITEM_TRKSEEK
+};
+
+/* sgi.h */
+#define SGI_FLAG_BOOT 1
+#define SGI_FLAG_SWAP 2
+extern int fdisk_sgi_set_bootfile(struct fdisk_context *cxt);
+extern int fdisk_sgi_create_info(struct fdisk_context *cxt);
+
+/**
+ * fdisk_labelitem_sgi:
+ * @SGI_LABELITEM_PCYLCOUNT: Physical cylinders
+ * @SGI_LABELITEM_SPARECYL: Extra sects/cyl
+ * @SGI_LABELITEM_ILFACT: nterleave
+ * @SGI_LABELITEM_BOOTFILE: Bootfile
+ *
+ * SGI specific label items.
+ */
+enum fdisk_labelitem_sgi {
+ SGI_LABELITEM_PCYLCOUNT = __FDISK_NLABELITEMS,
+ SGI_LABELITEM_SPARECYL,
+ SGI_LABELITEM_ILFACT,
+ SGI_LABELITEM_BOOTFILE
+};
+
+/* gpt */
+
+/*
+ * GPT partition attributes
+ */
+
+/**
+ * GPT_FLAG_REQUIRED:
+ *
+ * GPT attribute; marks a partition as system partition (disk
+ * partitioning utilities must preserve the partition as is)
+ */
+#define GPT_FLAG_REQUIRED 1
+
+/**
+ * GPT_FLAG_NOBLOCK:
+ *
+ * GPT attribute; EFI firmware should ignore the content of the
+ * partition and not try to read from it
+ */
+#define GPT_FLAG_NOBLOCK 2
+
+/**
+ * GPT_FLAG_LEGACYBOOT:
+ *
+ * GPT attribute; use the partition for legacy boot method
+ */
+#define GPT_FLAG_LEGACYBOOT 3
+
+/**
+ * GPT_FLAG_GUIDSPECIFIC:
+ *
+ * GPT attribute; for bites 48-63, defined and used by the individual partition
+ * type.
+ *
+ * The flag GPT_FLAG_GUIDSPECIFIC forces libfdisk to ask (by ask API)
+ * for a bit number. If you want to toggle specific bit and avoid any
+ * dialog, then use the bit number (in range 48..63). For example:
+ *
+ * // start dialog to ask for bit number
+ * fdisk_toggle_partition_flag(cxt, n, GPT_FLAG_GUIDSPECIFIC);
+ *
+ * // toggle bit 60
+ * fdisk_toggle_partition_flag(cxt, n, 60);
+ */
+#define GPT_FLAG_GUIDSPECIFIC 4
+
+extern int fdisk_gpt_is_hybrid(struct fdisk_context *cxt);
+extern int fdisk_gpt_set_npartitions(struct fdisk_context *cxt, uint32_t nents);
+extern int fdisk_gpt_get_partition_attrs(struct fdisk_context *cxt, size_t partnum, uint64_t *attrs);
+extern int fdisk_gpt_set_partition_attrs(struct fdisk_context *cxt, size_t partnum, uint64_t attrs);
+
+extern void fdisk_gpt_disable_relocation(struct fdisk_label *lb, int disable);
+extern void fdisk_gpt_enable_minimize(struct fdisk_label *lb, int enable);
+
+/**
+ * fdisk_labelitem_gpt:
+ * @GPT_LABELITEM_ID: GPT disklabel UUID (!= partition UUID)
+ * @GPT_LABELITEM_FIRSTLBA: First Usable LBA
+ * @GPT_LABELITEM_LASTLBA: Last Usable LBA
+ * @GPT_LABELITEM_ALTLBA: Alternative LBA (backup header LBA)
+ * @GPT_LABELITEM_ENTRIESLBA: Partitions entries array LBA
+ * @GPT_LABELITEM_ENTRIESALLOC: Number of allocated entries in entries array
+ * @GPT_LABELITEM_ENTRIESLASTLBA: Last LBA where is entries array
+ *
+ * GPT specific label items.
+ */
+enum fdisk_labelitem_gpt {
+ /* generic */
+ GPT_LABELITEM_ID = FDISK_LABELITEM_ID,
+ /* specific */
+ GPT_LABELITEM_FIRSTLBA = __FDISK_NLABELITEMS,
+ GPT_LABELITEM_LASTLBA,
+ GPT_LABELITEM_ALTLBA,
+ GPT_LABELITEM_ENTRIESLBA,
+ GPT_LABELITEM_ENTRIESALLOC,
+ GPT_LABELITEM_ENTRIESLASTLBA
+};
+
+/* script.c */
+struct fdisk_script *fdisk_new_script(struct fdisk_context *cxt);
+struct fdisk_script *fdisk_new_script_from_file(struct fdisk_context *cxt,
+ const char *filename);
+void fdisk_ref_script(struct fdisk_script *dp);
+void fdisk_unref_script(struct fdisk_script *dp);
+
+const char *fdisk_script_get_header(struct fdisk_script *dp, const char *name);
+int fdisk_script_set_header(struct fdisk_script *dp, const char *name, const char *data);
+struct fdisk_table *fdisk_script_get_table(struct fdisk_script *dp);
+int fdisk_script_set_table(struct fdisk_script *dp, struct fdisk_table *tb);
+int fdisk_script_get_nlines(struct fdisk_script *dp);
+int fdisk_script_has_force_label(struct fdisk_script *dp);
+
+int fdisk_script_set_userdata(struct fdisk_script *dp, void *data);
+void *fdisk_script_get_userdata(struct fdisk_script *dp);
+
+int fdisk_script_set_fgets(struct fdisk_script *dp,
+ char *(*fn_fgets)(struct fdisk_script *, char *, size_t, FILE *));
+int fdisk_script_read_context(struct fdisk_script *dp, struct fdisk_context *cxt);
+int fdisk_script_enable_json(struct fdisk_script *dp, int json);
+int fdisk_script_write_file(struct fdisk_script *dp, FILE *f);
+int fdisk_script_read_file(struct fdisk_script *dp, FILE *f);
+int fdisk_script_read_line(struct fdisk_script *dp, FILE *f, char *buf, size_t bufsz);
+
+int fdisk_set_script(struct fdisk_context *cxt, struct fdisk_script *dp);
+struct fdisk_script *fdisk_get_script(struct fdisk_context *cxt);
+
+int fdisk_apply_script_headers(struct fdisk_context *cxt, struct fdisk_script *dp);
+int fdisk_apply_script(struct fdisk_context *cxt, struct fdisk_script *dp);
+
+
+/* ask.c */
+#define fdisk_is_ask(a, x) (fdisk_ask_get_type(a) == FDISK_ASKTYPE_ ## x)
+
+int fdisk_set_ask(struct fdisk_context *cxt,
+ int (*ask_cb)(struct fdisk_context *, struct fdisk_ask *, void *),
+ void *data);
+
+
+void fdisk_ref_ask(struct fdisk_ask *ask);
+void fdisk_unref_ask(struct fdisk_ask *ask);
+const char *fdisk_ask_get_query(struct fdisk_ask *ask);
+int fdisk_ask_get_type(struct fdisk_ask *ask);
+const char *fdisk_ask_number_get_range(struct fdisk_ask *ask);
+uint64_t fdisk_ask_number_get_default(struct fdisk_ask *ask);
+uint64_t fdisk_ask_number_get_low(struct fdisk_ask *ask);
+uint64_t fdisk_ask_number_get_high(struct fdisk_ask *ask);
+uint64_t fdisk_ask_number_get_result(struct fdisk_ask *ask);
+int fdisk_ask_number_set_result(struct fdisk_ask *ask, uint64_t result);
+uint64_t fdisk_ask_number_get_base(struct fdisk_ask *ask);
+uint64_t fdisk_ask_number_get_unit(struct fdisk_ask *ask);
+int fdisk_ask_number_set_relative(struct fdisk_ask *ask, int relative);
+int fdisk_ask_number_is_wrap_negative(struct fdisk_ask *ask);
+int fdisk_ask_number_inchars(struct fdisk_ask *ask);
+int fdisk_ask_partnum(struct fdisk_context *cxt, size_t *partnum, int wantnew);
+
+int fdisk_ask_number(struct fdisk_context *cxt,
+ uintmax_t low,
+ uintmax_t dflt,
+ uintmax_t high,
+ const char *query,
+ uintmax_t *result);
+char *fdisk_ask_string_get_result(struct fdisk_ask *ask);
+int fdisk_ask_string_set_result(struct fdisk_ask *ask, char *result);
+int fdisk_ask_string(struct fdisk_context *cxt,
+ const char *query,
+ char **result);
+int fdisk_ask_yesno(struct fdisk_context *cxt,
+ const char *query,
+ int *result);
+int fdisk_ask_yesno_get_result(struct fdisk_ask *ask);
+int fdisk_ask_yesno_set_result(struct fdisk_ask *ask, int result);
+int fdisk_ask_menu_get_default(struct fdisk_ask *ask);
+int fdisk_ask_menu_set_result(struct fdisk_ask *ask, int key);
+int fdisk_ask_menu_get_result(struct fdisk_ask *ask, int *key);
+int fdisk_ask_menu_get_item(struct fdisk_ask *ask, size_t idx, int *key,
+ const char **name, const char **desc);
+size_t fdisk_ask_menu_get_nitems(struct fdisk_ask *ask);
+int fdisk_ask_print_get_errno(struct fdisk_ask *ask);
+const char *fdisk_ask_print_get_mesg(struct fdisk_ask *ask);
+
+int fdisk_info(struct fdisk_context *cxt, const char *fmt, ...)
+ __attribute__ ((__format__ (__printf__, 2, 3)));
+int fdisk_warn(struct fdisk_context *cxt, const char *fmt, ...)
+ __attribute__ ((__format__ (__printf__, 2, 3)));
+int fdisk_warnx(struct fdisk_context *cxt, const char *fmt, ...)
+ __attribute__ ((__format__ (__printf__, 2, 3)));
+
+/* utils.h */
+extern char *fdisk_partname(const char *dev, size_t partno);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _LIBFDISK_H */
diff --git a/libfdisk/src/libfdisk.sym b/libfdisk/src/libfdisk.sym
new file mode 100644
index 0000000..71de805
--- /dev/null
+++ b/libfdisk/src/libfdisk.sym
@@ -0,0 +1,322 @@
+/*
+ * The symbol versioning ensures that a new application requiring symbol foo;
+ * can't run with old library.so not providing foo.
+
+ * Version info can't enforce this since we never change the SONAME.
+ *
+ * Copyright (C) 2014-2015 Karel Zak <kzak@redhat.com>
+ */
+FDISK_2.26 {
+global:
+ fdisk_add_partition;
+ fdisk_align_lba;
+ fdisk_align_lba_in_range;
+ fdisk_apply_script;
+ fdisk_apply_script_headers;
+ fdisk_apply_table;
+ fdisk_ask_get_query;
+ fdisk_ask_get_type;
+ fdisk_ask_menu_get_default;
+ fdisk_ask_menu_get_item;
+ fdisk_ask_menu_get_nitems;
+ fdisk_ask_menu_get_result;
+ fdisk_ask_menu_set_result;
+ fdisk_ask_number;
+ fdisk_ask_number_get_base;
+ fdisk_ask_number_get_default;
+ fdisk_ask_number_get_high;
+ fdisk_ask_number_get_low;
+ fdisk_ask_number_get_range;
+ fdisk_ask_number_get_result;
+ fdisk_ask_number_get_unit;
+ fdisk_ask_number_inchars;
+ fdisk_ask_number_set_relative;
+ fdisk_ask_number_set_result;
+ fdisk_ask_partnum;
+ fdisk_ask_print_get_errno;
+ fdisk_ask_print_get_mesg;
+ fdisk_ask_string;
+ fdisk_ask_string_get_result;
+ fdisk_ask_string_set_result;
+ fdisk_ask_yesno;
+ fdisk_ask_yesno_get_result;
+ fdisk_ask_yesno_set_result;
+ fdisk_assign_device;
+ fdisk_bsd_edit_disklabel;
+ fdisk_bsd_link_partition;
+ fdisk_bsd_write_bootstrap;
+ fdisk_copy_parttype;
+ fdisk_create_disklabel;
+ fdisk_deassign_device;
+ fdisk_delete_all_partitions;
+ fdisk_delete_partition;
+ fdisk_dos_enable_compatible;
+ fdisk_dos_is_compatible;
+ fdisk_dos_move_begin;
+ fdisk_enable_details;
+ fdisk_enable_listonly;
+ fdisk_field_get_id;
+ fdisk_field_get_name;
+ fdisk_field_get_width;
+ fdisk_field_is_number;
+ fdisk_free_iter;
+ fdisk_get_alignment_offset;
+ fdisk_get_devfd;
+ fdisk_get_devname;
+ fdisk_get_disklabel_id;
+ fdisk_get_first_lba;
+ fdisk_get_freespaces;
+ fdisk_get_geom_cylinders;
+ fdisk_get_geom_heads;
+ fdisk_get_geom_sectors;
+ fdisk_get_grain_size;
+ fdisk_get_label;
+ fdisk_get_last_lba;
+ fdisk_get_library_features;
+ fdisk_get_library_version;
+ fdisk_get_minimal_iosize;
+ fdisk_get_nlabels;
+ fdisk_get_npartitions;
+ fdisk_get_nsectors;
+ fdisk_get_optimal_iosize;
+ fdisk_get_parent;
+ fdisk_get_partition;
+ fdisk_get_partitions;
+ fdisk_get_physector_size;
+ fdisk_get_script;
+ fdisk_get_sector_size;
+ fdisk_get_size_unit;
+ fdisk_get_unit;
+ fdisk_get_units_per_sector;
+ fdisk_gpt_is_hybrid;
+ fdisk_has_label;
+ fdisk_has_user_device_properties;
+ fdisk_info;
+ fdisk_init_debug;
+ fdisk_is_details;
+ fdisk_is_labeltype;
+ fdisk_is_listonly;
+ fdisk_is_partition_used;
+ fdisk_is_readonly;
+ fdisk_iter_get_direction;
+ fdisk_label_get_field;
+ fdisk_label_get_field_by_name;
+ fdisk_label_get_fields_ids;
+ fdisk_label_get_name;
+ fdisk_label_get_nparttypes;
+ fdisk_label_get_parttype;
+ fdisk_label_get_parttype_from_code;
+ fdisk_label_get_parttype_from_string;
+ fdisk_label_get_type;
+ fdisk_label_has_code_parttypes;
+ fdisk_label_is_changed;
+ fdisk_label_is_disabled;
+ fdisk_label_parse_parttype;
+ fdisk_label_require_geometry;
+ fdisk_label_set_changed;
+ fdisk_label_set_disabled;
+ fdisk_lba_is_phy_aligned;
+ fdisk_list_disklabel;
+ fdisk_locate_disklabel;
+ fdisk_new_context;
+ fdisk_new_iter;
+ fdisk_new_nested_context;
+ fdisk_new_partition;
+ fdisk_new_parttype;
+ fdisk_new_script;
+ fdisk_new_script_from_file;
+ fdisk_new_table;
+ fdisk_new_unknown_parttype;
+ fdisk_next_label;
+ fdisk_override_geometry;
+ fdisk_parse_version_string;
+ fdisk_partition_cmp_partno;
+ fdisk_partition_cmp_start;
+ fdisk_partition_end_follow_default;
+ fdisk_partition_end_is_default;
+ fdisk_partition_get_attrs;
+ fdisk_partition_get_end;
+ fdisk_partition_get_name;
+ fdisk_partition_get_parent;
+ fdisk_partition_get_partno;
+ fdisk_partition_get_size;
+ fdisk_partition_get_start;
+ fdisk_partition_get_type;
+ fdisk_partition_get_uuid;
+ fdisk_partition_has_end;
+ fdisk_partition_has_partno;
+ fdisk_partition_has_size;
+ fdisk_partition_has_start;
+ fdisk_partition_is_bootable;
+ fdisk_partition_is_container;
+ fdisk_partition_is_freespace;
+ fdisk_partition_is_nested;
+ fdisk_partition_is_used;
+ fdisk_partition_is_wholedisk;
+ fdisk_partition_next_partno;
+ fdisk_partition_partno_follow_default;
+ fdisk_partition_set_attrs;
+ fdisk_partition_set_name;
+ fdisk_partition_set_partno;
+ fdisk_partition_set_size;
+ fdisk_partition_set_start;
+ fdisk_partition_set_type;
+ fdisk_partition_set_uuid;
+ fdisk_partition_size_explicit;
+ fdisk_partition_start_follow_default;
+ fdisk_partition_start_is_default;
+ fdisk_partition_to_string;
+ fdisk_partition_unset_partno;
+ fdisk_partition_unset_size;
+ fdisk_partition_unset_start;
+ fdisk_partname;
+ fdisk_parttype_get_code;
+ fdisk_parttype_get_name;
+ fdisk_parttype_get_string;
+ fdisk_parttype_is_unknown;
+ fdisk_parttype_set_code;
+ fdisk_parttype_set_name;
+ fdisk_parttype_set_typestr;
+ fdisk_ref_ask;
+ fdisk_ref_context;
+ fdisk_ref_partition;
+ fdisk_ref_parttype;
+ fdisk_ref_script;
+ fdisk_ref_table;
+ fdisk_reorder_partitions;
+ fdisk_reread_partition_table;
+ fdisk_reset_alignment;
+ fdisk_reset_device_properties;
+ fdisk_reset_iter;
+ fdisk_reset_partition;
+ fdisk_reset_table;
+ fdisk_save_user_geometry;
+ fdisk_save_user_sector_size;
+ fdisk_script_get_header;
+ fdisk_script_get_nlines;
+ fdisk_script_get_table;
+ fdisk_script_read_context;
+ fdisk_script_read_file;
+ fdisk_script_read_line;
+ fdisk_script_set_header;
+ fdisk_script_write_file;
+ fdisk_set_ask;
+ fdisk_set_disklabel_id;
+ fdisk_set_first_lba;
+ fdisk_set_last_lba;
+ fdisk_set_partition;
+ fdisk_set_partition_type;
+ fdisk_set_script;
+ fdisk_set_size_unit;
+ fdisk_set_unit;
+ fdisk_sgi_create_info;
+ fdisk_sgi_set_bootfile;
+ fdisk_sun_set_alt_cyl;
+ fdisk_sun_set_ilfact;
+ fdisk_sun_set_pcylcount;
+ fdisk_sun_set_rspeed;
+ fdisk_sun_set_xcyl;
+ fdisk_table_add_partition;
+ fdisk_table_get_nents;
+ fdisk_table_get_partition;
+ fdisk_table_is_empty;
+ fdisk_table_next_partition;
+ fdisk_table_remove_partition;
+ fdisk_table_sort_partitions;
+ fdisk_table_wrong_order;
+ fdisk_toggle_partition_flag;
+ fdisk_unref_ask;
+ fdisk_unref_context;
+ fdisk_unref_partition;
+ fdisk_unref_parttype;
+ fdisk_unref_script;
+ fdisk_unref_table;
+ fdisk_use_cylinders;
+ fdisk_verify_disklabel;
+ fdisk_warn;
+ fdisk_warnx;
+ fdisk_write_disklabel;
+local:
+ *;
+};
+
+FDISK_2.27 {
+ fdisk_enable_bootbits_protection;
+ fdisk_gpt_get_partition_attrs;
+ fdisk_gpt_set_partition_attrs;
+ fdisk_has_protected_bootbits;
+ fdisk_label_get_fields_ids_all;
+ fdisk_script_get_userdata;
+ fdisk_script_set_fgets;
+ fdisk_script_set_userdata;
+ fdisk_table_get_partition_by_partno;
+ fdisk_get_disklabel_item;
+ fdisk_script_enable_json;
+} FDISK_2.26;
+
+FDISK_2.28 {
+ fdisk_enable_wipe;
+ fdisk_get_collision;
+ fdisk_has_wipe;
+} FDISK_2.27;
+
+FDISK_2.29 {
+ fdisk_wipe_partition;
+ fdisk_new_labelitem;
+ fdisk_ref_labelitem;
+ fdisk_reset_labelitem;
+ fdisk_unref_labelitem;
+ fdisk_labelitem_get_name;
+ fdisk_labelitem_get_id;
+ fdisk_labelitem_get_data_u64;
+ fdisk_labelitem_get_data_string;
+ fdisk_labelitem_is_string;
+ fdisk_labelitem_is_number;
+ fdisk_gpt_set_npartitions;
+} FDISK_2.28;
+
+
+FDISK_2.30 {
+ fdisk_script_has_force_label;
+ fdisk_is_regfile;
+ fdisk_is_ptcollision;
+ fdisk_partition_has_wipe;
+} FDISK_2.29;
+
+FDISK_2.31 {
+ fdisk_reassign_device;
+ fdisk_device_is_used;
+ fdisk_reread_changes;
+ fdisk_disable_dialogs;
+ fdisk_has_dialogs;
+ fdisk_save_user_grain;
+} FDISK_2.30;
+
+FDISK_2.32 {
+ fdisk_label_get_geomrange_sectors;
+ fdisk_label_get_geomrange_heads;
+ fdisk_label_get_geomrange_cylinders;
+} FDISK_2.31;
+
+FDISK_2.33 {
+ fdisk_ask_number_is_wrap_negative;
+ fdisk_get_devmodel;
+ fdisk_get_devno;
+} FDISK_2.32;
+
+FDISK_2.35 {
+ fdisk_script_set_table;
+ fdisk_assign_device_by_fd;
+} FDISK_2.33;
+FDISK_2.36 {
+ fdisk_set_disklabel_id_from_string;
+ fdisk_gpt_disable_relocation;
+ fdisk_gpt_enable_minimize;
+ fdisk_label_has_parttypes_shortcuts;
+ fdisk_label_advparse_parttype;
+ fdisk_label_get_parttype_shortcut;
+} FDISK_2.35;
+
+FDISK_2.38 {
+ fdisk_dos_fix_chs;
+} FDISK_2.36;
diff --git a/libfdisk/src/partition.c b/libfdisk/src/partition.c
new file mode 100644
index 0000000..bf3afad
--- /dev/null
+++ b/libfdisk/src/partition.c
@@ -0,0 +1,1627 @@
+
+#include "c.h"
+#include "strutils.h"
+
+#ifdef HAVE_LIBBLKID
+# include <blkid.h>
+#endif
+
+#include "fdiskP.h"
+
+/**
+ * SECTION: partition
+ * @title: Partition
+ * @short_description: generic label independent partition abstraction
+ *
+ * The fdisk_partition provides label independent abstraction. The partitions
+ * are not directly connected with partition table (label) data. Any change to
+ * fdisk_partition does not affects in-memory or on-disk label data.
+ *
+ * The fdisk_partition is possible to use as a template for
+ * fdisk_add_partition() or fdisk_set_partition() operations.
+ */
+
+static void init_partition(struct fdisk_partition *pa)
+{
+ FDISK_INIT_UNDEF(pa->size);
+ FDISK_INIT_UNDEF(pa->start);
+ FDISK_INIT_UNDEF(pa->partno);
+ FDISK_INIT_UNDEF(pa->parent_partno);
+ FDISK_INIT_UNDEF(pa->boot);
+
+ INIT_LIST_HEAD(&pa->parts);
+}
+
+/**
+ * fdisk_new_partition:
+ *
+ * Returns: new instance.
+ */
+struct fdisk_partition *fdisk_new_partition(void)
+{
+ struct fdisk_partition *pa = calloc(1, sizeof(*pa));
+ if (!pa)
+ return NULL;
+
+ pa->refcount = 1;
+ init_partition(pa);
+ DBG(PART, ul_debugobj(pa, "alloc"));
+ return pa;
+}
+
+/**
+ * fdisk_reset_partition:
+ * @pa: partition
+ *
+ * Resets partition content.
+ */
+void fdisk_reset_partition(struct fdisk_partition *pa)
+{
+ int ref;
+
+ if (!pa)
+ return;
+
+ DBG(PART, ul_debugobj(pa, "reset"));
+ ref = pa->refcount;
+
+ fdisk_unref_parttype(pa->type);
+ free(pa->name);
+ free(pa->uuid);
+ free(pa->attrs);
+ free(pa->fstype);
+ free(pa->fsuuid);
+ free(pa->fslabel);
+ free(pa->start_chs);
+ free(pa->end_chs);
+
+ memset(pa, 0, sizeof(*pa));
+ pa->refcount = ref;
+
+ init_partition(pa);
+}
+
+static struct fdisk_partition *__copy_partition(struct fdisk_partition *o)
+{
+ struct fdisk_partition *n = fdisk_new_partition();
+ int rc;
+
+ if (!n)
+ return NULL;
+
+ memcpy(n, o, sizeof(*n));
+
+ /* do not copy reference to lists, etc.*/
+ n->refcount = 1;
+ INIT_LIST_HEAD(&n->parts);
+
+ if (n->type)
+ fdisk_ref_parttype(n->type);
+
+ /* note that strdup_between_structs() deallocates destination pointer,
+ * so make sure it's NULL as we call memcpy() before ... */
+ n->name = NULL;
+ rc = strdup_between_structs(n, o, name);
+
+ n->uuid = NULL;
+ if (!rc)
+ rc = strdup_between_structs(n, o, uuid);
+ n->attrs = NULL;
+ if (!rc)
+ rc = strdup_between_structs(n, o, attrs);
+ n->fstype = NULL;
+ if (!rc)
+ rc = strdup_between_structs(n, o, fstype);
+ n->fsuuid = NULL;
+ if (!rc)
+ rc = strdup_between_structs(n, o, fsuuid);
+ n->fslabel = NULL;
+ if (!rc)
+ rc = strdup_between_structs(n, o, fslabel);
+ n->start_chs = NULL;
+ if (!rc)
+ rc = strdup_between_structs(n, o, start_chs);
+ n->end_chs = NULL;
+ if (!rc)
+ rc = strdup_between_structs(n, o, end_chs);
+
+ if (rc) {
+ fdisk_unref_partition(n);
+ n = NULL;
+ }
+ return n;
+}
+
+/**
+ * fdisk_ref_partition:
+ * @pa: partition pointer
+ *
+ * Increments reference counter.
+ */
+void fdisk_ref_partition(struct fdisk_partition *pa)
+{
+ if (pa)
+ pa->refcount++;
+}
+
+/**
+ * fdisk_unref_partition:
+ * @pa: partition pointer
+ *
+ * Decrements reference counter, on zero the @pa is automatically
+ * deallocated.
+ */
+void fdisk_unref_partition(struct fdisk_partition *pa)
+{
+ if (!pa)
+ return;
+
+ pa->refcount--;
+ if (pa->refcount <= 0) {
+ list_del(&pa->parts);
+ fdisk_reset_partition(pa);
+ DBG(PART, ul_debugobj(pa, "free"));
+ free(pa);
+ }
+}
+
+/**
+ * fdisk_partition_set_start:
+ * @pa: partition
+ * @off: offset in sectors, maximal is UINT64_MAX-1
+ *
+ * Note that zero is valid offset too. Use fdisk_partition_unset_start() to
+ * undefine the offset.
+ *
+ * Returns: 0 on success, <0 on error.
+ */
+int fdisk_partition_set_start(struct fdisk_partition *pa, fdisk_sector_t off)
+{
+ if (!pa)
+ return -EINVAL;
+ if (FDISK_IS_UNDEF(off))
+ return -ERANGE;
+ pa->start = off;
+ pa->fs_probed = 0;
+ return 0;
+}
+
+/**
+ * fdisk_partition_unset_start:
+ * @pa: partition
+ *
+ * Sets the size as undefined. See fdisk_partition_has_start().
+ *
+ * Returns: 0 on success, <0 on error.
+ */
+int fdisk_partition_unset_start(struct fdisk_partition *pa)
+{
+ if (!pa)
+ return -EINVAL;
+ FDISK_INIT_UNDEF(pa->start);
+ pa->fs_probed = 0;
+ return 0;
+}
+
+/**
+ * fdisk_partition_get_start:
+ * @pa: partition
+ *
+ * The zero is also valid offset. The function may return random undefined
+ * value when start offset is undefined (for example after
+ * fdisk_partition_unset_start()). Always use fdisk_partition_has_start() to be
+ * sure that you work with valid numbers.
+ *
+ * Returns: start offset in sectors
+ */
+fdisk_sector_t fdisk_partition_get_start(struct fdisk_partition *pa)
+{
+ return pa->start;
+}
+
+/**
+ * fdisk_partition_has_start:
+ * @pa: partition
+ *
+ * Returns: 1 or 0
+ */
+int fdisk_partition_has_start(struct fdisk_partition *pa)
+{
+ return pa && !FDISK_IS_UNDEF(pa->start);
+}
+
+
+/**
+ * fdisk_partition_cmp_start:
+ * @a: partition
+ * @b: partition
+ *
+ * Compares partitions according to start offset, See fdisk_table_sort_partitions().
+ *
+ * Return: 0 if the same, <0 if @b greater, >0 if @a greater.
+ */
+int fdisk_partition_cmp_start(struct fdisk_partition *a,
+ struct fdisk_partition *b)
+{
+ int no_a = FDISK_IS_UNDEF(a->start),
+ no_b = FDISK_IS_UNDEF(b->start);
+
+ if (no_a && no_b)
+ return 0;
+ if (no_a)
+ return -1;
+ if (no_b)
+ return 1;
+
+ return cmp_numbers(a->start, b->start);
+}
+
+/**
+ * fdisk_partition_start_follow_default
+ * @pa: partition
+ * @enable: 0|1
+ *
+ * When @pa used as a template for fdisk_add_partition() when force label driver
+ * to use the first possible space for the new partition.
+ *
+ * Returns: 0 on success, <0 on error.
+ */
+int fdisk_partition_start_follow_default(struct fdisk_partition *pa, int enable)
+{
+ if (!pa)
+ return -EINVAL;
+ pa->start_follow_default = enable ? 1 : 0;
+ return 0;
+}
+
+/**
+ * fdisk_partition_start_is_default:
+ * @pa: partition
+ *
+ * See fdisk_partition_start_follow_default().
+ *
+ * Returns: 1 if the partition follows default
+ */
+int fdisk_partition_start_is_default(struct fdisk_partition *pa)
+{
+ assert(pa);
+ return pa->start_follow_default;
+}
+
+/**
+ * fdisk_partition_set_size:
+ * @pa: partition
+ * @sz: size in sectors, maximal is UIN64_MAX-1
+ *
+ * Note that zero is valid size too. Use fdisk_partition_unset_size() to
+ * undefine the size.
+ *
+ * Returns: 0 on success, <0 on error.
+ */
+int fdisk_partition_set_size(struct fdisk_partition *pa, fdisk_sector_t sz)
+{
+ if (!pa)
+ return -EINVAL;
+ if (FDISK_IS_UNDEF(sz))
+ return -ERANGE;
+ pa->size = sz;
+ pa->fs_probed = 0;
+ return 0;
+}
+
+/**
+ * fdisk_partition_unset_size:
+ * @pa: partition
+ *
+ * Sets the size as undefined. See fdisk_partition_has_size().
+ *
+ * Returns: 0 on success, <0 on error.
+ */
+int fdisk_partition_unset_size(struct fdisk_partition *pa)
+{
+ if (!pa)
+ return -EINVAL;
+ FDISK_INIT_UNDEF(pa->size);
+ pa->fs_probed = 0;
+ return 0;
+}
+
+/**
+ * fdisk_partition_get_size:
+ * @pa: partition
+ *
+ * The zero is also valid size. The function may return random undefined
+ * value when size is undefined (for example after fdisk_partition_unset_size()).
+ * Always use fdisk_partition_has_size() to be sure that you work with valid
+ * numbers.
+ *
+ * Returns: size offset in sectors
+ */
+fdisk_sector_t fdisk_partition_get_size(struct fdisk_partition *pa)
+{
+ return pa->size;
+}
+
+/**
+ * fdisk_partition_has_size:
+ * @pa: partition
+ *
+ * Returns: 1 or 0
+ */
+int fdisk_partition_has_size(struct fdisk_partition *pa)
+{
+ return pa && !FDISK_IS_UNDEF(pa->size);
+}
+
+/**
+ * fdisk_partition_size_explicit:
+ * @pa: partition
+ * @enable: 0|1
+ *
+ * By default libfdisk aligns the size when add the new partition (by
+ * fdisk_add_partition()). If you want to disable this functionality use
+ * @enable = 1.
+ *
+ * Returns: 0 on success, <0 on error.
+ */
+int fdisk_partition_size_explicit(struct fdisk_partition *pa, int enable)
+{
+ if (!pa)
+ return -EINVAL;
+ pa->size_explicit = enable ? 1 : 0;
+ return 0;
+}
+
+/**
+ * fdisk_partition_set_partno:
+ * @pa: partition
+ * @num: partition number (0 is the first partition, maximal is SIZE_MAX-1)
+ *
+ * Note that zero is valid partno too. Use fdisk_partition_unset_partno() to
+ * undefine the partno.
+ *
+ * Returns: 0 on success, <0 on error.
+ */
+int fdisk_partition_set_partno(struct fdisk_partition *pa, size_t num)
+{
+ if (!pa)
+ return -EINVAL;
+ if (FDISK_IS_UNDEF(num))
+ return -ERANGE;
+ pa->partno = num;
+ return 0;
+}
+
+/**
+ * fdisk_partition_unset_partno:
+ * @pa: partition
+ *
+ * Sets the partno as undefined. See fdisk_partition_has_partno().
+ *
+ * Returns: 0 on success, <0 on error.
+ */
+int fdisk_partition_unset_partno(struct fdisk_partition *pa)
+{
+ if (!pa)
+ return -EINVAL;
+ FDISK_INIT_UNDEF(pa->partno);
+ return 0;
+}
+
+/**
+ * fdisk_partition_get_partno:
+ * @pa: partition
+ *
+ * The zero is also valid partition number. The function may return random
+ * value when partno is undefined (for example after fdisk_partition_unset_partno()).
+ * Always use fdisk_partition_has_partno() to be sure that you work with valid
+ * numbers.
+ *
+ * Returns: partition number (0 is the first partition)
+ */
+size_t fdisk_partition_get_partno(struct fdisk_partition *pa)
+{
+ return pa->partno;
+}
+
+/**
+ * fdisk_partition_has_partno:
+ * @pa: partition
+ *
+ * Returns: 1 or 0
+ */
+int fdisk_partition_has_partno(struct fdisk_partition *pa)
+{
+ return pa && !FDISK_IS_UNDEF(pa->partno);
+}
+
+
+/**
+ * fdisk_partition_cmp_partno:
+ * @a: partition
+ * @b: partition
+ *
+ * Compares partitions according to partition number See fdisk_table_sort_partitions().
+ *
+ * Return: 0 if the same, <0 if @b greater, >0 if @a greater.
+ */
+int fdisk_partition_cmp_partno(struct fdisk_partition *a,
+ struct fdisk_partition *b)
+{
+ return a->partno - b->partno;
+}
+
+/**
+ * fdisk_partition_partno_follow_default
+ * @pa: partition
+ * @enable: 0|1
+ *
+ * When @pa used as a template for fdisk_add_partition() when force label driver
+ * to add a new partition to the default (next) position.
+ *
+ * Returns: 0 on success, <0 on error.
+ */
+int fdisk_partition_partno_follow_default(struct fdisk_partition *pa, int enable)
+{
+ if (!pa)
+ return -EINVAL;
+ pa->partno_follow_default = enable ? 1 : 0;
+ return 0;
+}
+
+/**
+ * fdisk_partition_set_type:
+ * @pa: partition
+ * @type: partition type
+ *
+ * Sets partition type.
+ *
+ * Returns: 0 on success, <0 on error.
+ */
+int fdisk_partition_set_type(struct fdisk_partition *pa,
+ struct fdisk_parttype *type)
+{
+ if (!pa)
+ return -EINVAL;
+
+ fdisk_ref_parttype(type);
+ fdisk_unref_parttype(pa->type);
+ pa->type = type;
+
+ return 0;
+}
+
+/**
+ * fdisk_partition_get_type:
+ * @pa: partition
+ *
+ * Returns: pointer to partition type.
+ */
+struct fdisk_parttype *fdisk_partition_get_type(struct fdisk_partition *pa)
+{
+ return pa ? pa->type : NULL;
+}
+
+/**
+ * fdisk_partition_set_name:
+ * @pa: partition
+ * @name: partition name
+ *
+ * Returns: 0 on success, <0 on error.
+ */
+int fdisk_partition_set_name(struct fdisk_partition *pa, const char *name)
+{
+ if (!pa)
+ return -EINVAL;
+ return strdup_to_struct_member(pa, name, name);
+}
+
+/**
+ * fdisk_partition_get_name:
+ * @pa: partition
+ *
+ * Returns: partition name
+ */
+const char *fdisk_partition_get_name(struct fdisk_partition *pa)
+{
+ return pa ? pa->name : NULL;
+}
+
+/**
+ * fdisk_partition_set_uuid:
+ * @pa: partition
+ * @uuid: UUID of the partition
+ *
+ * Returns: 0 on success, <0 on error.
+ */
+int fdisk_partition_set_uuid(struct fdisk_partition *pa, const char *uuid)
+{
+ if (!pa)
+ return -EINVAL;
+ return strdup_to_struct_member(pa, uuid, uuid);
+}
+
+/**
+ * fdisk_partition_has_end:
+ * @pa: partition
+ *
+ * Returns: 1 if the partition has defined last sector
+ */
+int fdisk_partition_has_end(struct fdisk_partition *pa)
+{
+ return pa && !FDISK_IS_UNDEF(pa->start) && !FDISK_IS_UNDEF(pa->size);
+}
+
+/**
+ * fdisk_partition_get_end:
+ * @pa: partition
+ *
+ * This function may returns absolute non-sense, always check
+ * fdisk_partition_has_end().
+ *
+ * Note that partition end is defined by fdisk_partition_set_start() and
+ * fdisk_partition_set_size().
+ *
+ * Returns: last partition sector LBA.
+ */
+fdisk_sector_t fdisk_partition_get_end(struct fdisk_partition *pa)
+{
+ return pa->start + pa->size - (pa->size == 0 ? 0 : 1);
+}
+
+/**
+ * fdisk_partition_end_follow_default
+ * @pa: partition
+ * @enable: 0|1
+ *
+ * When @pa used as a template for fdisk_add_partition() when force label
+ * driver to use all the possible space for the new partition.
+ *
+ * Returns: 0 on success, <0 on error.
+ */
+int fdisk_partition_end_follow_default(struct fdisk_partition *pa, int enable)
+{
+ if (!pa)
+ return -EINVAL;
+ pa->end_follow_default = enable ? 1 : 0;
+ return 0;
+}
+
+/**
+ * fdisk_partition_end_is_default:
+ * @pa: partition
+ *
+ * Returns: 1 if the partition follows default
+ */
+int fdisk_partition_end_is_default(struct fdisk_partition *pa)
+{
+ assert(pa);
+ return pa->end_follow_default;
+}
+
+/**
+ * fdisk_partition_get_uuid:
+ * @pa: partition
+ *
+ * Returns: partition UUID as string
+ */
+const char *fdisk_partition_get_uuid(struct fdisk_partition *pa)
+{
+ return pa ? pa->uuid : NULL;
+}
+
+/**
+ * fdisk_partition_get_attrs:
+ * @pa: partition
+ *
+ * Returns: partition attributes in string format
+ */
+const char *fdisk_partition_get_attrs(struct fdisk_partition *pa)
+{
+ return pa ? pa->attrs : NULL;
+}
+
+/**
+ * fdisk_partition_set_attrs:
+ * @pa: partition
+ * @attrs: attributes
+ *
+ * Sets @attrs to @pa.
+ *
+ * Return: 0 on success, <0 on error.
+ */
+int fdisk_partition_set_attrs(struct fdisk_partition *pa, const char *attrs)
+{
+ if (!pa)
+ return -EINVAL;
+ return strdup_to_struct_member(pa, attrs, attrs);
+}
+
+/**
+ * fdisk_partition_is_nested:
+ * @pa: partition
+ *
+ * Returns: 1 if the partition is nested (e.g. MBR logical partition)
+ */
+int fdisk_partition_is_nested(struct fdisk_partition *pa)
+{
+ return pa && !FDISK_IS_UNDEF(pa->parent_partno);
+}
+
+/**
+ * fdisk_partition_is_container:
+ * @pa: partition
+ *
+ * Returns: 1 if the partition is container (e.g. MBR extended partition)
+ */
+int fdisk_partition_is_container(struct fdisk_partition *pa)
+{
+ return pa && pa->container;
+}
+
+/**
+ * fdisk_partition_get_parent:
+ * @pa: partition
+ * @parent: parent parno
+ *
+ * Returns: returns devno of the parent
+ */
+int fdisk_partition_get_parent(struct fdisk_partition *pa, size_t *parent)
+{
+ if (pa && parent)
+ *parent = pa->parent_partno;
+ else
+ return -EINVAL;
+ return 0;
+}
+
+/**
+ * fdisk_partition_is_used:
+ * @pa: partition
+ *
+ * Returns: 1 if the partition points to some area
+ */
+int fdisk_partition_is_used(struct fdisk_partition *pa)
+{
+ return pa && pa->used;
+}
+
+/**
+ * fdisk_partition_is_bootable:
+ * @pa: partition
+ *
+ * Returns: 1 if the partition has enabled boot flag
+ */
+int fdisk_partition_is_bootable(struct fdisk_partition *pa)
+{
+ return pa && pa->boot == 1;
+}
+
+/**
+ * fdisk_partition_is_freespace:
+ * @pa: partition
+ *
+ * Returns: 1 if @pa points to freespace
+ */
+int fdisk_partition_is_freespace(struct fdisk_partition *pa)
+{
+ return pa && pa->freespace;
+}
+
+/**
+ * fdisk_partition_is_wholedisk:
+ * @pa: partition
+ *
+ * Returns: 1 if the partition is special whole-disk (e.g. SUN) partition
+ */
+int fdisk_partition_is_wholedisk(struct fdisk_partition *pa)
+{
+ return pa && pa->wholedisk;
+}
+
+/**
+ * fdisk_partition_next_partno:
+ * @pa: partition
+ * @cxt: context
+ * @n: returns partition number
+ *
+ * If @pa specified and partno-follow-default (see fdisk_partition_partno_follow_default())
+ * enabled then returns next expected partno or -ERANGE on error.
+ *
+ * If @pa is NULL, or @pa does not specify any semantic for the next partno
+ * then use Ask API to ask user for the next partno. In this case returns 1 if
+ * no free partition available. If fdisk dialogs are disabled then returns -EINVAL.
+ *
+ * Returns: 0 on success, <0 on error, or 1 for non-free partno by Ask API.
+ */
+int fdisk_partition_next_partno(
+ struct fdisk_partition *pa,
+ struct fdisk_context *cxt,
+ size_t *n)
+{
+ if (!cxt || !n)
+ return -EINVAL;
+
+ if (pa && pa->partno_follow_default) {
+ size_t i;
+
+ DBG(PART, ul_debugobj(pa, "next partno (follow default)"));
+
+ for (i = 0; i < cxt->label->nparts_max; i++) {
+ if (!fdisk_is_partition_used(cxt, i)) {
+ *n = i;
+ return 0;
+ }
+ }
+ return -ERANGE;
+
+ }
+
+ if (pa && fdisk_partition_has_partno(pa)) {
+
+ DBG(PART, ul_debugobj(pa, "next partno (specified=%zu)", pa->partno));
+
+ if (pa->partno >= cxt->label->nparts_max ||
+ fdisk_is_partition_used(cxt, pa->partno))
+ return -ERANGE;
+ *n = pa->partno;
+ return 0;
+
+ }
+
+ if (fdisk_has_dialogs(cxt))
+ return fdisk_ask_partnum(cxt, n, 1);
+
+ return -EINVAL;
+}
+
+static int probe_partition_content(struct fdisk_context *cxt, struct fdisk_partition *pa)
+{
+ int rc = 1; /* nothing */
+
+ DBG(PART, ul_debugobj(pa, "start probe #%zu partition [cxt %p] >>>", pa->partno, cxt));
+
+ /* zeroize the current setting */
+ strdup_to_struct_member(pa, fstype, NULL);
+ strdup_to_struct_member(pa, fsuuid, NULL);
+ strdup_to_struct_member(pa, fslabel, NULL);
+
+ if (!fdisk_partition_has_start(pa) ||
+ !fdisk_partition_has_size(pa))
+ goto done;
+
+#ifdef HAVE_LIBBLKID
+ else {
+ uintmax_t start, size;
+
+ blkid_probe pr = blkid_new_probe();
+ if (!pr)
+ goto done;
+
+ DBG(PART, ul_debugobj(pa, "blkid prober: %p", pr));
+
+ blkid_probe_enable_superblocks(pr, 1);
+ blkid_probe_set_superblocks_flags(pr,
+ BLKID_SUBLKS_MAGIC |
+ BLKID_SUBLKS_TYPE |
+ BLKID_SUBLKS_LABEL |
+ BLKID_SUBLKS_UUID |
+ BLKID_SUBLKS_BADCSUM);
+
+ start = fdisk_partition_get_start(pa) * fdisk_get_sector_size(cxt);
+ size = fdisk_partition_get_size(pa) * fdisk_get_sector_size(cxt);
+
+ if (blkid_probe_set_device(pr, cxt->dev_fd, start, size) == 0
+ && blkid_do_fullprobe(pr) == 0) {
+
+ const char *data;
+ rc = 0;
+
+ if (!blkid_probe_lookup_value(pr, "TYPE", &data, NULL))
+ rc = strdup_to_struct_member(pa, fstype, data);
+
+ if (!rc && !blkid_probe_lookup_value(pr, "LABEL", &data, NULL))
+ rc = strdup_to_struct_member(pa, fslabel, data);
+
+ if (!rc && !blkid_probe_lookup_value(pr, "UUID", &data, NULL))
+ rc = strdup_to_struct_member(pa, fsuuid, data);
+ }
+
+ blkid_free_probe(pr);
+ pa->fs_probed = 1;
+ }
+#endif /* HAVE_LIBBLKID */
+
+done:
+ DBG(PART, ul_debugobj(pa, "<<< end probe #%zu partition[cxt %p, rc=%d]", pa->partno, cxt, rc));
+ return rc;
+}
+
+/**
+ * fdisk_partition_to_string:
+ * @pa: partition
+ * @cxt: context
+ * @id: field (FDISK_FIELD_*)
+ * @data: returns string with allocated data
+ *
+ * Returns info about partition converted to printable string.
+ *
+ * For example
+ * <informalexample>
+ * <programlisting>
+ * struct fdisk_partition *pa;
+ *
+ * fdisk_get_partition(cxt, 0, &pa);
+ * fdisk_partition_to_string(pa, FDISK_FIELD_UUID, &data);
+ * printf("first partition uuid: %s\n", data);
+ * free(data);
+ * fdisk_unref_partition(pa);
+ * </programlisting>
+ * </informalexample>
+ *
+ * returns UUID for the first partition.
+ *
+ * Returns: 0 on success, otherwise, a corresponding error.
+ */
+int fdisk_partition_to_string(struct fdisk_partition *pa,
+ struct fdisk_context *cxt,
+ int id,
+ char **data)
+{
+ char *p = NULL;
+ int rc = 0;
+ uint64_t x;
+
+ if (!pa || !cxt || !data)
+ return -EINVAL;
+
+ switch (id) {
+ case FDISK_FIELD_DEVICE:
+ if (pa->freespace)
+ p = strdup(_("Free space"));
+ else if (fdisk_partition_has_partno(pa) && cxt->dev_path) {
+ if (cxt->label->flags & FDISK_LABEL_FL_INCHARS_PARTNO)
+ rc = asprintf(&p, "%c", (int) pa->partno + 'a');
+ else
+ p = fdisk_partname(cxt->dev_path, pa->partno + 1);
+ }
+ break;
+ case FDISK_FIELD_BOOT:
+ p = fdisk_partition_is_bootable(pa) ? strdup("*") : NULL;
+ break;
+ case FDISK_FIELD_START:
+ if (fdisk_partition_has_start(pa)) {
+ x = fdisk_cround(cxt, pa->start);
+ rc = pa->start_post ?
+ asprintf(&p, "%"PRIu64"%c", x, pa->start_post) :
+ asprintf(&p, "%"PRIu64, x);
+ }
+ break;
+ case FDISK_FIELD_END:
+ if (fdisk_partition_has_end(pa)) {
+ x = fdisk_cround(cxt, fdisk_partition_get_end(pa));
+ rc = pa->end_post ?
+ asprintf(&p, "%"PRIu64"%c", x, pa->end_post) :
+ asprintf(&p, "%"PRIu64, x);
+ }
+ break;
+ case FDISK_FIELD_SIZE:
+ if (fdisk_partition_has_size(pa)) {
+ uint64_t sz = pa->size * cxt->sector_size;
+
+ switch (cxt->sizeunit) {
+ case FDISK_SIZEUNIT_BYTES:
+ rc = asprintf(&p, "%"PRIu64"", sz);
+ break;
+ case FDISK_SIZEUNIT_HUMAN:
+ if (fdisk_is_details(cxt))
+ rc = pa->size_post ?
+ asprintf(&p, "%"PRIu64"%c", sz, pa->size_post) :
+ asprintf(&p, "%"PRIu64, sz);
+ else {
+ p = size_to_human_string(SIZE_SUFFIX_1LETTER, sz);
+ if (!p)
+ rc = -ENOMEM;
+ }
+ break;
+ }
+ }
+ break;
+ case FDISK_FIELD_CYLINDERS:
+ {
+ uintmax_t sz = fdisk_partition_has_size(pa) ? pa->size : 0;
+ if (sz)
+ /* Why we need to cast that to uintmax_t? */
+ rc = asprintf(&p, "%ju", (uintmax_t)(sz / (cxt->geom.heads * cxt->geom.sectors)) + 1);
+ break;
+ }
+ case FDISK_FIELD_SECTORS:
+ rc = asprintf(&p, "%ju",
+ fdisk_partition_has_size(pa) ? (uintmax_t) pa->size : 0);
+ break;
+ case FDISK_FIELD_BSIZE:
+ rc = asprintf(&p, "%"PRIu64, pa->bsize);
+ break;
+ case FDISK_FIELD_FSIZE:
+ rc = asprintf(&p, "%"PRIu64, pa->fsize);
+ break;
+ case FDISK_FIELD_CPG:
+ rc = asprintf(&p, "%"PRIu64, pa->cpg);
+ break;
+ case FDISK_FIELD_TYPE:
+ p = pa->type && pa->type->name ? strdup(_(pa->type->name)) : NULL;
+ break;
+ case FDISK_FIELD_TYPEID:
+ if (pa->type && fdisk_parttype_get_string(pa->type))
+ rc = asprintf(&p, "%s", fdisk_parttype_get_string(pa->type));
+ else if (pa->type)
+ rc = asprintf(&p, "%x", fdisk_parttype_get_code(pa->type));
+ break;
+ case FDISK_FIELD_UUID:
+ p = pa->uuid && *pa->uuid? strdup(pa->uuid) : NULL;
+ break;
+ case FDISK_FIELD_NAME:
+ p = pa->name && *pa->name ? strdup(pa->name) : NULL;
+ break;
+ case FDISK_FIELD_ATTR:
+ p = pa->attrs && *pa->attrs ? strdup(pa->attrs) : NULL;
+ break;
+ case FDISK_FIELD_SADDR:
+ p = pa->start_chs && *pa->start_chs ? strdup(pa->start_chs) : NULL;
+ break;
+ case FDISK_FIELD_EADDR:
+ p = pa->end_chs && *pa->end_chs? strdup(pa->end_chs) : NULL;
+ break;
+ case FDISK_FIELD_FSUUID:
+ if (pa->fs_probed || probe_partition_content(cxt, pa) == 0)
+ p = pa->fsuuid && *pa->fsuuid ? strdup(pa->fsuuid) : NULL;
+ break;
+ case FDISK_FIELD_FSLABEL:
+ if (pa->fs_probed || probe_partition_content(cxt, pa) == 0)
+ p = pa->fslabel && *pa->fslabel ? strdup(pa->fslabel) : NULL;
+ break;
+ case FDISK_FIELD_FSTYPE:
+ if (pa->fs_probed || probe_partition_content(cxt, pa) == 0)
+ p = pa->fstype && *pa->fstype ? strdup(pa->fstype) : NULL;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ if (rc < 0) {
+ rc = -ENOMEM;
+ free(p);
+ p = NULL;
+
+ } else if (rc > 0)
+ rc = 0;
+
+ *data = p;
+
+ return rc;
+}
+
+
+/**
+ * fdisk_get_partition:
+ * @cxt: context
+ * @partno: partition number (0 is the first partition)
+ * @pa: returns data about partition
+ *
+ * Reads disklabel and fills in @pa with data about partition @n.
+ *
+ * Note that partno may address unused partition and then this function does
+ * not fill anything to @pa. See fdisk_is_partition_used(). If @pa points to
+ * NULL then the function allocates a newly allocated fdisk_partition struct,
+ * use fdisk_unref_partition() to deallocate.
+ *
+ * Returns: 0 on success, otherwise, a corresponding error.
+ */
+int fdisk_get_partition(struct fdisk_context *cxt, size_t partno,
+ struct fdisk_partition **pa)
+{
+ int rc;
+ struct fdisk_partition *np = NULL;
+
+ if (!cxt || !cxt->label || !pa)
+ return -EINVAL;
+ if (!cxt->label->op->get_part)
+ return -ENOSYS;
+ if (!fdisk_is_partition_used(cxt, partno))
+ return -EINVAL;
+
+ if (!*pa) {
+ np = *pa = fdisk_new_partition();
+ if (!*pa)
+ return -ENOMEM;
+ } else
+ fdisk_reset_partition(*pa);
+
+ (*pa)->partno = partno;
+ rc = cxt->label->op->get_part(cxt, partno, *pa);
+
+ if (rc) {
+ if (np) {
+ fdisk_unref_partition(np);
+ *pa = NULL;
+ } else
+ fdisk_reset_partition(*pa);
+ } else
+ (*pa)->size_explicit = 1;
+ return rc;
+}
+
+static struct fdisk_partition *area_by_offset(
+ struct fdisk_table *tb,
+ struct fdisk_partition *cur,
+ fdisk_sector_t off)
+{
+ struct fdisk_partition *pa = NULL;
+ struct fdisk_iter itr;
+
+ fdisk_reset_iter(&itr, FDISK_ITER_FORWARD);
+
+ while (fdisk_table_next_partition(tb, &itr, &pa) == 0) {
+ if (!fdisk_partition_has_start(pa) || !fdisk_partition_has_size(pa))
+ continue;
+ if (fdisk_partition_is_nested(cur) &&
+ pa->parent_partno != cur->parent_partno)
+ continue;
+ if (off >= pa->start && off < pa->start + pa->size)
+ return pa;
+ }
+
+ return NULL;
+}
+
+static int resize_get_first_possible(
+ struct fdisk_table *tb,
+ struct fdisk_partition *cur,
+ fdisk_sector_t *start)
+{
+ struct fdisk_partition *pa = NULL, *first = NULL;
+ struct fdisk_iter itr;
+
+ fdisk_reset_iter(&itr, FDISK_ITER_FORWARD);
+
+ *start = 0;
+ DBG(TAB, ul_debugobj(tb, "checking first possible before start=%ju", (uintmax_t) cur->start));
+
+
+ while (fdisk_table_next_partition(tb, &itr, &pa) == 0) {
+
+ if (pa->start > cur->start || pa == cur)
+ break;
+
+ DBG(TAB, ul_debugobj(tb, " checking entry %p [partno=%zu start=%ju, end=%ju, size=%ju%s%s%s]",
+ pa,
+ fdisk_partition_get_partno(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" : ""));
+
+
+ if (!fdisk_partition_is_freespace(pa)) {
+ DBG(TAB, ul_debugobj(tb, " ignored (no freespace)"));
+ first = NULL;
+ continue;
+ }
+ if (!fdisk_partition_has_start(pa) || !fdisk_partition_has_size(pa)) {
+ DBG(TAB, ul_debugobj(tb, " ignored (no start/size)"));
+ first = NULL;
+ continue;
+ }
+ /* The current is nested, free space has to be nested within the same parent */
+ if (fdisk_partition_is_nested(cur)
+ && pa->parent_partno != cur->parent_partno) {
+ DBG(TAB, ul_debugobj(tb, " ignore (nested required)"));
+ first = NULL;
+ continue;
+ }
+ if (pa->start + pa->size <= cur->start) {
+ first = pa;
+ DBG(TAB, ul_debugobj(tb, " entry usable"));
+ }
+ }
+
+ if (first)
+ *start = first->start;
+ else
+ DBG(PART, ul_debugobj(cur, "resize: nothing usable before %ju", (uintmax_t) cur->start));
+
+ return first ? 0 : -1;
+}
+
+/*
+ * Verify that area addressed by @start is freespace or the @cur[rent]
+ * partition and continue to the next table entries until it's freespace, and
+ * counts size of all this space.
+ *
+ * This is core of the partition start offset move operation. We can move the
+ * start within the current partition of to the another free space. It's
+ * forbidden to move start of the partition to another already defined
+ * partition.
+ */
+static int resize_get_last_possible(
+ struct fdisk_table *tb,
+ struct fdisk_partition *cur,
+ fdisk_sector_t start,
+ fdisk_sector_t *maxsz)
+{
+ struct fdisk_partition *pa = NULL, *last = NULL;
+ struct fdisk_iter itr;
+
+ fdisk_reset_iter(&itr, FDISK_ITER_FORWARD);
+
+ *maxsz = 0;
+ DBG(TAB, ul_debugobj(tb, "checking last possible for start=%ju", (uintmax_t) start));
+
+
+ while (fdisk_table_next_partition(tb, &itr, &pa) == 0) {
+
+ DBG(TAB, ul_debugobj(tb, " checking entry %p [partno=%zu start=%ju, end=%ju, size=%ju%s%s%s]",
+ pa,
+ fdisk_partition_get_partno(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" : ""));
+
+ if (!fdisk_partition_has_start(pa) ||
+ !fdisk_partition_has_size(pa) ||
+ (fdisk_partition_is_container(pa) && pa != cur)) {
+ DBG(TAB, ul_debugobj(tb, " ignored (no start/size or container)"));
+ continue;
+ }
+
+ if (fdisk_partition_is_nested(pa)
+ && fdisk_partition_is_container(cur)
+ && pa->parent_partno == cur->partno) {
+ DBG(TAB, ul_debugobj(tb, " ignore (nested child of the current partition)"));
+ continue;
+ }
+
+ /* The current is nested, free space has to be nested within the same parent */
+ if (fdisk_partition_is_nested(cur)
+ && pa->parent_partno != cur->parent_partno) {
+ DBG(TAB, ul_debugobj(tb, " ignore (nested required)"));
+ continue;
+ }
+
+ if (!last) {
+ if (start >= pa->start && start < pa->start + pa->size) {
+ if (fdisk_partition_is_freespace(pa) || pa == cur) {
+ DBG(TAB, ul_debugobj(tb, " accepted as last"));
+ last = pa;
+ } else {
+ DBG(TAB, ul_debugobj(tb, " failed to set last"));
+ break;
+ }
+
+
+ *maxsz = pa->size - (start - pa->start);
+ DBG(TAB, ul_debugobj(tb, " new max=%ju", (uintmax_t) *maxsz));
+ }
+ } else if (!fdisk_partition_is_freespace(pa) && pa != cur) {
+ DBG(TAB, ul_debugobj(tb, " no free space behind current"));
+ break;
+ } else {
+ last = pa;
+ *maxsz = pa->size - (start - pa->start);
+ DBG(TAB, ul_debugobj(tb, " new max=%ju (last updated)", (uintmax_t) *maxsz));
+ }
+ }
+
+ if (last)
+ DBG(PART, ul_debugobj(cur, "resize: max size=%ju", (uintmax_t) *maxsz));
+ else
+ DBG(PART, ul_debugobj(cur, "resize: nothing usable after %ju", (uintmax_t) start));
+
+ return last ? 0 : -1;
+}
+
+/*
+ * Uses template @tpl to recount start and size change of the partition @res. The
+ * @tpl->size and @tpl->start are interpreted as relative to the current setting.
+ */
+static int recount_resize(
+ struct fdisk_context *cxt, size_t partno,
+ struct fdisk_partition *res, struct fdisk_partition *tpl)
+{
+ fdisk_sector_t start, size, xsize;
+ struct fdisk_partition *cur = NULL;
+ struct fdisk_table *tb = NULL;
+ int rc;
+
+ DBG(PART, ul_debugobj(tpl, ">>> resize requested"));
+
+ FDISK_INIT_UNDEF(start);
+ FDISK_INIT_UNDEF(size);
+
+ rc = fdisk_get_partitions(cxt, &tb);
+ if (!rc) {
+ /* For resize we do not follow grain to detect free-space, but
+ * we support to resize with very small granulation. */
+ unsigned long org = cxt->grain;
+
+ cxt->grain = cxt->sector_size;
+ rc = fdisk_get_freespaces(cxt, &tb);
+ cxt->grain = org;
+ }
+ if (rc) {
+ fdisk_unref_table(tb);
+ return rc;
+ }
+
+ fdisk_table_sort_partitions(tb, fdisk_partition_cmp_start);
+
+ DBG(PART, ul_debugobj(tpl, "resize partition partno=%zu in table:", partno));
+ ON_DBG(PART, fdisk_debug_print_table(tb));
+
+ cur = fdisk_table_get_partition_by_partno(tb, partno);
+ if (!cur) {
+ fdisk_unref_table(tb);
+ return -EINVAL;
+ }
+
+ /* 1a) set new start - change relative to the current on-disk setting */
+ if (tpl->movestart && fdisk_partition_has_start(tpl)) {
+ start = fdisk_partition_get_start(cur);
+ if (tpl->movestart == FDISK_MOVE_DOWN) {
+ if (fdisk_partition_get_start(tpl) > start)
+ goto erange;
+ start -= fdisk_partition_get_start(tpl);
+ } else
+ start += fdisk_partition_get_start(tpl);
+
+ DBG(PART, ul_debugobj(tpl, "resize: moving start %s relative, new start: %ju",
+ tpl->movestart == FDISK_MOVE_DOWN ? "DOWN" : "UP", (uintmax_t)start));
+
+ /* 1b) set new start - try freespace before the curret partition */
+ } else if (tpl->movestart == FDISK_MOVE_DOWN) {
+
+ if (resize_get_first_possible(tb, cur, &start) != 0)
+ goto erange;
+
+ DBG(PART, ul_debugobj(tpl, "resize: moving start DOWN (first possible), new start: %ju",
+ (uintmax_t)start));
+
+ /* 1c) set new start - absolute number */
+ } else if (fdisk_partition_has_start(tpl)) {
+ start = fdisk_partition_get_start(tpl);
+ DBG(PART, ul_debugobj(tpl, "resize: moving start to absolute offset: %ju",
+ (uintmax_t)start));
+ }
+
+ /* 2) verify that start is within the current partition or any freespace area */
+ if (!FDISK_IS_UNDEF(start)) {
+ struct fdisk_partition *area = area_by_offset(tb, cur, start);
+
+ if (area == cur)
+ DBG(PART, ul_debugobj(tpl, "resize: start points to the current partition"));
+ else if (area && fdisk_partition_is_freespace(area))
+ DBG(PART, ul_debugobj(tpl, "resize: start points to freespace"));
+ else if (!area && start >= cxt->first_lba && start < cxt->first_lba + (cxt->grain / cxt->sector_size))
+ DBG(PART, ul_debugobj(tpl, "resize: start points before first partition"));
+ else {
+ DBG(PART, ul_debugobj(tpl, "resize: start verification failed"));
+ goto erange;
+ }
+ } else {
+ /* no change, start points to the current partition */
+ DBG(PART, ul_debugobj(tpl, "resize: start unchanged"));
+ start = fdisk_partition_get_start(cur);
+ }
+
+ /* 3a) set new size -- reduce */
+ if (tpl->resize == FDISK_RESIZE_REDUCE && fdisk_partition_has_size(tpl)) {
+ DBG(PART, ul_debugobj(tpl, "resize: reduce"));
+ size = fdisk_partition_get_size(cur);
+ if (fdisk_partition_get_size(tpl) > size)
+ goto erange;
+ size -= fdisk_partition_get_size(tpl);
+
+ /* 3b) set new size -- enlarge */
+ } else if (tpl->resize == FDISK_RESIZE_ENLARGE && fdisk_partition_has_size(tpl)) {
+ DBG(PART, ul_debugobj(tpl, "resize: enlarge"));
+ size = fdisk_partition_get_size(cur);
+ size += fdisk_partition_get_size(tpl);
+
+ /* 3c) set new size -- no size specified, enlarge to all freespace */
+ } else if (tpl->resize == FDISK_RESIZE_ENLARGE) {
+ DBG(PART, ul_debugobj(tpl, "resize: enlarge to all possible"));
+ if (resize_get_last_possible(tb, cur, start, &size))
+ goto erange;
+
+ /* 3d) set new size -- absolute number */
+ } else if (fdisk_partition_has_size(tpl)) {
+ DBG(PART, ul_debugobj(tpl, "resize: new absolute size"));
+ size = fdisk_partition_get_size(tpl);
+ }
+
+ /* 4) verify that size is within the current partition or next free space */
+ xsize = !FDISK_IS_UNDEF(size) ? size : fdisk_partition_get_size(cur);
+
+ if (fdisk_partition_has_size(cur)) {
+ fdisk_sector_t maxsz;
+
+ if (resize_get_last_possible(tb, cur, start, &maxsz))
+ goto erange;
+ DBG(PART, ul_debugobj(tpl, "resize: size=%ju, max=%ju",
+ (uintmax_t) xsize, (uintmax_t) maxsz));
+ if (xsize > maxsz)
+ goto erange;
+ }
+
+ if (FDISK_IS_UNDEF(size)) {
+ DBG(PART, ul_debugobj(tpl, "resize: size unchanged (undefined)"));
+ }
+
+
+ DBG(PART, ul_debugobj(tpl, "<<< resize: SUCCESS: start %ju->%ju; size %ju->%ju",
+ (uintmax_t) fdisk_partition_get_start(cur), (uintmax_t) start,
+ (uintmax_t) fdisk_partition_get_size(cur), (uintmax_t) size));
+ res->start = start;
+ res->size = size;
+ fdisk_unref_table(tb);
+ return 0;
+erange:
+ DBG(PART, ul_debugobj(tpl, "<<< resize: FAILED"));
+ fdisk_warnx(cxt, _("Failed to resize partition #%zu."), partno + 1);
+ fdisk_unref_table(tb);
+ return -ERANGE;
+
+}
+
+/**
+ * fdisk_set_partition:
+ * @cxt: context
+ * @partno: partition number (0 is the first partition)
+ * @pa: new partition setting
+ *
+ * Modifies disklabel according to setting with in @pa.
+ *
+ * Returns: 0 on success, <0 on error.
+ */
+int fdisk_set_partition(struct fdisk_context *cxt, size_t partno,
+ struct fdisk_partition *pa)
+{
+ struct fdisk_partition *xpa = pa, *tmp = NULL;
+ int rc, wipe = 0;
+
+ if (!cxt || !cxt->label || !pa)
+ return -EINVAL;
+ if (!cxt->label->op->set_part)
+ return -ENOSYS;
+
+ pa->fs_probed = 0;
+
+ if (!fdisk_is_partition_used(cxt, partno)) {
+ pa->partno = partno;
+ return fdisk_add_partition(cxt, pa, NULL);
+ }
+
+ if (pa->resize || pa->movestart
+ || fdisk_partition_has_start(pa) || fdisk_partition_has_size(pa)) {
+ xpa = __copy_partition(pa);
+ if (!xpa) {
+ rc = -ENOMEM;
+ goto done;
+ }
+ xpa->movestart = 0;
+ xpa->resize = 0;
+ FDISK_INIT_UNDEF(xpa->size);
+ FDISK_INIT_UNDEF(xpa->start);
+
+ rc = recount_resize(cxt, partno, xpa, pa);
+ if (rc)
+ goto done;
+ }
+
+ DBG(CXT, ul_debugobj(cxt, "setting partition %zu %p (start=%ju, end=%ju, size=%ju)",
+ partno, xpa,
+ (uintmax_t) fdisk_partition_get_start(xpa),
+ (uintmax_t) fdisk_partition_get_end(xpa),
+ (uintmax_t) fdisk_partition_get_size(xpa)));
+
+ /* disable wipe for old offset/size setting */
+ if (fdisk_get_partition(cxt, partno, &tmp) == 0 && tmp) {
+ wipe = fdisk_set_wipe_area(cxt, fdisk_partition_get_start(tmp),
+ fdisk_partition_get_size(tmp), FALSE);
+ fdisk_unref_partition(tmp);
+ }
+
+ /* call label driver */
+ rc = cxt->label->op->set_part(cxt, partno, xpa);
+
+ /* enable wipe for new offset/size */
+ if (!rc && wipe)
+ fdisk_wipe_partition(cxt, partno, TRUE);
+done:
+ DBG(CXT, ul_debugobj(cxt, "set_partition() rc=%d", rc));
+ if (xpa != pa)
+ fdisk_unref_partition(xpa);
+ return rc;
+}
+
+/**
+ * fdisk_wipe_partition:
+ * @cxt: fdisk context
+ * @partno: partition number
+ * @enable: 0 or 1
+ *
+ * Enable/disable filesystems/RAIDs wiping in area defined by partition start and size.
+ *
+ * Returns: <0 in case of error, 0 on success
+ * Since: 2.29
+ */
+int fdisk_wipe_partition(struct fdisk_context *cxt, size_t partno, int enable)
+{
+ struct fdisk_partition *pa = NULL;
+ int rc;
+
+ rc = fdisk_get_partition(cxt, partno, &pa);
+ if (rc)
+ return rc;
+
+ rc = fdisk_set_wipe_area(cxt, fdisk_partition_get_start(pa),
+ fdisk_partition_get_size(pa), enable);
+ fdisk_unref_partition(pa);
+ return rc < 0 ? rc : 0;
+}
+
+/**
+ * fdisk_partition_has_wipe:
+ * @cxt: fdisk context
+ * @pa: partition
+ *
+ * Since: 2.30
+ *
+ * Returns: 1 if the area specified by @pa will be wiped by write command, or 0.
+ */
+int fdisk_partition_has_wipe(struct fdisk_context *cxt, struct fdisk_partition *pa)
+{
+ return fdisk_has_wipe_area(cxt, fdisk_partition_get_start(pa),
+ fdisk_partition_get_size(pa));
+}
+
+
+/**
+ * fdisk_add_partition:
+ * @cxt: fdisk context
+ * @pa: template for the partition (or NULL)
+ * @partno: NULL or returns new partition number
+ *
+ * If @pa is not specified or any @pa item is missing the libfdisk will ask by
+ * fdisk_ask_ API.
+ *
+ * The @pa template is important for non-interactive partitioning,
+ * especially for MBR where is necessary to differentiate between
+ * primary/logical; this is done by start offset or/and partno.
+ * The rules for MBR:
+ *
+ * A) template specifies start within extended partition: add logical
+ * B) template specifies start out of extended partition: add primary
+ * C) template specifies start (or default), partno < 4: add primary
+ * D) template specifies default start, partno >= 4: add logical
+ *
+ * otherwise MBR driver uses Ask-API to get missing information.
+ *
+ * Adds a new partition to disklabel.
+ *
+ * Returns: 0 on success, <0 on error.
+ */
+int fdisk_add_partition(struct fdisk_context *cxt,
+ struct fdisk_partition *pa,
+ size_t *partno)
+{
+ int rc;
+
+ if (!cxt || !cxt->label)
+ return -EINVAL;
+ if (!cxt->label->op->add_part)
+ return -ENOSYS;
+ if (fdisk_missing_geometry(cxt))
+ return -EINVAL;
+
+ if (pa) {
+ pa->fs_probed = 0;
+ DBG(CXT, ul_debugobj(cxt, "adding new partition %p", pa));
+ if (fdisk_partition_has_start(pa))
+ DBG(CXT, ul_debugobj(cxt, " start: %ju", (uintmax_t) fdisk_partition_get_start(pa)));
+ if (fdisk_partition_has_end(pa))
+ DBG(CXT, ul_debugobj(cxt, " end: %ju", (uintmax_t) fdisk_partition_get_end(pa)));
+ if (fdisk_partition_has_size(pa))
+ DBG(CXT, ul_debugobj(cxt, " size: %ju", (uintmax_t) fdisk_partition_get_size(pa)));
+
+ DBG(CXT, ul_debugobj(cxt, " defaults: start=%s, end=%s, partno=%s",
+ pa->start_follow_default ? "yes" : "no",
+ pa->end_follow_default ? "yes" : "no",
+ pa->partno_follow_default ? "yes" : "no"));
+ } else
+ DBG(CXT, ul_debugobj(cxt, "adding partition"));
+
+ rc = cxt->label->op->add_part(cxt, pa, partno);
+
+ DBG(CXT, ul_debugobj(cxt, "add partition done (rc=%d)", rc));
+ return rc;
+}
+
+/**
+ * fdisk_delete_partition:
+ * @cxt: fdisk context
+ * @partno: partition number to delete (0 is the first partition)
+ *
+ * Deletes a @partno partition from disklabel.
+ *
+ * Returns: 0 on success, <0 on error
+ */
+int fdisk_delete_partition(struct fdisk_context *cxt, size_t partno)
+{
+ if (!cxt || !cxt->label)
+ return -EINVAL;
+ if (!cxt->label->op->del_part)
+ return -ENOSYS;
+
+ fdisk_wipe_partition(cxt, partno, 0);
+
+ DBG(CXT, ul_debugobj(cxt, "deleting %s partition number %zd",
+ cxt->label->name, partno));
+ return cxt->label->op->del_part(cxt, partno);
+}
+
+/**
+ * fdisk_delete_all_partitions:
+ * @cxt: fdisk context
+ *
+ * Delete all used partitions from disklabel.
+ *
+ * Returns: 0 on success, otherwise, a corresponding error.
+ */
+int fdisk_delete_all_partitions(struct fdisk_context *cxt)
+{
+ size_t i;
+ int rc = 0;
+
+ if (!cxt || !cxt->label)
+ return -EINVAL;
+
+ for (i = 0; i < cxt->label->nparts_max; i++) {
+
+ if (!fdisk_is_partition_used(cxt, i))
+ continue;
+ rc = fdisk_delete_partition(cxt, i);
+ if (rc)
+ break;
+ }
+
+ return rc;
+}
+
+/**
+ * fdisk_is_partition_used:
+ * @cxt: context
+ * @n: partition number (0 is the first partition)
+ *
+ * Check if the partition number @n is used by partition table. This function
+ * does not check if the device is used (e.g. mounted) by system!
+ *
+ * This is faster than fdisk_get_partition() + fdisk_partition_is_used().
+ *
+ * Returns: 0 or 1
+ */
+int fdisk_is_partition_used(struct fdisk_context *cxt, size_t n)
+{
+ if (!cxt || !cxt->label)
+ return -EINVAL;
+ if (!cxt->label->op->part_is_used)
+ return -ENOSYS;
+
+ return cxt->label->op->part_is_used(cxt, n);
+}
+
diff --git a/libfdisk/src/parttype.c b/libfdisk/src/parttype.c
new file mode 100644
index 0000000..271b671
--- /dev/null
+++ b/libfdisk/src/parttype.c
@@ -0,0 +1,569 @@
+
+#include <ctype.h>
+
+#include "fdiskP.h"
+#include "strutils.h"
+
+/**
+ * SECTION: parttype
+ * @title: Partition types
+ * @short_description: abstraction to partition types
+ *
+ * There are two basic types of parttypes, string based (e.g. GPT)
+ * and code/hex based (e.g. MBR).
+ */
+
+/**
+ * fdisk_new_parttype:
+ *
+ * It's recommended to use fdisk_label_get_parttype_from_code() or
+ * fdisk_label_get_parttype_from_string() for well known types rather
+ * than allocate a new instance.
+ *
+ * Returns: new instance.
+ */
+struct fdisk_parttype *fdisk_new_parttype(void)
+{
+ struct fdisk_parttype *t = calloc(1, sizeof(*t));
+
+ if (!t)
+ return NULL;
+
+ t->refcount = 1;
+ t->flags = FDISK_PARTTYPE_ALLOCATED;
+ DBG(PARTTYPE, ul_debugobj(t, "alloc"));
+ return t;
+}
+
+/**
+ * fdisk_ref_parttype:
+ * @t: partition type
+ *
+ * Increments reference counter for allocated types
+ */
+void fdisk_ref_parttype(struct fdisk_parttype *t)
+{
+ if (fdisk_parttype_is_allocated(t))
+ t->refcount++;
+}
+
+/**
+ * fdisk_unref_parttype
+ * @t: partition pointer
+ *
+ * Decrements reference counter, on zero the @t is automatically
+ * deallocated.
+ */
+void fdisk_unref_parttype(struct fdisk_parttype *t)
+{
+ if (!fdisk_parttype_is_allocated(t))
+ return;
+
+ t->refcount--;
+ if (t->refcount <= 0) {
+ DBG(PARTTYPE, ul_debugobj(t, "free"));
+ free(t->typestr);
+ free(t->name);
+ free(t);
+ }
+}
+
+/**
+ * fdisk_parttype_set_name:
+ * @t: partition type
+ * @str: type name
+ *
+ * Sets type name to allocated partition type, for static types
+ * it returns -EINVAL.
+ *
+ * Return: 0 on success, <0 on error
+ */
+int fdisk_parttype_set_name(struct fdisk_parttype *t, const char *str)
+{
+ if (!t || !fdisk_parttype_is_allocated(t))
+ return -EINVAL;
+ return strdup_to_struct_member(t, name, str);
+}
+
+/**
+ * fdisk_parttype_set_typestr:
+ * @t: partition type
+ * @str: type identifier (e.g. GUID for GPT)
+ *
+ * Sets type string to allocated partition type, for static types
+ * it returns -EINVAL. Don't use this function for MBR, see
+ * fdisk_parttype_set_code().
+ *
+ * Return: 0 on success, <0 on error
+ */
+int fdisk_parttype_set_typestr(struct fdisk_parttype *t, const char *str)
+{
+ if (!t || !fdisk_parttype_is_allocated(t))
+ return -EINVAL;
+ return strdup_to_struct_member(t, typestr, str);
+}
+
+/**
+ * fdisk_parttype_set_code:
+ * @t: partition type
+ * @code: type identifier (e.g. MBR type codes)
+ *
+ * Sets type code to allocated partition type, for static types it returns
+ * -EINVAL. Don't use this function for GPT, see fdisk_parttype_set_typestr().
+ *
+ * Return: 0 on success, <0 on error
+ */
+int fdisk_parttype_set_code(struct fdisk_parttype *t, int code)
+{
+ if (!t || !fdisk_parttype_is_allocated(t))
+ return -EINVAL;
+ t->code = code;
+ return 0;
+}
+
+/**
+ * fdisk_label_get_nparttypes:
+ * @lb: label
+ *
+ * Returns: number of types supported by label.
+ */
+size_t fdisk_label_get_nparttypes(const struct fdisk_label *lb)
+{
+ if (!lb)
+ return 0;
+ return lb->nparttypes;
+}
+
+/**
+ * fdisk_label_get_parttype:
+ * @lb: label
+ * @n: number
+ *
+ * Returns: return parttype
+ */
+struct fdisk_parttype *fdisk_label_get_parttype(const struct fdisk_label *lb, size_t n)
+{
+ if (!lb || n >= lb->nparttypes)
+ return NULL;
+ return &lb->parttypes[n];
+}
+
+/**
+ * fdisk_label_get_parttype_shortcut:
+ * @lb: label
+ * @n: number
+ * @typestr: returns type as string
+ * @shortcut: returns type shortcut string
+ * @alias: returns type alias string
+ *
+ * Returns: return 0 on success, <0 on error, 2 for deprecated alias, 1 for @n out of range
+ *
+ * Since: 2.36
+ */
+int fdisk_label_get_parttype_shortcut(const struct fdisk_label *lb, size_t n,
+ const char **typestr, const char **shortcut, const char **alias)
+{
+ const struct fdisk_shortcut *sc;
+
+ if (!lb)
+ return -EINVAL;
+ if (n >= lb->nparttype_cuts)
+ return 1;
+
+ sc = &lb->parttype_cuts[n];
+ if (typestr)
+ *typestr = sc->data;
+ if (shortcut)
+ *shortcut = sc->shortcut;
+ if (alias)
+ *alias = sc->alias;
+
+ return sc->deprecated == 1 ? 2 : 0;
+
+}
+
+
+/**
+ * fdisk_label_has_code_parttypes:
+ * @lb: label
+ *
+ * Returns: 1 if the label uses code as partition type
+ * identifiers (e.g. MBR) or 0.
+ */
+int fdisk_label_has_code_parttypes(const struct fdisk_label *lb)
+{
+ assert(lb);
+
+ if (lb->parttypes && lb->parttypes[0].typestr)
+ return 0;
+ return 1;
+}
+
+/**
+ * fdisk_label_has_parttypes_shortcuts
+ * @lb: label
+ *
+ * Returns: 1 if the label support shortuts/aliases for partition types or 0.
+ *
+ * Since: 2.36
+ */
+int fdisk_label_has_parttypes_shortcuts(const struct fdisk_label *lb)
+{
+ assert(lb);
+ return lb->nparttype_cuts ? 1 : 0;
+}
+
+
+/**
+ * fdisk_label_get_parttype_from_code:
+ * @lb: label
+ * @code: code to search for
+ *
+ * Search for partition type in label-specific table. The result
+ * is pointer to static array of label types.
+ *
+ * Returns: partition type or NULL upon failure or invalid @code.
+ */
+struct fdisk_parttype *fdisk_label_get_parttype_from_code(
+ const struct fdisk_label *lb,
+ unsigned int code)
+{
+ size_t i;
+
+ assert(lb);
+
+ if (!lb->nparttypes)
+ return NULL;
+
+ for (i = 0; i < lb->nparttypes; i++)
+ if (lb->parttypes[i].code == code)
+ return &lb->parttypes[i];
+ return NULL;
+}
+
+/**
+ * fdisk_label_get_parttype_from_string:
+ * @lb: label
+ * @str: string to search for
+ *
+ * Search for partition type in label-specific table. The result
+ * is pointer to static array of label types.
+ *
+ * Returns: partition type or NULL upon failure or invalid @str.
+ */
+struct fdisk_parttype *fdisk_label_get_parttype_from_string(
+ const struct fdisk_label *lb,
+ const char *str)
+{
+ size_t i;
+
+ assert(lb);
+
+ if (!lb->nparttypes)
+ return NULL;
+
+ for (i = 0; i < lb->nparttypes; i++)
+ if (lb->parttypes[i].typestr
+ && strcasecmp(lb->parttypes[i].typestr, str) == 0)
+ return &lb->parttypes[i];
+
+ return NULL;
+}
+
+/**
+ * fdisk_new_unknown_parttype:
+ * @code: type as number
+ * @typestr: type as string
+
+ * Allocates new 'unknown' partition type. Use fdisk_unref_parttype() to
+ * deallocate.
+ *
+ * Returns: newly allocated partition type, or NULL upon failure.
+ */
+struct fdisk_parttype *fdisk_new_unknown_parttype(unsigned int code,
+ const char *typestr)
+{
+ struct fdisk_parttype *t = fdisk_new_parttype();
+
+ if (!t)
+ return NULL;
+
+ fdisk_parttype_set_name(t, _("unknown"));
+ fdisk_parttype_set_code(t, code);
+ fdisk_parttype_set_typestr(t, typestr);
+ t->flags |= FDISK_PARTTYPE_UNKNOWN;
+
+ return t;
+}
+
+/**
+ * fdisk_copy_parttype:
+ * @type: type to copy
+ *
+ * Use fdisk_unref_parttype() to deallocate.
+ *
+ * Returns: newly allocated partition type, or NULL upon failure.
+ */
+struct fdisk_parttype *fdisk_copy_parttype(const struct fdisk_parttype *type)
+{
+ struct fdisk_parttype *t = fdisk_new_parttype();
+
+ if (!t)
+ return NULL;
+
+ fdisk_parttype_set_name(t, type->name);
+ fdisk_parttype_set_code(t, type->code);
+ fdisk_parttype_set_typestr(t, type->typestr);
+
+ return t;
+}
+
+static struct fdisk_parttype *parttype_from_data(
+ const struct fdisk_label *lb,
+ const char *str,
+ unsigned int *xcode,
+ int use_seqnum)
+{
+ struct fdisk_parttype *types, *ret = NULL;
+ char *end = NULL;
+
+ assert(lb);
+ assert(str);
+
+ if (xcode)
+ *xcode = 0;
+ if (!lb->nparttypes)
+ return NULL;
+
+ DBG(LABEL, ul_debugobj(lb, " parsing '%s' data", str));
+ types = lb->parttypes;
+
+ if (types[0].typestr == NULL) {
+ unsigned int code;
+
+ DBG(LABEL, ul_debugobj(lb, " +hex"));
+
+ errno = 0;
+ code = strtol(str, &end, 16);
+
+ if (errno || *end != '\0') {
+ DBG(LABEL, ul_debugobj(lb, " failed: %m"));
+ return NULL;
+ }
+ if (xcode)
+ *xcode = code;
+ ret = fdisk_label_get_parttype_from_code(lb, code);
+ } else {
+ DBG(LABEL, ul_debugobj(lb, " +string"));
+
+ /* maybe specified by type string (e.g. UUID) */
+ ret = fdisk_label_get_parttype_from_string(lb, str);
+
+ if (!ret) {
+ /* maybe specified by order number */
+ int i;
+
+ errno = 0;
+ i = strtol(str, &end, 0);
+
+ if (use_seqnum && errno == 0
+ && *end == '\0' && i > 0
+ && i - 1 < (int) lb->nparttypes)
+ ret = &types[i - 1];
+ }
+ }
+
+ if (ret)
+ DBG(PARTTYPE, ul_debugobj(ret, " result '%s'", ret->name));
+ return ret;
+}
+
+static struct fdisk_parttype *parttype_from_shortcut(
+ const struct fdisk_label *lb,
+ const char *str, int deprecated)
+{
+ size_t i;
+
+ DBG(LABEL, ul_debugobj(lb, " parsing '%s' shortcut", str));
+
+ for (i = 0; i < lb->nparttype_cuts; i++) {
+ const struct fdisk_shortcut *sc = &lb->parttype_cuts[i];
+
+ if (sc->deprecated && !deprecated)
+ continue;
+ if (sc->shortcut && strcmp(sc->shortcut, str) == 0)
+ return parttype_from_data(lb, sc->data, NULL, 0);
+ }
+ return NULL;
+}
+
+static struct fdisk_parttype *parttype_from_alias(
+ const struct fdisk_label *lb,
+ const char *str, int deprecated)
+{
+ size_t i;
+
+ DBG(LABEL, ul_debugobj(lb, " parsing '%s' alias", str));
+
+ for (i = 0; i < lb->nparttype_cuts; i++) {
+ const struct fdisk_shortcut *sc = &lb->parttype_cuts[i];
+
+ if (sc->deprecated && !deprecated)
+ continue;
+ if (sc->alias && strcmp(sc->alias, str) == 0)
+ return parttype_from_data(lb, sc->data, NULL, 0);
+ }
+ return NULL;
+}
+
+static struct fdisk_parttype *parttype_from_name(
+ const struct fdisk_label *lb,
+ const char *str)
+{
+ size_t i;
+
+ DBG(LABEL, ul_debugobj(lb, " parsing '%s' name", str));
+
+ for (i = 0; i < lb->nparttypes; i++) {
+ const char *name = lb->parttypes[i].name;
+
+ if (name && *name && ul_stralnumcmp(name, str) == 0)
+ return &lb->parttypes[i];
+ }
+
+ return NULL;
+}
+
+/**
+ * fdisk_label_advparse_parttype:
+ * @lb: label
+ * @str: string to parse from
+ * @flags: FDISK_PARTTYPE_PARSE_*
+ *
+ * This function is advanced partition types parser. It parses partition type
+ * from @str according to the label. The function returns a pointer to static
+ * table of the partition types, or newly allocated partition type for unknown
+ * types (see fdisk_parttype_is_unknown(). It's safe to call fdisk_unref_parttype()
+ * for all results.
+ *
+ * The @str may be type data (hex code or UUID), alias or shortcut. For GPT
+ * also sequence number of the type in the list of the supported types.
+ *
+ * Returns: pointer to type or NULL on error.
+ */
+struct fdisk_parttype *fdisk_label_advparse_parttype(
+ const struct fdisk_label *lb,
+ const char *str,
+ int flags)
+{
+ struct fdisk_parttype *res = NULL;
+ unsigned int code = 0;
+
+ if (!lb || !lb->nparttypes)
+ return NULL;
+
+ DBG(LABEL, ul_debugobj(lb, "parsing '%s' (%s) type", str, lb->name));
+
+ if ((flags & FDISK_PARTTYPE_PARSE_DATA)
+ && !(flags & FDISK_PARTTYPE_PARSE_DATALAST))
+ res = parttype_from_data(lb, str, &code,
+ flags & FDISK_PARTTYPE_PARSE_SEQNUM);
+
+ if (!res && (flags & FDISK_PARTTYPE_PARSE_ALIAS))
+ res = parttype_from_alias(lb, str,
+ flags & FDISK_PARTTYPE_PARSE_DEPRECATED);
+
+ if (!res && (flags & FDISK_PARTTYPE_PARSE_SHORTCUT))
+ res = parttype_from_shortcut(lb, str,
+ flags & FDISK_PARTTYPE_PARSE_DEPRECATED);
+
+ if (!res && (flags & FDISK_PARTTYPE_PARSE_NAME))
+ res = parttype_from_name(lb, str);
+
+ if (!res && (flags & FDISK_PARTTYPE_PARSE_DATA)
+ && (flags & FDISK_PARTTYPE_PARSE_DATALAST))
+ res = parttype_from_data(lb, str, &code,
+ flags & FDISK_PARTTYPE_PARSE_SEQNUM);
+
+ if (!res && !(flags & FDISK_PARTTYPE_PARSE_NOUNKNOWN)) {
+ if (lb->parttypes[0].typestr)
+ res = fdisk_new_unknown_parttype(0, str);
+ else
+ res = fdisk_new_unknown_parttype(code, NULL);
+ }
+
+ if (res)
+ DBG(PARTTYPE, ul_debugobj(res, "returns parsed '%s' [%s] partition type",
+ res->name, res->typestr ? : ""));
+ return res;
+}
+
+/**
+ * fdisk_label_parse_parttype:
+ * @lb: label
+ * @str: string to parse from (type name, UUID, etc.)
+ *
+ * Parses partition type from @str according to the label. The function returns
+ * a pointer to static table of the partition types, or newly allocated
+ * partition type for unknown types (see fdisk_parttype_is_unknown(). It's
+ * safe to call fdisk_unref_parttype() for all results.
+ *
+ * Note that for GPT it accepts sequence number of UUID.
+ *
+ * Returns: pointer to type or NULL on error.
+ */
+struct fdisk_parttype *fdisk_label_parse_parttype(
+ const struct fdisk_label *lb,
+ const char *str)
+{
+ return fdisk_label_advparse_parttype(lb, str, FDISK_PARTTYPE_PARSE_DATA);
+}
+
+/**
+ * fdisk_parttype_get_string:
+ * @t: type
+ *
+ * Returns: partition type string (e.g. GUID for GPT)
+ */
+const char *fdisk_parttype_get_string(const struct fdisk_parttype *t)
+{
+ assert(t);
+ return t->typestr && *t->typestr ? t->typestr : NULL;
+}
+
+/**
+ * fdisk_parttype_get_code:
+ * @t: type
+ *
+ * Returns: partition type code (e.g. for MBR)
+ */
+unsigned int fdisk_parttype_get_code(const struct fdisk_parttype *t)
+{
+ assert(t);
+ return t->code;
+}
+
+/**
+ * fdisk_parttype_get_name:
+ * @t: type
+ *
+ * Returns: partition type human readable name
+ */
+const char *fdisk_parttype_get_name(const struct fdisk_parttype *t)
+{
+ assert(t);
+ return t->name;
+}
+
+/**
+ * fdisk_parttype_is_unknown:
+ * @t: type
+ *
+ * Checks for example result from fdisk_label_parse_parttype().
+ *
+ * Returns: 1 is type is "unknown" or 0.
+ */
+int fdisk_parttype_is_unknown(const struct fdisk_parttype *t)
+{
+ return t && (t->flags & FDISK_PARTTYPE_UNKNOWN) ? 1 : 0;
+}
diff --git a/libfdisk/src/script.c b/libfdisk/src/script.c
new file mode 100644
index 0000000..f537a7d
--- /dev/null
+++ b/libfdisk/src/script.c
@@ -0,0 +1,1823 @@
+
+#include "fdiskP.h"
+#include "strutils.h"
+#include "carefulputc.h"
+#include "mangle.h"
+#include "jsonwrt.h"
+#include "fileutils.h"
+
+#ifdef FUZZ_TARGET
+#include "fuzz.h"
+#endif
+
+/**
+ * SECTION: script
+ * @title: Script
+ * @short_description: complex way to create and dump partition table
+ *
+ * This interface can be used to compose in-memory partition table with all details,
+ * write all partition table description to human readable text file, read it
+ * from the file, and apply the script to on-disk label.
+ *
+ * The libfdisk scripts are based on original sfdisk script (dumps). Each
+ * script has two parts: script headers and partition table entries
+ * (partitions). The script is possible to dump in JSON too (read JSON is not
+ * implemented yet).
+ *
+ * For more details about script format see sfdisk man page.
+ *
+ * There are four ways how to build the script:
+ *
+ * - read the current on-disk partition table by fdisk_script_read_context())
+ * - read it from text file by fdisk_script_read_file()
+ * - read it interactively from user by fdisk_script_read_line() and fdisk_script_set_fgets()
+ * - manually in code by fdisk_script_set_header() and fdisk_script_set_table()
+ *
+ * The read functions fdisk_script_read_context() and fdisk_script_read_file()
+ * creates always a new script partition table. The table (see
+ * fdisk_script_get_table()) is possible to modify by standard
+ * fdisk_table_...() functions and then apply by fdisk_apply_script().
+ *
+ * Note that script API is fully non-interactive and forces libfdisk to not use
+ * standard dialog driven partitioning as we have in fdisk(8).
+ */
+
+/* script header (e.g. unit: sectors) */
+struct fdisk_scriptheader {
+ struct list_head headers;
+ char *name;
+ char *data;
+};
+
+/* script control struct */
+struct fdisk_script {
+ struct fdisk_table *table;
+ struct list_head headers;
+ struct fdisk_context *cxt;
+
+ int refcount;
+ char *(*fn_fgets)(struct fdisk_script *, char *, size_t, FILE *);
+ void *userdata;
+
+ /* parser's state */
+ size_t nlines;
+ struct fdisk_label *label;
+
+ unsigned long sector_size; /* as defined by script */
+
+ unsigned int json : 1, /* JSON output */
+ force_label : 1; /* label: <name> specified */
+};
+
+static void fdisk_script_free_header(struct fdisk_scriptheader *fi)
+{
+ if (!fi)
+ return;
+
+ DBG(SCRIPT, ul_debugobj(fi, "free header %s", fi->name));
+ free(fi->name);
+ free(fi->data);
+ list_del(&fi->headers);
+ free(fi);
+}
+
+/**
+ * fdisk_new_script:
+ * @cxt: context
+ *
+ * The script hold fdisk_table and additional information to read/write
+ * script to the file.
+ *
+ * Returns: newly allocated script struct.
+ */
+struct fdisk_script *fdisk_new_script(struct fdisk_context *cxt)
+{
+ struct fdisk_script *dp = NULL;
+
+ dp = calloc(1, sizeof(*dp));
+ if (!dp)
+ return NULL;
+
+ DBG(SCRIPT, ul_debugobj(dp, "alloc"));
+ dp->refcount = 1;
+ dp->cxt = cxt;
+ fdisk_ref_context(cxt);
+
+ INIT_LIST_HEAD(&dp->headers);
+ return dp;
+}
+
+/**
+ * fdisk_new_script_from_file:
+ * @cxt: context
+ * @filename: path to the script file
+ *
+ * Allocates a new script and reads script from @filename.
+ *
+ * Returns: new script instance or NULL in case of error (check errno for more details).
+ */
+struct fdisk_script *fdisk_new_script_from_file(struct fdisk_context *cxt,
+ const char *filename)
+{
+ int rc;
+ FILE *f;
+ struct fdisk_script *dp, *res = NULL;
+
+ assert(cxt);
+ assert(filename);
+
+ DBG(SCRIPT, ul_debug("opening %s", filename));
+ f = fopen(filename, "r");
+ if (!f)
+ return NULL;
+
+ dp = fdisk_new_script(cxt);
+ if (!dp)
+ goto done;
+
+ rc = fdisk_script_read_file(dp, f);
+ if (rc) {
+ errno = -rc;
+ goto done;
+ }
+
+ res = dp;
+done:
+ fclose(f);
+ if (!res)
+ fdisk_unref_script(dp);
+ else
+ errno = 0;
+
+ return res;
+}
+
+/**
+ * fdisk_ref_script:
+ * @dp: script pointer
+ *
+ * Increments reference counter.
+ */
+void fdisk_ref_script(struct fdisk_script *dp)
+{
+ if (dp)
+ dp->refcount++;
+}
+
+static void fdisk_reset_script(struct fdisk_script *dp)
+{
+ assert(dp);
+
+ DBG(SCRIPT, ul_debugobj(dp, "reset"));
+
+ if (dp->table)
+ fdisk_reset_table(dp->table);
+
+ while (!list_empty(&dp->headers)) {
+ struct fdisk_scriptheader *fi = list_entry(dp->headers.next,
+ struct fdisk_scriptheader, headers);
+ fdisk_script_free_header(fi);
+ }
+ INIT_LIST_HEAD(&dp->headers);
+}
+
+/**
+ * fdisk_unref_script:
+ * @dp: script pointer
+ *
+ * Decrements reference counter, on zero the @dp is automatically
+ * deallocated.
+ */
+void fdisk_unref_script(struct fdisk_script *dp)
+{
+ if (!dp)
+ return;
+
+ dp->refcount--;
+ if (dp->refcount <= 0) {
+ fdisk_reset_script(dp);
+ fdisk_unref_context(dp->cxt);
+ fdisk_unref_table(dp->table);
+ DBG(SCRIPT, ul_debugobj(dp, "free script"));
+ free(dp);
+ }
+}
+
+/**
+ * fdisk_script_set_userdata
+ * @dp: script
+ * @data: your data
+ *
+ * Sets data usable for example in callbacks (e.g fdisk_script_set_fgets()).
+ *
+ * Returns: 0 on success, <0 on error.
+ */
+int fdisk_script_set_userdata(struct fdisk_script *dp, void *data)
+{
+ assert(dp);
+ dp->userdata = data;
+ return 0;
+}
+
+/**
+ * fdisk_script_get_userdata
+ * @dp: script
+ *
+ * Returns: user data or NULL.
+ */
+void *fdisk_script_get_userdata(struct fdisk_script *dp)
+{
+ assert(dp);
+ return dp->userdata;
+}
+
+static struct fdisk_scriptheader *script_get_header(struct fdisk_script *dp,
+ const char *name)
+{
+ struct list_head *p;
+
+ list_for_each(p, &dp->headers) {
+ struct fdisk_scriptheader *fi = list_entry(p, struct fdisk_scriptheader, headers);
+
+ if (strcasecmp(fi->name, name) == 0)
+ return fi;
+ }
+
+ return NULL;
+}
+
+/**
+ * fdisk_script_get_header:
+ * @dp: script instance
+ * @name: header name
+ *
+ * Returns: pointer to header data or NULL.
+ */
+const char *fdisk_script_get_header(struct fdisk_script *dp, const char *name)
+{
+ struct fdisk_scriptheader *fi;
+
+ assert(dp);
+ assert(name);
+
+ fi = script_get_header(dp, name);
+ return fi ? fi->data : NULL;
+}
+
+/**
+ * fdisk_script_set_header:
+ * @dp: script instance
+ * @name: header name
+ * @data: header data (or NULL)
+ *
+ * The headers are used as global options for whole partition
+ * table, always one header per line.
+ *
+ * If no @data is specified then the header is removed. If header does not exist
+ * and @data is specified then a new header is added.
+ *
+ * Note that libfdisk can be used to specify arbitrary custom header, the default
+ * built-in headers are "unit" and "label", and some label specific headers
+ * (for example "uuid" and "name" for GPT).
+ *
+ * Returns: 0 on success, <0 on error
+ */
+int fdisk_script_set_header(struct fdisk_script *dp,
+ const char *name,
+ const char *data)
+{
+ struct fdisk_scriptheader *fi;
+
+ if (!dp || !name)
+ return -EINVAL;
+
+ fi = script_get_header(dp, name);
+ if (!fi && !data)
+ return 0; /* want to remove header that does not exist, success */
+
+ if (!data) {
+ DBG(SCRIPT, ul_debugobj(dp, "freeing header %s", name));
+
+ /* no data, remove the header */
+ fdisk_script_free_header(fi);
+ return 0;
+ }
+
+ if (!fi) {
+ int rc;
+
+ DBG(SCRIPT, ul_debugobj(dp, "setting new header %s='%s'", name, data));
+
+ /* new header */
+ fi = calloc(1, sizeof(*fi));
+ if (!fi)
+ return -ENOMEM;
+ INIT_LIST_HEAD(&fi->headers);
+
+ rc = strdup_to_struct_member(fi, name, name);
+ if (!rc)
+ rc = strdup_to_struct_member(fi, data, data);
+ if (rc) {
+ fdisk_script_free_header(fi);
+ return rc;
+ }
+ list_add_tail(&fi->headers, &dp->headers);
+ } else {
+ /* update existing */
+ char *x = strdup(data);
+
+ DBG(SCRIPT, ul_debugobj(dp, "update '%s' header '%s' -> '%s'", name, fi->data, data));
+
+ if (!x)
+ return -ENOMEM;
+ free(fi->data);
+ fi->data = x;
+ }
+
+ if (strcmp(name, "label") == 0)
+ dp->label = NULL;
+
+ return 0;
+}
+
+/**
+ * fdisk_script_get_table:
+ * @dp: script
+ *
+ * The table represents partitions holded by the script. The table is possible to
+ * fill by fdisk_script_read_context() or fdisk_script_read_file(). All the "read"
+ * functions remove old partitions from the table. See also fdisk_script_set_table().
+ *
+ * Returns: NULL or script table.
+ */
+struct fdisk_table *fdisk_script_get_table(struct fdisk_script *dp)
+{
+ assert(dp);
+
+ if (!dp->table)
+ /*
+ * Make sure user has access to the same table as script. If
+ * there is no table then create a new one and reuse it later.
+ */
+ dp->table = fdisk_new_table();
+
+ return dp->table;
+}
+
+/**
+ * fdisk_script_set_table:
+ * @dp: script
+ * @tb: table
+ *
+ * Replaces table used by script and creates a new reference to @tb. This
+ * function can be used to generate a new script table independently on the current
+ * context and without any file reading.
+ *
+ * This is useful for example to create partition table with the same basic
+ * settings (e.g. label-id, ...) but with different partitions -- just call
+ * fdisk_script_read_context() to get current settings and then
+ * fdisk_script_set_table() to set a different layout.
+ *
+ * If @tb is NULL then the current script table is unreferenced.
+ *
+ * Note that script read_ functions (e.g. fdisk_script_read_context()) create
+ * always a new script table.
+ *
+ * Returns: 0 on success, <0 on error
+ *
+ * Since: 2.35
+ */
+int fdisk_script_set_table(struct fdisk_script *dp, struct fdisk_table *tb)
+{
+ if (!dp)
+ return -EINVAL;
+
+ fdisk_ref_table(tb);
+ fdisk_unref_table(dp->table);
+ dp->table = tb;
+
+ DBG(SCRIPT, ul_debugobj(dp, "table replaced"));
+ return 0;
+}
+
+static struct fdisk_label *script_get_label(struct fdisk_script *dp)
+{
+ assert(dp);
+ assert(dp->cxt);
+
+ if (!dp->label) {
+ dp->label = fdisk_get_label(dp->cxt,
+ fdisk_script_get_header(dp, "label"));
+ DBG(SCRIPT, ul_debugobj(dp, "label '%s'", dp->label ? dp->label->name : ""));
+ }
+ return dp->label;
+}
+
+/**
+ * fdisk_script_get_nlines:
+ * @dp: script
+ *
+ * Returns: number of parsed lines or <0 on error.
+ */
+int fdisk_script_get_nlines(struct fdisk_script *dp)
+{
+ assert(dp);
+ return dp->nlines;
+}
+
+/**
+ * fdisk_script_has_force_label:
+ * @dp: script
+ *
+ * Label has been explicitly specified in the script.
+ *
+ * Since: 2.30
+ *
+ * Returns: true if "label: name" has been parsed.
+ */
+int fdisk_script_has_force_label(struct fdisk_script *dp)
+{
+ assert(dp);
+ return dp->force_label;
+}
+
+
+/**
+ * fdisk_script_read_context:
+ * @dp: script
+ * @cxt: context
+ *
+ * Reads data from the @cxt context (on disk partition table) into the script.
+ * If the context is not specified then defaults to context used for fdisk_new_script().
+ *
+ * Return: 0 on success, <0 on error.
+ */
+int fdisk_script_read_context(struct fdisk_script *dp, struct fdisk_context *cxt)
+{
+ struct fdisk_label *lb;
+ int rc;
+ char *p = NULL;
+ char buf[64];
+
+ if (!dp || (!cxt && !dp->cxt))
+ return -EINVAL;
+
+ if (!cxt)
+ cxt = dp->cxt;
+
+ DBG(SCRIPT, ul_debugobj(dp, "reading context into script"));
+ fdisk_reset_script(dp);
+
+ lb = fdisk_get_label(cxt, NULL);
+ if (!lb)
+ return -EINVAL;
+
+ /* allocate (if not yet) and fill table */
+ rc = fdisk_get_partitions(cxt, &dp->table);
+ if (rc)
+ return rc;
+
+ /* generate headers */
+ rc = fdisk_script_set_header(dp, "label", fdisk_label_get_name(lb));
+
+ if (!rc && fdisk_get_disklabel_id(cxt, &p) == 0 && p) {
+ rc = fdisk_script_set_header(dp, "label-id", p);
+ free(p);
+ }
+ if (!rc && cxt->dev_path)
+ rc = fdisk_script_set_header(dp, "device", cxt->dev_path);
+ if (!rc)
+ rc = fdisk_script_set_header(dp, "unit", "sectors");
+
+ if (!rc && fdisk_is_label(cxt, GPT)) {
+ struct fdisk_labelitem item = FDISK_LABELITEM_INIT;
+
+ /* first-lba */
+ rc = fdisk_get_disklabel_item(cxt, GPT_LABELITEM_FIRSTLBA, &item);
+ if (!rc) {
+ snprintf(buf, sizeof(buf), "%"PRIu64, item.data.num64);
+ rc = fdisk_script_set_header(dp, "first-lba", buf);
+ }
+
+ /* last-lba */
+ if (!rc)
+ rc = fdisk_get_disklabel_item(cxt, GPT_LABELITEM_LASTLBA, &item);
+ if (!rc) {
+ snprintf(buf, sizeof(buf), "%"PRIu64, item.data.num64);
+ rc = fdisk_script_set_header(dp, "last-lba", buf);
+ }
+
+ /* table-length */
+ if (!rc) {
+ size_t n = fdisk_get_npartitions(cxt);
+ if (n != FDISK_GPT_NPARTITIONS_DEFAULT) {
+ snprintf(buf, sizeof(buf), "%zu", n);
+ rc = fdisk_script_set_header(dp, "table-length", buf);
+ }
+ }
+ }
+
+ if (!rc && fdisk_get_grain_size(cxt) != 2048 * 512) {
+ snprintf(buf, sizeof(buf), "%lu", fdisk_get_grain_size(cxt));
+ rc = fdisk_script_set_header(dp, "grain", buf);
+ }
+
+ if (!rc) {
+ snprintf(buf, sizeof(buf), "%lu", fdisk_get_sector_size(cxt));
+ rc = fdisk_script_set_header(dp, "sector-size", buf);
+ }
+
+ DBG(SCRIPT, ul_debugobj(dp, "read context done [rc=%d]", rc));
+ return rc;
+}
+
+/**
+ * fdisk_script_enable_json:
+ * @dp: script
+ * @json: 0 or 1
+ *
+ * Disable/Enable JSON output format.
+ *
+ * Returns: 0 on success, <0 on error.
+ */
+int fdisk_script_enable_json(struct fdisk_script *dp, int json)
+{
+ assert(dp);
+
+ dp->json = json;
+ return 0;
+}
+
+static int write_file_json(struct fdisk_script *dp, FILE *f)
+{
+ struct list_head *h;
+ struct fdisk_partition *pa;
+ struct fdisk_iter itr;
+ const char *devname = NULL;
+ struct ul_jsonwrt json;
+
+ assert(dp);
+ assert(f);
+
+ DBG(SCRIPT, ul_debugobj(dp, "writing json dump to file"));
+
+ ul_jsonwrt_init(&json, f, 0);
+ ul_jsonwrt_root_open(&json);
+
+ ul_jsonwrt_object_open(&json, "partitiontable");
+
+ /* script headers */
+ list_for_each(h, &dp->headers) {
+ struct fdisk_scriptheader *fi = list_entry(h, struct fdisk_scriptheader, headers);
+ const char *name = fi->name;
+ int num = 0;
+
+ if (strcmp(name, "first-lba") == 0) {
+ name = "firstlba";
+ num = 1;
+ } else if (strcmp(name, "last-lba") == 0) {
+ name = "lastlba";
+ num = 1;
+ } else if (strcmp(name, "sector-size") == 0) {
+ name = "sectorsize";
+ num = 1;
+ } else if (strcmp(name, "label-id") == 0)
+ name = "id";
+
+ if (num)
+ ul_jsonwrt_value_raw(&json, name, fi->data);
+ else
+ ul_jsonwrt_value_s(&json, name, fi->data);
+
+ if (strcmp(name, "device") == 0)
+ devname = fi->data;
+ }
+
+
+ if (!dp->table || fdisk_table_is_empty(dp->table)) {
+ DBG(SCRIPT, ul_debugobj(dp, "script table empty"));
+ goto done;
+ }
+
+ DBG(SCRIPT, ul_debugobj(dp, "%zu entries", fdisk_table_get_nents(dp->table)));
+
+ ul_jsonwrt_array_open(&json, "partitions");
+
+ fdisk_reset_iter(&itr, FDISK_ITER_FORWARD);
+ while (fdisk_table_next_partition(dp->table, &itr, &pa) == 0) {
+ char *p = NULL;
+
+ ul_jsonwrt_object_open(&json, NULL);
+ if (devname)
+ p = fdisk_partname(devname, pa->partno + 1);
+ if (p) {
+ DBG(SCRIPT, ul_debugobj(dp, "write %s entry", p));
+ ul_jsonwrt_value_s(&json, "node", p);
+ free(p);
+ }
+
+ if (fdisk_partition_has_start(pa))
+ ul_jsonwrt_value_u64(&json, "start", (uintmax_t)pa->start);
+
+ if (fdisk_partition_has_size(pa))
+ ul_jsonwrt_value_u64(&json, "size", (uintmax_t)pa->size);
+
+ if (pa->type && fdisk_parttype_get_string(pa->type))
+ ul_jsonwrt_value_s(&json, "type", fdisk_parttype_get_string(pa->type));
+
+ else if (pa->type) {
+ ul_jsonwrt_value_open(&json, "type");
+ fprintf(f, "\"%x\"", fdisk_parttype_get_code(pa->type));
+ ul_jsonwrt_value_close(&json);
+ }
+
+ if (pa->uuid)
+ ul_jsonwrt_value_s(&json, "uuid", pa->uuid);
+ if (pa->name && *pa->name)
+ ul_jsonwrt_value_s(&json, "name", pa->name);
+
+ /* for MBR attr=80 means bootable */
+ if (pa->attrs) {
+ struct fdisk_label *lb = script_get_label(dp);
+
+ if (!lb || fdisk_label_get_type(lb) != FDISK_DISKLABEL_DOS)
+ ul_jsonwrt_value_s(&json, "attrs", pa->attrs);
+ }
+
+ if (fdisk_partition_is_bootable(pa))
+ ul_jsonwrt_value_boolean(&json, "bootable", 1);
+ ul_jsonwrt_object_close(&json);
+ }
+
+ ul_jsonwrt_array_close(&json);
+done:
+ ul_jsonwrt_object_close(&json);
+ ul_jsonwrt_root_close(&json);
+
+ DBG(SCRIPT, ul_debugobj(dp, "write script done"));
+ return 0;
+}
+
+static int write_file_sfdisk(struct fdisk_script *dp, FILE *f)
+{
+ struct list_head *h;
+ struct fdisk_partition *pa;
+ struct fdisk_iter itr;
+ const char *devname = NULL;
+
+ assert(dp);
+ assert(f);
+
+ DBG(SCRIPT, ul_debugobj(dp, "writing sfdisk-like script to file"));
+
+ /* script headers */
+ list_for_each(h, &dp->headers) {
+ struct fdisk_scriptheader *fi = list_entry(h, struct fdisk_scriptheader, headers);
+ fprintf(f, "%s: %s\n", fi->name, fi->data);
+ if (strcmp(fi->name, "device") == 0)
+ devname = fi->data;
+ }
+
+ if (!dp->table || fdisk_table_is_empty(dp->table)) {
+ DBG(SCRIPT, ul_debugobj(dp, "script table empty"));
+ return 0;
+ }
+
+ DBG(SCRIPT, ul_debugobj(dp, "%zu entries", fdisk_table_get_nents(dp->table)));
+
+ fputc('\n', f);
+
+ fdisk_reset_iter(&itr, FDISK_ITER_FORWARD);
+ while (fdisk_table_next_partition(dp->table, &itr, &pa) == 0) {
+ char *p = NULL;
+
+ if (devname)
+ p = fdisk_partname(devname, pa->partno + 1);
+ if (p) {
+ DBG(SCRIPT, ul_debugobj(dp, "write %s entry", p));
+ fprintf(f, "%s :", p);
+ free(p);
+ } else
+ fprintf(f, "%zu :", pa->partno + 1);
+
+ if (fdisk_partition_has_start(pa))
+ fprintf(f, " start=%12ju", (uintmax_t)pa->start);
+ if (fdisk_partition_has_size(pa))
+ fprintf(f, ", size=%12ju", (uintmax_t)pa->size);
+
+ if (pa->type && fdisk_parttype_get_string(pa->type))
+ fprintf(f, ", type=%s", fdisk_parttype_get_string(pa->type));
+ else if (pa->type)
+ fprintf(f, ", type=%x", fdisk_parttype_get_code(pa->type));
+
+ if (pa->uuid)
+ fprintf(f, ", uuid=%s", pa->uuid);
+ if (pa->name && *pa->name) {
+ fputs(", name=", f);
+ fputs_quoted(pa->name, f);
+ }
+
+ /* for MBR attr=80 means bootable */
+ if (pa->attrs) {
+ struct fdisk_label *lb = script_get_label(dp);
+
+ if (!lb || fdisk_label_get_type(lb) != FDISK_DISKLABEL_DOS)
+ fprintf(f, ", attrs=\"%s\"", pa->attrs);
+ }
+ if (fdisk_partition_is_bootable(pa))
+ fprintf(f, ", bootable");
+ fputc('\n', f);
+ }
+
+ DBG(SCRIPT, ul_debugobj(dp, "write script done"));
+ return 0;
+}
+
+/**
+ * fdisk_script_write_file:
+ * @dp: script
+ * @f: output file
+ *
+ * Writes script @dp to the file @f.
+ *
+ * Returns: 0 on success, <0 on error.
+ */
+int fdisk_script_write_file(struct fdisk_script *dp, FILE *f)
+{
+ assert(dp);
+
+ if (dp->json)
+ return write_file_json(dp, f);
+
+ return write_file_sfdisk(dp, f);
+}
+
+static inline int is_header_line(const char *s)
+{
+ const char *p = strchr(s, ':');
+
+ if (!p || p == s || !*(p + 1) || strchr(s, '='))
+ return 0;
+
+ return 1;
+}
+
+/* parses "<name>: value", note modifies @s*/
+static int parse_line_header(struct fdisk_script *dp, char *s)
+{
+ size_t i;
+ char *name, *value;
+ static const char *supported[] = {
+ "label", "unit", "label-id", "device", "grain",
+ "first-lba", "last-lba", "table-length", "sector-size"
+ };
+
+ DBG(SCRIPT, ul_debugobj(dp, " parse header '%s'", s));
+
+ if (!s || !*s)
+ return -EINVAL;
+
+ name = s;
+ value = strchr(s, ':');
+ if (!value)
+ return -EINVAL;
+ *value = '\0';
+ value++;
+
+ ltrim_whitespace((unsigned char *) name);
+ rtrim_whitespace((unsigned char *) name);
+ ltrim_whitespace((unsigned char *) value);
+ rtrim_whitespace((unsigned char *) value);
+
+ if (!*name || !*value)
+ return -EINVAL;
+
+ /* check header name */
+ for (i = 0; i < ARRAY_SIZE(supported); i++) {
+ if (strcmp(name, supported[i]) == 0)
+ break;
+ }
+ if (i == ARRAY_SIZE(supported))
+ return -ENOTSUP;
+
+ /* header specific actions */
+ if (strcmp(name, "label") == 0) {
+ if (dp->cxt && !fdisk_get_label(dp->cxt, value))
+ return -EINVAL; /* unknown label name */
+ dp->force_label = 1;
+
+ } else if (strcmp(name, "sector-size") == 0) {
+ uint64_t x = 0;
+
+ if (ul_strtou64(value, &x, 10) != 0)
+ return -EINVAL;
+ if (x > ULONG_MAX || x % 512)
+ return -ERANGE;
+ dp->sector_size = (unsigned long) x;
+
+ if (dp->cxt && dp->sector_size && dp->cxt->sector_size
+ && dp->sector_size != dp->cxt->sector_size)
+ fdisk_warnx(dp->cxt, _("The script and device sector size differ; the sizes will be recalculated to match the device."));
+
+ } else if (strcmp(name, "unit") == 0) {
+ if (strcmp(value, "sectors") != 0)
+ return -EINVAL; /* only "sectors" supported */
+
+ }
+
+ return fdisk_script_set_header(dp, name, value);
+}
+
+static int partno_from_devname(char *s)
+{
+ intmax_t num;
+ size_t sz;
+ char *end, *p;
+
+ if (!s || !*s)
+ return -1;
+
+ sz = rtrim_whitespace((unsigned char *)s);
+ end = p = s + sz;
+
+ while (p > s && isdigit(*(p - 1)))
+ p--;
+ if (p == end)
+ return -1;
+ end = NULL;
+ errno = 0;
+ num = strtol(p, &end, 10);
+ if (errno || !end || p == end)
+ return -1;
+
+ if (num < INT32_MIN || num > INT32_MAX) {
+ errno = ERANGE;
+ return -1;
+ }
+ return num - 1;
+}
+
+
+/* returns zero terminated string with next token and @str is updated */
+static char *next_token(char **str)
+{
+ char *tk_begin = NULL,
+ *tk_end = NULL,
+ *end = NULL,
+ *p;
+ int open_quote = 0, terminated = 0;
+
+ for (p = *str; p && *p; p++) {
+ if (!tk_begin) {
+ if (isblank(*p))
+ continue;
+ tk_begin = *p == '"' ? p + 1 : p;
+ }
+ if (*p == '"')
+ open_quote ^= 1;
+ if (open_quote)
+ continue;
+ if (isblank(*p) || *p == ',' || *p == ';' || *p == '"' )
+ tk_end = p;
+ else if (*(p + 1) == '\0')
+ tk_end = p + 1;
+ if (tk_begin && tk_end)
+ break;
+ }
+
+ if (!tk_end)
+ return NULL;
+
+ end = tk_end;
+
+ /* skip closing quotes */
+ if (*end == '"')
+ end++;
+
+ /* token is terminated by blank (or blank is before "," or ";") */
+ if (isblank(*end)) {
+ end = (char *) skip_blank(end);
+ terminated++;
+ }
+
+ /* token is terminated by "," or ";" */
+ if (*end == ',' || *end == ';') {
+ end++;
+ terminated++;
+
+ /* token is terminated by \0 */
+ } else if (!*end)
+ terminated++;
+
+ if (!terminated) {
+ DBG(SCRIPT, ul_debug("unterminated token '%s'", end));
+ return NULL;
+ }
+
+ /* skip extra space after terminator */
+ end = (char *) skip_blank(end);
+
+ *tk_end = '\0';
+ *str = end;
+ return tk_begin;
+}
+
+/*
+ * "[-]<,;>"
+ * "[ ]<,;>"
+ * "- <value>"
+ */
+static int is_default_value(char **str)
+{
+ char *p = (char *) skip_blank(*str);
+ int blank = 0;
+
+ if (*p == '-') {
+ char *x = ++p;
+ p = (char *) skip_blank(x);
+ blank = x < p; /* "- " */
+ }
+
+ if (*p == ';' || *p == ',') {
+ *str = ++p;
+ return 1;
+ }
+ if (*p == '\0' || blank) {
+ *str = p;
+ return 1;
+ }
+
+ return 0;
+}
+
+static int next_string(char **s, char **str)
+{
+ char *tk, *p = NULL;
+ int rc = -EINVAL;
+
+ assert(s);
+ assert(str);
+
+ tk = next_token(s);
+ if (tk) {
+ p = strdup(tk);
+ rc = p ? 0 : -ENOMEM;
+ }
+
+ *str = p;
+ return rc;
+}
+
+static int skip_optional_sign(char **str)
+{
+ char *p = (char *) skip_blank(*str);
+
+ if (*p == '-' || *p == '+') {
+ *str = p+1;
+ return *p;
+ }
+ return 0;
+}
+
+static int recount_script2device_sectors(struct fdisk_script *dp, uint64_t *num)
+{
+ if (!dp->cxt ||
+ !dp->sector_size ||
+ !dp->cxt->sector_size)
+ return 0;
+
+ if (dp->sector_size > dp->cxt->sector_size)
+ *num *= (dp->sector_size / dp->cxt->sector_size);
+
+ else if (dp->sector_size < dp->cxt->sector_size) {
+ uint64_t x = dp->cxt->sector_size / dp->sector_size;
+
+ if (*num % x)
+ return -EINVAL;
+ *num /= x;
+ }
+
+ return 0;
+}
+
+static int parse_start_value(struct fdisk_script *dp, struct fdisk_partition *pa, char **str)
+{
+ char *tk;
+ int rc = 0;
+
+ assert(str);
+
+ if (is_default_value(str)) {
+ fdisk_partition_start_follow_default(pa, 1);
+ return 0;
+ }
+
+ tk = next_token(str);
+ if (!tk)
+ return -EINVAL;
+
+ if (strcmp(tk, "+") == 0) {
+ fdisk_partition_start_follow_default(pa, 1);
+ pa->movestart = FDISK_MOVE_DOWN;
+ } else {
+ int pow = 0, sign = skip_optional_sign(&tk);
+ uint64_t num;
+
+ rc = parse_size(tk, (uintmax_t *) &num, &pow);
+ if (!rc) {
+ if (pow) { /* specified as <num><suffix> */
+ if (!dp->cxt->sector_size) {
+ rc = -EINVAL;
+ goto done;
+ }
+ num /= dp->cxt->sector_size;
+ } else {
+ rc = recount_script2device_sectors(dp, &num);
+ if (rc) {
+ fdisk_warnx(dp->cxt, _("Can't recalculate partition start to the device sectors"));
+ goto done;
+ }
+ }
+
+ fdisk_partition_set_start(pa, num);
+
+ pa->movestart = sign == '-' ? FDISK_MOVE_DOWN :
+ sign == '+' ? FDISK_MOVE_UP :
+ FDISK_MOVE_NONE;
+ }
+ fdisk_partition_start_follow_default(pa, 0);
+ }
+
+done:
+ DBG(SCRIPT, ul_debugobj(dp, " start parse result: rc=%d, move=%s, start=%ju, default=%s",
+ rc, pa->movestart == FDISK_MOVE_DOWN ? "down" :
+ pa->movestart == FDISK_MOVE_UP ? "up" : "none",
+ pa->start,
+ pa->start_follow_default ? "on" : "off"));
+ return rc;
+}
+
+static int parse_size_value(struct fdisk_script *dp, struct fdisk_partition *pa, char **str)
+{
+ char *tk;
+ int rc = 0;
+
+ if (is_default_value(str)) {
+ fdisk_partition_end_follow_default(pa, 1);
+ return 0;
+ }
+
+ tk = next_token(str);
+ if (!tk)
+ return -EINVAL;
+
+ if (strcmp(tk, "+") == 0) {
+ fdisk_partition_end_follow_default(pa, 1);
+ pa->resize = FDISK_RESIZE_ENLARGE;
+ } else {
+ /* '[+-]<number>[<suffix] */
+ int pow = 0, sign = skip_optional_sign(&tk);
+ uint64_t num;
+
+ rc = parse_size(tk, (uintmax_t *) &num, &pow);
+ if (!rc) {
+ if (pow) { /* specified as <size><suffix> */
+ if (!dp->cxt->sector_size) {
+ rc = -EINVAL;
+ goto done;
+ }
+ num /= dp->cxt->sector_size;
+ } else {
+ /* specified as number of sectors */
+ fdisk_partition_size_explicit(pa, 1);
+ rc = recount_script2device_sectors(dp, &num);
+ if (rc) {
+ fdisk_warnx(dp->cxt, _("Can't recalculate partition size to the device sectors"));
+ goto done;
+ }
+ }
+
+ fdisk_partition_set_size(pa, num);
+ pa->resize = sign == '-' ? FDISK_RESIZE_REDUCE :
+ sign == '+' ? FDISK_RESIZE_ENLARGE :
+ FDISK_RESIZE_NONE;
+ }
+ fdisk_partition_end_follow_default(pa, 0);
+ }
+
+done:
+ DBG(SCRIPT, ul_debugobj(dp, " size parse result: rc=%d, move=%s, size=%ju, default=%s",
+ rc, pa->resize == FDISK_RESIZE_REDUCE ? "reduce" :
+ pa->resize == FDISK_RESIZE_ENLARGE ? "enlage" : "none",
+ pa->size,
+ pa->end_follow_default ? "on" : "off"));
+ return rc;
+}
+
+
+#define FDISK_SCRIPT_PARTTYPE_PARSE_FLAGS \
+ (FDISK_PARTTYPE_PARSE_DATA | FDISK_PARTTYPE_PARSE_DATALAST | \
+ FDISK_PARTTYPE_PARSE_SHORTCUT | FDISK_PARTTYPE_PARSE_ALIAS | \
+ FDISK_PARTTYPE_PARSE_NAME | \
+ FDISK_PARTTYPE_PARSE_DEPRECATED)
+
+/* dump format
+ * <device>: start=<num>, size=<num>, type=<string>, ...
+ */
+static int parse_line_nameval(struct fdisk_script *dp, char *s)
+{
+ char *p, *x;
+ struct fdisk_partition *pa;
+ int rc = 0;
+ int pno;
+
+ assert(dp);
+ assert(s);
+ assert(dp->table);
+
+ DBG(SCRIPT, ul_debugobj(dp, " parse script line: '%s'", s));
+
+ pa = fdisk_new_partition();
+ if (!pa)
+ return -ENOMEM;
+
+ fdisk_partition_start_follow_default(pa, 1);
+ fdisk_partition_end_follow_default(pa, 1);
+ fdisk_partition_partno_follow_default(pa, 1);
+
+ /* set partno */
+ p = strchr(s, ':');
+ x = strchr(s, '=');
+ if (p && (!x || p < x)) {
+ *p = '\0';
+ p++;
+
+ pno = partno_from_devname(s);
+ if (pno >= 0) {
+ fdisk_partition_partno_follow_default(pa, 0);
+ fdisk_partition_set_partno(pa, pno);
+ }
+ } else
+ p = s;
+
+ while (rc == 0 && p && *p) {
+
+ DBG(SCRIPT, ul_debugobj(dp, " parsing '%s'", p));
+ p = (char *) skip_blank(p);
+
+ if (!strncasecmp(p, "start=", 6)) {
+ p += 6;
+ rc = parse_start_value(dp, pa, &p);
+
+ } else if (!strncasecmp(p, "size=", 5)) {
+ p += 5;
+ rc = parse_size_value(dp, pa, &p);
+
+ } else if (!strncasecmp(p, "bootable", 8)) {
+ /* we use next_token() to skip possible extra space */
+ char *tk = next_token(&p);
+ if (tk && strcasecmp(tk, "bootable") == 0)
+ pa->boot = 1;
+ else
+ rc = -EINVAL;
+
+ } else if (!strncasecmp(p, "attrs=", 6)) {
+ p += 6;
+ free(pa->attrs);
+ rc = next_string(&p, &pa->attrs);
+
+ } else if (!strncasecmp(p, "uuid=", 5)) {
+ p += 5;
+ free(pa->uuid);
+ rc = next_string(&p, &pa->uuid);
+
+ } else if (!strncasecmp(p, "name=", 5)) {
+ p += 5;
+ free(pa->name);
+ rc = next_string(&p, &pa->name);
+ if (!rc)
+ unhexmangle_string(pa->name);
+
+ } else if (!strncasecmp(p, "type=", 5) ||
+ !strncasecmp(p, "Id=", 3)) { /* backward compatibility */
+ char *type = NULL;
+
+ fdisk_unref_parttype(pa->type);
+ pa->type = NULL;
+
+ p += ((*p == 'I' || *p == 'i') ? 3 : 5); /* "Id=", "type=" */
+
+ rc = next_string(&p, &type);
+ if (rc == 0) {
+ pa->type = fdisk_label_advparse_parttype(script_get_label(dp),
+ type, FDISK_SCRIPT_PARTTYPE_PARSE_FLAGS);
+ if (!pa->type)
+ rc = -EINVAL;
+ }
+ free(type);
+ } else {
+ DBG(SCRIPT, ul_debugobj(dp, "script parse error: unknown field '%s'", p));
+ rc = -EINVAL;
+ break;
+ }
+ }
+
+ if (!rc)
+ rc = fdisk_table_add_partition(dp->table, pa);
+ if (rc)
+ DBG(SCRIPT, ul_debugobj(dp, "script parse error: [rc=%d]", rc));
+
+ fdisk_unref_partition(pa);
+ return rc;
+}
+
+/* simple format:
+ * <start>, <size>, <type>, <bootable>, ...
+ */
+static int parse_line_valcommas(struct fdisk_script *dp, char *s)
+{
+ int rc = 0;
+ char *p = s;
+ struct fdisk_partition *pa;
+ enum { ITEM_START, ITEM_SIZE, ITEM_TYPE, ITEM_BOOTABLE };
+ int item = -1;
+
+ assert(dp);
+ assert(s);
+ assert(dp->table);
+
+ pa = fdisk_new_partition();
+ if (!pa)
+ return -ENOMEM;
+
+ fdisk_partition_start_follow_default(pa, 1);
+ fdisk_partition_end_follow_default(pa, 1);
+ fdisk_partition_partno_follow_default(pa, 1);
+
+ while (rc == 0 && p && *p) {
+ char *begin;
+
+ p = (char *) skip_blank(p);
+ item++;
+
+ DBG(SCRIPT, ul_debugobj(dp, " parsing item %d ('%s')", item, p));
+ begin = p;
+
+ switch (item) {
+ case ITEM_START:
+ rc = parse_start_value(dp, pa, &p);
+ break;
+ case ITEM_SIZE:
+ rc = parse_size_value(dp, pa, &p);
+ break;
+ case ITEM_TYPE:
+ {
+ char *str = NULL;
+
+ fdisk_unref_parttype(pa->type);
+ pa->type = NULL;
+
+ if (*p == ',' || *p == ';' || is_default_value(&p))
+ break; /* use default type */
+
+ rc = next_string(&p, &str);
+ if (rc)
+ break;
+
+ pa->type = fdisk_label_advparse_parttype(script_get_label(dp),
+ str, FDISK_SCRIPT_PARTTYPE_PARSE_FLAGS);
+ free(str);
+ if (!pa->type)
+ rc = -EINVAL;
+ break;
+ }
+ case ITEM_BOOTABLE:
+ if (*p == ',' || *p == ';')
+ break;
+ else {
+ char *tk = next_token(&p);
+ if (tk && *tk == '*' && *(tk + 1) == '\0')
+ pa->boot = 1;
+ else if (tk && *tk == '-' && *(tk + 1) == '\0')
+ pa->boot = 0;
+ else if (tk && *tk == '+' && *(tk + 1) == '\0')
+ pa->boot = 1;
+ else
+ rc = -EINVAL;
+ }
+ break;
+ default:
+ break;
+ }
+
+ if (begin == p)
+ p++;
+ }
+
+ if (!rc)
+ rc = fdisk_table_add_partition(dp->table, pa);
+ if (rc)
+ DBG(SCRIPT, ul_debugobj(dp, "script parse error: [rc=%d]", rc));
+
+ fdisk_unref_partition(pa);
+ return rc;
+}
+
+/* modifies @s ! */
+static int fdisk_script_read_buffer(struct fdisk_script *dp, char *s)
+{
+ int rc = 0;
+
+ assert(dp);
+ assert(s);
+
+ DBG(SCRIPT, ul_debugobj(dp, " parsing buffer"));
+
+ s = (char *) skip_blank(s);
+ if (!s || !*s)
+ return 0; /* nothing baby, ignore */
+
+ if (!dp->table && fdisk_script_get_table(dp) == NULL)
+ return -ENOMEM;
+
+ /* parse header lines only if no partition specified yet */
+ if (fdisk_table_is_empty(dp->table) && is_header_line(s))
+ rc = parse_line_header(dp, s);
+
+ /* parse script format */
+ else if (strchr(s, '='))
+ rc = parse_line_nameval(dp, s);
+
+ /* parse simple <value>, ... format */
+ else
+ rc = parse_line_valcommas(dp, s);
+
+ if (rc)
+ DBG(SCRIPT, ul_debugobj(dp, "%zu: parse error [rc=%d]",
+ dp->nlines, rc));
+ return rc;
+}
+
+/**
+ * fdisk_script_set_fgets:
+ * @dp: script
+ * @fn_fgets: callback function
+ *
+ * The library uses fgets() function to read the next line from the script.
+ * This default maybe overridden by another function. Note that the function has
+ * to return the line terminated by \n (for example readline(3) removes \n).
+ *
+ * Return: 0 on success, <0 on error
+ */
+int fdisk_script_set_fgets(struct fdisk_script *dp,
+ char *(*fn_fgets)(struct fdisk_script *, char *, size_t, FILE *))
+{
+ assert(dp);
+
+ dp->fn_fgets = fn_fgets;
+ return 0;
+}
+
+/**
+ * fdisk_script_read_line:
+ * @dp: script
+ * @f: file
+ * @buf: buffer to store one line of the file
+ * @bufsz: buffer size
+ *
+ * Reads next line into dump.
+ *
+ * Returns: 0 on success, <0 on error, 1 when nothing to read. For unknown headers
+ * returns -ENOTSUP, it's usually safe to ignore this error.
+ */
+int fdisk_script_read_line(struct fdisk_script *dp, FILE *f, char *buf, size_t bufsz)
+{
+ char *s;
+
+ assert(dp);
+ assert(f);
+ assert(bufsz);
+
+ DBG(SCRIPT, ul_debugobj(dp, " parsing line %zu", dp->nlines));
+
+ /* read the next non-blank non-comment line */
+ do {
+ buf[0] = '\0';
+ if (dp->fn_fgets) {
+ if (dp->fn_fgets(dp, buf, bufsz, f) == NULL)
+ return 1;
+ } else if (fgets(buf, bufsz, f) == NULL)
+ return 1;
+
+ dp->nlines++;
+ s = strchr(buf, '\n');
+ if (!s) {
+ /* Missing final newline? Otherwise an extremely */
+ /* long line - assume file was corrupted */
+ if (feof(f)) {
+ DBG(SCRIPT, ul_debugobj(dp, "no final newline"));
+ s = strchr(buf, '\0');
+ } else {
+ DBG(SCRIPT, ul_debugobj(dp,
+ "%zu: missing newline at line", dp->nlines));
+ return -EINVAL;
+ }
+ }
+
+ *s = '\0';
+ if (--s >= buf && *s == '\r')
+ *s = '\0';
+ s = (char *) skip_blank(buf);
+ } while (*s == '\0' || *s == '#');
+
+ return fdisk_script_read_buffer(dp, s);
+}
+
+
+/**
+ * fdisk_script_read_file:
+ * @dp: script
+ * @f: input file
+ *
+ * Reads file @f into script @dp.
+ *
+ * Returns: 0 on success, <0 on error.
+ */
+int fdisk_script_read_file(struct fdisk_script *dp, FILE *f)
+{
+ char buf[BUFSIZ] = { '\0' };
+ int rc = 1;
+
+ assert(dp);
+ assert(f);
+
+ DBG(SCRIPT, ul_debugobj(dp, "parsing file"));
+
+ while (!feof(f)) {
+ rc = fdisk_script_read_line(dp, f, buf, sizeof(buf));
+ if (rc && rc != -ENOTSUP)
+ break;
+ }
+
+ if (rc == 1)
+ rc = 0; /* end of file */
+
+ DBG(SCRIPT, ul_debugobj(dp, "parsing file done [rc=%d]", rc));
+ return rc;
+}
+
+/**
+ * fdisk_set_script:
+ * @cxt: context
+ * @dp: script (or NULL to remove previous reference)
+ *
+ * Sets reference to the @dp script and remove reference to the previously used
+ * script.
+ *
+ * The script headers might be used by label drivers to overwrite
+ * built-in defaults (for example disk label Id) and label driver might
+ * optimize the default semantic to be more usable for scripts (for example to
+ * not ask for primary/logical/extended partition type).
+ *
+ * Note that script also contains reference to the fdisk context (see
+ * fdisk_new_script()). This context may be completely independent on
+ * context used for fdisk_set_script().
+ *
+ * Don't forget to call fdisk_set_script(cxt, NULL); to remove this reference
+ * if no more necessary!
+ *
+ * Returns: <0 on error, 0 on success.
+ */
+int fdisk_set_script(struct fdisk_context *cxt, struct fdisk_script *dp)
+{
+ assert(cxt);
+
+ /* unref old */
+ if (cxt->script)
+ fdisk_unref_script(cxt->script);
+
+ /* ref new */
+ cxt->script = dp;
+ if (cxt->script) {
+ DBG(CXT, ul_debugobj(cxt, "setting reference to script %p", cxt->script));
+ fdisk_ref_script(cxt->script);
+ }
+
+ return 0;
+}
+
+/**
+ * fdisk_get_script:
+ * @cxt: context
+ *
+ * Returns: the current script or NULL.
+ */
+struct fdisk_script *fdisk_get_script(struct fdisk_context *cxt)
+{
+ assert(cxt);
+ return cxt->script;
+}
+
+/**
+ * fdisk_apply_script_headers:
+ * @cxt: context
+ * @dp: script
+ *
+ * Associate context @cxt with script @dp and creates a new empty disklabel.
+ * The script may be later unreference by fdisk_set_script() with NULL as script.
+ *
+ * Returns: 0 on success, <0 on error.
+ */
+int fdisk_apply_script_headers(struct fdisk_context *cxt, struct fdisk_script *dp)
+{
+ const char *name;
+ const char *str;
+ int rc;
+
+ assert(cxt);
+ assert(dp);
+
+ DBG(SCRIPT, ul_debugobj(dp, "applying script headers"));
+ fdisk_set_script(cxt, dp);
+
+ if (dp->sector_size && dp->cxt->sector_size != dp->sector_size) {
+ /*
+ * Ignore last and first LBA if device sector size mismatch
+ * with sector size in script. It would be possible to
+ * recalculate it, but for GPT it will not work in some cases
+ * as these offsets are calculated by relative number of
+ * sectors. It's better to use library defaults than try
+ * to be smart ...
+ */
+ if (fdisk_script_get_header(dp, "first-lba")) {
+ fdisk_script_set_header(dp, "first-lba", NULL);
+ fdisk_info(dp->cxt, _("Ignore \"first-lba\" header due to sector size mismatch."));
+ }
+ if (fdisk_script_get_header(dp, "last-lba")) {
+ fdisk_script_set_header(dp, "last-lba", NULL);
+ fdisk_info(dp->cxt, _("Ignore \"last-lba\" header due to sector size mismatch."));
+ }
+ }
+
+ str = fdisk_script_get_header(dp, "grain");
+ if (str) {
+ uintmax_t sz;
+
+ rc = parse_size(str, &sz, NULL);
+ if (rc == 0)
+ rc = fdisk_save_user_grain(cxt, sz);
+ if (rc)
+ return rc;
+ }
+
+ if (fdisk_has_user_device_properties(cxt))
+ fdisk_apply_user_device_properties(cxt);
+
+ /* create empty label */
+ name = fdisk_script_get_header(dp, "label");
+ if (!name)
+ return -EINVAL;
+
+ rc = fdisk_create_disklabel(cxt, name);
+ if (rc)
+ return rc;
+
+ str = fdisk_script_get_header(dp, "table-length");
+ if (str) {
+ uintmax_t sz;
+
+ rc = parse_size(str, &sz, NULL);
+ if (rc == 0)
+ rc = fdisk_gpt_set_npartitions(cxt, sz);
+ }
+
+ return rc;
+}
+
+/**
+ * fdisk_apply_script:
+ * @cxt: context
+ * @dp: script
+ *
+ * This function creates a new disklabel and partition within context @cxt. You
+ * have to call fdisk_write_disklabel() to apply changes to the device.
+ *
+ * Returns: 0 on error, <0 on error.
+ */
+int fdisk_apply_script(struct fdisk_context *cxt, struct fdisk_script *dp)
+{
+ int rc;
+ struct fdisk_script *old;
+
+ assert(dp);
+ assert(cxt);
+
+ DBG(CXT, ul_debugobj(cxt, "applying script %p", dp));
+
+ old = fdisk_get_script(cxt);
+ fdisk_ref_script(old);
+
+ /* create empty disk label */
+ rc = fdisk_apply_script_headers(cxt, dp);
+
+ /* create partitions */
+ if (!rc && dp->table)
+ rc = fdisk_apply_table(cxt, dp->table);
+
+ fdisk_set_script(cxt, old);
+ fdisk_unref_script(old);
+
+ DBG(CXT, ul_debugobj(cxt, "script done [rc=%d]", rc));
+ return rc;
+}
+
+#ifdef FUZZ_TARGET
+# include "all-io.h"
+
+int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
+{
+ char name[] = "/tmp/test-script-fuzz.XXXXXX";
+ int fd;
+ struct fdisk_script *dp;
+ struct fdisk_context *cxt;
+ FILE *f;
+
+ fd = mkstemp_cloexec(name);
+ if (fd < 0)
+ err(EXIT_FAILURE, "mkstemp() failed");
+ if (write_all(fd, data, size) != 0)
+ err(EXIT_FAILURE, "write() failed");
+ f = fopen(name, "r");
+ if (!f)
+ err(EXIT_FAILURE, "cannot open %s", name);
+
+ cxt = fdisk_new_context();
+ dp = fdisk_new_script(cxt);
+
+ fdisk_script_read_file(dp, f);
+ fclose(f);
+
+ fdisk_script_write_file(dp, stdout);
+ fdisk_unref_script(dp);
+ fdisk_unref_context(cxt);
+
+ close(fd);
+ unlink(name);
+
+ return 0;
+}
+#endif
+
+#ifdef TEST_PROGRAM
+static int test_dump(struct fdisk_test *ts, int argc, char *argv[])
+{
+ char *devname = argv[1];
+ struct fdisk_context *cxt;
+ struct fdisk_script *dp;
+
+ cxt = fdisk_new_context();
+ fdisk_assign_device(cxt, devname, 1);
+
+ dp = fdisk_new_script(cxt);
+ fdisk_script_read_context(dp, NULL);
+
+ fdisk_script_write_file(dp, stdout);
+ fdisk_unref_script(dp);
+ fdisk_unref_context(cxt);
+
+ return 0;
+}
+
+static int test_read(struct fdisk_test *ts, int argc, char *argv[])
+{
+ char *filename = argv[1];
+ struct fdisk_script *dp;
+ struct fdisk_context *cxt;
+ FILE *f;
+
+ if (!(f = fopen(filename, "r")))
+ err(EXIT_FAILURE, "%s: cannot open", filename);
+
+ cxt = fdisk_new_context();
+ dp = fdisk_new_script(cxt);
+
+ fdisk_script_read_file(dp, f);
+ fclose(f);
+
+ fdisk_script_write_file(dp, stdout);
+ fdisk_unref_script(dp);
+ fdisk_unref_context(cxt);
+
+ return 0;
+}
+
+static int test_stdin(struct fdisk_test *ts, int argc, char *argv[])
+{
+ char buf[BUFSIZ] = { '\0' };
+ struct fdisk_script *dp;
+ struct fdisk_context *cxt;
+ int rc = 0;
+
+ cxt = fdisk_new_context();
+ dp = fdisk_new_script(cxt);
+ fdisk_script_set_header(dp, "label", "dos");
+
+ printf("<start>, <size>, <type>, <bootable: *|->\n");
+ do {
+ struct fdisk_partition *pa;
+ size_t n = dp->table ? fdisk_table_get_nents(dp->table) : 0;
+
+ printf(" #%zu :\n", n + 1);
+ rc = fdisk_script_read_line(dp, stdin, buf, sizeof(buf));
+
+ if (rc == 0) {
+ pa = fdisk_table_get_partition(dp->table, n);
+ printf(" #%zu %12ju %12ju\n", n + 1,
+ (uintmax_t)fdisk_partition_get_start(pa),
+ (uintmax_t)fdisk_partition_get_size(pa));
+ }
+ } while (rc == 0);
+
+ if (!rc)
+ fdisk_script_write_file(dp, stdout);
+ fdisk_unref_script(dp);
+ fdisk_unref_context(cxt);
+
+ return rc;
+}
+
+static int test_apply(struct fdisk_test *ts, int argc, char *argv[])
+{
+ char *devname = argv[1], *scriptname = argv[2];
+ struct fdisk_context *cxt;
+ struct fdisk_script *dp;
+ struct fdisk_table *tb = NULL;
+ struct fdisk_iter *itr = NULL;
+ struct fdisk_partition *pa = NULL;
+ int rc;
+
+ cxt = fdisk_new_context();
+ fdisk_assign_device(cxt, devname, 0);
+
+ dp = fdisk_new_script_from_file(cxt, scriptname);
+ if (!dp)
+ return -errno;
+
+ rc = fdisk_apply_script(cxt, dp);
+ if (rc)
+ goto done;
+ fdisk_unref_script(dp);
+
+ /* list result */
+ fdisk_list_disklabel(cxt);
+ fdisk_get_partitions(cxt, &tb);
+
+ itr = fdisk_new_iter(FDISK_ITER_FORWARD);
+ while (fdisk_table_next_partition(tb, itr, &pa) == 0) {
+ printf(" #%zu %12ju %12ju\n", fdisk_partition_get_partno(pa),
+ (uintmax_t)fdisk_partition_get_start(pa),
+ (uintmax_t)fdisk_partition_get_size(pa));
+ }
+
+done:
+ fdisk_free_iter(itr);
+ fdisk_unref_table(tb);
+
+ /*fdisk_write_disklabel(cxt);*/
+ fdisk_unref_context(cxt);
+ return 0;
+}
+
+static int test_tokens(struct fdisk_test *ts, int argc, char *argv[])
+{
+ char *p, *str = argc == 2 ? strdup(argv[1]) : NULL;
+ int i;
+
+ for (i = 1, p = str; p && *p; i++) {
+ char *tk = next_token(&p);
+
+ if (!tk)
+ break;
+
+ printf("#%d: '%s'\n", i, tk);
+ }
+
+ free(str);
+ return 0;
+}
+
+int main(int argc, char *argv[])
+{
+ struct fdisk_test tss[] = {
+ { "--dump", test_dump, "<device> dump PT as script" },
+ { "--read", test_read, "<file> read PT script from file" },
+ { "--apply", test_apply, "<device> <file> try apply script from file to device" },
+ { "--stdin", test_stdin, " read input like sfdisk" },
+ { "--tokens", test_tokens, "<string> parse string" },
+ { NULL }
+ };
+
+ return fdisk_run_test(tss, argc, argv);
+}
+
+#endif
diff --git a/libfdisk/src/sgi.c b/libfdisk/src/sgi.c
new file mode 100644
index 0000000..6740535
--- /dev/null
+++ b/libfdisk/src/sgi.c
@@ -0,0 +1,1210 @@
+/*
+ *
+ * Copyright (C) 2012 Davidlohr Bueso <dave@gnu.org>
+ * 2013 Karel Zak <kzak@redhat.com>
+ *
+ * This is a re-written version for libfdisk, the original was fdisksgilabel.c
+ * from util-linux fdisk, by:
+ *
+ * Andreas Neuper, Sep 1998,
+ * Arnaldo Carvalho de Melo <acme@conectiva.com.br>, Mar 1999,
+ * Phillip Kesling <pkesling@sgi.com>, Mar 2003.
+ */
+
+#include "c.h"
+#include "all-io.h"
+
+#include "blkdev.h"
+
+#include "bitops.h"
+#include "pt-sgi.h"
+#include "pt-mbr.h"
+#include "fdiskP.h"
+
+/**
+ * SECTION: sgi
+ * @title: SGI
+ * @short_description: disk label specific functions
+ *
+ */
+
+/*
+ * in-memory fdisk SGI stuff
+ */
+struct fdisk_sgi_label {
+ struct fdisk_label head; /* generic fdisk part */
+ struct sgi_disklabel *header; /* on-disk data (pointer to cxt->firstsector) */
+
+ struct sgi_freeblocks {
+ unsigned int first;
+ unsigned int last;
+ } freelist[SGI_MAXPARTITIONS + 1];
+};
+
+static struct fdisk_parttype sgi_parttypes[] =
+{
+ {SGI_TYPE_VOLHDR, N_("SGI volhdr")},
+ {SGI_TYPE_TRKREPL, N_("SGI trkrepl")},
+ {SGI_TYPE_SECREPL, N_("SGI secrepl")},
+ {SGI_TYPE_SWAP, N_("SGI raw")},
+ {SGI_TYPE_BSD, N_("SGI bsd")},
+ {SGI_TYPE_SYSV, N_("SGI sysv")},
+ {SGI_TYPE_ENTIRE_DISK, N_("SGI volume")},
+ {SGI_TYPE_EFS, N_("SGI efs")},
+ {SGI_TYPE_LVOL, N_("SGI lvol")},
+ {SGI_TYPE_RLVOL, N_("SGI rlvol")},
+ {SGI_TYPE_XFS, N_("SGI xfs")},
+ {SGI_TYPE_XFSLOG, N_("SGI xfslog")},
+ {SGI_TYPE_XLV, N_("SGI xlv")},
+ {SGI_TYPE_XVM, N_("SGI xvm")},
+ {MBR_LINUX_SWAP_PARTITION, N_("Linux swap")},
+ {MBR_LINUX_DATA_PARTITION, N_("Linux native")},
+ {MBR_LINUX_LVM_PARTITION, N_("Linux LVM")},
+ {MBR_LINUX_RAID_PARTITION, N_("Linux RAID")},
+ {0, NULL }
+};
+
+static unsigned int sgi_get_start_sector(struct fdisk_context *cxt, int i );
+static unsigned int sgi_get_num_sectors(struct fdisk_context *cxt, int i );
+static int sgi_get_bootpartition(struct fdisk_context *cxt);
+static int sgi_get_swappartition(struct fdisk_context *cxt);
+
+/* Returns a pointer buffer with on-disk data. */
+static inline struct sgi_disklabel *self_disklabel(struct fdisk_context *cxt)
+{
+ assert(cxt);
+ assert(cxt->label);
+ assert(fdisk_is_label(cxt, SGI));
+
+ return ((struct fdisk_sgi_label *) cxt->label)->header;
+}
+
+/* Returns in-memory fdisk data. */
+static inline struct fdisk_sgi_label *self_label(struct fdisk_context *cxt)
+{
+ assert(cxt);
+ assert(cxt->label);
+ assert(fdisk_is_label(cxt, SGI));
+
+ return (struct fdisk_sgi_label *) cxt->label;
+}
+
+/*
+ * Information within second on-disk block
+ */
+#define SGI_INFO_MAGIC 0x00072959
+
+struct sgi_info {
+ unsigned int magic; /* looks like a magic number */
+ unsigned int a2;
+ unsigned int a3;
+ unsigned int a4;
+ unsigned int b1;
+ unsigned short b2;
+ unsigned short b3;
+ unsigned int c[16];
+ unsigned short d[3];
+ unsigned char scsi_string[50];
+ unsigned char serial[137];
+ unsigned short check1816;
+ unsigned char installer[225];
+};
+
+static struct sgi_info *sgi_new_info(void)
+{
+ struct sgi_info *info = calloc(1, sizeof(struct sgi_info));
+
+ if (!info)
+ return NULL;
+
+ info->magic = cpu_to_be32(SGI_INFO_MAGIC);
+ info->b1 = cpu_to_be32(-1);
+ info->b2 = cpu_to_be16(-1);
+ info->b3 = cpu_to_be16(1);
+
+ /* You may want to replace this string !!!!!!! */
+ strcpy((char *) info->scsi_string, "IBM OEM 0662S12 3 30");
+ strcpy((char *) info->serial, "0000");
+ info->check1816 = cpu_to_be16(18 * 256 + 16);
+ strcpy((char *) info->installer, "Sfx version 5.3, Oct 18, 1994");
+
+ return info;
+}
+
+static void sgi_free_info(struct sgi_info *info)
+{
+ free(info);
+}
+
+/**
+ * fdisk_sgi_create_info:
+ * @cxt: context
+ *
+ * This function add hint about SGI label (e.g. set "sgilabel" as volume name)
+ * to the first SGI volume. This is probably old SGI convention without any
+ * effect to the device partitioning.
+ *
+ * Returns: 0 on success, <0 on error
+ */
+int fdisk_sgi_create_info(struct fdisk_context *cxt)
+{
+ struct sgi_disklabel *sgilabel = self_disklabel(cxt);
+
+ /* I keep SGI's habit to write the sgilabel to the second block */
+ sgilabel->volume[0].block_num = cpu_to_be32(2);
+ sgilabel->volume[0].num_bytes = cpu_to_be32(sizeof(struct sgi_info));
+ memcpy((char *) sgilabel->volume[0].name, "sgilabel", 8);
+
+ fdisk_info(cxt, _("SGI info created on second sector."));
+ return 0;
+}
+
+
+/*
+ * only dealing with free blocks here
+ */
+static void set_freelist(struct fdisk_context *cxt,
+ size_t i, unsigned int f, unsigned int l)
+{
+ struct fdisk_sgi_label *sgi = self_label(cxt);
+
+ if (i < ARRAY_SIZE(sgi->freelist)) {
+ sgi->freelist[i].first = f;
+ sgi->freelist[i].last = l;
+ }
+}
+
+static void add_to_freelist(struct fdisk_context *cxt,
+ unsigned int f, unsigned int l)
+{
+ struct fdisk_sgi_label *sgi = self_label(cxt);
+ size_t i;
+
+ for (i = 0; i < ARRAY_SIZE(sgi->freelist); i++) {
+ if (sgi->freelist[i].last == 0)
+ break;
+ }
+ set_freelist(cxt, i, f, l);
+}
+
+static void clear_freelist(struct fdisk_context *cxt)
+{
+ struct fdisk_sgi_label *sgi = self_label(cxt);
+
+ memset(sgi->freelist, 0, sizeof(sgi->freelist));
+}
+
+static unsigned int is_in_freelist(struct fdisk_context *cxt, unsigned int b)
+{
+ struct fdisk_sgi_label *sgi = self_label(cxt);
+ size_t i;
+
+ for (i = 0; i < ARRAY_SIZE(sgi->freelist); i++) {
+ if (sgi->freelist[i].first <= b
+ && sgi->freelist[i].last >= b)
+ return sgi->freelist[i].last;
+ }
+
+ return 0;
+}
+
+
+static int sgi_get_nsect(struct fdisk_context *cxt)
+{
+ struct sgi_disklabel *sgilabel = self_disklabel(cxt);
+ return be16_to_cpu(sgilabel->devparam.nsect);
+}
+
+static int sgi_get_ntrks(struct fdisk_context *cxt)
+{
+ struct sgi_disklabel *sgilabel = self_disklabel(cxt);
+ return be16_to_cpu(sgilabel->devparam.ntrks);
+}
+
+static size_t count_used_partitions(struct fdisk_context *cxt)
+{
+ size_t i, ct = 0;
+
+ for (i = 0; i < cxt->label->nparts_max; i++)
+ ct += sgi_get_num_sectors(cxt, i) > 0;
+
+ return ct;
+}
+
+static int sgi_probe_label(struct fdisk_context *cxt)
+{
+ struct fdisk_sgi_label *sgi;
+ struct sgi_disklabel *sgilabel;
+
+ assert(cxt);
+ assert(cxt->label);
+ assert(fdisk_is_label(cxt, SGI));
+ assert(sizeof(struct sgi_disklabel) <= 512);
+
+ /* map first sector to header */
+ sgi = (struct fdisk_sgi_label *) cxt->label;
+ sgi->header = (struct sgi_disklabel *) cxt->firstsector;
+ sgilabel = sgi->header;
+
+ if (be32_to_cpu(sgilabel->magic) != SGI_LABEL_MAGIC) {
+ sgi->header = NULL;
+ return 0;
+ }
+
+ /*
+ * test for correct checksum
+ */
+ if (sgi_pt_checksum(sgilabel) != 0)
+ fdisk_warnx(cxt, _("Detected an SGI disklabel with wrong checksum."));
+
+ clear_freelist(cxt);
+ cxt->label->nparts_max = SGI_MAXPARTITIONS;
+ cxt->label->nparts_cur = count_used_partitions(cxt);
+ return 1;
+}
+
+static int sgi_get_disklabel_item(struct fdisk_context *cxt, struct fdisk_labelitem *item)
+{
+ struct sgi_disklabel *sgilabel;
+ struct sgi_device_parameter *sgiparam;
+ int rc = 0;
+
+ assert(cxt);
+ assert(cxt->label);
+ assert(fdisk_is_label(cxt, SGI));
+
+ sgilabel = self_disklabel(cxt);
+ sgiparam = &sgilabel->devparam;
+
+ switch (item->id) {
+ case SGI_LABELITEM_PCYLCOUNT:
+ item->name = _("Physical cylinders");
+ item->type = 'j';
+ item->data.num64 = (uint64_t) be16_to_cpu(sgiparam->pcylcount);
+ break;
+ case SGI_LABELITEM_SPARECYL:
+ item->name = _("Extra sects/cyl");
+ item->type = 'j';
+ item->data.num64 = (uint64_t) sgiparam->sparecyl;
+ break;
+ case SGI_LABELITEM_ILFACT:
+ item->name = _("Interleave");
+ item->type = 'j';
+ item->data.num64 = (uint64_t) be16_to_cpu(sgiparam->ilfact);
+ break;
+ case SGI_LABELITEM_BOOTFILE:
+ item->name = _("Bootfile");
+ item->type = 's';
+ item->data.str = *sgilabel->boot_file ? strdup((char *) sgilabel->boot_file) : NULL;
+ break;
+ default:
+ if (item->id < __FDISK_NLABELITEMS)
+ rc = 1; /* unsupported generic item */
+ else
+ rc = 2; /* out of range */
+ break;
+ }
+
+ return rc;
+}
+
+static unsigned int sgi_get_start_sector(struct fdisk_context *cxt, int i)
+{
+ struct sgi_disklabel *sgilabel = self_disklabel(cxt);
+ return be32_to_cpu(sgilabel->partitions[i].first_block);
+}
+
+static unsigned int sgi_get_num_sectors(struct fdisk_context *cxt, int i)
+{
+ struct sgi_disklabel *sgilabel = self_disklabel(cxt);
+ return be32_to_cpu(sgilabel->partitions[i].num_blocks);
+}
+
+static int sgi_get_sysid(struct fdisk_context *cxt, int i)
+{
+ struct sgi_disklabel *sgilabel = self_disklabel(cxt);
+ return be32_to_cpu(sgilabel->partitions[i].type);
+}
+
+static int sgi_get_bootpartition(struct fdisk_context *cxt)
+{
+ struct sgi_disklabel *sgilabel = self_disklabel(cxt);
+ return be16_to_cpu(sgilabel->root_part_num);
+}
+
+static int sgi_get_swappartition(struct fdisk_context *cxt)
+{
+ struct sgi_disklabel *sgilabel = self_disklabel(cxt);
+ return be16_to_cpu(sgilabel->swap_part_num);
+}
+
+static unsigned int sgi_get_lastblock(struct fdisk_context *cxt)
+{
+ return cxt->geom.heads * cxt->geom.sectors * cxt->geom.cylinders;
+}
+
+static struct fdisk_parttype *sgi_get_parttype(struct fdisk_context *cxt, size_t n)
+{
+ struct fdisk_parttype *t;
+
+ if (n >= cxt->label->nparts_max)
+ return NULL;
+
+ t = fdisk_label_get_parttype_from_code(cxt->label, sgi_get_sysid(cxt, n));
+ return t ? : fdisk_new_unknown_parttype(sgi_get_sysid(cxt, n), NULL);
+}
+
+/* fdisk_get_partition() backend */
+static int sgi_get_partition(struct fdisk_context *cxt, size_t n, struct fdisk_partition *pa)
+{
+ fdisk_sector_t start, len;
+
+ pa->used = sgi_get_num_sectors(cxt, n) > 0;
+ if (!pa->used)
+ return 0;
+
+ start = sgi_get_start_sector(cxt, n);
+ len = sgi_get_num_sectors(cxt, n);
+
+ pa->type = sgi_get_parttype(cxt, n);
+ pa->size = len;
+ pa->start = start;
+
+ if (pa->type && pa->type->code == SGI_TYPE_ENTIRE_DISK)
+ pa->wholedisk = 1;
+
+ pa->attrs = sgi_get_swappartition(cxt) == (int) n ? "swap" :
+ sgi_get_bootpartition(cxt) == (int) n ? "boot" : NULL;
+ if (pa->attrs)
+ pa->attrs = strdup(pa->attrs);
+
+ return 0;
+}
+
+
+static int sgi_check_bootfile(struct fdisk_context *cxt, const char *name)
+{
+ size_t sz;
+ struct sgi_disklabel *sgilabel = self_disklabel(cxt);
+
+ sz = strlen(name);
+
+ if (sz < 3) {
+ /* "/a\n" is minimum */
+ fdisk_warnx(cxt, _("Invalid bootfile! The bootfile must "
+ "be an absolute non-zero pathname, "
+ "e.g. \"/unix\" or \"/unix.save\"."));
+ return -EINVAL;
+
+ }
+
+ if (sz > sizeof(sgilabel->boot_file)) {
+ fdisk_warnx(cxt, P_("Name of bootfile is too long: %zu byte maximum.",
+ "Name of bootfile is too long: %zu bytes maximum.",
+ sizeof(sgilabel->boot_file)),
+ sizeof(sgilabel->boot_file));
+ return -EINVAL;
+
+ }
+
+ if (*name != '/') {
+ fdisk_warnx(cxt, _("Bootfile must have a fully qualified pathname."));
+ return -EINVAL;
+ }
+
+ if (strncmp(name, (char *) sgilabel->boot_file,
+ sizeof(sgilabel->boot_file)) != 0) {
+ fdisk_warnx(cxt, _("Be aware that the bootfile is not checked "
+ "for existence. SGI's default is \"/unix\", "
+ "and for backup \"/unix.save\"."));
+ return 0; /* filename is correct and did change */
+ }
+
+ return 1; /* filename did not change */
+}
+
+/**
+ * fdisk_sgi_set_bootfile:
+ * @cxt: context
+ *
+ * Allows to set SGI boot file. The function uses Ask API for dialog with
+ * user.
+ *
+ * Returns: 0 on success, <0 on error
+ */
+int fdisk_sgi_set_bootfile(struct fdisk_context *cxt)
+{
+ int rc = 0;
+ size_t sz;
+ char *name = NULL;
+ struct sgi_disklabel *sgilabel = self_disklabel(cxt);
+
+ fdisk_info(cxt, _("The current boot file is: %s"), sgilabel->boot_file);
+
+ rc = fdisk_ask_string(cxt, _("Enter of the new boot file"), &name);
+ if (rc == 0)
+ rc = sgi_check_bootfile(cxt, name);
+ if (rc) {
+ if (rc == 1)
+ fdisk_info(cxt, _("Boot file is unchanged."));
+ goto done;
+ }
+
+ memset(sgilabel->boot_file, 0, sizeof(sgilabel->boot_file));
+ sz = strlen(name);
+
+ assert(sz <= sizeof(sgilabel->boot_file)); /* see sgi_check_bootfile() */
+
+ memcpy(sgilabel->boot_file, name, sz);
+
+ fdisk_info(cxt, _("Bootfile has been changed to \"%s\"."), name);
+done:
+ free(name);
+ return rc;
+}
+
+static int sgi_write_disklabel(struct fdisk_context *cxt)
+{
+ struct sgi_disklabel *sgilabel;
+ struct sgi_info *info = NULL;
+
+ assert(cxt);
+ assert(cxt->label);
+ assert(fdisk_is_label(cxt, SGI));
+
+ sgilabel = self_disklabel(cxt);
+ sgilabel->csum = 0;
+ sgilabel->csum = cpu_to_be32(sgi_pt_checksum(sgilabel));
+
+ assert(sgi_pt_checksum(sgilabel) == 0);
+
+ if (lseek(cxt->dev_fd, 0, SEEK_SET) < 0)
+ goto err;
+ if (write_all(cxt->dev_fd, sgilabel, DEFAULT_SECTOR_SIZE))
+ goto err;
+ if (!strncmp((char *) sgilabel->volume[0].name, "sgilabel", 8)) {
+ /*
+ * Keep this habit of first writing the "sgilabel".
+ * I never tested whether it works without. (AN 1998-10-02)
+ */
+ int infostartblock
+ = be32_to_cpu(sgilabel->volume[0].block_num);
+
+ if (lseek(cxt->dev_fd, (off_t) infostartblock *
+ DEFAULT_SECTOR_SIZE, SEEK_SET) < 0)
+ goto err;
+ info = sgi_new_info();
+ if (!info)
+ goto err;
+ if (write_all(cxt->dev_fd, info, sizeof(*info)))
+ goto err;
+ }
+
+ sgi_free_info(info);
+ return 0;
+err:
+ sgi_free_info(info);
+ return -errno;
+}
+
+static int compare_start(struct fdisk_context *cxt,
+ const void *x, const void *y)
+{
+ /*
+ * Sort according to start sectors and prefer the largest partition:
+ * entry zero is the entire-disk entry.
+ */
+ const unsigned int i = *(const int *) x;
+ const unsigned int j = *(const int *) y;
+ unsigned int a = sgi_get_start_sector(cxt, i);
+ unsigned int b = sgi_get_start_sector(cxt, j);
+ unsigned int c = sgi_get_num_sectors(cxt, i);
+ unsigned int d = sgi_get_num_sectors(cxt, j);
+
+ if (a == b)
+ return (d > c) ? 1 : (d == c) ? 0 : -1;
+ return (a > b) ? 1 : -1;
+}
+
+static void generic_swap(void *a0, void *b0, int size)
+{
+ char *a = a0, *b = b0;
+
+ for (; size > 0; --size, a++, b++) {
+ char t = *a;
+ *a = *b;
+ *b = t;
+ }
+}
+
+
+/* heap sort, based on Matt Mackall's linux kernel version */
+static void sort(void *base0, size_t num, size_t size, struct fdisk_context *cxt,
+ int (*cmp_func)(struct fdisk_context *, const void *, const void *))
+{
+ /* pre-scale counters for performance */
+ int i = (num/2 - 1) * size;
+ size_t n = num * size, c, r;
+ char *base = base0;
+
+ /* heapify */
+ for ( ; i >= 0; i -= size) {
+ for (r = i; r * 2 + size < n; r = c) {
+ c = r * 2 + size;
+ if (c < n - size &&
+ cmp_func(cxt, base + c, base + c + size) < 0)
+ c += size;
+ if (cmp_func(cxt, base + r, base + c) >= 0)
+ break;
+ generic_swap(base + r, base + c, size);
+ }
+ }
+
+ /* sort */
+ for (i = n - size; i > 0; i -= size) {
+ generic_swap(base, base + i, size);
+ for (r = 0; r * 2 + size < (size_t) i; r = c) {
+ c = r * 2 + size;
+ if (c < i - size &&
+ cmp_func(cxt, base + c, base + c + size) < 0)
+ c += size;
+ if (cmp_func(cxt, base + r, base + c) >= 0)
+ break;
+ generic_swap(base + r, base + c, size);
+ }
+ }
+}
+
+static int verify_disklabel(struct fdisk_context *cxt, int verbose)
+{
+ int Index[SGI_MAXPARTITIONS]; /* list of valid partitions */
+ int sortcount = 0; /* number of used partitions, i.e. non-zero lengths */
+ int entire = 0, i = 0;
+ unsigned int start = 0;
+ long long gap = 0; /* count unused blocks */
+ unsigned int lastblock = sgi_get_lastblock(cxt);
+
+ assert(cxt);
+ assert(cxt->label);
+ assert(fdisk_is_label(cxt, SGI));
+
+ clear_freelist(cxt);
+ memset(Index, 0, sizeof(Index));
+
+ for (i=0; i < SGI_MAXPARTITIONS; i++) {
+ if (sgi_get_num_sectors(cxt, i) != 0) {
+ Index[sortcount++] = i;
+ if (sgi_get_sysid(cxt, i) == SGI_TYPE_ENTIRE_DISK
+ && entire++ == 1 && verbose) {
+ fdisk_info(cxt, _("More than one entire "
+ "disk entry present."));
+ }
+ }
+ }
+ if (sortcount == 0) {
+ if (verbose)
+ fdisk_info(cxt, _("No partitions defined."));
+ if (lastblock)
+ add_to_freelist(cxt, 0, lastblock);
+ return (lastblock > 0) ? 1 : (lastblock == 0) ? 0 : -1;
+ }
+
+ sort(Index, sortcount, sizeof(Index[0]), cxt, compare_start);
+
+ if (sgi_get_sysid(cxt, Index[0]) == SGI_TYPE_ENTIRE_DISK) {
+ if (verbose && Index[0] != 10)
+ fdisk_info(cxt, _("IRIX likes it when partition 11 "
+ "covers the entire disk."));
+
+ if (verbose && sgi_get_start_sector(cxt, Index[0]) != 0)
+ fdisk_info(cxt, _("The entire disk partition should "
+ "start at block 0, not at block %d."),
+ sgi_get_start_sector(cxt, Index[0]));
+
+ if (verbose && sgi_get_num_sectors(cxt, Index[0]) != lastblock)
+ DBG(LABEL, ul_debug(
+ "entire disk partition=%ds, but disk=%ds",
+ sgi_get_num_sectors(cxt, Index[0]),
+ lastblock));
+ lastblock = sgi_get_num_sectors(cxt, Index[0]);
+ } else if (verbose) {
+ fdisk_info(cxt, _("Partition 11 should cover the entire disk."));
+ DBG(LABEL, ul_debug("sysid=%d\tpartition=%d",
+ sgi_get_sysid(cxt, Index[0]), Index[0]+1));
+ }
+ for (i=1, start=0; i<sortcount; i++) {
+ int cylsize = sgi_get_nsect(cxt) * sgi_get_ntrks(cxt);
+
+ if (verbose && cylsize
+ && (sgi_get_start_sector(cxt, Index[i]) % cylsize) != 0)
+ DBG(LABEL, ul_debug("partition %d does not start on "
+ "cylinder boundary.", Index[i]+1));
+
+ if (verbose && cylsize
+ && sgi_get_num_sectors(cxt, Index[i]) % cylsize != 0)
+ DBG(LABEL, ul_debug("partition %d does not end on "
+ "cylinder boundary.", Index[i]+1));
+
+ /* We cannot handle several "entire disk" entries. */
+ if (sgi_get_sysid(cxt, Index[i]) == SGI_TYPE_ENTIRE_DISK)
+ continue;
+
+ if (start > sgi_get_start_sector(cxt, Index[i])) {
+ if (verbose)
+ fdisk_info(cxt,
+ P_("Partitions %d and %d overlap by %d sector.",
+ "Partitions %d and %d overlap by %d sectors.",
+ start - sgi_get_start_sector(cxt, Index[i])),
+ Index[i-1]+1, Index[i]+1,
+ start - sgi_get_start_sector(cxt, Index[i]));
+ if (gap > 0) gap = -gap;
+ if (gap == 0) gap = -1;
+ }
+ if (start < sgi_get_start_sector(cxt, Index[i])) {
+ if (verbose)
+ fdisk_info(cxt,
+ P_("Unused gap of %8u sector: sector %8u",
+ "Unused gap of %8u sectors: sectors %8u-%u",
+ sgi_get_start_sector(cxt, Index[i]) - start),
+ sgi_get_start_sector(cxt, Index[i]) - start,
+ start, sgi_get_start_sector(cxt, Index[i])-1);
+ gap += sgi_get_start_sector(cxt, Index[i]) - start;
+ add_to_freelist(cxt, start,
+ sgi_get_start_sector(cxt, Index[i]));
+ }
+ start = sgi_get_start_sector(cxt, Index[i])
+ + sgi_get_num_sectors(cxt, Index[i]);
+ /* Align free space on cylinder boundary. */
+ if (cylsize && start % cylsize)
+ start += cylsize - (start % cylsize);
+
+ DBG(LABEL, ul_debug("%2d:%12d\t%12d\t%12d", Index[i],
+ sgi_get_start_sector(cxt, Index[i]),
+ sgi_get_num_sectors(cxt, Index[i]),
+ sgi_get_sysid(cxt, Index[i])));
+ }
+ if (start < lastblock) {
+ if (verbose)
+ fdisk_info(cxt, P_("Unused gap of %8u sector: sector %8u",
+ "Unused gap of %8u sectors: sectors %8u-%u",
+ lastblock - start),
+ lastblock - start, start, lastblock-1);
+ gap += lastblock - start;
+ add_to_freelist(cxt, start, lastblock);
+ }
+ /*
+ * Done with arithmetic. Go for details now.
+ */
+ if (verbose) {
+ if (sgi_get_bootpartition(cxt) < 0
+ || !sgi_get_num_sectors(cxt, sgi_get_bootpartition(cxt)))
+ fdisk_info(cxt, _("The boot partition does not exist."));
+
+ if (sgi_get_swappartition(cxt) < 0
+ || !sgi_get_num_sectors(cxt, sgi_get_swappartition(cxt)))
+ fdisk_info(cxt, _("The swap partition does not exist."));
+
+ else if (sgi_get_sysid(cxt, sgi_get_swappartition(cxt)) != SGI_TYPE_SWAP
+ && sgi_get_sysid(cxt, sgi_get_swappartition(cxt)) != MBR_LINUX_SWAP_PARTITION)
+ fdisk_info(cxt, _("The swap partition has no swap type."));
+
+ if (sgi_check_bootfile(cxt, "/unix"))
+ fdisk_info(cxt, _("You have chosen an unusual bootfile name."));
+ }
+
+ return (gap > 0) ? 1 : (gap == 0) ? 0 : -1;
+}
+
+static int sgi_verify_disklabel(struct fdisk_context *cxt)
+{
+ return verify_disklabel(cxt, 1);
+}
+
+static int sgi_gaps(struct fdisk_context *cxt)
+{
+ /*
+ * returned value is:
+ * = 0 : disk is properly filled to the rim
+ * < 0 : there is an overlap
+ * > 0 : there is still some vacant space
+ */
+ return verify_disklabel(cxt, 0);
+}
+
+/* Returns partition index of first entry marked as entire disk. */
+static int sgi_entire(struct fdisk_context *cxt)
+{
+ size_t i;
+
+ for (i = 0; i < SGI_MAXPARTITIONS; i++)
+ if (sgi_get_sysid(cxt, i) == SGI_TYPE_ENTIRE_DISK)
+ return i;
+ return -1;
+}
+
+static int set_partition(struct fdisk_context *cxt, size_t i,
+ unsigned int start, unsigned int length, int sys)
+{
+ struct sgi_disklabel *sgilabel;
+
+ assert(cxt);
+ assert(cxt->label);
+ assert(fdisk_is_label(cxt, SGI));
+
+ sgilabel = self_disklabel(cxt);
+ sgilabel->partitions[i].type = cpu_to_be32(sys);
+ sgilabel->partitions[i].num_blocks = cpu_to_be32(length);
+ sgilabel->partitions[i].first_block = cpu_to_be32(start);
+
+ fdisk_label_set_changed(cxt->label, 1);
+
+ if (sgi_gaps(cxt) < 0) /* rebuild freelist */
+ fdisk_warnx(cxt, _("Partition overlap on the disk."));
+ if (length) {
+ struct fdisk_parttype *t =
+ fdisk_label_get_parttype_from_code(cxt->label, sys);
+ fdisk_info_new_partition(cxt, i + 1, start, start + length, t);
+ }
+
+ return 0;
+}
+
+static void sgi_set_entire(struct fdisk_context *cxt)
+{
+ size_t n;
+
+ for (n = 10; n < cxt->label->nparts_max; n++) {
+ if (!sgi_get_num_sectors(cxt, n)) {
+ set_partition(cxt, n, 0, sgi_get_lastblock(cxt), SGI_TYPE_ENTIRE_DISK);
+ break;
+ }
+ }
+}
+
+static void sgi_set_volhdr(struct fdisk_context *cxt)
+{
+ size_t n;
+
+ for (n = 8; n < cxt->label->nparts_max; n++) {
+ if (!sgi_get_num_sectors(cxt, n)) {
+ /* Choose same default volume header size as IRIX fx uses. */
+ if (4096 < sgi_get_lastblock(cxt))
+ set_partition(cxt, n, 0, 4096, SGI_TYPE_VOLHDR);
+ break;
+ }
+ }
+}
+
+static int sgi_delete_partition(struct fdisk_context *cxt, size_t partnum)
+{
+ int rc;
+
+ assert(cxt);
+ assert(cxt->label);
+
+ if (partnum > cxt->label->nparts_max)
+ return -EINVAL;
+
+ rc = set_partition(cxt, partnum, 0, 0, 0);
+
+ cxt->label->nparts_cur = count_used_partitions(cxt);
+
+ return rc;
+}
+
+static int sgi_add_partition(struct fdisk_context *cxt,
+ struct fdisk_partition *pa,
+ size_t *partno)
+{
+ struct fdisk_sgi_label *sgi;
+ char mesg[256];
+ unsigned int first = 0, last = 0;
+ struct fdisk_ask *ask;
+ int sys = pa && pa->type ? pa->type->code : SGI_TYPE_XFS;
+ int rc;
+ size_t n;
+
+ assert(cxt);
+ assert(cxt->label);
+ assert(fdisk_is_label(cxt, SGI));
+
+ rc = fdisk_partition_next_partno(pa, cxt, &n);
+ if (rc)
+ return rc;
+ if (n == 10)
+ sys = SGI_TYPE_ENTIRE_DISK;
+ else if (n == 8)
+ sys = 0;
+
+ sgi = self_label(cxt);
+
+ if (sgi_get_num_sectors(cxt, n)) {
+ fdisk_warnx(cxt, _("Partition %zu is already defined. "
+ "Delete it before re-adding it."), n + 1);
+ return -EINVAL;
+ }
+ if (!cxt->script && sgi_entire(cxt) == -1 && sys != SGI_TYPE_ENTIRE_DISK) {
+ fdisk_info(cxt, _("Attempting to generate entire disk entry automatically."));
+ sgi_set_entire(cxt);
+ sgi_set_volhdr(cxt);
+ }
+ if (sgi_gaps(cxt) == 0 && sys != SGI_TYPE_ENTIRE_DISK) {
+ fdisk_warnx(cxt, _("The entire disk is already covered with partitions."));
+ return -EINVAL;
+ }
+ if (sgi_gaps(cxt) < 0) {
+ fdisk_warnx(cxt, _("You got a partition overlap on the disk. Fix it first!"));
+ return -EINVAL;
+ }
+
+ if (sys == SGI_TYPE_ENTIRE_DISK) {
+ first = 0;
+ last = sgi_get_lastblock(cxt);
+ } else {
+ first = sgi->freelist[0].first;
+ last = sgi->freelist[0].last;
+ }
+
+ /* first sector */
+ if (pa && pa->start_follow_default)
+ ;
+ else if (pa && fdisk_partition_has_start(pa)) {
+ first = pa->start;
+ last = is_in_freelist(cxt, first);
+
+ if (sys != SGI_TYPE_ENTIRE_DISK && !last)
+ return -ERANGE;
+ } else {
+ snprintf(mesg, sizeof(mesg), _("First %s"),
+ fdisk_get_unit(cxt, FDISK_SINGULAR));
+ ask = fdisk_new_ask();
+ if (!ask)
+ return -ENOMEM;
+
+ fdisk_ask_set_query(ask, mesg);
+ fdisk_ask_set_type(ask, FDISK_ASKTYPE_NUMBER);
+
+ fdisk_ask_number_set_low(ask, fdisk_scround(cxt, first)); /* minimal */
+ fdisk_ask_number_set_default(ask, fdisk_scround(cxt, first)); /* default */
+ fdisk_ask_number_set_high(ask, fdisk_scround(cxt, last) - 1); /* maximal */
+
+ rc = fdisk_do_ask(cxt, ask);
+ first = fdisk_ask_number_get_result(ask);
+ fdisk_unref_ask(ask);
+
+ if (rc)
+ return rc;
+ if (fdisk_use_cylinders(cxt))
+ first *= fdisk_get_units_per_sector(cxt);
+ }
+
+ if (first && sys == SGI_TYPE_ENTIRE_DISK)
+ fdisk_info(cxt, _("It is highly recommended that the "
+ "eleventh partition covers the entire "
+ "disk and is of type 'SGI volume'."));
+ if (!last)
+ last = is_in_freelist(cxt, first);
+
+ /* last sector */
+ if (pa && pa->end_follow_default)
+ last -= 1ULL;
+ else if (pa && fdisk_partition_has_size(pa)) {
+ if (first + pa->size - 1ULL > last)
+ return -ERANGE;
+ last = first + pa->size - 1ULL;
+ } else {
+ snprintf(mesg, sizeof(mesg),
+ _("Last %s or +%s or +size{K,M,G,T,P}"),
+ fdisk_get_unit(cxt, FDISK_SINGULAR),
+ fdisk_get_unit(cxt, FDISK_PLURAL));
+
+ ask = fdisk_new_ask();
+ if (!ask)
+ return -ENOMEM;
+
+ fdisk_ask_set_query(ask, mesg);
+ fdisk_ask_set_type(ask, FDISK_ASKTYPE_OFFSET);
+
+ fdisk_ask_number_set_low(ask, fdisk_scround(cxt, first)); /* minimal */
+ fdisk_ask_number_set_default(ask, fdisk_scround(cxt, last) - 1);/* default */
+ fdisk_ask_number_set_high(ask, fdisk_scround(cxt, last) - 1);/* maximal */
+ fdisk_ask_number_set_base(ask, fdisk_scround(cxt, first));
+ fdisk_ask_number_set_wrap_negative(ask, 1); /* wrap negative around high */
+
+ if (fdisk_use_cylinders(cxt))
+ fdisk_ask_number_set_unit(ask,
+ cxt->sector_size *
+ fdisk_get_units_per_sector(cxt));
+ else
+ fdisk_ask_number_set_unit(ask,cxt->sector_size);
+
+ rc = fdisk_do_ask(cxt, ask);
+ last = fdisk_ask_number_get_result(ask) + 1;
+
+ fdisk_unref_ask(ask);
+ if (rc)
+ return rc;
+ if (fdisk_use_cylinders(cxt))
+ last *= fdisk_get_units_per_sector(cxt);
+ }
+
+ if (sys == SGI_TYPE_ENTIRE_DISK
+ && (first != 0 || last != sgi_get_lastblock(cxt)))
+ fdisk_info(cxt, _("It is highly recommended that the "
+ "eleventh partition covers the entire "
+ "disk and is of type 'SGI volume'."));
+
+ set_partition(cxt, n, first, last - first, sys);
+ cxt->label->nparts_cur = count_used_partitions(cxt);
+ if (partno)
+ *partno = n;
+ return 0;
+}
+
+static int sgi_create_disklabel(struct fdisk_context *cxt)
+{
+ struct fdisk_sgi_label *sgi;
+ struct sgi_disklabel *sgilabel;
+ int rc;
+
+ assert(cxt);
+ assert(cxt->label);
+ assert(fdisk_is_label(cxt, SGI));
+
+ if (cxt->geom.heads && cxt->geom.sectors) {
+ fdisk_sector_t llsectors;
+
+ if (blkdev_get_sectors(cxt->dev_fd, (unsigned long long *) &llsectors) == 0) {
+ /* the get device size ioctl was successful */
+ fdisk_sector_t llcyls;
+ int sec_fac = cxt->sector_size / 512;
+
+ llcyls = llsectors / (cxt->geom.heads * cxt->geom.sectors * sec_fac);
+ cxt->geom.cylinders = llcyls;
+ if (cxt->geom.cylinders != llcyls) /* truncated? */
+ cxt->geom.cylinders = ~0;
+ } else {
+ /* otherwise print error and use truncated version */
+ fdisk_warnx(cxt,
+ _("BLKGETSIZE ioctl failed on %s. "
+ "Using geometry cylinder value of %ju. "
+ "This value may be truncated for devices "
+ "> 33.8 GB."), cxt->dev_path,
+ (uintmax_t) cxt->geom.cylinders);
+ }
+ }
+
+ rc = fdisk_init_firstsector_buffer(cxt, 0, 0);
+ if (rc)
+ return rc;
+
+ sgi = (struct fdisk_sgi_label *) cxt->label;
+ sgi->header = (struct sgi_disklabel *) cxt->firstsector;
+
+ sgilabel = sgi->header;
+
+ sgilabel->magic = cpu_to_be32(SGI_LABEL_MAGIC);
+ sgilabel->root_part_num = cpu_to_be16(0);
+ sgilabel->swap_part_num = cpu_to_be16(1);
+
+ /* sizeof(sgilabel->boot_file) = 16 > 6 */
+ memset(sgilabel->boot_file, 0, 16);
+ strcpy((char *) sgilabel->boot_file, "/unix");
+
+ sgilabel->devparam.skew = (0);
+ sgilabel->devparam.gap1 = (0);
+ sgilabel->devparam.gap2 = (0);
+ sgilabel->devparam.sparecyl = (0);
+ sgilabel->devparam.pcylcount = cpu_to_be16(cxt->geom.cylinders);
+ sgilabel->devparam.head_vol0 = cpu_to_be16(0);
+ sgilabel->devparam.ntrks = cpu_to_be16(cxt->geom.heads);
+ /* tracks/cylinder (heads) */
+ sgilabel->devparam.cmd_tag_queue_depth = (0);
+ sgilabel->devparam.unused0 = (0);
+ sgilabel->devparam.unused1 = cpu_to_be16(0);
+ sgilabel->devparam.nsect = cpu_to_be16(cxt->geom.sectors);
+ /* sectors/track */
+ sgilabel->devparam.bytes = cpu_to_be16(cxt->sector_size);
+ sgilabel->devparam.ilfact = cpu_to_be16(1);
+ sgilabel->devparam.flags = cpu_to_be32(
+ SGI_DEVPARAM_TRACK_FWD
+ | SGI_DEVPARAM_IGNORE_ERRORS
+ | SGI_DEVPARAM_RESEEK);
+ sgilabel->devparam.datarate = cpu_to_be32(0);
+ sgilabel->devparam.retries_on_error = cpu_to_be32(1);
+ sgilabel->devparam.ms_per_word = cpu_to_be32(0);
+ sgilabel->devparam.xylogics_gap1 = cpu_to_be16(0);
+ sgilabel->devparam.xylogics_syncdelay = cpu_to_be16(0);
+ sgilabel->devparam.xylogics_readdelay = cpu_to_be16(0);
+ sgilabel->devparam.xylogics_gap2 = cpu_to_be16(0);
+ sgilabel->devparam.xylogics_readgate = cpu_to_be16(0);
+ sgilabel->devparam.xylogics_writecont = cpu_to_be16(0);
+
+ memset(&(sgilabel->volume), 0,
+ sizeof(struct sgi_volume) * SGI_MAXVOLUMES);
+ memset(&(sgilabel->partitions), 0,
+ sizeof(struct sgi_partition) * SGI_MAXPARTITIONS);
+ cxt->label->nparts_max = SGI_MAXPARTITIONS;
+
+ /* don't create default layout when a script defined */
+ if (!cxt->script) {
+ sgi_set_entire(cxt);
+ sgi_set_volhdr(cxt);
+ }
+ cxt->label->nparts_cur = count_used_partitions(cxt);
+
+ fdisk_info(cxt, _("Created a new SGI disklabel."));
+ return 0;
+}
+
+static int sgi_set_partition(struct fdisk_context *cxt,
+ size_t i,
+ struct fdisk_partition *pa)
+{
+ struct sgi_disklabel *sgilabel;
+
+ if (i >= cxt->label->nparts_max)
+ return -EINVAL;
+
+ sgilabel = self_disklabel(cxt);
+
+ if (pa->type) {
+ struct fdisk_parttype *t = pa->type;
+
+ if (sgi_get_num_sectors(cxt, i) == 0) /* caught already before, ... */ {
+ fdisk_warnx(cxt, _("Sorry, only for non-empty partitions you can change the tag."));
+ return -EINVAL;
+ }
+
+ if ((i == 10 && t->code != SGI_TYPE_ENTIRE_DISK)
+ || (i == 8 && t->code != 0))
+ fdisk_info(cxt, _("Consider leaving partition 9 as volume header (0), "
+ "and partition 11 as entire volume (6), "
+ "as IRIX expects it."));
+
+ if (cxt->script == NULL
+ && ((t->code != SGI_TYPE_ENTIRE_DISK) && (t->code != SGI_TYPE_VOLHDR))
+ && (sgi_get_start_sector(cxt, i) < 1)) {
+ int yes = 0;
+ fdisk_ask_yesno(cxt,
+ _("It is highly recommended that the partition at offset 0 "
+ "is of type \"SGI volhdr\", the IRIX system will rely on it to "
+ "retrieve from its directory standalone tools like sash and fx. "
+ "Only the \"SGI volume\" entire disk section may violate this. "
+ "Are you sure about tagging this partition differently?"), &yes);
+ if (!yes)
+ return 1;
+ }
+
+ sgilabel->partitions[i].type = cpu_to_be32(t->code);
+ }
+
+ if (fdisk_partition_has_start(pa))
+ sgilabel->partitions[i].first_block = cpu_to_be32(pa->start);
+ if (fdisk_partition_has_size(pa))
+ sgilabel->partitions[i].num_blocks = cpu_to_be32(pa->size);
+
+ fdisk_label_set_changed(cxt->label, 1);
+ return 0;
+}
+
+
+static int sgi_partition_is_used(
+ struct fdisk_context *cxt,
+ size_t i)
+{
+ assert(cxt);
+ assert(fdisk_is_label(cxt, SGI));
+
+ if (i >= cxt->label->nparts_max)
+ return 0;
+ return sgi_get_num_sectors(cxt, i) ? 1 : 0;
+}
+
+static int sgi_toggle_partition_flag(struct fdisk_context *cxt, size_t i, unsigned long flag)
+{
+ struct sgi_disklabel *sgilabel;
+ assert(cxt);
+ assert(cxt->label);
+ assert(fdisk_is_label(cxt, SGI));
+
+ if (i >= cxt->label->nparts_max)
+ return -EINVAL;
+
+ sgilabel = self_disklabel(cxt);
+
+ switch (flag) {
+ case SGI_FLAG_BOOT:
+ sgilabel->root_part_num =
+ be16_to_cpu(sgilabel->root_part_num) == i ?
+ 0 : cpu_to_be16(i);
+ fdisk_label_set_changed(cxt->label, 1);
+ break;
+ case SGI_FLAG_SWAP:
+ sgilabel->swap_part_num =
+ be16_to_cpu(sgilabel->swap_part_num) == i ?
+ 0 : cpu_to_be16(i);
+ fdisk_label_set_changed(cxt->label, 1);
+ break;
+ default:
+ return 1;
+ }
+
+ return 0;
+}
+
+static const struct fdisk_field sgi_fields[] =
+{
+ { FDISK_FIELD_DEVICE, N_("Device"), 10, 0 },
+ { FDISK_FIELD_START, N_("Start"), 5, FDISK_FIELDFL_NUMBER },
+ { FDISK_FIELD_END, N_("End"), 5, FDISK_FIELDFL_NUMBER },
+ { FDISK_FIELD_SECTORS, N_("Sectors"), 5, FDISK_FIELDFL_NUMBER },
+ { FDISK_FIELD_CYLINDERS,N_("Cylinders"), 5, FDISK_FIELDFL_NUMBER },
+ { FDISK_FIELD_SIZE, N_("Size"), 5, FDISK_FIELDFL_NUMBER | FDISK_FIELDFL_EYECANDY },
+ { FDISK_FIELD_TYPEID, N_("Id"), 2, FDISK_FIELDFL_NUMBER },
+ { FDISK_FIELD_TYPE, N_("Type"), 0.1, FDISK_FIELDFL_EYECANDY },
+ { FDISK_FIELD_ATTR, N_("Attrs"), 0, FDISK_FIELDFL_NUMBER }
+};
+
+static const struct fdisk_label_operations sgi_operations =
+{
+ .probe = sgi_probe_label,
+ .write = sgi_write_disklabel,
+ .verify = sgi_verify_disklabel,
+ .get_item = sgi_get_disklabel_item,
+ .create = sgi_create_disklabel,
+
+ .get_part = sgi_get_partition,
+ .set_part = sgi_set_partition,
+ .add_part = sgi_add_partition,
+ .del_part = sgi_delete_partition,
+
+ .part_is_used = sgi_partition_is_used,
+ .part_toggle_flag = sgi_toggle_partition_flag
+};
+
+/* Allocates an SGI label driver. */
+struct fdisk_label *fdisk_new_sgi_label(struct fdisk_context *cxt __attribute__ ((__unused__)))
+{
+ struct fdisk_label *lb;
+ struct fdisk_sgi_label *sgi;
+
+ sgi = calloc(1, sizeof(*sgi));
+ if (!sgi)
+ return NULL;
+
+ /* initialize generic part of the driver */
+ lb = (struct fdisk_label *) sgi;
+ lb->name = "sgi";
+ lb->id = FDISK_DISKLABEL_SGI;
+ lb->op = &sgi_operations;
+ lb->parttypes = sgi_parttypes;
+ lb->nparttypes = ARRAY_SIZE(sgi_parttypes) - 1;
+ lb->fields = sgi_fields;
+ lb->nfields = ARRAY_SIZE(sgi_fields);
+
+ lb->flags |= FDISK_LABEL_FL_REQUIRE_GEOMETRY;
+
+ /* return calloc() result to keep static anaylizers happy */
+ return (struct fdisk_label *) sgi;
+}
diff --git a/libfdisk/src/sun.c b/libfdisk/src/sun.c
new file mode 100644
index 0000000..dde9750
--- /dev/null
+++ b/libfdisk/src/sun.c
@@ -0,0 +1,1192 @@
+/*
+ * Copyright (C) 2013 Karel Zak <kzak@redhat.com>
+ *
+ * Based on original code from fdisk:
+ * Jakub Jelinek (jj@sunsite.mff.cuni.cz), July 1996
+ * Merged with fdisk for other architectures, aeb, June 1998.
+ * Arnaldo Carvalho de Melo <acme@conectiva.com.br> Mar 1999, Internationalization
+ */
+#include <stdio.h> /* stderr */
+#include <stdlib.h> /* qsort */
+#include <string.h> /* strstr */
+#include <unistd.h> /* write */
+#include <sys/ioctl.h> /* ioctl */
+
+#include "blkdev.h"
+#include "bitops.h"
+
+#include "fdiskP.h"
+#include "pt-sun.h"
+#include "all-io.h"
+
+
+/**
+ * SECTION: sun
+ * @title: SUN
+ * @short_description: disk label specific functions
+ *
+ */
+
+/*
+ * in-memory fdisk SUN stuff
+ */
+struct fdisk_sun_label {
+ struct fdisk_label head; /* generic part */
+ struct sun_disklabel *header; /* on-disk data (pointer to cxt->firstsector) */
+};
+
+static struct fdisk_parttype sun_parttypes[] = {
+ {SUN_TAG_UNASSIGNED, N_("Unassigned")},
+ {SUN_TAG_BOOT, N_("Boot")},
+ {SUN_TAG_ROOT, N_("SunOS root")},
+ {SUN_TAG_SWAP, N_("SunOS swap")},
+ {SUN_TAG_USR, N_("SunOS usr")},
+ {SUN_TAG_WHOLEDISK, N_("Whole disk")},
+ {SUN_TAG_STAND, N_("SunOS stand")},
+ {SUN_TAG_VAR, N_("SunOS var")},
+ {SUN_TAG_HOME, N_("SunOS home")},
+ {SUN_TAG_ALTSCTR, N_("SunOS alt sectors")},
+ {SUN_TAG_CACHE, N_("SunOS cachefs")},
+ {SUN_TAG_RESERVED, N_("SunOS reserved")},
+ {SUN_TAG_LINUX_SWAP, N_("Linux swap")},
+ {SUN_TAG_LINUX_NATIVE, N_("Linux native")},
+ {SUN_TAG_LINUX_LVM, N_("Linux LVM")},
+ {SUN_TAG_LINUX_RAID, N_("Linux raid autodetect")},
+ { 0, NULL }
+};
+
+/* return pointer buffer with on-disk data */
+static inline struct sun_disklabel *self_disklabel(struct fdisk_context *cxt)
+{
+ assert(cxt);
+ assert(cxt->label);
+ assert(fdisk_is_label(cxt, SUN));
+
+ return ((struct fdisk_sun_label *) cxt->label)->header;
+}
+
+/* return in-memory sun fdisk data */
+static inline struct fdisk_sun_label *self_label(struct fdisk_context *cxt)
+{
+ assert(cxt);
+ assert(cxt->label);
+ assert(fdisk_is_label(cxt, SUN));
+
+ return (struct fdisk_sun_label *) cxt->label;
+}
+
+static void set_partition(struct fdisk_context *cxt, size_t i,
+ uint64_t start, uint64_t stop, uint16_t sysid)
+{
+ struct sun_disklabel *sunlabel = self_disklabel(cxt);
+ struct fdisk_parttype *t =
+ fdisk_label_get_parttype_from_code(cxt->label, sysid);
+
+ if (start / (cxt->geom.heads * cxt->geom.sectors) > UINT32_MAX)
+ fdisk_warnx(cxt, _("#%zu: start cylinder overflows Sun label limits"), i+1);
+
+ if (stop - start > UINT32_MAX)
+ fdisk_warnx(cxt, _("#%zu: number of sectors overflow Sun label limits"), i+1);
+
+ sunlabel->vtoc.infos[i].id = cpu_to_be16(sysid);
+ sunlabel->vtoc.infos[i].flags = cpu_to_be16(0);
+ sunlabel->partitions[i].start_cylinder =
+ cpu_to_be32(start / (cxt->geom.heads * cxt->geom.sectors));
+ sunlabel->partitions[i].num_sectors = cpu_to_be32(stop - start);
+ fdisk_label_set_changed(cxt->label, 1);
+
+ fdisk_info_new_partition(cxt, i + 1, start, stop, t);
+}
+
+static size_t count_used_partitions(struct fdisk_context *cxt)
+{
+ struct sun_disklabel *sunlabel = self_disklabel(cxt);
+ size_t ct = 0, i;
+
+ assert(sunlabel);
+
+ for (i = 0; i < cxt->label->nparts_max; i++) {
+ if (sunlabel->partitions[i].num_sectors)
+ ct++;
+ }
+ return ct;
+}
+
+static int sun_probe_label(struct fdisk_context *cxt)
+{
+ struct fdisk_sun_label *sun;
+ struct sun_disklabel *sunlabel;
+ int need_fixing = 0;
+
+ assert(cxt);
+ assert(cxt->label);
+ assert(fdisk_is_label(cxt, SUN));
+
+ /* map first sector to header */
+ sun = self_label(cxt);
+ sun->header = (struct sun_disklabel *) cxt->firstsector;
+ sunlabel = sun->header;
+
+ if (be16_to_cpu(sunlabel->magic) != SUN_LABEL_MAGIC) {
+ sun->header = NULL;
+ return 0; /* failed */
+ }
+
+ if (sun_pt_checksum(sunlabel)) {
+ fdisk_warnx(cxt, _("Detected sun disklabel with wrong checksum. "
+ "Probably you'll have to set all the values, "
+ "e.g. heads, sectors, cylinders and partitions "
+ "or force a fresh label (s command in main menu)"));
+ return 1;
+ }
+
+ cxt->label->nparts_max = SUN_MAXPARTITIONS;
+ cxt->geom.heads = be16_to_cpu(sunlabel->nhead);
+ cxt->geom.cylinders = be16_to_cpu(sunlabel->ncyl);
+ cxt->geom.sectors = be16_to_cpu(sunlabel->nsect);
+
+ /* we have on label geom, but user has to win */
+ if (fdisk_has_user_device_geometry(cxt))
+ fdisk_apply_user_device_properties(cxt);
+
+ if (be32_to_cpu(sunlabel->vtoc.version) != SUN_VTOC_VERSION) {
+ fdisk_warnx(cxt, _("Detected sun disklabel with wrong version [%d]."),
+ be32_to_cpu(sunlabel->vtoc.version));
+ need_fixing = 1;
+ }
+ if (be32_to_cpu(sunlabel->vtoc.sanity) != SUN_VTOC_SANITY) {
+ fdisk_warnx(cxt, _("Detected sun disklabel with wrong vtoc.sanity [0x%08x]."),
+ be32_to_cpu(sunlabel->vtoc.sanity));
+ need_fixing = 1;
+ }
+ if (be16_to_cpu(sunlabel->vtoc.nparts) != SUN_MAXPARTITIONS) {
+ fdisk_warnx(cxt, _("Detected sun disklabel with wrong vtoc.nparts [%u]."),
+ be16_to_cpu(sunlabel->vtoc.nparts));
+ need_fixing = 1;
+ }
+ if (need_fixing) {
+ fdisk_warnx(cxt, _("Warning: Wrong values need to be fixed up and "
+ "will be corrected by w(rite)"));
+
+ sunlabel->vtoc.version = cpu_to_be32(SUN_VTOC_VERSION);
+ sunlabel->vtoc.sanity = cpu_to_be32(SUN_VTOC_SANITY);
+ sunlabel->vtoc.nparts = cpu_to_be16(SUN_MAXPARTITIONS);
+ sunlabel->csum = 0;
+ sunlabel->csum = sun_pt_checksum(sunlabel);
+
+ fdisk_label_set_changed(cxt->label, 1);
+ }
+
+ cxt->label->nparts_cur = count_used_partitions(cxt);
+
+ return 1;
+}
+
+static void ask_geom(struct fdisk_context *cxt)
+{
+ uintmax_t res;
+
+ assert(cxt);
+
+ if (fdisk_ask_number(cxt, cxt->label->geom_min.heads, 1,
+ cxt->label->geom_max.heads,
+ _("Heads"), &res) == 0)
+ cxt->geom.heads = res;
+
+ if (fdisk_ask_number(cxt, cxt->label->geom_min.sectors, 1,
+ cxt->label->geom_max.sectors,
+ _("Sectors/track"), &res) == 0)
+ cxt->geom.sectors = res;
+
+ if (fdisk_ask_number(cxt, cxt->label->geom_min.cylinders, 1,
+ cxt->label->geom_max.cylinders,
+ _("Cylinders"), &res) == 0)
+ cxt->geom.cylinders = res;
+}
+
+static int sun_create_disklabel(struct fdisk_context *cxt)
+{
+ unsigned int ndiv;
+ struct fdisk_sun_label *sun; /* libfdisk sun handler */
+ struct sun_disklabel *sunlabel; /* on disk data */
+ int rc = 0;
+
+ assert(cxt);
+ assert(cxt->label);
+ assert(fdisk_is_label(cxt, SUN));
+
+ /* map first sector to header */
+ rc = fdisk_init_firstsector_buffer(cxt, 0, 0);
+ if (rc)
+ return rc;
+
+ sun = self_label(cxt);
+ sun->header = (struct sun_disklabel *) cxt->firstsector;
+
+ sunlabel = sun->header;
+
+ cxt->label->nparts_max = SUN_MAXPARTITIONS;
+
+ sunlabel->magic = cpu_to_be16(SUN_LABEL_MAGIC);
+ sunlabel->vtoc.version = cpu_to_be32(SUN_VTOC_VERSION);
+ sunlabel->vtoc.sanity = cpu_to_be32(SUN_VTOC_SANITY);
+ sunlabel->vtoc.nparts = cpu_to_be16(SUN_MAXPARTITIONS);
+
+ if (cxt->geom.heads && cxt->geom.sectors) {
+ fdisk_sector_t llsectors;
+
+ if (blkdev_get_sectors(cxt->dev_fd, (unsigned long long *) &llsectors) == 0) {
+ int sec_fac = cxt->sector_size / 512;
+ fdisk_sector_t llcyls;
+
+ llcyls = llsectors / (cxt->geom.heads * cxt->geom.sectors * sec_fac);
+ cxt->geom.cylinders = llcyls;
+ if (cxt->geom.cylinders != llcyls)
+ cxt->geom.cylinders = ~0;
+ } else {
+ fdisk_warnx(cxt,
+ _("BLKGETSIZE ioctl failed on %s. "
+ "Using geometry cylinder value of %ju. "
+ "This value may be truncated for devices "
+ "> 33.8 GB."),
+ cxt->dev_path, (uintmax_t) cxt->geom.cylinders);
+ }
+ } else
+ ask_geom(cxt);
+
+ sunlabel->acyl = cpu_to_be16(0);
+ sunlabel->pcyl = cpu_to_be16(cxt->geom.cylinders);
+ sunlabel->rpm = cpu_to_be16(5400);
+ sunlabel->intrlv = cpu_to_be16(1);
+ sunlabel->apc = cpu_to_be16(0);
+
+ sunlabel->nhead = cpu_to_be16(cxt->geom.heads);
+ sunlabel->nsect = cpu_to_be16(cxt->geom.sectors);
+ sunlabel->ncyl = cpu_to_be16(cxt->geom.cylinders);
+
+ snprintf((char *) sunlabel->label_id, sizeof(sunlabel->label_id),
+ "Linux cyl %ju alt %u hd %u sec %ju",
+ (uintmax_t) cxt->geom.cylinders,
+ be16_to_cpu(sunlabel->acyl),
+ cxt->geom.heads,
+ (uintmax_t) cxt->geom.sectors);
+
+ if (cxt->geom.cylinders * cxt->geom.heads * cxt->geom.sectors >= 150 * 2048) {
+ ndiv = cxt->geom.cylinders - (50 * 2048 / (cxt->geom.heads * cxt->geom.sectors)); /* 50M swap */
+ } else
+ ndiv = cxt->geom.cylinders * 2 / 3;
+
+ /* create the default layout only if no-script defined */
+ if (!cxt->script) {
+ set_partition(cxt, 0, 0,
+ (uint64_t) ndiv * cxt->geom.heads * cxt->geom.sectors,
+ SUN_TAG_LINUX_NATIVE);
+ set_partition(cxt, 1,
+ (uint64_t) ndiv * cxt->geom.heads * cxt->geom.sectors,
+ (uint64_t) cxt->geom.cylinders * cxt->geom.heads * cxt->geom.sectors,
+ SUN_TAG_LINUX_SWAP);
+ sunlabel->vtoc.infos[1].flags |= cpu_to_be16(SUN_FLAG_UNMNT);
+
+ set_partition(cxt, 2, 0,
+ (uint64_t) cxt->geom.cylinders * cxt->geom.heads * cxt->geom.sectors,
+ SUN_TAG_WHOLEDISK);
+ }
+
+ sunlabel->csum = 0;
+ sunlabel->csum = sun_pt_checksum(sunlabel);
+
+ fdisk_label_set_changed(cxt->label, 1);
+ cxt->label->nparts_cur = count_used_partitions(cxt);
+
+ fdisk_info(cxt, _("Created a new Sun disklabel."));
+ return 0;
+}
+
+static int sun_toggle_partition_flag(struct fdisk_context *cxt, size_t i, unsigned long flag)
+{
+ struct sun_disklabel *sunlabel;
+ struct sun_info *p;
+
+ assert(cxt);
+ assert(cxt->label);
+ assert(fdisk_is_label(cxt, SUN));
+
+ if (i >= cxt->label->nparts_max)
+ return -EINVAL;
+
+ sunlabel = self_disklabel(cxt);
+ p = &sunlabel->vtoc.infos[i];
+
+ switch (flag) {
+ case SUN_FLAG_UNMNT:
+ p->flags ^= cpu_to_be16(SUN_FLAG_UNMNT);
+ fdisk_label_set_changed(cxt->label, 1);
+ break;
+ case SUN_FLAG_RONLY:
+ p->flags ^= cpu_to_be16(SUN_FLAG_RONLY);
+ fdisk_label_set_changed(cxt->label, 1);
+ break;
+ default:
+ return 1;
+ }
+
+ return 0;
+}
+
+static void fetch_sun(struct fdisk_context *cxt,
+ uint32_t *starts,
+ uint32_t *lens,
+ uint32_t *start,
+ uint32_t *stop)
+{
+ struct sun_disklabel *sunlabel;
+ int continuous = 1;
+ size_t i;
+ int sectors_per_cylinder = cxt->geom.heads * cxt->geom.sectors;
+
+ assert(cxt);
+ assert(cxt);
+ assert(cxt->label);
+ assert(fdisk_is_label(cxt, SUN));
+
+ sunlabel = self_disklabel(cxt);
+
+ *start = 0;
+ *stop = cxt->geom.cylinders * sectors_per_cylinder;
+
+ for (i = 0; i < cxt->label->nparts_max; i++) {
+ struct sun_partition *part = &sunlabel->partitions[i];
+ struct sun_info *info = &sunlabel->vtoc.infos[i];
+
+ if (part->num_sectors &&
+ be16_to_cpu(info->id) != SUN_TAG_UNASSIGNED &&
+ be16_to_cpu(info->id) != SUN_TAG_WHOLEDISK) {
+ starts[i] = be32_to_cpu(part->start_cylinder) *
+ sectors_per_cylinder;
+ lens[i] = be32_to_cpu(part->num_sectors);
+ if (continuous) {
+ if (starts[i] == *start) {
+ *start += lens[i];
+ int remained_sectors = *start % sectors_per_cylinder;
+ if (remained_sectors) {
+ *start += sectors_per_cylinder - remained_sectors;
+ }
+ } else if (starts[i] + lens[i] >= *stop)
+ *stop = starts[i];
+ else
+ continuous = 0;
+ /* There will be probably more gaps
+ than one, so lets check afterwards */
+ }
+ } else {
+ starts[i] = 0;
+ lens[i] = 0;
+ }
+ }
+}
+
+/* non-Linux qsort_r(3) has usually differently ordered arguments */
+#if !defined (__linux__) || !defined (__GLIBC__)
+# undef HAVE_QSORT_R
+#endif
+
+#ifdef HAVE_QSORT_R
+static int verify_sun_cmp(int *a, int *b, void *data)
+{
+ unsigned int *verify_sun_starts = (unsigned int *) data;
+
+ if (*a == -1)
+ return 1;
+ if (*b == -1)
+ return -1;
+ if (verify_sun_starts[*a] > verify_sun_starts[*b])
+ return 1;
+ return -1;
+}
+#endif
+
+static int sun_verify_disklabel(struct fdisk_context *cxt)
+{
+ uint32_t starts[SUN_MAXPARTITIONS], lens[SUN_MAXPARTITIONS], start, stop;
+ uint32_t i,j,k,starto,endo;
+#ifdef HAVE_QSORT_R
+ int array[SUN_MAXPARTITIONS];
+ unsigned int *verify_sun_starts;
+#endif
+ assert(cxt);
+ assert(cxt->label);
+ assert(fdisk_is_label(cxt, SUN));
+
+ fetch_sun(cxt, starts, lens, &start, &stop);
+
+ for (k = 0; k < 7; k++) {
+ for (i = 0; i < SUN_MAXPARTITIONS; i++) {
+ if (k && (lens[i] % (cxt->geom.heads * cxt->geom.sectors)))
+ fdisk_warnx(cxt, _("Partition %u doesn't end on cylinder boundary."), i+1);
+ if (lens[i]) {
+ for (j = 0; j < i; j++)
+ if (lens[j]) {
+ if (starts[j] == starts[i]+lens[i]) {
+ starts[j] = starts[i]; lens[j] += lens[i];
+ lens[i] = 0;
+ } else if (starts[i] == starts[j]+lens[j]){
+ lens[j] += lens[i];
+ lens[i] = 0;
+ } else if (!k) {
+ if (starts[i] < starts[j]+lens[j] &&
+ starts[j] < starts[i]+lens[i]) {
+ starto = starts[i];
+ if (starts[j] > starto)
+ starto = starts[j];
+ endo = starts[i]+lens[i];
+ if (starts[j]+lens[j] < endo)
+ endo = starts[j]+lens[j];
+ fdisk_warnx(cxt, _("Partition %u overlaps with others in "
+ "sectors %u-%u."), i+1, starto, endo);
+ }
+ }
+ }
+ }
+ }
+ }
+
+#ifdef HAVE_QSORT_R
+ for (i = 0; i < SUN_MAXPARTITIONS; i++) {
+ if (lens[i])
+ array[i] = i;
+ else
+ array[i] = -1;
+ }
+ verify_sun_starts = starts;
+
+ qsort_r(array,ARRAY_SIZE(array),sizeof(array[0]),
+ (int (*)(const void *,const void *,void *)) verify_sun_cmp,
+ verify_sun_starts);
+
+ if (array[0] == -1) {
+ fdisk_info(cxt, _("No partitions defined."));
+ return 0;
+ }
+ stop = cxt->geom.cylinders * cxt->geom.heads * cxt->geom.sectors;
+ if (starts[array[0]])
+ fdisk_warnx(cxt, _("Unused gap - sectors 0-%u."), starts[array[0]]);
+ for (i = 0; i < 7 && array[i+1] != -1; i++) {
+ fdisk_warnx(cxt, _("Unused gap - sectors %u-%u."),
+ (starts[array[i]] + lens[array[i]]),
+ starts[array[i+1]]);
+ }
+ start = (starts[array[i]] + lens[array[i]]);
+ if (start < stop)
+ fdisk_warnx(cxt, _("Unused gap - sectors %u-%u."), start, stop);
+#endif
+ return 0;
+}
+
+
+static int is_free_sector(struct fdisk_context *cxt,
+ fdisk_sector_t s, uint32_t starts[], uint32_t lens[])
+{
+ size_t i;
+
+ for (i = 0; i < cxt->label->nparts_max; i++) {
+ if (lens[i] && starts[i] <= s
+ && starts[i] + lens[i] > s)
+ return 0;
+ }
+ return 1;
+}
+
+static int sun_add_partition(
+ struct fdisk_context *cxt,
+ struct fdisk_partition *pa,
+ size_t *partno)
+{
+ struct sun_disklabel *sunlabel = self_disklabel(cxt);
+ uint32_t starts[SUN_MAXPARTITIONS], lens[SUN_MAXPARTITIONS];
+ struct sun_partition *part;
+ struct sun_info *info;
+ uint32_t start, stop, stop2;
+ int whole_disk = 0;
+ int sys = pa && pa->type ? pa->type->code : SUN_TAG_LINUX_NATIVE;
+ int rc;
+ size_t n;
+
+ char mesg[256];
+ size_t i;
+ unsigned int first, last;
+
+ DBG(LABEL, ul_debug("SUN adding partition"));
+
+ rc = fdisk_partition_next_partno(pa, cxt, &n);
+ if (rc)
+ return rc;
+
+ part = &sunlabel->partitions[n];
+ info = &sunlabel->vtoc.infos[n];
+
+ if (part->num_sectors && be16_to_cpu(info->id) != SUN_TAG_UNASSIGNED) {
+ fdisk_info(cxt, _("Partition %zu is already defined. Delete "
+ "it before re-adding it."), n + 1);
+ return -EINVAL;
+ }
+
+ fetch_sun(cxt, starts, lens, &start, &stop);
+
+ if (pa && pa->type && pa->type->code == SUN_TAG_WHOLEDISK)
+ whole_disk = 1;
+
+ if (stop <= start) {
+ if (n == 2)
+ whole_disk = 1;
+ else {
+ fdisk_info(cxt, _("Other partitions already cover the "
+ "whole disk. Delete some/shrink them before retry."));
+ return -EINVAL;
+ }
+ }
+
+ if (pa && pa->start_follow_default)
+ first = start;
+ else if (pa && fdisk_partition_has_start(pa)) {
+ first = pa->start;
+
+ if (!whole_disk && !is_free_sector(cxt, first, starts, lens))
+ return -ERANGE;
+ } else {
+ struct fdisk_ask *ask;
+
+ if (n == 2)
+ fdisk_info(cxt, _("It is highly recommended that the "
+ "third partition covers the whole disk "
+ "and is of type `Whole disk'"));
+
+ snprintf(mesg, sizeof(mesg), _("First %s"),
+ fdisk_get_unit(cxt, FDISK_SINGULAR));
+ for (;;) {
+ ask = fdisk_new_ask();
+ if (!ask)
+ return -ENOMEM;
+
+ fdisk_ask_set_query(ask, mesg);
+ fdisk_ask_set_type(ask, FDISK_ASKTYPE_NUMBER);
+
+ if (whole_disk) {
+ fdisk_ask_number_set_low(ask, 0); /* minimal */
+ fdisk_ask_number_set_default(ask, 0); /* default */
+ fdisk_ask_number_set_high(ask, 0); /* maximal */
+ } else if (n == 2) {
+ fdisk_ask_number_set_low(ask, 0); /* minimal */
+ fdisk_ask_number_set_default(ask, 0); /* default */
+ fdisk_ask_number_set_high(ask, fdisk_scround(cxt, stop)); /* maximal */
+ } else {
+ fdisk_ask_number_set_low(ask, fdisk_scround(cxt, start)); /* minimal */
+ fdisk_ask_number_set_default(ask, fdisk_scround(cxt, start)); /* default */
+ fdisk_ask_number_set_high(ask, fdisk_scround(cxt, stop)); /* maximal */
+ }
+ rc = fdisk_do_ask(cxt, ask);
+ first = fdisk_ask_number_get_result(ask);
+ fdisk_unref_ask(ask);
+ if (rc)
+ return rc;
+
+ if (fdisk_use_cylinders(cxt))
+ first *= fdisk_get_units_per_sector(cxt);
+
+ if (!fdisk_use_cylinders(cxt)) {
+ /* Starting sector has to be properly aligned */
+ int cs = cxt->geom.heads * cxt->geom.sectors;
+ int x = first % cs;
+
+ if (x) {
+ fdisk_info(cxt, _("Aligning the first sector from %u to %u "
+ "to be on cylinder boundary."),
+ first, first + cs - x);
+ first += cs - x;
+ }
+ }
+
+ /* ewt asks to add: "don't start a partition at cyl 0"
+ However, edmundo@rano.demon.co.uk writes:
+ "In addition to having a Sun partition table, to be able to
+ boot from the disc, the first partition, /dev/sdX1, must
+ start at cylinder 0. This means that /dev/sdX1 contains
+ the partition table and the boot block, as these are the
+ first two sectors of the disc. Therefore you must be
+ careful what you use /dev/sdX1 for. In particular, you must
+ not use a partition starting at cylinder 0 for Linux swap,
+ as that would overwrite the partition table and the boot
+ block. You may, however, use such a partition for a UFS
+ or EXT2 file system, as these file systems leave the first
+ 1024 bytes undisturbed. */
+ /* On the other hand, one should not use partitions
+ starting at block 0 in an md, or the label will
+ be trashed. */
+ if (!is_free_sector(cxt, first, starts, lens) && !whole_disk) {
+ if (n == 2 && !first) {
+ whole_disk = 1;
+ break;
+ }
+ fdisk_warnx(cxt, _("Sector %d is already allocated"), first);
+ } else
+ break;
+ }
+ }
+
+ stop = cxt->geom.cylinders * cxt->geom.heads * cxt->geom.sectors; /* ancient */
+ stop2 = stop;
+ for (i = 0; i < cxt->label->nparts_max; i++) {
+ if (starts[i] > first && starts[i] < stop)
+ stop = starts[i];
+ }
+
+ /* last */
+ if (pa && pa->end_follow_default)
+ last = whole_disk || (n == 2 && !first) ? stop2 : stop;
+
+ else if (pa && fdisk_partition_has_size(pa)) {
+ last = first + pa->size;
+
+ if (!whole_disk && last > stop)
+ return -ERANGE;
+ } else {
+ struct fdisk_ask *ask = fdisk_new_ask();
+
+ if (!ask)
+ return -ENOMEM;
+
+ snprintf(mesg, sizeof(mesg),
+ _("Last %s or +/-%s or +/-size{K,M,G,T,P}"),
+ fdisk_get_unit(cxt, FDISK_SINGULAR),
+ fdisk_get_unit(cxt, FDISK_PLURAL));
+ fdisk_ask_set_query(ask, mesg);
+ fdisk_ask_set_type(ask, FDISK_ASKTYPE_OFFSET);
+
+ if (whole_disk) {
+ fdisk_ask_number_set_low(ask, fdisk_scround(cxt, stop2)); /* minimal */
+ fdisk_ask_number_set_default(ask, fdisk_scround(cxt, stop2)); /* default */
+ fdisk_ask_number_set_high(ask, fdisk_scround(cxt, stop2)); /* maximal */
+ fdisk_ask_number_set_base(ask, 0);
+ } else if (n == 2 && !first) {
+ fdisk_ask_number_set_low(ask, fdisk_scround(cxt, first)); /* minimal */
+ fdisk_ask_number_set_default(ask, fdisk_scround(cxt, stop2)); /* default */
+ fdisk_ask_number_set_high(ask, fdisk_scround(cxt, stop2)); /* maximal */
+ fdisk_ask_number_set_base(ask, fdisk_scround(cxt, first));
+ } else {
+ fdisk_ask_number_set_low(ask, fdisk_scround(cxt, first)); /* minimal */
+ fdisk_ask_number_set_default(ask, fdisk_scround(cxt, stop)); /* default */
+ fdisk_ask_number_set_high(ask, fdisk_scround(cxt, stop)); /* maximal */
+ fdisk_ask_number_set_base(ask, fdisk_scround(cxt, first));
+ }
+
+ fdisk_ask_number_set_wrap_negative(ask, 1); /* wrap negative around high */
+
+ if (fdisk_use_cylinders(cxt))
+ fdisk_ask_number_set_unit(ask,
+ cxt->sector_size *
+ fdisk_get_units_per_sector(cxt));
+ else
+ fdisk_ask_number_set_unit(ask, cxt->sector_size);
+
+ rc = fdisk_do_ask(cxt, ask);
+ last = fdisk_ask_number_get_result(ask);
+
+ fdisk_unref_ask(ask);
+ if (rc)
+ return rc;
+ if (fdisk_use_cylinders(cxt))
+ last *= fdisk_get_units_per_sector(cxt);
+ }
+
+ if (n == 2 && !first) {
+ if (last >= stop2) {
+ whole_disk = 1;
+ last = stop2;
+ } else if (last > stop) {
+ fdisk_warnx(cxt,
+ _("You haven't covered the whole disk with the 3rd partition, but your value\n"
+ "%lu %s covers some other partition. Your entry has been changed\n"
+ "to %lu %s"),
+ (unsigned long) fdisk_scround(cxt, last), fdisk_get_unit(cxt, FDISK_SINGULAR),
+ (unsigned long) fdisk_scround(cxt, stop), fdisk_get_unit(cxt, FDISK_SINGULAR));
+ last = stop;
+ }
+ } else if (!whole_disk && last > stop)
+ last = stop;
+
+ if (whole_disk)
+ sys = SUN_TAG_WHOLEDISK;
+
+ DBG(LABEL, ul_debug("SUN new partition #%zu: first=%u, last=%u, sys=%d", n, first, last, sys));
+
+ set_partition(cxt, n, first, last, sys);
+ cxt->label->nparts_cur = count_used_partitions(cxt);
+ if (partno)
+ *partno = n;
+ return 0;
+}
+
+static int sun_delete_partition(struct fdisk_context *cxt,
+ size_t partnum)
+{
+ struct sun_disklabel *sunlabel;
+ struct sun_partition *part;
+ struct sun_info *info;
+ unsigned int nsec;
+
+ assert(cxt);
+ assert(cxt->label);
+ assert(fdisk_is_label(cxt, SUN));
+
+ sunlabel = self_disklabel(cxt);
+ part = &sunlabel->partitions[partnum];
+ info = &sunlabel->vtoc.infos[partnum];
+
+ if (partnum == 2 &&
+ be16_to_cpu(info->id) == SUN_TAG_WHOLEDISK &&
+ !part->start_cylinder &&
+ (nsec = be32_to_cpu(part->num_sectors))
+ == cxt->geom.heads * cxt->geom.sectors * cxt->geom.cylinders)
+ fdisk_info(cxt, _("If you want to maintain SunOS/Solaris compatibility, "
+ "consider leaving this "
+ "partition as Whole disk (5), starting at 0, with %u "
+ "sectors"), nsec);
+ info->id = cpu_to_be16(SUN_TAG_UNASSIGNED);
+ part->num_sectors = 0;
+ cxt->label->nparts_cur = count_used_partitions(cxt);
+ fdisk_label_set_changed(cxt->label, 1);
+ return 0;
+}
+
+static int sun_get_disklabel_item(struct fdisk_context *cxt, struct fdisk_labelitem *item)
+{
+ struct sun_disklabel *sunlabel;
+ int rc = 0;
+
+ assert(cxt);
+ assert(cxt->label);
+ assert(fdisk_is_label(cxt, SUN));
+
+ sunlabel = self_disklabel(cxt);
+
+ switch (item->id) {
+ case SUN_LABELITEM_LABELID:
+ item->name =_("Label ID");
+ item->type = 's';
+ item->data.str = *sunlabel->label_id ? strndup((char *)sunlabel->label_id, sizeof(sunlabel->label_id)) : NULL;
+ break;
+ case SUN_LABELITEM_VTOCID:
+ item->name =_("Volume ID");
+ item->type = 's';
+ item->data.str = *sunlabel->vtoc.volume_id ? strndup((char *)sunlabel->vtoc.volume_id, sizeof(sunlabel->vtoc.volume_id)) : NULL;
+ break;
+ case SUN_LABELITEM_RPM:
+ item->name =_("Rpm");
+ item->type = 'j';
+ item->data.num64 = be16_to_cpu(sunlabel->rpm);
+ break;
+ case SUN_LABELITEM_ACYL:
+ item->name =_("Alternate cylinders");
+ item->type = 'j';
+ item->data.num64 = be16_to_cpu(sunlabel->acyl);
+ break;
+ case SUN_LABELITEM_PCYL:
+ item->name =_("Physical cylinders");
+ item->type = 'j';
+ item->data.num64 = be16_to_cpu(sunlabel->pcyl);
+ break;
+ case SUN_LABELITEM_APC:
+ item->name =_("Extra sects/cyl");
+ item->type = 'j';
+ item->data.num64 = be16_to_cpu(sunlabel->apc);
+ break;
+ case SUN_LABELITEM_INTRLV:
+ item->name =_("Interleave");
+ item->type = 'j';
+ item->data.num64 = be16_to_cpu(sunlabel->intrlv);
+ break;
+ default:
+ if (item->id < __FDISK_NLABELITEMS)
+ rc = 1; /* unsupported generic item */
+ else
+ rc = 2; /* out of range */
+ break;
+ }
+
+ return rc;
+}
+
+static struct fdisk_parttype *sun_get_parttype(
+ struct fdisk_context *cxt,
+ size_t n)
+{
+ struct sun_disklabel *sunlabel = self_disklabel(cxt);
+ struct fdisk_parttype *t;
+
+ if (n >= cxt->label->nparts_max)
+ return NULL;
+
+ t = fdisk_label_get_parttype_from_code(cxt->label,
+ be16_to_cpu(sunlabel->vtoc.infos[n].id));
+ return t ? : fdisk_new_unknown_parttype(be16_to_cpu(sunlabel->vtoc.infos[n].id), NULL);
+}
+
+
+static int sun_get_partition(struct fdisk_context *cxt, size_t n,
+ struct fdisk_partition *pa)
+{
+ struct sun_disklabel *sunlabel;
+ struct sun_partition *part;
+ uint16_t flags;
+ uint64_t start, len;
+
+ assert(cxt);
+ assert(cxt->label);
+ assert(fdisk_is_label(cxt, SUN));
+
+ if (n >= cxt->label->nparts_max)
+ return -EINVAL;
+
+ sunlabel = self_disklabel(cxt);
+ part = &sunlabel->partitions[n];
+
+ pa->used = part->num_sectors ? 1 : 0;
+ if (!pa->used)
+ return 0;
+
+ flags = be16_to_cpu(sunlabel->vtoc.infos[n].flags);
+ start = (uint64_t) be32_to_cpu(part->start_cylinder)
+ * cxt->geom.heads * cxt->geom.sectors;
+ len = be32_to_cpu(part->num_sectors);
+
+ pa->type = sun_get_parttype(cxt, n);
+ if (pa->type && pa->type->code == SUN_TAG_WHOLEDISK)
+ pa->wholedisk = 1;
+
+ if (flags & SUN_FLAG_UNMNT || flags & SUN_FLAG_RONLY) {
+ if (asprintf(&pa->attrs, "%c%c",
+ flags & SUN_FLAG_UNMNT ? 'u' : ' ',
+ flags & SUN_FLAG_RONLY ? 'r' : ' ') < 0)
+ return -ENOMEM;
+ }
+
+ pa->start = start;
+ pa->size = len;
+
+ return 0;
+}
+
+/**
+ * fdisk_sun_set_alt_cyl:
+ * @cxt: context
+ *
+ * Sets number of alternative cylinders. This function uses libfdisk Ask API
+ * for dialog with user.
+ *
+ * Returns: 0 on success, <0 on error.
+ */
+int fdisk_sun_set_alt_cyl(struct fdisk_context *cxt)
+{
+ struct sun_disklabel *sunlabel = self_disklabel(cxt);
+ uintmax_t res;
+ int rc = fdisk_ask_number(cxt, 0, /* low */
+ be16_to_cpu(sunlabel->acyl), /* default */
+ 65535, /* high */
+ _("Number of alternate cylinders"), /* query */
+ &res); /* result */
+ if (rc)
+ return rc;
+
+ sunlabel->acyl = cpu_to_be16(res);
+ return 0;
+}
+
+/**
+ * fdisk_sun_set_xcyl:
+ * @cxt: context
+ *
+ * Sets number of extra sectors per cylinder. This function uses libfdisk Ask API
+ * for dialog with user.
+ *
+ * Returns: 0 on success, <0 on error.
+ */
+int fdisk_sun_set_xcyl(struct fdisk_context *cxt)
+{
+ struct sun_disklabel *sunlabel = self_disklabel(cxt);
+ uintmax_t res;
+ int rc = fdisk_ask_number(cxt, 0, /* low */
+ be16_to_cpu(sunlabel->apc), /* default */
+ cxt->geom.sectors, /* high */
+ _("Extra sectors per cylinder"), /* query */
+ &res); /* result */
+ if (rc)
+ return rc;
+ sunlabel->apc = cpu_to_be16(res);
+ return 0;
+}
+
+/**
+ * fdisk_sun_set_ilfact:
+ * @cxt: context
+ *
+ * Sets interleave factor. This function uses libfdisk Ask API for dialog with
+ * user.
+ *
+ * Returns: 0 on success, <0 on error.
+ */
+int fdisk_sun_set_ilfact(struct fdisk_context *cxt)
+{
+ struct sun_disklabel *sunlabel = self_disklabel(cxt);
+ uintmax_t res;
+ int rc = fdisk_ask_number(cxt, 1, /* low */
+ be16_to_cpu(sunlabel->intrlv), /* default */
+ 32, /* high */
+ _("Interleave factor"), /* query */
+ &res); /* result */
+ if (rc)
+ return rc;
+ sunlabel->intrlv = cpu_to_be16(res);
+ return 0;
+}
+
+/**
+ * fdisk_sun_set_rspeed
+ * @cxt: context
+ *
+ * Sets rotation speed. This function uses libfdisk Ask API for dialog with
+ * user.
+ *
+ * Returns: 0 on success, <0 on error.
+ */
+int fdisk_sun_set_rspeed(struct fdisk_context *cxt)
+{
+ struct sun_disklabel *sunlabel = self_disklabel(cxt);
+ uintmax_t res;
+ int rc = fdisk_ask_number(cxt, 1, /* low */
+ be16_to_cpu(sunlabel->rpm), /* default */
+ USHRT_MAX, /* high */
+ _("Rotation speed (rpm)"), /* query */
+ &res); /* result */
+ if (rc)
+ return rc;
+ sunlabel->rpm = cpu_to_be16(res);
+ return 0;
+}
+
+/**
+ * fdisk_sun_set_pcylcount
+ * @cxt: context
+ *
+ * Sets number of physical cylinders. This function uses libfdisk Ask API for
+ * dialog with user.
+ *
+ * Returns: 0 on success, <0 on error.
+ */
+int fdisk_sun_set_pcylcount(struct fdisk_context *cxt)
+{
+ struct sun_disklabel *sunlabel = self_disklabel(cxt);
+ uintmax_t res;
+ int rc = fdisk_ask_number(cxt, 0, /* low */
+ be16_to_cpu(sunlabel->pcyl), /* default */
+ USHRT_MAX, /* high */
+ _("Number of physical cylinders"), /* query */
+ &res); /* result */
+ if (!rc)
+ return rc;
+ sunlabel->pcyl = cpu_to_be16(res);
+ return 0;
+}
+
+static int sun_write_disklabel(struct fdisk_context *cxt)
+{
+ struct sun_disklabel *sunlabel;
+ const size_t sz = sizeof(struct sun_disklabel);
+
+ assert(cxt);
+ assert(cxt->label);
+ assert(fdisk_is_label(cxt, SUN));
+
+ sunlabel = self_disklabel(cxt);
+
+ /* Maybe geometry has been modified */
+ sunlabel->nhead = cpu_to_be16(cxt->geom.heads);
+ sunlabel->nsect = cpu_to_be16(cxt->geom.sectors);
+
+ if (cxt->geom.cylinders != be16_to_cpu(sunlabel->ncyl)) {
+ int a = cpu_to_be16(cxt->geom.cylinders);
+ int b = be16_to_cpu(sunlabel->acyl);
+ sunlabel->ncyl = a - b;
+ }
+
+ sunlabel->csum = 0;
+ sunlabel->csum = sun_pt_checksum(sunlabel);
+
+ if (lseek(cxt->dev_fd, 0, SEEK_SET) < 0)
+ return -errno;
+ if (write_all(cxt->dev_fd, sunlabel, sz) != 0)
+ return -errno;
+
+ return 0;
+}
+
+static int sun_set_partition(
+ struct fdisk_context *cxt,
+ size_t i,
+ struct fdisk_partition *pa)
+{
+ struct sun_disklabel *sunlabel;
+ struct sun_partition *part;
+ struct sun_info *info;
+
+ assert(cxt);
+ assert(cxt->label);
+ assert(fdisk_is_label(cxt, SUN));
+
+ sunlabel = self_disklabel(cxt);
+
+ if (i >= cxt->label->nparts_max)
+ return -EINVAL;
+
+ if (pa->type) {
+ struct fdisk_parttype *t = pa->type;
+
+ if (t->code > UINT16_MAX)
+ return -EINVAL;
+
+ if (i == 2 && t->code != SUN_TAG_WHOLEDISK)
+ fdisk_info(cxt, _("Consider leaving partition 3 as Whole disk (5),\n"
+ "as SunOS/Solaris expects it and even Linux likes it.\n"));
+
+ part = &sunlabel->partitions[i];
+ info = &sunlabel->vtoc.infos[i];
+
+ if (cxt->script == NULL &&
+ t->code == SUN_TAG_LINUX_SWAP && !part->start_cylinder) {
+ int yes, rc;
+
+ rc = fdisk_ask_yesno(cxt,
+ _("It is highly recommended that the partition at offset 0\n"
+ "is UFS, EXT2FS filesystem or SunOS swap. Putting Linux swap\n"
+ "there may destroy your partition table and bootblock.\n"
+ "Are you sure you want to tag the partition as Linux swap?"), &yes);
+ if (rc)
+ return rc;
+ if (!yes)
+ return 1;
+ }
+
+ switch (t->code) {
+ case SUN_TAG_SWAP:
+ case SUN_TAG_LINUX_SWAP:
+ /* swaps are not mountable by default */
+ info->flags |= cpu_to_be16(SUN_FLAG_UNMNT);
+ break;
+ default:
+ /* assume other types are mountable;
+ user can change it anyway */
+ info->flags &= ~cpu_to_be16(SUN_FLAG_UNMNT);
+ break;
+ }
+ info->id = cpu_to_be16(t->code);
+ }
+
+ if (fdisk_partition_has_start(pa))
+ sunlabel->partitions[i].start_cylinder =
+ cpu_to_be32(pa->start / (cxt->geom.heads * cxt->geom.sectors));
+ if (fdisk_partition_has_size(pa))
+ sunlabel->partitions[i].num_sectors = cpu_to_be32(pa->size);
+
+ fdisk_label_set_changed(cxt->label, 1);
+ return 0;
+}
+
+
+static int sun_reset_alignment(struct fdisk_context *cxt __attribute__((__unused__)))
+{
+ fdisk_set_first_lba(cxt, 0);
+ return 0;
+}
+
+
+static int sun_partition_is_used(
+ struct fdisk_context *cxt,
+ size_t i)
+{
+ struct sun_disklabel *sunlabel;
+
+ assert(cxt);
+ assert(cxt->label);
+ assert(fdisk_is_label(cxt, SUN));
+
+ if (i >= cxt->label->nparts_max)
+ return 0;
+
+ sunlabel = self_disklabel(cxt);
+ return sunlabel->partitions[i].num_sectors ? 1 : 0;
+}
+
+static const struct fdisk_field sun_fields[] =
+{
+ { FDISK_FIELD_DEVICE, N_("Device"), 10, 0 },
+ { FDISK_FIELD_START, N_("Start"), 5, FDISK_FIELDFL_NUMBER },
+ { FDISK_FIELD_END, N_("End"), 5, FDISK_FIELDFL_NUMBER },
+ { FDISK_FIELD_SECTORS, N_("Sectors"), 5, FDISK_FIELDFL_NUMBER },
+ { FDISK_FIELD_CYLINDERS,N_("Cylinders"), 5, FDISK_FIELDFL_NUMBER },
+ { FDISK_FIELD_SIZE, N_("Size"), 5, FDISK_FIELDFL_NUMBER },
+ { FDISK_FIELD_TYPEID, N_("Id"), 2, FDISK_FIELDFL_NUMBER },
+ { FDISK_FIELD_TYPE, N_("Type"), 0.1, 0 },
+ { FDISK_FIELD_ATTR, N_("Flags"), 0, FDISK_FIELDFL_NUMBER }
+};
+
+static const struct fdisk_label_operations sun_operations =
+{
+ .probe = sun_probe_label,
+ .write = sun_write_disklabel,
+ .verify = sun_verify_disklabel,
+ .create = sun_create_disklabel,
+ .get_item = sun_get_disklabel_item,
+
+ .get_part = sun_get_partition,
+ .set_part = sun_set_partition,
+ .add_part = sun_add_partition,
+ .del_part = sun_delete_partition,
+
+ .part_is_used = sun_partition_is_used,
+ .part_toggle_flag = sun_toggle_partition_flag,
+
+ .reset_alignment = sun_reset_alignment,
+};
+
+/*
+ * allocates SUN label driver
+ */
+struct fdisk_label *fdisk_new_sun_label(struct fdisk_context *cxt __attribute__ ((__unused__)))
+{
+ struct fdisk_label *lb;
+ struct fdisk_sun_label *sun;
+
+ sun = calloc(1, sizeof(*sun));
+ if (!sun)
+ return NULL;
+
+ /* initialize generic part of the driver */
+ lb = (struct fdisk_label *) sun;
+ lb->name = "sun";
+ lb->id = FDISK_DISKLABEL_SUN;
+ lb->op = &sun_operations;
+ lb->parttypes = sun_parttypes;
+ lb->nparttypes = ARRAY_SIZE(sun_parttypes) - 1;
+ lb->fields = sun_fields;
+ lb->nfields = ARRAY_SIZE(sun_fields);
+ lb->flags |= FDISK_LABEL_FL_REQUIRE_GEOMETRY;
+
+ lb->geom_min.sectors = 1;
+ lb->geom_min.heads = 1;
+ lb->geom_min.cylinders = 1;
+
+ lb->geom_max.sectors = 1024;
+ lb->geom_max.heads = 1024;
+ lb->geom_max.cylinders = USHRT_MAX;
+
+ /* return calloc() result to keep static anaylizers happy */
+ return (struct fdisk_label *) sun;
+}
diff --git a/libfdisk/src/table.c b/libfdisk/src/table.c
new file mode 100644
index 0000000..84c60d4
--- /dev/null
+++ b/libfdisk/src/table.c
@@ -0,0 +1,797 @@
+
+#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 partitions 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
+ *
+ * Increments reference counter.
+ */
+void fdisk_ref_table(struct fdisk_table *tb)
+{
+ if (tb)
+ tb->refcount++;
+}
+
+/**
+ * fdisk_unref_table:
+ * @tb: table pointer
+ *
+ * Descrements 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;
+
+ 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;
+}
+
+/**
+ * fdisk_table_get_partition:
+ * @tb: tab pointer
+ * @n: number of entry in table
+ *
+ * Returns: n-th entry from table or NULL
+ */
+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_get_partition_by_partno:
+ * @tb: tab pointer
+ * @partno: partition number
+ *
+ * Returns: partition with @partno or NULL.
+ */
+struct fdisk_partition *fdisk_table_get_partition_by_partno(
+ struct fdisk_table *tb,
+ size_t partno)
+{
+ 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 (pa->partno == partno)
+ return pa;
+ }
+
+ 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_partition() 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)
+{
+ if (!tb || !pa)
+ return -EINVAL;
+
+ if (!list_empty(&pa->parts))
+ return -EBUSY;
+
+ 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),
+ fdisk_partition_has_end(pa) ? (uintmax_t) fdisk_partition_get_end(pa) : 0,
+ fdisk_partition_has_size(pa) ? (uintmax_t) fdisk_partition_get_size(pa) : 0,
+ 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)
+{
+ 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 @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;
+}
+
+void fdisk_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%s%s%s] ",
+ pa, pa->partno,
+ (uintmax_t) fdisk_partition_get_start(pa),
+ (uintmax_t) fdisk_partition_get_end(pa),
+ (uintmax_t) fdisk_partition_get_size(pa),
+ fdisk_partition_is_nested(pa) ? " nested" : "",
+ fdisk_partition_is_freespace(pa) ? " freespace" : "",
+ fdisk_partition_is_container(pa) ? " container" : "");
+
+}
+
+
+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, fdisk_debug_print_table(tb));
+ */
+
+ list_sort(&tb->parts, cmp_parts_wrapper, (void *) cmp);
+
+ /*
+ DBG(TAB, ul_debugobj(tb, "After sort:"));
+ ON_DBG(TAB, fdisk_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)
+{
+ fdisk_sector_t aligned_start, size;
+
+ assert(cxt);
+ assert(pa);
+
+ *pa = NULL;
+
+ if (start == end)
+ return 0;
+
+ assert(start >= cxt->first_lba);
+ assert(end);
+ assert(end > start);
+
+ aligned_start = fdisk_align_lba_in_range(cxt, start, start, end);
+ size = end - aligned_start + 1ULL;
+
+ if (size == 0) {
+ DBG(TAB, ul_debug("ignore freespace (aligned size is zero)"));
+ return 0;
+ }
+
+ *pa = fdisk_new_partition();
+ if (!*pa)
+ return -ENOMEM;
+
+ (*pa)->freespace = 1;
+ (*pa)->start = aligned_start;
+ (*pa)->size = size;
+
+ 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 the_end, best_end = 0;
+
+ if (!fdisk_partition_has_end(x))
+ continue;
+
+ the_end = fdisk_partition_get_end(x);
+ if (best)
+ best_end = fdisk_partition_get_end(best);
+
+ if (the_end < pa->start && (!best || best_end < the_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",
+ (uintmax_t)last, (uintmax_t)grain));
+
+ while (fdisk_table_next_partition(parts, &itr, &pa) == 0) {
+
+ DBG(CXT, ul_debugobj(cxt, "partno=%zu, start=%ju",
+ pa->partno, (uintmax_t)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_size()) 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;
+ size_t nparts = 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",
+ (uintmax_t)last, (uintmax_t)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, (uintmax_t)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)));
+
+ /* We ignore small free spaces (smaller than grain) to keep partitions
+ * aligned, the exception is space before the first partition when
+ * cxt->first_lba is aligned. */
+ if (last + grain < pa->start
+ || (nparts == 0 &&
+ (fdisk_align_lba(cxt, last, FDISK_ALIGN_UP) <
+ pa->start))) {
+ rc = table_add_freespace(cxt, *tb,
+ last + (nparts == 0 ? 0 : 1),
+ pa->start - 1, NULL);
+ }
+ /* add gaps between logical partitions */
+ if (fdisk_partition_is_container(pa))
+ rc = check_container_freespace(cxt, parts, *tb, pa);
+
+ if (fdisk_partition_has_end(pa)) {
+ fdisk_sector_t pa_end = fdisk_partition_get_end(pa);
+ if (pa_end > last)
+ last = fdisk_partition_get_end(pa);
+ }
+ nparts++;
+ }
+
+ /* 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->last_lba - 1) {
+ DBG(CXT, ul_debugobj(cxt, "freespace behind last partition detected"));
+ rc = new_freespace(cxt,
+ last + (last > cxt->first_lba || nparts ? 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) || fdisk_partition_is_wholedisk(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 partitions
+ * that does not define start (or does not follow the default start)
+ * are ignored.
+ *
+ * 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;
+}
+
+int fdisk_diff_tables(struct fdisk_table *a, struct fdisk_table *b,
+ struct fdisk_iter *itr,
+ struct fdisk_partition **res, int *change)
+{
+ struct fdisk_partition *pa = NULL, *pb;
+ int rc = 1;
+
+ assert(itr);
+ assert(res);
+ assert(change);
+
+ DBG(TAB, ul_debugobj(a, "table diff [new table=%p]", b));
+
+ if (a && (itr->head == NULL || itr->head == &a->parts)) {
+ DBG(TAB, ul_debugobj(a, " scanning old table"));
+ do {
+ rc = fdisk_table_next_partition(a, itr, &pa);
+ if (rc != 0)
+ break;
+ } while (!fdisk_partition_has_partno(pa));
+ }
+
+ if (rc == 1 && b) {
+ DBG(TAB, ul_debugobj(a, " scanning new table"));
+ if (itr->head != &b->parts) {
+ DBG(TAB, ul_debugobj(a, " initialize to TAB=%p", b));
+ fdisk_reset_iter(itr, FDISK_ITER_FORWARD);
+ }
+
+ while (fdisk_table_next_partition(b, itr, &pb) == 0) {
+ if (!fdisk_partition_has_partno(pb))
+ continue;
+ if (a == NULL ||
+ fdisk_table_get_partition_by_partno(a, pb->partno) == NULL) {
+ DBG(TAB, ul_debugobj(a, " #%zu ADDED", pb->partno));
+ *change = FDISK_DIFF_ADDED;
+ *res = pb;
+ return 0;
+ }
+ }
+ }
+
+ if (rc) {
+ DBG(TAB, ul_debugobj(a, "table diff done [rc=%d]", rc));
+ return rc; /* error or done */
+ }
+
+ pb = fdisk_table_get_partition_by_partno(b, pa->partno);
+
+ if (!pb) {
+ DBG(TAB, ul_debugobj(a, " #%zu REMOVED", pa->partno));
+ *change = FDISK_DIFF_REMOVED;
+ *res = pa;
+ } else if (pb->start != pa->start) {
+ DBG(TAB, ul_debugobj(a, " #%zu MOVED", pb->partno));
+ *change = FDISK_DIFF_MOVED;
+ *res = pb;
+ } else if (pb->size != pa->size) {
+ DBG(TAB, ul_debugobj(a, " #%zu RESIZED", pb->partno));
+ *change = FDISK_DIFF_RESIZED;
+ *res = pb;
+ } else {
+ DBG(TAB, ul_debugobj(a, " #%zu UNCHANGED", pb->partno));
+ *change = FDISK_DIFF_UNCHANGED;
+ *res = pa;
+ }
+ return 0;
+}
+
diff --git a/libfdisk/src/test.c b/libfdisk/src/test.c
new file mode 100644
index 0000000..31ed7e0
--- /dev/null
+++ b/libfdisk/src/test.c
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2008-2009 Karel Zak <kzak@redhat.com>
+ *
+ * This file may be redistributed under the terms of the
+ * GNU Lesser General Public License.
+ *
+ * Routines for TEST_PROGRAMs
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+#ifndef TEST_PROGRAM
+#define TEST_PROGRAM
+#endif
+
+#include "fdiskP.h"
+
+int fdisk_run_test(struct fdisk_test *tests, int argc, char *argv[])
+{
+ int rc = -1;
+ struct fdisk_test *ts;
+
+ assert(tests);
+ assert(argc);
+ assert(argv);
+
+ if (argc < 2 ||
+ strcmp(argv[1], "--help") == 0 ||
+ strcmp(argv[1], "-h") == 0)
+ goto usage;
+
+ fdisk_init_debug(0);
+
+ for (ts = tests; ts->name; ts++) {
+ if (strcmp(ts->name, argv[1]) == 0) {
+ rc = ts->body(ts, argc - 1, argv + 1);
+ if (rc)
+ printf("FAILED [rc=%d]", rc);
+ break;
+ }
+ }
+
+ if (rc < 0 && ts->name == NULL)
+ goto usage;
+
+ return rc == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
+usage:
+ printf("\nUsage:\n\t%s <test> [testoptions]\nTests:\n",
+ program_invocation_short_name);
+ for (ts = tests; ts->name; ts++) {
+ printf("\t%-15s", ts->name);
+ if (ts->usage)
+ printf(" %s\n", ts->usage);
+ }
+ printf("\n");
+ return EXIT_FAILURE;
+}
diff --git a/libfdisk/src/utils.c b/libfdisk/src/utils.c
new file mode 100644
index 0000000..6b6167d
--- /dev/null
+++ b/libfdisk/src/utils.c
@@ -0,0 +1,214 @@
+
+#include "fdiskP.h"
+#include "pathnames.h"
+#include "canonicalize.h"
+
+#include <ctype.h>
+
+/**
+ * SECTION: utils
+ * @title: Utils
+ * @short_description: misc fdisk functions
+ */
+
+static int read_from_device(struct fdisk_context *cxt,
+ unsigned char *buf,
+ uintmax_t start, size_t size)
+{
+ ssize_t r;
+
+ assert(cxt);
+
+ DBG(CXT, ul_debugobj(cxt, "reading: offset=%ju, size=%zu",
+ start, size));
+
+ r = lseek(cxt->dev_fd, start, SEEK_SET);
+ if (r == -1)
+ {
+ DBG(CXT, ul_debugobj(cxt, "failed to seek to offset %ju: %m", start));
+ return -errno;
+ }
+
+ errno = 0;
+ r = read(cxt->dev_fd, buf, size);
+ if (r < 0 || (size_t)r != size) {
+ if (!errno)
+ errno = EINVAL; /* probably too small file/device */
+ DBG(CXT, ul_debugobj(cxt, "failed to read %zu from offset %ju: %m",
+ size, start));
+ return -errno;
+ }
+
+ return 0;
+}
+
+
+/*
+ * Zeros in-memory first sector buffer
+ */
+int fdisk_init_firstsector_buffer(struct fdisk_context *cxt,
+ unsigned int protect_off,
+ unsigned int protect_size)
+{
+ if (!cxt)
+ return -EINVAL;
+
+ assert(protect_off + protect_size <= cxt->sector_size);
+
+ if (!cxt->firstsector || cxt->firstsector_bufsz != cxt->sector_size) {
+ /* Let's allocate a new buffer if no allocated yet, or the
+ * current buffer has incorrect size */
+ if (!cxt->parent || cxt->parent->firstsector != cxt->firstsector)
+ free(cxt->firstsector);
+
+ DBG(CXT, ul_debugobj(cxt, "initialize in-memory first sector "
+ "buffer [sector_size=%lu]", cxt->sector_size));
+ cxt->firstsector = calloc(1, cxt->sector_size);
+ if (!cxt->firstsector)
+ return -ENOMEM;
+
+ cxt->firstsector_bufsz = cxt->sector_size;
+ return 0;
+ }
+
+ DBG(CXT, ul_debugobj(cxt, "zeroize in-memory first sector buffer"));
+ memset(cxt->firstsector, 0, cxt->firstsector_bufsz);
+
+ if (protect_size) {
+ /*
+ * It would be possible to reuse data from cxt->firstsector
+ * (call memset() for non-protected area only) and avoid one
+ * read() from the device, but it seems like a too fragile
+ * solution as we have no clue about stuff in the buffer --
+ * maybe it was already modified. Let's re-read from the device
+ * to be sure. -- kzak 13-Apr-2015
+ */
+ DBG(CXT, ul_debugobj(cxt, "first sector protection enabled -- re-reading"));
+ read_from_device(cxt, cxt->firstsector, protect_off, protect_size);
+ }
+ return 0;
+}
+
+int fdisk_read_firstsector(struct fdisk_context *cxt)
+{
+ int rc;
+
+ assert(cxt);
+ assert(cxt->sector_size);
+
+ rc = fdisk_init_firstsector_buffer(cxt, 0, 0);
+ if (rc)
+ return rc;
+
+ assert(cxt->sector_size == cxt->firstsector_bufsz);
+
+
+ return read_from_device(cxt, cxt->firstsector, 0, cxt->sector_size);
+}
+
+/**
+ * fdisk_partname:
+ * @dev: device name
+ * @partno: partition name
+ *
+ * Return: allocated buffer with partition name, use free() to deallocate.
+ */
+char *fdisk_partname(const char *dev, size_t partno)
+{
+ char *res = NULL;
+ const char *p = "";
+ char *dev_mapped = NULL;
+ int w = 0;
+
+ if (!dev || !*dev) {
+ if (asprintf(&res, "%zd", partno) > 0)
+ return res;
+ return NULL;
+ }
+
+ /* It is impossible to predict /dev/dm-N partition names. */
+ if (strncmp(dev, "/dev/dm-", sizeof("/dev/dm-") - 1) == 0) {
+ dev_mapped = canonicalize_dm_name (dev + 5);
+ if (dev_mapped)
+ dev = dev_mapped;
+ }
+
+ w = strlen(dev);
+ if (isdigit(dev[w - 1]))
+#ifdef __GNU__
+ p = "s";
+#else
+ p = "p";
+#endif
+
+ /* devfs kludge - note: fdisk partition names are not supposed
+ to equal kernel names, so there is no reason to do this */
+ if (endswith(dev, "disc")) {
+ w -= 4;
+ p = "part";
+ }
+
+ /* udev names partitions by appending -partN
+ e.g. ata-SAMSUNG_SV8004H_0357J1FT712448-part1
+ multipath-tools kpartx.rules also append -partN */
+ if ((strncmp(dev, _PATH_DEV_BYID, sizeof(_PATH_DEV_BYID) - 1) == 0) ||
+ strncmp(dev, _PATH_DEV_BYPATH, sizeof(_PATH_DEV_BYPATH) - 1) == 0 ||
+ strncmp(dev, "/dev/mapper", sizeof("/dev/mapper") - 1) == 0) {
+
+ /* check for <name><partno>, e.g. mpatha1 */
+ if (asprintf(&res, "%.*s%zu", w, dev, partno) <= 0)
+ res = NULL;
+ if (res && access(res, F_OK) == 0)
+ goto done;
+
+ free(res);
+
+ /* check for partition separator "p" */
+ if (asprintf(&res, "%.*sp%zu", w, dev, partno) <= 0)
+ res = NULL;
+ if (res && access(res, F_OK) == 0)
+ goto done;
+
+ free(res);
+
+ /* otherwise, default to "-path" */
+ p = "-part";
+ }
+
+ if (asprintf(&res, "%.*s%s%zu", w, dev, p, partno) <= 0)
+ res = NULL;
+done:
+ free(dev_mapped);
+ return res;
+}
+
+#ifdef TEST_PROGRAM
+struct fdisk_label *fdisk_new_dos_label(struct fdisk_context *cxt) { return NULL; }
+struct fdisk_label *fdisk_new_bsd_label(struct fdisk_context *cxt) { return NULL; }
+
+static int test_partnames(struct fdisk_test *ts, int argc, char *argv[])
+{
+ size_t i;
+ const char *disk = argv[1];
+
+ for (i = 0; i < 5; i++) {
+ char *p = fdisk_partname(disk, i + 1);
+ if (p)
+ printf("%zu: '%s'\n", i + 1, p);
+ free(p);
+ }
+
+ return 0;
+}
+
+int main(int argc, char *argv[])
+{
+ struct fdisk_test tss[] = {
+ { "--partnames", test_partnames, "<diskname>" },
+ { NULL }
+ };
+
+ return fdisk_run_test(tss, argc, argv);
+}
+
+#endif
diff --git a/libfdisk/src/version.c b/libfdisk/src/version.c
new file mode 100644
index 0000000..9d84b4c
--- /dev/null
+++ b/libfdisk/src/version.c
@@ -0,0 +1,125 @@
+/*
+ * version.c - Return the version of the library
+ *
+ * Copyright (C) 2015 Karel Zak <kzak@redhat.com>
+ *
+ */
+
+/**
+ * SECTION: version-utils
+ * @title: Version functions
+ * @short_description: functions to get the library version.
+ */
+
+#include <ctype.h>
+
+#include "fdiskP.h"
+
+static const char *lib_version = LIBFDISK_VERSION;
+static const char *lib_features[] = {
+#if !defined(NDEBUG) /* libc assert.h stuff */
+ "assert",
+#endif
+ "debug", /* always enabled */
+ NULL
+};
+
+/**
+ * fdisk_parse_version_string:
+ * @ver_string: version string (e.g "2.18.0")
+ *
+ * Returns: release version code.
+ */
+int fdisk_parse_version_string(const char *ver_string)
+{
+ const char *cp;
+ int version = 0;
+
+ assert(ver_string);
+
+ for (cp = ver_string; *cp; cp++) {
+ if (*cp == '.')
+ continue;
+ if (!isdigit(*cp))
+ break;
+ version = (version * 10) + (*cp - '0');
+ }
+ return version;
+}
+
+/**
+ * fdisk_get_library_version:
+ * @ver_string: return pointer to the static library version string if not NULL
+ *
+ * Returns: release version number.
+ */
+int fdisk_get_library_version(const char **ver_string)
+{
+ if (ver_string)
+ *ver_string = lib_version;
+
+ return fdisk_parse_version_string(lib_version);
+}
+
+/**
+ * fdisk_get_library_features:
+ * @features: returns a pointer to the static array of strings, the array is
+ * terminated by NULL.
+ *
+ * Returns: number of items in the features array not including the last NULL,
+ * or less than zero in case of error
+ *
+ * Example:
+ * <informalexample>
+ * <programlisting>
+ * const char *features;
+ *
+ * fdisk_get_library_features(&features);
+ * while (features && *features)
+ * printf("%s\n", *features++);
+ * </programlisting>
+ * </informalexample>
+ *
+ */
+int fdisk_get_library_features(const char ***features)
+{
+ if (!features)
+ return -EINVAL;
+
+ *features = lib_features;
+ return ARRAY_SIZE(lib_features) - 1;
+}
+
+#ifdef TEST_PROGRAM
+static int test_version(struct fdisk_test *ts, int argc, char *argv[])
+{
+ const char *ver;
+ const char **features;
+
+ fdisk_get_library_version(&ver);
+
+ printf("Library version: %s\n", ver);
+ printf("Library API version: " LIBFDISK_VERSION "\n");
+ printf("Library features:");
+
+ fdisk_get_library_features(&features);
+ while (features && *features)
+ printf(" %s", *features++);
+
+ if (fdisk_get_library_version(NULL) ==
+ fdisk_parse_version_string(LIBFDISK_VERSION))
+ return 0;
+
+ return -1;
+}
+
+int main(int argc, char *argv[])
+{
+ struct fdisk_test ts[] = {
+ { "--print", test_version, "print versions" },
+ { NULL }
+ };
+
+ return fdisk_run_test(ts, argc, argv);
+}
+#endif
diff --git a/libfdisk/src/wipe.c b/libfdisk/src/wipe.c
new file mode 100644
index 0000000..54f4213
--- /dev/null
+++ b/libfdisk/src/wipe.c
@@ -0,0 +1,213 @@
+#include "c.h"
+#include "strutils.h"
+
+#ifdef HAVE_LIBBLKID
+# include <blkid.h>
+#endif
+
+#include "fdiskP.h"
+
+struct fdisk_wipe {
+ struct list_head wipes;
+ uint64_t start; /* sectors */
+ uint64_t size; /* sectors */
+};
+
+static struct fdisk_wipe *fdisk_get_wipe_area(
+ struct fdisk_context *cxt,
+ uint64_t start,
+ uint64_t size)
+{
+ struct list_head *p;
+
+ if (cxt == NULL || list_empty(&cxt->wipes))
+ return NULL;
+
+ list_for_each(p, &cxt->wipes) {
+ struct fdisk_wipe *wp = list_entry(p, struct fdisk_wipe, wipes);
+ if (wp->start == start && wp->size == size)
+ return wp;
+ }
+ return NULL;
+}
+
+void fdisk_free_wipe_areas(struct fdisk_context *cxt)
+{
+ while (!list_empty(&cxt->wipes)) {
+ struct fdisk_wipe *wp = list_entry(cxt->wipes.next,
+ struct fdisk_wipe, wipes);
+ DBG(WIPE, ul_debugobj(wp, "free [start=%ju, size=%ju]",
+ (uintmax_t) wp->start, (uintmax_t) wp->size));
+ list_del(&wp->wipes);
+ free(wp);
+ }
+}
+
+int fdisk_has_wipe_area(struct fdisk_context *cxt,
+ uint64_t start,
+ uint64_t size)
+{
+ return fdisk_get_wipe_area(cxt, start, size) != NULL;
+}
+
+/* Add/remove new wiping area
+ *
+ * Returns: <0 on error, or old area setting (1: enabled, 0: disabled)
+ */
+int fdisk_set_wipe_area(struct fdisk_context *cxt,
+ uint64_t start,
+ uint64_t size,
+ int enable)
+{
+ struct fdisk_wipe *wp;
+
+ if (FDISK_IS_UNDEF(start) || FDISK_IS_UNDEF(size))
+ return -EINVAL;
+
+ wp = fdisk_get_wipe_area(cxt, start, size);
+
+ /* disable */
+ if (!enable) {
+ if (wp) {
+ DBG(WIPE, ul_debugobj(wp, "disable [start=%ju, size=%ju]",
+ (uintmax_t) start, (uintmax_t) size));
+ list_del(&wp->wipes);
+ free(wp);
+ return 1;
+ }
+ return 0;
+ }
+
+ /* enable */
+ if (wp)
+ return 1; /* already enabled */
+
+ wp = calloc(1, sizeof(*wp));
+ if (!wp)
+ return -ENOMEM;
+
+ DBG(WIPE, ul_debugobj(wp, "enable [start=%ju, size=%ju]",
+ (uintmax_t) start, (uintmax_t) size));
+
+ INIT_LIST_HEAD(&wp->wipes);
+ wp->start = start;
+ wp->size = size;
+ list_add_tail(&wp->wipes, &cxt->wipes);
+
+ return 0;
+}
+
+#ifndef HAVE_LIBBLKID
+int fdisk_do_wipe(struct fdisk_context *cxt __attribute__((__unused__)))
+{
+ return 0;
+}
+#else
+int fdisk_do_wipe(struct fdisk_context *cxt)
+{
+ struct list_head *p;
+ blkid_probe pr;
+ int rc;
+
+ assert(cxt);
+ assert(cxt->dev_fd >= 0);
+
+ if (list_empty(&cxt->wipes))
+ return 0;
+
+ pr = blkid_new_probe();
+ if (!pr)
+ return -ENOMEM;
+
+ list_for_each(p, &cxt->wipes) {
+ struct fdisk_wipe *wp = list_entry(p, struct fdisk_wipe, wipes);
+ blkid_loff_t start = (blkid_loff_t) wp->start * cxt->sector_size,
+ size = (blkid_loff_t) wp->size * cxt->sector_size;
+
+ DBG(WIPE, ul_debugobj(wp, "initialize libblkid prober [start=%ju, size=%ju]",
+ (uintmax_t) start, (uintmax_t) size));
+
+ rc = blkid_probe_set_device(pr, cxt->dev_fd, start, size);
+ if (rc) {
+ DBG(WIPE, ul_debugobj(wp, "blkid_probe_set_device() failed [rc=%d]", rc));
+ return rc;
+ }
+
+ blkid_probe_enable_superblocks(pr, 1);
+ blkid_probe_set_superblocks_flags(pr, BLKID_SUBLKS_MAGIC |
+ BLKID_SUBLKS_BADCSUM);
+ blkid_probe_enable_partitions(pr, 1);
+ blkid_probe_set_partitions_flags(pr, BLKID_PARTS_MAGIC |
+ BLKID_PARTS_FORCE_GPT);
+
+ while (blkid_do_probe(pr) == 0) {
+ DBG(WIPE, ul_debugobj(wp, " wiping..."));
+ blkid_do_wipe(pr, FALSE);
+ }
+ }
+
+ blkid_free_probe(pr);
+ return 0;
+}
+#endif
+
+
+/*
+ * Please don't call this function if there is already a PT.
+ *
+ * Returns: 0 if nothing found, < 0 on error, 1 if found a signature
+ */
+#ifndef HAVE_LIBBLKID
+int fdisk_check_collisions(struct fdisk_context *cxt __attribute__((__unused__)))
+{
+ return 0;
+}
+#else
+int fdisk_check_collisions(struct fdisk_context *cxt)
+{
+ int rc = 0;
+ blkid_probe pr;
+
+ assert(cxt);
+ assert(cxt->dev_fd >= 0);
+
+ DBG(WIPE, ul_debugobj(cxt, "wipe check: initialize libblkid prober"));
+
+ pr = blkid_new_probe();
+ if (!pr)
+ return -ENOMEM;
+ rc = blkid_probe_set_device(pr, cxt->dev_fd, 0, 0);
+ if (rc)
+ return rc;
+
+ cxt->pt_collision = 0;
+ free(cxt->collision);
+ cxt->collision = NULL;
+
+ blkid_probe_enable_superblocks(pr, 1);
+ blkid_probe_set_superblocks_flags(pr, BLKID_SUBLKS_TYPE |
+ BLKID_SUBLKS_BADCSUM);
+ blkid_probe_enable_partitions(pr, 1);
+ blkid_probe_set_partitions_flags(pr, BLKID_PARTS_FORCE_GPT);
+
+ /* we care about the first found FS/raid, so don't call blkid_do_probe()
+ * in loop or don't use blkid_do_fullprobe() ... */
+ rc = blkid_do_probe(pr);
+ if (rc == 0) {
+ const char *name = NULL;
+
+ if (blkid_probe_lookup_value(pr, "TYPE", &name, 0) == 0)
+ cxt->collision = strdup(name);
+ else if (blkid_probe_lookup_value(pr, "PTTYPE", &name, 0) == 0) {
+ cxt->collision = strdup(name);
+ cxt->pt_collision = 1;
+ }
+
+ if (name && !cxt->collision)
+ rc = -ENOMEM;
+ }
+
+ blkid_free_probe(pr);
+ return rc < 0 ? rc : cxt->collision ? 1 : 0;
+}
+#endif