From cfe5e3905201349e9cf3f95d52ff4bd100bde37d Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 14 Apr 2024 21:10:49 +0200 Subject: Adding upstream version 2.39.3. Signed-off-by: Daniel Baumann --- libfdisk/src/Makemodule.am | 140 ++ libfdisk/src/alignment.c | 721 ++++++++++ libfdisk/src/ask.c | 1077 ++++++++++++++ libfdisk/src/bsd.c | 1065 ++++++++++++++ libfdisk/src/context.c | 1555 ++++++++++++++++++++ libfdisk/src/dos.c | 2912 ++++++++++++++++++++++++++++++++++++++ libfdisk/src/fdiskP.h | 540 +++++++ libfdisk/src/field.c | 88 ++ libfdisk/src/gpt.c | 3381 ++++++++++++++++++++++++++++++++++++++++++++ libfdisk/src/init.c | 62 + libfdisk/src/item.c | 255 ++++ libfdisk/src/iter.c | 82 ++ libfdisk/src/label.c | 752 ++++++++++ libfdisk/src/libfdisk.h.in | 895 ++++++++++++ libfdisk/src/libfdisk.sym | 322 +++++ libfdisk/src/partition.c | 1627 +++++++++++++++++++++ libfdisk/src/parttype.c | 569 ++++++++ libfdisk/src/script.c | 1823 ++++++++++++++++++++++++ libfdisk/src/sgi.c | 1210 ++++++++++++++++ libfdisk/src/sun.c | 1192 ++++++++++++++++ libfdisk/src/table.c | 797 +++++++++++ libfdisk/src/test.c | 59 + libfdisk/src/utils.c | 214 +++ libfdisk/src/version.c | 125 ++ libfdisk/src/wipe.c | 213 +++ 25 files changed, 21676 insertions(+) create mode 100644 libfdisk/src/Makemodule.am create mode 100644 libfdisk/src/alignment.c create mode 100644 libfdisk/src/ask.c create mode 100644 libfdisk/src/bsd.c create mode 100644 libfdisk/src/context.c create mode 100644 libfdisk/src/dos.c create mode 100644 libfdisk/src/fdiskP.h create mode 100644 libfdisk/src/field.c create mode 100644 libfdisk/src/gpt.c create mode 100644 libfdisk/src/init.c create mode 100644 libfdisk/src/item.c create mode 100644 libfdisk/src/iter.c create mode 100644 libfdisk/src/label.c create mode 100644 libfdisk/src/libfdisk.h.in create mode 100644 libfdisk/src/libfdisk.sym create mode 100644 libfdisk/src/partition.c create mode 100644 libfdisk/src/parttype.c create mode 100644 libfdisk/src/script.c create mode 100644 libfdisk/src/sgi.c create mode 100644 libfdisk/src/sun.c create mode 100644 libfdisk/src/table.c create mode 100644 libfdisk/src/test.c create mode 100644 libfdisk/src/utils.c create mode 100644 libfdisk/src/version.c create mode 100644 libfdisk/src/wipe.c (limited to 'libfdisk/src') 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 +#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 + * + * 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 , March 1999 + * David Huggins-Daines , January 2000 + */ +#include +#include +#include +#include +#include +#include +#include + +#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 +#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 + * + * + * + * // 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); + * + * + * + * 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()). + * + * + * + * 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 ... + * } + * + * + * + * 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 + * 2012 Davidlohr Bueso + * 2021 Pali Rohár + * + * 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 + +#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 + * + * This file may be redistributed under the terms of the + * GNU Lesser General Public License. + */ + +#ifndef _LIBFDISK_PRIVATE_H +#define _LIBFDISK_PRIVATE_H + +#include +#include +#include +#include +#include +#include +#include + +#include "c.h" +#include "libfdisk.h" + +#include "list.h" +#include "debug.h" +#include +#include + +/* + * 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//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; /*