diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-14 19:10:49 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-14 19:10:49 +0000 |
commit | cfe5e3905201349e9cf3f95d52ff4bd100bde37d (patch) | |
tree | d0baf160cbee3195249d095f85e52d20c21acf02 /libfdisk/src | |
parent | Initial commit. (diff) | |
download | util-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.am | 140 | ||||
-rw-r--r-- | libfdisk/src/alignment.c | 721 | ||||
-rw-r--r-- | libfdisk/src/ask.c | 1077 | ||||
-rw-r--r-- | libfdisk/src/bsd.c | 1065 | ||||
-rw-r--r-- | libfdisk/src/context.c | 1555 | ||||
-rw-r--r-- | libfdisk/src/dos.c | 2912 | ||||
-rw-r--r-- | libfdisk/src/fdiskP.h | 540 | ||||
-rw-r--r-- | libfdisk/src/field.c | 88 | ||||
-rw-r--r-- | libfdisk/src/gpt.c | 3381 | ||||
-rw-r--r-- | libfdisk/src/init.c | 62 | ||||
-rw-r--r-- | libfdisk/src/item.c | 255 | ||||
-rw-r--r-- | libfdisk/src/iter.c | 82 | ||||
-rw-r--r-- | libfdisk/src/label.c | 752 | ||||
-rw-r--r-- | libfdisk/src/libfdisk.h.in | 895 | ||||
-rw-r--r-- | libfdisk/src/libfdisk.sym | 322 | ||||
-rw-r--r-- | libfdisk/src/partition.c | 1627 | ||||
-rw-r--r-- | libfdisk/src/parttype.c | 569 | ||||
-rw-r--r-- | libfdisk/src/script.c | 1823 | ||||
-rw-r--r-- | libfdisk/src/sgi.c | 1210 | ||||
-rw-r--r-- | libfdisk/src/sun.c | 1192 | ||||
-rw-r--r-- | libfdisk/src/table.c | 797 | ||||
-rw-r--r-- | libfdisk/src/test.c | 59 | ||||
-rw-r--r-- | libfdisk/src/utils.c | 214 | ||||
-rw-r--r-- | libfdisk/src/version.c | 125 | ||||
-rw-r--r-- | libfdisk/src/wipe.c | 213 |
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, §_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 |